# OPS — Déploiement Archicratie Web Edition (Mac Studio → DS220+) Objectif : déployer une nouvelle version du site sur le NAS (DS220+) sans jamais casser la prod, en utilisant un schéma blue/green piloté par DSM Reverse Proxy, avec une procédure robuste même quand docker compose build est instable sur le NAS. > 🟧 **LEGACY / HISTORIQUE** — Ce document n’est plus la source de vérité. > Référence actuelle : docs/DEPLOY_PROD_SYNOLOGY_DS220.md (canonique). > Statut : gelé (on n’édite plus que pour ajouter un lien vers le canonique, si nécessaire). > Raison : doublon → risque de divergence → risque d’erreur en prod. > Si tu lis ceci pour déployer : stop → ouvre le canonique. ## 0) Repères essentiels Noms & domaines • Site public (prod) : https://archicratie.trans-hands.synology.me • Gitea public : https://gitea.archicratie.trans-hands.synology.me Attention : tu as eu un “piège” de typo (trans-hands-synology.me ≠ trans-hands.synology.me). Toujours vérifier l’orthographe exacte. Ports locaux NAS • web_blue : 127.0.0.1:8081 → container:80 • web_green : 127.0.0.1:8082 → container:80 • DSM Reverse Proxy pointe soit vers 8081 soit vers 8082. Règle d’or “safe prod” • On ne touche jamais au slot live (celui pointé par DSM). • On construit/teste sur l’autre slot. • On bascule DSM (10 secondes). • On rollback DSM (10 secondes) si besoin. ## 1) Blue/Green : qui fait quoi ? Convention simple et pro • blue = slot A (souvent “actif” par défaut) → 8081 • green = slot B (staging/next) → 8082 Mais : la vérité = DSM. Ce n’est pas “blue” ou “green” qui décide : c’est le Reverse Proxy DSM qui pointe vers 8081 ou 8082. ➡️ Donc : • blue et green sont deux environnements identiques • l’un est “live”, l’autre est “next” • après bascule, les rôles s’inversent ## 2) Arborescence NAS (standard à stabiliser) Chemin racine : • /volume2/docker/archicratie-web/ Sous-dossiers recommandés : • incoming/ : dépôt d’archives venant du Mac (upload File Station / scp) • releases/ : releases dépliées, horodatées • current : symlink vers la release “courante” (le code utilisé pour builder) • ops/ : scripts d’exploitation (smoke, which-live, helpers) • current__backup_before_cleanup/ : backup historique (optionnel) Exemple sain : /volume2/docker/archicratie-web/ incoming/ archicratie-web-20260130-104937.tar.gz archicratie-web-20260130-104937.tar.gz.sha256 releases/ 20260130-104937/ app/ (code déplié prêt) current -> releases/20260130-104937/app ops/ smoke.sh which-live.sh ## 3) Permissions NAS (ce qui est “propre”) Tu as eu des problèmes de release dépliée “root:root” → impossible à lire en user. Politique simple • propriétaire : archicratia:users • dossiers : 750 • fichiers : 640 • incoming peut être 770 si tu y upload souvent via File Station. Commandes (à lancer à la racine) : BASE="/volume2/docker/archicratie-web" sudo chown -R archicratia:users "$BASE" sudo chmod 750 "$BASE" "$BASE/ops" "$BASE/releases" "$BASE/current__backup_before_cleanup" 2>/dev/null || true sudo chmod 770 "$BASE/incoming" 2>/dev/null || true # Important : options find AVANT les tests find "$BASE/releases" -maxdepth 3 -type d -exec chmod 750 {} \; find "$BASE/releases" -maxdepth 3 -type f -exec chmod 640 {} \; ⚠️ Éviter chmod 700 * “au hasard” : ça peut te bloquer toi-même + bloquer des outils DSM. Le trio 750/640 est ton bon compromis. ## 4) Variables Gitea (les 3 variables qui conditionnent “Proposer”) Dans .env sur le NAS (dans current/ ou dans la release) : PUBLIC_GITEA_BASE=https://gitea.archicratie.trans-hands.synology.me PUBLIC_GITEA_OWNER=Archicratia PUBLIC_GITEA_REPO=archicratie-edition Point critique : la casse du OWNER Tu as déjà vu le symptôme : • archicratia/archicratie-edition → peut renvoyer 404 • Archicratia/archicratie-edition → OK Donc : OWNER doit être exactement la casse que Gitea attend. Test rapide (NAS) : BASE="https://gitea.archicratie.trans-hands.synology.me" curl -kI "$BASE/Archicratia/archicratie-edition/" | head -n 8 ✅ attendu : HTTP/2 200 ## 5) Côté Mac Studio : préparer une release “propre” (sans scories macOS) Pourquoi Tu as eu : • fichiers ._* • xattrs LIBARCHIVE.xattr.com.apple.* • warnings à l’extraction ➡️ Ce n’est pas bloquant, mais c’est sale et ça te pollue les releases. Script Mac : release-pack.sh (sans Git, ultra simple) À placer à la racine du repo site/ sur Mac. #!/bin/zsh set -euo pipefail TS="${1:-$(date +%Y%m%d-%H%M%S)}" NAME="archicratie-web-$TS" OUT_DIR="_release_out" ARCHIVE="$OUT_DIR/$NAME.tar.gz" SHA="$ARCHIVE.sha256" mkdir -p "$OUT_DIR" ### Évite les AppleDouble (._*) + certaines métadonnées export COPYFILE_DISABLE=1 export COPY_EXTENDED_ATTRIBUTES_DISABLE=1 ### Nettoyages “safe” find . -name ".DS_Store" -delete 2>/dev/null || true ### Crée un dossier staging STAGE="$(mktemp -d)" mkdir -p "$STAGE/$NAME" ### Copie le repo SANS déchets rsync -a --delete \ --exclude ".git/" \ --exclude "node_modules/" \ --exclude "dist/" \ --exclude ".astro/" \ --exclude "_release_out/" \ --exclude ".DS_Store" \ --exclude "__MACOSX/" \ --exclude "._*" \ ./ "$STAGE/$NAME/" ### Tar propre tar -czf "$ARCHIVE" -C "$STAGE" "$NAME" ### Checksum shasum -a 256 "$ARCHIVE" > "$SHA" echo "OK: $ARCHIVE" echo "OK: $SHA" Usage : chmod +x release-pack.sh ./release-pack.sh 20260130-104937 Résultat : • _release_out/archicratie-web-20260130-104937.tar.gz • _release_out/archicratie-web-20260130-104937.tar.gz.sha256 ## 6) Transfert vers le NAS Option A — DSM File Station (simple) • Upload les 2 fichiers dans : ◦ /volume2/docker/archicratie-web/incoming/ Option B — scp (si SSH) Depuis le Mac : scp _release_out/archicratie-web-20260130-104937.tar.gz* \ archicratia@192.168.1.20:/volume2/docker/archicratie-web/incoming/ ## 7) NAS : vérifier + déplier une release (procédure canonique) BASE="/volume2/docker/archicratie-web" TS="20260130-104937" cd "$BASE" ### 1) checksum (IMPORTANT) sha256sum -c "incoming/archicratie-web-$TS.tar.gz.sha256" ### 2) créer dossier release rm -rf "releases/$TS" mkdir -p "releases/$TS" ### 3) extraire tar -xzf "incoming/archicratie-web-$TS.tar.gz" -C "releases/$TS" ### 4) pointer APP vers le dossier extrait APP="$BASE/releases/$TS/archicratie-web-$TS" ### option: normaliser en "app" mv "$APP" "$BASE/releases/$TS/app" APP="$BASE/releases/$TS/app" ### 5) ownership + perms sudo chown -R archicratia:users "$BASE/releases/$TS" find "$BASE/releases/$TS" -type d -exec chmod 750 {} \; find "$BASE/releases/$TS" -type f -exec chmod 640 {} \; ### 6) basculer current (symlink) ln -sfn "$APP" "$BASE/current" ### 7) sanity check : compose présent ? ls -la "$BASE/current/docker-compose.yml" "$BASE/current/Dockerfile" "$BASE/current/nginx.conf" "$BASE/current/.env" Cas réel déjà rencontré : la release “n’a pas les fichiers ops” Tu as eu un tar “inner” (archicratie-web.tar.gz) qui n’embarque pas docker-compose.yml/Dockerfile/nginx.conf/.env. Règle pro : ces fichiers doivent être dans la release. Fix immédiat si c’est absent (tu l’as déjà fait) : OPS_SRC="$BASE/releases/20260129-174516_clean" cp -a "$OPS_SRC/docker-compose.yml" "$APP/" cp -a "$OPS_SRC/Dockerfile" "$APP/" cp -a "$OPS_SRC/nginx.conf" "$APP/" cp -a "$OPS_SRC/.env" "$APP/.env" ## 8) Build GREEN sur NAS (robuste) — IMPORTANT Pourquoi docker compose build n’est PAS fiable sur DS220+ Tu as vu : • Temporary failure resolving 'deb.debian.org' • apt-get update qui part en Ign: puis Err: ➡️ En pratique sur ton NAS : le builder n’a pas toujours le bon réseau, même si build: network: host est déclaré. Procédure “qui marche vraiment” (golden path) ➡️ On build en host network avec docker build, puis on déploie avec compose sans rebuild. cd /volume2/docker/archicratie-web/current ### charge les env vars du .env set -a . ./.env set +a ### BUILD image green (host network) sudo docker build --no-cache --network host \ --build-arg PUBLIC_GITEA_BASE="$PUBLIC_GITEA_BASE" \ --build-arg PUBLIC_GITEA_OWNER="$PUBLIC_GITEA_OWNER" \ --build-arg PUBLIC_GITEA_REPO="$PUBLIC_GITEA_REPO" \ -t archicratie-web:green \ -f Dockerfile . Voir Ign: pendant apt-get n’est pas forcément une erreur. Ce qui compte : est-ce qu’il finit par télécharger / réussir. On s’alarme uniquement si on voit Err: ... Temporary failure resolving + exit code non-zéro. ## 9) Démarrer GREEN (sans impacter BLUE) cd /volume2/docker/archicratie-web/current # ne jamais lancer "up" sans préciser le service sudo docker rm -f archicratie-web-green 2>/dev/null || true sudo docker compose up -d --force-recreate --no-build web_green Smoke test (port 8082) : /volume2/docker/archicratie-web/ops/smoke.sh 8082 ## 10) DSM : basculer le Reverse Proxy (10 secondes) DSM → Panneau de configuration → Portail de connexion (ou “Portail des applications”) → Proxy inversé Trouve la règle : • Source : https://archicratie.trans-hands.synology.me (443) Destination : • pour BLUE : http://127.0.0.1:8081 • pour GREEN : http://127.0.0.1:8082 Tu changes seulement le port, tu “Appliques”. Test immédiat : curl -kI https://archicratie.trans-hands.synology.me/ | head -n 8 ## 11) Rollback (10 secondes) Si ça ne va pas : • DSM Reverse Proxy → remettre l’ancien port (8081 ou 8082) • refresh navigateur Option pro : ne stoppe pas l’autre container tout de suite (tu peux comparer / investiguer calmement). ## 12) Ops scripts (à garder dans /volume2/docker/archicratie-web/ops) smoke.sh (version “complète”) #!/bin/sh set -eu PORT="${1:-8081}" BASE="http://127.0.0.1:${PORT}" echo "Smoke test ${BASE}" curl -fsSI "${BASE}/" | head -n 5 curl -fsSI "${BASE}/pagefind/pagefind.js" | head -n 5 curl -fsSI "${BASE}/pagefind/pagefind-ui.js" | head -n 5 || true curl -fsSI "${BASE}/pagefind/pagefind-ui.css" | head -n 5 || true curl -fsSI "${BASE}/pagefind/pagefind-entry.json" | head -n 5 || true echo "OK" which-live.sh (déduire quel port est live) Idée : on compare les Last-Modified ou ETag entre le domaine public et les ports locaux. #!/bin/sh set -eu DOMAIN="https://archicratie.trans-hands.synology.me" A="http://127.0.0.1:8081" B="http://127.0.0.1:8082" etag() { curl -ksI "$1/" | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r'; } lm() { curl -ksI "$1/" | awk -F': ' 'tolower($1)=="last-modified"{print $2}' | tr -d '\r'; } E_D="$(etag "$DOMAIN")" E_A="$(etag "$A")" E_B="$(etag "$B")" echo "DOMAIN ETag: $E_D" echo "8081 ETag: $E_A" echo "8082 ETag: $E_B" if [ -n "$E_D" ] && [ "$E_D" = "$E_A" ]; then echo "LIVE=8081 (blue slot probable)" elif [ -n "$E_D" ] && [ "$E_D" = "$E_B" ]; then echo "LIVE=8082 (green slot probable)" else echo "LIVE=INCONNU (ETag mismatch) — check Last-Modified:" echo "DOMAIN: $(lm "$DOMAIN")" echo "8081 : $(lm "$A")" echo "8082 : $(lm "$B")" fi ## 13) Pannes & diagnostics (les vrais cas rencontrés) ### A) 504 via DSM Reverse Proxy Symptôme : HTTP/2 504 côté domaine. Check : 1. le container répond en local : curl -I http://127.0.0.1:8081/ curl -I http://127.0.0.1:8082/ 2. DSM Reverse Proxy destination = http://127.0.0.1:808X (pas https) 3. ports bindés en loopback (recommandé) : 127.0.0.1:808X:80 4. logs container : sudo docker logs --tail=80 archicratie-web-blue sudo docker logs --tail=80 archicratie-web-green ### B) “Proposer” ouvre Gitea mais tombe sur 404 C’est quasiment toujours l’un de ces trois points : 1. OWNER/REPO incorrects (casse) Test : BASE="https://gitea.archicratie.trans-hands.synology.me" curl -kI "$BASE/Archicratia/archicratie-edition/" | head • Si 404 → c’est ton .env / build args. • La build a embarqué une ancienne config Vérifie dans le HTML servi (dans le container) : sudo docker exec -it archicratie-web-green sh -lc \ 'grep -Rin "const GITEA_BASE" /usr/share/nginx/html | head -n 20' 1. Tu dois voir https://gitea.archicratie.trans-hands.synology.me + bon OWNER/REPO. 2. Problème d’auth / droits Gitea Si tu es loggé mais “pas autorisé”, vérifier que tu as accès au repo et que l’URL pointe au bon repo. ### C) “Proposer” échoue avant Gitea (pas de pop-up, pas de 2 choix) Check : • console navigateur (JS) • vérifier que le script “Proposer” est bien injecté dans la page (view source) • tester une page simple (ex: /archicratie/) ### D) Pagefind n’indexe plus Ce que tu as déjà observé : • Pagefind ignore les pages sans data-pagefind-body • Tu avais bien ~12 pages indexées (log Pagefind) Check dans le container : sudo docker exec -it archicratie-web-green sh -lc \ 'ls -la /usr/share/nginx/html/pagefind | head' Note : • GET /pagefind/ peut renvoyer 403 (listing interdit) : ce n’est pas un bug • tu testes plutôt : ◦ /pagefind/pagefind.js ◦ /pagefind/pagefind-entry.json ### E) docker compose build casse sur apt-get update (DNS) Symptôme : • Temporary failure resolving 'deb.debian.org' Diagnostic rapide : sudo docker run --rm --network host node:22-bookworm-slim sh -lc \ 'apt-get -o Acquire::ForceIPv4=true update -qq && echo OK_APT' ✅ si OK_APT : ton NAS sait sortir, c’est le builder compose qui est instable → utiliser la golden path : docker build --network host. ## 14) Dockerfile : version robuste “NAS host-network” Dans ton Dockerfile, garde : RUN --network=host apt-get update \ && apt-get install -y --no-install-recommends ca-certificates git \ && rm -rf /var/lib/apt/lists/* Option “anti-fragile” (retries + IPv4) : RUN --network=host apt-get -o Acquire::Retries=5 -o Acquire::ForceIPv4=true update \ && apt-get install -y --no-install-recommends ca-certificates git \ && rm -rf /var/lib/apt/lists/* ## 15) Important : ne pas se faire piéger par le docker.sock Sur ton DS220+, /var/run/docker.sock est root:root avec 660 → ton user ne peut pas accéder Docker sans sudo. ➡️ Règle : • commandes Docker = sudo docker ... • compose = sudo docker compose ... (Optionnel : alias) alias d='sudo docker' alias dc='sudo docker compose' ## 16) Workflow résumé “cockpit” (le plus important) Depuis le Mac : 1. ./release-pack.sh 20260130-104937 2. upload _release_out/*.tar.gz* → NAS incoming/ Sur le NAS : 1. vérifier checksum 2. déplier releases/$TS + chown/chmod 3. ln -sfn releases/$TS/app current 4. build green : ◦ sudo docker build --network host ... -t archicratie-web:green . 5. run green : ◦ sudo docker compose up -d --force-recreate --no-build web_green 6. smoke : ◦ /volume2/docker/archicratie-web/ops/smoke.sh 8082 7. switch DSM Reverse Proxy 8081 ↔ 8082 8. si souci → rollback DSM Post-scriptum : ton problème local Mac (Astro “Cannot find module astro/config”) Ce symptôme arrive typiquement quand : • node_modules est incomplet/corrompu • tu as basculé de dossier (ex: un _release_out/... a été pris pour le vrai repo) • ou le process dev a reload sur un tsconfig “nouveau” mais pas les deps Fix standard (dans le vrai dossier site/) : rm -rf node_modules .astro dist npm ci npm run dev