Files
maintenance_page/PROMPT.md
T

323 lines
19 KiB
Markdown
Raw Normal View History

# PROMPT.md — Reverse Proxy Nginx avec Page de Maintenance Conditionnelle par IP
> Document de spécification autonome du projet. Toute personne (humaine ou agent IA) peut s'y référer pour comprendre, implémenter et maintenir le projet sans contexte externe.
---
## 1. Vision du projet
Construire un **reverse proxy Nginx** qui, selon l'adresse IP du client, **redirige les requêtes vers une application locale** (proxy_pass) **ou affiche une page de maintenance statique** avec un code HTTP `503 Service Unavailable`. Le projet est packagé en Docker, agnostique de l'application backend, et la logique (mode whitelist / blacklist + liste d'IP + upstream) est entièrement pilotée par variables d'environnement au démarrage du conteneur.
Cas d'usage typique : exposer une application en pré-production à une équipe interne uniquement, ou bloquer ponctuellement quelques IP indésirables pendant qu'on travaille sur un correctif.
---
## 2. Stack technique
| Composant | Choix | Confiance | Justification |
|---|---|---|---|
| Reverse proxy | **Nginx** (image `nginx:stable-alpine`) | 🟢 élevée | Standard de l'industrie, déterministe, configuration purement déclarative. |
| Logique conditionnelle | Directives natives `geo` + `map` + `if` (pas de Lua, pas de njs) | 🟢 élevée | Couvre 100 % du besoin sans dépendance tierce. Évite les modules dynamiques. |
| Templating de config | `envsubst` (depuis `gettext`) appelé par l'entrypoint Nginx officiel via `/etc/nginx/templates/` | 🟢 élevée | Mécanisme officiel de l'image Nginx, zéro magie, pas de runtime supplémentaire. |
| Page de maintenance | HTML + CSS inline, system font stack, icône SVG inline, **zéro JavaScript** | 🟢 élevée | Sert même si tout est cassé en aval. Pas de dépendance réseau (pas de Google Fonts, pas de CDN). |
| Packaging | **Docker** + **docker-compose** (pour le dev local et la démo) | 🟢 élevée | Reproductible, mêmes commandes en local et en prod. |
| Entrypoint | Script **bash** (validation des env vars + `nginx -t`) | 🟢 élevée | Échec rapide au boot si conf invalide, plutôt qu'un 502 silencieux. |
| Tests | Scripts **bash + curl** lancés contre un conteneur de test (avec un upstream simulé en `nginx:alpine` ou `python -m http.server`) | 🟢 élevée | Boîte noire = vérité terrain. Pas de framework de test à apprendre. |
| Lint shell | **shellcheck** sur tous les `.sh` | 🟢 élevée | Outil standard, signaux nets. |
| Validation conf | **`nginx -t`** systématique avant tout commit (pre-commit local) | 🟢 élevée | Coût zéro, attrape 90 % des bugs. |
**Aucune techno 🟡 ou 🔵 dans ce projet.** Toutes les briques sont dans la zone de fiabilité maximale d'un agent comme moi : Nginx natif, bash POSIX, Docker, curl. Aucune librairie de niche.
---
## 3. Architecture et structure des dossiers
```
appli_test/
├── PROMPT.md # Ce fichier — source de vérité du projet
├── README.md # Quickstart : comment lancer, comment configurer
├── CHANGELOG.md # Conventional Changelog
├── .editorconfig # Cohérence indentation entre éditeurs
├── .gitignore
├── .dockerignore
├── Dockerfile # Image basée sur nginx:stable-alpine
├── docker-compose.yml # Stack de dev : proxy + upstream factice
├── docker-compose.test.yml # Stack de tests d'intégration
├── nginx/
│ ├── templates/
│ │ └── default.conf.template # Template traité par envsubst au boot
│ └── snippets/
│ └── maintenance-log.conf # Format de log dédié maintenance.log
├── public/
│ └── maintenance.html # Page statique servie en 503 (HTML+CSS inline)
├── scripts/
│ ├── entrypoint.sh # Validation env + génération conf + nginx -t + exec nginx
│ ├── build-ip-list.sh # Transforme MAINTENANCE_IP_LIST="a,b,c" en directives geo
│ └── lint.sh # Lance shellcheck + nginx -t
└── tests/
├── run.sh # Orchestrateur : up compose.test, attend, lance les cas, tear down
├── cases/
│ ├── whitelist_authorized_ip.sh # IP listée → 200 (proxy OK)
│ ├── whitelist_unauthorized_ip.sh # IP non listée → 503 + page maintenance
│ ├── blacklist_blocked_ip.sh # IP listée → 503
│ ├── blacklist_normal_ip.sh # IP non listée → 200
│ ├── log_dedicated.sh # Une requête bloquée apparaît dans maintenance.log
│ └── nginx_syntax.sh # `nginx -t` retourne 0
└── fixtures/
└── upstream/ # Petit serveur factice pour simuler l'app cible
```
**Pourquoi cette structure ?**
- `nginx/templates/` est le chemin que l'entrypoint officiel de l'image Nginx scanne automatiquement pour `envsubst`. On suit la convention upstream.
- `public/` séparé du templating : la page statique n'a pas à passer par envsubst.
- `scripts/` regroupe tout le shell pour qu'il soit lintable et testable d'un coup.
- `tests/cases/` : un fichier `.sh` par scénario, lisible, débogable individuellement.
---
## 4. Spécification fonctionnelle détaillée
### 4.1 Variables d'environnement (contrat d'API)
| Variable | Obligatoire | Exemple | Description |
|---|---|---|---|
| `MAINTENANCE_MODE` | ✅ | `whitelist` ou `blacklist` | Sémantique de la liste. `whitelist` = seules les IP listées passent, le reste voit la maintenance. `blacklist` = inverse. |
| `MAINTENANCE_IP_LIST` | ✅ | `81.92.47.8,10.0.0.42,192.168.1.5` | Liste d'IP **IPv4 individuelles** séparées par virgules. Espaces tolérés autour des virgules. Pas de CIDR, pas d'IPv6 (out of scope MVP). |
| `UPSTREAM_HOST` | ✅ | `127.0.0.1:3000` | Hôte:port de l'application backend. Le proxy fait `proxy_pass http://$UPSTREAM_HOST`. |
| `LISTEN_PORT` | ❌ (défaut `8080`) | `80` | Port d'écoute du conteneur. |
| `SERVER_NAME` | ❌ (défaut `_`) | `app.example.com` | Valeur de `server_name` dans le bloc Nginx. |
### 4.2 Comportement
- **Topologie** : Nginx est **directement exposé** au client. L'IP source = `$remote_addr`. Aucune confiance accordée à `X-Forwarded-For` reçu de l'extérieur.
- **Code HTTP** : la page de maintenance est servie avec **`503 Service Unavailable`** (et un en-tête `Retry-After: 3600` indicatif).
- **Scope** : la maintenance s'applique à **tout le domaine** — chaque path, chaque méthode, y compris assets et appels API. Aucune exclusion (pas de healthcheck particulier, pas d'exception assets).
- **Logging** :
- `access.log` standard pour toutes les requêtes (format combined).
- `maintenance.log` **dédié** : ne contient **que** les requêtes ayant abouti sur la page de maintenance. Format : `$time_iso8601 $remote_addr "$request" "$http_user_agent"`.
- **Pas de bypass token** : le seul critère discriminant est l'IP. Pas de cookie magique, pas de header de contournement.
- **Validation au démarrage** : si `MAINTENANCE_MODE` n'est ni `whitelist` ni `blacklist`, ou si `MAINTENANCE_IP_LIST` est vide, ou si `UPSTREAM_HOST` est absent, l'entrypoint **échoue immédiatement** avec un message clair sur stderr et exit code 1. Pas de fallback silencieux.
### 4.3 Logique Nginx (cœur du projet)
Le mécanisme repose sur deux directives :
```nginx
# Construit dynamiquement par envsubst depuis MAINTENANCE_IP_LIST
geo $ip_in_list {
default 0;
81.92.47.8 1;
10.0.0.42 1;
# ... une ligne par IP, générée au boot
}
# MAINTENANCE_MODE = whitelist → on bloque si pas dans la liste (ip_in_list=0)
# MAINTENANCE_MODE = blacklist → on bloque si dans la liste (ip_in_list=1)
map "$maintenance_mode:$ip_in_list" $is_in_maintenance {
default 0;
"whitelist:0" 1;
"blacklist:1" 1;
}
```
L'astuce : `$maintenance_mode` est lui-même injecté par `envsubst`, et `MAINTENANCE_IP_LIST` est transformée en lignes `geo` par `scripts/build-ip-list.sh` avant que Nginx ne démarre.
---
## 5. Conventions de codage
### 5.1 Nginx
- **Indentation** : 4 espaces, jamais de tabulations.
- **Une directive par ligne**, accolades sur leur propre ligne pour les blocs longs.
- **Commentaires** uniquement quand le *pourquoi* n'est pas évident depuis la lecture. Jamais de commentaire qui paraphrase la directive.
- **Pas de `if` dans les blocs `location`** sauf cas spécifiquement supportés (cf. [If Is Evil](https://nginx.org/en/docs/faq/if_is_evil.html)). On préfère `map` + `try_files` + `return`.
- Toute conf doit passer `nginx -t` ; aucune exception.
### 5.2 Bash
- **Shebang** `#!/usr/bin/env bash` partout.
- `set -euo pipefail` en première ligne après le shebang.
- **Toutes les variables entre guillemets** : `"$VAR"`, `"${ARR[@]}"`.
- Vérification d'arguments en début de script avec message d'usage.
- Tous les scripts `.sh` doivent passer `shellcheck` sans warning.
### 5.3 HTML/CSS (page de maintenance)
- **Un seul fichier** `public/maintenance.html` autonome.
- CSS **inline dans `<style>`**, pas de fichier externe.
- **System font stack** : `system-ui, -apple-system, "Segoe UI", Roboto, sans-serif`.
- **Icône SVG inline** pour l'illustration (pas de `<img>` qui pourrait 404).
- Layout centré (flex), max-width raisonnable (~480px), respiration visuelle.
- Doit être **lisible sans JS et sans réseau**.
### 5.4 Conventional Commits
Format obligatoire :
```
<type>(<scope>): <description courte impérative>
[corps optionnel expliquant le pourquoi]
```
Types autorisés : `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`.
**❌ Ne JAMAIS ajouter de ligne `Co-Authored-By: Claude ...` dans les messages de commit.** Aucune attribution d'agent IA dans l'historique Git.
### 5.5 Git workflow — **trunk-based**
- Tous les commits vont **directement sur `main`**.
- Pas de branches `feature/*`, pas de pull requests internes.
- Conséquence : chaque commit doit être **atomique, vert, et déployable**. `nginx -t` + `tests/run.sh` doivent passer avant chaque commit.
- Tags Git pour les versions notables : `v0.1.0`, `v0.2.0`, etc.
---
## 6. Outils et plugins Claude Code
Cette section décrit comment un agent Claude Code doit travailler sur ce projet.
### 6.1 Superpowers — skills à utiliser systématiquement
| Skill | Quand l'invoquer |
|---|---|
| `superpowers:brainstorming` | Avant **toute** modification non triviale du comportement (nouvelle env var, nouveau code HTTP, nouvelle exclusion de path). On clarifie l'intention avant le code. |
| `superpowers:test-driven-development` | Pour toute feature comportementale : écrire d'abord un cas de test dans `tests/cases/` qui échoue, puis modifier la conf Nginx pour qu'il passe. |
| `superpowers:systematic-debugging` | Dès qu'un test échoue ou qu'une requête ne se comporte pas comme attendu. Pas de "patch jusqu'à ce que ça marche". |
| `superpowers:verification-before-completion` | Avant de déclarer une tâche terminée : `nginx -t` ✅, `tests/run.sh` ✅, `shellcheck scripts/*.sh` ✅. **Évidence avant assertion**. |
| `superpowers:writing-plans` | Pour toute évolution dépassant 2-3 fichiers (ex : ajout du support CIDR ultérieurement). |
### 6.2 Superpowers — skills **non utilisés** pour ce projet
- `superpowers:using-git-worktrees` : **non applicable**. Le projet est en trunk-based, mono-développeur, scope minimal. Si le projet grossit et que des features parallèles deviennent nécessaires, ce skill pourra être réintroduit — mais pas avant.
### 6.3 Context7
- **À utiliser** quand on a un doute sur une directive Nginx récente, une option de l'image Docker `nginx:stable-alpine`, ou la sémantique de `envsubst`.
- **Inutile** pour le HTML/CSS/bash basique — c'est dans la zone 🟢.
- Préférer Context7 à la recherche web pour toute documentation officielle.
### 6.4 Frontend Design
- **Applicable uniquement** à `public/maintenance.html`. Si on veut faire évoluer le visuel (au-delà du sobre actuel), on peut invoquer ce skill.
- Pour le MVP, le design est intentionnellement simple : pas besoin du skill au démarrage.
### 6.5 Commit Commands
- Tout commit doit suivre les Conventional Commits (cf. § 5.4).
- **Interdiction absolue de `Co-Authored-By` dans les commits.**
- Commits atomiques : un commit = un changement cohérent et testé.
---
## 7. Phases de développement
**MVP en une seule phase** (choix utilisateur). Le projet est suffisamment petit pour qu'un découpage en phases produirait plus de cérémonie que de valeur.
### Phase 1 — MVP complet
- [ ] **Squelette projet** : `Dockerfile`, `docker-compose.yml`, `.gitignore`, `.dockerignore`, `.editorconfig`, `README.md` quickstart, `CHANGELOG.md`.
- [ ] **Page de maintenance** `public/maintenance.html` : HTML+CSS inline, system fonts, icône SVG, layout centré, message sobre en français.
- [ ] **Template Nginx** `nginx/templates/default.conf.template` :
- Bloc `server` écoutant sur `${LISTEN_PORT}`.
- Directives `geo` (générées dynamiquement) et `map` pour la décision.
- `location /` qui :
- sert `/maintenance.html` avec status 503 + `Retry-After` si `$is_in_maintenance = 1`,
- fait `proxy_pass http://${UPSTREAM_HOST}` sinon.
- Log dédié `maintenance.log` conditionné par `$is_in_maintenance`.
- [ ] **Script `scripts/build-ip-list.sh`** : transforme `MAINTENANCE_IP_LIST` en lignes Nginx dans un fichier inclus par le template.
- [ ] **Script `scripts/entrypoint.sh`** : valide les env vars (`MAINTENANCE_MODE` ∈ {whitelist, blacklist}, `MAINTENANCE_IP_LIST` non vide, `UPSTREAM_HOST` défini), appelle `build-ip-list.sh`, lance `nginx -t`, puis `exec nginx -g "daemon off;"`.
- [ ] **Tests d'intégration** `tests/run.sh` + 6 cas dans `tests/cases/` couvrant whitelist (autorisée + bloquée), blacklist (bloquée + normale), log dédié, syntaxe Nginx.
- [ ] **`docker-compose.test.yml`** : stack qui combine le proxy avec un upstream factice (image `python:3.12-alpine` lançant un `http.server` ou un autre `nginx:alpine` qui sert un texte fixe).
- [ ] **`scripts/lint.sh`** : `shellcheck scripts/*.sh tests/run.sh tests/cases/*.sh` + `nginx -t` (via conteneur jetable).
- [ ] **README.md** : usage (`docker run -e MAINTENANCE_MODE=... -e MAINTENANCE_IP_LIST=... -e UPSTREAM_HOST=... -p 8080:8080 image`), exemples docker-compose, comment lancer les tests, comment ajouter une IP.
- [ ] **Validation finale** : tous les tests verts, image Docker construit, README à jour.
---
## 8. Design & UX (page de maintenance)
**Direction esthétique** : sobre avec un peu de style. Pas minimaliste austère, pas branding. Niveau "page d'erreur soignée d'une bonne app indé".
| Élément | Choix |
|---|---|
| Palette | Fond `#fafaf9` (presque blanc cassé), texte `#1c1917` (presque noir), accent `#0891b2` (cyan calme) pour l'icône. |
| Typographie | System font stack, titre 28-32px, corps 16px, line-height 1.6. |
| Icône | SVG inline ~64px représentant un outil (clé, engrenage, ou cône de chantier). Couleur d'accent. |
| Layout | Flex centré horizontalement et verticalement, `min-height: 100vh`, max-width contenu ~480px, padding généreux. |
| Ton | Neutre, en français, rassurant : « Site en maintenance. Nous revenons très vite. » + sous-titre court. Aucune date estimée (pas demandé). |
| Responsive | Mobile-first par défaut (largeurs en pourcentage / max-width). |
| Accessibilité | Contraste AA minimum, `<title>` explicite, `lang="fr"` sur `<html>`. |
---
## 9. Règles impératives
Liste de règles **non négociables**. Tout PR/commit qui les viole doit être refusé.
1. **Aucune dépendance Lua, njs, ou module Nginx tiers.** On reste sur les directives natives.
2. **Aucun JavaScript dans la page de maintenance.** Elle doit fonctionner navigateur texte / curl / w3m.
3. **Aucune requête réseau sortante depuis la page de maintenance** (pas de Google Fonts, pas de CDN, pas d'analytics).
4. **`nginx -t` doit passer avant chaque commit.**
5. **Tous les scripts shell doivent passer `shellcheck` sans warning.**
6. **Tous les tests d'intégration doivent passer avant chaque commit** sur `main`.
7. **Aucun secret en dur** dans l'image, les templates ou les scripts. Tout passe par variables d'environnement.
8. **Aucune écriture sur le disque** par Nginx en dehors des logs (`access.log`, `error.log`, `maintenance.log`).
9. **La page de maintenance retourne strictement `503`**, jamais `200` ni `307`.
10. **La maintenance s'applique à tout le domaine.** Aucune exclusion de path tant que ce n'est pas une exigence explicite et discutée.
11. **Aucune ligne `Co-Authored-By` dans les messages de commit.**
12. **Toute IP doit être validée syntaxiquement** par `build-ip-list.sh` avant d'être injectée dans la conf Nginx (regex IPv4). Une IP malformée → erreur de boot, pas un silence.
13. **Validation explicite à l'entrypoint.** Si une variable obligatoire manque ou est invalide, le conteneur sort en erreur avec un message lisible. Pas de défaut implicite.
14. **Pas de bypass token, pas de cookie magique, pas de header de contournement.** Le seul critère est l'IP.
---
## 10. Stratégie de fiabilité
Cette section décrit comment un agent doit se comporter face à l'incertitude.
### 10.1 Toutes les technos sont 🟢 dans ce projet
Aucun composant 🟡 ni 🔵. Si une évolution future introduit une techno 🟡 (ex : OpenResty/Lua) ou 🔵 (ex : njs, module dynamique exotique), elle doit être :
- explicitement marquée avec son niveau de confiance dans une mise à jour de PROMPT.md,
- accompagnée d'une vérification Context7 pour récupérer la doc officielle à jour,
- isolée dans un commit dédié (pas mêlée à d'autres changements), pour pouvoir être revertée proprement.
### 10.2 Quand un pattern Nginx ne marche pas comme prévu
1. **Lire `error.log`** d'abord. 80 % du temps, il y a un message explicite.
2. **Reproduire avec un cas de test minimal** dans `tests/cases/`.
3. **Ne pas ajouter de `if` dans une `location`** comme rustine — c'est presque toujours le mauvais outil.
4. Si la directive native ne suffit pas, **proposer une alternative** dans un commit séparé et prévenir l'utilisateur avant de la merger.
### 10.3 Vérification systématique avant tout `Done`
Checklist à exécuter mentalement avant de déclarer quoi que ce soit terminé :
- [ ] `docker compose -f docker-compose.test.yml up --abort-on-container-exit` retourne 0 ?
- [ ] `tests/run.sh` affiche `OK` pour chaque cas ?
- [ ] `scripts/lint.sh` ne produit aucune sortie ?
- [ ] `nginx -t` passe sur le template instancié avec un jeu d'env vars représentatif ?
- [ ] `README.md` reflète bien les env vars actuelles ?
- [ ] `CHANGELOG.md` mentionne le changement ?
**Évidence avant assertion** : ne jamais dire « ça marche » sans avoir relancé les tests dans la session courante. Le skill `superpowers:verification-before-completion` formalise cette discipline.
### 10.4 Quand demander à l'utilisateur
- **Toute extension du contrat d'env vars** (nouvelle variable, changement de format).
- **Tout changement de comportement HTTP** (code de retour, en-têtes).
- **Toute exception au scope « maintenance s'applique à tout le domaine »**.
- **Toute introduction d'un composant 🟡 ou 🔵.**
Pour le reste (refactor interne, amélioration de la page de maintenance, ajout de tests), l'agent peut avancer en autonomie tant qu'il respecte les règles impératives du § 9.
---
*Fin du PROMPT.md. Toute évolution de ce document doit être commitée avec un message `docs(prompt): <changement>` et discutée avec l'utilisateur en amont si elle modifie le scope ou le contrat d'API.*