Files
admin/.wiki/concepts/mappa-vds-deploy-runbook.md

110 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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-26
---
# 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-26 = **v0.24.0** (GIT_SHA 1293bb7).
- **Стек 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 — теги-темы, не значения).