second-brain/deploy/tower/README.md
Travis Herbranson 913d4f0335 deploy/tower + claim race + transcribe smoke tests
deploy/tower/:
- second-brain-transcribe.service — systemd unit. User=herbyadmin,
  Type=simple, After=/Wants= wg-quick@wg-lan.service so the WG tunnel
  must come up first. Restart=always with a StartLimitBurst guard.
- second-brain-transcribe.env.example — env file template documenting
  the SECOND_BRAIN_DATABASE_URL form for db.wg.herbylab.dev (10.99.0.1)
  and the optional WHISPER_* overrides.
- README.md — EndeavourOS install steps (nvidia/cuda/cudnn, ffmpeg, uv
  + tower extra, model pre-warm), WG topology reference, validation
  checklist for what to confirm once the tunnel is live, and a
  follow-ups section flagging the local-disk → NAS media migration as
  out-of-scope-for-this-round.

Tests:
- tests/test_claim.py — live-DB race test. Two threads call
  claim_next_source against a single PULLED video row; SKIP LOCKED
  must give exactly one of them the row, the other gets None. Also
  asserts the claimed_by/at columns land + release nulls them.
  Auto-skips when no SECOND_BRAIN_DATABASE_URL is set.
- tests/test_transcribe.py — pure-Python coverage of resolve_settings
  (cpu→int8, cuda→float16, env-over-block) and write_srt; plus a CPU
  smoke test that synthesizes a numpy audio array and runs the `tiny`
  model on cpu/int8 (auto-skipped when faster-whisper isn't installed,
  i.e. on the dev side without --extra tower).
2026-05-25 10:20:46 -04:00

5.6 KiB

Tower-side transcribe worker — deploy notes

Target host: EndeavourOS tower (Arch base), RTX 3080. Runs the second-brain transcribe-worker daemon under systemd. Reaches the petalbrain Postgres over WireGuard at db.wg.herbylab.dev:5432.

This worker only does pull + transcribe for VIDEO sources. Articles and all extraction/embedding stay on the dev side.


1. System packages

# Build / runtime tooling.
sudo pacman -S --needed base-devel git ffmpeg python uv

# NVIDIA — driver, CUDA, cuDNN. faster-whisper / CTranslate2 needs cuDNN
# at runtime; missing-symbol errors at first transcribe are usually a
# cuDNN install gap. Use whichever driver/cuda channel matches your
# Arch derivative; on stock EndeavourOS the AUR/community packages work:
sudo pacman -S --needed nvidia nvidia-utils cuda cudnn

# Confirm the GPU is visible.
nvidia-smi

yt-dlp comes in via the Python project (uv sync step below), so the distro yt-dlp is optional. Leave it off the host unless you want it on your CLI PATH.


2. Project checkout + venv

sudo mkdir -p /opt/projects/homelab
sudo chown herbyadmin:herbyadmin /opt/projects/homelab
cd /opt/projects/homelab
git clone gitea-lovebug:petal-power/second-brain.git
cd second-brain

# Install the runtime deps + the `tower` extra (faster-whisper / CTranslate2).
# The dev side does NOT install --extra tower; only the tower needs it.
uv sync --extra tower

faster-whisper will pull a couple hundred MB of CUDA wheels. First-run model download (~3 GB for large-v3) lands under ~/.cache/huggingface/ the first time the worker transcribes; pre-warm it if you want:

uv run python -c "from faster_whisper import WhisperModel; WhisperModel('large-v3', device='cuda', compute_type='float16')"

3. WireGuard

The worker assumes the tunnel is already configured and that wg-quick@wg-lan.service brings it up at boot. Topology:

Peer WG IP
hub (herbydev) 10.99.0.1
tower 10.99.0.2

db.wg.herbylab.dev resolves to 10.99.0.1. Confirm connectivity from the tower:

ping -c2 db.wg.herbylab.dev
nc -vz db.wg.herbylab.dev 5432

Tunnel setup itself is out of scope for this README — that's configured separately as part of Travis's infra.


4. EnvironmentFile

sudo install -m 0600 -o root -g root \
    deploy/tower/second-brain-transcribe.env.example \
    /etc/default/second-brain-transcribe
sudo $EDITOR /etc/default/second-brain-transcribe   # paste the lovebug password

The lovebug password lives in /opt/backups/postgres-consolidation/credentials.env on the dev host. Copy it across through whatever channel you trust (personal Bitwarden, scp over WG, etc.) — do not commit it.


5. systemd unit

sudo install -m 0644 deploy/tower/second-brain-transcribe.service \
    /etc/systemd/system/second-brain-transcribe.service
sudo systemctl daemon-reload
sudo systemctl enable --now second-brain-transcribe.service

The unit declares After= + Wants= on wg-quick@wg-lan.service, so the worker starts after the tunnel comes up at boot. If the tunnel disappears mid-run the worker keeps polling and logs DB-unreachable warnings; it doesn't crash-loop.

Day-to-day commands

# What's it doing right now?
systemctl status second-brain-transcribe.service
journalctl -u second-brain-transcribe.service -f

# Pause without uninstalling — toggle from the dashboard at
# https://brain.herbylab.dev/settings  (or whichever host).
# Or stop the unit:
sudo systemctl stop second-brain-transcribe.service

# Pick up code updates:
cd /opt/projects/homelab/second-brain
git pull
uv sync --extra tower
sudo systemctl restart second-brain-transcribe.service

6. Validation — what to confirm once WG is live

These can only be verified on the tower itself:

  1. WG reachability. nc -vz db.wg.herbylab.dev 5432 succeeds.
  2. DB auth. uv run alembic current returns the latest revision id.
  3. GPU large-v3 path. With a short test video queued via second-brain add <url> on the dev side, run uv run second-brain transcribe-worker --once on the tower. Watch journalctl — first run pulls the model from HuggingFace (slow), subsequent runs are fast. The source row should end up in TRANSCRIBED with transcript_text populated and claimed_by cleared.
  4. Race safety smoke test. Run two --once invocations in parallel against an empty queue, then with one PENDING video. Only one should claim it; the other should see no work.
  5. Settings round-trip. Flip transcription_enabled off on the dashboard; within one poll cycle (~15s) the worker logs transcription_enabled=false — sleeping. Flip back on, the next PENDING video gets picked up.

Follow-ups (out of scope for this round)

  • Media + subtitles directory currently lives on the tower's local disk (under ~/.local/share/second-brain/). Move to a NAS mount later so the dev side can compile/inspect the SRTs without a cross-machine fetch. Open question on the right mount point + cred flow — see the project's postgres-migration-planning.md for the related vault-location decision Travis still owes.
  • Tower-side observability. No metrics endpoint yet; journald is the only signal. A /api/worker-status heartbeat the dashboard could read would be nice once two workers exist.
  • GPU concurrency > 1. transcription_max_concurrent_gpu_jobs exists in pipeline_settings but the worker only runs one job at a time. Scale-out would need a per-job semaphore (or just running N worker instances with distinct SECOND_BRAIN_WORKER_IDs).