7.4 KiB
7.4 KiB
created, tags, updated
| created | tags | updated | |||||
|---|---|---|---|---|---|---|---|
| 2026-06-25 |
|
2026-06-25 |
Eagle Dashboard — Launchd Integration
Обзор
Eagle Dashboard (eagle-dash) имеет полноценную поддержку launchd-джобов наравне с Hermes Eagle/Whale кронами. Пользователь может создавать, просматривать, запускать/останавливать и удалять launchd-агенты прямо из интерфейса дашборда.
Архитектура
Backend (main.py)
Три ключевые функции:
_load_launchd_plist(plist)— читает один.plistфайл, парсит его, вызываетlaunchctl listдля статуса (PID, LastExitStatus, enabled), возвращает унифицированный словарь._list_all_launchd()— читает все*.plistиз~/Library/LaunchAgents/, вызывает_load_launchd_plist()для каждого._list_launchd_crons()— фильтрует только те, у которых естьStartIntervalилиStartCalendarInterval._list_launchd_services()— фильтрует те, у которых НЕТ расписания (always-on демоны).
API endpoints
| Endpoint | Метод | Описание |
|---|---|---|
/api/crons?source=launchd |
GET | Список launchd-кронов (с расписанием) |
/api/services/launchd |
GET | Список launchd-сервисов (без расписания, always-on) |
/api/crons |
POST | Создание нового launchd-агента (source=launchd) |
/api/crons/launchd/{label} |
PATCH | Обновление launchd-агента (plist) |
/api/crons/launchd/{label} |
DELETE | Удаление launchd-агента |
/api/crons/launchd/{label}/toggle |
POST | Вкл/выкл launchd-агента (launchctl load/unload) |
Создание launchd-крона (POST /api/crons)
- Поле
source: "launchd"в body name— Label для plist (если не начинается сcom., добавляется префиксcom.)schedule— парсится какevery Ns/m/h/d→StartIntervalв секундахscript—ProgramArguments(разбивается по пробелам)workdir—WorkingDirectoryrun_at_load(boolean) —RunAtLoadв plist- Поля
prompt,deliver,skills,toolsets,model,provider— скрыты на фронте, не отправляются для launchd - Plist пишется атомарно через
.plist.tmp→.replace() - После записи —
launchctl load -w
Чтение статуса launchd
launchctl list {label} на macOS (Sequoia 15.x, next-формат) возвращает openstep-style plist (не JSON, не tab-separated):
{
"PID" = 93796;
"LastExitStatus" = 0;
"Label" = "com.eagle.dashboard";
...
}
Парсинг через regex: r'"PID"\s*=\s*(\d+)' и r'"LastExitStatus"\s*=\s*(-?\d+)'.
Логика статуса:
state = "running"— если PID > 0state = "scheduled"— если есть расписание, процесс не запущенstate = "stopped"— если disabled (launchctl вернул non-zero)state = "idle"— enabled, без расписания, без PIDlast_status = Noneесли процесс запущен (PID есть), иначе числовой exit codelast_status = null— ни разу не запускался
Фронтенд (templates/index.html)
- Services вкладка — показывает Docker контейнеры, сервисы из registry, и секцию "Launchd (Always-On)" с always-on демонами. Карточки launchd-сервисов имеют кнопки Pause/Resume и Delete.
- Crons вкладка — показывает Hermes Eagle/Whale кроны + launchd-кроны (только с расписанием). Для launchd-кронов показывается schedule, exit code, runs count (runs не реализован из-за отсутствия данных в launchd).
- Модалка создания — при выборе
source = launchdскрываются поля: Deliver, Prompt, Model+Provider, Skills+Toolsets; показывается чекбокс "Run on load" (runAtLoad, отправляется какrun_at_load: boolean) - Schedule hint — для launchd показывается упрощённый хинт: только
every Ns/m/h/d - Tab URL persistence — через
history.replaceState+ URLSearchParams (?tab=crons) - Контролы на launchd-сервисах (Services вкладка): кнопка ⏸ pause / ▶ resume (вызывает
/api/crons/launchd/{label}/toggle), кнопка 🗑 delete (вызываетDELETE /api/crons/launchd/{label})
Ключевые решения
- Зачем отдельный launchd CRUD — чтобы пользователь мог управлять своими кронами из единого интерфейса, не открывая терминал.
- Разделение crons/services — launchd-агенты без
StartInterval/StartCalendarIntervalне показываются в Crons. Вместо этого они отображаются на Services вкладке как "Launchd (Always-On)". - PID vs LastExitStatus — для запущенного процесса exit code неактуален, поэтому
last_status = Noneпри PID > 0. - Next-формат — macOS Sequoia возвращает openstep plist (не tab-separated, не JSON):
"Key" = value;. Парсинг через regex. - Атомарная запись plist — через
.tmp+.replace()чтобы не повредить plist при ошибке записи. _list_launchd_crons()vs_list_launchd_services()— обе читают все plist из_list_all_launchd(), но фильтруют поhas_schedule. Используют общий_load_launchd_plist().
Файлы
/Users/admin/Developer/eagle-dash/main.py— backend/Users/admin/Developer/eagle-dash/templates/index.html— frontend/Users/admin/Developer/eagle-dash/.venv/— virtualenv (FastAPI + uvicorn)- Запуск через launchd:
~/Library/LaunchAgents/com.eagle.dashboard.plist
Статус (2026-06-25)
- Backend CRUD для launchd (create, read, update, delete, toggle)
- Список launchd-кронов на Crons вкладке
- Список always-on launchd-демонов на Services вкладке
- Чекбокс RunAtLoad при создании
- Правильные статусы: running / scheduled / idle / stopped + exit code
- PID и LastExitStatus читаются из launchctl list (next-format)
- Спрятаны поля для launchd (prompt, deliver, skills и т.д.)
- Tab URL persistence
- Кнопки Pause/Resume/Delete на карточках launchd (и в Crons, и в Services)
- Разделение crons/services: с расписанием → Crons, без расписания → Services
_list_launchd_services()— новый endpoint/api/services/launchd