# 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 `; перед передачей логов убедитесь, что в них нет чувствительных данных. ## Ручной откат и восстановление `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, автоматически выбирающий последние образы.