Fleet
Nodes
The panel ("master") is the single web app you log into — it also doubles as a single-box install with its own built-in "local node". Every additional VPS you pair is a worker edge node. A node runs:
sshd— the SSH tunnel plane.xray(systemd) — VMess/VLESS/Trojan over WS/xHTTP/HTTPUpgrade.zivpn(systemd) — UDP VPN.nginx— TLS termination and reverse-proxy fan-out across every Cloudflare-compatible port.stunnel4(autoscript-node-stunnel) — SSH-over-TLS on 445/447/777.autoscript-node— the node agent (FastAPI + uvicorn), the only process the panel talks to. It owns enrolment, liveness, remote control, local enforcement, WARP egress, benchmarking and log hygiene.autoscript-node-ssh-ws— a companion process bridging SSH over WebSocket, run in its own systemd unit at higher CPU priority than the agent so telemetry never throttles real tunnel throughput.
The panel never SSHes into a node for day-to-day operation. Everything goes through a signed HTTPS RPC channel (/rpc/*) proxied by the panel's own server route (src/routes/api/public/node-rpc.ts), because a browser cannot pin a self-signed certificate by fingerprint on its own. See /docs/node-security for the pinning model.
Browser (panel UI)
-> Panel server route /api/public/node-rpc (Node.js https client, cert-pin + HMAC)
-> Node agent /rpc/* (FastAPI/uvicorn, self-signed TLS on AGENT_PORT)
-> xray / zivpn / sshd / nginx / stunnel4
Node agent -> POST /api/public/node-handshake (enrol / re-pin)
Node agent -> POST /api/public/node-heartbeat (every ~3-5s / adaptive)
Node agent -> POST /api/public/node-ssh-usage (SSH byte ledger, own channel)The main fleet management screen shows a table of every paired node: status (online / offline / installing / degraded), CPU/RAM/disk, live throughput, connected sessions, agent version and region.
Actions
- Add Node — generates the install one-liner (see Adding a node below).
- Delete Node — a
DeleteNodeDialogproduces the matching uninstall one-liner, and removal also runs a background fleet-wide purge (purgeNodeEverywhere(row.id)) so a deleted-then-recreated node with the same id never resurrects stale usage. - Per-row RPC actions (badge shows amber "Restarting…"/"Stopping…"/"Updating…" while in flight): restart node, stop agent, update agent, rebuild/re-tune, run benchmark, force-repin.
- Bulk actions: bulk delete, bulk stop, bulk update.
Self-healing behaviours
- Auto-update: if the panel's own single-box ("local") node reports an older agent version than the panel, it self-updates in the background (
autoUpdateLocalNode()); remote nodes always require a manual "Update" click. - Status repair: a node marked disabled in the database but reporting installed:true and not soft-deleted gets its status corrected automatically from heartbeat data.
A deep-dive on one node: live metrics, NIC cards, service status cards, session list, protocol breakdown, Xray version/upgrade UI, and a logs viewer. Buttons drive individual RPCs:
| Button | RPC |
|---|---|
| Restart node | node.restart |
| Restart Xray / Restart ZiVPN | service.restart {service:"xray"|"zivpn"} |
| Force re-pin | POST /api/nodes/:id/force-repin |
note
- Per-region rollup and Region groups — aggregate node counts/health by region.
- Infrastructure cost — cost tracking widget.
- Agent versions — which nodes are on which agent build, driving the "needs update" indicator.
- Cert expiry — surfaces nodes whose TLS certificate is approaching expiry (reads cert.log / cert metadata pushed via heartbeat/logs).
| Plane | Service | Notes |
|---|---|---|
| SSH tunnels | sshd | Native OpenSSH, tuned via sshd_config.d/99-autoscript-node.conf. |
| SSH over WebSocket | autoscript-node-ssh-ws | Own systemd unit, Nice=-5 / CPUWeight=800 — outranks the agent since it is customer data plane. |
| SSH over TLS | stunnel4 (autoscript-node-stunnel) | Ports 445/447/777. |
| VMess/VLESS/Trojan | xray | WS/xHTTP/HTTPUpgrade over nginx-fronted Cloudflare-compatible ports. |
| UDP VPN | zivpn | Static binary, port 5667/UDP. |
| TLS termination / fan-out | nginx | Reverse-proxies every Cloudflare-compatible port to loopback Xray/SSH-WS listeners. |
| Port(s) | Protocol | Purpose |
|---|---|---|
AGENT_PORT | TCP, HTTPS (self-signed, pinned) | Panel <-> node RPC + heartbeat channel. Default 4001, auto-reassigned if busy. Firewalled to panel IPs only. |
| 22 | TCP | Native SSH (also reachable via the nginx WS bridge and stunnel). |
| 443, 2053, 2083, 2087, 2096, 8443 | TCP, TLS | Cloudflare-compatible HTTPS fronting for Xray WS/xHTTP/HTTPUpgrade. |
| 80, 8080, 8880, 2052, 2082, 2086, 2095 | TCP, plain HTTP | Same Xray transports over plaintext, plus ACME http-01 challenge. |
| 445, 447, 777 | TCP, TLS (stunnel) | SSH-over-TLS for clients that don't speak WebSocket. |
| 5667 | UDP | ZIVPN. |
| 10001-10003 / 10011-10013 / 10021-10023 / 10080 (loopback) | TCP | Internal Xray/SSH-WS inbound listeners nginx reverse-proxies to. |
- 1Prereqs: a fresh Ubuntu/Debian VPS with root SSH access and outbound internet, and DNS A/AAAA record(s) for the VPN hostname pointed at the VPS (needed for Let's Encrypt http-01, unless using dns-01 for a wildcard).
- 2In the panel, go to Nodes → Add Node. This generates a unique
NODE_ID, aBEARERtoken and aSHARED_SECRET(fleet-wideNODE_SHARED_SECRETor a per-node override), and the install one-liner. - 3Paste the one-liner into the VPS's own terminal — not your laptop. If
EXPECTED_IPis set, the installer verifies (via ipify/ifconfig.me/icanhazip) that it's actually running on that box and refuses + cleans up if not. - 4If
PANEL_DOMAIN/VPN_DOMAINare omitted and a TTY is attached, the installer runs an interactive domain wizard asking for the node's API/RPC hostname and its customer-facing VPN hostname (wildcard requires dns-01 +DNS_API). - 5The installer runs 17 phases end to end, installing packages, fetching agent code, generating the self-signed TLS cert, installing Xray/ZIVPN/sshd, tuning the kernel, issuing the public TLS cert via acme.sh, publishing Xray on every Cloudflare port, firewalling the agent port, and starting all the systemd units.
- 6The panel flips the node to Online on the next heartbeat once
installed:trueis seen and the handshake fingerprint is pinned. - 7Push a first user set (or wait for the panel's normal sync cycle) — the node terminates real traffic as soon as
users.applylands.
curl -fsSL https://<panel-host>/api/public/node-install.sh \
| NODE_ID=<id> MASTER=https://<panel-host> BEARER=<token> \
SHARED_SECRET=<secret> EXPECTED_IP=<vps-ip> \
VPN_DOMAIN=<vpn.example.com> PANEL_DOMAIN=<node-api.example.com> \
sudo -E bashKey install phases
- 1. Verify server identity (EXPECTED_IP check).
- 2. Select/verify AGENT_PORT (default 4001; falls back to a random 20000-60000 port if busy, reported back to the panel).
- 3. Reset any previous agent install (idempotent re-run support).
- 4. apt-get install curl jq socat cron nginx stunnel4 python3 python3-venv python3-pip iptables iproute2 openssl wireguard-tools iputils-ping ca-certificates.
- 5. Fetch agent code from $MASTER/api/public/node-agent.tar.gz into /opt/autoscript-node; generate the self-signed TLS cert for the agent RPC listener.
- 6. Create the Python venv, pip install fastapi/uvicorn[standard]/httpx/psutil/pydantic/websockets; install sshd, Xray-core (official installer), ZIVPN (arch-detected static binary).
- 6b-2. Kernel/network tuning: BBR + fq, huge socket buffers, somaxconn, nf_conntrack_max, fs.file-max=1048576, initcwnd 40; xray.service.d/99-autoscript-tuning.conf grants CAP_NET_ADMIN/CAP_NET_BIND_SERVICE/CAP_NET_RAW and runs Xray as root for SO_MARK shaping.
- 6c. Issue the public TLS certificate via acme.sh (http-01 via nginx webroot, or dns-01 for wildcards); install stunnel for SSH-over-TLS on 445/447/777.
- 6c-2. Publish Xray on every Cloudflare-supported port (TLS: 443 2053 2083 2087 2096 8443; plain: 80 8080 8880 2052 2082 2086 2095), each on a real-looking path (/assets, /stream, /media...) rewritten internally to VMess/VLESS/Trojan WS/xHTTP/HTTPUpgrade backends. SSH-over-WebSocket answers only on /.
- 8. Write /etc/default/autoscript-node (mode 0600) with NODE_ID/MASTER/AGENT_PORT/BEARER/SHARED_SECRET; install the self-updater and the logclean timer/service pair.
- 8b. Firewall the agent port to panel IPs only.
- 8c. POST the handshake fingerprint to the panel immediately, without waiting for the agent's own first boot.
- 9. Migrate away from the legacy single-box per-UID shaper (autoscript-ip-limit.timer/.service) if present: stop, disable, mask, clear its tc/ipset state.
- 10. Enable + start autoscript-node, autoscript-node-ssh-ws, autoscript-node-stunnel; write /var/lib/autoscript-node/install-complete.json and bounce the agent so the next heartbeat reports installed:true.
The installer is fully idempotent. Re-running the exact same one-liner on an already-installed node repairs configuration, re-issues certificates if needed, and does not duplicate systemd units or ports. This is the standard fix for a node stuck mid-install or with drifted config.
From the panel's Delete-node dialog, the generated one-liner is:
curl -fsSL <panel>/api/public/node-uninstall | sudo [PURGE_VPN=1 ]bashPURGE_VPN=1 additionally removes Xray/ZIVPN/SSH account data instead of just the agent. Seenode/uninstall.sh / backend/uninstall.sh.
