diff --git a/stacks/news-digest/README.md b/stacks/news-digest/README.md index 4c90823..042f1c5 100644 --- a/stacks/news-digest/README.md +++ b/stacks/news-digest/README.md @@ -27,8 +27,10 @@ noisy. This stack: (state in `/output/hidden.json`, shared across every device the user opens the digest from). -Two editions per day: 8am and 8pm local. Plus per-edition archives -at `/edition-YYYY-MM-DD-{am,pm}.html`. +Two editions per day by default (0800 / 2000 local), parametrized via +`DIGEST_CRON_AM` / `DIGEST_CRON_PM` env so each per-user instance can +fire on its own schedule. Plus per-edition archives at +`/edition-YYYY-MM-DD-{am,pm}.html`. ## Architecture @@ -127,12 +129,133 @@ with tool/JSON-mode support give better summarization quality; ## Forcing a fresh digest now ```bash -ssh ana-docker 'docker exec news-digest-worker python3 /app/digest.py' +ssh ana-docker 'docker exec news-digest-worker /usr/local/bin/run-digest.sh' ``` Runs the full pipeline once, ignoring cron. Useful after changing filtering knobs or adding feeds. +## Customizing the run schedule + +Times come from two env vars on the worker, written into the busybox +crontab at container start. Standard 5-field cron syntax. + +| Var | Default | Effect | +|---|---|---| +| `DIGEST_CRON_AM` | `0 8 * * *` | morning fire | +| `DIGEST_CRON_PM` | `0 20 * * *` | evening fire | + +`busybox crond` honors `$NEWS_DIGEST_TZ` (defaults to +`America/Los_Angeles`), so values are interpreted in the configured TZ. + +```bash +# Shift the canonical instance to 7am / 6pm +ssh ana-docker ' + cd /opt/docker/compose/news-digest + sed -i "s|^DIGEST_CRON_AM=.*|DIGEST_CRON_AM=0 7 * * *|" .env + sed -i "s|^DIGEST_CRON_PM=.*|DIGEST_CRON_PM=0 18 * * *|" .env + docker compose up -d --force-recreate news-digest-worker +' +``` + +Verify the rendered crontab: + +```bash +ssh ana-docker 'docker exec news-digest-worker cat /etc/crontabs/root' +``` + +## Multi-tenant: one instance per teammate + +Architecture: **shared miniflux + per-user digest stack**. Miniflux +already supports multi-user natively (each user has their own feeds, +categories, hide-state); we layer a separate news-digest stack per +user on its own port + output dir, scoped to that miniflux user's +credentials. + +### Onboarding a new user + +```bash +# Defaults — 8am / 8pm local, random password +scripts/add-digest-user.sh alice + +# Custom hours +scripts/add-digest-user.sh bob --am "0 6 * * *" --pm "0 17 * * *" + +# Weekday-only PM run +scripts/add-digest-user.sh carol --pm "30 18 * * 1-5" + +# Pin a known password (still creates the miniflux user if missing) +scripts/add-digest-user.sh dan 'pickyourpassword' --am "0 9 * * *" +``` + +What the script does: + +1. Reads miniflux admin creds from + `ana-docker:/opt/docker/compose/miniflux/.env`. +2. Allocates the next free `NEWS_DIGEST_PORT` (scans existing + `news-digest` + `digest-*` `.env` files). +3. Creates the miniflux user via the admin API. Already-exists is + non-fatal (kept; password not reset). +4. Provisions per-user dirs at + `/opt/docker/compose/digest-/` and + `/opt/docker/data/digest-/` (one-time sudo prompt — the + script falls back to printing the manual command if there's no TTY). +5. Materializes a per-user `.env` (inherits `NEWS_DIGEST_TAG` from the + canonical stack so all tenants run the same image). +6. Brings the stack up via `docker compose -p digest- up -d`. +7. Runs `seed-headlines.py` against miniflux as the new user (creates + the World + Local categories with default feeds). +8. Triggers a first digest run so the page isn't blank. + +Outputs the digest URL, miniflux login, and rendered cron schedule. + +### Per-user file layout + +``` +ana-docker: + /opt/docker/compose/digest-/ # compose + .env + build context + .env # auto-generated, contains MINIFLUX_PASSWORD + compose.yaml + Dockerfile + digest.py + ... # build context (image is shared/cached) + /opt/docker/data/digest-/ # rendered HTML + per-user hidden.json + index.html + edition-YYYY-MM-DD-{am,pm}.html + hidden.json + .article-cache.json # extracted article text, 7-day TTL +``` + +Container names: `digest--worker` and `digest--web`. + +### Updating an existing user's schedule + +Re-running the script with the same username is idempotent: + +```bash +# Change alice's evening run to 5:30pm +scripts/add-digest-user.sh alice --pm "30 17 * * *" +``` + +The miniflux user is kept (password unchanged), the .env is +re-materialized with the new schedule, and the worker container is +recreated. Use the same flow to bump cron times, rotate passwords +(by passing a new one explicitly), or rerun feed seeding. + +### Removing a user + +```bash +ssh ana-docker ' + cd /opt/docker/compose/digest-alice + docker compose -p digest-alice down -v +' +# Optional: nuke compose dir + rendered output +ssh -t ana-docker 'sudo rm -rf /opt/docker/compose/digest-alice /opt/docker/data/digest-alice' +# Optional: delete the miniflux user via the admin UI at http://10.250.50.70:8080/ +``` + +The digest is gone immediately; the miniflux account stays around +unless you delete it explicitly (cheap to leave; ~zero resource cost +when no stack is querying its feeds). + ## Logs ```bash