Files
javis_bot/Dockerfile
javis-bot b17961e9e3 feat(tts): add MeloTTS Korean voice via warm worker with offline-baked cache
Adds a dedicated MeloTTS Korean voice (speed 1.5) as the primary TTS engine,
served by a long-lived in-container worker so each Discord turn pays only
inference cost, not model-load cost.

- bridge/melo_worker.py: tiny HTTP service in its own /opt/melo py3.11 venv,
  keeps the KR model warm, returns PCM16 WAV on POST /synth.
- bridge/server.py: synthesize() routes to the melo worker first; Piper stays
  as an opt-in fallback (MELO_FALLBACK_PIPER, default off so Korean is never
  mangled through the English voice). /health reports tts_engine.
- docker/setup-melo.sh: builds the isolated venv (pinned torch 2.12.0 /
  torchaudio 2.11.0 CPU, MeloTTS pinned to a commit for reproducible rebuilds),
  pre-fetches mecab-ko, and warms a dedicated HF cache (/opt/melo-cache) with a
  real KR synth so all BERT + KR checkpoint assets are baked into the image.
- docker/supervisord.conf: runs melo-worker before the bridge with
  HF_HOME=/opt/melo-cache (the whisper_cache volume shadows the default HF
  cache) plus HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE so it reads the baked cache
  and never retries the network on load.
- Dockerfile/.env.example: wire the melo build layer and config knobs.

Verified: offline synth passes with --network none and the prod volume mounted;
prod container recreated, all supervisord services up, bot logged in, and an
end-to-end /tts call returns a 44.1kHz mono PCM16 WAV.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-12 19:01:54 +09:00

82 lines
3.6 KiB
Docker

# ============================================================================
# Javis Bot — all-in-one container
# VNC + XFCE desktop + Chrome + Python brain bridge + Node/bun Discord bot.
# Ollama (the LLM backend) runs as a separate service (see docker-compose.yml).
# ============================================================================
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive \
LANG=C.UTF-8 \
DISPLAY=:1 \
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \
PATH=/opt/venv/bin:/root/.bun/bin:/usr/local/bin:/usr/bin:/bin \
NVIDIA_VISIBLE_DEVICES=all \
NVIDIA_DRIVER_CAPABILITIES=compute,utility
# --- System packages: desktop, VNC, Chrome deps, ffmpeg, python, ocr ---
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl wget gnupg unzip procps \
tigervnc-standalone-server tigervnc-common tigervnc-tools \
xfce4 xfce4-goodies dbus-x11 x11-utils xfonts-base \
fonts-noto-cjk fonts-noto-cjk-extra fonts-nanum \
ffmpeg tesseract-ocr \
pulseaudio pulseaudio-utils \
python3 python3-venv python3-pip \
novnc websockify supervisor gettext-base \
&& rm -rf /var/lib/apt/lists/*
# --- Google Chrome (stable) ---
RUN wget -q -O /tmp/chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
&& (apt-get update && apt-get install -y --no-install-recommends /tmp/chrome.deb || (apt-get -f install -y)) \
&& rm -f /tmp/chrome.deb && rm -rf /var/lib/apt/lists/*
# --- bun (Discord bot runtime/package manager) ---
RUN curl -fsSL https://bun.sh/install | bash
# --- Node.js + Gemini CLI (the GEMINI_AUTH=oauth real-time search path shells
# out to `gemini`; it needs a real Node runtime, not bun). The account
# login itself is provided at runtime via the mounted ~/.gemini (see
# docker-compose.yml); here we only install the binary. ---
ARG GEMINI_CLI_VERSION=0.46.0
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& npm install -g "@google/gemini-cli@${GEMINI_CLI_VERSION}" \
&& rm -rf /var/lib/apt/lists/* \
&& gemini --version
# --- Python brain/bridge deps (slim set) ---
COPY bridge/requirements-bridge.txt /app/bridge/requirements-bridge.txt
RUN python3 -m venv /opt/venv \
&& /opt/venv/bin/pip install --no-cache-dir --upgrade pip \
&& /opt/venv/bin/pip install --no-cache-dir -r /app/bridge/requirements-bridge.txt
# faster-whisper>=1.1 / ctranslate2 self-locates these pip CUDA libs at runtime,
# so no hardcoded LD_LIBRARY_PATH is needed. We also register them with the
# dynamic linker (full path to ldconfig; PATH excludes /sbin) as a robust
# fallback. Path-glob is python-version agnostic.
RUN ls -d /opt/venv/lib/python*/site-packages/nvidia/cublas/lib \
/opt/venv/lib/python*/site-packages/nvidia/cudnn/lib \
> /etc/ld.so.conf.d/nvidia-cu12.conf 2>/dev/null \
&& /sbin/ldconfig || true
# --- MeloTTS Korean voice (separate /opt/melo py3.11 venv; see setup-melo.sh).
# Heavy layer (torch CPU + transformers + MeCab); placed before the app
# COPY so it stays cached across source-only changes. ---
COPY docker/setup-melo.sh /app/docker/setup-melo.sh
RUN bash /app/docker/setup-melo.sh
# --- Discord bot deps (cache layer on lockfile) ---
COPY bot/package.json bot/bun.lock /app/bot/
RUN cd /app/bot && bun install --frozen-lockfile || bun install
# --- App source ---
COPY . /app
WORKDIR /app
# --- Default Piper voice (best-effort at build; entrypoint retries if absent) ---
RUN bash docker/download-piper.sh || true
EXPOSE 5901 6080 8765
ENTRYPOINT ["/app/docker/entrypoint.sh"]