Files
proxy-station/.claude/skills/edge-node-multi-protocol/references/workflow.md
YANG JIANKUAN 5326c572a2 feat: 合并边缘节点运维并完善 Surge 配置安全
迁入通用节点部署工具、离线交付页生成器和共用技能,隔离真实配置与交付产物。统一 Codex 与 Claude Code 开发约定,补充节点迁移与运维文档。

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

验证:全量类型检查、44 项单测、前端构建、脚本语法、技能入口与凭据隔离检查均通过。
2026-10-03 23:19:59 +08:00

12 KiB
Raw Blame History

aaPanel deployment workflow

This is the migrated aaPanel workflow. Run shell commands from the LOCAL private node workspace: Bash scripts invoke SSH and their Python companions run on the VPS. Standalone nginx, QUIC and upgrade repairs are documented in standalone-and-quic.md. Keep live config out of the skill source directory.

Turn a bare Debian/Ubuntu VPS into a fully managed edge node running three proxy protocols side by side, each with a direct-egress and a Cloudflare-WARP clean-exit inbound — six inbounds in total. This is the standard target of this skill, not an optional extra: a single protocol is a fragile bet (if it gets fingerprinted or a client stops supporting it, the whole node goes dark), and three protocol families sharing the same domain/cert/firewall costs almost nothing extra to run once the panel infrastructure is up.

What it builds

Layer Component Result
Panel aaPanel International (7.x) + nginx <SUB_PANEL>.<root> reverse-proxied, secret-path entrance, hardened
Proxy 3x-ui (Xray) <SUB_XUI>.<root> panel; 6 inbounds behind nginx: VMess/Trojan/Shadowsocks × direct/WARP
Clean exit cloudflare-warp (proxy mode) every protocol's WARP inbound egresses through Cloudflare WARP
TLS acme.sh DNS-01 (any provider) wildcard *.<root> cert, auto-renew + reload, shared by all 6 inbounds
DNS Cloudflare API (or your provider, by hand) <root> + *.<root> A/AAAA (DNS-only)
Cover nginx vhosts landing page + node cover (6 WS paths) + default-server 444
Watchdog systemd timer WARP proxy self-heal every 60s
Monitoring Nezha + Komari agents (optional) node appears on your panels
Access SSH key + optional shortcut passwordless login

This is a template, not a turnkey binary. You supply your own domain, Cloudflare token, server IP, and passwords. No secrets, domains, or routing rules are baked in.

Why three protocols instead of one

VMess, Trojan, and Shadowsocks(+WS/TLS) use TCP and are implemented by 3x-ui/Xray-core. Client support depends on the protocol/transport combination: Surge does not support Shadowsocks over WebSocket. Proxy Station filters unsupported combinations using shared/src/protocols.ts. Running all three from day one means: if a client only supports a subset (e.g. Surge doesn't do VLESS), you're already covered; if one protocol family gets fingerprinted, you have two independent fallbacks without touching the box again. See protocol-choice.md for the fuller reasoning (performance/detection tradeoffs, and how to add a fourth protocol like VLESS+REALITY later).

Inputs to collect first

Fill a working copy of scripts/config.env.example → config.env (git-ignored by you). Collect these before touching the box:

  • SERVER_IP / SERVER_IPV6 — the new VPS public addresses (amd64 or arm64)
  • SSH_USER / SSH_KEY — root or a sudo user (scripts escalate via sudo), and the private key
  • TZ — server timezone (default UTC)
  • DOMAIN_ROOT — the sub-zone this node owns, e.g. nodeA.example.com (you get *.nodeA.example.com)
  • ACME_DNS_PLUGIN + creds — DNS provider for the wildcard cert. Default dns_cf (Cloudflare). Any acme.sh-supported provider works (DNSPod/Aliyun/Route53/GoDaddy/…) — set the plugin + its env vars (dns-records.md).
  • CF_API_TOKEN / CF_ZONE_ID — Cloudflare only: token with Zone:DNS:Edit (enables the turnkey A/AAAA record script). Non-CF users create the A/AAAA records themselves.
  • SUB_PANEL / SUB_XUI / SUB_COVER — the subdomain labels for the three planes. If your zone is public, choose non-obvious labels — don't reuse a guessable convention or people can enumerate your control planes from DNS.
  • AAPANEL_USER / AAPANEL_ENTRANCE — desired panel username + secret entrance path (e.g. /somesecret)
  • XUI_USER / XUI_PATH — 3x-ui username + webBasePath (e.g. /somesecret/)
  • Six WS cover paths + two ports each (PORT_<PROTO>_{DIRECT,WARP} / WS_PATH_<PROTO>_{DIRECT,WARP} for VMESS/TROJAN/SS) — pick your own, non-obvious, don't reuse a naming convention across them
  • XUI_UUID + NODE_PASSWORD (+ XUI_SUBID, SS_METHOD default chacha20-poly1305) — the credentials for the single node-main client that owns all six inbounds: XUI_UUID authenticates the two VMess inbounds, NODE_PASSWORD authenticates the two Trojan and two Shadowsocks inbounds (they share it — it's one client). Generate strong values at runtime; store them in your password manager, never in git.
  • monitoring (optional) — Nezha SERVER:PORT + shared key; Komari endpoint + auto-discovery key

Workflow

For a fresh aaPanel node, follow the selected steps in order from the local private workspace. Shell scripts read scripts/config.env and invoke SSH; their Python companions run on the VPS. For an existing node, read its handover notes and run only the steps required by the task.

0. Verify access & prep

scripts/00-preflight.sh          # confirms SSH, records specs, sets timezone, installs base pkgs (curl/gnupg/ufw)

Also drop your public key into the box's ~/.ssh/authorized_keys for passwordless login.

1. Wildcard DNS records

python3 scripts/02-cf-dns.py     # Cloudflare: upserts <root> + *.<root> A/AAAA -> SERVER_IP/IPV6 (proxied=false)

Non-Cloudflare users: create the same records by hand, see dns-records.md.

2. Wildcard TLS cert (acme.sh DNS-01)

scripts/03-sign-wildcard-cert.sh   # on-box acme.sh --dns dns_cf, issues *.<root> (ECDSA P-256, Let's Encrypt)

One cert, shared by all 6 inbounds via the configured <SUB_COVER>.<root> cover domain. Fleet cert signing: centralized-certs.md.

3. aaPanel International + nginx

scripts/04-install-aapanel.sh    # official install_7.0_en.sh (headless); waits for the log's success banner
scripts/05-harden-aapanel.sh     # sets username/password/entrance, binds panel domain, CDN_PROXY=True
scripts/04b-install-nginx.sh     # install_soft.sh 0 install nginx 1.24 (aaPanel-managed)

pitfalls.md has three aaPanel-7.x-specific fixes baked into these scripts already (install-wait logic, class_v2 import path, anti-scan UA strictness) — read it if you're editing these rather than just running them.

4. Cloudflare WARP (clean exit)

scripts/06-install-warp.sh       # cloudflare-warp, proxy mode SOCKS5 127.0.0.1:40000, verify warp=on
scripts/06b-warp-watchdog.sh     # installs warp-healthcheck.sh + systemd timer (self-heal every 60s)

5. 3x-ui (Xray) + six inbounds under one client

scripts/07-install-3xui.sh       # installs 3x-ui, sets panel 127.0.0.1:14533 + XUI_PATH + creds
scripts/08-xui-bootstrap.sh      # adds ALL SIX inbounds under ONE node-main client, in one pass

08-xui-bootstrap.py creates a single node-main client (carrying both the VMess UUID and the shared Trojan/Shadowsocks password) linked to all six inbounds — so the panel's Clients page shows one row whose Info dialog lists all six share links + QR codes and one subscription. It bakes in four version-compat fixes that are easy to miss and each cost real debugging on a live v3.5.0 box: user_id=1 (else inbounds are invisible in the panel), an externalProxy link hint (else generated links point at the useless internal 127.0.0.1:<port>), a top-level SS password (else the panel's copy-links endpoint panics and returns empty → "No response data"), and read-modify-write of the WARP routing rule (else re-runs wipe it). It writes directly into 3x-ui's sqlite DB rather than using the panel API (which 403s scripted requests on this build) — see 3x-ui-schema.md and pitfalls.md for the table layout if you need to add a 4th protocol or debug a client not authenticating.

6. aaPanel sites (panel-native, UI-manageable)

scripts/09-aapanel-sites.sh      # AddSite + SetSSL + CreateProxy (x6 for cover) + HttpToHttps, via panel classes
scripts/09b-default-444.sh       # unmatched server_name -> return 444; drop landing page

7. Firewall + monitoring (optional)

scripts/10-ufw.sh                # allow 22/80/443 + panel port + aaPanel 888/39000-40000
scripts/11-monitoring.sh         # optional: install Nezha + Komari agents (placeholders in config.env)

No extra firewall ports needed for the 6 inbounds — they're all 127.0.0.1-only, reverse-proxied through nginx on 443.

8. Smoke test — all six inbounds

scripts/12-smoke-test.sh

Checks: WS upgrade returns 101 on all 6 cover paths; landing/3x-ui/aaPanel panels return 200 (aaPanel test uses a full realistic UA string, see pitfalls); an unmatched hostname gets dropped (444); and — via a temporary on-box xray client covering all 6 outbounds — every *-direct egress IP equals SERVER_IP with warp=off, and every *-warp egress IP is a Cloudflare address with warp=on.

python3 scripts/14-generate-links-and-qr.py   # run LOCALLY, not on the box

Writes vmess://, trojan:// and Xray-style ss:// share links + QR codes via local qrencode, plus sing-box JSON snippets for Shadowsocks. Proxy Station parses these SS links; other clients may require transport-specific import. Nothing is sent to a third-party service. See templates/02-node-info-template.md for how to hand these to a client.

10. Documentation

Fill in the three templates in templates/ (handover doc, node info, ops guide) with this node's real values — see "Documentation templates" below.

Domains this creates

With DOMAIN_ROOT=nodeA.example.com and example labels SUB_COVER=edge, SUB_XUI=gateway, SUB_PANEL=console (pick your own):

Domain Role Backend
nodeA.example.com landing page static
<SUB_COVER>.nodeA.example.com node cover: 6 WS paths, one per inbound 127.0.0.1:57707-57712
<SUB_XUI>.nodeA.example.com 3x-ui panel 127.0.0.1:14533
<SUB_PANEL>.nodeA.example.com aaPanel 127.0.0.1:<panel port>
*.nodeA.example.com (default) unmatched → return 444 —

The labels above are just examples. Never publish the real labels/paths you pick — on a public zone they are the map to your control planes and tunnels.

Adding a protocol to an already-provisioned node

You don't need to redo steps 0-4. Add the new protocol's env vars to config.env, extend the PROTOCOLS list in scripts/08-xui-bootstrap.py and the PROXIES loop in scripts/09-aapanel-sites.py (both scripts are idempotent — re-running them skips ports/sites that already exist), re-run 08-xui-bootstrap.sh then 09-aapanel-sites.sh, then re-run the smoke test with the new outbound added. Full walkthrough with the concrete steps used to add Trojan+Shadowsocks to a VMess-only node: 3x-ui-schema.md.

Documentation templates

templates/01-handover-template.md, templates/02-node-info-template.md, templates/03-ops-guide-template.md are meant to be copied out and filled in with a real node's values after deployment (they're written with {{PLACEHOLDER}} tokens, not a real node's domain/keys). See ../templates/README.md for the full list of tokens each one needs.

References

  • pitfalls.md — read first. aaPanel 7.x version-compatibility fixes (install-wait logic, class_v2, anti-scan UA strictness), 3x-ui multi-protocol schema gotchas, WARP/nginx/monitoring/SSH pitfalls. Skipping it costs hours.
  • 3x-ui-schema.md — the sqlite table layout 3x-ui uses, and how to add a 4th protocol.
  • protocol-choice.md — why VMess+Trojan+Shadowsocks, performance/detection tradeoffs vs VLESS+REALITY/AnyTLS/Hysteria2/TUIC, and when you'd want a 4th protocol.
  • dns-records.md — the exact records to create, and how to use a non-Cloudflare DNS provider.
  • centralized-certs.md — sign wildcards on one host and push to a fleet.

Project entry points

Use the project root AGENTS.md and the shared edge-node-multi-protocol skill. The former standalone agent guides are migration snapshots, not active instructions.

Privacy / safety notes

  • Never commit config.env, cert keys, panel/xui passwords, or any UUID/password/token. Add them to your password manager.
  • The scripts contain no real domains, IPs, subscription rules, or routing policy — only placeholders. Keep it that way when you adapt them.
  • WARP mode here is for your own clean egress; it is not a bypass for anyone else's controls.
  • The link/QR-code generator (step 9) never sends anything to a third-party service — QR codes are generated fully offline with local qrencode.