From 33ef0f7330c184a828c5b00ce5a41628fd3838be Mon Sep 17 00:00:00 2001 From: bot-backend Date: Mon, 24 Aug 2026 03:48:44 +0300 Subject: [PATCH] =?UTF-8?q?chore(ci):=20=D1=81=D0=B8=D0=BD=D1=85=D1=80?= =?UTF-8?q?=D0=BE=D0=BD=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20/opt/gendesign=20=D0=BD=D0=B0=20=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=8E=D1=89=D0=B5=D0=BC=D1=81=D1=8F=20=D1=85=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit После переезда 30.08 `secrets.DEPLOY_HOST` станет указывать на Selectel — и рабочее дерево /opt/gendesign на Beget перестанет обновляться совсем. Молча: ни ошибки, ни падения пайплайна. А оттуда на Beget продолжат работать: - cron-скрипты бэкапов (backup.sh, backup-forgejo.sh, check-backup-staleness.sh, lib-backup.sh, docker-prune.sh) - docker-compose.prod.yml для Forgejo / GlitchTip / CouchDB - Caddyfile + caddy/sites/infra.caddy — единственный публичный вход для git / errors / obsidian (#3062) Любая будущая правка этих файлов легла бы в main и никогда не доехала до машины, которая их исполняет. Это класс #2887 («скрипт запускается по cron из /opt/gendesign, куда попадает только через жёсткую пересинхронизацию дерева на шаге деплоя»), но не на уровне одного файла, а на уровне целого хоста. Отдельный workflow, а не job в deploy.yml: разные адресаты и разные вердикты — «выкатили продукт на Selectel» и «синхронизировали инфру на Beget» независимы, и падение второго не должно читаться как неудавшийся деплой продукта. Триггеры тоже разные: инфра-хосту не нужны пересборки backend/frontend. ИНЕРТЕН, пока не задан INFRA_DEPLOY_HOST. Сегодня Beget и есть DEPLOY_HOST, второй проход по тому же хосту был бы лишним и мог бы состязаться с основным деплоем за докер-демон (#2950). Включается одной настройкой в момент, когда хосты разъедутся. Намеренно НЕ делает: прун образов (конкурирует за leases докер-демона, #2950) и перезапуск Forgejo/GlitchTip/CouchDB (правка ops-скрипта не повод ронять git). Caddy — reload, а не recreate: невалидный конфиг откажет громко, старый продолжит работать. Проверено: YAML разбирается, триггеры и paths те, что задуманы, шагов 2; гейт прогнан обеими ветками (пустой секрет -> enabled=false, заданный -> enabled=true). Refs #3059, #3057, #3029 --- .forgejo/workflows/deploy-infra.yml | 103 ++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 .forgejo/workflows/deploy-infra.yml diff --git a/.forgejo/workflows/deploy-infra.yml b/.forgejo/workflows/deploy-infra.yml new file mode 100644 index 00000000..5fb3df26 --- /dev/null +++ b/.forgejo/workflows/deploy-infra.yml @@ -0,0 +1,103 @@ +name: Deploy Infra Host + +# Синхронизация /opt/gendesign на ХОСТЕ, КОТОРЫЙ ОСТАЁТСЯ (#3059, #3057). +# +# ЗАЧЕМ. Сегодня Beget — и прод, и инфраструктура одновременно, поэтому его +# рабочее дерево обновляет обычный `deploy.yml` (шаг `git reset --hard +# origin/main` по SSH на `secrets.DEPLOY_HOST`). После переезда 30.08 +# `DEPLOY_HOST` станет указывать на Selectel — и /opt/gendesign на Beget +# перестанет обновляться СОВСЕМ. Молча. +# +# А из этого каталога на Beget продолжат работать: +# - cron-скрипты бэкапов: ops/backup.sh, ops/backup-forgejo.sh, +# ops/check-backup-staleness.sh, ops/lib-backup.sh, ops/docker-prune.sh +# - docker-compose.prod.yml для Forgejo / GlitchTip / CouchDB +# - Caddyfile + caddy/sites/infra.caddy — единственный публичный вход для +# git.gendsgn.ru, errors.gendsgn.ru, obsidian.gendsgn.ru (#3062) +# +# То есть любая будущая правка этих файлов легла бы в main и никогда не доехала +# до машины, которая их исполняет. Это ровно класс #2887 («скрипт запускается по +# cron из /opt/gendesign, куда попадает только через git reset --hard шага +# деплоя»), но не на уровне одного файла, а на уровне целого хоста. +# +# ПОЧЕМУ ОТДЕЛЬНЫЙ WORKFLOW, А НЕ JOB В deploy.yml. Разные адресаты и разные +# вердикты: «выкатили приложение на Selectel» и «синхронизировали инфраструктуру +# на Beget» — два независимых факта, и падение второго не должно читаться как +# неудавшийся деплой продукта. Плюс триггеры разные: инфра-хосту не нужны +# пересборки backend/frontend. +# +# ИНЕРТЕН, ПОКА НЕ ЗАДАН INFRA_DEPLOY_HOST. Сейчас, до переезда, Beget и есть +# DEPLOY_HOST — второй проход по тому же хосту был бы лишним и мог бы состязаться +# с основным деплоем за докер-демон (#2950). Поэтому job не делает ничего, пока +# секрет пуст: включается ОДНОЙ настройкой в момент, когда хосты разъедутся. +on: + push: + branches: [main] + paths: + # Ровно то, что исполняется НА ОСТАЮЩЕМСЯ хосте. Намеренно НЕ включены + # backend/** и frontend/** — их образы туда не едут. + - "ops/*.sh" + - "Caddyfile" + - "caddy/**" + - "docker-compose.prod.yml" + - "docker-compose.obsidian.yml" + - ".forgejo/workflows/deploy-infra.yml" + workflow_dispatch: + +jobs: + sync-infra-host: + runs-on: ubuntu-latest + steps: + - name: Проверить, разъехались ли хосты + id: gate + env: + INFRA_HOST: ${{ secrets.INFRA_DEPLOY_HOST }} + run: | + set -euo pipefail + if [ -z "${INFRA_HOST:-}" ]; then + echo "enabled=false" >> "$GITHUB_OUTPUT" + echo "INFRA_DEPLOY_HOST не задан — хосты ещё не разъехались." + echo "Инфраструктуру обновляет обычный deploy.yml. Ничего не делаю." + else + echo "enabled=true" >> "$GITHUB_OUTPUT" + echo "INFRA_DEPLOY_HOST задан — синхронизирую остающийся хост." + fi + + - name: Синхронизировать /opt/gendesign на остающемся хосте + if: steps.gate.outputs.enabled == 'true' + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.INFRA_DEPLOY_HOST }} + username: ${{ secrets.INFRA_DEPLOY_USER || secrets.DEPLOY_USER }} + key: ${{ secrets.INFRA_DEPLOY_SSH_KEY || secrets.DEPLOY_SSH_KEY }} + port: ${{ secrets.INFRA_DEPLOY_PORT || secrets.DEPLOY_PORT }} + script: | + set -euo pipefail + cd /opt/gendesign + git fetch origin main + git reset --hard origin/main + + # Тот же chmod, что и в deploy.yml: cron зовёт скрипты через `bash`, + # но restore-drill и ручные запуски рассчитывают на +x. + chmod +x ops/*.sh 2>/dev/null || true + + # Caddy здесь несёт ТОЛЬКО остающиеся домены (CADDY_SITES=infra). + # reload, а не recreate: конфиг примонтирован read-only, контейнер + # читает тот же файл, который только что обновил git. Если конфиг + # невалиден — reload откажет ГРОМКО, а старый останется работать, + # то есть падение здесь не роняет git/errors/obsidian. + if docker ps --format '{{.Names}}' | grep -q '^gendesign-caddy-1$'; then + docker compose -p gendesign -f docker-compose.prod.yml exec -T caddy \ + caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile + echo "✓ конфиг прокси перезагружен" + else + echo "⚠ контейнер caddy не найден — пропускаю reload" + fi + + # НАМЕРЕННО НЕ ДЕЛАЕТСЯ: + # - docker image prune: конкурирует с деплоем продукта за leases + # докер-демона (#2950). Прун на этом хосте остаётся за + # еженедельным ops/docker-prune.sh. + # - перезапуск Forgejo / GlitchTip / CouchDB: правка ops-скрипта + # не повод ронять git. Их обновление — осознанное действие. + echo "✓ рабочее дерево синхронизировано: $(git rev-parse --short HEAD)" -- 2.45.3