feat: 合并边缘节点运维并完善 Surge 配置安全

迁入通用节点部署工具、离线交付页生成器和共用技能,隔离真实配置与交付产物。统一 Codex 与 Claude Code 开发约定,补充节点迁移与运维文档。

完善 FINAL、策略组引用、输入校验、Surge 字段编码与 AI DNS 例外保护,增加隔离测试和订阅验证工具。

验证:全量类型检查、44 项单测、前端构建、脚本语法、技能入口与凭据隔离检查均通过。
This commit is contained in:
2026-10-03 23:19:59 +08:00
parent 7358008936
commit 5326c572a2
93 changed files with 6780 additions and 320 deletions

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""Runs ON the box with aaPanel's bundled python:
/www/server/panel/pyenv/bin/python /root/_aapanel-sites.py
Registers 4 domains as panel-native sites (AddSite + SetSSL + CreateProxy + HttpToHttps)
so they are visible/manageable in the aaPanel UI (never hand-write vhosts — the UI won't
see those). Reads subdomain labels + cover paths from environment variables.
"""
import json, os, sys
sys.path.insert(0, "/www/server/panel")
sys.path.insert(0, "/www/server/panel/class")
sys.path.insert(0, "/www/server/panel/class_v2") # aaPanel 7.x moved some modules here (e.g. theme_config)
import public # noqa: E402
import panelSite # noqa: E402
ROOT = os.environ["DOMAIN_ROOT"]
SUB_PANEL = os.environ["SUB_PANEL"]
SUB_XUI = os.environ["SUB_XUI"]
SUB_COVER = os.environ["SUB_COVER"]
XUI_PORT = os.environ.get("XUI_PORT", "14533")
PANEL_PORT = public.readFile("/www/server/panel/data/port.pl").strip()
D_LANDING = ROOT
D_COVER = "%s.%s" % (SUB_COVER, ROOT)
D_XUI = "%s.%s" % (SUB_XUI, ROOT)
D_PANEL = "%s.%s" % (SUB_PANEL, ROOT)
site = panelSite.panelSite()
SITES = [
(D_LANDING, "/www/wwwroot/" + D_LANDING, "edge landing"),
(D_COVER, "/www/wwwroot/" + D_COVER, "node cover"),
(D_XUI, "/www/wwwroot/" + D_XUI, "3x-ui panel proxy"),
(D_PANEL, "/www/wwwroot/" + D_PANEL, "aaPanel proxy"),
]
# 3 protocols x 2 egress = 6 cover proxy rules, all on the same D_COVER site/cert.
_PROTOCOLS = ["VMESS", "TROJAN", "SS"]
PROXIES = [
("xui-panel", D_XUI, "/", "http://127.0.0.1:%s" % XUI_PORT, D_XUI),
("aapanel", D_PANEL, "/", "https://127.0.0.1:%s" % PANEL_PORT, D_PANEL),
]
for proto in _PROTOCOLS:
for suffix in ("DIRECT", "WARP"):
path = os.environ["WS_PATH_%s_%s" % (proto, suffix)]
port = os.environ["PORT_%s_%s" % (proto, suffix)]
PROXIES.append(("cover-%s-%s" % (proto.lower(), suffix.lower()), D_COVER, path,
"http://127.0.0.1:%s" % port, D_COVER))
def obj(d):
return public.to_dict_obj(d)
def main():
for name, path, ps in SITES:
get = obj({"webname": json.dumps({"domain": name, "domainlist": [], "count": 0}),
"path": path, "port": "80", "version": "00", "ps": ps,
"type_id": "0", "type": "static", "ftp": "false", "sql": "false"})
print("AddSite", name, "->", site.AddSite(get).get("siteStatus", "?"))
for name, _p, _ps in SITES:
cdir = "/www/server/panel/vhost/cert/" + name
key = public.readFile(cdir + "/privkey.pem"); csr = public.readFile(cdir + "/fullchain.pem")
if key and csr:
print("SetSSL", name, "->", site.SetSSL(obj({"siteName": name, "key": key, "csr": csr})).get("status"))
for pname, sname, pdir, target, todomain in PROXIES:
get = obj({"proxyname": pname, "sitename": sname, "proxydir": pdir, "proxysite": target,
"todomain": todomain, "type": "1", "cache": "0", "subfilter": "[]",
"advanced": "0", "cachetime": "1", "nocheck": "1"})
print("CreateProxy", sname, pdir, "->", site.CreateProxy(get).get("status"))
for name, _p, _ps in SITES:
print("HttpToHttps", name, "->", site.HttpToHttps(obj({"siteName": name})).get("status"))
print("DONE — reload nginx: /etc/init.d/nginx reload")
if __name__ == "__main__":
main()