Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .claude/deploiement.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,19 @@ cat .deployed-sha # SHA actuellement déployé
tail -20 var/log/deploy-nightly.log # derniĂšre tentative
```

### Garde-fous : `composer` en CLI, vérification de `vendor/`, rollback (#570)

Incident du 2026-10-02 : le déploiement nocturne a lancé composer avec le PHP **CGI** du PATH cron. Composer a affiché son aide et **sorti en code 0** sans rien installer ; le code avait déjà été mis à jour par `git checkout`, `vendor/` est resté ancien (3 paquets manquants : `monolog/monolog`, `symfony/monolog-bridge`, `symfony/monolog-bundle`) et 6 instances sont passées en HTTP 500.

Ce qui est en place depuis, **commun aux trois scripts** (`bin/lib/deploy-common.sh`, sourcé par `deploy-nightly.sh`, `deploy-all.sh` et `deploy.sh`) :

- **composer via le PHP CLI explicite** : `$HC_PHP -d memory_limit=512M /usr/local/bin/composer install 
`. `$HC_COMPOSER_BIN` vaut plusieurs mots : toujours l'utiliser **non quotĂ©** (entre guillemets, bash cherche un exĂ©cutable nommĂ© « php composer » et sort en 127). Plus aucun `composer` nu dans `bin/` (test `test_aucun_composer_nu_dans_les_scripts_de_deploiement`).
- **Vérification de `vendor/`** aprÚs `composer install` : `composer install --dry-run --no-dev` doit afficher « Nothing to install, update or remove ». Sinon l'étape « vérification de vendor/ » échoue (validé avec le vrai composer : message présent sur ronan, absent sur une instance à `vendor/` incomplet).
- **Rollback automatique si l'Ă©chec prĂ©cĂšde les migrations** : `git checkout --force <HEAD d'avant>`, `composer install`, vĂ©rification de `vendor/`, `cache:clear`. Le rapport indique `→ code restaurĂ© (<sha>)` ou `→ ROLLBACK ÉCHOUÉ 
, instance probablement hors service` (alors : intervention manuelle). **Pas de rollback Ă  partir des migrations** : l'Ă©tat de la base est incertain (migration partielle), un ancien code sur un schĂ©ma Ă  moitiĂ© migrĂ© serait pire. Pas de rollback non plus si `HEAD` est dĂ©jĂ  la cible.
- La cible du rollback est le `HEAD` réellement en place avant l'opération, **pas** `.deployed-sha` (un déploiement interrompu laisse `HEAD` sur le nouveau code alors que `.deployed-sha` reste ancien).
- Un correctif du script nocturne n'est actif sur une instance qu'**Ă  la nuit suivant celle oĂč elle l'a rĂ©cupĂ©rĂ©** (le script du cron est celui du disque au lancement). AprĂšs un correctif de script, passer par `bash bin/deploy-all.sh` (exĂ©cutĂ© depuis le poste) plutĂŽt que d'attendre le cron.
- Tests : `bash tests/bash/run.sh` (28 tests). Ils ne tournent pas dans la CI.

### Crons cPanel — créés et actifs depuis le 2026-09-12

> Vérifié en SSH le 2026-09-12 22h : les 8 crons ci-dessous sont bien
Expand Down
12 changes: 12 additions & 0 deletions .github/avancement.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@

---

## 🚧 DĂ©ploiement : composer en CLI, vĂ©rification de vendor/, rollback (2026-10-02, #570, branche `fix/570-deploy-composer-rollback`)

- Cause de l'incident #569 (6 instances en 500) : composer lancé en PHP CGI par le cron affichait son aide et sortait en 0 ; code déjà mis à jour par `git checkout`, `vendor/` ancien, aucun rollback.
- Le correctif `61ba614` Ă©tait lui-mĂȘme cassĂ© : `"$COMPOSER_BIN"` (deux mots) entre guillemets → exit 127 « commande introuvable ». 7 tests bash Ă©chouaient dĂ©jĂ  sur `main` sans que personne le voie (les tests bash ne tournent pas en CI).
- `bin/lib/deploy-common.sh` : définition unique de composer (PHP CLI explicite, `memory_limit`) et de la vérification de `vendor/`, sourcée par `deploy-nightly.sh`, `deploy-all.sh` et `deploy.sh`. Plus de `composer` nu dans `bin/`.
- VĂ©rification de `vendor/` (`composer install --dry-run` → « Nothing to install ») validĂ©e avec le vrai composer : prĂ©sent sur ronan, absent sur yannick (3 paquets manquants listĂ©s).
- Rollback si l'Ă©chec prĂ©cĂšde les migrations (jamais aprĂšs : Ă©tat de la base incertain), cible = `HEAD` rĂ©ellement en place ; rapport `→ code restaurĂ© (<sha>)` / `→ ROLLBACK ÉCHOUÉ`.
- Tests bash : 28/28 (dont 11 nouveaux, 7 réparés). Suite PHP non concernée.
- Reste : revue, `gh pr create` (label + `Closes #570` + assignee + board), CI verte, merge. Piste proposée : exécuter `tests/bash/run.sh` en CI.

---

## ✅ Mises Ă  jour composer — Symfony 8.0 → 8.1 (2026-10-02, #538, PR #562 mergĂ©e)

- Contraintes `symfony/*` et `extra.symfony.require` passées de `8.0.*` à `8.1.*` (un `composer update` seul n'aurait rien monté). Symfony en 8.1.8, Doctrine ORM 3.7.3, DoctrineBundle 3.3.2, phpunit 13.4.0, twig 3.30, monolog 3.12.1.
Expand Down
49 changes: 40 additions & 9 deletions bin/deploy-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,14 @@ SSH_HOST="lenouvel.me"
SSH_PORT=22
GIT_REPO="https://github.com/ronan-develop/home-cloud"
GIT_BRANCH="main"
# -d memory_limit=512M : sur le mutualisé o2switch (LVE CloudLinux), le
# memory_limit par dĂ©faut du php.ini fait tuer cache:clear --env=prod mĂȘme
# isolĂ© dans son propre process SSH (vĂ©cu 2026-09-27) — la valeur par dĂ©faut
# est trop juste pour la compilation du container Symfony en prod.
PHP_BIN="/usr/local/bin/php -d memory_limit=512M"
COMPOSER_BIN="composer"
# PHP/composer (chemins absolus, composer via PHP CLI explicite, memory_limit)
# et vérification de vendor/ : une seule définition, partagée avec
# deploy-nightly.sh (#570). Valeurs de plusieurs mots : utilisées dans des
# chaĂźnes de commande distantes, jamais comme un seul argument.
# shellcheck source=lib/deploy-common.sh
source "${SCRIPT_DIR}/lib/deploy-common.sh" || exit 1
PHP_BIN="$HC_PHP_BIN"
COMPOSER_BIN="$HC_COMPOSER_BIN"

SSH_KEY_OPTS=""
if [[ -n "${SSH_KEY_PATH:-}" && -f "${SSH_KEY_PATH}" ]]; then
Expand Down Expand Up @@ -157,6 +159,23 @@ run_step() {
return 0
}

# ── Restauration du code prĂ©cĂ©dent (#570) ────────────────────────────────────
# Appelée seulement si l'échec précÚde les migrations : aprÚs, l'état de la
# base est incertain (migration partielle) et un ancien code sur un schéma à
# moitié migré serait pire que le nouveau code.
rollback_remote() {
local sha="$1"
warn "${SUBDOMAIN} — restauration du code prĂ©cĂ©dent (${sha:0:7})
"
if run_step "rollback git checkout" "git checkout --force ${sha}" \
&& run_step "rollback composer install" "${COMPOSER_BIN} ${HC_COMPOSER_INSTALL_ARGS}" \
&& run_step "rollback vérification de vendor/" "${HC_VERIFY_VENDOR_SNIPPET}" \
&& run_step "rollback cache:clear" "${PHP_BIN} bin/console cache:clear --env=prod"; then
success "${SUBDOMAIN} — code restaurĂ© (${sha:0:7})"
else
error "${SUBDOMAIN} — ROLLBACK ÉCHOUÉ, instance probablement hors service : intervention manuelle requise"
fi
}

# ── Build Tailwind une seule fois, hors boucle (#421) ─────────────────────────
# Le CSS est identique pour les 7 instances — le reconstruire à chaque
# itération était un gaspillage pur, déjà vrai avant #421.
Expand Down Expand Up @@ -230,7 +249,8 @@ ENVEOF
continue
fi

if run_step "composer install" "${COMPOSER_BIN} install --no-interaction --prefer-dist --no-progress --no-dev --no-scripts" \
if run_step "composer install" "${COMPOSER_BIN} ${HC_COMPOSER_INSTALL_ARGS}" \
&& run_step "vérification de vendor/" "${HC_VERIFY_VENDOR_SNIPPET}" \
&& run_step "install-ffmpeg" "bash bin/install-ffmpeg.sh || echo '⚠ ffmpeg non installĂ© — vignettes vidĂ©o indisponibles'" \
&& run_step "cache:clear" "${PHP_BIN} bin/console cache:clear --env=prod" \
&& run_step "assets:install" "${PHP_BIN} bin/console assets:install public --env=prod" \
Expand Down Expand Up @@ -271,12 +291,20 @@ ENVEOF
# app.built.css vient d'ĂȘtre Ă©crasĂ© par le scp ci-dessus : annuler cette
# modification locale avant le pull, sinon git refuse de merger
# ("Your local changes ... would be overwritten by merge").
# Code réellement en place avant le pull : cible du rollback (#570).
PREV_SHA=$(ssh ${SSH_KEY_OPTS} -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" "cd ${DEPLOY_PATH} && git rev-parse HEAD" 2>/dev/null || echo "")
CODE_READY=false
if run_step "git pull" "git checkout -- var/tailwind/app.built.css 2>/dev/null; mkdir -p var/log && git pull origin ${GIT_BRANCH}" \
&& run_step "composer install" "${COMPOSER_BIN} install --no-interaction --prefer-dist --no-progress --no-dev --no-scripts" \
&& run_step "composer install" "${COMPOSER_BIN} ${HC_COMPOSER_INSTALL_ARGS}" \
&& run_step "vérification de vendor/" "${HC_VERIFY_VENDOR_SNIPPET}" \
&& run_step "install-ffmpeg" "bash bin/install-ffmpeg.sh || echo '⚠ ffmpeg non installĂ© — vignettes vidĂ©o indisponibles'" \
&& run_step "cache:clear" "${PHP_BIN} bin/console cache:clear --env=prod" \
&& run_step "assets:install" "${PHP_BIN} bin/console assets:install public --env=prod" \
&& run_step "importmap:install" "${PHP_BIN} bin/console importmap:install --env=prod" \
&& run_step "importmap:install" "${PHP_BIN} bin/console importmap:install --env=prod"; then
CODE_READY=true
fi

if [[ "$CODE_READY" == true ]] \
&& run_step "migrations" "${PHP_BIN} bin/console doctrine:migrations:migrate --no-interaction --env=prod" \
&& run_step "asset-map:compile" "${PHP_BIN} bin/console asset-map:compile" \
&& run_step "deploy-info" "echo '${DEPLOY_INFO_LINE}' > templates/deploy-info.html.twig" \
Expand All @@ -287,6 +315,9 @@ ENVEOF
success "${SUBDOMAIN} — mise à jour OK"
RESULTS_OK+=("$SUBDOMAIN")
else
if [[ "$CODE_READY" != true && -n "$PREV_SHA" ]]; then
rollback_remote "$PREV_SHA"
fi
RESULTS_FAIL+=("$SUBDOMAIN")
fi
fi
Expand Down
74 changes: 49 additions & 25 deletions bin/deploy-nightly.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,14 @@ PRENOM="${1:?Usage: deploy-nightly.sh <prenom> <chemin_instance> <chemin_rapport
INSTANCE_PATH="${2:?chemin_instance manquant}"
REPORT_FILE="${3:?chemin_rapport manquant}"

# Chemins absolus obligatoires : un cron cPanel s'exécute avec un PATH minimal
# (pas celui du profil shell interactif) — "composer"/"php" seuls ne rĂ©solvent
# à rien et font échouer le déploiement en silence (#421, échec réel constaté
# la nuit du 2026-09-12 : « composer : commande introuvable »).
# -d memory_limit=512M : sur le mutualisé o2switch (LVE CloudLinux), le
# memory_limit par dĂ©faut du php.ini fait tuer cache:clear --env=prod mĂȘme
# isolĂ© dans son propre process SSH (vĂ©cu 2026-09-27) — la valeur par dĂ©faut
# est trop juste pour la compilation du container Symfony en prod.
PHP_BIN="${DEPLOY_NIGHTLY_PHP_BIN:-/usr/local/bin/php} -d memory_limit=512M"
# composer est un script `#!/usr/bin/env php` — sous le PATH minimal du cron,
# `env` résout "php" vers /usr/bin/php (CGI, le premier dans ce PATH), pas
# vers le CLI de $PHP_BIN. Invoquer composer.phar explicitement via $PHP_BIN
# court-circuite ce shebang, plutÎt que de compter sur la résolution de env
# (piÚge distinct de celui déjà documenté plus haut sur composer/php absents
# du PATH — ici ils sont prĂ©sents, mais le MAUVAIS binaire est rĂ©solu).
# Constaté en conditions réelles le 2026-10-02 : composer "réussit" en
# apparence (exit 0) mais vendor/ reste incomplet (symfony/monolog-bundle
# absent malgré composer.lock à jour), cache:clear échoue ensuite avec
# ClassNotFoundError.
COMPOSER_BIN="${DEPLOY_NIGHTLY_PHP_BIN:-/usr/local/bin/php} ${DEPLOY_NIGHTLY_COMPOSER_PHAR:-/usr/local/bin/composer}"
# PHP/composer en chemin absolu, composer via PHP CLI explicite, vérification
# de vendor/ : définis une seule fois dans bin/lib/deploy-common.sh (#570).
# Résolu avant le cd : $0 est relatif au répertoire courant du cron.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/deploy-common.sh
source "${SCRIPT_DIR}/lib/deploy-common.sh" || exit 1
PHP_BIN="$HC_PHP_BIN"
COMPOSER_BIN="$HC_COMPOSER_BIN"

cd "$INSTANCE_PATH" || exit 1
mkdir -p var/log
Expand Down Expand Up @@ -140,12 +128,42 @@ if is_activity_recent; then
exit 0
fi

# Code réellement en place avant le checkout (peut différer de .deployed-sha :
# un déploiement interrompu laisse HEAD sur le nouveau code, #570).
PREVIOUS_HEAD=$(git rev-parse HEAD 2>/dev/null || echo "")

# Restaure le code précédent et un vendor/ cohérent avec son composer.lock.
# Appelée seulement quand l'échec survient AVANT les migrations : aprÚs, l'état
# de la base est incertain (migration partielle) et un ancien code sur un schéma
# à moitié migré serait pire que le nouveau code.
ROLLBACK_NOTE=""
rollback_code() {
local sha="$1"
local original_failed_step="$FAILED_STEP"
echo "↩ ${PRENOM} — restauration du code prĂ©cĂ©dent (${sha:0:7})" >&2
if run_step "rollback git checkout" git checkout --force "$sha" \
&& run_step "rollback composer install" $COMPOSER_BIN $HC_COMPOSER_INSTALL_ARGS \
&& run_step "rollback vérification de vendor/" hc_verify_vendor \
&& run_step "rollback cache:clear" $PHP_BIN bin/console cache:clear --env=prod; then
ROLLBACK_NOTE=" → code restaurĂ© (${sha:0:7})"
else
ROLLBACK_NOTE=" → ROLLBACK ÉCHOUÉ Ă  « ${FAILED_STEP} », instance probablement hors service"
fi
FAILED_STEP="$original_failed_step"
}

CODE_READY=false
if run_step "git checkout" git checkout --force "$REMOTE_SHA" \
&& run_step "composer install" "$COMPOSER_BIN" install --no-interaction --prefer-dist --no-progress --no-dev --no-scripts \
&& run_step "composer install" $COMPOSER_BIN $HC_COMPOSER_INSTALL_ARGS \
&& run_step "vérification de vendor/" hc_verify_vendor \
&& run_step "install-ffmpeg" bash bin/install-ffmpeg.sh \
&& run_step "cache:clear" $PHP_BIN bin/console cache:clear --env=prod \
&& run_step "assets:install" bash -c "umask 022 && $PHP_BIN bin/console assets:install public --env=prod" \
&& run_step "importmap:install" bash -c "umask 022 && $PHP_BIN bin/console importmap:install --env=prod" \
&& run_step "importmap:install" bash -c "umask 022 && $PHP_BIN bin/console importmap:install --env=prod"; then
CODE_READY=true
fi

if [[ "$CODE_READY" == true ]] \
&& run_step "migrations" $PHP_BIN bin/console doctrine:migrations:migrate --no-interaction --env=prod \
&& run_step "asset-map:compile" bash -c "umask 022 && $PHP_BIN bin/console asset-map:compile"; then
rm -f "$IMMINENT_FILE"
Expand All @@ -156,12 +174,18 @@ if run_step "git checkout" git checkout --force "$REMOTE_SHA" \
exit 0
else
rm -f "$IMMINENT_FILE"
echo "${PRENOM} : échec du déploiement, .deployed-sha inchangé." >&2
# Rollback seulement si l'échec précÚde les migrations ET qu'il y a un
# code différent à restaurer (si HEAD est déjà la cible, un second
# checkout n'améliorerait rien).
if [[ "$CODE_READY" != true && -n "$PREVIOUS_HEAD" && "$PREVIOUS_HEAD" != "$REMOTE_SHA" ]]; then
rollback_code "$PREVIOUS_HEAD"
fi
echo "${PRENOM} : échec du déploiement${ROLLBACK_NOTE}, .deployed-sha inchangé." >&2
if [[ "$IS_CRITICAL" == true ]]; then
report_line "critical" "${FAILED_STEP:-inconnue}"
report_line "critical" "${FAILED_STEP:-inconnue}${ROLLBACK_NOTE}"
open_critical_ticket "${FAILED_STEP:-inconnue}"
else
report_line "failed" "${FAILED_STEP:-inconnue}"
report_line "failed" "${FAILED_STEP:-inconnue}${ROLLBACK_NOTE}"
fi
exit 1
fi
13 changes: 9 additions & 4 deletions bin/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ if [[ -n "${SSH_KEY_PATH:-}" && -f "$SSH_KEY_PATH" ]]; then
SSH_KEY_OPTS="-i ${SSH_KEY_PATH}"
fi
GIT_BRANCH="main"
PHP_BIN="/usr/local/bin/php"
COMPOSER_BIN="composer"
# PHP/composer en chemin absolu, composer via PHP CLI explicite : définis une
# seule fois dans bin/lib/deploy-common.sh (#570).
# shellcheck source=lib/deploy-common.sh
source "${SCRIPT_DIR}/lib/deploy-common.sh" || exit 1
PHP_BIN="$HC_PHP_BIN"
COMPOSER_BIN="$HC_COMPOSER_BIN"

# ── Questionnaire ─────────────────────────────────────────────────────────────
title "═══════════════════════════════════════"
Expand Down Expand Up @@ -189,7 +193,8 @@ if [[ "$UPDATE_MODE" == true ]]; then
ssh ${SSH_KEY_OPTS} -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" \
"cd ${DEPLOY_PATH} && \
git pull origin main && \
${COMPOSER_BIN} install --no-interaction --prefer-dist --no-progress --no-dev && \
${COMPOSER_BIN} ${HC_COMPOSER_INSTALL_ARGS} && \
( ${HC_VERIFY_VENDOR_SNIPPET} ) && \
${PHP_BIN} bin/console cache:clear --env=prod && \
rm -rf var/cache/prod/* && \
rm -rf public/assets/* && \
Expand All @@ -200,7 +205,7 @@ if [[ "$UPDATE_MODE" == true ]]; then
{ error "❌ Erreur lors du dĂ©ploiement."; exit 1; }
else
info "Mode primo déploiement : clonage repo + setup"
ssh ${SSH_KEY_OPTS} -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" "mkdir -p ${DEPLOY_PATH} && cd ${DEPLOY_PATH} && git clone ${GIT_REPO} . && composer install --no-interaction --prefer-dist --no-progress && bash bin/install-ffmpeg.sh || echo '⚠ ffmpeg non installĂ© — vignettes vidĂ©o indisponibles'" && \
ssh ${SSH_KEY_OPTS} -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" "mkdir -p ${DEPLOY_PATH} && cd ${DEPLOY_PATH} && git clone ${GIT_REPO} . && ${COMPOSER_BIN} ${HC_COMPOSER_INSTALL_ARGS} && ( ${HC_VERIFY_VENDOR_SNIPPET} ) && bash bin/install-ffmpeg.sh || echo '⚠ ffmpeg non installĂ© — vignettes vidĂ©o indisponibles'" && \
success "✅ DĂ©ploiement rĂ©ussi !" || \
{ error "❌ Erreur lors du dĂ©ploiement."; exit 1; }
fi
Expand Down
Loading
Loading