迁入通用节点部署工具、离线交付页生成器和共用技能,隔离真实配置与交付产物。统一 Codex 与 Claude Code 开发约定,补充节点迁移与运维文档。 完善 FINAL、策略组引用、输入校验、Surge 字段编码与 AI DNS 例外保护,增加隔离测试和订阅验证工具。 验证:全量类型检查、44 项单测、前端构建、脚本语法、技能入口与凭据隔离检查均通过。
12 KiB
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—rootor a sudo user (scripts escalate viasudo), and the private keyTZ— server timezone (defaultUTC)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. Defaultdns_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}forVMESS/TROJAN/SS) — pick your own, non-obvious, don't reuse a naming convention across them XUI_UUID+NODE_PASSWORD(+XUI_SUBID,SS_METHODdefaultchacha20-poly1305) — the credentials for the singlenode-mainclient that owns all six inbounds:XUI_UUIDauthenticates the two VMess inbounds,NODE_PASSWORDauthenticates 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.
9. Generate client links + QR codes
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.