Files
obsidian-vault/personal/projects/eagle-dashboard.md
T

4.9 KiB

Eagle Dashboard

Process supervisor and control panel for all Eagle local services.

URL: http://localhost:8880 (internal) · https://dashboard.qentra.top (external, token-required) Source: ~/Developer/eagle-dash/ LaunchAgent: com.eagle.dashboard (single Login Item, KeepAlive=true)


Architecture

FastAPI + HTMX + Tailwind CDN + Alpine.js. No build step, CDN-only frontend.

~/Developer/eagle-dash/
├── main.py           # FastAPI app, lifespan, SSE
├── auth.py           # Origin-aware auth: 127.0.0.1 → free; cloudflared → token
├── supervisor.py     # PID-file-based process supervisor (asyncio, setsid)
├── registry.py       # Service registry + YAML loader
├── health.py         # Health checks: http / port / process / launchctl
├── control.py        # /restart /stop /start endpoints
├── memory.py         # RSS tracking (main process + children)
├── loader.py         # services.yaml loader
├── services.yaml     # Service definitions (source of truth)
├── .env              # EAGLE_TOKEN=<secret>  (not committed)
├── templates/
│   └── index.html    # Dashboard UI — 3 tabs: Services, Pages, Files
└── com.eagle.dashboard.plist

Auth

  • 127.0.0.1 direct access → no auth
  • Requests via cloudflared (have X-Forwarded-For) → require Authorization: Bearer <EAGLE_TOKEN> or eagle_token cookie (set on first login, 30-day expiry)
  • Token stored in .env

Supervisor (supervisor.py)

PID-file-based — survives Dashboard restarts without crashing.

  • Services launched with start_new_session=True (setsid) → run in own session
  • PID written to ~/.eagle-dash/pids/<id>.pid immediately on launch
  • On Dashboard restart: reads PID file → if process alive → adopts without re-launching
  • If process dead → launches fresh
  • Auto-restart with exponential backoff (2s → 60s max)

macOS integration

  • pf anchor: localhost:80127.0.0.1:8880 (setup-pf-redirect.sh)
  • cloudflared tunnel: dashboard.qentra.toplocalhost:8880
  • FileBrowser: http://127.0.0.1:8181 (FB_NOAUTH=true, auth delegated to Dashboard)

Services

20 services in services.yaml, 10 with autostart: true (daemon category).

ID Name Access Type
hermes Hermes Eagle localhost daemon
hermes_whale Hermes Whale localhost daemon
claude_proxy_node Claude Proxy (node) localhost daemon
claude_proxy_python Claude Proxy (python) localhost daemon
asana_mcp Asana MCP localhost daemon
virfield Virfield localhost daemon
reflect_proxy Reflect Proxy localhost daemon
reflect_fdroid Reflect F-Droid localhost daemon
aw_ddg AW DDG Watcher localhost daemon
aw_xcode AW Xcode Watcher localhost daemon
mlx_lm MLX LM Server lan (0.0.0.0:8080) on-demand
ollama Ollama lan (0.0.0.0:11434) on-demand
filebrowser FileBrowser localhost daemon

Remaining LaunchAgents (not under Dashboard):

  • com.colima.start — Homebrew managed
  • homebrew.mxcl.postgresql@17 — Homebrew managed
  • personal.os.wiki-ingest — cron schedule
  • com.personalos.heartbeat — cron schedule

Network Access Badges

  • 🟢 localhost — bound to 127.0.0.1, safe
  • 🟡 lan — bound to 0.0.0.0, accessible on home network (mlx_lm, ollama)
  • 🔵 tunnel — cloudflared-managed, token-protected

Tabs

Services — health cards with: status badge, PID, uptime, memory (RSS + children), start/stop/restart controls, log tail drawer (last 200 lines).

Pages — URL input + iframe for quick local page testing.

Files — FileBrowser iframe at http://localhost:8181.


Deployment

cd ~/Developer/eagle-dash

# Install deps
uv sync

# Generate token
python3 -c "import secrets; print('EAGLE_TOKEN='+secrets.token_hex(32))" > .env

# Setup pf redirect (localhost:80 → :8880)
sudo bash setup-pf-redirect.sh

# Load LaunchAgent
launchctl load ~/Library/LaunchAgents/com.eagle.dashboard.plist

Troubleshooting

Services show running: null — check supervisor.supervisor_status field in /api/services (not top-level running). The supervisor status is in the nested supervisor object.

PID files stalels ~/.eagle-dash/pids/ and compare with ps. Supervisor adopts automatically on next poll.

Ghost launchctl entries — after removing plists, use launchctl remove <label> (not bootout) to clear bootstrap session entries.

Virfield port conflict — if previously a LaunchAgent (plist deleted but still loaded), stop it: launchctl stop com.virfield.server && launchctl remove com.virfield.server.


History

  • 2026-06-04: Initial build. Migrated 14 LaunchAgent plist services under Dashboard supervisor. Single com.eagle.dashboard Login Item. cloudflared tunnel at dashboard.qentra.top. Ghost entries cleaned via launchctl remove.