612d68f56f
Go + Vue 管理台,SQLite 持久化;支持隧道/域名穿透、域名转发插件链、访客认证、 IP 规则、API Key、Docker 单镜像部署;配置通过 Web 系统设置管理,无需 YAML 文件。 Co-authored-by: Cursor <cursoragent@cursor.com>
452 lines
19 KiB
Go
452 lines
19 KiB
Go
package api
|
||
|
||
// OpenAPI 3.0 spec for Wormhole external API (authenticate with X-API-Key header).
|
||
const openAPISpec = `{
|
||
"openapi": "3.0.3",
|
||
"info": {
|
||
"title": "Wormhole API",
|
||
"version": "1.0.0",
|
||
"description": "使用 X-API-Key 请求头或 Bearer JWT 访问。Base URL: http://host:8529/api。域名转发接口需管理员权限。"
|
||
},
|
||
"servers": [{ "url": "/api" }],
|
||
"components": {
|
||
"securitySchemes": {
|
||
"ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key" },
|
||
"BearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" }
|
||
},
|
||
"schemas": {
|
||
"Error": {
|
||
"type": "object",
|
||
"properties": {
|
||
"error": { "type": "string", "description": "错误信息" }
|
||
}
|
||
},
|
||
"Ok": {
|
||
"type": "object",
|
||
"properties": {
|
||
"ok": { "type": "boolean", "example": true }
|
||
}
|
||
},
|
||
"PagedDomainForwards": {
|
||
"type": "object",
|
||
"properties": {
|
||
"items": {
|
||
"type": "array",
|
||
"items": { "$ref": "#/components/schemas/DomainForward" }
|
||
},
|
||
"total": { "type": "integer", "description": "总条数" }
|
||
}
|
||
},
|
||
"DomainForward": {
|
||
"type": "object",
|
||
"description": "域名转发规则(服务端直连,无需 Agent)",
|
||
"properties": {
|
||
"id": { "type": "integer", "readOnly": true },
|
||
"source_host": { "type": "string", "description": "源域名,支持通配如 *.example.com", "example": "old.example.com" },
|
||
"source_location": { "type": "string", "default": "/", "description": "路径前缀", "example": "/" },
|
||
"source_scheme": { "type": "string", "enum": ["all", "http", "https"], "default": "all" },
|
||
"mode": { "type": "string", "enum": ["redirect", "proxy"], "description": "redirect=跳转,proxy=反向代理" },
|
||
"target_url": { "type": "string", "format": "uri", "description": "目标 URL,需含协议与主机", "example": "https://new.example.com" },
|
||
"redirect_mode": { "type": "string", "enum": ["preserve", "fixed"], "default": "preserve", "description": "跳转模式:保留路径或固定地址" },
|
||
"redirect_code": { "type": "integer", "enum": [301, 302], "default": 302 },
|
||
"proxy_type": {
|
||
"type": "string",
|
||
"enum": ["http", "http_proxy", "socks5"],
|
||
"default": "http",
|
||
"description": "http=直连,http_proxy=经 HTTP 代理出站,socks5=经 SOCKS5 出站"
|
||
},
|
||
"http_proxy_addr": { "type": "string", "description": "HTTP 代理地址,proxy_type=http_proxy 时必填", "example": "127.0.0.1:8080" },
|
||
"http_proxy_user": { "type": "string", "description": "HTTP 代理用户名(可选)" },
|
||
"socks5_addr": { "type": "string", "description": "SOCKS5 地址,proxy_type=socks5 时必填", "example": "127.0.0.1:1080" },
|
||
"socks5_user": { "type": "string", "description": "SOCKS5 用户名(可选)" },
|
||
"preserve_host": { "type": "boolean", "default": false, "description": "反代时是否将源域名作为 Host 发给上游" },
|
||
"plugins": {
|
||
"type": "array",
|
||
"description": "反代插件链,按数组顺序执行;redirect 模式忽略",
|
||
"items": { "$ref": "#/components/schemas/ForwardPluginEntry" }
|
||
},
|
||
"status": { "type": "boolean", "default": true },
|
||
"created_at": { "type": "string", "format": "date-time", "readOnly": true },
|
||
"updated_at": { "type": "string", "format": "date-time", "readOnly": true }
|
||
},
|
||
"required": ["source_host", "mode", "target_url"]
|
||
},
|
||
"DomainForwardInput": {
|
||
"allOf": [
|
||
{ "$ref": "#/components/schemas/DomainForward" },
|
||
{
|
||
"type": "object",
|
||
"properties": {
|
||
"http_proxy_pass": { "type": "string", "writeOnly": true, "description": "HTTP 代理密码;更新时留空表示不修改" },
|
||
"socks5_pass": { "type": "string", "writeOnly": true, "description": "SOCKS5 密码;更新时留空表示不修改" }
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"ForwardPluginEntry": {
|
||
"type": "object",
|
||
"required": ["type"],
|
||
"properties": {
|
||
"type": {
|
||
"type": "string",
|
||
"enum": ["sub_filter", "inject_js", "cookie_domain", "response_cache"],
|
||
"description": "插件类型"
|
||
},
|
||
"enabled": { "type": "boolean", "default": true },
|
||
"config": {
|
||
"description": "插件配置,结构随 type 变化",
|
||
"oneOf": [
|
||
{ "$ref": "#/components/schemas/SubFilterPluginConfig" },
|
||
{ "$ref": "#/components/schemas/InjectJSPluginConfig" },
|
||
{ "$ref": "#/components/schemas/CookieDomainPluginConfig" },
|
||
{ "$ref": "#/components/schemas/ResponseCachePluginConfig" }
|
||
]
|
||
}
|
||
}
|
||
},
|
||
"SubFilterPluginConfig": {
|
||
"type": "object",
|
||
"description": "type=sub_filter:按 MIME 对响应体做字符串替换",
|
||
"properties": {
|
||
"filters": {
|
||
"type": "array",
|
||
"minItems": 1,
|
||
"items": {
|
||
"type": "object",
|
||
"required": ["from"],
|
||
"properties": {
|
||
"from": { "type": "string" },
|
||
"to": { "type": "string" }
|
||
}
|
||
}
|
||
},
|
||
"types": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"default": ["text/html"],
|
||
"example": ["text/html", "text/css"]
|
||
}
|
||
},
|
||
"required": ["filters"]
|
||
},
|
||
"InjectJSPluginConfig": {
|
||
"type": "object",
|
||
"description": "type=inject_js:在 HTML </body> 前注入脚本",
|
||
"properties": {
|
||
"script": { "type": "string", "example": "<script></script>" }
|
||
},
|
||
"required": ["script"]
|
||
},
|
||
"CookieDomainPluginConfig": {
|
||
"type": "object",
|
||
"description": "type=cookie_domain:改写 Set-Cookie 的 Domain",
|
||
"properties": {
|
||
"from": { "type": "string", "example": ".old.com" },
|
||
"to": { "type": "string", "example": ".new.com" }
|
||
},
|
||
"required": ["from", "to"]
|
||
},
|
||
"ResponseCachePluginConfig": {
|
||
"type": "object",
|
||
"description": "type=response_cache:缓存上游原始 GET 响应(不含 Set-Cookie)",
|
||
"properties": {
|
||
"max_mb": { "type": "integer", "minimum": 0, "description": "缓存容量 MB,0 表示禁用", "example": 50 },
|
||
"ttl_sec": { "type": "integer", "minimum": 1, "description": "TTL 秒,max_mb>0 时默认 300", "example": 300 }
|
||
}
|
||
},
|
||
"ForwardPluginTypes": {
|
||
"type": "object",
|
||
"properties": {
|
||
"items": {
|
||
"type": "array",
|
||
"items": { "$ref": "#/components/schemas/ForwardPluginTypeMeta" }
|
||
}
|
||
}
|
||
},
|
||
"ForwardPluginTypeMeta": {
|
||
"type": "object",
|
||
"properties": {
|
||
"type": { "type": "string" },
|
||
"label": { "type": "string" },
|
||
"description": { "type": "string" },
|
||
"config_schema": { "type": "object", "additionalProperties": true },
|
||
"default_config": { "type": "object", "additionalProperties": true }
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"BadRequest": {
|
||
"description": "请求参数无效",
|
||
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
|
||
},
|
||
"Forbidden": {
|
||
"description": "权限不足(需管理员)",
|
||
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
|
||
},
|
||
"NotFound": {
|
||
"description": "资源不存在",
|
||
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
|
||
},
|
||
"Conflict": {
|
||
"description": "路由冲突",
|
||
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
|
||
}
|
||
}
|
||
},
|
||
"security": [{ "ApiKeyAuth": [] }, { "BearerAuth": [] }],
|
||
"paths": {
|
||
"/clients": {
|
||
"get": {
|
||
"summary": "列出客户端及在线状态",
|
||
"responses": { "200": { "description": "OK" } }
|
||
}
|
||
},
|
||
"/tunnels": {
|
||
"get": {
|
||
"summary": "分页查询隧道",
|
||
"parameters": [
|
||
{ "name": "page", "in": "query", "schema": { "type": "integer" } },
|
||
{ "name": "page_size", "in": "query", "schema": { "type": "integer" } },
|
||
{ "name": "q", "in": "query", "schema": { "type": "string" } }
|
||
],
|
||
"responses": { "200": { "description": "OK" } }
|
||
},
|
||
"post": { "summary": "创建隧道", "responses": { "200": { "description": "OK" } } }
|
||
},
|
||
"/tunnels/{id}": {
|
||
"put": { "summary": "更新隧道", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } },
|
||
"delete": { "summary": "删除隧道", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } }
|
||
},
|
||
"/tunnels/{id}/start": { "post": { "summary": "启动隧道", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } } },
|
||
"/tunnels/{id}/stop": { "post": { "summary": "停止隧道", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } } },
|
||
"/tunnels/{id}/pause": { "post": { "summary": "暂停隧道", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } } },
|
||
"/tunnels/{id}/resume": { "post": { "summary": "恢复隧道", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } } },
|
||
"/hosts": {
|
||
"get": {
|
||
"summary": "分页查询域名",
|
||
"parameters": [
|
||
{ "name": "page", "in": "query", "schema": { "type": "integer" } },
|
||
{ "name": "page_size", "in": "query", "schema": { "type": "integer" } },
|
||
{ "name": "q", "in": "query", "schema": { "type": "string" } }
|
||
],
|
||
"responses": { "200": { "description": "OK" } }
|
||
},
|
||
"post": { "summary": "创建域名", "responses": { "200": { "description": "OK" } } }
|
||
},
|
||
"/hosts/{id}": {
|
||
"put": { "summary": "更新域名", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } },
|
||
"delete": { "summary": "删除域名", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } }
|
||
},
|
||
"/hosts/{id}/pause": { "post": { "summary": "暂停域名", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } } },
|
||
"/hosts/{id}/resume": { "post": { "summary": "恢复域名", "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "OK" } } } },
|
||
"/domain-forwards": {
|
||
"get": {
|
||
"summary": "分页查询域名转发",
|
||
"description": "需管理员权限",
|
||
"parameters": [
|
||
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||
{ "name": "page_size", "in": "query", "schema": { "type": "integer", "default": 20 } },
|
||
{ "name": "q", "in": "query", "schema": { "type": "string" }, "description": "搜索源域名、目标 URL" }
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/PagedDomainForwards" }
|
||
}
|
||
}
|
||
},
|
||
"403": { "$ref": "#/components/responses/Forbidden" }
|
||
}
|
||
},
|
||
"post": {
|
||
"summary": "创建域名转发",
|
||
"description": "需管理员权限。proxy 模式下可配置 plugins 插件链。",
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/DomainForwardInput" },
|
||
"examples": {
|
||
"redirect": {
|
||
"summary": "302 跳转",
|
||
"value": {
|
||
"source_host": "old.example.com",
|
||
"source_location": "/",
|
||
"mode": "redirect",
|
||
"target_url": "https://new.example.com",
|
||
"redirect_mode": "preserve",
|
||
"redirect_code": 302,
|
||
"status": true
|
||
}
|
||
},
|
||
"proxy_with_plugins": {
|
||
"summary": "反代 + 插件链 + HTTP 代理出站",
|
||
"value": {
|
||
"source_host": "proxy.example.com",
|
||
"source_location": "/",
|
||
"source_scheme": "all",
|
||
"mode": "proxy",
|
||
"target_url": "https://backend.internal",
|
||
"proxy_type": "http_proxy",
|
||
"http_proxy_addr": "127.0.0.1:8080",
|
||
"http_proxy_user": "user",
|
||
"http_proxy_pass": "secret",
|
||
"preserve_host": false,
|
||
"plugins": [
|
||
{
|
||
"type": "response_cache",
|
||
"enabled": true,
|
||
"config": { "max_mb": 50, "ttl_sec": 300 }
|
||
},
|
||
{
|
||
"type": "sub_filter",
|
||
"enabled": true,
|
||
"config": {
|
||
"filters": [{ "from": "old.internal", "to": "proxy.example.com" }],
|
||
"types": ["text/html"]
|
||
}
|
||
},
|
||
{
|
||
"type": "inject_js",
|
||
"enabled": true,
|
||
"config": { "script": "<script>console.log(1)</script>" }
|
||
},
|
||
{
|
||
"type": "cookie_domain",
|
||
"enabled": true,
|
||
"config": { "from": ".internal", "to": ".example.com" }
|
||
}
|
||
],
|
||
"status": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "创建成功",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/DomainForward" }
|
||
}
|
||
}
|
||
},
|
||
"400": { "$ref": "#/components/responses/BadRequest" },
|
||
"403": { "$ref": "#/components/responses/Forbidden" },
|
||
"409": { "$ref": "#/components/responses/Conflict" }
|
||
}
|
||
}
|
||
},
|
||
"/domain-forwards/{id}": {
|
||
"put": {
|
||
"summary": "更新域名转发",
|
||
"description": "需管理员权限。http_proxy_pass / socks5_pass 留空时不修改原密码。",
|
||
"parameters": [
|
||
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
|
||
],
|
||
"requestBody": {
|
||
"required": true,
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/DomainForwardInput" }
|
||
}
|
||
}
|
||
},
|
||
"responses": {
|
||
"200": {
|
||
"description": "更新成功",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/DomainForward" }
|
||
}
|
||
}
|
||
},
|
||
"400": { "$ref": "#/components/responses/BadRequest" },
|
||
"403": { "$ref": "#/components/responses/Forbidden" },
|
||
"404": { "$ref": "#/components/responses/NotFound" },
|
||
"409": { "$ref": "#/components/responses/Conflict" }
|
||
}
|
||
},
|
||
"delete": {
|
||
"summary": "删除域名转发",
|
||
"parameters": [
|
||
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "删除成功",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/Ok" }
|
||
}
|
||
}
|
||
},
|
||
"403": { "$ref": "#/components/responses/Forbidden" },
|
||
"404": { "$ref": "#/components/responses/NotFound" }
|
||
}
|
||
}
|
||
},
|
||
"/domain-forwards/{id}/pause": {
|
||
"post": {
|
||
"summary": "暂停域名转发",
|
||
"parameters": [
|
||
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/Ok" }
|
||
}
|
||
}
|
||
},
|
||
"403": { "$ref": "#/components/responses/Forbidden" },
|
||
"404": { "$ref": "#/components/responses/NotFound" }
|
||
}
|
||
}
|
||
},
|
||
"/domain-forwards/{id}/resume": {
|
||
"post": {
|
||
"summary": "恢复域名转发",
|
||
"parameters": [
|
||
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
|
||
],
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/Ok" }
|
||
}
|
||
}
|
||
},
|
||
"403": { "$ref": "#/components/responses/Forbidden" },
|
||
"404": { "$ref": "#/components/responses/NotFound" }
|
||
}
|
||
}
|
||
},
|
||
"/forward-plugin-types": {
|
||
"get": {
|
||
"summary": "获取域名转发插件类型元数据",
|
||
"description": "返回可用插件的 type、label、config_schema、default_config,供前端或 API 客户端动态构建配置。",
|
||
"responses": {
|
||
"200": {
|
||
"description": "OK",
|
||
"content": {
|
||
"application/json": {
|
||
"schema": { "$ref": "#/components/schemas/ForwardPluginTypes" }
|
||
}
|
||
}
|
||
},
|
||
"403": { "$ref": "#/components/responses/Forbidden" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}`
|