Compare commits

...

2 commits

Author SHA1 Message Date
admin_emi
209cce1cb5 Merge pull request 'forgejo: make dev.jinemi.com canonical (ROOT_URL and OAuth callback)' (#151) from feat/forgejo-oauth-jinemi into dev
All checks were successful
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Successful in 6s
Sync infra to hosts / sync-centralis (push) Has been skipped
secrets-guard / encrypted (push) Successful in 4s
shell-lint / shellcheck (push) Successful in 6s
2026-08-01 16:33:16 +00:00
Emi Griffith
a83a0b7d32 forgejo: make dev.jinemi.com canonical (ROOT_URL and OAuth callback)
All checks were successful
PR build (required check) / changes (pull_request) Successful in 6s
PR build (required check) / build-backend (pull_request) Has been skipped
PR build (required check) / build-frontend (pull_request) Has been skipped
secrets-guard / encrypted (pull_request) Successful in 9s
shell-lint / shellcheck (pull_request) Successful in 7s
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 1s
Forgejo derives every absolute URL from its single ROOT_URL, so this is what
moves clone URLs, the Google OAuth callback, webhook payload URLs and mail
links onto the new domain. Both redirect URIs are registered on the Google
OAuth client and were verified against Google before the flip — login is
SSO-only, so a mismatch locks everyone out of the UI including out of undoing
it. README documents the probe so the check is repeatable rather than assumed.

git.thermograph.org stays on the same Caddy site block and stays load-bearing:
the registry host baked into image names, the mesh /etc/hosts pins that make
the /v2/* matcher see a mesh source IP, and the runners' registered instance
URL all still use it. Renaming the registry is a separate migration; the docs
now say so where someone would otherwise retire the name as dead weight.

The stack has no auto-deploy, so this takes effect on a manual
`docker stack deploy` from the manager.
2026-08-01 09:32:12 -07:00
6 changed files with 77 additions and 52 deletions

View file

@ -26,7 +26,7 @@ rather than which environment happens to live there:
| Host | Public IP | Mesh IP | Role |
|------|-----------|---------|------|
| **vps1** | `75.119.132.91` | `10.10.0.2` | "Operational programs": Forgejo (git + CI + registry, `git.thermograph.org`, also served at `dev.jinemi.com` — migration in progress, `.org` still canonical), Grafana/Loki/Alloy (`dashboard.thermograph.org`), the `emigriffith.dev` portfolio, and the **dev** environment (mesh-only, `10.10.0.2:8137`, no public DNS/Caddy/TLS) |
| **vps1** | `75.119.132.91` | `10.10.0.2` | "Operational programs": Forgejo (git + CI + registry; `dev.jinemi.com` canonical, `git.thermograph.org` still served and still used by the registry/mesh pins/runners), Grafana/Loki/Alloy (`dashboard.thermograph.org`), the `emigriffith.dev` portfolio, and the **dev** environment (mesh-only, `10.10.0.2:8137`, no public DNS/Caddy/TLS) |
| **vps2** | `169.58.46.181` | `10.10.0.1` | "The deployed environment": **prod** and **beta**, as two separate Docker Swarm stacks, plus Centralis, Postfix, the backups, and the one shared TimescaleDB instance both stacks use |
| **desktop** | — | `10.10.0.3` | AI-model hosting (voice-to-text, an upcoming-feature LLM) plus flex Swarm-worker capacity. Hosts **no** Thermograph environment — `make dev-up` still works there, but only as a laptop-local rehearsal |

View file

@ -12,9 +12,10 @@ Two VPS boxes plus the operator's desktop, on one WireGuard mesh, named by
ROLE rather than by environment:
- **vps1**`75.119.132.91`, mesh `10.10.0.2`. "Operational programs": Forgejo
(git + CI + registry, `git.thermograph.org`, also served at `dev.jinemi.com`
— one Caddy site block, the `.org` name still canonical; migration in
progress, see `deploy/forgejo/README.md`), Grafana/Loki/Alloy
(git + CI + registry; `dev.jinemi.com` is canonical — Forgejo's `ROOT_URL`
and `git.thermograph.org` is still served off the same Caddy site block and
still load-bearing for the registry, mesh pins and runner URLs, so don't
retire it; see `deploy/forgejo/README.md`), Grafana/Loki/Alloy
(`dashboard.thermograph.org`), the `emigriffith.dev` portfolio site, and the
**dev** environment (`/opt/thermograph-dev`, tracks `dev`), with its own
Postgres container. Dev is mesh-only here — published on `10.10.0.2:8137`,

View file

@ -2,10 +2,11 @@
#
# vps1 serves:
# emigriffith.dev -> static portfolio site (from disk)
# git.thermograph.org -> Forgejo (see deploy/forgejo/caddy-git.conf)
# dev.jinemi.com -> Forgejo, same site block — migration target for
# git.thermograph.org, both served, the .org one
# still canonical (that file's header explains)
# dev.jinemi.com -> Forgejo, CANONICAL (Forgejo's ROOT_URL)
# git.thermograph.org -> Forgejo, same site block — still served and
# still needed: the registry host in image names,
# the mesh /etc/hosts pins and the runners' URLs
# all use it (see deploy/forgejo/caddy-git.conf)
# dashboard.thermograph.org -> Grafana (see observability/caddy-grafana.conf)
#
# and hosts the **dev** environment at dev.thermograph.org (below).

View file

@ -85,50 +85,55 @@ pattern as its other site blocks, same automatic-HTTPS.
## Migrating the domain (git.thermograph.org -> dev.jinemi.com)
In progress. **Both hostnames are served**; `git.thermograph.org` is still the
canonical one.
**Web UI and OAuth: done.** `dev.jinemi.com` is Forgejo's `ROOT_URL`
(`FORGEJO_DOMAIN` in `docker-stack.yml`), so it is the name Forgejo generates in
clone URLs, the Google OAuth callback, webhook payload URLs and mail links.
**Registry: deliberately not done.** Renaming the registry host is a separate
migration from renaming the web UI, and the two need not happen together.
`caddy-git.conf` lists both names on a *single* site block. That is deliberate,
not cosmetic: the `/v2/*` mesh-only matcher is per-block, so a second block for
`dev.jinemi.com` would re-expose the registry API publicly and undo hazard #15.
either name would re-expose the registry API publicly and undo hazard #15.
Whatever else changes, keep the two names in one block.
Serving a second hostname is the cheap half. Forgejo has exactly one
`ROOT_URL`, and it builds every absolute URL from it, so on the non-canonical
host today:
**`git.thermograph.org` must stay served.** It is not a courtesy redirect for
old bookmarks — CI resolves it:
- clone URLs on repo pages read `git.thermograph.org`,
- the OAuth callback and post-login redirect land on `git.thermograph.org`
(login is Google-SSO-only — see `docker-stack.yml`),
- webhook payload URLs and notification mail links use `git.thermograph.org`.
- images are named by registry host, and the `git.thermograph.org/` prefix is
baked into the runner labels (`register-lan-runner.sh`,
`runner-vps2/README.md`), the CI-runner image (`ci-runner/Dockerfile`),
`REGISTRY_HOST` in `infra/.env.example`, and every already-pushed tag;
- every mesh client in "Registry access from mesh clients" below pins
`git.thermograph.org` to `10.10.0.2` in `/etc/hosts`, which is what makes
Caddy's `/v2/*` matcher see a mesh source IP instead of returning 403;
- registered runners hold the instance URL they registered with
(`https://git.thermograph.org`); they keep working while that name resolves.
Browsing, git-over-HTTP and the API all work on `dev.jinemi.com` regardless.
### Before changing ROOT_URL again
Flipping canonical (`FORGEJO_DOMAIN=dev.jinemi.com`, then `docker stack deploy`)
needs these **first**, or it breaks login and CI rather than just renaming
things:
Login is Google-SSO-only, so the Google OAuth client must already carry a
matching redirect URI, or nobody can reach the UI — including to undo the
change. Both of these are registered on client
`337247886376-4360kepi20f24pue62ptg0lnqhcht46l.apps.googleusercontent.com`:
1. **Google OAuth** — add `https://dev.jinemi.com/user/oauth2/<name>/callback`
as an authorized redirect URI in the Google Cloud console *before* the flip.
Not in this repo; nothing here can verify it. Miss it and no one can log in,
including to undo the flip.
2. **The registry host in image names** — images are named by registry host, and
the `git.thermograph.org/` prefix is baked into the runner labels
(`register-lan-runner.sh`, `runner-vps2/README.md`), the CI-runner image
(`ci-runner/Dockerfile`), `REGISTRY_HOST` in `infra/.env.example`, and every
already-pushed tag. Renaming the registry host is a separate migration from
renaming the web UI; the two need not happen together, and doing the web UI
alone is much the smaller change.
3. **Mesh `/etc/hosts` pins** — every node in "Registry access from mesh
clients" below pins `git.thermograph.org` to `10.10.0.2`. A node pulling
under a new hostname needs its own pin, or Caddy's `/v2/*` matcher sees a
public source IP and returns 403.
4. **Runner instance URL** — registered runners hold the instance URL they were
registered with. Existing runners keep working against the old name as long
as it resolves; a *re*-registration must use whichever name is canonical then.
https://dev.jinemi.com/user/oauth2/google/callback
https://git.thermograph.org/user/oauth2/google/callback
Until all four are handled, leaving `ROOT_URL` on `git.thermograph.org` is the
correct state, not an unfinished one.
Verify rather than assume. Google answers this with no credentials: a
registered URI serves the sign-in page, an unregistered one returns
`redirect_uri_mismatch`. Substitute the host you intend to make canonical:
```sh
CID=337247886376-4360kepi20f24pue62ptg0lnqhcht46l.apps.googleusercontent.com
curl -sL "https://accounts.google.com/o/oauth2/v2/auth?client_id=$CID&redirect_uri=https%3A%2F%2F<host>%2Fuser%2Foauth2%2Fgoogle%2Fcallback&response_type=code&scope=openid+email+profile&state=probe" \
| grep -q redirect_uri_mismatch && echo REJECTED || echo ACCEPTED
```
The stack has **no auto-deploy**: a `FORGEJO_DOMAIN` change here only takes
effect when someone re-runs `docker stack deploy` by hand on the manager (vps2)
— see "Deploy / update" above. Until then the running service keeps whatever
`ROOT_URL` it was last deployed with, regardless of what this file says.
## Registry access from mesh clients

View file

@ -14,15 +14,16 @@
# both. Splitting dev.jinemi.com into its own block serves the same Forgejo
# with the registry API open to the public internet.
#
# dev.jinemi.com is the migration target for git.thermograph.org; both are
# served during the transition. Each gets its own Let's Encrypt cert
# automatically (HTTP-01) — both A records already point at vps1.
# dev.jinemi.com is CANONICAL — it is Forgejo's ROOT_URL (docker-stack.yml,
# FORGEJO_DOMAIN), so it is the name Forgejo puts in clone URLs, the OAuth
# callback, webhook payload URLs and mail links.
#
# git.thermograph.org stays CANONICAL until Forgejo's ROOT_URL is flipped
# (docker-stack.yml, FORGEJO_DOMAIN). Until then dev.jinemi.com serves every
# page, but Forgejo builds absolute URLs from ROOT_URL, so clone URLs, the
# OAuth callback and post-login redirects still name git.thermograph.org.
# Flipping it is NOT just this file — see README.md, "Migrating the domain".
# git.thermograph.org is kept as a served alias, NOT as dead weight: the
# registry host baked into image names, the mesh /etc/hosts pins and the
# runners' registered instance URL all still say git.thermograph.org. Removing
# this name from the block breaks CI, not just old bookmarks. Both names get
# their own Let's Encrypt cert automatically (HTTP-01); both A records point
# at vps1. See README.md, "Migrating the domain".
#
# Registry exposure (hazard #15, see docker-stack.yml's header comment):
# /v2/* is the built-in OCI registry API. It's blocked from anywhere except

View file

@ -80,8 +80,25 @@ services:
FORGEJO__database__HOST: db:5432
FORGEJO__database__NAME: forgejo
FORGEJO__database__USER: forgejo
FORGEJO__server__DOMAIN: "${FORGEJO_DOMAIN:-git.thermograph.org}"
FORGEJO__server__ROOT_URL: "https://${FORGEJO_DOMAIN:-git.thermograph.org}/"
# Canonical domain. Forgejo has exactly ONE ROOT_URL and derives every
# absolute URL from it — clone URLs, the Google OAuth callback, webhook
# payload URLs, mail links. git.thermograph.org is still served (Caddy
# answers for both names off one site block, deploy/forgejo/caddy-git.conf)
# and still works for browsing, git-over-HTTP and the API; it is simply no
# longer the name Forgejo generates.
#
# Changing this breaks login unless the matching redirect URI exists on
# the Google OAuth client FIRST — login is SSO-only, so a mismatch locks
# everyone out of the UI, including out of the ability to undo it. Both
# https://dev.jinemi.com/user/oauth2/google/callback and the
# git.thermograph.org one are registered; verify with a probe before
# changing this again (deploy/forgejo/README.md, "Migrating the domain").
#
# NOT changed by this: the registry host baked into image names, the mesh
# /etc/hosts pins, and the runners' registered instance URL all still say
# git.thermograph.org. That is a separate migration.
FORGEJO__server__DOMAIN: "${FORGEJO_DOMAIN:-dev.jinemi.com}"
FORGEJO__server__ROOT_URL: "https://${FORGEJO_DOMAIN:-dev.jinemi.com}/"
FORGEJO__server__SSH_PORT: "2222"
# Without this, Forgejo starts no SSH server at all despite SSH_PORT
# being set — git@ clones get "connection refused", not a slow failure.