/** * pm2 ecosystem entry for legal-chat-service — the host-side SSE bridge * to ``claude`` CLI that powers the /training chat tab. * * Security: the service spawns the claude CLI on behalf of any caller * that hits /chat/start. claude tools include Bash, Read, Edit — so an * unauthenticated request to /chat/start is effectively RCE-equivalent. * Two defenses, both required: * 1. Bind to 10.0.1.1 (docker0 bridge gateway) — only host + containers * on docker bridges can reach the socket; nothing outside the host. * 2. Bearer token auth — secret loaded from /home/chaim/.legal-chat-service.env * (chmod 600) and mirrored in Coolify as LEGAL_CHAT_SHARED_SECRET. * The service refuses to start without the secret set. * * Why pm2: * - Auto-restart if the process dies (claude CLI subprocess failures * should never leave the service in a half-dead state). * - Log rotation matches paperclip's behavior so the chair sees * consistent log paths under ~/.pm2/logs/. * * Install (once): * pm2 start /home/chaim/legal-ai/scripts/legal-chat-service.config.cjs * pm2 save * * Smoke test: * curl http://10.0.1.1:8770/health * # → {"ok":true,"service":"legal-chat-service"} * * Update: * pm2 restart legal-chat-service --update-env * * Stop: * pm2 stop legal-chat-service */ const fs = require("fs"); // Load LEGAL_CHAT_SHARED_SECRET from a chmod 600 file off the repo. // The same value is mirrored in Coolify as the LEGAL_CHAT_SHARED_SECRET // env var so the FastAPI proxy sends a matching Authorization header. // SoT in Infisical: nautilus:/legal-ai/LEGAL_CHAT_SHARED_SECRET (migrated // 2026-06-07). This local file remains the runtime source; rotate in both. const ENV_FILE = "/home/chaim/.legal-chat-service.env"; const env = { HOME: "/home/chaim", PATH: "/home/chaim/.local/bin:/usr/local/bin:/usr/bin:/bin", PYTHONUNBUFFERED: "1", }; try { const text = fs.readFileSync(ENV_FILE, "utf8"); for (const line of text.split("\n")) { if (!line || line.trim().startsWith("#")) continue; const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*?)\s*$/); if (m) env[m[1]] = m[2]; } } catch (e) { console.error(`legal-chat-service: failed to load ${ENV_FILE}: ${e.message}`); console.error("Service will refuse to start without LEGAL_CHAT_SHARED_SECRET."); } module.exports = { apps: [ { name: "legal-chat-service", cwd: "/home/chaim/legal-ai/mcp-server", script: "/home/chaim/legal-ai/mcp-server/.venv/bin/python", args: "-m legal_mcp.chat_service.server --port 8770 --host 10.0.1.1", env, restart_delay: 5000, max_restarts: 10, autorestart: true, max_memory_restart: "500M", }, ], };