Compare commits
5 Commits
chore/anno
...
chore/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
| 390f2c33e5 | |||
| 16485dc4a9 | |||
| 0519ae2dd0 | |||
| 342e21b9ea | |||
| c7ae883c6a |
@@ -106,7 +106,8 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
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 files =="
|
||||||
echo "$CHANGED" | sed -n '1,240p'
|
echo "$CHANGED" | sed -n '1,240p'
|
||||||
|
|
||||||
@@ -184,27 +185,68 @@ jobs:
|
|||||||
[[ "${GO:-0}" == "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
[[ "${GO:-0}" == "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
||||||
PROJ="${COMPOSE_PROJECT_NAME:-archicratie-web}"
|
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)"
|
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
|
||||||
|
|
||||||
# ✅ 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
|
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 rm -f archicratie-web-blue || true
|
||||||
|
|
||||||
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_blue
|
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
|
retry_url "http://127.0.0.1:8081/para-index.json" 60 1 || { dump_container archicratie-web-blue; exit 31; }
|
||||||
curl -fsS "http://127.0.0.1:8081/annotations-index.json" >/dev/null
|
retry_url "http://127.0.0.1:8081/annotations-index.json" 60 1 || { dump_container archicratie-web-blue; exit 32; }
|
||||||
curl -fsS "http://127.0.0.1:8081/pagefind/pagefind.js" >/dev/null
|
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 "canonical(blue)=$CANON"
|
||||||
echo "$CANON" | grep -q 'https://staging\.archicratie\.trans-hands\.synology\.me/' || {
|
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"
|
echo "✅ staging OK"
|
||||||
@@ -221,6 +263,45 @@ jobs:
|
|||||||
PROJ="${COMPOSE_PROJECT_NAME:-archicratie-web}"
|
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)}"
|
||||||
|
|
||||||
|
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() {
|
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
|
||||||
@@ -228,24 +309,36 @@ jobs:
|
|||||||
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green || true
|
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 config >/dev/null
|
||||||
docker compose -p "$PROJ" -f docker-compose.yml build web_green
|
|
||||||
|
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 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
|
retry_url "http://127.0.0.1:8082/para-index.json" 90 1 || { dump_container archicratie-web-green; rollback; exit 42; }
|
||||||
curl -fsS "http://127.0.0.1:8082/annotations-index.json" >/dev/null
|
retry_url "http://127.0.0.1:8082/annotations-index.json" 90 1 || { dump_container archicratie-web-green; rollback; exit 43; }
|
||||||
curl -fsS "http://127.0.0.1:8082/pagefind/pagefind.js" >/dev/null
|
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 "canonical(green)=$CANON"
|
||||||
echo "$CANON" | grep -q 'https://archicratie\.trans-hands\.synology\.me/' || {
|
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"
|
echo "✅ live OK"
|
||||||
set -e
|
|
||||||
|
|
||||||
- name: Hotpatch annotations-index.json (deep merge shards) into blue+green
|
- name: Hotpatch annotations-index.json (deep merge shards) into blue+green
|
||||||
run: |
|
run: |
|
||||||
@@ -292,7 +385,6 @@ jobs:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
def deep_merge(dst, src):
|
def deep_merge(dst, src):
|
||||||
# non destructif : ne supprime rien, n'écrase pas les scalaires existants
|
|
||||||
for k, v in (src or {}).items():
|
for k, v in (src or {}).items():
|
||||||
if k in ("media", "refs", "comments_editorial") and is_arr(v):
|
if k in ("media", "refs", "comments_editorial") and is_arr(v):
|
||||||
if k == "media":
|
if k == "media":
|
||||||
@@ -310,7 +402,6 @@ jobs:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if is_arr(v):
|
if is_arr(v):
|
||||||
# fallback: union by json string
|
|
||||||
cur = dst.get(k, [])
|
cur = dst.get(k, [])
|
||||||
if not is_arr(cur): cur = []
|
if not is_arr(cur): cur = []
|
||||||
seen = set()
|
seen = set()
|
||||||
@@ -326,7 +417,6 @@ jobs:
|
|||||||
dst[k] = out
|
dst[k] = out
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# scalaires: set seulement si absent / vide
|
|
||||||
if k not in dst or dst.get(k) in (None, ""):
|
if k not in dst or dst.get(k) in (None, ""):
|
||||||
dst[k] = v
|
dst[k] = v
|
||||||
|
|
||||||
@@ -376,7 +466,6 @@ jobs:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append({"file": os.path.relpath(fp, ROOT), "error": str(e)})
|
errors.append({"file": os.path.relpath(fp, ROOT), "error": str(e)})
|
||||||
|
|
||||||
# tri paras
|
|
||||||
for page, obj in pages.items():
|
for page, obj in pages.items():
|
||||||
keys = list((obj.get("paras") or {}).keys())
|
keys = list((obj.get("paras") or {}).keys())
|
||||||
keys.sort(key=lambda k: (para_num(k), k))
|
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"])
|
print("OK: wrote /tmp/annotations-index.json pages=", out["stats"]["pages"], "paras=", out["stats"]["paras"], "errors=", out["stats"]["errors"])
|
||||||
PY
|
PY
|
||||||
|
|
||||||
# inject into running containers
|
|
||||||
for c in archicratie-web-blue archicratie-web-green; do
|
for c in archicratie-web-blue archicratie-web-green; do
|
||||||
echo "== patch $c =="
|
echo "== patch $c =="
|
||||||
docker cp /tmp/annotations-index.json "${c}:/usr/share/nginx/html/annotations-index.json"
|
docker cp /tmp/annotations-index.json "${c}:/usr/share/nginx/html/annotations-index.json"
|
||||||
done
|
done
|
||||||
|
|
||||||
# quick smoke: check new file exists and is readable
|
|
||||||
for p in 8081 8082; do
|
for p in 8081 8082; do
|
||||||
echo "== smoke annotations-index on $p =="
|
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 {}))'
|
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 {}))'
|
||||||
|
|||||||
Reference in New Issue
Block a user