mirror of
https://github.com/akitaonrails/ai-memory.git
synced 2026-10-02 03:24:46 +08:00
Per Phase 2 decision to NOT terminate TLS in-binary: ai-memory
stays HTTP-on-loopback by default (zero change for existing users
on upgrade) and operators front it with a battle-tested reverse
proxy when they cross the deployment shapes that need TLS.
Three new files:
- docs/https-via-proxy.md — the deployment guide. Covers:
- When you DON'T need TLS (loopback + stdio cases honestly
don't, called out up front so we don't add ceremony where
it doesn't earn its keep).
- When you DO need TLS (multi-user, non-loopback bind, /web
from another machine, public exposure).
- Five deployment paths with copy-paste configs:
- Caddy + public domain + Let's Encrypt
- Caddy + internal CA (LAN-only)
- Cloudflare Tunnel (no open ports, TLS at the edge)
- External cert files (Caddy or nginx)
- Native (non-Docker) Caddy
- Each path documents what can go wrong + the trust-install
step for the internal-CA case (the load-bearing manual
step — skipping it produces security theatre).
- Final section "Don't paper over the security gap" calls out
three specific anti-patterns operators reach for when
things "don't work" (disable allowed-hosts, --insecure on
clients, --no-tls-verify on cloudflared).
- docker/compose.tls.caddy.yml — copy-paste compose template
with Caddy front. Three variants documented inline (LE,
internal CA, external cert files). Same service definition
across all three; only the Caddyfile differs.
- docker/compose.tls.cloudflared.yml — Cloudflare Tunnel sidecar
compose template. Walks through the one-time CF dashboard
setup, the .env.production shape (incl. CLOUDFLARE_TUNNEL_TOKEN),
and the client-config commands. No ports exposed; the tunnel is
outbound-only.
Code change (small):
- crates/ai-memory-cli/src/commands/serve.rs: extend the
existing non-loopback startup warning. Previously fired only
when bind wasn't loopback AND no auth token was configured;
now ALSO fires (with different message) when bind isn't
loopback AND auth IS configured — to remind the operator
that bearer tokens still travel cleartext on plain HTTP and
point them at docs/https-via-proxy.md. One-shot at startup,
not a refusal to serve (operators may already be behind a
proxy we can't reliably detect; refusing to bind would break
their flow). Single line of log output, links to the doc.
Cross-linking:
- README "Security" section gets a clear "Want HTTPS?"
paragraph pointing at docs/https-via-proxy.md + the two
compose templates. Spells out the loopback exception so the
single-user happy-path user doesn't think they're missing
something.
- README docs table updated: docs/deploy.md row reframed as
"pointers to the TLS guide"; new row added for
docs/https-via-proxy.md with a full description of what it
covers.
- docs/deploy.md's brief "encrypted transport" section
trimmed from inline two-paragraph table to pointers at the
new dedicated doc + the compose templates. Avoids drift
between two places documenting the same thing.
Why this shape (instead of in-binary TLS):
- Reverse proxies are the production-grade answer anyway.
- Caddy does Let's Encrypt better than we ever would; we don't
have to chase rcgen / instant-acme / rustls CVEs.
- Scope discipline — TLS termination is infrastructure, not
ai-memory's job. Bundling them would repeat cognee's
LiteLLM mistake at a smaller scale.
- The four "thinking you're secure when you're not" failure
modes all become the proxy's problem, not ours: silent ACME
expiry, trust-store dance, cert reload races, fail-open
fallback.
No behavior change for existing single-user installs.
720/720 tests pass.
107 lines
4.6 KiB
YAML
107 lines
4.6 KiB
YAML
# docker compose template — ai-memory + Cloudflare Tunnel.
|
|
#
|
|
# Outbound-only tunnel from the homelab to Cloudflare's edge. TLS is
|
|
# terminated at the edge with a Cloudflare cert (universally trusted
|
|
# everywhere). No ports exposed on the host, no public IP needed,
|
|
# no DNS records to set up (Cloudflare manages the CNAME).
|
|
#
|
|
# Pairs especially well with the multi-user homelab case from
|
|
# docs/users.md — every per-user token rides encrypted from each
|
|
# client through Cloudflare to the tunnel.
|
|
#
|
|
# Prerequisites (one-time, in the Cloudflare dashboard):
|
|
# 1. Have a domain on Cloudflare (DNS hosted there; registrar
|
|
# doesn't matter).
|
|
# 2. Zero Trust → Networks → Tunnels → Create a tunnel → name it
|
|
# `ai-memory-homelab` → save.
|
|
# 3. Copy the token Cloudflare gives you into .env as
|
|
# CLOUDFLARE_TUNNEL_TOKEN.
|
|
# 4. In the tunnel's "Public hostnames" tab: add `memory.example.com`
|
|
# pointing at service `http://ai-memory:49374`.
|
|
# 5. (Optional, recommended) Wrap that hostname in a Cloudflare
|
|
# Access application for SSO on top of ai-memory's bearer auth.
|
|
#
|
|
# Adjacent files this compose expects:
|
|
# ./.env.production — AI_MEMORY_AUTH_TOKEN +
|
|
# AI_MEMORY_ALLOWED_HOSTS +
|
|
# CLOUDFLARE_TUNNEL_TOKEN. NOT committed.
|
|
#
|
|
# Walkthrough + client-side install:
|
|
# docs/https-via-proxy.md
|
|
|
|
name: ai-memory-cloudflared
|
|
|
|
services:
|
|
ai-memory:
|
|
image: akitaonrails/ai-memory:latest
|
|
container_name: ai-memory
|
|
restart: unless-stopped
|
|
# No host port. The tunnel reaches ai-memory over the internal
|
|
# docker network — nothing inbound from the public internet.
|
|
expose:
|
|
- "49374"
|
|
volumes:
|
|
- ai-memory-data:/data
|
|
env_file:
|
|
- .env.production
|
|
environment:
|
|
- RUST_LOG=ai_memory=info,ai_memory_store=info,ai_memory_wiki=info,ai_memory_mcp=info,tracing_appender=warn
|
|
healthcheck:
|
|
test: ["CMD", "/usr/local/bin/ai-memory", "status"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 5s
|
|
|
|
cloudflared:
|
|
image: cloudflare/cloudflared:latest
|
|
container_name: ai-memory-tunnel
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- ai-memory
|
|
command: tunnel --no-autoupdate run
|
|
# `cloudflared` exits with code 1 on token rejection, which the
|
|
# `restart: unless-stopped` policy will loop on. Watch the logs
|
|
# the first time you start it — `docker compose logs -f cloudflared`
|
|
# — to confirm registration succeeded.
|
|
environment:
|
|
- TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
|
|
|
|
volumes:
|
|
ai-memory-data:
|
|
name: ai-memory-data
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# .env.production minimum content (DO NOT COMMIT THIS FILE):
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
#
|
|
# AI_MEMORY_AUTH_TOKEN=<long-random-token-from-`ai-memory generate-auth-token`>
|
|
# AI_MEMORY_ALLOWED_HOSTS=memory.example.com,localhost,127.0.0.1
|
|
# AI_MEMORY_BIND=0.0.0.0:49374
|
|
#
|
|
# CLOUDFLARE_TUNNEL_TOKEN=eyJ...<long-base64-from-the-cf-dashboard>...
|
|
#
|
|
# # Optional LLM provider — see docker-compose.yml for the catalogue.
|
|
# # AI_MEMORY_LLM_PROVIDER=anthropic
|
|
# # ANTHROPIC_API_KEY=sk-ant-...
|
|
#
|
|
# `AI_MEMORY_ALLOWED_HOSTS` MUST include the public hostname that
|
|
# Cloudflare forwards under, or ai-memory's DNS-rebinding guard will
|
|
# reject the tunneled requests.
|
|
#
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
# Client config after the tunnel is up
|
|
# ──────────────────────────────────────────────────────────────────────
|
|
#
|
|
# ai-memory install-mcp --client claude-code --apply \
|
|
# --server-url "https://memory.example.com/mcp" \
|
|
# --auth-token "$AI_MEMORY_AUTH_TOKEN"
|
|
#
|
|
# ai-memory install-hooks --agent claude-code --apply \
|
|
# --server-url "https://memory.example.com" \
|
|
# --auth-token "$AI_MEMORY_AUTH_TOKEN"
|
|
#
|
|
# (Substitute codex / cursor / gemini-cli / antigravity / opencode /
|
|
# omp / openclaw — same shape, see docs/install.md for the catalogue.)
|