Compare commits

...

2 Commits

Author SHA1 Message Date
0d5b790e52 ci: fix deploy checkout (no eval) + workflow_dispatch fallback
All checks were successful
CI / build-and-anchors (push) Successful in 1m50s
SMOKE / smoke (push) Successful in 17s
2026-02-26 18:48:12 +01:00
4dec9e182b ci: fix deploy checkout (no eval) + workflow_dispatch fallback
All checks were successful
CI / build-and-anchors (push) Successful in 1m38s
SMOKE / smoke (push) Successful in 18s
2026-02-26 17:58:58 +01:00

View File

@@ -37,75 +37,60 @@ jobs:
node --version node --version
npm --version npm --version
- name: Checkout (from event.json, no external actions) - name: Checkout (push or workflow_dispatch, no external actions)
env:
EVENT_JSON: /var/run/act/workflow/event.json
run: | run: |
set -euo pipefail set -euo pipefail
EVENT_JSON="/var/run/act/workflow/event.json"
test -f "$EVENT_JSON" || { echo "❌ Missing $EVENT_JSON"; exit 1; } test -f "$EVENT_JSON" || { echo "❌ Missing $EVENT_JSON"; exit 1; }
# Node prints REPO_URL, DEFAULT_BRANCH, REF, SHA_CAND (may be empty in workflow_dispatch) node --input-type=module <<'NODE'
OUT="$(node --input-type=module -e ' import fs from "node:fs";
import fs from "node:fs"; const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON, "utf8"));
const ev = JSON.parse(fs.readFileSync(process.env.EVENT_JSON,"utf8")); const repoObj = ev?.repository || {};
const cloneUrl =
repoObj?.clone_url ||
(repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : "");
if (!cloneUrl) throw new Error("No repository clone_url/html_url in event.json");
const repoObj = ev?.repository || {}; const defaultBranch = repoObj?.default_branch || "main";
const repo = const sha =
repoObj?.clone_url || (process.env.GITHUB_SHA && String(process.env.GITHUB_SHA).trim()) ||
(repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : ""); ev?.after ||
ev?.sha ||
ev?.head_commit?.id ||
ev?.pull_request?.head?.sha ||
"";
if (!repo) throw new Error("No repository url in event.json"); const shq = (s) => "'" + String(s).replace(/'/g, "'\\''") + "'";
fs.writeFileSync("/tmp/deploy.env", [
`REPO_URL=${shq(cloneUrl)}`,
`DEFAULT_BRANCH=${shq(defaultBranch)}`,
`SHA=${shq(sha)}`
].join("\n") + "\n");
NODE
const defaultBranch = repoObj?.default_branch || "main"; source /tmp/deploy.env
echo "Repo URL: $REPO_URL"
const ref = echo "Default branch: $DEFAULT_BRANCH"
ev?.ref || `refs/heads/${defaultBranch}`; echo "SHA: ${SHA:-<empty>}"
const sha =
ev?.after ||
ev?.pull_request?.head?.sha ||
ev?.head_commit?.id ||
ev?.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:-<empty>}"
rm -rf .git rm -rf .git
git init -q git init -q
git remote add origin "$REPO_URL" git remote add origin "$REPO_URL"
if [[ -n "${SHA_CAND:-}" ]]; then if [[ -n "${SHA:-}" ]]; then
echo "Checkout by SHA: $SHA_CAND" git fetch --depth 1 origin "$SHA"
git fetch --depth 1 origin "$SHA_CAND"
git -c advice.detachedHead=false checkout -q FETCH_HEAD git -c advice.detachedHead=false checkout -q FETCH_HEAD
else else
# workflow_dispatch often has no SHA; fetch by ref/branch git fetch --depth 1 origin "$DEFAULT_BRANCH"
REF_TO_FETCH="$REF" git -c advice.detachedHead=false checkout -q "origin/$DEFAULT_BRANCH"
if [[ "$REF_TO_FETCH" == refs/heads/* ]]; then SHA="$(git rev-parse HEAD)"
REF_TO_FETCH="${REF_TO_FETCH#refs/heads/}" echo "SHA='$SHA'" >> /tmp/deploy.env
fi echo "Resolved SHA: $SHA"
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 fi
SHA="$(git rev-parse HEAD)"
git log -1 --oneline 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 - name: Gate — auto deploy only on annotations/media changes
env: env:
@@ -121,19 +106,9 @@ jobs:
exit 0 exit 0
fi fi
# Robust changed-files list (merge commits included) CHANGED="$(git show --name-only --pretty="" "$SHA" | sed '/^$/d' || true)"
# 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 files =="
echo "$CHANGED" | sed -n '1,200p' echo "$CHANGED" | sed -n '1,240p'
if echo "$CHANGED" | grep -qE '^(src/annotations/|public/media/)'; then if echo "$CHANGED" | grep -qE '^(src/annotations/|public/media/)'; then
echo "GO=1" >> /tmp/deploy.env echo "GO=1" >> /tmp/deploy.env
@@ -149,9 +124,6 @@ jobs:
source /tmp/deploy.env source /tmp/deploy.env
[[ "${GO:-0}" == "1" ]] || { echo " skipped"; exit 0; } [[ "${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 -o Acquire::Retries=5 -o Acquire::ForceIPv4=true update
apt-get install -y --no-install-recommends ca-certificates curl docker.io apt-get install -y --no-install-recommends ca-certificates curl docker.io
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@@ -165,6 +137,15 @@ jobs:
docker version docker version
docker compose version docker compose version
# 🔥 KEY FIX: reuse existing compose project name if containers already exist
PROJ="$(docker inspect archicratie-web-blue --format '{{ index .Config.Labels "com.docker.compose.project" }}' 2>/dev/null || true)"
if [[ -z "${PROJ:-}" ]]; then
PROJ="$(docker inspect archicratie-web-green --format '{{ index .Config.Labels "com.docker.compose.project" }}' 2>/dev/null || true)"
fi
if [[ -z "${PROJ:-}" ]]; then PROJ="archicratie-web"; fi
echo "COMPOSE_PROJECT_NAME='$PROJ'" >> /tmp/deploy.env
echo "✅ Using COMPOSE_PROJECT_NAME=$PROJ"
- name: Assert required vars (PUBLIC_GITEA_*) - name: Assert required vars (PUBLIC_GITEA_*)
env: env:
PUBLIC_GITEA_BASE: ${{ vars.PUBLIC_GITEA_BASE }} PUBLIC_GITEA_BASE: ${{ vars.PUBLIC_GITEA_BASE }}
@@ -200,15 +181,20 @@ jobs:
set -euo pipefail set -euo pipefail
source /tmp/deploy.env source /tmp/deploy.env
[[ "${GO:-0}" == "1" ]] || { echo " skipped"; exit 0; } [[ "${GO:-0}" == "1" ]] || { echo " skipped"; exit 0; }
PROJ="${COMPOSE_PROJECT_NAME:-archicratie-web}"
TS="$(date -u +%Y%m%d-%H%M%S)" TS="$(date -u +%Y%m%d-%H%M%S)"
echo "TS=$TS" >> /tmp/deploy.env echo "TS='$TS'" >> /tmp/deploy.env
docker image tag archicratie-web:blue "archicratie-web:blue.BAK.${TS}" || true docker image tag archicratie-web:blue "archicratie-web:blue.BAK.${TS}" || true
docker image tag archicratie-web:green "archicratie-web:green.BAK.${TS}" || true docker image tag archicratie-web:green "archicratie-web:green.BAK.${TS}" || true
docker compose build --no-cache web_blue # ✅ use cache (DO NOT --no-cache)
docker compose up -d --force-recreate web_blue docker compose -p "$PROJ" -f docker-compose.yml build web_blue
# ✅ hard fix: remove existing container if name conflicts
docker rm -f archicratie-web-blue || true
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_blue
curl -fsS "http://127.0.0.1:8081/para-index.json" >/dev/null 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/annotations-index.json" >/dev/null
@@ -216,7 +202,9 @@ jobs:
CANON="$(curl -fsS "http://127.0.0.1:8081/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)" 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 "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" echo "✅ staging OK"
@@ -229,31 +217,31 @@ jobs:
set -euo pipefail set -euo pipefail
source /tmp/deploy.env source /tmp/deploy.env
[[ "${GO:-0}" == "1" ]] || { echo " skipped"; exit 0; } [[ "${GO:-0}" == "1" ]] || { echo " skipped"; exit 0; }
PROJ="${COMPOSE_PROJECT_NAME:-archicratie-web}"
TS="${TS:-$(date -u +%Y%m%d-%H%M%S)}" TS="${TS:-$(date -u +%Y%m%d-%H%M%S)}"
rollback() { rollback() {
echo "⚠️ rollback green -> previous image tag (best effort)" echo "⚠️ rollback green -> previous image tag (best effort)"
docker image tag "archicratie-web:green.BAK.${TS}" archicratie-web:green || true docker image tag "archicratie-web:green.BAK.${TS}" archicratie-web:green || true
docker compose up -d --force-recreate web_green || true docker rm -f archicratie-web-green || true
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green || true
} }
set +e set +e
docker compose build --no-cache web_green docker compose -p "$PROJ" -f docker-compose.yml build web_green
BRC=$?
[[ "$BRC" -eq 0 ]] || { echo "❌ build green failed"; rollback; exit 4; }
docker compose up -d --force-recreate web_green docker rm -f archicratie-web-green || true
URC=$? docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green
[[ "$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/para-index.json" >/dev/null
curl -fsS "http://127.0.0.1:8082/annotations-index.json" >/dev/null || { rollback; exit 4; } 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 || { rollback; exit 4; } curl -fsS "http://127.0.0.1:8082/pagefind/pagefind.js" >/dev/null
CANON="$(curl -fsS "http://127.0.0.1:8082/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)" 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 "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" echo "✅ live OK"
set -e set -e