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

167 lines
5.5 KiB
Markdown

# 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: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
24 services in `services.yaml`, 10 with `autostart: true`. Секция **Docker** — сервисы, запускающиеся через docker-compose — отображается первой в дашборде.
### Docker
| ID | Name | Access | Type |
|----|------|--------|------|
| zulip | Zulip Connector | localhost | container |
| filebrowser | FileBrowser | localhost | container |
### AI
| 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 |
### ML
| ID | Name | Access | Type |
|----|------|--------|------|
| mlx_lm | MLX LM Server | lan (0.0.0.0:8080) | on-demand |
| ollama | Ollama | lan (0.0.0.0:11434) | on-demand |
### Reflect
| ID | Name | Access | Type |
|----|------|--------|------|
| reflect_proxy | Reflect Proxy | lan | daemon |
| reflect_fdroid | Reflect F-Droid Server | lan | daemon |
### Monitoring
| ID | Name | Access | Type |
|----|------|--------|------|
| aw_ddg | AW Watcher DDG | localhost | daemon |
| aw_xcode | AW Watcher Xcode | localhost | daemon |
### Orchestration
| ID | Name | Access | Type |
|----|------|--------|------|
| wiki_ingest | Wiki Ingest | localhost | cron |
**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 <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`.