Commit Graph

7 Commits

Author SHA1 Message Date
3260bfb35b fix(ci): share one nginx-vhost renderer; drop unused deploy.sh
The nightly refresh.yml and deploy.yml each substituted asset/nginx/
site.conf.tmpl with their own inline python. When bb2f5b1 templated the
listen line as {{WEB_LISTEN}} (moving the vhost behind oolon's stream SNI
router), it added the substitution to the template and deploy.yml but not
to refresh.yml. The daily refresh then rsynced a literal
`listen {{WEB_LISTEN}};` into /etc/nginx/conf.d/rob.tn.conf, `nginx -t`
failed for the whole edge, and — because the file is written into the live
conf.d before it is tested — every vhost's reload (including the step@
cert renewals) stayed frozen. Internal vhosts, cichlid.internal among
them, served certs that had expired days earlier while the renewed certs
sat unused on disk.

- Replace both inline renderers with script/render-site-conf.py, shared by
  deploy.yml and refresh.yml so they cannot drift on what they substitute.
- Guard rails: the renderer fails if any {{PLACEHOLDER}} lacks an env value
  or survives substitution, so a forgotten/misnamed variable is a red build
  on the runner instead of a broken vhost on the edge.
- Add the missing WEB_LISTEN to refresh.yml's env (the immediate drift).
- Rename the template's {{DOCROOT}} to {{WEB_ROOT}} so every placeholder
  maps to the env var of the same name.
- Remove script/deploy.sh: the third, unused renderer of the same template
  (superseded by the Actions workflows) and a standing source of drift.
- Docs (readme, CLAUDE.md) updated to the Actions-only deploy path.

Known follow-up (needs a sudoers change + infra-setup re-run on oolon, so
out of scope here): the rendered vhost is still rsynced straight into the
live conf.d and only then `nginx -t`'d, so a valid-but-wrong config could
still wedge nginx. Stage-validate-swap with rollback would close that.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsH1rcWQYtRVhvaftiKm22
2026-07-26 15:52:18 +03:00
bb2f5b1f9b fix(web): stop binding 443 behind the edge SNI router; verify the reload
Some checks failed
deploy / Build prerendered web (push) Successful in 7m14s
deploy / Deploy web to oolon (push) Successful in 18s
deploy / Build api + worker (static musl) (push) Successful in 5m41s
deploy / Deploy moments-worker to frootmig (push) Successful in 19s
deploy / Deploy moments-api to nikola (push) Successful in 24s
refresh / Rebuild prerendered web (push) Successful in 7m15s
refresh / Deploy refreshed web to oolon (push) Failing after 26s
oolon's TCP 443 belongs to the stream SNI router, which ssl_prereads the
handshake and forwards to the local https tier on 127.0.0.1:14443 with
PROXY protocol. site.conf.tmpl predates that and still bound 443 itself,
so every deploy and every daily refresh rsynced a vhost that collides
with the router.

Nothing in the pipeline caught it. `nginx -t` only detects duplicate
listeners within a context, not across http{} and stream{}, and
`systemctl reload` merely sends SIGHUP, so it exits 0 while nginx logs
"bind() to 0.0.0.0:443 failed (98: Address already in use) ... still
could not bind()", aborts the reconfiguration and keeps its old cycle.
The deploy went green while oolon's running config was frozen. It stayed
frozen for a day, stranding every cert the step@ timers renewed on disk
until eleven internal vhosts were serving expired certs. A cold start
would have failed outright, taking the whole public edge down.

Template the listen line from WEB_LISTEN (manifest web.config.listen for
script/deploy.sh, which renders the same template), and assert that the
reload landed by requiring a fresh worker generation, dumping the nginx
error log when it did not.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182wzZE8DguMPWhxD21gfP2
2026-07-21 11:42:39 +03:00
5e4cf21e2d ci: prerender against the internal mesh API + fail loud if empty
All checks were successful
deploy / Build api + worker (static musl) (push) Successful in 6m11s
deploy / Build prerendered web (push) Successful in 7m28s
deploy / Deploy web to oolon (push) Successful in 55s
deploy / Deploy moments-api to nikola (push) Successful in 59s
deploy / Deploy moments-worker to frootmig (push) Successful in 1m1s
The fedora-44 runner reaches the public internet (the CV gist on
api.github.com prerendered fine) but not the public rob.tn — split-horizon
DNS — so every moments-API-sourced route baked empty loading states while the
build still "succeeded". prefetchQuery swallows fetch errors, so the empty
snapshot shipped silently.

- Point VITE_API_BASE at the internal API the deploy already reaches
  (http://nikola.kosherinata.internal:42424/v1), verified reachable over the
  mesh. Only affects the SSR/prerender build; the browser bundle still uses the
  relative /api/v1.
- run-prerender.mjs now aborts (exit 1) if zero dynamic routes are enumerated,
  i.e. the API was unreachable — so an empty snapshot can never deploy again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7zF7Kf4JqDwa6M8Qgge9M
2026-06-25 14:14:11 +03:00
ba96cddebc ci: install web deps with --ignore-scripts + explicit rebuild
All checks were successful
deploy / Build prerendered web (push) Successful in 2m53s
deploy / Deploy web to oolon (push) Successful in 58s
deploy / Build api + worker (static musl) (push) Successful in 6m10s
deploy / Deploy moments-api to nikola (push) Successful in 55s
deploy / Deploy moments-worker to frootmig (push) Successful in 55s
CI's pnpm did not honor the build-script allowlist from package.json (removed
in pnpm 10) nor from pnpm-workspace.yaml under `pnpm --dir ui`, so build-web
kept failing with ERR_PNPM_IGNORED_BUILDS. Make it version- and discovery-
independent: run in ui/ via working-directory, install with --ignore-scripts
(no approval gate), then `pnpm rebuild @swc/core esbuild` to place the native
binaries vite needs. Verified locally: cold install + rebuild + vite build all
succeed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7zF7Kf4JqDwa6M8Qgge9M
2026-06-25 14:00:47 +03:00
ed5acd9f4e ci: split build across rust + fedora runners, deploy on fedora-44
Some checks failed
deploy / Build api + worker (static musl) (push) Successful in 7m57s
deploy / Deploy moments-worker to frootmig (push) Successful in 1m1s
deploy / Deploy moments-api to nikola (push) Successful in 1m6s
deploy / Build prerendered web (push) Failing after 2m1s
deploy / Deploy web to oolon (push) Has been skipped
The `rust` runner image has cargo + musl but no node/pnpm, so the web build
(and the previous npm-install workaround) can't run there. The fedora runner
images bake in node + pnpm + rsync. Split the build:
- build-binaries on `rust`  (cargo musl + lint/test gate)
- build-web      on `fedora-44` (pnpm install + prerender, no install step)
Deploy jobs move to `fedora-44` (has rsync/ssh/pnpm/ca-trust) and depend on
the relevant build job.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7zF7Kf4JqDwa6M8Qgge9M
2026-06-25 13:19:57 +03:00
a7750def17 ci: install pnpm via npm (gongfoo rust image lacks corepack)
Some checks failed
deploy / Build api + worker + web (push) Waiting to run
deploy / Deploy moments-api to nikola (push) Has been cancelled
deploy / Deploy moments-worker to frootmig (push) Has been cancelled
deploy / Deploy web to oolon (push) Has been cancelled
The build job failed with `corepack: command not found`: Fedora's nodejs
package (gongfoo runner-rust image) ships node + npm but not corepack. The
job runs as root, so install pnpm globally via npm instead. Longer term,
bake pnpm into the runner image to drop this step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7zF7Kf4JqDwa6M8Qgge9M
2026-06-25 13:13:59 +03:00
1b753f991f feat: prerender every route + Gitea Actions deploy
Some checks failed
deploy / Build api + worker + web (push) Failing after 53s
deploy / Deploy moments-api to nikola (push) Has been skipped
deploy / Deploy moments-worker to frootmig (push) Has been skipped
deploy / Deploy web to oolon (push) Has been skipped
Make the site fully prerendered so a plain curl returns complete content
for every route (crawlers / AI screening tools see real text, not an empty
#root), while humans keep full client interactivity.

Prerender:
- Build-time per-route render: prefetch data, renderToString, inline the
  dehydrated react-query cache as window.__RQ_STATE__; client hydrateRoots
  and refetches live (activity stays fresh; crawlers get the baked snapshot).
- New entry-server.tsx + prerender/{prefetch,routes,meta}.ts + run-prerender.mjs;
  shared lib/ranges.ts keeps SSR and client query keys identical.
- pnpm build now: tsc -b -> vite client build -> ssr build -> prerender.
- API base absolute at build (VITE_API_BASE), relative /api/v1 in the browser.
- CSS imports moved to the client entry so the tree imports under Node.
- schema.org Person + Occupation JSON-LD and per-route title/description/og.
- UTC + explicit field widths on shared date formatting so SSR and client
  hydration match byte-for-byte (fixes hydration mismatch on /activity).
- Strip non-text gist content from the CV fetch (1MB -> 25KB gzipped page).

Deploy (Gitea Actions, replaces script/deploy.sh):
- deploy.yml: on push to main, lint/test gate, build api+worker as static
  musl binaries (pure-rustls, no glibc skew) + prerendered web, deploy each
  over SSH as gitea_ci with scoped sudo.
- refresh.yml: daily cron re-bakes only the web snapshot so gist/activity
  edits propagate without a push or bouncing the api/worker.
- script/infra-setup.sh + asset/sudoers.d/{api,worker,web}-host.conf for
  one-time per-host provisioning. Secrets: RSYNC_SSH_KEY, QUERY_GITHUB_TOKEN,
  QUERY_GITEA_TOKEN.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7zF7Kf4JqDwa6M8Qgge9M
2026-06-25 12:53:46 +03:00