# Bascule du serveur vers notre dépôt git

**Staging : fait le 06/08/2026.** Production : à faire — **et PAS avec le même ordre d'opérations** (étape 4 bis).

> ⚠️ **Correction du 08/08/2026 (incident #137).** L'étape 5 « recaler les migrations » était
> fautive : elle comparait des **noms** de migrations et en concluait que les objets existaient.
> Appliquée au staging le 06/08 puis à la production, elle a enregistré des migrations jamais
> exécutées. Elle a été **réécrite** et exige désormais une preuve objet par objet.
> Contexte et état réel de la production : `19_DIFF_SCHEMA_PROD.md`.

Objectif : que le code déployé provienne de `AAZTEKDEV/EFEKTIVACADEMIE-BACK` et non d'un dépôt
tiers obsolète, afin d'obtenir traçabilité et retour arrière.

---

## Ce qui a été fait sur le staging

| Étape | Résultat |
|---|---|
| Export de la base | `~/backups/staging-db-20260806-145135.sql` (526 Ko, intègre) |
| Archive du code | `~/backups/staging-code-20260806-145135.tar.gz` (1,5 Mo) |
| Sauvegarde du `.env` | `~/backups/staging-env-20260806-145135.backup` |
| Clé de déploiement | Générée sur le serveur, déclarée sur GitHub **en lecture seule** |
| Dépôt repointé | `Rigictech/e-learning-api` → `AAZTEKDEV/EFEKTIVACADEMIE-BACK` |
| Alignement | Branche `development`, commit `ff8c98c` |
| Dépendances, permissions, caches | `composer install --no-dev`, `chown www-data`, caches vidés |
| Table `migrations` recalée | 43 → 70 entrées (27 ajoutées en batch 9) — ⚠️ **opération fautive**, cf. incident #137 |

**Vérifications après bascule :**

- API staging : **HTTP 200** · Front staging : **HTTP 200**
- `git status` : **0 fichier divergent** (contre 318 avant)
- `migrate --pretend` : **« Nothing to migrate »**
- Les correctifs de la PR #11 sont bien déployés : `RoleHierarchy.php` présent, contrôle de
  propriété sur les certificats présent, évaluation du seuil de quiz présente
- `.env` intact, 289 Mo de fichiers téléversés préservés
- Les copies manuelles de l'équipe (`app-old-16-10/`, `app--13-05-2026/`…) sont conservées

---

## La procédure, étape par étape

### Préalable — vérifier ce qui risque d'être écrasé

```bash
cd <racine-laravel>
git status --porcelain | grep -c '^ M'    # fichiers suivis, seront écrasés
git status --porcelain | grep -c '^??'    # non suivis, seront PRÉSERVÉS
git check-ignore .env && echo ".env protégé"
git ls-files storage/app/public | wc -l   # doit être proche de 0
```

> **Règle de sûreté : ne jamais lancer `git clean`.** `git reset` et `git checkout` ne touchent
> pas aux fichiers non suivis ; `git clean` les supprimerait — y compris les 289 Mo de
> certificats, avatars et ressources de cours.

### 1. Sauvegarder

```bash
TS=$(date +%Y%m%d-%H%M%S); mkdir -p ~/backups
export MYSQL_PWD=$(grep '^DB_PASSWORD=' .env | cut -d= -f2-)
mysqldump -u root --single-transaction --quick <base> > ~/backups/db-$TS.sql
tar czf ~/backups/code-$TS.tar.gz --exclude=vendor --exclude=storage --exclude=node_modules -C .. "$(basename $PWD)"
cp .env ~/backups/env-$TS.backup
unset MYSQL_PWD
```

Contrôler que le dump se termine par `Dump completed` avant d'aller plus loin.

### 2. Authentifier le serveur auprès de GitHub

Une clé de déploiement en lecture seule, propre au serveur — jamais un compte personnel.

```bash
ssh-keygen -t ed25519 -f ~/.ssh/deploy_efektiv_back -N "" -C "deploy-efektiv-back@$(hostname)"
cat >> ~/.ssh/config <<'CFG'

Host github-efektiv-back
  HostName github.com
  User git
  IdentityFile ~/.ssh/deploy_efektiv_back
  IdentitiesOnly yes
CFG
chmod 600 ~/.ssh/config
cat ~/.ssh/deploy_efektiv_back.pub    # à déclarer sur GitHub
```

Déclaration côté GitHub (lecture seule) :

```bash
gh api repos/AAZTEKDEV/EFEKTIVACADEMIE-BACK/keys -X POST \
  -f title="Serveur OVH ns3233390 (lecture seule)" -f key="<clé publique>" -F read_only=true
```

Test : `ssh -T github-efektiv-back` doit répondre « successfully authenticated ».

### 3. Repointer et aligner

```bash
git remote get-url origin > ~/backups/ancien-remote.txt   # trace
git remote set-url origin github-efektiv-back:AAZTEKDEV/EFEKTIVACADEMIE-BACK.git
git fetch origin --prune
git checkout -f -B <branche> origin/<branche>    # development pour le staging, main pour la prod
```

### 4. Finaliser le déploiement

```bash
composer install --no-dev --optimize-autoloader
sudo chown -R www-data:www-data storage bootstrap/cache
php artisan config:clear && php artisan route:clear && php artisan view:clear
```

### 4 bis. ⛔ AVANT tout `migrate` en production : convertir la base en InnoDB

> **L'ordre « code puis `migrate` » de cette procédure est FAUX pour la production.**
> Mesuré le 08/08 sur une réplique fidèle de la base de production (issue #157) :
>
> ```
> 2026_08_06_100001_create_login_events_table ......... FAIL
> SQLSTATE[HY000] 1824 Failed to open the referenced table 'users'
> ```
>
> Les tables des lots L1/L2 naissent en **InnoDB** (moteur par défaut) et déclarent une clé
> étrangère vers `users`, qui est en **MyISAM** en production. InnoDB ne peut pas référencer
> MyISAM : MySQL refuse, la migration échoue, **le déploiement s'arrête net**. Dix migrations
> passent avant celle-là — l'échec survient donc en cours de route, base à moitié migrée.
>
> Le blocage **ne vient pas de nos correctifs** : il tient aux migrations L1/L2 elles-mêmes.
> Il ne s'est jamais manifesté parce que L1/L2 n'ont été déployés que sur le staging, déjà
> converti en InnoDB.

**Séquence obligatoire en production** (détail et inventaire dans l'issue #136) :

| # | Opération | Sans elle |
|---|---|---|
| a | Purger les références pendouillantes | la création des FK échoue (errno **1452**) |
| b | `ALTER … ENGINE=InnoDB` sur les 58 tables | `migrate` échoue (errno **1824**) |
| c | `php artisan migrate --force` | — |
| d | Créer les clés étrangères manquantes | — |
| e | Diff outillé (`scripts/schema/`) | aucune preuve que le schéma est correct |

La base de production fait **1,5 Mo** : la conversion se compte en secondes. Ce n'est pas le
volume qui demande de la préparation, c'est cet ordre.

**Le staging n'est pas concerné** (déjà InnoDB) : sur staging, l'ordre historique
code → `migrate` reste valable.

### 5. Recaler les migrations — ⚠️ procédure corrigée le 08/08/2026

> **La version précédente de cette étape a causé l'incident #137. Elle est conservée ci-dessous
> comme contre-exemple, pas comme alternative.**
>
> ```bash
> # ❌ NE JAMAIS FAIRE — c'est cette commande qui a créé le problème
> comm -13 /tmp/db.txt /tmp/disk.txt | while read m; do
>   echo "INSERT INTO migrations (migration,batch) SELECT '$m',$BATCH ..."
> done | mysql -u root <base>
> ```
>
> **Comparer des NOMS de migrations ne prouve rien.** Ce `comm` compare les noms de fichiers du
> disque aux lignes de la table `migrations` et conclut « les objets existent ». Le 06/08, il a
> enregistré 23 migrations sans les exécuter. Le 08/08, `courses.category_id` s'est révélée
> absente alors que sa migration était marquée appliquée : Laravel croyait le schéma à jour, ne
> rejouerait jamais la migration, et EA-001 a échoué dessus (errno 1072). Le diff complet
> (`19_DIFF_SCHEMA_PROD.md`) a ensuite montré que le recalage avait aussi masqué **11 divergences
> silencieuses** : des colonnes bien présentes, mais avec un type, une nullabilité ou un défaut
> différents de ce que définit le code.

Le recalage n'est légitime que si l'on a **prouvé, objet par objet**, que le schéma réel correspond
déjà à ce que produisent les migrations. Un nom de migration n'est pas une preuve ; une colonne
présente n'est pas une preuve non plus tant que son type, sa nullabilité et son défaut n'ont pas été
comparés.

#### 5.1 Établir la preuve (obligatoire, avant toute écriture)

```bash
# Schéma de référence : ce que le code CROIT être le schéma
mysql -u root -e "DROP DATABASE IF EXISTS refschema; CREATE DATABASE refschema CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
DB_DATABASE=refschema php artisan migrate:fresh --force

# Extraction des deux schémas (structure seule — lecture seule sur la cible)
sed 's/@@DB@@/refschema/g' scripts/schema/extract_schema.sql | mysql -u root -N --batch > /tmp/ref.tsv
sed 's/@@DB@@/<base>/g'    scripts/schema/extract_schema.sql | mysql -u root -N --batch > /tmp/cible.tsv

# Comparaison objet par objet
python3 scripts/schema/diff_schema.py /tmp/ref.tsv /tmp/cible.tsv > /tmp/diff.json
```

Outillage et limites : `scripts/schema/README.md`.

#### 5.2 Lire le verdict

| Résultat du diff | Signification | Geste |
|---|---|---|
| Aucun écart hors clés étrangères | Le schéma est réellement conforme | Le recalage est **légitime** → 5.3 |
| Objets **manquants** | Des migrations enregistrées n'ont pas été exécutées | **Ne pas recaler.** Migration de rattrapage idempotente d'abord |
| Objets **divergents** | Le schéma existe mais diffère du code | **Ne pas recaler.** Arbitrage explicite, une décision par écart |
| Clés étrangères absentes en masse sur base MyISAM | Attendu (MyISAM les ignore) | Traiter via #136, ne bloque pas le recalage |

**En cas de doute, ne pas recaler.** Une migration non enregistrée qui se rejoue est au pire une
erreur bruyante et corrigible ; une migration enregistrée à tort est un mensonge permanent que plus
aucun déploiement ne rattrapera.

#### 5.3 Recaler, migration par migration

Uniquement si 5.2 conclut « conforme ». Le recalage est nominatif et tracé — jamais un `INSERT` en
masse issu d'un `comm` :

```bash
BATCH=$(mysql -u root <base> -N -e "SELECT IFNULL(MAX(batch),0)+1 FROM migrations;")
# Une ligne par migration dont les objets ont été VÉRIFIÉS présents et conformes en 5.1
for m in <migration_1> <migration_2>; do
  mysql -u root <base> -e "INSERT INTO migrations (migration,batch)
    SELECT '$m',$BATCH FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM migrations WHERE migration='$m');"
done
```

#### 5.4 Contrôler — et savoir ce que le contrôle ne dit pas

```bash
php artisan migrate --pretend --force   # « Nothing to migrate » attendu
```

> ⚠️ **« Nothing to migrate » ne prouve pas que le schéma est correct.** Cette commande ne lit que
> la table `migrations` : c'est précisément ce qu'affichait la production le 06/08, alors que
> `courses.category_id` manquait. **La seule preuve reste le diff de 5.1**, à relancer après le
> recalage.

#### 5.5 Vérifier aussi le cas miroir

Le diff révèle deux symétries, et l'étape 5 ne traitait que la première :

- migration **enregistrée**, objet **absent** → ne se rattrapera jamais seul ;
- migration **non enregistrée**, objet **déjà présent** → **le prochain `migrate` échouera**
  (errno 1060, `Duplicate column`).

Pour tout objet présent en base dont la migration n'est pas enregistrée, vérifier que la migration
porte bien une garde `Schema::hasColumn()` / `hasTable()`. Sinon, ajouter la garde **avant** de
déployer. Deux migrations étaient dans ce cas au 08/08 (cf. `19_DIFF_SCHEMA_PROD.md` §8).

### 6. Vérifier

```bash
curl -s -o /dev/null -w "%{http_code}\n" <url-api>/plans   # 200 attendu
git status --porcelain | grep -c '^ M'                     # 0 attendu
```

### Retour arrière

```bash
git remote set-url origin $(cat ~/backups/ancien-remote.txt)
tar xzf ~/backups/code-<TS>.tar.gz -C ..
mysql -u root <base> < ~/backups/db-<TS>.sql
```

---

## Pour la production — points de vigilance supplémentaires

1. **Branche `main`, pas `development`.** Or `main` n'a pas encore reçu les correctifs :
   à fusionner d'abord, conformément à la règle « merge sur `main` par sprint ».
2. **Prendre un snapshot OVH** en plus des exports : la production justifie les deux niveaux.
3. **Mettre le site en maintenance** pendant l'opération : `php artisan down` puis `up`.
4. **Choisir un créneau creux** : l'opération dure quelques minutes, mais `composer install`
   peut brièvement perturber le service.
5. ⛔ **La base de production est en MyISAM : `migrate` ÉCHOUERA** tant qu'elle n'est pas
   convertie en InnoDB (errno 1824 sur `create_login_events_table`, issue #157). **L'étape 4 bis
   est un préalable non négociable**, pas une recommandation — et elle exige elle-même une purge
   préalable des références pendouillantes (issue #136). Prévoir ce temps dans la fenêtre.
6. ⚠️ **La table `migrations` de la production a été recalée le 06/08 — à tort** (incident #137).
   L'étape 5 n'est donc *pas* sans objet : elle doit être **rejouée en entier**, à commencer par la
   preuve 5.1. Le diff `19_DIFF_SCHEMA_PROD.md` établit l'état réel au 08/08 :
   `courses.category_id` est enregistrée mais absente, 11 colonnes divergent silencieusement, et
   deux migrations non gardées feront **échouer le prochain déploiement** (§8 du diff). Ces points
   sont à traiter **avant** la bascule de la production, pas pendant.
