feat: split-deployment roles (browser-host on LAN + remote bot)

Add JARVIS_ROLE (full|browser|bot) via a run-if-role.sh supervisord guard so
one image serves three layouts. Make Chrome CDP bind configurable (CDP_BIND)
and publishable on the LAN (CDP_PUBLISH_BIND) so a bot on another PC can drive
this host's on-screen Chrome over the internal network (no auth, as requested).
This commit is contained in:
javis-bot
2026-06-15 10:23:55 +09:00
parent bdb012fc7c
commit 1935c1a6bc
4 changed files with 46 additions and 9 deletions

View File

@@ -79,6 +79,18 @@ services:
STT_LANGUAGE: ${STT_LANGUAGE:-ko}
VOICE_SILENCE_MS: ${VOICE_SILENCE_MS:-600}
BRIDGE_URL: http://127.0.0.1:8765
# Split-deployment role: full (default, all-in-one), browser (only the
# desktop + Chrome + CDP, reused over the LAN), or bot (only bot + bridge
# + TTS, driving a remote browser via CDP_HOST). See docker/run-if-role.sh.
JARVIS_ROLE: ${JARVIS_ROLE:-full}
# Chrome CDP bind address INSIDE the container. 0.0.0.0 lets a remote bot
# (JARVIS_ROLE=bot on another PC) drive this host's browser. Loopback by
# default so the all-in-one layout stays unreachable off-host.
CDP_BIND: ${CDP_BIND:-127.0.0.1}
CDP_PORT: ${CDP_PORT:-9222}
# Where the bot drives Chrome. Loopback for full/browser; on a remote bot
# set CDP_HOST to the browser host's LAN IP (e.g. 192.168.10.9).
CDP_HOST: ${CDP_HOST:-127.0.0.1}
depends_on:
- ollama
# GPU: accelerates Whisper STT (and anything else CUDA) in this container.
@@ -95,6 +107,9 @@ services:
# .env pins VNC_PORT=5902.
- "${VNC_BIND:-127.0.0.1}:${VNC_PORT:-5901}:5901" # VNC
- "${VNC_BIND:-127.0.0.1}:${NOVNC_PORT:-6080}:6080" # noVNC (browser)
# Chrome CDP for a remote bot (JARVIS_ROLE=bot). Loopback by default; for a
# LAN browser-host set CDP_PUBLISH_BIND=0.0.0.0 (internal network, no auth).
- "${CDP_PUBLISH_BIND:-127.0.0.1}:${CDP_PORT:-9222}:9222" # Chrome CDP
# The brain bridge is NOT published: it binds the container's loopback
# (BRIDGE_HOST=127.0.0.1) and is only consumed by the bot in this same
# container, so it needs no host port and stays unreachable off-container.

View File

@@ -19,7 +19,7 @@ exec google-chrome \
--disable-translate \
--disable-features=Translate,TranslateUI,InfoBars \
--remote-debugging-port="${CDP_PORT:-9222}" \
--remote-debugging-address=127.0.0.1 \
--remote-debugging-address="${CDP_BIND:-127.0.0.1}" \
--user-data-dir="${CHROME_PROFILE_DIR:-/root/chrome-profile}" \
--password-store=basic --start-maximized \
"${CHROME_START_URL:-about:blank}"

22
docker/run-if-role.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Role guard for split deployments.
#
# run-if-role.sh <roles-csv> <command...>
#
# Runs <command> only when JARVIS_ROLE is one of <roles-csv> (or "full"/unset).
# Otherwise it idles so supervisord keeps the program slot "running" without
# doing any work. This lets ONE image serve three layouts:
#
# JARVIS_ROLE=full (default) everything in one container
# JARVIS_ROLE=browser only the desktop + Chrome + CDP (reused over the LAN)
# JARVIS_ROLE=bot only the bot + bridge + TTS (drives a remote browser
# via CDP_HOST/CDP_PORT)
set -e
want="$1"; shift
role="${JARVIS_ROLE:-full}"
if [ "$role" = "full" ]; then exec "$@"; fi
case ",$want," in
*",$role,"*) exec "$@" ;;
esac
echo "[role-guard] JARVIS_ROLE=$role not in '$want' — idling: $*" >&2
exec sleep infinity

View File

@@ -14,7 +14,7 @@ serverurl=unix:///run/supervisor.sock
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[program:xvnc]
command=/app/docker/run-xvnc.sh
command=/app/docker/run-if-role.sh full,browser /app/docker/run-xvnc.sh
priority=100
autorestart=true
stdout_logfile=/dev/stdout
@@ -23,7 +23,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:pulse]
command=/app/docker/run-pulse.sh
command=/app/docker/run-if-role.sh full,browser /app/docker/run-pulse.sh
priority=150
autorestart=true
stdout_logfile=/dev/stdout
@@ -32,7 +32,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:xfce]
command=/app/docker/run-xfce.sh
command=/app/docker/run-if-role.sh full,browser /app/docker/run-xfce.sh
priority=200
autorestart=true
stdout_logfile=/dev/stdout
@@ -41,7 +41,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:novnc]
command=websockify --web=/usr/share/novnc 6080 localhost:5901
command=/app/docker/run-if-role.sh full,browser websockify --web=/usr/share/novnc 6080 localhost:5901
priority=250
autorestart=true
stdout_logfile=/dev/stdout
@@ -52,7 +52,7 @@ stderr_logfile_maxbytes=0
[program:melo-worker]
; Warm MeloTTS Korean voice (speed 1.5) in its own py3.11 venv. The bridge's
; synthesize() POSTs here; if this is down the bridge falls back to Piper.
command=/opt/melo/bin/python /app/bridge/melo_worker.py
command=/app/docker/run-if-role.sh full,bot /opt/melo/bin/python /app/bridge/melo_worker.py
directory=/app
; HF_HOME points at the dedicated, image-baked melo cache (warmed in
; setup-melo.sh). The brain's whisper_cache volume is mounted over
@@ -73,7 +73,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:bridge]
command=/opt/venv/bin/python -m bridge.server
command=/app/docker/run-if-role.sh full,bot /opt/venv/bin/python -m bridge.server
directory=/app
priority=300
autorestart=true
@@ -83,7 +83,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:chrome]
command=/app/docker/run-chrome.sh
command=/app/docker/run-if-role.sh full,browser /app/docker/run-chrome.sh
priority=350
autorestart=true
stdout_logfile=/dev/stdout
@@ -92,7 +92,7 @@ stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:bot]
command=/app/docker/run-bot.sh
command=/app/docker/run-if-role.sh full,bot /app/docker/run-bot.sh
directory=/app/bot
priority=400
autorestart=true