[Unit] Description=MemPalace remote MCP server (the fleet primary — RFC-001) Documentation=https://gitea.jordbo.se/joakimp/mempalace-toolkit Documentation=file:%h/mempalace-toolkit/docs/rfc-001-global-palace.md After=network-online.target Wants=network-online.target # Refuse to start if the palace is missing — better a clear failure than a # server quietly creating an empty palace somewhere unexpected. ConditionPathExists=%h/.mempalace/palace [Service] Type=simple # ── Bind address: NOT 127.0.0.1. This is deliberate and load-bearing. ──────── # mempalace pins the HTTP Host header to loopback literals *only on a loopback # bind* (mcp_server.py: enforce_host_pin = _http_is_loopback(host)). Behind a # reverse proxy / tunnel that forwards the public hostname, a loopback bind # answers 403 Forbidden. Verified empirically on synlig 2026-08-10: # bind 127.0.0.1 + Host: palace.example.com -> 403 # bind 172.17.0.1 + Host: palace.example.com -> 200 # 172.17.0.1 is the docker0 gateway: non-loopback (so the pin relaxes), but # reachable only from this host and its containers — so a newt/Pangolin tunnel # container on this box can reach it while the LAN cannot. Use 0.0.0.0 only if # the tunnel does not run in Docker here, and only with the firewall closed. # # The Origin check is NEVER relaxed: a request carrying a non-loopback Origin # is 403 with no override. Fine for MCP clients (they send none); fatal for # browser-based clients. # # No --token here on purpose: for a non-loopback bind, serve reuses or mints a # 0600 token at ~/.mempalace/server//token and keeps it # stable across restarts. Read it from there to configure clients; never paste # it into this unit (units are world-readable). ExecStart=%h/.local/bin/mempalace serve --host 172.17.0.1 --port 8765 Restart=on-failure RestartSec=5s # The server serializes every request behind one lock, so a wedged process is # a fleet-wide outage. Fail fast and let Restart= recover. TimeoutStopSec=30 # Journal: journalctl --user -u mempalace-serve -f StandardOutput=journal StandardError=journal # Modest hardening (user units can't do much, but these are free) NoNewPrivileges=true PrivateTmp=true ProtectKernelTunables=true ProtectControlGroups=true [Install] WantedBy=default.target # ── Enabling (needs one sudo, hence not done by the Phase 0 prep) ─────────── # sudo loginctl enable-linger $USER # else the unit dies with your login # systemctl --user daemon-reload # systemctl --user enable --now mempalace-serve # curl -s localhost:8765/healthz # -> 403 (expected! see bind note) # curl -s 172.17.0.1:8765/healthz # -> ok