Dockerize: one-command stack with auto Ollama model pull
Some checks failed
Release / semantic-release (push) Successful in 22s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 9m55s
Release / build-linux (push) Failing after 7m36s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
Some checks failed
Release / semantic-release (push) Successful in 22s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 9m55s
Release / build-linux (push) Failing after 7m36s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
`docker compose up -d --build` now brings up the whole thing automatically — no host setup needed: - All-in-one javis image: TigerVNC+XFCE desktop, Chrome, Python brain bridge, Node/bun bot, managed by supervisord (verified: all 6 programs RUNNING). - ollama service + one-shot ollama-init that auto-pulls chat+embed models (verified end-to-end; `ollama list` shows pulled models). - Discord token deferred: without DISCORD_BOT_TOKEN the desktop, bridge, Ollama and models all run; only the bot waits (no crash loop). - Slim container deps (bridge/requirements-bridge.txt) drop the unused PyQt6/torch/chatterbox/sounddevice stack. Piper voice + Whisper models auto-download into named volumes. - Configurable host ports (VNC_PORT/NOVNC_PORT/BRIDGE_PORT) to avoid clashing with a host VNC already on 5901. Bridge binds 0.0.0.0 in-container. Verified: image builds; brain imports; bridge /health 200; noVNC 200; X display :1 @1920x1080; auto-pull completes; supervisorctl status all RUNNING.
This commit is contained in:
30
docker/download-piper.sh
Executable file
30
docker/download-piper.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download the default Piper voice model if it is not already present.
|
||||
# Used both at image build time and (as a fallback) at container start.
|
||||
set -euo pipefail
|
||||
|
||||
VOICE="${PIPER_VOICE:-en_GB-alan-medium}"
|
||||
DEST_DIR="${PIPER_VOICE_DIR:-/opt/piper-voices}"
|
||||
BASE="https://huggingface.co/rhasspy/piper-voices/resolve/v1.0.0"
|
||||
|
||||
# en_GB-alan-medium -> en/en_GB/alan/medium
|
||||
lang2="${VOICE%%-*}" # en_GB
|
||||
lang1="${lang2%%_*}" # en
|
||||
rest="${VOICE#*-}" # alan-medium
|
||||
name="${rest%%-*}" # alan
|
||||
quality="${rest#*-}" # medium
|
||||
path="${lang1}/${lang2}/${name}/${quality}"
|
||||
|
||||
mkdir -p "$DEST_DIR"
|
||||
onnx="$DEST_DIR/${VOICE}.onnx"
|
||||
json="$DEST_DIR/${VOICE}.onnx.json"
|
||||
|
||||
if [ -f "$onnx" ] && [ -f "$json" ]; then
|
||||
echo "[piper] voice already present: $onnx"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[piper] downloading voice $VOICE ..."
|
||||
wget -q -O "$onnx" "${BASE}/${path}/${VOICE}.onnx"
|
||||
wget -q -O "$json" "${BASE}/${path}/${VOICE}.onnx.json"
|
||||
echo "[piper] saved to $onnx"
|
||||
42
docker/entrypoint.sh
Executable file
42
docker/entrypoint.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# Container entrypoint: render config from env, set the VNC password, ensure the
|
||||
# Piper voice exists, then hand off to supervisord (which runs the desktop,
|
||||
# bridge, and bot).
|
||||
set -euo pipefail
|
||||
|
||||
# --- Defaults (override via .env / compose) ---
|
||||
: "${VNC_PASSWORD:=javis123}"
|
||||
: "${VNC_RESOLUTION:=1920x1080}"
|
||||
: "${OLLAMA_BASE_URL:=http://ollama:11434}"
|
||||
: "${OLLAMA_CHAT_MODEL:=llama3.1:8b}"
|
||||
: "${OLLAMA_EMBED_MODEL:=nomic-embed-text}"
|
||||
: "${WHISPER_MODEL:=small}"
|
||||
: "${WHISPER_DEVICE:=cpu}"
|
||||
: "${WHISPER_COMPUTE_TYPE:=int8}"
|
||||
: "${JARVIS_DB_PATH:=/data/jarvis.db}"
|
||||
: "${BRIDGE_HOST:=0.0.0.0}"
|
||||
: "${BRIDGE_PORT:=8765}"
|
||||
: "${PIPER_VOICE:=en_GB-alan-medium}"
|
||||
: "${PIPER_VOICE_DIR:=/opt/piper-voices}"
|
||||
: "${TTS_PIPER_MODEL_PATH:=${PIPER_VOICE_DIR}/${PIPER_VOICE}.onnx}"
|
||||
|
||||
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
|
||||
|
||||
mkdir -p /data /app/config "$(dirname "$JARVIS_DB_PATH")"
|
||||
|
||||
# --- VNC password file ---
|
||||
mkdir -p /root/.vnc
|
||||
echo "$VNC_PASSWORD" | tigervncpasswd -f > /root/.vnc/passwd
|
||||
chmod 600 /root/.vnc/passwd
|
||||
|
||||
# --- Render jarvis brain config from template ---
|
||||
envsubst < /app/docker/jarvis-config.template.json > /app/config/jarvis.json
|
||||
export JARVIS_CONFIG_PATH=/app/config/jarvis.json
|
||||
|
||||
# --- Ensure the Piper voice exists (best effort) ---
|
||||
bash /app/docker/download-piper.sh || echo "[entrypoint] piper download failed; TTS may be unavailable"
|
||||
|
||||
echo "[entrypoint] display=$DISPLAY ollama=$OLLAMA_BASE_URL whisper=$WHISPER_MODEL/$WHISPER_DEVICE"
|
||||
exec supervisord -c /app/docker/supervisord.conf
|
||||
18
docker/jarvis-config.template.json
Normal file
18
docker/jarvis-config.template.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"db_path": "${JARVIS_DB_PATH}",
|
||||
"sqlite_vss_path": null,
|
||||
"ollama_base_url": "${OLLAMA_BASE_URL}",
|
||||
"ollama_embed_model": "${OLLAMA_EMBED_MODEL}",
|
||||
"ollama_chat_model": "${OLLAMA_CHAT_MODEL}",
|
||||
"tts_enabled": true,
|
||||
"tts_engine": "piper",
|
||||
"tts_piper_model_path": "${TTS_PIPER_MODEL_PATH}",
|
||||
"whisper_model": "${WHISPER_MODEL}",
|
||||
"whisper_backend": "faster-whisper",
|
||||
"whisper_device": "${WHISPER_DEVICE}",
|
||||
"whisper_compute_type": "${WHISPER_COMPUTE_TYPE}",
|
||||
"location_enabled": true,
|
||||
"web_search_enabled": true,
|
||||
"wikipedia_fallback_enabled": true,
|
||||
"mcps": {}
|
||||
}
|
||||
22
docker/run-bot.sh
Executable file
22
docker/run-bot.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
# Wait for the brain bridge, then run the Discord bot.
|
||||
#
|
||||
# The Discord token is intentionally deferred: if DISCORD_BOT_TOKEN is not set
|
||||
# yet, the rest of the stack (desktop, bridge, ollama) still runs fully. The bot
|
||||
# just waits. Add the token to .env and `docker compose up -d` to start it.
|
||||
set -e
|
||||
cd /app/bot
|
||||
|
||||
if [ -z "${DISCORD_BOT_TOKEN:-}" ]; then
|
||||
echo "[bot] DISCORD_BOT_TOKEN 미설정 — 봇 대기 중. .env에 토큰을 넣고 'docker compose up -d' 하면 시작됩니다."
|
||||
echo "[bot] (그동안 VNC 데스크톱 / 브릿지 / Ollama 는 정상 동작합니다.)"
|
||||
exec sleep infinity
|
||||
fi
|
||||
|
||||
BRIDGE="${BRIDGE_URL:-http://127.0.0.1:8765}"
|
||||
for i in $(seq 1 60); do
|
||||
curl -fsS "$BRIDGE/health" >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
bun run register || echo "[bot] slash command registration failed (continuing)"
|
||||
exec bun run start
|
||||
14
docker/run-chrome.sh
Executable file
14
docker/run-chrome.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Wait for the desktop, then launch Chrome on :1 so the VNC screen shows a
|
||||
# controllable browser (jarvis can also drive it). Runs as root -> --no-sandbox.
|
||||
set -e
|
||||
for i in $(seq 1 40); do
|
||||
xdpyinfo -display :1 >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
sleep 3
|
||||
export DISPLAY=:1
|
||||
exec google-chrome \
|
||||
--no-sandbox --no-first-run --disable-dev-shm-usage \
|
||||
--password-store=basic --start-maximized \
|
||||
"${CHROME_START_URL:-about:blank}"
|
||||
12
docker/run-xfce.sh
Executable file
12
docker/run-xfce.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Wait for the X server, then start the XFCE session (with a dbus session).
|
||||
set -e
|
||||
for i in $(seq 1 30); do
|
||||
xdpyinfo -display :1 >/dev/null 2>&1 && break
|
||||
sleep 1
|
||||
done
|
||||
export DISPLAY=:1
|
||||
export XDG_DATA_DIRS=/usr/local/share:/usr/share
|
||||
export XDG_CONFIG_DIRS=/etc/xdg
|
||||
# startxfce4 bails when X is already up; call the session directly.
|
||||
exec dbus-launch --exit-with-session xfce4-session
|
||||
10
docker/run-xvnc.sh
Executable file
10
docker/run-xvnc.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Start the TigerVNC X server on display :1.
|
||||
# NOTE: do NOT pass `-extension RENDER` — it blanks XFCE menus/panels
|
||||
# (see docs/vnc-xfce-setup.md §3-4).
|
||||
set -e
|
||||
: "${VNC_RESOLUTION:=1920x1080}"
|
||||
exec /usr/bin/Xvnc :1 \
|
||||
-geometry "$VNC_RESOLUTION" -depth 24 \
|
||||
-rfbport 5901 -rfbauth /root/.vnc/passwd \
|
||||
-SecurityTypes VncAuth -localhost no -AlwaysShared
|
||||
71
docker/supervisord.conf
Normal file
71
docker/supervisord.conf
Normal file
@@ -0,0 +1,71 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisord.log
|
||||
pidfile=/run/supervisord.pid
|
||||
|
||||
[unix_http_server]
|
||||
file=/run/supervisor.sock
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///run/supervisor.sock
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[program:xvnc]
|
||||
command=/app/docker/run-xvnc.sh
|
||||
priority=100
|
||||
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
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:novnc]
|
||||
command=websockify --web=/usr/share/novnc 6080 localhost:5901
|
||||
priority=250
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:bridge]
|
||||
command=/opt/venv/bin/python -m bridge.server
|
||||
directory=/app
|
||||
priority=300
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:chrome]
|
||||
command=/app/docker/run-chrome.sh
|
||||
priority=350
|
||||
autorestart=true
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:bot]
|
||||
command=/app/docker/run-bot.sh
|
||||
directory=/app/bot
|
||||
priority=400
|
||||
autorestart=true
|
||||
startretries=999
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
Reference in New Issue
Block a user