rob thijssen 88fbbba60b feat(hg): revset-based author query, group discovery, one-shot ingest script
Rewrites the hg worker to use json-log?rev=author() which matches the
changeset author (not the pusher), capturing commits landed by sheriffs.
Repos are discovered within configured groups plus individually listed
repos. The worker skips entirely after the first successful backfill.

Adds script/hg-ingest.sh for offline ingestion via local hg clones —
clones one repo at a time, caches extracted changesets to .tsv, inserts
via psql, and sets poller_state when done.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-05 13:58:21 +03:00

moments

Personal activity timeline. Polls public sources (GitHub, Gitea, Mercurial, Bugzilla), 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. In production this is 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 <env> all          # api + worker + web
./script/deploy.sh <env> api worker   # subset
./script/deploy.sh <env> default      # api + web only (worker untouched)
./script/deploy.sh <env> all --dry-run

Concrete hosts, ports, and the site's server_name live in asset/manifest.yml. The shape of the deployment:

Component Notes
api binds the port from api.config.bind; firewalld service moments-api
worker no listening port; pollers only
web per-site nginx ingress; /api/* reverse-proxies to the api host
db postgres mTLS, passwordless

Postgres roles moments_rw and moments_ro must exist on the primary, with pg_ident.conf.d/<host>.conf mapping the api host's FQDN → moments_ro and the worker host's FQDN → moments_rw. See asset/sql/bootstrap-moments.sql, asset/postgres/ident.conf.tmpl, and script/db-perms.sh (idempotently adds the cert_cn lines on the postgres primary + standby and reloads postgres).

Inter-host traffic over the WG mesh: web's nginx connects to the api host 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 are resolved at deploy time via pass. The mapping of env-var name → pass-store path lives under worker.secrets in manifest.yml; deploy.sh iterates the map, fetches each secret, and substitutes the matching {{NAME}} placeholder in worker.env.tmpl. To add a secret: add a worker.secrets entry, add NAME={{NAME}} to worker.env.tmpl, and ensure pass show <path> returns the value on the deploying machine.

First-time setup

After the first successful prod deploy:

  1. Point public DNS for the site at the web host's public IP (unproxied).
  2. Confirm curl --fail --silent --show-error https://<site>/api/v1/healthz returns ok.
  3. If migrating from a predecessor, archive the old repo with a pointer to this one.
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%