Files
clash-party/deploy/gateway/deploy.sh
ezequielnick a092eb50c3 feat(plugin): CPX v2 availability hardening (squash of cpx-v2-availability-hardening)
Client-side hardening of the CPX airport plugin, squashed from 46 commits
forked at 89c2bb0e. Behaviour changes:

- Unified operation model (§0.4/0.5): one deadline + one AbortSignal + one
  persistence commit per operation; per-plugin lock → vault lock hierarchy;
  tombstone on delete; every wait (lock, DNS preflight, proxy resolution,
  vault decrypt) is bounded by the same budget.
- Routing: direct/proxy auto-fallback with a pre-send guard; proxied https
  builds its own CONNECT tunnel (an aborted hung CONNECT closes its socket);
  invalid local-proxy ports are refused instead of falling back to :80; the
  core's inbound credentials are carried to the local proxy; NAT64 and
  site-local IPv6 ranges are non-public.
- Gateways: multi-gateway recovery with one rediscovery per operation,
  normalized endpoint paths, signed discovery documents (Ed25519, seq/digest
  accept/align/rollback/equivocation), commit order vault → plugin.yaml.
- Subscriptions: a fetched subscription is validated by the core (mihomo -t)
  against the current override set before it replaces the profile, inside
  the profile write critical section (profile.yaml and override.yaml share
  one write queue); schedule fields are read at write time; the first
  subscription is activated through the real switch flow; profile deletion
  removes the record last so any failure stays retryable.
- Devices: a re-login that replaces a still-valid device records it in the
  vault (staleDevices) and retires it after the login, after later
  successful fetches and on removal; enroll compensation restores the old
  vault and keeps an un-revoked new device for retirement.
- Vault: on Linux the vault is persisted only behind a system secret store
  (backend name + ciphertext-prefix canary); otherwise it stays in memory.
  A cache-miss read releases the caller at the budget while the lock is held
  until the decrypt ends.
- Config caches (plugin.yaml, profile.yaml, override.yaml) can no longer be
  rolled back by a late cold read.
- Reference gateway and provider guides updated (deploy contract, discoveryUrls
  same-origin rule, /revoke after re-login); https-proxy-agent dropped.

Reviewed in a Codex loop (gpt-6, 38 calls): 74 findings, 68 fixed and
verified, 6 invalid, no backlog. Tests: vitest 501, gateway 106.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 22:44:13 +08:00

59 lines
2.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# One-click deploy for the cpx-gateway reference server (Caddy auto-HTTPS + gateway).
set -euo pipefail
cd "$(dirname "$0")"
command -v docker >/dev/null 2>&1 || {
echo "Docker is required. Install Docker, then re-run: https://docs.docker.com/engine/install/" >&2
exit 1
}
docker compose version >/dev/null 2>&1 || {
echo "Docker Compose v2 is required (the 'docker compose' subcommand)." >&2
exit 1
}
if [ ! -f .env ]; then
cp .env.example .env
read -rp "Public domain (its DNS A/AAAA record must already point at this VPS), e.g. gw.example.com: " DOMAIN
[ -n "$DOMAIN" ] || { echo "A domain is required." >&2; exit 1; }
sed -i.bak "s|^DOMAIN=.*|DOMAIN=${DOMAIN}|" .env && rm -f .env.bak
echo "Wrote .env (DOMAIN=${DOMAIN})."
fi
# sed prints nothing (and exits 0) when a key is absent, so `set -e` does not abort on a .env
# that sets only one of DOMAIN / DOMAINS (docker compose accepts either).
DOMAIN=$(sed -n 's/^DOMAIN=//p' .env | tail -n 1 | sed -E 's/^[[:space:]]+|[[:space:]]+$//g')
DOMAINS=$(sed -n 's/^DOMAINS=//p' .env | tail -n 1)
DOMAINS=${DOMAINS:-$DOMAIN}
# Caddy takes {$DOMAINS} verbatim as site addresses and rejects "a,b" without a space:
# normalize any comma list to "a, b".
DOMAINS=$(printf '%s' "$DOMAINS" | sed -E 's/[[:space:]]*,[[:space:]]*/, /g; s/^[[:space:]]+|[[:space:]]+$//g')
DOMAIN=${DOMAIN:-${DOMAINS%%,*}}
[ -n "$DOMAIN" ] || { echo "DOMAIN or DOMAINS must be set in .env" >&2; exit 1; }
export DOMAINS
echo "Building and starting containers..."
docker compose up -d --build
cat <<EOF
✅ Deployed. DOMAIN=${DOMAIN} (Caddy serves: ${DOMAINS})
Next steps:
1) Wait ~30s for Caddy to obtain the TLS certificate, then verify discovery:
curl https://${DOMAIN}/.well-known/cpx-gateway
2) Add an account (you'll be prompted for a password):
docker compose exec gateway cpx-admin add-user <name> '<hidden-subscription-url>' --limit 3
3) Generate the .cpx plugin file for your users (run from the repository root):
node scripts/plugin/gen-cpx.mjs https://${DOMAIN}/oauth/authorize "Your Airport" https://${DOMAIN} your-airport.cpx
4) Distribute your-airport.cpx. Users import it in Clash Party, log in via the
system browser with the account you created, and the subscription loads automatically.
Manage: docker compose exec gateway cpx-admin list-users | list-devices <name> | revoke-device <id>
Logs: docker compose logs -f gateway
Update: git pull && ./deploy.sh (account data persists in the gateway_data volume)
EOF