diff --git a/.env.example b/.env.example index 17dee9e..aa5e0ef 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,15 @@ # Скопировать в .env и заполнить реальными значениями. .env — в .gitignore, # никогда не коммитить с настоящими секретами. На деплое (см. -# .github/workflows/deploy.yml) этот файл целиком собирается из GitHub Actions -# secrets прямо на VPS — локальный .env нужен только для ручного/dev-запуска. +# .github/workflows/deploy.yml) этот файл целиком собирается из GitHub Actions/ +# Gitea Actions secrets прямо на VPS — локальный .env нужен только для +# ручного/dev-запуска. -# --- Postgres (данные Vaultwarden) --- -DB_USER=vaultwarden -DB_PASSWORD=CHANGE_ME_DB_PASSWORD_openssl_rand_base64_24 -DB_NAME=vaultwarden +# --- Postgres (общий контейнер netrunner-db из netrunner-backend, отдельная +# база+юзер внутри него — см. scripts/migrate-vaultwarden.md, шаг "CREATE +# DATABASE/CREATE USER") --- +VW_DB_USER=vaultwarden +VW_DB_PASSWORD=CHANGE_ME_VW_DB_PASSWORD_openssl_rand_base64_24 +VW_DB_NAME=vaultwarden # --- Vaultwarden --- VAULTWARDEN_DOMAIN=https://vault.netrunner-vpn.com diff --git a/docker-compose.yml b/docker-compose.yml index 3559ba3..cbb917e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,45 +1,31 @@ # ===================================================================== # netrunner-data — сервисы хранения данных, вынесенные в отдельный от # основного control-plane стек: сейчас только Vaultwarden (личный/командный -# менеджер паролей) + его собственный Postgres. +# менеджер паролей). # # Раньше Vaultwarden жил на проде голым файлом (~/vaultwarden-data/docker-compose.yml) # без git/CI, с захардкоженными в файле секретами и на SQLite (./data/db.sqlite3). # Здесь: секреты — только через .env (сам .env НЕ в git, см. .gitignore; на -# деплое собирается из GitHub Actions secrets, см. .github/workflows/deploy.yml), -# БД — Postgres вместо SQLite. +# деплое собирается из GitHub Actions/Gitea Actions secrets, см. +# .github/workflows/deploy.yml), БД — Postgres вместо SQLite. # -# ВАЖНО про переход на Postgres: Vaultwarden сам не мигрирует данные из уже -# существующего ./data/db.sqlite3 в Postgres при простой смене DATABASE_URL — -# это отдельная, аккуратная операция (см. официальный вики Vaultwarden, -# "Backing up your vault" + миграция бэкенда), а не просто "поднять новый -# сервис". Перед первым запуском этого compose поверх УЖЕ ЖИВОГО инстанса с -# реальными данными пользователей — сначала сделать полный бэкап текущей -# ./data (включая attachments и rsa-ключи), затем мигрировать содержимое -# db.sqlite3 в Postgres, и только потом переключать DATABASE_URL. Поднимать -# этот compose "как есть" поверх существующих данных без миграции = Vaultwarden -# просто заведёт новую пустую БД в Postgres, старые данные останутся -# нетронутыми в volume, но перестанут быть видны приложению. +# ВАЖНО: своего Postgres-контейнера здесь больше нет — Vaultwarden делит один +# и тот же Postgres-процесс с netrunner-backend (сервис `db`/контейнер +# `netrunner-db` из netrunner-backend/docker-compose.prod.yml), просто в +# отдельной базе (vaultwarden) с собственным пользователем. Оба compose-стека +# должны быть подняты на одном VPS и слиты в общую внешнюю сеть +# `netrunner_grid` (см. миграцию в scripts/migrate-vaultwarden.md — там же и +# однократная команда CREATE DATABASE/CREATE USER на этом общем Postgres). +# +# ВАЖНО про переход со старого SQLite-инстанса: Vaultwarden сам не мигрирует +# данные из ./data/db.sqlite3 в Postgres при простой смене DATABASE_URL — это +# отдельная, аккуратная операция через pgloader (см. scripts/migrate-vaultwarden.md), +# а не просто "поднять новый сервис". Поднимать этот compose "как есть" поверх +# старых данных без миграции = Vaultwarden просто заведёт новую пустую БД в +# Postgres, старые данные останутся нетронутыми в ./data, но перестанут быть +# видны приложению. # ===================================================================== services: - db: - image: postgres:16-alpine - container_name: netrunner-data-db - restart: unless-stopped - environment: - POSTGRES_USER: ${DB_USER} - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_DB: ${DB_NAME:-vaultwarden} - volumes: - - netrunner_data_db:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME:-vaultwarden}"] - interval: 5s - timeout: 5s - retries: 5 - networks: - - netrunner_data - vaultwarden: image: vaultwarden/server:latest container_name: vaultwarden @@ -51,9 +37,10 @@ services: - LOG_LEVEL=${VAULTWARDEN_LOG_LEVEL:-info} - ROCKET_PORT=8081 - WEBSOCKET_ENABLED=true - # postgresql:// вместо файла-заглушки sqlite — сам образ vaultwarden/server - # уже собран со всеми тремя бэкендами, отдельный тег не нужен. - - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME:-vaultwarden} + # Общий Postgres-процесс с netrunner-backend (контейнер netrunner-db, + # см. шапку файла) — отдельная база и юзер, не переиспользуем DB_USER/ + # DB_PASSWORD бэкенда. + - DATABASE_URL=postgresql://${VW_DB_USER}:${VW_DB_PASSWORD}@netrunner-db:5432/${VW_DB_NAME:-vaultwarden} - SMTP_HOST=${SMTP_HOST} - SMTP_FROM=${SMTP_FROM} - SMTP_PORT=${SMTP_PORT:-465} @@ -66,15 +53,12 @@ services: - ./data:/data ports: - "127.0.0.1:8081:8081" - depends_on: - db: - condition: service_healthy networks: - - netrunner_data + - netrunner_grid networks: - netrunner_data: - driver: bridge - -volumes: - netrunner_data_db: + # external: этот стек не поднимает Postgres сам — сеть уже создана + # netrunner-backend/docker-compose.prod.yml (там же живёт netrunner-db). + # Поднимать backend-стек первым. + netrunner_grid: + external: true diff --git a/scripts/migrate-vaultwarden.md b/scripts/migrate-vaultwarden.md new file mode 100644 index 0000000..60b39f5 --- /dev/null +++ b/scripts/migrate-vaultwarden.md @@ -0,0 +1,147 @@ +# Перенос Vaultwarden со старого VPS на новый (136.244.78.208), SQLite → Postgres + +Все команды — ручные, выполняются вами на серверах (у меня нет SSH-доступа ни +к старому, ни к новому VPS). Ниже — точная последовательность без потери +данных. Не удалять старый сервер/старые файлы, пока не пройден пункт +"Проверка" в самом конце. + +Старый инстанс живёт на SQLite в WAL-режиме (видно по файлам `db.sqlite3-wal` +и `db.sqlite3-shm` рядом с `db.sqlite3` — это значит часть последних изменений +может ещё не быть слита в основной файл). Поэтому нельзя просто скопировать +`db.sqlite3` в одиночку — копируем директорию `data/` целиком и только после +чистой остановки контейнера. + +## Фаза 0 — предварительно, на новом VPS (136.244.78.208) + +Задеплоить (или обновить) прод `netrunner-backend` **первым** — Vaultwarden +теперь не поднимает свой Postgres, а подключается к тому же контейнеру +`netrunner-db` и сети `netrunner_grid`, которые создаёт +`netrunner-backend/docker-compose.prod.yml`. Пока backend не поднят на этом +VPS — `docker compose up` для vaultwarden ниже упадёт с "network netrunner_grid +not found". + +Затем создать отдельную базу и роль для Vaultwarden в этом общем Postgres +(не переиспользовать логин backend'а): + +```bash +# На новом VPS, после того как netrunner-backend/docker-compose.prod.yml поднят +docker exec -it netrunner-db psql -U "$DB_USER" -d postgres -c \ + "CREATE ROLE vaultwarden WITH LOGIN PASSWORD 'СЮДА_ТОТ_ЖЕ_ПАРОЛЬ_ЧТО_В_VW_DB_PASSWORD';" +docker exec -it netrunner-db psql -U "$DB_USER" -d postgres -c \ + "CREATE DATABASE vaultwarden OWNER vaultwarden;" +``` + +(`$DB_USER` — существующий админ-юзер backend-Postgres, из его `.env`.) + +## Фаза 1 — на СТАРОМ сервере (текущий Vaultwarden) + +```bash +cd ~/vaultwarden-data + +# Останавливаем контейнер — чистое закрытие sqlite-соединения (checkpoint +# WAL в основной файл на выходе). НЕ удалять после — только stop. +docker compose stop vaultwarden + +# На всякий случай форсируем checkpoint+truncate WAL явно, а не полагаемся +# только на чистое закрытие процессом: +docker run --rm -v "$PWD/data:/data" alpine:3.20 sh -c \ + "apk add --no-cache sqlite >/dev/null && sqlite3 /data/db.sqlite3 'PRAGMA wal_checkpoint(TRUNCATE);'" + +# Теперь весь ./data — самодостаточный снимок. Архивируем целиком одним +# куском (attachments, rsa_key.pem, db.sqlite3 — всё вместе, не выборочно). +tar czf ~/vaultwarden-data-backup-$(date +%Y%m%d-%H%M%S).tar.gz -C ~/vaultwarden-data data + +ls -la ~/vaultwarden-data-backup-*.tar.gz +``` + +Скопировать архив на новый VPS (с любой машины, у которой есть доступ к +обоим — например, с вашего ноутбука): + +```bash +scp root@<СТАРЫЙ_VPS_IP>:~/vaultwarden-data-backup-*.tar.gz . +scp vaultwarden-data-backup-*.tar.gz root@136.244.78.208:~/ +``` + +Старый Vaultwarden пока НЕ запускать обратно — он останется единственным +живым источником правды до подтверждённого успеха миграции (Фаза 4). + +## Фаза 2 — распаковка на НОВОМ сервере + +```bash +# На новом VPS (136.244.78.208) +mkdir -p ~/netrunner-data +cd ~/netrunner-data +git clone . 2>/dev/null || true # если ещё не клонирован +tar xzf ~/vaultwarden-data-backup-*.tar.gz -C ~/netrunner-data +# теперь ~/netrunner-data/data/db.sqlite3 (+ -wal/-shm, rsa_key.pem, ...) на месте + +ls -la ~/netrunner-data/data +``` + +## Фаза 3 — конвертация SQLite → Postgres через pgloader + +Сначала дать Vaultwarden создать пустую Postgres-схему (diesel-миграции идут +из самого бинаря при первом старте с `DATABASE_URL=postgresql://...`) — +**НЕ через pgloader**: типы колонок в postgres-схеме vaultwarden подогнаны под +его собственный Rust/diesel-код, автосоздание схемы pgloader'ом из sqlite-типов +даст несовместимую структуру. + +```bash +# .env уже должен быть заполнен (VW_DB_USER/VW_DB_PASSWORD/VW_DB_NAME и т.д.) +cd ~/netrunner-data +docker compose up -d vaultwarden +sleep 5 +docker compose logs vaultwarden | tail -30 # убедиться что миграции прошли, схема создана +docker compose stop vaultwarden # снова стоп — дальше пишет только pgloader +``` + +Теперь перелить данные (не схему) из sqlite-файла в уже созданную схему: + +```bash +docker run --rm \ + --network netrunner_grid \ + -v ~/netrunner-data/data:/data \ + dimitri/pgloader:latest \ + pgloader \ + --with "data only" \ + --with "disable triggers" \ + --with "quote identifiers" \ + sqlite:///data/db.sqlite3 \ + postgresql://vaultwarden:СЮДА_VW_DB_PASSWORD@netrunner-db:5432/vaultwarden +``` + +Проверить, что pgloader завершился без ошибок ("... rows" summary без Errors) +— если есть ошибки по конкретным таблицам, здесь и надо разбираться, ДО того +как запускать vaultwarden поверх. + +## Фаза 4 — запуск и проверка + +```bash +cd ~/netrunner-data +docker compose up -d vaultwarden +docker compose logs -f vaultwarden # ждём "Rocket has launched" без ошибок БД +``` + +Затем, ДО переключения DNS на новый IP: +- Зайти по прежнему домену через `/etc/hosts`-подмену (временно указать + `vault.netrunner-vpn.com` на `136.244.78.208` только на своей машине) и + залогиниться существующим аккаунтом — проверить, что все записи/сейфы на + месте, вложения открываются. +- Проверить 2FA/WebAuthn там, где он был настроен — привязан к домену, не к + IP, так что при простой смене DNS (см. ниже) ломаться не должен. + +Только после успешного логина и видимых данных: + +1. Переключить DNS A-запись `vault.netrunner-vpn.com` на `136.244.78.208`. +2. Подождать TTL, ещё раз проверить логин уже по-настоящему через домен. +3. Только теперь можно выключить (не обязательно сразу удалять) старый + Vaultwarden — оставить его на пару дней выключенным как холодный откат, + прежде чем сносить директорию `~/vaultwarden-data` на старом сервере. + +## Если pgloader ругается на конкретную таблицу + +Обычно это `twofactor`/`ciphers` из-за BLOB/бинарных полей — можно догнать их +руками через `INSERT INTO ... SELECT` в отдельном скрипте или повторным +прогоном pgloader с `--with "batch rows = 1"` для этой таблицы (медленнее, но +проще диагностировать построчно). Не обязательно решать это заранее — сначала +просто прогнать команду выше и смотреть на реальный лог ошибок.