br0 setup
This commit is contained in:
@@ -0,0 +1,411 @@
|
||||
# MemPalace on Unraid — GUI install with `br0` networking
|
||||
|
||||
This walks through installing MemPalace as a single Docker container on
|
||||
Unraid, using the **dockerMan WebUI** (no compose, no SSH-driven YAML),
|
||||
attached to **`br0`** so the container has its own IP address on your
|
||||
LAN — separate from Unraid's IP.
|
||||
|
||||
If you'd rather do an SSH/compose install with TLS + bearer auth via a
|
||||
Caddy sidecar, see [`README.md`](README.md). This guide is the
|
||||
GUI-friendly path.
|
||||
|
||||
---
|
||||
|
||||
## Why `br0`?
|
||||
|
||||
`br0` (macvlan-style bridge to your physical network) gives the
|
||||
container its **own LAN IP**. That means:
|
||||
|
||||
* No port conflicts with Unraid itself (you can keep `8765`/`8766`
|
||||
instead of remapping).
|
||||
* Clients connect to the container directly — `http://<container-ip>:8765/sse`
|
||||
rather than `http://<unraid-ip>:8765/sse`.
|
||||
* The container can be reached by every machine on your LAN without
|
||||
punching holes in the Unraid host.
|
||||
|
||||
The trade-off, classic macvlan:
|
||||
|
||||
* By default the **Unraid host itself can't talk to the container**.
|
||||
If you only ever connect from other machines, this is fine. If you
|
||||
also want to test from Unraid's own shell, enable **Host access to
|
||||
custom networks** (see Step 1).
|
||||
* The container is exposed bare to the LAN. Anyone on the LAN can hit
|
||||
the MCP endpoint — it has no auth in this mode. See "Security
|
||||
notes" at the bottom.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* Unraid 6.12+ with Docker enabled.
|
||||
* The repo synced to a path on Unraid (e.g. `/mnt/user/system/build/mempalace`).
|
||||
You can clone over SSH or just copy the folder via SMB.
|
||||
* `/mnt/user/appdata` available (default on every Unraid).
|
||||
* A free LAN IP for the container, ideally with a DHCP reservation on
|
||||
your router so it doesn't drift.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Enable br0 in Unraid
|
||||
|
||||
Skip this if you already use `br0` for other containers.
|
||||
|
||||
1. **Stop the Docker service** so settings are editable: *Settings →
|
||||
Docker → Enable Docker = No → Apply*.
|
||||
2. *Settings → Docker → Advanced View*.
|
||||
3. **Host access to custom networks** = **Enabled**. (Lets the Unraid
|
||||
shell reach `br0` containers — needed for `curl` health checks
|
||||
from Unraid itself. Skip if you don't need that.)
|
||||
4. **Docker custom network type** = **macvlan** (default) or
|
||||
**ipvlan** if your switch/router doesn't like macvlan MAC churn.
|
||||
5. Re-enable Docker: *Enable Docker = Yes → Apply*.
|
||||
6. Confirm `br0` shows up under *Settings → Docker → Docker custom
|
||||
networks* (or implicitly — every Unraid with a bridge has it).
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Build the image on Unraid
|
||||
|
||||
There's no public registry image — the container is built locally from
|
||||
the repo's root `Dockerfile`. SSH into Unraid (or open the WebUI
|
||||
terminal) and run:
|
||||
|
||||
```bash
|
||||
cd /mnt/user/system/build/mempalace # wherever you put the repo
|
||||
docker build -t mempalace-server:latest .
|
||||
```
|
||||
|
||||
First build pulls `python:3.13-slim` and pip-installs `mempalace` +
|
||||
`mcp-proxy` (~3–5 minutes). When it finishes:
|
||||
|
||||
```bash
|
||||
docker images mempalace-server
|
||||
# REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
# mempalace-server latest <hash> <time> ~600 MB
|
||||
```
|
||||
|
||||
You only repeat this step when you `git pull` an update.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Create appdata directories
|
||||
|
||||
```bash
|
||||
mkdir -p /mnt/user/appdata/mempalace
|
||||
chown -R 99:100 /mnt/user/appdata/mempalace
|
||||
```
|
||||
|
||||
The container runs as UID/GID **99:100** (`nobody:users` — the Unraid
|
||||
appdata convention). Wrong ownership here is the #1 cause of "permission
|
||||
denied" on first start.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Add the container in the WebUI
|
||||
|
||||
1. *Docker → Add Container* (don't pick a template — fill it in
|
||||
manually).
|
||||
2. Fill the top section:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Name** | `MemPalace` |
|
||||
| **Repository** | `mempalace-server:latest` |
|
||||
| **Network Type** | **Custom: br0** |
|
||||
| **Fixed IP address** | *(your chosen LAN IP, e.g. `192.168.1.50`)* |
|
||||
| **Console shell command** | `Bash` |
|
||||
| **Privileged** | `Off` |
|
||||
| **Extra Parameters** | `--user 99:100` |
|
||||
|
||||
Use a fixed IP. macvlan + DHCP can race the DHCP server on
|
||||
container restart and end up unreachable.
|
||||
|
||||
3. Leave **WebUI** blank (there isn't one). **Icon URL** optional:
|
||||
`https://raw.githubusercontent.com/MemPalace/mempalace/develop/assets/mempalace_logo.png`
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Add the path mapping
|
||||
|
||||
Click **Add another Path, Port, Variable, Label or Device** → **Path**:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| **Name** | `Appdata` |
|
||||
| **Container Path** | `/data` |
|
||||
| **Host Path** | `/mnt/user/appdata/mempalace` |
|
||||
| **Access Mode** | `Read/Write` |
|
||||
|
||||
This holds ChromaDB, the knowledge graph, the embedding-model cache,
|
||||
and the inbox for ingested transcripts. Lose it = lose all memory.
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Add the ports
|
||||
|
||||
In `br0` mode the "host port" field is just a label — the container's
|
||||
ports are published on **the container's own IP**, not Unraid's. Set
|
||||
both to match the container port.
|
||||
|
||||
Click **Add another …** → **Port** twice:
|
||||
|
||||
| Name | Container Port | Host Port | Connection Type |
|
||||
|---|---|---|---|
|
||||
| `MCP SSE` | `8765` | `8765` | `TCP` |
|
||||
| `Ingest HTTP` | `8766` | `8766` | `TCP` |
|
||||
|
||||
Skip the ingest port if you only use this from a single machine and
|
||||
won't push transcripts in via HTTP hooks.
|
||||
|
||||
---
|
||||
|
||||
## Step 7 — Add environment variables
|
||||
|
||||
Click **Add another …** → **Variable** for each:
|
||||
|
||||
### Required
|
||||
|
||||
| Name | Key | Value |
|
||||
|---|---|---|
|
||||
| Palace path | `MEMPALACE_PALACE_PATH` | `/data/palace` |
|
||||
|
||||
### Recommended (enables transcript ingest)
|
||||
|
||||
| Name | Key | Value |
|
||||
|---|---|---|
|
||||
| Ingest port | `MEMPALACE_INGEST_PORT` | `8766` |
|
||||
| Ingest bind | `MEMPALACE_INGEST_HOST` | `0.0.0.0` |
|
||||
| Ingest token | `MEMPALACE_INGEST_TOKEN` | *a long random string* |
|
||||
|
||||
`MEMPALACE_INGEST_TOKEN` is optional but **strongly recommended in
|
||||
`br0` mode** — without it, anyone on the LAN can POST transcripts
|
||||
into your palace. Generate one with:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
…and save it; you'll set the same value as `MEMPAL_REMOTE_TOKEN` on
|
||||
each client.
|
||||
|
||||
### Optional
|
||||
|
||||
| Name | Key | Default | Notes |
|
||||
|---|---|---|---|
|
||||
| Embedding device | `MEMPALACE_EMBEDDING_DEVICE` | *(auto)* | `cpu`, `cuda`, `dml`, or `coreml`. CUDA needs a rebuild with the `[gpu]` extra and the NVIDIA driver plugin. |
|
||||
| Entity languages | `MEMPALACE_ENTITY_LANGUAGES` | `en` | Comma-separated, e.g. `en,es,de`. |
|
||||
|
||||
---
|
||||
|
||||
## Step 8 — Apply and verify
|
||||
|
||||
Click **Apply**. Unraid will start the container; first start takes
|
||||
~10 s while ChromaDB initializes.
|
||||
|
||||
From any machine on your LAN (or from Unraid itself if you enabled
|
||||
*Host access to custom networks*):
|
||||
|
||||
```bash
|
||||
# Unauth liveness check on the ingest port:
|
||||
curl http://<container-ip>:8766/healthz
|
||||
# → {"status":"ok","version":"3.3.x"}
|
||||
|
||||
# Confirm token gating works (only relevant if you set MEMPALACE_INGEST_TOKEN):
|
||||
curl -i http://<container-ip>:8766/ingest/transcript
|
||||
# → HTTP/1.0 401 Unauthorized
|
||||
```
|
||||
|
||||
For the MCP SSE endpoint:
|
||||
|
||||
```bash
|
||||
curl -i http://<container-ip>:8765/sse
|
||||
# Should hang open with `Content-Type: text/event-stream` —
|
||||
# that's healthy. Ctrl-C to bail.
|
||||
```
|
||||
|
||||
If both work, you're done. Container logs are under *Docker → MemPalace
|
||||
→ Logs* in the WebUI, or `docker logs MemPalace` from the shell.
|
||||
|
||||
---
|
||||
|
||||
## Connect AI tools
|
||||
|
||||
Each client needs `mcp-proxy`:
|
||||
|
||||
```bash
|
||||
uv tool install mcp-proxy
|
||||
# or: pip install mcp-proxy
|
||||
```
|
||||
|
||||
### Claude Code
|
||||
|
||||
`~/.claude.json` (or project `.mcp.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"mempalace": {
|
||||
"command": "mcp-proxy",
|
||||
"args": ["http://<container-ip>:8765/sse"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
No bearer header on this path — the MCP endpoint is unauthenticated
|
||||
in `br0` mode.
|
||||
|
||||
### Codex CLI — `~/.codex/config.toml`
|
||||
|
||||
```toml
|
||||
[mcp_servers.mempalace]
|
||||
command = "mcp-proxy"
|
||||
args = ["http://<container-ip>:8765/sse"]
|
||||
```
|
||||
|
||||
### Antigravity / Windsurf
|
||||
|
||||
Settings → AI → MCP Servers, or `~/.antigravity/mcp.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mempalace": {
|
||||
"command": "mcp-proxy",
|
||||
"args": ["http://<container-ip>:8765/sse"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Smoke test
|
||||
|
||||
Start a session in any of those clients and ask:
|
||||
|
||||
> "Use mempalace_status to show palace stats."
|
||||
|
||||
You should get back a JSON blob with wing/room/drawer counts. A
|
||||
connection error means wrong IP or the container isn't on `br0`.
|
||||
|
||||
---
|
||||
|
||||
## Set up auto-save hooks (optional)
|
||||
|
||||
Enables transcripts to flow into the palace automatically as you work.
|
||||
Each client needs the env vars:
|
||||
|
||||
**Windows (PowerShell)**:
|
||||
|
||||
```powershell
|
||||
[Environment]::SetEnvironmentVariable("MEMPAL_REMOTE_URL", "http://<container-ip>:8766", "User")
|
||||
[Environment]::SetEnvironmentVariable("MEMPAL_REMOTE_TOKEN", "<the-ingest-token>", "User")
|
||||
```
|
||||
|
||||
**macOS/Linux**: same exports in `~/.zshrc` / `~/.bashrc`.
|
||||
|
||||
Then wire `hooks/mempal_save_hook_remote.sh` and
|
||||
`hooks/mempal_precompact_hook_remote.sh` into your client's hook
|
||||
config — see the "Set up auto-save hooks" section in
|
||||
[`README.md`](README.md), it's identical from this point on.
|
||||
|
||||
Note: these hooks talk **HTTP**, not HTTPS, since this guide doesn't
|
||||
use Caddy. If `MEMPAL_REMOTE_URL` starts with `http://`, no TLS env
|
||||
vars are needed.
|
||||
|
||||
---
|
||||
|
||||
## Security notes
|
||||
|
||||
`br0` puts the container on your LAN with no auth in front of MCP.
|
||||
That's fine if and only if:
|
||||
|
||||
* You trust every device on your LAN (no untrusted IoT, no guest
|
||||
network bridged in, no roommates).
|
||||
* You've set `MEMPALACE_INGEST_TOKEN` so nobody can drop transcripts
|
||||
in unprompted.
|
||||
|
||||
If those don't both hold, do one of these instead:
|
||||
|
||||
* **Run on a Tailscale subnet** rather than `br0` so only your
|
||||
tailnet devices can reach it.
|
||||
* **Use the Caddy compose path** in [`README.md`](README.md) to add
|
||||
TLS + bearer-token auth on the MCP side too.
|
||||
* **Front it with SWAG / Nginx Proxy Manager** if you already run
|
||||
one — drop the bare `8765` exposure, route through the proxy with
|
||||
bearer-token auth and SSE pass-through configured.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container starts then dies immediately
|
||||
|
||||
99% of the time this is `/data` ownership. From Unraid shell:
|
||||
|
||||
```bash
|
||||
ls -ld /mnt/user/appdata/mempalace
|
||||
# Want: owner 99 (nobody), group 100 (users)
|
||||
chown -R 99:100 /mnt/user/appdata/mempalace
|
||||
```
|
||||
|
||||
Then *Docker → MemPalace → Restart*.
|
||||
|
||||
### "Address already in use" on start
|
||||
|
||||
Another container on `br0` is using the same IP. Pick a different
|
||||
**Fixed IP address** in Step 4. (Port conflicts are not possible in
|
||||
`br0` mode since each container has its own IP.)
|
||||
|
||||
### Unraid shell can't reach the container, but other machines can
|
||||
|
||||
Default macvlan behavior. *Settings → Docker → Host access to custom
|
||||
networks → Enabled* (Step 1).
|
||||
|
||||
### Clients see SSE connect but tool calls hang
|
||||
|
||||
Network path is fine; this is usually a stale `mcp-proxy` install on
|
||||
the client. `uv tool upgrade mcp-proxy` or `pip install -U mcp-proxy`.
|
||||
|
||||
### Embedding model download stalls on first request
|
||||
|
||||
The ~80 MB MiniLM ONNX model downloads from HuggingFace on first use.
|
||||
Pre-warm it from the Unraid shell:
|
||||
|
||||
```bash
|
||||
docker exec MemPalace python -c \
|
||||
"from chromadb.utils.embedding_functions import ONNXMiniLM_L6_V2; ONNXMiniLM_L6_V2()(['warmup'])"
|
||||
```
|
||||
|
||||
Subsequent uses load from cache (~50 ms).
|
||||
|
||||
### Port 8765 / 8766 already used by something else
|
||||
|
||||
In `br0` mode the container has its own IP, so this only happens if
|
||||
you literally run a second mempalace on the same IP. Pick a different
|
||||
fixed IP and restart.
|
||||
|
||||
---
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd /mnt/user/system/build/mempalace
|
||||
git pull
|
||||
docker build -t mempalace-server:latest .
|
||||
```
|
||||
|
||||
Then in the WebUI: *Docker → MemPalace → Force Update* (or just
|
||||
*Restart*). Your palace data persists because it's on the
|
||||
`/mnt/user/appdata/mempalace` volume, outside the container.
|
||||
|
||||
---
|
||||
|
||||
## Backups
|
||||
|
||||
Add `/mnt/user/appdata/mempalace/` to **CA Backup / Appdata Backup**.
|
||||
Three subdirectories matter:
|
||||
|
||||
* `palace/` — ChromaDB vectors + SQLite metadata
|
||||
* `kg/` — knowledge graph
|
||||
* `inbox/` — uploaded transcripts (kept for re-mining)
|
||||
|
||||
Losing this directory = losing all memory. The image itself is
|
||||
disposable — you can rebuild it from the repo any time.
|
||||
Reference in New Issue
Block a user