# Unraid Install Guide — totalmcp Step-by-step walkthrough for deploying the **totalmcp** gateway on Unraid using the **Docker Add Container** GUI. Targets Unraid 6.12+ / 7.0+. After installing, totalmcp is reachable at: - **LAN:** `http://10.2.0.35:8811` - **Health:** `http://10.2.0.35:8811/health` (no auth) - **MCP endpoint:** `http://10.2.0.35:8811/mcp` (Bearer token required) - **Public proxy** (optional, after NPM entry): `https://mcp.alwisp.com` once the existing `gitea-mcp` proxy is repointed --- ## 0. Prerequisites ### Gitea Container Registry login The image lives in your private Gitea registry, so the Unraid host needs credentials. 1. Open the Unraid web UI → **Console** (top right) or SSH into the box. 2. Run: ```bash docker login git.alwisp.com ``` 3. Enter your Gitea username and a personal access token (with `read:packages` scope). Credentials are saved to `/root/.docker/config.json`. ### Confirm `br0` and pick the IP - `br0` should already exist (every other ALPHA container uses it). - Reserve **`10.2.0.35`** for totalmcp (next free above codedump @ `.34`). - If `.35` is taken when you go to assign it, pick the next free IP and update `PLAN.md` + `SERVICES.md` to match. ### Pre-create the appdata directory ```bash mkdir -p /mnt/user/appdata/totalmcp/data mkdir -p /mnt/user/appdata/totalmcp/logs chown -R 99:100 /mnt/user/appdata/totalmcp ``` (99:100 = `nobody:users`, the standard Unraid container ownership.) --- ## 1. Add Container — top-level fields **Docker tab → Add Container** | Field | Value | |---|---| | **Name** | `totalmcp` | | **Overview** | `Unified MCP gateway for ALPHA — exposes every backend service to Claude Code, Codex, Antigravity` | | **Repository** | `git.alwisp.com/jason/totalmcp:latest` | | **Docker Hub URL** | (leave blank — private registry) | | **Icon URL** | (optional — set later) | | **WebUI** | `http://[IP]:[PORT:8811]/health` | | **Extra Parameters** | `--pids-limit=2048` | | **Post Arguments** | (leave blank) | | **Network Type** | `Custom: br0` | | **Use IPv4 only** | ☑ checked | | **Fixed IP address (optional)** | `10.2.0.35` | | **Privileged** | ☐ unchecked | | **Console shell command** | `Shell` | | **Auto update / Restart Policy** | `unless-stopped` | > **Why `br0` instead of `bridge`?** br0 gives the gateway its own LAN IP so agents on workstations connect directly without going through Unraid's port-mapped 10.2.0.2:8811. Matches the rest of the ALPHA service catalog. --- ## 2. Port mappings Click **Add another Path, Port, Variable, Label or Device** → **Port**. | Container Port | Host Port | Connection Type | Description | |---|---|---|---| | `8811` | `8811` | `TCP` | MCP gateway (HTTP + SSE) | > With `br0` and a fixed IP, the host-port column is mostly cosmetic — the container has its own IP. Listed for completeness. --- ## 3. Path mappings Click **Add Path** for each row. ### Required | Container Path | Host Path | Access Mode | Description | |---|---|---|---| | `/app/data` | `/mnt/user/appdata/totalmcp/data` | `Read/Write` | SQLite event log + plugin runtime data | | `/app/logs` | `/mnt/user/appdata/totalmcp/logs` | `Read/Write` | Structured JSON logs (Phase 5) | ### Required if `docker` plugin is enabled (Phase 2) | Container Path | Host Path | Access Mode | Description | |---|---|---|---| | `/var/run/docker.sock` | `/var/run/docker.sock` | `Read/Write` | Docker socket — needed for start/stop/restart | > Read-only is enough for `docker_list_containers`/`get_logs`/`get_stats`, but `start`/`stop`/`restart` require RW. ### Required if `codex-mrp` plugin is enabled (Phase 3) | Container Path | Host Path | Access Mode | Description | |---|---|---|---| | `/app/codex/db.sqlite` | `/mnt/user/appdata/codex/db.sqlite` | `Read/Write` | CODEX SQLite database | > Plugin reads in WAL mode so concurrent reads with CODEX itself are safe. Writes (`codex_create_work_order`) bypass CODEX validation — use the CODEX UI for routine work-order creation. --- ## 4. Environment variables Click **Add Variable** for each row. Group by phase — only add variables for plugins you're enabling now. ### Core (always required) | Variable | Value | Description | |---|---|---| | `NODE_ENV` | `production` | | | `PORT` | `8811` | Must match the port mapping above | | `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` | | `GATEWAY_VERSION` | `0.1.0` | | | `PLUGINS_DIR` | `./dist/plugins` | (default — only override for advanced setups) | | `ENABLED_PLUGINS` | `gitea,unraid,docker,openclaw,unifi,codex-mrp,streamvault,rackmapper` | Comma-separated. Trim to only the plugins you've configured. | | `AGENT_TOKENS` | `claude-code:TOKEN1,antigravity:TOKEN2,codex:TOKEN3` | Generate with `openssl rand -hex 32`. **Save the same tokens in your agent configs.** | ### Phase 1 — Gitea + Unraid | Variable | Value | Required for plugin | |---|---|---| | `GITEA_HOST` | `https://git.alwisp.com` | `gitea` | | `GITEA_TOKEN` | *(Gitea PAT with repo + issue + admin scopes)* | `gitea` | | `UNRAID_HOST` | `http://10.2.0.2` | `unraid` | | `UNRAID_API_KEY` | *(generated via `unraid-api start` on the host)* | `unraid` | ### Phase 2 — Docker + OpenClaw | Variable | Value | Required for plugin | |---|---|---| | `OPENCLAW_HOST` | `http://10.2.0.26:18789` | `openclaw` (NOVA) | > The `docker` plugin needs no env vars — it uses the mounted socket. ### Phase 3 — Service Connectors | Variable | Value | Required for plugin | |---|---|---| | `UNIFI_HOST` | `https://` | `unifi` | | `UNIFI_API_KEY` | *(UniFi Access → Settings → Security → API tokens)* | `unifi` | | `UNIFI_SITE_ID` | *(your site UUID, optional)* | `unifi` | | `CODEX_DB_PATH` | `/app/codex/db.sqlite` | `codex-mrp` (must match the path mapping above) | | `STREAMVAULT_HOST` | `http://streamvault:3100` | `streamvault` (skip until StreamVault is deployed) | | `STREAMVAULT_TOKEN` | *(if StreamVault requires auth)* | `streamvault` (optional) | | `RACKMAPPER_HOST` | `http://10.2.0.23` | `rackmapper` | | `RACKMAPPER_TOKEN` | *(if RackMapper requires auth)* | `rackmapper` (optional) | ### Phase 6 — Deferred | Variable | Value | Required for plugin | |---|---|---| | `CHRONICLE_HOST` | *(after deploy)* | `chronicle` (deferred) | | `CHRONICLE_TOKEN` | *(after deploy)* | `chronicle` (deferred) | | `OBSIDIAN_REST_HOST` | `http://10.2.0.2:27123` | `obsidian` (deferred) | | `OBSIDIAN_API_KEY` | *(Obsidian Local REST API plugin)* | `obsidian` (deferred) | ### Phase 7+ (defer until Phase 7 ships) The full set is documented in [.env.example](.env.example) — `NPM_*`, `UISP_*`, `TRANSMISSION_*`, `SYNCTHING_*`, `PLEX_*`, `NYAA_*`, `HA_*`, `INVOICENINJA_*`, `FABDASH_*`, `CPAS_*`, `WFH_*`, `BREEDR_*`, `CODEDUMP_*`, `UITRACKER_*`, `STEPVIEW_*`, `QRKNIT_*`, `MEMER_*`, `ALWISP_WEB_*`. Add only when those plugin phases land. --- ## 5. Apply & verify 1. Click **Apply** at the bottom of the Add Container form. 2. Unraid pulls the image from `git.alwisp.com/jason/totalmcp:latest` and starts the container. 3. Watch the **Docker tab** — totalmcp should show **started** within 5–10 seconds. ### Liveness check From the Unraid console (or any LAN host): ```bash curl http://10.2.0.35:8811/health ``` Expected: ```json { "status": "ok", "version": "0.1.0", "plugins": 8, "enabled": ["gitea","unraid","docker","openclaw","unifi","codex-mrp","streamvault","rackmapper"] } ``` ### Plugin diagnostics ```bash curl -H "Authorization: Bearer " http://10.2.0.35:8811/plugins ``` You'll see a per-plugin breakdown with tool counts. If a plugin is missing, check the container logs: ```bash docker logs totalmcp | grep -E "plugin_(loaded|connect_failed|invalid)" ``` ### Connect Claude Code ```bash claude mcp add --scope user --transport http totalmcp http://10.2.0.35:8811/mcp \ -H "Authorization: Bearer " ``` Then run `/mcp` inside Claude Code — the totalmcp tools should appear in the catalog. --- ## 6. Updates ### Manual (one-time) 1. Docker tab → **totalmcp** → click the icon → **Force Update**. 2. Unraid pulls `git.alwisp.com/jason/totalmcp:latest` and restarts the container in place. 3. Re-verify via `curl /health`. ### Automated (Phase 4 — Gitea Actions CI) Once the CI pipeline lands, every push to `main` rebuilds the image. To pick up updates without manual intervention: - Set Unraid's update polling to a short interval (Settings → Docker → "Check for updates every"), or - Hit the Docker tab → **Check for Updates** button when ready. --- ## 7. Troubleshooting ### `/health` returns 500 or container crash-loops Check the logs: ```bash docker logs totalmcp ``` Common causes: - **`Invalid environment configuration`** — `AGENT_TOKENS` malformed or `PORT` not a number. Fix the Variable in the Unraid GUI and the container will auto-restart. - **`Cannot find module ...`** — image was built without one of the new deps (e.g., `dockerode` or `better-sqlite3`). Force-update to pull the latest image. ### `docker` plugin: `docker_connect_failed` Confirm the path mapping is `/var/run/docker.sock → /var/run/docker.sock` with **Read/Write** access mode. ### `codex-mrp` plugin: `codex_mrp_connect_failed` - Check that `/mnt/user/appdata/codex/db.sqlite` exists on the host. - Confirm the path mapping is `/app/codex/db.sqlite` (file path, not directory). - Confirm `CODEX_DB_PATH=/app/codex/db.sqlite` matches. ### `unraid` plugin: `unraid_connect_failed` - Run `unraid-api status` on the host to confirm the API plugin is running. - Confirm `UNRAID_API_KEY` matches the key from `unraid-api key`. ### Authentication 401s from Claude Code - Token mismatch — the `AGENT_TOKENS` value in Unraid must contain the same token Claude Code is sending. - Format reminder: `agentName:token,agentName2:token2` (no spaces, comma-separated). ### Port collision on 8811 If something else on `10.2.0.35` (or your chosen IP) already binds 8811, change `PORT` and the port mapping to `8812` (or whichever) and update agent configs accordingly. The PLAN.md default is 8811. --- ## 8. Reference: minimal viable install If you just want the gateway running with **only the Gitea plugin** to start: | Setting | Value | |---|---| | Repository | `git.alwisp.com/jason/totalmcp:latest` | | Network / IP | `br0` / `10.2.0.35` | | Port | `8811:8811 TCP` | | Path | `/app/data → /mnt/user/appdata/totalmcp/data (RW)` | | Variable `NODE_ENV` | `production` | | Variable `PORT` | `8811` | | Variable `AGENT_TOKENS` | `claude-code:` | | Variable `ENABLED_PLUGINS` | `gitea` | | Variable `GITEA_HOST` | `https://git.alwisp.com` | | Variable `GITEA_TOKEN` | *(your PAT)* | Apply, verify with `curl /health`, then expand by adding more variables and updating `ENABLED_PLUGINS` as each phase comes online.