From 722883ab4a4583b799ce8bfd0cfad70b4b89daf9 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 12:00:03 +0100 Subject: docker: bake espeak-ng data for Piper TTS espeak-rs-sys installs espeak-ng-data into the build tree and compiles that path in as the default, so the runtime image failed with 'phontab: No such file or directory'. Copy the data from the builder to /usr/share and point PIPER_ESPEAKNG_DATA_DIRECTORY there; compose overrides the host path from .env. Also trim trailing slashes from Copyparty base URLs, document that env_file changes need --force-recreate, and clean inline comments in .env.example. --- .env.example | 15 +++++++++++++-- Dockerfile | 8 ++++++++ README.md | 26 +++++++++++++++++++++----- compose.yaml | 2 ++ src/copyparty/mod.rs | 5 +++-- 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 01e24b3..e67827d 100644 --- a/.env.example +++ b/.env.example @@ -3,17 +3,28 @@ TOKEN= # Optional - Bot Configuration STATUS_CHANNEL_ID= +SENATOR_DB_PATH=data/senator.db # Optional - Copyparty File Server (for music /search commands) +# No trailing slash; a trailing slash produces double slashes in built URLs. COPYPARTY_URL=http://localhost:8080 COPYPARTY_URL_UPLOAD=http://localhost:8080/uploads COPYPARTY_PASSWORD= # Optional - AI functionality -ASSISTANT_KEYWORD=jarvis # Keyword to trigger AI assistant +ASSISTANT_KEYWORD=jarvis +# Full chat-completions URL. For a local server running on the Docker host, +# use the host's LAN IP (localhost inside the container is the container). +LLM_API_URL=https://openrouter.ai/api/v1/chat/completions +LLM_API_KEY= +LLM_MODEL= +# Legacy OpenRouter fallbacks (LLM_* takes priority) OPENROUTER_API_KEY= OPENROUTER_MODEL= # Optional - Local TTS -TTS_MODEL_PATH=/path/to/models # or a full path to pl_PL-bass-high.onnx +# Directory containing .onnx, or a full path to the .onnx file. +TTS_MODEL_PATH=/path/to/piper TTS_VOICE=pl_PL-bass-high +# Parent directory of espeak-ng-data. Docker overrides this to /usr/share. +PIPER_ESPEAKNG_DATA_DIRECTORY=/path/to/piper diff --git a/Dockerfile b/Dockerfile index eaedc7f..891768a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,11 @@ RUN mkdir src \ COPY src ./src RUN cargo build --release +# espeak-rs-sys installs the espeak-ng data into the build directory and +# compiles that path in as the default. Without this the runtime image has no +# data files and TTS fails with "phontab: No such file or directory". +RUN cp -a target/release/build/espeak-rs-sys-*/out/share/espeak-ng-data /app/espeak-ng-data + # ── Runtime ─────────────────────────────────────────────────────────── FROM debian:trixie-slim @@ -65,11 +70,14 @@ RUN useradd --create-home --uid 1000 --shell /usr/sbin/nologin bot \ WORKDIR /app COPY --from=builder /app/target/release/senator-sperma /usr/local/bin/senator-sperma +# espeak looks for /espeak-ng-data. +COPY --from=builder /app/espeak-ng-data /usr/share/espeak-ng-data USER bot ENV TZ=Europe/Warsaw \ TTS_MODEL_PATH=/app/piper \ + PIPER_ESPEAKNG_DATA_DIRECTORY=/usr/share \ RUST_LOG=info \ HOME=/home/bot diff --git a/README.md b/README.md index e52c1b9..699066d 100644 --- a/README.md +++ b/README.md @@ -69,10 +69,11 @@ docker compose up -d --build Or without Compose: ```bash -docker build -t senator-sperma . +docker buildx build --load -t senator-sperma:latest . docker run -d --name senator-sperma --restart unless-stopped \ --env-file .env \ -e TTS_MODEL_PATH=/app/piper \ + -e PIPER_ESPEAKNG_DATA_DIRECTORY=/usr/share \ -v "$PWD/data:/app/data" \ -v "$PWD/piper:/app/piper:ro" \ senator-sperma @@ -86,14 +87,29 @@ Notes: overrides `TTS_MODEL_PATH` to `/app/piper`, so the absolute host path in `.env` is harmless. Put the `.onnx` and `.onnx.json` files there (the same directory used for local runs). +- The espeak-ng data used by Piper is baked into the image at + `/usr/share/espeak-ng-data`; the container sets + `PIPER_ESPEAKNG_DATA_DIRECTORY=/usr/share`. This overrides the host path in + `.env`. - The container runs as uid 1000. Make sure `data/` is writable by that uid. - `TZ` defaults to `Europe/Warsaw` for cron jobs; override it in `compose.yaml` or with `-e TZ=...` if needed. -- If Copyparty runs on the host, use `http://host.docker.internal:` in - `.env` and enable the commented `extra_hosts` entry in `compose.yaml`. +- If Copyparty or a local LLM runs on the host, use the host's LAN IP (for + example `http://10.0.0.3:2137/...`) and make sure the service listens on + `0.0.0.0` or that LAN IP, not only `127.0.0.1`. `localhost` inside the + container is the container itself. +- After editing `.env`, recreate the container: + `docker compose up -d --force-recreate`. `docker compose restart` does not + reload `env_file` values. Verify what the container actually sees with: + `docker compose exec senator-sperma env | grep -E 'LLM_API_URL|TTS_MODEL_PATH|PIPER_ESPEAKNG'` + and check the resolved LLM URL with `docker compose logs | grep 'LLM client'`. +- To test the LLM endpoint from inside the container: + `docker compose exec senator-sperma python3 -c 'import urllib.request; print(urllib.request.urlopen("http://10.0.0.3:2137/v1/models", timeout=5).status)'` + If that fails, the host service is probably listening only on `127.0.0.1`; + bind it to `0.0.0.0` (or the LAN IP) and allow the port through the firewall. - If the build fails to resolve `crates.io` or `deb.debian.org`, run - `docker build --network=host -t senator-sperma .`; `compose.yaml` already - sets `network: host` for builds on this host. + `docker buildx build --load --network=host -t senator-sperma:latest .`; + `compose.yaml` already sets `network: host` for builds on this host. - yt-dlp is fetched from the latest upstream release at image build time. Rebuild with `--no-cache` (`docker compose build --no-cache`) to refresh it. diff --git a/compose.yaml b/compose.yaml index eac326f..68b7cae 100644 --- a/compose.yaml +++ b/compose.yaml @@ -16,6 +16,8 @@ services: # The host .env points TTS_MODEL_PATH at an absolute host path, which # does not exist inside the container. The model is mounted below. TTS_MODEL_PATH: /app/piper + # espeak-ng data is baked into the image by the Dockerfile. + PIPER_ESPEAKNG_DATA_DIRECTORY: /usr/share # Override if the bot should not use Polish local time for cron jobs. # TZ: Europe/Warsaw volumes: diff --git a/src/copyparty/mod.rs b/src/copyparty/mod.rs index a9f7d01..d3620b2 100644 --- a/src/copyparty/mod.rs +++ b/src/copyparty/mod.rs @@ -147,8 +147,9 @@ pub struct UploadResponse { impl CopypartyClient { pub fn new(base_url: String, upload_url: String, password: Option) -> Self { Self { - base_url, - upload_url, + // A trailing slash would produce `//` when a path is appended. + base_url: base_url.trim_end_matches('/').to_string(), + upload_url: upload_url.trim_end_matches('/').to_string(), password, client: Client::new(), } -- cgit v1.2.3