# Guide d'opérations de production OVH — LÉZIDÉJOU

Ce guide est la procédure humaine de référence pour les opérations de release et de rollback du portail LÉZIDÉJOU. Il complète le guide de préparation OVH et le runbook de rollback. Il est destiné à un administrateur qui ne manipule pas régulièrement les liens symboliques.

> **Périmètre et sécurité.** Exécuter les commandes depuis une session SSH autorisée. Ne jamais saisir un mot de passe, une clé privée, une variable `.env` ou un secret dans Git, dans un ticket public ou dans un terminal partagé. Ne jamais modifier une release déjà finalisée sous `releases/`.

## 1. Le modèle de répertoires

Depuis la racine applicative, habituellement `~/lezidejou` :

```text
~/lezidejou/
├── releases/                     # code immuable, une version par dossier
│   ├── v1.0.0/
│   └── v1.0.1/
├── shared/                       # données et configuration persistantes
│   ├── .env
│   └── storage/
├── current -> releases/v1.0.0    # release servie par Apache
├── deploy-release.sh
└── rollback-release.sh
```

- `releases/` contient le code construit par GitHub Actions. Une release finalisée ne doit plus être éditée : elle doit notamment contenir `.release-ready`, `vendor/autoload.php`, `public/index.php` et `public/build/`.
- `shared/` contient ce qui doit survivre aux releases : le vrai fichier `.env` et le répertoire `storage/`. Chaque release contient des liens vers ces cibles partagées.
- `current` est un **lien symbolique** : il ne copie aucun fichier. Il indique instantanément à Apache quelle release doit être servie. Les scripts versionnés effectuent sa bascule atomique en interne.

Le Document Root OVH est configuré et validé en production sur `lezidejou/current/public`. Apache suit donc `current`, puis sert uniquement le dossier `public/` de la release active.

### Règle impérative OVH : `current` est relatif

La seule forme valide est :

```text
current -> releases/vX.Y.Z
```

La cible ne doit jamais être absolue, ne doit pas contenir `..` et doit respecter exactement le format `releases/vX.Y.Z`.

Incident observé et validé le 23/08/2026 :

```text
current -> /home/.../lezidejou/releases/v1.0.0  => Apache OVH : 403
current -> releases/v1.0.0                       => fonctionnement validé
```

Ce comportement est propre à la résolution Apache de cet hébergement OVH. Le code des scripts conserve des chemins absolus canoniques pour les opérations filesystem, mais écrit toujours une cible relative pour `current`.

## 2. Contrôles SSH avant toute opération

Se connecter puis se placer dans la racine applicative :

```bash
ssh -i ~/.ssh/lezidejou_deploy_ed25519 lezidea@ssh.cluster131.hosting.ovh.net
cd ~/lezidejou
pwd
```

Résultat attendu : le dernier affichage est le répertoire applicatif, par exemple `/homez.2034/lezidea/lezidejou`.

Vérifier le lien actif et sa résolution :

```bash
readlink current
readlink -f current
namei -l current/public
```

Résultats attendus :

- `readlink current` retourne **exactement** `releases/vX.Y.Z` ; ce contrôle lit le texte du lien et détecte un lien absolu.
- `readlink -f current` retourne le chemin réel de la release, sous `.../lezidejou/releases/vX.Y.Z`.
- `namei -l current/public` affiche les composants du chemin ; `current` doit y apparaître comme un lien vers `releases/vX.Y.Z`, et `public` comme un répertoire accessible.

Contrôler la release active et les liens vers les données partagées :

```bash
test -f current/.release-ready && echo ".release-ready: OK"
test -f current/vendor/autoload.php && echo "vendor: OK"
test -f current/public/index.php && echo "front controller: OK"
test -d current/public/build && echo "assets build: OK"
readlink current/.env
readlink current/storage
readlink current/public/storage
test -f shared/.env && echo "shared/.env: OK"
test -d shared/storage && echo "shared/storage: OK"
test -d shared/storage/app/public && echo "shared public storage: OK"
```

Les trois `readlink` doivent pointer respectivement vers `.../shared/.env`, `.../shared/storage` et `.../shared/storage/app/public`. Tous les messages `OK` doivent être affichés. Si un contrôle échoue, ne pas activer ni restaurer une release : investiguer d'abord.

Vérifier l'état des migrations avec la release effectivement active :

```bash
php current/artisan migrate:status
```

Résultat attendu : la commande se termine sans erreur de connexion et les migrations attendues sont marquées `Yes`. Cette commande est consultative : elle ne modifie pas le schéma.

## 3. Préparer une release

Avant de créer un tag ou d’exécuter le workflow de production :

1. Vérifier que la branche et la PR prévues ont une CI verte et ont été relues.
2. Vérifier que le changement est compatible avec la release précédente, en particulier pour les migrations. Préférer les migrations *expand / contract*.
3. Créer une sauvegarde MySQL dans l’espace client OVHcloud et noter l’heure ainsi que l’identifiant de sauvegarde. Une sauvegarde existante trop ancienne ne remplace pas ce contrôle.
4. Vérifier que le Document Root est bien `lezidejou/current/public` et que le lien actuellement actif respecte la règle relative.
5. Préparer un tag SemVer, par exemple `v1.0.1`, seulement lorsque son commit a été validé et que l’autorisation de tagger a été donnée.

La création du tag et le déclenchement du workflow GitHub ne se font que selon le processus de livraison autorisé. Le workflow attend le tag et une confirmation humaine explicite de sauvegarde : `confirm_database_backup=true`.

Dans GitHub : **Actions → Deploy Production → Run workflow**, sélectionner le tag `vX.Y.Z`, saisir `true` pour la confirmation de backup, puis attendre la fin du workflow. Le workflow construit l’archive, transfère l’archive et les scripts par SSH, crée la release, exécute les migrations en staging, écrit `.release-ready`, puis bascule `current`.

## 4. Cycle complet d’une release

Après le succès GitHub Actions, retourner en SSH et contrôler immédiatement :

```bash
cd ~/lezidejou
readlink current
namei -l current/public
test -f current/.release-ready && echo ".release-ready: OK"
php current/artisan migrate:status
```

Pour une release `v1.0.1`, `readlink current` doit retourner exactement :

```text
releases/v1.0.1
```

Le workflow de déploiement appelle les scripts versionnés, qui créent et basculent `current` puis vérifient eux-mêmes le texte du lien après activation. C’est la procédure normative : ne pas créer ni remplacer `current` manuellement lors d’un déploiement normal. Toute manipulation directe de `current` est réservée au diagnostic ou à une récupération exceptionnelle, après diagnostic et selon une procédure approuvée.

Si une activation a échoué après finalisation, relancer le même workflow avec le même tag peut reprendre l’activation : la release prête (`.release-ready`) est réutilisée, sans ré-extraire l’archive ni rejouer les migrations. Vérifier toutefois les logs du workflow avant toute reprise.

## 5. Vérification HTTP et redirection canonique

`public/.htaccess` redirige HTTP et `www.lezidejou.fr` vers l’apex HTTPS avant le front controller Laravel. Depuis un poste ou une session disposant de `curl` :

```bash
curl -I http://lezidejou.fr/
curl -I https://www.lezidejou.fr/
curl -I https://lezidejou.fr/
curl -s -o /dev/null -w "%{http_code}\n" https://lezidejou.fr/
```

Résultats attendus :

- HTTP répond `301` avec `Location: https://lezidejou.fr/...` ; l’URI et la query string sont conservées.
- `https://www.lezidejou.fr/` répond `301` vers `https://lezidejou.fr/`.
- `https://lezidejou.fr/` ne redirige pas en boucle et répond normalement, généralement `200` pour la page d’accueil.

Effectuer ensuite les smoke tests fonctionnels : accueil, navigation publique, une page projet, assets CSS/JS, `/admin/login` et les pages légales. Examiner le dernier journal applicatif sans exposer son contenu sensible :

```bash
tail -n 50 "$(ls -t shared/storage/logs/laravel*.log 2>/dev/null | head -n 1)"
```

Une erreur 403 immédiatement après une bascule doit faire contrôler en premier `readlink current`. Un résultat commençant par `/` est non conforme sur OVH et nécessite un rollback contrôlé ou une correction par les scripts versionnés, jamais une manipulation improvisée de production.

## 6. Rollback applicatif (base compatible)

Utiliser ce cas pour un bug de code, d’assets ou de configuration lorsque la base et ses données restent compatibles avec la release précédente.

```bash
cd ~/lezidejou
bash ./rollback-release.sh v1.0.0 "$PWD"
readlink current
php current/artisan migrate:status
```

Résultat attendu :

```text
releases/v1.0.0
```

Le script vérifie l’intégrité de la release cible, `.release-ready`, les assets et les trois liens shared avant de faire la bascule atomique. Il ne modifie pas le contenu de la release cible et ne restaure pas la base de données. Refaire ensuite les smoke tests de la section 5.

## 7. Rollback avec restauration de base (base incompatible)

Ce cas concerne une migration incompatible, une altération de données ou une release précédente qui ne peut pas fonctionner avec le schéma actuel. L’ordre est impératif :

1. Activer le mode maintenance avec un secret éphémère gardé hors des journaux publics.
2. Restaurer dans l’espace client OVHcloud la sauvegarde MySQL créée avant la release défaillante et attendre la confirmation complète d’OVH.
3. Basculer l’application vers la release compatible.
4. Contrôler les migrations et exécuter les smoke tests via le bypass de maintenance.
5. Désactiver la maintenance seulement après validation.

Commandes applicatives :

```bash
cd ~/lezidejou
MAINTENANCE_SECRET=$(php -r 'echo bin2hex(random_bytes(16));')
php current/artisan down --secret="${MAINTENANCE_SECRET}"

# Après confirmation de restauration de la sauvegarde dans le Manager OVH :
bash ./rollback-release.sh v1.0.0 "$PWD"
php current/artisan migrate:status
php current/artisan up
```

Ne jamais lancer `php artisan migrate:rollback` automatiquement pour tenter de remettre la base en état. En cas de doute sur la compatibilité, conserver le site en maintenance et suivre le runbook détaillé de rollback.

## 8. Erreurs courantes et réaction attendue

| Symptôme | Cause probable | Action sûre |
|---|---|---|
| Apache répond `403` après une bascule | `current` est absolu ou sa topologie est non conforme sur OVH | Exécuter `readlink current`; il doit afficher `releases/vX.Y.Z`. Ne pas modifier le tag ni la release active manuellement. |
| `readlink current` commence par `/` | Lien absolu | Le lien est non conforme. Utiliser le script de rollback vers une release valide ou le correctif versionné ; ne pas fabriquer de lien absolu. |
| `readlink current` contient `..` ou autre chose que `releases/vX.Y.Z` | Lien invalide ou tentative de sortie de `releases/` | Stopper l’opération. Les scripts doivent le refuser avant activation. |
| `.release-ready` est absent | Release incomplète ou non vérifiée | Ne pas activer cette release. Consulter les logs GitHub Actions et reconstruire par le workflow autorisé. |
| Un lien `.env`, `storage` ou `public/storage` est cassé | `shared/` absent ou link incorrect | Réparer la configuration persistante selon la procédure approuvée avant toute activation ; ne pas copier les secrets dans une release. |
| Erreur pendant le workflow | Build, transfert, migration ou activation non terminés | Lire le journal du workflow. Ne pas supprimer la release ni relancer une migration manuellement sans diagnostic. |
| Régression avec base incompatible | Schéma ou données incompatibles | Activer la maintenance, restaurer la sauvegarde OVH, puis exécuter le rollback applicatif. |

## 9. Références

- [Préparation et configuration OVH](ovhcloud-readiness.md)
- [Rollback de production détaillé](production-rollback.md)
- [Scripts versionnés de déploiement](../../scripts/deploy-release.sh)
- [Script versionné de rollback](../../scripts/rollback-release.sh)
