feat: add versioned deployment bundle
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
# WealthLedger deploy
|
||||
|
||||
Установка и обновление одного Linux-сервера с Docker Compose и SQLite:
|
||||
|
||||
```bash
|
||||
./deploy.sh vX.Y.Z
|
||||
```
|
||||
|
||||
`vX.Y.Z` — Git-тег **этого репозитория**. Его [release.env](release.env)
|
||||
закрепляет полные адреса frontend и backend. Версии компонентов могут различаться.
|
||||
Скрипт получает тег из `origin` и выполняет код и Compose выбранного commit;
|
||||
изменения рабочего checkout не подменяют содержимое релиза.
|
||||
|
||||
Образы загружаются из `nexus.personal.ksv741.ru/docker-hosted/wealthledger/`.
|
||||
Текущий кандидат пары — frontend/backend `0.2.3`; образы опубликованы для
|
||||
`linux/amd64`. Не создавайте тег комплекта до проверки пары по разделу выпуска.
|
||||
|
||||
## Подготовка сервера
|
||||
|
||||
Выполняется один раз. Автоматическая установка Docker, настройка DNS, HTTPS,
|
||||
firewall и registry в скрипт не входят.
|
||||
|
||||
1. Установите Git, Bash, Docker Engine с Compose plugin (2.24.4 или новее),
|
||||
`flock` из util-linux и стандартные `tar`, `cp`, `mktemp`. Используется
|
||||
локальный Docker daemon через Unix socket, не удалённый context.
|
||||
2. Настройте внешний HTTPS reverse proxy: весь домен направляется на
|
||||
`127.0.0.1:8080`. Отдельный upstream `/api` не нужен. Порт можно поменять
|
||||
через `FRONTEND_PORT`; `FRONTEND_ORIGIN` должен совпадать с HTTPS origin.
|
||||
3. Выберите одного оператора с доступом к Docker, Git origin, ключам и данным.
|
||||
Скрипту нужны права читать всю БД и создавать копии. Процесс backend работает
|
||||
с UID/GID `1000:1000`; при другом UID оператора настройте доступ заранее.
|
||||
Запускайте команды из одной и той же учётной записи, чтобы Git и Docker
|
||||
использовали подготовленные SSH-ключи и registry credentials.
|
||||
4. Войдите в Nexus, вводя пароль интерактивно, без записи в shell history:
|
||||
|
||||
```bash
|
||||
docker login nexus.personal.ksv741.ru --username wealthledger-ci
|
||||
```
|
||||
|
||||
5. Разместите checkout отдельно от постоянных ресурсов. Пример:
|
||||
|
||||
```text
|
||||
/opt/wealthledger/
|
||||
├── deploy/ # этот Git checkout
|
||||
│ ├── compose.yaml
|
||||
│ ├── deploy.sh
|
||||
│ ├── release.env # версии frontend и backend
|
||||
│ ├── .env.example
|
||||
│ └── README.md
|
||||
├── production.env # секреты и настройки, chmod 600
|
||||
├── certs/tbank-ca.pem # CA для подключения к T-Bank, не HTTPS сайта
|
||||
├── data/wealthledger.db
|
||||
├── backups/ # согласованные копии, без автоочистки
|
||||
└── .deploy/ # lock, current и при сбое pending
|
||||
```
|
||||
|
||||
```bash
|
||||
git clone ssh://git@git.personal.ksv741.ru:2222/WealthLedger/deploy.git /opt/wealthledger/deploy
|
||||
cd /opt/wealthledger/deploy
|
||||
install -m 600 .env.example ../production.env
|
||||
```
|
||||
|
||||
Последнюю команду выполняйте только при отсутствии `production.env`:
|
||||
существующий файл с ключами не перезаписывайте.
|
||||
|
||||
6. Заполните [настройки](.env.example) защищённым редактором. `DATA_DIRECTORY`
|
||||
и `TBANK_CA_CERT_FILE` — существующие абсолютные пути, не внутри checkout.
|
||||
`TBANK_CA_CERT_PATH` — путь внутри контейнера; сертификат монтируется read-only.
|
||||
Каталог данных должен быть доступен для записи UID 1000, сертификат — для
|
||||
чтения. Не отключайте проверку TLS вместо установки корректного CA.
|
||||
7. Проверьте доступ к образам, свободное место под полную копию data и существующую
|
||||
схему HTTPS. Сохраните ключ шифрования отдельно от локальных backups.
|
||||
|
||||
Не храните рабочие секреты в Git, не выполняйте `source production.env` и не
|
||||
печатайте полный `docker compose config` с реальными ключами.
|
||||
|
||||
## Подключение существующей установки
|
||||
|
||||
Сохраните её Compose и текущие image references до перехода. Заполните
|
||||
`production.env` **прежними** `JWT_SECRET` и `TOKEN_ENCRYPTION_KEY` и укажите
|
||||
действующий data directory и сертификат. Не копируйте один `.db` из работающего
|
||||
приложения и не создавайте новые ключи: старые подключения должны расшифровываться.
|
||||
|
||||
Проект Compose должен называться `wealthledger`, сервисы — `backend`, `frontend`
|
||||
и `migration`, база — `wealthledger.db`. Оператор должен исключить другие
|
||||
процессы записи в этот каталог; старый и новый способы запуска параллельно
|
||||
не используются. Скрипт откажется менять data mount или действующие ключи.
|
||||
|
||||
```bash
|
||||
cd /opt/wealthledger/deploy
|
||||
./deploy.sh vX.Y.Z
|
||||
```
|
||||
|
||||
Первый переход всегда выполняет полный цикл с backup, поскольку ещё нет
|
||||
подтверждённого baseline комплекта. Данные не переносятся и не переименовываются.
|
||||
`--init` для действующей установки не применяется.
|
||||
|
||||
## Новая установка
|
||||
|
||||
Только для нового пустого data directory подготовьте UID/GID `1000:1000`,
|
||||
permissions `0700` и новые ключи: JWT secret не короче 32 символов и отдельный
|
||||
32-byte encryption key в base64. Например, `openssl rand -hex 32` и
|
||||
`openssl rand -base64 32` в защищённом терминале; значения перенесите в env.
|
||||
Предварительно установите сертификат T-Bank.
|
||||
|
||||
```bash
|
||||
./deploy.sh vX.Y.Z --init
|
||||
```
|
||||
|
||||
Отсутствие файла БД без `--init` считается ошибкой, а не разрешением создать
|
||||
пустую базу. `--init` отклоняется при наличии прежних контейнеров, успешной
|
||||
установки или непустого data directory. Для первой пустой базы backup не нужен.
|
||||
|
||||
Если env расположен иначе, используйте `--env-file /absolute/path/production.env`.
|
||||
Backups и `.deploy` всегда находятся рядом с фактическим data directory,
|
||||
поэтому другой env-файл не создаёт отдельную блокировку для той же базы.
|
||||
|
||||
## Обновление
|
||||
|
||||
После публикации нового тега комплекта достаточно той же команды:
|
||||
|
||||
```bash
|
||||
./deploy.sh vX.Y.Z
|
||||
```
|
||||
|
||||
1. Проверка тега, настроек, данных и CA; загрузка образов и проверка прав
|
||||
runtime user. Ошибка до остановки сохраняет работающие сервисы.
|
||||
2. Сравнение образов и конфигурации с реальными контейнерами. При исправной
|
||||
неизменной установке — no-op. Если изменился только frontend, backend
|
||||
продолжает работать, backup и migration не запускаются.
|
||||
3. Если изменился backend или его конфигурация: stop → копия **всего** data
|
||||
directory с WAL/SHM → migration → backend readiness. Неизменный frontend
|
||||
не пересоздаётся; если меняется и он, запускается после готовности backend.
|
||||
4. Успех записывается в `.deploy/current` только после проверки запуска.
|
||||
Краткий простой допустим. После обновления проверьте через HTTPS deep link,
|
||||
login/refresh, существующие карты и чтение зашифрованного подключения.
|
||||
|
||||
Проверка сертификата/прав запускает кратковременный контейнер с Node validation,
|
||||
не второй API. Контейнерная готовность не заменяет browser smoke.
|
||||
|
||||
## Ошибка или прерывание
|
||||
|
||||
Скрипт возвращает ненулевой код, этап ошибки, предыдущие images и расположение
|
||||
готовой копии. Он не печатает raw application logs или секреты. После начала
|
||||
изменений `.deploy/pending` сохраняет безопасные сведения для восстановления;
|
||||
следующая команда остановится, пока оператор не разберёт инцидент.
|
||||
|
||||
- Ошибка backup: миграции не запускались, backend остановлен. Скрытые каталоги
|
||||
`backups/.partial-*` — незавершённые копии, использовать их для restore нельзя.
|
||||
- Ошибка migration: новый backend не запускается. Не применяйте `migrate reset`
|
||||
или автоматический `resolve`; сначала выясните состояние схемы.
|
||||
- Ошибка готовности backend: неисправный backend останавливается. Старый image
|
||||
автоматически не запускается — он может быть несовместим с новой схемой.
|
||||
- Ошибка frontend: исправный backend остаётся работающим, БД не восстанавливается.
|
||||
- После жёсткого прерывания migration может ещё работать: перед любым копированием
|
||||
или восстановлением проверьте контейнеры и остановите писателей.
|
||||
|
||||
Посмотрите `docker ps -a` и `.deploy/pending`. Для диагностики соответствующего
|
||||
контейнера используйте локально `docker logs <container>`; перед передачей логов
|
||||
убедитесь, что в них нет чувствительных данных.
|
||||
|
||||
## Ручной откат и восстановление
|
||||
|
||||
`deploy.sh` не является автоматическим rollback: выбор старого тега сам по себе
|
||||
не доказывает совместимость со схемой. Сначала проверьте план на отдельной копии
|
||||
данных с прежним ключом, другим Compose project и отдельным портом.
|
||||
|
||||
Подготовьте снимок выбранного **совместимого** тега для ручных Compose-команд:
|
||||
|
||||
```bash
|
||||
RECOVERY_BUNDLE=$(mktemp -d)
|
||||
git archive vCOMPATIBLE.VERSION.HERE | tar -x -C "$RECOVERY_BUNDLE"
|
||||
unset FRONTEND_IMAGE BACKEND_IMAGE COMPOSE_FILE COMPOSE_PROJECT_NAME COMPOSE_PROFILES
|
||||
COMPOSE=(docker compose --project-name wealthledger \
|
||||
--env-file /opt/wealthledger/production.env \
|
||||
--env-file "$RECOVERY_BUNDLE/release.env" -f "$RECOVERY_BUNDLE/compose.yaml")
|
||||
```
|
||||
|
||||
Замените демонстрационный тег настоящим. Используйте Bash и реальные абсолютные
|
||||
пути. Для image rollback без изменения данных:
|
||||
|
||||
```bash
|
||||
"${COMPOSE[@]}" stop backend migration
|
||||
"${COMPOSE[@]}" pull backend
|
||||
"${COMPOSE[@]}" up -d --no-deps --wait --wait-timeout 120 backend
|
||||
```
|
||||
|
||||
Для frontend аналогично выполняются `pull frontend` и `up ... frontend`.
|
||||
Migration при image rollback намеренно не запускается.
|
||||
|
||||
**Restore — отдельное решение с потерей всех записей после выбранного backup.**
|
||||
После проверки целостности выбранной копии и наличия её ключа, при остановленных
|
||||
писателях сохраните текущие данные под новым именем, не удаляя их:
|
||||
|
||||
```bash
|
||||
"${COMPOSE[@]}" stop backend migration
|
||||
mv /opt/wealthledger/data /opt/wealthledger/data-before-restore-UNIQUE_TIMESTAMP
|
||||
cp -a /opt/wealthledger/backups/CHOSEN_BACKUP/data /opt/wealthledger/data
|
||||
"${COMPOSE[@]}" up -d --no-deps --force-recreate --wait --wait-timeout 120 backend
|
||||
```
|
||||
|
||||
Подставьте уникальное ещё не существующее имя и конкретный backup. Убедитесь,
|
||||
что backend именно пересоздан: после замены bind-каталога простой stop/start
|
||||
может сохранить mount прежнего каталога и вернуть более новые данные.
|
||||
Проверьте owner `1000:1000`, `PRAGMA integrity_check`, доступность данных, auth, карты
|
||||
и расшифровку подключения. Убедитесь, что восстановленный image совместим
|
||||
со схемой; старые migration-файлы не являются down-migrations.
|
||||
|
||||
Только после проверки успешного recovery переместите `.deploy/pending` в файл
|
||||
инцидента, например `.deploy/recovered-UNIQUE_TIMESTAMP`, и повторите smoke.
|
||||
Если новый релиз ещё не установлен, текущая запись может оставаться прежней:
|
||||
следующее обновление сверяется с реальными контейнерами. Файл `lock` не удаляйте:
|
||||
блокировка освобождается процессом; удаление файла при живом процессе опасно.
|
||||
|
||||
## Резервные копии
|
||||
|
||||
Скрипт создаёт уникальный каталог и не перезаписывает прежние копии. Готовой
|
||||
копия становится только после полного успешного `cp -a`; частичные копии
|
||||
отмечены префиксом `.partial-`. Проверяйте восстановление на отдельной копии.
|
||||
|
||||
Очистка только ручная. Контролируйте свободное место; не удаляйте backup,
|
||||
нужный незавершённому обновлению. Копия на том же диске не защищает от его потери.
|
||||
Независимое хранение ключа обязательно; внешнее backup-хранилище сюда не входит.
|
||||
|
||||
## Выпуск комплекта и проверки
|
||||
|
||||
1. Дождитесь публикации компонентов в Nexus. В `release.env` закрепите ровно
|
||||
`FRONTEND_IMAGE` и `BACKEND_IMAGE` с version tags. Не используйте `latest`.
|
||||
2. Выполните локальный gate (Python 3 нужен только для tests):
|
||||
|
||||
```bash
|
||||
bash -n deploy.sh tests/deploy.sh
|
||||
bash tests/deploy.sh
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Tests используют настоящий Git, backup-файлы и file locks, а Docker заменяют
|
||||
управляемым double. Проверяются update/no-op, смена настроек, сохранение ключей,
|
||||
ошибки pull/backup/migration/readiness, конкуренция и незавершённая операция.
|
||||
Отдельная проверка реального Docker для restore: `bash tests/deploy.sh --docker-restore`.
|
||||
Она создаёт временный project, заменяет bind-каталог и проверяет пересоздание
|
||||
контейнера. На arm64 для проверки текущих amd64 images можно использовать
|
||||
`DOCKER_DEFAULT_PLATFORM=linux/amd64` при наличии Docker emulation.
|
||||
3. На disposable Linux-установке с настоящими images проверьте `--init`, adoption,
|
||||
frontend-only/backend-only update, повтор, backup/restore и HTTPS smoke.
|
||||
Сверьте OCI revision images с release commits компонентов. Для проверки
|
||||
кандидата используйте временный Git origin/tag, не двигайте опубликованный тег.
|
||||
4. После review и проверок примите commit в `main`, создайте аннотированный
|
||||
`vX.Y.Z` именно на проверенном commit и отправьте этот тег. Версия комплекта
|
||||
самостоятельная: одинаковые номера frontend/backend не требуются.
|
||||
|
||||
Выпуск компонента не выпускает комплект и не обновляет сервер автоматически.
|
||||
Опубликованные Git и Nexus tags не переназначаются. Для исправления нужен
|
||||
новый релиз комплекта. В репозитории не нужны npm, собственный release framework
|
||||
или pipeline, автоматически выбирающий последние образы.
|
||||
Reference in New Issue
Block a user