Cronmaster 2.4.1 API 详解
接口篇:实测 Cronmaster 2.4.1 的 REST 与 SSE 接口,覆盖 API Key 与会话 Cookie 双鉴权、任务列表详情更新执行、日志流与事件流、系统状态与会话管理,并揭示 POST /api/cronjobs 返回 405、paused 与 isPaused 字段名不符、wrapper-check 免鉴权、业务错误用 HTTP 200 承载等真实差异。
Cronmaster 2.4.1 API 详解
目标:把 Cronmaster 2.4.1 的 REST 与 SSE 接口讲透——怎么鉴权、每个端点返回什么、字段名与官方文档有哪些出入、哪些能力根本没有接口。所有响应体都来自测试机真实请求,字段名以实际回显为准;未实测的部分会明确标注。
适用场景:把 Cronmaster 接入自有运维平台、做任务巡检、或写自动化脚本批量管理 crontab。
一、鉴权模型
API 支持两种凭证,任一即可:
| 方式 | 用法 | 保护范围 |
|---|---|---|
| API Key | 请求头 Authorization: Bearer <API_KEY> | 只保护 /api/*,不保护 Web 界面 |
| 会话 Cookie | 先 POST /api/auth/login 拿 cronmaster-session | 保护界面与 API |
API_KEY 未设置时,除少数公开端点外,API 完全裸奔。官方明确说明这是为了开发方便,生产环境必须设置。
1.1 两种方式对照实测
无凭证:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/api/cronjobs
401
{"error":"Unauthorized","message":"Authentication required. Use session cookie or API key (Bearer token)."}
Bearer:
curl -s -H 'Authorization: Bearer cronmaster-api-key-2026' http://127.0.0.1:3000/api/cronjobs
返回 {"success":true,"data":[...]}。
会话 Cookie:
curl -s -c cookies.txt -X POST -H 'Content-Type: application/json' \
-d '{"password":"q1w2e3r4"}' http://127.0.0.1:3000/api/auth/login
curl -s -b cookies.txt http://127.0.0.1:3000/api/cronjobs
登录回显:
{"success":true,"message":"Login successful"}
1.2 免鉴权的端点
实测确认以下端点无需任何凭证:
| 端点 | 用途 |
|---|---|
GET /api/health | 健康检查,返回 {"status":"healthy"} |
GET /api/system/wrapper-check | 检查日志包装脚本是否被改动,返回 {"modified":false} |
/api/health 免鉴权是文档化的设计(供健康检查用)。但 /api/system/wrapper-check 也免鉴权,官方文档并未说明——它会暴露「包装脚本是否被修改」这一运维信号。虽然信息量不大,但如果你把 API 暴露在公网,需要知道这个端点能被匿名访问。
二、端点总览与实测覆盖
| 端点 | 方法 | 实测 | 说明 |
|---|---|---|---|
/api/auth/login | POST | ✅ | 密码登录,写会话 Cookie |
/api/auth/check-session | GET | ✅ | 校验会话 |
/api/auth/logout | POST | ✅ | 注销并清 Cookie |
/api/cronjobs | GET | ✅ | 列出任务 |
/api/cronjobs | POST | ✅(405) | 不存在,创建任务只能走界面 |
/api/cronjobs/:id | GET | ✅ | 任务详情 |
/api/cronjobs/:id | PATCH | ✅ | 更新任务 |
/api/cronjobs/:id | DELETE | ⚠️ 未实测 | 官方文档有,本次未执行删除 |
/api/cronjobs/:id/execute | GET | ✅ | 手动执行,支持同步/异步 |
/api/scripts | GET | ✅ | 脚本库列表 |
/api/system-stats | GET | ✅ | 系统状态 |
/api/events | GET | ✅ | SSE 事件流 |
/api/logs/stream | GET | ✅ | 执行日志流 |
/api/system/wrapper-check | GET | ✅ | 包装脚本完整性 |
/api/health | GET | ✅ | 健康检查 |
/api/oidc/* | GET | ⚠️ 未实测 | 需配置 SSO_MODE=oidc 与真实 IdP |
三、任务管理
3.1 列出任务 GET /api/cronjobs
curl -s -H 'Authorization: Bearer <API_KEY>' http://127.0.0.1:3000/api/cronjobs
实测回显:
{"success":true,"data":[{"id":"mi57-o0t1","schedule":"* * * * *","command":"echo cronmaster-b1-test >> /tmp/b1-test.log","comment":"B1 test job","user":"root","paused":false,"logsEnabled":false,"logError":{"hasError":false}}]}
字段名与官方文档不一致,以实测为准:
| 官方文档写的 | 实际返回 |
|---|---|
isPaused | paused |
| 无 | logError(对象,含 hasError,启用日志时还有 latestExitCode、hasHistoricalFailures) |
如果按文档写 job.isPaused,会永远拿到 undefined——这是对接时最容易踩的坑。
3.2 任务详情 GET /api/cronjobs/:id
curl -s -H 'Authorization: Bearer <API_KEY>' \
http://127.0.0.1:3000/api/cronjobs/mi57-o0t1
回显:
{"success":true,"data":{"id":"mi57-o0t1","schedule":"* * * * *","command":"echo cronmaster-b1-test >> /tmp/b1-test.log","comment":"B1 test job","user":"root","paused":false,"logsEnabled":false,"logError":{"hasError":false}}}
不存在的 ID:
curl -s -H 'Authorization: Bearer <API_KEY>' \
http://127.0.0.1:3000/api/cronjobs/nope-1234
HTTP 404
{"success":false,"error":"Cron job not found"}
注意错误体用的是 error 字段,而不是列表成功时的 data。
3.3 更新任务 PATCH /api/cronjobs/:id
curl -s -H 'Authorization: Bearer <API_KEY>' -X PATCH \
-H 'Content-Type: application/json' \
-d '{"comment":"B1 test job (patched)"}' \
http://127.0.0.1:3000/api/cronjobs/mi57-o0t1
回显:
{"success":true,"message":"Cron job updated successfully"}
再次 GET 详情,comment 已变为 B1 test job (patched)。可更新字段与创建表单一致:schedule、command、comment、logsEnabled。
注意:PATCH 是部分更新语义,只传 comment 不会清空其他字段——实测确认。
3.4 没有创建接口
这是 2.4.1 API 最重要的能力缺口。实测:
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
-H 'Authorization: Bearer <API_KEY>' -H 'Content-Type: application/json' \
-d '{"schedule":"* * * * *","command":"echo x","comment":"t"}' \
http://127.0.0.1:3000/api/cronjobs
405
/api/cronjobs 只实现了 GET。新建任务只能通过 Web 界面的 Server Action 完成,没有公开的 REST 创建端点。
影响:如果你要「用自动化脚本批量下发任务」,就得退而:
- 走界面自动化(浏览器操作);
- 直接写
/var/spool/cron/crontabs/<user>,再让面板刷新读取; - 自己构造 Server Action 请求(不稳定,随版本变化)。
这是评估接入方案时必须提前知道的限制,不要按「有完整 CRUD」做设计。
3.5 手动执行 GET /api/cronjobs/:id/execute
支持 runInBackground 查询参数,默认 true。
同步模式(等待执行完成):
curl -s -H 'Authorization: Bearer <API_KEY>' \
'http://127.0.0.1:3000/api/cronjobs/mi57-o0t1/execute?runInBackground=false'
回显:
{"success":true,"message":"Cron job executed successfully","output":"Command executed successfully","mode":"sync"}
异步模式(立即返回 runId):
curl -s -H 'Authorization: Bearer <API_KEY>' \
'http://127.0.0.1:3000/api/cronjobs/mi57-o0t1/execute?runInBackground=true'
回显:
{"success":true,"message":"Job started in background","runId":"run-mi57-o0t1-1793104284496","mode":"async"}
与官方文档的出入:文档示例返回 {"success":true,"runId":"run-123","message":"Job execution started"},但实测异步返回的是 "Job started in background" + mode: "async",同步模式则返回 output 与 mode: "sync"、没有 runId。按 mode 分支处理,不要只判断 runId 是否存在。
runId 的格式为 run-<jobId>-<毫秒时间戳>,后续查日志流要用它。
四、日志
4.1 日志流 GET /api/logs/stream
参数:runId(必填)、offset(字节偏移,默认 0)、maxLines(默认 500,范围 100~50000)。
任务运行中(执行一个 sleep 12 的任务,第 4 秒查询):
curl -s -H 'Authorization: Bearer <API_KEY>' \
"http://127.0.0.1:3000/api/logs/stream?runId=$RID&offset=0&maxLines=500"
回显:
{"status":"running","content":"--- [ JOB START ] ----------------------------------------------------\nCommand ...","newContent":"--- [ JOB START ] ----------------------------------------------------\nCommand ...","fullContent":"--- [ JOB START ] ----------------------------------------------------\nCommand ...","logFile":"2026-10-27_20-32-32.log","isComplete":false,"fileSize":258,"offset":0,"totalLines":8,"displayedLines":8,"truncated":false}
任务完成后(同一 runId 再查):
{"status":"completed","content":"--- [ JOB START ] ... --- [ JOB END ] -----\n","newContent":"...","fullContent":"...","logFile":"2026-10-27_20-32-32.log","isComplete":true,"exitCode":0,"fileSize":497,"offset":0,"totalLines":16,"displayedLines":16,"truncated":false}
status 取 running / completed / failed;isComplete 为 false 时 exitCode 为 null。
注意三点:
- 实际返回比官方文档多一个
fullContent字段(完整内容),文档未列出。 - 运行记录存在期间,完成态也能查到完整日志;一旦运行记录被清理(或进程重启),再查会返回:
{"error":"Running job not found"}
- 若日志目录尚未生成(任务刚起、日志还没落盘),返回:
{"status":"failed","content":"","message":"Log directory not yet created"}
——注意这是 HTTP 200 + status:"failed" 的业务态,不是 HTTP 错误码。脚本必须看 body 的 status,不能只看 HTTP 状态码。
4.2 事件流 GET /api/events
SSE 长连接,用于界面实时刷新任务状态与日志。响应头实测:
HTTP/1.1 200 OK
content-type: text/event-stream
cache-control: no-cache, no-transform
x-accel-buffering: no
X-Content-Type-Options: nosniff
连接建立后服务端先发:
: Connected to Cronmaster SSE
retry: 5000
retry: 5000 表示断线后客户端 5 秒重连。x-accel-buffering: no 是给 Nginx 的提示——反向代理必须关闭缓冲,否则事件会被攒着一起发。
LIVE_UPDATES=false 时该端点关闭:
HTTP 503
{"error":"Live updates are disabled"}
五、其他端点
5.1 脚本库 GET /api/scripts
curl -s -H 'Authorization: Bearer <API_KEY>' http://127.0.0.1:3000/api/scripts
回显:
{"success":true,"data":[{"id":"demo-script","name":"Hi, this is a demo script","description":"This script logs a \"hello world\" to teach you how scripts work.","filename":"demo-script.sh","createdAt":"2026-10-27T12:01:09.779Z"},{"id":"script_1762773565914_sjuilgih3","name":"hello-world","description":"echoes test","filename":"hello-world.sh","createdAt":"2026-10-27T12:01:09.779Z"}]}
与文档出入:文档示例包含 content 字段,实测列表不含 content(只有元数据)。脚本内容需要另走界面或直接读脚本目录。
没有新建脚本的 POST 端点,脚本库同样只能在界面维护。
5.2 系统状态 GET /api/system-stats
顶层键实测:
uptime, uptimeSeconds, memory, cpu, network, systemStatus, gpu, disks
配置影响:
DISABLE_SYSTEM_STATS=true→ 整个响应体为null(HTTP 仍 200)。DISABLE_DISK_STATS=true→disks键整体消失。
详见本专栏《Cronmaster 2.4.1 配置参数详解》第 3.2、3.3 节。
5.3 会话管理
curl -s -b cookies.txt http://127.0.0.1:3000/api/auth/check-session # {"valid":true}
curl -s -b cookies.txt -X POST http://127.0.0.1:3000/api/auth/logout # {"success":true,"message":"Logout successful"}
注销后原 Cookie 立即失效,再用它请求 /api/cronjobs 返回 401。
六、错误码速查
| 场景 | HTTP | 响应体 |
|---|---|---|
| 无凭证/凭证失效 | 401 | {"error":"Unauthorized","message":"..."} |
| 任务不存在 | 404 | {"success":false,"error":"Cron job not found"} |
对 /api/cronjobs 发 POST | 405 | 空 |
| 登录连续失败超阈值 | 429 | {"success":false,"message":"Too many failed attempts, try again later"} |
LIVE_UPDATES=false 访问事件流 | 503 | {"error":"Live updates are disabled"} |
| 日志运行记录已清理 | 200 | {"error":"Running job not found"} |
| 日志未落盘 | 200 | {"status":"failed","content":"","message":"Log directory not yet created"} |
注意最后两行:业务错误用 200 承载,脚本必须解析 body。这是本篇最容易踩的第二个坑。
七、接入示例
一个「巡检所有任务 + 检测上次执行是否失败」的最小脚本:
#!/usr/bin/env bash
set -euo pipefail
BASE="${CRONMASTER_URL:-http://127.0.0.1:3000}"
KEY="${CRONMASTER_API_KEY:?need API_KEY}"
curl -fsS -H "Authorization: Bearer $KEY" "$BASE/api/cronjobs" \
| python3 -c '
import json, sys
d = json.load(sys.stdin)
if not d.get("success"):
print("API error:", d); sys.exit(1)
for j in d["data"]:
err = j.get("logError") or {}
flag = "FAIL" if err.get("hasError") else "ok "
print(f"{flag} {j[\"id\"]:14} {j[\"schedule\"]:16} paused={j[\"paused\"]} logs={j[\"logsEnabled\"]} {j[\"comment\"]}")
'
要点:
- 用
logError.hasError判断上次执行是否失败,比自己去拉日志便宜。 - 字段用实际名
paused,不是isPaused。 - 判断
success为 true 再取data。
八、排障实录
8.1 按文档取 isPaused 拿到 undefined
现象:脚本里 job.isPaused === true 永远不成立。
原因:实测字段名为 paused,官方文档写的是 isPaused。
解决:改成 paused;或用 job.paused ?? job.isPaused 兼容。
8.2 想批量建任务发现没有 POST
现象:POST /api/cronjobs 返回 405。
原因:2.4.1 未实现创建端点,创建只走界面 Server Action。
解决:改用界面自动化、直接写 crontab 文件,或把批量下发这一步放在配置管理层(如 Ansible 写 crontab),面板只做可视化与巡检。
8.3 日志流接口偶发「失败」
现象:刚触发任务立刻查 /api/logs/stream,拿到 {"status":"failed","content":"","message":"Log directory not yet created"}。
原因:包装脚本还没创建日志文件。这是时序问题,不是任务失败。
解决:查到 status:"failed" 且 message 含 Log directory not yet created 时,退避 1~2 秒重试;不要把它当成任务执行失败上报。
8.4 完成后再查日志流报 Running job not found
现象:任务完成一段时间后再查同一 runId,返回 {"error":"Running job not found"}。
原因:/api/logs/stream 依赖「运行记录表」,任务完成后记录会被清理,接口随之失效。日志文件本身仍在 data/logs/<jobId>/ 下。
解决:需要在任务运行期间就采集,或直接读宿主机日志文件;不要指望接口长期留存历史日志。
8.5 SSE 经反代后不实时
现象:页面能加载,但日志区域长时间不更新。
原因:反向代理默认开启响应缓冲,把 SSE 数据攒住了。
解决:Nginx 加 proxy_buffering off; 与 proxy_read_timeout 3600s;;上游已返回 x-accel-buffering: no,但 Nginx 需要显式关闭缓冲。详见部署篇的反代配置。
九、结论
Cronmaster 2.4.1 的 API 覆盖「读、改、执行、看日志」这一条主线,足够做巡检、告警与手动触发,但明确缺少创建与删除脚本、缺少创建任务的写能力,批量下发必须绕道。
对接时记住四个与官方文档不一致的事实:
- 暂停字段是
paused,不是isPaused; POST /api/cronjobs返回 405,没有创建接口;/api/logs/stream多返回fullContent,且业务失败用 HTTP 200 承载;/api/system/wrapper-check免鉴权,可被匿名访问。
做好这几点,API 接入基本不会踩坑;剩下的边界都在部署与配置层面,见本专栏的部署篇与配置篇。