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