feat(db): перевести хранилище с SQLite на PostgreSQL
Docker images / prepare-release (push) Successful in 12s
Docker images / backend-test (push) Successful in 4m9s
Docker images / frontend-image (push) Successful in 4m28s
Docker images / updater-image (push) Successful in 58s
Docker images / backend-image (push) Successful in 2m49s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 11s
Docker images / prepare-release (push) Successful in 12s
Docker images / backend-test (push) Successful in 4m9s
Docker images / frontend-image (push) Successful in 4m28s
Docker images / updater-image (push) Successful in 58s
Docker images / backend-image (push) Successful in 2m49s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 11s
При старте backend накатывает схему PostgreSQL 18 и, если база пустая, один раз импортирует mikrotik.db с тома. Повторный старт не копирует данные. Бэкап в UI идёт через pg_dump. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
| Компонент | Путь | Стек | Порт (runtime) | Docker-образ |
|
||||
|-----------|------|------|----------------|--------------|
|
||||
| Frontend | корень (`app/`, `components/`, `lib/`, …) | Next.js 16.2.4 (App Router), React 19 | 3000 | `…-frontend` |
|
||||
| Backend | `backend/` | Fastify 5, TypeScript ESM, SQLite, Drizzle | 8000 | `…-backend` |
|
||||
| Backend | `backend/` | Fastify 5, TypeScript ESM, PostgreSQL 18, Drizzle | 8000 | `…-backend` |
|
||||
| Контракты | `packages/contracts/` | Zod 4, `@mmapp/contracts` | — | встраиваются в frontend/backend |
|
||||
| Updater | `deploy/updater/` | bash, `docker:27-cli`, curl, jq | — | `…-updater` |
|
||||
| Деплой | `deploy/docker-compose.yml` | Docker Compose | — | — |
|
||||
@@ -43,11 +43,11 @@ flowchart TB
|
||||
Browser["Браузер"]
|
||||
NextDev["Next.js :3000"]
|
||||
FastifyDev["Fastify :8000"]
|
||||
SqliteDev["SQLite файл"]
|
||||
PostgresDev["PostgreSQL 18"]
|
||||
Contracts["@mmapp/contracts"]
|
||||
Browser --> NextDev
|
||||
Browser -->|"fetch /api, CORS"| FastifyDev
|
||||
FastifyDev --> SqliteDev
|
||||
FastifyDev --> PostgresDev
|
||||
Contracts --> NextDev
|
||||
Contracts --> FastifyDev
|
||||
end
|
||||
@@ -60,7 +60,8 @@ flowchart TB
|
||||
subgraph prod [Прод-сервер]
|
||||
FE["mmapp-frontend :3000"]
|
||||
BE["mmapp-backend :8000"]
|
||||
DBVol["volume backend-data /app/data"]
|
||||
PG["mmapp-postgres"]
|
||||
DBVol["volume sqlite ETL /app/data"]
|
||||
UPD["mmapp-updater"]
|
||||
Sock["/var/run/docker.sock"]
|
||||
Reg --> FE
|
||||
@@ -69,6 +70,7 @@ flowchart TB
|
||||
UPD --> Sock
|
||||
UPD --> FE
|
||||
UPD --> BE
|
||||
BE --> PG
|
||||
BE --> DBVol
|
||||
BrowserProd["Клиент"] --> FE
|
||||
BrowserProd -->|"live + backendUrl"| BE
|
||||
@@ -117,7 +119,7 @@ sequenceDiagram
|
||||
```
|
||||
|
||||
- Триггер workflow: `push` в ветку `main`, ручной `workflow_dispatch`.
|
||||
- Параллельные jobs: `backend-image`, `frontend-image`, `updater-image`; опционально `notify-webhook` после backend и frontend, если задан секрет `DEPLOY_WEBHOOK_URL`.
|
||||
- Параллельные jobs: `backend-test` (PostgreSQL 18), `backend-image`, `frontend-image`, `updater-image`; опционально `notify-webhook` после backend и frontend, если задан секрет `DEPLOY_WEBHOOK_URL`.
|
||||
- Теги на каждый успешный push: **`:latest`** и **`:<commit-sha>`**; платформа **linux/amd64**.
|
||||
- На сервере образы подтягиваются вручную (`docker compose pull`) и/или через **updater** (сравнение digest у тега из `targets.json`). Webhook CI **не заменяет** updater.
|
||||
|
||||
@@ -125,7 +127,8 @@ sequenceDiagram
|
||||
|
||||
| Зависимость | Реализация |
|
||||
|-------------|------------|
|
||||
| SQLite | Не отдельный контейнер. Файл `mikrotik.db` в томе `backend-data` → `/app/data` (`DATABASE_PATH=/app/data/mikrotik.db` в образе backend). |
|
||||
| PostgreSQL | Контейнер `mmapp-postgres` (`postgres:18-alpine`). `DATABASE_URL=postgres://mmapp:…@postgres:5432/mmapp`. |
|
||||
| SQLite (ETL) | Файл `mikrotik.db` на томе `/app/data` (`DATABASE_PATH=/app/data/mikrotik.db`). При первом старте, если PG пустой, backend сам импортирует данные и ставит маркер. Повторный старт не копирует заново. |
|
||||
| Docker socket | Только у контейнера updater: `/var/run/docker.sock` — доступ к Docker API хоста (управление контейнерами, pull). |
|
||||
|
||||
## Локальная разработка
|
||||
@@ -134,7 +137,8 @@ sequenceDiagram
|
||||
|
||||
- **Node.js 22** (как в `Dockerfile.frontend` и `backend/Dockerfile`).
|
||||
- **npm** с workspaces; установка из корня: `npm ci` или `npm install`.
|
||||
- Для нативной сборки `better-sqlite3` на Linux может понадобиться toolchain (`python3`, `make`, `g++`); в Docker-образе backend они уже ставятся.
|
||||
- Для нативной сборки `better-sqlite3` на Linux может понадобиться toolchain (`python3`, `make`, `g++`); в Docker-образе backend они уже ставятся (нужен только для одноразового boot-ETL из `mikrotik.db`).
|
||||
- Локально нужен PostgreSQL 18 (`deploy/docker-compose.postgres.yml` или `DATABASE_URL`).
|
||||
- Backend Docker-образ ставит только workspaces `backend` + `contracts` (без корневых Next/React deps); в production логи — JSON без `pino-pretty`.
|
||||
|
||||
### Запуск
|
||||
@@ -161,7 +165,8 @@ Backend — скопировать `backend/.env.example` в `backend/.env`:
|
||||
|
||||
| Переменная | По умолчанию | Назначение |
|
||||
|------------|--------------|------------|
|
||||
| `DATABASE_PATH` | `./mikrotik.db` | путь к файлу SQLite |
|
||||
| `DATABASE_URL` | `postgres://mmapp:[email protected]:5432/mmapp` | подключение к PostgreSQL |
|
||||
| `DATABASE_PATH` | `./mikrotik.db` | SQLite только для boot-ETL, если PG пустой |
|
||||
| `PORT` | `8000` | порт Fastify |
|
||||
| `CORS_ORIGIN` | `http://localhost:3000` | origin фронтенда для CORS |
|
||||
|
||||
@@ -179,12 +184,10 @@ Frontend — в репозитории нет корневого `.env.example`.
|
||||
npm run build -w @mmapp/contracts
|
||||
```
|
||||
|
||||
После изменения схем Drizzle:
|
||||
Схема PostgreSQL накатывается при старте backend из [`backend/drizzle/0000_postgresql.sql`](backend/drizzle/0000_postgresql.sql). Опциональный ETL:
|
||||
|
||||
```bash
|
||||
npm --prefix backend run db:generate
|
||||
npm --prefix backend run db:migrate
|
||||
npm --prefix backend run db:studio
|
||||
npm --prefix backend run db:migrate-from-sqlite
|
||||
```
|
||||
|
||||
## CI/CD (Gitea Actions)
|
||||
@@ -281,7 +284,8 @@ SSO auth-portal: [`docs/integrate-auth-portal.md`](docs/integrate-auth-portal.md
|
||||
|
||||
| Сервис | `container_name` | Образ (пример) | Порты host:container | Тома | `restart` |
|
||||
|--------|------------------|----------------|----------------------|------|-----------|
|
||||
| backend | `mmapp-backend` | `git.shx.one/denozord/mikrotikmanager-backend:latest` | `8000:8000` | `backend-data` → `/app/data` | `unless-stopped` |
|
||||
| postgres | `mmapp-postgres` | `postgres:18-alpine` | нет (внутренняя сеть) | `mmapp-pgdata` | `unless-stopped` |
|
||||
| backend | `mmapp-backend` | `git.shx.one/denozord/mikrotikmanager-backend:latest` | `8000:8000` | sqlite ETL → `/app/data` | `unless-stopped` |
|
||||
| frontend | `mmapp-frontend` | `git.shx.one/denozord/mikrotikmanager-frontend:latest` | `3000:3000` | — | `unless-stopped` |
|
||||
| updater | `mmapp-updater` | `git.shx.one/denozord/mikrotikmanager-updater:latest` | не публикуются | docker.sock, `updater-state` → `/state`, `targets.json` → `/etc/updater/targets.json:ro` | `unless-stopped` |
|
||||
|
||||
@@ -297,11 +301,14 @@ SSO auth-portal: [`docs/integrate-auth-portal.md`](docs/integrate-auth-portal.md
|
||||
|
||||
### Переменные окружения (прод)
|
||||
|
||||
**Backend** (в образе заданы `NODE_ENV=production`, `PORT=8000`, `DATABASE_PATH=/app/data/mikrotik.db`; в compose обычно переопределяют только CORS):
|
||||
**Backend** (в образе заданы `NODE_ENV=production`, `PORT=8000`; в compose задают `DATABASE_URL`, `DATABASE_PATH` для ETL и CORS):
|
||||
|
||||
| Переменная | Источник в compose | Назначение |
|
||||
|------------|-------------------|------------|
|
||||
| `CORS_ORIGIN` | `${CORS_ORIGIN:-http://localhost:3000}` | origin UI, с которого браузер вызывает API |
|
||||
| `DATABASE_URL` | `postgres://mmapp:${POSTGRES_PASSWORD}@postgres:5432/mmapp` | PostgreSQL |
|
||||
| `DATABASE_PATH` | `/app/data/mikrotik.db` | SQLite на томе для одноразового ETL |
|
||||
| `POSTGRES_PASSWORD` | обязателен | пароль пользователя `mmapp` |
|
||||
|
||||
**Frontend** (в образе: `PORT=3000`, `HOSTNAME=0.0.0.0`). URL API в образ **не** зашит — задаётся в браузере (режим live + URL бэкенда) вместе с `CORS_ORIGIN` на backend.
|
||||
|
||||
@@ -312,7 +319,7 @@ SSO auth-portal: [`docs/integrate-auth-portal.md`](docs/integrate-auth-portal.md
|
||||
| `TARGETS_FILE` | `/etc/updater/targets.json` | список целей |
|
||||
| `STATE_FILE` | `/state/updater-state.json` | digest и ошибки |
|
||||
| `POLL_INTERVAL_SECONDS` | `300` | пауза между циклами опроса |
|
||||
| `HEALTH_TIMEOUT_SECONDS` | `120` | ожидание HTTP health |
|
||||
| `HEALTH_TIMEOUT_SECONDS` | `600` | ожидание HTTP health (первый boot с ETL может занять минуты) |
|
||||
| `STOP_TIMEOUT_SECONDS` | `30` | `docker stop -t` |
|
||||
| `REGISTRY` | `git.shx.one` | registry для `docker login` |
|
||||
| `REGISTRY_USERNAME` | пусто | логин (если заданы оба с паролем) |
|
||||
@@ -516,18 +523,20 @@ docker compose up -d --force-recreate
|
||||
### Откат
|
||||
|
||||
- **Автоматически:** updater при failed health после обновления — контейнер на `previous_digest`.
|
||||
- **Вручную:** остановить контейнер, запустить образ с нужным тегом или digest из registry, сохранив те же volume и labels. Откат **схемы SQLite** updater не выполняет — нужен отдельный backup тома `backend-data` / файла `mikrotik.db`.
|
||||
- **Вручную:** остановить контейнер, запустить образ с нужным тегом или digest из registry, сохранив те же volume и labels. Откат **схемы PostgreSQL** updater не выполняет — нужен `pg_dump` / restore или том `mmapp-pgdata`.
|
||||
|
||||
### Резервное копирование SQLite
|
||||
### Резервное копирование PostgreSQL
|
||||
|
||||
Том `backend-data` (или `mmapp-backend-data` при явном `docker volume create`). Пример остановки backend для консистентной копии:
|
||||
Том `mmapp-pgdata` и (до cleanup) sqlite `mikrotik.db` на `/app/data`. Из UI: Настройки → «Скачать бэкап» (`pg_dump -Fc`). С хоста:
|
||||
|
||||
```bash
|
||||
docker stop mmapp-backend
|
||||
docker run --rm -v backend-data:/data -v $(pwd):/backup alpine tar czf /backup/mikrotik-db-backup.tar.gz -C /data .
|
||||
docker start mmapp-backend
|
||||
docker exec mmapp-postgres pg_dump -Fc -U mmapp mmapp > mikrotik-manager.dump
|
||||
```
|
||||
|
||||
Восстановление: UI «Восстановить из файла» или `pg_restore --clean --if-exists -d …`.
|
||||
|
||||
Том sqlite после успешного ETL и smoke **не удаляйте сразу** — оставьте как fallback, затем уберите `mikrotik.db`, когда маркер `data_migration.sqlite_imported_at` подтверждён.
|
||||
|
||||
### Отладка
|
||||
|
||||
| Действие | Команда |
|
||||
|
||||
Reference in New Issue
Block a user