Seed from NAS prod snapshot 20260130-190531
This commit is contained in:
385
docs/OPS-DEPLOYMENT.md
Normal file
385
docs/OPS-DEPLOYMENT.md
Normal file
@@ -0,0 +1,385 @@
|
||||
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
|
||||
Reference in New Issue
Block a user