commit df12d211658b1334c0fe381a2843b439953b9433 Author: lrdlg Date: Wed Aug 5 22:40:03 2026 +0200 Site personnel courcellematthi.eu diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..369e0f6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,56 @@ +# ============================================================================= +# .dockerignore — Fichiers exclus du contexte de build +# ----------------------------------------------------------------------------- +# Réduit le contexte envoyé au démon Docker : build plus rapide, et surtout +# aucun risque d'embarquer un secret ou l'historique Git dans une couche. +# +# ATTENTION — NE JAMAIS AJOUTER « Dockerfile » NI « .dockerignore » ICI. +# +# BuildKit (utilisé par Coolify, et par Docker depuis la version 23) applique +# les exclusions AVANT de lire le Dockerfile. Si le Dockerfile est filtré, le +# build échoue avec : +# +# #1 transferring dockerfile: 2B done +# ERROR: failed to solve: failed to read dockerfile: +# open Dockerfile: no such file or directory +# +# Le message est trompeur : le fichier existe bien dans le dépôt, c'est ce +# fichier-ci qui l'a rendu invisible. Le builder classique tolérait le cas ; +# BuildKit non. +# +# Ces deux fichiers sont retirés de l'image finale par le « RUN rm » du +# Dockerfile : ils n'apparaissent donc pas dans le dossier servi au public. +# ============================================================================= + +# Contrôle de version +.git +.gitignore +.gitattributes + +# Documentation, outillage et configurations hors conteneur +README.md +verifier-depot.sh +DEPLOIEMENT-COOLIFY.md +nginx.conf.example +.htaccess + +# Orchestration (le Dockerfile lui-même reste dans le contexte, cf. ci-dessus) +docker-compose.yaml +docker-compose.yml + +# Environnement — ne doit JAMAIS entrer dans une image +.env +.env.* +*.pem +*.key + +# Artefacts d'éditeur et de système +.vscode +.idea +.DS_Store +Thumbs.db +*.swp +*~ + +# Dépendances éventuelles (aucune aujourd'hui, mais si le projet évolue) +node_modules diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f62510 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Environnement et secrets +.env +.env.* +*.pem +*.key + +# Éditeurs +.vscode/ +.idea/ +*.swp +*~ + +# Systèmes +.DS_Store +Thumbs.db + +# Dépendances éventuelles +node_modules/ diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..bbf3965 --- /dev/null +++ b/.htaccess @@ -0,0 +1,103 @@ +# ============================================================================= +# .htaccess — Configuration Apache pour courcellematthi.eu +# ----------------------------------------------------------------------------- +# Équivalent de nginx.conf.example, pour un hébergement mutualisé où l'accès +# à la configuration serveur n'est pas possible. +# +# Ne PAS utiliser ce fichier ET la configuration Nginx : ils font double emploi. +# Sur Nginx, ce fichier est simplement ignoré. +# ============================================================================= + +# ----------------------------------------------------------------------------- +# REDIRECTIONS CANONIQUES +# ----------------------------------------------------------------------------- + + RewriteEngine On + + # HTTP -> HTTPS + RewriteCond %{HTTPS} !=on + RewriteRule ^ https://www.courcellematthi.eu%{REQUEST_URI} [R=301,L] + + # Domaine nu -> www (doit correspondre au rel="canonical" du HTML) + RewriteCond %{HTTP_HOST} ^courcellematthi\.eu$ [NC] + RewriteRule ^ https://www.courcellematthi.eu%{REQUEST_URI} [R=301,L] + + +# ----------------------------------------------------------------------------- +# EN-TÊTES DE SÉCURITÉ +# Voir nginx.conf.example pour le détail de chaque directive. +# ----------------------------------------------------------------------------- + + # N'activer HSTS qu'une fois le HTTPS vérifié : irréversible côté serveur. + Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" + + Header always set Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; upgrade-insecure-requests" + + Header always set X-Content-Type-Options "nosniff" + Header always set Referrer-Policy "strict-origin-when-cross-origin" + Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), interest-cohort=()" + Header always set X-Frame-Options "DENY" + + # Retire la signature du serveur des en-têtes de réponse + Header unset X-Powered-By + + +# ----------------------------------------------------------------------------- +# COMPRESSION +# ----------------------------------------------------------------------------- + + AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css + AddOutputFilterByType DEFLATE application/javascript application/json + AddOutputFilterByType DEFLATE application/manifest+json image/svg+xml + + + + AddOutputFilterByType BROTLI_COMPRESS text/html text/css application/javascript image/svg+xml + + +# ----------------------------------------------------------------------------- +# CACHE +# ----------------------------------------------------------------------------- + + ExpiresActive On + + # HTML : revalidation systématique, sinon les visiteurs restent sur une + # version périmée après chaque publication. + ExpiresByType text/html "access plus 0 seconds" + + ExpiresByType text/css "access plus 30 days" + ExpiresByType application/javascript "access plus 30 days" + ExpiresByType image/png "access plus 90 days" + ExpiresByType image/svg+xml "access plus 90 days" + ExpiresByType image/x-icon "access plus 90 days" + ExpiresByType font/woff2 "access plus 1 year" + ExpiresByType application/manifest+json "access plus 1 day" + + +# ----------------------------------------------------------------------------- +# TYPES MIME +# ----------------------------------------------------------------------------- + + AddType application/manifest+json .webmanifest + AddType image/svg+xml .svg + AddType font/woff2 .woff2 + + # Encodage explicite : évite les accents cassés dans llms.txt et humans.txt + AddDefaultCharset UTF-8 + AddCharset UTF-8 .html .css .js .txt .xml .json .webmanifest + + +# ----------------------------------------------------------------------------- +# PROTECTION DES FICHIERS +# ----------------------------------------------------------------------------- + +# Interdit l'accès direct à ce fichier et aux fichiers de configuration + + Require all denied + + +# Désactive le listage des répertoires sans index +Options -Indexes + +# Page d'erreur +ErrorDocument 404 /index.html diff --git a/.well-known/security.txt b/.well-known/security.txt new file mode 100644 index 0000000..cbed74f --- /dev/null +++ b/.well-known/security.txt @@ -0,0 +1,8 @@ +# Politique de divulgation de vulnérabilités — RFC 9116 +# Site personnel statique : la surface d'attaque est minimale, mais déclarer +# un canal de contact reste une bonne pratique. + +Contact: mailto:m@tthieu.be +Expires: 2027-08-04T00:00:00.000Z +Preferred-Languages: fr, en +Canonical: https://www.courcellematthi.eu/.well-known/security.txt diff --git a/DEPLOIEMENT-COOLIFY.md b/DEPLOIEMENT-COOLIFY.md new file mode 100644 index 0000000..e33143a --- /dev/null +++ b/DEPLOIEMENT-COOLIFY.md @@ -0,0 +1,379 @@ +# Déploiement sur Coolify + +Guide complet pour mettre `courcellematthi.eu` en ligne depuis Coolify. + +--- + +## Résumé + +| | | +|---|---| +| Type d'application | Dockerfile (recommandé) ou Docker Compose | +| Port interne | 80 | +| Image finale | ~50 Mo | +| Build | Aucune compilation — copie de fichiers statiques | +| TLS | Géré par Traefik, **pas** par le conteneur | + +--- + +## Étape 1 — Préparer le dépôt Git + +Coolify déploie depuis un dépôt. Sur ton Gitea auto-hébergé : + +```bash +cd courcellematthi +git init +git add . +git commit -m "Site personnel — version initiale" +git branch -M main +git remote add origin https://git.ghostinthemachine.fr/matthieu/courcellematthi.git +git push -u origin main +``` + +Vérifie que `.htaccess` et `.well-known/` sont bien poussés — Git les suit +normalement, mais un `.gitignore` trop large les fait parfois disparaître : + +```bash +git ls-files | grep -E "htaccess|well-known" +``` + +--- + +## Étape 2 — Configurer le DNS + +Avant de créer l'application, les enregistrements doivent pointer vers le +serveur, sinon Let's Encrypt échouera à émettre le certificat. + +| Type | Nom | Valeur | +|---|---|---| +| A | `www` | IP de ton serveur | +| A | `@` | IP de ton serveur | + +Si le domaine est chez Cloudflare, **désactive le proxy (nuage orange → gris)** +pendant l'émission du certificat. Le proxy Cloudflare intercepte le challenge +HTTP-01 et fait échouer la validation ACME. Tu pourras le réactiver ensuite. + +Vérification : + +```bash +dig +short www.courcellematthi.eu +dig +short courcellematthi.eu +``` + +--- + +## Étape 3 — Créer l'application dans Coolify + +**Interface Coolify → Projet → + New → Application → Public/Private Repository** + +Renseigne : + +- **Repository** : l'URL de ton dépôt Gitea +- **Branch** : `main` +- **Build Pack** : `Dockerfile` +- **Dockerfile Location** : `/Dockerfile` +- **Base Directory** : `/` +- **Port Exposes** : `80` + +> **Ne choisis pas Nixpacks.** Il tenterait de détecter un projet Node, +> échouerait faute de `package.json`, ou pire, installerait une chaîne de +> build inutile. Le Dockerfile fourni est explicite et reproductible. + +--- + +## Étape 4 — Domaines + +Dans **Configuration → General → Domains**, saisis : + +``` +https://www.courcellematthi.eu +``` + +Coolify génère alors automatiquement les étiquettes Traefik et demande le +certificat Let's Encrypt. + +### Redirection du domaine nu + +Ajoute `https://courcellematthi.eu` comme second domaine, puis active +**Redirect to primary domain** (ou l'option *www ↔ non-www* selon ta version +de Coolify). + +Ce point n'est pas cosmétique : le HTML déclare +``. Si le domaine +nu répond aussi en 200 au lieu de rediriger, les moteurs voient deux URL pour +le même contenu et diluent le référencement. + +**Si tu préfères le domaine nu**, il faut modifier trois endroits pour rester +cohérent : + +1. `index.html` — la balise `canonical`, les `og:url`, les `hreflang`, et les + URL absolues du JSON-LD +2. `sitemap.xml` — la balise `` +3. `robots.txt` — la ligne `Sitemap:` + +--- + +## Étape 5 — Déployer + +Bouton **Deploy**. Le build prend une trentaine de secondes : il n'y a rien à +compiler, seulement à copier. + +Suis les journaux dans l'onglet **Logs**. Une fin de build normale ressemble à : + +``` +naming to docker.io/library/xxxxx done +Container started +``` + +--- + +## Étape 6 — Vérifications post-déploiement + +```bash +# Le site répond +curl -I https://www.courcellematthi.eu + +# La sonde de santé +curl https://www.courcellematthi.eu/healthz # -> ok + +# Le domaine nu redirige (301 attendu, pas 200) +curl -I https://courcellematthi.eu + +# Les en-têtes de sécurité sont présents +curl -sI https://www.courcellematthi.eu | grep -iE "content-security|x-content-type|referrer|permissions" + +# La compression fonctionne +curl -sI -H "Accept-Encoding: gzip" https://www.courcellematthi.eu | grep -i encoding + +# Les fichiers de service sont servis +curl -s https://www.courcellematthi.eu/robots.txt | head -3 +curl -s https://www.courcellematthi.eu/llms.txt | head -3 +curl -sI https://www.courcellematthi.eu/.well-known/security.txt +``` + +--- + +## Les deux pannes classiques sur Coolify + +Ce sont les deux mêmes que tu as déjà rencontrées sur d'autres déploiements. + +### 1. Conteneur absent du réseau `coolify` + +**Symptôme** : Traefik renvoie 404, alors que le conteneur tourne et que ses +journaux sont vides. La requête ne l'atteint jamais. + +**Diagnostic** : + +```bash +docker inspect --format '{{json .NetworkSettings.Networks}}' | python3 -m json.tool +``` + +Si `coolify` n'apparaît pas dans la liste : + +```bash +docker network connect coolify +``` + +En mode Docker Compose, le fichier fourni déclare déjà le réseau. En mode +Dockerfile, Coolify le fait normalement seul — mais pas toujours après une +mise à jour de Coolify. + +### 2. Port du load balancer non déclaré + +**Symptôme** : `502 Bad Gateway`, ou `service not found` dans le tableau de +bord Traefik. + +Traefik ne devine le port que si le conteneur n'en expose qu'un seul, et cette +détection échoue régulièrement. Il faut donc le déclarer explicitement. + +**En mode Docker Compose**, l'étiquette est déjà présente : + +```yaml +- traefik.http.services.courcellematthi.loadbalancer.server.port=80 +``` + +**En mode Dockerfile**, va dans **Configuration → Advanced → Custom Labels** +et ajoute : + +``` +traefik.http.services..loadbalancer.server.port=80 +``` + +Récupère le nom généré dans les étiquettes existantes du conteneur : + +```bash +docker inspect --format '{{json .Config.Labels}}' | grep -o 'traefik[^"]*' +``` + +--- + +## HSTS — à traiter en dernier, et prudemment + +Le HSTS n'est **pas** émis par le conteneur. C'est un choix délibéré : le +conteneur est redéployé souvent, et un réglage irréversible n'a rien à faire +dans une couche jetable. + +C'est Traefik qui doit l'émettre, une fois le HTTPS confirmé fonctionnel. + +> **Pourquoi cette prudence.** `Strict-Transport-Security` avec +> `max-age=63072000` indique au navigateur de refuser toute connexion HTTP à ce +> domaine pendant deux ans. Si le certificat casse ensuite, le site devient +> inaccessible — et **tu ne peux rien faire côté serveur** pour l'annuler : la +> directive est mémorisée par le navigateur du visiteur. + +### Procédure recommandée + +**Phase 1 — validation.** Configure `max-age=300` (cinq minutes). En mode +Docker Compose, c'est déjà la valeur du fichier fourni. En mode Dockerfile, +ajoute dans **Custom Labels** : + +``` +traefik.http.middlewares.hsts-test.headers.stsSeconds=300 +traefik.http.middlewares.hsts-test.headers.forceSTSHeader=true +traefik.http.routers..middlewares=hsts-test +``` + +Vérifie sur plusieurs jours que le certificat se renouvelle bien. + +**Phase 2 — activation.** Passe `stsSeconds` à `63072000`. + +N'inscris le domaine sur la liste de préchargement HSTS (hstspreload.org) que +si tu es certain de ne jamais revenir en arrière : la radiation prend des mois. + +--- + +## Redéploiement + +### Automatique + +Dans **Configuration → General**, active **Auto Deploy**. Coolify crée un +webhook dans Gitea : chaque `git push` sur `main` déclenche un déploiement. + +C'est pertinent ici — un site statique sans test à faire tourner ne risque +rien à se redéployer automatiquement. + +### Manuel + +Bouton **Redeploy**. + +### Après une modification de contenu + +Pense à mettre à jour la date dans `sitemap.xml` : + +```xml +2026-08-05 +``` + +--- + +## Test local avant déploiement + +Si tu veux vérifier l'image sur ta machine : + +```bash +docker build -t courcellematthi . +docker run --rm -p 8080:80 courcellematthi +# puis http://localhost:8080 +``` + +Contrôles utiles : + +```bash +# Poids de l'image +docker images courcellematthi + +# Le dossier deploy/ ne doit PAS être dans l'image +docker run --rm courcellematthi ls /usr/share/nginx/html + +# La configuration Nginx est syntaxiquement correcte +docker run --rm courcellematthi nginx -t +``` + +--- + +## Une fois en ligne + +1. **Google Search Console** — ajoute la propriété et soumets + `https://www.courcellematthi.eu/sitemap.xml` +2. **Test des résultats enrichis** — vérifie le JSON-LD sur + `search.google.com/test/rich-results` +3. **Débogueur LinkedIn** — force le rafraîchissement de l'aperçu de partage + sur `linkedin.com/post-inspector` (LinkedIn met les aperçus en cache + longtemps : sans cette étape, un ancien aperçu peut persister des semaines) +4. **securityheaders.com** — la note attendue est A ou A+ +5. **PageSpeed Insights** — contrôle les Core Web Vitals + +--- + +## Si quelque chose ne va pas + +| Symptôme | Cause probable | +|---|---| +| `failed to read dockerfile: open Dockerfile: no such file or directory` avec `transferring dockerfile: 2B` | `Dockerfile` listé dans `.dockerignore` — voir ci-dessous | +| `failed to read dockerfile` sans la ligne `2B` | Fichier réellement absent du dépôt, ou mauvais **Dockerfile Location** dans Coolify | +| 404 Traefik, conteneur sain | Conteneur hors du réseau `coolify` | +| 502 Bad Gateway | Étiquette `loadbalancer.server.port` absente | +| Certificat non émis | DNS non propagé, ou proxy Cloudflare actif | +| Page blanche, CSS absent | Chemins `/assets/...` — vérifier que la racine servie est bien `/usr/share/nginx/html` | +| Polices non chargées | CSP trop stricte — `fonts.googleapis.com` et `fonts.gstatic.com` doivent être autorisés | +| Ancienne version affichée | Cache navigateur sur `index.html` — vérifier l'en-tête `Cache-Control: must-revalidate` | +| Boucle de redirection | Redirection HTTPS dupliquée : Traefik **et** Nginx. Le `deploy/nginx.conf` fourni n'en contient pas — vérifier qu'on n'a pas utilisé `nginx.conf.example` par erreur | + +### Le Dockerfile filtré par `.dockerignore` + +Journal caractéristique : + +``` +#1 transferring dockerfile: 2B done +ERROR: failed to solve: failed to read dockerfile: + open Dockerfile: no such file or directory +``` + +La ligne **`2B`** est le signal décisif : BuildKit a bien reçu un contexte, mais +le Dockerfile qu'il en extrait est vide. Le fichier existe donc dans le dépôt — +c'est `.dockerignore` qui l'a rendu invisible. + +BuildKit, utilisé par Coolify et par Docker depuis la version 23, applique les +exclusions **avant** de lire le Dockerfile. Le builder classique tolérait ce +cas ; BuildKit non. + +Vérification : + +```bash +grep -nE '^\s*(Dockerfile|\.dockerignore)\s*$' .dockerignore +``` + +Si l'une des deux lignes ressort, supprime-la et repousse. Ces fichiers n'ont +pas besoin d'y figurer : le `RUN rm` du Dockerfile les retire déjà de l'image +finale, donc ils ne sont jamais servis au public. + +### Vérifier que le fichier est bien dans le dépôt + +Si la ligne `2B` **n'apparaît pas**, le fichier est réellement absent : + +```bash +git ls-files | grep -E '^Dockerfile$' +``` + +Sans résultat, il n'a pas été commité : + +```bash +git add Dockerfile .dockerignore deploy/ +git commit -m "Ajout des fichiers de déploiement" +git push +``` + +Contrôle également, dans **Configuration → Build** de Coolify : + +- **Base Directory** : `/` +- **Dockerfile Location** : `/Dockerfile` + +Si le dépôt contient le site dans un sous-dossier, ces deux chemins doivent +être ajustés en conséquence. + +Journaux : + +```bash +docker logs -f # Conteneur du site +docker logs -f coolify-proxy # Traefik +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6c00022 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +# ============================================================================= +# Dockerfile — Site statique courcellematthi.eu +# ----------------------------------------------------------------------------- +# Image mono-étape : le site n'a aucune dépendance et aucune compilation. +# Une étape de build (Node, npm install…) n'apporterait ici que du temps +# de déploiement et de la surface d'attaque en plus. +# +# Image finale : environ 50 Mo, dont 48 Mo pour Nginx Alpine. +# ============================================================================= + +# Version épinglée plutôt que « latest » : un redéploiement six mois plus tard +# doit produire exactement la même image. « latest » rend les builds +# non reproductibles et peut casser la production sans qu'on ait rien changé. +FROM nginx:1.27-alpine + +# ----------------------------------------------------------------------------- +# MÉTADONNÉES (spécification OCI) +# Visibles via « docker inspect », utiles pour l'inventaire et le débogage. +# ----------------------------------------------------------------------------- +LABEL org.opencontainers.image.title="courcellematthi.eu" +LABEL org.opencontainers.image.description="Site personnel et CV de Matthieu Courcelle" +LABEL org.opencontainers.image.authors="Matthieu Courcelle " +LABEL org.opencontainers.image.url="https://www.courcellematthi.eu" +LABEL org.opencontainers.image.licenses="UNLICENSED" + +# ----------------------------------------------------------------------------- +# CONFIGURATION NGINX +# On remplace le fichier par défaut du site, pas nginx.conf global : la +# configuration de base de l'image (workers, types MIME, limites) est saine. +# ----------------------------------------------------------------------------- +RUN rm /etc/nginx/conf.d/default.conf +COPY deploy/nginx.conf /etc/nginx/conf.d/site.conf + +# ----------------------------------------------------------------------------- +# CONTENU DU SITE +# .dockerignore exclut le superflu (README, configs alternatives, .git). +# ----------------------------------------------------------------------------- +COPY . /usr/share/nginx/html/ + +# Retire du conteneur les fichiers qui n'ont rien à y faire : documentation, +# configurations serveur alternatives, sources de déploiement. Ils sont utiles +# dans le dépôt, pas dans l'image servie au public. +RUN rm -rf /usr/share/nginx/html/deploy \ + /usr/share/nginx/html/Dockerfile \ + /usr/share/nginx/html/.dockerignore \ + /usr/share/nginx/html/docker-compose.yaml \ + /usr/share/nginx/html/README.md \ + /usr/share/nginx/html/DEPLOIEMENT-COOLIFY.md \ + /usr/share/nginx/html/nginx.conf.example \ + /usr/share/nginx/html/.htaccess \ + /usr/share/nginx/html/verifier-depot.sh + +# ----------------------------------------------------------------------------- +# PERMISSIONS +# Lecture seule pour tous, aucun fichier exécutable : rien dans un site +# statique n'a besoin du bit d'exécution, sauf les répertoires (parcours). +# ----------------------------------------------------------------------------- +RUN chmod -R a=r,u+w /usr/share/nginx/html && \ + find /usr/share/nginx/html -type d -exec chmod a+x {} \; + +# ----------------------------------------------------------------------------- +# RÉSEAU +# EXPOSE est purement déclaratif : il documente le port et permet à Coolify +# de le détecter automatiquement. Il n'ouvre rien par lui-même. +# ----------------------------------------------------------------------------- +EXPOSE 80 + +# ----------------------------------------------------------------------------- +# SONDE DE SANTÉ +# Coolify affiche l'état du conteneur et peut le redémarrer s'il ne répond +# plus. wget est présent dans l'image Alpine, inutile d'installer curl. +# ----------------------------------------------------------------------------- +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget --quiet --tries=1 --spider http://localhost/healthz || exit 1 + +# L'image officielle définit déjà le bon CMD ; on le réécrit pour rendre +# explicite le fonctionnement au premier plan (daemon off), indispensable +# pour que Docker suive correctement le cycle de vie du processus. +CMD ["nginx", "-g", "daemon off;"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae75f86 --- /dev/null +++ b/README.md @@ -0,0 +1,343 @@ +# courcellematthi.eu + +Site personnel et CV en ligne de Matthieu Courcelle. + +Statique, sans dépendance, sans étape de compilation. Aucun framework, aucun +gestionnaire de paquets, aucun outil de build : les fichiers déposés sur le +serveur sont exactement ceux qui sont servis au navigateur. + +--- + +## Arborescence + +``` +. +├── index.html Document unique, sections ancrées +├── favicon.ico Repli multi-résolutions (16/32/48) +├── favicon.svg Icône vectorielle — monogramme MC +├── robots.txt Directives d'exploration, y compris robots IA +├── sitemap.xml Plan du site +├── site.webmanifest Manifeste d'application web +├── llms.txt Résumé structuré pour agents conversationnels +├── humans.txt Crédits (convention humanstxt.org) +├── .htaccess Configuration Apache (mutualisé) +├── nginx.conf.example Configuration Nginx (serveur dédié, hors conteneur) +├── Dockerfile Image du conteneur +├── docker-compose.yaml Déploiement Coolify (mode Compose) +├── .dockerignore Exclusions du contexte de build +├── DEPLOIEMENT-COOLIFY.md Guide de mise en ligne pas à pas +├── deploy/ +│ └── nginx.conf Configuration Nginx DANS le conteneur +├── .well-known/ +│ └── security.txt Contact de divulgation (RFC 9116) +└── assets/ + ├── css/ + │ ├── tokens.css Variables + ordre de cascade + │ ├── base.css Reset et éléments HTML natifs + │ ├── layout.css Conteneurs, en-tête, navigation, grilles + │ ├── components.css Blocs réutilisables (BEM) + │ ├── utilities.css Classes utilitaires + │ └── print.css Feuille d'impression (media="print") + ├── js/ + │ ├── main.js Point d'entrée (module ES) + │ └── modules/ + │ ├── navigation.js Menu mobile, piège de focus + │ ├── scroll-spy.js Section active (IntersectionObserver) + │ ├── back-to-top.js Bouton de remontée + │ └── copy-email.js Copie de l'adresse e-mail + └── img/ + ├── og-image.png Bannière de partage 1200×630 + ├── apple-touch-icon.png Écran d'accueil iOS + ├── icon-192.png PWA + ├── icon-512.png PWA + ├── icon-maskable-512.png PWA, zone sûre Android + ├── favicon-16/32/48.png Replis + └── safari-pinned-tab.svg Onglet épinglé Safari +``` + +--- + +## Architecture CSS + +### Couches en cascade + +L'ordre de priorité est déclaré une seule fois, en tête de `tokens.css` : + +```css +@layer tokens, base, layout, components, utilities; +``` + +Conséquence : une règle de `utilities` l'emporte sur une règle de `components` +même si son sélecteur est moins spécifique, et l'ordre des balises `` +dans le `` n'a aucune incidence. C'est ce qui permet de se passer +entièrement de `!important` et des sélecteurs à rallonge écrits uniquement +pour gagner un duel de spécificité. + +### Pas de `@import` + +Les feuilles sont liées séparément dans le ``. Un `@import` obligerait +le navigateur à télécharger et analyser le fichier parent avant de découvrir +l'existence de l'enfant : les requêtes s'enchaînent en série au lieu de +partir en parallèle. Sur cinq fichiers, l'écart est mesurable. + +### Jetons de conception + +Toute couleur, taille, espacement ou durée est déclarée dans `tokens.css`. +Aucune valeur littérale n'apparaît ailleurs. Changer l'identité visuelle du +site ne demande donc la modification que d'un seul fichier. + +Le fichier ne contient aucun jeton inutilisé : 56 variables définies, +56 utilisées. + +### Propriétés logiques + +`inline-size` plutôt que `width`, `padding-block` plutôt que +`padding-top/bottom`, `inset-inline-start` plutôt que `left`. Le site est +en français, mais ces propriétés sont aujourd'hui le standard et rendent +une éventuelle version en écriture de droite à gauche triviale. + +### Typographie fluide + +`clamp()` sur toutes les tailles de titre. Le terme central mêle `rem` et +`vw` : le `rem` garantit que le zoom navigateur et la taille de police +système continuent d'agir (une valeur en `vw` seule casserait +l'accessibilité), le `vw` apporte la fluidité entre les points de rupture. + +Résultat : une seule media query dans tout le projet, pour la bascule du +menu en mode mobile. + +--- + +## Architecture JavaScript + +### Amélioration progressive + +Le site est intégralement lisible, navigable et imprimable sans JavaScript. +Aucun contenu n'est injecté par script — le HTML servi est complet. C'est ce +qui garantit l'indexation par les moteurs et la lecture par les agents +conversationnels, dont beaucoup n'exécutent pas JavaScript. + +Les quatre modules n'ajoutent que du confort : repli du menu sur petit écran, +surbrillance de la section lue, bouton de remontée, copie de l'adresse. + +### Modules ES natifs + +` + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+ profil + + +

Matthieu Courcelle

+ +

+ Professeur d'informatique & développeur d'applications de gestion +

+ + + + +
+ + + Me contacter + + + + +
+
+ + +
+

+ Enseigner, transmettre, construire des outils qui servent +

+ +

+ Professeur d'informatique depuis 2018, j'enseigne aujourd'hui à la fois dans le + secondaire technique (IPES Ath) et dans le supérieur pour adultes à l'Institut + Gabrielle Petit — développement d'applications, administration réseau, sécurisation + d'infrastructures. La transmission est au cœur de ce que je fais : structurer une + matière complexe, la rendre accessible, accompagner un public en formation jusqu'à + l'autonomie. +

+ +

+ En parallèle des cours, j'ai développé des outils concrets pour les + établissements où j'interviens — une application de gestion d'événements + scolaires, des systèmes d'aide à l'administration — et j'ai construit, en autodidacte, + une vraie maîtrise de l'intelligence artificielle appliquée : skills Claude et + assistants Gemini pour aider mes collègues à préparer cours et évaluations, pour + accompagner des directions d'école sur l'administratif, la communication avec les + familles ou le RGPD + scolaire. Je gère aussi ma propre infrastructure serveur (Docker, déploiement, + monitoring) pour héberger ces outils. +

+ +

+ Depuis janvier 2026, je développe également en indépendant complémentaire + (underscorelab) des outils de gestion pour des + ASBL, notamment dans le secteur + du handicap. +

+ +

+ Cette double compétence — pédagogue et développeur formé à l'IA appliquée — ouvre + plusieurs directions : continuer à enseigner et former, accompagner des équipes dans + l'intégration d'outils IA, ou concevoir des solutions sur mesure pour des structures + qui en ont besoin. +

+
+ + +
+

Enseignement

+ +
+ +

Professeur d'informatique — Enseignement supérieur pour adultes

+

Institut Gabrielle Petit (ex-IPEPS Tournai-Leuze)

+ +
+ Développement d'applications orientées gestion — Projet SGBD (Bac 3) +

+ Conception et développement d'un système de gestion de données complet — de la + modélisation (MCD/MLD, normalisation) à une application 3-tiers connectée en base, + intégrant transactions ACID, gestion d'erreurs, déploiement Cloud/DevOps + (Git, CI/CD) et soutenance technique devant jury. +

+
+ +
+ Administration et sécurisation des réseaux et services (Bac 3) +

+ Formation à l'administration autonome d'une infrastructure réseau complète : + services (DHCP, DNS, Web, messagerie), virtualisation, adressage IPv4/IPv6, + et sécurisation avancée (pare-feu, DMZ, IDS/IPS, Active Directory/LDAP, + VLAN, GPO, VPN). +

+
+ +
+ Base d'administration réseaux (Bac 1) +

+ Introduction aux modèles OSI/TCP-IP, à l'adressage IP et au subnetting, au câblage + et aux équipements actifs (switch, routeur), avec diagnostic autonome + (ping, traceroute, sniffers) et mise en œuvre d'un petit réseau interconnecté. +

+
+ +
+ Initiation à l'informatique (Complément CESS) +

+ Apprentissage des bases de l'outil informatique (Windows, navigation, bureautique) + pour un public adulte en reprise d'études. +

+
+
+ +
+ +

Professeur d'informatique — Secondaire technique

+

IPES Ath · Technique de transition & de qualification (DI et DS)

+ +
+ Traitements de Problèmes Techniques +

+ Apprentissage méthodique du diagnostic et de la résolution de pannes matérielles + et logicielles, pour développer une démarche rigoureuse de dépannage informatique. +

+
+ +
+ Laboratoire Informatique +

+ Mise en pratique des notions théoriques via des ateliers concrets de montage, + configuration et manipulation de systèmes informatiques. +

+
+ +
+ Informatique Appliquée +

+ Application des concepts informatiques à des cas pratiques, faisant le lien entre + théorie algorithmique/logicielle et mise en œuvre réelle. +

+
+ +
+ Laboratoire de Maintenance et de Télématique +

+ Travaux pratiques sur la maintenance des postes de travail et les bases des réseaux + et télécommunications. +

+
+ +
+ Informatique de Gestion (Office 365) +

+ Maîtrise des outils bureautiques et collaboratifs de gestion appliqués aux besoins + administratifs et professionnels. +

+
+
+ +
+ +

Professeur d'informatique de gestion

+

IPES Tournai · Technique de qualification Gestion (DI)

+

Informatique de gestion appliquée à la suite Office 365.

+
+ +
+ +

Formateur en entreprise

+

ORDITECH SA

+

+ Formation professionnelle d'adultes sur les environnements Office 365 et les + systèmes d'exploitation. +

+
+
+ + +
+

Coordination pédagogique

+ +
+ +

Développement d'outils de gestion interne

+

École des Frères — Tournai

+ +
    +
  • Application de gestion des remédiations : suivi des élèves, planification et affectation des séances.
  • +
  • Application de gestion des activités complémentaires.
  • +
  • Système de valve électronique pour la diffusion d'annonces aux élèves.
  • +
+
+
+ + +
+

Projets

+ +
+
+

Festifun

+ Institut du Sacré-Cœur Mouscron +
+

+ Application web progressive de gestion d'événement scolaire, conçue et déployée + pour l'établissement. +

+
    +
  • PWA
  • JavaScript
  • Node.js
  • +
+
+ +
+
+

Outils IA d'aide à la direction

+ Institut du Sacré-Cœur Mouscron +
+

+ Assistants IA sur mesure destinés aux équipes de direction : maîtrise des outils + informatiques administratifs, communication avec les familles, application du RGPD + en contexte scolaire. +

+
    +
  • Claude
  • Gemini
  • NotebookLM
  • +
+
+ +
+
+

Bibliothèque de skills Claude & Gems Gemini

+ Pédagogie · Administration +
+

+ Assistants IA spécialisés développés pour l'équipe éducative : rédaction de cours, + création d'exercices et d'exemples, conception d'évaluations et aide à la correction. +

+

+ Déclinaison à destination des directions d'établissement sur les volets + administratifs, la communication institutionnelle et la conformité RGPD. +

+
+ +
+
+

underscorelab

+ Indépendant complémentaire · depuis janvier 2026 +
+

+ Développement d'outils de gestion sur mesure pour le secteur associatif, + principalement des ASBL d'aide aux personnes en situation de handicap. +

+
    +
  • Node.js
  • PHP
  • Python
  • PostgreSQL
  • Vue
  • +
+
+ +
+
+

Écosystème d'agents automatisés

+ + en cours de développement +
+

+ Architecture d'agents IA orchestrés via n8n sur infrastructure auto-hébergée, + pour l'automatisation de flux de travail administratifs et pédagogiques. +

+
    +
  • n8n
  • Docker
  • API
  • +
+
+
+ + +
+

Compétences

+ +
+ +
+

+ + Développement +

+
    +
  • PHP, Python, JavaScript (Node.js)
  • +
  • React, Vue
  • +
  • PostgreSQL, SQL, modélisation MCD/MLD
  • +
  • Applications de gestion, PWA
  • +
  • VBA / Excel avancé
  • +
+
+ +
+

+ + Systèmes & réseaux +

+
    +
  • Administration et sécurisation réseau
  • +
  • Active Directory, LDAP, GPO
  • +
  • Pare-feu, DMZ, VPN, VLAN
  • +
  • Windows, Linux, macOS
  • +
  • Montage, configuration, audit
  • +
+
+ +
+

+ + Infrastructure & déploiement +

+
    +
  • Docker, conteneurisation
  • +
  • Reverse proxy, SSL/TLS
  • +
  • Déploiement continu (Git, CI/CD)
  • +
  • Monitoring et sauvegarde
  • +
  • Auto-hébergement de services
  • +
+
+ +
+

+ + Intelligence artificielle appliquée +

+
    +
  • Conception de skills Claude
  • +
  • Création de Gems Gemini
  • +
  • Structuration documentaire (NotebookLM)
  • +
  • Automatisation de flux de travail
  • +
  • Accompagnement et formation d'équipes
  • +
+
+ +
+

+ + Pédagogie +

+
    +
  • Enseignement secondaire technique
  • +
  • Enseignement supérieur pour adultes
  • +
  • Conception de séquences et d'évaluations
  • +
  • Vulgarisation technique
  • +
  • Accompagnement en reprise d'études
  • +
+
+ +
+

+ + Langues +

+
    +
  • Français — langue maternelle
  • +
  • Anglais — lecture technique courante
  • +
+
+ +
+
+ + +
+

Formation

+ +
+
+ +

Certificat d'Aptitudes Pédagogiques

+

Institut de Promotion Sociale Saint Henri

+
+ +
+ +

Bachelier en informatique de gestion

+

IEPSCF

+
+
+
+ + +
+

Contact

+ +
+

+ Une question, un projet, une opportunité d'enseignement ou de collaboration — + écrivez-moi, je réponds. +

+ +
+ + + m@tthieu.be + + + + +
+
+ + +
+
+ +
+
+ + + + + + + + + diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000..5b266ed --- /dev/null +++ b/llms.txt @@ -0,0 +1,85 @@ +# Matthieu Courcelle + +> Professeur d'informatique en enseignement secondaire technique et en +> enseignement supérieur pour adultes à Tournai (Belgique, Fédération +> Wallonie-Bruxelles). Développeur d'applications de gestion et praticien +> de l'intelligence artificielle appliquée à la pédagogie et à +> l'administration scolaire. + +Ce fichier suit la proposition llms.txt : il fournit aux agents +conversationnels un résumé structuré et fiable du site, en texte brut, +sans avoir à interpréter la mise en page HTML. + +Contact : m@tthieu.be +Site : https://www.courcellematthi.eu/ +Localisation : région de Tournai, Hainaut, Belgique +Langues : français (maternelle), anglais (lecture technique) + +## Fonctions d'enseignement actuelles + +- **Institut Gabrielle Petit** (anciennement IPEPS Tournai-Leuze), depuis 2023 — + enseignement supérieur pour adultes. Cours : Développement d'applications + orientées gestion / Projet SGBD (Bac 3), Administration et sécurisation des + réseaux et services (Bac 3), Base d'administration réseaux (Bac 1), + Initiation à l'informatique (Complément CESS). + +- **IPES Ath**, depuis 2020 — enseignement secondaire technique de transition + et de qualification. Cours : Traitements de Problèmes Techniques, Laboratoire + Informatique, Informatique Appliquée, Laboratoire de Maintenance et de + Télématique, Informatique de Gestion. + +## Fonctions antérieures + +- **IPES Tournai**, 2020-2022 — professeur d'informatique de gestion. +- **ORDITECH SA**, 2018-2020 — formateur en entreprise (Office 365, systèmes + d'exploitation). + +## Coordination pédagogique + +- **École des Frères, Tournai** — développement d'outils de gestion interne dans + le cadre d'heures de coordination : gestion des remédiations, gestion des + activités complémentaires, valve électronique d'annonces. + +## Projets + +- **Festifun** — application web progressive de gestion d'événement scolaire + (Institut du Sacré-Cœur Mouscron). +- **Outils IA d'aide à la direction** — assistants sur mesure pour équipes de + direction : outils administratifs, communication avec les familles, RGPD + scolaire (Institut du Sacré-Cœur Mouscron). +- **Bibliothèque de skills Claude et Gems Gemini** — assistants IA pour la + rédaction de cours, d'exercices, d'évaluations et l'aide à la correction ; + déclinaison à destination des directions d'établissement. +- **underscorelab** — activité d'indépendant complémentaire lancée en janvier + 2026 : outils de gestion sur mesure pour ASBL, principalement dans le secteur + de l'aide aux personnes en situation de handicap. +- **Écosystème d'agents automatisés** — en cours de développement : agents IA + orchestrés via n8n sur infrastructure auto-hébergée. + +## Compétences techniques + +- Développement : PHP, Python, JavaScript, Node.js, React, Vue, PostgreSQL, + SQL, modélisation MCD/MLD, PWA, VBA/Excel. +- Systèmes et réseaux : administration et sécurisation réseau, Active Directory, + LDAP, GPO, pare-feu, DMZ, VPN, VLAN, Windows, Linux, macOS. +- Infrastructure : Docker, reverse proxy, SSL/TLS, Git, CI/CD, monitoring, + sauvegarde, auto-hébergement. +- Intelligence artificielle appliquée : conception de skills Claude, création de + Gems Gemini, structuration documentaire avec NotebookLM, automatisation de + flux de travail, formation d'équipes. +- Pédagogie : enseignement secondaire technique et supérieur pour adultes, + conception de séquences et d'évaluations, vulgarisation technique. + +## Formation + +- Certificat d'Aptitudes Pédagogiques — Institut de Promotion Sociale + Saint Henri (2021-2024). +- Bachelier en informatique de gestion — IEPSCF (2008-2011). + +## Notes à l'attention des agents + +- Le nom de l'établissement a changé : « IPEPS Tournai-Leuze » et « Institut + Gabrielle Petit » désignent la même structure. +- « underscorelab » s'écrit en minuscules, en un seul mot. +- Ne pas confondre le poste d'enseignant (fonction officielle) avec les projets + développés pour des établissements tiers (réalisations, sans titre officiel). diff --git a/nginx.conf.example b/nginx.conf.example new file mode 100644 index 0000000..87942d7 --- /dev/null +++ b/nginx.conf.example @@ -0,0 +1,212 @@ +# ============================================================================= +# nginx.conf.example — Configuration serveur pour courcellematthi.eu +# ----------------------------------------------------------------------------- +# À adapter puis inclure dans la configuration Nginx. +# Couvre : redirections canoniques, en-têtes de sécurité, cache, compression. +# +# Sans ces réglages, un site statique parfaitement écrit reste noté médiocre +# par les outils d'audit (Lighthouse, Mozilla Observatory, securityheaders.com) +# et sert des fichiers non compressés. +# ============================================================================= + +# ----------------------------------------------------------------------------- +# REDIRECTION HTTP -> HTTPS +# Le certificat est supposé géré par Certbot ou acme.sh. +# ----------------------------------------------------------------------------- +server { + listen 80; + listen [::]:80; + server_name courcellematthi.eu www.courcellematthi.eu; + + # Laisse passer la validation ACME avant de rediriger + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://www.courcellematthi.eu$request_uri; + } +} + +# ----------------------------------------------------------------------------- +# REDIRECTION DU DOMAINE NU VERS WWW +# Une seule URL canonique évite la dilution du référencement et le contenu +# dupliqué. Le choix www / sans-www est indifférent, la constance ne l'est pas. +# La valeur retenue ici doit correspondre à dans le HTML. +# ----------------------------------------------------------------------------- +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name courcellematthi.eu; + + ssl_certificate /etc/letsencrypt/live/courcellematthi.eu/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/courcellematthi.eu/privkey.pem; + + return 301 https://www.courcellematthi.eu$request_uri; +} + +# ----------------------------------------------------------------------------- +# SERVEUR PRINCIPAL +# ----------------------------------------------------------------------------- +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name www.courcellematthi.eu; + + root /var/www/courcellematthi.eu; + index index.html; + charset utf-8; + + # ------------------------------------------------------------------------- + # TLS + # TLS 1.2 minimum : les versions antérieures sont obsolètes et vulnérables. + # ------------------------------------------------------------------------- + ssl_certificate /etc/letsencrypt/live/courcellematthi.eu/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/courcellematthi.eu/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; # Laisse le client choisir : recommandé + # depuis TLS 1.3 + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + ssl_session_tickets off; + + # Agrafage OCSP : le serveur fournit lui-même la preuve de non-révocation + # du certificat, ce qui évite au navigateur une requête vers l'autorité. + ssl_stapling on; + ssl_stapling_verify on; + resolver 1.1.1.1 8.8.8.8 valid=300s; + + # ========================================================================= + # EN-TÊTES DE SÉCURITÉ + # ========================================================================= + + # HSTS — force HTTPS pendant deux ans, y compris sur les sous-domaines. + # ATTENTION : ne l'activer qu'une fois le HTTPS confirmé fonctionnel + # partout. Un HSTS émis par erreur rend le site inaccessible jusqu'à + # expiration, sans possibilité d'annulation côté serveur. + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; + + # CSP — liste blanche des sources autorisées. Défense principale contre + # l'injection de scripts. + # default-src 'self' : tout provient du domaine par défaut + # style-src ... 'unsafe-inline' : requis par le CSS injecté de Google Fonts + # font-src fonts.gstatic.com : fichiers de police + # script-src 'self' 'unsafe-inline' : 'unsafe-inline' n'est nécessaire + # que pour l'attribut onclick du bouton d'impression. Le retirer si + # ce bouton est déplacé dans un module. + # frame-ancestors 'none' : interdit l'inclusion en iframe (clickjacking) + # base-uri 'self' : empêche la réécriture des URL relatives + # form-action 'self' : limite les cibles de soumission + add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; upgrade-insecure-requests" always; + + # Empêche le navigateur de deviner le type MIME d'un fichier, technique + # utilisée pour faire exécuter une image comme du script. + add_header X-Content-Type-Options "nosniff" always; + + # Ne transmet le référent complet qu'en navigation interne ; vers + # l'extérieur, seul le domaine est envoyé. Compromis usuel entre mesure + # d'audience et confidentialité. + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Désactive des API sensibles jamais utilisées par ce site. + add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), interest-cohort=()" always; + + # Redondant avec frame-ancestors, conservé pour les navigateurs anciens. + add_header X-Frame-Options "DENY" always; + + # ========================================================================= + # COMPRESSION + # Gzip partout ; Brotli en plus si le module est disponible (taux + # supérieur de 15 à 20 % sur du texte). + # ========================================================================= + gzip on; + gzip_vary on; # Ajoute « Vary: Accept-Encoding » : sans lui, + # un proxy peut servir la version compressée + # à un client qui ne la supporte pas. + gzip_comp_level 6; # Au-delà, le gain marginal ne compense plus + # le coût CPU. + gzip_min_length 256; # En dessous, l'en-tête coûte plus que le gain. + gzip_types + text/plain + text/css + text/xml + text/javascript + application/javascript + application/json + application/manifest+json + application/xml + image/svg+xml; + + # Décommenter si le module ngx_brotli est compilé + # brotli on; + # brotli_comp_level 6; + # brotli_types text/plain text/css application/javascript application/json image/svg+xml; + + # ========================================================================= + # CACHE + # Stratégie en deux niveaux : + # - Les ressources d'assets changent rarement : cache long. + # - Le HTML change à chaque mise à jour de contenu : jamais mis en cache + # durablement, sinon les visiteurs voient une version périmée. + # + # NOTE : « immutable » suppose des noms de fichiers versionnés + # (style.a1b2c3.css). Sans versionnement, garder une durée modérée + # pour pouvoir publier un correctif. + # ========================================================================= + + location ~* \.(css|js|woff2?|ttf|otf|eot)$ { + expires 30d; + add_header Cache-Control "public, max-age=2592000"; + access_log off; + } + + location ~* \.(png|jpe?g|gif|svg|webp|avif|ico)$ { + expires 90d; + add_header Cache-Control "public, max-age=7776000"; + access_log off; + } + + location = /index.html { + add_header Cache-Control "public, max-age=0, must-revalidate"; + } + + location ~* \.(txt|xml|webmanifest)$ { + expires 1d; + add_header Cache-Control "public, max-age=86400"; + } + + # ========================================================================= + # TYPES MIME PARTICULIERS + # ========================================================================= + location = /site.webmanifest { + types { } default_type application/manifest+json; + } + + location = /llms.txt { + types { } default_type text/plain; + charset utf-8; + } + + location = /.well-known/security.txt { + types { } default_type text/plain; + charset utf-8; + } + + # ========================================================================= + # ROUTAGE + # ========================================================================= + location / { + try_files $uri $uri/ /index.html; + } + + # Refuse l'accès aux fichiers cachés, sauf .well-known (RFC 8615) + location ~ /\.(?!well-known) { + deny all; + return 404; + } + + # Page d'erreur : renvoyer vers l'accueil plutôt qu'une page vide + error_page 404 /index.html; +} diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..bcdc673 --- /dev/null +++ b/robots.txt @@ -0,0 +1,50 @@ +# ============================================================================= +# robots.txt — courcellematthi.eu +# ============================================================================= +# Site personnel entièrement public : rien à masquer, tout à indexer. +# Un robots.txt permissif mais explicite vaut mieux qu'un fichier absent : +# il évite les 404 répétées dans les journaux et déclare le sitemap. +# ============================================================================= + +# --- Robots d'indexation classiques ----------------------------------------- +User-agent: * +Allow: / + +# --- Robots des moteurs de réponse et agents IA ------------------------------ +# Autorisés explicitement : sur un CV, être cité par un agent conversationnel +# lors d'une recherche de profil est un bénéfice, pas une fuite. +# Retirer ces blocs (ou les passer en Disallow) si l'arbitrage change. +User-agent: GPTBot +Allow: / + +User-agent: OAI-SearchBot +Allow: / + +User-agent: ChatGPT-User +Allow: / + +User-agent: ClaudeBot +Allow: / + +User-agent: Claude-Web +Allow: / + +User-agent: PerplexityBot +Allow: / + +User-agent: Google-Extended +Allow: / + +User-agent: Applebot-Extended +Allow: / + +# --- Aspirateurs de site ---------------------------------------------------- +# Bloqués : consommation de bande passante sans contrepartie. +User-agent: HTTrack +Disallow: / + +User-agent: WebZIP +Disallow: / + +# --- Plan du site ----------------------------------------------------------- +Sitemap: https://www.courcellematthi.eu/sitemap.xml diff --git a/site.webmanifest b/site.webmanifest new file mode 100644 index 0000000..41353b8 --- /dev/null +++ b/site.webmanifest @@ -0,0 +1,40 @@ +{ + "$comment": "Manifeste d'application web. Permet l'ajout à l'écran d'accueil sur mobile et fournit les icônes aux systèmes d'exploitation. display=browser : ce site est un document, pas une application — le masquer dans une coque sans barre d'adresse priverait l'utilisateur du partage et de la navigation.", + "name": "Matthieu Courcelle — Professeur d'informatique et développeur", + "short_name": "M. Courcelle", + "description": "Professeur d'informatique en secondaire technique et en supérieur pour adultes, développeur d'applications de gestion.", + "lang": "fr-BE", + "dir": "ltr", + "start_url": "/", + "scope": "/", + "display": "browser", + "background_color": "#f7f8f8", + "theme_color": "#f7f8f8", + "categories": ["education", "business"], + "icons": [ + { + "src": "/favicon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any" + }, + { + "src": "/assets/img/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/assets/img/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/assets/img/icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..3d56c42 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,17 @@ + + + + + https://www.courcellematthi.eu/ + 2026-08-04 + monthly + 1.0 + + + + diff --git a/verifier-depot.sh b/verifier-depot.sh new file mode 100644 index 0000000..e6195cb --- /dev/null +++ b/verifier-depot.sh @@ -0,0 +1,96 @@ +#!/usr/bin/env bash +# ============================================================================= +# verifier-depot.sh — Contrôle avant push +# ----------------------------------------------------------------------------- +# Vérifie que tous les fichiers nécessaires au build Docker sont bien suivis +# par Git. Les fichiers cachés (.dockerignore, .well-known/) sont ceux qui +# disparaissent le plus facilement lors d'une copie manuelle ou d'une +# décompression. +# +# Usage : bash verifier-depot.sh +# ============================================================================= + +set -u + +REQUIS=( + # --- Build --- + "Dockerfile" + ".dockerignore" + "deploy/nginx.conf" + # --- Site --- + "index.html" + "favicon.svg" + "favicon.ico" + "robots.txt" + "sitemap.xml" + "llms.txt" + "humans.txt" + "site.webmanifest" + ".well-known/security.txt" + # --- Styles --- + "assets/css/tokens.css" + "assets/css/base.css" + "assets/css/layout.css" + "assets/css/components.css" + "assets/css/utilities.css" + "assets/css/print.css" + # --- Scripts --- + "assets/js/main.js" + "assets/js/modules/navigation.js" + "assets/js/modules/scroll-spy.js" + "assets/js/modules/back-to-top.js" + "assets/js/modules/copy-email.js" + # --- Images --- + "assets/img/og-image.png" + "assets/img/apple-touch-icon.png" + "assets/img/icon-192.png" + "assets/img/icon-512.png" + "assets/img/icon-maskable-512.png" + "assets/img/safari-pinned-tab.svg" + "assets/img/favicon-16x16.png" + "assets/img/favicon-32x32.png" + "assets/img/favicon-48x48.png" +) + +echo "== Fichiers suivis par Git ==" +manquants=0 +for f in "${REQUIS[@]}"; do + if git ls-files --error-unmatch "$f" >/dev/null 2>&1; then + printf " OK %s\n" "$f" + else + printf " MANQUANT %s\n" "$f" + manquants=$((manquants + 1)) + fi +done + +echo +echo "== Pièges connus ==" + +# Le Dockerfile ne doit jamais être exclu : BuildKit applique .dockerignore +# AVANT de lire le Dockerfile, contrairement au builder classique. +if grep -qE '^[[:space:]]*(Dockerfile|\.dockerignore)[[:space:]]*$' .dockerignore 2>/dev/null; then + echo " ERREUR .dockerignore exclut Dockerfile ou .dockerignore" + echo " -> le build echouera avec « transferring dockerfile: 2B »" + manquants=$((manquants + 1)) +else + echo " OK .dockerignore n'exclut pas le Dockerfile" +fi + +# deploy/ doit rester dans le contexte : le COPY du Dockerfile en depend. +if grep -qE '^[[:space:]]*deploy' .dockerignore 2>/dev/null; then + echo " ERREUR .dockerignore exclut deploy/" + echo " -> COPY deploy/nginx.conf echouera" + manquants=$((manquants + 1)) +else + echo " OK deploy/ reste dans le contexte de build" +fi + +echo +if [ "$manquants" -eq 0 ]; then + echo "Tout est en ordre. Le push peut etre effectue." + exit 0 +else + echo "$manquants probleme(s). Corriger avant de pousser :" + echo " git add -A && git commit -m 'Ajout des fichiers manquants' && git push" + exit 1 +fi