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

При старте backend накатывает схему PostgreSQL 18 и, если база пустая, один раз импортирует mikrotik.db с тома. Повторный старт не копирует данные. Бэкап в UI идёт через pg_dump.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-09-08 01:36:48 +07:00
co-authored by Cursor
parent 0e5fb065e2
commit ec43591a99
128 changed files with 4304 additions and 3639 deletions
+51 -3
View File
@@ -117,6 +117,49 @@ services:
max-size: "10m"
max-file: "3"
postgres:
image: ${POSTGRES_IMAGE_TAG:-postgres:18-alpine}
container_name: mmapp-postgres
restart: unless-stopped
shm_size: "256mb"
environment:
POSTGRES_USER: mmapp
POSTGRES_DB: mmapp
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C.UTF-8 --data-checksums"
TZ: UTC
PGTZ: UTC
command:
- postgres
- -c
- timezone=UTC
- -c
- listen_addresses=*
- -c
- max_connections=100
- -c
- shared_buffers=256MB
- -c
- work_mem=16MB
- -c
- maintenance_work_mem=128MB
- -c
- wal_compression=on
volumes:
- mmapp-pgdata:/var/lib/postgresql/data
networks:
- mmapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mmapp -d mmapp"]
interval: 5s
timeout: 5s
retries: 10
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
# --- MikrotikManager -------------------------------------------------------
backend:
image: git.shx.one/denozord/mikrotikmanager-backend:${MM_BACKEND_IMAGE_TAG:-latest}
@@ -124,10 +167,14 @@ services:
container_name: mmapp-backend
restart: unless-stopped
depends_on:
- traefik
traefik:
condition: service_started
postgres:
condition: service_healthy
environment:
NODE_ENV: production
PORT: "8000"
DATABASE_URL: postgres://mmapp:${POSTGRES_PASSWORD}@postgres:5432/mmapp
DATABASE_PATH: /app/data/mikrotik.db
CORS_ORIGIN: ${CORS_ORIGIN:-https://mm.shnt.top}
AUTH_REQUIRED: ${AUTH_REQUIRED:-true}
@@ -160,7 +207,7 @@ services:
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
start_period: 300s
logging:
driver: json-file
options:
@@ -222,7 +269,7 @@ services:
REGISTRY_USERNAME: ${REGISTRY_USERNAME:-}
REGISTRY_PASSWORD: ${REGISTRY_PASSWORD:-}
POLL_INTERVAL_SECONDS: ${POLL_INTERVAL_SECONDS:-300}
HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-120}
HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-600}
STOP_TIMEOUT_SECONDS: ${STOP_TIMEOUT_SECONDS:-30}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
@@ -239,6 +286,7 @@ services:
volumes:
traefik_letsencrypt:
name: cdn_mm_traefik_letsencrypt
mmapp-pgdata:
networks:
edge:
+50
View File
@@ -0,0 +1,50 @@
# Local PostgreSQL 18 for MikrotikManager (dev / ETL dry-run).
# copy env.postgres.example → deploy/.env.postgres
# docker compose -f deploy/docker-compose.postgres.yml --env-file deploy/.env.postgres up -d
services:
postgres:
image: ${POSTGRES_IMAGE_TAG:-postgres:18-alpine}
container_name: mmapp-postgres
restart: unless-stopped
shm_size: "256mb"
environment:
POSTGRES_USER: mmapp
POSTGRES_DB: mmapp
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C.UTF-8 --data-checksums"
TZ: UTC
PGTZ: UTC
command:
- postgres
- -c
- timezone=UTC
- -c
- listen_addresses=*
- -c
- max_connections=100
- -c
- shared_buffers=256MB
- -c
- work_mem=16MB
- -c
- maintenance_work_mem=128MB
- -c
- wal_compression=on
volumes:
- mmapp-pgdata:/var/lib/postgresql/data
ports:
- "127.0.0.1:${POSTGRES_PORT:-5432}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mmapp -d mmapp"]
interval: 5s
timeout: 5s
retries: 10
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
volumes:
mmapp-pgdata:
+52 -2
View File
@@ -19,14 +19,61 @@
# └─ rewrite /api,/health → backend:8000 (network mmapp)
services:
postgres:
image: ${POSTGRES_IMAGE_TAG:-postgres:18-alpine}
container_name: mmapp-postgres
restart: unless-stopped
shm_size: "256mb"
environment:
POSTGRES_USER: mmapp
POSTGRES_DB: mmapp
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C.UTF-8 --data-checksums"
TZ: UTC
PGTZ: UTC
command:
- postgres
- -c
- timezone=UTC
- -c
- listen_addresses=*
- -c
- max_connections=100
- -c
- shared_buffers=256MB
- -c
- work_mem=16MB
- -c
- maintenance_work_mem=128MB
- -c
- wal_compression=on
volumes:
- mmapp-pgdata:/var/lib/postgresql/data
networks:
- mmapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mmapp -d mmapp"]
interval: 5s
timeout: 5s
retries: 10
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
backend:
image: git.shx.one/denozord/mikrotikmanager-backend:${MM_BACKEND_IMAGE_TAG:-latest}
pull_policy: always
container_name: mmapp-backend
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
NODE_ENV: production
PORT: "8000"
DATABASE_URL: postgres://mmapp:${POSTGRES_PASSWORD}@postgres:5432/mmapp
DATABASE_PATH: /app/data/mikrotik.db
CORS_ORIGIN: ${CORS_ORIGIN:-https://mm.shnt.top}
AUTH_REQUIRED: ${AUTH_REQUIRED:-true}
@@ -54,7 +101,7 @@ services:
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
start_period: 300s
logging:
driver: json-file
options:
@@ -116,7 +163,7 @@ services:
REGISTRY_USERNAME: ${REGISTRY_USERNAME:-}
REGISTRY_PASSWORD: ${REGISTRY_PASSWORD:-}
POLL_INTERVAL_SECONDS: ${POLL_INTERVAL_SECONDS:-300}
HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-120}
HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-600}
STOP_TIMEOUT_SECONDS: ${STOP_TIMEOUT_SECONDS:-30}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
@@ -136,3 +183,6 @@ networks:
cdnmanager:
external: true
name: cdnmanager
volumes:
mmapp-pgdata:
+51 -3
View File
@@ -54,17 +54,64 @@ services:
max-size: "10m"
max-file: "3"
postgres:
image: ${POSTGRES_IMAGE_TAG:-postgres:18-alpine}
container_name: mmapp-postgres
restart: unless-stopped
shm_size: "256mb"
environment:
POSTGRES_USER: mmapp
POSTGRES_DB: mmapp
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C.UTF-8 --data-checksums"
TZ: UTC
PGTZ: UTC
command:
- postgres
- -c
- timezone=UTC
- -c
- listen_addresses=*
- -c
- max_connections=100
- -c
- shared_buffers=256MB
- -c
- work_mem=16MB
- -c
- maintenance_work_mem=128MB
- -c
- wal_compression=on
volumes:
- mmapp-pgdata:/var/lib/postgresql/data
networks:
- mmapp
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mmapp -d mmapp"]
interval: 5s
timeout: 5s
retries: 10
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
backend:
image: git.shx.one/denozord/mikrotikmanager-backend:${MM_BACKEND_IMAGE_TAG:-latest}
pull_policy: always
container_name: mmapp-backend
restart: unless-stopped
depends_on:
- traefik
traefik:
condition: service_started
postgres:
condition: service_healthy
# No host ports — frontend reaches backend on the Compose network.
environment:
NODE_ENV: production
PORT: "8000"
DATABASE_URL: postgres://mmapp:${POSTGRES_PASSWORD}@postgres:5432/mmapp
DATABASE_PATH: /app/data/mikrotik.db
CORS_ORIGIN: ${CORS_ORIGIN:-https://mm.shnt.top}
AUTH_REQUIRED: ${AUTH_REQUIRED:-true}
@@ -90,7 +137,7 @@ services:
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
start_period: 300s
logging:
driver: json-file
options:
@@ -150,7 +197,7 @@ services:
REGISTRY_USERNAME: ${REGISTRY_USERNAME:-}
REGISTRY_PASSWORD: ${REGISTRY_PASSWORD:-}
POLL_INTERVAL_SECONDS: ${POLL_INTERVAL_SECONDS:-300}
HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-120}
HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-600}
STOP_TIMEOUT_SECONDS: ${STOP_TIMEOUT_SECONDS:-30}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
@@ -167,6 +214,7 @@ services:
volumes:
traefik_letsencrypt:
name: mmapp_traefik_letsencrypt
mmapp-pgdata:
networks:
mmapp:
+58 -2
View File
@@ -1,18 +1,71 @@
services:
postgres:
image: ${POSTGRES_IMAGE_TAG:-postgres:18-alpine}
container_name: mmapp-postgres
restart: unless-stopped
shm_size: "256mb"
environment:
POSTGRES_USER: mmapp
POSTGRES_DB: mmapp
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C.UTF-8 --data-checksums"
TZ: UTC
PGTZ: UTC
command:
- postgres
- -c
- timezone=UTC
- -c
- listen_addresses=*
- -c
- max_connections=100
- -c
- shared_buffers=256MB
- -c
- work_mem=16MB
- -c
- maintenance_work_mem=128MB
- -c
- wal_compression=on
volumes:
- mmapp-pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U mmapp -d mmapp"]
interval: 5s
timeout: 5s
retries: 10
backend:
image: git.shx.one/denozord/mikrotikmanager-backend:latest
container_name: mmapp-backend
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "8000:8000"
# IPFIX: только на WG-адресе хоста после `wg-quick up wg-flow`, не 0.0.0.0
# - "10.255.254.1:4739:4739/udp"
environment:
CORS_ORIGIN: ${CORS_ORIGIN:-http://localhost:3000}
# Внутри контейнера слушаем все интерфейсы; на хосте UDP 4739 публикуется только на WG-IP
DATABASE_URL: postgres://mmapp:${POSTGRES_PASSWORD}@postgres:5432/mmapp
DATABASE_PATH: /app/data/mikrotik.db
# IPFIX: внутри контейнера слушаем все интерфейсы; на хосте UDP 4739 публикуется только на WG-IP
FLOW_LISTEN_HOST: "0.0.0.0"
volumes:
- /opt/mmapp/data:/app/data
healthcheck:
test:
[
"CMD",
"node",
"-e",
"fetch('http://127.0.0.1:8000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))",
]
interval: 30s
timeout: 5s
retries: 3
start_period: 300s
labels:
mmapp.updater.managed: "true"
mmapp.updater.target: backend
@@ -42,9 +95,12 @@ services:
REGISTRY_USERNAME: ${REGISTRY_USERNAME:-}
REGISTRY_PASSWORD: ${REGISTRY_PASSWORD:-}
POLL_INTERVAL_SECONDS: ${POLL_INTERVAL_SECONDS:-300}
HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-120}
HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-600}
STOP_TIMEOUT_SECONDS: ${STOP_TIMEOUT_SECONDS:-30}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/mmapp/state:/state
- /opt/mmapp/updater/targets.json:/etc/updater/targets.json:ro
volumes:
mmapp-pgdata:
+3
View File
@@ -29,6 +29,9 @@ CLOUDFLARE_API_TOKEN=
LOG_LEVEL=info
NODE_ENV=production
# --- MikrotikManager PostgreSQL ---
POSTGRES_PASSWORD=
# Portal SSO — used by CDN Manager and MikrotikManager backend
AUTH_REQUIRED=true
# Same HS256 secret as auth-portal JWT_SECRET (required)
+8
View File
@@ -0,0 +1,8 @@
# Copy to deploy/.env.postgres (gitignored) or merge into prod .env
POSTGRES_PASSWORD=change-me
POSTGRES_PORT=5432
# POSTGRES_IMAGE_TAG=postgres:18-alpine
# Backend
DATABASE_URL=postgres://mmapp:[email protected]:5432/mmapp
DATABASE_PATH=./mikrotik.db
+4
View File
@@ -0,0 +1,4 @@
# PostgreSQL 18 for MikrotikManager backend.
POSTGRES_PASSWORD=
HEALTH_TIMEOUT_SECONDS=600
+3
View File
@@ -24,6 +24,9 @@ [email protected]
# TRAEFIK_HTTPS_PORT=443
# TRAEFIK_LOG_LEVEL=INFO
# --- PostgreSQL ---
POSTGRES_PASSWORD=
# --- Images ---
MM_BACKEND_IMAGE_TAG=latest
MM_FRONTEND_IMAGE_TAG=latest
+32 -1
View File
@@ -80,6 +80,36 @@ fi
echo "==> Ensure internal network mmapp"
docker network inspect mmapp >/dev/null 2>&1 || docker network create mmapp >/dev/null
: "${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}"
echo "==> Ensure mmapp-postgres"
if ! docker inspect mmapp-postgres >/dev/null 2>&1; then
docker volume inspect mmapp-pgdata >/dev/null 2>&1 || docker volume create mmapp-pgdata >/dev/null
docker run -d \
--name mmapp-postgres \
--restart unless-stopped \
--network mmapp \
--network-alias postgres \
--shm-size 256mb \
-e POSTGRES_USER=mmapp \
-e POSTGRES_DB=mmapp \
-e POSTGRES_PASSWORD="${POSTGRES_PASSWORD}" \
-e POSTGRES_INITDB_ARGS="--encoding=UTF8 --locale=C.UTF-8 --data-checksums" \
-e TZ=UTC \
-e PGTZ=UTC \
-v mmapp-pgdata:/var/lib/postgresql/data \
--health-cmd="pg_isready -U mmapp -d mmapp" \
--health-interval=5s \
--health-timeout=5s \
--health-retries=10 \
postgres:18-alpine \
postgres -c timezone=UTC -c listen_addresses=* -c max_connections=100 \
-c shared_buffers=256MB -c work_mem=16MB -c maintenance_work_mem=128MB -c wal_compression=on
fi
until docker exec mmapp-postgres pg_isready -U mmapp -d mmapp >/dev/null 2>&1; do
sleep 1
done
if [[ -n "${REGISTRY_USERNAME:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then
echo "==> docker login ${REGISTRY}"
echo "${REGISTRY_PASSWORD}" | docker login "${REGISTRY}" -u "${REGISTRY_USERNAME}" --password-stdin
@@ -107,6 +137,7 @@ docker run -d \
--network-alias backend \
-e NODE_ENV=production \
-e PORT=8000 \
-e DATABASE_URL="postgres://mmapp:${POSTGRES_PASSWORD}@mmapp-postgres:5432/mmapp" \
-e DATABASE_PATH=/app/data/mikrotik.db \
-e "CORS_ORIGIN=${CORS_ORIGIN}" \
-e "AUTH_REQUIRED=${AUTH_REQUIRED:-true}" \
@@ -121,7 +152,7 @@ docker run -d \
--health-interval=30s \
--health-timeout=5s \
--health-retries=3 \
--health-start-period=15s \
--health-start-period=300s \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
+1 -1
View File
@@ -14,7 +14,7 @@ RUN chmod +x /updater/entrypoint.sh
ENV TARGETS_FILE=/etc/updater/targets.json
ENV STATE_FILE=/state/updater-state.json
ENV POLL_INTERVAL_SECONDS=300
ENV HEALTH_TIMEOUT_SECONDS=120
ENV HEALTH_TIMEOUT_SECONDS=600
ENV STOP_TIMEOUT_SECONDS=30
ENV REGISTRY=git.shx.one
+1 -1
View File
@@ -4,7 +4,7 @@ set -euo pipefail
TARGETS_FILE="${TARGETS_FILE:-/etc/updater/targets.json}"
STATE_FILE="${STATE_FILE:-/state/updater-state.json}"
POLL_INTERVAL_SECONDS="${POLL_INTERVAL_SECONDS:-300}"
HEALTH_TIMEOUT_SECONDS="${HEALTH_TIMEOUT_SECONDS:-120}"
HEALTH_TIMEOUT_SECONDS="${HEALTH_TIMEOUT_SECONDS:-600}"
STOP_TIMEOUT_SECONDS="${STOP_TIMEOUT_SECONDS:-30}"
REGISTRY="${REGISTRY:-git.shx.one}"
MANAGED_LABEL="mmapp.updater.managed"