Files
esh-pfi-infrastructure/stacks/news-digest/compose.yaml
T
vh 9bdb41ea6a news-digest: add world + local headlines sections
Two new dense headline rails above the existing reddit/tech cards.
Designed for high-volume "what happened" coverage where the title
is the deliverable — no LLM summarization, ~15 items per section,
6-column-collapsing grid (title / source / time).

Digest pipeline:
  * fetch_miniflux_headlines(category) — flat list per category, dedup
    by lowercased title (different feeds syndicate the same wire stories)
  * 8h look-back window (vs 12h for tech/reddit) since headlines move
    faster
  * cap of 15 per section (DIGEST_MINIFLUX_HEADLINES_MAX)

Frontend:
  * .headline element parallels .item for the hide-button machinery
    (both have data-id, both honored by app.js)
  * dense 3-col layout collapses to 1-col on narrow screens
  * jumpnav now numbers world=01, local=02, reddit=03, tech=04

Setup:
  * seed-headlines.py — one-shot script (lives in the image at
    /app/seed-headlines.py). Creates the World + Local categories in
    miniflux, subscribes a curated feed list, and renames each feed
    to a short display title (BBC vs "BBC News", "LA Times" vs "California").
    Idempotent — reruns only add new feeds.
  * Default world: BBC, NPR, Al Jazeera. Default local: LA Times Local,
    LA Times CA, Voice of OC. (OC Register blocks miniflux; left out.)
  * entrypoint.sh now syncs templates/{style.css,app.js,favicon.svg}
    to /output on container start so frontend asset updates land
    without a manual copy after rebuild.
2026-04-28 11:14:15 -07:00

102 lines
4.1 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
container_name: 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}
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: 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=Daily Digest
- homepage.icon=mdi-newspaper-variant-outline
- homepage.description=LLM-curated briefing across feeds, twice daily
- homepage.href=http://10.250.50.70:${NEWS_DIGEST_PORT}
networks:
tnet:
name: traefik-net
external: true