Cronmaster 2.4.1 API 详解

接口篇:实测 Cronmaster 2.4.1 的 REST 与 SSE 接口,覆盖 API Key 与会话 Cookie 双鉴权、任务列表详情更新执行、日志流与事件流、系统状态与会话管理,并揭示 POST /api/cronjobs 返回 405、paused 与 isPaused 字段名不符、wrapper-check 免鉴权、业务错误用 HTTP 200 承载等真实差异。

Cronmastercron定时调度任务管理2026/10/27

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/loginPOST✅密码登录,写会话 Cookie
/api/auth/check-sessionGET✅校验会话
/api/auth/logoutPOST✅注销并清 Cookie
/api/cronjobsGET✅列出任务
/api/cronjobsPOST✅(405)不存在,创建任务只能走界面
/api/cronjobs/:idGET✅任务详情
/api/cronjobs/:idPATCH✅更新任务
/api/cronjobs/:idDELETE⚠️ 未实测官方文档有,本次未执行删除
/api/cronjobs/:id/executeGET✅手动执行,支持同步/异步
/api/scriptsGET✅脚本库列表
/api/system-statsGET✅系统状态
/api/eventsGET✅SSE 事件流
/api/logs/streamGET✅执行日志流
/api/system/wrapper-checkGET✅包装脚本完整性
/api/healthGET✅健康检查
/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}}]}

字段名与官方文档不一致,以实测为准:

官方文档写的实际返回
isPausedpaused
无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 创建端点。

影响:如果你要「用自动化脚本批量下发任务」,就得退而:

  1. 走界面自动化(浏览器操作);
  2. 直接写 /var/spool/cron/crontabs/<user>,再让面板刷新读取;
  3. 自己构造 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。

注意三点:

  1. 实际返回比官方文档多一个 fullContent 字段(完整内容),文档未列出。
  2. 运行记录存在期间,完成态也能查到完整日志;一旦运行记录被清理(或进程重启),再查会返回:
{"error":"Running job not found"}
  1. 若日志目录尚未生成(任务刚起、日志还没落盘),返回:
{"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 发 POST405空
登录连续失败超阈值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 覆盖「读、改、执行、看日志」这一条主线,足够做巡检、告警与手动触发,但明确缺少创建与删除脚本、缺少创建任务的写能力,批量下发必须绕道。

对接时记住四个与官方文档不一致的事实:

  1. 暂停字段是 paused,不是 isPaused;
  2. POST /api/cronjobs 返回 405,没有创建接口;
  3. /api/logs/stream 多返回 fullContent,且业务失败用 HTTP 200 承载;
  4. /api/system/wrapper-check 免鉴权,可被匿名访问。

做好这几点,API 接入基本不会踩坑;剩下的边界都在部署与配置层面,见本专栏的部署篇与配置篇。