# Plan de déploiement et configuration OVHcloud — LÉZIDÉJOU (Niveau 3)

Ce document constitue la référence technique normative pour le déploiement sur l'infrastructure OVHcloud validée empiriquement.

---

## 1. Infrastructure cible et faits OVHcloud confirmés

* **Offre** : OVHcloud Web Hosting PRO mutualisé (cluster 131, Gravelines).
* **Environnement** : `production` / `stable64`.
* **Moteur PHP CLI** : PHP 8.4.22 natif (`/usr/local/php8.4/bin/php` ou alias `php`).
* **Sécurité** : ModSecurity OVH activé.
* **Accès SSH** :
  * **Hôte public SSH** : `ssh.cluster131.hosting.ovh.net` *(Note : le nom `ssh01.cluster131.gra.hosting.ovh.net` qui apparaît dans le prompt distant n'est pas résolvable depuis Internet et ne doit pas être utilisé)*.
  * **Port** : `22`
  * **Utilisateur** : `lezidea`
  * **Répertoire Home** : `/homez.2034/lezidea`
  * **Répertoire applicatif cible** : `/homez.2034/lezidea/lezidejou` (soit `~/lezidejou`)
  * **Authentification** : Clé ED25519 dédiée validée avec `IdentitiesOnly=yes` et `BatchMode=yes` sans mot de passe.
* **Outillage système validé** :
  * GNU `coreutils 8.30` (`mv -Tf` et `ln -sfn` testés et validés avec succès sur l'hébergement, y compris pour la bascule et le rollback).
  * Git disponible.
  * Absence de Composer et Node.js/npm sur l'hébergement (build intégralement réalisé dans GitHub Actions).

---

## 2. Configuration du domaine et racine Web

* **Domaine canonique** : `https://lezidejou.fr`
* **Redirection** : Apache applique dans `public/.htaccess` la redirection HTTP et `www.lezidejou.fr` vers `https://lezidejou.fr`, avant le front controller Laravel.
* **SSL** : Certificat Let's Encrypt actif.
* **Racine Web (`Document Root`)** : Est configurée et validée en production sur `lezidejou/current/public` (soit `/homez.2034/lezidea/lezidejou/current/public`).

---

## 3. Architecture de déploiement (Bascule atomique et immutabilité validées)

L'architecture par liens symboliques (symlinks) garantit une bascule atomique sans indisponibilité du code :

```text
/homez.2034/lezidea/lezidejou/
├── releases/
│   ├── v1.0.0/
│   │   ├── app/
│   │   ├── bootstrap/
│   │   ├── config/
│   │   ├── public/
│   │   │   ├── build/
│   │   │   ├── storage -> /homez.2034/lezidea/lezidejou/shared/storage/app/public
│   │   │   └── index.php
│   │   ├── vendor/
│   │   ├── .release-ready             # Marqueur de finalisation post-migration
│   │   ├── .env -> /homez.2034/lezidea/lezidejou/shared/.env
│   │   └── storage -> /homez.2034/lezidea/lezidejou/shared/storage
│   └── v1.0.1/
├── shared/
│   ├── .env                           # Secrets et configuration de production (droits 600)
│   └── storage/                       # Persistance (droits 755 / 775)
│       ├── app/
│       │   └── public/
│       ├── framework/
│       │   ├── cache/
│       │   │   └── data/
│       │   ├── sessions/
│       │   └── views/
│       └── logs/                      # Rotation quotidienne laravel-YYYY-MM-DD.log
│           └── laravel-2026-08-20.log
├── current -> releases/v1.0.0          # Lien symbolique vers la release active
├── deploy-release.sh                  # Script de déploiement et reprise
└── rollback-release.sh                # Script de rollback atomique
```

### Mécanismes de bascule, reprise et rollback
* **Bascule atomique — diagnostic ou récupération exceptionnelle uniquement** :
  ```bash
  cd ~/lezidejou
  ln -sfn "releases/<tag>" current.new
  mv -Tf current.new current
  readlink current
  ```
  Le résultat attendu est exactement `releases/<tag>`. Ces commandes ne constituent pas une procédure de déploiement normale : celle-ci passe exclusivement par le workflow et les scripts versionnés. Ne les exécuter qu’à des fins de diagnostic ou de récupération exceptionnelle, après diagnostic et selon une procédure approuvée. Le lien `current` doit toujours être **relatif** : sur l'hébergement OVH, un lien absolu de type `/home/.../releases/<tag>` a provoqué une réponse Apache `403` alors que la même cible relative fonctionne.
* **Reprise d'activation après échec de switch (`resume activation`)** :
  Si `releases/<tag>` existe déjà avec son marqueur `.release-ready` et ses liens valides mais n'est pas pointée par `current`, le script `deploy-release.sh` reprend directement la bascule atomique sans ré-extraire l'archive ni rejouer les migrations.
* **Rollback atomique** (re-pointage immédiat) :
  ```bash
  bash ~/lezidejou/rollback-release.sh <tag_precedent> ~/lezidejou
  ```

---

## 4. Base de données de production & Procédure de sauvegarde OVHcloud

* **Moteur serveur** : MySQL 8.4 (créée sur OVHcloud Web Hosting PRO, quota 2 Go).
* **Hôte** : `lezideaapp.mysql.db`
* **Port** : `3306`
* **Nom de la base** : `lezideaapp`
* **Utilisateur** : `lezideaapp`
* **Mot de passe** : Clé secrète de production, configurée exclusivement dans `shared/.env`.
* **Remarque client SSH** : Le binaire client CLI sur le serveur SSH indique MariaDB 10.3.39. Il s'agit uniquement du client système de l'hébergement et non du serveur de base MySQL 8.4 distant.

### 4.1 Procédure de sauvegarde manuelle dans l'Espace Client OVHcloud
Sur l'hébergement mutualisé OVHcloud, la sauvegarde de la base de données s'effectue via les outils d'infrastructure de l'espace client :
1. Se connecter à l'espace [OVHcloud Web Cloud](https://www.ovh.com/manager/) ;
2. Naviguer vers : **Web Cloud > Hébergements > lezidea > Bases de données** ;
3. Repérer la ligne de la base `lezideaapp` ;
4. Cliquer sur le menu contextuel `...` à droite et sélectionner **Créer une sauvegarde** ;
5. Attendre la finalisation de la création du dump.

> [!IMPORTANT]
> **Confirmation humaine obligatoire** : GitHub Actions n'a pas accès à l'API de gestion OVH pour vérifier l'existence de la sauvegarde. Une vérification automatique ne doit jamais être présumée. Le déclenchement manuel du workflow impose obligatoirement la confirmation explicite `confirm_database_backup=true`.

### 4.2 Procédure de restauration MySQL OVHcloud (en cas de rupture de schéma)
1. Dans **Bases de données > lezideaapp > `...`**, sélectionner **Restaurer une sauvegarde** ;
2. Choisir la sauvegarde créée juste avant le déploiement ;
3. Valider et attendre la notification de confirmation d'OVHcloud avant de procéder au rollback applicatif.

---

## 5. Configuration de l'environnement de production (`shared/.env`)

Le fichier `/homez.2034/lezidea/lezidejou/shared/.env` est en place sur le serveur avec les permissions `600` et contient l'`APP_KEY` unique générée pour la production.

### Modèle de référence de la configuration de production :
```ini
APP_NAME="LÉZIDÉJOU"
APP_ENV=production
APP_KEY=base64:CLE_SECRETE_PRODUCTION_GENEREE
APP_DEBUG=false
APP_URL=https://lezidejou.fr

APP_LOCALE=fr
APP_FALLBACK_LOCALE=fr
APP_FAKER_LOCALE=fr_FR

APP_MAINTENANCE_DRIVER=file

BCRYPT_ROUNDS=12

LOG_CHANNEL=stack
LOG_STACK=daily
LOG_LEVEL=error
LOG_DAILY_DAYS=14

DB_CONNECTION=mysql
DB_HOST=lezideaapp.mysql.db
DB_PORT=3306
DB_DATABASE=lezideaapp
DB_USERNAME=lezideaapp
DB_PASSWORD="MOT_DE_PASSE_PRODUCTION"
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci

SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
SESSION_SECURE_COOKIE=true
SESSION_HTTP_ONLY=true
SESSION_SAME_SITE=lax

BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync

CACHE_STORE=file

MAIL_MAILER=log
MAIL_FROM_ADDRESS="contact@lezidejou.fr"
MAIL_FROM_NAME="LÉZIDÉJOU"

VITE_APP_NAME="${APP_NAME}"
```

> [!IMPORTANT]
> **Action manuelle requise sur OVH avant le premier déploiement v1.0.0** :
> Mettre à jour le fichier `shared/.env` réel sur le serveur OVH (`/homez.2034/lezidea/lezidejou/shared/.env`) avec les paramètres de rotation des logs :
> ```ini
> LOG_STACK=daily
> LOG_DAILY_DAYS=14
> ```
> *(Note : le fichier `shared/.env` de production n'est pas versionné dans Git et doit être mis à jour manuellement par l'administrateur).*

---

## 6. Clé SSH ED25519 & Secrets GitHub Actions

Pour permettre à GitHub Actions de déployer de manière sécurisée et non interactive :

### 6.1 Secrets GitHub configurés dans le dépôt (`Settings > Secrets and variables > Actions`) :
| Nom du Secret | Rôle / Valeur | Statut |
|---|---|---|
| `OVH_SSH_HOST` | `ssh.cluster131.hosting.ovh.net` | Configuré |
| `OVH_SSH_PORT` | `22` | Configuré |
| `OVH_SSH_USER` | `lezidea` | Configuré |
| `OVH_TARGET_DIR` | `/homez.2034/lezidea/lezidejou` | Configuré |
| `OVH_SSH_PRIVATE_KEY` | Clé privée ED25519 dédiée | Configuré |
| `OVH_SSH_KNOWN_HOSTS` | Empreinte SSH authentifiée | Configuré |

---

## 7. Cycle de vie standard d'un déploiement de production

```text
┌────────────────────────────────────────────────────────┐
│ 1. AVANT LA MEP (Responsabilité Administrateur)        │
│    - Création de la sauvegarde MySQL sur OVH Manager   │
│    - Déclenchement manuel GitHub Actions avec :        │
│      release_tag=vX.Y.Z et confirm_database_backup=true│
└──────────────────────────┬─────────────────────────────┘
                           │
┌──────────────────────────▼─────────────────────────────┐
│ 2. PENDANT LA MEP (Workflow CI/CD automatisé)          │
│    - Validation du tag SemVer via variables d'env      │
│    - Build Composer (--no-dev) & Build Vite (assets)   │
│    - Transfert archive & scripts deploy / rollback     │
│    - Precheck topologie current (symlink valide/absent)│
│    - Extraction staging (/releases/.vX.Y.Z.staging.<pid>)│
│    - Preflights intégrité & liens shared/.env, storage │
│    - Exécution des migrations (php artisan migrate)    │
│    - Création du marqueur .release-ready               │
│    - Finalisation immuable (/releases/vX.Y.Z)          │
│    - Bascule atomique du symlink current (mv -Tf)      │
└──────────────────────────┬─────────────────────────────┘
                           │
┌──────────────────────────▼─────────────────────────────┐
│ 3. APRÈS LA MEP (Validation)                           │
│    - Consultation des journaux quotidiens              │
│    - Création du premier compte administrateur         │
│    - Vérification du Document Root OVH : current/public│
│    - Smoke tests de navigation publique (HTTPS)        │
└──────────────────────────┬─────────────────────────────┘
                           │
┌──────────────────────────▼─────────────────────────────┐
│ 4. EN CAS D'ANOMALIE (Rollback)                        │
│    - CAS A (DB compatible) : rollback-release.sh <tag> │
│    - CAS B (DB incompatible) : maintenance +           │
│      restauration OVH + rollback-release.sh            │
│    (Voir runbook détaillé docs/runbooks/production-    │
│     rollback.md)                                       │
└────────────────────────────────────────────────────────┘
```

> [!NOTE]
> **Absence de test HTTP automatique post-bascule dans le workflow CI/CD** :
> Aucun appel `curl` automatique vers `https://lezidejou.fr` n'est intégré à la fin du workflow GitHub Actions. Les contrôles HTTP complets (Document Root, cible relative de `current`, HTTPS et hôte canonique) restent une vérification humaine en conditions réelles après chaque release ; voir le guide d'opérations de production.

---

## 8. Création du premier compte administrateur en production

Cette étape s'exécute **après le premier déploiement et les migrations**, et **avant la recette du panel `/admin`** :

### Procédure opérationnelle :
1. **Connexion SSH au serveur** :
   ```bash
   ssh -i ~/.ssh/lezidejou_deploy_ed25519 lezidea@ssh.cluster131.hosting.ovh.net
   ```
2. **Exécution de la commande dédiée de création d'administrateur** :
   ```bash
   php ~/lezidejou/current/artisan lezidejou:admin:create
   ```
3. **Saisie interactive des informations** :
   * Nom et prénom de l'administrateur ;
   * Adresse électronique officielle d'administration ;
   * Mot de passe fort respectant strictement la politique de complexité (au moins 12 caractères, majuscule, minuscule, chiffre, symbole).
4. **Première connexion sur `/admin/login`** :
   * Saisir l'adresse électronique et le mot de passe créés.
5. **Enrôlement TOTP (MFA) obligatoire** :
   * Scanner le QR Code ou saisir la clé secrète dans une application d'authentification 2FA (ex: Google Authenticator, 2FAS, Bitwarden) ;
   * Saisir le code à 6 chiffres généré pour valider l'enrôlement.
6. **Sauvegarde des 8 codes de récupération** :
   * Copier immédiatement les 8 codes de récupération à usage unique générés par Filament ;
   * Les enregistrer dans un gestionnaire de mots de passe sécurisé hors dépôt Git et hors documentation.
7. **Procédure de récupération en cas de perte de l'appareil TOTP** :
   * Utiliser l'un des 8 codes de récupération à usage unique sur l'interface de connexion `/admin`.
8. **Cas de perte conjointe du TOTP et des codes de récupération** :
   * Ne jamais exécuter de manipulation SQL directe non documentée. Une procédure administrative contrôlée (avec vérification d'identité) devra être formellement définie et validée avant toute réinitialisation manuelle de MFA.

---

## 9. Sécurité et durcissement de production (Jalon N3-4)

### 9.1 Document Root et isolation
- **Exposition publique stricte** : Le Document Root configuré et validé en production dans le Manager OVH est `/homez.2034/lezidea/lezidejou/current/public`.
- **Protection des répertoires sensibles** : Le fichier `public/.htaccess` conserve `Options -MultiViews -Indexes`. Les répertoires `.env`, `vendor/`, `storage/`, `bootstrap/`, `config/`, `database/`, `routes/`, `docs/`, `scripts/` et `.git` ne sont jamais exposés au serveur web.

### 9.2 En-têtes HTTP de sécurité (`SecurityHeadersMiddleware`)
Les en-têtes sont injectés au niveau applicatif via `App\Http\Middleware\SecurityHeadersMiddleware` sur l'ensemble des réponses :
- `X-Content-Type-Options: nosniff` (protection contre le MIME-type sniffing).
- `X-Frame-Options: SAMEORIGIN` (protection contre le clickjacking).
- `Referrer-Policy: strict-origin-when-cross-origin` (préservation de la confidentialité des référents).
- `Permissions-Policy: camera=(), microphone=(), geolocation=()` (désactivation des API sensibles inutilisées).
- `Strict-Transport-Security: max-age=86400` (HSTS actif uniquement sur requêtes HTTPS en environnement `production`).
  - **Décision HSTS** : Durée prudente de 24 heures (`max-age=86400`), sans `preload` et sans `includeSubDomains` pour la première mise en production, évitant tout blocage irréversible. La durée sera augmentée (vers 6 mois ou 1 an) après validation réelle et confirmation de la stabilité HTTPS en production.
  - **Décision Content-Security-Policy (CSP)** : Aucune CSP stricte aveugle n'est introduite en v1 afin de ne pas bloquer les composants dynamiques (Livewire, Filament admin, Vite assets, Swiper). Une politique CSP fine avec gestion de nonces/hashes est documentée comme évolution future.

### 9.3 Domaine canonique et HTTPS
- **Domaine canonique** : `https://lezidejou.fr` (apex).
- `URL::forceScheme('https')` est activé en environnement `production` dans `AppServiceProvider`.
- La redirection HTTP et `www.lezidejou.fr` vers `https://lezidejou.fr` est assurée par `public/.htaccess` avant Laravel. Le DNS doit toutefois faire parvenir les deux hôtes à cet hébergement.

### 9.4 Pare-feu applicatif OVHcloud (ModSecurity)
- **État** : ModSecurity (pare-feu applicatif mutualisé OVH) est **activé** sur l'hébergement.
- **Règle** : Aucune désactivation préventive.
- **Matrice de tests réels de recette (Jalon N3-6)** :
  1. Navigation publique sur l'ensemble des 13 routes éditoriales et catalogue ;
  2. Accès à la mire d'authentification `/admin/login` ;
  3. Soumission du formulaire d'authentification et validation TOTP ;
  4. Interactions avec les composants Livewire et formulaires Filament ;
  5. Chargement des polices auto-hébergées et médias WebP/SVG ;
  6. Vérification des logs d'erreurs Apache et PHP pour détecter d'éventuels faux positifs ModSecurity.

---

## 10. Statut des jalons Niveau 3

* **N3-0** : Décisions d'infrastructure et mise à jour du plan (Terminé le 20 août 2026).
* **N3-1** : Pipeline CI/CD et script de déploiement (Terminé, testé et poussé le 20 août 2026, SHA `3d9d5b2c5dae973eaaf0f09a6edbca27317cf887`).
* **N3-2** : OVH/env (Configuration de l'environnement `shared`, `.env`, clé SSH et secrets GitHub Actions) (Terminé et poussé le 20 août 2026, SHA `e9c5d67401717842b25e677b7e361e7773705093`).
* **N3-3** : Backup/rollback (Garde-fou confirmation backup, migrations en staging, scripts de rollback et runbook) (Terminé et poussé le 20 août 2026, SHA `e323a1eefe9445e22638b85fb53b82bcd19939f9`).
* **N3-4** : Sécurité (Headers HTTP, HSTS prudent, vérification isolation et matrice de recette ModSecurity) (Terminé, validé et poussé le 20 août 2026, SHA `ab323e7e4d785ec91d5f23e25c50cba68b3f5c83`).
* **N3-5** : Première release `v1.0.0` effectuée en production le 23 août 2026.
* **N3-6** : Recette/GO (vérifications de production en cours ; un correctif séparé durcit la topologie relative de `current` après le constat Apache OVH `403` sur la cible absolue).
