Убирает собственный контейнер db — вместо своего Postgres подключается внешней сетью (netrunner_grid, external: true) к netrunner-db из netrunner-backend/docker-compose.prod.yml, в отдельной базе/юзере (VW_DB_*, был DB_*). Экономит ресурсы на VPS, где раньше крутились бы два отдельных Postgres-процесса. Добавляет пошаговый ранбук переноса существующих данных со старого SQLite-инстанса (scripts/migrate-vaultwarden.md, через checkpoint WAL + pgloader data-only на уже созданную diesel-схему). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8.4 KiB
Перенос 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'а):
# На новом 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)
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 (с любой машины, у которой есть доступ к обоим — например, с вашего ноутбука):
scp root@<СТАРЫЙ_VPS_IP>:~/vaultwarden-data-backup-*.tar.gz .
scp vaultwarden-data-backup-*.tar.gz root@136.244.78.208:~/
Старый Vaultwarden пока НЕ запускать обратно — он останется единственным живым источником правды до подтверждённого успеха миграции (Фаза 4).
Фаза 2 — распаковка на НОВОМ сервере
# На новом VPS (136.244.78.208)
mkdir -p ~/netrunner-data
cd ~/netrunner-data
git clone <URL_РЕПО_netrunner-data> . 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-типов
даст несовместимую структуру.
# .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-файла в уже созданную схему:
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 — запуск и проверка
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 (см. ниже) ломаться не должен.
Только после успешного логина и видимых данных:
- Переключить DNS A-запись
vault.netrunner-vpn.comна136.244.78.208. - Подождать TTL, ещё раз проверить логин уже по-настоящему через домен.
- Только теперь можно выключить (не обязательно сразу удалять) старый
Vaultwarden — оставить его на пару дней выключенным как холодный откат,
прежде чем сносить директорию
~/vaultwarden-dataна старом сервере.
Если pgloader ругается на конкретную таблицу
Обычно это twofactor/ciphers из-за BLOB/бинарных полей — можно догнать их
руками через INSERT INTO ... SELECT в отдельном скрипте или повторным
прогоном pgloader с --with "batch rows = 1" для этой таблицы (медленнее, но
проще диагностировать построчно). Не обязательно решать это заранее — сначала
просто прогнать команду выше и смотреть на реальный лог ошибок.