Phase 4: delivery -- ntfy, working feedback buttons, digest page
ntfy push is deliberately short: a title, one line, and a Click through to the story. The digest is a WEB PAGE, not a push payload -- the old system sent the whole report as one long markdown message and ntfy turned it into a .md attachment, which is what made it miserable to read on a phone. Feedback buttons finally work. The old ones never fired once: NEWS_API_BASE_URL was unset so they were dropped behind a single boot WARN, and /api/feedback had no auth anyway. ntfy runs http actions FROM THE PHONE, so the endpoint must be public -- which is why each button now carries an HMAC signed over (cluster_id, kind). A leaked URL authorises one vote on one story rather than acting as a bearer token for the whole model. Missing config drops the buttons loudly instead of silently. Digest prose is one LLM call per day through LiteLLM, and that is the entire generative-model surface of this system -- clustering, relevance, importance and vetoes are all embeddings and arithmetic. Feed text is passed as JSON data, never interpolated into prompt prose, so a headline cannot read as an instruction. Any failure falls back to the plain list; the digest never depends on a model being reachable. Verified against the live proxy: 20s, themed paragraphs, no invented facts. Digest page is mobile-first, dark-mode aware, and renders from notification_log so it is a record of what was actually sent. deploy/README.md documents the Caddy vhost and env the buttons require, and uses a python healthcheck rather than curl -- the v0.2 image shipped no curl, so its healthcheck failed forever while the service was fine. 62 tests.
This commit is contained in:
1 parent
b852840729
commit
dc6b6fea7e
9 files changed
+829
No files matched your search
@@ -0,0 +1,86 @@
|
||||
# Deploying
|
||||
|
||||
Three things must be true before the system is useful, and the third is the one
|
||||
the previous version got wrong.
|
||||
|
||||
## 1. Compose service
|
||||
|
||||
```yaml
|
||||
news:
|
||||
image: git.rcjohnstone.com/connor/news:latest
|
||||
restart: unless-stopped
|
||||
networks: [internal]
|
||||
environment:
|
||||
- TZ=America/Louisville
|
||||
- NEWS_DB=/data/news.db
|
||||
- NEWS_CONFIG_DIR=/config
|
||||
# ntfy. The topic must differ from news_triage's during the parallel run.
|
||||
- NTFY_URL=${NTFY_URL}
|
||||
- NTFY_TOPIC=news2
|
||||
- NTFY_USER=${NTFY_USER}
|
||||
- NTFY_PASS=${NTFY_PASS}
|
||||
# REQUIRED for feedback buttons -- see section 3.
|
||||
- NEWS_API_BASE_URL=https://news2.rcjohnstone.com
|
||||
- NEWS_FEEDBACK_SECRET=${NEWS_FEEDBACK_SECRET}
|
||||
# One LLM call per day, for the digest prose only.
|
||||
- NEWS_LITELLM_KEY=${NEWS_LITELLM_KEY}
|
||||
volumes:
|
||||
- ./data/news2:/data
|
||||
- ./config/news2:/config:ro
|
||||
healthcheck:
|
||||
# NOT curl. The v0.2 image shipped no curl, so its healthcheck failed
|
||||
# forever while the service was fine and `podman ps` lied for four days.
|
||||
test: ["CMD", "python", "-c",
|
||||
"import urllib.request;urllib.request.urlopen('http://localhost:3000/healthz')"]
|
||||
interval: 60s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
```
|
||||
|
||||
Remember that a bind-mounted config change needs `sudo podman restart news`;
|
||||
`podman-compose up -d` will not restart a container when only a mounted file
|
||||
changed. An **environment** change does need the full `up -d`.
|
||||
|
||||
## 2. Caddy vhost
|
||||
|
||||
```
|
||||
news2.rcjohnstone.com {
|
||||
reverse_proxy news:3000
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Why the feedback buttons need all of this
|
||||
|
||||
ntfy executes an action button's HTTP request **from the phone**, not from the
|
||||
server. So a container-local address can never work, and this is exactly how the
|
||||
previous version failed: `NEWS_API_BASE_URL` was unset, the buttons were dropped
|
||||
behind a single boot WARN, and nobody noticed for a week. The result was 32
|
||||
feedback labels in total -- for a system whose entire premise is learning from
|
||||
feedback.
|
||||
|
||||
Because the endpoint is therefore public, every button carries an HMAC signed
|
||||
over `(cluster_id, kind)`. A leaked URL authorises one vote on one story rather
|
||||
than acting as a bearer token for the whole model. Generate the secret once:
|
||||
|
||||
```sh
|
||||
openssl rand -hex 32 # -> NEWS_FEEDBACK_SECRET in ~/.env
|
||||
```
|
||||
|
||||
`news notify` prints a loud warning and omits the buttons if either
|
||||
`NEWS_API_BASE_URL` or `NEWS_FEEDBACK_SECRET` is missing, rather than shipping
|
||||
inert buttons.
|
||||
|
||||
## 4. Scheduling
|
||||
|
||||
Two timers, or one loop inside the container:
|
||||
|
||||
```
|
||||
*/15 * * * * news poll && news notify
|
||||
45 8 * * * news digest
|
||||
```
|
||||
|
||||
## 5. Running in parallel with news_triage
|
||||
|
||||
Use a different ntfy topic and a different vhost, and leave `news_triage`
|
||||
running. Compare a week of both before retiring it.
|
||||
Reference in new issue
Block a user