# 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 `