@goodandready/dsh-cron 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/docs/README.ru.md +126 -0
- package/docs/README.zh.md +126 -0
- package/lib/api.js +138 -4
- package/lib/channels.js +6 -2
- package/lib/client.js +32 -9
- package/lib/config-jobs.js +206 -0
- package/lib/external-api.js +74 -0
- package/lib/index.js +55 -0
- package/lib/metrics.js +97 -0
- package/lib/scheduler.js +148 -113
- package/lib/store.js +3 -3
- package/lib/task-patch.js +17 -0
- package/package.json +3 -2
- package/docs/design/DESIGN.md +0 -83
- package/docs/plans/0.2.5-ui-block.md +0 -50
- package/docs/plans/0.2.6-economy-block.md +0 -61
package/README.md
CHANGED
|
@@ -198,6 +198,125 @@ If the daemon was offline at a scheduled time, the run is recorded as `missed` o
|
|
|
198
198
|
* Set `heartbeatUrl` and `heartbeatIntervalSec` in the plugin settings and the scheduler pings that URL on schedule — an external monitor alerts when the pings stop.
|
|
199
199
|
* A built-in `GET /dsh-cron/heartbeat` endpoint reports liveness, active task count and the last run time for your own watchdogs.
|
|
200
200
|
|
|
201
|
+
### 15. Declarative Jobs From the Profile Config (#50)
|
|
202
|
+
Long-lived operational jobs can be declared in the profile configuration instead of being recreated by hand in the UI. The config file owns the jobs it declares: at every plugin start they are created or updated, and a job that disappears from the file is removed.
|
|
203
|
+
|
|
204
|
+
Add a `jobs` list to the plugin section of your profile config (`cordis.patch.yml`):
|
|
205
|
+
|
|
206
|
+
```yaml
|
|
207
|
+
dsh-cron:
|
|
208
|
+
jobs:
|
|
209
|
+
- id: nightly-backup
|
|
210
|
+
title: Nightly backup
|
|
211
|
+
schedule: "0 3 * * *"
|
|
212
|
+
type: script
|
|
213
|
+
prompt: "bash /path/to/backup.sh"
|
|
214
|
+
channels: ["telegram"]
|
|
215
|
+
timeoutSeconds: 3600
|
|
216
|
+
- id: morning-digest
|
|
217
|
+
title: Morning digest
|
|
218
|
+
schedule: "0 8 * * 1-5"
|
|
219
|
+
type: llm
|
|
220
|
+
prompt: "Prepare a brief morning digest of active tasks."
|
|
221
|
+
provider: my-provider
|
|
222
|
+
model: provider-id/model-id
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
* Required per entry: `id`, `title`, `schedule`; the types that carry their payload in the prompt (`script`, `node`, `python`, `ssh`, `docker`, `llm`, `skill`, `workflow`) also need a non-empty `prompt`. `http` is exempt: its target is given by `httpUrl` (or `prompt`).
|
|
226
|
+
* Any other task field is passed through with the same validation as the API: `channels`, `model`, `provider`, `fallbackModel`, `silentRule`, `inspectOnFailure`, `timezone`, `timeoutSeconds`, `template`, `env`, `cwd`, and the runtime fields (`nodePath`, `pythonPath`, `httpUrl`, `httpMethod`, `httpHeaders`, `httpBody`, `sshProfileId`, `sshTarget`, `dockerImage`, `workspaceId`, `worktree`, `keepWorktree`, `skillName`, `workflowName`).
|
|
227
|
+
* Declared jobs are marked **managed by the config**; the panel shows a source label instead of edit and delete actions.
|
|
228
|
+
* Editing, pausing, resuming, toggling or deleting a config-owned task is refused with `409` on the panel and on the API, and a create-or-update `POST /dsh-cron/tasks` that carries the existing `id` of a config-owned task is refused the same way — the config file is the source of truth. **Run Now** stays available.
|
|
229
|
+
* A task with the same `id` created through the UI, the API or an agent tool is never overwritten: the entry is skipped and the conflict is written to the log.
|
|
230
|
+
* Code-executing types are activated like any other declared job, but at startup the plugin writes a warning to the log, so a code path introduced through the config file is visible.
|
|
231
|
+
* Entries are validated one by one with an indexed message (`config.jobs[i]: …`); a broken entry is skipped and cannot stop the remaining jobs or the profile.
|
|
232
|
+
|
|
233
|
+
### 16. External REST API (`/dsh-cron/api/*`, #54)
|
|
234
|
+
External systems (CI, host cron, `curl`) can drive the scheduler without opening the browser panel. This is the only surface behind a bearer token; the panel routes stay local and cross-origin-protected.
|
|
235
|
+
|
|
236
|
+
Set the token as the plugin setting `apiToken` (masked like every secret). Auth and errors:
|
|
237
|
+
* no token configured → the whole surface answers `503`;
|
|
238
|
+
* a missing or wrong `Authorization: Bearer <token>` → `401`, compared in constant time.
|
|
239
|
+
|
|
240
|
+
| Method | Path | Description |
|
|
241
|
+
|:---|:---|:---|
|
|
242
|
+
| `GET` | `/dsh-cron/api/tasks` | List tasks (`status` / `query` filters as the panel) |
|
|
243
|
+
| `GET` | `/dsh-cron/api/tasks/:id` | Read one task |
|
|
244
|
+
| `POST` | `/dsh-cron/api/tasks` | Create a task, or update the existing one when `id` is present |
|
|
245
|
+
| `DELETE` | `/dsh-cron/api/tasks/:id` | Delete a task |
|
|
246
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | Force an immediate run |
|
|
247
|
+
|
|
248
|
+
The operations reuse the panel handlers, so the `x-dsh-cron-confirm: script` gate for code-executing types and the `409` refusals for config-owned tasks behave exactly as in the UI.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
BASE="http://127.0.0.1:3080"
|
|
252
|
+
TOKEN="<API_TOKEN>"
|
|
253
|
+
|
|
254
|
+
# list
|
|
255
|
+
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks"
|
|
256
|
+
|
|
257
|
+
# create, or update when the body carries the id
|
|
258
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
259
|
+
-d '{"id":"cleanup","title":"Cleanup","schedule":"0 4 * * *","prompt":"Remove stale temporary files."}' \
|
|
260
|
+
"$BASE/dsh-cron/api/tasks"
|
|
261
|
+
|
|
262
|
+
# force a run
|
|
263
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup/run"
|
|
264
|
+
|
|
265
|
+
# delete
|
|
266
|
+
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup"
|
|
267
|
+
|
|
268
|
+
# a code-executing task also needs the confirmation header
|
|
269
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "x-dsh-cron-confirm: script" \
|
|
270
|
+
-H "Content-Type: application/json" \
|
|
271
|
+
-d '{"title":"Disk check","schedule":"0 * * * *","type":"script","prompt":"df -h"}' \
|
|
272
|
+
"$BASE/dsh-cron/api/tasks"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 17. Prometheus Metrics (#53)
|
|
276
|
+
`GET /dsh-cron/metrics` returns Prometheus text exposition, so the scheduler can be scraped without any extra dependency:
|
|
277
|
+
|
|
278
|
+
* `dsh_cron_tasks_total{status}` — tasks by status (gauge).
|
|
279
|
+
* `dsh_cron_task_last_duration_seconds{task}` — duration of a task's last finished run, in seconds (gauge).
|
|
280
|
+
* `dsh_cron_runs_total{status}` — finished runs since the plugin process started (counter); the statuses are `success`, `error`, `timeout`, `skipped` and `missed`.
|
|
281
|
+
* `dsh_cron_run_records` — run records currently kept in memory (gauge).
|
|
282
|
+
|
|
283
|
+
Only counts, statuses and durations are exported; prompts, run output and task configuration never appear in the exposition.
|
|
284
|
+
|
|
285
|
+
```yaml
|
|
286
|
+
scrape_configs:
|
|
287
|
+
- job_name: dsh-cron
|
|
288
|
+
static_configs:
|
|
289
|
+
- targets: ["127.0.0.1:3080"]
|
|
290
|
+
metrics_path: /dsh-cron/metrics
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 18. Strict Channel Validation (#121)
|
|
294
|
+
Creating or updating a task with an unknown delivery-channel id is now rejected with `400`, and the offending ids are listed:
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{ "ok": false, "error": "Unknown channel ids: email_ping", "unknownChannels": ["email_ping"] }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Changed in v0.2.7: previously an unknown id was silently dropped, so a client with a typo received `ok: true` and ended up with a task that delivered nowhere.
|
|
301
|
+
|
|
302
|
+
Import deliberately stays tolerant (a file may come from an older build): unknown ids are dropped from the imported task, but they are named in the response (`unknownChannels`) and written to the scheduler log instead of disappearing silently.
|
|
303
|
+
|
|
304
|
+
### 19. Post-Install Verification (#126)
|
|
305
|
+
`deploy.sh` has a verify-only mode that inspects an already installed profile without installing anything:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
bash deploy.sh verify [exact-version]
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
It checks that the profile reports the requested version (default: the `package.json` version), authenticates to the web UI, then downloads the client bundle and confirms the package name is present.
|
|
312
|
+
|
|
313
|
+
Why it is needed: the web profile can sit behind an authentication plugin and answer `401` to an anonymous request, and a plugin client bundle is served only through the exact combined `??` URL printed in the authenticated index — a bare `/plugins/<name>/client.js` answers `404`. The check therefore builds an authenticated session first.
|
|
314
|
+
|
|
315
|
+
Environment used by the check: `DSH_WEB_BASE` (default `http://127.0.0.1:3080`), `DSH_WEB_TOKEN` (the token; when unset, the script reads the last one printed to the unit journal), `DSH_WEB_UNIT` (default `dsh-web.service`). No secret is stored in the script.
|
|
316
|
+
|
|
317
|
+
### 20. Internal Refactor: Schedule Parsing and Arming (#97)
|
|
318
|
+
Developer-facing, no behaviour change. `parseScheduleExpression` was split into small functions that keep the same branch order — `parseAtExpression`, `parseRelativeOneShot`, `parseIntervalExpression`, `parseAliasExpression`, `parseCronExpression` — and `scheduleTask` into `clearScheduled`, `scheduleOneShot` and `scheduleCron`. The existing test suite passed unchanged and targeted tests were added for branch precedence and error messages.
|
|
319
|
+
|
|
201
320
|
---
|
|
202
321
|
|
|
203
322
|
## 📦 Installation
|
|
@@ -246,6 +365,8 @@ dsh-cron:
|
|
|
246
365
|
giteaBaseUrl: "" # giteaRepo = owner/repo, giteaTokenRef = credential NAME
|
|
247
366
|
giteaRepo: ""
|
|
248
367
|
giteaTokenRef: ""
|
|
368
|
+
# --- external REST API (#54) ---
|
|
369
|
+
apiToken: "" # bearer token for the external /dsh-cron/api/* surface (masked; empty = 503)
|
|
249
370
|
```
|
|
250
371
|
|
|
251
372
|
### Configuration Parameters
|
|
@@ -271,6 +392,7 @@ dsh-cron:
|
|
|
271
392
|
| `pushplusUrl` / `pushplusTokenRef` | `string` | `"https://www.pushplus.plus/send"` / `""` | PushPlus endpoint (override for a self-hosted proxy) and token credential name |
|
|
272
393
|
| `ttsBaseUrl` | `string` | `"http://127.0.0.1:3080"` | Base URL of the `dsh-tts` plugin used for voice announcements |
|
|
273
394
|
| `giteaBaseUrl` / `giteaRepo` / `giteaTokenRef` | `string` | `""` | Gitea channel: base URL, `owner/repo`, and the credential name of the API token |
|
|
395
|
+
| `apiToken` | `string` | `""` | Bearer token for the external `/dsh-cron/api/*` surface. Stored as a secret field and returned masked; empty disables the surface (503), a wrong value answers 401 |
|
|
274
396
|
|
|
275
397
|
Notes:
|
|
276
398
|
|
|
@@ -307,6 +429,10 @@ All endpoints are served by the DSH web server under `/dsh-cron/`. Read endpoint
|
|
|
307
429
|
| `POST` | `/dsh-cron/telegram/test` | Send a Telegram test message |
|
|
308
430
|
| `POST` | `/dsh-cron/kanban/test` | Create a Kanban connectivity-test card |
|
|
309
431
|
| `*` | `/dsh-cron/action/:id/:action` | Legacy alias for the task action routes (`run`, `toggle`, `delete`, `history`) |
|
|
432
|
+
| `GET` | `/dsh-cron/metrics` | Prometheus text exposition of task and run counters — never prompts or output (#53) |
|
|
433
|
+
| `GET` / `POST` | `/dsh-cron/api/tasks` | External token-guarded surface: list / create-or-update (#54) |
|
|
434
|
+
| `GET` / `DELETE` | `/dsh-cron/api/tasks/:id` | External token-guarded surface: read / delete (#54) |
|
|
435
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | External token-guarded surface: force a run (#54) |
|
|
310
436
|
|
|
311
437
|
---
|
|
312
438
|
|
package/docs/README.ru.md
CHANGED
|
@@ -239,12 +239,133 @@ dsh-cron:
|
|
|
239
239
|
giteaBaseUrl: "" # giteaRepo = owner/repo, giteaTokenRef = ИМЯ credential
|
|
240
240
|
giteaRepo: ""
|
|
241
241
|
giteaTokenRef: ""
|
|
242
|
+
# --- внешний REST API (#54) ---
|
|
243
|
+
apiToken: "" # bearer-токен внешнего префикса /dsh-cron/api/* (маскируется; пусто = 503)
|
|
242
244
|
```
|
|
243
245
|
|
|
244
246
|
### 14. Мониторинг heartbeat (dead man's switch)
|
|
245
247
|
* Задайте `heartbeatUrl` и `heartbeatIntervalSec` в настройках плагина — планировщик будет пинговать этот адрес по расписанию, и внешний монитор сообщит, когда пинги прекратятся.
|
|
246
248
|
* Встроенный эндпоинт `GET /dsh-cron/heartbeat` сообщает живость, число активных задач и время последнего запуска для ваших собственных сторожей.
|
|
247
249
|
|
|
250
|
+
### 15. Задачи из конфига профиля (#50)
|
|
251
|
+
Долгоживущие эксплуатационные задачи можно объявлять в конфиге профиля, а не пересоздавать руками в интерфейсе. Владелец объявленных задач — файл конфига: при каждом старте плагина они создаются или обновляются, а задача, исчезнувшая из файла, удаляется.
|
|
252
|
+
|
|
253
|
+
Добавьте список `jobs` в секцию плагина конфига профиля (`cordis.patch.yml`):
|
|
254
|
+
|
|
255
|
+
```yaml
|
|
256
|
+
dsh-cron:
|
|
257
|
+
jobs:
|
|
258
|
+
- id: nightly-backup
|
|
259
|
+
title: Nightly backup
|
|
260
|
+
schedule: "0 3 * * *"
|
|
261
|
+
type: script
|
|
262
|
+
prompt: "bash /path/to/backup.sh"
|
|
263
|
+
channels: ["telegram"]
|
|
264
|
+
timeoutSeconds: 3600
|
|
265
|
+
- id: morning-digest
|
|
266
|
+
title: Morning digest
|
|
267
|
+
schedule: "0 8 * * 1-5"
|
|
268
|
+
type: llm
|
|
269
|
+
prompt: "Prepare a brief morning digest of active tasks."
|
|
270
|
+
provider: my-provider
|
|
271
|
+
model: provider-id/model-id
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
* Обязательные поля записи: `id`, `title`, `schedule`; типам, у которых полезная нагрузка — это промпт (`script`, `node`, `python`, `ssh`, `docker`, `llm`, `skill`, `workflow`), нужен ещё непустой `prompt`. `http` — исключение: цель задаётся `httpUrl` (или `prompt`).
|
|
275
|
+
* Остальные поля задачи проходят как есть с той же валидацией, что и в API: `channels`, `model`, `provider`, `fallbackModel`, `silentRule`, `inspectOnFailure`, `timezone`, `timeoutSeconds`, `template`, `env`, `cwd` и рантайм-поля (`nodePath`, `pythonPath`, `httpUrl`, `httpMethod`, `httpHeaders`, `httpBody`, `sshProfileId`, `sshTarget`, `dockerImage`, `workspaceId`, `worktree`, `keepWorktree`, `skillName`, `workflowName`).
|
|
276
|
+
* Объявленные задачи помечаются как **управляемые конфигом**; в панели вместо действий правки и удаления выводится метка источника.
|
|
277
|
+
* Правка, пауза, возобновление, переключение и удаление конфиг-задачи отклоняются с `409` в панели и по API, и создание-обновление через `POST /dsh-cron/tasks` с существующим `id` конфиг-задачи отклоняется так же — источник правды файл конфига. **Запустить сейчас** остаётся доступным.
|
|
278
|
+
* Задача с тем же `id`, созданная через UI, API или инструмент агента, никогда не перезаписывается: запись пропускается, конфликт пишется в лог.
|
|
279
|
+
* Код-исполняющие типы активируются как обычные объявленные задачи, но при старте плагин пишет предупреждение в лог — путь исполнения кода, добавленный правкой конфига, остаётся видимым.
|
|
280
|
+
* Записи валидируются по одной с указанием индекса (`config.jobs[i]: …`); одна плохая запись пропускается и не может остановить остальные задачи или профиль.
|
|
281
|
+
|
|
282
|
+
### 16. Внешний REST API (`/dsh-cron/api/*`, #54)
|
|
283
|
+
Внешние системы (CI, cron хоста, `curl`) могут управлять планировщиком без открытия панели. Это единственная поверхность за bearer-токеном; маршруты панели остаются локальными и защищёнными от cross-origin.
|
|
284
|
+
|
|
285
|
+
Токен задаётся настройкой плагина `apiToken` (маскируется, как любой секрет). Аутентификация и ошибки:
|
|
286
|
+
* токен не задан → вся поверхность отвечает `503`;
|
|
287
|
+
* нет заголовка `Authorization: Bearer <token>` или токен неверный → `401`; сравнение постоянное по времени.
|
|
288
|
+
|
|
289
|
+
| Метод | Путь | Описание |
|
|
290
|
+
|:---|:---|:---|
|
|
291
|
+
| `GET` | `/dsh-cron/api/tasks` | Список задач (фильтры `status` / `query`, как в панели) |
|
|
292
|
+
| `GET` | `/dsh-cron/api/tasks/:id` | Чтение одной задачи |
|
|
293
|
+
| `POST` | `/dsh-cron/api/tasks` | Создание задачи или обновление существующей при наличии `id` |
|
|
294
|
+
| `DELETE` | `/dsh-cron/api/tasks/:id` | Удаление задачи |
|
|
295
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | Принудительный немедленный запуск |
|
|
296
|
+
|
|
297
|
+
Операции переиспользуют обработчики панели, поэтому гейт `x-dsh-cron-confirm: script` для код-исполняющих типов и отказ `409` для конфиг-задач действуют здесь так же, как в UI.
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
BASE="http://127.0.0.1:3080"
|
|
301
|
+
TOKEN="<API_TOKEN>"
|
|
302
|
+
|
|
303
|
+
# список
|
|
304
|
+
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks"
|
|
305
|
+
|
|
306
|
+
# создание или обновление, если в теле есть id
|
|
307
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
308
|
+
-d '{"id":"cleanup","title":"Cleanup","schedule":"0 4 * * *","prompt":"Remove stale temporary files."}' \
|
|
309
|
+
"$BASE/dsh-cron/api/tasks"
|
|
310
|
+
|
|
311
|
+
# принудительный запуск
|
|
312
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup/run"
|
|
313
|
+
|
|
314
|
+
# удаление
|
|
315
|
+
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup"
|
|
316
|
+
|
|
317
|
+
# код-исполняющей задаче нужен ещё заголовок подтверждения
|
|
318
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "x-dsh-cron-confirm: script" \
|
|
319
|
+
-H "Content-Type: application/json" \
|
|
320
|
+
-d '{"title":"Disk check","schedule":"0 * * * *","type":"script","prompt":"df -h"}' \
|
|
321
|
+
"$BASE/dsh-cron/api/tasks"
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 17. Метрики Prometheus (#53)
|
|
325
|
+
`GET /dsh-cron/metrics` отдаёт текст в формате Prometheus, поэтому планировщик можно снимать scrape'ом без новых зависимостей:
|
|
326
|
+
|
|
327
|
+
* `dsh_cron_tasks_total{status}` — число задач по статусам (gauge).
|
|
328
|
+
* `dsh_cron_task_last_duration_seconds{task}` — длительность последнего завершённого запуска задачи в секундах (gauge).
|
|
329
|
+
* `dsh_cron_runs_total{status}` — завершённые запуски с момента старта процесса плагина (counter); статусы `success`, `error`, `timeout`, `skipped`, `missed`.
|
|
330
|
+
* `dsh_cron_run_records` — число записей о запусках, хранимых в памяти (gauge).
|
|
331
|
+
|
|
332
|
+
В экспозицию попадают только счётчики, статусы и длительности; промпты, вывод запусков и конфигурация задач в неё не входят.
|
|
333
|
+
|
|
334
|
+
```yaml
|
|
335
|
+
scrape_configs:
|
|
336
|
+
- job_name: dsh-cron
|
|
337
|
+
static_configs:
|
|
338
|
+
- targets: ["127.0.0.1:3080"]
|
|
339
|
+
metrics_path: /dsh-cron/metrics
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 18. Строгая проверка каналов (#121)
|
|
343
|
+
Создание или обновление задачи с неизвестным идентификатором канала теперь отклоняется с `400`, а виновники перечисляются в ответе:
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{ "ok": false, "error": "Unknown channel ids: email_ping", "unknownChannels": ["email_ping"] }
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Changed in v0.2.7: раньше неизвестный идентификатор молча отбрасывался, поэтому клиент с опечаткой получал `ok: true` и задачу, которая никуда не доставляет.
|
|
350
|
+
|
|
351
|
+
Импорт намеренно остаётся терпимым (файл может быть из старой версии): неизвестные идентификаторы отбрасываются у импортируемой задачи, но перечисляются в ответе (`unknownChannels`) и пишутся в лог планировщика, а не исчезают молча.
|
|
352
|
+
|
|
353
|
+
### 19. Проверка после установки (#126)
|
|
354
|
+
У `deploy.sh` есть режим только-проверки уже установленного профиля, ничего не устанавливающий:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
bash deploy.sh verify [exact-version]
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Он проверяет, что профиль сообщает нужную версию (по умолчанию — версия из `package.json`), аутентифицируется в web UI, затем скачивает клиентский бандл и убеждается, что имя пакета в нём присутствует.
|
|
361
|
+
|
|
362
|
+
Зачем это нужно: web-профиль может стоять за плагином аутентификации и отвечать `401` на анонимный запрос, а клиентский бандл плагина отдаётся только по точному combined-URL вида `??` из аутентифицированного индекса — голый `/plugins/<name>/client.js` отвечает `404`. Поэтому проверка сначала строит аутентифицированную сессию.
|
|
363
|
+
|
|
364
|
+
Переменные окружения проверки: `DSH_WEB_BASE` (по умолчанию `http://127.0.0.1:3080`), `DSH_WEB_TOKEN` (токен; если не задан, скрипт берёт последний из журнала юнита), `DSH_WEB_UNIT` (по умолчанию `dsh-web.service`). Секретов в скрипте нет.
|
|
365
|
+
|
|
366
|
+
### 20. Внутренняя разбивка: разбор расписания и постановка (#97)
|
|
367
|
+
Только для разработчиков, поведение не меняется. `parseScheduleExpression` разбит на маленькие функции с тем же порядком ветвей — `parseAtExpression`, `parseRelativeOneShot`, `parseIntervalExpression`, `parseAliasExpression`, `parseCronExpression`, — а `scheduleTask` — на `clearScheduled`, `scheduleOneShot` и `scheduleCron`. Прежний набор тестов прошёл без правок, добавлены точечные тесты на приоритет ветвей и ошибки.
|
|
368
|
+
|
|
248
369
|
### Параметры
|
|
249
370
|
|
|
250
371
|
| Параметр | Тип | По умолчанию | Описание |
|
|
@@ -268,6 +389,7 @@ dsh-cron:
|
|
|
268
389
|
| `pushplusUrl` / `pushplusTokenRef` | `string` | `"https://www.pushplus.plus/send"` / `""` | Endpoint PushPlus (переопределяется для self-hosted прокси) и имя credential токена |
|
|
269
390
|
| `ttsBaseUrl` | `string` | `"http://127.0.0.1:3080"` | Базовый URL плагина `dsh-tts` для голосовых объявлений |
|
|
270
391
|
| `giteaBaseUrl` / `giteaRepo` / `giteaTokenRef` | `string` | `""` | Канал Gitea: базовый URL, `owner/repo` и имя credential API-токена |
|
|
392
|
+
| `apiToken` | `string` | `""` | Bearer-токен внешней поверхности `/dsh-cron/api/*`. Секретное поле, отдаётся замаскированным; пусто отключает поверхность (503), неверное значение — 401 |
|
|
271
393
|
|
|
272
394
|
Примечания:
|
|
273
395
|
|
|
@@ -304,6 +426,10 @@ dsh-cron:
|
|
|
304
426
|
| `POST` | `/dsh-cron/telegram/test` | Тестовое сообщение в Telegram |
|
|
305
427
|
| `POST` | `/dsh-cron/kanban/test` | Тестовая карточка в Kanban |
|
|
306
428
|
| `*` | `/dsh-cron/action/:id/:action` | Legacy-алиас действий над задачей (`run`, `toggle`, `delete`, `history`) |
|
|
429
|
+
| `GET` | `/dsh-cron/metrics` | Текст в формате Prometheus: счётчики задач и запусков — без промптов и вывода (#53) |
|
|
430
|
+
| `GET` / `POST` | `/dsh-cron/api/tasks` | Внешняя поверхность под токеном: список / создание-обновление (#54) |
|
|
431
|
+
| `GET` / `DELETE` | `/dsh-cron/api/tasks/:id` | Внешняя поверхность под токеном: чтение / удаление (#54) |
|
|
432
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | Внешняя поверхность под токеном: принудительный запуск (#54) |
|
|
307
433
|
|
|
308
434
|
---
|
|
309
435
|
|
package/docs/README.zh.md
CHANGED
|
@@ -197,6 +197,125 @@ cron_create_task({
|
|
|
197
197
|
* 在插件设置中配置 `heartbeatUrl` 与 `heartbeatIntervalSec`,调度器会按间隔 GET 该地址 —— 外部监控可在心跳停止时告警。
|
|
198
198
|
* 内置 `GET /dsh-cron/heartbeat` 端点返回存活状态、活跃任务数与最近运行时间,便于自建看门狗。
|
|
199
199
|
|
|
200
|
+
### 15. 来自配置的声明式任务(#50)
|
|
201
|
+
长期运行的任务可以直接声明在配置文件里,而无需在界面中手工重建。配置文件拥有这些任务:每次插件启动时会创建或更新它们,从文件中消失的任务会被删除。
|
|
202
|
+
|
|
203
|
+
在配置文件(`cordis.patch.yml`)的插件段加入 `jobs` 列表:
|
|
204
|
+
|
|
205
|
+
```yaml
|
|
206
|
+
dsh-cron:
|
|
207
|
+
jobs:
|
|
208
|
+
- id: nightly-backup
|
|
209
|
+
title: Nightly backup
|
|
210
|
+
schedule: "0 3 * * *"
|
|
211
|
+
type: script
|
|
212
|
+
prompt: "bash /path/to/backup.sh"
|
|
213
|
+
channels: ["telegram"]
|
|
214
|
+
timeoutSeconds: 3600
|
|
215
|
+
- id: morning-digest
|
|
216
|
+
title: Morning digest
|
|
217
|
+
schedule: "0 8 * * 1-5"
|
|
218
|
+
type: llm
|
|
219
|
+
prompt: "Prepare a brief morning digest of active tasks."
|
|
220
|
+
provider: my-provider
|
|
221
|
+
model: provider-id/model-id
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
* 每条必填:`id`、`title`、`schedule`;以提示词承载有效载荷的类型(`script`、`node`、`python`、`ssh`、`docker`、`llm`、`skill`、`workflow`)还需非空 `prompt`。`http` 例外:目标由 `httpUrl`(或 `prompt`)给出。
|
|
225
|
+
* 其余任务字段按原样透传,校验与 API 一致:`channels`、`model`、`provider`、`fallbackModel`、`silentRule`、`inspectOnFailure`、`timezone`、`timeoutSeconds`、`template`、`env`、`cwd`,以及运行时字段(`nodePath`、`pythonPath`、`httpUrl`、`httpMethod`、`httpHeaders`、`httpBody`、`sshProfileId`、`sshTarget`、`dockerImage`、`workspaceId`、`worktree`、`keepWorktree`、`skillName`、`workflowName`)。
|
|
226
|
+
* 声明式任务标记为**由配置管理**;面板中显示来源标签而不是编辑/删除按钮。
|
|
227
|
+
* 对配置任务的编辑、暂停、恢复、切换与删除在面板和 API 上返回 `409`,携带配置任务现有 `id` 的创建或更新请求 `POST /dsh-cron/tasks` 同样被拒绝 —— 配置文件的来源为唯一真值。**立即运行**仍然可用。
|
|
228
|
+
* 通过 UI、API 或智能体工具创建的、`id` 相同的任务绝不会被覆盖:该条目会被跳过,冲突写入日志。
|
|
229
|
+
* 会执行代码的类型照常激活,但启动时插件会向日志写警告,使通过配置引入的代码路径可见。
|
|
230
|
+
* 条目逐条校验并带下标(`config.jobs[i]: …`);一条坏条目会被跳过,不会阻止其余任务或整个配置。
|
|
231
|
+
|
|
232
|
+
### 16. 外部 REST API(`/dsh-cron/api/*`,#54)
|
|
233
|
+
外部系统(CI、宿主机 cron、`curl`)无需打开面板即可驱动调度器。这是唯一由 bearer 令牌保护的接口;面板路由保持本地且防跨站。
|
|
234
|
+
|
|
235
|
+
令牌是插件设置 `apiToken`(与所有密钥一样掩码显示)。认证与错误:
|
|
236
|
+
* 未配置令牌 → 整个接口返回 `503`;
|
|
237
|
+
* 缺少或错误的 `Authorization: Bearer <token>` → `401`,比较为常量时间。
|
|
238
|
+
|
|
239
|
+
| 方法 | 路径 | 说明 |
|
|
240
|
+
|:---|:---|:---|
|
|
241
|
+
| `GET` | `/dsh-cron/api/tasks` | 任务列表(`status` / `query` 过滤,同面板) |
|
|
242
|
+
| `GET` | `/dsh-cron/api/tasks/:id` | 读取单个任务 |
|
|
243
|
+
| `POST` | `/dsh-cron/api/tasks` | 创建任务;带 `id` 时更新现有任务 |
|
|
244
|
+
| `DELETE` | `/dsh-cron/api/tasks/:id` | 删除任务 |
|
|
245
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | 强制执行一次 |
|
|
246
|
+
|
|
247
|
+
这些操作复用面板处理器,因此对会执行代码类型的 `x-dsh-cron-confirm: script` 门禁以及对配置任务的 `409` 拒绝与 UI 完全一致。
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
BASE="http://127.0.0.1:3080"
|
|
251
|
+
TOKEN="<API_TOKEN>"
|
|
252
|
+
|
|
253
|
+
# 列表
|
|
254
|
+
curl -s -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks"
|
|
255
|
+
|
|
256
|
+
# 创建;请求体带 id 时为更新
|
|
257
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
|
258
|
+
-d '{"id":"cleanup","title":"Cleanup","schedule":"0 4 * * *","prompt":"Remove stale temporary files."}' \
|
|
259
|
+
"$BASE/dsh-cron/api/tasks"
|
|
260
|
+
|
|
261
|
+
# 强制执行
|
|
262
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup/run"
|
|
263
|
+
|
|
264
|
+
# 删除
|
|
265
|
+
curl -s -X DELETE -H "Authorization: Bearer $TOKEN" "$BASE/dsh-cron/api/tasks/cleanup"
|
|
266
|
+
|
|
267
|
+
# 会执行代码的任务还需确认头
|
|
268
|
+
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "x-dsh-cron-confirm: script" \
|
|
269
|
+
-H "Content-Type: application/json" \
|
|
270
|
+
-d '{"title":"Disk check","schedule":"0 * * * *","type":"script","prompt":"df -h"}' \
|
|
271
|
+
"$BASE/dsh-cron/api/tasks"
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### 17. Prometheus 指标(#53)
|
|
275
|
+
`GET /dsh-cron/metrics` 返回 Prometheus 文本格式,无需新增依赖即可被抓取:
|
|
276
|
+
|
|
277
|
+
* `dsh_cron_tasks_total{status}` —— 按状态统计的任务数(gauge)。
|
|
278
|
+
* `dsh_cron_task_last_duration_seconds{task}` —— 任务最近一次完成运行的耗时(秒,gauge)。
|
|
279
|
+
* `dsh_cron_runs_total{status}` —— 自插件进程启动以来完成的运行数(counter);状态为 `success`、`error`、`timeout`、`skipped`、`missed`。
|
|
280
|
+
* `dsh_cron_run_records` —— 当前保存在内存中的运行记录数(gauge)。
|
|
281
|
+
|
|
282
|
+
导出内容只有计数、状态和耗时;提示词、运行输出与任务配置不会出现在其中。
|
|
283
|
+
|
|
284
|
+
```yaml
|
|
285
|
+
scrape_configs:
|
|
286
|
+
- job_name: dsh-cron
|
|
287
|
+
static_configs:
|
|
288
|
+
- targets: ["127.0.0.1:3080"]
|
|
289
|
+
metrics_path: /dsh-cron/metrics
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### 18. 严格的渠道校验(#121)
|
|
293
|
+
创建或更新任务时若包含未知的投递渠道 id,现在会返回 `400` 并列出违规项:
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{ "ok": false, "error": "Unknown channel ids: email_ping", "unknownChannels": ["email_ping"] }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Changed in v0.2.7:此前未知 id 会被静默丢弃,客户端即使有拼写错误也会得到 `ok: true`,最终得到一个不投递任何地方的任务。
|
|
300
|
+
|
|
301
|
+
导入有意保持宽容(文件可能来自旧版本):未知 id 会从导入的任务中丢弃,但会在响应(`unknownChannels`)中列出并写入调度器日志,而不是无声消失。
|
|
302
|
+
|
|
303
|
+
### 19. 安装后校验(#126)
|
|
304
|
+
`deploy.sh` 新增仅校验模式,用于检查已安装的配置而不安装任何东西:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
bash deploy.sh verify [exact-version]
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
它确认配置报告了指定版本(默认取 `package.json` 的版本),登录 Web UI,然后下载客户端 bundle 并确认其中包含包名。
|
|
311
|
+
|
|
312
|
+
为什么需要它:Web 配置可能位于认证插件之后并对匿名请求返回 `401`,而插件客户端 bundle 只能通过认证后索引中打印的精确组合 `??` URL 获取 —— 裸的 `/plugins/<name>/client.js` 会返回 `404`。因此校验需要先建立已认证会话。
|
|
313
|
+
|
|
314
|
+
校验使用的环境变量:`DSH_WEB_BASE`(默认 `http://127.0.0.1:3080`)、`DSH_WEB_TOKEN`(令牌;未设置时脚本从单元日志读取最后一个)、`DSH_WEB_UNIT`(默认 `dsh-web.service`)。脚本中不含任何密钥。
|
|
315
|
+
|
|
316
|
+
### 20. 内部重构:调度解析与排程(#97)
|
|
317
|
+
面向开发者,行为不变。`parseScheduleExpression` 被拆分为保持相同分支顺序的小函数 —— `parseAtExpression`、`parseRelativeOneShot`、`parseIntervalExpression`、`parseAliasExpression`、`parseCronExpression`,`scheduleTask` 拆分为 `clearScheduled`、`scheduleOneShot`、`scheduleCron`。原有测试全部通过,并新增了针对分支优先级与错误的测试。
|
|
318
|
+
|
|
200
319
|
---
|
|
201
320
|
|
|
202
321
|
## 📦 安装
|
|
@@ -243,6 +362,8 @@ dsh-cron:
|
|
|
243
362
|
giteaBaseUrl: "" # giteaRepo = owner/repo,giteaTokenRef = 凭据名称
|
|
244
363
|
giteaRepo: ""
|
|
245
364
|
giteaTokenRef: ""
|
|
365
|
+
# --- 外部 REST API(#54)---
|
|
366
|
+
apiToken: "" # 外部 /dsh-cron/api/* 接口的 bearer 令牌(掩码;空 = 503)
|
|
246
367
|
```
|
|
247
368
|
|
|
248
369
|
### 配置参数
|
|
@@ -268,6 +389,7 @@ dsh-cron:
|
|
|
268
389
|
| `pushplusUrl` / `pushplusTokenRef` | `string` | `"https://www.pushplus.plus/send"` / `""` | PushPlus 端点(可指向自建代理)与 token 凭据名称 |
|
|
269
390
|
| `ttsBaseUrl` | `string` | `"http://127.0.0.1:3080"` | 用于语音播报的 `dsh-tts` 基础地址 |
|
|
270
391
|
| `giteaBaseUrl` / `giteaRepo` / `giteaTokenRef` | `string` | `""` | Gitea 渠道:基础地址、`owner/repo` 与 API token 的凭据名称 |
|
|
392
|
+
| `apiToken` | `string` | `""` | 外部 `/dsh-cron/api/*` 接口的 Bearer 令牌。保密字段,返回时掩码;为空时接口返回 503,错误值返回 401 |
|
|
271
393
|
|
|
272
394
|
说明:
|
|
273
395
|
|
|
@@ -304,6 +426,10 @@ dsh-cron:
|
|
|
304
426
|
| `POST` | `/dsh-cron/telegram/test` | 发送 Telegram 测试消息 |
|
|
305
427
|
| `POST` | `/dsh-cron/kanban/test` | 创建 Kanban 连通性测试卡片 |
|
|
306
428
|
| `*` | `/dsh-cron/action/:id/:action` | 任务操作路由的兼容别名(`run`、`toggle`、`delete`、`history`) |
|
|
429
|
+
| `GET` | `/dsh-cron/metrics` | Prometheus 文本格式的任务与运行计数 —— 不含提示词与输出(#53) |
|
|
430
|
+
| `GET` / `POST` | `/dsh-cron/api/tasks` | 令牌保护的外部接口:列表 / 创建或更新(#54) |
|
|
431
|
+
| `GET` / `DELETE` | `/dsh-cron/api/tasks/:id` | 令牌保护的外部接口:读取 / 删除(#54) |
|
|
432
|
+
| `POST` | `/dsh-cron/api/tasks/:id/run` | 令牌保护的外部接口:强制执行(#54) |
|
|
307
433
|
|
|
308
434
|
---
|
|
309
435
|
|