跳至內容
Deployboardv0.0.3

macOS · 一個指令 · 127.0.0.1

Launchd 沒有 dashboard。這是在瀏覽器管理自己的 launchd services 的 console,同時輸出 Prometheus metrics。

一個指令裝好。瀏覽器裡看狀態、日誌、警報。

$brew install --cask francistse/tap/deployboard
安裝一個指令瀏覽器狀態 · 日誌 · 警報輸出Prometheus /metrics
127.0.0.1:9410 · Deployboard
Deployboard
在瀏覽器管理自己的 launchd services
SystemLightDark⚙ 設定
16 開 · 2 關我的 16 · 其他 1 · 雜訊 528寫入模式http://127.0.0.1:9410
搜尋標籤…
全部 (545)執行中 (163)已排程 (2)已完成 (0)已停止 (239)錯誤 (139)離線 (1)已停用 (1)
標籤PID結束重啟操作
範例應用3105 項
com.example.app.api我的已列入
730872h 14m028
🔔重啟日誌
com.example.app.web我的自動(路徑)↻ 頻繁重啟
730916d 3h01207↻
🔔重啟日誌
com.example.app.collector我的自動(路徑)
7310418m021
🔔重啟日誌
com.example.worker.nightly我的已列入
—13
🔔啟動日誌
com.example.backup.weekly我的自動(路徑)下次 · Sun 03:00
—00
🔔啟動日誌
維運0012 項
com.example.worker.old我的已列入⏹ 已退役 12 天
——0
啟用日誌
com.example.legacy.agent我的自動(路徑)
—00
啟動日誌
GET /api/eventstext/event-stream每 5 秒推送

按「我的」、「其他」或「雜訊」。然後看 /metrics 和 Telegram 警報。

為何

launchctl 列出全部,所以你什麼都看不見。

原有的工具回答「載入了什麼」,從不回答「我部署了什麼,它是否健康」。用使用者網域來篩也幫不上忙——一台在用的 Mac 上,看起來像「你的」作業,大多是 Dropbox、Google 的更新程式、Spotify、Tailscale,再加上一兩個瀏覽器輔助程式。那個重啟了 1,207 次的作業,就埋在正中間。

Deployboard 從一份明確的清單出發:你自己控制的允許清單,再加上路徑推導——plist 的記錄路徑、工作目錄、program 或參數只要落在你的項目樹裡,那就是你的作業,自動算進去。其餘是系統自己的事,永遠只隔一下點擊,但不會擋在前面。

沒有 Deployboard
  • launchctl list — 545 行,分不清哪些是你的
  • 悄悄重啟了 1,207 次的開發伺服器,看起來仍很健康
  • 你刻意停用的 plist,一直被當成失敗
  • 沒有歷史:「這算正常嗎?」沒有答案
  • 要等你偶然發現,才知道有東西已經死了
有了 Deployboard
  • 我的 / 其他 / 雜訊 — 預設先顯示你的部署
  • 來自 launchctl print 的 runs(重啟次數),超過閾值就標出
  • disabled 是一種狀態,不是錯誤 — 退役就是退役
  • Prometheus 的 /metrics,讓 Grafana 能顯示「從何時開始」
  • 狀態轉變時才發 Telegram 警報,按應用程式,帶冷卻時間

狀態

七種狀態,其中一種的意思是「我把它退役了」。

大多數工具把狀態收成 running / stopped / error,於是你幾個月前自己關掉的 plist,會永遠看起來像失敗。Deployboard 保留 launchd 真正區分的那些狀態,並讀取 launchctl print-disabled,讓意圖也成為答案的一部分。

執行中

PID > 0 — launchd print 同意它仍在運行

已排程

PID = 0、正常結束、已定義排程、沒有近期記錄

已完成

PID = 0、正常結束、記錄的 mtime 在 --recent-window 之內

已停止

PID = 0、正常結束、沒有排程、沒有近期記錄

錯誤

上次結束狀態非零

已停用

launchctl print-disabled 表示 disabled,沒有存活的 pid — 退役,不是壞掉

離線

有 plist 檔案,但 launchctl list 不返回該 label

這個分叉加上的

上游回答「載入了什麼」。這裡回答「我部署了什麼」。

四項新增,全部建在 launchctl 自己的輸出上——不去猜一個作業屬於哪個廠商。

01

用 launchctl print,而不是 launchctl list。

launchctl list 沒有重啟次數,也沒有 disabled 旗標。Deployboard 用 launchctl print 讀 runs 和 pid,用 launchctl print-disabled 讀意圖——於是抖動看得見,刻意退役的作業也不再看起來像壞掉。

02

清單由你決定。

我的 / 其他 / 雜訊,每一行都有出處(已列入、自動(路徑)、未分類)。分類控制會寫回 config.json,大約 2 秒內即時重新載入——教它一個新應用程式,不必重啟。

03

給 launchd 的 Prometheus 指標。

deployboard_job_up、_runs、_last_exit_code、_disabled,再加上 IPv4/IPv6 連接埠探測。儀表板只能告訴你「現在停了」;時間序列能告訴你「03:10 開始抖動」。

04

可按應用程式開關的 Telegram 警報。

只在狀態轉變時通知——error、offline、連接埠無法連線、重啟風暴,以及復原——並有按應用程式的開關、冷卻時間、靜音時段。bot token 放在 macOS 鑰匙圈,永不寫進檔案。

上游仍然保留的部分
記錄追蹤

每個作業的 stdout 與 stderr,最多 10,000 行,可即時篩選。

六項診斷

結束碼、program 是否存在、program 可否執行、plist 擁有者、plist 權限、記錄路徑。

SSE,不是輪詢

GET /api/events 每 5 秒推送完整的作業清單。

一按控制

經 launchctl 做 start / stop / reload,並有確認對話框。

JSON API

與介面用的是同一面,有文件,curl 也好用。

零依賴

一個 Go 二進位檔,前端以 go:embed 內嵌,沒有常駐程式,沒有遙測。

指標

這個領域裡,唯一的 launchd exporter。

把 Prometheus 指到介面用的同一個連接埠。標籤基數有界,省略是誠實的(從未結束的作業沒有 last-exit 序列),只有暴露了連接埠的作業才有 probe 序列——探測也會試 IPv6 的 ::1,因為有些開發伺服器只綁在那裡。

GET /metrics
# HELP deployboard_job_up 1 when the launchd job is running.
# TYPE deployboard_job_up gauge
deployboard_job_up{label="com.example.app.api",category="ours",group="Example App"} 1
deployboard_job_runs{label="com.example.app.web",category="ours",group="Example App"} 1207
deployboard_job_disabled{label="com.example.worker.old",category="ours"} 1
deployboard_job_probe_reachable{label="com.example.app.api",port="8001"} 1
deployboard_job_probe_latency_seconds{label="com.example.app.api",port="8001"} 0.003
deployboard_jobs_total{category="ours"} 16
deployboard_jobs_total{category="noise"} 528
promql
# restart churn, per job, over 6h
deriv(deployboard_job_runs{category="ours"}[6h]) * 3600
# anything of yours that is not running
deployboard_job_up{category="ours"} == 0

從提供介面的同一個行程擷取即可,不必另外部署 exporter,也不需要 sidecar。沒有的序列就省略,不會造假:從未結束的作業不會有 last_exit 行,沒有連接埠的作業也不會有 probe 行。

警報

它會在 Telegram 通知你,而且只說一次。

按狀態轉變來發,所以一個來回抖動的作業不能洗版:壞掉時一則,若持續重啟則一則受速率限制的,恢復時再一則。復原訊息永不被靜音時段壓掉。bot token 放在 macOS 鑰匙圈,由儀表板自己寫入——永不進檔案,永不進記錄行,也永不回到頁面上。

✈telegram · deployboard
🔴 RUN_STORM
Example App · com.example.app.web
狀態: 執行中
重啟: 1207
2026-10-03 10:41:07 +0800
觸發種類
狀態變為 errorerror
有 plist,但未載入offline
連接埠不再回應unreachable
每輪掃描之間 runs +25run_storm
回到 runningrecovery
按應用程式的開關、冷卻時間、靜音時段,以及靜音時段永不吞掉的復原訊息。

API

介面用到的一切,你的腳本也能用。

Web 介面是 /api/jobs 和 /api/events 的一個消費者。curl 也是。綁定伺服器,指向端點,解析 JSON。

方法路徑說明
GET/api/inventory類別與群組計數,以及每個作業如何被分類
POST/api/inventory/classify把作業釘選為我的 / 隱藏 / 回到自動
GET/metricsPrometheus 文本格式 — 作業、runs、結束碼、探測
GET/api/jobs列出每個作業,包括離線的 plist
GET/api/jobs/{label}單一作業快照(status、pid、runs、disabled)
GET/api/alerts按應用程式的警報狀態,以及已啟用 / 已停用計數
POST/api/alerts/{label}切換警報: {"enabled": false}
POST/api/alerts/{label}/test為該作業發送一則測試警報
GET/api/settings/telegram已儲存 token?有 chat id?——永不回傳 token 本身
GET/api/jobs/{label}/logs?lines=200追蹤 stdout 與 stderr(最多 10,000)
GET/api/eventsSSE 串流 — 每 5 秒推送完整作業清單
POST/api/jobs/{label}/reloadlaunchctl bootout + bootstrap(唯讀模式回 403)

安裝

一行指令。一個設定檔。一個分頁。

三種安裝方式、同一個二進位檔:Homebrew cask(首屏指令)、不用 Go 也能裝 LaunchAgent 的 curl install-release.sh、或從 clone 執行 bash install.sh。不用 sudo;腳本支援 --dry-run 與 --uninstall。

01 · 安裝
$ brew install --cask francistse/tap/deployboard

用 Homebrew cask 安裝二進位檔,或用 install-release.sh / install.sh 一併寫入帶 RunAtLoad + KeepAlive 的 LaunchAgent。不用 sudo;腳本支援 --dry-run 與 --uninstall。

02 · 指向你的應用程式
$ open ~/.config/deployboard/config.json

你可以在 config.json 的 inventory.ours 下列出命名空間,也可以把項目資料夾加進 derive_roots,讓 plist 路徑自己分類。未分類的會出現在「其他」——在介面按一下就釘選,config.json 會即時重新載入。

03 · 看著它,或擷取它

儀表板在 127.0.0.1 打開。用 Prometheus 擷取 /metrics,用鈴鐺按應用程式開關警報,並在系統、淺色、深色之間切換外觀。

7

狀態值

上游六個 + disabled

2s

設定即時重新載入

不必重啟就能分類新應用程式

12

Prometheus 序列家族

作業、runs、結束碼、探測

127.0.0.1

唯一綁定的地址

沒有雲端、沒有遙測、沒有更新探測

FAQ

一個分叉必須回答的問題。

包括它和上游是什麼關係。

它和所分叉的 launch-pilot 有何不同?

上游做的事都還在——作業表、記錄追蹤、六項診斷、start/stop/reload。Deployboard 加上清單視圖(我的 / 其他 / 雜訊,帶路徑推導)、launchctl print 的補充(runs 和 disabled)、Prometheus 端點、可按應用程式開關的 Telegram 警報、一行指令的 macOS 安裝,以及唯讀模式。

它怎麼知道哪些作業是我的?

兩條規則,按順序:先是你寫下的允許清單(inventory.ours),然後是推導——如果 plist 的記錄路徑、工作目錄、program 或參數落在你列於 derive_roots 的目錄裡,那就是你的作業。plist 指向的一切都是證據,所以沒有猜測,也沒有網域的經驗法則。

如果它把一個作業判錯了呢?

它會出現在「其他」,標成「未分類」,你在該行選擇自動 / 我的 / 隱藏。這會寫入 config.json,伺服器大約兩秒內即時重新載入。沒有東西會被悄悄藏起:完整的 launchd 清單永遠只隔一個標籤。

這些東西會離開我的機器嗎?

不會。HTTP 伺服器只綁定 127.0.0.1,沒有分析追蹤,也不會回報。整個項目唯一能打出去的,是你明確開啟的 Telegram 訊息——用的 token 存在你自己的鑰匙圈裡。

可以只用指標、不要警報嗎?

可以。警報預設對你自己的應用程式開啟,對其餘的關閉;可以按應用程式、按類別,或整個關掉。/metrics 和 JSON API 照樣可用,而 --read-only 會拒絕每一次會改動狀態的呼叫。

為什麼它從不修改我的 plist?

故意的。Deployboard 讀取 launchd 狀態,也可以請 launchctl 對一個 label 做 start、stop 或 reload,但不會改寫磁碟上的 plist。若你要的是 plist 編輯器,LaunchControl 是既有的付費選擇;這個工具關心的是,你的部署正在做什麼。