Files
esh-pfi-infrastructure/stacks/news-digest/compose.yaml
T
vh f692b7ec7a news-digest: per-item × button + cross-device hidden tray
Adds a small × on each item that hides it from the page. State is
server-side at /output/hidden.json so the same hidden set follows
the user across devices (home, ipad, laptop, work). A "Hidden (N)"
tray at the bottom shows what's hidden on the current page with a
restore button per row; older hidden ids that aren't on this page
sit silently and continue to filter future editions that include
the same article.

Architecture change: news-digest-web swaps from nginx:alpine to a
FastAPI app on uvicorn, built from the same Dockerfile as the
worker. Same image, different command (`uvicorn web:app` overrides
the worker's cron entrypoint via compose). Drops one image dependency,
adds /api/{hidden,hide,restore}.

Item ids are stable 12-char sha1 prefixes (`reddit:<post_id>` /
`miniflux:<entry_id>`) computed in digest.py at render time and
emitted as `data-id` on each .item. The frontend reads /api/hidden
once on load, applies `is-hidden` to matching items, and POSTs
hide/restore on user interaction (optimistic, with rollback on
network error).

Storage: single JSON array at /output/hidden.json, atomic writes
via tempfile + rename, threading.Lock around the read-modify-write
inside the single uvicorn worker. No auth — the digest itself is
unauthenticated on LAN; same trust boundary applies.

Playbook also drops the DOCKER_BUILDKIT=0 fallback now that
ana-docker is on docker-ce 29, and adds three verify steps
(/api/hidden returns a JSON array, app.js is reachable, full
hide/restore round-trip with a synthetic id).
2026-04-26 15:05:25 -07:00

93 lines
3.5 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}
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:
test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || 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