Two commits since the last merge: - web: add-to-queue form on /queue too, with per-page HTMX dispatch. Shared form partial reused by /dashboard and /queue; the POST /sources/add endpoint dispatches the response partial by inspecting HX-Current-URL so each page's own list/counts refresh inline. Same service call, same flash behavior. - tower: pin CUDA-12 wheels + LD_LIBRARY_PATH wrapper for the systemd service. ctranslate2 4.7.2 needs CUDA 12 + cuDNN 9 but Arch ships CUDA 13, so we pin nvidia-cublas-cu12 + nvidia-cudnn-cu12>=9,<10 in the `tower` extra and add deploy/tower/run-transcribe-worker.sh, which resolves the venv's lib dirs at runtime and prepends them to LD_LIBRARY_PATH before exec-ing the worker. systemd doesn't inherit shell exports, so this is what makes the service survive reboots and Python upgrades. ExecStart= now points at the wrapper, and the StartLimit* directives moved into [Unit] where modern systemd expects them. Travis's tower-side corrections on main (`User=trucktrav`, `gitea-pbs-trucktrav` clone alias, tower WG IP 10.99.0.3) auto-merge cleanly against these branch edits — different lines in the same files.
226 lines
8.0 KiB
Markdown
226 lines
8.0 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# Build / runtime tooling.
|
|
sudo pacman -S --needed base-devel git ffmpeg python uv
|
|
|
|
# NVIDIA driver only — see the CUDA-12 gotcha below before installing
|
|
# the distro's `cuda` / `cudnn` packages.
|
|
sudo pacman -S --needed nvidia nvidia-utils
|
|
|
|
# Confirm the GPU is visible.
|
|
nvidia-smi
|
|
```
|
|
|
|
### ⚠ CUDA-12 gotcha — do NOT install pacman's `cuda` / `cudnn`
|
|
|
|
Arch / EndeavourOS now ships **CUDA 13** (`libcublas.so.13`). The
|
|
`ctranslate2` 4.7.x backend that `faster-whisper` 1.x rides on top of
|
|
wants **CUDA 12** (`libcublas.so.12`) + **cuDNN 9** (`libcudnn.so.9`)
|
|
and will not load against the system's CUDA-13 libs.
|
|
|
|
Rather than fight the distro, this project pins the CUDA-12 runtime as
|
|
pip wheels in the venv via the `tower` extra (`nvidia-cublas-cu12`,
|
|
`nvidia-cudnn-cu12>=9,<10`). The `uv sync --extra tower` step in §2
|
|
brings them in automatically; the systemd `ExecStart` wrapper
|
|
(`deploy/tower/run-transcribe-worker.sh`) prepends those wheel-bundled
|
|
`.so` directories to `LD_LIBRARY_PATH` at start time.
|
|
|
|
If you've already installed pacman's `cuda` / `cudnn`, you can leave
|
|
them — the wrapper's `LD_LIBRARY_PATH` prepend wins for this service.
|
|
|
|
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
|
|
|
|
```bash
|
|
sudo mkdir -p /opt/projects/homelab
|
|
sudo chown trucktrav:trucktrav /opt/projects/homelab
|
|
cd /opt/projects/homelab
|
|
git clone gitea-pbs-trucktrav: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
|
|
```
|
|
|
|
`uv sync --extra tower` will pull ~1 GB of pinned CUDA-12 wheels
|
|
(`nvidia-cublas-cu12`, `nvidia-cudnn-cu12`, plus `faster-whisper` /
|
|
`ctranslate2`). First-run model download (~3 GB for large-v3) lands
|
|
under `~/.cache/huggingface/` the first time the worker transcribes;
|
|
pre-warm if you want:
|
|
|
|
```bash
|
|
# Run the wrapper so the CUDA-12 lib paths are pinned exactly the way
|
|
# the systemd service will see them — this is the cheapest end-to-end
|
|
# sanity check that the venv + LD_LIBRARY_PATH plumbing is correct.
|
|
deploy/tower/run-transcribe-worker.sh --once
|
|
```
|
|
|
|
Or just instantiate the model directly:
|
|
|
|
```bash
|
|
LD_LIBRARY_PATH="$(uv run python -c 'import os, nvidia.cublas, nvidia.cudnn; print(":".join(os.path.join(os.path.dirname(m.__file__),"lib") for m in [nvidia.cublas, nvidia.cudnn]))')" \
|
|
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.3` |
|
|
|
|
`db.wg.herbylab.dev` resolves to `10.99.0.1`. Confirm connectivity from
|
|
the tower:
|
|
|
|
```bash
|
|
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
|
|
|
|
systemd reads this BEFORE dropping to `User=herbyadmin`. The file is
|
|
**not** a shell init script — `export VAR=…` in your shell does not
|
|
apply here, and changes require `systemctl restart` to take effect.
|
|
|
|
There's a ready-to-scp file on **herbys-dev** with the real lovebug
|
|
password already populated, generated from
|
|
`/opt/backups/postgres-consolidation/credentials.env`:
|
|
|
|
```
|
|
/opt/backups/postgres-consolidation/second-brain-transcribe.env (mode 0600)
|
|
```
|
|
|
|
Pull it across instead of hand-copying the password:
|
|
|
|
```bash
|
|
# from the tower
|
|
scp herbys-dev:/opt/backups/postgres-consolidation/second-brain-transcribe.env /tmp/sb-env
|
|
sudo install -m 0600 -o root -g root /tmp/sb-env /etc/default/second-brain-transcribe
|
|
rm /tmp/sb-env
|
|
```
|
|
|
|
If you ever need to regenerate the dev-host file (e.g. after a password
|
|
rotation), the recipe is:
|
|
|
|
```bash
|
|
# on herbys-dev — never echoes the password, mode 0600
|
|
PW=$(grep ^LOVEBUG_PG_PASSWORD= /opt/backups/postgres-consolidation/credentials.env | cut -d= -f2-)
|
|
umask 077
|
|
echo "SECOND_BRAIN_DATABASE_URL=postgresql://lovebug:${PW}@db.wg.herbylab.dev:5432/petalbrain" \
|
|
> /opt/backups/postgres-consolidation/second-brain-transcribe.env
|
|
unset PW
|
|
```
|
|
|
|
The committed `deploy/tower/second-brain-transcribe.env.example`
|
|
documents the format and never carries a real secret.
|
|
|
|
---
|
|
|
|
## 5. systemd unit
|
|
|
|
```bash
|
|
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.
|
|
|
|
`ExecStart=` calls the in-repo `deploy/tower/run-transcribe-worker.sh`
|
|
wrapper, which resolves the venv's CUDA-12 lib dirs at runtime and
|
|
prepends them to `LD_LIBRARY_PATH` before exec-ing `uv run`. systemd
|
|
does NOT inherit your shell's `LD_LIBRARY_PATH`, so the wrapper is
|
|
how the CUDA-12 pin survives across reboots and python upgrades.
|
|
|
|
### Day-to-day commands
|
|
|
|
```bash
|
|
# 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_ID`s).
|