Files
esh-pfi-infrastructure/stacks/news-digest/compose.yaml
T
vh d552e289cb news-digest: per-instance cron schedule via env
Hardcoded crontab → render at container start from
DIGEST_CRON_AM + DIGEST_CRON_PM. Defaults match the original
0800 / 2000 so existing deploys are no-ops.

scripts/add-digest-user.sh learns --am and --pm flags so each
teammate's stack can fire on their hours:

  scripts/add-digest-user.sh bob --am "0 6 * * *" --pm "0 17 * * *"
  scripts/add-digest-user.sh carol --pm "30 18 * * 1-5"   # weekdays only

Standard 5-field cron syntax; busybox crond honors the container's
\$TZ. Removed the now-unused stacks/news-digest/crontab file and
the matching COPY in the Dockerfile.
2026-04-28 14:29:55 -07:00

110 lines
4.7 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# news-digest — twice-daily LLM-curated briefing.
#
# Two containers in this stack, both built from the same Dockerfile:
#
# news-digest-worker — python + cron, runs digest.py at 0800/2000
# local, writes /output/index.html and
# /output/edition-*.html.
# news-digest-web — FastAPI on uvicorn, serves /output as static
# and exposes /api/{hidden,hide,restore} for
# the per-item × button (state in
# /output/hidden.json, shared across devices).
# Homepage card lives on this container.
#
# Same bind-mounted /output for both: worker writes HTML, web reads
# it back. Worker uses atomic writes (.tmp + rename) so partial pages
# never get served.
services:
news-digest-worker:
image: local/news-digest:${NEWS_DIGEST_TAG:-v1}
build:
context: .
dockerfile: Dockerfile
# ${DIGEST_PROJECT} prefixes container names so multiple instances
# (one per teammate, scoped to their miniflux user) can coexist on
# the same host. Default keeps backward-compat with the original
# singleton deploy.
container_name: ${DIGEST_PROJECT:-news-digest}-worker
restart: unless-stopped
environment:
- TZ=${NEWS_DIGEST_TZ:-America/Los_Angeles}
- LLAMA_SWAP_URL=${LLAMA_SWAP_URL:-http://10.250.50.54:9292}
- LLAMA_SWAP_MODEL=${LLAMA_SWAP_MODEL:-qwen3.5-35-a3b}
- LLAMA_SWAP_TIMEOUT=${LLAMA_SWAP_TIMEOUT:-180}
- MINIFLUX_URL=${MINIFLUX_URL:-http://miniflux:8080}
- MINIFLUX_USER=${MINIFLUX_USER:-lkraven}
- MINIFLUX_PASSWORD=${MINIFLUX_PASSWORD}
- DIGEST_OUTPUT_DIR=/output
- DIGEST_TEMPLATE_DIR=/app/templates
- DIGEST_REDDIT_HOURS=${DIGEST_REDDIT_HOURS:-12}
- DIGEST_MIN_SCORE=${DIGEST_MIN_SCORE:-50}
- DIGEST_MIN_RATIO=${DIGEST_MIN_RATIO:-0.85}
- DIGEST_MAX_PER_SUB=${DIGEST_MAX_PER_SUB:-8}
- DIGEST_MINIFLUX_HOURS=${DIGEST_MINIFLUX_HOURS:-12}
- DIGEST_MINIFLUX_MAX=${DIGEST_MINIFLUX_MAX:-8}
- DIGEST_MINIFLUX_TECH_CATEGORY=${DIGEST_MINIFLUX_TECH_CATEGORY:-Tech aggregators}
- DIGEST_MINIFLUX_WORLD_CATEGORY=${DIGEST_MINIFLUX_WORLD_CATEGORY:-World}
- DIGEST_MINIFLUX_LOCAL_CATEGORY=${DIGEST_MINIFLUX_LOCAL_CATEGORY:-Local}
- DIGEST_MINIFLUX_HEADLINES_HOURS=${DIGEST_MINIFLUX_HEADLINES_HOURS:-8}
- DIGEST_MINIFLUX_HEADLINES_MAX=${DIGEST_MINIFLUX_HEADLINES_MAX:-15}
# Per-instance cron schedule. Standard 5-field syntax. busybox
# crond honors $TZ. Defaults preserve the original 0800 / 2000.
- DIGEST_CRON_AM=${DIGEST_CRON_AM:-0 8 * * *}
- DIGEST_CRON_PM=${DIGEST_CRON_PM:-0 20 * * *}
volumes:
- ${NEWS_DIGEST_OUTPUT_DIR}:/output
networks:
- tnet
# Cron-driven worker — no healthcheck endpoint. The web container
# is what users hit; if the worker dies we'll see stale content.
# Restart policy handles transient crashes.
news-digest-web:
image: local/news-digest:${NEWS_DIGEST_TAG:-v1}
build:
context: .
dockerfile: Dockerfile
container_name: ${DIGEST_PROJECT:-news-digest}-web
restart: unless-stopped
depends_on:
- news-digest-worker
ports:
- "${NEWS_DIGEST_BIND:-0.0.0.0}:${NEWS_DIGEST_PORT}:80"
volumes:
# Read-write here so the API can persist hidden.json. Worker also
# writes here (HTML); both processes serialize via filenames they
# don't share, plus uvicorn's threading.Lock around hidden.json.
- ${NEWS_DIGEST_OUTPUT_DIR}:/output
environment:
- DIGEST_OUTPUT_DIR=/output
# Override the worker's cron entrypoint to launch uvicorn instead.
# tini still wraps the process for clean signal forwarding.
entrypoint: ["/sbin/tini", "--"]
command: ["uvicorn", "web:app", "--host", "0.0.0.0", "--port", "80",
"--no-access-log"]
healthcheck:
# 127.0.0.1 instead of localhost — alpine's busybox wget tries
# IPv6 first when localhost resolves to both ::1 and 127.0.0.1
# (per /etc/hosts). uvicorn only binds 0.0.0.0 (IPv4), so the
# v6 attempt gets connection-refused and busybox doesn't fall
# back. Pin to v4 explicitly.
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1/ || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
networks:
- tnet
labels:
- homepage.group=News
- homepage.name=${DIGEST_HOMEPAGE_NAME:-Daily Digest}
- homepage.icon=mdi-newspaper-variant-outline
- homepage.description=${DIGEST_HOMEPAGE_DESC:-LLM-curated briefing across feeds, twice daily}
- homepage.href=http://10.250.50.70:${NEWS_DIGEST_PORT}
networks:
tnet:
name: traefik-net
external: true