# 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.
