2026-08-24 11:50:25 +02:00
|
|
|
services:
|
|
|
|
|
control-plane:
|
|
|
|
|
build: ./control_plane
|
2026-10-07 15:49:49 +02:00
|
|
|
# Always build from this checkout — never pull a registry image that may
|
|
|
|
|
# be older than the code you just pulled. The image name below only
|
|
|
|
|
# matters for `docker stack deploy` (Swarm can't build; see the Swarm
|
|
|
|
|
# caveats in README.md), which pulls what CI publishes on every push to
|
|
|
|
|
# master (`latest` plus the short commit sha).
|
|
|
|
|
pull_policy: build
|
2026-08-26 12:20:16 +02:00
|
|
|
image: ${GATEWAY_CONTROL_PLANE_IMAGE:-forgejo.sourcegate.online/robert/furtka-gateway-control-plane:latest}
|
2026-08-24 11:50:25 +02:00
|
|
|
container_name: furtka-gateway-control-plane
|
|
|
|
|
environment:
|
|
|
|
|
- GATEWAY_MODE=${GATEWAY_MODE:-single}
|
|
|
|
|
- GATEWAY_STATE_DIR=/data
|
|
|
|
|
- GATEWAY_DYNAMIC_DIR=/dynamic
|
|
|
|
|
- GATEWAY_ADMIN_TOKEN=${GATEWAY_ADMIN_TOKEN}
|
|
|
|
|
- GATEWAY_BOX_TOKEN=${GATEWAY_BOX_TOKEN}
|
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
|
|
|
# Blank means no explicit resolver (`tls: {}`) — Traefik applies
|
|
|
|
|
# whatever default TLS is already configured on YOUR Traefik. Set
|
|
|
|
|
# this to your Traefik's certResolver name if it doesn't have one.
|
|
|
|
|
- GATEWAY_CERT_RESOLVER=${GATEWAY_CERT_RESOLVER:-}
|
|
|
|
|
# Your existing wg-easy's admin API — reachable at whatever
|
|
|
|
|
# host:port makes sense for your setup (a sibling container on the
|
|
|
|
|
# network this joins — see `networks:` below — or a real host:port
|
|
|
|
|
# if it's elsewhere). See the README's Prerequisites checklist for
|
|
|
|
|
# the network-reachability requirement this doesn't (can't)
|
|
|
|
|
# automate away.
|
|
|
|
|
- WG_EASY_URL=${WG_EASY_URL}
|
2026-08-24 11:50:25 +02:00
|
|
|
- WG_EASY_ADMIN_PASSWORD=${WG_EASY_ADMIN_PASSWORD}
|
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
|
|
|
# "14" (default) or "15" — these are different, incompatible APIs
|
|
|
|
|
# (see control_plane/wgeasy.py vs wgeasy_v15.py's module docstrings).
|
|
|
|
|
# Check which one your wg-easy image tag actually is.
|
|
|
|
|
- WG_EASY_API_VERSION=${WG_EASY_API_VERSION:-14}
|
|
|
|
|
# v15 only: the admin user's username (v14 has no username concept —
|
|
|
|
|
# see WG_EASY_ADMIN_PASSWORD above).
|
|
|
|
|
- WG_EASY_ADMIN_USERNAME=${WG_EASY_ADMIN_USERNAME:-}
|
|
|
|
|
# v15 only: restricts new clients' AllowedIPs to your wg-easy peer
|
|
|
|
|
# subnet (v15 defaults new clients to full-tunnel 0.0.0.0/0, ::/0
|
|
|
|
|
# otherwise — see wgeasy_v15.py's restrict_default_allowed_ips()).
|
|
|
|
|
- GATEWAY_WG_ALLOWED_IPS=${GATEWAY_WG_ALLOWED_IPS:-10.8.0.0/24}
|
2026-08-24 11:50:25 +02:00
|
|
|
volumes:
|
|
|
|
|
- control_plane_data:/data
|
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
|
|
|
# Bind-mount this at whatever host path YOUR Traefik's file provider
|
|
|
|
|
# already watches (or add a second `providers.file` directory to
|
|
|
|
|
# your Traefik's static config pointed at this same path — additive,
|
|
|
|
|
# doesn't touch your existing dynamic config). Default here is just
|
|
|
|
|
# a placeholder; ops/deploy.sh asks for the real path.
|
|
|
|
|
- ${GATEWAY_DYNAMIC_DIR_HOST:-./dynamic}:/dynamic
|
2026-08-24 11:50:25 +02:00
|
|
|
ports:
|
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
|
|
|
# Loopback-only by default. This is how a remote Furtka box reaches
|
|
|
|
|
# the registration API in the COMMON case (your own Traefik, on the
|
|
|
|
|
# network below, proxies to it — see
|
|
|
|
|
# traefik/dynamic/control-plane.yml.example) — a public bind here
|
|
|
|
|
# isn't needed. Only widen GATEWAY_CONTROL_PLANE_BIND if your
|
|
|
|
|
# Traefik genuinely can't join this container's docker network (a
|
|
|
|
|
# separate-host setup) — ops/deploy.sh flags the access-control
|
|
|
|
|
# implication of doing that when you choose it.
|
|
|
|
|
- "${GATEWAY_CONTROL_PLANE_BIND:-127.0.0.1:8090}:8090"
|
2026-08-24 11:50:25 +02:00
|
|
|
restart: unless-stopped
|
2026-10-07 15:49:49 +02:00
|
|
|
# No `networks:` here on purpose — your Traefik's network name differs
|
|
|
|
|
# per setup. ops/deploy.sh writes a gitignored
|
|
|
|
|
# docker-compose.override.yaml (auto-loaded by `docker compose`) that
|
|
|
|
|
# joins this container to it from the first second.
|
2026-08-24 11:50:25 +02:00
|
|
|
|
|
|
|
|
volumes:
|
|
|
|
|
control_plane_data:
|