# 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= (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 ` 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/.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:80` → `127.0.0.1:8880` (setup-pf-redirect.sh) - cloudflared tunnel: `dashboard.qentra.top` → `localhost: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 ```bash 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 stale** — `ls ~/.eagle-dash/pids/` and compare with `ps`. Supervisor adopts automatically on next poll. **Ghost launchctl entries** — after removing plists, use `launchctl remove