Files
vector/SETUP_GUIDE.md

570 lines
31 KiB
Markdown
Raw Permalink 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.
# ВЕКТОР МЧС — Гайд по развёртыванию на серверах МЧС
**Пошаговое руководство для IT-специалистов МЧС Республики Беларусь по развёртыванию системы определения приоритетных направлений поиска (ВЕКТОР) на серверах организации.**
> **Версия документа:** 1.1 от 25 сентября 2026 г.
> **Репозиторий:** [`Sadmin/vector`](http://192.168.0.106:3000/Sadmin/vector)
---
## Оглавление
1. [Требования к серверу](#1-требования-к-серверу)
2. [Архитектура развёртывания](#2-архитектура-развёртывания)
3. [Подготовка сервера](#3-подготовка-сервера)
4. [Установка Docker и Docker Compose](#4-установка-docker-и-docker-compose)
5. [Получение кода проекта](#5-получение-кода-проекта)
6. [Конфигурация окружения (.env)](#6-конфигурация-окружения-env)
7. [Запуск сервисов](#7-запуск-сервисов)
8. [Инициализация базы данных (миграции, сиды)](#8-инициализация-базы-данных-миграции-сиды)
9. [Загрузка OSM-геослоя (PostGIS)](#9-загрузка-osm-геослоя-postgis)
10. [Проверка работоспособности](#10-проверка-работоспособности)
11. [Создание пользователей и справочника подразделений](#11-создание-пользователей-и-справочника-подразделений)
12. [Настройка интеграции с КОНТУР](#12-настройка-интеграции-с-контуром)
13. [Обратный прокси и HTTPS](#13-обратный-прокси-и-https)
14. [Обновление проекта](#14-обновление-проекта)
15. [Резервное копирование (RPO/RTO)](#15-резервное-копирование)
16. [Мониторинг и логи](#16-мониторинг-и-логи)
17. [Диагностика проблем](#17-диагностика-проблем)
17а. [Чек-лист передачи проекта](#17а-чек-лист-передачи-проекта)
18. [Чек-лист развёртывания](#18-чек-лист-развёртывания)
---
## 1. Требования к серверу
### Минимальные требования
| Параметр | Значение |
|----------|----------|
| ОС | Debian 12/13, Ubuntu 22.04+ (любая с Docker) |
| CPU | 2 vCPU |
| RAM | 4 ГБ (2 ГБ свободно для импорта OSM — см. раздел 9) |
| Диск | 20 ГБ + 1 ГБ под дамп OSM |
| Сеть | Доступ браузеров к серверу по HTTP(S); для первичной загрузки дампа OSM — доступ в интернет (одноразово) |
### Требования безопасности
- Сервер в закрытом контуре организации; **данные ПДн не должны покидать контур** — не настраивать проксирование на внешние сервисы
- Смена всех паролей по умолчанию (см. раздел 6)
- Для полностью offline-сценария без интернета: скачать дамп OSM заранее (раздел 9) и учесть, что фронт тянет OSM-тайлы с CDN — для абсолютного offline нужен локальный tile-сервер (в планах)
---
## 2. Архитектура развёртывания
Docker Compose поднимает 4 сервиса:
| Контейнер | Образ | Порт | Назначение |
|-----------|-------|------|-----------|
| `vector-postgres` | postgres:16 (+PostGIS 3.6 в контейнере) | 5432 | БД `vector_mchs`, данные + OSM |
| `vector-backend` | сборка backend/Dockerfile | 8000 | FastAPI (uvicorn --reload) |
| `vector-frontend` | сборка frontend/Dockerfile | 3000 | React SPA (CRA dev-сервер) |
| `vector-adminer` | adminer | 8080 | Веб-администрирование БД |
Код backend/services и frontend смонтирован volume'ами — правки подхватываются без пересборки (dev-режим).
---
## 3. Подготовка сервера
```bash
# 1. Обновление системы
apt update && apt upgrade -y
# 2. Базовые утилиты
apt install -y curl git ca-certificates gnupg
# 3. (опционально) NTP-синхронизация — важна для корректных меток времени аудита
apt install -y systemd-timesyncd
timedatectl set-ntp true
```
---
## 4. Установка Docker и Docker Compose
```bash
# Официальный скрипт (или репозиторий docker-ce — по политике организации)
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
docker --version # 24+
docker compose version # v2
```
---
## 5. Получение кода проекта
```bash
mkdir -p /root/vector && cd /root/vector
git clone http://<gitea-host>:3000/Sadmin/vector.git .
```
Ключевые ветки: `master` — рабочая.
---
## 6. Конфигурация окружения (.env)
```bash
cp .env.example .env
```
Обязательные переменные:
```ini
# Подключение к БД (внутри compose — хост postgres)
DATABASE_URL=postgresql://postgres:<СМЕНИТЬ_ПАРОЛЬ>@postgres:5432/vector_mchs
# Секрет JWT — сгенерировать: openssl rand -hex 32
JWT_SECRET=<СЛУЧАЙНАЯ_СТРОКА>
# CORS — адрес(а) фронтенда
CORS_ORIGINS=http://<адрес-сервера>:3000
# Интеграция с КОНТУР (раздел 12; можно отложить)
CONTOUR_API_URL=http://<kontur-host>:8000
CONTOUR_TOKEN=<токен из КОНТУРа>
```
> **НЕ добавлять** никаких ключей внешних ИИ/API — по политике закрытого контура их в проекте нет и быть не должно (тест-страж `TestNoLLM`).
Пароль БД должен совпадать с `POSTGRES_PASSWORD` в `docker-compose.yml` (сервис postgres).
---
## 7. Запуск сервисов
```bash
cd /root/vector
docker compose up -d
docker compose ps # все 4 контейнера Up/healthy
```
Первый старт backend создаёт таблицы через `init_db` (create_all). Порядок применения Alembic-миграций — раздел 8.
---
## 8. Инициализация базы данных (миграции, сиды)
### 8.1 Миграции Alembic
```bash
docker exec vector-backend sh -c 'cd /app/backend && alembic upgrade head'
docker exec vector-backend sh -c 'cd /app/backend && alembic current' # 010_b23_policy
```
Миграции идемпотентны на чистой БД. **Если** БД уже была инициализирована через create_all и `alembic upgrade` падает `DuplicateTable` — применить стемп-скрипт:
```bash
docker exec -i vector-postgres psql -U postgres -d vector_mchs < scripts/prod-stamp-backfill.sql
```
### 8.2 Сид первого администратора
```bash
docker exec vector-backend sh -c 'cd /app && python backend/seed_users.py'
```
Создаётся стартовый администратор (логин `admin`, пароль по умолчанию — сменить при первом входе через форму). Скрипт пропускается, если пользователи уже есть.
### 8.3 Сид подразделений (142 юнита)
```bash
docker exec vector-backend sh -c 'cd /app && python backend/seed_units.py'
```
Создаёт РЦУ РЧС → 6 ОУМЧС → 135 Г(Р)ОЧС/ПАСО (справочник идемпотентный: существующие названия не дублируются). Без него админка пользователей бесполезна (пустые селекты подразделений).
---
## 9. Загрузка OSM-геослоя (PostGIS)
Бэкенд рассчитывает рельеф секторов из локальной OSM. На чистой БД геосервис автоматически fallback'ится на публичный Overpass (медленно, требует интернет) — **для продуктива импортируйте дамп**.
### 9.1 Установка PostGIS в контейнер БД
```bash
docker exec -u root vector-postgres apt update
docker exec -u root vector-postgres apt install -y postgresql-16-postgis-3 osm2pgsql
docker exec vector-postgres psql -U postgres -d vector_mchs -c 'CREATE EXTENSION IF NOT EXISTS postgis;'
```
> Установка пакетом в живой контенер переживает перезапуск контейнера (данные в volume), но слетает при ПЕРЕСОЗДАНИИ контейнера — тогда повторить команды.
### 9.2 Импорт дампа OSM
```bash
# Скачивание (одноразово, ~333 МБ; можно перенести на сервер любым носителем)
wget https://download.geofabrik.de/europe/belarus-latest.osm.pbf -O /tmp/belarus-latest.osm.pbf
# Импорт (~4,5 мин, RAM 2 ГБ, node cache 800)
sh scripts/b16-import.sh
```
Скрипт сам: удаляет старые planet_osm_* таблицы, копирует дамп в контейнер, запускает osm2pgsql (--slim, SRID по умолчанию), выводит список таблиц.
### 9.3 Проверка
```bash
docker exec vector-postgres psql -U postgres -d vector_mchs -t -c "
SELECT 'roads', count(*) FROM planet_osm_roads
UNION ALL SELECT 'forests', count(*) FROM planet_osm_polygon WHERE landuse='forest';"
```
Ожидается ~1 млн дорог, ~190 тыс. лесов.
---
## 10. Проверка работоспособности
```bash
# 1. Health бэкенда
curl -s http://localhost:8000/api/v1/health
# 2. Логин (ТОЛЬКО form-urlencoded!)
TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'username=admin&password=<пароль>' | python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
# 3. Профиль (9 прав у admin)
curl -s http://localhost:8000/api/v1/auth/me -H "Authorization: Bearer $TOKEN"
# 4. Фронт
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/ # 200
# 5. Полный расчёт (e2e-скрипты в scripts/e2e-*.sh — см. примеры вызовов)
```
В браузере: `http://<адрес>:3000` → дашборд «Активные поиски».
---
## 11. Создание пользователей и справочника подразделений
Пользователи создаются через UI: **Дашборд → Пользователи → «+ Новый пользователь»** (доступ: роль admin РЦУ РЧС или coordinator ОУМЧС в рамках своего поддерева).
- При создании задаётся временный пароль — пользователь обязан сменить его при первом входе
- Роли: РЦУ РЧС / ОУМЧС / Г(Р)ОЧС / Наблюдатель (описание прав — README.md, раздел RBAC)
- Инлайн-операции в таблице: смена роли/подразделения, отключение/включение, сброс пароля (отзывает все сессии)
- Все изменения пишутся в журнал аудита (вкладка «Аудит», ретенция 90 дней)
Иерархию подразделений менять: правкой `backend/seed_units.py` (идемпотентно, kind ∈ rcu/oblast/gor_rayon — ограничение БД `ck_mchs_units_kind`).
---
## 12. Настройка интеграции с КОНТУР
КОНТУР — система полевой координации (ATAK + Meshtastic) на отдельном сервере. ВЕКТОР отправляет в неё приоритетные зоны.
1. В `.env`: `CONTOUR_API_URL` (адрес API КОНТУРа) и `CONTOUR_TOKEN` (выдаётся на стороне КОНТУРа)
2. `docker compose up -d backend` — **пересоздать** контейнер (restart не перечитывает env!)
3. Проверка: операция → «Отправить зоны в КОНТУР» на странице анализа; при недоступности КОНТУРа бэкенд вернёт понятную ошибку 502 и запишет `contour_send_failed` в аудит
Входящие данные (треки групп, проверенные квадраты) — этап B17, в разработке.
---
## 13. Обратный прокси и HTTPS
Для доступа пользователей по HTTPS поставьте nginx/traefik перед фронтендом и API:
```nginx
server {
listen 443 ssl;
server_name vector.mchs.example;
ssl_certificate /etc/ssl/certs/vector.crt;
ssl_certificate_key /etc/ssl/private/vector.key;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
}
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
client_max_body_size 10m;
}
}
```
В `CORS_ORIGINS` укажите итоговый https-адрес. В закрытом контуре допустима работа по HTTP без прокси (порты 3000/8000 напрямую).
---
## 14. Обновление проекта
```bash
cd /root/vector
git pull origin master
# Если менялись миграции:
docker exec vector-backend sh -c 'cd /app/backend && alembic upgrade head'
# Если менялись env-переменные (.env / compose environment) — ПЕРЕСОЗДАТЬ контейнер:
docker compose up -d backend # restart НЕ перечитывает env!
# Обычное обновление кода:
docker restart vector-backend vector-frontend
```
Схема обновления, применяемая при разработке: правки коммитятся в Gitea → на сервере `git pull` → `docker restart`. После рестарта frontend пользователи перелогиниваются.
---
## 15. Резервное копирование
```bash
# Полный дамп БД (данные + OSM-таблицы)
docker exec vector-postgres pg_dump -U postgres -d vector_mchs -F c \
-f /tmp/vector_mchs_$(date +%F).dump
docker cp vector-postgres:/tmp/vector_mchs_$(date +%F).dump /root/backups/
# Только прикладные данные (без тяжёлых planet_osm_* — их восстанавливают импортом дампа):
docker exec vector-postgres pg_dump -U postgres -d vector_mchs \
--exclude-table='planet_osm_*' -F c -f /tmp/vector_app_$(date +%F).dump
```
Рекомендация: ежедневный cron прикладного дампа (он небольшой) + еженедельный полный. Хранить копии вне сервера.
### Cron (рекомендуемый на сервере)
```cron
# 03:10 — ретенция журналов (удаление audit_events/auth_events старше audit_retention_days)
10 3 * * * docker exec vector-backend python -m backend.purge_logs >> /var/log/vector-purge.log 2>&1
# 03:00 — прикладной дамп (planet_osm_* исключены; OSM восстанавливается b16-import.sh)
0 3 * * * /root/vector/scripts/backup-app.sh >> /var/log/vector-backup.log 2>&1
```
`backup-app.sh` (в репо, `scripts/`) делает: дамп прикладных таблиц → выгрузка в
`/root/backups/` → архив `.env` + `docker-compose.yml` (chmod 600) → проверка
оглавления через pg_restore → чистка старше 14 дней.
Проверка разового прогона: `docker exec vector-backend python -m backend.purge_logs`
→ `purge: {'audit_events': N, 'auth_events': N}` (0 — норма).
**Грабля pg_wrapper PG18** (реальный прогон на CT108, postgres:16): `pg_dump`/`pg_restore`
без пути — это клиент PG18. `pg_dump 18` создаёт архив версии 1.16, который `pg_restore 16`
не читает (`unsupported version`), и пишет `SET transaction_timeout`, отсутствующий в
PG16-сервере. Оба скрипта используют только `/usr/lib/postgresql/16/bin/pg_*`.
**Docker volume — не backup.** Если сервер умер вместе с диском, volume умер вместе с ним. Что подлежит резервному копированию:
```text
БД (прикладные таблицы + OSM)
+ .env (JWT_SECRET, пароль БД, CONTOUR_TOKEN)
+ конфигурация compose
```
`backup-app.sh` покрывает БД и конфигурацию. **Копии должны уходить с сервера** —
иначе гибель LXC/диска убивает и бэкапы. Варианты (по возрастанию надёжности):
1. rsync на второй Proxmox-узел по SSH (минимум для пилота):
`rsync -az -e ssh /root/backups/ <второй-узел>:/root/backups/vector/` — добавить в backup-app.sh
2. Syncthing/NAS (в инфраструктуре Виктора NAS уже есть)
3. Для боевого контура МЧС — сетевой шарой организации
**Backup ≠ восстановление.** Копия проверяется периодическим restore на отдельном стенде:
файл читается → БД восстанавливается → backend подключается → расчёт выполняется. Непроверенный restore — это не стратегия, а файл с именем «backup».
### Восстановление
```bash
/root/vector/scripts/restore-app.sh /root/backups/vector_app_<дата>.dump
```
Скрипт: `--clean --if-exists` (пересоздание объектов из дампа) + TOC без
EXTENSION postgis (иначе дропает postgis и падает на зависимостях planet_osm_*).
OSM-таблицы не трогает (их в дампе нет). Проверено на CT108: restore → counts
(cases/users/audit/password_history) совпали → login ok → analyze 200.
### RPO и RTO
| Величина | Вопрос | Рекомендация для пилота |
|----------|--------|------------------------|
| **RPO** | Сколько данных допустимо потерять | Ежедневный дамп → RPO ≤ 24 ч. Потеря последних суток карточек; активная операция восстановляется из бумажного журнала смены |
| **RTO** | Сколько времени допустимо восстанавливать | Развернуть compose + restore дампа на новом LXC ≈ 30–60 мин (без импорта OSM — прикладной дамп + повторный импорт по необходимости; расчёт временно fallback'ится на Overpass) |
---
## 16. Мониторинг и логи
```bash
docker compose logs -f # все сервисы
docker compose logs -f backend # API + прикладные логи vector.*
docker compose logs -f frontend # сборка CRA / ошибки компиляции
docker stats # потребление контейнеров
tail -f /tmp/vector/vector.log # файл-лог внутри контейнера (ротация 5×2 МБ)
```
Прикладные события (B22, `backend/logging_setup.py`) — префикс `vector.<модуль>`:
```bash
# Все матрасчёты с длительностью
docker compose logs backend | grep 'vector.analyze'
# Входы и блокировки
docker compose logs backend | grep -E 'vector\.auth'
# Отправки в КОНТУР (ok/failed/rejected)
docker compose logs backend | grep 'vector.contour'
# Аудит-записи (операционный след; юридический — в БД audit_events)
docker compose logs backend | grep 'vector.audit'
```
Ключевые события: `анализ case=… радиус=… время=…мс` (каждый расчёт),
`login_ok/login_failed/lockout` (безопасность), `contour_send_ok/failed` (связь с КОНТУРом),
`operation_complete op=… исход=…` (завершение поиска), `admin closed case …` (синхронизация закрытия),
`Overpass … недоступен` / `OSM PostGIS недоступен` (геослой ушёл в fallback).
Уровень детализации: `VectorLOGLEVEL=debug` в `.env` + `docker compose up -d backend` (restart не перечитывает env). Файл-лог отключается `VectorLOGFILE=0`.
---
## 17. Диагностика проблем
### Быстрые проверки
```bash
docker compose ps # 4 контейнера Up/healthy
curl -s http://localhost:8000/api/v1/health # {"status":"ok"}
df -h && free -h # диск/память
```
### Типовые симптомы
| Симптом | Причина | Решение |
|---------|---------|---------|
| Логин даёт 422 | JSON вместо form-urlencoded | Логин строго `application/x-www-form-urlencoded` |
| «Session expired or revoked» сразу после логина | Несовпадение sha256-токена | Убедиться, что БД и backend на одной БД; пересоздать сессии |
| Селекты подразделений пусты | Не выполнен сид юнитов | `python backend/seed_units.py` |
| `alembic upgrade` → DuplicateTable | create_all создал таблицы раньше миграций | `scripts/prod-stamp-backfill.sql`, затем upgrade |
| Зоны «всегда вверх-вправо» | Расчёт по case_id терял tnp_lat/lon | Исправлено; при доработках сверять имена полей карточки с входом движка |
| Расчёт падает TypeError float×None | Payload содержит None-поля | SearchInput.to_case_data() выбрасывает None — регресс-тест есть |
| Правка .env не применилась | restart не перечитывает env | `docker compose up -d backend` (пересоздание) |
| Импорт OSM OOM | Мало RAM | Параметры --cache 800 --number-processes 2 уже в скрипте; закрыть лишние сервисы |
| КОНТУР недоступен при отправке зон | Нет связи/токена | curl до CONTOUR_API_URL; проверить CONTOUR_TOKEN; после правки env — up -d |
| Тайлы карты не грузятся | Нет интернета у браузера | Для полного offline — локальный tile-сервер (в планах) |
### Диагностика по цепочке (начинать с места проблемы, не с перезапусков)
**Смена пароля даёт 400 «Пароль должен содержать буквы и цифры» / «совпадает с ранее использованным»** — это B23-политика (`security_settings`), не ошибка: с 25.09.2026 требования сложности и истории исполняются. Пароль, возвращённый ранее, отвергается, пока он в последних `password_history_count` записях.
**Бэкенд не подключается к БД** — цепочка `backend → docker network → postgres:5432 → authentication → vector_mchs`:
```bash
docker compose logs backend | grep -iE 'error|failed|refused'
docker exec vector-backend python -c "from services.osm_local import osm_available; print(osm_available())"
docker exec vector-postgres pg_isready -U postgres
```
Проверить: `DATABASE_URL` (хост `postgres`, пароль совпадает с POSTGRES_PASSWORD), общую compose-сеть, состояние БД.
**500 Internal Server Error** — «500» это результат, а не диагноз. Сначала traceback:
```bash
docker compose logs backend --tail 100
```
Затем определить слой: FastAPI / SQLAlchemy / БД / данные / конфигурация. Прикладной контекст ищется по `vector.*` в логах.
**Расчёт отдал fallback-зоны («Ближняя зона N / Средняя E»)** — геослой не ответил:
```bash
docker compose logs backend | grep -E 'vector\.(rules|geo)'
```
`Geo/scoring service error` → смотреть причину выше по стеку (PostGIS? Overpass breaker?). При работе на локальном OSM — проверить таблицы:
```bash
docker exec vector-postgres psql -U postgres -d vector_mchs -t -c \
"SELECT count(*) FROM planet_osm_line;"
```
**Пользователь не входит** — цепочка `login → bcrypt → lockout → session → JWT`:
```bash
docker compose logs backend | grep 'vector.auth'
docker exec vector-postgres psql -U postgres -d vector_mchs -c \
"SELECT username, is_active, status, failed_login_count, locked_until FROM users WHERE username='<логин>';"
```
401 после смены JWT_SECRET — все сессии инвалидны, ожидаемо: перелогин. 403 при валидном входе — проверить роль/юнит пользователя (скоуп).
---
## 17а. Чек-лист передачи проекта (по образцу РВС)
### Код
- [ ] код в Git (Gitea, `Sadmin/vector`), история коммитов осмысленная
- [ ] production-конфигурация без паролей в репо (`.env.example` вместо `.env`)
- [ ] README актуален (архитектура, API, БД, логирование §13а)
- [ ] SETUP_GUIDE актуален (развертывание, диагностика)
- [ ] тесты зелёные: `pytest` (239 passed)
### База
- [ ] известен production-сервер PostgreSQL и БД `vector_mchs`
- [ ] схема актуальна: `alembic current` = `009_e3_operations`
- [ ] понятны таблицы OSM (planet_osm_*) и способ их восстановления (дамп + b16-import.sh)
- [ ] сиды (users, units) воспроизводимы
### Docker
- [ ] понятны compose-файлы, volumes, сети
- [ ] понятно, что можно пересоздавать (backend/frontend/adminer), а что содержит данные (postgres volume)
- [ ] известен порядок обновления (§14) и отката
### Security
- [ ] `JWT_SECRET` не дефолтный; пароль БД сменён
- [ ] пароль admin сменён; `must_change_password` для созданных пользователей работает
- [ ] `.env` защищён (chmod 600, не в репо)
- [ ] внешних ИИ/API-ключей нет (политика закрытого контура, тест `TestNoLLM`)
- [ ] Adminer и порты БД не торчат наружу без необходимости
### Логи и аудит
- [ ] прикладные логи `vector.*` доступны (`docker compose logs backend`, файл в контейнере)
- [ ] аудит 90 дней читается (вкладка «Аудит», право `view_audit`)
- [ ] ретенция **исполняется**: cron purge-логов установлен, `python -m backend.purge_logs` даёт счётчики
### Backup
- [ ] cron прикладного дампа настроен (`backup-app.sh`, 03:00), копии вне сервера
- [ ] первый дамп лежит в `/root/backups/` и проверен `pg_restore --list`
- [ ] restore проверен на стенде (`restore-app.sh`), counts совпали
- [ ] копии выгружаются вне сервера (rsync на второй узел / NAS) — см. §15
- [ ] RPO/RTO осознаны и приняты (§15)
---
## 18. Чек-лист развёртывания
- [ ] Docker + Compose установлены
- [ ] Код получен (`/root/vector`), `.env` создан из `.env.example`
- [ ] `JWT_SECRET` заменён на случайный; пароль БД сменён (compose + DATABASE_URL)
- [ ] `docker compose up -d` — 4 контейнера Up
- [ ] `alembic current` = `010_b23_policy`
- [ ] cron бэкапа И cron purge-логов настроены (`crontab -l`), первый дамп лежит в /root/backups
- [ ] `seed_users.py` выполнен, пароль админа сменён при первом входе
- [ ] `seed_units.py` выполнен (142 подразделения в селектах)
- [ ] PostGIS установлен в контейнере БД, extension создан
- [ ] Дамп OSM импортирован (`b16-import.sh`), таблицы заполнены
- [ ] Логин через UI работает, смена пароля обязательна
- [ ] Пробный поиск: карточка → анализ → зоны на карте, радиус соответствует контрольным точкам
- [ ] Создан тестовый пользователь ОУМЧС — скоуп видимости проверен
- [ ] Завершение поиска: исход внесён, кейс в архиве, «прогноз vs факт» отображается
- [ ] (опционально) КОНТУР: env-переменные, пересоздание backend, тестовая отправка зон
- [ ] (опционально) HTTPS/обратный прокси настроен, CORS обновлён
- [ ] Cron резервного копирования настроен, копия выгружена вне сервера
- [ ] Внешние ИИ/API-ключи отсутствуют в `.env` и `docker-compose.yml` (политика закрытого контура)
---
*Сопровождение: репозиторий [`Sadmin/vector`](http://192.168.0.106:3000/Sadmin/vector), план работ — `vector_tasks.md`, описание системы — [README.md](README.md).*