386 lines
15 KiB
Markdown
386 lines
15 KiB
Markdown
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.
|
||
|
||
## 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
|