deploy-caddy: сторож отставания прода, общий лок и квотирование путей (#3448)
Оживший быстрый путь снимает гард свежести :latest. Пока caddy_only был мёртв, любой push шёл полным деплоем, и гард #2950 прикрывал прод по умолчанию. Теперь при caddy_only=true джоба `deploy` пропускается целиком — вместе с гардом. Сценарий отказа. Push A правит бэкенд, билды ~6 мин, `deploy` в очереди. Через 2 мин push B правит только caddy/. На Forgejo 10.0.3 ещё не стартовавшая `deploy` предыдущего прогона отменяется ДАЖЕ при cancel-in-progress: false (наблюдение 21.08.2026 10:35:13, шапка scripts/check-latest-image-revision.sh; workflow-level concurrency там не исполняется, см. deploy.yml). Дифф A..B — один caddy-файл, быстрый путь включается, `compose pull` + `up -d` не делает никто: прод крутит старый образ, голова main зелёная, сигнала нет. Сторож в ssh-скрипте deploy-caddy: если прод отстаёт от origin/main не только по Caddyfile/caddy/**, быстрый путь запрещён, шаг падает и называет файлы. Стоит ДО `git reset --hard` намеренно — при отказе прод-HEAD остаётся честным для следующего прогона. У Trade-In для того же заведён отдельный маркер (/opt/gendesign/.tradein-deployed-sha, deploy-tradein.yml), у ПТИЦЫ маркера нет, и `git reset` делает прод-HEAD его эквивалентом. ЧЕГО СТОРОЖ НЕ ЛОВИТ: `deploy`, упавшую ПОСЛЕ `git reset --hard` (например на миграции). Тогда прод-HEAD уже равен новому коммиту, а контейнеры старые — это остаётся за настоящим маркером «что задеплоено». Тот же лок, что и у полного деплоя. deploy-caddy делает `git reset --hard` в /opt/gendesign, то есть правит прод-дерево — ровно то, что job `deploy` сериализует через flock /var/lock/gendesign-docker-deploy.lock. Пока путь был мёртв, сталкиваться было нечему; теперь это первая джоба, трогающая прод-дерево в обход сериализации. Квотирование путей. `git diff --name-only` и `git ls-files` при core.quotePath (умолчание true) отдают не-ASCII пути закавыченными с \NNN-экранированием — `^backend/` такую строку не матчит, и файл backend/<кириллица>.py дал бы backend=false. Старый paths-filter брал `--name-status -z`, где квотирования нет: это единственное место, где переход на свой diff менял поведение. В дереве такие пути уже живут (docs/Бизнес-план…). Добавлен `-c core.quotePath=false` в обе команды и в сторож выше. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
091137a0c5
commit
9a8aaa2d2d
1 changed files with 56 additions and 2 deletions
|
|
@ -181,12 +181,12 @@ jobs:
|
|||
fi
|
||||
|
||||
if [ -n "$BASE" ]; then
|
||||
FILES=$(git diff --no-renames --name-only "$BASE" HEAD)
|
||||
FILES=$(git -c core.quotePath=false diff --no-renames --name-only "$BASE" HEAD)
|
||||
N=$(printf '%s\n' "$FILES" | grep -c . || true)
|
||||
echo "База: $BASE → $(git rev-parse HEAD); изменённых файлов: $N"
|
||||
printf '%s\n' "$FILES" | sed 's/^/ /'
|
||||
else
|
||||
FILES=$(git ls-files)
|
||||
FILES=$(git -c core.quotePath=false ls-files)
|
||||
N=$(printf '%s\n' "$FILES" | grep -c . || true)
|
||||
echo "База не определена (event=$EVENT, before='${BEFORE:-}') — считаем изменённым весь репозиторий ($N файлов), деплой полный"
|
||||
fi
|
||||
|
|
@ -1343,8 +1343,62 @@ jobs:
|
|||
fingerprint: ${{ secrets.DEPLOY_SSH_FINGERPRINT }}
|
||||
script: |
|
||||
set -euo pipefail
|
||||
# #3448: ТОТ ЖЕ ЛОК, что берёт полный деплой (см. job `deploy` выше).
|
||||
# Эта джоба делает `git reset --hard` в /opt/gendesign, то есть правит
|
||||
# прод-дерево — ровно то, что полный деплой сериализует локом. Пока
|
||||
# быстрый путь был мёртв, столкнуться было нечему; теперь есть.
|
||||
exec 9>/var/lock/gendesign-docker-deploy.lock
|
||||
if flock -n 9; then
|
||||
echo "→ докер-лок свободен, взят сразу"
|
||||
else
|
||||
echo "→ докер-лок занят соседним деплоем, жду (до 900с)…"
|
||||
lock_wait_started=$(date +%s)
|
||||
if ! flock -w 900 9; then
|
||||
echo "ERROR: не дождался лока докер-деплоя за 900с."
|
||||
echo " Кто держит: ssh на хост, затем fuser -v /var/lock/gendesign-docker-deploy.lock"
|
||||
exit 1
|
||||
fi
|
||||
echo "→ докер-лок получен через $(( $(date +%s) - lock_wait_started ))с ожидания"
|
||||
fi
|
||||
cd /opt/gendesign
|
||||
git fetch origin main
|
||||
|
||||
# ── #3448: быстрый путь законен, только если прод отстаёт РОВНО на
|
||||
# конфиг прокси ────────────────────────────────────────────────────
|
||||
#
|
||||
# Джоба `changes` считает дифф between-push (before→HEAD) и не знает,
|
||||
# что доехало до прода. Пока caddy_only был мёртв, любой push шёл
|
||||
# полным деплоем и гард свежести :latest (#2950, job `deploy`)
|
||||
# прикрывал прод по умолчанию. Оживший быстрый путь этот гард
|
||||
# обходит: при caddy_only=true джоба `deploy` пропускается целиком.
|
||||
#
|
||||
# Сценарий отказа: push A правит бэкенд, билды ~6 мин, `deploy` в
|
||||
# очереди; через 2 мин push B правит только caddy/. Forgejo на
|
||||
# 10.0.3 отменяет ещё не стартовавший `deploy` предыдущего прогона
|
||||
# ДАЖЕ при cancel-in-progress: false (наблюдение 21.08.2026 10:35:13,
|
||||
# см. шапку scripts/check-latest-image-revision.sh). Дифф A..B — один
|
||||
# caddy-файл, быстрый путь включается, `compose pull` + `up -d` не
|
||||
# делает никто: прод крутит старый образ при зелёной голове main.
|
||||
#
|
||||
# Единственный источник правды о том, что реально на проде, — HEAD
|
||||
# прод-дерева (у Trade-In для этого заведён отдельный маркер
|
||||
# /opt/gendesign/.tradein-deployed-sha, см. deploy-tradein.yml:150;
|
||||
# у ПТИЦЫ маркера нет, но git reset ниже делает HEAD эквивалентом).
|
||||
# Проверка стоит ДО reset намеренно: при отказе прод-HEAD остаётся
|
||||
# честным для следующего прогона.
|
||||
#
|
||||
# ЧЕГО ЭТА ПРОВЕРКА НЕ ЛОВИТ: `deploy` прогона A, упавшую ПОСЛЕ
|
||||
# `git reset --hard` (например на миграции). Тогда прод-HEAD уже
|
||||
# равен A, а контейнеры старые, и caddy-only push пройдёт быстрым
|
||||
# путём. Это остаётся за настоящим маркером «что задеплоено».
|
||||
PROD_HEAD=$(git rev-parse HEAD)
|
||||
OUTSIDE=$(git -c core.quotePath=false diff --name-only "$PROD_HEAD" origin/main | grep -vE '^(Caddyfile$|caddy/)' || true)
|
||||
if [ -n "$OUTSIDE" ]; then
|
||||
echo "::error::прод отстаёт не только по конфигу прокси — быстрый путь запрещён:"
|
||||
printf '%s\n' "$OUTSIDE" | sed 's/^/ /'
|
||||
echo "Запусти полный деплой через workflow_dispatch."
|
||||
exit 1
|
||||
fi
|
||||
git reset --hard origin/main
|
||||
# Конфиг примонтирован read-only с хоста, пересборка не нужна —
|
||||
# контейнер читает тот же файл, что только что обновил git.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue