# 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 | `.` reverse-proxied, secret-path entrance, hardened | | Proxy | 3x-ui (Xray) | `.` 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 `*.` cert, auto-renew + reload, shared by all 6 inbounds | | DNS | Cloudflare API (or your provider, by hand) | `` + `*.` 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__{DIRECT,WARP}` / `WS_PATH__{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 ```bash 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 ```bash python3 scripts/02-cf-dns.py # Cloudflare: upserts + *. 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) ```bash scripts/03-sign-wildcard-cert.sh # on-box acme.sh --dns dns_cf, issues *. (ECDSA P-256, Let's Encrypt) ``` One cert, shared by all 6 inbounds via the configured `.` cover domain. Fleet cert signing: `centralized-certs.md`. ### 3. aaPanel International + nginx ```bash 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) ```bash 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 ```bash 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:`), 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) ```bash 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) ```bash 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 ```bash 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 ```bash 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 | | `.nodeA.example.com` | node cover: 6 WS paths, one per inbound | 127.0.0.1:57707-57712 | | `.nodeA.example.com` | 3x-ui panel | 127.0.0.1:14533 | | `.nodeA.example.com` | aaPanel | 127.0.0.1:\ | | `*.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`.