No description
Find a file
Robert Syrnicki 3a9d18fcd5 Scaffold gateway stack and single-tenant control plane
wg-easy + Traefik docker-compose stack (Phase 1) plus a stdlib-only
control-plane API for box registration, WireGuard peer provisioning via
wg-easy, and per-box route publish/unpublish backed by Traefik's file
provider (Phase 2, single-tenant mode). SQLite holds accounts/boxes/routes
so a later multi-tenant shared instance is the same schema with more rows,
not a reshape.

wg-easy's actual REST API was verified against its source rather than
assumed: it has no bearer-token auth (session-cookie login via
POST /api/auth/password) and no way to accept an externally-generated
public key (it always mints the keypair itself, private key included) —
both corrected from the original plan during implementation.
2026-08-24 11:50:25 +02:00
control_plane Scaffold gateway stack and single-tenant control plane 2026-08-24 11:50:25 +02:00
tests Scaffold gateway stack and single-tenant control plane 2026-08-24 11:50:25 +02:00
traefik Scaffold gateway stack and single-tenant control plane 2026-08-24 11:50:25 +02:00
.env.example Scaffold gateway stack and single-tenant control plane 2026-08-24 11:50:25 +02:00
.gitignore Scaffold gateway stack and single-tenant control plane 2026-08-24 11:50:25 +02:00
docker-compose.yaml Scaffold gateway stack and single-tenant control plane 2026-08-24 11:50:25 +02:00
pyproject.toml Scaffold gateway stack and single-tenant control plane 2026-08-24 11:50:25 +02:00
README.md Scaffold gateway stack and single-tenant control plane 2026-08-24 11:50:25 +02:00

furtka-gateway

WireGuard (wg-easy) + Traefik reverse-proxy that lets a Furtka box — typically sitting behind NAT/CGNAT with no public IP — expose individual apps to the internet under a real domain with automatic TLS.

A Furtka box becomes a WireGuard peer of this gateway. The gateway's Traefik reverse-proxies public HTTPS traffic over that tunnel to whichever apps the box owner has explicitly published. Only apps whose Furtka manifest.json declares internet.viable: true can be published at all, and publishing is always an explicit per-app opt-in on the box side — the gateway never exposes anything on its own.

Supports two deployment modes from the same codebase:

  • single — the common case: one person/operator runs this on their own VPS with their own domain's A/AAAA records pointed at it, for their own Furtka box(es).
  • shared — one larger, multi-tenant instance (operated by the Furtka project) for people who don't want to run their own. Same schema, same code; single is just the one-account case.

See docker-compose.yaml and control_plane/ for the moving parts. Status: Phase 1 (scaffold) — wg-easy + Traefik + a stub control-plane exposing only /healthz, enough to manually prove the wiring end-to-end before the real control-plane (accounts/boxes/routes) lands.

Local dev

cp .env.example .env
# edit .env: set GATEWAY_PUBLIC_HOST to a real hostname you control (or a
# LAN-reachable IP for a first smoke test), and a PASSWORD_HASH for wg-easy
# (see https://github.com/wg-easy/wg-easy for how to generate one).
docker compose up -d
curl http://127.0.0.1:8090/healthz

Traefik's dashboard/API and wg-easy's own UI are intentionally not published on a host port — reach them via docker compose exec / port-forwarding during development. Neither should ever be reachable from the internet in a real deployment; only the control-plane (8090, bound to 127.0.0.1) and Traefik's 80/443 entrypoints are meant to be exposed.