docs(runbooks): ранбуки-файлы → стубы с указателями [[wiki:N]] (канал файлов закрыт) — task:1507

18 runbook-файлов .wiki/concepts/ заменены на стубы-указатели на mappa wiki-сущности.
5 без дубля заингестены в mappa: wiki:3329-3333 (gitea-project-create, pilorama98-vds-deploy,
sched-pipelines-local-stack, sched-publish, sched-vds-deploy). Индекс wiki:3316 переведён на wiki-ссылки.
This commit is contained in:
2026-08-29 11:13:47 +03:00
parent b6e0e776e8
commit 6547558833
18 changed files with 36 additions and 2193 deletions

View File

@@ -1,123 +1,3 @@
---
title: mappa — VDS deploy runbook (БД / образ / Portainer-стек 27 / редеплой / verify)
type: concept
tags: [mappa, vds, deploy, docker, portainer, traefik, postgres, runbook, meta]
related: [[../entities/vds-kzntsv]], [[portainer-stack-management-vds]], [[mappa-prod-infra]]
updated: 2026-08-28
---
# ⛔ Файловый канал закрыт
# mappa → VDS deploy runbook
Развёртывание **mappa** (единый meta-сервис цеха: HTTP-ядро + MCP-адаптер, Postgres)
на [[../entities/vds-kzntsv]] (89.253.255.94) рядом с общей БД (решения 1/17/18 спеки
`concepts/mappa`). Первый деплой — 2026-08-23 (#1012, закрыта).
**Разделение труда (директива 2026-08-23):** деплой/редеплой = АДМИН (таска + письмо);
прод-импорт, verify счётчиков, read-only+фолбэк, ntfy-алерт, артефакты, MCP-регистрация =
mappa (#985). Не делать чужую часть.
## Артефакты
- **Код:** `OpeItcLoc03/mappa` @ master. Сборка TS: `npm ci && npm run build``dist/`.
- **Образ:** `registry.kzntsv.site/mappa:<version>` — тег = version из package.json, GIT_SHA передаётся
`--build-arg GIT_SHA=<sha>` (Dockerfile в корне репо, multi-stage node:22-alpine;
**обязательный COPY `src/lib/schema.sql` → `dist/src/lib/schema.sql`** — tsc не копирует .sql,
без него migrate() падает «schema.sql not found»). Прод на 2026-08-29 = **v0.32.0** (GIT_SHA 27d1d2a,
CC-диспатчер mappa.ps1 + mappa-statusline.ps1, только hooks/ — серверный код не менялся).
Deploy-паттерн: релизная ветка `deploy/vX.Y.Z` из батч-tip → бамп package.json → merge в master.
- **Стек Portainer:** `mappa` (Id 27, endpoint 1). Source-of-truth compose:
`admin/host-stacks/vds-kzntsv/mappa.compose.yml`.
- **БД:** база `mappa` + role `mappa` на общем `postgres:16` VDS (owner = mappa).
Креды: **`pass show mappa/full-env`** (`MAPPA_DB_PASS`, `DATABASE_URL` docker-сетевой:
`postgres://mappa:…@postgres:5432/mappa`; внешний host `postgres.vds.kzntsv.site:5432` — TLS-verify-skip).
- **Endpoint:** `https://mappa.vds.kzntsv.site` (traefik websecure, letsEncrypt, service-port 8731).
## Реденплой (обновление образа) — быстрый путь
```bash
# 1. Собрать и запушить новый тег образа (с воркстейшена, Docker Desktop)
cd ~/projects/mappa
docker build -t registry.kzntsv.site/mappa:<tag> .
docker push registry.kzntsv.site/mappa:<tag>
# 2. НА VDS: docker pull ДО restart стека (иначе застрянет на старом digest!)
ssh vds.kzntsv.site 'docker pull registry.kzntsv.site/mappa:<tag>'
# 3. Поменять image: в host-stacks/vds-kzntsv/mappa.compose.yml И в Portainer stack 27
# (обе копии синхронизировать — иначе следующий redeploy из гита откатит тег)
# 4. Обновить стек через Portainer API (НЕ ad-hoc docker compose up!): PUT /api/stacks/27?endpointId=1
# с {stackFileContent (новый тег), env 3/3, pullImage:true} — X-API-Key: pass vds-kzntsv/full-env PORTAINER_API_KEY.
# PUT без env сбрасывает env-массив (gotcha 4b) — env обязателен. pullImage:true тянет новый digest сам.
# Рестарт-only (POST /restart) НЕ меняет тег образа — только для рестарта без смены образа.
# 5. Verify
curl -ksS https://mappa.vds.kzntsv.site/health # {"ok":true,"service":"mappa"}
curl -ksS https://mappa.vds.kzntsv.site/admin/status # entities/by_type/projects
curl -ksS https://mappa.vds.kzntsv.site/graph/stats # nodes/edges/components
ssh vds.kzntsv.site 'docker ps --filter name=mappa --format "{{.Status}}"'
```
## Обновление env (пароль БД и т.п.)
Через Portainer → Stack 27 → Edit (env array), НЕ файлом на VDS (Portainer string-mode не
материализует .env; env передаётся API-массивом — gotcha 1 `portainer-stack-management-vds`).
**Env 3/3** (все из `pass mappa/full-env`): `MAPPA_DB_PASS` (подставляется в DATABASE_URL compose),
`MAPPA_GITEA_TOKEN`, `MAPPA_API_TOKEN`.
## Первичный деплой с нуля (cold start)
1. **БД**: на VDS `docker exec postgres psql -U postgres`:
`CREATE ROLE mappa LOGIN PASSWORD '<pw>'; CREATE DATABASE mappa OWNER mappa;`
Пароль → `pass insert -m mappa/full-env`.
2. **Образ**: собрать (см. выше), `docker push`.
3. **Compose**: `admin/host-stacks/vds-kzntsv/mappa.compose.yml` → Portainer create
(standalone string-mode, endpointId=1, env `[{"name":"MAPPA_DB_PASS","value":…}]`).
4. **Проверка миграций**: при старте migrate() сам создаёт таблицы (6 шт: projects/counters/
entities/edges/leases/logs, owner=mappa) — `docker logs mappa` + `\dt` в БД.
5. **Пинг mappa**: письмо в `mappa/.agents/inbox/` (event: mappa-deployed) — импорт и остальное за ними.
## Мониторинг живости (монитор → ntfy)
Остаток #985 (решение 15/18: алерт при падении сервиса) — реализован #1013 (2026-08-23).
- **Скрипт:** `/root/mappa-ntfy-monitor/monitor.py` (python3 stdlib; источник: `admin/scripts/mappa-ntfy-monitor/monitor.py`).
- **Проверка:** GET `https://mappa.vds.kzntsv.site/health` — HTTP 200 + JSON `{"ok":true,"service":"mappa"}` (таймаут 15s).
- **Алерт:** ntfy топик `mappa-alerts` (basic-auth vitya, admin-role → rw все топики), после 3 подряд фейлов (~15 мин), раз за эпизод (dedupe); recovery-сообщение при возврате.
- **Крон:** `/etc/cron.d/mappa-ntfy-monitor``*/5 * * * *` (root, лог `/var/log/mappa-ntfy-monitor.log`).
- **Конфиг:** `/root/.mappa-ntfy-monitor.env` (600; NTFY_* из `pass vds-kzntsv/full-env`). State: `/root/.mappa-ntfy-monitor.state`.
- ⚠️ Монитор НЕ трогает стек 27 — только наблюдение; редеплой/рестарт — отдельной задачей.
## Gotchas (из первых деплоев)
1. **schema.sql в dist** — tsc компилирует только .ts; `migrate()` ищет `.sql` рядом с
`dist/src/lib/`. Без COPY в Dockerfile → контейнер в restart-loop («schema.sql not found»).
2. **docker pull до restart** — Portainer start/stop НЕ тянет новый digest сам по себе;
перезалитый тег без pull на VDS = старый образ (ловил 2× за сессию 2026-08-23;
начиная с v0.23.0 tag-смена идёт через PUT + pullImage:true — pull не нужен отдельно, но безвреден).
3. **Не ad-hoc compose** — все стеки VDS через Portainer (VDS ops rule). `docker rm -f`
вручную снёс контейнер — Portainer recreate поднял, но это не путь.
4. **mem_limit 512m** — канон app-стеков, не убирать (cgroup-лимит vs вся память хоста).
4b. **PUT stack сбрасывает env!** — при обновлении compose через `PUT /api/stacks/:id` Portainer обнуляет env-массив стека (Env: null), если не передать `env` в payload. Симптом: контейнер в restart-loop с `SASL: client password must be a string` (подстановка `${MAPPA_DB_PASS}` падает). Фикс: повторный PUT с `{stackFileContent, env:[{name,value}×3]}` (значения из `pass mappa/full-env`). Поймано на деплое v0.13.1 (2026-08-24).
5. **Внешний DATABASE_URL** — контейнерный `@postgres:5432` валиден только внутри docker-сети;
для импорта с воркстейшена — `postgres.vds.kzntsv.site:5432` (+ TLS-verify-skip, self-signed).
6. **Закрытие деплоя** — всегда пара «таска + письмо mappa» (канон `mappa-deploy-workflow`).
7. **E2 backfill с воркстейшена (task:1274, 2026-08-26)** — скрипт `scripts/backfill-frontmatter-tags.ts`
в репо (tsx). Нужен ВНЕШНИЙ DATABASE_URL (не docker-сетевой):
`postgres://mappa:<MAPPA_DB_PASS>@postgres.vds.kzntsv.site:5432/mappa?sslmode=require` +
`NODE_TLS_REJECT_UNAUTHORIZED=0` (self-signed, gotcha 5). dry-run → `--apply`.
Идемпотентен (ON CONFLICT DO NOTHING); повторный прогон добавляет только новые страницы.
Первый прогон: 2539 тегов / 6555 рёбер; проверку «секреты не стали тегами» — скан имён
на sk-/AKIA/JWT перед apply (имена password/token — теги-темы, не значения).
8. **Одноразовый ре-индекс tsvec при создании конфига токенизатора (v0.28.0, 2026-08-28)**
миграция создаёт PG-конфиг `mappa` (гейт newly_created) и перестраивает tsvec ВСЕХ сущностей
(~13.5k коммитов). Старт после деплоя дольше обычного; health может отвечать 404/не стартовать
до ~1 мин — это ожидаемо, не паника. Проверять `docker ps` (health: starting) + повторять /health.
9. **attachment upload: boundary регистрозависим, content-type lowercases (v0.29.0, 2026-08-28)**
сервер приводит content-type к lowercase (`ct.toLowerCase()`), а boundary в multipart —
регистрозависим (RFC 2046). Клиент с boundary, содержащим ЗАГЛАВНЫЕ (напр. `----LV0290`),
получает 400 «file field required»: delimiter не матчится. Рабочий вариант — lowercase-boundary
(`----lv0290`). Плюс: curl на Windows (mingw) шлёт multipart с LF-разделителями — серверный
парсер ждёт CRLF; надёжно — собрать multipart с CRLF вручную (`--data-binary`). Кандидат на
фикс в mappa: не lowercasing'ить boundary / матчить регистронезависимо.
10. **Контролируемый апдейт core-скилов (v0.30.0, 2026-08-28)** — PATCH `/skills` kind=core больше НЕ 403: требуется `source=admin` (event-аудит skill.core_update, task:1466). deploy-catalog.mjs шлёт source=admin → core-обновления применяются программно (пробел закрыт). DELETE core — по-прежнему 403.
**Не читать. Не править.** Канон — mappa wiki-сущность [[wiki:1254]] (concepts/mappa-vds-deploy-runbook). Скил: `admin-runbooks` / `mappa-knowledge`.