From 3b3f589d646a30c19e8e794100698764535b0eec Mon Sep 17 00:00:00 2001 From: Gerhard Scheikl Date: Sat, 30 May 2026 14:18:53 +0200 Subject: [PATCH] dev: document server-only pieces (authorizer + frps.toml) Add dev/SERVER-ONLY.md (extended check-subdomain authorizer code + dev frps notes) and dev/frps/frps.toml.example (sanitized template) so the gitignored, server-only parts of the dev setup are reproducible from the repo. --- dev/SERVER-ONLY.md | 72 ++++++++++++++++++++++++++++++++++++++ dev/frps/frps.toml.example | 23 ++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 dev/SERVER-ONLY.md create mode 100644 dev/frps/frps.toml.example diff --git a/dev/SERVER-ONLY.md b/dev/SERVER-ONLY.md new file mode 100644 index 0000000..ab3247a --- /dev/null +++ b/dev/SERVER-ONLY.md @@ -0,0 +1,72 @@ +# Server-only dev pieces (NOT in git) + +Two files required by the dev environment live **only on the server** because +they sit under gitignored paths (Supabase `volumes/` and the secret-bearing +`frps.toml`), matching the production gitignore policy. They are documented here +so the dev setup is fully reproducible from the repo. + +--- + +## 1. Shared on-demand-TLS authorizer (prod `check-subdomain` edge function) + +Path on server (gitignored, prod stack): +`/docker/supabase/volumes/functions/check-subdomain/index.ts` + +The shared Caddy uses a single global `on_demand_tls { ask ... }` endpoint, so +this prod function authorizes dev hostnames too. It was extended **additively** +— the prod allow-list logic is byte-for-byte unchanged. The added block, placed +at the top of the request handler: + +```ts +// Dev environment (served by the same shared Caddy). +const DEV_RESERVED = new Set([ + "app-dev.linumiq.net", + "api-dev.linumiq.net", +]); +const DEV_SUFFIX = ".dev.linumiq.net"; +const DEV_AUTHORIZER = "http://supabase-dev-edge-functions:9000/check-subdomain"; + +// ...inside Deno.serve handler, before the prod reserved/label checks: + +// Dev reserved hosts served by the shared Caddy. +if (DEV_RESERVED.has(domain)) { + return new Response("ok: dev reserved", { status: 200 }); +} + +// Dev tunnel subdomains: delegate to the dev authorizer, which validates the +// label against the dev tunnels table. Fail closed if it is unreachable. +if (domain.endsWith(DEV_SUFFIX)) { + try { + const r = await fetch( + `${DEV_AUTHORIZER}?domain=${encodeURIComponent(domain)}`, + ); + const body = await r.text(); + return new Response(body, { status: r.status }); + } catch (_e) { + return deny("dev authorizer unreachable"); + } +} +``` + +The dev authorizer it delegates to is the **unmodified** prod logic running in +the dev stack (`/docker/dev/supabase/volumes/functions/check-subdomain/index.ts`, +identical to prod's original, with `TUNNEL_BASE_DOMAIN=dev.linumiq.net`). + +To reach the dev authorizer, the prod `functions` container is also attached to +the `dev_edge` network (see `caddy`/supabase compose). After editing, restart: +`cd /docker/supabase && docker compose restart functions`. + +--- + +## 2. Dev frps config (`/docker/dev/frps/frps.toml`) + +Gitignored because it carries the dev frps dashboard password. A sanitized +template is tracked at `frps/frps.toml.example`; copy it and set the password. + +--- + +## 3. Dev secrets + +All dev secrets are generated fresh and independent of prod. They live in the +gitignored `*/.env` / `.env.production` files under `/docker/dev/` and in +`/docker/dev/.dev-secrets.env`. Never commit them. diff --git a/dev/frps/frps.toml.example b/dev/frps/frps.toml.example new file mode 100644 index 0000000..32bbb59 --- /dev/null +++ b/dev/frps/frps.toml.example @@ -0,0 +1,23 @@ +# Dev frps server config — TEMPLATE (no secrets). +# +# The live file is `frps.toml` (gitignored) and is identical to this except +# `webServer.password` holds the real dev frps dashboard password. +# Copy this to frps.toml and fill the password before first start. + +bindPort = 7001 +vhostHTTPPort = 7080 +log.level = "info" +log.maxDays = 7 + +webServer.addr = "0.0.0.0" +webServer.port = 7500 +webServer.user = "admin" +webServer.password = "__SET_DEV_FRPS_DASHBOARD_PASSWORD__" + +# Tunnel auth/lifecycle is delegated to the dev edge function (same design as +# prod): frps calls the auth-webhook on Login / NewProxy / Ping. +[[httpPlugins]] +name = "auth" +addr = "http://supabase-dev-edge-functions:9000" +path = "/auth-webhook" +ops = ["Login", "NewProxy", "Ping"]