rob thijssen 110b523fd0 chore(deploy): add manifest, systemd units, nginx config, deploy.sh
Wires up the prod deployment per architecture-doc conventions:

- api → nikola.kosherinata.internal, loopback bind 127.0.0.1:42424
  (less-common port, registered with SELinux as http_port_t).
- worker → frootmig.kosherinata.internal, no listening port.
- web (static ui/dist + nginx server_name rob.tn) → nikola, with
  /api/* reverse-proxied to the loopback API.
- db → existing magrathea cluster via mTLS, hostname-baked DATABASE_URL
  rendered into /etc/moments/{api,worker}.env at deploy time.

Cert rotation: step-ca renews host certs every 24h; .path units watch
/etc/pki/tls/misc/<host>.pem and trigger systemctl restart of the
relevant service. Both binaries hold cert state in rustls and read
once at startup, so restart is the right reload semantics.

deploy.sh contract matches the architecture doc: positional env arg,
component list (or `all` / `default`), --dry-run support. Renders
config templates from `pass`, rsyncs over ssh+sudo, runs sysusers /
restorecon / semanage / systemctl / nginx -t idempotently.

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

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 nikola.kosherinata.internal binds 127.0.0.1:42424, fronted by local nginx
worker frootmig.kosherinata.internal no listening port; pollers only
web nikola.kosherinata.internal static ui/dist/ under /var/www/moments
db magrathea.kosherinata.internal postgres mTLS, passwordless

Postgres roles moments_rw and moments_ro must exist on the primary, with pg_ident.conf mappings in place for nikola.kosherinata.internalmoments_ro and frootmig.kosherinata.internalmoments_rw. See asset/sql/bootstrap-moments.sql and asset/postgres/ident.conf.tmpl.

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 nikola (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.
Description
Personal activity timeline for rob.tn — successor to grenade-events-react
Readme 1.4 MiB
Languages
Rust 61.4%
TypeScript 24.3%
Shell 12.1%
CSS 1.4%
HTML 0.8%