Убирает свой Postgres — БД физически переезжает на VPS с Vaultwarden

backend будет жить на отдельном, ещё не арендованном VPS — свой
контейнер `db` и бэкапы (scripts/backup.sh) здесь больше не нужны и
переехали в netrunner-data, где Postgres теперь физически стоит.
DATABASE_URL собирается из DB_HOST/DB_PORT (внешний IP того VPS) с
обязательным sslmode=require — трафик пересекает публичную сеть между
двумя разными серверами.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 17:17:20 +07:00
parent ebc618743a
commit 3feb2f431c
4 changed files with 27 additions and 154 deletions
+16 -20
View File
@@ -6,17 +6,22 @@
# (например `openssl rand -base64 24`) и никогда не коммитить .env с настоящим
# значением (см. .gitignore — .env/.env.* уже исключены).
#
# ДЛЯ ЛОКАЛЬНЫХ ТЕСТОВ НА ТВОЕМ ПК:
# DATABASE_URL=postgres://netrunner_admin:CHANGE_ME_DB_PASSWORD@<ip-впс>:5432/netrunner
#
# ДЛЯ ЗАПУСКА НА САМОЙ ВПС (Через Docker Compose):
# DATABASE_URL=postgres://netrunner_admin:CHANGE_ME_DB_PASSWORD@db:5432/netrunner
DATABASE_URL=postgres://netrunner_admin:CHANGE_ME_DB_PASSWORD@db:5432/netrunner
# Postgres физически стоит НЕ на этом VPS, а на отдельном сервере вместе с
# Vaultwarden (см. netrunner-data/docker-compose.yml, сервис `db`) — этот
# бэкенд подключается к нему по сети. Полный DATABASE_URL собирает сам
# docker-compose.prod.yml из DB_USER/DB_PASSWORD/DB_HOST/DB_PORT/DB_NAME ниже
# (не пишите его здесь отдельной строкой — env_file не подставляет ${...},
# в отличие от самого docker-compose, так что отдельная строка DATABASE_URL
# тут просто не сработает). sslmode=require обязателен: без него подключение
# к hostssl-правилу в pg_hba.conf того сервера будет отклонено.
DB_USER=netrunner_admin
DB_NAME=netrunner
DB_PASSWORD=CHANGE_ME_DB_PASSWORD
# IP VPS, где физически стоит Postgres (netrunner-data). Совпадает с DATA_VPS_IP
# в CI-переменных netrunner-data.
DB_HOST=CHANGE_ME_DB_HOST_IP
DB_PORT=5432
# Кеш курса TON (не источник истины — если недоступен, приложение просто
# ходит в TonAPI напрямую на каждый инвойс, как раньше).
@@ -30,18 +35,9 @@ ARGON_SALT=CHANGE_ME_ARGON_SALT_MIN_16_CHARS
GRAFANA_PASSWORD=CHANGE_ME_GRAFANA_PASSWORD
TON_MASTER_WALLET=UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKK8y
# =====================================================================
# === БЭКАПЫ POSTGRES (см. scripts/backup.sh, сервис `backup` в
# === docker-compose.prod.yml) ===
# =====================================================================
# Без RCLONE_REMOTE/RCLONE_CONFIG_CONTENT бэкапы всё равно снимаются каждые
# BACKUP_INTERVAL_SEC (по умолчанию 6ч) и хранятся локально в volume
# netrunner_db_backups — но только на этом же VPS. Для реальной защиты от
# падения диска/потери сервера нужна офсайт-копия:
# BACKUP_INTERVAL_SEC=21600
# BACKUP_RETENTION_DAYS=14
# RCLONE_REMOTE=b2:netrunner-backups
# RCLONE_CONFIG_CONTENT содержимое rclone.conf одной переменной, см. rclone.conf.example
# Бэкапы Postgres (pg_dump + опциональный rclone) теперь настраиваются в
# netrunner-data/.env (там же, где физически стоит БД) — см.
# netrunner-data/scripts/backup.sh. Здесь их больше нет.
# Опционально: без него оплата через @CryptoBot вернёт "provider not
# supported", остальные провайдеры продолжают работать. Токен — у @CryptoBot:
@@ -98,7 +94,7 @@ COOKIE_SECURE=true
# === DEPLOYMENT & REPOSITORY SETTINGS ===
# =====================================================================
GHCR_TOKEN=ghp_ТВОЙ_ТОКЕН_ОТ_ГИТХАБА_ДЛЯ_ДОСТУПА_К_РЕЕСТРУ
VPS_IP=77.110.106.113
VPS_IP=CHANGE_ME_VPS_IP
DEPLOY_DIR=/root/netrunner-core
BINARY_NAME=netrunner-control-plane
# Образ, который тянет прод-стек (docker-compose.prod.yml).
+11 -56
View File
@@ -1,6 +1,15 @@
# =====================================================================
# PRODUCTION — VPS только тянет готовый образ из GHCR и запускает его.
# Никакой компиляции Rust на сервере. Деплой = pull + migrate + up.
#
# ВАЖНО: своего Postgres здесь больше нет. БД физически стоит на отдельном
# VPS (см. netrunner-data/docker-compose.yml — там же Vaultwarden, тот же
# Postgres-процесс, вторая база) и слушает по сети, не в одном
# Docker-стеке с этим compose. DATABASE_URL ниже указывает на DB_HOST:DB_PORT
# (внешний IP того VPS), не на "db" как раньше — и требует sslmode=require,
# т.к. трафик идёт по публичной сети между двумя разными серверами. Бэкапы
# тоже переехали туда же (netrunner-data/scripts/backup.sh) — дампить
# логичнее там, где физически лежат данные.
# =====================================================================
services:
# One-shot: применяет миграции и завершается. Требует только DATABASE_URL.
@@ -9,10 +18,7 @@ services:
container_name: netrunner-migrate
command: ["./netrunner-control-plane", "--migrate"]
environment:
- DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
depends_on:
db:
condition: service_healthy
- DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT:-5432}/${DB_NAME}?sslmode=require
restart: "no"
networks:
- netrunner_grid
@@ -28,12 +34,10 @@ services:
environment:
- APP_ENV=production
- FRONTEND_DIR=/app/frontend/dist
- DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
- DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT:-5432}/${DB_NAME}?sslmode=require
volumes:
- ./keys:/app/keys:ro
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
migrate:
@@ -47,24 +51,6 @@ services:
networks:
- netrunner_grid
db:
image: postgres:16-alpine
container_name: netrunner-db
restart: always
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USER}
POSTGRES_DB: ${DB_NAME}
volumes:
- netrunner_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 5s
timeout: 5s
retries: 5
networks:
- netrunner_grid
redis:
image: redis:7-alpine
container_name: netrunner-redis
@@ -75,31 +61,6 @@ services:
networks:
- netrunner_grid
# Периодический pg_dump + опциональная офсайт-копия через rclone (см.
# scripts/backup.sh). RCLONE_REMOTE/RCLONE_CONFIG — задать в .env, если нужна
# копия за пределы этого VPS (настоятельно рекомендуется — единственная
# локальная копия на volume ничем не защищена от падения диска этой машины).
backup:
image: postgres:16-alpine
container_name: netrunner-backup
restart: always
entrypoint: ["/bin/sh", "/scripts/backup.sh"]
environment:
- DATABASE_URL=postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
- BACKUP_DIR=/backups
- BACKUP_INTERVAL_SEC=${BACKUP_INTERVAL_SEC:-21600}
- BACKUP_RETENTION_DAYS=${BACKUP_RETENTION_DAYS:-14}
- RCLONE_REMOTE=${RCLONE_REMOTE:-}
- RCLONE_CONFIG_CONTENT=${RCLONE_CONFIG_CONTENT:-}
volumes:
- ./scripts/backup.sh:/scripts/backup.sh:ro
- netrunner_db_backups:/backups
depends_on:
db:
condition: service_healthy
networks:
- netrunner_grid
# ==========================================
# МОНИТОРИНГ
# ==========================================
@@ -161,13 +122,7 @@ services:
networks:
netrunner_grid:
# Явное имя (не дефолтный <project>_netrunner_grid) — на этот же bridge
# снаружи подключается netrunner-data/docker-compose.yml (Vaultwarden) как
# external-сеть, ссылаясь по этому фиксированному имени.
name: netrunner_grid
driver: bridge
volumes:
netrunner_db_data:
netrunner_db_backups:
grafana_data:
-16
View File
@@ -1,16 +0,0 @@
# Конфиг rclone для офсайт-копии бэкапов Postgres (см. scripts/backup.sh).
#
# 1. Скопировать этот файл в rclone.conf (рядом с docker-compose.prod.yml на VPS)
# и заполнить реальными данными удалённого хранилища (Backblaze B2, S3,
# другой VPS по SFTP — что угодно, что поддерживает rclone).
# 2. Прописать в .env: RCLONE_REMOTE=b2:netrunner-backups (имя секции ниже
# + путь/бакет), совпадающее с именем секции [remote-name] тут.
# 3. Пустой/незаполненный rclone.conf — не ошибка: офсайт-копия просто не
# выполнится (см. лог "не удалось скопировать"), локальный дамп всё равно
# создаётся каждый цикл.
#
# Пример для Backblaze B2:
# [b2]
# type = b2
# account = YOUR_B2_ACCOUNT_ID
# key = YOUR_B2_APPLICATION_KEY
-62
View File
@@ -1,62 +0,0 @@
#!/bin/sh
# Периодический pg_dump с опциональной офсайт-копией. Запускается как отдельный
# долгоживущий контейнер (см. docker-compose.prod.yml/docker-compose.dev-remote.yml,
# сервис `backup`) — сам себе крон через бесконечный цикл + sleep, без внешнего
# cron-демона в образе postgres:16-alpine.
#
# Обязательные переменные окружения: DATABASE_URL, BACKUP_DIR (примонтированный volume).
# Опциональные: BACKUP_INTERVAL_SEC (по умолчанию 21600 = 6ч), BACKUP_RETENTION_DAYS
# (по умолчанию 14), RCLONE_REMOTE (например "b2:netrunner-backups" — если не задан,
# офсайт-копия просто пропускается, бэкапы остаются только локально на VPS) и
# RCLONE_CONFIG_CONTENT (содержимое rclone.conf целиком, одной строкой/переменной
# окружения — без bind-mount'а файла, чтобы не требовать его существования на диске
# при первом деплое).
set -eu
BACKUP_DIR="${BACKUP_DIR:-/backups}"
INTERVAL="${BACKUP_INTERVAL_SEC:-21600}"
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-14}"
mkdir -p "$BACKUP_DIR"
if [ -n "${RCLONE_REMOTE:-}" ] && ! command -v rclone >/dev/null 2>&1; then
echo "[backup] Устанавливаю rclone для офсайт-копий..."
apk add --no-cache rclone >/dev/null
fi
if [ -n "${RCLONE_CONFIG_CONTENT:-}" ]; then
mkdir -p /root/.config/rclone
printf '%s\n' "$RCLONE_CONFIG_CONTENT" > /root/.config/rclone/rclone.conf
fi
echo "[backup] Запущен. Интервал: ${INTERVAL}с, хранение: ${RETENTION_DAYS}д, офсайт: ${RCLONE_REMOTE:-<не настроен>}"
while true; do
timestamp=$(date -u +%Y%m%dT%H%M%SZ)
dump_file="$BACKUP_DIR/netrunner_${timestamp}.sql.gz"
echo "[backup] $(date -u +%FT%TZ) Снимаю дамп -> $dump_file"
if pg_dump "$DATABASE_URL" | gzip > "$dump_file.tmp"; then
mv "$dump_file.tmp" "$dump_file"
echo "[backup] Готово: $(du -h "$dump_file" | cut -f1)"
else
echo "[backup] ОШИБКА: pg_dump упал, файл не сохранён" >&2
rm -f "$dump_file.tmp"
sleep "$INTERVAL"
continue
fi
if [ -n "${RCLONE_REMOTE:-}" ]; then
if rclone copy "$dump_file" "$RCLONE_REMOTE" 2>&1; then
echo "[backup] Скопировано на $RCLONE_REMOTE"
else
echo "[backup] ОШИБКА: не удалось скопировать на $RCLONE_REMOTE (локальная копия сохранена)" >&2
fi
fi
# Локальная ротация — офсайт-копия (если настроена) хранится по правилам
# самого remote, это не отменяет отдельную политику хранения там.
find "$BACKUP_DIR" -name 'netrunner_*.sql.gz' -mtime "+${RETENTION_DAYS}" -delete
sleep "$INTERVAL"
done