disinto/docs/edge-routing-fallback.md
Claude d367c9d258
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/smoke-init Pipeline was successful
fix: vision(#623): per-project subdomain fallback path (contingency) (#713)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 03:27:05 +00:00

6.5 KiB

Edge Routing Fallback: Per-Project Subdomains

Status: Contingency plan. Only implement if subpath routing (#704 / #708) proves unworkable.

Context

The primary approach routes services under subpaths of <project>.disinto.ai:

Service Primary (subpath)
Forgejo <project>.disinto.ai/forge/
Woodpecker <project>.disinto.ai/ci/
Chat <project>.disinto.ai/chat/
Staging <project>.disinto.ai/staging/

The fallback uses per-service subdomains instead:

Service Fallback (subdomain)
Forgejo forge.<project>.disinto.ai/
Woodpecker ci.<project>.disinto.ai/
Chat chat.<project>.disinto.ai/
Staging <project>.disinto.ai/ (root)

The wildcard cert from #621 already covers *.<project>.disinto.ai — no new DNS records or certs are needed for sub-subdomains because *.disinto.ai matches one level deep. For sub-subdomains like forge.<project>.disinto.ai we would need to add a second wildcard (*.*.disinto.ai) or explicit DNS records per project. Both are straightforward with the existing Gandi DNS-01 setup.

Pivot Decision Criteria

Pivot if:

  • Forgejo ROOT_URL under a subpath (/forge/) causes redirect loops that cannot be fixed with X-Forwarded-Prefix or Caddy uri strip_prefix.
  • Woodpecker's WOODPECKER_HOST does not honour subpath prefixes, causing OAuth callback mismatches that persist after adjusting redirect URIs.
  • Forward-auth on /chat/* conflicts with Forgejo's own OAuth flow when both share the same origin (cookie collision, CSRF token mismatch).

Do NOT pivot if:

  • Forgejo login redirects to / instead of /forge/ — fixable with Caddy handle_path + uri prefix rewrite.
  • Woodpecker UI assets 404 under /ci/ — fixable with asset prefix config (WOODPECKER_ROOT_PATH).
  • A single OAuth app needs a second redirect URI — Forgejo supports multiple redirect_uris in the same app.

Fallback Topology

Caddyfile

Replace the single :80 block with four host blocks:

# Main project domain — staging / landing
<project>.disinto.ai {
    reverse_proxy staging:80
}

# Forgejo — root path, no subpath rewrite needed
forge.<project>.disinto.ai {
    reverse_proxy forgejo:3000
}

# Woodpecker CI — root path
ci.<project>.disinto.ai {
    reverse_proxy woodpecker:8000
}

# Chat — with forward_auth (same as #709, but on its own host)
chat.<project>.disinto.ai {
    handle /login {
        reverse_proxy chat:8080
    }
    handle /oauth/callback {
        reverse_proxy chat:8080
    }
    handle /* {
        forward_auth chat:8080 {
            uri /auth/verify
            copy_headers X-Forwarded-User
            header_up X-Forward-Auth-Secret {$FORWARD_AUTH_SECRET}
        }
        reverse_proxy chat:8080
    }
}

Current file: docker/Caddyfile (generated by lib/generators.sh:_generate_caddyfile_impl, line ~596).

Service Configuration Changes

Variable / Setting Current (subpath) Fallback (subdomain) File
Forgejo ROOT_URL https://<project>.disinto.ai/forge/ https://forge.<project>.disinto.ai/ forgejo app.ini
WOODPECKER_HOST http://localhost:8000 (subpath via proxy) https://ci.<project>.disinto.ai lib/ci-setup.sh line ~164
Woodpecker OAuth redirect https://<project>.disinto.ai/ci/authorize https://ci.<project>.disinto.ai/authorize lib/ci-setup.sh line ~153
Chat OAuth redirect https://<project>.disinto.ai/chat/oauth/callback https://chat.<project>.disinto.ai/oauth/callback lib/ci-setup.sh line ~188
EDGE_TUNNEL_FQDN <project>.disinto.ai unchanged (main domain) lib/generators.sh line ~432

New Environment Variables (pivot only)

These would be added to lib/generators.sh _generate_compose_impl() in the edge service environment block (currently line ~415):

Variable Value
EDGE_TUNNEL_FQDN_FORGE forge.<project>.disinto.ai
EDGE_TUNNEL_FQDN_CI ci.<project>.disinto.ai
EDGE_TUNNEL_FQDN_CHAT chat.<project>.disinto.ai

DNS

No new records needed if the registrar supports *.*.disinto.ai wildcards. Otherwise, add explicit A/CNAME records per project:

forge.<project>.disinto.ai  → edge server IP
ci.<project>.disinto.ai     → edge server IP
chat.<project>.disinto.ai   → edge server IP

The edge server already handles TLS via Caddy's automatic HTTPS with the existing ACME / DNS-01 challenge.

Edge Control (tools/edge-control/register.sh)

Currently do_register() creates a single route for <project>.disinto.ai. The fallback would need to register four routes (or accept a --subdomain parameter). See the TODO in register.sh.

Files to Change on Pivot

File What changes
docker/Caddyfile Replace single host block → four host blocks (see above)
lib/generators.sh Add EDGE_TUNNEL_FQDN_{FORGE,CI,CHAT} env vars to compose
lib/ci-setup.sh ~line 153 Woodpecker OAuth redirect URI → ci.<project> subdomain
lib/ci-setup.sh ~line 188 Chat OAuth redirect URI → chat.<project> subdomain
tools/edge-control/register.sh Register four routes per project instead of one
tools/edge-control/lib/caddy.sh add_route() gains subdomain support
forgejo app.ini ROOT_URLhttps://forge.<project>.disinto.ai/

Estimated effort for a full pivot: under one day given this plan.