--- title: sched-runtime-to-file-managed-migration type: concept status: live tags: [books, sched, file-managed, durable-config, incident, live-sync] related: "[[sched-ozon-creds-stub-contract]], [[snolla-smtp-mail-delivery-2026-08]], [[portainer-stack-management-books-vds]]" updated: 2026-08-23 --- # Sched runtime → file-managed миграция (durable config) ## TL;DR После инцидента 2026-08-23 (спин `ozonFbsPostingsSyncronization`, ~75 req/сек) оба тенанта books (slovo + bookva) переведены на **file-managed** расписания sched: декларатив в `deploy/sched-tasks.json` (git-tracked), демон подхватывает файл live-sync'ом. Runtime-путь (таски в сторе, `fileManaged:false`) хрупкий — расписания живут в памяти демона и слетают при pause/перезапуске, таски остаются сиротами без `data`. **Ключевой факт миграции: live-sync НЕ конвертирует runtime-таски** (sync создаёт только новые из файла, существующие runtime НЕ перезаписывает). Перевод = явный `DELETE` runtime- тасок и расписаний через admin API, после чего sync доставляет file-managed версии. ## Почему runtime хрупкий (контекст инцидента) - Расписания runtime-тасок держатся **в памяти демона** → при `pause`/`restart` слетают. - Таски остаются сиротами: `fileManaged:false`, `schedule:null`, `data:undefined`. - `deploy/sched-tasks.json` был **gitignored** (прод-файл на хосте) — конфиг не версионировался, восстановление = ручные миграции из `agendaJobs`. - Runtime-таска могла нести **чужой URL runner'а** (cross-tenant leak: `books-task-runner` в bookva-конфиге) — файл чинит (bookva → `bookva-task-runner`). ## Миграционный рецепт (по тенанту) 1. **Файл** — из git-репо в прод-расположение: - slovo: `/opt/books/sched/tasks.json` (bind-директория `/opt/books/sched:/app/config:ro`). - bookva: volume `/var/lib/docker/volumes/bookva-sched-config/_data/tasks.json` (→ `/app/config`). - Заливка **in-place**: `cp /tmp/new /path/tasks.json` (не mv — сохранить inode/perms). Бэкап: `cp tasks.json tasks.json.bak-`. - Перед заливкой: diff против live — допускаются только целевые изменения (books собирает файл байт-идентичным live + новые таски). 2. **Live-sync** — демон подхватывает файл ≤60с, рестарт НЕ нужен. Верифицировать через `GET /api/tasks` (новые таски появляются как `fileManaged:true`). 3. **DELETE runtime** (sync их не убирает — только API, runs-история сохраняется): - `DELETE /api/tasks/:name` ×N — имена URL-encode (`%20`). - `DELETE /api/schedules/:id` ×N — **не каскадятся**, удалять отдельно. Все → 204. 4. **Verify**: - `fileManaged:true` у всех целевых, `disabled:false`, `paused:false`. - Дублей 0: `[].taskName | group_by(.) | map(select(length>1))` пуст. - `data` на расписании (не на таске): slovo `{idSeller:2}` / bookva `{idSeller:1}`, отчёт — `{recipients:[...], idSeller}`. - Ручной раунд: `POST /api/tasks/:name/run` → `succeeded {sellersProcessed:N}`. ## Gotchas | Гоча | Деталь | |---|---| | id-паттерн | file-managed schedule `id` = taskName; runtime = UUID | | ghost-таски | `fileManaged:true` сироты (выпилены из файла) висят `disabled:true` — удалять `DELETE /api/tasks/:name` | | task-runner config | `/opt/books/task-runner/config/default.json` кэшируется при первом send (lazy init `sendReportsByEmail.js`) → после правки **рестарт обязателен** | | SMTP | `from` == auth-user обязателен (Yandex: 550 not owned / 525 disabled). Креды `e-16513832@yandex.ru` (app-pass, `pass show snolla-smtp/full-env`). См. [[snolla-smtp-mail-delivery-2026-08]] | | имена тасок | «ozon fbs postings syncronization» — пробелы, в DELETE URL-encode | | шаблон vs прод | конвертер из шаблона может давать лишние trigger-only таски — в деплой-файл не включать без причины | ## Текущее состояние (2026-08-23, #991 closed) - slovo: 25 тасок, file-managed, отчёт `{idSeller:2, recipients:[3]}` (recipients восстановлены, commit `854e381`). - bookva: 23 таски, file-managed, `data {idSeller:1}` (commit `6d4f163` bookva-overlay). - Ghost `system-cleanup-task-runs` удалён на обоих тенантах. - Доступ: sched admin API — books-sched `172.20.0.20:3031`, bookva-sched `172.20.0.12:3031`, Bearer `SCHED_ADMIN_KEY` (env контейнера), через SSH books-vds. Роуты — `packages/admin-api/src/admin-api.ts` (репо sched). ## Related - [[sched-ozon-creds-stub-contract]] — почему мисматч-пары кредов намеренные (тот же инцидент). - [[snolla-smtp-mail-delivery-2026-08]] — SMTP-уроки (525/554/550), перенос на e-16513832@yandex.ru. - [[portainer-stack-management-books-vds]] — управление стеками books VDS.