Files
obsidian-vault/personal/tech/eagle-dashboard-launchd.md
T
Taiga b53dd70a15 [2026-09-26 09:10:28] syncthing: personal/tech/archive-org-collection-harvest.md personal/tech/balda-go-fx-pitfalls.md personal/tech/bilety-oxota-interaktiv.md personal/tech/cloudflare-tunnel-ssh-published-route.md personal/tech/deepseek-language-drift.md personal/tech/docker-data-root-mount-race.md personal/tech/docker-mac-disk-issues.md personal/tech/docker_colima_setup.md personal/tech/eagle-dashboard-launchd.md personal/tech/hermes-agent-improvements.md personal/tech/hermes-browser-toolsets.md personal/tech/hermes-fork-vs-upstream.md personal/tech/hermes-git-repo.md personal/tech/hermes-memory-architecture.md personal/tech/hermes-secret-redaction-bypass.md personal/tech/hermes-self-improvement-review.md personal/tech/home-network-config.md personal/tech/inter-site-throughput-taiga-to-mac.md personal/tech/inter-site-throughput-taiga-to-mac.md.bak-nedstab2 personal/tech/jellyfin-db-schema-mismatch.md personal/tech/karabiner-keyboard-config.md personal/tech/kraken-media-inventory-facts.md personal/tech/kraken-truenas-media-copies.md personal/tech/lampa-jellyfin-plugin.md personal/tech/lampac-deployment.md personal/tech/lampac-torrserver.md personal/tech/llm-model-selection-2026.md personal/tech/mac-print-shared-services.md personal/tech/modbus-rtu-sniffer-debugging.md personal/tech/multistream-single-flow-limit.md personal/tech/multistream-single-flow-limit.md.bak-nedstab2 personal/tech/music-tags-repair-truenas.md personal/tech/obsidian-mcp-ecosystem.md personal/tech/original-xbox-2025-guide.md personal/tech/prowlarr-flaresolverr-cloudflare.md personal/tech/radarr-sonarr-phantom-records.md personal/tech/roundtrip-key-verification.md personal/tech/ryujinx-mac-setup.md personal/tech/skill-arr-media-stack-setup-additions.md personal/tech/skill-docker-data-root-mount-race.md personal/tech/skill-lampac-kraken-deploy.md personal/tech/tirith-trust.md personal/tech/truenas-local-tree-comparison.md personal/tech/truenas-nfs4-acl-and-arrmultiuser.md personal/tech/truenas-zfs-panic-recovery.md personal/tech/vault-doc-pruning.md personal/tech/vless-space-subscription-egress.md personal/tech/watchlist-sync-feed.md personal/tech/watchtower-docker-updates.md personal/tech/xray-outbound-subscription-3xui.md personal/tech/xray-reverse-limits-private-network.md personal/tech/xray-reverse-tunnel-kraken-truenas.md personal/tech/xray-reverse-tunnel-kraken-truenas.md.bak-nedstab2 personal/tech/yandex-music-api.md
2026-09-26 09:10:28 +00:00

7.4 KiB
Executable File
Raw Blame History

created, tags, updated
created tags updated
2026-06-25
eagle-dashboard
launchd
cron
macos
devops
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 — WorkingDirectory
  • run_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 > 0
  • state = "scheduled" — если есть расписание, процесс не запущен
  • state = "stopped" — если disabled (launchctl вернул non-zero)
  • state = "idle" — enabled, без расписания, без PID
  • last_status = None если процесс запущен (PID есть), иначе числовой exit code
  • last_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})

Ключевые решения

  1. Зачем отдельный launchd CRUD — чтобы пользователь мог управлять своими кронами из единого интерфейса, не открывая терминал.
  2. Разделение crons/services — launchd-агенты без StartInterval/StartCalendarInterval не показываются в Crons. Вместо этого они отображаются на Services вкладке как "Launchd (Always-On)".
  3. PID vs LastExitStatus — для запущенного процесса exit code неактуален, поэтому last_status = None при PID > 0.
  4. Next-формат — macOS Sequoia возвращает openstep plist (не tab-separated, не JSON): "Key" = value;. Парсинг через regex.
  5. Атомарная запись plist — через .tmp + .replace() чтобы не повредить plist при ошибке записи.
  6. _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