7c00dd6001
The feeder now defaults to <palace-root>/pi-stage upstream, so pinning
MEMPALACE_PI_STAGE into ~/.pi here is unnecessary -- and was actively wrong. It
created a second convention that could still diverge from the palace: keep the
devbox-palace volume, drop devbox-pi-config, and a scoped `mempalace sync`
prunes every conversation drawer, because dedup keys on the staged path. Both
the ENV and the entrypoint export are gone; a comment explains why adding one
back re-introduces the split it was meant to fix.
docker-compose.mempalace.yml was broken on mempalace 3.6.0 in both directions:
- `--host 0.0.0.0` with no token in the environment makes the server refuse
to start, crash-looping under `restart: unless-stopped`.
- Supply a token and the healthcheck's unauthenticated `tools/list` POST 401s,
marking a perfectly healthy server unhealthy forever.
Now the token is required via ${MEMPALACE_REMOTE_TOKEN:?...} so it fails fast at
`docker compose up` with a readable message, and the healthcheck probes the
deliberately token-free /healthz. The "no authentication of its own" security
note has been stale since 3.6.0 and is replaced with the actual posture
(bearer token + Host pin + Origin allowlist), including why browser-shaped auth
must not be put in front of it.
Dockerfile.base: mempalace-pi-session symlinked onto PATH, with a build-time
`--help` check so a broken feeder fails the image build rather than the first
session.
smoke-test: assert the stage resolves beside the palace (default, and following
$MEMPALACE_PALACE_PATH) instead of asserting the removed ENV pin. The two
behavioural guards -- a synthetic session that must be captured, an abandoned
one that must not be -- are unchanged.
.env.example: recommend `mempalace serve` on the docker0 gateway rather than
`mempalace-mcp --transport http --host 0.0.0.0`, with the two binds to avoid.
112 lines
5.1 KiB
YAML
112 lines
5.1 KiB
YAML
# Shared MemPalace server (optional) — one palace for many clients.
|
|
#
|
|
# Runs `mempalace-mcp` over HTTP so several containers/harnesses (pi +
|
|
# opencode + native) can share ONE palace instead of each keeping its own.
|
|
# Point every client at it by setting, in that client's .env:
|
|
#
|
|
# MEMPALACE_REMOTE_URL=http://<reachable-host>:8765/mcp
|
|
# MEMPALACE_REMOTE_TOKEN=<the shared bearer token>
|
|
#
|
|
# (see .env.example). When set, the client connects over HTTP and does NOT
|
|
# spawn its own local mempalace-mcp.
|
|
#
|
|
# Start: docker compose -f docker-compose.mempalace.yml up -d
|
|
# Stop: docker compose -f docker-compose.mempalace.yml down
|
|
# Logs: docker compose -f docker-compose.mempalace.yml logs -f
|
|
#
|
|
# Why reuse the devbox image? mempalace-mcp is already installed in it, and
|
|
# reusing it GUARANTEES the server's mempalace version matches the clients'
|
|
# (both are pinned by the same image build). Override with a slimmer image via
|
|
# MEMPALACE_SERVER_IMAGE if you prefer (it must provide `mempalace-mcp`).
|
|
#
|
|
# ⚠ SECURITY: the HTTP transport IS authenticated as of mempalace 3.6.0 — an
|
|
# earlier version of this comment said otherwise and was wrong. The server
|
|
# compares `Authorization: Bearer <token>` with hmac.compare_digest and
|
|
# **refuses to start on a non-loopback bind without a token**, so
|
|
# MEMPALACE_REMOTE_TOKEN below is required, not optional: without it this
|
|
# service crash-loops. It also pins `Host` and allowlists `Origin`.
|
|
#
|
|
# Still do not publish port 8765 to an untrusted network. The default binds to
|
|
# 127.0.0.1 (host loopback) only. To let sibling containers reach it, attach
|
|
# them to the shared `mempalace-net` network (container-to-container, no host
|
|
# port needed — use http://mempalace-server:8765/mcp). To reach it from
|
|
# elsewhere, terminate TLS in a tunnel/reverse proxy and let the bearer token be
|
|
# the authentication — do NOT add browser-shaped auth (SSO/PIN/password) in
|
|
# front, because every MCP client here is a headless JSON-RPC POST and would
|
|
# receive a login page where JSON should be.
|
|
|
|
name: mempalace-server
|
|
|
|
services:
|
|
mempalace:
|
|
image: ${MEMPALACE_SERVER_IMAGE:-joakimp/pi-devbox:latest}
|
|
container_name: mempalace-server
|
|
# Bypass the devbox entrypoint (dev-shell/LAN/config setup) and run the
|
|
# HTTP MCP server directly. HOME + explicit --palace pin the data path so
|
|
# it does not depend on the image's default user/HOME. Runs as root so it
|
|
# can initialise the fresh named volume; the volume is dedicated to this
|
|
# server (clients reach it over HTTP, never by mounting it).
|
|
entrypoint: []
|
|
user: "0:0"
|
|
environment:
|
|
- HOME=/data
|
|
# Required: mempalace refuses a non-loopback bind without a token (it
|
|
# would exit at startup and, with restart:unless-stopped, crash-loop).
|
|
# `:?` fails fast at `docker compose up` with a readable message instead.
|
|
# Clients send the same value as MEMPALACE_REMOTE_TOKEN.
|
|
- MEMPALACE_MCP_HTTP_TOKEN=${MEMPALACE_REMOTE_TOKEN:?set MEMPALACE_REMOTE_TOKEN in .env — the shared palace requires a bearer token}
|
|
command:
|
|
- mempalace-mcp
|
|
- --transport
|
|
- http
|
|
- --host
|
|
- "0.0.0.0"
|
|
- --port
|
|
- "8765"
|
|
- --palace
|
|
- /data/.mempalace
|
|
restart: unless-stopped
|
|
# Loopback-only by default (see SECURITY note). Use "8765:8765" to expose on
|
|
# all host interfaces, or drop `ports:` entirely and rely on mempalace-net.
|
|
ports:
|
|
- "127.0.0.1:8765:8765"
|
|
volumes:
|
|
# The shared palace data — precious; back this up.
|
|
- mempalace-shared:/data/.mempalace
|
|
# Embedding-model cache (~79 MB, disposable) so search does not re-download.
|
|
- mempalace-shared-chroma:/data/.cache/chroma
|
|
# Transcript inbox. Clients cannot mine into a remote palace directly:
|
|
# `mempalace_mine` expands its source path in THIS process, so it can only
|
|
# see paths inside this container. Each client rsyncs its staged session
|
|
# exports to a per-device subdirectory on the host (see
|
|
# MEMPALACE_PI_SSH_TARGET in .env.example) and then calls mempalace_mine
|
|
# with the container-side path below (MEMPALACE_PI_REMOTE_PATH=/data/feed).
|
|
# Read-only: mining only reads sources, and all locks live palace-side.
|
|
- ${MEMPALACE_FEED_DIR:-./feed}:/data/feed:ro
|
|
networks:
|
|
- mempalace-net
|
|
healthcheck:
|
|
# GET /healthz, which is Host/Origin-gated but deliberately token-free —
|
|
# so this probe needs no credentials. Do NOT go back to POSTing
|
|
# `tools/list` here: that carries no Authorization header and now 401s,
|
|
# marking a perfectly healthy server unhealthy forever. The Host pin is
|
|
# only enforced on loopback *binds* (this one is 0.0.0.0), so a request to
|
|
# 127.0.0.1 inside the container passes.
|
|
test:
|
|
- CMD
|
|
- python3
|
|
- -c
|
|
- "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8765/healthz',timeout=5).status==200 else 1)"
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 60s
|
|
|
|
volumes:
|
|
mempalace-shared:
|
|
mempalace-shared-chroma:
|
|
|
|
networks:
|
|
mempalace-net:
|
|
name: mempalace-net
|