diff --git a/personal/projects/eagle-dashboard.md b/personal/projects/eagle-dashboard.md index 8d49d390..84b824ab 100644 --- a/personal/projects/eagle-dashboard.md +++ b/personal/projects/eagle-dashboard.md @@ -1,1621 +1,137 @@ -# Eagle Dashboard — Implementation Plan +# Eagle Dashboard -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +Process supervisor and control panel for all Eagle local services. -**Goal:** Replace `localhost` with a visual control panel that shows health, enables management (start/stop/restart) of all 25+ Eagle LaunchAgent services, and displays network security posture per service. - -**Architecture:** FastAPI backend reads a YAML service registry and checks health + memory via HTTP/port/process checks every 30s. HTMX + Tailwind CDN frontend with three tabs: **Services** (health cards with memory usage + control), **Pages** (local page tester — URL input + iframe), **Files** (embedded FileBrowser). Origin-aware auth middleware: `127.0.0.1` → free access; external via cloudflared `dash.qentra.top` → Bearer token required. - -Runs internally on `127.0.0.1:8880`. macOS `pf` anchor redirects `localhost:80` → `:8880` so it's accessible at plain `http://localhost` (replacing macOS default httpd). Cloudflared tunnel points directly to `:8880`. FileBrowser Docker container on `:8181`. - -**Tech Stack:** Python 3.11, FastAPI, uvicorn, HTMX 2, Tailwind CDN, Alpine.js, PyYAML, psutil, httpx, python-dotenv +**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) --- -## Scope note +## Architecture -Three independent subsystems: -- **Plan A (this doc):** Service registry + health API + dashboard UI + LaunchAgent control + auth + FileBrowser + Pages tab + cloudflared tunnel -- **Plan B (future):** Network security panel — pf rules, per-service firewall toggling, bind-address runtime verification - ---- - -## File Structure +FastAPI + HTMX + Tailwind CDN + Alpine.js. No build step, CDN-only frontend. ``` ~/Developer/eagle-dash/ -├── main.py # FastAPI app — routes, startup, SSE -├── auth.py # Origin-aware auth middleware -├── registry.py # Service registry loader + YAML parser -├── health.py # Health check engine (http/port/process/launchctl) -├── control.py # launchctl start/stop/restart wrappers -├── services.yaml # Service definitions (source of truth) -├── .env # EAGLE_DASH_TOKEN= +├── 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 — 3-tab HTMX + Tailwind CDN + Alpine.js -├── docker/ -│ └── filebrowser.yml # FileBrowser Docker Compose -├── requirements.txt -├── justfile -└── com.eagle.dashboard.plist # LaunchAgent plist +│ └── 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 ``` --- -## Task 1: Project Bootstrap +## Troubleshooting -**Files:** -- Create: `~/Developer/eagle-dash/requirements.txt` -- Create: `~/Developer/eagle-dash/justfile` +**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. -- [ ] **Step 1: Create project directory and requirements** +**PID files stale** — `ls ~/.eagle-dash/pids/` and compare with `ps`. Supervisor adopts automatically on next poll. -```bash -mkdir -p ~/Developer/eagle-dash/templates -``` +**Ghost launchctl entries** — after removing plists, use `launchctl remove