furtka-gateway/examples/starter-stack/README.md

74 lines
3.7 KiB
Markdown
Raw Normal View History

feat: ship control-plane standalone, bring your own Traefik/wg-easy Restructure the deployment model: instead of bundling and owning its own Traefik + wg-easy, the control-plane now plugs into a Traefik and wg-easy the operator already runs (as sibling containers, or otherwise reachable). This avoids duplicating infra self-hosters commonly already have, and sidesteps having to run a second ACME/DNS setup entirely — the operator's own Traefik already has a working cert resolver. - New control_plane/wgeasy_v15.py: a wg-easy v15 REST client, verified live against a real running container (auth model, route shapes, and the new per-client PresharedKey all differ materially from v14's API). Threaded the preshared key through to the box side (furtka/wireguard.py in the furtka repo) so v15's handshake actually completes. - GATEWAY_CERT_RESOLVER is now a real env var instead of a GATEWAY_MODE- derived hardcoded "le", so it can mirror whatever resolver name (if any) the operator's own Traefik already has configured. - Root docker-compose.yaml/ops/deploy.sh: control-plane only now. deploy.sh asks how the operator's Traefik reaches it (same docker network, joined via `docker network connect`, or a separate host) instead of prompting for DNS-01 providers and port remaps single-parallel mode needed. - single-parallel mode is retired outright — nothing needs it once furtka-gateway doesn't own a second Traefik/wg-easy to remap ports for. - The old bundled stack (own Traefik+wg-easy, HTTP-01) moves to examples/starter-stack/ as the documented zero-infra on-ramp. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R3jVwAqESRjPdW9P9nvfem
2026-09-19 13:34:29 +02:00
# furtka-gateway starter stack
Zero-infra on-ramp: bundles its own Traefik + wg-easy alongside the
control-plane, for an operator who doesn't already run either. This is a
secondary path — if you already have Traefik and/or wg-easy running
(a common self-hosting setup), use the main repo's root `docker-compose.yaml`
+ `ops/deploy.sh` instead, which plugs into what you already have rather
than running a second copy. See the root `README.md`'s Prerequisites
checklist.
This stack always runs in `GATEWAY_MODE=single`, owns ports `80/tcp`,
`443/tcp`, and `51820/udp` on the host directly, and uses HTTP-01 certs
(no DNS provider needed) — the tradeoffs that make `single-parallel` mode
necessary elsewhere don't apply here, because this stack assumes nothing
else is already using those ports.
## Deploying on a VPS
1. Get a VPS with a static public IP, and point two A/AAAA records at it:
one for `GATEWAY_PUBLIC_HOST` (the WireGuard endpoint) and one for
`GATEWAY_CONTROL_PLANE_HOST` (the registration API — needs its own
hostname since the response carries a WireGuard private key in
plaintext JSON, so that call must go over HTTPS).
2. Run `./deploy.sh` from this directory and answer its prompts. It
generates the wg-easy admin password hash, `GATEWAY_BOX_TOKEN`, and
`GATEWAY_ADMIN_TOKEN`, writes `.env`, renders
`traefik/static/traefik.active.yml` and
`traefik/dynamic/control-plane.yml` from their tracked templates, runs
`sudo ops/host-sysctls.sh` (wg-easy needs `net.ipv4.ip_forward=1` and
`net.ipv4.conf.all.src_valid_mark` — see that script's comments for why
Docker's own `sysctls:` key can't set these under `network_mode: host`),
brings the stack up, runs `sudo ops/firewall.sh` (locks wg-easy's admin
API off the public interface — `network_mode: host` means Docker itself
can no longer do that automatically), and health-checks it.
3. Open `80/tcp`, `443/tcp`, `51820/udp` in whatever firewall/security
group sits in front of the VPS.
Run `./deploy.sh --render-only` to just (re)write `.env` and the rendered
Traefik files without touching sysctls/compose/firewall — useful to inspect
what would be generated, or after hand-editing `.env`.
## Local dev
```bash
./deploy.sh --render-only
# GATEWAY_PUBLIC_HOST can be a LAN-reachable IP for a first smoke test.
docker compose up -d
curl http://127.0.0.1:8090/healthz
```
`ops/firewall.sh` and the `control-plane.yml` Traefik route are about
reaching this gateway from the real internet — skip both for local dev.
## Docker Swarm caveats
If deploying via `docker stack deploy` instead of `docker compose up`:
- `docker-compose.yaml`'s `build: ../../control_plane` doesn't work under
Swarm at all — it only ever schedules pre-built, tagged images. Swarm
falls back to the `image:` key on the same service instead, which CI
keeps published to the registry on every push to `master` (tagged
`latest` plus the short commit sha). Set `GATEWAY_CONTROL_PLANE_IMAGE` in
`.env` first if you want a specific commit-sha tag rather than whatever
`latest` currently points to.
- `docker stack deploy` does not read a `.env` file next to the compose
file the way `docker compose up` does — `set -a; source .env; set +a`
first, or the `${VAR}` substitutions come out empty. **Do not `source`
`.env` directly if `WG_EASY_PASSWORD_HASH` is set** — bash expands `$$`
(its own PID) while sourcing, silently corrupting the hash's doubled-`$`
escaping. Export that one variable separately, unescaped, or set it
directly on the `docker stack deploy` command line.
- `depends_on` is silently ignored by Swarm — harmless here, since
`control-plane` only talks to wg-easy/Traefik lazily on the first real
request, not at startup.