295 lines
11 KiB
Markdown
295 lines
11 KiB
Markdown
# 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://<your-unifi-controller>` | `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 <CLAUDE_CODE_TOKEN>" 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 <CLAUDE_CODE_TOKEN>"
|
||
```
|
||
|
||
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:<your-token>` |
|
||
| 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.
|