aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-28 12:00:03 +0100
committersalmonik <salmonik@tuta.io>2026-10-28 12:00:03 +0100
commit722883ab4a4583b799ce8bfd0cfad70b4b89daf9 (patch)
tree8b19ff073d3981ebc4e3a5428cd953463fa5c46e
parentcontextual play should add to the last in the queue (diff)
downloadsenator-sperma-722883ab4a4583b799ce8bfd0cfad70b4b89daf9.tar.gz
senator-sperma-722883ab4a4583b799ce8bfd0cfad70b4b89daf9.zip
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.
-rw-r--r--.env.example15
-rw-r--r--Dockerfile8
-rw-r--r--README.md26
-rw-r--r--compose.yaml2
-rw-r--r--src/copyparty/mod.rs5
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=
3 3
4# Optional - Bot Configuration 4# Optional - Bot Configuration
5STATUS_CHANNEL_ID= 5STATUS_CHANNEL_ID=
6SENATOR_DB_PATH=data/senator.db
6 7
7# Optional - Copyparty File Server (for music /search commands) 8# Optional - Copyparty File Server (for music /search commands)
9# No trailing slash; a trailing slash produces double slashes in built URLs.
8COPYPARTY_URL=http://localhost:8080 10COPYPARTY_URL=http://localhost:8080
9COPYPARTY_URL_UPLOAD=http://localhost:8080/uploads 11COPYPARTY_URL_UPLOAD=http://localhost:8080/uploads
10COPYPARTY_PASSWORD= 12COPYPARTY_PASSWORD=
11 13
12# Optional - AI functionality 14# Optional - AI functionality
13ASSISTANT_KEYWORD=jarvis # Keyword to trigger AI assistant 15ASSISTANT_KEYWORD=jarvis
16# Full chat-completions URL. For a local server running on the Docker host,
17# use the host's LAN IP (localhost inside the container is the container).
18LLM_API_URL=https://openrouter.ai/api/v1/chat/completions
19LLM_API_KEY=
20LLM_MODEL=
21# Legacy OpenRouter fallbacks (LLM_* takes priority)
14OPENROUTER_API_KEY= 22OPENROUTER_API_KEY=
15OPENROUTER_MODEL= 23OPENROUTER_MODEL=
16 24
17# Optional - Local TTS 25# Optional - Local TTS
18TTS_MODEL_PATH=/path/to/models # or a full path to pl_PL-bass-high.onnx 26# Directory containing <voice>.onnx, or a full path to the .onnx file.
27TTS_MODEL_PATH=/path/to/piper
19TTS_VOICE=pl_PL-bass-high 28TTS_VOICE=pl_PL-bass-high
29# Parent directory of espeak-ng-data. Docker overrides this to /usr/share.
30PIPER_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 \
33COPY src ./src 33COPY src ./src
34RUN cargo build --release 34RUN cargo build --release
35 35
36# espeak-rs-sys installs the espeak-ng data into the build directory and
37# compiles that path in as the default. Without this the runtime image has no
38# data files and TTS fails with "phontab: No such file or directory".
39RUN cp -a target/release/build/espeak-rs-sys-*/out/share/espeak-ng-data /app/espeak-ng-data
40
36# ── Runtime ─────────────────────────────────────────────────────────── 41# ── Runtime ───────────────────────────────────────────────────────────
37FROM debian:trixie-slim 42FROM debian:trixie-slim
38 43
@@ -65,11 +70,14 @@ RUN useradd --create-home --uid 1000 --shell /usr/sbin/nologin bot \
65 70
66WORKDIR /app 71WORKDIR /app
67COPY --from=builder /app/target/release/senator-sperma /usr/local/bin/senator-sperma 72COPY --from=builder /app/target/release/senator-sperma /usr/local/bin/senator-sperma
73# espeak looks for <PIPER_ESPEAKNG_DATA_DIRECTORY>/espeak-ng-data.
74COPY --from=builder /app/espeak-ng-data /usr/share/espeak-ng-data
68 75
69USER bot 76USER bot
70 77
71ENV TZ=Europe/Warsaw \ 78ENV TZ=Europe/Warsaw \
72 TTS_MODEL_PATH=/app/piper \ 79 TTS_MODEL_PATH=/app/piper \
80 PIPER_ESPEAKNG_DATA_DIRECTORY=/usr/share \
73 RUST_LOG=info \ 81 RUST_LOG=info \
74 HOME=/home/bot 82 HOME=/home/bot
75 83
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
69Or without Compose: 69Or without Compose:
70 70
71```bash 71```bash
72docker build -t senator-sperma . 72docker buildx build --load -t senator-sperma:latest .
73docker run -d --name senator-sperma --restart unless-stopped \ 73docker run -d --name senator-sperma --restart unless-stopped \
74 --env-file .env \ 74 --env-file .env \
75 -e TTS_MODEL_PATH=/app/piper \ 75 -e TTS_MODEL_PATH=/app/piper \
76 -e PIPER_ESPEAKNG_DATA_DIRECTORY=/usr/share \
76 -v "$PWD/data:/app/data" \ 77 -v "$PWD/data:/app/data" \
77 -v "$PWD/piper:/app/piper:ro" \ 78 -v "$PWD/piper:/app/piper:ro" \
78 senator-sperma 79 senator-sperma
@@ -86,14 +87,29 @@ Notes:
86 overrides `TTS_MODEL_PATH` to `/app/piper`, so the absolute host path in 87 overrides `TTS_MODEL_PATH` to `/app/piper`, so the absolute host path in
87 `.env` is harmless. Put the `.onnx` and `.onnx.json` files there (the same 88 `.env` is harmless. Put the `.onnx` and `.onnx.json` files there (the same
88 directory used for local runs). 89 directory used for local runs).
90- The espeak-ng data used by Piper is baked into the image at
91 `/usr/share/espeak-ng-data`; the container sets
92 `PIPER_ESPEAKNG_DATA_DIRECTORY=/usr/share`. This overrides the host path in
93 `.env`.
89- The container runs as uid 1000. Make sure `data/` is writable by that uid. 94- The container runs as uid 1000. Make sure `data/` is writable by that uid.
90- `TZ` defaults to `Europe/Warsaw` for cron jobs; override it in 95- `TZ` defaults to `Europe/Warsaw` for cron jobs; override it in
91 `compose.yaml` or with `-e TZ=...` if needed. 96 `compose.yaml` or with `-e TZ=...` if needed.
92- If Copyparty runs on the host, use `http://host.docker.internal:<port>` in 97- If Copyparty or a local LLM runs on the host, use the host's LAN IP (for
93 `.env` and enable the commented `extra_hosts` entry in `compose.yaml`. 98 example `http://10.0.0.3:2137/...`) and make sure the service listens on
99 `0.0.0.0` or that LAN IP, not only `127.0.0.1`. `localhost` inside the
100 container is the container itself.
101- After editing `.env`, recreate the container:
102 `docker compose up -d --force-recreate`. `docker compose restart` does not
103 reload `env_file` values. Verify what the container actually sees with:
104 `docker compose exec senator-sperma env | grep -E 'LLM_API_URL|TTS_MODEL_PATH|PIPER_ESPEAKNG'`
105 and check the resolved LLM URL with `docker compose logs | grep 'LLM client'`.
106- To test the LLM endpoint from inside the container:
107 `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)'`
108 If that fails, the host service is probably listening only on `127.0.0.1`;
109 bind it to `0.0.0.0` (or the LAN IP) and allow the port through the firewall.
94- If the build fails to resolve `crates.io` or `deb.debian.org`, run 110- If the build fails to resolve `crates.io` or `deb.debian.org`, run
95 `docker build --network=host -t senator-sperma .`; `compose.yaml` already 111 `docker buildx build --load --network=host -t senator-sperma:latest .`;
96 sets `network: host` for builds on this host. 112 `compose.yaml` already sets `network: host` for builds on this host.
97- yt-dlp is fetched from the latest upstream release at image build time. 113- yt-dlp is fetched from the latest upstream release at image build time.
98 Rebuild with `--no-cache` (`docker compose build --no-cache`) to refresh it. 114 Rebuild with `--no-cache` (`docker compose build --no-cache`) to refresh it.
99 115
diff --git a/compose.yaml b/compose.yaml
index eac326f..68b7cae 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -16,6 +16,8 @@ services:
16 # The host .env points TTS_MODEL_PATH at an absolute host path, which 16 # The host .env points TTS_MODEL_PATH at an absolute host path, which
17 # does not exist inside the container. The model is mounted below. 17 # does not exist inside the container. The model is mounted below.
18 TTS_MODEL_PATH: /app/piper 18 TTS_MODEL_PATH: /app/piper
19 # espeak-ng data is baked into the image by the Dockerfile.
20 PIPER_ESPEAKNG_DATA_DIRECTORY: /usr/share
19 # Override if the bot should not use Polish local time for cron jobs. 21 # Override if the bot should not use Polish local time for cron jobs.
20 # TZ: Europe/Warsaw 22 # TZ: Europe/Warsaw
21 volumes: 23 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 {
147impl CopypartyClient { 147impl CopypartyClient {
148 pub fn new(base_url: String, upload_url: String, password: Option<String>) -> Self { 148 pub fn new(base_url: String, upload_url: String, password: Option<String>) -> Self {
149 Self { 149 Self {
150 base_url, 150 // A trailing slash would produce `//` when a path is appended.
151 upload_url, 151 base_url: base_url.trim_end_matches('/').to_string(),
152 upload_url: upload_url.trim_end_matches('/').to_string(),
152 password, 153 password,
153 client: Client::new(), 154 client: Client::new(),
154 } 155 }