radio: session log update — Jupiter container live at 172.16.3.20:8765

Append to 2026-04-28-session.md covering the FastAPI/SQLite container
deploy: build + ship + verify, plus credentials, paths, and re-deploy
procedures for both DB updates and source updates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-28 06:05:34 -07:00
parent 71ada136a8
commit 519278a664

View File

@@ -187,3 +187,136 @@ No new credentials this session. Reference from prior session log:
- **FTS query examples (verified working):**
- `SELECT e.title, snippet(segments_fts, 0, '[', ']', '...', 12) FROM segments_fts JOIN segments s ON s.id=segments_fts.rowid JOIN episodes e ON e.id=s.episode_id WHERE segments_fts MATCH 'wireless'`
- Same shape for `qa_fts` against `qa_pairs`.
---
## Update: 06:02
Continuation later the same day — the deferred Jupiter Docker container was built, deployed, and verified end-to-end.
### Update Summary
A small FastAPI + SQLite query server was built in `projects/radio-show/audio-processor/server/` and deployed to Jupiter (Unraid) as a Docker container. The container is bound to `172.16.3.20:8765`, runs with `restart: unless-stopped`, and serves a read-only mount of `archive.db`. End-to-end verification from `GURU-BEAST-ROG` over the LAN confirmed `GET /` (HTML index, 200 in 111 ms, 3,647 bytes), `/api/stats` (correct counts), `/api/search?q=BIOS&kind=qa` (Pete 2012-09-22 + Frank 2011-09-24, bm25-ranked, snippets with `<mark>` highlighting), and clean uvicorn logs.
The HTML index page uses fetch + vanilla JS with debounced search (250 ms) and a kind filter (both / Q&A only / transcript only). No build step. The SQLite connection uses `mode=ro` URI and `check_same_thread=False` so future multi-worker uvicorn deployments can share the connection safely. Source committed at `71ada13` on `main` after rebasing over an intervening commit (`b3da922`) from another machine.
Total work item is closed: the original architectural plan (sqlite-first, Jupiter Docker, internal-only) is fully realized and reachable at **http://172.16.3.20:8765/**.
### Key Decisions (this update)
- **LAN-only re-framing**: Jupiter is NOT on Tailscale (no daemon, not in tailnet status). The original "Tailscale-only" framing was tightened to "LAN-only via 172.16.3.0/22". Effective protection is the same — no public exposure — and any user on the office LAN or with subnet routing through Tailscale can reach the service.
- **Explicit host bind `172.16.3.20:8765:8765`** in compose.yml instead of `0.0.0.0:8765:8765` or `8765:8765`. Pinning to a specific host IP eliminates accidental exposure if Jupiter ever gets a second IP (e.g. an Tailscale interface added later).
- **Single-file HTML, no build step**: index served as a 3.6 KB string from a `@app.get("/")` handler. Trades polish for zero deploy complexity. If the UI grows, swap in a `static/` mount and a real frontend.
- **`mode=ro` + `check_same_thread=False`**: read-only is enforced at the SQLite-URI layer (immune to a future bug in app-level read-only checks). `check_same_thread=False` is safe with `mode=ro` and lets uvicorn's worker model share connections without per-request reconnects.
- **Build on Jupiter, not locally**: avoids `docker save | ssh ... docker load` round-trip and the cross-platform image-format edge cases. Only source files crossed the wire.
### Problems Encountered (this update)
- **No new problems**. Smoke test was clean; deploy was clean. Push hit the gitea pre-receive once because Howard/another machine had landed `b3da922` in the meantime — resolved with a clean `git pull --rebase` (single commit, no conflicts) and a re-push.
### Files Created (this update)
| Path | Purpose |
|---|---|
| `projects/radio-show/audio-processor/server/main.py` | FastAPI app — endpoints + HTML index |
| `projects/radio-show/audio-processor/server/requirements.txt` | `fastapi==0.115.6`, `uvicorn[standard]==0.34.0` |
| `projects/radio-show/audio-processor/server/Dockerfile` | python:3.12-slim base, pip install, copy main.py, default `python -u main.py` |
| `projects/radio-show/audio-processor/server/compose.yml` | Service `radio-archive`, bind `172.16.3.20:8765`, mount `/mnt/user/appdata/radio-archive/data:/data:ro`, `restart: unless-stopped` |
### Endpoints (live on http://172.16.3.20:8765/)
| Method | Path | Notes |
|---|---|---|
| GET | `/` | HTML search UI (debounced, kind filter, snippet highlighting) |
| GET | `/api/stats` | Counts per table + episodes/hours per year |
| GET | `/api/episodes?year=YYYY&limit=N` | List, ordered by air_date then title |
| GET | `/api/episodes/{id}` | Episode meta + intros + qa_pairs |
| GET | `/api/episodes/{id}/transcript` | Segments + diarization turns (chronological) |
| GET | `/api/search?q=...&kind=both\|segments\|qa&limit=N` | FTS5, bm25-ranked, snippet-formatted with `<mark>` |
| GET | `/api/callers?limit=N` | Top recurring caller_names |
### Credentials (used this update)
#### Jupiter (Unraid Primary)
- **Vault path:** `infrastructure/jupiter-unraid-primary.sops.yaml`
- **Host:** 172.16.3.20 (LAN; office network 172.16.3.0/22)
- **SSH port:** 22
- **Username:** root
- **Password:** `Th1nk3r^99##`
- **iDRAC IP:** 172.16.1.73
- **iDRAC user / pass:** root / `Window123!@#-idrac`
- **OS / Docker:** Linux Jupiter 6.12.54-Unraid; Docker 27.5.1
- **Notes:** Primary container host (Gitea, NPM, Seafile + many others). Tailscale daemon NOT installed on this host.
### Infrastructure & Paths (Jupiter)
| Resource | Value |
|---|---|
| App source dir on Jupiter | `/mnt/user/appdata/radio-archive/app/` |
| DB dir on Jupiter | `/mnt/user/appdata/radio-archive/data/` |
| DB file on Jupiter | `/mnt/user/appdata/radio-archive/data/archive.db` (60,465,152 bytes) |
| Image | `radio-archive:latest` (sha256:9a04d06ba2c1…) |
| Container name | `radio-archive` |
| Bind | `172.16.3.20:8765 -> 8765/tcp` |
| Restart policy | `unless-stopped` |
| Service URL | http://172.16.3.20:8765/ |
### Commands Run
```bash
# SSH (PuTTY plink — system OpenSSH password auth doesn't work without sshpass)
echo y | "/c/Program Files/PuTTY/plink.exe" -ssh -pw '<jupiter pw>' root@172.16.3.20 "<cmd>"
# File transfer (PuTTY pscp)
"/c/Program Files/PuTTY/pscp.exe" -batch -pw '<jupiter pw>' -scp \
server/main.py server/requirements.txt server/Dockerfile server/compose.yml \
root@172.16.3.20:/mnt/user/appdata/radio-archive/app/
"/c/Program Files/PuTTY/pscp.exe" -batch -pw '<jupiter pw>' -scp \
archive-data/archive.db \
root@172.16.3.20:/mnt/user/appdata/radio-archive/data/archive.db
# Build + run (on Jupiter via SSH)
cd /mnt/user/appdata/radio-archive/app && docker compose build # ~70s
cd /mnt/user/appdata/radio-archive/app && docker compose up -d # ~5s
# Verify (from GURU-BEAST-ROG)
curl -s http://172.16.3.20:8765/api/stats
curl -s "http://172.16.3.20:8765/api/search?q=BIOS&kind=qa"
```
### Re-deploy Procedure (when DB updates)
```bash
# 1. Regenerate DB on GURU-BEAST-ROG
cd /c/Users/guru/ClaudeTools/projects/radio-show/audio-processor
.venv/Scripts/python.exe import_to_sqlite.py # incremental
# or for full rebuild:
.venv/Scripts/python.exe import_to_sqlite.py --rebuild
# 2. Ship to Jupiter
"/c/Program Files/PuTTY/pscp.exe" -pw '<pw>' -scp archive-data/archive.db \
root@172.16.3.20:/mnt/user/appdata/radio-archive/data/archive.db
# 3. No container restart needed — read-only connection picks up fresh data on next request
```
### Re-deploy Procedure (when source changes)
```bash
# 1. ship updated source
"/c/Program Files/PuTTY/pscp.exe" -pw '<pw>' -scp server/*.py server/Dockerfile \
root@172.16.3.20:/mnt/user/appdata/radio-archive/app/
# 2. rebuild + restart on Jupiter
echo y | "/c/Program Files/PuTTY/plink.exe" -ssh -pw '<pw>' root@172.16.3.20 \
"cd /mnt/user/appdata/radio-archive/app && docker compose build && docker compose up -d"
```
### Pending / Next Up (refreshed)
1. ~~**Stand up the Jupiter Docker container**~~ DONE (this update).
2. **(Followup)** Fix the intro_roster.json aggregation bug — load+merge or drop-and-use-DB.
3. **(Future)** Voice profiles for Randall, Rob, and producers (Andrew/Shannon/Ken). Currently every non-Mike-non-Tara voice falls into the CALLER bucket, inflating Q&A false positives in early-years and 2018+ episodes.
4. **(Future)** Speaker-name resolution view — defer until query patterns emerge.
5. **(Optional)** Add Tailscale to Jupiter (subnet router for 172.16.3.0/22, or direct daemon for `ts-` hostname access). Only needed if remote, off-LAN access becomes a requirement.