From 35e754d6ee2d65b3fb58e84951327d0248c3e620 Mon Sep 17 00:00:00 2001 From: javis-bot Date: Thu, 11 Jun 2026 01:36:30 +0900 Subject: [PATCH] fix(docker): in-container Gemini CLI OAuth, broadcast audio, ports + hardening Makes the all-in-one image actually run the new real-time-search features and closes review gaps: - Gemini OAuth path: install Node 22 + @google/gemini-cli (pinned 0.46.0) in the image; mount a DEDICATED host dir (~/.config/javis/gemini) holding only the OAuth creds to /root/.gemini (not the whole ~/.gemini). Verified in-container: `gemini -p ... -o json` returns a grounded answer with no API key. - Broadcast audio: add PulseAudio + a headless null-sink (run-pulse.sh, new supervisor program); export XDG_RUNTIME_DIR/PULSE_SERVER so Chrome playback and the selfbot `ffmpeg -f pulse -i @DEFAULT_MONITOR@` share one daemon. Verified: default sink virtual_speaker, monitor present, ffmpeg capture OK. - Bind the brain bridge to 127.0.0.1 only (internal, unauthenticated API). - VNC host port is overridable; this server pins VNC_PORT=5902 (.env) since the host already runs Xvnc on 5901. Verified in-container with CDI GPU passthrough: RTX 5050 visible, NVENC encoders (h264/hevc/av1) available. Co-Authored-By: Claude Opus 4.7 --- Dockerfile | 12 ++++++++++++ docker-compose.yml | 16 ++++++++++++++-- docker/entrypoint.sh | 8 +++++++- docker/run-pulse.sh | 23 +++++++++++++++++++++++ docker/supervisord.conf | 9 +++++++++ 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100755 docker/run-pulse.sh diff --git a/Dockerfile b/Dockerfile index 2c07557..8d09629 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ 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/* @@ -31,6 +32,17 @@ RUN wget -q -O /tmp/chrome.deb https://dl.google.com/linux/direct/google-chrome- # --- 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 \ diff --git a/docker-compose.yml b/docker-compose.yml index 224d8ce..70e582e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -69,14 +69,26 @@ services: shm_size: "1gb" # Chrome needs a larger /dev/shm ports: # Host ports are overridable. If the HOST already runs VNC on 5901 - # (see docs/vnc-xfce-setup.md), set VNC_PORT=5902 in .env. + # (see docs/vnc-xfce-setup.md), set VNC_PORT=5902 in .env — this server + # does, so .env pins VNC_PORT=5902. - "${VNC_PORT:-5901}:5901" # VNC - "${NOVNC_PORT:-6080}:6080" # noVNC (open in a browser) - - "${BRIDGE_PORT:-8765}:8765" # brain bridge (usually internal-only) + # Brain bridge: bind to loopback only — it is an internal bot<->brain API + # with no auth and must not be reachable off-host. + - "127.0.0.1:${BRIDGE_PORT:-8765}:8765" volumes: - javis_data:/data # jarvis db + memory - whisper_cache:/root/.cache/huggingface # cached Whisper models - piper_voices:/opt/piper-voices # TTS voices + # Gemini account login for GEMINI_AUTH=oauth real-time search. Mounts a + # DEDICATED dir holding only the Gemini OAuth creds (not the whole + # ~/.gemini), so the container can refresh its token without exposing + # unrelated host state. Seed it once with the host login: + # mkdir -p ~/.config/javis/gemini + # cp ~/.gemini/oauth_creds.json ~/.config/javis/gemini/ + # Override GEMINI_OAUTH_DIR to point elsewhere. Only used when + # GEMINI_AUTH=oauth. + - ${GEMINI_OAUTH_DIR:-${HOME}/.config/javis/gemini}:/root/.gemini volumes: ollama_models: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 130a913..acc6ce3 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -20,9 +20,15 @@ set -euo pipefail : "${PIPER_VOICE_DIR:=/opt/piper-voices}" : "${TTS_PIPER_MODEL_PATH:=${PIPER_VOICE_DIR}/${PIPER_VOICE}.onnx}" +# Audio: shared PulseAudio socket so Chrome (playback) and the selfbot ffmpeg +# (capture of @DEFAULT_MONITOR@) reach the same headless daemon (run-pulse.sh). +: "${XDG_RUNTIME_DIR:=/run/user/0}" +: "${PULSE_SERVER:=unix:${XDG_RUNTIME_DIR}/pulse/native}" + export VNC_RESOLUTION OLLAMA_BASE_URL OLLAMA_CHAT_MODEL OLLAMA_EMBED_MODEL \ WHISPER_MODEL WHISPER_DEVICE WHISPER_COMPUTE_TYPE JARVIS_DB_PATH \ - PIPER_VOICE PIPER_VOICE_DIR TTS_PIPER_MODEL_PATH BRIDGE_HOST BRIDGE_PORT + PIPER_VOICE PIPER_VOICE_DIR TTS_PIPER_MODEL_PATH BRIDGE_HOST BRIDGE_PORT \ + XDG_RUNTIME_DIR PULSE_SERVER mkdir -p /data /app/config "$(dirname "$JARVIS_DB_PATH")" diff --git a/docker/run-pulse.sh b/docker/run-pulse.sh new file mode 100755 index 0000000..8da56b3 --- /dev/null +++ b/docker/run-pulse.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Headless PulseAudio for the broadcast audio path. +# +# The selfbot streamer captures desktop audio with `ffmpeg -f pulse -i +# @DEFAULT_MONITOR@` (bot/src/stream/selfbot.ts). In a container there is no +# real sound card, so we run a PulseAudio daemon with a null sink set as the +# default: Chrome/desktop play into it, and its `.monitor` source is what +# ffmpeg captures. Without this the broadcast fails to start when STREAM_AUDIO=1. +set -e + +export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/0}" +mkdir -p "$XDG_RUNTIME_DIR/pulse" +chmod 700 "$XDG_RUNTIME_DIR" + +exec pulseaudio \ + --exit-idle-time=-1 \ + --disallow-exit \ + --disable-shm \ + -n \ + --load="module-native-protocol-unix auth-anonymous=1 socket=${XDG_RUNTIME_DIR}/pulse/native" \ + --load="module-null-sink sink_name=virtual_speaker sink_properties=device.description=Virtual_Speaker" \ + --load="module-always-sink" \ + --log-target=stderr diff --git a/docker/supervisord.conf b/docker/supervisord.conf index 59a0a75..d178a35 100644 --- a/docker/supervisord.conf +++ b/docker/supervisord.conf @@ -22,6 +22,15 @@ stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 +[program:pulse] +command=/app/docker/run-pulse.sh +priority=150 +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + [program:xfce] command=/app/docker/run-xfce.sh priority=200