Compare commits

...

5 Commits

Author SHA1 Message Date
390f2c33e5 ci: fix deploy workflow (warmup + hotpatch)
All checks were successful
CI / build-and-anchors (push) Successful in 2m2s
SMOKE / smoke (push) Successful in 17s
2026-02-26 21:06:01 +01:00
16485dc4a9 Merge pull request 'ci: shard annotations by para + deploy deep-merge hotpatch' (#135) from chore/anno-shard-deepmerge into main
Some checks failed
CI / build-and-anchors (push) Successful in 1m54s
Deploy staging+live (annotations) / deploy (push) Failing after 7m20s
SMOKE / smoke (push) Successful in 13s
Reviewed-on: #135
2026-02-26 19:58:22 +01:00
0519ae2dd0 Merge pull request 'ci: fix deploy checkout (no eval) + workflow_dispatch fallback' (#134) from chore/fix-deploy-container-conflict into main
All checks were successful
CI / build-and-anchors (push) Successful in 1m52s
Deploy staging+live (annotations) / deploy (push) Successful in 8m48s
SMOKE / smoke (push) Successful in 19s
Reviewed-on: #134
2026-02-26 18:51:53 +01:00
342e21b9ea Merge pull request 'ci: fix deploy checkout (no eval) + workflow_dispatch fallback' (#133) from chore/fix-deploy-checkout-quote into main
Some checks failed
CI / build-and-anchors (push) Successful in 1m40s
Deploy staging+live (annotations) / deploy (push) Failing after 10m11s
SMOKE / smoke (push) Successful in 16s
Reviewed-on: #133
2026-02-26 18:01:24 +01:00
c7ae883c6a Merge pull request 'ci: fix deploy workflow (workflow_dispatch checkout + gate)' (#132) from chore/fix-deploy-workflow into main
Some checks failed
CI / build-and-anchors (push) Successful in 1m37s
Deploy staging+live (annotations) / deploy (push) Failing after 19s
SMOKE / smoke (push) Successful in 17s
Reviewed-on: #132
2026-02-26 17:44:28 +01:00

View File

@@ -106,7 +106,8 @@ jobs:
exit 0
fi
CHANGED="$(git show --name-only --pretty="" "$SHA" | sed '/^$/d' || true)"
# merge commit safe: -m => considère les parents (liste de fichiers plus fiable)
CHANGED="$(git show -m --name-only --pretty="" "$SHA" | sed '/^$/d' || true)"
echo "== changed files =="
echo "$CHANGED" | sed -n '1,240p'
@@ -184,27 +185,68 @@ jobs:
[[ "${GO:-0}" == "1" ]] || { echo " skipped"; exit 0; }
PROJ="${COMPOSE_PROJECT_NAME:-archicratie-web}"
retry_url() {
local url="$1"; local tries="${2:-45}"; local delay="${3:-1}"
local i=1
while (( i <= tries )); do
if curl -fsS --max-time 5 --retry 2 --retry-delay 0 --retry-all-errors "$url" >/dev/null 2>&1; then
return 0
fi
echo "… wait [$i/$tries] $url"
sleep "$delay"
((i++))
done
return 1
}
fetch_html() {
local url="$1"; local tries="${2:-45}"; local delay="${3:-1}"
local i=1
while (( i <= tries )); do
local html
html="$(curl -fsS --max-time 8 --retry 2 --retry-delay 0 --retry-all-errors "$url" 2>/dev/null || true)"
if [[ -n "$html" ]]; then
printf "%s" "$html"
return 0
fi
echo "… wait HTML [$i/$tries] $url"
sleep "$delay"
((i++))
done
return 1
}
dump_container() {
local name="$1"
echo "== docker ps (filter=$name) =="
docker ps -a --filter "name=$name" --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' || true
echo "== docker logs (tail) $name =="
docker logs --tail 200 "$name" 2>/dev/null || true
}
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
# ✅ use cache (DO NOT --no-cache)
docker compose -p "$PROJ" -f docker-compose.yml config >/dev/null
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/annotations-index.json" >/dev/null
curl -fsS "http://127.0.0.1:8081/pagefind/pagefind.js" >/dev/null
retry_url "http://127.0.0.1:8081/para-index.json" 60 1 || { dump_container archicratie-web-blue; exit 31; }
retry_url "http://127.0.0.1:8081/annotations-index.json" 60 1 || { dump_container archicratie-web-blue; exit 32; }
retry_url "http://127.0.0.1:8081/pagefind/pagefind.js" 60 1 || { dump_container archicratie-web-blue; exit 33; }
CANON="$(curl -fsS "http://127.0.0.1:8081/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)"
HTML="$(fetch_html "http://127.0.0.1:8081/archicrat-ia/chapitre-1/" 60 1 || true)"
CANON="$(echo "$HTML" | 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;
dump_container archicratie-web-blue
echo "❌ staging canonical mismatch"
exit 34
}
echo "✅ staging OK"
@@ -221,6 +263,45 @@ jobs:
PROJ="${COMPOSE_PROJECT_NAME:-archicratie-web}"
TS="${TS:-$(date -u +%Y%m%d-%H%M%S)}"
retry_url() {
local url="$1"; local tries="${2:-45}"; local delay="${3:-1}"
local i=1
while (( i <= tries )); do
if curl -fsS --max-time 5 --retry 2 --retry-delay 0 --retry-all-errors "$url" >/dev/null 2>&1; then
return 0
fi
echo "… wait [$i/$tries] $url"
sleep "$delay"
((i++))
done
return 1
}
fetch_html() {
local url="$1"; local tries="${2:-45}"; local delay="${3:-1}"
local i=1
while (( i <= tries )); do
local html
html="$(curl -fsS --max-time 8 --retry 2 --retry-delay 0 --retry-all-errors "$url" 2>/dev/null || true)"
if [[ -n "$html" ]]; then
printf "%s" "$html"
return 0
fi
echo "… wait HTML [$i/$tries] $url"
sleep "$delay"
((i++))
done
return 1
}
dump_container() {
local name="$1"
echo "== docker ps (filter=$name) =="
docker ps -a --filter "name=$name" --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' || true
echo "== docker logs (tail) $name =="
docker logs --tail 200 "$name" 2>/dev/null || true
}
rollback() {
echo "⚠️ rollback green -> previous image tag (best effort)"
docker image tag "archicratie-web:green.BAK.${TS}" archicratie-web:green || true
@@ -228,24 +309,36 @@ jobs:
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green || true
}
set +e
docker compose -p "$PROJ" -f docker-compose.yml build web_green
docker compose -p "$PROJ" -f docker-compose.yml config >/dev/null
if ! docker compose -p "$PROJ" -f docker-compose.yml build web_green; then
dump_container archicratie-web-green
rollback
exit 40
fi
docker rm -f archicratie-web-green || true
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green
if ! docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green; then
dump_container archicratie-web-green
rollback
exit 41
fi
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
retry_url "http://127.0.0.1:8082/para-index.json" 90 1 || { dump_container archicratie-web-green; rollback; exit 42; }
retry_url "http://127.0.0.1:8082/annotations-index.json" 90 1 || { dump_container archicratie-web-green; rollback; exit 43; }
retry_url "http://127.0.0.1:8082/pagefind/pagefind.js" 90 1 || { dump_container archicratie-web-green; rollback; exit 44; }
CANON="$(curl -fsS "http://127.0.0.1:8082/archicrat-ia/chapitre-1/" | grep -oE 'rel="canonical" href="[^"]+"' | head -n1 || true)"
HTML="$(fetch_html "http://127.0.0.1:8082/archicrat-ia/chapitre-1/" 90 1 || true)"
CANON="$(echo "$HTML" | 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;
dump_container archicratie-web-green
echo "❌ live canonical mismatch"
rollback
exit 45
}
echo "✅ live OK"
set -e
- name: Hotpatch annotations-index.json (deep merge shards) into blue+green
run: |
@@ -292,7 +385,6 @@ jobs:
return out
def deep_merge(dst, src):
# non destructif : ne supprime rien, n'écrase pas les scalaires existants
for k, v in (src or {}).items():
if k in ("media", "refs", "comments_editorial") and is_arr(v):
if k == "media":
@@ -310,7 +402,6 @@ jobs:
continue
if is_arr(v):
# fallback: union by json string
cur = dst.get(k, [])
if not is_arr(cur): cur = []
seen = set()
@@ -326,7 +417,6 @@ jobs:
dst[k] = out
continue
# scalaires: set seulement si absent / vide
if k not in dst or dst.get(k) in (None, ""):
dst[k] = v
@@ -376,7 +466,6 @@ jobs:
except Exception as e:
errors.append({"file": os.path.relpath(fp, ROOT), "error": str(e)})
# tri paras
for page, obj in pages.items():
keys = list((obj.get("paras") or {}).keys())
keys.sort(key=lambda k: (para_num(k), k))
@@ -400,13 +489,11 @@ jobs:
print("OK: wrote /tmp/annotations-index.json pages=", out["stats"]["pages"], "paras=", out["stats"]["paras"], "errors=", out["stats"]["errors"])
PY
# inject into running containers
for c in archicratie-web-blue archicratie-web-green; do
echo "== patch $c =="
docker cp /tmp/annotations-index.json "${c}:/usr/share/nginx/html/annotations-index.json"
done
# quick smoke: check new file exists and is readable
for p in 8081 8082; do
echo "== smoke annotations-index on $p =="
curl -fsS "http://127.0.0.1:${p}/annotations-index.json" | python3 -c 'import sys,json; j=json.load(sys.stdin); print("generatedAt:", j.get("generatedAt")); print("pages:", len(j.get("pages") or {}))'