Эта инструкция — для первого переноса в новую пустую установку на одном VPS с Local-хранилищами. Сначала успешно завершите Provision и первый Deploy. Затем переносите согласованный набор: БД, снимок изображений и снимок файлов. Образы Docker этих данных не содержат.
Если на сервере уже есть нужные пользователи или записи, полный restore им не подходит: он заменяет БД. Для сохранения серверных аккаунтов нужен выборочный импорт только бизнес-данных по вашей модели. Исключение локальных auth-строк из дампа само по себе не сохраняет пользователей цели: pg_restore --clean всё равно пересоздаёт таблицы. Не запускайте этот сценарий поверх рабочего набора данных.
Для переноса production-копии используйте backup/restore контроллера, который согласует БД и native snapshots. Ниже — ручной dev → новый VPS. Сценарий рассчитан на Local storage; для S3 сначала подготовьте соответствующую схему доступа и переноса.
Пакет создают команды ниже; JSON-файлы вручную писать не нужно:
TRANSFER_DIR/
database.dump
media/ native-снимок изображений
media-result.json результат export со storeIdentity
files/ native-снимок документов
files-result.json результат export со storeIdentity
Папки и result-файлы отсутствующего в модели вида вложений пропускаются.
1. Согласовать версию и остановить писателей
Локальная схема и набор миграций должны соответствовать серверному релизу. В Source CI временно выберите точный production-коммит на локальной машине, а не просто main: merge мог создать другой SHA. Основной серверный checkout также обновляйте отдельно — CI не переключает его ветку.
Остановите make dev, cleanup и custom-задачи, пишущие в исходную БД и хранилища. До завершения export не возобновляйте запись. На VPS не создавайте аккаунты или контент до импорта.
PROJECT_NAME, YOUR_SERVER_HOST и имена контейнеров ниже замените своими значениями. Имя dev-контейнера PostgreSQL найдите через docker ps; оно станет DEV_POSTGRES. Во всех следующих локальных блоках оставайтесь в открытой Bash-сессии. set -e останавливает сессию при ошибке: последующие команды не выполняйте, пока не разберёте её.
2. Подготовить локальные параметры
bash
Откроется Bash. В этой сессии выполните:
set -e
cd /path/to/PROJECT_NAME
export APP_PROFILE=local
unset DATABASE_URL DATABASE_URL_FILE
make migrate
Теперь отдельно выполните команду ввода строки подключения, вставьте приватный DATABASE_URL и нажмите Enter:
read -r -s -p 'Local DATABASE_URL: ' DATABASE_URL
После ввода продолжите:
printf '\n'
export DATABASE_URL
export MEDIA_ROOT=/absolute/path/to/local/media
export FILES_ROOT=/absolute/path/to/local/files
ENV_NAME=local
DEV_POSTGRES=YOUR_DEV_POSTGRES_CONTAINER
TRANSFER_DIR="$HOME/project-transfer-$(date +%Y%m%d-%H%M%S)"
umask 077
mkdir -m 700 "$TRANSFER_DIR"
make migrate применяет недостающие миграции до снимка. Если он сообщает ошибку checksum, остановитесь: исправьте соответствие исходников и БД, не редактируйте журнал вручную.
DATABASE_URL возьмите из приватной конфигурации dev-приложения; это подключение к той же БД, что использует DEV_POSTGRES. Ввод через read не сохраняет URL командой в истории. Задайте действительные абсолютные MEDIA_ROOT и FILES_ROOT своей локальной среды. Go-команды ниже не загружают .env автоматически. --env local — метка снимка, она не выбирает конфигурационный файл.
Если проект не использует изображения или файлы, соответствующей snapshot-команды может не быть: пропустите её блок и связанные действия на VPS. TRANSFER_DIR — новая приватная папка вне Git.
3. Экспортировать изображения и файлы
Для изображений:
mkdir -m 700 "$TRANSFER_DIR/media"
(
cd backend
go run ./cmd/media-snapshot --mode export \
--env "$ENV_NAME" --stage "$TRANSFER_DIR/media" --writers-frozen \
> "$TRANSFER_DIR/media-result.json"
)
Для нативных файлов, включая PDF и презентации:
mkdir -m 700 "$TRANSFER_DIR/files"
(
cd backend
go run ./cmd/files-snapshot --mode export \
--env "$ENV_NAME" --stage "$TRANSFER_DIR/files" --writers-frozen \
> "$TRANSFER_DIR/files-result.json"
)
Каталоги stage должны быть пустыми. Snapshot-команды сверяют объекты с БД и записывают их manifest/hash. Сохраните также media-result.json и files-result.json: в них находится source storeIdentity, который потребуется при restore. Это не поле snapshot.json.
Экспорт snapshots выполняется до pg_dump: создание первой привязки пустого хранилища тогда тоже попадёт в дамп. После ошибки не переиспользуйте частичный stage — разберите сообщение и выберите новую папку.
4. Сохранить БД и передать весь пакет
docker exec "$DEV_POSTGRES" sh -c \
'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc --no-owner --no-acl' \
> "$TRANSFER_DIR/database.dump"
scp -r "$TRANSFER_DIR" deploy@YOUR_SERVER_HOST:/home/deploy/
Указанный dump включает локальные аккаунты и сессии. После полного переноса вход будет с локальными учётными данными. Если нужны новые серверные аккаунты, добавьте --exclude-table-data='admingen_auth.*' к pg_dump и после восстановления выполните bootstrap нового суперадминистратора. Этот вариант по-прежнему предназначен только для пустой цели.
Не отправляйте только database.dump: в PostgreSQL нет содержимого медиафайлов. После успешного export можно возобновить локальную разработку; пакет остаётся согласованным снимком выбранного момента.
5. На VPS остановить приложение и подготовить restore
Дальнейшие команды выполняйте под deployment-пользователем в Bash. Убедитесь, что цель действительно пустая. Остановите также CI-выпуски, cleanup и внешних писателей на всё время восстановления.
ENV_NAME — имя профиля. RUNTIME_ROOT и Local storage roots возьмите в Deployment → Environments → Preview effective JSON. TRANSFER_DIR — папка, которую только что передали scp; её имя включает дату. COMPOSE_PROJECT найдите по метке контейнера своего проекта:
docker ps -a --format 'table {{.Names}}\t{{.Label "com.docker.compose.project"}}'
bash
В открытой Bash-сессии:
set -e
cd "$HOME/projects/PROJECT_NAME"
ENV_NAME=production
RUNTIME_ROOT=/srv/admingen/PROJECT_NAME/production
COMPOSE_PROJECT=YOUR_COMPOSE_PROJECT
TRANSFER_DIR="$HOME/project-transfer-YYYYMMDD-HHMMSS"
MEDIA_ROOT=/absolute/production/media
FILES_ROOT=/absolute/production/files
bash ./deploy/control down --env "$ENV_NAME" --confirm "$ENV_NAME"
BACKEND_IMAGE=$(jq -er .backend "$RUNTIME_ROOT/state/current.json")
PG=$(docker ps -a \
--filter "label=com.docker.compose.project=$COMPOSE_PROJECT" \
--filter label=com.docker.compose.service=postgres --format '{{.ID}}')
docker start "$PG"
docker exec "$PG" pg_isready -U postgres -d app
RUNTIME_ROOT, storage roots и COMPOSE_PROJECT берите из фактической установки. Для generated server БД называется app, пользователь postgres. У PG должен быть ровно один ID; если контейнер не найден или найдено несколько, остановитесь. Продолжайте только после ответа accepting connections от pg_isready.
Backend/frontend остаются остановленными, запускается только существующая PostgreSQL. Не создавайте новую БД и не выполняйте второй Provision ради переноса.
6. Проверить пустоту хранилищ
Следующая функция запускает native snapshot-команды из текущего backend-образа с производственным подключением и его CA. Укажите именно действительные Local-пути из профиля. Secrets не копируются в пакет.
snapshot() {
local kind="$1" storage="$2" variable
shift 2
case "$kind" in
media) variable=MEDIA_ROOT ;;
files) variable=FILES_ROOT ;;
*) return 1 ;;
esac
docker run --rm --platform linux/amd64 \
--network "$COMPOSE_PROJECT-private" \
--mount "type=bind,src=$RUNTIME_ROOT/secrets/database_url,dst=/run/secrets/database_url,readonly" \
--mount "type=bind,src=$RUNTIME_ROOT/pki/ca.crt,dst=/run/secrets/ca.crt,readonly" \
--mount "type=bind,src=$storage,dst=$storage" \
--mount "type=bind,src=$TRANSFER_DIR/$kind,dst=/snapshot,readonly" \
-e APP_PROFILE=production -e DATABASE_URL_FILE=/run/secrets/database_url \
-e "$variable=$storage" \
--entrypoint "$kind-snapshot" "$BACKEND_IMAGE" "$@"
}
snapshot media "$MEDIA_ROOT" --mode verify-empty --env "$ENV_NAME" --writers-frozen
snapshot files "$FILES_ROOT" --mode verify-empty --env "$ENV_NAME" --writers-frozen
Verify-empty проверяет физическое хранилище. Если в нём уже есть объекты, команда остановится: существующие данные не удаляют ради продолжения. Не передавайте verify-empty параметры stage/restore.
7. Восстановить БД и native snapshots
Только после успешной проверки обоих используемых хранилищ:
docker exec -i "$PG" pg_restore -U postgres -d app \
--clean --if-exists --exit-on-error --single-transaction --no-owner --no-acl \
< "$TRANSFER_DIR/database.dump"
if [ -d "$TRANSFER_DIR/media" ]; then
sudo chown -R 10001:10001 "$TRANSFER_DIR/media"
fi
if [ -d "$TRANSFER_DIR/files" ]; then
sudo chown -R 10001:10001 "$TRANSFER_DIR/files"
fi
snapshot media "$MEDIA_ROOT" --mode restore \
--env "$ENV_NAME" --stage /snapshot --writers-frozen \
--confirm-restore "$ENV_NAME" \
--expected-identity "$(jq -er .storeIdentity "$TRANSFER_DIR/media-result.json")"
snapshot files "$FILES_ROOT" --mode restore \
--env "$ENV_NAME" --stage /snapshot --writers-frozen \
--confirm-restore "$ENV_NAME" \
--expected-identity "$(jq -er .storeIdentity "$TRANSFER_DIR/files-result.json")"
Chown меняет владельца только переданных snapshot-папок для чтения стандартным UID backend. Не применяйте его к чужим каталогам. Если deployment-пользователь не имеет sudo, этот шаг выполнит оператор сервера.
Restore проверяет БД, source identity и manifest объектов, переносит файлы и сам регистрирует привязку к новому хранилищу. SQL для ручной замены store_identity и копирование сырой media-папки не нужны. При ошибке оставьте приложение остановленным и разберите сообщение; повторный полный restore вслепую не выполняют.
8. Запустить и проверить
Для Source/локального on-host управления запустите записанный релиз:
bash ./deploy/control up --env "$ENV_NAME" --confirm "$ENV_NAME"
bash ./deploy/control status --env "$ENV_NAME" --json
Если окружением управляет удалённый Archive/Registry CI, используйте тот же контроллер с его штатными доступами, а не SSH к самому себе без ключей. Для Source CI также можно повторить Deploy того же коммита после успешного импорта.
Проверьте записи, переводы, изображения разных размеров, скачивание файлов и вход. При исключённой auth-части сначала создайте нового приватного суперадминистратора; публичную demo-учётку создавайте отдельно с нужными правами. Обычный следующий deploy сохраняет эти данные — перенос заново не требуется.