From 9b4584f70a141c5e49e56e6d4673ae1b0513a164 Mon Sep 17 00:00:00 2001 From: Archicratia Date: Thu, 26 Feb 2026 17:39:51 +0100 Subject: [PATCH] ci: fix deploy workflow (workflow_dispatch checkout + gate) --- .gitea/workflows/deploy-staging-live.yml | 114 ++++++++++++++++------- 1 file changed, 78 insertions(+), 36 deletions(-) diff --git a/.gitea/workflows/deploy-staging-live.yml b/.gitea/workflows/deploy-staging-live.yml index bed9635..8fb62d3 100644 --- a/.gitea/workflows/deploy-staging-live.yml +++ b/.gitea/workflows/deploy-staging-live.yml @@ -40,45 +40,79 @@ jobs: - name: Checkout (from event.json, no external actions) run: | set -euo pipefail - export EVENT_JSON="/var/run/act/workflow/event.json" + EVENT_JSON="/var/run/act/workflow/event.json" test -f "$EVENT_JSON" || { echo "❌ Missing $EVENT_JSON"; exit 1; } - eval "$(node --input-type=module -e 'import fs from "node:fs"; + # Node prints REPO_URL, DEFAULT_BRANCH, REF, SHA_CAND (may be empty in workflow_dispatch) + OUT="$(node --input-type=module -e ' + import fs from "node:fs"; const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON,"utf8")); + + const repoObj = ev?.repository || {}; const repo = - ev?.repository?.clone_url || - (ev?.repository?.html_url ? (ev.repository.html_url.replace(/\/$/,"") + ".git") : ""); + repoObj?.clone_url || + (repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : ""); + + if (!repo) throw new Error("No repository url in event.json"); + + const defaultBranch = repoObj?.default_branch || "main"; + + const ref = + ev?.ref || `refs/heads/${defaultBranch}`; + const sha = ev?.after || ev?.pull_request?.head?.sha || ev?.head_commit?.id || ev?.sha || ""; - if (!repo) throw new Error("No repository url in event.json"); - if (!sha) throw new Error("No sha in event.json"); - process.stdout.write(`REPO_URL=${JSON.stringify(repo)}\nSHA=${JSON.stringify(sha)}\n`); - ')" - echo "Repo URL: $REPO_URL" - echo "SHA: $SHA" + process.stdout.write( + `REPO_URL=${JSON.stringify(repo)}\n` + + `DEFAULT_BRANCH=${JSON.stringify(defaultBranch)}\n` + + `REF=${JSON.stringify(ref)}\n` + + `SHA_CAND=${JSON.stringify(sha)}\n` + ); + ' EVENT_JSON="$EVENT_JSON")" || { echo "❌ Cannot parse event.json"; exit 1; }" + + eval "$OUT" + + echo "Repo URL: $REPO_URL" + echo "Default branch: $DEFAULT_BRANCH" + echo "Ref: $REF" + echo "SHA candidate: ${SHA_CAND:-}" rm -rf .git git init -q git remote add origin "$REPO_URL" - git fetch --depth 1 origin "$SHA" - git -c advice.detachedHead=false checkout -q FETCH_HEAD - git log -1 --oneline + if [[ -n "${SHA_CAND:-}" ]]; then + echo "Checkout by SHA: $SHA_CAND" + git fetch --depth 1 origin "$SHA_CAND" + git -c advice.detachedHead=false checkout -q FETCH_HEAD + else + # workflow_dispatch often has no SHA; fetch by ref/branch + REF_TO_FETCH="$REF" + if [[ "$REF_TO_FETCH" == refs/heads/* ]]; then + REF_TO_FETCH="${REF_TO_FETCH#refs/heads/}" + fi + echo "Checkout by ref: $REF_TO_FETCH" + git fetch --depth 1 origin "$REF_TO_FETCH" + git -c advice.detachedHead=false checkout -q FETCH_HEAD + fi + + SHA="$(git rev-parse HEAD)" + git log -1 --oneline echo "SHA=$SHA" >> /tmp/deploy.env echo "REPO_URL=$REPO_URL" >> /tmp/deploy.env + echo "DEFAULT_BRANCH=$DEFAULT_BRANCH" >> /tmp/deploy.env - name: Gate — auto deploy only on annotations/media changes env: INPUT_FORCE: ${{ inputs.force }} run: | set -euo pipefail - - source /tmp/deploy.env 2>/dev/null || true + source /tmp/deploy.env FORCE="${INPUT_FORCE:-0}" if [[ "$FORCE" == "1" ]]; then @@ -87,28 +121,37 @@ jobs: exit 0 fi - # fichiers touchés par CE commit (⚠️ merge commits -> utiliser -m) - CHANGED="$(git diff-tree -m --no-commit-id --name-only -r "$SHA" || true)" + # Robust changed-files list (merge commits included) + # Prefer diff vs first parent; fallback to git show. + if git rev-parse "${SHA}^" >/dev/null 2>&1; then + CHANGED="$(git diff --name-only "${SHA}^" "$SHA" || true)" + else + CHANGED="" + fi + if [[ -z "$CHANGED" ]]; then + CHANGED="$(git show --name-only --pretty="" -m "$SHA" | sed '/^$/d' || true)" + fi + echo "== changed files ==" echo "$CHANGED" | sed -n '1,200p' - # Gate strict : uniquement annotations + media if echo "$CHANGED" | grep -qE '^(src/annotations/|public/media/)'; then echo "GO=1" >> /tmp/deploy.env echo "✅ deploy allowed (annotations/media change detected)" - exit 0 + else + echo "GO=0" >> /tmp/deploy.env + echo "ℹ️ no annotations/media change -> skip deploy" fi - echo "GO=0" >> /tmp/deploy.env - echo "ℹ️ no annotations/media change -> skip deploy" - exit 0 - - name: Install docker client + docker compose plugin (v2) run: | set -euo pipefail source /tmp/deploy.env [[ "${GO:-0}" == "1" ]] || { echo "ℹ️ skipped"; exit 0; } + # Must have docker socket mounted by runner + test -S /var/run/docker.sock || { echo "❌ /var/run/docker.sock missing in job container"; exit 10; } + apt-get -o Acquire::Retries=5 -o Acquire::ForceIPv4=true update apt-get install -y --no-install-recommends ca-certificates curl docker.io rm -rf /var/lib/apt/lists/* @@ -158,26 +201,22 @@ jobs: source /tmp/deploy.env [[ "${GO:-0}" == "1" ]] || { echo "ℹ️ skipped"; exit 0; } - # backup tags (best effort) TS="$(date -u +%Y%m%d-%H%M%S)" echo "TS=$TS" >> /tmp/deploy.env + docker image tag archicratie-web:blue "archicratie-web:blue.BAK.${TS}" || true docker image tag archicratie-web:green "archicratie-web:green.BAK.${TS}" || true - # build + restart staging (blue=8081) docker compose build --no-cache web_blue docker compose up -d --force-recreate web_blue - # smoke staging (local port) curl -fsS "http://127.0.0.1:8081/para-index.json" >/dev/null curl -fsS "http://127.0.0.1:8081/annotations-index.json" >/dev/null curl -fsS "http://127.0.0.1:8081/pagefind/pagefind.js" >/dev/null CANON="$(curl -fsS "http://127.0.0.1:8081/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)" echo "canonical(blue)=$CANON" - echo "$CANON" | grep -q 'https://staging\.archicratie\.trans-hands\.synology\.me/' || { - echo "❌ staging canonical mismatch"; exit 3; - } + echo "$CANON" | grep -q 'https://staging\.archicratie\.trans-hands\.synology\.me/' || { echo "❌ staging canonical mismatch"; exit 3; } echo "✅ staging OK" @@ -201,17 +240,20 @@ jobs: set +e docker compose build --no-cache web_green - docker compose up -d --force-recreate web_green + BRC=$? + [[ "$BRC" -eq 0 ]] || { echo "❌ build green failed"; rollback; exit 4; } - curl -fsS "http://127.0.0.1:8082/para-index.json" >/dev/null - curl -fsS "http://127.0.0.1:8082/annotations-index.json" >/dev/null - curl -fsS "http://127.0.0.1:8082/pagefind/pagefind.js" >/dev/null + docker compose up -d --force-recreate web_green + URC=$? + [[ "$URC" -eq 0 ]] || { echo "❌ up green failed"; rollback; exit 4; } + + curl -fsS "http://127.0.0.1:8082/para-index.json" >/dev/null || { rollback; exit 4; } + curl -fsS "http://127.0.0.1:8082/annotations-index.json" >/dev/null || { rollback; exit 4; } + curl -fsS "http://127.0.0.1:8082/pagefind/pagefind.js" >/dev/null || { rollback; exit 4; } CANON="$(curl -fsS "http://127.0.0.1:8082/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)" echo "canonical(green)=$CANON" - echo "$CANON" | grep -q 'https://archicratie\.trans-hands\.synology\.me/' || { - echo "❌ live canonical mismatch"; rollback; exit 4; - } + echo "$CANON" | grep -q 'https://archicratie\.trans-hands\.synology\.me/' || { echo "❌ live canonical mismatch"; rollback; exit 4; } echo "✅ live OK" set -e \ No newline at end of file