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

118 lines
4.8 KiB
Markdown
Raw Permalink Normal View History

# furtka-gateway on a new server
Everything in one Docker Compose stack: **wg-easy** (WireGuard), **Traefik**
(HTTPS reverse proxy with Let's Encrypt), and the **furtka-gateway
control-plane**. Use this on a server that doesn't run anything else on
ports 80/443. If you already run Traefik and wg-easy, see
[the root README](../../README.md#option-b-existing-traefik--wg-easy) instead.
```
internet ──443──▶ Traefik ──WireGuard tunnel──▶ Furtka box (behind NAT) ──▶ app
▲
Furtka box ──51820/udp──▶ wg-easy (box dials out; no port forwarding at home)
```
## Server requirements
| | |
|---|---|
| Machine | Any Linux VPS, x86_64 or arm64. 1 vCPU / 512 MB RAM is plenty. |
| Public IP | A static public IPv4 address. |
| Kernel | WireGuard support (Linux 5.6+ — every current distro). |
| Software | Docker Engine with the Compose plugin **v2.24 or newer** (`docker compose version`), plus `git`, `curl`, `openssl`. |
| Free ports | `80/tcp`, `443/tcp`, `51820/udp` — nothing else may use them. |
| Firewall | Allow those three ports inbound, in the server's firewall *and* any provider firewall/security group. Nothing else needs to be open. |
Installing Docker on a fresh Debian/Ubuntu server:
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
```bash
curl -fsSL https://get.docker.com | sh
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
```
## DNS requirements
Easiest: **one wildcard record** for your domain, pointing at the server.
| Type | Name | Value |
|---|---|---|
| A | `*.example.com` | your server's IPv4 |
That covers everything at once:
- `vpn.example.com` — the WireGuard endpoint boxes dial
- `gw.example.com` — the gateway API you enter on a box
- `<app>.example.com` — every app a box publishes later, with no DNS change per app
No wildcard possible? Create A records for `vpn.` and `gw.`, plus one per app
before you publish it. Certificates use Let's Encrypt's HTTP challenge, so no
DNS provider API is needed — any registrar works.
If you also add AAAA (IPv6) records, the server must be reachable over IPv6 on
the same three ports.
## Install
```bash
git clone https://forgejo.sourcegate.online/robert/furtka-gateway.git
cd furtka-gateway/examples/starter-stack
./deploy.sh
```
It asks for four things — your domain, the two hostnames (defaults
`vpn.<domain>` and `gw.<domain>`), and an email for Let's Encrypt — then
generates all passwords and tokens into `.env`, starts the stack, checks DNS
and health, and prints what to enter on a box.
`./deploy.sh --render-only` writes `.env` and `dynamic/control-plane.yml`
without starting anything. `./deploy.sh --non-interactive` takes every value
from an existing `.env`, for scripted installs.
## Connect a box
On the Furtka box: **Settings → Gateway**, enter
- **Gateway URL:** `https://gw.example.com`
- **Registration token:** `GATEWAY_BOX_TOKEN` from `.env`
- **Box name:** anything
Then publish an app from the Apps page: **⋯ → Expose to Internet**, and give it
a hostname like `tools.example.com`.
## Day to day
| Task | Command (in this folder) |
|---|---|
| Update | `git pull && ./deploy.sh --non-interactive` |
| Logs | `docker compose logs -f control-plane` (or `wg-easy`, `traefik`) |
| Restart | `docker compose restart` |
| wg-easy admin UI | `ssh -L 51821:127.0.0.1:51821 <server>`, then open http://localhost:51821 — user `admin`, password `WG_EASY_ADMIN_PASSWORD` from `.env` |
| Backup | `.env`, `dynamic/`, and the Docker volumes `*_wg_easy_data`, `*_control_plane_data`, `*_traefik_acme` |
Changed the wg-easy password in its UI? Put the new one in `.env` as
`WG_EASY_ADMIN_PASSWORD` and run `./deploy.sh --non-interactive`.
## How it works
wg-easy creates the `wg0` interface and the route to the boxes
(`10.8.0.0/24`) inside its own network namespace. Traefik must be in that same
namespace to reach a box's tunnel address. So both join one namespace owned by
`gateway-net`, a do-nothing container whose only job is to hold the namespace
and publish the ports. Because it never restarts, wg-easy and Traefik can each
restart independently without cutting the other off. No host routes, NAT rules,
sysctls or host networking are needed, and wg-easy's admin UI is never
published to the internet.
Two consequences:
- **Restart through Compose.** `docker compose restart` handles everything.
If `furtka-gateway-net` itself was restarted with a bare `docker restart`,
wg-easy and Traefik lose their network — fix with `docker compose restart`
(plain `docker compose up -d` does *not* repair it).
- **Compose only, not Swarm.** `network_mode: service:` isn't supported by
`docker stack deploy`.
Verified with a simulated box: route publishing, plus restarts of wg-easy,
Traefik and `gateway-net`, and a full stack stop/start. A real server reboot
hasn't been tested yet — after your first reboot, check that
`https://gw.example.com/healthz` answers.