Aller au contenu

Session 5 — PerfShop.io — Plateforme de suivi des déploiements

Durée ~8 heures
Conversations 2
Objectif initial Créer perfshop.io — site vitrine + admin + suivi déploiements
Objectif final Plateforme complète avec UUID auto, hostname/IP publique, statut instances temps réel, gestion comptes admin

🎯 Réalisations

1. Infrastructure perfshop.io · ~1h · ⭐⭐

Backend Express.js + PostgreSQL 16 + Nginx reverse proxy (SSL Synology) + sessions admin sécurisées (bcrypt + express-session).

2. Deploy-ping — comptage déploiements actifs · ~1h30 · ⭐⭐⭐

Service one-shot perfshop-deploy-ping lancé au docker compose up : - Envoie : version, compose file, hostname hôte, hostname Docker, IP publique, UUID instance, OS/arch - Géolocalisation côté serveur → pays, ville, FAI - Tableau admin avec drapeaux, version, hostname

Problème clé : Variables vides dans l'entrypoint inline Docker Compose (double quotes → évaluées par le shell hôte).

Solution : Script dédié perfshop-deploy-ping.sh monté en volume.

3. UUID instance — génération automatique stable · ~45 min · ⭐⭐⭐

  • Volume nommé → bind mount ./instance (résout permissions Synology)
  • UUID basé sur hostname via md5sum (reproductible si volume perdu)
  • /etc/hostname monté en read-only → hostname hôte réel
  • Fallback $COMPUTERNAME pour Windows
  • ./instance/ ajouté au .gitignore

4. Statut instances — monitoring temps réel · ~1h · ⭐⭐⭐

Service permanent perfshop-status-checker (60s) — carte "Ma production" + tableau autres instances + stats globales. Corbeille → suppression dans service_status ET deployments.

Problème clé : Instances toujours "hors ligne" → 304 Not Modified → ETag automatique malgré etag: false.

Solution : Middleware Cache-Control: no-store + removeHeader('ETag') global sur /api/.

5. Corrections backend · ~45 min · ⭐⭐⭐

  • public_ip extraite après utilisation → corrigé
  • JOIN DISTINCT ONJOIN LATERAL pour dernier déploiement par instance
  • hostname_docker oublié dans 2 SELECT → ajouté

6. Gestion comptes admin — profil + protection fondateur · ~1h · ⭐⭐

  • Colonnes first_name, last_name, phone sur la table admin
  • Routes GET /accounts/me + PATCH /accounts/me
  • Badge 🔒 Fondateur pour id=1, suppression bloquée côté backend (HTTP 403)
  • initAdminAccount() au démarrage si table vide

7. Redesign statut instances — pastilles colorées · ~30 min · ⭐

Remplacement emojis ✅/❌/⬛ par pastilles CSS avec glow vert/rouge. Texte UP/DOWN centré dans les cartes.

8. Docker Compose — compatibilité Windows + Synology · ~1h30 · ⭐⭐⭐⭐

docker-compose.build.yml mis à jour, bind mount ./instance, /etc/hostname en volume, fallback $COMPUTERNAME. README.md mis à jour.


🎢 Chronologie Détaillée

Phase Ressenti Événement clé
Phase 1 (~2h) 🟢 Clair Script ping + UUID stable + bind mount
Phase 2 (~1h30) 🟡 Non évident Variables vides entrypoint → script dédié + bug ordre SQL
Phase 3 (~1h30) 🔴 Long 304 Not Modified → ETag → middleware no-store
Phase 4 (~1h) 🟢 Méthodique Profil admin + protection id=1 + seed auto
Phase 5 (~30 min) 🟢 Rapide Redesign pastilles + centrage

🧠 Moments Clés d'Apprentissage

  1. Entrypoint Docker Compose inline = piège de quotes — Double quotes → variables évaluées par le shell hôte. Toujours déporter dans un script .sh.
  2. /etc/hostname$HOSTNAME dans Docker$HOSTNAME = hostname container. Monter /etc/hostname de l'hôte en bind mount.
  3. ETag + Cache-Control sur les APIs dynamiques — helmet regénère des ETags malgré etag: false. Middleware global no-store + removeHeader('ETag').
  4. Ordre d'extraction des variables Node — Toujours const { public_ip } = req.body avant d'utiliser public_ip.
  5. Table vide après rebuilddocker compose up --build ne rejoue pas les seeds. initAdminAccount() au démarrage.
  6. --remove-orphans supprime les services retirés — Vérifier le compose avant d'exécuter.

🎯 Difficultés Rencontrées

Sévérité Problème Temps perdu Solution
🔴 ⭐⭐⭐⭐⭐ Instance "hors ligne" malgré rapports envoyés (304 + ETag + rebuild) ~1h Middleware no-store global
🟡 ⭐⭐⭐⭐ Variables vides dans deploy-ping ~45 min Script dédié monté en volume
🟡 ⭐⭐⭐⭐ Table admin vide après rebuild → 404 ~30 min initAdminAccount() au démarrage

📊 Métriques de la Session

Métrique Valeur
Durée totale ~8 heures
Conversations Claude 2
Fichiers backend créés/modifiés 5
Scripts Docker créés 2
Fichiers Docker Compose modifiés 2
Bugs corrigés 6
Lignes de code ~600 (JS + Shell + YAML)

✅ Ce Qui a Bien Fonctionné

Architecture deploy-ping

  • Service one-shot propre : une responsabilité unique, exit après l'envoi
  • Géolocalisation côté serveur → aucune dépendance client
  • UUID basé sur md5sum hostname : reproductible même après perte de volume

Diagnostic méthodique ETag

  • curl -v sur les endpoints → identification des headers réels
  • Isolation du problème par couches (Express → helmet → nginx)
  • Solution générique : middleware global plutôt que correctif ponctuel

Protection fondateur robuste

  • Blocage côté backend (HTTP 403), pas seulement côté UI
  • Seed automatique au démarrage → aucune intervention manuelle post-deploy

🎓 Conclusion

Cette session de ~8 heures en vibe coding a permis de :

Construire perfshop.io de zéro — backend Express.js, PostgreSQL, Nginx, sessions sécurisées, frontend admin complet ✅ Implémenter le deploy-ping — chaque docker compose up envoie automatiquement version, hostname, IP, géolocalisation ✅ Résoudre l'UUID stable — bind mount + md5sum sur hostname, reproductible même si le volume est perdu ✅ Monitoring temps réel — statut instances avec pastilles colorées, suppression propre, cache ETag neutralisé ✅ Protéger le compte fondateurid=1 protégé côté backend HTTP 403, seed automatique au démarrage

Ratio temps productif / temps total : ~75% Objectifs atteints : 100% (plateforme déployée et opérationnelle sur Synology)

Le vibe coding a particulièrement bien fonctionné sur l'architecture deploy-ping : un besoin exprimé simplement ("je veux savoir quand quelqu'un déploie") s'est transformé en un service one-shot propre, avec géolocalisation et UUID stable, en moins de 2 heures.

Le moment le plus laborieux reste le bug ETag/304 : une instance qui rapporte correctement mais apparaît toujours "hors ligne" est un symptôme trompeur — le problème venait d'une couche HTTP invisible (cache helmet) et non de la logique métier. Le diagnostic a nécessité d'inspecter les headers bruts avec curl -v pour identifier la vraie cause.

Leçon de cette session : dans un projet Node.js/Express, toujours tester les APIs avec curl -v dès qu'un comportement de cache est suspect — les middlewares comme helmet peuvent réintroduire silencieusement des comportements qu'on croyait désactivés.


🤖 Erreurs imputables à Claude AI — Session 5


❌ Erreur S5-1 — Variables Docker Compose inline sans anticiper l'évaluation par le shell hôte

Ce que j'ai proposé : Un entrypoint inline dans docker-compose.yml avec des variables d'environnement entre double quotes pour le service perfshop-deploy-ping.

Ce qui s'est passé : Les variables ($HOSTNAME, $COMPUTERNAME) étaient évaluées par le shell hôte au moment du docker compose up, pas dans le container. Résultat : variables vides dans le ping.

Pourquoi c'était une erreur : Le comportement de substitution des variables dans les entrypoints inline Docker Compose est connu — les doubles quotes ne protègent pas de l'expansion côté hôte. J'aurais dû proposer d'emblée un script .sh dédié monté en volume, qui est la pratique standard pour tout entrypoint non trivial.

Temps perdu : ~45 min | Sévérité : ⭐⭐⭐⭐


❌ Erreur S5-2 — ETag non neutralisé malgré etag: false sur l'app Express

Ce que j'ai proposé : Désactiver le cache avec app.set('etag', false) sur l'application Express pour éviter les 304 sur les endpoints de statut.

Ce qui s'est passé : Les instances restaient "hors ligne" malgré des rapports envoyés correctement. helmet réinjectait des ETags au niveau middleware, après le app.set. Le 304 Not Modified renvoyait l'ancien statut au lieu des données fraîches.

Pourquoi c'était une erreur : app.set('etag', false) contrôle uniquement le générateur ETag natif d'Express — il ne désactive pas les ETags ajoutés par des middlewares tiers. J'aurais dû proposer directement un middleware global Cache-Control: no-store + removeHeader('ETag') sur toutes les routes /api/, ce qui est la seule garantie réelle.

Temps perdu : ~1h | Sévérité : ⭐⭐⭐⭐⭐


❌ Erreur S5-3 — public_ip utilisée avant extraction du body

Ce que j'ai proposé : Du code qui référençait public_ip dans la logique de traitement avant la ligne const { public_ip } = req.body.

Ce qui s'est passé : public_ip était undefined dans les enregistrements en base — bug silencieux, aucune erreur levée, juste une valeur manquante.

Pourquoi c'était une erreur : Erreur d'ordre de déclaration classique, que j'aurais dû éviter en structurant systématiquement le handler : d'abord toutes les extractions du body, puis la logique.

Temps perdu : ~15 min | Sévérité : ⭐⭐⭐


❌ Erreur S5-4 — JOIN DISTINCT ON au lieu de JOIN LATERAL pour le dernier déploiement

Ce que j'ai proposé : Une requête avec DISTINCT ON pour récupérer le dernier déploiement par instance.

Ce qui s'est passé : Résultats incorrects — plusieurs lignes par instance ou mauvaise ligne sélectionnée selon l'ordre de tri.

Pourquoi c'était une erreur : DISTINCT ON en PostgreSQL nécessite que l'ORDER BY commence par les mêmes colonnes que le DISTINCT ON, ce qui entre en conflit avec un tri secondaire. JOIN LATERAL avec une sous-requête ORDER BY … LIMIT 1 est la forme correcte et lisible pour "le dernier enregistrement par groupe".

Temps perdu : ~20 min | Sévérité : ⭐⭐⭐


❌ Erreur S5-5 — Table admin vide après rebuild non anticipée

Ce que j'ai proposé : Un setup initial sans mécanisme de seed automatique pour le compte admin.

Ce qui s'est passé : Après un docker compose up --build, la table admin était vide → route /accounts/me retournait 404 → l'interface admin était inutilisable sans intervention manuelle.

Pourquoi c'était une erreur : Tout projet avec une table d'administration doit avoir un seed de sécurité au démarrage. J'aurais dû inclure initAdminAccount() dès la création de la route, pas comme correctif après incident.

Temps perdu : ~30 min | Sévérité : ⭐⭐⭐⭐


📊 Récapitulatif Session 5

# Erreur Temps perdu Sévérité
S5-1 Variables Docker Compose inline évaluées par le shell hôte ~45 min ⭐⭐⭐⭐
S5-2 ETag réinjecté par helmet malgré etag: false ~1h ⭐⭐⭐⭐⭐
S5-3 public_ip utilisée avant extraction du body ~15 min ⭐⭐⭐
S5-4 DISTINCT ON au lieu de JOIN LATERAL ~20 min ⭐⭐⭐
S5-5 Table admin vide après rebuild non anticipée ~30 min ⭐⭐⭐⭐
TOTAL ~2h50

Pattern : Hypothèses implicites sur le comportement des outils (etag: false suffisant, variables inline évaluées dans le container, seed inutile) — le même pattern que les sessions précédentes, appliqué cette fois à l'écosystème Node.js/Docker plutôt qu'à Java/Spring.