Compare commits
9 Commits
chore/fix-
...
chore/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d5b790e52 | |||
| 4dec9e182b | |||
| 9b4584f70a | |||
| 7b64fb7401 | |||
|
|
57cb23ce8b | ||
| 708b87ff35 | |||
| 577cfd08e8 | |||
| de9edbe532 | |||
| 006fec7efd |
@@ -4,6 +4,11 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
force:
|
||||||
|
description: "Force deploy even if gate would skip (1=yes, 0=no)"
|
||||||
|
required: false
|
||||||
|
default: "0"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --dns-result-order=ipv4first
|
NODE_OPTIONS: --dns-result-order=ipv4first
|
||||||
@@ -32,61 +37,86 @@ 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
|
||||||
export 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; }
|
||||||
|
|
||||||
eval "$(node --input-type=module -e 'import fs from "node:fs";
|
node --input-type=module <<'NODE'
|
||||||
|
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 repo =
|
const repoObj = ev?.repository || {};
|
||||||
ev?.repository?.clone_url ||
|
const cloneUrl =
|
||||||
(ev?.repository?.html_url ? (ev.repository.html_url.replace(/\/$/,"") + ".git") : "");
|
repoObj?.clone_url ||
|
||||||
const sha =
|
(repoObj?.html_url ? (repoObj.html_url.replace(/\/$/,"") + ".git") : "");
|
||||||
ev?.after ||
|
if (!cloneUrl) throw new Error("No repository clone_url/html_url in event.json");
|
||||||
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`);
|
|
||||||
')"
|
|
||||||
|
|
||||||
|
const defaultBranch = repoObj?.default_branch || "main";
|
||||||
|
const sha =
|
||||||
|
(process.env.GITHUB_SHA && String(process.env.GITHUB_SHA).trim()) ||
|
||||||
|
ev?.after ||
|
||||||
|
ev?.sha ||
|
||||||
|
ev?.head_commit?.id ||
|
||||||
|
ev?.pull_request?.head?.sha ||
|
||||||
|
"";
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
source /tmp/deploy.env
|
||||||
echo "Repo URL: $REPO_URL"
|
echo "Repo URL: $REPO_URL"
|
||||||
echo "SHA: $SHA"
|
echo "Default branch: $DEFAULT_BRANCH"
|
||||||
|
echo "SHA: ${SHA:-<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:-}" ]]; then
|
||||||
git fetch --depth 1 origin "$SHA"
|
git fetch --depth 1 origin "$SHA"
|
||||||
git -c advice.detachedHead=false checkout -q FETCH_HEAD
|
git -c advice.detachedHead=false checkout -q FETCH_HEAD
|
||||||
|
else
|
||||||
|
git fetch --depth 1 origin "$DEFAULT_BRANCH"
|
||||||
|
git -c advice.detachedHead=false checkout -q "origin/$DEFAULT_BRANCH"
|
||||||
|
SHA="$(git rev-parse HEAD)"
|
||||||
|
echo "SHA='$SHA'" >> /tmp/deploy.env
|
||||||
|
echo "Resolved SHA: $SHA"
|
||||||
|
fi
|
||||||
|
|
||||||
git log -1 --oneline
|
git log -1 --oneline
|
||||||
|
|
||||||
echo "SHA=$SHA" >> /tmp/deploy.env
|
|
||||||
echo "REPO_URL=$REPO_URL" >> /tmp/deploy.env
|
|
||||||
|
|
||||||
- name: Gate — auto deploy only on annotations/media changes
|
- name: Gate — auto deploy only on annotations/media changes
|
||||||
|
env:
|
||||||
|
INPUT_FORCE: ${{ inputs.force }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source /tmp/deploy.env
|
source /tmp/deploy.env
|
||||||
|
|
||||||
# fichiers touchés par CE commit (merge commit inclus)
|
FORCE="${INPUT_FORCE:-0}"
|
||||||
CHANGED="$(git diff-tree --no-commit-id --name-only -r "$SHA" || true)"
|
if [[ "$FORCE" == "1" ]]; then
|
||||||
echo "== changed files =="
|
echo "✅ force=1 -> bypass gate -> deploy allowed"
|
||||||
echo "$CHANGED" | sed -n '1,200p'
|
|
||||||
|
|
||||||
# Gate strict : uniquement annotations + media + le workflow lui-même (si tu veux autoriser)
|
|
||||||
if echo "$CHANGED" | grep -qE '^(src/annotations/|public/media/)'; then
|
|
||||||
echo "GO=1" >> /tmp/deploy.env
|
echo "GO=1" >> /tmp/deploy.env
|
||||||
echo "✅ deploy allowed (annotations/media change detected)"
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
CHANGED="$(git show --name-only --pretty="" "$SHA" | sed '/^$/d' || true)"
|
||||||
|
echo "== changed files =="
|
||||||
|
echo "$CHANGED" | sed -n '1,240p'
|
||||||
|
|
||||||
|
if echo "$CHANGED" | grep -qE '^(src/annotations/|public/media/)'; then
|
||||||
|
echo "GO=1" >> /tmp/deploy.env
|
||||||
|
echo "✅ deploy allowed (annotations/media change detected)"
|
||||||
|
else
|
||||||
echo "GO=0" >> /tmp/deploy.env
|
echo "GO=0" >> /tmp/deploy.env
|
||||||
echo "ℹ️ no annotations/media change -> skip deploy"
|
echo "ℹ️ no annotations/media change -> skip deploy"
|
||||||
exit 0
|
fi
|
||||||
|
|
||||||
- name: Install docker client + docker compose plugin (v2)
|
- name: Install docker client + docker compose plugin (v2)
|
||||||
run: |
|
run: |
|
||||||
@@ -107,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 }}
|
||||||
@@ -122,6 +161,17 @@ jobs:
|
|||||||
test -n "${PUBLIC_GITEA_REPO:-}" || { echo "❌ missing repo var PUBLIC_GITEA_REPO"; exit 2; }
|
test -n "${PUBLIC_GITEA_REPO:-}" || { echo "❌ missing repo var PUBLIC_GITEA_REPO"; exit 2; }
|
||||||
echo "✅ vars OK"
|
echo "✅ vars OK"
|
||||||
|
|
||||||
|
- name: Assert deploy files exist
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
source /tmp/deploy.env
|
||||||
|
[[ "${GO:-0}" == "1" ]] || { echo "ℹ️ skipped"; exit 0; }
|
||||||
|
|
||||||
|
test -f docker-compose.yml
|
||||||
|
test -f Dockerfile
|
||||||
|
test -f nginx.conf
|
||||||
|
echo "✅ deploy files OK"
|
||||||
|
|
||||||
- name: Build + deploy staging (blue) then smoke
|
- name: Build + deploy staging (blue) then smoke
|
||||||
env:
|
env:
|
||||||
PUBLIC_GITEA_BASE: ${{ vars.PUBLIC_GITEA_BASE }}
|
PUBLIC_GITEA_BASE: ${{ vars.PUBLIC_GITEA_BASE }}
|
||||||
@@ -131,18 +181,21 @@ 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}"
|
||||||
|
|
||||||
# backup tags (best effort)
|
|
||||||
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
|
||||||
|
|
||||||
# build + restart staging (blue=8081)
|
# ✅ use cache (DO NOT --no-cache)
|
||||||
docker compose build --no-cache web_blue
|
docker compose -p "$PROJ" -f docker-compose.yml build web_blue
|
||||||
docker compose up -d --force-recreate 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
|
||||||
|
|
||||||
# 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/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
|
||||||
curl -fsS "http://127.0.0.1:8081/pagefind/pagefind.js" >/dev/null
|
curl -fsS "http://127.0.0.1:8081/pagefind/pagefind.js" >/dev/null
|
||||||
@@ -164,18 +217,21 @@ 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
|
||||||
docker compose up -d --force-recreate web_green
|
|
||||||
|
docker rm -f archicratie-web-green || true
|
||||||
|
docker compose -p "$PROJ" -f docker-compose.yml up -d --force-recreate --remove-orphans web_green
|
||||||
|
|
||||||
curl -fsS "http://127.0.0.1:8082/para-index.json" >/dev/null
|
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/annotations-index.json" >/dev/null
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 816 KiB |
@@ -19,3 +19,12 @@ paras:
|
|||||||
credit: ""
|
credit: ""
|
||||||
ts: 2026-02-25T19:11:32.634Z
|
ts: 2026-02-25T19:11:32.634Z
|
||||||
fromIssue: 121
|
fromIssue: 121
|
||||||
|
p-11-67c14c09:
|
||||||
|
media:
|
||||||
|
- type: image
|
||||||
|
src: /media/archicrat-ia/chapitre-4/p-11-67c14c09/Capture_d_e_cran_2026-02-16_a_13.07.35.png
|
||||||
|
caption: "[Media] p-11-67c14c09 — Chapitre 4 — Histoire archicratique des
|
||||||
|
révolutions industrielles"
|
||||||
|
credit: ""
|
||||||
|
ts: 2026-02-26T13:17:41.286Z
|
||||||
|
fromIssue: 129
|
||||||
|
|||||||
Reference in New Issue
Block a user