150 lines
6.5 KiB
Markdown
150 lines
6.5 KiB
Markdown
|
|
# 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.
|