Have these in a scratch file before starting — easier than tab-switching during the GUI install.
| Token | Where to get it |
|---|---|
| **`AGENT_TOKENS`** | Generate fresh: `openssl rand -hex 32` (run 3× — one per agent: claude-code, antigravity, codex). Save them — you'll paste the same values into agent configs after install. |
| **`GITEA_TOKEN`** | `git.alwisp.com` → your avatar → **Settings** → **Applications** → **Generate New Token**. Scopes: `repo`, `issue`, `read:user`. Copy the token immediately (Gitea won't show it again). |
| **`UNRAID_API_KEY`** | On the Unraid host: `unraid-api start`, then `unraid-api key` to print the key. (Requires the unraid-api plugin from Apps.) |
> **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 |
> ⚠️ **Permission gotcha:** the container runs as the unprivileged `mcp` user, but Unraid's `/var/run/docker.sock` is owned by `root:root` (mode 660). The `mcp` user has no access by default — `docker_list_containers` will return `EACCES`. The simplest fix is to add `--user 0:0` to the **Extra Parameters** field at the top of the Add Container form (run the container as root). For a LAN-only gateway behind bearer auth on a single-tenant box, this tradeoff is acceptable. See troubleshooting §7 for stricter alternatives.
> 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 |
| `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.** |
| `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.
Run these from Claude Code in order of risk (lowest first). Each row = the safest first call to confirm a plugin actually works end-to-end.
| Plugin | First call | Expected on success | Likely failure mode |
|---|---|---|---|
| **gitea** | `gitea_list_repos` | `{ repos: [...] }` including the `totalmcp` repo | 401 → `GITEA_TOKEN` missing/wrong scopes |
| **unraid** | `unraid_host_summary` | `{ host, os, uptime, cpu, memory, array }` | GraphQL field shape mismatch — adjust queries in `src/plugins/unraid/index.ts` to match your unraid-api version |
| **docker** | `docker_list_containers` | ~35 containers from your Unraid stack | `EACCES` on `/var/run/docker.sock` — confirm `--user 0:0` is in Extra Parameters |
| **openclaw** | `openclaw_list_models` | List of models on NOVA | Connection timeout → confirm NOVA at `10.2.0.26:18789` is reachable from `10.2.0.35` |
| **unifi** | `unifi_list_sites` | List of UniFi Access locations | 404 on `/api/v1/developer/locations` — UniFi Access REST paths vary by version; adjust the plugin's endpoint paths |
| **codex-mrp** | `codex_list_work_orders` | `{ workOrders: [...] }` | `no such table: work_orders` — **expected.** Run `sqlite3 /mnt/user/appdata/codex/db.sqlite ".schema"`, then update the SQL in `src/plugins/codex-mrp/index.ts` to match real CODEX table names |
| **streamvault** | (skip) | n/a | Service not deployed — `onLoad` will warn at startup, tool calls return connection error. Disable by removing from `ENABLED_PLUGINS` until ready. |
| **rackmapper** | `rackmapper_list_racks` | `{ racks: [...] }` | 404 — RackMapper API path may differ; adjust |
### What to expect on first run
- **gitea**, **openclaw**, **docker** are highest-confidence. Built against well-documented APIs that haven't drifted.
- **unraid**, **unifi** are medium-confidence. APIs evolve between versions; field shapes may need adjustment.
- **codex-mrp** is low-confidence. Placeholder SQL by design — needs the real CODEX schema before it works.
- **streamvault** will be in failure state until the service exists. Disable for now.
- **rackmapper** depends on whether RackMapper has a JSON API; adjust if not.
Capture each plugin's failure (if any) and we'll fix the schemas/paths in a follow-up pass.
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.
**Quickest fix:** add `--user 0:0` to **Extra Parameters** on the container (run as root inside the container).
**Stricter alternatives** if you don't want root in the container:
1. Find the docker GID on the host: `stat -c '%g' /var/run/docker.sock` (commonly `281` on Unraid).
2. Either:
- Override the container's group with `--group-add <GID>` in Extra Parameters, **or**
- Rebuild the image with that GID baked in (modify the Dockerfile's `addgroup` step).
3. Or run a [docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy) in front and point `dockerode` at the proxy URL via a `socketPath` override.
For a LAN-only gateway behind bearer auth on a single-tenant Unraid box, `--user 0:0` is the pragmatic choice.
Also confirm the path mapping is `/var/run/docker.sock → /var/run/docker.sock` with **Read/Write** access mode.
- 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:
> **Note:** even the minimal install does **not** need `--user 0:0` since the `docker` plugin isn't enabled. Add `--user 0:0` only when you turn `docker` on.