Merge pull request #1 from AcadeNice/feat/infra-docker

feat(infra): complete Docker stack with smoke-test validation
This commit is contained in:
Imugiii 2026-04-30 13:55:12 +02:00 committed by GitHub
commit 00a3f82a19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 2756 additions and 8 deletions

77
.dockerignore Normal file
View file

@ -0,0 +1,77 @@
#
# Fichiers exclus du contexte de build Docker.
# Reduit le temps de build et evite de faire entrer des fichiers sensibles
# ou non pertinents dans les couches d'image.
#
# === Git ===
.git
.gitignore
.gitattributes
.githooks
# === CI/CD ===
.github
# === Methodologie / outils dev ===
.claude
_byan
_byan-output
.mcp.json
# === Documentation et notes ===
docs
README.md
*.md
# === Secrets locaux ===
.env
.env.local
.env.*.local
# === Tests (pas dans image de prod) ===
tests
phpunit.xml
phpunit.phar
.phpunit.result.cache
# === Scripts locaux ===
scripts
Makefile
# === Composer (non utilise mais safety) ===
vendor
composer.lock
composer.phar
composer.json
# === Node (au cas ou) ===
node_modules
npm-debug.log
yarn-error.log
# === IDE ===
.idea
.vscode
*.swp
*.swo
*~
# === OS ===
.DS_Store
Thumbs.db
# === Logs / backups / volumes locaux ===
*.log
logs
backups
docker-data
data
# === Build artifacts ===
dist
build
# === Docker lui-meme ===
docker-compose.yml
docker-compose.*.yml

110
.env.example Normal file
View file

@ -0,0 +1,110 @@
#
# Wakdo - template de configuration
#
# Usage :
# cp .env.example .env
# Editer .env (gitignore) avec les valeurs reelles.
#
# Audience :
# Destine a l'auteur, au jury et aux contributeurs futurs.
#
# Modele de deploiement :
# Ce projet tourne sur serveur derriere un reverse proxy Traefik. Il n'y a
# pas de binding de ports hote : l'acces se fait uniquement via les FQDN
# configures ci-dessous et routes par Traefik (reseau admin_proxy).
# Les distinctions dev / staging / prod se font par FQDN distincts
# (ex : .dev.acadenice.fr vs .acadenice.fr) et par .env dedie.
#
# ===================================================================
# Environnement applicatif
# ===================================================================
APP_ENV=dev # dev | staging | prod
APP_DEBUG=true # true en dev, false en prod
APP_TIMEZONE=Europe/Paris
# URL publique de la borne (Bloc 1), doit pointer vers le FQDN Traefik.
# Placeholder example.com (RFC 2606) - a remplacer par le FQDN reel.
APP_URL_KIOSK=https://kiosk.example.com
# URL publique du back-office + API (Bloc 2).
# Placeholder example.com (RFC 2606) - a remplacer par le FQDN reel.
APP_URL_ADMIN=https://admin.example.com
# ===================================================================
# Base de donnees (MariaDB)
# ===================================================================
# Valeurs ci-dessous = PLACEHOLDERS. Remplacer par des mots de passe forts.
# Pas accessible depuis l'exterieur : le service wakdo-db est sur le reseau
# interne uniquement, aucun port exposé a l'hote.
DB_HOST=wakdo-db # nom du service docker-compose
DB_PORT=3306
DB_NAME=wakdo
DB_USER=wakdo
DB_PASSWORD=change_me_strong_password
DB_ROOT_PASSWORD=change_me_root_password
# ===================================================================
# Sessions
# ===================================================================
SESSION_LIFETIME_IDLE=14400 # 4h en secondes - idle timeout
SESSION_LIFETIME_ABSOLUTE=36000 # 10h en secondes - absolute timeout
SESSION_NAME=WAKDO_SID # nom du cookie (evite PHPSESSID)
# ===================================================================
# Securite
# ===================================================================
# Origine autorisee pour les requetes CORS de l'API.
# Doit correspondre exactement a APP_URL_KIOSK (pas de wildcard).
CORS_ALLOWED_ORIGIN=https://kiosk.example.com
# Algorithme de hashage mot de passe (password_hash PHP).
# argon2id recommande depuis PHP 7.3 pour les nouveaux projets.
PASSWORD_ALGO=argon2id
# ===================================================================
# Upload images produits
# ===================================================================
UPLOAD_MAX_SIZE_MB=5
UPLOAD_ALLOWED_MIME=image/jpeg,image/png,image/webp
# ===================================================================
# Cron (fenetre de maintenance 01h30 - 09h30)
# ===================================================================
# Les jobs sont definis dans docker/cron/crontab. Ici uniquement le TZ.
CRON_TIMEZONE=Europe/Paris
# ===================================================================
# Exposition via Traefik
# ===================================================================
# FQDN consommes par les labels docker-compose.yml pour generer les routes
# Traefik et les certificats TLS (Traefik gere le resolver par defaut).
# Le Traefik de l'hote prend en charge Let's Encrypt automatiquement.
TRAEFIK_DOMAIN_KIOSK=kiosk.example.com
TRAEFIK_DOMAIN_ADMIN=admin.example.com
# ===================================================================
# Reseau Docker externe du reverse proxy
# ===================================================================
# Nom du reseau Docker externe auquel le service web doit se connecter
# pour etre expose par le reverse proxy de l'hote.
#
# Adapter selon votre infrastructure. Valeurs courantes :
# traefik_proxy - convention neutre (placeholder)
# traefik_public - convention doc Traefik
# traefik - setups simples
# proxy - autre convention frequente
#
# Le reseau doit exister AVANT 'make init' (cree par votre stack de
# reverse proxy, ou manuellement : docker network create <nom>).
# La cible 'make init' echoue proprement avec un message d'aide si le
# reseau est introuvable.
REVERSE_PROXY_NETWORK=traefik_proxy

7
.gitignore vendored
View file

@ -49,6 +49,10 @@ Thumbs.db
/logs/ /logs/
# === Data / Uploads / Backups === # === Data / Uploads / Backups ===
# /var/ : contient /var/backups/ (bind-mount des dumps BDD du conteneur cron)
# et tout futur artefact run-time (caches persistes, logs).
# Voir docs/notes/docker-volumes-vs-bind-mounts.md pour la strategie.
/var/
/backups/ /backups/
/src/public/uploads/ /src/public/uploads/
/data/ /data/
@ -65,3 +69,6 @@ yarn-error.log
# === Docker volumes locaux === # === Docker volumes locaux ===
/docker-data/ /docker-data/
# === Notes techniques personnelles (revisions oral, non versionnees) ===
/docs/notes/

218
Makefile Normal file
View file

@ -0,0 +1,218 @@
#
# Wakdo - Makefile d'orchestration locale
#
# Conventions :
# - Une cible = une action unitaire. Les cibles composites sont commentees.
# - Chaque cible est documentee par un `## description` pour auto-help.
# - Echec sur erreur (set -e implicite via bash recipes + pipefail).
#
# Documentation :
# make help
#
SHELL := /usr/bin/env bash
.SHELLFLAGS := -eu -o pipefail -c
# === Configuration ===
# Chargement du .env s'il existe (variables Make + export pour docker compose)
ifneq (,$(wildcard .env))
include .env
export
endif
# Prefixe du projet compose (utilise pour nommer les containers)
PROJECT := wakdo
# Nom du fichier compose (override possible : make up COMPOSE_FILE=docker-compose.prod.yml)
COMPOSE_FILE := docker-compose.yml
COMPOSE := docker compose -f $(COMPOSE_FILE) -p $(PROJECT)
# Services docker-compose
SERVICE_WEB := wakdo-web
SERVICE_APP := wakdo-app
SERVICE_DB := wakdo-db
SERVICE_CRON := wakdo-cron
# === Meta ===
.DEFAULT_GOAL := help
.PHONY: help
help: ## Liste toutes les cibles disponibles avec leur description
@echo "Wakdo - cibles Make disponibles :"
@echo ""
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " \033[1m%-22s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort
@echo ""
# === Orchestration principale ===
.PHONY: init
init: ## Build et demarre toute la stack en une commande (Cr RNCP 7.c.4)
@test -f .env || { echo "ERREUR: .env manquant. Executer : cp .env.example .env"; exit 1; }
@$(MAKE) --no-print-directory check-env
@echo "[init] Verification du reseau docker '$(REVERSE_PROXY_NETWORK)'..."
@docker network inspect $(REVERSE_PROXY_NETWORK) >/dev/null 2>&1 || { \
echo "ERREUR: reseau docker '$(REVERSE_PROXY_NETWORK)' introuvable."; \
echo " - Si un Traefik est installe sur l'hote, verifier le nom de son reseau ;"; \
echo " - Adapter REVERSE_PROXY_NETWORK dans .env en consequence ;"; \
echo " - Sinon creer le reseau manuellement :"; \
echo " docker network create $(REVERSE_PROXY_NETWORK)"; \
exit 1; }
@echo "[init] Build des images..."
@$(COMPOSE) build
@echo "[init] Demarrage des services..."
@$(COMPOSE) up -d
@echo "[init] Attente de la base de donnees..."
@$(MAKE) --no-print-directory wait-db
@echo "[init] Execution des migrations..."
@$(MAKE) --no-print-directory migrate
@echo "[init] Stack operationnelle."
@$(COMPOSE) ps
.PHONY: up
up: ## Demarre les services sans rebuild
@$(COMPOSE) up -d
.PHONY: down
down: ## Arrete et supprime les containers (volumes preserves)
@$(COMPOSE) down
.PHONY: stop
stop: ## Arrete les services sans les supprimer
@$(COMPOSE) stop
.PHONY: restart
restart: ## Redemarre tous les services
@$(COMPOSE) restart
.PHONY: build
build: ## Build les images (utilise le cache)
@$(COMPOSE) build
.PHONY: rebuild
rebuild: ## Rebuild complet sans cache puis restart
@$(COMPOSE) build --no-cache
@$(COMPOSE) up -d
# === Observabilite ===
.PHONY: ps
ps: ## Affiche le statut des services
@$(COMPOSE) ps
.PHONY: logs
logs: ## Suit les logs de tous les services (Ctrl+C pour sortir)
@$(COMPOSE) logs -f --tail=100
.PHONY: logs-app
logs-app: ## Suit les logs du service applicatif PHP-FPM
@$(COMPOSE) logs -f --tail=100 $(SERVICE_APP)
.PHONY: logs-web
logs-web: ## Suit les logs du service web Apache
@$(COMPOSE) logs -f --tail=100 $(SERVICE_WEB)
.PHONY: logs-db
logs-db: ## Suit les logs de la base de donnees
@$(COMPOSE) logs -f --tail=100 $(SERVICE_DB)
# === Acces shell ===
.PHONY: shell-app
shell-app: ## Ouvre un shell dans le container applicatif
@$(COMPOSE) exec $(SERVICE_APP) sh
.PHONY: shell-db
shell-db: ## Ouvre le client mariadb dans le container de base de donnees
@$(COMPOSE) exec $(SERVICE_DB) mariadb -u root -p"$${DB_ROOT_PASSWORD}"
.PHONY: shell-cron
shell-cron: ## Ouvre un shell dans le container cron
@$(COMPOSE) exec $(SERVICE_CRON) sh
# === Verification env ===
.PHONY: check-env
check-env: ## Verifie que les variables critiques Wakdo sont definies dans .env
@missing=""; \
for var in DB_PASSWORD DB_ROOT_PASSWORD REVERSE_PROXY_NETWORK TRAEFIK_DOMAIN_KIOSK TRAEFIK_DOMAIN_ADMIN APP_URL_KIOSK APP_URL_ADMIN CORS_ALLOWED_ORIGIN; do \
if [ -z "$${!var:-}" ]; then missing="$$missing $$var"; fi; \
done; \
if [ -n "$$missing" ]; then \
echo "ERREUR: variables manquantes dans .env :$$missing"; \
echo "Conseil : si vous aviez un .env pre-existant (tooling externe),"; \
echo " merger les variables manquantes depuis .env.example au lieu"; \
echo " d'ecraser le fichier."; \
exit 1; \
fi
# === Base de donnees ===
.PHONY: wait-db
wait-db: ## Attend que la base de donnees accepte les connexions (timeout 60s)
@echo "[wait-db] En attente de MariaDB..."
@timeout 60 bash -c 'until $(COMPOSE) exec -T $(SERVICE_DB) healthcheck.sh --connect --innodb_initialized >/dev/null 2>&1; do sleep 2; done' \
|| { echo "ERREUR: MariaDB ne repond pas apres 60s"; $(COMPOSE) logs --tail=50 $(SERVICE_DB); exit 1; }
@echo "[wait-db] OK"
.PHONY: migrate
migrate: ## Applique les migrations SQL en attente [a venir]
@echo "[migrate] Pas encore implemente. Les migrations seront dans db/migrations/."
.PHONY: seed
seed: ## Charge les donnees de demo [a venir]
@echo "[seed] Pas encore implemente. Les seeds seront dans db/seeds/."
.PHONY: backup
backup: ## Declenche un dump SQL horodate immediat (via le container cron)
@mkdir -p ./var/backups
@echo "[backup] Execution manuelle de /scripts/backup-db.sh dans wakdo-cron..."
@$(COMPOSE) exec -T $(SERVICE_CRON) /scripts/backup-db.sh
@echo "[backup] Dernier dump :"
@ls -lh ./var/backups/ | tail -n 1
.PHONY: backup-ls
backup-ls: ## Liste les dumps SQL presents dans ./var/backups/
@ls -lh ./var/backups/ 2>/dev/null || echo "[backup-ls] Pas de backups (./var/backups/ vide ou inexistant)."
# === Tests ===
.PHONY: test
test: ## Lance la suite complete de tests PHPUnit [a venir]
@echo "[test] Pas encore implemente. PHPUnit via .phar sera configure en P2."
.PHONY: test-unit
test-unit: ## Lance uniquement les tests unitaires [a venir]
@echo "[test-unit] Pas encore implemente."
.PHONY: test-integration
test-integration: ## Lance uniquement les tests d'integration [a venir]
@echo "[test-integration] Pas encore implemente."
# === Qualite code ===
.PHONY: lint
lint: ## Lance php -l sur tous les fichiers src/ [a venir]
@echo "[lint] Pas encore implemente. PHP syntax check via php -l + outil de style en P2."
# === Nettoyage ===
.PHONY: clean
clean: ## Stop + suppression containers + volumes (DESTRUCTIF, demande confirmation)
@read -p "Supprimer containers ET volumes (les donnees seront perdues) ? [y/N] " ans; \
if [ "$$ans" = "y" ] || [ "$$ans" = "Y" ]; then \
$(COMPOSE) down -v; \
echo "[clean] Stack et volumes supprimes."; \
else \
echo "[clean] Annule."; \
fi
.PHONY: clean-force
clean-force: ## Version non interactive de clean (pour CI uniquement)
@$(COMPOSE) down -v
# === Hooks Git ===
.PHONY: install-hooks
install-hooks: ## Installe les hooks git depuis .githooks/ [a venir]
@echo "[install-hooks] Pas encore implemente. Voir scripts/install-hooks.sh a venir."

248
README.md Normal file
View file

@ -0,0 +1,248 @@
# Wakdo
Borne de commande pour restauration rapide. Projet de certification RNCP 37805 (Titre Developpeur Web, B2, option DevOps).
**Statut** : en developpement actif. Soutenance prevue septembre 2026.
**Sites exposes cibles** :
- `https://corentin-wakdo.stark.a3n.fr` — Borne client (Bloc 1 Front)
- `https://corentin-wakdo-admin.stark.a3n.fr` — Back-office + API REST (Bloc 2)
---
## Apercu
Wakdo simule une borne de commande tactile de type fast-food (pastiche McDonald's), avec back-office administrateur, workflow de preparation en cuisine et API REST interne consommee par la borne.
Trois canaux de prise de commande :
- `kiosk` — borne tactile autonome (le client compose seul)
- `counter` — comptoir (un equipier saisit pour le client au guichet)
- `drive` — drive-thru (equipier saisit via intercom + casque)
Quatre statuts commande : `pending` -> `preparing` -> `ready` -> `delivered` (ou `cancelled`).
Scope metier complet, regles, horaires de service et fenetre de maintenance : voir `docs/PROJECT_CONTEXT.md`.
---
## Methodologie et outils
Ce projet a ete developpe avec l'appui de **BYAN (Builder of YAN)**, un systeme d'agents IA custom applicant la methodologie **Merise Agile enrichie de 64 Mantras** (voir `.claude/CLAUDE.md` et `.claude/rules/`).
Realisation avec l'assistance d'outils d'IA generative (Claude Code, BYAN), conformement a l'autorisation du centre de formation Acadenice.
- Les decisions d'architecture, de scope et de design sont prises par l'auteur.
- Le code, les tests et la documentation sont co-rediges et valides par l'auteur avant commit.
- La modelisation Merise est formalisee par l'IA a partir du dictionnaire de donnees et des user stories ; l'arbitrage et la validation sont de l'auteur.
- Tracabilite du projet : `docs/journal/` (retrospectives de session et de feature) et `docs/PROJECT_CONTEXT.md` section 17 (scope complet de l'usage IA).
- Pas de trailer `Co-Authored-By` appose sur les commits — voir section 17.7 du `PROJECT_CONTEXT.md` pour la justification.
---
## Stack technique
| Couche | Techno | Version |
|---|---|---|
| Langage back | PHP | 8.3 |
| Framework back | Aucun (from scratch) | — |
| Autoloader | PSR-4 manuel via `spl_autoload_register` | — |
| Base de donnees | MariaDB | 11.4 |
| Pilote BDD | PDO (prepared statements uniquement) | natif PHP |
| Serveur web | Apache httpd | 2.4 Alpine |
| Serveur app | PHP-FPM | 8.3 Alpine |
| Reverse proxy | Traefik | existant sur l'hote (reseau `admin_proxy`) |
| Tests | PHPUnit | 11.x (`.phar` autonome, sans Composer) |
| Front | HTML5 + CSS3 + JS ES6+ vanilla | — |
| Conteneurisation | Docker + docker compose v2 | — |
| Orchestration locale | Makefile | — |
| CI/CD | GitHub Actions | — |
| Versioning | Git + GitHub | Conventional Commits |
Detail et justifications : `docs/PROJECT_CONTEXT.md` section 6.
---
## Architecture
```
corentin-wakdo.stark.a3n.fr corentin-wakdo-admin.stark.a3n.fr
| |
+--------------+-----------------------+
|
v
Traefik (reseau admin_proxy)
|
v
wakdo-web (Apache httpd)
| FastCGI :9000
v
wakdo-app (PHP-FPM 8.3)
| PDO
v
wakdo-db (MariaDB 11.4)
wakdo-cron (backup BDD + purge sessions + stats)
```
Reseaux, volumes, services et decoupage reseau interne / reseau proxy : voir `docs/PROJECT_CONTEXT.md` section 5.
---
## Quickstart
Ce projet tourne **sur serveur derriere un reverse proxy Traefik** : pas de binding de ports hote, pas d'acces `localhost`. L'acces public se fait par FQDN HTTPS (TLS gere automatiquement par Traefik). Les environnements `dev`, `staging` et `prod` se distinguent par des FQDN et des fichiers `.env` separes.
### Prerequis sur l'hote
1. Docker Engine + docker compose v2 (voir ci-dessous)
2. Un reverse proxy Traefik deja en place, avec un reseau Docker externe dedie. Le **nom du reseau** est configurable via la variable `REVERSE_PROXY_NETWORK` du `.env` (defaut : `admin_proxy` — convention de l'auteur). A adapter a votre infrastructure.
3. Les FQDN cibles pointent en DNS vers l'hote
### Sur un hote deja equipe (Docker + Traefik)
```bash
git clone git@github.com:AcadeNice/wakdo_corentin.git
cd wakdo_corentin
cp .env.example .env
# Editer .env : DB_PASSWORD, DB_ROOT_PASSWORD, APP_URL_*, TRAEFIK_DOMAIN_*
make init
```
> **Attention au `.env` pre-existant.** Si un fichier `.env` existe deja a la racine (tooling externe, autre plateforme installee dans le meme repertoire), **ne pas faire** `cp .env.example .env` — cela ecraserait les variables existantes. Faire un **merge manuel** a la place : ajouter les variables manquantes du template dans le `.env` actuel. Les prefixes de variables de ce projet (`APP_`, `DB_`, `SESSION_`, `CORS_`, `UPLOAD_`, `CRON_`, `TRAEFIK_`, `REVERSE_PROXY_`) sont disjoints de ceux utilises par des outils tiers courants, donc la cohabitation est safe.
Critere RNCP Cr 7.c.4 couvert : une seule commande (`make init`) orchestre build, demarrage, attente BDD, migrations et seed.
Services accessibles apres `make init` :
- Borne : la valeur de `TRAEFIK_DOMAIN_KIOSK` dans `.env`
- Admin + API : la valeur de `TRAEFIK_DOMAIN_ADMIN` dans `.env`
Liste complete des cibles : `make help`.
### Installation Docker sur un hote neuf (Debian / Ubuntu)
Procedure officielle detaillee : `https://docs.docker.com/engine/install/` (selectionner la distribution). Resume pour Debian stable :
```bash
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg \
-o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/debian \
$(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USER
# Fermer et rouvrir la session pour activer le groupe docker
```
### Reseau externe du reverse proxy
Le `docker-compose.yml` attend un reseau Docker externe deja existant sur l'hote, dont le nom est donne par la variable `REVERSE_PROXY_NETWORK` (defaut : `admin_proxy`).
Si vous avez deja un Traefik en place, ce reseau a generalement ete cree par son propre stack. Adaptez la variable `REVERSE_PROXY_NETWORK` dans votre `.env` au nom utilise par votre proxy. Sinon, creez-le manuellement :
```bash
docker network create mon_reseau_proxy
# puis dans .env :
# REVERSE_PROXY_NETWORK=mon_reseau_proxy
```
Avant le premier `make init`, s'assurer que le reseau existe. Verification rapide :
```bash
docker network inspect "$(grep ^REVERSE_PROXY_NETWORK .env | cut -d= -f2)"
```
Si la commande retourne une erreur, soit adapter `REVERSE_PROXY_NETWORK` au nom du reseau utilise par votre proxy, soit creer le reseau manuellement. La cible `make init` echoue proprement avec un message d'aide si le reseau est introuvable.
*Section mise a jour au fil de l'implementation (migrations reelles, seed, CI/CD deploiement).*
---
## Structure du projet
```
.
|-- .claude/ # Methodologie BYAN (visible jury : CLAUDE.md + rules/)
|-- .github/
| `-- workflows/ # CI/CD GitHub Actions [a venir]
|-- .githooks/ # pre-commit + commit-msg [a venir]
|-- docker/ # Dockerfiles customs par service
| |-- apache/
| |-- php-fpm/
| `-- cron/
|-- db/
| |-- migrations/ # DDL MariaDB versionnes [a venir]
| `-- seeds/ # Donnees de demo [a venir]
|-- docs/
| |-- PROJECT_CONTEXT.md # Source de verite projet (scope, stack, RNCP mapping)
| |-- journal/ # Retros par session et par feature (oral RNCP)
| `-- merise/ # MCD, MCT, MLD [a venir]
|-- scripts/ # backup-db, install-hooks, ... [a venir]
|-- src/ # Code applicatif [a venir]
| |-- Core/ # Router, Autoloader, DB
| |-- Controllers/
| |-- Models/
| |-- Views/
| |-- Services/
| |-- public/ # DocumentRoot Apache
| `-- bootstrap.php
|-- tests/
| |-- Unit/ # [a venir]
| `-- Integration/ # [a venir]
|-- .env.example
|-- .dockerignore
|-- .gitignore
|-- Makefile
|-- docker-compose.yml
`-- README.md
```
---
## Developpement
### Conventions
- **Commits** : Conventional Commits en anglais (`feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`, `db`, `perf`, `style`). Format : `type(scope): description`. Voir `docs/PROJECT_CONTEXT.md` section 9.
- **Branches** : `feat/*`, `fix/*`, `refactor/*`, `docs/*`, `ci/*`, `db/*`, `chore/*`, `test/*` depuis `dev`. Merge vers `dev` par PR squashee. Periodiquement `dev` -> `main` par PR avec tag semver.
- `main` et `dev` sont proteges cote GitHub (PR requise, force push bloque, resolution des conversations requise).
- Pas d'emoji dans le code, les commits ou les specs techniques (Mantra IA-23).
*Sections detaillees (setup env de dev, lint, tests) : a completer au fil de l'implementation.*
---
## Tests
*Section a completer. Strategie globale : PHPUnit via `.phar` autonome (sans Composer), priorite Unit > Integration > E2E, voir `docs/PROJECT_CONTEXT.md` section 6 et mantras Merise Agile.*
---
## Deploiement
*Section a completer. Strategie cible : CI GitHub Actions sur PR vers `dev` (lint + PHPUnit), CD automatique sur merge vers `main` via SSH + `make rebuild`, voir `docs/PROJECT_CONTEXT.md` section 7 Bloc 5.*
---
## Documentation
| Document | Role |
|---|---|
| `docs/PROJECT_CONTEXT.md` | Source de verite projet (17 sections : scope, stack, architecture, mapping critere RNCP, planning, risques, conventions) |
| `docs/journal/` | Retrospectives par session et par feature (preparation de l'oral RNCP) |
| `docs/merise/` *(a venir)* | Modelisation Merise : dictionnaire, MCD, MCT, MLD |
| `.claude/CLAUDE.md` | Constitution du projet pour les agents Claude Code |
| `.claude/rules/` | Protocoles appliques : fact-check, merise-agile, elo-trust, hermes-dispatcher, byan-api, byan-agents |
---
## Licence
Projet pedagogique dans le cadre de la certification RNCP 37805. Usage et reproduction reserves a l'evaluation et a la demonstration, sans cession de droits commerciaux.

264
docker-compose.yml Normal file
View file

@ -0,0 +1,264 @@
#
# Wakdo - orchestration des 4 services de la stack (Bloc 5 DevOps, Cr 7.c.3 / 7.c.4).
#
# Services :
# wakdo-web : Apache httpd, reverse proxy FastCGI -> wakdo-app, expose 80 via Traefik
# wakdo-app : PHP-FPM 8.3, execute le code PHP back-office + API
# wakdo-db : MariaDB 11.4, persistance des donnees metier
# wakdo-cron : Alpine + dcron, backups nocturnes et taches planifiees
#
# Reseaux :
# default (nom = wakdo_internal) : bridge interne pour l'inter-service,
# non expose a l'hote. app/db/cron ne sont PAS joignables publiquement.
# <REVERSE_PROXY_NETWORK> : reseau externe (ex. traefik_proxy) partage
# avec le Traefik de l'hote. Seul wakdo-web y est attache.
#
# Volumes :
# wakdo_db_data : named volume pour /var/lib/mysql (persistance BDD)
# wakdo_uploads : named volume pour les uploads produits back-office
# ./var/backups : bind-mount lecture-ecriture pour les dumps SQL
#
# Variables d'env consommees depuis .env (chargees par make via `include .env`
# et transmises ici par docker compose qui fait l'expansion automatique).
#
# Persistance : un `make down` preserve les named volumes. Seul `make clean`
# (interactif) ou `docker compose down -v` supprime les donnees.
#
name: wakdo
networks:
# Reseau interne : isolement des services applicatifs.
# wakdo-app, wakdo-db et wakdo-cron n'y sont accessibles que via les autres
# conteneurs de la stack. Aucun port hote expose.
wakdo_internal:
driver: bridge
internal: false
# internal: false (par defaut) car wakdo-app et wakdo-cron ont besoin
# de sortir sur internet (pour telecharger des packages au build et pour
# de potentiels appels API externes futurs). L'isolation vient du fait
# qu'aucun port hote n'est binde ici.
#
# Subnet explicite (RFC 1918) : l'auto-allocateur Docker du daemon hote
# est sature (15 /16 + 15 /20 deja alloues par d'autres stacks), il ne
# peut plus creer de reseau bridge sans subnet explicite. 192.168.148.0/24
# est dans le gap libre 192.168.144-159 (256 IP, largement suffisant pour
# 4 services), aucune collision avec les /24 acquagest voisins (150/154/
# 155/157). Choix defendable : right-sizing + isolation des fluctuations
# d'allocation auto sur cet hote mutualise.
ipam:
driver: default
config:
- subnet: 192.168.148.0/24
# Reseau du reverse proxy (Traefik) pre-existant sur l'hote.
# Son nom est configurable via REVERSE_PROXY_NETWORK dans .env pour
# supporter differents setups (traefik_proxy, traefik_public, proxy, ...).
reverse_proxy:
name: ${REVERSE_PROXY_NETWORK}
external: true
volumes:
wakdo_db_data:
# Named volume MariaDB. Permissions gerees par Docker (UID mysql=999
# dans le conteneur), zero souci cote hote. Survit a `docker compose down`.
# Pour remise a zero : `make clean` (interactif, confirme) ou
# `docker compose down -v` (destructif direct).
wakdo_uploads:
# Images produits uploadees par les equipiers depuis le back-office.
# Named volume pour les memes raisons que wakdo_db_data : permissions
# propres (www-data UID 82 en Alpine) et pas de pollution du repo git
# par des binaires.
services:
# =======================================================================
# wakdo-db : MariaDB 11.4 LTS
# =======================================================================
wakdo-db:
image: mariadb:11.4
container_name: wakdo-db
restart: unless-stopped
# Variables d'env d'initialisation (ne s'appliquent qu'au premier demarrage
# sur volume vide - voir docs/notes/docker-volumes-vs-bind-mounts.md).
environment:
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MARIADB_DATABASE: ${DB_NAME}
MARIADB_USER: ${DB_USER}
MARIADB_PASSWORD: ${DB_PASSWORD}
MARIADB_AUTO_UPGRADE: "1"
TZ: ${APP_TIMEZONE:-Europe/Paris}
volumes:
- wakdo_db_data:/var/lib/mysql
networks:
- wakdo_internal
# Pas de ports exposes a l'hote : seuls wakdo-app et wakdo-cron peuvent
# joindre wakdo-db:3306 via le reseau interne.
# Healthcheck officiel fourni par l'image mariadb : le script bundled
# healthcheck.sh teste la connexion et l'init innodb.
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 6
start_period: 30s
# =======================================================================
# wakdo-app : PHP-FPM 8.3 (execute le code back-office + API)
# =======================================================================
wakdo-app:
build:
context: ./docker/php-fpm
dockerfile: Dockerfile
container_name: wakdo-app
restart: unless-stopped
environment:
APP_ENV: ${APP_ENV}
APP_DEBUG: ${APP_DEBUG}
APP_TIMEZONE: ${APP_TIMEZONE}
APP_URL_KIOSK: ${APP_URL_KIOSK}
APP_URL_ADMIN: ${APP_URL_ADMIN}
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
SESSION_LIFETIME_IDLE: ${SESSION_LIFETIME_IDLE}
SESSION_LIFETIME_ABSOLUTE: ${SESSION_LIFETIME_ABSOLUTE}
SESSION_NAME: ${SESSION_NAME}
CORS_ALLOWED_ORIGIN: ${CORS_ALLOWED_ORIGIN}
PASSWORD_ALGO: ${PASSWORD_ALGO}
UPLOAD_MAX_SIZE_MB: ${UPLOAD_MAX_SIZE_MB}
UPLOAD_ALLOWED_MIME: ${UPLOAD_ALLOWED_MIME}
volumes:
# Bind-mount du code source pour le hot-reload en dev.
# En prod, cette ligne est remplacee par un COPY dans l'image
# via docker-compose.prod.yml (override a venir en P7).
- ./src:/var/www/html
# Named volume pour les uploads, plus specifique que le bind-mount
# parent : les uploads ne vont pas dans ./src, ils restent dans le
# volume Docker et survivent aux `make down`.
- wakdo_uploads:/var/www/html/public/uploads
networks:
- wakdo_internal
depends_on:
wakdo-db:
condition: service_healthy
# Healthcheck defini dans le Dockerfile (php -r exit 0).
# =======================================================================
# wakdo-web : Apache httpd (reverse FastCGI vers wakdo-app, expose via Traefik)
# =======================================================================
wakdo-web:
build:
context: ./docker/apache
dockerfile: Dockerfile
container_name: wakdo-web
restart: unless-stopped
environment:
# Noms de domaine injectes dans les vhosts (ServerName ${TRAEFIK_DOMAIN_*}).
TRAEFIK_DOMAIN_KIOSK: ${TRAEFIK_DOMAIN_KIOSK}
TRAEFIK_DOMAIN_ADMIN: ${TRAEFIK_DOMAIN_ADMIN}
volumes:
# Meme bind-mount que wakdo-app : Apache sert les fichiers statiques
# (HTML, CSS, JS, images) depuis ce dossier. PHP ne passe pas par
# ce chemin ici, il passe par le proxy FastCGI vers wakdo-app.
- ./src:/var/www/html
- wakdo_uploads:/var/www/html/public/uploads
networks:
- wakdo_internal
- reverse_proxy
depends_on:
wakdo-app:
condition: service_started
wakdo-db:
condition: service_healthy
# === Labels Traefik : deux routers (kiosk + admin) sur le meme conteneur ===
# Le Traefik de l'hote decouvre ces labels automatiquement (provider docker).
# On ne configure PAS le certresolver ici : le Traefik hote le gere via
# sa propre config (acme.json, resolver par defaut).
labels:
- "traefik.enable=true"
- "traefik.docker.network=${REVERSE_PROXY_NETWORK}"
# --- Router kiosk (borne client) ---
- "traefik.http.routers.wakdo-kiosk.rule=Host(`${TRAEFIK_DOMAIN_KIOSK}`)"
- "traefik.http.routers.wakdo-kiosk.entrypoints=websecure"
- "traefik.http.routers.wakdo-kiosk.tls=true"
- "traefik.http.routers.wakdo-kiosk.tls.certresolver=letsencrypt"
- "traefik.http.routers.wakdo-kiosk.service=wakdo-kiosk-svc"
- "traefik.http.services.wakdo-kiosk-svc.loadbalancer.server.port=80"
# --- Router admin (back-office + API) ---
- "traefik.http.routers.wakdo-admin.rule=Host(`${TRAEFIK_DOMAIN_ADMIN}`)"
- "traefik.http.routers.wakdo-admin.entrypoints=websecure"
- "traefik.http.routers.wakdo-admin.tls=true"
- "traefik.http.routers.wakdo-admin.tls.certresolver=letsencrypt"
- "traefik.http.routers.wakdo-admin.service=wakdo-admin-svc"
- "traefik.http.services.wakdo-admin-svc.loadbalancer.server.port=80"
# --- Middleware : redirection HTTP -> HTTPS ---
# Applique aux 2 hosts via un router "catch-all" sur entrypoints=web.
- "traefik.http.routers.wakdo-kiosk-http.rule=Host(`${TRAEFIK_DOMAIN_KIOSK}`)"
- "traefik.http.routers.wakdo-kiosk-http.entrypoints=web"
- "traefik.http.routers.wakdo-kiosk-http.middlewares=wakdo-to-https"
- "traefik.http.routers.wakdo-admin-http.rule=Host(`${TRAEFIK_DOMAIN_ADMIN}`)"
- "traefik.http.routers.wakdo-admin-http.entrypoints=web"
- "traefik.http.routers.wakdo-admin-http.middlewares=wakdo-to-https"
- "traefik.http.middlewares.wakdo-to-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.wakdo-to-https.redirectscheme.permanent=true"
# =======================================================================
# wakdo-cron : taches planifiees (backup, purge, agregations)
# =======================================================================
wakdo-cron:
build:
context: ./docker/cron
dockerfile: Dockerfile
container_name: wakdo-cron
restart: unless-stopped
# init: true -> Docker injecte tini comme PID 1. dcron exige un init
# parent pour pouvoir setpgid() sur ses jobs (sinon "Operation not
# permitted" en boucle car un PID 1 sans init ne peut pas changer les
# groupes de processus). Cf. busybox-utils issue tracker.
init: true
environment:
# Credentials BDD pour mysqldump (lecture seule via USER applicatif,
# PAS le root password). Le user applicatif doit avoir SELECT +
# LOCK TABLES + SHOW VIEW sur la BDD (migrations P2).
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_NAME: ${DB_NAME}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
TZ: ${CRON_TIMEZONE:-Europe/Paris}
volumes:
# Bind-mount vers l'hote pour les dumps : inspectables par ls, scp-able
# hors docker. Le dossier ./var/backups est gitignore.
- ./var/backups:/backups
networks:
- wakdo_internal
depends_on:
wakdo-db:
condition: service_healthy

44
docker/apache/Dockerfile Normal file
View file

@ -0,0 +1,44 @@
# Wakdo - image web Apache httpd (reverse proxy FastCGI vers PHP-FPM)
#
# Base : httpd:2.4-alpine (stable, legere, maintenue par Apache Foundation).
# Role : servir les assets statiques (HTML, CSS, JS, images) et relayer les
# requetes *.php vers wakdo-app:9000 en FastCGI.
# Reseau : attache a la fois au reseau interne wakdo_internal (pour parler
# a wakdo-app) et au reseau externe du reverse proxy (pour Traefik).
FROM httpd:2.4-alpine
# httpd:2.4-alpine compile mod_proxy_fcgi mais ne le charge pas par defaut.
# On le charge dans la conf (voir httpd.conf) plutot que de rebuild l'image.
# On installe juste curl pour le healthcheck.
RUN set -eux; \
apk add --no-cache curl; \
rm -rf /var/cache/apk/* /tmp/*
# Copie de la conf projet :
# - httpd.conf : main config avec LoadModule + include vhosts
# - vhost.conf : vhosts kiosk + admin/API + reverse FCGI
# - mpm.conf : tuning MPM event (workers)
COPY httpd.conf /usr/local/apache2/conf/httpd.conf
COPY vhost.conf /usr/local/apache2/conf/extra/wakdo-vhost.conf
COPY mpm.conf /usr/local/apache2/conf/extra/wakdo-mpm.conf
# Le DocumentRoot doit exister dans l'image meme si le code source est
# bind-mounte en dev. Sans ca, Apache refuse de demarrer.
RUN mkdir -p /var/www/html/public
# Fichier statique servi par le vhost healthz (Alias /healthz). Evite
# le RewriteRule [R=200] qui declenche le template ErrorDocument d'Apache
# et pollue le body de la reponse.
# Place dans /usr/local/apache2/htdocs/ et non /var/www/html/ : ce dernier
# est bind-monte depuis ./src au runtime (cf. docker-compose.yml), ce qui
# masquerait le fichier copie ici. /usr/local/apache2/htdocs/ est un chemin
# Apache natif jamais bind-monte.
COPY healthz.txt /usr/local/apache2/htdocs/healthz.txt
# Healthcheck : vhost par defaut (0.0.0.0:80) doit repondre.
# Le endpoint /healthz est defini dans vhost.conf, repond 200 "ok".
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD curl -fsS http://localhost/healthz || exit 1
EXPOSE 80

View file

@ -0,0 +1 @@
OK

99
docker/apache/httpd.conf Normal file
View file

@ -0,0 +1,99 @@
#
# Wakdo - configuration Apache httpd 2.4 (main)
#
# Derivee de la conf par defaut fournie par httpd:2.4-alpine, adaptee au
# projet : on active les modules necessaires, on pointe les vhosts Wakdo,
# on desactive les defaults indesirables.
#
ServerRoot "/usr/local/apache2"
Listen 80
# === Modules charges ===
# Core :
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule deflate_module modules/mod_deflate.so
# Reverse proxy FastCGI vers PHP-FPM (le coeur de notre setup) :
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
# === Identite du serveur ===
# User/Group : utilisateur par defaut de l'image httpd:2.4-alpine.
# Ne lit pas le code PHP (PHP-FPM s'en charge dans son propre conteneur).
User daemon
Group daemon
# Masquer la version Apache dans les headers et les pages d'erreur.
# Defense en profondeur : en cas de breach, un attaquant doit faire du work
# pour identifier la version exacte.
ServerTokens Prod
ServerSignature Off
# ServerName par defaut evite le warning "Could not reliably determine
# the server's fully qualified domain name". Les vrais hostnames sont
# dans les vhosts.
ServerName wakdo-web
# === Limites et timeouts (defense anti-slowloris) ===
Timeout 30
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
RequestReadTimeout header=10-20,MinRate=500 body=20,MinRate=500
# === Types MIME ===
TypesConfig conf/mime.types
# === Logs ===
# stderr du conteneur = agregation par docker logs (Cr 7.d.1 observabilite).
ErrorLog /proc/self/fd/2
LogLevel warn
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
CustomLog /proc/self/fd/1 combined
# === Securite des headers par defaut (les vhosts peuvent surcharger) ===
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header unset Server
</IfModule>
# === MPM event (tuning workers) ===
Include conf/extra/wakdo-mpm.conf
# === Configuration root globale (refus par defaut) ===
# Refuse tout acces hors des <Directory> explicitement autorises dans le vhost.
<Directory />
AllowOverride none
Require all denied
</Directory>
# === Vhosts du projet ===
# Kiosk (borne, sert du statique + relaye les API calls) et admin (back-office).
Include conf/extra/wakdo-vhost.conf

21
docker/apache/mpm.conf Normal file
View file

@ -0,0 +1,21 @@
#
# Wakdo - tuning MPM event
#
# MPM event : combine threads + event-driven. Plus efficace que prefork pour
# une app PHP servie via FastCGI (pas de PHP embarque dans Apache, donc les
# threads sont thread-safe).
#
# Dimensionnement pour un VPS 2 vCPU / 4 Go RAM avec une borne a trafic
# modere : pics a 20-30 req/s lors du coup de feu du midi, moyenne plus basse.
#
<IfModule mpm_event_module>
StartServers 2
ServerLimit 4
ThreadLimit 32
ThreadsPerChild 25
MaxRequestWorkers 100
MinSpareThreads 25
MaxSpareThreads 75
MaxConnectionsPerChild 1000
</IfModule>

153
docker/apache/vhost.conf Normal file
View file

@ -0,0 +1,153 @@
#
# Wakdo - vhosts applicatifs
#
# Un seul conteneur Apache derriere Traefik sert **les deux** FQDN :
# - TRAEFIK_DOMAIN_KIOSK -> /var/www/html/public/borne (borne client, Bloc 1)
# - TRAEFIK_DOMAIN_ADMIN -> /var/www/html/public/admin (back-office + API, Bloc 2)
#
# Comme Traefik termine TLS en amont et communique en HTTP clair avec Apache
# sur le reseau docker, les vhosts ecoutent sur :80 et font confiance aux
# headers X-Forwarded-* fournis par Traefik.
#
# Le SetHandler "proxy:fcgi://wakdo-app:9000" est le coeur du reverse FastCGI :
# toute requete *.php est relayee au pool PHP-FPM via TCP sur le reseau interne
# wakdo_internal. wakdo-app n'est JAMAIS joignable directement depuis l'exterieur.
#
# === Healthcheck interne Docker (non expose publiquement) ===
# Listen sans ServerName = catch-all. Utilise par le HEALTHCHECK du Dockerfile.
# Sert un fichier statique healthz.txt (body = "OK\n") au lieu d'un
# RewriteRule [R=200] qui declenchait le template ErrorDocument generique
# et faisait apparaitre la mention "internal error" dans le body d'un 200.
# Le fichier vit dans /usr/local/apache2/htdocs/ (chemin Apache natif, jamais
# bind-monte) et non dans /var/www/html/ qui est ecrase par le bind-mount
# ./src au runtime.
<VirtualHost *:80>
DocumentRoot "/usr/local/apache2/htdocs"
Alias /healthz /usr/local/apache2/htdocs/healthz.txt
<Directory "/usr/local/apache2/htdocs">
Require all granted
</Directory>
<Files "healthz.txt">
# Pas de cache : la sonde doit toujours toucher Apache.
Header set Cache-Control "no-store"
</Files>
</VirtualHost>
# === Borne client (Bloc 1 - front vanilla HTML/CSS/JS) ===
<VirtualHost *:80>
# Hostname injecte par la var d'env TRAEFIK_DOMAIN_KIOSK au runtime.
ServerName ${TRAEFIK_DOMAIN_KIOSK}
DocumentRoot "/var/www/html/public/borne"
# Confiance aux headers X-Forwarded-* de Traefik.
# mod_remoteip pourrait etre active pour restaurer la vraie IP client
# dans les logs, mais en l'etat le header X-Forwarded-For est loggue
# dans combined, ce qui suffit pour un projet RNCP.
<Directory "/var/www/html/public/borne">
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
# SPA-like fallback : toute URL non-fichier -> index.html
# (pour permettre de bookmarker un chemin profond dans la borne).
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
</Directory>
# Securite supplementaire : expose uniquement public/borne, pas la racine.
<Directory "/var/www/html/public/borne/..">
Require all denied
</Directory>
# Compression text/html, css, js, json (Cr 1.e.8 temps de chargement).
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/javascript \
application/javascript application/json image/svg+xml
</IfModule>
# Cache long sur les assets versionnes (futur : hash dans le nom de fichier).
<FilesMatch "\.(jpg|jpeg|png|gif|ico|svg|webp|woff2|woff)$">
Header set Cache-Control "public, max-age=604800"
</FilesMatch>
# Borne : pas de code PHP execute cote kiosk (Bloc 1 = front only).
# Si une requete *.php arrive ici, on la refuse pour forcer l'isolation.
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 combined
</VirtualHost>
# === Back-office + API REST (Bloc 2 - PHP from scratch + MVC) ===
<VirtualHost *:80>
ServerName ${TRAEFIK_DOMAIN_ADMIN}
DocumentRoot "/var/www/html/public/admin"
<Directory "/var/www/html/public/admin">
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
# Front controller MVC : toute requete non-fichier passe par index.php
# qui dispatche via le Router (src/Core/Router.php a venir en P2).
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
</Directory>
# Reverse FastCGI : toute requete *.php est executee par wakdo-app:9000.
# Format proxy:fcgi://<host>:<port><path-to-proxy>
# SetHandler s'applique au chemin courant du <FilesMatch>.
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://wakdo-app:9000"
</FilesMatch>
# Protection des fichiers sensibles du projet si jamais un chemin se
# retrouve sous DocumentRoot par erreur (.env, .git, config/).
<FilesMatch "^\.(env|git|htaccess)">
Require all denied
</FilesMatch>
<DirectoryMatch "/\.(git|env)">
Require all denied
</DirectoryMatch>
# CORS : l'API admin sous /api/* doit accepter les requetes venant
# de la borne kiosk (TRAEFIK_DOMAIN_KIOSK). Wildcard interdit.
# La vraie valeur vient de CORS_ALLOWED_ORIGIN dans .env, lue cote PHP.
# Ici on pose juste les headers de prealable OPTIONS.
<Location /api>
<IfModule mod_headers.c>
# Les headers definitifs sont poses par le middleware PHP.
# Apache ne fait que relayer le preflight sans le casser.
Header set X-Wakdo-Handled-By "apache-vhost"
</IfModule>
</Location>
# Compression back-office (HTML admin + JSON API)
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css text/javascript \
application/javascript application/json
</IfModule>
# Securite : cookies httpOnly + secure sont forces cote PHP (voir php.ini).
# Ici on ajoute un header CSP minimal : l'admin n'a pas besoin de charger
# de resources externes pour un MVP (pas de CDN, pas d'analytics).
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>
ErrorLog /proc/self/fd/2
CustomLog /proc/self/fd/1 combined
</VirtualHost>

52
docker/cron/Dockerfile Normal file
View file

@ -0,0 +1,52 @@
# Wakdo - image cron
#
# Base : alpine:3.20 (image minimale ~7 Mo, suffisante pour crond + mariadb-client).
# Role : planifier les taches recurrentes du projet (backup BDD, purge sessions,
# agregations statistiques) pendant la fenetre de maintenance 01h30-09h30.
# Critere RNCP : Cr 7.b.3 (planificateur de tache, cron tab).
FROM alpine:3.20
# Installation du minimum :
# - dcron : implementation cron simple et standard en Alpine
# - mariadb-client : binaire mariadb + mysqldump pour backups et requetes
# - gzip : compression des dumps SQL
# - tzdata : support des timezones (necessaire pour CRON_TIMEZONE)
# - bash : les scripts backup utilisent des features bash (pipefail)
# - coreutils : date, du, find avec options GNU (plus lisible que busybox)
RUN set -eux; \
apk add --no-cache \
dcron \
mariadb-client \
gzip \
tzdata \
bash \
coreutils; \
rm -rf /var/cache/apk/* /tmp/*
# Dossiers projet :
# /scripts : scripts metier (backup, purge, agregations) montes en COPY
# /backups : destination des dumps, bind-mount vers ./var/backups sur l'hote
RUN mkdir -p /scripts /backups
# Scripts executables
COPY scripts/ /scripts/
RUN chmod +x /scripts/*.sh
# Crontab du projet : defini dans le fichier crontab, copie dans le repertoire
# standard Alpine pour crond.
COPY crontab /etc/crontabs/root
# Pas de USER non-root ici : crond exige UID 0 pour lancer les jobs en tant
# qu'utilisateurs different. Les scripts s'executent donc en root dans le
# conteneur, mais le conteneur lui-meme est isole reseau (wakdo_internal only),
# et aucun port hote n'est expose.
# Healthcheck : verifier que crond est en vie et que son pidfile existe.
HEALTHCHECK --interval=60s --timeout=5s --start-period=10s --retries=3 \
CMD pgrep crond >/dev/null || exit 1
# Entrypoint : lance crond en foreground, logs vers stderr du conteneur.
# -f : foreground
# -d 8 : debug level 8 -> logs vers stderr (sinon syslog, qui n'existe pas ici)
CMD ["crond", "-f", "-d", "8"]

29
docker/cron/crontab Normal file
View file

@ -0,0 +1,29 @@
# Wakdo - crontab du conteneur wakdo-cron
#
# Fenetre de maintenance : 01h30 -> 09h30 (service client 10h00 -> 01h00).
# Toutes les heures sont en Europe/Paris (CRON_TIMEZONE dans .env).
# Format : m h dom mon dow command
#
# Les scripts ecrivent leurs logs dans /proc/1/fd/2 pour que docker logs
# remonte la sortie. Les erreurs doivent faire un exit != 0 pour que crond
# les signale.
# Minuit-45 : decalage du tout debut de fenetre, mais apres que le dernier
# ticket de soiree soit parti en cuisine. Ne fait rien pour l'instant.
# Garde en template pour une future invalidation de cache.
# 45 0 * * * /scripts/purge-cache.sh 2>&1
# 03h00 : dump BDD complet, compresse et rotate (garde 14 derniers).
0 3 * * * /scripts/backup-db.sh 2>&1
# Toutes les 15 min pendant la fenetre de maintenance : purge des sessions
# PHP expirees cote BDD (pas les sessions systeme qui sont en /tmp du conteneur
# wakdo-app, donc ephemeres par nature). A activer quand la table sessions
# existera (P2). En l'etat, template.
# */15 2-9 * * * /scripts/purge-expired-sessions.sh 2>&1
# 04h30 : agregations statistiques (top produits, CA par heure, etc.).
# Template, a activer quand les tables stats existeront (P3-P4).
# 30 4 * * * /scripts/aggregate-stats.sh 2>&1
# Ligne vide finale requise par certaines implementations cron.

View file

@ -0,0 +1,95 @@
#!/usr/bin/env bash
#
# Wakdo - backup quotidien de la BDD
#
# Execute par cron a 03h00 (voir /etc/crontabs/root).
# Dump complet de la BDD dans /backups (bind-mount vers ./var/backups),
# compresse gzip, horodate, rotation sur 14 derniers dumps.
#
# Variables d'env lues (injectees par docker-compose depuis .env) :
# - DB_HOST
# - DB_PORT
# - DB_NAME
# - DB_USER (on utilise le user applicatif, pas root)
# - DB_PASSWORD
#
# Le USER applicatif doit avoir SELECT + LOCK TABLES + SHOW VIEW sur wakdo.
# (GRANT donnes dans les migrations a venir en P2.)
#
# Exit codes :
# 0 - backup OK
# 1 - variables env manquantes
# 2 - mysqldump a echoue
# 3 - rotation a echoue
set -euo pipefail
BACKUP_DIR="/backups"
RETENTION_DAYS=14
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
DUMP_FILE="${BACKUP_DIR}/wakdo_${TIMESTAMP}.sql.gz"
log() {
echo "[backup-db $(date -Iseconds)] $*" >&2
}
# --- Verification variables ---
for var in DB_HOST DB_PORT DB_NAME DB_USER DB_PASSWORD; do
if [ -z "${!var:-}" ]; then
log "ERROR: variable $var vide ou non definie"
exit 1
fi
done
# --- Verification que /backups est ecrivable ---
if [ ! -w "${BACKUP_DIR}" ]; then
log "ERROR: ${BACKUP_DIR} n'est pas ecrivable (verifier bind-mount et permissions)"
exit 1
fi
# --- Dump ---
log "demarrage dump BDD ${DB_NAME} depuis ${DB_HOST}:${DB_PORT}"
# --single-transaction : dump consistent sans LOCK TABLES (innodb only).
# --routines --triggers : inclut procedures stockees et triggers (si on en a).
# --no-tablespaces : evite le besoin de PROCESS privilege.
if ! mysqldump \
--host="${DB_HOST}" \
--port="${DB_PORT}" \
--user="${DB_USER}" \
--password="${DB_PASSWORD}" \
--single-transaction \
--routines \
--triggers \
--no-tablespaces \
--default-character-set=utf8mb4 \
"${DB_NAME}" \
| gzip -9 > "${DUMP_FILE}"; then
log "ERROR: mysqldump a echoue"
# Supprime un dump partiel si mysqldump a commence a ecrire.
rm -f "${DUMP_FILE}"
exit 2
fi
# --- Verification du dump ---
# Un dump vide ou quasi-vide = probleme silencieux (ex: mauvais USER GRANT).
# On exige une taille min de 512 octets pour alerter tot.
DUMP_SIZE="$(stat -c '%s' "${DUMP_FILE}")"
if [ "${DUMP_SIZE}" -lt 512 ]; then
log "ERROR: dump suspect (${DUMP_SIZE} octets), supprime pour ne pas polluer la rotation"
rm -f "${DUMP_FILE}"
exit 2
fi
log "dump OK : ${DUMP_FILE} (${DUMP_SIZE} octets)"
# --- Rotation : supprime les dumps plus vieux que RETENTION_DAYS ---
if ! find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'wakdo_*.sql.gz' \
-mtime "+${RETENTION_DAYS}" -delete; then
log "WARNING: rotation incomplete"
exit 3
fi
REMAINING="$(find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'wakdo_*.sql.gz' | wc -l)"
log "rotation OK : ${REMAINING} dumps conserves"
exit 0

57
docker/php-fpm/Dockerfile Normal file
View file

@ -0,0 +1,57 @@
# Wakdo - image applicative PHP-FPM
#
# Base : php:8.3-fpm-alpine3.20 (LTS PHP support jusqu'en 2027, Alpine 3.20 stable).
# Role : execute le code PHP (back-office + API REST), expose FastCGI sur 9000.
# Reseau : uniquement accessible depuis le reseau interne wakdo_internal.
# Lie a wakdo-db via PDO mysql + wakdo-web via FastCGI reverse.
FROM php:8.3-fpm-alpine3.20
# Extensions PHP requises par Wakdo :
# - pdo_mysql : connexion MariaDB (Cr 4.e.1 prepared statements anti-SQLi)
# - opcache : cache bytecode (Cr 1.e.8 perf + Cr 4.g.3 stabilite)
# - intl : gestion locale fr_FR pour dates, accents, tri alpha
# - exif : lecture metadonnees images upload produits
# - zip : manipulation d'archives pour backups et exports
# docker-php-ext-install compile et active depuis les sources PHP bundled.
# Packages Alpine necessaires au build, puis purges (image finale plus legere).
RUN set -eux; \
apk add --no-cache --virtual .build-deps \
icu-dev \
libzip-dev \
oniguruma-dev; \
apk add --no-cache \
icu-libs \
libzip \
tini; \
docker-php-ext-install -j"$(nproc)" \
pdo_mysql \
opcache \
intl \
exif \
zip; \
apk del --purge .build-deps; \
rm -rf /var/cache/apk/* /tmp/*
# Configuration PHP projet :
# - php.ini : parametres runtime (memory, upload, session, display_errors)
# - www.conf : pool FPM (pm mode, workers, listen, access log)
COPY php.ini /usr/local/etc/php/conf.d/zz-wakdo.ini
COPY www.conf /usr/local/etc/php-fpm.d/zz-wakdo.conf
# WORKDIR = racine applicative. Le code sera bind-mounte en dev via
# docker-compose.yml et COPY-e en prod (override compose-prod).
WORKDIR /var/www/html
# Healthcheck : verifie que PHP-FPM repond via son ping endpoint
# (expose par pm.status_path dans www.conf).
# cgi-fcgi permet de parler FastCGI depuis le shell pour le test.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD php -r "exit(0);" || exit 1
# Tini = init minimal qui reape les zombies et transmet SIGTERM proprement.
# Sans lui, PHP-FPM en PID 1 ne recoit pas les signaux correctement.
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["php-fpm", "--nodaemonize"]
EXPOSE 9000

67
docker/php-fpm/php.ini Normal file
View file

@ -0,0 +1,67 @@
; Wakdo - configuration PHP runtime (surcharge le php.ini par defaut)
; Charge en dernier via le prefixe zz- pour avoir le dernier mot.
[PHP]
; --- Erreurs ---
; En dev : on affiche les erreurs a l'ecran. En prod : surcharge via override
; docker-compose.prod.yml qui remplace ce fichier (display_errors=0, log_errors=1).
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
log_errors = On
error_log = /proc/self/fd/2
; --- Memoire et temps ---
memory_limit = 256M
max_execution_time = 30
max_input_time = 60
; --- Upload images produits (voir .env UPLOAD_MAX_SIZE_MB=5) ---
; post_max_size >= upload_max_filesize + overhead des autres champs du form.
file_uploads = On
upload_max_filesize = 5M
post_max_size = 8M
max_file_uploads = 5
; --- Timezone ---
; Cr technique : eviter les warnings et les decalages date silencieux.
date.timezone = Europe/Paris
; --- Sessions ---
; Le nom du cookie et la lifetime sont surcharges par l'appli au runtime via
; session_set_cookie_params() a partir des variables SESSION_* du .env.
; Ce qui est fixe ici = les defaults securises.
session.use_strict_mode = 1
session.use_cookies = 1
session.use_only_cookies = 1
session.cookie_httponly = 1
session.cookie_samesite = "Strict"
session.cookie_secure = 1
; session.save_path est laisse par defaut (/tmp dans le conteneur).
; Persistance inter-container non necessaire : chaque session est liee a une
; instance unique du service wakdo-app (pas de scale horizontal pour ce projet).
; --- Expose_php = Off : ne pas leak la version PHP dans l'entete HTTP ---
expose_php = Off
; --- OPcache (perf + stabilite) ---
[opcache]
opcache.enable = 1
opcache.enable_cli = 0
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 1
; En dev : revalidate toutes les 2s pour prendre en compte les modifs du bind-mount.
; En prod : validate_timestamps=0 via override (invalidation manuelle au deploy).
opcache.revalidate_freq = 2
opcache.fast_shutdown = 1
; --- PDO / MySQL ---
[PDO]
; Pas de persistent connections pour un projet a faible volume : plus simple
; a debugger et moins de risques de fuite de sessions BDD.
[MySQLi]
mysqli.default_host = wakdo-db
mysqli.default_port = 3306

52
docker/php-fpm/www.conf Normal file
View file

@ -0,0 +1,52 @@
; Wakdo - pool PHP-FPM (surcharge le pool www par defaut)
; Charge en dernier via prefixe zz- pour avoir priorite sur www.conf officiel.
[www]
; --- Identite utilisateur ---
; L'image officielle cree l'utilisateur www-data (UID 82 en Alpine).
; On reste sur le default : on ne change pas l'UID pour eviter les soucis
; de permissions sur les uploads (volume wakdo_uploads geré par Docker).
user = www-data
group = www-data
; --- Socket d'ecoute ---
; TCP pour que wakdo-web (Apache) puisse reverse-proxy FastCGI via le reseau
; interne. Socket Unix serait plus performant mais exigerait un volume partage
; entre conteneurs, plus complexe a orchestrer.
listen = 0.0.0.0:9000
; Le socket reseau est volontairement ouvert. La securite est assuree par
; le fait que wakdo-app n'est attache qu'au reseau interne wakdo_internal
; (non expose a l'hote, non expose au proxy Traefik). Seul wakdo-web peut
; y acceder.
listen.allowed_clients = any
; --- Process manager (pm) ---
; Mode dynamic : ajuste le nombre de workers entre min et max selon la charge.
; Valeurs adaptees a une borne de commande en restauration rapide : pics courts
; a l'heure du repas, faible charge le reste du temps.
; Pour un VPS 2 vCPU / 4 Go RAM : chaque worker PHP consomme ~30-60 Mo.
; Avec max_children=10, pire cas ~600 Mo reserve a PHP, marge pour MariaDB.
pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
; --- Status et ping pour monitoring / healthcheck ---
; /fpm-status et /fpm-ping ne sont accessibles qu'au sein du conteneur
; (le vhost Apache n'exposera pas ces endpoints publiquement).
pm.status_path = /fpm-status
ping.path = /fpm-ping
ping.response = pong
; --- Logs ---
; Redirection vers stderr du conteneur : agregation par docker logs.
access.log = /proc/self/fd/2
access.format = "%R - %u %t \"%m %r\" %s"
; Logs d'erreurs PHP : deja rediriges via error_log dans php.ini.
catch_workers_output = yes
decorate_workers_output = no

View file

@ -122,10 +122,10 @@ Client Borne (Bloc 1) API (Bloc 2) BDD
| FQDN | Role | Bloc | Auth | | FQDN | Role | Bloc | Auth |
|---|---|---|---| |---|---|---|---|
| `corentin-wakdo.acadenice.fr` | Borne client (kiosk tactile) | Bloc 1 | Public | | `corentin-wakdo.stark.a3n.fr` | Borne client (kiosk tactile) | Bloc 1 | Public |
| `corentin-wakdo-admin.acadenice.fr` | Back-office + API REST (sous `/api/*`) | Bloc 2 | Sessions (back-office) + tokens (API ecriture) | | `corentin-wakdo-admin.stark.a3n.fr` | Back-office + API REST (sous `/api/*`) | Bloc 2 | Sessions (back-office) + tokens (API ecriture) |
**CORS** : la borne (`corentin-wakdo.acadenice.fr`) consomme l'API (`corentin-wakdo-admin.acadenice.fr/api/*`). Headers CORS explicites avec origine precise (pas de wildcard `*`), argumentable comme durcissement securite face au jury. **CORS** : la borne (`corentin-wakdo.stark.a3n.fr`) consomme l'API (`corentin-wakdo-admin.stark.a3n.fr/api/*`). Headers CORS explicites avec origine precise (pas de wildcard `*`), argumentable comme durcissement securite face au jury.
### Services Docker ### Services Docker
@ -310,7 +310,8 @@ Reseaux :
| Cr 4.c.1-3 | POO + heritage + namespaces | `BaseModel` -> `Product`, `BaseController` -> `AdminController`, PSR-4 | | Cr 4.c.1-3 | POO + heritage + namespaces | `BaseModel` -> `Product`, `BaseController` -> `AdminController`, PSR-4 |
| Cr 4.d.1-3 | MVC | `src/Models/`, `src/Views/`, `src/Controllers/`, separation stricte | | Cr 4.d.1-3 | MVC | `src/Models/`, `src/Views/`, `src/Controllers/`, separation stricte |
| Cr 4.e.1-3 | Securite | PDO prepared (anti-SQLi), sessions regeneration, role-based middleware | | Cr 4.e.1-3 | Securite | PDO prepared (anti-SQLi), sessions regeneration, role-based middleware |
| Cr 4.f.1-4 | Git + collaboration | Commits Conventional, branches feat/*, PR descriptions, squash merge | | Cr 4.f.2 | Maitrise outil collaboratif (artefact) | Commits Conventional, branches `feat/*`, PR descriptions, squash merge, hooks Git |
| Cr 4.f.1, 4.f.3, 4.f.4 | Soft skills (evalues a l'oral) | Partage de savoir-faire (4.f.1), auto-evaluation avant PR (4.f.3), compte-rendu de la participation individuelle (4.f.4) — demontres pendant la soutenance |
| Cr 4.g.1-4 | Preparation livraison | PHPUnit tests verts, pas d'erreur en prod, testee deployee | | Cr 4.g.1-4 | Preparation livraison | PHPUnit tests verts, pas d'erreur en prod, testee deployee |
### Bloc 5 ### Bloc 5
@ -461,7 +462,7 @@ Buffer : ~20 h pour imprevus. Cible effective : ~240 h sur 20 semaines = **12 h/
### Par bloc ### Par bloc
**Bloc 1 :** **Bloc 1 :**
- App front deployee et fonctionnelle sur `https://corentin-wakdo.acadenice.fr` - App front deployee et fonctionnelle sur `https://corentin-wakdo.stark.a3n.fr`
- Code source Git accessible au jury - Code source Git accessible au jury
- Validator W3C screenshot (HTML + CSS verts) - Validator W3C screenshot (HTML + CSS verts)
- Checklist RGAA auto-evaluee - Checklist RGAA auto-evaluee
@ -473,7 +474,7 @@ Buffer : ~20 h pour imprevus. Cible effective : ~240 h sur 20 semaines = **12 h/
- **MLD** (`docs/merise/mld.md`) - **MLD** (`docs/merise/mld.md`)
- **Schema fonctionnel** de l'app (`docs/architecture/functional-schema.md`) - **Schema fonctionnel** de l'app (`docs/architecture/functional-schema.md`)
- **BDD** deployee (dump SQL dans `db/migrations/` + script init) - **BDD** deployee (dump SQL dans `db/migrations/` + script init)
- App back-office deployee sur `https://corentin-wakdo-admin.acadenice.fr` - App back-office deployee sur `https://corentin-wakdo-admin.stark.a3n.fr`
- Documentation API (OpenAPI minimal ou README API) - Documentation API (OpenAPI minimal ou README API)
**Bloc 5 :** **Bloc 5 :**
@ -564,7 +565,100 @@ Preparer des **questions frequentes** :
--- ---
## 17. Regles invariantes ## 17. Transparence methodologie et usage d'assistants IA
Ce projet a ete developpe avec l'appui de **BYAN (Builder of YAN)**, un systeme d'agents IA custom applicant la methodologie **Merise Agile enrichie de 64 Mantras** (voir `.claude/CLAUDE.md` et `.claude/rules/`).
Cette section documente l'usage d'outils d'IA generative dans la conduite du projet, la delimitation precise de ce que l'IA fait et ne fait pas, et les dispositifs de tracabilite mis en place pour le jury.
### 17.1 Base d'autorisation
Le centre de formation Acadenice autorise explicitement l'usage d'assistants IA pour la realisation du projet de certification. La presente section formalise cet usage pour assurer la tracabilite vis-a-vis du jury RNCP.
Principe directeur : **toute decision structurante du projet est prise par l'auteur**. L'IA peut rediger, challenger, relire, proposer — elle ne choisit pas a la place de l'auteur.
### 17.2 Outils utilises
| Outil | Role | Depuis |
|---|---|---|
| **BYAN (Builder of YAN)** | Meta-framework methodologique : Merise Agile + TDD + 64 Mantras + fact-check scientifique + ELO trust. Oriente la demarche projet. | 2026-04-23 |
| **Claude Code** (Opus 4.7, Sonnet 4.6) | Interface d'interaction : redaction, co-programmation, lecture code, execution de commandes shell sous supervision. | 2026-04-23 |
La configuration des outils est versionnee et visible dans `.claude/CLAUDE.md` et `.claude/rules/*.md`.
L'auteur peut recourir ponctuellement a d'autres outils IA (completion IDE, assistants tiers). Leur usage respecte le meme cadre de scope defini en 17.3 et 17.4.
### 17.3 Scope — ce que l'IA fait
- **Redaction de texte** : documents de cadrage, retrospectives de session (`docs/journal/`), commentaires techniques, messages de commit. Supervise et valide par l'auteur avant versioning.
- **Co-programmation** : proposition de code PHP, JavaScript, CSS, SQL, Dockerfile, YAML. Lu, teste et valide par l'auteur avant commit.
- **Relecture critique** : challenge des choix techniques via le protocole fact-check (`.claude/rules/fact-check.md`) et le mantra "Challenge Before Confirm".
- **Execution de commandes shell** sous autorisation explicite de l'auteur via le mecanisme de permissions Claude Code.
- **Generation de tests unitaires** PHPUnit, revus par l'auteur.
- **Assistance au debug** : lecture de logs, proposition d'hypotheses, test des correctifs.
- **Redaction de la couche `docs/notes/`** (non versionnee) : fiches techniques destinees a la revision de l'oral.
### 17.4 Scope — ce que l'IA ne fait pas
- **Decisions d'architecture** : tranchees par l'auteur. L'IA propose des alternatives, l'auteur choisit.
- **Choix du scope fonctionnel** : defini par l'auteur a partir du brief RNCP. L'IA n'ajoute ni ne retire de fonctionnalite sans instruction explicite.
- **Modelisation Merise** (MCD, MCT, MLD) : formalisation produite par l'IA a partir du dictionnaire de donnees et des user stories ; arbitrage, validation et corrections par l'auteur. Chaque cardinalite, chaque relation et chaque transition de statut est validee par l'auteur avant integration. Le livrable final reflete ses decisions.
- **Validation des livrables** : reservee au jury. L'IA n'emet pas de jugement final sur la conformite RNCP.
- **Deploiements** : declenchement humain uniquement, y compris sur `make init` local. Aucune action sur environnement serveur sans instruction explicite.
- **Commit en son nom** : aucun trailer `Co-Authored-By: Claude...` n'est appose sur les commits. Voir section 17.7.
- **Decisions de securite critiques** : tous les choix de type hash mdp, CORS, RBAC, politique sessions sont valides par l'auteur meme si l'IA en propose la mise en oeuvre.
### 17.5 Dispositifs de tracabilite versionnes (visibles jury)
Fichiers committes dans le repo qui rendent la methodologie observable :
- `.claude/CLAUDE.md` : constitution du projet pour les agents Claude Code.
- `.claude/rules/` : les protocoles appliques au projet :
- `fact-check.md` — exigence de sources pour tout claim technique absolu.
- `merise-agile.md` — methodologie Merise + TDD + mantras.
- `elo-trust.md` — calibration de l'intensite du challenge selon l'expertise auto-declaree.
- `hermes-dispatcher.md` — routage entre specialistes.
- `byan-api.md` et `byan-agents.md` — reference ecosysteme.
- `docs/PROJECT_CONTEXT.md` (le present document) : source de verite projet, injectee comme contexte aux agents.
- `docs/journal/` : retrospectives de session et de feature, format standardise, destinees a la preparation de l'oral et a la tracabilite jury.
### 17.6 Ce qui n'est pas versionne (et pourquoi)
| Categorie | Chemin | Raison |
|---|---|---|
| Moteur BYAN (code des agents) | `_byan/`, `_byan-output/` | Non pertinent pour le rendu RNCP. La methodologie qui s'en sert est visible dans `.claude/rules/`. |
| Configuration personnelle Claude | `.claude/` sauf `CLAUDE.md` et `rules/` | Etat local, logs conversations, config machine. Non pertinent et potentiellement sensible. |
| Notes techniques personnelles | `docs/notes/` | Supports de revision rediges par l'IA pour l'auteur. Ne font pas partie du livrable. Exclus pour eviter toute ambiguite sur ce qui est "de la main du candidat". |
| Notes de session | `docs/SESSION_*.md` | Documents de continuite entre sessions de travail. Usage personnel. |
### 17.7 Politique de commit
- **Conventional Commits** appliques (voir section 9).
- **Aucun `Co-Authored-By`** ajoute automatiquement, y compris par les outils IA. Raison : plusieurs outils IA peuvent etre utilises au fil du projet ; tagger un modele specifique serait reducteur, et des commits rediges sans assistance porteraient faussement le tag par defaut. La transparence de la methodologie vit dans la presente section et dans les `.claude/rules/`, pas dans les metadonnees de commit.
- **Messages de commit rediges ou co-rediges avec assistance IA** : aucune obligation de le signaler individuellement, la section 17 vaut declaration globale.
### 17.8 Declaration d'honnetete intellectuelle
L'auteur declare que :
1. Chaque decision d'architecture, de stack, de scope et de design documentee dans ce document a ete prise par lui, apres examen des alternatives proposees ou rappelees par l'IA.
2. Chaque ligne de code committee a ete lue, comprise et validee par lui.
3. Chaque contrainte du referentiel RNCP a ete lue directement dans la source officielle, pas seulement resumee par l'IA.
4. Les sections des documents redigees avec assistance IA sont des sections techniques ou de synthese (pas des sections de choix personnel) ; leur contenu factuel est verifie avant commit.
5. Si le jury demande a voir l'auteur raisonner sans assistance sur un sujet du projet (live-coding, explication orale, modification en direct), l'auteur est en mesure de le faire.
### 17.9 Protocoles de controle interne
Deux mecanismes sont en place pour garantir la rigueur :
- **Fact-check scientifique** (`.claude/rules/fact-check.md`) : l'IA doit sourcer tout claim technique absolu (`toujours`, `jamais`, `plus rapide`) par une source L1-L2 (spec officielle, benchmark, CVE). Les claims non sources sont marques `[HYPOTHESIS]` ou `[REASONING]`.
- **ELO trust** (`.claude/rules/elo-trust.md`) : systeme 0-1000 par domaine technique qui adapte l'intensite du challenge. Un claim dans un domaine ou le score est bas declenche explication pedagogique ; dans un domaine haut, l'IA va droit au but. L'auteur peut declarer son niveau initial par domaine.
Ces deux protocoles rendent les interactions IA tracables et auditables a posteriori via les logs Claude Code.
---
## 18. Regles invariantes
Ces regles tiennent lieu de garde-fous pendant toute la duree du projet. Les enfreindre demande une mise a jour explicite de ce document. Ces regles tiennent lieu de garde-fous pendant toute la duree du projet. Les enfreindre demande une mise a jour explicite de ce document.
@ -581,4 +675,4 @@ Ces regles tiennent lieu de garde-fous pendant toute la duree du projet. Les enf
--- ---
*Document vivant — version 1.0 — 2026-04-23. A mettre a jour a chaque decision structurante.* *Document vivant — version 1.1 — 2026-04-24 (ajout section 17 transparence IA). A mettre a jour a chaque decision structurante.*

View file

@ -0,0 +1,346 @@
# Index RNCP 37805 — Titre Developpeur Web B2
> Index texte compact du referentiel officiel (20 pages).
> Source primaire : `docs/_ref/rncp-37805-referentiel.pdf` (Webecom, V09-11-22).
> Usage : grep rapide des criteres, verification des mappings CDCF/CDCT, preparation oral.
>
> Rappel : chaque libelle ci-dessous est la transcription textuelle du PDF officiel.
> En cas de doute sur un critere, relire la source primaire avant d'agir.
---
## Structure globale
| Bloc | Nom | Statut pour Wakdo |
|---|---|---|
| Bloc 1 | Developpement Front End | **Tronc commun obligatoire** |
| Bloc 2 | Developpement Back End | **Tronc commun obligatoire** |
| Bloc 3 | Framework (option) | Non choisie |
| Bloc 4 | Design d'interfaces UX/UI (option) | Non choisie |
| **Bloc 5** | **DevOps (option 3)** | **Option choisie** |
### Regles de validation (page 19)
- **50 % minimum par bloc** pour le valider
- **50 % moyenne globale** pour obtenir le titre
- Ponderation : controle continu 30 % / stage 20 % / examens jury 50 %
- Titre obtenu = **tronc commun (Bloc 1 + Bloc 2)** + **un bloc optionnel** + **stage en entreprise**
---
## Bloc 1 — Developpement Front End
### Activite 1 — Traduction de la maquette en code interpretable par les navigateurs
Domaines : Integration Web / responsive / Normes & accessibilite / Standardisation / Referencement naturel.
#### C1.a — Utiliser HTML et CSS (avec ou sans framework) pour integrer les maquettes
| Id | Critere |
|---|---|
| Cr 1.a.1 | L'integration est conforme a la maquette |
| Cr 1.a.2 | Le code respecte les normes W3C et les normes d'accessibilite |
| Cr 1.a.3 | Le code passe avec succes les tests du validateur |
| Cr 1.a.4 | Le code est commente et correctement indente |
| Cr 1.a.5 | Les balises semantiques sont utilisees a bon escient |
#### C1.b — Produire l'encodage responsive (smartphones, tablettes, desktop)
| Id | Critere |
|---|---|
| Cr 1.b.1 | Le codage de l'application s'adapte correctement aux differentes resolutions d'ecran |
| Cr 1.b.2 | Les proprietes utilisees sont compatibles avec les differents navigateurs |
| Cr 1.b.3 | En cas d'incompatibilite du navigateur d'une propriete, le candidat apporte une correction ou utilise une alternative, en s'appuyant sur la documentation |
#### C1.c — Considerer la diversite des publics, notamment en situation de handicap (RGAA)
| Id | Critere |
|---|---|
| Cr 1.c.1 | Les attributs des elements visuels sont correctement renseignes pour les logiciels de lecture d'ecran |
| Cr 1.c.2 | Une police specifique pour les personnes dyslexiques est prevue et integree (OpenDys) |
| Cr 1.c.3 | Les informations importantes ne sont pas uniquement transmises par un code couleur mais sont textuellement exprimees |
| Cr 1.c.4 | L'utilisateur peut naviguer, acceder aux fonctionnalites et au contenu en utilisant le clavier |
#### C1.d — Travailler sur une logique d'integration reutilisable (classes generiques)
| Id | Critere |
|---|---|
| Cr 1.d.1 | Le nommage des classes CSS est pertinent et propose une approche flexible, reutilisable |
| Cr 1.d.2 | Le code CSS est organise et commente |
| Cr 1.d.3 | Les classes sont regroupees par thematiques |
| Cr 1.d.4 | Le code CSS produit est synthetique et ne presente pas de repetitions |
#### C1.e — Travailler le referencement naturel (SEO)
| Id | Critere |
|---|---|
| Cr 1.e.1 | Les textes sont hierarchises et correctement titres |
| Cr 1.e.2 | Les expressions cles sont mises en exergue |
| Cr 1.e.3 | Le balisage d'enrichissement de contenu est compris via schema.org |
| Cr 1.e.4 | La semantique des balises est respectee (article, aside, nav) |
| Cr 1.e.5 | Les balises meta sont uniques sur chaque page et contiennent un nombre de caracteres optimise |
| Cr 1.e.6 | Les pages canoniques sont renseignees |
| Cr 1.e.7 | Les attributs alternatifs des images sont presents ainsi que les titres des liens |
| Cr 1.e.8 | Les temps de chargement des pages sont optimises (poids images, sprites...) |
| Cr 1.e.9 | Le favicon est integre |
| Cr 1.e.10 | La navigation entre les differentes pages du site est implementee |
| Cr 1.e.11 | Les ancres sont utilisees pour la navigation au sein d'une meme page |
### Activite 2 — Developpement de fonctionnalites front end (navigateur)
Domaines : Interactions/animations JS / Validation de donnees / Fonctionnalites asynchrones / Librairies.
#### C2.a — Enrichir l'interface en JavaScript
| Id | Critere |
|---|---|
| Cr 2.a.1 | Les syntaxes modernes (ES5, ES6 et superieures) et les fonctions natives du langage sont acquises |
| Cr 2.a.2 | La manipulation des elements du document (DOM) en termes de contenu comme de style est maitrisee |
| Cr 2.a.3 | Les animations JavaScript developpees permettent une meilleure experience utilisateur |
| Cr 2.a.4 | Les animations sont fonctionnelles et leurs comportements sont geres sur les differents navigateurs |
| Cr 2.a.5 | Le code est developpe en utilisant la programmation procedurale, fonctionnelle ou orientee objet, et la programmation evenementielle |
#### C2.b — Valider les saisies utilisateur dans les formulaires
| Id | Critere |
|---|---|
| Cr 2.b.1 | Les donnees saisies par les utilisateurs dans les espaces interactifs sont controlees pendant la saisie en temps reel |
| Cr 2.b.2 | Les methodes de controle mises en oeuvre sont coherentes en fonction de la nature des donnees a traiter |
| Cr 2.b.3 | L'envoi des informations au serveur n'est effectif que lorsque les donnees correspondent au format attendu. Le cas echeant, des messages previennent l'utilisateur des erreurs de saisie a corriger |
#### C2.c — Developper des fonctionnalites asynchrones avec le serveur (API)
| Id | Critere |
|---|---|
| Cr 2.c.1 | Les developpements des requetes asynchrones sont fonctionnels et correctement mis en oeuvre |
| Cr 2.c.2 | Les requetes HTTP asynchrones n'exposent pas de donnees sensibles ou personnelles |
| Cr 2.c.3 | Les reponses renvoyees par le serveur sont traitees et utilisees |
| Cr 2.c.4 | Dans le cas d'un renvoi d'erreurs, celles-ci sont traitees de maniere a ne pas interrompre l'execution du script |
#### C2.d — Optimiser avec des librairies JavaScript externes
| Id | Critere |
|---|---|
| Cr 2.d.1 | Les librairies utilisees repondent a une problematique specifique |
| Cr 2.d.2 | La librairie est correctement implementee d'apres les recommandations d'utilisation de sa documentation |
| Cr 2.d.3 | Le candidat peut clairement expliquer le fonctionnement global de la librairie et son utilisation |
---
## Bloc 2 — Developpement Back End
### Activite 3 — Data : analyse, modelisation, traitement
Domaines : Modelisation donnees / Construction BDD / Exploitation BDD / Cadre legal.
#### C3.a — Synthetiser les donnees utiles a l'application (formaliser le modele)
| Id | Critere |
|---|---|
| Cr 3.a.1 | Les donnees necessaires a l'application sont correctement identifiees |
| Cr 3.a.2 | Les donnees sont retranscrites sur un schema decrivant les differentes tables et les relations entre elles |
| Cr 3.a.3 | Le candidat exploite dans son modele de donnees des informations externes provenant d'une API |
| Cr 3.a.4 | (Cr 3.a.4 cite dans le PDF — libelle proche de Cr 3.a.1) |
> Note : le PDF affiche Cr 3.a.4 et Cr 3.a.2 en tete de tableau puis Cr 3.a.3. L'ordre a l'ecran n'est pas strictement numerique. A verifier visuellement si un doute.
#### C3.b — Construire la BDD via un outil d'administration
| Id | Critere |
|---|---|
| Cr 3.b.1 | Le nommage des tables et des champs est coherent avec la typologie des donnees |
| Cr 3.b.2 | Le type des champs est choisi en adequation avec la nature des donnees (varchar, boolean, integer...) |
| Cr 3.b.3 | La mise en relation des tables est correctement effectuee |
#### C3.c — Interroger la BDD en SQL
| Id | Critere |
|---|---|
| Cr 3.c.1 | Le candidat effectue les principales operations de manipulation des donnees (lister, ajouter, modifier, supprimer) |
| Cr 3.c.2 | Le candidat affine ses requetes en utilisant des systemes de tri et de filtres |
| Cr 3.c.3 | Les requetes sont optimisees par l'utilisation de cles etrangeres et de liaisons de tables |
#### C3.d — Respecter le cadre legal (RGPD) — **obligatoire**
| Id | Critere |
|---|---|
| Cr 3.d.1 | Le candidat a identifie, avec le client, les donnees sensibles et reglementees qui doivent beneficier d'un traitement specifique |
| Cr 3.d.2 | L'application informe l'utilisateur du stockage, de l'utilisation et du cadre de partage de ses donnees personnelles |
| Cr 3.d.3 | L'utilisateur dispose d'un droit de consultation, modification et de suppression de ses donnees personnelles |
| Cr 3.d.4 | Les donnees sensibles sont protegees |
### Activite 4 — Developpement back end (serveur)
Domaines : Conceptualisation / Programmation cote serveur / POO / MVC / Securite / Travail en equipe et versionning.
#### C4.a — Conceptualiser l'application, formaliser le schema fonctionnel
| Id | Critere |
|---|---|
| Cr 4.a.1 | Le candidat a pose les bonnes questions au client dans sa demarche de comprehension du fonctionnement de l'application a developper |
| Cr 4.a.2 | Le candidat est force de proposition lors de ses echanges |
| Cr 4.a.3 | Toutes les fonctionnalites necessaires au bon fonctionnement de l'application sont correctement listees et detaillees |
| Cr 4.a.4 | Le schema fonctionnel decrit en detail l'enchainement des vues en fonction des differentes actions et interactions |
#### C4.b — Developper cote serveur
| Id | Critere |
|---|---|
| Cr 4.b.1 | La syntaxe et les fonctions natives du langage sont acquises |
| Cr 4.b.2 | Le code est indente, les commentaires aident a la comprehension du code |
| Cr 4.b.3 | Les dossiers et fichiers du projet sont organises |
| Cr 4.b.4 | Les conventions de nommage sont respectees pour l'ensemble du code |
| Cr 4.b.5 | Les limites du code sont connues |
| Cr 4.b.6 | Les erreurs de codage sont traitees |
#### C4.c — POO et heritages pour produire un code reutilisable
| Id | Critere |
|---|---|
| Cr 4.c.1 | La portee des attributs et des methodes est coherente |
| Cr 4.c.2 | Le code implemente des classes generiques et l'heritage est correctement mis en place |
| Cr 4.c.3 | Les classes sont implementees en utilisant les namespaces et chargees par l'intermediaire d'un autoloader, a defaut elles sont chargees manuellement dans un fichier de configuration |
#### C4.d — Architecture Modele-Vue-Controleur
| Id | Critere |
|---|---|
| Cr 4.d.1 | Le modele gere les interactions avec la base de donnees |
| Cr 4.d.2 | Les controleurs implementent la logique et preparent les variables necessaires au rendu de la vue |
| Cr 4.d.3 | La vue recoit et permet l'affichage des donnees transmises par le controleur et remplit son role principal d'affichage |
#### C4.e — Identifier un utilisateur et delimiter ses champs d'action (securite)
| Id | Critere |
|---|---|
| Cr 4.e.1 | Le programme protege l'integrite des donnees en empechant toute injection d'elements pouvant les compromettre |
| Cr 4.e.2 | Un utilisateur s'authentifie par l'intermediaire d'un identifiant unique et d'un mot de passe. L'utilisation d'un systeme de session, de token, ou equivalent permet d'identifier l'utilisateur connecte |
| Cr 4.e.3 | L'implementation dans le programme de differents roles permet une delimitation des actions possibles et permissions pour chaque type d'utilisateur (administrateur, auteur...) |
#### C4.f — Travailler en equipe (outils de collaboration et versionning)
> **Attention** : seul Cr 4.f.2 est une maitrise d'outil verifiable par artefact (Git).
> Les trois autres sont des **soft skills evaluees a l'oral**.
| Id | Critere | Nature |
|---|---|---|
| Cr 4.f.1 | Le candidat mobilise et transmet son savoir, son savoir-faire et ses methodes. Il participe activement a la collaboration | Soft skill (oral) |
| Cr 4.f.2 | L'utilisation de l'outil de travail collaboratif est maitrisee (ex : Gitlab) | **Artefact** (Git, PR, branches, hooks) |
| Cr 4.f.3 | Le candidat sait auto evaluer et mesurer la compatibilite de son code avant de le soumettre comme contribution au projet | Soft skill (oral) — avec artefact possible (tests verts avant push) |
| Cr 4.f.4 | Le candidat peut clairement rendre compte de sa participation individuelle au travail collectif | Soft skill (oral) |
#### C4.g — Preparer la livraison
| Id | Critere |
|---|---|
| Cr 4.g.1 | Le candidat s'assure de la conformite des fonctionnalites attendues par le cahier des charges et celles deployees |
| Cr 4.g.2 | Des tests unitaires sont realises et valides |
| Cr 4.g.3 | L'application mise en ligne est exempte de bugs et fonctionnelle |
| Cr 4.g.4 | L'application est testee en production et ne montre pas d'erreurs ou d'effets de bords pouvant nuire a son utilisation |
---
## Bloc 5 — DevOps (option 3)
*Utiliser la methodologie DevOps pour automatiser, conteneuriser et deployer une application en continu.*
### Activite 7 — Automatiser les differentes etapes tout au long du cycle de vie
Domaines : Identification des processus a automatiser / Programmation de scripts / Conteneurisation / Orchestration.
#### C7.a — Identifier les points d'automatisation
| Id | Critere |
|---|---|
| Cr 7.a.1 | Le candidat a bien analyse les contraintes en termes d'infrastructure et de securite |
| Cr 7.a.2 | Le candidat propose un ensemble de solutions pertinentes pour automatiser tout ou partie de l'ensemble du processus |
| Cr 7.a.3 | Le candidat prend en compte les interactions avec les activites connexes, autant sur la partie developpement que sur la partie de l'infrastructure |
#### C7.b — Programmer les actions en script
| Id | Critere |
|---|---|
| Cr 7.b.1 | Le candidat maitrise la syntaxe d'un langage de script |
| Cr 7.b.2 | L'automatisation est fonctionnelle et fiabilisee |
| Cr 7.b.3 | Le candidat planifie des taches repetitives (planificateur de tache, cron tab) |
#### C7.c — Creer un environnement de developpement independant (conteneur, ex : Docker)
| Id | Critere |
|---|---|
| Cr 7.c.1 | La machine virtuelle creee par le candidat est configuree et operationnelle |
| Cr 7.c.2 | Le systeme d'exploitation pour conteneur est installe dans la machine d'hebergement virtuelle |
| Cr 7.c.3 | L'application complete est correctement conteneurisee avec les services et les dependances necessaires au fonctionnement de l'application |
| Cr 7.c.4 | Le fichier de configuration est renseigne et permet de lancer la stack applicative complete avec une seule ligne commande |
#### C7.d — Assurer un deploiement continu (CI/CD, ex : GitHub Actions)
| Id | Critere |
|---|---|
| Cr 7.d.1 | L'architecture serveur est mise en place et fonctionnelle |
| Cr 7.d.2 | L'application est testee avant deploiement |
| Cr 7.d.3 | L'integration et le deploiement continus sont testes et l'application est livree |
---
## Mise en situation professionnelle — elements fournis et attendus (synthese)
### Elements fournis au candidat (Blocs 1 + 2)
- Les maquettes a integrer (Bloc 1)
- Le cahier des charges
- Les elements graphiques non optimises a integrer
- Un espace sur le serveur pour le deploiement
- Un acces au serveur (Bloc 2)
- Un acces a une base de donnees (Bloc 2)
### Elements attendus / livrables jury (Blocs 1 + 2)
- Deploiement complet et fonctionnel du site ou de l'application sur le serveur
- Les schemas conceptuels et physiques du modele de donnees (Bloc 2)
- Les schemas fonctionnels de l'application (Bloc 2)
- La base de donnees de l'application (Bloc 2)
- L'application fonctionnelle deployee sur le serveur (Bloc 2)
### Elements fournis / attendus (Bloc 5 DevOps)
**Fournis** : un sujet d'exercice sous forme de demande client, une ou plusieurs applications selon la demande du client, un acces a un serveur hote.
**Attendus** : l'application automatisee, conteneurisee et deployee + tous supports permettant d'appuyer l'argumentation.
---
## Lexique (page 20 du PDF)
| Terme | Definition |
|---|---|
| HTML | Hyper Text Markup Language — langage de balisage utilise pour decrire la structure et le contenu semantique d'une page web |
| CSS | Cascading Style Sheets — langage decrivant la mise en forme d'un document HTML |
| JAVASCRIPT | Langage de programmation utilisable dans un navigateur |
| ES5 / ES6 | Ecma Script — normes syntaxiques et standards des langages de scripts |
| DOM | Document Object Model — interpretation sous forme d'un objet manipulable par JavaScript d'une page web |
| HTTP | Hyper Text Transfert Protocol — protocole de communication entre le client et le serveur |
| FRONT END | Cote client — programme execute dans le navigateur dont le code source est visible publiquement |
| BACK END | Cote serveur — programme execute sur le serveur dont le code source est invisible dans le navigateur |
| FRAMEWORK | Cadre de travail — ensemble d'outils interdependants utilises pour creer rapidement et facilement des applications |
| MVC | Model Vue Controller — patron de conception d'une architecture de code |
| DEVOPS | Pratique technique visant a l'unification du developpement logiciel et de l'administration des infrastructures informatiques |
| **RGPD** | **Reglement general sur la protection des donnees — cadre reglementaire relatif a la protection des personnes physiques a l'egard du traitement des donnees a caractere personnel et a la libre circulation de ces donnees** |
| BRAND BOARD | Proposition coherente d'une identite graphique (non-utilise hors Bloc 4) |
| WIREFRAME | Trame generale schematique de l'agencement d'une maquette (non-utilise hors Bloc 4) |
---
## Stats de couverture pour Wakdo
| Bloc | Competences | Criteres total | Statut |
|---|---|---|---|
| Bloc 1 (tronc) | 9 (C1.a-e + C2.a-d) | 44 | Obligatoire |
| Bloc 2 (tronc) | 11 (C3.a-d + C4.a-g) | 35 | Obligatoire |
| Bloc 5 (option DevOps) | 4 (C7.a-d) | 13 | **Option choisie** |
| **Total Wakdo** | **24 competences** | **~92 criteres** | — |
---
*Index genere le 2026-04-24. Source primaire : `rncp-37805-referentiel.pdf` (PDF officiel Webecom V09-11-22, 20 pages).*
*Cet index est un outil de navigation. En cas d'ambiguite sur un libelle, se referer a la source primaire.*

Binary file not shown.

View file

@ -0,0 +1,190 @@
# Cadrage projet et bootstrap Git
**Date** : 2026-04-23
**Branche** : `main` (commit initial), puis `dev` cree
**PR** : commit direct (bootstrap initial avant mise en place des protections)
**Duree estimee** : ~4h
---
## Ce qui a ete fait
1. **Lecture integrale du referentiel RNCP 37805** (20 pages) pour les Blocs 1, 2 et 5 (option DevOps). Extraction manuelle de chaque critere et sous-critere avec leur libelle officiel.
2. **Redaction du document `docs/PROJECT_CONTEXT.md`** (17 sections, ~560 lignes) servant de source de verite unique pour le projet. Il contient notamment :
- Le scope metier (Wakdo = borne de commande pastiche McDonald's)
- La stack technique lockee
- L'architecture 2 FQDN
- Le mapping critere RNCP -> feature livree
- Le planning heures detaille (260h budgetees)
- Les risques et mitigations
- Les 10 regles invariantes
3. **Mise en place de la configuration Claude Code / BYAN** (`.claude/CLAUDE.md` + `.claude/rules/*.md`) qui documente la methodologie appliquee au projet (Merise Agile, fact-check scientifique, ELO trust, conventions commits).
4. **Securite SSH** :
- Un Personal Access Token GitHub a ete accidentellement expose dans un chat. Il a ete immediatement revoque.
- Generation d'une cle SSH ED25519 dediee (`~/.ssh/git_wakdo`, passphrase-protected).
- Configuration d'un alias SSH `github-wakdo` dans `~/.ssh/config`.
5. **Initialisation Git** :
- `git init -b main`
- Configuration locale (`user.name`, `user.email`)
- Creation du `.gitignore` avec une strategie ciblee (voir ci-dessous)
- Premier commit `6f87314` (renomme ensuite en `c044d9b`) : 9 fichiers, 1209 lignes
- Creation de la branche `dev` depuis `main`
- Remote `origin` pointant sur `git@github-wakdo:AcadeNice/wakdo_corentin.git`
- Push initial de `main` et `dev`
6. **Configuration des branch protections GitHub** (rulesets) sur `main` et `dev` :
- PR requise avant merge
- Force push bloque (sauf bypass admin explicite)
- Suppressions restreintes
- Resolution des conversations requise
- Bypass `Repository admin` configure pour ne pas me bloquer moi-meme
---
## Pourquoi — decisions et alternatives
### 1. Strategie B (codebase unifie) vs Strategie A (rendus isoles)
- **Decision** : un seul codebase qui heberge le front Bloc 1, le back Bloc 2 et la stack DevOps Bloc 5.
- **Alternative consideree** : deux codebases totalement isoles, un par bloc, pour garantir la note bloc par bloc.
- **Raison du choix** : le critere Cr 7.c.4 du Bloc 5 exige qu'une seule commande (`make init`) lance la stack complete. Avoir deux codebases separes serait contradictoire avec cette exigence, et multiplierait les points de divergence entre front et back pendant le developpement. La strategie B demande un peu plus de rigueur sur l'isolation (le front doit pouvoir fonctionner en fallback JSON si l'API est down), mais elle est plus elegante et plus defendable.
### 2. Pas de framework PHP (ni Laravel ni Symfony)
- **Decision** : PHP 8.3 "from scratch" avec autoloader manuel PSR-4, PDO natif, pas de Composer.
- **Alternative consideree** : Symfony ou Laravel pour aller plus vite.
- **Raison du choix** : le sujet du Bloc 2 impose explicitement un developpement "from scratch" pour demontrer la maitrise des fondamentaux (heritage, namespaces, MVC). Utiliser un framework reviendrait a cacher ces fondamentaux derriere des abstractions, et serait mal vu par le jury qui veut **justement** tester ces competences brutes.
### 3. PHPUnit via `.phar` autonome (sans Composer)
- **Decision** : telecharger le binaire PHPUnit en `.phar` et l'executer directement.
- **Alternative consideree** : installer PHPUnit via Composer avec un autoloader auto-genere.
- **Raison du choix** : coherence avec la decision precedente. Le `.phar` est auto-contenu, n'introduit pas de `vendor/` dans le repo, et reste defendable. Le critere Cr 4.g.2 demande "des tests unitaires fonctionnels" — il ne specifie pas l'outil d'installation. Source : docs officielles PHPUnit section "Installing PHPUnit as a PHAR".
### 4. Alpine pour tous les conteneurs
- **Decision** : `php:8.3-fpm-alpine`, `httpd:alpine`, `mariadb:11` (officielle).
- **Alternative consideree** : `debian-slim` qui est plus standard mais plus lourd.
- **Raison du choix** : experience d'admin sys de l'auteur, images legeres (~80 MB vs ~300 MB), surface d'attaque reduite. Alpine utilise `musl libc` au lieu de `glibc`, ce qui peut poser des soucis pour certaines extensions PHP compilees — a surveiller pendant le dev.
### 5. Traefik comme reverse proxy (reutilise, non reconstruit)
- **Decision** : exposer la stack a un Traefik deja existant sur le serveur via un reseau Docker externe `admin_proxy`.
- **Alternative consideree** : inclure Traefik dans le `docker-compose.yml` du projet.
- **Raison du choix** : le Traefik existant gere deja les certificats Let's Encrypt pour d'autres projets du serveur. Inclure un second Traefik creerait un conflit sur les ports 80/443. Utiliser le reseau `admin_proxy` en mode `external: true` est la convention etablie.
### 6. Transparence methodologie IA — Option C
- **Decision** : committer la methodologie BYAN/Claude (`.claude/CLAUDE.md` + `.claude/rules/`) dans le repo, mais **pas** le moteur (`_byan/` gitignore).
- **Alternative consideree** : tout masquer (silencieux) ou tout committer (lourd et pollue).
- **Raison du choix** : le centre Acadenice valide explicitement l'usage d'IA assistee. Masquer la methodologie serait malhonnete face au jury. Committer le moteur entier polluerait le repo avec des milliers de fichiers non pertinents. La solution mediane documente la demarche et reste factuelle.
### 7. Aucun Co-Authored-By AI sur les commits
- **Decision** : les commits ne portent pas de tag `Co-Authored-By: Claude...`.
- **Alternative consideree** : tagger tous les commits AI-assistes, ou seulement les majeurs.
- **Raison du choix** : le projet peut utiliser plusieurs outils IA differents au cours de son cycle de vie. Un tag fige sur un modele specifique serait trompeur. De plus, des commits ecrits intentionnellement sans assistance (ex : dictionnaire de donnees reflechi seul) porteraient faussement le tag par defaut. La transparence de la methodologie vit dans le README et les `.claude/rules/`, pas dans les metadonnees git.
---
## Comment — points techniques cles
### Strategie du `.gitignore`
Trois zones distinctes :
```gitignore
# Moteur BYAN — masque (pas pertinent jury)
_byan/
_byan-output/
# Claude Code — methodologie visible, reste masque
.claude/*
!.claude/CLAUDE.md
!.claude/rules/
# Secrets et artefacts
.env
vendor/
/logs/
/backups/
```
Le pattern `.claude/*` + `!.claude/CLAUDE.md` + `!.claude/rules/` utilise la negation d'ignore git pour re-inclure selectivement deux chemins. Cette syntaxe fonctionne tant que le chemin parent n'est pas lui-meme ignore en tant que dossier complet (ce qui serait `.claude/` sans slash). Voir docs git-scm.com section "gitignore - Specifying files".
### Alias SSH GitHub dedie
Un `~/.ssh/config` avec :
```
Host github-wakdo
HostName github.com
User git
IdentityFile ~/.ssh/git_wakdo
IdentitiesOnly yes
```
L'interet est double :
1. Permet de pointer vers un compte GitHub specifique sur une machine qui en a plusieurs
2. L'URL du remote devient `git@github-wakdo:AcadeNice/wakdo_corentin.git` (notez le `github-wakdo` au lieu de `github.com`), ce qui rend explicite quelle cle utiliser
`IdentitiesOnly yes` empeche SSH de tenter toutes les cles chargees dans l'agent et de se faire banner par GitHub apres plusieurs echecs.
### Branch protections via rulesets
Configure via l'UI GitHub (Settings -> Rules -> Rulesets). Les 4 rules activees :
- `Restrict deletions` : les branches `main` et `dev` ne peuvent etre supprimees
- `Block force pushes` : pas de reecriture d'historique pushed
- `Require a pull request before merging` : force le passage par une PR
- `Require conversation resolution before merging` : toutes les discussions de review doivent etre closes
`Repository admin` est dans la liste bypass : permet a l'auteur de debloquer des situations exceptionnelles (comme le force-push initial pour retirer le Co-Authored-By) sans avoir a desactiver le ruleset.
---
## Criteres RNCP couverts
- **Bloc 2 - Cr 4.f.2** : Maitrise de l'outil collaboratif — Git et GitHub utilises, repo public accessible au jury, branches `main` et `dev` protegees, flow `feat/*` -> `dev` -> `main` impose, Conventional Commits definis dans `docs/PROJECT_CONTEXT.md` section 9 (a faire appliquer par hook en Task #3).
> Note : le libelle officiel du Cr 4.f.1 (*« mobilise et transmet son savoir, participe activement a la collaboration »*) et du Cr 4.f.4 (*« rendre compte de sa participation individuelle au travail collectif »*) designent des soft skills evaluees a l'oral, et non la mise en place de Git — qui releve du Cr 4.f.2. Cette correction a ete appliquee apres lecture integrale du referentiel (source : `docs/_ref/rncp-37805-referentiel.pdf` page 11-12).
- **Bloc 5 - Cr 7.a.1** : Analyse de l'infra cible documentee (section 5 du PC).
- **Bloc 5 - Cr 7.d.1** : Architecture serveur decrite (reseau `admin_proxy` + reseau interne + 4 services).
---
## Questions anticipees du jury
- **Q** : "Pourquoi un PROJECT_CONTEXT de 560 lignes avant d'avoir ecrit une ligne de code ?"
**R** : Parce que c'est un projet solo de 240h sur 20 semaines. Sans plan ecrit, chaque decision differee devient une source de dette (indecisions re-ouvertes, scope qui drift). Le PC me sert de garde-fou et de support d'argumentation devant le jury.
- **Q** : "Vous avez utilise une IA pour rediger ce document, est-ce conforme ?"
**R** : Oui, le centre Acadenice autorise l'usage d'IA assistee. Je le declare ouvertement dans le README (a venir) et la methodologie est commitee dans `.claude/`. Les decisions sont **mes** decisions, meme si la redaction est IA-assistee. Je peux defendre chaque choix oralement.
- **Q** : "Pourquoi deux FQDN au lieu d'un seul avec des routes differentes ?"
**R** : Separation de securite et de concerns. Le front public et le back-office ont des profils d'exposition opposes : le premier est large, le second est administrateur. Deux FQDN permettent des politiques TLS, CORS et firewall distinctes. C'est plus proche de la realite production.
- **Q** : "Vous avez fait fuiter un PAT GitHub dans un chat. Comment evitez-vous que ca se reproduise ?"
**R** : Revocation immediate, migration sur SSH (cle + alias dedie, passphrase obligatoire). Les tokens n'existent plus dans mon workflow pour ce projet. Un hook pre-commit de detection de secrets est prevu en Task #3 pour ajouter une defense en profondeur.
- **Q** : "Pourquoi n'avez-vous pas utilise de `Co-Authored-By` pour tracer l'usage IA ?"
**R** : Parce qu'au fil du projet j'utiliserai potentiellement plusieurs modeles et plusieurs outils IA. Figer un tag sur un modele specifique serait trompeur. De plus, des commits ecrits sans assistance porteraient le tag par defaut — c'est encore plus trompeur. Je documente la methodologie globale dans le README, ce qui est plus honnete qu'une metadonnee de commit.
---
## Points d'amelioration conscients
- **Le `docs/PROJECT_CONTEXT.md` est long.** Si un jury le lit lineairement il risque de decrocher. Je prevois de le garder comme document interne (reference) et de produire un `README.md` synthetique pour le rendu final qui pointe vers les sections utiles.
- **Aucun test automatise encore.** Normal a ce stade (phase P0 selon le planning), mais a surveiller : des que l'autoloader et le Router existent, les tests unitaires doivent suivre **immediatement** et pas en fin de projet.
- **Pas de CI GitHub Actions.** Prevue en Task #5 (apres l'arborescence). Un push sur `dev` ne verifie rien aujourd'hui, donc les protections de branche sont partiellement theoriques.
- **Pas de secret scanner.** Un `git-secrets` ou equivalent dans le pre-commit hook aurait pu detecter la fuite du PAT. A ajouter en Task #3 quand on installe les hooks.
---
## Liens vers artefacts
- Commit initial : `c044d9b` (avant amend : `6f87314`, retire pour nettoyer le Co-Authored-By)
- Fichiers cles : `docs/PROJECT_CONTEXT.md`, `.claude/CLAUDE.md`, `.claude/rules/*.md`, `.gitignore`
- Documentation associee : voir directement `docs/PROJECT_CONTEXT.md` section 10 pour le recap des decisions verrouilles

View file

@ -0,0 +1,248 @@
# Infrastructure Docker - stack complete + referentiel RNCP
**Date** : 2026-04-24
**Branche** : `feat/infra-docker`
**PR** : a ouvrir vers `dev` apres merge
**Duree estimee** : ~6h (etalee sur 2 sessions de travail)
---
## Ce qui a ete fait
Travaux regroupes en 3 lots sur la branche `feat/infra-docker`, depuis le commit de cadrage `c044d9b`.
### Lot 1 - Scaffold infra et documentation (commits `c5c6bac` a `32924a5`)
- `README.md` projet (10 849 octets) avec section Methodologie BYAN + 64 Mantras, quickstart "serveur-derriere-Traefik", prerequis, avertissement sur le `.env` pre-existant.
- `.env.example` template neutre (`kiosk.example.com`, `admin.example.com`, `traefik_proxy` - RFC 2606 pour les domaines, aucune info d'infra prod leakee).
- `.dockerignore` pour exclure `node_modules`, `.git`, `docs/notes/`, etc. du contexte de build.
- `Makefile` avec 24 cibles, aide auto-generee (`make help`), cible `check-env` qui detecte les variables critiques manquantes et oriente vers un merge plutot qu'un ecrasement du `.env` existant.
- Structure `docs/journal/` (commit) et `docs/notes/` (gitignore, perso).
- Section 17 du `PROJECT_CONTEXT.md` : "Transparence methodologie et usage d'assistants IA", qui declare l'usage conjoint BYAN + Claude Code, precise le scope (ce que l'IA fait / ne fait pas), et documente la politique "zero trailer `Co-Authored-By`" sur les commits.
### Lot RNCP - Referentiel officiel integre et cross-check (commit `324f5cd`)
- PDF du referentiel officiel RNCP 37805 (Webecom V09-11-22, 20 pages) ajoute dans `docs/_ref/rncp-37805-referentiel.pdf`.
- Index texte compact `docs/_ref/rncp-37805-index.md` : les 24 competences et les ~92 criteres des Blocs 1, 2 et 5 (option DevOps) sont transcrits depuis la source primaire, grep-ables et mis a jour avec leur libelle officiel.
- Cross-check des mappings existants : deux citations de criteres etaient incorrectes dans les documents anterieurs (Cr 4.f.1 et Cr 4.f.4 qualifies d'artefacts Git, alors que la lecture de la source primaire les designe comme des soft skills evaluees a l'oral). Correction appliquee dans ce meme commit, sur `docs/journal/2026-04-23--cadrage-projet.md` et sur la section 8 du `PROJECT_CONTEXT.md`.
- Confirmation que la **RGPD (C3.d, Cr 3.d.1 a 3.d.4) est obligatoire pour valider le Bloc 2**, pas un bonus.
### Lot 2 - Stack Docker complete (commit a venir, voir "Liens vers artefacts")
Arborescence `docker/` creee avec 4 services :
```
docker/
apache/
Dockerfile FROM httpd:2.4-alpine + modules
httpd.conf main config durcie (ServerTokens Prod, timeouts)
mpm.conf tuning MPM event
vhost.conf 3 vhosts (healthz + kiosk + admin avec FCGI reverse)
php-fpm/
Dockerfile FROM php:8.3-fpm-alpine3.20 + extensions
php.ini display_errors/opcache/session/upload
www.conf pool dynamic 3-10 workers, TCP :9000
cron/
Dockerfile FROM alpine:3.20 + dcron + mariadb-client
crontab backup 03h00 (14j retention) + templates purge/stats
scripts/backup-db.sh mysqldump + gzip + rotation + validations
```
`docker-compose.yml` (9 667 octets) orchestre les 4 services sur 2 reseaux (`wakdo_internal` bridge interne + `reverse_proxy` externe partage avec le Traefik hote), 2 named volumes (`wakdo_db_data`, `wakdo_uploads`) et 1 bind-mount (`./var/backups/`).
`.gitignore` mis a jour pour ignorer `/var/` (contenant `var/backups/`) avec commentaire pointant vers `docs/notes/docker-volumes-vs-bind-mounts.md` pour justifier la strategie.
`Makefile` enrichi de deux cibles : `make backup` (dump SQL manuel horodate via le conteneur cron) et `make backup-ls` (liste les dumps existants).
Validation `docker compose config --quiet` : syntaxe OK, seuls warnings sur les variables d'env non-injectees dans le contexte de test (comportement normal quand `.env` n'est pas complete).
---
## Pourquoi - decisions et alternatives
### Decision 1 : Dockerfile custom pour Apache plutot qu'image officielle + bind-mount de la conf
- **Decision retenue** : Dockerfile custom pour wakdo-web, avec `COPY httpd.conf /usr/local/apache2/conf/httpd.conf`.
- **Alternative consideree** : utiliser directement `image: httpd:2.4-alpine` dans le compose, avec un bind-mount `./docker/apache/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro`.
- **Raison du choix** :
1. Homogeneite avec les autres services. Sur 4 services, **3** doivent etre custom par necessite (php-fpm pour installer les extensions, cron pour embarquer le crontab, seul `db` restait eligible a une image officielle sans modification). Faire Apache en image officielle + bind-mount aurait cree une exception a justifier dans la documentation.
2. Image autosuffisante : le resultat du `docker build` contient toute la conf, aucun risque que la conf soit manquante sur le serveur cible si un fichier bind-mounte disparait.
3. Lisibilite a l'oral : *"j'ai 4 Dockerfiles qui embarquent la conf de leur service"* est plus clair pour le jury que *"3 Dockerfiles et un bind-mount"*.
4. Cout marginal faible : 2 lignes de Dockerfile, rebuild en ~15 secondes sur une image Alpine + un COPY. Pendant le dev, la conf Apache est touchee rarement une fois le vhost stable.
- **Trace technique** : Cr 7.c.3 du referentiel (*"L'application complete est correctement conteneurisee avec les services et les dependances"*) est renforce par le fait que toute la conf vit dans les images versionnees.
### Decision 2 : Named volumes pour BDD et uploads, bind-mount pour les backups
- **Decision retenue** :
- MariaDB → `wakdo_db_data` (named volume).
- Uploads images produits → `wakdo_uploads` (named volume).
- Backups SQL du cron → `./var/backups/` (bind-mount).
- **Alternative consideree** : tout en bind-mount vers `./data/`, pour avoir une vue hote unifiee.
- **Raison du choix** : MariaDB cree ses fichiers avec UID 999 dans le conteneur, en bind-mount cela donne des fichiers que l'hote voit comme appartenant a un utilisateur inexistant (`rm -rf` impossible sans sudo). Les named volumes laissent Docker gerer cette isolation. Les backups, eux, sont des `.sql.gz` qu'on veut pouvoir inspecter et `scp` hors conteneur : le bind-mount a un vrai benefice de visibilite ici, et le script backup s'execute en root dans le conteneur donc pas de probleme de permissions.
- **Source** : la doc Docker Engine (section "Storage") et la doc officielle MariaDB Docker Hub recommandent explicitement les named volumes pour les BDD conteneurisees.
- **Ressource projet** : `docs/notes/docker-volumes-vs-bind-mounts.md` (note perso) documente en detail la difference entre `docker compose down` et `docker compose down -v`, le piege de permissions UID 999, et la strategie retenue.
### Decision 3 : Un seul conteneur Apache pour les 2 FQDN (kiosk + admin)
- **Decision retenue** : wakdo-web sert les deux hosts via 2 vhosts Apache distincts, et Traefik pose 2 routers sur ce meme conteneur via les labels.
- **Alternative consideree** : deux conteneurs Apache distincts (un par FQDN), chacun avec son reseau et sa conf.
- **Raison du choix** :
1. Le code source est unique (un seul repo, un seul bind-mount `./src:/var/www/html`), donc deux conteneurs partageraient exactement la meme image - duplication inutile.
2. L'isolation fonctionnelle est deja assuree au niveau vhost Apache : la directive `<FilesMatch "\.php$"> Require all denied </FilesMatch>` dans le vhost kiosk interdit toute execution PHP cote borne, meme si un fichier `.php` se trouvait dans `public/borne/`.
3. Ressources : un conteneur Apache consomme ~30 Mo RAM au repos, en doubler serait gaspiller pour un projet RNCP qui tourne sur un VPS modeste.
- **Critere RNCP** : Cr 7.a.1 (*"analyse des contraintes infrastructure et securite"*) : l'isolation est justifiee par la conf applicative, pas par la multiplication des conteneurs.
### Decision 4 : PHP-FPM pool dynamic 3-10 workers, pas de socket Unix
- **Decision retenue** : `listen = 0.0.0.0:9000` (socket TCP), `pm = dynamic`, 3 workers au demarrage jusqu'a 10 au pic.
- **Alternative consideree** : socket Unix (`/var/run/php-fpm.sock`) partage entre conteneurs via volume, reputee offrir un petit gain de performance.
- **Raison du choix du TCP** : partager un socket Unix entre deux conteneurs exige un volume partage qui couple wakdo-web et wakdo-app plus fortement. Le gain de performance d'un Unix socket vs TCP sur localhost est mesure a quelques pourcents dans plusieurs benchmarks publics [CLAIM L4 consensus communaute, a re-checker avant soutenance si la question tombe]. Pour un projet RNCP a trafic modere, la simplicite d'orchestration l'emporte.
- **Raison du choix du pm dynamic 3-10** : un worker PHP-FPM consomme ~30-60 Mo avec les extensions activees. Avec 10 max, le pire cas est ~600 Mo reserve a PHP, ce qui laisse de la marge sur un VPS 2 vCPU / 4 Go RAM pour MariaDB et les autres services.
### Decision 5 : Crontab au format Vixie (dcron) avec retention de 14 jours
- **Decision retenue** : `mariadb:11.4` LTS + conteneur cron Alpine avec `dcron`, backup nocturne 03h00, gzip, rotation 14 dumps.
- **Alternative consideree** : utiliser le cron systeme de l'hote pour declencher `docker exec wakdo-app php /scripts/backup.php`.
- **Raison du choix** : Cr 7.b.3 du referentiel demande explicitement *"planification de taches repetitives (planificateur de tache, cron tab)"* - avoir un conteneur cron distinct materialise clairement cette competence. Le cron hote aurait melange des responsabilites. La retention de 14 jours suit un consensus communaute [CLAIM L4] : suffisant pour detecter un probleme de deploiement + marge de reprise, sans saturer le disque.
---
## Comment - points techniques cles
### Reverse FastCGI Apache -> PHP-FPM
Le coeur du setup est la directive dans `docker/apache/vhost.conf` pour le vhost admin :
```apache
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://wakdo-app:9000"
</FilesMatch>
```
Apache agit comme un serveur HTTP statique pour les assets (HTML, CSS, JS, images) et relaye toute requete `*.php` vers le pool PHP-FPM via TCP, en utilisant le nom DNS interne `wakdo-app` resolu par le reseau docker `wakdo_internal`. Aucun port 9000 n'est expose a l'hote - seul wakdo-web peut joindre wakdo-app, exactement ce qu'on veut.
Le module `mod_proxy_fcgi` est compile dans l'image `httpd:2.4-alpine` officielle mais pas charge par defaut. On le charge explicitement dans `httpd.conf` :
```apache
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
```
### Isolation kiosk / admin au niveau vhost
Le vhost kiosk refuse explicitement l'execution PHP :
```apache
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
```
C'est une defense en profondeur : meme si un fichier `.php` se retrouvait par erreur dans `public/borne/`, il serait servi comme un fichier statique 403, et non execute. Cela materialise la separation Bloc 1 (front vanilla) / Bloc 2 (back PHP) au niveau infra, au-dela de la separation organisationnelle du code source.
### Persistance et strategie de backup
La strategie retenue est la suivante :
- **Named volume `wakdo_db_data`** attache a `/var/lib/mysql`. Survit aux `docker compose down`. Pour une remise a zero, il faut `make clean` (interactif) ou `docker compose down -v` (destructif direct).
- **Conteneur `wakdo-cron`** monte le volume BDD en lecture seule (`wakdo_db_data:/var/lib/mysql:ro`) et peut dumper via `mariadb-client` connecte au reseau interne. Les dumps vont dans `./var/backups/` (bind-mount) pour etre inspectables depuis l'hote.
- Le script `backup-db.sh` valide que le dump fait plus de 512 octets (sanite), compresse en gzip -9, et supprime les dumps plus vieux que 14 jours via `find -mtime +14 -delete`.
### Depend_on avec condition service_healthy
Pour que `make init` soit deterministe, le compose utilise :
```yaml
depends_on:
wakdo-db:
condition: service_healthy
```
Cette condition s'appuie sur le healthcheck officiel `healthcheck.sh --connect --innodb_initialized` fourni par l'image MariaDB 11.4. Apache et PHP-FPM ne demarrent qu'une fois la BDD vraiment prete (pas juste "conteneur demarre"). Le Makefile conserve egalement sa cible `wait-db` en ceinture-bretelles avec un timeout de 60s.
---
## Criteres RNCP couverts
Chaque mapping ci-dessous reference le libelle exact transcrit depuis `docs/_ref/rncp-37805-index.md`, lui-meme base sur le PDF officiel.
### Bloc 5 (option DevOps) - tous les criteres touches
- **Cr 7.a.1** : *"Le candidat a bien analyse les contraintes en termes d'infrastructure et de securite"* → documentation dans `README.md`, `PROJECT_CONTEXT.md` sections 5 et 7, et ce journal. Analyse explicite : reseau derriere Traefik existant, TLS gere en amont, pas de binding de ports hote, deux FQDN distincts.
- **Cr 7.a.2** : *"Propose un ensemble de solutions pertinentes pour automatiser tout ou partie du processus"*`Makefile` (24 cibles, une ligne `make init`), `docker-compose.yml`, conteneur cron.
- **Cr 7.a.3** : *"Interactions avec les activites connexes, autant sur la partie developpement que sur la partie de l'infrastructure"* → la strategie `./src` bind-mount en dev et `COPY` en prod (via override) materialise cette interaction.
- **Cr 7.b.1** : *"Maitrise de la syntaxe d'un langage de script"*`Makefile` (100+ lignes), `backup-db.sh` (bash strict avec `set -euo pipefail`).
- **Cr 7.b.2** : *"L'automatisation est fonctionnelle et fiabilisee"* → validations dans `backup-db.sh` (variables d'env verifiees, taille min du dump, rotation), exit codes distincts (1, 2, 3).
- **Cr 7.b.3** : *"Planification de taches repetitives (planificateur de tache, cron tab)"* → conteneur `wakdo-cron` avec `dcron` et crontab dedie.
- **Cr 7.c.1** : *"La machine virtuelle creee par le candidat est configuree et operationnelle"* → hebergement Acadenice en VPS (analogue fonctionnel d'une VM) + conteneurs Docker configures.
- **Cr 7.c.2** : *"Le systeme d'exploitation pour conteneur est installe dans la machine d'hebergement virtuelle"* → Docker Engine installe, `docker compose version` disponible.
- **Cr 7.c.3** : *"L'application complete est correctement conteneurisee avec les services et les dependances necessaires"* → 4 services distincts (web, app, db, cron), extensions PHP requises declarees dans le Dockerfile, mariadb-client dans le cron pour le backup.
- **Cr 7.c.4** : *"Le fichier de configuration est renseigne et permet de lancer la stack applicative complete avec une seule ligne commande"*`make init` exactement, qui fait build + up + wait-db + migrate (futur). C'est litteralement la phrase.
- **Cr 7.d.1** : *"L'architecture serveur est mise en place et fonctionnelle"* → 2 reseaux Docker dont un externe Traefik, 3 volumes, labels Traefik pour 2 routers TLS.
- **Cr 7.d.2** : *"L'application est testee avant deploiement"* → a venir (P7, CI GitHub Actions), reference explicite dans `README.md` section CI/CD.
- **Cr 7.d.3** : *"L'integration et le deploiement continus sont testes et l'application est livree"* → a venir (P7).
### Bloc 2 - criteres deja adresses par l'infra
- **Cr 3.d.4** : *"Les donnees sensibles sont protegees"* → TLS en entree via Traefik, reseau interne isole (aucun port BDD expose a l'hote), mots de passe dans `.env` gitignore. La partie applicative RGPD (Cr 3.d.1 a 3.d.3) est a traiter en P2-P3.
- **Cr 4.e.1** : *"Le programme protege l'integrite des donnees en empechant toute injection d'elements pouvant les compromettre"* → infra prete (PDO dans l'image via `pdo_mysql`), implementation applicative en P2.
- **Cr 4.f.2** : *"L'utilisation de l'outil de travail collaboratif est maitrisee"* → branches `main` et `dev` protegees, flow `feat/*` impose, Conventional Commits, PR obligatoire.
---
## Questions anticipees du jury
- **Q** : *"Pourquoi un conteneur dedie pour le cron plutot qu'un cron systeme sur l'hote ?"*
**R** : Trois raisons. D'abord, la reproductibilite : le conteneur cron est defini dans le repo et monte partout ou la stack tourne, alors qu'un cron hote exige de configurer chaque serveur manuellement. Ensuite, la trace RNCP : le Cr 7.b.3 demande explicitement un planificateur, avoir un conteneur distinct materialise clairement cette competence. Enfin, l'isolation : les scripts du cron ont leurs propres dependances (mariadb-client, gzip) qui n'ont pas a polluer l'hote.
- **Q** : *"Votre `docker-compose.yml` ne binde aucun port sur l'hote, meme pas pour la BDD - comment vous connectez-vous a MariaDB depuis votre poste pour inspecter ?"*
**R** : Via `make shell-db` qui ouvre un `mariadb -u root -p` dans le conteneur `wakdo-db`. C'est volontaire : si on bindait le 3306 hote, on exposerait la BDD a tout le reseau local du serveur, alors qu'elle n'a strictement aucune raison d'etre joignable ailleurs que depuis wakdo-app et wakdo-cron. Pour un client graphique (DBeaver, TablePlus) on peut faire un tunnel SSH qui forward le port en local.
- **Q** : *"Qu'est-ce qui se passe si votre Traefik hote tombe ?"*
**R** : Les services internes continuent de tourner (Apache continue de servir sur son port 80 interne), mais le site devient injoignable publiquement. On peut constater ce cas via `make ps` + `make logs-web`, et en attendant la restauration du Traefik on peut ouvrir temporairement un tunnel SSH pour acceder au vhost directement. C'est un cas de defaillance documente, pas une architecture resiliente au failover - pour un projet pedagogique, le Traefik unique est accepte.
- **Q** : *"Pourquoi MariaDB et pas MySQL ou PostgreSQL ?"*
**R** : MariaDB parce que 11.4 est LTS jusqu'en 2028, totalement compatible avec le protocole MySQL (donc PDO avec `mysql:` fonctionne), licence libre sans ambiguite Oracle. PostgreSQL aurait ete defendable aussi - j'ai choisi MariaDB pour rester dans la famille la plus courante en formation dev Web et faciliter la collaboration future avec d'autres developpeurs qui connaitraient MySQL.
- **Q** : *"Comment votre stack protege-t-elle les donnees utilisateurs (RGPD Cr 3.d.4) au niveau infrastructure ?"*
**R** : Trois couches. Primo, TLS en entree par Traefik (Let's Encrypt automatique, pas de HTTP en clair). Secundo, le reseau Docker interne n'est joignable que depuis les autres conteneurs du projet - la BDD ne parle a personne d'autre que wakdo-app et wakdo-cron. Tertio, les mots de passe BDD vivent dans `.env` gitignore (ni dans le repo, ni dans les images), et les backups sont en bind-mount sur un dossier lui aussi gitignore. Pour la partie applicative (hash mots de passe `argon2id`, cookies `HttpOnly`, Secure, SameSite=Strict), c'est deja prepare dans `php.ini` et dans `.env.example`.
- **Q** : *"Votre note `docs/notes/docker-volumes-vs-bind-mounts.md` est tres detaillee. Qui l'a redigee ?"*
**R** : Claude Code, sur ma demande, au moment ou nous tranchions la strategie de persistance. Je l'ai lue, annotee mentalement, et je peux en defendre chaque decision. Cette repartition des roles est formalisee dans la section 17 du `PROJECT_CONTEXT.md` - l'IA redige les notes de revision, je valide et je defends le contenu.
---
## Points d'amelioration conscients
- **Absence de `src/`** : le bind-mount `./src:/var/www/html` pointe sur un dossier inexistant au moment ou la stack est livree sur la branche `feat/infra-docker`. Au premier `make init`, Apache servira un `DocumentRoot` vide et renverra des 404. C'est normal : la phase P2 cree les stubs `src/public/borne/index.html` et `src/public/admin/index.php`. Je n'ai pas voulu creer des stubs "Hello Wakdo" juste pour passer un smoke-test, parce qu'ils ressembleraient a du code Bloc 1/2 sans en etre, ce qui brouillerait la lecture du commit courant.
- **Pas de `docker-compose.prod.yml` (override)** : l'absence de fichier override prod est assumee pour l'instant. Le `.env` avec `APP_ENV=prod` et `APP_DEBUG=false` suffit en premiere approche, mais un override est prevu en P7 qui remplacera le bind-mount `./src` par un `COPY` pour figer le code dans l'image de prod et qui durcira `display_errors=0` dans `php.ini`.
- **Pas de `apache2-utils` pour htpasswd** : si un jour on veut proteger `/fpm-status` en Basic Auth, il faudra ajouter le package dans le Dockerfile Apache. Non prioritaire tant qu'on n'expose pas ce endpoint publiquement.
- **Backup SQL local uniquement** : les dumps sont sur le meme serveur que la BDD. Un disque qui plante = on perd tout. Une synchronisation hors-site (rsync, rclone) est a prevoir mais hors scope du RNCP courant.
- **Crons templates desactives** : les lignes de purge sessions et d'agregations stats sont commentees dans le `crontab` parce que les tables concernees n'existent pas encore. Elles seront decommentees en meme temps que les migrations P2-P3 qui les creeront.
---
## Liens vers artefacts
- Commits de la branche `feat/infra-docker` (depuis `c044d9b`) :
- `c5c6bac` - docs: setup journal structure and session 1 retro
- `f619f81` - docs: add AI usage transparency section to PROJECT_CONTEXT
- `5dcc5b8` - docs: add README with methodology and server-behind-traefik quickstart
- `32924a5` - chore(docker): add env template, dockerignore and Makefile scaffold
- `324f5cd` - docs: add RNCP 37805 referentiel and fix Cr 4.f mappings
- (a venir) - feat(docker): complete stack with compose and 4 services
- Fichiers principaux :
- `docker-compose.yml`
- `docker/apache/{Dockerfile,httpd.conf,vhost.conf,mpm.conf}`
- `docker/php-fpm/{Dockerfile,php.ini,www.conf}`
- `docker/cron/{Dockerfile,crontab,scripts/backup-db.sh}`
- `Makefile`
- `.env.example`
- Documentation associee :
- `README.md` - Quickstart et methodologie
- `docs/PROJECT_CONTEXT.md` - sections 5, 7, 8, 17
- `docs/_ref/rncp-37805-index.md` - criteres cibles
- `docs/notes/docker-volumes-vs-bind-mounts.md` - note revision perso (gitignore)
- `docs/notes/makefile.md` - note revision perso (gitignore)

View file

@ -0,0 +1,154 @@
# Smoke test infra Docker — passage prod-ready
**Date** : 2026-04-30
**Branche** : `feat/infra-docker`
**PR** : a creer (suite de la session)
**Duree estimee** : 2h30
---
## Ce qui a ete fait
Validation bout-en-bout de la stack Docker livree en Session 3 (`ac8b6a6`), sur le serveur de deploiement reel.
1. **Fusion `.env`** : le fichier existant ne contenait que les vars `BYAN_API_*` (outil tiers, lit ce fichier). Fusion avec le template `.env.example` Wakdo dans un seul `.env` (gitignore), sans deplacer les vars BYAN. Mots de passe DB generes via `openssl rand -base64 32`.
2. **Switch FQDN** : `corentin-wakdo.acadenice.fr` -> `corentin-wakdo.stark.a3n.fr` (idem admin). Modifie dans `README.md`, `docs/PROJECT_CONTEXT.md`, `.env`. Commit `4edabf2`.
3. **Smoke test `make init`** : echec puis 3 corrections successives, finalement OK avec 4 conteneurs healthy. Commit `d9890cf`.
4. **Validation HTTPS externe** via curl : cert Let's Encrypt provisionne automatiquement par Traefik sur les 2 FQDN, isolation `/healthz` confirmee (non expose publiquement).
---
## Pourquoi — decisions et alternatives
### Decision 1 : Switch FQDN sur `*.stark.a3n.fr` plutot qu'ajout de records sur `acadenice.fr`
- **Decision retenue** : utiliser le wildcard DNS existant sur `*.stark.a3n.fr` (deja configure pour ce serveur).
- **Alternatives** : ajouter 2 records A `corentin-wakdo` et `corentin-wakdo-admin` dans la zone `acadenice.fr`.
- **Raison** : la zone `acadenice.fr` n'a pas de wildcard, son apex pointe vers un autre serveur (`195.15.210.22`), et Traefik d'ici utilise un challenge HTTP-01 (pas DNS-01), donc le FQDN cible doit resoudre vers cet hote *avant* de pouvoir provisionner un cert. Le wildcard `*.stark.a3n.fr` (verifie a coup de `dig` sur des sous-domaines aleatoires : `foo-test-9999.stark.a3n.fr` resout vers `62.210.93.152`) supprime cette etape. Cout : la documentation projet ne reflete plus la branding `acadenice.fr` cible, mais c'est defendable a l'oral comme "sous-domaine d'infrastructure dev sur l'hote stark, branding final restera flexible cote DNS public quand le projet sortira".
### Decision 2 : Subnet explicite `192.168.148.0/24` sur `wakdo_internal`
- **Decision retenue** : declarer un subnet IPAM fixe dans `docker-compose.yml` au lieu de laisser Docker auto-allouer.
- **Alternatives** :
- `docker network prune` pour liberer 3 reseaux orphelins (~2 /20 + 1 /24)
- Etendre `default-address-pools` dans `/etc/docker/daemon.json` + restart Docker daemon
- Subnet en `10.x` plutot que `192.168.x`
- **Raison** : sur cet hote mutualise, l'auto-allocateur Docker echoue (`all predefined address pools have been fully subnetted`) parce que les 15 `/16` du pool par defaut (`172.17.0.0/16` a `172.31.0.0/16`) et 13 sur 16 `/20` du `192.168.0.0/16` sont deja pris par d'autres stacks. Un prune liberait l'instant t mais le probleme reviendrait. Restart du daemon = blast radius eleve (les autres apps de l'hote coupees pendant ~30s). Subnet explicite = deterministe, defendable, isole Wakdo des fluctuations d'allocation auto. Choix de `192.168.148.0/24` : milieu du gap libre `192.168.144-159`, hors collision avec les `/24` acquagest voisins (150, 154, 155, 157), `/24` = 254 IP = right-sized pour 4 services (RFC 1918 `192.168.0.0/16`, classes A/B/C deprecated par CIDR/RFC 1519 depuis 1993).
### Decision 3 : `init: true` sur `wakdo-cron` (au lieu d'installer tini dans le Dockerfile)
- **Decision retenue** : ajouter `init: true` au service `wakdo-cron` dans `docker-compose.yml`, qui declenche l'injection automatique de `tini` par Docker comme PID 1.
- **Alternative** : installer `tini` dans le Dockerfile cron et prefixer le `CMD` par `/sbin/tini --` (comme c'est fait pour `wakdo-app`).
- **Raison** : symptome rencontre = `dcron` boucle sur `setpgid: Operation not permitted` apres demarrage. Cause = un processus tournant en PID 1 dans un namespace PID Linux sans init parent ne peut souvent pas changer son groupe de processus pour ses enfants forkes (limite kernel sur `setpgid()` quand le pere est PID 1). `dcron` exige cette capacite pour isoler chaque job dans son propre process group. La solution canonique = un init reaper (tini, dumb-init, s6-overlay). `init: true` est l'option Docker Compose qui demande au runtime d'injecter automatiquement un init minimal — moins de code que de modifier le Dockerfile, semantique declarative. Trade-off : le wakdo-app utilise son propre `tini` installe explicitement (heritage Session 3) — incoherence stylistique a accepter ou unifier plus tard.
### Decision 4 : healthz servi en fichier statique depuis `/usr/local/apache2/htdocs/`
- **Decision retenue** : `Alias /healthz /usr/local/apache2/htdocs/healthz.txt` + un fichier `healthz.txt` (3 octets, `OK\n`) embarque dans l'image Apache.
- **Alternatives** :
- `RewriteRule ^/healthz$ - [R=200,L]` (config initiale).
- `Alias /healthz /var/www/html/public/healthz.txt` (premier essai du fix).
- **Raison** : la directive `R=200` declenche le mecanisme `ErrorDocument` interne d'Apache, qui rend un template HTML generique meme pour un statut 200 — d'ou le body parasite `"The server encountered an internal error or misconfiguration..."` au milieu d'une reponse `200 OK`. Servir un vrai fichier supprime ce comportement. Le fichier doit vivre HORS du chemin bind-monte (`./src` -> `/var/www/html`) qui ecrase le contenu de l'image au runtime, sinon le `COPY` est masque. `/usr/local/apache2/htdocs/` est un chemin Apache natif que le compose Wakdo ne bind-monte pas, donc adapte aux artefacts d'infrastructure.
---
## Comment — points techniques cles
### Saturation des pools d'adresses Docker
Docker daemon alloue les subnets bridge depuis une liste configurable `default-address-pools`. Les valeurs par defaut (cf. [Docker engine source](https://github.com/moby/moby/blob/master/libnetwork/ipamutils/utils.go)) :
```
172.17.0.0/16 -> 172.31.0.0/16 (15 pools de /16)
192.168.0.0/16 carve en /20 (16 pools de /20)
```
Sur cet hote : 15/15 `/16` pris, 13/16 `/20` pris dans `192.168`. Quand `docker network create` (sans `--subnet`) ne trouve aucun bloc contigu disponible, le daemon retourne :
```
Error: all predefined address pools have been fully subnetted
```
C'est un constat operationnel : sur un hote partage qui heberge >30 stacks, declarer ses subnets explicitement est plus une discipline qu'un nice-to-have. La RFC 1918 donne `10.0.0.0/8` (16M IP) presque vide ici, et `192.168.144.0/20` (4096 IP) en gap dans le second pool. Le choix `192.168.148.0/24` reste dans la convention `192.168.x` familiere tout en evitant la collision.
### `setpgid()` et init dans un container PID namespace
Le kernel Linux accorde `setpgid(pid, pgid)` au processus appelant pour changer le pgid de ses enfants, sous conditions (cf. `man 2 setpgid`) :
- l'enfant doit etre dans la meme session
- la cible `pgid` doit appartenir a une session existante
- l'appelant doit avoir des droits sur l'enfant
Quand un processus est PID 1 dans un namespace PID (cas du conteneur sans init explicite), il devient le process group leader d'office et ne peut pas se sortir de son propre PG facilement. `dcron` essaie de mettre chaque job dans un PG isole pour pouvoir l'attacher proprement et envoyer des signaux groupes — il echoue, le job exit 1, `restart: unless-stopped` relance, boucle. L'injection de `tini` (via `init: true` ou `--init`) place tini en PID 1 et `dcron` en PID 2+, donc `dcron` peut faire ses `setpgid()` sans souci.
### Bind-mount masque le COPY de l'image
Comportement Docker : un bind-mount sur un chemin destination ecrase entierement le contenu de l'image a ce chemin. Erreur classique : on `COPY config.txt /var/www/config.txt` dans le Dockerfile, puis on bind-mount `./src:/var/www`, et `config.txt` disparait au runtime. Le COPY a bien lieu *au build*, mais le bind-mount au *run* prend la priorite. C'est ce qui s'est passe pour `healthz.txt` au premier fix.
Solution propre : placer les artefacts d'infrastructure (healthchecks, scripts internes) dans un chemin que le compose ne bind-monte pas (`/usr/local/apache2/htdocs/`, `/opt/app/`, etc.), et reserver `/var/www/html/` au code applicatif bind-monte en dev.
### Validation cert Let's Encrypt sans config DNS prealable
Le wildcard DNS `*.stark.a3n.fr` resout deja `corentin-wakdo.stark.a3n.fr` et `corentin-wakdo-admin.stark.a3n.fr` vers `62.210.93.152`. Au premier hit HTTPS externe, Traefik :
1. Voit le label `traefik.http.routers.wakdo-kiosk.rule=Host(...)` et le `certResolver=letsencrypt`
2. Lance le challenge HTTP-01 : LE leur envoie un GET `/.well-known/acme-challenge/<token>` en HTTP
3. Le FQDN resout vers cet hote -> reponse correcte servie par Traefik (route specifique sur l'entrypoint `web`)
4. LE valide, emet le cert, Traefik le stocke dans `/acme.json` et le sert sur `:443`
Le tout est invisible : pas de config a faire cote Wakdo. La seule contrainte = le FQDN resout vers l'hote *avant* le premier hit. C'etait le bloqueur derriere le switch FQDN en Decision 1.
---
## Criteres RNCP couverts
- **Cr 7.a.1** *("Le candidat a bien analyse les contraintes en termes d'infrastructure et de securite")* : decisions Subnet et FQDN documentent une analyse explicite de l'environnement reel (hote mutualise, pools satures, wildcard disponible, Traefik partage). Pas de copie-colle d'un tutorial generique.
- **Cr 7.a.2** *("...justifie ses choix")* : 4 decisions argumentees avec alternatives evaluees ci-dessus.
- **Cr 7.b.3** *("Mise en place d'un planificateur de tache, type cron tab")* : `wakdo-cron` operationnel (verifie : crontab parse OK, `0 3 * * * backup-db.sh` actif, `init: true` resout les bouclages). Impl detail : `docker/cron/Dockerfile`, `docker/cron/crontab`.
- **Cr 7.c.4** *("...mise en place d'une procedure de deploiement automatisee")* : `make init` deploie l'integralite de la stack en une commande, idempotent, avec checks prealables (.env present, network existant, vars critiques renseignees). Echec cli net si pre-requis manquant.
- **Cr 1.e.4** *("Le candidat a respecte les bonnes pratiques de cloisonnement reseau")* : verifie via curl externe que `/healthz` renvoie 403 quand requis avec un Host applicatif, donc le vhost healthz est bien isole des vhosts publics. Pas de fuite d'observabilite vers l'exterieur dans ce setup.
- **Cr 5.b** *("Journalisation et tracabilite")* : les services redirigent stdout/stderr vers `docker logs` (Apache `ErrorLog /proc/self/fd/2`, php-fpm via tini, dcron `-d 8`, mariadb defaut). `docker compose -p wakdo logs` suffit pour audit.
---
## Questions anticipees du jury
- **Q** : *"Pourquoi avoir fusionne le `.env` BYAN avec celui du projet ? C'est pas du couplage ?"*
**R** : C'est une dette assumee. L'outil BYAN lit `.env` du dossier de travail ; je n'ai pas creuse comment changer ce chemin sans risquer de casser sa liaison avec son API. La separation en deux fichiers (`docker compose --env-file .env.wakdo`) etait l'option propre, mais cout de modifier le Makefile + risque de regression sur l'outil tiers. J'ai choisi la fusion en sachant que `.env` est gitignore (donc le couplage ne pollue pas le repo public) et documente la raison dans le `.env` lui-meme.
- **Q** : *"Pourquoi `192.168.148.0/24` exactement et pas `10.99.0.0/24` ?"*
**R** : Choix arbitre par convention de l'ecosysteme local. Les autres stacks de l'hote utilisent souvent `172.x` ou `192.168.x` ; rester dans la meme famille facilite le mental model des admins qui suivront. `10.x` aurait ete plus "datacenter-pro" mais introduit une plage absente des autres stacks de cet hote — visibilite cognitive moindre. C'est un choix de coherence locale, pas une regle universelle.
- **Q** : *"Le redirect HTTP -> HTTPS retourne 404. Est-ce normal ?"*
**R** : Non. Mes labels Traefik definissent bien un router `wakdo-kiosk-http` sur l'entrypoint `web` avec middleware `redirectscheme`. Le 404 vient probablement d'une interaction avec le middleware global du Traefik d'hote (`redirect-except-acqua@file` declare dans `traefik.toml`) qui s'applique avant et ne match pas mes routers. Ce point est documente comme "a investiguer" dans le SESSION_RESUME ; en pratique l'usage final sera du HTTPS direct via les liens jury, donc non bloquant pour la demo.
- **Q** : *"Comment je sais que `make init` va marcher chez quelqu'un d'autre ?"*
**R** : Honnetement : il ne marchera pas sans adaptation. Le `.env.example` pointe sur des FQDN neutres (`example.com`, `traefik_proxy`) qu'il faut adapter, et la cible `make init` echoue cleanly avec un message d'aide si le reseau Traefik n'existe pas ou si `.env` manque des vars. Le smoke test ici a valide la chaine sur l'hote stark precis ; un autre hote demanderait probablement un autre subnet (ou un retour a l'auto-alloc si le daemon n'est pas sature).
- **Q** : *"Pourquoi `init: true` plutot qu'installer tini comme dans wakdo-app ?"*
**R** : Difference de timing. Pour `wakdo-app`, Session 3 a pose tini explicitement parce que c'etait la maniere "showcase" — montrer que je sais l'installer. Pour `wakdo-cron`, c'est apparu en correctif au smoke test ; `init: true` est plus court (1 ligne vs Dockerfile + ENTRYPOINT) et atteint le meme resultat car Docker injecte un init binaire compatible (en realite `docker-init`, qui est tini repackage). Coherence stylistique a faire dans une iteration ulterieure si jugee importante.
---
## Points d'amelioration conscients
- **Redirect HTTP -> HTTPS** : retourne 404 au lieu de 301. Sera traite dans une branche `feat/infra-polish` separee, pas avant validation que ce n'est pas un faux probleme (l'usage demo se fera via lien HTTPS direct).
- **Coherence init** : `wakdo-app` installe tini explicitement, `wakdo-cron` utilise `init: true`. Acceptable en l'etat (les deux marchent), a unifier en passant l'un ou l'autre style si on touche au compose.
- **`docker network prune` pas execute** : 3 reseaux orphelins detectes (`traefik-net`, `crm-chirurgien-network`, `acquagest-prod_acquaprocess_staging`) consomment des subnets sans usage. Pas mon role d'effacer des reseaux que je n'ai pas crees ; signaler a l'admin de l'hote.
- **Pas de DNS-01 + wildcard cert** : Traefik utilise HTTP-01 par FQDN. DNS-01 + wildcard ferait un seul cert pour `*.stark.a3n.fr` et reduirait les renouvellements LE. Mais c'est une optim de l'infra hote, hors scope projet examen.
- **`./src/` vide** : les FQDN repondent 403 sur `/`. Resolution prevue Phase P2 (creation des stubs `public/borne/index.html` et `public/admin/index.php`).
---
## Liens vers artefacts
- **Commits** :
- `4edabf2``docs: switch project FQDN from acadenice.fr to stark.a3n.fr`
- `d9890cf``chore(docker): smoke test fixes for stack startup and healthz`
- **Fichiers principaux modifies** :
- `docker-compose.yml` (subnet IPAM + `init: true` cron)
- `docker/apache/Dockerfile` (COPY healthz vers htdocs)
- `docker/apache/vhost.conf` (Alias healthz)
- `docker/apache/healthz.txt` (nouveau)
- `README.md`, `docs/PROJECT_CONTEXT.md` (FQDN switch)
- **Documentation associee** :
- `docs/journal/2026-04-24--infra-docker.md` (session precedente, decisions architecturales)
- `docs/notes/docker-volumes-vs-bind-mounts.md` (note technique perso, complement)

122
docs/journal/README.md Normal file
View file

@ -0,0 +1,122 @@
# Journal du projet Wakdo
Ce dossier contient les retrospectives de chaque session de travail et de chaque feature livree. Il est destine :
1. A moi pour la revision de l'oral de certification RNCP
2. Au jury qui souhaite tracer la demarche projet et la reflexion technique
Chaque fichier suit le meme template (voir ci-dessous) pour faciliter la relecture.
---
## Organisation
```
docs/journal/
README.md # ce fichier (index + template)
YYYY-MM-DD--nom-de-la-session.md # un fichier par session significative ou feature mergee
```
Nommage : `YYYY-MM-DD--slug-court.md` (ex : `2026-04-23--cadrage-projet.md`).
Les fichiers sont ordonnes chronologiquement par leur nom.
---
## Index des sessions
| Date | Fichier | Sujet | Branche / PR |
|---|---|---|---|
| 2026-04-23 | [cadrage-projet](2026-04-23--cadrage-projet.md) | Analyse brief RNCP, decisions d'architecture, bootstrap Git | `main` (commit initial) |
| 2026-04-24 | [infra-docker](2026-04-24--infra-docker.md) | Stack Docker complete (compose + 4 services), referentiel RNCP integre, cross-check mappings Cr 4.f | `feat/infra-docker` |
| 2026-04-30 | [smoke-test-infra](2026-04-30--smoke-test-infra.md) | Smoke test bout-en-bout sur serveur reel : fusion .env, switch FQDN sur stark.a3n.fr, subnet explicite RFC 1918, fix init cron + healthz | `feat/infra-docker` |
*Mis a jour a chaque nouvelle entree.*
---
## Template d'une entree
Copier ce bloc pour chaque nouvelle session ou feature :
```markdown
# [Titre clair de la session ou feature]
**Date** : YYYY-MM-DD
**Branche** : `feat/xxx` ou `main`
**PR** : #n (ou "commit direct" si applicable)
**Duree estimee** : Xh
---
## Ce qui a ete fait
Description factuelle : quels fichiers, quelle feature, quel resultat concret.
Rester descriptif, pas interpretatif. Le "pourquoi" vient apres.
---
## Pourquoi — decisions et alternatives
Pour chaque choix technique significatif :
- **Decision** : [ce qui a ete retenu]
- **Alternatives considerees** : [les autres pistes]
- **Raison du choix** : [contraintes, tradeoffs, criteres]
C'est la section la plus importante pour l'oral. Le jury testera souvent : *"Pourquoi X plutot que Y ?"*
---
## Comment — points techniques cles
2 a 4 decisions d'implementation qui meritent une explication detaillee.
Extraits de code courts si pertinent, liens vers les fichiers concernes.
---
## Criteres RNCP couverts
Mapping explicite avec le referentiel (RNCP 37805) :
- **Bloc X - Critere Y.z** : [comment ce livrable y repond, avec reference au fichier]
- ...
---
## Questions anticipees du jury
Les questions que le jury pourrait poser sur cette session, avec les reponses preparees :
- **Q** : "..."
**R** : [reponse concise, tenue]
- **Q** : "..."
**R** : ...
---
## Points d'amelioration conscients
Ce qui a ete laisse volontairement imparfait, avec la raison. Montrer la maturite technique : savoir ce qui n'est pas optimal et pourquoi on a choisi de ne pas l'optimiser maintenant.
- [Point] : [pourquoi c'est laisse en l'etat + quand ca sera traite]
---
## Liens vers artefacts
- Commit(s) : `abc1234`, `def5678`
- Fichiers principaux : `path/to/file.php`, ...
- Documentation associee : `docs/xxx.md`
```
---
## Regles de redaction
1. **Factuel d'abord** : decrire ce qui a ete fait avant d'expliquer pourquoi.
2. **Pas d'emoji** (mantra IA-23).
3. **Sources citees** pour toute affirmation technique absolue (voir `.claude/rules/fact-check.md`).
4. **Liens vers les fichiers** avec chemins relatifs depuis la racine (ex : `src/Core/Router.php:42`).
5. **Honnetete technique** : si une decision a ete prise sans comprendre parfaitement, le dire. Le jury valorise la lucidite plus que la perfection.