Files
moments/readme.md
rob thijssen 7843c2c13f chore(deploy): co-locate api + worker on anjie
nikola and frootmig are flagging power events and drive warnings on
the iLO interface and need drive replacement. Move both moments
components onto anjie.kosherinata.internal until those hosts are
back in service. Update the nginx upstream and the readme topology
table to match; the postgres pg_ident.conf on magrathea now needs
to map anjie's cert CN to both moments_ro and moments_rw (two lines
for the same cert_cn).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 08:24:21 +03:00

3.9 KiB

moments

Personal activity timeline for rob.tn. Polls public sources (GitHub, Gitea, hg-edge.mozilla.org, bugzilla.mozilla.org), stores raw payloads in Postgres, and serves a reshaped timeline to a static React frontend.

Successor to the now-defunct grenade-events-react, which depended on MongoDB Stitch (retired by MongoDB in September 2022).

Layout

crates/
  moments-entities/   # types and DTOs
  moments-core/       # ingestion + reshape logic
  moments-data/       # postgres adapter + migrations
  moments-api/        # axum read-only HTTP API (binary)
  moments-worker/     # ingestion daemon (binary)
ui/                   # vite + react + swc + ts frontend
asset/                # systemd, nginx, firewalld, manifest.yml
script/deploy.sh

Architectural conventions follow grenade/architecture/generic.md.

Local development

cargo build --workspace
cargo run -p moments-api      # serves on 127.0.0.1:8080
cargo run -p moments-worker   # one-shot ingest tick (until --interval is wired up)

The API expects a Postgres reachable at DATABASE_URL. For magrathea, that's an mTLS connection using the host cert. For local dev against a throwaway database:

DATABASE_URL=postgres://localhost/moments cargo run -p moments-api

Migrations live in crates/moments-data/migrations/ and run automatically on worker startup. The API connects as moments_ro and never runs migrations — the worker (as moments_rw) is the schema owner.

Deployment

./script/deploy.sh prod all          # api + worker + web
./script/deploy.sh prod api worker   # subset
./script/deploy.sh prod default      # api + web only (worker untouched)
./script/deploy.sh prod all --dry-run

Topology:

Component Host Notes
api anjie.kosherinata.internal binds 0.0.0.0:42424; firewalld service moments-api
worker anjie.kosherinata.internal no listening port; pollers only
web oolon.kosherinata.internal per-site nginx ingress for rob.tn; /api/* → anjie across the WG
db magrathea.kosherinata.internal postgres mTLS, passwordless

api and worker are co-located on anjie while nikola and frootmig are out for drive replacement.

Postgres roles moments_rw and moments_ro must exist on the primary, with pg_ident.conf mapping anjie.kosherinata.internal to both roles (one cert_cn line per mapping). See asset/sql/bootstrap-moments.sql and asset/postgres/ident.conf.tmpl.

Inter-host traffic over the WG mesh: oolon's nginx connects to http://anjie.kosherinata.internal:42424 in plaintext. The mesh provides the encryption layer; per-hop TLS for an internal HTTP read-only API on already-public data is deferred. If that changes, swap the api binary to rustls + the host cert pair, and update the nginx upstream to https://.

Secrets resolved by deploy.sh via pass:

  • github.com/grenade/admin-token — GitHub PAT for events + search APIs (worker only).

Optional, set if needed in worker.env: GITEA_TOKEN, BUGZILLA_API_KEY.

DNS cutover

rob.tn currently resolves to GitHub Pages. After the first successful prod deploy:

  1. Update Cloudflare DNS for rob.tn to the WAN IP that fronts oolon (unproxied — see architecture doc §11).
  2. Confirm curl -fsS https://rob.tn/api/v1/healthz returns ok.
  3. Add an archival notice to the top of grenade-events-react/readme.md pointing at this repo, and archive the GitHub repo.