Merge pull request 'fix: vision(#623): per-project subdomain fallback path (contingency) (#713)' (#732) from fix/issue-713 into main
All checks were successful
ci/woodpecker/push/ci Pipeline was successful

This commit is contained in:
dev-bot 2026-04-12 03:38:24 +00:00
commit eaf0f724fa
3 changed files with 157 additions and 0 deletions

View file

@ -0,0 +1,149 @@
# 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:
```caddy
# 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_URL``https://forge.<project>.disinto.ai/` |
Estimated effort for a full pivot: **under one day** given this plan.

View file

@ -430,6 +430,9 @@ services:
- EDGE_TUNNEL_USER=${EDGE_TUNNEL_USER:-tunnel}
- EDGE_TUNNEL_PORT=${EDGE_TUNNEL_PORT:-}
- EDGE_TUNNEL_FQDN=${EDGE_TUNNEL_FQDN:-}
# Subdomain fallback (#713): if subpath routing (#704/#708) fails, add:
# EDGE_TUNNEL_FQDN_FORGE, EDGE_TUNNEL_FQDN_CI, EDGE_TUNNEL_FQDN_CHAT
# See docs/edge-routing-fallback.md for the full pivot plan.
# Shared secret for Caddy ↔ chat forward_auth (#709)
- FORWARD_AUTH_SECRET=${FORWARD_AUTH_SECRET:-}
volumes:

View file

@ -39,6 +39,11 @@ EOF
exit 1
}
# TODO(#713): Subdomain fallback — if subpath routing (#704/#708) fails, this
# function would need to register additional routes for forge.<project>,
# ci.<project>, chat.<project> subdomains (or accept a --subdomain parameter).
# See docs/edge-routing-fallback.md for the full pivot plan.
# Register a new tunnel
# Usage: do_register <project> <pubkey>
do_register() {