diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f500a2..262443a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi --- +## [1.28.0] — 2026-09-14 + +### Added +- **Second deployment: `correspondence-nuc` (192.168.178.19), live alongside the DGX Spark instance.** No TTS/OCR engines of its own — every generation and OCR call crosses the LAN to the DGX, which required pinning `vision_ocr_url`/`vision_ocr_fallback_url` explicitly (their default, `host.docker.internal`, resolves to whichever host the container happens to run on, which is silently wrong off the DGX). Voice library is NFS-mounted read-write from the DGX's own directory (`/etc/exports` on the DGX, persisted in `/etc/fstab` on the NUC) — confirmed the NUC instance sees the exact same 344 voices, never a separate copy that could drift. + - Deployed by building directly on the NUC from an rsynced copy of the working tree, rather than pulling the published `ghcr.io` image — that registry copy turned out to be both several versions stale and private with no credentials configured on the NUC. + - New `docker-compose.nuc.yml`, tracked in this repo as the reviewable reference for that host (the existing `docker-compose.yml` stays the DGX's own — different build/network/socket-proxy shape). + - Accepted limitation carried over from planning: a voice change still needs a manual restart of the DGX's TTS containers — no `docker-socket-proxy` on the NUC instance, since cross-host container control was never going to work through it anyway. + +--- + ## [1.27.5] — 2026-09-13 ### Fixed diff --git a/PROJECT_MAP.md b/PROJECT_MAP.md index 0a49d2a..8f5a659 100644 --- a/PROJECT_MAP.md +++ b/PROJECT_MAP.md @@ -10,10 +10,22 @@ features are mature and in daily use. ## Deployment -Self-hosted Docker on the DGX Spark, port 7890, image -`ghcr.io/martin-b78/tts-voice-creator-clone-and-design-2`. -Python and static files are bind-mounted, so code changes need only a container -recreate; rebuild and push only when `Dockerfile` or `requirements.txt` change. +Two self-hosted Docker instances, both port 7890: + +- **DGX Spark** (`192.168.178.8`) — primary. `docker-compose.yml` builds locally and tags the result + `ghcr.io/martin-b78/tts-voice-creator-clone-and-design-2`, but that registry copy is not kept current on every + deploy (several versions behind as of 2026-09-14) — treat it as a snapshot, not a source of truth. Python and + static files are bind-mounted, so a code change needs only a container recreate; rebuild only when `Dockerfile` + or `requirements.txt` change. +- **`correspondence-nuc`** (`192.168.178.19`) — secondary, live since 2026-09-14. No TTS/OCR engines of its own; + every generation call crosses the LAN to the DGX (`docker-compose.nuc.yml`, tracked here, pins + `vision_ocr_url`/`vision_ocr_fallback_url` explicitly to the DGX's IP — their default resolves relative to + whichever host the container runs on, silently wrong here otherwise). Voice library is the DGX's own directory, + NFS-mounted read-write (`/etc/exports` on the DGX, `/etc/fstab` on the NUC) — never a separate copy that could + drift. Deployed by rsyncing the working tree (excluding `.git`, `config/`, `logs/`) and building on the NUC + itself, since the `ghcr.io` image is both stale and private with no registry login there. See TODO.md's + "P2 — deployment" entry for the full setup and the one accepted limitation (voice-change auto-restart of the + DGX's TTS containers doesn't reach across hosts — manual restart on the DGX, same as before this existed). ## Layout diff --git a/TODO.md b/TODO.md index fa346a0..ed200c1 100644 --- a/TODO.md +++ b/TODO.md @@ -97,23 +97,29 @@ architecture change: the larger Audiobook/Rehearser/style split. ### P2 — deployment -- [ ] **S — Deploy the app to `correspondence-nuc` (192.168.178.19), pointed at the DGX Spark's TTS engine over the - network.** Blocked on sudo access on the DGX (no passwordless sudo in the Claude session): needs - `nfs-kernel-server` installed and `/home/sparky/Media/_Sounds/TTS_Voices` exported read-write to - `192.168.178.19` only, so the NUC's voice library never drifts from what the DGX's TTS engine actually has on - disk (the NUC only NFS-mounts Synology shares today, nothing from the DGX). `tts_url`/`vision_ocr_url` settings - already default to the DGX's LAN IP (`192.168.178.8`) and OpenAI-compatible network calls, so once the mount - exists this is mostly a new compose file + `docker compose up -d` on the NUC. Accepted limitation: voice-change - auto-restart of the DGX's TTS containers (`TTS_CONTAINER_NAME(S)`) won't work cross-host from a NUC-hosted app - instance, since `docker-socket-proxy` there only controls the NUC's own Docker socket — that's a manual restart - on the DGX after a voice change, same as today. - Commands needed on the DGX (run manually, or grant this session sudo to run them): - ``` - sudo apt-get install -y nfs-kernel-server - echo "/home/sparky/Media/_Sounds/TTS_Voices 192.168.178.19(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports - sudo exportfs -ra - sudo systemctl enable --now nfs-kernel-server - ``` +- [x] **S — Deploy the app to `correspondence-nuc` (192.168.178.19), pointed at the DGX Spark's TTS engine over the + network.** Live since 2026-09-14, `v1.27.5`. NFS export set up on the DGX (`/home/sparky/Media/_Sounds/TTS_Voices` + → `192.168.178.19` only, `rw,no_root_squash`), mounted read-write on the NUC at `/mnt/dgx-tts-voices` and + persisted in `/etc/fstab` there — confirmed the NUC instance sees the same 344 voices the DGX does, not a + separate copy. Deployed by rsyncing the working tree from the DGX (excluding `.git`, `config/`, `logs/` and other + local-only paths) to `/home/sparky/Docker/tts-voice-creator-clone-and-design-2` on the NUC and building the image + there directly — the published `ghcr.io` image turned out to be both several versions stale and private with no + registry credentials on the NUC, not worth setting up for an image that would need re-pushing on every deploy + anyway when the source is right there to build from. + **One real gap found along the way:** `vision_ocr_url`/`vision_ocr_fallback_url` default to + `host.docker.internal`, which resolves to *whichever host the container is running on* — correct on the DGX + itself, silently wrong on the NUC (would have pointed at the NUC's own, nonexistent OCR engine). Pinned both to + the DGX's LAN IP explicitly via environment overrides in the NUC's compose file. + **Also needed:** the NUC's `config`/`logs` bind-mount directories were created root-owned by `docker compose` + (this session's NUC access is a root SSH key) while the container's own `app` user is uid 1000 — `chown -R + 1000:1000` on both before the app could open its SQLite state DB. Confirmed the accepted limitation from before + still holds as expected: no `docker-socket-proxy` on the NUC instance (nothing local for it to control, and + cross-host container restart never worked anyway), so a voice change still needs a manual restart on the DGX. + NUC compose file tracked here as `docker-compose.nuc.yml` (the plain `docker-compose.yml` in this repo stays the + DGX's own, per its `build:`/`dgx_net`/`docker-socket-proxy` shape) — deployed to the NUC as its + `docker-compose.yml`, since that host has no `.git` checkout of its own to diff against, only the rsynced tree. + A code change means: re-rsync the tree (excluding `.git`, `config/`, `logs/`), `docker compose build`, `docker + compose up -d` — no separate push/pull step, same as the DGX's own bind-mount-and-recreate pattern. ## Deferred — public multi-user readiness (cross-app initiative) diff --git a/VERSION b/VERSION index 694a1ca..3c71e47 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.27.5 \ No newline at end of file +1.28.0 \ No newline at end of file diff --git a/docker-compose.nuc.yml b/docker-compose.nuc.yml new file mode 100644 index 0000000..ac248da --- /dev/null +++ b/docker-compose.nuc.yml @@ -0,0 +1,68 @@ +services: + tts-voice-creator-clone-and-design-2: + # Built locally rather than pulled: the ghcr.io image is private (this + # host has no registry credentials) and, checked on the DGX, several + # versions stale besides. The full source is already deployed here + # (see the deploy step that rsyncs it from the DGX) -- build directly + # from it instead of standing up registry auth for a copy that would + # need pushing to stay current anyway. + build: + context: . + dockerfile: Dockerfile + container_name: tts-voice-creator-clone-and-design-2 + restart: unless-stopped + + # Same hardening as the DGX deployment: image filesystem stays + # immutable, the app writes only to the explicit mounts below. + read_only: true + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + tmpfs: + - /tmp:size=1g,mode=1777 + + mem_limit: "${APP_MEM_LIMIT:-4g}" + mem_reservation: "${APP_MEM_RESERVATION:-1g}" + + ports: + - "7890:7890" + + volumes: + # NFS-exported from the DGX (see /etc/exports there) so this instance + # shares exactly the voice library the DGX's TTS engine has on disk -- + # never a separate copy that could drift. + - ${VOICE_HOST_DIR:-/mnt/dgx-tts-voices}:/voices:rw + - ./config:/home/app/.config/tts-voice-creator:rw # settings, presets, routes + - ./logs:/logs:rw # application logs + # Live-editable without a rebuild -- same pattern as the DGX compose. + - ./static:/app/static:ro + - ./server.py:/app/server.py:ro + - ./core:/app/core:ro + - ./routes:/app/routes:ro + - ./workflows:/app/workflows:ro + - ./VERSION:/app/VERSION:ro + - ./CHANGELOG.md:/app/CHANGELOG.md:ro + + environment: + - PYTHONUNBUFFERED=1 + - VOICES_DIR=/voices + - OUTPUT_DIR=/voices/active_voices + # Most engine defaults already point at the DGX's real LAN IP and work + # unmodified from any host. Vision OCR is the one exception: its + # default is host.docker.internal, which resolves to whichever HOST + # the container happens to run on -- correct from the DGX itself, but + # on the NUC that would mean the NUC's own (nonexistent) OCR engine + # rather than the DGX's. Pin both explicitly to the DGX. + - VISION_OCR_URL=http://192.168.178.8:8098/v1 + - VISION_OCR_FALLBACK_URL=http://192.168.178.8:8099/v1 + # + # No docker-socket-proxy / DOCKER_HOST here on purpose: this instance + # has no local Docker socket to control, and the DGX's TTS containers + # can't be restarted cross-host through it either way. Voice-change + # auto-restart is an accepted manual step on the DGX after a change -- + # same as documented for the DGX deployment itself. Docker-dependent + # diagnostics on this instance report unavailable rather than failing. + + extra_hosts: + - "host.docker.internal:host-gateway"