Adds an in-dashboard AI assistant that answers questions about live game
state. Designed reactively (no background loops) — every user message in
the chat window or via /api/agent/ask runs one `claude -p` invocation.
Architecture:
- New host-side FastAPI service (agent/) on 127.0.0.1:8767, OUTSIDE the
dereth-tracker Docker container because `claude` and ~/.claude
credentials live on the host.
- nginx routes /api/agent/* to the host service.
- The same browser session cookie the tracker issues authenticates
agent requests (shared SECRET_KEY).
- The agent shells out to `claude -p --session-id <uuid>` with
cwd=/home/erik/MosswartOverlord. Sessions persist as JSONL on disk
via Claude Code's built-in machinery.
- An MCP stdio server (agent/mcp_overlord.py) exposes tools to Claude:
get_live_players, get_recent_rares, query_telemetry_db (read-only,
parsed by sqlglot to reject DML/DDL), get_player_state, get_inventory,
get_inventory_search, get_combat_stats, get_equipment_cantrips,
get_quest_status, get_server_health, suitbuilder_search.
- Read-only PG role (overlord_agent_ro) is the second line of defense
on the SQL tool — even a parser bypass can't mutate.
Frontend:
- AgentWindow.tsx — draggable chat window with localStorage-pinned
session UUID, "New Chat" button, on-mount rehydration from
/agent/sessions/{id}/history (parses Claude Code's JSONL).
- Wired into WindowRenderer + Sidebar (🤖 Assistant button).
Operational:
- systemd unit (overlord-agent.service) + install.sh.
- agent/README.md documents env vars, deploy flow, smoke tests.
- nginx/overlord.conf gets a new /api/agent/ location with 180s timeout.
- CLAUDE.md gets an "Overlord Assistant Mode" section briefing the
agent on which tools to use and how to behave.
NOT YET DEPLOYED — server still needs:
1. Apply agent/sql/0001_overlord_agent_ro.sql + ALTER ROLE password
2. Add AGENT_DB_DSN to /home/erik/MosswartOverlord/.env
3. bash agent/install.sh (creates venv, installs unit, starts service)
4. sudo cp /home/erik/MosswartOverlord/nginx/overlord.conf to nginx + reload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
113 lines
5.1 KiB
Text
113 lines
5.1 KiB
Text
# Nginx site config for overlord.snakedesert.se
|
|
#
|
|
# Lives on the host (not in the Docker stack) at:
|
|
# /etc/nginx/sites-enabled/overlord
|
|
#
|
|
# This file is the source-of-truth copy committed to git. To deploy a change:
|
|
# 1. Edit this file in the repo
|
|
# 2. SSH to the host
|
|
# 3. sudo cp /home/erik/MosswartOverlord/nginx/overlord.conf /etc/nginx/sites-enabled/overlord
|
|
# 4. sudo nginx -t && sudo nginx -s reload
|
|
#
|
|
# Critical settings:
|
|
# - proxy_read_timeout / proxy_send_timeout 1d on /websocket/ and /
|
|
# WebSockets are long-lived; nginx's default 60s timeout drops idle clients.
|
|
# Removing these timeouts caused all plugin connections to drop every
|
|
# ~60s when no data flowed from backend to client (April 2026 incident).
|
|
# - Bearer token in /grafana/ proxy_set_header is a Grafana service account
|
|
# token used for anonymous panel embeds. Rotate when credentials leak.
|
|
|
|
server {
|
|
listen 443 ssl;
|
|
server_name overlord.snakedesert.se;
|
|
|
|
# Security hardening
|
|
server_tokens off;
|
|
add_header X-Frame-Options SAMEORIGIN always;
|
|
add_header X-Content-Type-Options nosniff always;
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
add_header Referrer-Policy strict-origin-when-cross-origin always;
|
|
add_header Permissions-Policy "geolocation=(), camera=(), microphone=()" always;
|
|
|
|
# SSL certificates
|
|
ssl_certificate /etc/letsencrypt/live/overlord.snakedesert.se/fullchain.pem;
|
|
ssl_certificate_key /etc/letsencrypt/live/overlord.snakedesert.se/privkey.pem;
|
|
include /etc/letsencrypt/options-ssl-nginx.conf;
|
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
|
|
|
# Plugin WebSocket ingest — `/ws/position` upstream
|
|
location /websocket/ {
|
|
proxy_pass http://tracker/ws/position;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header X-Plugin-Secret $http_x_plugin_secret;
|
|
proxy_cache_bypass $http_upgrade;
|
|
# Long-lived WebSocket: don't time out the proxy
|
|
proxy_read_timeout 1d;
|
|
proxy_send_timeout 1d;
|
|
}
|
|
|
|
# Overlord Agent — host-side service running OUTSIDE the Docker stack
|
|
# because it shells out to `claude` which depends on host-side
|
|
# ~/.claude credentials. Long timeout because agent calls can spin
|
|
# while Claude Code chains tool invocations.
|
|
location /api/agent/ {
|
|
proxy_pass http://127.0.0.1:8767/agent/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_pass_request_headers on;
|
|
proxy_read_timeout 180s;
|
|
proxy_send_timeout 180s;
|
|
}
|
|
|
|
# API endpoints (live, trails, history, stats) — short-lived HTTP
|
|
location /api/ {
|
|
proxy_pass http://tracker/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_cache_bypass $http_upgrade;
|
|
}
|
|
|
|
# Frontend UI and browser WebSocket (`/ws/live` upstream)
|
|
location / {
|
|
proxy_pass http://tracker/;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_cache_bypass $http_upgrade;
|
|
# Long-lived browser WebSocket (/ws/live): don't time out
|
|
proxy_read_timeout 1d;
|
|
proxy_send_timeout 1d;
|
|
}
|
|
|
|
# Grafana Dashboard UI (served under /grafana)
|
|
location /grafana/ {
|
|
proxy_pass http://grafana;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
proxy_set_header Authorization "Bearer glsa_AcDTcN5CUX9h5Bi2ipmVAs6g1FRTSIWk_8b81cf99";
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_cache_bypass $http_upgrade;
|
|
}
|
|
}
|