diff --git a/projects/radio-show/audio-processor/session-logs/2026-04-28-session.md b/projects/radio-show/audio-processor/session-logs/2026-04-28-session.md index d04ec71..9da362a 100644 --- a/projects/radio-show/audio-processor/session-logs/2026-04-28-session.md +++ b/projects/radio-show/audio-processor/session-logs/2026-04-28-session.md @@ -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 `` 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 `` | +| 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 '' root@172.16.3.20 "" + +# File transfer (PuTTY pscp) +"/c/Program Files/PuTTY/pscp.exe" -batch -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 '' -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 '' -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 '' -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 '' 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.