From f899aac48a9cd820bbca225a12707c9fe8a73c11 Mon Sep 17 00:00:00 2001 From: Adam Belkacemi Date: Mon, 12 Oct 2026 21:50:53 +0200 Subject: [PATCH] =?UTF-8?q?corrige=20le=20code=20de=20classe=20(caract?= =?UTF-8?q?=C3=A8res=20ambigus)=20et=20fige=20l'image=20Uptime=20Kuma=20pa?= =?UTF-8?q?r=20digest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 --- ansible/roles/supervision/defaults/main.yml | 4 +++ ansible/roles/supervision/tasks/main.yml | 2 +- docs/dette.md | 2 +- docs/journal.md | 21 +++++++++++++++ docs/runbooks/supervision.md | 25 ++++++++++++++++++ portail/app/Models/Classe.php | 4 +-- .../app/Services/GenerateurCompteEleve.php | 11 ++------ portail/app/Support/CodeLisible.php | 26 +++++++++++++++++++ portail/tests/Feature/CreationClasseTest.php | 6 +++++ portail/tests/Reels/preparer-parcours.php | 3 ++- portail/tests/Reels/preparer-visio.php | 3 ++- 11 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 portail/app/Support/CodeLisible.php diff --git a/ansible/roles/supervision/defaults/main.yml b/ansible/roles/supervision/defaults/main.yml index 388314b..91d092e 100644 --- a/ansible/roles/supervision/defaults/main.yml +++ b/ansible/roles/supervision/defaults/main.yml @@ -2,4 +2,8 @@ supervision_fichier_webhook: /root/.alertes-webhook supervision_port_relais: 9099 supervision_port_uptime_kuma: 3001 +# Figée par un digest, pas par le tag flottant ":1" (dette notée le +# 2026-10-09) : 1.23.17, telle que déployée le 2026-10-09. Procédure de +# mise à jour : docs/runbooks/supervision.md. +supervision_image_uptime_kuma: "louislam/uptime-kuma@sha256:70233f4acb5163fd2a59a49909cf01e44415cecff13dba69e3a86647a2919f83" supervision_dossier_textfile: /var/lib/node_exporter/textfile diff --git a/ansible/roles/supervision/tasks/main.yml b/ansible/roles/supervision/tasks/main.yml index 38b572a..ff23384 100644 --- a/ansible/roles/supervision/tasks/main.yml +++ b/ansible/roles/supervision/tasks/main.yml @@ -91,7 +91,7 @@ - name: déployer Uptime Kuma community.docker.docker_container: name: uptime-kuma - image: louislam/uptime-kuma:1 + image: "{{ supervision_image_uptime_kuma }}" state: started restart_policy: unless-stopped network_mode: host diff --git a/docs/dette.md b/docs/dette.md index f8a45d4..da332af 100644 --- a/docs/dette.md +++ b/docs/dette.md @@ -5,7 +5,7 @@ retirer la ligne (et le noter dans le journal) quand c'est fait. | Sujet | Pourquoi c'est une dette | Quand | Origine | |---|---|---|---| -| **Image Uptime Kuma non figée** (`louislam/uptime-kuma:1`) | Une version différente peut arriver à n'importe quel redéploiement, sans test. Figer un tag précis (voire le digest) dans une variable du rôle `supervision`, et documenter la mise à jour par Ansible (changer la variable, relancer le rôle, vérifier) — jamais par le bouton de l'interface. Kuma lui-même qualifie ce tag d'« historique » (il injecte `docker-latest-warning.js` dans les images `latest`/`1`, constaté le 2026-10-09) : prévoir la migration vers un tag versionné, en testant la page de statut et les sondes. | Avant le pilote | Demande d'Adam, 2026-10-09 | +| Image Uptime Kuma encore « historique » (même contenu que `:1`, désormais figé par digest — 2026-10-12) | Le digest figé est celui publié sous le tag flottant `:1` (1.23.17), qui injecte encore `docker-latest-warning.js` (sans impact : visible seulement sur `/setup`, jamais atteint). Migrer vers le build du tag versionné `1.23.17` (digest différent, probablement une image reconstruite sans ce script) demande de re-pull et de revérifier la page de statut et les sondes — pas fait ici. | Avant le pilote | Demande d'Adam, 2026-10-09 ; réduite le 2026-10-12 | | Projet `paillasse`, compte `paillasse-service` et son rôle créés à la main (étape 6) | Pas rejouables par Ansible (gabarit et quota le sont depuis l'étape 13, rôle `openstack-projet`). | Bloc B | Étape 6 | | Clé age des sauvegardes : copie hors du serveur non confirmée | Sans elle, aucune sauvegarde n'est déchiffrable si le serveur est perdu (voir `docs/runbooks/sauvegardes.md`). Adam doit confirmer l'avoir mise en lieu sûr. | Dès que possible | Étape 3 | | Vitrine : Tailwind par CDN d'exécution (`cdn.tailwindcss.com`, 19 pages) | Non destiné à la production selon Tailwind, lent, et oblige la vitrine à tolérer les scripts en ligne dans sa CSP. Compiler ces styles avec Vite. | Avant ouverture publique | Étape 12 (design) | diff --git a/docs/journal.md b/docs/journal.md index 06db128..dcd856d 100644 --- a/docs/journal.md +++ b/docs/journal.md @@ -773,3 +773,24 @@ Prochaines étapes du bloc A : TURN 443 pour la visio, sauvegarde OpenStack (fai **Comprendre** La plupart des 72 erreurs venaient d'une seule cause répétée dix fois (des relations Eloquent sans generics), pas de dix bugs différents — las de ça, régler la cause réelle (les generics) a fait disparaître les trois quarts du travail d'un coup. Les erreurs qui restaient ensuite étaient plus intéressantes : deux ne venaient pas du code de Syllabus mais d'une imprécision des annotations de Laravel lui-même sur sa façade Redis, et une révélait un vrai défaut (une signature de type qui en disait moins que ce que le code vérifiait vraiment). La règle du prompt (« ne jamais juste faire taire l'outil ») a forcé à distinguer ces trois cas plutôt qu'à tout traiter de la même façon. + +## 2026-10-12 — Deux dettes (en attendant le DNS du TURN) : code de classe et image Uptime Kuma + +**Code de classe sans caractère ambigu** + +- `App\Support\CodeLisible` : alphabet sans 0/O ni 1/I/L (`ABCDEFGHJKMNPQRSTUVWXYZ23456789`), repris de `GenerateurCompteEleve::genererCodeSecret` (qui l'avait en privé) pour que `Classe::genererCodeUnique` l'utilise aussi — jusqu'ici il tirait dans `Str::random(6)` (alphabet complet, ambigu). Scripts de test réels (`tests/Reels/preparer-*.php`) alignés. 1 test Pest (55 au total) : 100 codes de classe générés, tous conformes à l'alphabet. +- Dette retirée. + +**Image Uptime Kuma figée par digest** + +- `louislam/uptime-kuma:1` est un tag flottant : son contenu peut changer sans préavis à n'importe quel redéploiement. Vérifié sur l'API Docker Hub que le digest du tag `:1` actuellement déployé (`sha256:70233f4a...`, version 1.23.17) correspond bien à ce qui tourne sur le serveur (`docker inspect` → même `RepoDigests`) : figé tel quel dans `supervision_image_uptime_kuma` (`ansible/roles/supervision/defaults/main.yml`), au lieu du tag. +- **Nuance pour la suite** : ce digest est celui du tag `:1`, pas celui du tag versionné `1.23.17` (une image différente, reconstruite séparément — vérifié : digests différents). La vraie « migration vers un tag versionné » demandée par Adam demande donc un second pas (re-pull, revérifier la page de statut et les sondes) : dette réduite, pas fermée. +- Déployé (`supervision.yml`, `changed=1`) et vérifié : même version (1.23.17), même base de données (`kuma.db`, sondes conservées), toujours sur `127.0.0.1:3001` seulement. Procédure de mise à jour future écrite dans `docs/runbooks/supervision.md`. + +**Comprendre** + +Les deux corrections de ce tour-ci illustrent une même idée : une dette notée n'est réglée que quand on peut dire exactement ce qui a changé et le prouver. Pour le code de classe, prouver, c'est un test qui génère une centaine de codes et vérifie qu'aucun caractère ambigu n'y figure jamais — pas seulement relire le code une fois. Pour Uptime Kuma, prouver, c'est avoir comparé le digest réellement déployé à celui que l'API de Docker Hub donne pour le tag flottant, plutôt que de supposer qu'ils coïncident : une fois comparés, il est apparu qu'ils coïncidaient aujourd'hui, mais que « corriger » et « migrer vers une version propre » sont deux choses différentes — la première protège contre les surprises futures, la seconde reste à faire. + +**Suite** + +Toujours en attente du DNS `turn.syllabuslab.fr` (ADR 0006) pour le relais TURN de la visio. diff --git a/docs/runbooks/supervision.md b/docs/runbooks/supervision.md index cc83569..1a74d9d 100644 --- a/docs/runbooks/supervision.md +++ b/docs/runbooks/supervision.md @@ -115,3 +115,28 @@ ssh paillasse "sudo -i bash -c 'export PATH=/opt/kolla-venv/bin:\$PATH; kolla-an `kolla.yml`/`supervision.yml` pour le copier sur le serveur, **puis** `kolla-ansible reconfigure`. Oublier la première étape fait tourner `reconfigure` sur l'ancienne config, sans erreur visible. + +## Mettre à jour l'image Uptime Kuma + +Figée par **digest**, pas par le tag flottant `louislam/uptime-kuma:1` (qui +peut changer de contenu à tout moment, sans avertissement ni changelog +attaché au tag) : `supervision_image_uptime_kuma` +(`ansible/roles/supervision/defaults/main.yml`). Version actuelle : 1.23.17. + +1. Choisir la version visée sur + (préférer un tag de version exacte, ex. `1.23.18`, jamais `1` ou `latest`) + et lire les notes de version . +2. Trouver son digest exact (pas celui d'un tag flottant) : + ``` + curl -s "https://hub.docker.com/v2/namespaces/louislam/repositories/uptime-kuma/tags/1.23.18" \ + | python3 -c "import json,sys; print(json.load(sys.stdin)['digest'])" + ``` +3. Mettre à jour `supervision_image_uptime_kuma` dans `defaults/main.yml` + (`louislam/uptime-kuma@sha256:...`) et le commentaire de version. +4. `cd ansible && script -q /dev/null ansible-playbook supervision.yml -i inventory.ini` + (tire la nouvelle image, recrée le conteneur ; le volume `uptime-kuma-data` + est conservé, rien n'est perdu). +5. Vérifier : `ss -tlnp | grep 3001` (toujours 127.0.0.1 seulement), rouvrir + le tunnel SSH, se connecter, vérifier que les sondes existantes sont + toujours là et actives. Rejouer `visio-e2e.mjs`/`parcours-lab.py` n'est + pas nécessaire (Kuma ne fait pas partie du chemin critique d'un lab). diff --git a/portail/app/Models/Classe.php b/portail/app/Models/Classe.php index 48ccbaf..71cabc8 100644 --- a/portail/app/Models/Classe.php +++ b/portail/app/Models/Classe.php @@ -2,13 +2,13 @@ namespace App\Models; +use App\Support\CodeLisible; use Carbon\Carbon; use Database\Factories\ClasseFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Support\Str; class Classe extends Model { @@ -31,7 +31,7 @@ class Classe extends Model public static function genererCodeUnique(): string { do { - $code = Str::upper(Str::random(6)); + $code = CodeLisible::genere(6); } while (static::where('code_classe', $code)->exists()); return $code; diff --git a/portail/app/Services/GenerateurCompteEleve.php b/portail/app/Services/GenerateurCompteEleve.php index 22372dd..631002e 100644 --- a/portail/app/Services/GenerateurCompteEleve.php +++ b/portail/app/Services/GenerateurCompteEleve.php @@ -4,6 +4,7 @@ namespace App\Services; use App\Models\Classe; use App\Models\Eleve; +use App\Support\CodeLisible; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; @@ -13,9 +14,6 @@ use Illuminate\Support\Str; */ class GenerateurCompteEleve { - // Sans 0/O ni 1/I/L, pour qu'un élève ne confonde pas à la lecture. - private const ALPHABET_CODE = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'; - public function genererIdentifiant(string $prenom, ?string $nom): string { $base = Str::slug($prenom, ''); @@ -35,12 +33,7 @@ class GenerateurCompteEleve public function genererCodeSecret(): string { - $code = ''; - for ($i = 0; $i < 6; $i++) { - $code .= self::ALPHABET_CODE[random_int(0, strlen(self::ALPHABET_CODE) - 1)]; - } - - return $code; + return CodeLisible::genere(6); } /** diff --git a/portail/app/Support/CodeLisible.php b/portail/app/Support/CodeLisible.php new file mode 100644 index 0000000..63dad1e --- /dev/null +++ b/portail/app/Support/CodeLisible.php @@ -0,0 +1,26 @@ +toBe(0); }); + +test('le code de classe ne contient aucun caractère ambigu (0/O, 1/I/L)', function () { + $codes = collect(range(1, 100))->map(fn () => Classe::genererCodeUnique()); + + expect($codes->every(fn ($code) => preg_match('/^[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{6}$/', $code) === 1))->toBeTrue(); +}); diff --git a/portail/tests/Reels/preparer-parcours.php b/portail/tests/Reels/preparer-parcours.php index 8aea9d0..a868447 100644 --- a/portail/tests/Reels/preparer-parcours.php +++ b/portail/tests/Reels/preparer-parcours.php @@ -11,11 +11,12 @@ use App\Models\Etablissement; use App\Models\Lab; use App\Models\Seance; use App\Models\User; +use App\Support\CodeLisible; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; $motDePasse = Str::password(24, symbols: false); -$code = Str::upper(Str::random(6)); +$code = CodeLisible::genere(6); $etablissement = Etablissement::create(['nom' => 'Lycée Test Étape 11', 'ville' => 'Test']); $prof = User::create(['name' => 'Prof Test 11', 'email' => 'prof11@syllabus.test', 'password' => $motDePasse, 'etablissement_id' => $etablissement->id]); diff --git a/portail/tests/Reels/preparer-visio.php b/portail/tests/Reels/preparer-visio.php index 76ee1cf..78aa2f1 100644 --- a/portail/tests/Reels/preparer-visio.php +++ b/portail/tests/Reels/preparer-visio.php @@ -10,6 +10,7 @@ use App\Models\Etablissement; use App\Models\Lab; use App\Models\Seance; use App\Models\User; +use App\Support\CodeLisible; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; @@ -19,7 +20,7 @@ $prof = User::create(['name' => 'Prof Visio', 'email' => 'prof-visio@syllabus.te $classe = Classe::create(['etablissement_id' => $etablissement->id, 'user_id' => $prof->id, 'nom' => 'Classe Test Visio', 'formation' => 'Test']); $eleves = []; foreach ([['Lea', 'Martin', 'lea.visio'], ['Yanis', 'Bernard', 'yanis.visio']] as [$prenom, $nom, $identifiant]) { - $code = Str::upper(Str::random(6)); + $code = CodeLisible::genere(6); Eleve::create(['classe_id' => $classe->id, 'prenom' => $prenom, 'nom' => $nom, 'identifiant' => $identifiant, 'code_secret' => Hash::make($code), 'jeton_qr' => Str::random(32)]); $eleves[] = ['identifiant' => $identifiant, 'code' => $code]; }