docs+memory: catch up — domain, Sentry, ops bundle, prod wipe Apr26

- README: status table updated to 2026-04-26 (gendsgn.ru pending DNS,
  Sentry live, healthchecks all green, multi-stage images deployed),
  added ops/ and backend/db/init/ to repo layout, "operational gotchas"
  section (restart vs --force-recreate; reset --hard clobbers manual edits).
- memory/memory-gendesign.jsonl: +5 entities (Domain_Setup_Apr26,
  Sentry_Setup_Apr26, OpsBundle_Apr26, Production_Wipe_Apr26, DockerOps_Apr26)
  + 8 relations linking them into the graph. 34 lines → 47.
This commit is contained in:
lekss361 2026-04-26 13:42:55 +03:00
parent 6c4ba1777a
commit a1633bf6db
2 changed files with 60 additions and 21 deletions

View file

@ -10,25 +10,27 @@ Acceptance criteria разбиты на 3 блока (функционально
---
## Текущий статус (25 апреля 2026)
## Текущий статус (26 апреля 2026)
**Инфраструктура: live.** Полный цикл автодеплоя из GitHub Actions работает.
**Инфраструктура: live.** Полный цикл автодеплоя из GitHub Actions работает. 26.04 прод полностью пересоздан (`down -v` + redeploy) — БД свежая, backend в multi-stage образе под non-root пользователем, healthcheck-и работают, ловит ошибки в Sentry.
| Что | Где | Статус |
|---|---|---|
| Production VPS | Beget Cloud, Москва, 2 vCPU / 4 GB / 40 GB NVMe | ✅ работает |
| IP сервера | `46.173.16.127` | — |
| Postgres + PostGIS 3.4 | Docker, в составе compose | ✅ Up (healthy) |
| Redis 7 | Docker | ✅ Up |
| Backend FastAPI | Docker (образ из GHCR) | ✅ Up, `/health` отвечает |
| Frontend Next.js 15 | Docker (образ из GHCR) | ✅ Up |
| Caddy reverse proxy | Docker, ports 80/443 | ✅ Up (TLS ждёт домена) |
| Redis 7 | Docker | ✅ Up (healthy, redis-cli ping) |
| Backend FastAPI | Docker, multi-stage, non-root, 1.44 GB | ✅ Up (healthy, curl /health) |
| Frontend Next.js 15 | Docker, multi-stage, npm ci | ✅ Up |
| Caddy reverse proxy | Docker, ports 80/443 | ✅ Up (TLS ждёт DNS-пропагации) |
| GitHub Actions deploy | `.github/workflows/deploy.yml` | ✅ build → push GHCR → ssh deploy |
| GHCR пакеты | `ghcr.io/lekss361/gendesign-{backend,frontend}` | приватные, сервер авторизуется через PAT |
| UFW firewall | 22 / 80 / 443 | ✅ активен |
| SSH hardening | root login off, password auth off | ✅ |
| Домен | — | ⏸ не подключён |
| Бэкапы pg_dump | — | ⏸ не настроены |
| **Домен** | `gendsgn.ru` (Beget, до 26.04.2027) | ⏳ DNS пропагируется к публичным резолверам |
| **Caddyfile с `gendsgn.ru`** | в `main` (commit 68e95aa) | ⏸ ждёт DNS чтобы накатить на VM (Let's Encrypt rate-limit) |
| **Sentry** | проект `gendesign-backend` (org `qqq`, free tier) | ✅ DSN на VM, тестовое событие прошло |
| Бэкапы pg_dump | `ops/backup.sh` готов | ⏸ S3-бакет не настроен (Selectel/Yandex) |
**Решение по плану разработки: pending.** Антон (второй разработчик) сообщил о доступе к ДДУ-данным. Решается в понедельник 27.04.2026 — выбор между:
- **Plan A (Analytics First)** — Stage 1 переезжает на «3 плитки» аналитики (что строить / по какой цене / за какое время), Generative и Site Finder сдвигаются;
@ -69,14 +71,22 @@ gendesign/
├── memory/
│ └── memory-gendesign.jsonl # хард-линк на C:\mcp\.aim — общий граф памяти
├── docs/ # стратегические документы (бизнес-план, цели MVP)
├── .env.example # шаблон prod env (POSTGRES_*, IMAGE_TAG, NEXT_PUBLIC_API_BASE_URL)
├── ops/ # эксплуатация
│ ├── backup.sh # pg_dump cron + опциональный S3 upload
│ └── restore.sh # восстановление из дампа (RESTORE_CONFIRM=yes)
├── backend/
│ ├── alembic.ini, alembic/ # миграции БД (versions/ пуст до Stage 2a)
│ └── db/init/ # init-скрипты, монтятся в /docker-entrypoint-initdb.d
│ └── 99_drop_unused_extensions.sql # дроп TIGER/topology/fuzzystrmatch на свежем volume
├── .env.example # шаблон prod env (POSTGRES_*, IMAGE_TAG)
├── docker-compose.yml # dev: Postgres + Redis + backend + frontend
├── docker-compose.prod.yml # prod: образы из GHCR + Caddy + Postgres+Redis
├── Caddyfile # авто-TLS Let's Encrypt
├── Makefile # make up / down / test / codegen / migrate
├── Caddyfile # авто-TLS Let's Encrypt для gendsgn.ru
├── Makefile # make up / migrate / migration / test / codegen / pre-commit-install
├── .pre-commit-config.yaml # ruff + prettier + housekeeping hooks
└── .github/workflows/
├── ci.yml # ruff + mypy(core) + pytest + frontend build
└── deploy.yml # build → push в GHCR → ssh deploy на VM
└── deploy.yml # build → push в GHCR → ssh: git fetch + reset --hard + caddy reload
```
---
@ -89,7 +99,7 @@ gendesign/
**БД:** PostgreSQL 16 + PostGIS 3.4, Redis 7
**Frontend:** Next.js 15, React 19, TypeScript 5, Tailwind 4, TanStack Query, Leaflet (Mapbox в Stage 2b), react-leaflet 5
**Экспорт:** WeasyPrint (PDF), ezdxf (DXF), openpyxl (Excel)
**Dev:** uv, ruff, mypy (strict только на core), pytest
**Dev:** uv (lockfile зафиксирован), ruff (lint+format), mypy (strict только на core), pytest, pre-commit (hooks: ruff, prettier, detect-private-key)
---
@ -196,11 +206,16 @@ make codegen # обновить TS-типы из OpenAPI
### Что осталось до «infra-ready»
- [ ] Купить домен .ru (REG.RU / РуЦентр), направить A-запись на `46.173.16.127`
- [ ] Обновить `Caddyfile` с реальным доменом → авто-TLS Let's Encrypt
- [ ] Задать `NEXT_PUBLIC_API_BASE_URL=https://<домен>` в `/opt/gendesign/.env` либо оставить пустым (frontend ходит через Caddy `/api/*` → backend)
- [ ] Настроить ежесуточный `pg_dump` → Object Storage (Yandex free tier 1 GB или Selectel S3)
- [ ] Sentry DSN в `backend/.env`
- [x] ~~Купить домен~~`gendsgn.ru` зарегистрирован в Beget 26.04.2026 (199₽/420₽). DNS на NS Beget, A-записи прописаны
- [ ] Дождаться пропагации DNS к публичным резолверам (30 мин 4 ч), потом запушить **deploy.yml** с `git fetch + reset --hard + caddy reload` → Caddy сам выпустит Let's Encrypt cert для `gendsgn.ru`
- [x] ~~Sentry DSN~~ — проект `gendesign-backend` создан в org `qqq`, DSN в `/opt/gendesign/backend/.env`, тестовое событие прошло
- [ ] Настроить ежесуточный `pg_dump` → Object Storage. Скрипт `ops/backup.sh` готов, осталось создать Selectel/Yandex bucket и положить креды в `/etc/default/gendesign-backup` на VM
- [ ] При следующем wipe `down -v` — TIGER/topology/fuzzystrmatch автоматом дропнутся (init-скрипт `backend/db/init/99_drop_unused_extensions.sql`)
### Sensitive операционные грабли
- **`docker compose restart` НЕ перечитывает `env_file:`** — после правки `backend/.env` всегда `docker compose -f docker-compose.prod.yml up -d --force-recreate --no-deps backend`
- **`deploy.yml` использует `git reset --hard origin/main`** — любые ручные правки в `/opt/gendesign` будут стёрты при следующем деплое. `.env` файлы переживут (в `.gitignore`)
---
@ -296,23 +311,34 @@ git push
⚠️ Эти документы — **снимок состояния на момент написания**. Свежее состояние стратегии — в графе памяти. При расхождении доверяем графу.
Релевантные сущности графа на 25.04.2026:
Релевантные сущности графа на 26.04.2026:
**Стратегия / план:**
- `Project_GenDesign` — общая шапка
- `TechStack_v2.2` — Python + Next.js стек
- `NorthStar_DiscoveryMVP` — 3 пилота + реестр ПО → Раунд А
- `Stages_MVP_v1` — базовая разбивка по этапам
- `Team_Setup` — 2 фуллстека part-time
- `WorkSplit_Generative_Stage1` — кто что делает в Generative
- `PlanA_AnalyticsFirst_Apr25` — pending pivot решение
- `PlanA_AnalyticsFirst_Apr25` — pending pivot решение (ждём 27.04 — Антон по ДДУ)
- `SalesTracking_Strategy_Apr25` — шахматки застройщиков
- `DataSources_Pricing_Apr25` — источники цен и площадей
- `Validation_Findings_Apr25` — валидация бизнес-плана
**Инфраструктура:**
- `Hosting_Pricing_Apr25` — сравнение провайдеров
- `Hosting_Decision_Apr25` — решение по Yandex Cloud (для будущей миграции)
- `Runbook_VPS_Setup_Beget` — пошаговая настройка production VM
- `SSH_Setup_Apr25` — локальная настройка SSH
- `DockerOps_Apr26` — multi-stage Dockerfile, npm ci, healthcheck fix, same-origin API
- `Production_Wipe_Apr26` — clean re-deploy с нуля (event)
- `Domain_Setup_Apr26` — gendsgn.ru, Beget, DNS, ESIA-правило
- `Sentry_Setup_Apr26` — проект, DSN, тестовое событие
- `OpsBundle_Apr26` — Alembic + TIGER cleanup + pre-commit + pg_dump
**Процесс / правила:**
- `Onboarding_Colleague_KG` — подключение нового участника к графу
- `Feedback_MemoryFirst` — правило: память first, docs только по запросу
- `Validation_Findings_Apr25` — валидация бизнес-плана
---

View file

@ -32,3 +32,16 @@
{"type":"relation","from":"PlanA_AnalyticsFirst_Apr25","to":"SalesTracking_Strategy_Apr25","relationType":"depends_on"}
{"type":"relation","from":"PlanA_AnalyticsFirst_Apr25","to":"DataSources_Pricing_Apr25","relationType":"depends_on"}
{"type":"relation","from":"PlanA_AnalyticsFirst_Apr25","to":"Project_GenDesign","relationType":"reshapes"}
{"type": "entity", "name": "Domain_Setup_Apr26", "entityType": "infrastructure", "observations": ["Дата: 26.04.2026", "Домен: gendsgn.ru, регистрация в Beget, срок до 26.04.2027", "Стоимость: 199 ₽ первый год, 420 ₽/год продление. Сравнение из браузера 26.04.2026: Reg.ru 169₽ первый год + ~890₽ продление; Timeweb 200/399; nic.ru 747₽ регистрация (премиум)", "Решение: Beget — потому что (1) хостинг VPS уже там, единая админка и единая платежка для бухгалтерии; (2) продление 420₽ — лучшее на рынке; (3) free DNS включён", "DNS-серверы: ns1.beget.com, ns2.beget.com (плюс ns1.beget.pro, ns2.beget.pro как backup)", "A-записи прописаны 26.04.2026: @ (apex) и www → 46.173.16.127. Beget NS отдают корректно (проверено nslookup gendsgn.ru ns1.beget.com → 46.173.16.127). Публичные резолверы (8.8.8.8, 1.1.1.1) на 26.04 ещё NXDOMAIN — пропагация .ru registry → public DNS, ожидаемо 30 мин - 4 часа", "Caddyfile обновлён под gendsgn.ru: основной site block с auto-TLS Let's Encrypt, www → 301 redirect на apex, :80 fallback для IP-доступа сохранён. Закоммичен 68e95aa, запушен в main. На VM не применён (старый deploy.yml не делает git pull)", "deploy.yml будет дополнен `git fetch + reset --hard origin/main` + `caddy reload --config /etc/caddy/Caddyfile` после `up -d`. Закоммитим и запушим когда DNS пропагируется. Это финальный шаг для HTTPS", "Важное правило отрасли: с 1 сентября 2026 владелец .ru-домена обязан иметь подтверждённую учётку ЕСИА (Госуслуги) — иначе домен не продлят. Для ИП/ООО — через ЭДО/СБИС. Касается всех регистраторов одинаково. К моменту продления (26.04.2027) ООО уже должно быть зарегистрировано — учёт без проблем"]}
{"type": "entity", "name": "Sentry_Setup_Apr26", "entityType": "infrastructure", "observations": ["Дата: 26.04.2026", "Sentry организация: qqq (qqq-5y.sentry.io), free tier (5К ошибок + 10К транзакций / мес)", "Sentry проект: gendesign-backend, платформа FastAPI, регион Frankfurt (ingest.de.sentry.io)", "DSN сохранён в /opt/gendesign/backend/.env как SENTRY_DSN. На локальной машине — не нужен (Sentry init в backend/app/main.py условный — отключён если DSN пустой)", "Конфиг init: sentry_sdk.init(dsn=settings.sentry_dsn, environment=settings.environment, traces_sample_rate=0.1) — 10% запросов трейсятся для performance", "Алерты: «high priority» по умолчанию + email на alexandro361@gmail.com", "Тестовое событие отправлено и получено 26.04.2026 — event_id 67f05bdd59234a608a828fa81f72ded5. Sentry интеграция работает на проде", "Важная операционная заметка: docker compose restart НЕ перечитывает env_file. После правки backend/.env нужно `docker compose -f docker-compose.prod.yml up -d --force-recreate --no-deps backend`. Применимо к любым env-изменениям, не только Sentry", "DSN — не критичный секрет (в frontend SDK всегда зашит публично в JS-бандл). Держим в .env, не в репе. Ротация при необходимости через Sentry → Settings → Client Keys (DSN) → New Key"]}
{"type": "entity", "name": "OpsBundle_Apr26", "entityType": "infrastructure", "observations": ["Дата: 26.04.2026", "Подготовлен пакет инфраструктурных правок (в working tree, не закоммичено): Alembic baseline + TIGER cleanup + pre-commit + pg_dump backups + Makefile targets", "Alembic: создан backend/alembic.ini + backend/alembic/env.py (импорт Base из app.core.db, моделей через app.models.*) + script.py.mako + versions/.gitkeep. Первая реальная миграция — в Stage 2a, когда Parcel-модель будет финализирована. Сейчас versions/ намеренно пуст", "TIGER cleanup: backend/db/init/99_drop_unused_extensions.sql дропает postgis_tiger_geocoder + postgis_topology + fuzzystrmatch на свежем postgres-volume (через bind-mount ./backend/db/init:/docker-entrypoint-initdb.d:ro в обоих compose-файлах). Срабатывает только при первой инициализации пустого volume", "Pre-commit: .pre-commit-config.yaml в корне (trailing-whitespace, end-of-file-fixer, ruff lint+format на backend/, prettier на frontend/, detect-private-key, check-yaml/toml). Pre-commit добавлен в dev-deps backend/pyproject.toml. Локально — `python -m pre_commit install` или `make pre-commit-install`", "pg_dump: ops/backup.sh — cron-скрипт с опциональной выгрузкой в S3. Ретенция 7 дней локально. ops/restore.sh с защитой RESTORE_CONFIRM=yes", "Makefile: добавлены `make migration NAME=...` и `make pre-commit-install`. `make migrate` использует `alembic` напрямую (uv нет в production-образе)", "backend/Dockerfile: COPY alembic.ini + COPY alembic в обе стадии (builder и runner)", "S3 для бэкапов: пока не настроено. Когда настроим Selectel или Yandex Object Storage, env-конфиг — /etc/default/gendesign-backup на VM, cron — `30 3 * * * /usr/local/bin/gendesign-backup`"]}
{"type": "entity", "name": "Production_Wipe_Apr26", "entityType": "event", "observations": ["Дата: 26.04.2026", "Прод полностью пересоздан с нуля для чистого состояния. Не было критичных данных — БД содержала только postgis-defaults", "Команды на VM: `docker compose down -v` → `docker rmi` GHCR-образов и базовых (caddy:2, postgis/postgis:16-3.4, redis:7-alpine) → `git pull --ff-only` (Caddyfile, compose) → `docker system prune -af --volumes`", "Re-deploy через GitHub Actions Deploy → все 5 контейнеров поднялись с image-тегом 08a63f6 (главный коммит на момент wipe)", "Состояние после re-deploy: postgres healthy, redis healthy (новый healthcheck), backend healthy (фикс curl-healthcheck в multi-stage Dockerfile), frontend up, caddy up. PostGIS 3.4 USE_GEOS=1 USE_PROJ=1", "TIGER-мусор вернулся (как ожидалось) при свежей инициализации postgis-image. Будет автоматически удаляться при следующих wipe-ах когда применим OpsBundle_Apr26 (init-скрипт 99_drop_unused_extensions.sql)"]}
{"type": "entity", "name": "DockerOps_Apr26", "entityType": "infrastructure", "observations": ["Дата: 26.04.2026", "Серия Docker-фиксов задеплоена в main: e22aeb9 (multi-stage + npm ci + healthcheck + same-origin API), d0909e6 (ruff fixes), 08a63f6 (mypy fixes), 68e95aa (real domain Caddyfile)", "backend/Dockerfile: переписан на multi-stage (builder с build-essential + libgdal-dev + uv sync; runner только с runtime-либами libpq5, libgdal36, libproj25, libgeos-c1t64, libcairo2, libpango*, curl). Финальный образ 1.44 GB вместо ~2 GB. Запускается под non-root пользователем `app` (uid 1000)", "backend curl healthcheck в docker-compose.prod.yml теперь работает (до этого был баг — curl не был установлен в slim-образе)", "frontend/Dockerfile: переключён на `npm ci --legacy-peer-deps` вместо `npm install`. Запускается под `node` (uid 1000). package-lock.json закоммичен", "docker-compose.prod.yml + dev: убран dead NEXT_PUBLIC_API_BASE_URL (Next.js bakes NEXT_PUBLIC_* на build-time, runtime override no-op). Frontend ходит на same-origin /api/* — в проде через Caddy, в dev через Next.js rewrites (BACKEND_URL env)", "Caddyfile: исправлен баг — handle_path /api/* стрипал префикс /api, FastAPI router на /api/v1/* возвращал 404. Заменено на handle /api/*", "backend/uv.lock сгенерирован (через docker run python:3.12-slim + pip install uv && uv lock). Дальнейшие билды детерминированы: `uv sync --frozen --no-dev`", "Ruff config: ignore=['RUF001','RUF002','RUF003'] — проект русскоязычный, кириллица в комментариях/docstring (МСК-66 etc.) — норма, иначе постоянно ругается"]}
{"type": "relation", "from": "Domain_Setup_Apr26", "to": "Project_GenDesign", "relationType": "infrastructure_for"}
{"type": "relation", "from": "Domain_Setup_Apr26", "to": "Hosting_Decision_Apr25", "relationType": "extends"}
{"type": "relation", "from": "Sentry_Setup_Apr26", "to": "Project_GenDesign", "relationType": "monitors"}
{"type": "relation", "from": "Sentry_Setup_Apr26", "to": "Phase0_DiscoveryMVP", "relationType": "infrastructure_for"}
{"type": "relation", "from": "OpsBundle_Apr26", "to": "Project_GenDesign", "relationType": "infrastructure_for"}
{"type": "relation", "from": "OpsBundle_Apr26", "to": "Stages_MVP_v1", "relationType": "prepares_for"}
{"type": "relation", "from": "Production_Wipe_Apr26", "to": "Runbook_VPS_Setup_Beget", "relationType": "refreshed_state_of"}
{"type": "relation", "from": "DockerOps_Apr26", "to": "Runbook_VPS_Setup_Beget", "relationType": "extends"}