# Zero-infra starter stack: bundles its own Traefik + wg-easy alongside the # control-plane. If you already run Traefik and/or wg-easy, use the main # repo's root docker-compose.yaml + ops/deploy.sh instead — it plugs into # what you already have rather than running a second copy. # # Filling this file in by hand? Run `deploy.sh` instead — it prompts for # the values below, generates the secrets/tokens, handles the `$$` # escaping gotcha noted under WG_EASY_PASSWORD_HASH for you, and renders # traefik/static/traefik.active.yml + traefik/dynamic/control-plane.yml. # The hostname (or IP, for a first local smoke test) wg-easy advertises to # peers as the WireGuard endpoint. Must be reachable on udp/51820. GATEWAY_PUBLIC_HOST=vpn.example.com # ACME email for Let's Encrypt registration (HTTP-01, per-subdomain — # fine at self-hosted scale, nowhere near Let's Encrypt's ~50-certs/ # registered-domain/week limit with a handful of published apps). GATEWAY_ACME_EMAIL=admin@example.com # bcrypt hash of the wg-easy admin password. Generate with: # docker run --rm ghcr.io/wg-easy/wg-easy:14 node -e \ # "console.log(require('bcryptjs').hashSync(process.argv[1], 10))" 'your-password' # # IMPORTANT: bcrypt hashes contain literal `$` characters (e.g. # "$2a$10$..."), and Docker Compose's own .env-file parser treats `$word` # as a variable reference to substitute — confirmed by actually hitting # this: an unescaped hash silently got truncated to "$2a$10" with # everything after dropped. Every `$` in the value below MUST be doubled # as `$$`, e.g. WG_EASY_PASSWORD_HASH=$$2a$$10$$abc123... WG_EASY_PASSWORD_HASH= # PLAINTEXT password matching the hash above. wg-easy's admin API (this # specific pinned image, ghcr.io/wg-easy/wg-easy:14 — confirmed by reading # its actual source, which turned out to differ from what wg-easy's current # docs describe) has no login call or session cookie at all: every request # just carries this password as a plain `Authorization` header, checked # with bcrypt against WG_EASY_PASSWORD_HASH. No username concept exists. WG_EASY_ADMIN_PASSWORD= # The one box token this stack accepts at /v1/boxes/register, skipping # full account/registration-token issuance. Generate with: openssl rand -hex 32 GATEWAY_BOX_TOKEN= # Hostname a Furtka box actually reaches the control-plane API on — needs # its own A/AAAA record pointing at this VPS. Not read by docker-compose # itself; it's what deploy.sh substitutes into traefik/dynamic/control-plane.yml. GATEWAY_CONTROL_PLANE_HOST=gw.example.com # Bearer token required to create accounts via POST /v1/accounts. # Only meaningful once account endpoints exist (Phase 2+); harmless to set # now. Generate with: openssl rand -hex 32 GATEWAY_ADMIN_TOKEN= # Optional: override the control-plane image `docker stack deploy` pulls # (Swarm ignores docker-compose.yaml's `build:` key). Defaults to the # `latest` tag CI publishes on every push to master; pin to a specific # commit-sha tag for a reproducible deploy. Not read by plain # `docker compose up`, which always builds locally. GATEWAY_CONTROL_PLANE_IMAGE=