From a82489c95f8c8f3e55f92939224599598bd0de96 Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 27 Oct 2026 18:35:58 +0100 Subject: docker: build and run on Debian trixie Add a multi-stage image that compiles inside Debian trixie and runs on the same glibc/libssl/libopus, avoiding the Arch-built binary failing on Debian. The runtime image installs ffmpeg, qalc, the shared libraries, and the latest yt-dlp zipapp, and runs as uid 1000. compose.yaml mounts data/ and piper/, overrides TTS_MODEL_PATH for the container layout, and builds with host networking because the default Docker bridge DNS cannot resolve crates.io or deb.debian.org on this host. --- .dockerignore | 12 ++++++++++ Dockerfile | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 41 ++++++++++++++++++++++++++++++++ compose.yaml | 27 +++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 compose.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0a22f10 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.gitignore +.zed +.venv +target +data +piper +models +.env +docs +tests +*.wav diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..eaedc7f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,76 @@ +# Build on the same Debian release as the runtime image. This pins glibc, +# libssl and libopus, so a binary built on a rolling-release host (Arch, etc.) +# no longer fails on Debian due to newer toolchain/glibc symbols. +FROM rust:1-trixie AS builder + +ENV DEBIAN_FRONTEND=noninteractive + +# Build dependencies: +# - cmake + clang + libclang-dev: espeak-rs-sys (pulled in by piper-rs) builds +# espeak-ng and runs bindgen +# - pkg-config + libopus-dev: libopus_sys +# - libssl-dev: native-tls / openssl-sys +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + cmake \ + clang \ + libclang-dev \ + pkg-config \ + libopus-dev \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Compile the dependency graph once with a dummy main so source edits do not +# invalidate the expensive piper/espeak/onnxruntime build. +COPY Cargo.toml Cargo.lock build.rs ./ +RUN mkdir src \ + && printf 'fn main() {}\n' > src/main.rs \ + && cargo build --release \ + && rm -rf src + +COPY src ./src +RUN cargo build --release + +# ── Runtime ─────────────────────────────────────────────────────────── +FROM debian:trixie-slim + +ENV DEBIAN_FRONTEND=noninteractive + +# Runtime libraries reported by `readelf -d`: libstdc++, libopus, libssl, +# libcrypto. ffmpeg/qalc/yt-dlp are invoked as subprocesses. +# Full python3 (not python3-minimal) is required by the yt-dlp zipapp. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + ffmpeg \ + libopus0 \ + libssl3t64 \ + libstdc++6 \ + passwd \ + python3 \ + qalc \ + tzdata \ + && rm -rf /var/lib/apt/lists/* + +# yt-dlp is distributed as a self-contained Python zipapp; using the latest +# release avoids the stale Debian package breaking on YouTube changes. +ADD https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp /usr/local/bin/yt-dlp +RUN chmod 755 /usr/local/bin/yt-dlp + +RUN useradd --create-home --uid 1000 --shell /usr/sbin/nologin bot \ + && mkdir -p /app/data /app/piper /tmp/senator_sperma \ + && chown -R bot:bot /app /tmp/senator_sperma + +WORKDIR /app +COPY --from=builder /app/target/release/senator-sperma /usr/local/bin/senator-sperma + +USER bot + +ENV TZ=Europe/Warsaw \ + TTS_MODEL_PATH=/app/piper \ + RUST_LOG=info \ + HOME=/home/bot + +ENTRYPOINT ["/usr/local/bin/senator-sperma"] diff --git a/README.md b/README.md index 41ef0c3..e52c1b9 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,47 @@ bot falls back to Google Translate TTS. cargo run --release ``` +## Docker + +The image builds and runs on Debian trixie, so it does not depend on the +host's gcc/glibc version. This is the recommended way to deploy on Debian +when developing on a rolling-release distro. + +```bash +docker compose up -d --build +``` + +Or without Compose: + +```bash +docker build -t senator-sperma . +docker run -d --name senator-sperma --restart unless-stopped \ + --env-file .env \ + -e TTS_MODEL_PATH=/app/piper \ + -v "$PWD/data:/app/data" \ + -v "$PWD/piper:/app/piper:ro" \ + senator-sperma +``` + +Notes: + +- `data/` holds the SQLite database and is mounted for persistence. Create it + first (`mkdir -p data`) so it is owned by your user rather than root. +- `piper/` holds the Piper model and is mounted read-only; the container + 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 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 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. +- 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. + ## Commands Slash commands are autoregistered globally on startup. Some are registered diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..eac326f --- /dev/null +++ b/compose.yaml @@ -0,0 +1,27 @@ +services: + senator-sperma: + build: + context: . + # This host's default Docker bridge DNS cannot resolve crates.io / + # deb.debian.org, so build on the host network. Remove this line if + # your Docker setup resolves DNS normally. + network: host + image: senator-sperma:latest + container_name: senator-sperma + restart: unless-stopped + init: true + env_file: + - .env + environment: + # 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 + # Override if the bot should not use Polish local time for cron jobs. + # TZ: Europe/Warsaw + volumes: + - ./data:/app/data + - ./piper:/app/piper:ro + # If Copyparty runs on the host, uncomment so the container can reach it + # (then use http://host.docker.internal: in .env): + # extra_hosts: + # - "host.docker.internal:host-gateway" -- cgit v1.2.3