9bdb41ea6a
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.
43 lines
1.6 KiB
Docker
43 lines
1.6 KiB
Docker
# news-digest — base image for two containers in this stack:
|
|
#
|
|
# news-digest-worker — runs alpine's busybox crond + the one-shot
|
|
# digest.py per fire (default ENTRYPOINT).
|
|
# news-digest-web — runs uvicorn web:app (overridden in compose)
|
|
# to serve /output as static + the tiny
|
|
# hidden-items API at /api/*.
|
|
#
|
|
# Single image, two roles selected via compose `command:`.
|
|
# Bind-mounted /output is the shared canvas: worker writes HTML, web
|
|
# serves it.
|
|
|
|
FROM python:3.12-alpine
|
|
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
PIP_DISABLE_PIP_VERSION_CHECK=1
|
|
|
|
# tzdata so $TZ works for cron + datetime; tini so signals propagate cleanly.
|
|
RUN apk add --no-cache tzdata tini bash
|
|
|
|
# fastapi + uvicorn[standard] for the web container; requests + jinja2
|
|
# for the worker. Both shipped in both containers — neither set is
|
|
# heavy enough to justify splitting the image.
|
|
RUN pip install --no-cache-dir requests jinja2 'fastapi>=0.115' 'uvicorn[standard]>=0.30'
|
|
|
|
WORKDIR /app
|
|
COPY digest.py /app/digest.py
|
|
COPY seed-headlines.py /app/seed-headlines.py
|
|
COPY web.py /app/web.py
|
|
COPY templates /app/templates
|
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
COPY run-digest.sh /usr/local/bin/run-digest.sh
|
|
COPY crontab /etc/crontabs/root
|
|
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/run-digest.sh
|
|
|
|
# Sentinel + first-run output dir
|
|
VOLUME /output
|
|
|
|
# Default ENTRYPOINT runs the worker (cron). The web container in
|
|
# compose overrides both entrypoint and command to launch uvicorn.
|
|
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]
|