# Deployment layouts

One image, three roles (`JARVIS_ROLE`), selected in `.env`. GPU is added per OS
via a compose override picked with `COMPOSE_FILE`.

> `COMPOSE_FILE`'s file separator is OS-specific: Linux/macOS use `:`, Windows
> uses `;` (a colon collides with the `C:` drive letter). Using `:` on Windows
> yields `... The system cannot find the file specified`. If in doubt, leave
> `COMPOSE_FILE` unset and pass the files explicitly:
> `docker compose -f docker-compose.yml -f docker-compose.gpu-windows.yml up -d`.

## A. All-in-one (single machine)

Everything (desktop + Chrome + bridge + bot + TTS) in one container.

```
# .env
JARVIS_ROLE=full
COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml      # Ubuntu/macOS (":" )
# COMPOSE_FILE=docker-compose.yml;docker-compose.gpu-windows.yml  # Windows 11 (";" )
DISCORD_SELFBOT_TOKEN=...
DISCORD_GUILD_ID=...

docker compose up -d            # Ollama + javis (COMPOSE_FILE adds GPU)
```

## B. Split: browser host (LAN) + bot on your PC

The on-screen Chrome, real mouse/keyboard (xdotool) and screen live on the
**browser host**. Your PC runs the **bot** and drives that browser over the
internal network — no auth (internal only).

### Browser host (the LAN machine that shows Chrome, e.g. 192.168.10.5)

```
# .env
JARVIS_ROLE=browser
CDP_BIND=0.0.0.0
BROWSER_CONTROL_BIND=0.0.0.0
CDP_PUBLISH_BIND=0.0.0.0
# no GPU needed → leave COMPOSE_FILE unset (base compose only)

docker compose up -d javis      # desktop + Chrome + control-server (port 8777)
```

Watch it on this machine’s VNC (`localhost:5901`) / noVNC (`localhost:6080`).

### Bot host (your PC — Ubuntu or Windows 11)

```
# .env
JARVIS_ROLE=bot
BROWSER_CONTROL_URL=http://192.168.10.5:8777      # the browser host's LAN IP
COMPOSE_FILE=docker-compose.yml:docker-compose.gpu-linux.yml      # Ubuntu/macOS (":" )
# COMPOSE_FILE=docker-compose.yml;docker-compose.gpu-windows.yml  # Windows 11 (";" )
DISCORD_SELFBOT_TOKEN=...
DISCORD_GUILD_ID=...

docker compose up -d            # bot + bridge + TTS + Ollama (GPU per OS)
```

The bot’s `controlBrowser` tool posts commands to `BROWSER_CONTROL_URL`, so
"네이버에서 X 검색", "구글로 돌아가" etc. drive the **browser host’s** Chrome with real
human-style input (visible on its VNC).

### External Ollama (LLM on a separate host)

To run Ollama on a different LAN machine (e.g. a dedicated GPU host at
`192.168.10.9`) instead of inside this stack:

```
# .env on the app host
OLLAMA_BASE_URL=http://192.168.10.9:11434
```

Then start the app WITHOUT the in-stack LLM services: `docker compose up -d javis`
(do not `docker compose up -d`, which would also start `ollama`/`ollama-init`).
On the LLM host, make Ollama listen on the LAN (`OLLAMA_HOST=0.0.0.0:11434`) and
pull the models there (`ollama pull <chat>`, `<intent>`, `<embed>`). The compose
respects `OLLAMA_BASE_URL`, defaulting to the in-stack `ollama` service when unset.

## Windows 11 notes

- Install the NVIDIA driver on Windows and enable GPU in Docker Desktop
  (Settings → Resources → WSL Integration). Use the `gpu-windows.yml` override.
- Paths: named volumes are cross-platform. The Gemini OAuth login (for
  `GEMINI_AUTH=oauth`) is bind-mounted from the project-local `./docker/gemini-oauth`
  into the container's `~/.gemini`. A project-relative path is used so it resolves
  the same on Windows Docker Desktop and Linux (`${HOME}` is often unset when
  compose runs from PowerShell/cmd). Seed it once from a machine with a browser and
  the logged-in Gemini CLI (`npm i -g @google/gemini-cli`, then `gemini` ->
  "Sign in with Google"), copying the login state:
  (Note: as of 2026-06 Google blocks personal Google accounts on this CLI login
  with "This client is no longer supported for Gemini Code Assist for
  individuals". Workspace/org accounts may still work; personal accounts should
  use `GEMINI_AUTH=apikey` with a key from https://aistudio.google.com/app/apikey
  instead. Real-time search fail-opens to DDG/Brave/Wikipedia either way.)
  `cp -r ~/.gemini/. docker/gemini-oauth/`. The essential file is `oauth_creds.json`
  (it holds the refresh token; `GOOGLE_GENAI_USE_GCA=true` forces OAuth, so that is
  the file the startup readiness check looks for) - copying the whole dir simply also
  carries the cached account/settings. To reuse an existing host login without
  copying, set `GEMINI_OAUTH_DIR=~/.gemini` in `.env`. If unseeded, real-time search
  fail-opens to DDG/Brave and the container logs a `🔑` warning on startup.

## Known limitation

Discord Go-Live broadcast of the **browser host's** screen from a **remote** bot
is not supported (the bot's WebRTC screen capture is local to the bot machine).
Use the browser host's VNC to view it. A full remote-broadcast path is separate,
larger work.
