Ранбук первого деплоя паттерна sched+worker (модель для yt-digest): стек tg-digest (воркер internal, mem_limit 256m), runtime-регистрация задачи POST /tasks, env-контракт, smoke/verify/rollback, gotchas (сессия Telethon, TZ UTC, timeoutMs). Артефакты: compose source-of-truth, Dockerfile + http_worker.py (черновики для коммита в victor/tg-digest). task:1311 разблокирован (1305 done) -> ready.
12 KiB
title, type, tags, related, updated
| title | type | tags | related | updated | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| tg-digest — VDS deploy runbook (sched cron + HTTP-воркер) | concept |
|
|
2026-08-28 |
tg-digest — VDS deploy runbook (sched cron + HTTP-воркер)
⚙️ СЛУЖЕБНЫЙ РАНБУК — только для администратора проекта
.admin. Применять/выполнять шаги может только.admin(оператор проекта admin). Другим проектам/агентам — читать по запросу, не выполнять. Из проекта не выносить: не копировать в другие вики, не пересказывать, не публиковать. Нужен деплой или прод-операция по этому ранбуку — поставить задачу админу (.admin) и написать письмо. Админ выполняет, остальные верифицируют.
Развёртывание воркера tg-digest (дневной дайджест телеграм-каналов) на инфра-VDS:
контейнер-воркер (internal, сеть proxy, без traefik-роута) + задача в sched (ядро уже
задеплоено, стек 28, см. sched-vds-deploy-runbook): cron 0 5 * * *, runner http, simple mode.
Опер-таска: task:1311 (tg-digest-ops-stack). Аналог: yt-digest task:1190 (не задеплоен —
этот ранбук первый для паттерна sched+worker).
Параметры
| Что | Значение |
|---|---|
| Хост | VDS kzntsv 89.253.255.94 |
| Стек Portainer | tg-digest (новый, создаётся первым деплоем), endpointId 1; после создания id записать сюда |
| Образ | registry.kzntsv.site/tg-digest-worker:<tag> (python:3.13-alpine, telethon+requests) |
| Compose source-of-truth | admin/host-stacks/vds-kzntsv/tg-digest.compose.yml |
| Воркер | контейнер tg-digest, HTTP :8080: GET /healthz, POST /run (simple mode: 2xx = ok) |
| Задача sched | runtime-регистрация POST /tasks (fileManaged:false, без рестарта ядра); cron 0 5 * * * (tz UTC = 08:00 MSK) |
| Sched admin key | pass sched/admin-key (Bearer) |
| Per-task auth | x-sched-api-key = WORKER_API_KEY воркера = pass sched/tg-digest-api-key (завести при деплое) |
| mem_limit | 256m |
| Сети | proxy (external, internal — НЕТ traefik-labels, наружу не публикуется) |
| Registry | REGISTRY_URL/REGISTRY_USER/REGISTRY_PASS = pass vds-kzntsv/full-env |
Артефакты
- Код: репо victor/tg-digest, ветка main, минимум
d3aefb2(raw-ингест, task:1475 — старыйingest_wikiудалён, per-post ингест не работает). Воркер:python -m src.worker. - Dockerfile + HTTP-обёртка (
http_worker.py): коммитятся в корень репо victor/tg-digest (dev-сторона); черновики —admin/host-stacks/vds-kzntsv/tg-digest/. Без них image не собрать. - Env-контракт воркера (см. таблицу ниже) — все значения env (pass в контейнере НЕТ).
Env-контракт (воркер)
| Переменная | Источник (pass) | Обязательна | Назначение |
|---|---|---|---|
TG_API_ID |
telegram/api-id — НЕТ в pass, оператор |
да | MTProto app id (my.telegram.org) |
TG_API_HASH |
telegram/api-hash — НЕТ в pass, оператор |
да | MTProto app hash |
TG_SESSION |
/data/session.session (volume; создаётся интерактивным логином) |
да | Telethon-сессия |
TG_PHONE |
оператор (только при первом логине) | первый запуск | телефон аккаунта |
TG_CACHE |
— (дефолт tg-cache; на деплое /data/tg-cache) |
опц | кэш коллектора + state.json (персист) |
TG_WINDOW_HOURS |
— (дефолт 24; бэкфилл 26-27 → 48-72) | опц | окно сбора |
TELEGRAM_BOT_TOKEN |
telegram/full-env |
да | доставка дайджеста |
TELEGRAM_CHAT_ID |
telegram/full-env |
да | чат доставки |
LLM_API_KEY |
pass-запись отсутствует — уточнить у оператора (deepseek; локально в gitignored src/config/llm.json) |
да | стадия-2 (LLM-находки) |
LLM_BASE_URL |
— (дефолт https://api.deepseek.com) |
опц | |
LLM_MODEL |
— (дефолт deepseek-chat) |
опц | |
MAPPA_API_TOKEN |
mappa/full-env |
да | ингест raw (wiki:3312) |
MAPPA_URL |
— (дефолт https://mappa.vds.kzntsv.site) |
опц | |
MAPPA_PROJECT |
— (дефолт tg-digest) |
опц | |
WORKER_API_KEY |
sched/tg-digest-api-key (завести) |
да | проверка x-sched-api-key в /run |
TG_SEND |
— (0 — dry-run smoke без отправки) |
опц | доставка |
Задача sched (POST /tasks, runtime)
{
"name": "tg-digest",
"schedules": [{ "cron": "0 5 * * *" }],
"config": {
"url": "http://tg-digest:8080/run",
"method": "POST",
"timeoutMs": 900000,
"auth": { "apiKey": "<из pass sched/tg-digest-api-key>" }
}
}
Простой режим (без envelope): sched шлёт POST, ждёт ответ до config.timeoutMs, 2xx = succeeded.
task.timeoutMs НЕ ставить (ceiling шлёт воркеру cancel — воркер его не реализует).
Первый деплой
0. Предусловия (оператор):
pass telegram/api-id+pass telegram/api-hash(my.telegram.org → Apptg-digest);- сессия Telethon: первый
python -m src.collector --liveинтерактивен (телефон + код из СМС) — создать session-файл заранее, положить в volume (см. gotcha 1); LLM_API_KEY(deepseek) — pass-запись или env;pass sched/tg-digest-api-key(новая запись, ключ дляx-sched-api-key).
1. Dockerfile + http_worker.py в репо (корень victor/tg-digest), main. Билд строго из main.
2. Build + push образа:
docker login $REGISTRY_URL -u $REGISTRY_USER -p "$(pass show vds-kzntsv/full-env | awk -F= '/^REGISTRY_PASS=/{print $2}')"
docker build -t registry.kzntsv.site/tg-digest-worker:<tag> <чекаут victor/tg-digest>
docker push registry.kzntsv.site/tg-digest-worker:<tag>
3. Стек Portainer (канон portainer-stack-management-vds): создать tg-digest
(POST /api/stacks/create/standalone/string?endpointId=1, stackFileContent = compose из
source-of-truth, env-массив = секреты из таблицы; pullImage: true). Без traefik-labels
(воркер internal, доступа снаружи нет).
4. Регистрация задачи (без рестарта sched):
AK=$(pass show sched/admin-key)
curl -ksS -X POST https://sched.vds.kzntsv.site/api/tasks \
-H "Authorization: Bearer $AK" -H 'Content-Type: application/json' \
-d '{"name":"tg-digest","schedules":[{"cron":"0 5 * * *"}],
"config":{"url":"http://tg-digest:8080/run","method":"POST","timeoutMs":900000,
"auth":{"apiKey":"<из pass sched/tg-digest-api-key>"}}}'
5. Smoke (ручной триггер):
curl -ksS -X POST https://sched.vds.kzntsv.site/api/tasks/tg-digest/run \
-H "Authorization: Bearer $AK" -H 'Content-Type: application/json' \
-d '{"temporary": true, "triggeredBy": "admin-smoke"}'
Блокируется до конца рана (sync runner) → ответ = терминальный run.
Сухой деплой без ключей: контейнер Up, /healthz 200, ран падает die 2 creds missing — ок как
проверка плумбинга, не как acceptance.
Verify
- Контейнер:
Up (healthy);docker exec tg-digest python -c "import urllib.request;print(urllib.request.urlopen('http://127.0.0.1:8080/healthz').read())"→{"ok": true}. - sched:
GET /api/tasks(Bearer) →tg-digestв списке,nextRunAtзавтра 05:00 UTC. - Smoke run:
GET /api/runs?task=tg-digest→ последний runsucceeded(с ключами) с полемresult. - Артефакт дня: raw-запись в mappa
raw/research/<дата>-<topic>(конвенция wiki:3312) + доставка в TG.
Rollback
- Задача:
DELETE /api/tasks/tg-digest(runtime-таска, 204) — расписание снято. - Стек: удалить
tg-digestв Portainer. - Образ: старый тег в registry (повторный деплой с предыдущим тегом).
Gotchas
- Сессия Telethon обязательна, api_id/api_hash недостаточны (2026-08-28): первый
--liveинтерактивен — телефон + СМС-код; без session-файла в контейнере коллектор не стартует. Сессию создать заранее (локально или TTY контейнера), смонтировать в/data/session.session. - pass в контейнере НЕТ (2026-08-28): коллектор умеет читать pass через bash (
_pass), но в образе его нет → только env. Все секреты — env при деплое (chmod 600 / Portainer env, вне git). - TZ cron = UTC (дефолт sched):
0 5 * * *= 08:00 MSK (утренний дайджест, как yt-digest). config.timeoutMs(транспортный) — 900000 (пайплайн collect→stage2 LLM→ingest→tg занимает минуты);task.timeoutMs(ceiling) НЕ ставить — при срабатывании sched шлётPOST /cancel, воркер (не паттерн C, не @apilki) его не реализует → ран пометится failed при живом воркере.- mem_limit 256m: python+telethon на старте ~150-200m — достаточно; не поднимать без нужды.
- Образ строго из main с
d3aefb2(raw-ингест): старыйingest_wikiудалён, per-post ингест не работает; билд из старого коммита → падение на ингесте. - Бэкфилл за 26-27 августа:
TG_WINDOW_HOURS48-72 на один прогон (или два прогона);ingest_rawидемпотентен по дню (raw/<дата>-<topic>). - Runtime-регистрация = fileManaged:false:
POST /tasksне трогаетsched.tasks.json(остаётся{"tasks": []}); таска живёт доDELETE /tasks/:name. tasks.json — только декларативный источник (рестарт ядра НЕ нужен). - Portainer env не env_file — секреты в env-массив стека (gotcha 4b портайнер-канона: PUT сбрасывает env, если не передать массив целиком).
Связи
- concepts/runbooks-index.md — индекс ранбуков
- concepts/sched-vds-deploy-runbook.md — ядро sched (стек 28, admin API, MariaDB)
- concepts/portainer-stack-management-vds.md — общий Portainer-канон (JWT, gotchas)
- Репо victor/tg-digest:
src/worker.py(оркестратор), спека wiki:3260, конвенция raw wiki:3312