From febc88d37b68a1b402a8b5705f443c808ff2ea8b Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 20 Jun 2026 16:05:19 +0300 Subject: [PATCH] =?UTF-8?q?fix(ci):=20deploy-tradein=20paths-filter=20base?= =?UTF-8?q?=20=3D=20last=20deployed=20SHA=20(=D0=BD=D0=B0=D0=BA=D0=BE?= =?UTF-8?q?=D0=BF=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B=D0=B9=20diff,=20fail-safe?= =?UTF-8?q?=20build-all)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit При быстрых мержах dorny/paths-filter без explicit base диффает per-merge-commit (parent of merge), а не накопленно от последнего деплоя. Итог: backend-мерж #1829 не попадает в diff frontend-мержа #1830 → build-backend skipped, образа нет. Решение (host-file mechanism): - deploy-джоба пишет `echo "$GITHUB_SHA" > /opt/gendesign/.tradein-deployed-sha` в самом конце SSH-скрипта — только после успешного up+миграций. - changes-джоба читает файл по SSH (те же DEPLOY_* секреты), валидирует SHA как 40-char hex + git merge-base --is-ancestor HEAD, передаёт в `base:` paths-filter. - Если файл отсутствует / SHA не предок HEAD / SSH недоступен → FAIL-SAFE: step `set-all` эмитит все outputs=true, paths-filter пропускается, все образы собираются (better safe than sorry). Почему host-file, не git-ref: в workflows не используется `gitea.token` / `GITHUB_TOKEN` с contents:write — добавлять новое разрешение ради ref push ненужный риск. DEPLOY_* секреты уже есть, SSH уже есть. Не изменено: concurrency.cancel-in-progress=false, workflow_dispatch форс, логика build/deploy/migrations. --- .forgejo/workflows/deploy-tradein.yml | 85 +++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/.forgejo/workflows/deploy-tradein.yml b/.forgejo/workflows/deploy-tradein.yml index 1c10cf23..eafe2980 100644 --- a/.forgejo/workflows/deploy-tradein.yml +++ b/.forgejo/workflows/deploy-tradein.yml @@ -24,16 +24,82 @@ jobs: changes: runs-on: ubuntu-latest outputs: - backend: ${{ steps.filter.outputs.backend }} - frontend: ${{ steps.filter.outputs.frontend }} - browser: ${{ steps.filter.outputs.browser }} - infra: ${{ steps.filter.outputs.infra }} - scraper: ${{ steps.filter.outputs.scraper }} + backend: ${{ steps.set-all.outputs.backend || steps.filter.outputs.backend }} + frontend: ${{ steps.set-all.outputs.frontend || steps.filter.outputs.frontend }} + browser: ${{ steps.set-all.outputs.browser || steps.filter.outputs.browser }} + infra: ${{ steps.set-all.outputs.infra || steps.filter.outputs.infra }} + scraper: ${{ steps.set-all.outputs.scraper || steps.filter.outputs.scraper }} steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Resolve base SHA: read last-successfully-deployed SHA from the VPS host file. + # The file is written by the deploy job on every successful deploy. + # Fail-safe: if we cannot read the file, or the SHA is not an ancestor of HEAD, + # we leave DEPLOYED_SHA empty — the next step will then build everything. + - name: Resolve deployed base SHA + id: resolve-base + env: + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }} + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + run: | + # Write SSH key to a temp file + SSH_KEY_FILE=$(mktemp) + echo "$DEPLOY_SSH_KEY" > "$SSH_KEY_FILE" + chmod 600 "$SSH_KEY_FILE" + + # Try to read the marker file from the VPS. Suppress errors — if host is + # unreachable or file missing, RAW_SHA will be empty. + RAW_SHA=$(ssh -i "$SSH_KEY_FILE" \ + -o StrictHostKeyChecking=no \ + -o ConnectTimeout=10 \ + -p "${DEPLOY_PORT:-22}" \ + "${DEPLOY_USER}@${DEPLOY_HOST}" \ + "cat /opt/gendesign/.tradein-deployed-sha 2>/dev/null || true" \ + 2>/dev/null || true) + RAW_SHA=$(echo "$RAW_SHA" | tr -d '[:space:]') + + rm -f "$SSH_KEY_FILE" + + # Validate: non-empty, looks like a git SHA, and is an ancestor of HEAD. + DEPLOYED_SHA="" + if [ -n "$RAW_SHA" ] && echo "$RAW_SHA" | grep -qE '^[0-9a-f]{40}$'; then + if git merge-base --is-ancestor "$RAW_SHA" HEAD 2>/dev/null; then + DEPLOYED_SHA="$RAW_SHA" + echo "Resolved deployed base: $DEPLOYED_SHA" + else + echo "WARNING: stored SHA $RAW_SHA is not an ancestor of HEAD — falling back to build-all" + fi + else + echo "No valid deployed SHA found — falling back to build-all" + fi + + echo "deployed_sha=$DEPLOYED_SHA" >> "$GITHUB_OUTPUT" + + # FAIL-SAFE: if no valid base SHA, emit all=true and skip paths-filter. + # This covers: first run, post-force-push, VPS unreachable, corrupt marker. + # A spurious full build is always safer than a missed build. + - name: Build-all fallback (no base SHA) + id: set-all + if: steps.resolve-base.outputs.deployed_sha == '' + run: | + echo "No base SHA — enabling build-all" + echo "backend=true" >> "$GITHUB_OUTPUT" + echo "frontend=true" >> "$GITHUB_OUTPUT" + echo "browser=true" >> "$GITHUB_OUTPUT" + echo "infra=true" >> "$GITHUB_OUTPUT" + echo "scraper=true" >> "$GITHUB_OUTPUT" + + # Cumulative diff: compare deployed SHA → HEAD so that a fast chain of merges + # (e.g. backend #1829 then frontend #1830) doesn't lose earlier changes. - uses: dorny/paths-filter@v3 id: filter + if: steps.resolve-base.outputs.deployed_sha != '' with: + base: ${{ steps.resolve-base.outputs.deployed_sha }} filters: | backend: - 'tradein-mvp/backend/**' @@ -211,12 +277,13 @@ jobs: IMAGE_TAG: latest GHCR_PAT: ${{ secrets.GHCR_PAT }} SCRAPER_CHANGED: ${{ needs.changes.outputs.scraper == 'true' || needs.changes.outputs.infra == 'true' || github.event_name == 'workflow_dispatch' }} + GITHUB_SHA: ${{ github.sha }} with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} port: ${{ secrets.DEPLOY_PORT }} - envs: IMAGE_TAG,GHCR_PAT,SCRAPER_CHANGED + envs: IMAGE_TAG,GHCR_PAT,SCRAPER_CHANGED,GITHUB_SHA script: | set -euo pipefail cd /opt/gendesign @@ -371,3 +438,9 @@ jobs: | xargs -r docker rmi 2>/dev/null || true done docker image prune -af || true + + # Mark this SHA as successfully deployed. + # Written LAST — only after all of the above completed without error. + # The changes job reads this file on the next run to compute cumulative diff. + echo "$GITHUB_SHA" > /opt/gendesign/.tradein-deployed-sha + echo "→ Deployed SHA marker updated: $GITHUB_SHA"