infra/forgejo: give the LAN runner's PATH ~/.local/bin #84

Merged
admin_emi merged 19 commits from fix/lan-runner-path into dev 2026-07-25 06:59:23 +00:00
Owner

The thermograph-lan runner's systemd --user unit had no Environment=PATH=..., so it inherited systemd's bare default PATH, missing ~/.local/bin where sops/age live. Once this box was flipped into SOPS-configured mode (adding /etc/thermograph/secrets-env, needed to fix the daemon's crash-loop on a missing THERMOGRAPH_AUTH_SECRET), every LAN deploy failed outright at the render step: sops not installed but this host is configured for SOPS secrets. Also explains the earlier, less legible git clone: some refs were not updated / Health check failed for daemon (status=none) noise in PR #83's LAN-deploy check -- same PATH-starved runner environment.

Applied live to ~/.config/systemd/user/forgejo-runner.service already and confirmed the LAN deploy succeeds and thermograph-dev-daemon-1 is healthy. This PR is the fix landing in the provisioning script (register-lan-runner.sh) so a future re-provision doesn't regress it.

No tests -- shell provisioning script, verified live on the one box it provisions.

The `thermograph-lan` runner's systemd --user unit had no `Environment=PATH=...`, so it inherited systemd's bare default PATH, missing `~/.local/bin` where `sops`/`age` live. Once this box was flipped into SOPS-configured mode (adding `/etc/thermograph/secrets-env`, needed to fix the daemon's crash-loop on a missing `THERMOGRAPH_AUTH_SECRET`), every LAN deploy failed outright at the render step: `sops not installed but this host is configured for SOPS secrets`. Also explains the earlier, less legible `git clone: some refs were not updated` / `Health check failed for daemon (status=none)` noise in PR #83's LAN-deploy check -- same PATH-starved runner environment. Applied live to `~/.config/systemd/user/forgejo-runner.service` already and confirmed the LAN deploy succeeds and `thermograph-dev-daemon-1` is healthy. This PR is the fix landing in the provisioning script (`register-lan-runner.sh`) so a future re-provision doesn't regress it. No tests -- shell provisioning script, verified live on the one box it provisions.
admin_emi added 19 commits 2026-07-25 06:55:09 +00:00
Merge pull request 'Promote dev to main (deploy beta): ERA5 lake stack' (#26) from dev into main
All checks were successful
Build + push backend image (Forgejo registry) / build-push (push) Successful in 28s
Sync infra to hosts / sync-beta (push) Successful in 9s
Sync infra to hosts / sync-prod (push) Successful in 7s
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 10s
Deploy backend to beta VPS / deploy (push) Successful in 50s
755886e244
frontend: rewrite the SSR content service in Go (#28)
All checks were successful
Sync infra to hosts / sync-beta (push) Successful in 13s
Sync infra to hosts / sync-prod (push) Successful in 12s
secrets-guard / encrypted (push) Successful in 7s
shell-lint / shellcheck (push) Successful in 8s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 53s
Deploy frontend to beta VPS / deploy (push) Successful in 1m16s
secrets-guard / encrypted (pull_request) Successful in 5s
shell-lint / shellcheck (pull_request) Successful in 6s
92e74c585a
Ports frontend/ (Jinja2/FastAPI, ~1180 LOC) to Go with html/template. No
climate math, no DB, no auth -- every route fetches from the backend's
/content/* API.

Verified with a golden-HTML diff, not just unit tests: both the Python
original and the Go rewrite were run against the same committed fixtures
and every route compared byte-for-byte, confirmed programmatically. That
process caught defects unit tests alone missed, since map[string]any has
no compile-time field check:

- Render-context keys were snake_case throughout while the templates read
  PascalCase fields. A missing map key doesn't error, it silently renders
  empty -- title, meta description, canonical URL, OpenGraph tags, and the
  homepage's entire ranked list were blank on every page despite every
  route returning 200. Fixed by renaming every key to match each
  template's own documented field contract, and passing API structs
  straight through wherever their fields already matched (removes a whole
  layer of future drift risk).
- Three pages 500'd: ToolHref needed a composed href, not a bare
  "lat,lon" fragment; the records table needed the raw API struct.
- JSON-LD was double-encoded: <script type="application/ld+json"> is
  JAVASCRIPT context to html/template's escaper regardless of the
  script's type attribute, so template.HTML gets re-escaped as a quoted
  JS string. Needed template.JS. The glossary term page's JSON-LD was
  never built at all -- added.
- html/template silently strips literal HTML and JS comments from parsed
  output (verified in isolation) -- both need a FuncMap function
  returning template.HTML/template.JS to survive.

Packaging: 187MB -> 22.6MB. Two defects caught before reaching a host: the
Swarm stack's entrypoint override with no explicit command drops the
image's CMD entirely (every deploy would have exited 127), and
COPY --chown by name fails under the classic Docker builder on Alpine.
Both fixed.

go build/vet/test -race clean; docker build passes its embedded test step
under both BuildKit and the classic builder; shellcheck 0 findings.
Merge pull request 'Promote dev to main: lake extension bake + daemon healthcheck fixes' (#31) from dev into main
All checks were successful
Deploy backend to beta VPS / deploy (push) Successful in 42s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 44s
Sync infra to hosts / sync-beta (push) Successful in 5s
Sync infra to hosts / sync-prod (push) Successful in 6s
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 8s
secrets-guard / encrypted (pull_request) Successful in 9s
shell-lint / shellcheck (pull_request) Successful in 9s
b83bfdbd67
deploy.sh: fix the daemon-binary probe, which always dropped daemon (#33)
All checks were successful
Sync infra to hosts / sync-beta (push) Successful in 5s
Sync infra to hosts / sync-prod (push) Successful in 4s
secrets-guard / encrypted (push) Successful in 5s
shell-lint / shellcheck (push) Successful in 7s
979653f407
docker compose config --images daemon does not filter to the named service
on this host's Compose v5.3.1 -- it prints every service's image, one per
line, in file order, so `| head -1` was silently grabbing db's image
(timescaledb) instead of daemon's. The probe then always found no
/usr/local/bin/thermograph-daemon in a Postgres image and dropped daemon
from every backend deploy, regardless of what the real backend image
contained.

Fixed by building the image reference directly from the same vars
docker-compose.yml's daemon.image: already interpolates, instead of going
through docker compose config at all.

Reproduced against beta directly: the old sequence selected the wrong
image; the new construction resolves correctly and the binary probe
passes.
frontend: fetch City() concurrently with the page's primary API call (#37)
All checks were successful
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 14s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 42s
Deploy frontend to beta VPS / deploy (push) Successful in 1m5s
da82abda27
MonthPage and RecordsPage each made two backend calls sequentially
(CityMonth/CityRecords, then City) where the second never depended on the
first's result. fetchWithCity launches both concurrently via goroutines
and a WaitGroup.

Verified live against a stub with an injected 400ms delay on both
endpoints: city page (1 call) and month/records pages (2 calls each) all
cost ~0.404s now, not double for the two-call pages.

Error priority preserved exactly: primary's error wins even when City
also fails, matching the old sequential code. One trade-off: City() is
now always launched even on a request about to 404 from primary, costing
one extra cheap lookup on that rare path.

Tests include a deterministic concurrency proof via rendezvous channels
(the old sequential code would deadlock this test, not just run it
slower). Full suite green under -race -count=2.
infra/forgejo: add resource limits and a leaner CI job image (#38)
Some checks failed
Sync infra to hosts / sync-beta (push) Successful in 7s
Sync infra to hosts / sync-prod (push) Failing after 8s
secrets-guard / encrypted (push) Successful in 7s
shell-lint / shellcheck (push) Successful in 8s
dcf15ea572
Merge pull request 'Promote dev to main: daemon roll + Iceberg-backed lake query' (#41) from dev into main
All checks were successful
Sync infra to hosts / sync-beta (push) Successful in 6s
Sync infra to hosts / sync-prod (push) Successful in 11s
secrets-guard / encrypted (push) Successful in 9s
Validate observability stack / validate (push) Successful in 14s
shell-lint / shellcheck (push) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 9s
shell-lint / shellcheck (pull_request) Successful in 8s
88f5d0cce0
infra/forgejo: raise LAN runner concurrency to 8 (#43)
All checks were successful
Sync infra to hosts / sync-beta (push) Successful in 8s
Sync infra to hosts / sync-prod (push) Successful in 10s
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 8s
81c579ddcb
Merge pull request 'Promote dev to main: Iceberg-backed lake query' (#45) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 8s
secrets-guard / encrypted (pull_request) Successful in 9s
shell-lint / shellcheck (pull_request) Successful in 10s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 41s
Deploy backend to beta VPS / deploy (push) Successful in 47s
6f5682b7d7
Merge pull request 'Promote dev to main: geocode P0 fix + ops tooling + privacy logging' (#52) from dev into main
All checks were successful
Sync infra to hosts / sync-beta (push) Successful in 8s
secrets-guard / encrypted (push) Successful in 12s
shell-lint / shellcheck (push) Successful in 10s
Sync infra to hosts / sync-prod (push) Successful in 16s
Deploy backend to beta VPS / deploy (push) Successful in 38s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 52s
secrets-guard / encrypted (pull_request) Successful in 9s
shell-lint / shellcheck (pull_request) Successful in 10s
0935eea394
Merge pull request 'Promote dev to main: off-box encrypted backups' (#57) from dev into main
All checks were successful
Sync infra to hosts / sync-beta (push) Successful in 6s
secrets-guard / encrypted (push) Successful in 5s
Sync infra to hosts / sync-prod (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 7s
60d5af41dd
Merge pull request 'Promote dev to main: first alerting + Postfix supervision' (#60) from dev into main
Some checks failed
Sync infra to hosts / sync-prod (push) Failing after 7s
Sync infra to hosts / sync-beta (push) Successful in 13s
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 13s
Validate observability stack / validate (push) Successful in 17s
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 6s
633c6a5ace
Merge pull request 'Promote dev to main: MET forecast coverage gate' (#64) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 9s
shell-lint / shellcheck (push) Successful in 16s
secrets-guard / encrypted (pull_request) Successful in 11s
shell-lint / shellcheck (pull_request) Successful in 13s
Deploy backend to beta VPS / deploy (push) Successful in 51s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m22s
150029075e
Promote dev → main (frontend QA batch → beta) (#71)
Some checks failed
Deploy frontend to beta VPS / deploy (push) Failing after 8s
Sync infra to hosts / sync-beta (push) Successful in 7s
Deploy backend to beta VPS / deploy (push) Failing after 13s
secrets-guard / encrypted (push) Successful in 12s
Sync infra to hosts / sync-prod (push) Successful in 14s
shell-lint / shellcheck (push) Successful in 12s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 57s
Build + push frontend image (Forgejo registry) / build-push (push) Successful in 1m2s
ca84e0ce95
Merge pull request 'Promote dev to main: NASA retirement' (#73) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 12s
shell-lint / shellcheck (push) Successful in 11s
secrets-guard / encrypted (pull_request) Successful in 12s
shell-lint / shellcheck (pull_request) Successful in 10s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 1m54s
Deploy backend to beta VPS / deploy (push) Successful in 2m24s
f596fa87d8
Merge pull request 'Promote dev to main: vault consolidation + renderer fail-closed' (#76) from dev into main
All checks were successful
Sync infra to hosts / sync-beta (push) Successful in 7s
Sync infra to hosts / sync-prod (push) Successful in 6s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 7s
9350045866
Merge pull request 'Promote dev to main: dev + Centralis vaults' (#78) from dev into main
All checks were successful
Sync infra to hosts / sync-prod (push) Successful in 5s
secrets-guard / encrypted (push) Successful in 7s
shell-lint / shellcheck (push) Successful in 9s
Sync infra to hosts / sync-beta (push) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 6s
shell-lint / shellcheck (pull_request) Successful in 8s
6c6842ab9e
Merge pull request 'promote: dev → main' (#83) from dev into main
All checks were successful
secrets-guard / encrypted (push) Successful in 8s
shell-lint / shellcheck (push) Successful in 7s
Build + push backend image (Forgejo registry) / build-push (push) Successful in 44s
Deploy backend to beta VPS / deploy (push) Successful in 48s
be88987d95
infra/forgejo: give the LAN runner's PATH ~/.local/bin
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
PR build (required check) / build-backend (pull_request) Has been skipped
secrets-guard / encrypted (pull_request) Successful in 8s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 10s
PR build (required check) / gate (pull_request) Successful in 3s
c41feaa2f6
sops (and other user-installed CLI tools) live in ~/.local/bin, which
isn't on systemd --user's default PATH. Once a host is flipped into
SOPS-configured mode (the /etc/thermograph/secrets-env marker),
render-secrets.sh's own render step fails outright ("sops not
installed but this host is configured for SOPS secrets") even though
it's installed for the interactive user -- the runner just can't see
it. Found via the LAN dev daemon crash-looping on a missing
THERMOGRAPH_AUTH_SECRET after dev.yaml correctly gained one.
admin_emi merged commit 2c7ceefab4 into dev 2026-07-25 06:59:23 +00:00
admin_emi deleted branch fix/lan-runner-path 2026-07-25 06:59:23 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Jinemi/thermograph#84
No description provided.