Compare commits

..

2 commits

Author SHA1 Message Date
83b074fa9e Merge pull request 'docs: correct file references and the dev reachability claim' (#156) from fix/doc-accuracy into dev
All checks were successful
Validate observability stack / validate (push) Successful in 12s
Sync infra to hosts / sync-beta (push) Has been skipped
Build + push images (Forgejo registry) / build-push (frontend) (push) Successful in 8s
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-centralis (push) Has been skipped
Deploy / deploy (frontend) (push) Successful in 8s
Sync infra to hosts / sync-dev (push) Successful in 8s
secrets-guard / encrypted (push) Successful in 5s
Build + push images (Forgejo registry) / build-push (backend) (push) Successful in 29s
shell-lint / shellcheck (push) Successful in 19s
Deploy / deploy (backend) (push) Successful in 50s
2026-08-01 18:51:08 +00:00
Emi Griffith
af21d8e477 docs: correct file references and the dev reachability claim
All checks were successful
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (pull_request) Successful in 10s
PR build (required check) / changes (pull_request) Successful in 16s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Successful in 20s
PR build (required check) / build-backend (pull_request) Successful in 45s
PR build (required check) / gate (pull_request) Successful in 5s
Audited the five CLAUDE.md files and all twenty-one README.md files against the
tree, machine-checking every in-repo path they name and verifying the testable
claims against the live hosts.

The one that matters is in the root file: dev was documented as reachable on
the mesh at 10.10.0.2:8137. It is not, and never was from anywhere but vps1 —
infra/docker-compose.yml binds the port to 127.0.0.1, and the address answers
from neither vps2 nor vps1 itself. Anyone following it gets a connection
refused with nothing to explain it.

The rest are stale paths, several from the reunification:

  * assetlinks.json moved under frontend/static/ in the subtree merge; the TWA
    README kept the pre-merge path in both places it names it. Following it
    would put the file where nothing serves it and Android app-link
    verification would fail silently.
  * push.py and notify.py now live in backend/notifications/.
  * INFRA.md and deploy/stack/README have never existed in this repo, in any
    branch.
  * the Caddyfile is at deploy/stack/lb/Caddyfile.
  * three bare relative paths that resolve for a reader but not from the
    directory the file sits in: units.js is the frontend's, deploy.sh is
    infra's, entrypoint.sh is the backend's.

Also records why mesh clients must pin the ROOT_URL host and not only the image
host: the registry's bearer-token realm follows ROOT_URL, so pinning
git.thermograph.org alone still sends the token request out the public route,
where the /v2/* matcher returns 403 and docker falls back to anonymous. That
surfaces as `unauthorized: reqPackageAccess`, indistinguishable from a bad
credential.

Verified true and left alone: the four-domain layout, both .claude runbooks,
the absence of any domain-level .forgejo directory, the pinned compose project
name, the deploy contract, prod's eight stack services, beta's five prefixed
ones with no db of its own, dev's five, and every documented make target.
2026-08-01 11:49:28 -07:00
8 changed files with 38 additions and 12 deletions

View file

@ -29,8 +29,10 @@ concurrently. Read both before dispatching parallel work.
**`dev` is a first-class hosted environment, not just an integration branch.**
It runs on vps1 — the same box as Forgejo and the monitoring stack — with its
own Postgres container, reachable only on the WireGuard mesh
(`10.10.0.2:8137`): no public DNS record, no Caddy site, no TLS. It is deployed
own Postgres container, reachable only from vps1 itself (`127.0.0.1:8137` —
`infra/docker-compose.yml` binds the port to loopback, so the mesh does not
reach it either; use an SSH tunnel): no public DNS record, no Caddy site, no
TLS. It is deployed
by CI like beta and prod. The desktop hosts no Thermograph environment at all;
`make dev-up` there is a laptop convenience for running the stack locally, not
a deployment target.

View file

@ -45,7 +45,7 @@ for systemd/CI callers. Entry target is `app:app`.
that disagree with the labels the API returns.
- **Fahrenheit country set**`api/content_payloads.py`'s `F_COUNTRIES` is
canonical; `frontend`'s `server/internal/format::FCountries` and
`static/units.js`'s `F_REGIONS` must stay identical to it. Both are asserted
`frontend/static/units.js`'s `F_REGIONS` must stay identical to it. Both are asserted
by tests in `frontend/server/internal/format/format_test.go` — but note the
backend cross-check **skips in CI** (the frontend image's build context is
`frontend/`, so this file is unreachable from the builder stage, which is the

View file

@ -69,7 +69,7 @@ cities_flavor.json bundled reference data (generated by gen_cities.py /
only builds and publishes its own container image
(`git.thermograph.org/admin_emi/thermograph-backend/app`) and hands infra a tag to
roll out (`SERVICE=backend` + `BACKEND_IMAGE_TAG` into infra's
`deploy/deploy.sh`).
`infra/deploy/deploy.sh`).
- **`thermograph-docs`** holds the cross-repo architecture/runbook docs; this
README only covers what's local to this service.

View file

@ -16,7 +16,7 @@ The `db` service runs the stock **`timescale/timescaledb:latest-pg18`** image
TTL, and the `recent_stamp` token embedded in derived-payload validity.
The schema is created by Alembic (`backend/alembic/versions/0002_climate_hypertables.py`,
run at app boot via `deploy/entrypoint.sh`). The app reads/writes it through
run at app boot via `backend/deploy/entrypoint.sh`). The app reads/writes it through
`backend/data/climate_store.py` (psycopg + polars). See
`deploy/POSTGRES-MIGRATION.md` for the parquet→hypertable cutover.

View file

@ -3,7 +3,7 @@
Runs as `deploy/forgejo/docker-stack.yml` — the only Swarm-scheduled workload
this cluster carries (prod and beta's app stacks are separate `docker stack
deploy`s that happen to run on the manager node, vps2 — see
`deploy/stack/README` context in `DEPLOY.md`). Pinned to the **vps1** node
`DEPLOY.md`). Pinned to the **vps1** node
(`75.119.132.91`) via the `role=forge` label from
`deploy/swarm/label-forge-node.sh`.
@ -153,6 +153,28 @@ echo "10.10.0.2 git.thermograph.org" | sudo tee -a /etc/hosts
numbering — adjust if you assigned it differently.) The git/web UI keeps
working normally for everyone else since only `/v2/*` is restricted.
**Pin the canonical name too, not just the image host.** Forgejo derives the
registry's bearer-token realm from `ROOT_URL`, so a client that already
resolves `git.thermograph.org` over the mesh is still sent to
`https://<ROOT_URL host>/v2/token` to collect a token. When `ROOT_URL` became
`dev.jinemi.com`, that second request took the public route, the `/v2/*`
matcher above answered 403, and docker fell back to anonymous — every push then
failed with `unauthorized: reqPackageAccess`, which reads exactly like a
revoked token or a missing scope. It is neither. Pin both names:
```
echo "10.10.0.2 git.thermograph.org dev.jinemi.com" | sudo tee -a /etc/hosts
```
The realm moves whenever `ROOT_URL` does, so read it rather than assuming:
```sh
curl -sI https://git.thermograph.org/v2/ | grep -i www-authenticate
```
These `/etc/hosts` pins are host state. Nothing in this repo writes them, so a
reprovisioned node has to be pinned again before it can pull.
## Register the Actions runner
Once Forgejo answers at its domain:

View file

@ -14,7 +14,7 @@ submission ($25 one-time) is deferred.
## What's in the repo
- `twa-manifest.json` — Bubblewrap config (reference values; see comment inside).
- `../../frontend/.well-known/assetlinks.json` — Digital Asset Links, served at
- `../../../frontend/static/.well-known/assetlinks.json` — Digital Asset Links, served at
`https://thermograph.org/.well-known/assetlinks.json`. Its fingerprint is a
**placeholder** until you generate the signing key (step 4).
@ -45,7 +45,7 @@ bubblewrap build
bubblewrap fingerprint list # or: keytool -list -v -keystore android-keystore.jks
```
Copy the `SHA256` value into `frontend/.well-known/assetlinks.json`, replacing
Copy the `SHA256` value into `frontend/static/.well-known/assetlinks.json`, replacing
`REPLACE_WITH_TWA_SIGNING_KEY_SHA256_FINGERPRINT`, then **deploy the site** so the
new file is live. Confirm:
@ -80,6 +80,7 @@ install for every user. Store it out of the repo, backed up.
`.aab`; the store review for a well-formed TWA is light. Not part of this pass.
## Why no backend changes
Android delegates the TWA's web notifications to the OS, so `backend/push.py`
(VAPID) and `backend/notify.py` deliver push to the app unchanged. Nothing in
Android delegates the TWA's web notifications to the OS, so
`backend/notifications/push.py` (VAPID) and `backend/notifications/notify.py`
deliver push to the app unchanged. Nothing in
`backend/` is touched by this track.

View file

@ -183,7 +183,8 @@ Terraform brings the stack up — don't let Terraform recreate containers mid-mi
`git.thermograph.org` reaches Forgejo's loopback port on vps1.
`COOKIE_SECURE` is auto-set to `0` when there's no domain (a Secure
cookie is never sent over plain HTTP) and `1` behind Caddy TLS.
- The rendered Caddyfile only reverse-proxies the app. The repo's `deploy/Caddyfile`
- The rendered Caddyfile only reverse-proxies the app. The repo's
`deploy/stack/lb/Caddyfile`
additionally serves the `emigriffith.dev` portfolio and legacy redirects; those are
host-specific and not templated here.
- Provisioner-based by design: the hosts already exist, so this is not a

View file

@ -147,7 +147,7 @@ LOKI_URL=http://10.10.0.2:3100/loki/api/v1/push \
docker compose -f docker-compose.agent.yml -f docker-compose.agent.beta.yml up -d
```
The mesh must already be up (see the main repo's `deploy/swarm/` / `INFRA.md`);
The mesh must already be up (see `infra/deploy/swarm/`);
Swarm is **not** required for the agent itself — it talks plain HTTP over wg0.
<!-- TODO(cutover): desktop no longer runs a Thermograph app stack and ships no