macOS · 一個指令 · 127.0.0.1
Launchd 沒有 dashboard。這是在瀏覽器管理自己的 launchd services 的 console,同時輸出 Prometheus metrics。
一個指令裝好。瀏覽器裡看狀態、日誌、警報。
| 標籤 | PID | 結束 | 重啟 | 操作 | |
|---|---|---|---|---|---|
範例應用3105 項 | |||||
com.example.app.api我的已列入 | 730872h 14m | 0 | 28 | 🔔重啟日誌 | |
com.example.app.web我的自動(路徑)↻ 頻繁重啟 | 730916d 3h | 0 | 1207↻ | 🔔重啟日誌 | |
com.example.app.collector我的自動(路徑) | 7310418m | 0 | 21 | 🔔重啟日誌 | |
com.example.worker.nightly我的已列入 | — | 1 | 3 | 🔔啟動日誌 | |
com.example.backup.weekly我的自動(路徑)下次 · Sun 03:00 | — | 0 | 0 | 🔔啟動日誌 | |
維運0012 項 | |||||
com.example.worker.old我的已列入⏹ 已退役 12 天 | — | — | 0 | 啟用日誌 | |
com.example.legacy.agent我的自動(路徑) | — | 0 | 0 | 啟動日誌 | |
按「我的」、「其他」或「雜訊」。然後看 /metrics 和 Telegram 警報。
為何
launchctl 列出全部,所以你什麼都看不見。
原有的工具回答「載入了什麼」,從不回答「我部署了什麼,它是否健康」。用使用者網域來篩也幫不上忙——一台在用的 Mac 上,看起來像「你的」作業,大多是 Dropbox、Google 的更新程式、Spotify、Tailscale,再加上一兩個瀏覽器輔助程式。那個重啟了 1,207 次的作業,就埋在正中間。
Deployboard 從一份明確的清單出發:你自己控制的允許清單,再加上路徑推導——plist 的記錄路徑、工作目錄、program 或參數只要落在你的項目樹裡,那就是你的作業,自動算進去。其餘是系統自己的事,永遠只隔一下點擊,但不會擋在前面。
- launchctl list — 545 行,分不清哪些是你的
- 悄悄重啟了 1,207 次的開發伺服器,看起來仍很健康
- 你刻意停用的 plist,一直被當成失敗
- 沒有歷史:「這算正常嗎?」沒有答案
- 要等你偶然發現,才知道有東西已經死了
- 我的 / 其他 / 雜訊 — 預設先顯示你的部署
- 來自 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 自己的輸出上——不去猜一個作業屬於哪個廠商。
用 launchctl print,而不是 launchctl list。
launchctl list 沒有重啟次數,也沒有 disabled 旗標。Deployboard 用 launchctl print 讀 runs 和 pid,用 launchctl print-disabled 讀意圖——於是抖動看得見,刻意退役的作業也不再看起來像壞掉。
清單由你決定。
我的 / 其他 / 雜訊,每一行都有出處(已列入、自動(路徑)、未分類)。分類控制會寫回 config.json,大約 2 秒內即時重新載入——教它一個新應用程式,不必重啟。
給 launchd 的 Prometheus 指標。
deployboard_job_up、_runs、_last_exit_code、_disabled,再加上 IPv4/IPv6 連接埠探測。儀表板只能告訴你「現在停了」;時間序列能告訴你「03:10 開始抖動」。
可按應用程式開關的 Telegram 警報。
只在狀態轉變時通知——error、offline、連接埠無法連線、重啟風暴,以及復原——並有按應用程式的開關、冷卻時間、靜音時段。bot token 放在 macOS 鑰匙圈,永不寫進檔案。
每個作業的 stdout 與 stderr,最多 10,000 行,可即時篩選。
結束碼、program 是否存在、program 可否執行、plist 擁有者、plist 權限、記錄路徑。
GET /api/events 每 5 秒推送完整的作業清單。
經 launchctl 做 start / stop / reload,並有確認對話框。
與介面用的是同一面,有文件,curl 也好用。
一個 Go 二進位檔,前端以 go:embed 內嵌,沒有常駐程式,沒有遙測。
指標
這個領域裡,唯一的 launchd exporter。
把 Prometheus 指到介面用的同一個連接埠。標籤基數有界,省略是誠實的(從未結束的作業沒有 last-exit 序列),只有暴露了連接埠的作業才有 probe 序列——探測也會試 IPv6 的 ::1,因為有些開發伺服器只綁在那裡。
# HELP deployboard_job_up 1 when the launchd job is running.# TYPE deployboard_job_up gaugedeployboard_job_up{label="com.example.app.api",category="ours",group="Example App"} 1deployboard_job_runs{label="com.example.app.web",category="ours",group="Example App"} 1207deployboard_job_disabled{label="com.example.worker.old",category="ours"} 1deployboard_job_probe_reachable{label="com.example.app.api",port="8001"} 1deployboard_job_probe_latency_seconds{label="com.example.app.api",port="8001"} 0.003deployboard_jobs_total{category="ours"} 16deployboard_jobs_total{category="noise"} 528
# restart churn, per job, over 6hderiv(deployboard_job_runs{category="ours"}[6h]) * 3600# anything of yours that is not runningdeployboard_job_up{category="ours"} == 0
從提供介面的同一個行程擷取即可,不必另外部署 exporter,也不需要 sidecar。沒有的序列就省略,不會造假:從未結束的作業不會有 last_exit 行,沒有連接埠的作業也不會有 probe 行。
警報
它會在 Telegram 通知你,而且只說一次。
按狀態轉變來發,所以一個來回抖動的作業不能洗版:壞掉時一則,若持續重啟則一則受速率限制的,恢復時再一則。復原訊息永不被靜音時段壓掉。bot token 放在 macOS 鑰匙圈,由儀表板自己寫入——永不進檔案,永不進記錄行,也永不回到頁面上。
API
介面用到的一切,你的腳本也能用。
Web 介面是 /api/jobs 和 /api/events 的一個消費者。curl 也是。綁定伺服器,指向端點,解析 JSON。
安裝
一行指令。一個設定檔。一個分頁。
三種安裝方式、同一個二進位檔:Homebrew cask(首屏指令)、不用 Go 也能裝 LaunchAgent 的 curl install-release.sh、或從 clone 執行 bash install.sh。不用 sudo;腳本支援 --dry-run 與 --uninstall。
$ brew install --cask francistse/tap/deployboard用 Homebrew cask 安裝二進位檔,或用 install-release.sh / install.sh 一併寫入帶 RunAtLoad + KeepAlive 的 LaunchAgent。不用 sudo;腳本支援 --dry-run 與 --uninstall。
$ open ~/.config/deployboard/config.json你可以在 config.json 的 inventory.ours 下列出命名空間,也可以把項目資料夾加進 derive_roots,讓 plist 路徑自己分類。未分類的會出現在「其他」——在介面按一下就釘選,config.json 會即時重新載入。
儀表板在 127.0.0.1 打開。用 Prometheus 擷取 /metrics,用鈴鐺按應用程式開關警報,並在系統、淺色、深色之間切換外觀。
狀態值
上游六個 + disabled
設定即時重新載入
不必重啟就能分類新應用程式
Prometheus 序列家族
作業、runs、結束碼、探測
唯一綁定的地址
沒有雲端、沒有遙測、沒有更新探測
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 是既有的付費選擇;這個工具關心的是,你的部署正在做什麼。