# Checklist de mise en production — Récré

> Hébergement **mutualisé, sous Apache**. Le déploiement se fait par `git pull`
> sur le serveur, puis build sur place. Même système que LintellO.

---

## ⛔ Avant tout : ce qui bloque encore

Ces quatre points ne se codent pas, et l'application **dit elle-même** qu'elle
n'est pas prête tant qu'ils ne sont pas réglés — les pages de CGU et de mentions
légales affichent un bandeau rouge « à compléter avant toute mise en service ».

- [ ] **Éditeur et hébergeur** renseignés dans `templates/home/mentions.html.twig`.
- [ ] **Base légale du traitement** des données personnelles, et information des
      représentants légaux des élèves mineurs.
- [ ] Inscription au **registre des traitements** de l'établissement.
- [ ] **Politique de sauvegarde.** Sans elle, une perte est définitive ; avec une
      rétention infinie, elle annule en silence la suppression à trois ans décidée
      par l'[ADR-0011](decisions/0011-conservation-trois-ans.md).

---

## 1. Sur le serveur, une fois pour toutes

### Le document root

Il doit pointer sur **`public/`**. C'est le point le plus souvent raté sur un
mutualisé, et l'erreur ne se voit pas tout de suite : la page d'accueil répond,
et tout le reste renvoie un 404 d'Apache.

Si l'hébergeur ne permet pas de déplacer le document root, poser à la racine du
projet un `.htaccess` qui n'y renvoie que le trafic — et **surtout pas** un lien
symbolique, qui exposerait `.env`, `src/` et `vendor/` :

```apache
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule ^(.*)$ public/$1 [L]
```

Le `.htaccess` d'Apache que Symfony attend est déjà versionné dans
[`public/.htaccess`](../public/.htaccess). Sans lui, rien ne fonctionne : en
développement c'est nginx qui aiguille, et il n'est pas là.

### `.env.local`

Le fichier n'est **jamais** suivi par git : il est à créer sur le serveur, et
c'est lui qui porte les vraies valeurs. Les fichiers suivis — `.env`, `.env.dev`,
`.env.test` — ne contiennent que des valeurs inoffensives.

```bash
cat > .env.local <<'EOF'
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=<32 caractères hexadécimaux>
DATABASE_URL="mysql://utilisateur:motdepasse@serveur:3306/base?serverVersion=8.0&charset=utf8mb4"
DEFAULT_URI=https://votre-domaine.fr
EOF
```

```bash
# Pour générer le secret :
php -r 'echo bin2hex(random_bytes(16)), PHP_EOL;'
```

⚠️ **`serverVersion` doit correspondre au serveur réel.** En local c'est MariaDB
11.4, sur le mutualisé c'est MySQL : une valeur fausse fait générer à Doctrine du
SQL que la base refuse, et l'erreur ne parle pas de ça.

### Les clés Brevo

Trois lignes de plus dans `.env.local`, et rien de tout ça ne va dans `.env` :

```
MAILER_DSN="brevo+api://VOTRE_CLE@default"
MAILER_FROM=recre@votre-domaine.fr
CONTACT_EMAIL=vous@votre-domaine.fr
```

⚠️ **`MAILER_FROM` doit être un expéditeur validé dans le compte Brevo.** Une
adresse non validée fait échouer l'envoi côté fournisseur, et l'application ne le
dit nulle part : elle journalise et continue ([ADR-0013](decisions/0013-mail-ne-bloque-jamais.md)).

⚠️ **Authentifier le domaine chez Brevo (SPF + DKIM)** avant de compter dessus.
Sans ça les messages arrivent en indésirables, ce qui est indiscernable de « rien
n'est parti » pour la personne qui attend son lien de réinitialisation.

⚠️ **Tester depuis le web, pas depuis le shell SSH.** Sur le mutualisé, le shell
n'a pas la même sortie réseau que le PHP qui sert les pages : le 2026-09-02,
`mailer:test` en SSH a reçu un `Connection refused` immédiat sur
`api.brevo.com:443`, et une sonde TCP a trouvé injoignable jusqu'au relais d'OVH
lui-même. Ce n'est pas concluant sur ce que peut faire le site. La vraie
vérification est une demande d'accès ou un « mot de passe oublié » **depuis le
domaine de production**.

Pour lire ce qui s'est passé — chaque envoi laisse une ligne, réussi ou non :

```bash
tail -n 30 var/log/app-*.log
```

Le fichier porte la date du jour (`app-2026-09-02.log`) : c'est un
`rotating_file`, qui garde quatorze jours et pas davantage. **Il n'existe pas
tant que rien n'a été journalisé** — un `Aucun fichier de ce type` signifie donc
qu'aucun envoi n'a encore été tenté depuis ce déploiement, pas que le journal est
cassé.

**Aucun worker Messenger à prévoir.** Les e-mails partent pendant la requête ;
c'est justement pour ça, un mutualisé n'héberge pas de démon. Rien à surveiller,
rien qui puisse s'arrêter en silence.

Pour tester le transport sans passer par un écran :

```bash
php bin/console mailer:test destinataire@exemple.fr --from=recre@votre-domaine.fr
```

### Les clés reCAPTCHA

Deux lignes de plus dans `.env.local`, prises sur
<https://www.google.com/recaptcha/admin> (type **v2, case à cocher**) :

```
RECAPTCHA_SITE_KEY=6Lxxxxxxxxxxxxxxxxxx
RECAPTCHA_SECRET=6Lxxxxxxxxxxxxxxxxxx
```

Elles ne protègent qu'une page : le **formulaire public de demande d'accès**,
seul point ouvert à tout internet. Vides, le formulaire fonctionne sans
vérification et la page ne charge aucun script.

⚠️ **Le domaine doit être déclaré dans la console Google.** Sinon la case
s'affiche, la vérification échoue systématiquement, et le formulaire devient
inutilisable sans rien dire de plus que « cochez la case ». À vérifier juste
après la mise en place, depuis une fenêtre de navigation privée.

⚠️ **Le reCAPTCHA fait sortir une donnée du serveur** : Google reçoit l'adresse
IP du visiteur et dépose un cookie. Les CGU et les mentions légales le disent
déjà ; c'est un point à reporter dans le registre des traitements, au même titre
que le prestataire d'envoi d'e-mails.

### Le cron de purge

Une fois par an suffit — [ADR-0011](decisions/0011-conservation-trois-ans.md).
La commande **ne fait rien sans `--executer`** : à programmer telle quelle, elle
enverra la liste par mail sans rien toucher, ce qui est le bon réglage tant que
personne n'a relu ce qu'elle propose.

```
0 4 1 9 * cd ~/recre && php bin/console app:purger-comptes --env=prod
```

---

## 2. À chaque déploiement

> **Sur le mutualisé OVH, le projet est à la racine du home** (`~`), pas dans un
> sous-dossier : la trace d'erreur du 2026-09-02 donnait `/home/<compte>/vendor/`.
>
> **Et le shell SSH ne charge pas `~/.bashrc` tout seul.** Sans ça, ni `node` ni
> `composer` ne sont dans le `PATH`, et on croit à tort qu'ils ne sont pas
> installés — c'est le même constat que sur EvoHub, qui tourne sur le même
> cluster. Commencer par `source ~/.bashrc`.

```bash
git pull

composer install --no-dev --optimize-autoloader

npm ci
npm run build

php bin/console doctrine:migrations:migrate --no-interaction --env=prod
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prod
```

Dans l'ordre, et chaque ligne pour une raison :

| | |
|---|---|
| `--no-dev` | Sans lui, le profileur Symfony part en production. Il expose la configuration, les requêtes SQL et les jetons de session. |
| `--optimize-autoloader` | Une carte de classes figée, au lieu d'une recherche sur le disque à chaque requête. |
| `npm ci` puis `npm run build` | Les assets compilés **ne sont pas versionnés** (`public/build/` est ignoré) : ils se construisent sur le serveur. |
| `migrate` avant `cache:clear` | Une migration qui échoue doit le faire **avant** que le cache ne pointe sur un schéma qui n'existe pas. |

### Après une migration, toujours

```bash
php bin/console app:reconcilier-stock --env=prod
```

Elle ne fait rien sans `--executer` : elle vérifie que `Product.stock` est bien
la somme exacte du journal des mouvements. Tout le reste en dépend — alertes,
cadencier, couverture, propositions de commande — et **rien d'autre ne le
contrôle**. C'est ainsi que deux entrées sont restées sans effet pendant deux
mois sans que personne ne s'en aperçoive.

---

## 3. Vérifications après le premier déploiement

- [ ] `https://votre-domaine.fr/` affiche l'accueil, photo comprise.
- [ ] **Aucune barre de debug** en bas de l'écran. Si elle est là, `APP_ENV` n'est
      pas à `prod` — arrêter tout et corriger avant d'aller plus loin.
- [ ] `/conditions-generales` et `/mentions-legales` répondent.
- [ ] La connexion fonctionne, et la **modale d'acceptation des CGU** apparaît.
- [ ] Une vente passe, la caisse se clôture.
- [ ] Un export PDF se télécharge — **en partant du menu**, pas de l'URL directe.
- [ ] Une demande d'accès envoyée depuis `/demande-acces` arrive dans la boîte de
      `CONTACT_EMAIL`, et le demandeur reçoit son accusé.
- [ ] Un « mot de passe oublié » demandé sur son propre compte arrive, et le lien
      permet bien de se reconnecter. À vérifier **avant** d'ouvrir les comptes des
      élèves : après, c'est eux qui découvriront que ça ne marche pas.
- [ ] `https://` partout, y compris sur la page de connexion. Un mot de passe
      d'élève sur un réseau d'établissement ne doit pas circuler en clair.

---

## 4. Ce qui ne marche pas sur un mutualisé, et qu'il faut savoir

- **Les e-mails ne passent pas par le serveur, mais par Brevo** — voir plus haut.
  Tant que `MAILER_DSN` reste à `null://null`, rien ne part : la demande d'accès
  s'enregistre quand même, mais personne n'est prévenu, et le « mot de passe
  oublié » affiche son écran de confirmation sans qu'aucun lien ne parte.
- **Les identifiants ne sont jamais envoyés**, et ça ne changera pas. Ceux que
  crée l'import de membres s'affichent **une seule fois** à l'écran ; il faut
  imprimer la page avant de la quitter.
- **Les polices viennent de Google Fonts par CDN.** Si le serveur ou le poste
  n'a pas d'accès sortant, Caprasimo et Figtree ne chargent pas et la
  typographie retombe sur celle du système.
- **`npm` doit exister sur l'hébergement.** S'il n'y est pas, construire les
  assets en local et transférer `public/build/` — mais alors il faut le refaire
  à chaque déploiement, et personne n'y pense la troisième fois.
- Le **mode sombre du back-office** n'est pas thématisé : un navigateur en
  sombre affichera des restes bleus. Ce n'est pas un bug d'installation.
