# [COURS] EXERCICES CORRIGÉS APACHE - ULTRA DÉTAILLÉS

## [LIVRE] INTRODUCTION

Ce document contient **5 exercices pratiques corrigés** sur Apache HTTP Server, allant du niveau débutant au niveau avancé. Chaque exercice est conçu pour :

- **Renforcer** tes compétences pratiques
- **Consolider** les concepts théoriques
- **Simuler** des situations réelles en entreprise
- **Te préparer** à des projets professionnels

**Niveau de progression :**
- [VERT] Exercice 1 : Débutant
- [JAUNE] Exercice 2 : Intermédiaire
- [JAUNE] Exercice 3 : Intermédiaire
- [ROUGE] Exercice 4 : Avancé
- [ROUGE] Exercice 5 : Expert

**Chaque exercice contient :**
- [OK] Énoncé détaillé avec contexte
- [OK] Prérequis et objectifs pédagogiques
- [OK] Solution complète étape par étape
- [OK] Code commenté ligne par ligne
- [OK] Tests de validation
- [OK] Erreurs courantes et comment les éviter
- [OK] Points clés à retenir
- [OK] Pour aller plus loin

**Conseils avant de commencer :**
1. Lis l'énoncé en entier avant de commencer
2. Essaie de résoudre par toi-même avant de regarder la solution
3. Tape chaque commande manuellement (pas de copier-coller aveugle)
4. Teste à chaque étape pour vérifier que ça fonctionne
5. Prends des notes sur ce que tu apprends

**Bon courage ! [RAPIDE]**

---

---

# [VERT] EXERCICE 1 : SITE PORTFOLIO MULTI-PAGES

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu es développeur web junior dans une agence digitale. Un client te demande de créer son **site portfolio personnel** pour présenter ses projets et compétences.

### Cahier des charges

Le client souhaite :
- **5 pages** : Accueil, À propos, Portfolio, Services, Contact
- Un **menu de navigation** fonctionnel sur toutes les pages
- Un **design moderne** et responsive
- Une **galerie de projets** avec images
- Un **formulaire de contact** (HTML uniquement pour cet exercice)
- Le site doit être **hébergé sur Apache**

### Contraintes techniques

- Serveur : Apache 2.4
- Pas de framework (HTML/CSS pur)
- Compatible tous navigateurs modernes
- Temps estimé : 2-3 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Créer une structure de site multi-pages
- [OK] Configurer un Virtual Host Apache
- [OK] Organiser les fichiers d'un projet web
- [OK] Gérer les permissions de fichiers
- [OK] Créer une navigation inter-pages
- [OK] Tester un site en local

---

## [DOCS] PRÉREQUIS

- Apache installé et fonctionnel
- Connaissances HTML/CSS de base
- Accès root ou sudo sur le serveur
- Éditeur de texte (nano, vim, ou VS Code)

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Planification de la structure

**Avant de coder, planifions la structure du site.**

**Structure des dossiers :**

```
/var/www/portfolio/
├── index.html          <- Page d'accueil
├── about.html          <- À propos
├── portfolio.html      <- Galerie de projets
├── services.html       <- Services proposés
├── contact.html        <- Formulaire de contact
├── css/
│   └── style.css       <- Feuille de style
├── images/
│   ├── profile.jpg     <- Photo de profil
│   ├── project1.jpg    <- Image projet 1
│   ├── project2.jpg    <- Image projet 2
│   └── project3.jpg    <- Image projet 3
└── js/
    └── script.js       <- JavaScript (optionnel)
```

**Pourquoi cette structure ?**
- **Séparation des responsabilités** : HTML, CSS, JS, images dans des dossiers séparés
- **Maintenabilité** : Facile de trouver et modifier les fichiers
- **Scalabilité** : Possibilité d'ajouter facilement de nouvelles pages
- **Standard de l'industrie** : Structure reconnue et professionnelle

---

### ÉTAPE 2 : Création de la structure de dossiers

```bash
# Se connecter en SSH au serveur (si distant)
# Sinon, ouvrir un terminal local

# Créer le dossier principal du site
sudo mkdir -p /var/www/portfolio

# Créer les sous-dossiers
sudo mkdir -p /var/www/portfolio/css
sudo mkdir -p /var/www/portfolio/images
sudo mkdir -p /var/www/portfolio/js

# Vérifier la structure créée
tree /var/www/portfolio
```

**Explication de `mkdir -p` :**
- `mkdir` = Make Directory (créer un dossier)
- `-p` = Parents (crée les dossiers parents si nécessaires)
- Exemple : `mkdir -p a/b/c` crée `a`, puis `a/b`, puis `a/b/c`

**Si la commande `tree` n'existe pas :**

```bash
sudo apt install tree -y
```

**Ou utilise `ls` pour vérifier :**

```bash
ls -lR /var/www/portfolio
```

---

### ÉTAPE 3 : Créer la feuille de style CSS

**Pourquoi créer le CSS en premier ?**
- On va l'inclure dans toutes les pages HTML
- Évite de dupliquer le code CSS
- Plus facile de maintenir un style cohérent

```bash
sudo nano /var/www/portfolio/css/style.css
```

**Contenu (copie ceci) :**

```css
/* ═══════════════════════════════════════════════════════════════
   PORTFOLIO - FEUILLE DE STYLE PRINCIPALE
   ═══════════════════════════════════════════════════════════════
   Auteur: [Ton Nom]
   Date: Décembre 2024
   Description: Style moderne et responsive pour site portfolio
   ═══════════════════════════════════════════════════════════════ */

/* ───────────────────────────────────────────────────────────────
   1. RÉINITIALISATION ET STYLES GLOBAUX
   ─────────────────────────────────────────────────────────────── */

/* Reset basique : supprime les marges par défaut des navigateurs */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* box-sizing: border-box signifie :
   Largeur = contenu + padding + bordure
   Sinon, par défaut : Largeur = contenu seulement
   
   Exemple sans border-box :
   - width: 300px
   - padding: 20px
   - Largeur réelle = 340px (300 + 20*2)
   
   Avec border-box :
   - width: 300px
   - padding: 20px
   - Largeur réelle = 300px (le padding est inclus)
*/

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    background: #f4f4f4;
}

/* line-height: 1.6 signifie :
   Hauteur de ligne = 1.6 × taille de la police
   Exemple : police 16px -> hauteur de ligne = 25.6px
   Améliore la lisibilité du texte
*/

/* ───────────────────────────────────────────────────────────────
   2. BARRE DE NAVIGATION
   ─────────────────────────────────────────────────────────────── */

header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 1rem 0;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
    position: sticky;
    top: 0;
    z-index: 1000;
}

/* position: sticky :
   Le header reste en haut quand on scroll
   Meilleure UX : menu toujours accessible
   
   z-index: 1000 :
   Assure que le header passe AU-DESSUS des autres éléments
*/

nav {
    max-width: 1200px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 2rem;
}

/* display: flex :
   Layout flexbox (moderne et puissant)
   
   justify-content: space-between :
   Espace maximum entre les éléments
   Logo à gauche, menu à droite
   
   align-items: center :
   Centre verticalement les éléments
*/

nav .logo {
    font-size: 1.5rem;
    font-weight: bold;
    text-decoration: none;
    color: white;
}

nav ul {
    list-style: none;
    display: flex;
    gap: 2rem;
}

/* gap: 2rem :
   Espace de 2rem entre chaque élément de la liste
   Plus propre que des margin sur chaque élément
*/

nav ul li a {
    color: white;
    text-decoration: none;
    transition: all 0.3s ease;
    padding: 0.5rem 1rem;
    border-radius: 5px;
}

/* transition: all 0.3s ease :
   Animation fluide de 0.3 seconde sur tous les changements
   "ease" = commence lent, accélère, finit lent
*/

nav ul li a:hover {
    background: rgba(255,255,255,0.2);
    transform: translateY(-2px);
}

/* transform: translateY(-2px) :
   Déplace l'élément de 2px vers le HAUT
   Effet de "soulèvement" au survol
*/

nav ul li a.active {
    background: rgba(255,255,255,0.3);
    font-weight: bold;
}

/* .active :
   Classe pour indiquer la page courante
   À ajouter manuellement dans le HTML de chaque page
*/

/* ───────────────────────────────────────────────────────────────
   3. CONTENEUR PRINCIPAL
   ─────────────────────────────────────────────────────────────── */

.container {
    max-width: 1200px;
    margin: 2rem auto;
    padding: 0 2rem;
}

/* max-width: 1200px :
   Le contenu ne dépasse jamais 1200px de largeur
   Sur grand écran, centré avec des marges
   Sur petit écran, prend 100% de la largeur
   
   margin: 2rem auto :
   2rem en haut/bas
   "auto" en gauche/droite = centrage automatique
*/

/* ───────────────────────────────────────────────────────────────
   4. SECTIONS
   ─────────────────────────────────────────────────────────────── */

section {
    background: white;
    padding: 3rem;
    margin-bottom: 2rem;
    border-radius: 10px;
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

/* box-shadow: 0 5px 15px rgba(0,0,0,0.1) :
   Décomposition :
   - 0 = décalage horizontal (0 = centré)
   - 5px = décalage vertical (vers le bas)
   - 15px = flou (plus c'est grand, plus c'est diffus)
   - rgba(0,0,0,0.1) = noir à 10% d'opacité
   
   Résultat : ombre douce sous la section
*/

section h1 {
    color: #667eea;
    margin-bottom: 1rem;
    font-size: 2.5rem;
}

section h2 {
    color: #764ba2;
    margin-bottom: 1rem;
    font-size: 2rem;
}

section p {
    margin-bottom: 1rem;
    text-align: justify;
}

/* text-align: justify :
   Texte justifié (aligné à gauche ET à droite)
   Comme dans un livre
*/

/* ───────────────────────────────────────────────────────────────
   5. GRILLE DE PROJETS (PORTFOLIO)
   ─────────────────────────────────────────────────────────────── */

.projects-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
}

/* grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)) :
   C'est magique ! Décomposons :
   
   - display: grid = Layout en grille
   - repeat() = Répète un pattern
   - auto-fit = Nombre de colonnes automatique selon l'espace
   - minmax(300px, 1fr) = Chaque colonne fait :
     * Minimum 300px
     * Maximum 1 fraction de l'espace restant
   
   Résultat :
   - Écran large (1200px) = 3 colonnes (400px chacune)
   - Écran moyen (800px) = 2 colonnes (400px chacune)
   - Écran petit (600px) = 2 colonnes (300px chacune)
   - Mobile (350px) = 1 colonne (350px)
   
   Responsive SANS media queries ! [BRAVO]
*/

.project-card {
    background: #f9f9f9;
    border-radius: 10px;
    overflow: hidden;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
    cursor: pointer;
}

/* overflow: hidden :
   Cache tout ce qui dépasse du conteneur
   Important pour que les images ne débordent pas
   des coins arrondis (border-radius)
*/

.project-card:hover {
    transform: translateY(-10px);
    box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}

/* Au survol, la carte "lève" de 10px
   Et l'ombre devient plus prononcée
   Effet visuel qui attire l'œil
*/

.project-card img {
    width: 100%;
    height: 200px;
    object-fit: cover;
}

/* object-fit: cover :
   L'image remplit COMPLÈTEMENT la zone (200px de haut)
   Si l'image est trop grande, elle est rognée (crop)
   Si l'image est trop petite, elle est agrandie
   Garde toujours les proportions
   
   Alternatives :
   - contain = Image entière visible (peut laisser des espaces vides)
   - fill = Étire l'image (déforme)
   - none = Taille originale (peut déborder)
*/

.project-card .project-info {
    padding: 1.5rem;
}

.project-card h3 {
    color: #667eea;
    margin-bottom: 0.5rem;
}

.project-card p {
    color: #666;
    font-size: 0.9rem;
}

/* ───────────────────────────────────────────────────────────────
   6. LISTE DE SERVICES
   ─────────────────────────────────────────────────────────────── */

.services-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
}

.service-item {
    text-align: center;
    padding: 2rem;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    border-radius: 10px;
    transition: transform 0.3s ease;
}

.service-item:hover {
    transform: scale(1.05);
}

/* transform: scale(1.05) :
   Agrandit l'élément de 5%
   Effet zoom au survol
*/

.service-item h3 {
    font-size: 1.5rem;
    margin-bottom: 1rem;
    color: white;
}

.service-item p {
    text-align: center;
    color: rgba(255,255,255,0.9);
}

/* ───────────────────────────────────────────────────────────────
   7. FORMULAIRE DE CONTACT
   ─────────────────────────────────────────────────────────────── */

form {
    max-width: 600px;
    margin: 2rem auto;
}

.form-group {
    margin-bottom: 1.5rem;
}

.form-group label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: bold;
    color: #333;
}

/* display: block :
   Le label prend toute la largeur
   Force un retour à la ligne après
   Le champ input sera sur la ligne suivante
*/

.form-group input,
.form-group textarea {
    width: 100%;
    padding: 0.8rem;
    border: 2px solid #ddd;
    border-radius: 5px;
    font-size: 1rem;
    transition: border-color 0.3s ease;
}

/* Plusieurs sélecteurs séparés par des virgules :
   Les styles s'appliquent à input ET textarea
   Évite la duplication de code
*/

.form-group input:focus,
.form-group textarea:focus {
    outline: none;
    border-color: #667eea;
}

/* :focus = quand l'élément est sélectionné (cliqué)
   outline: none = Supprime la bordure bleue par défaut
   border-color: #667eea = Bordure violette à la place
*/

.form-group textarea {
    min-height: 150px;
    resize: vertical;
}

/* resize: vertical :
   L'utilisateur peut redimensionner, mais seulement verticalement
   Évite qu'il déforme la mise en page horizontalement
*/

button[type="submit"] {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 1rem 2rem;
    border: none;
    border-radius: 5px;
    font-size: 1.1rem;
    cursor: pointer;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
}

/* button[type="submit"] :
   Sélecteur d'attribut
   Cible UNIQUEMENT les boutons avec type="submit"
   Pas les boutons normaux ou type="button"
*/

button[type="submit"]:hover {
    transform: translateY(-2px);
    box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}

/* ───────────────────────────────────────────────────────────────
   8. FOOTER
   ─────────────────────────────────────────────────────────────── */

footer {
    background: #333;
    color: white;
    text-align: center;
    padding: 2rem 0;
    margin-top: 3rem;
}

footer p {
    margin: 0;
    text-align: center;
}

footer a {
    color: #667eea;
    text-decoration: none;
}

footer a:hover {
    text-decoration: underline;
}

/* ───────────────────────────────────────────────────────────────
   9. RESPONSIVE (MOBILE)
   ─────────────────────────────────────────────────────────────── */

@media (max-width: 768px) {
    /* Media query :
       Les styles ci-dessous s'appliquent UNIQUEMENT
       si la largeur de l'écran est ≤ 768px (tablettes et mobiles)
    */
    
    nav {
        flex-direction: column;
        gap: 1rem;
    }
    
    /* flex-direction: column :
       Change de horizontal (row) à vertical (column)
       Logo et menu l'un en dessous de l'autre
    */
    
    nav ul {
        flex-direction: column;
        gap: 0.5rem;
        text-align: center;
    }
    
    section {
        padding: 2rem 1rem;
    }
    
    section h1 {
        font-size: 2rem;
    }
    
    section h2 {
        font-size: 1.5rem;
    }
    
    .projects-grid,
    .services-list {
        grid-template-columns: 1fr;
        /* 1 seule colonne sur mobile */
    }
}

/* ═══════════════════════════════════════════════════════════════
   FIN DE LA FEUILLE DE STYLE
   ═══════════════════════════════════════════════════════════════ */
```

**Sauvegarde : `Ctrl + O`, `Entrée`, `Ctrl + X`**

---

### ÉTAPE 4 : Créer la page d'accueil (index.html)

```bash
sudo nano /var/www/portfolio/index.html
```

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portfolio - Accueil</title>
    <meta name="description" content="Portfolio professionnel de [Ton Nom] - Développeur Web">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <!-- ═══════════════════════════════════════════════════════════════
         HEADER ET NAVIGATION
         ═══════════════════════════════════════════════════════════════ -->
    <header>
        <nav>
            <a href="index.html" class="logo">MON PORTFOLIO</a>
            <ul>
                <li><a href="index.html" class="active">Accueil</a></li>
                <li><a href="about.html">À propos</a></li>
                <li><a href="portfolio.html">Portfolio</a></li>
                <li><a href="services.html">Services</a></li>
                <li><a href="contact.html">Contact</a></li>
            </ul>
        </nav>
    </header>

    <!-- Explication de la navigation :
         
         1. class="active" sur le lien "Accueil"
            -> Indique visuellement la page courante
            -> À changer sur chaque page
         
         2. Liens relatifs (pas de http://)
            -> about.html au lieu de http://site.com/about.html
            -> Plus simple, fonctionne en local et en prod
         
         3. Structure <nav> -> <ul> -> <li> -> <a>
            -> Sémantiquement correct (accessibilité)
            -> Moteurs de recherche comprennent que c'est un menu
    -->

    <!-- ═══════════════════════════════════════════════════════════════
         CONTENEUR PRINCIPAL
         ═══════════════════════════════════════════════════════════════ -->
    <div class="container">
        <!-- SECTION HERO (BANNIÈRE D'ACCUEIL) -->
        <section>
            <h1>[WAVING_HAND_SIGN] Bienvenue sur mon Portfolio</h1>
            <p style="font-size: 1.2rem; color: #666;">
                Développeur Web passionné, spécialisé en création de sites modernes et performants.
            </p>
            <p>
                Je suis <strong>[Ton Nom]</strong>, développeur web avec 3 ans d'expérience dans la création d'applications web innovantes. Mon expertise couvre le développement front-end et back-end, avec une passion particulière pour l'expérience utilisateur et la performance.
            </p>
            <p>
                Sur ce site, vous découvrirez mes projets récents, mes compétences techniques, et les services que je propose. N'hésitez pas à me contacter pour discuter de vos projets !
            </p>
        </section>

        <!-- SECTION COMPÉTENCES PRINCIPALES -->
        <section>
            <h2>[RAPIDE] Mes Compétences Principales</h2>
            <div class="services-list">
                <div class="service-item">
                    <h3>[DESIGN] Front-End</h3>
                    <p>HTML5, CSS3, JavaScript, React, Vue.js</p>
                </div>
                <div class="service-item">
                    <h3>[CONFIG] Back-End</h3>
                    <p>Node.js, Python, PHP, MySQL, MongoDB</p>
                </div>
                <div class="service-item">
                    <h3>[OUTILS] DevOps</h3>
                    <p>Git, Docker, Apache, Nginx, CI/CD</p>
                </div>
            </div>
        </section>

        <!-- SECTION PROJETS RÉCENTS (APERÇU) -->
        <section>
            <h2>[PRO] Projets Récents</h2>
            <p>Découvrez quelques-uns de mes derniers projets :</p>
            <div class="projects-grid">
                <div class="project-card">
                    <img src="images/project1.jpg" alt="Projet E-commerce" 
                         onerror="this.src='https://via.placeholder.com/400x200/667eea/ffffff?text=Projet+E-commerce'">
                    <div class="project-info">
                        <h3>Site E-commerce</h3>
                        <p>Boutique en ligne complète avec panier et paiement sécurisé.</p>
                    </div>
                </div>
                
                <div class="project-card">
                    <img src="images/project2.jpg" alt="Application Web" 
                         onerror="this.src='https://via.placeholder.com/400x200/764ba2/ffffff?text=Application+Web'">
                    <div class="project-info">
                        <h3>Application de Gestion</h3>
                        <p>CRM personnalisé pour une PME avec tableau de bord analytique.</p>
                    </div>
                </div>
                
                <div class="project-card">
                    <img src="images/project3.jpg" alt="Site Corporate" 
                         onerror="this.src='https://via.placeholder.com/400x200/667eea/ffffff?text=Site+Corporate'">
                    <div class="project-info">
                        <h3>Site Corporate</h3>
                        <p>Site vitrine responsive pour une entreprise de consulting.</p>
                    </div>
                </div>
            </div>
            <p style="text-align: center; margin-top: 2rem;">
                <a href="portfolio.html" style="color: #667eea; text-decoration: none; font-weight: bold; font-size: 1.1rem;">
                    -> Voir tous mes projets
                </a>
            </p>
        </section>

        <!-- Explication de onerror :
             
             onerror="this.src='https://via.placeholder.com/...'"
             
             Si l'image "images/project1.jpg" n'existe pas (erreur 404),
             alors charge une image de placeholder à la place.
             
             via.placeholder.com génère des images de test
             Format : largeur x hauteur / couleur-fond / couleur-texte ? text=Texte
             
             Utile pour :
             - Développement sans images finales
             - Éviter les images cassées (icône [X])
             - Tester la mise en page
        -->
    </div>

    <!-- ═══════════════════════════════════════════════════════════════
         FOOTER
         ═══════════════════════════════════════════════════════════════ -->
    <footer>
        <p>&copy; 2024 [Ton Nom] - Tous droits réservés</p>
        <p>
            <a href="mailto:contact@example.com">contact@example.com</a> | 
            <a href="tel:+221771234567">+221 77 123 45 67</a>
        </p>
    </footer>

    <!-- Explication du footer :
         
         &copy; = Entité HTML pour le symbole © (copyright)
         
         mailto:email = Ouvre le client email par défaut
         tel:+221... = Sur mobile, lance l'application téléphone
         
         Format international du téléphone :
         +221 = Code pays (Sénégal)
         77 = Indicatif mobile
         123 45 67 = Numéro
    -->
</body>
</html>
```

**Sauvegarde.**

---

### ÉTAPE 5 : Créer la page "À propos" (about.html)

```bash
sudo nano /var/www/portfolio/about.html
```

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portfolio - À propos</title>
    <meta name="description" content="En savoir plus sur [Ton Nom] - Parcours, compétences et passion">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <nav>
            <a href="index.html" class="logo">MON PORTFOLIO</a>
            <ul>
                <li><a href="index.html">Accueil</a></li>
                <li><a href="about.html" class="active">À propos</a></li>
                <li><a href="portfolio.html">Portfolio</a></li>
                <li><a href="services.html">Services</a></li>
                <li><a href="contact.html">Contact</a></li>
            </ul>
        </nav>
    </header>

    <div class="container">
        <section>
            <h1>[GUIDE] À propos de moi</h1>
            
            <h2>Qui suis-je ?</h2>
            <p>
                Bonjour ! Je suis <strong>[Ton Nom]</strong>, développeur web passionné basé à Dakar, Sénégal. Diplômé en génie logiciel, j'ai démarré ma carrière dans le développement web en 2021 et depuis, j'ai eu la chance de travailler sur des projets variés et stimulants.
            </p>
            
            <h2>Mon Parcours</h2>
            <p>
                Mon aventure dans le monde du développement a commencé à l'université, où j'ai découvert ma passion pour la création d'interfaces utilisateur intuitives et performantes. Après mes études, j'ai rapidement intégré le monde professionnel en tant que développeur junior dans une agence digitale.
            </p>
            <p>
                Au fil des années, j'ai eu l'opportunité de :
            </p>
            <ul style="margin-left: 2rem; margin-bottom: 1rem;">
                <li>Développer plus de 20 sites web pour divers clients</li>
                <li>Maîtriser des technologies front-end (React, Vue.js) et back-end (Node.js, Python)</li>
                <li>Collaborer avec des équipes multidisciplinaires</li>
                <li>Gérer des projets de A à Z, de la conception au déploiement</li>
            </ul>
            
            <h2>Ma Philosophie</h2>
            <p>
                Je crois fermement que la technologie doit servir les utilisateurs, et non l'inverse. Chaque projet est pour moi une opportunité de créer quelque chose d'utile, d'accessible et d'agréable à utiliser. Je m'efforce toujours de :
            </p>
            <ul style="margin-left: 2rem; margin-bottom: 1rem;">
                <li><strong>Comprendre les besoins réels</strong> avant de coder</li>
                <li><strong>Écrire du code propre</strong> et maintenable</li>
                <li><strong>Optimiser les performances</strong> pour une expérience fluide</li>
                <li><strong>Rester à jour</strong> avec les dernières technologies</li>
            </ul>
            
            <h2>Mes Compétences Techniques</h2>
            <div class="services-list">
                <div class="service-item">
                    <h3>Languages</h3>
                    <p>JavaScript, Python, PHP, HTML5, CSS3, SQL</p>
                </div>
                <div class="service-item">
                    <h3>Frameworks</h3>
                    <p>React, Vue.js, Node.js, Django, Flask, Laravel</p>
                </div>
                <div class="service-item">
                    <h3>Outils</h3>
                    <p>Git, Docker, VS Code, Webpack, Apache, Nginx</p>
                </div>
                <div class="service-item">
                    <h3>Bases de données</h3>
                    <p>MySQL, PostgreSQL, MongoDB, Redis</p>
                </div>
            </div>
            
            <h2>En dehors du code...</h2>
            <p>
                Quand je ne suis pas devant mon écran à coder, j'aime :
            </p>
            <ul style="margin-left: 2rem; margin-bottom: 1rem;">
                <li>[DOCS] Lire des articles techniques et des livres sur le développement</li>
                <li>[VIDEO_GAME] Jouer à des jeux vidéo (parfois en analyser les interfaces !)</li>
                <li>[SON] Écouter de la musique ou jouer de la guitare</li>
                <li>[RUNNER] Faire du sport pour garder un esprit sain dans un corps sain</li>
                <li>[UTILISATEURS] Participer à des meetups tech et partager mes connaissances</li>
            </ul>
        </section>
    </div>

    <footer>
        <p>&copy; 2024 [Ton Nom] - Tous droits réservés</p>
        <p>
            <a href="mailto:contact@example.com">contact@example.com</a> | 
            <a href="tel:+221771234567">+221 77 123 45 67</a>
        </p>
    </footer>
</body>
</html>
```

**Sauvegarde.**

---

### ÉTAPE 6 : Créer la page Portfolio (portfolio.html)

```bash
sudo nano /var/www/portfolio/portfolio.html
```

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portfolio - Mes Projets</title>
    <meta name="description" content="Découvrez mes projets de développement web">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <nav>
            <a href="index.html" class="logo">MON PORTFOLIO</a>
            <ul>
                <li><a href="index.html">Accueil</a></li>
                <li><a href="about.html">À propos</a></li>
                <li><a href="portfolio.html" class="active">Portfolio</a></li>
                <li><a href="services.html">Services</a></li>
                <li><a href="contact.html">Contact</a></li>
            </ul>
        </nav>
    </header>

    <div class="container">
        <section>
            <h1>[PRO] Mes Projets</h1>
            <p>
                Voici une sélection de mes projets récents. Chaque projet a été une opportunité d'apprentissage et de perfectionnement de mes compétences.
            </p>
        </section>

        <section>
            <h2>[SHOPPING_BAGS] Projets E-commerce</h2>
            <div class="projects-grid">
                <div class="project-card">
                    <img src="images/project1.jpg" alt="Boutique Mode" 
                         onerror="this.src='https://via.placeholder.com/400x200/667eea/ffffff?text=Boutique+Mode'">
                    <div class="project-info">
                        <h3>Boutique de Mode en Ligne</h3>
                        <p><strong>Technologies :</strong> React, Node.js, Stripe</p>
                        <p>Site e-commerce complet avec catalogue produits, panier d'achat, et système de paiement sécurisé. Plus de 500 produits référencés.</p>
                        <p><strong>Résultats :</strong> +150% de ventes en 6 mois</p>
                    </div>
                </div>
                
                <div class="project-card">
                    <img src="images/project2.jpg" alt="Marketplace Artisanat" 
                         onerror="this.src='https://via.placeholder.com/400x200/764ba2/ffffff?text=Marketplace'">
                    <div class="project-info">
                        <h3>Marketplace Artisanat Local</h3>
                        <p><strong>Technologies :</strong> Laravel, Vue.js, MySQL</p>
                        <p>Plateforme multi-vendeurs connectant artisans locaux et clients. Système de commissions, tableau de bord vendeur, et paiement mobile intégré.</p>
                        <p><strong>Résultats :</strong> 50+ artisans inscrits, 1000+ produits</p>
                    </div>
                </div>
            </div>
        </section>

        <section>
            <h2>[ENTREPRISE] Projets Corporate</h2>
            <div class="projects-grid">
                <div class="project-card">
                    <img src="images/project3.jpg" alt="Site Consulting" 
                         onerror="this.src='https://via.placeholder.com/400x200/667eea/ffffff?text=Consulting'">
                    <div class="project-info">
                        <h3>Site Cabinet de Consulting</h3>
                        <p><strong>Technologies :</strong> WordPress, PHP, MySQL</p>
                        <p>Site vitrine moderne avec blog intégré, formulaire de prise de rendez-vous, et espace client sécurisé.</p>
                        <p><strong>Résultats :</strong> +200% de leads qualifiés</p>
                    </div>
                </div>
                
                <div class="project-card">
                    <img src="images/project1.jpg" alt="Intranet Entreprise" 
                         onerror="this.src='https://via.placeholder.com/400x200/764ba2/ffffff?text=Intranet'">
                    <div class="project-info">
                        <h3>Intranet pour PME</h3>
                        <p><strong>Technologies :</strong> Django, React, PostgreSQL</p>
                        <p>Plateforme interne de gestion RH, suivi des projets, et communication d'équipe. 100+ employés utilisateurs actifs.</p>
                        <p><strong>Résultats :</strong> -40% de temps administratif</p>
                    </div>
                </div>
            </div>
        </section>

        <section>
            <h2>[MOBILE] Projets Web App</h2>
            <div class="projects-grid">
                <div class="project-card">
                    <img src="images/project2.jpg" alt="CRM" 
                         onerror="this.src='https://via.placeholder.com/400x200/667eea/ffffff?text=CRM'">
                    <div class="project-info">
                        <h3>CRM Personnalisé</h3>
                        <p><strong>Technologies :</strong> Vue.js, Node.js, MongoDB</p>
                        <p>Application de gestion de la relation client avec pipeline de ventes, suivi des interactions, et reporting avancé.</p>
                        <p><strong>Résultats :</strong> +80% de conversions</p>
                    </div>
                </div>
                
                <div class="project-card">
                    <img src="images/project3.jpg" alt="Outil Gestion Projet" 
                         onerror="this.src='https://via.placeholder.com/400x200/764ba2/ffffff?text=Gestion+Projet'">
                    <div class="project-info">
                        <h3>Outil de Gestion de Projet</h3>
                        <p><strong>Technologies :</strong> React, Flask, Redis</p>
                        <p>Application collaborative style Trello/Asana avec tableaux Kanban, chat intégré, et notifications temps réel.</p>
                        <p><strong>Résultats :</strong> 20+ équipes utilisatrices</p>
                    </div>
                </div>
            </div>
        </section>

        <section>
            <h2>[DESIGN] Projets Créatifs</h2>
            <div class="projects-grid">
                <div class="project-card">
                    <img src="images/project1.jpg" alt="Portfolio Photographe" 
                         onerror="this.src='https://via.placeholder.com/400x200/667eea/ffffff?text=Portfolio+Photo'">
                    <div class="project-info">
                        <h3>Portfolio Photographe</h3>
                        <p><strong>Technologies :</strong> HTML5, CSS3, JavaScript</p>
                        <p>Site vitrine minimaliste avec galerie photos interactive, lightbox, et filtres par catégorie.</p>
                        <p><strong>Résultats :</strong> 95/100 Google PageSpeed</p>
                    </div>
                </div>
                
                <div class="project-card">
                    <img src="images/project2.jpg" alt="Blog Culinaire" 
                         onerror="this.src='https://via.placeholder.com/400x200/764ba2/ffffff?text=Blog+Cuisine'">
                    <div class="project-info">
                        <h3>Blog Culinaire</h3>
                        <p><strong>Technologies :</strong> WordPress, PHP</p>
                        <p>Blog de recettes avec recherche avancée, système de notation, et espace communautaire.</p>
                        <p><strong>Résultats :</strong> 10K visiteurs/mois</p>
                    </div>
                </div>
            </div>
        </section>
    </div>

    <footer>
        <p>&copy; 2024 [Ton Nom] - Tous droits réservés</p>
        <p>
            <a href="mailto:contact@example.com">contact@example.com</a> | 
            <a href="tel:+221771234567">+221 77 123 45 67</a>
        </p>
    </footer>
</body>
</html>
```

**Sauvegarde.**

---

### ÉTAPE 7 : Créer la page Services (services.html)

```bash
sudo nano /var/www/portfolio/services.html
```

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portfolio - Mes Services</title>
    <meta name="description" content="Services de développement web proposés par [Ton Nom]">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <nav>
            <a href="index.html" class="logo">MON PORTFOLIO</a>
            <ul>
                <li><a href="index.html">Accueil</a></li>
                <li><a href="about.html">À propos</a></li>
                <li><a href="portfolio.html">Portfolio</a></li>
                <li><a href="services.html" class="active">Services</a></li>
                <li><a href="contact.html">Contact</a></li>
            </ul>
        </nav>
    </header>

    <div class="container">
        <section>
            <h1>[OUTILS] Mes Services</h1>
            <p>
                Je propose des services de développement web complets, adaptés à vos besoins spécifiques. Que vous soyez une startup, une PME ou une grande entreprise, je peux vous accompagner dans vos projets digitaux.
            </p>
        </section>

        <section>
            <h2>Services Proposés</h2>
            <div class="services-list">
                <div class="service-item">
                    <h3>[DESIGN] Développement Front-End</h3>
                    <p>Création d'interfaces utilisateur modernes, intuitives et responsives. HTML5, CSS3, JavaScript, React, Vue.js.</p>
                    <p style="margin-top: 1rem;"><strong>À partir de 500 €</strong></p>
                </div>
                
                <div class="service-item">
                    <h3>[CONFIG] Développement Back-End</h3>
                    <p>Développement d'API REST, gestion de bases de données, architecture serveur. Node.js, Python, PHP.</p>
                    <p style="margin-top: 1rem;"><strong>À partir de 800 €</strong></p>
                </div>
                
                <div class="service-item">
                    <h3>[SHOPPING_BAGS] Sites E-commerce</h3>
                    <p>Boutiques en ligne complètes avec panier, paiement sécurisé, gestion stock. WooCommerce, Shopify, custom.</p>
                    <p style="margin-top: 1rem;"><strong>À partir de 1500 €</strong></p>
                </div>
                
                <div class="service-item">
                    <h3>[MOBILE] Applications Web</h3>
                    <p>Développement d'applications web sur mesure (CRM, ERP, plateformes métier). Technologies modernes et scalables.</p>
                    <p style="margin-top: 1rem;"><strong>Sur devis</strong></p>
                </div>
                
                <div class="service-item">
                    <h3>[OUTIL] Maintenance & Support</h3>
                    <p>Maintenance préventive et corrective, mises à jour, support technique, monitoring serveur.</p>
                    <p style="margin-top: 1rem;"><strong>À partir de 200 €/mois</strong></p>
                </div>
                
                <div class="service-item">
                    <h3>[HAUSSE] Optimisation & Performance</h3>
                    <p>Audit de performance, optimisation vitesse de chargement, SEO technique, amélioration UX.</p>
                    <p style="margin-top: 1rem;"><strong>À partir de 300 €</strong></p>
                </div>
            </div>
        </section>

        <section>
            <h2>[OBJECTIF] Ma Méthodologie</h2>
            <p>
                Pour chaque projet, je suis une méthodologie éprouvée qui garantit qualité et satisfaction :
            </p>
            
            <h3 style="color: #667eea; margin-top: 2rem;">1⃣ Phase de Découverte</h3>
            <ul style="margin-left: 2rem; margin-bottom: 1.5rem;">
                <li>Écoute de vos besoins et objectifs</li>
                <li>Analyse de votre cible et de vos concurrents</li>
                <li>Définition du périmètre du projet</li>
                <li>Estimation budgétaire et planning</li>
            </ul>
            
            <h3 style="color: #667eea;">2⃣ Phase de Conception</h3>
            <ul style="margin-left: 2rem; margin-bottom: 1.5rem;">
                <li>Création de wireframes et maquettes</li>
                <li>Validation de la charte graphique</li>
                <li>Architecture technique et choix technologiques</li>
                <li>Rédaction des spécifications fonctionnelles</li>
            </ul>
            
            <h3 style="color: #667eea;">3⃣ Phase de Développement</h3>
            <ul style="margin-left: 2rem; margin-bottom: 1.5rem;">
                <li>Développement itératif avec démonstrations régulières</li>
                <li>Tests unitaires et d'intégration</li>
                <li>Optimisation des performances</li>
                <li>Documentation du code</li>
            </ul>
            
            <h3 style="color: #667eea;">4⃣ Phase de Déploiement</h3>
            <ul style="margin-left: 2rem; margin-bottom: 1.5rem;">
                <li>Tests finaux et recettage</li>
                <li>Mise en production sécurisée</li>
                <li>Formation à l'utilisation (si nécessaire)</li>
                <li>Transfert de compétences</li>
            </ul>
            
            <h3 style="color: #667eea;">5⃣ Phase de Suivi</h3>
            <ul style="margin-left: 2rem; margin-bottom: 1.5rem;">
                <li>Garantie et corrections de bugs</li>
                <li>Monitoring et maintenance préventive</li>
                <li>Support technique</li>
                <li>Évolutions et améliorations</li>
            </ul>
        </section>

        <section>
            <h2>[IDEE] Pourquoi Travailler Avec Moi ?</h2>
            <div style="display: grid; gap: 1.5rem; margin-top: 2rem;">
                <div style="background: #f9f9f9; padding: 1.5rem; border-radius: 8px; border-left: 4px solid #667eea;">
                    <h3 style="color: #667eea; margin-bottom: 0.5rem;">[OK] Expertise Technique</h3>
                    <p>Plus de 3 ans d'expérience avec les dernières technologies web.</p>
                </div>
                
                <div style="background: #f9f9f9; padding: 1.5rem; border-radius: 8px; border-left: 4px solid #764ba2;">
                    <h3 style="color: #764ba2; margin-bottom: 0.5rem;">[OK] Communication Transparente</h3>
                    <p>Mises à jour régulières, réponses rapides, disponibilité par email/téléphone.</p>
                </div>
                
                <div style="background: #f9f9f9; padding: 1.5rem; border-radius: 8px; border-left: 4px solid #667eea;">
                    <h3 style="color: #667eea; margin-bottom: 0.5rem;">[OK] Respect des Délais</h3>
                    <p>Engagement ferme sur les deadlines avec méthodologie agile.</p>
                </div>
                
                <div style="background: #f9f9f9; padding: 1.5rem; border-radius: 8px; border-left: 4px solid #764ba2;">
                    <h3 style="color: #764ba2; margin-bottom: 0.5rem;">[OK] Code de Qualité</h3>
                    <p>Code propre, commenté, testé et facilement maintenable.</p>
                </div>
                
                <div style="background: #f9f9f9; padding: 1.5rem; border-radius: 8px; border-left: 4px solid #667eea;">
                    <h3 style="color: #667eea; margin-bottom: 0.5rem;">[OK] Support Post-Lancement</h3>
                    <p>Accompagnement même après la livraison du projet.</p>
                </div>
            </div>
        </section>

        <section style="text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
            <h2 style="color: white;">[TEL] Prêt à Démarrer Votre Projet ?</h2>
            <p style="font-size: 1.2rem; margin-bottom: 2rem;">
                Contactez-moi dès maintenant pour discuter de vos besoins !
            </p>
            <a href="contact.html" style="display: inline-block; background: white; color: #667eea; padding: 1rem 2rem; border-radius: 5px; text-decoration: none; font-weight: bold; font-size: 1.1rem;">
                Me Contacter ->
            </a>
        </section>
    </div>

    <footer>
        <p>&copy; 2024 [Ton Nom] - Tous droits réservés</p>
        <p>
            <a href="mailto:contact@example.com">contact@example.com</a> | 
            <a href="tel:+221771234567">+221 77 123 45 67</a>
        </p>
    </footer>
</body>
</html>
```

**Sauvegarde.**

---

### ÉTAPE 8 : Créer la page Contact (contact.html)

```bash
sudo nano /var/www/portfolio/contact.html
```

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Portfolio - Contact</title>
    <meta name="description" content="Contactez [Ton Nom] pour vos projets web">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <nav>
            <a href="index.html" class="logo">MON PORTFOLIO</a>
            <ul>
                <li><a href="index.html">Accueil</a></li>
                <li><a href="about.html">À propos</a></li>
                <li><a href="portfolio.html">Portfolio</a></li>
                <li><a href="services.html">Services</a></li>
                <li><a href="contact.html" class="active">Contact</a></li>
            </ul>
        </nav>
    </header>

    <div class="container">
        <section>
            <h1>[EMAIL] Contactez-moi</h1>
            <p>
                Vous avez un projet en tête ? Une question ? N'hésitez pas à me contacter. Je vous répondrai dans les plus brefs délais !
            </p>
        </section>

        <section>
            <h2>[NOTE] Formulaire de Contact</h2>
            <form action="#" method="POST">
                <!-- Explication du formulaire :
                     
                     action="#" = Pour l'instant, le formulaire ne fait rien
                     En production, tu mettrais :
                     - action="process-form.php" (PHP)
                     - action="/api/contact" (API REST)
                     - Ou utiliser un service comme Formspree, Netlify Forms
                     
                     method="POST" = Méthode HTTP
                     POST = Envoie les données dans le corps de la requête (invisible dans l'URL)
                     GET = Envoie les données dans l'URL (?nom=valeur&email=valeur)
                     
                     Pour un formulaire de contact : TOUJOURS POST
                     (sécurité et confidentialité)
                -->
                
                <div class="form-group">
                    <label for="nom">Votre Nom *</label>
                    <input type="text" id="nom" name="nom" required placeholder="Ex: Marie Dupont">
                    
                    <!-- Explication des attributs :
                         
                         type="text" = Champ texte simple
                         id="nom" = Identifiant unique (pour le label)
                         name="nom" = Nom du champ (envoyé au serveur)
                         required = Champ obligatoire (validation HTML5)
                         placeholder = Texte d'exemple (disparaît au focus)
                         
                         L'attribut "for" du label correspond à l'id de l'input
                         -> Cliquer sur le label = focus sur l'input
                    -->
                </div>

                <div class="form-group">
                    <label for="email">Votre Email *</label>
                    <input type="email" id="email" name="email" required placeholder="Ex: marie@example.com">
                    
                    <!-- type="email" :
                         Validation automatique du format email
                         Sur mobile : clavier spécial email (@, .com, etc.)
                         Rejette les formats invalides (sans @, etc.)
                    -->
                </div>

                <div class="form-group">
                    <label for="telephone">Téléphone (optionnel)</label>
                    <input type="tel" id="telephone" name="telephone" placeholder="Ex: +221 77 123 45 67">
                    
                    <!-- type="tel" :
                         Champ téléphone
                         Sur mobile : clavier numérique
                         Pas de validation stricte (formats internationaux variés)
                    -->
                </div>

                <div class="form-group">
                    <label for="sujet">Sujet *</label>
                    <select id="sujet" name="sujet" required>
                        <option value="">-- Choisissez un sujet --</option>
                        <option value="devis">Demande de devis</option>
                        <option value="question">Question générale</option>
                        <option value="support">Support technique</option>
                        <option value="collaboration">Proposition de collaboration</option>
                        <option value="autre">Autre</option>
                    </select>
                    
                    <!-- <select> = Liste déroulante
                         
                         <option value="">-- Choisissez --</option>
                         -> Première option vide (invite l'utilisateur à choisir)
                         
                         <option value="devis">Demande de devis</option>
                         -> "Demande de devis" affiché à l'utilisateur
                         -> "devis" envoyé au serveur
                    -->
                </div>

                <div class="form-group">
                    <label for="message">Votre Message *</label>
                    <textarea id="message" name="message" rows="8" required placeholder="Décrivez votre projet ou votre question..."></textarea>
                    
                    <!-- <textarea> :
                         Champ texte multi-lignes
                         rows="8" = Hauteur initiale de 8 lignes
                         L'utilisateur peut redimensionner verticalement (cf. CSS)
                    -->
                </div>

                <div class="form-group">
                    <button type="submit">Envoyer le Message</button>
                    
                    <!-- type="submit" :
                         Bouton qui soumet le formulaire
                         Déclenche l'événement "submit" du <form>
                         
                         Alternatives :
                         type="button" = Bouton normal (rien par défaut)
                         type="reset" = Réinitialise le formulaire
                    -->
                </div>
            </form>
            
            <p style="color: #666; font-size: 0.9rem; margin-top: 1rem;">
                * Champs obligatoires
            </p>
        </section>

        <section>
            <h2>[IMPORTANT] Autres Moyens de Me Joindre</h2>
            <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 2rem; margin-top: 2rem;">
                <div style="text-align: center; padding: 2rem; background: #f9f9f9; border-radius: 10px;">
                    <h3 style="color: #667eea; font-size: 3rem; margin-bottom: 0.5rem;">[EMAIL]</h3>
                    <h4>Email</h4>
                    <a href="mailto:contact@example.com" style="color: #667eea; text-decoration: none;">
                        contact@example.com
                    </a>
                </div>
                
                <div style="text-align: center; padding: 2rem; background: #f9f9f9; border-radius: 10px;">
                    <h3 style="color: #764ba2; font-size: 3rem; margin-bottom: 0.5rem;">[MOBILE]</h3>
                    <h4>Téléphone</h4>
                    <a href="tel:+221771234567" style="color: #764ba2; text-decoration: none;">
                        +221 77 123 45 67
                    </a>
                </div>
                
                <div style="text-align: center; padding: 2rem; background: #f9f9f9; border-radius: 10px;">
                    <h3 style="color: #667eea; font-size: 3rem; margin-bottom: 0.5rem;">[PRO]</h3>
                    <h4>LinkedIn</h4>
                    <a href="https://linkedin.com/in/ton-profil" target="_blank" style="color: #667eea; text-decoration: none;">
                        /in/ton-profil
                    </a>
                    
                    <!-- target="_blank" :
                         Ouvre le lien dans un nouvel onglet
                         
                         Bonne pratique pour les liens externes :
                         Évite que l'utilisateur "quitte" ton site
                         
                         [ATTENTION] Sécurité : Ajouter aussi rel="noopener noreferrer"
                         <a href="..." target="_blank" rel="noopener noreferrer">
                    -->
                </div>
                
                <div style="text-align: center; padding: 2rem; background: #f9f9f9; border-radius: 10px;">
                    <h3 style="color: #764ba2; font-size: 3rem; margin-bottom: 0.5rem;">[OCTOPUS]</h3>
                    <h4>GitHub</h4>
                    <a href="https://github.com/ton-username" target="_blank" style="color: #764ba2; text-decoration: none;">
                        /ton-username
                    </a>
                </div>
            </div>
        </section>

        <section>
            <h2>[ALARM_CLOCK] Disponibilité</h2>
            <p>
                Je suis généralement disponible du lundi au vendredi, de 9h à 18h (GMT+0). Je m'efforce de répondre à tous les emails dans les 24 heures ouvrables.
            </p>
            <p>
                Pour les urgences, n'hésitez pas à me contacter directement par téléphone.
            </p>
        </section>
    </div>

    <footer>
        <p>&copy; 2024 [Ton Nom] - Tous droits réservés</p>
        <p>
            <a href="mailto:contact@example.com">contact@example.com</a> | 
            <a href="tel:+221771234567">+221 77 123 45 67</a>
        </p>
    </footer>
</body>
</html>
```

**Sauvegarde.**

---

### ÉTAPE 9 : Définir les permissions

**Pourquoi c'est important ?**
- Apache tourne sous l'utilisateur `www-data`
- Cet utilisateur doit pouvoir **lire** les fichiers
- Mais pas forcément les **écrire** (sécurité)

```bash
# Donner la propriété à www-data
sudo chown -R www-data:www-data /var/www/portfolio

# Définir les bonnes permissions
# 755 pour les dossiers (rwxr-xr-x)
# 644 pour les fichiers (rw-r--r--)
sudo find /var/www/portfolio -type d -exec chmod 755 {} \;
sudo find /var/www/portfolio -type f -exec chmod 644 {} \;
```

**Explication des commandes :**

**`chown -R www-data:www-data`**
- `chown` = Change Owner (changer propriétaire)
- `-R` = Récursif (tous les sous-dossiers et fichiers)
- `www-data:www-data` = Utilisateur:Groupe

---

**`find ... -type d -exec chmod 755 {} \;`**
- `find /var/www/portfolio` = Cherche dans ce dossier
- `-type d` = Type Directory (seulement les dossiers)
- `-exec chmod 755 {} \;` = Exécute `chmod 755` sur chaque résultat

**Permissions 755 (dossiers) :**
```
7 = rwx (read, write, execute) = Propriétaire peut tout faire
5 = r-x (read, execute) = Groupe peut lire et traverser
5 = r-x (read, execute) = Autres peuvent lire et traverser
```

**"Traverser" un dossier = Pouvoir y entrer (cd /dossier/)**

---

**`find ... -type f -exec chmod 644 {} \;`**
- `-type f` = Type File (seulement les fichiers)

**Permissions 644 (fichiers) :**
```
6 = rw- (read, write) = Propriétaire peut lire et écrire
4 = r-- (read) = Groupe peut lire
4 = r-- (read) = Autres peuvent lire
```

**Pas besoin d'exécution (x) pour des fichiers HTML/CSS/JS !**

---

**Vérifier les permissions :**

```bash
ls -lh /var/www/portfolio
```

**Résultat attendu :**

```
drwxr-xr-x 2 www-data www-data 4.0K Dec 16 10:00 css
drwxr-xr-x 2 www-data www-data 4.0K Dec 16 10:00 images
drwxr-xr-x 2 www-data www-data 4.0K Dec 16 10:00 js
-rw-r--r-- 1 www-data www-data  12K Dec 16 10:00 index.html
-rw-r--r-- 1 www-data www-data 8.5K Dec 16 10:00 about.html
...
```

**[OK] Si tu vois `www-data www-data` et `rwxr-xr-x` / `rw-r--r--`, c'est bon !**

---

### ÉTAPE 10 : Configurer le Virtual Host Apache

```bash
sudo nano /etc/apache2/sites-available/portfolio.conf
```

**Contenu :**

```apache
# ═══════════════════════════════════════════════════════════════
# VIRTUAL HOST - PORTFOLIO
# ═══════════════════════════════════════════════════════════════
# Description : Site portfolio personnel
# Serveur : Apache 2.4
# Port : 80 (HTTP)
# ═══════════════════════════════════════════════════════════════

<VirtualHost *:80>
    # ───────────────────────────────────────────────────────────
    # IDENTIFICATION DU SERVEUR
    # ───────────────────────────────────────────────────────────
    
    # Nom de domaine principal
    ServerName portfolio.local
    
    # Explication :
    # En production, ce serait ton vrai domaine
    # Exemple : ServerName www.mon-portfolio.com
    #
    # En local (développement), on utilise un .local
    # Il faudra ajouter cette entrée dans le fichier hosts
    
    # Alias (domaines alternatifs pointant vers ce site)
    # ServerAlias www.portfolio.local
    
    # Email de l'administrateur (affiché dans certaines pages d'erreur)
    ServerAdmin contact@example.com
    
    # ───────────────────────────────────────────────────────────
    # EMPLACEMENT DES FICHIERS
    # ───────────────────────────────────────────────────────────
    
    # Racine du site web
    DocumentRoot /var/www/portfolio
    
    # Explication :
    # Quand l'utilisateur accède à http://portfolio.local/
    # Apache cherche les fichiers dans /var/www/portfolio/
    #
    # http://portfolio.local/index.html -> /var/www/portfolio/index.html
    # http://portfolio.local/css/style.css -> /var/www/portfolio/css/style.css
    
    # ───────────────────────────────────────────────────────────
    # CONFIGURATION DU RÉPERTOIRE
    # ───────────────────────────────────────────────────────────
    
    <Directory /var/www/portfolio>
        # Options pour ce répertoire
        Options Indexes FollowSymLinks
        
        # Explication des Options :
        #
        # Indexes :
        # Si pas de index.html, affiche la liste des fichiers
        # [ATTENTION] En production, mieux vaut désactiver (-Indexes)
        #
        # FollowSymLinks :
        # Suit les liens symboliques
        # Si tu as un lien symbolique vers un autre dossier,
        # Apache peut y accéder
        #
        # Autres options possibles :
        # ExecCGI = Permet d'exécuter des scripts CGI
        # MultiViews = Négociation de contenu
        # All = Active toutes les options
        # None = Désactive toutes les options
        
        # Autoriser .htaccess à écraser la configuration
        AllowOverride All
        
        # Explication :
        # All = Le fichier .htaccess peut écraser N'IMPORTE QUELLE directive
        #
        # En production, pour de meilleures performances :
        # AllowOverride None
        # Et mettre toutes les directives ici directement
        #
        # Autres valeurs :
        # None = .htaccess ignoré
        # Limit = Seulement les directives de contrôle d'accès
        # FileInfo = Seulement les directives de fichiers (cache, mime, etc.)
        
        # Contrôle d'accès
        Require all granted
        
        # Explication :
        # "Require all granted" = Autoriser TOUT LE MONDE
        #
        # Alternatives :
        # Require all denied = Bloquer tout le monde
        # Require ip 192.168.1.0/24 = Autoriser seulement ce réseau
        # Require host example.com = Autoriser seulement ce domaine
        # Require user admin = Autoriser seulement l'utilisateur "admin"
        
        # Fichiers d'index (par ordre de priorité)
        DirectoryIndex index.html index.htm
        
        # Explication :
        # Quand on accède à http://portfolio.local/
        # Apache cherche dans cet ordre :
        # 1. index.html
        # 2. index.htm
        #
        # Le premier trouvé est affiché.
        # Si aucun n'existe, affiche la liste des fichiers (si Indexes activé)
        #
        # Avec PHP, on ajouterait :
        # DirectoryIndex index.php index.html index.htm
    </Directory>
    
    # ───────────────────────────────────────────────────────────
    # LOGS
    # ───────────────────────────────────────────────────────────
    
    # Fichier de log des erreurs
    ErrorLog ${APACHE_LOG_DIR}/portfolio-error.log
    
    # Explication :
    # ${APACHE_LOG_DIR} = Variable d'environnement
    # Équivaut à /var/log/apache2/
    #
    # Toutes les erreurs de CE site seront loguées ici :
    # - Erreurs 404 (pages non trouvées)
    # - Erreurs 500 (erreurs serveur)
    # - Erreurs PHP
    # - Etc.
    
    # Fichier de log des accès
    CustomLog ${APACHE_LOG_DIR}/portfolio-access.log combined
    
    # Explication :
    # Log de TOUTES les requêtes HTTP sur CE site
    # Format "combined" = Format détaillé avec :
    # - IP du client
    # - Date/heure
    # - URL demandée
    # - Code de statut (200, 404, etc.)
    # - Taille de la réponse
    # - Referer (d'où vient le client)
    # - User-Agent (navigateur)
    #
    # Autres formats possibles :
    # - common = Format standard (moins d'infos)
    # - vhost_combined = Ajoute le nom du Virtual Host
    
    # ───────────────────────────────────────────────────────────
    # SÉCURITÉ ET OPTIMISATION (OPTIONNEL POUR CET EXERCICE)
    # ───────────────────────────────────────────────────────────
    
    # Désactiver le listing de répertoires (recommandé en production)
    # <Directory /var/www/portfolio>
    #     Options -Indexes
    # </Directory>
    
    # Cacher certains fichiers sensibles
    # <FilesMatch "^\.">
    #     Require all denied
    # </FilesMatch>
    
    # Compression GZIP
    # <IfModule mod_deflate.c>
    #     AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript
    # </IfModule>
    
    # Cache des ressources statiques
    # <FilesMatch "\.(jpg|jpeg|png|gif|css|js)$">
    #     Header set Cache-Control "max-age=2592000, public"
    # </FilesMatch>
    
</VirtualHost>

# ═══════════════════════════════════════════════════════════════
# FIN DE LA CONFIGURATION
# ═══════════════════════════════════════════════════════════════
```

**Sauvegarde : `Ctrl + O`, `Entrée`, `Ctrl + X`**

---

### ÉTAPE 11 : Activer le site

```bash
# Désactiver le site par défaut (optionnel)
sudo a2dissite 000-default.conf

# Activer notre site portfolio
sudo a2ensite portfolio.conf
```

**Résultat :**

```
Enabling site portfolio.
To activate the new configuration, you need to run:
  systemctl reload apache2
```

---

**Tester la configuration :**

```bash
sudo apache2ctl configtest
```

**Résultat attendu :**

```
Syntax OK
```

**[OK] Si "Syntax OK", tout est bon !**

**[X] Si erreur, lis le message et corrige. Exemple :**

```
AH00526: Syntax error on line 42 of /etc/apache2/sites-enabled/portfolio.conf:
Invalid command 'InvalidDirective'
```

**Ligne 42 a une erreur. Édite et corrige.**

---

**Recharger Apache :**

```bash
sudo systemctl reload apache2
```

**Ou redémarrer (si reload ne suffit pas) :**

```bash
sudo systemctl restart apache2
```

---

### ÉTAPE 12 : Configurer le fichier hosts

**Pour accéder au site via `http://portfolio.local`, on doit ajouter une entrée DNS locale.**

**Sur ton PC (PAS sur le serveur) :**

**Windows :**

```
1. Ouvre Notepad en tant qu'administrateur
   (Clic droit -> "Exécuter en tant qu'administrateur")

2. Fichier -> Ouvrir

3. Va dans : C:\Windows\System32\drivers\etc\

4. Dans "Type de fichier", choisis "Tous les fichiers"

5. Ouvre le fichier "hosts" (sans extension)

6. Ajoute à la fin :
   192.168.1.100    portfolio.local

7. Sauvegarde (Ctrl + S)
```

**macOS / Linux :**

```bash
sudo nano /etc/hosts
```

**Ajoute à la fin :**

```
192.168.1.100    portfolio.local
```

**[ATTENTION] Remplace `192.168.1.100` par l'IP de TON serveur !**

**Sauvegarde.**

---

**Explication du fichier hosts :**

Le fichier `hosts` est une table de correspondance locale :
- Nom de domaine -> Adresse IP
- Consulté AVANT les serveurs DNS

**Exemple de contenu :**

```
127.0.0.1       localhost
192.168.1.100   portfolio.local
192.168.1.100   test.local
```

**Quand tu tapes `http://portfolio.local` dans le navigateur :**

1. Le navigateur consulte le fichier hosts
2. Trouve `192.168.1.100` pour `portfolio.local`
3. Envoie la requête à `192.168.1.100`
4. Apache sur `192.168.1.100` reçoit la requête
5. Voit que `Host: portfolio.local` dans les headers HTTP
6. Cherche un Virtual Host avec `ServerName portfolio.local`
7. Trouve notre configuration `portfolio.conf`
8. Sert les fichiers de `/var/www/portfolio/`

**C'est magique ! ***

---

### ÉTAPE 13 : Tester le site

**Ouvre ton navigateur et accède à :**

```
http://portfolio.local
```

**[BRAVO] TU DEVRAIS VOIR TON SITE PORTFOLIO ! [BRAVO]**

---

**Teste la navigation :**

[OK] Accueil -> À propos -> Portfolio -> Services -> Contact
[OK] Tous les liens fonctionnent
[OK] Le menu souligne la page courante
[OK] Le design est responsive (redimensionne la fenêtre)
[OK] Les images de placeholder s'affichent

---

**Si la page ne charge pas :**

**Vérifications :**

**1. Apache tourne ?**

```bash
sudo systemctl status apache2
```

**Si inactif, démarrer :**

```bash
sudo systemctl start apache2
```

---

**2. Fichier hosts correct ?**

**Teste avec l'IP directement :**

```
http://192.168.1.100
```

**Si ça marche avec l'IP mais pas avec `portfolio.local`, problème dans hosts.**

---

**3. Virtual Host activé ?**

```bash
ls -la /etc/apache2/sites-enabled/
```

**Tu devrais voir :**

```
portfolio.conf -> ../sites-available/portfolio.conf
```

---

**4. Consulter les logs :**

```bash
sudo tail -20 /var/log/apache2/portfolio-error.log
```

**Le message d'erreur te dira ce qui ne va pas.**

---

### [OK] TESTS DE VALIDATION

**Pour vérifier que tout fonctionne correctement :**

**1. Navigation complète**

- [ ] Page d'accueil charge correctement
- [ ] Menu de navigation fonctionne
- [ ] Toutes les pages sont accessibles
- [ ] Pas d'erreur 404
- [ ] Le design est cohérent sur toutes les pages

---

**2. Responsive Design**

- [ ] Redimensionne la fenêtre à 1200px -> OK
- [ ] Redimensionne à 800px -> OK
- [ ] Redimensionne à 400px (mobile) -> OK
- [ ] Sur mobile, le menu s'affiche verticalement
- [ ] Les projets s'affichent en 1 colonne sur mobile

---

**3. Formulaire de contact**

- [ ] Tous les champs s'affichent
- [ ] Les champs obligatoires sont marqués
- [ ] Le placeholder s'affiche
- [ ] Le bouton est stylisé
- [ ] (Le formulaire ne soumet rien, c'est normal pour l'instant)

---

**4. Performance**

**Ouvre les outils de développement (F12) -> Onglet "Network"**

- [ ] Fichier `index.html` se charge (200 OK)
- [ ] Fichier `style.css` se charge (200 OK)
- [ ] Temps de chargement total < 1 seconde (en local)

---

**5. Logs Apache**

```bash
# Consulter les logs d'accès
sudo tail -20 /var/log/apache2/portfolio-access.log
```

**Tu devrais voir tes requêtes :**

```
192.168.1.50 - - [16/Dec/2024:10:00:00 +0000] "GET / HTTP/1.1" 200 12345
192.168.1.50 - - [16/Dec/2024:10:00:01 +0000] "GET /css/style.css HTTP/1.1" 200 6789
...
```

**Code 200 = OK ! [OK]**

---

### [ROUGE] ERREURS COURANTES ET SOLUTIONS

#### Erreur 1 : 403 Forbidden

**Symptôme :**

```
Forbidden
You don't have permission to access this resource.
```

**Causes possibles :**

**1. Permissions incorrectes**

```bash
# Vérifier
ls -la /var/www/portfolio/

# Corriger
sudo chown -R www-data:www-data /var/www/portfolio
sudo find /var/www/portfolio -type d -exec chmod 755 {} \;
sudo find /var/www/portfolio -type f -exec chmod 644 {} \;
```

---

**2. Directive "Require all denied"**

**Dans `/etc/apache2/sites-available/portfolio.conf` :**

```apache
# [X] MAUVAIS
Require all denied

# [OK] BON
Require all granted
```

---

**3. Pas de fichier index.html**

**Vérifie que le fichier existe :**

```bash
ls -l /var/www/portfolio/index.html
```

**Si absent, le créer.**

---

#### Erreur 2 : 404 Not Found sur les CSS/images

**Symptôme :**

- La page HTML s'affiche
- Mais sans style (pas de CSS)
- Les images ne s'affichent pas

**Cause : Chemins relatifs incorrects**

**Dans `index.html`, vérifie :**

```html
<!-- [OK] BON (chemin relatif depuis la racine du site) -->
<link rel="stylesheet" href="css/style.css">

<!-- [X] MAUVAIS (chemin absolu local) -->
<link rel="stylesheet" href="/var/www/portfolio/css/style.css">

<!-- [X] MAUVAIS (chemin incorrect) -->
<link rel="stylesheet" href="/css/style.css">
```

**Test dans le navigateur (F12 -> Network) :**

```
Requête : http://portfolio.local/css/style.css
Résultat : 404 Not Found
```

**Vérifie que le fichier existe :**

```bash
ls -l /var/www/portfolio/css/style.css
```

---

#### Erreur 3 : Le site par défaut s'affiche à la place

**Symptôme :**

En accédant à `http://portfolio.local`, tu vois la page "Apache2 Ubuntu Default Page".

**Cause : Le site par défaut est toujours activé**

**Solution :**

```bash
# Désactiver le site par défaut
sudo a2dissite 000-default.conf

# Vérifier que portfolio.conf est activé
ls -la /etc/apache2/sites-enabled/

# Recharger Apache
sudo systemctl reload apache2
```

---

#### Erreur 4 : Le menu ne s'affiche pas correctement

**Symptôme :**

- Le menu s'affiche en liste verticale même sur grand écran
- Pas de couleur de fond

**Cause : CSS non chargé**

**Vérifier dans le navigateur (F12 -> Console) :**

```
Failed to load resource: the server responded with a status of 404 (Not Found)
http://portfolio.local/css/style.css
```

**Solution :**

1. Vérifie que `style.css` existe dans `/var/www/portfolio/css/`
2. Vérifie les permissions (644 pour le fichier, 755 pour le dossier)
3. Vérifie le chemin dans le HTML

---

### [IMPORTANT] POINTS CLÉS À RETENIR

**1. Structure de site web**
- Séparer HTML, CSS, images dans des dossiers dédiés
- Utiliser des chemins relatifs pour les ressources
- Nommer les fichiers en minuscules sans espaces

**2. Permissions Linux**
- Propriétaire : `www-data:www-data`
- Dossiers : `755` (rwxr-xr-x)
- Fichiers : `644` (rw-r--r--)

**3. Virtual Host Apache**
- Un VirtualHost = Un site web
- `ServerName` = Nom de domaine principal
- `DocumentRoot` = Racine du site
- `DirectoryIndex` = Fichiers d'index (ordre de priorité)

**4. Fichier hosts**
- Permet de faire correspondre un nom de domaine à une IP localement
- Utile pour le développement en local
- À éditer sur l'ordinateur CLIENT (pas le serveur)

**5. Logs Apache**
- `access.log` = Journal des requêtes HTTP
- `error.log` = Journal des erreurs
- Séparer les logs par site (un log par Virtual Host)

---

### [RAPIDE] POUR ALLER PLUS LOIN

**Améliorations possibles :**

**1. Ajouter des images réelles**

Au lieu des placeholders, ajoute de vraies images :

```bash
# Copier des images dans le dossier
sudo cp /chemin/vers/tes/images/*.jpg /var/www/portfolio/images/

# Corriger les permissions
sudo chown www-data:www-data /var/www/portfolio/images/*
sudo chmod 644 /var/www/portfolio/images/*
```

**Modifier le HTML :**

```html
<!-- Au lieu de -->
<img src="images/project1.jpg" alt="..." onerror="...">

<!-- Simplement -->
<img src="images/project1.jpg" alt="Mon Projet E-commerce">
```

---

**2. Rendre le formulaire fonctionnel avec PHP**

**Créer un fichier `process-form.php` :**

```bash
sudo nano /var/www/portfolio/process-form.php
```

**Contenu basique :**

```php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $nom = htmlspecialchars($_POST['nom']);
    $email = htmlspecialchars($_POST['email']);
    $sujet = htmlspecialchars($_POST['sujet']);
    $message = htmlspecialchars($_POST['message']);
    
    // Envoyer un email
    $to = "contact@example.com";
    $subject = "Contact Portfolio : " . $sujet;
    $body = "De : $nom ($email)\n\n$message";
    $headers = "From: $email";
    
    if (mail($to, $subject, $body, $headers)) {
        echo "Message envoyé avec succès !";
    } else {
        echo "Erreur lors de l'envoi.";
    }
} else {
    header("Location: contact.html");
}
?>
```

**Modifier `contact.html` :**

```html
<form action="process-form.php" method="POST">
```

---

**3. Ajouter JavaScript pour interactivité**

**Créer `/var/www/portfolio/js/script.js` :**

```javascript
// Animation au scroll
document.addEventListener('DOMContentLoaded', function() {
    const sections = document.querySelectorAll('section');
    
    const observerOptions = {
        threshold: 0.2
    };
    
    const observer = new IntersectionObserver(function(entries) {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                entry.target.style.opacity = '1';
                entry.target.style.transform = 'translateY(0)';
            }
        });
    }, observerOptions);
    
    sections.forEach(section => {
        section.style.opacity = '0';
        section.style.transform = 'translateY(50px)';
        section.style.transition = 'all 0.6s ease';
        observer.observe(section);
    });
});
```

**Inclure dans les pages HTML :**

```html
<script src="js/script.js"></script>
</body>
</html>
```

---

**4. Optimiser pour la production**

**Compression GZIP :**

```apache
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript
</IfModule>
```

**Cache navigateur :**

```apache
<FilesMatch "\.(jpg|jpeg|png|gif|css|js)$">
    Header set Cache-Control "max-age=2592000, public"
</FilesMatch>
```

**Minifier CSS/JS :**

Utilise des outils comme :
- CSS Minifier : https://cssminifier.com/
- JavaScript Minifier : https://javascript-minifier.com/

---

**5. Déployer en production**

**Acheter un nom de domaine :**
- OVH, Gandi, Namecheap, etc.
- Coût : 5-15 €/an

**Configurer HTTPS avec Let's Encrypt :**

```bash
sudo certbot --apache -d ton-domaine.com
```

**Configurer le pare-feu :**

```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```

---

## [COURS] CONCLUSION DE L'EXERCICE 1

**[OK] Félicitations ! Tu as créé et déployé un site portfolio complet avec Apache !**

**Ce que tu as appris :**
- Structurer un projet web multi-pages
- Créer du HTML/CSS responsive
- Configurer un Virtual Host Apache
- Gérer les permissions de fichiers Linux
- Utiliser le fichier hosts pour tester localement
- Lire et interpréter les logs Apache
- Diagnostiquer et corriger des erreurs courantes

**Compétences acquises :**
- [OK] Organisation de projet web
- [OK] Configuration Apache (niveau débutant-intermédiaire)
- [OK] HTML/CSS responsive
- [OK] Permissions Linux
- [OK] Dépannage de base

**Temps moyen de réalisation :** 2-3 heures

**Prochaine étape :** Exercice 2 - HTTPS avec certificat auto-signé ! [VERROUILLE]

---

---

(À cause de la limite de longueur, je vais créer la suite dans le prochain fichier...)


---

# [JAUNE] EXERCICE 2 : HTTPS AVEC CERTIFICAT AUTO-SIGNÉ

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu travailles pour une startup qui développe une application web interne. L'équipe te demande de sécuriser le site avec HTTPS pour l'environnement de développement, avant de déployer en production avec un vrai certificat.

### Objectifs

- Créer un certificat SSL auto-signé
- Configurer Apache pour HTTPS (port 443)
- Rediriger automatiquement HTTP -> HTTPS
- Comprendre les différences entre certificat auto-signé et certificat reconnu

### Contraintes

- Le site portfolio de l'exercice 1 doit servir de base
- HTTPS doit fonctionner avec `https://portfolio.local`
- HTTP doit rediriger automatiquement vers HTTPS
- Temps estimé : 1-2 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Créer un certificat SSL/TLS avec OpenSSL
- [OK] Comprendre la structure d'un certificat (clé privée, clé publique, CSR)
- [OK] Configurer Apache pour HTTPS
- [OK] Gérer les protocoles SSL/TLS sécurisés
- [OK] Configurer les suites de chiffrement
- [OK] Rediriger HTTP vers HTTPS
- [OK] Diagnostiquer les problèmes SSL

---

## [DOCS] PRÉREQUIS

- Exercice 1 terminé (site portfolio fonctionnel en HTTP)
- Module `mod_ssl` Apache disponible
- OpenSSL installé
- Compréhension basique de la cryptographie asymétrique

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Comprendre SSL/TLS et les certificats

**Avant de plonger dans la pratique, comprenons la théorie.**

#### Qu'est-ce que SSL/TLS ?

**SSL** = Secure Sockets Layer (ancien nom, déprécié)
**TLS** = Transport Layer Security (nom actuel)

**Rôle :** Chiffrer les communications entre client et serveur

**Analogie :**
HTTP = Envoyer une carte postale (tout le monde peut lire)
HTTPS = Envoyer une lettre dans une enveloppe scellée

---

#### Comment fonctionne HTTPS ?

**1. Le client demande une connexion HTTPS**

```
Client -> Serveur : "Bonjour, je veux parler en HTTPS"
```

---

**2. Le serveur envoie son certificat**

```
Serveur -> Client : "Voici mon certificat SSL (ma carte d'identité)"
```

Le certificat contient :
- Nom de domaine du serveur
- Clé publique du serveur
- Signature d'une autorité de certification (CA)
- Date de validité

---

**3. Le client vérifie le certificat**

```
Client : "Est-ce que je fais confiance à ce certificat ?"
```

**Vérifications :**
- [OK] Le certificat est-il signé par une CA reconnue ?
- [OK] Le nom de domaine correspond-il ?
- [OK] Le certificat est-il encore valide (pas expiré) ?

**Si certificat auto-signé :**
[X] Pas signé par une CA reconnue
-> Le navigateur affiche un avertissement

---

**4. Établissement d'une session chiffrée**

```
Client <-> Serveur : Négociation d'une clé de session symétrique
```

**Chiffrement asymétrique (RSA, etc.) :**
- Utilisé pour échanger la clé de session
- Lent mais sécurisé

**Chiffrement symétrique (AES, etc.) :**
- Utilisé pour chiffrer les données
- Rapide et efficace

---

**5. Communication chiffrée**

```
Client <-> Serveur : Toutes les données sont chiffrées avec la clé de session
```

Même si quelqu'un intercepte le trafic, il ne peut pas le déchiffrer.

---

#### Structure d'un certificat SSL

**Un certificat SSL comprend :**

**1. Clé privée (.key)**
- Gardée SECRÈTE sur le serveur
- Utilisée pour déchiffrer les messages
- Jamais partagée !

**2. Certificat (.crt ou .pem)**
- Contient la clé PUBLIQUE
- Peut être partagé
- Signé par une autorité de certification (CA)

**3. CSR - Certificate Signing Request (optionnel)**
- Demande de signature de certificat
- Envoyée à une CA pour obtenir un vrai certificat
- Contient les infos du serveur (nom, organisation, etc.)

---

#### Certificat auto-signé vs Certificat signé par une CA

| Caractéristique | Auto-signé | CA (Let's Encrypt, etc.) |
|----------------|------------|--------------------------|
| **Coût** | Gratuit | Gratuit (Let's Encrypt) ou payant |
| **Confiance navigateur** | [ATTENTION] Avertissement | [OK] Reconnu |
| **Usage** | Développement, intranet | Production, sites publics |
| **Validité** | Définie par toi | Généralement 90 jours |
| **Renouvellement** | Manuel | Automatisable |

---

### ÉTAPE 2 : Activer le module SSL d'Apache

```bash
sudo a2enmod ssl
```

**Résultat :**

```
Considering dependency setenvif for ssl:
Module setenvif already enabled
Considering dependency mime for ssl:
Module mime already enabled
Considering dependency socache_shmcb for ssl:
Enabling module socache_shmcb.
Enabling module ssl.
To activate the new configuration, you need to run:
  systemctl restart apache2
```

**Explication des modules activés :**

**ssl**
- Module principal pour HTTPS
- Gère les certificats, les connexions SSL/TLS

**socache_shmcb**
- Shared Memory Cache Shared Memory Cyclic Buffer
- Cache pour les sessions SSL
- Améliore les performances (évite de renégocier à chaque requête)

**setenvif** et **mime**
- Dépendances nécessaires au module SSL

---

**Redémarrer Apache :**

```bash
sudo systemctl restart apache2
```

---

**Vérifier que le module est chargé :**

```bash
apache2ctl -M | grep ssl
```

**Résultat :**

```
ssl_module (shared)
socache_shmcb_module (shared)
```

**[OK] Module SSL activé !**

---

### ÉTAPE 3 : Créer le certificat SSL auto-signé

#### Créer un dossier pour les certificats

```bash
sudo mkdir -p /etc/apache2/ssl
```

**Explication :**
- `/etc/apache2/ssl/` = Emplacement conventionnel pour les certificats Apache
- `-p` = Crée les dossiers parents si nécessaires

---

#### Générer la clé privée et le certificat en une commande

```bash
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/apache2/ssl/portfolio.key \
  -out /etc/apache2/ssl/portfolio.crt
```

**Décortiquons cette commande :**

**`openssl`**
- Outil de cryptographie en ligne de commande
- Très puissant, utilisé partout

**`req`**
- Subcommand pour créer des demandes de certificat
- Peut aussi créer des certificats auto-signés

**`-x509`**
- Génère un certificat X.509 auto-signé
- X.509 = Standard pour les certificats numériques
- Sans `-x509`, génère une CSR (demande de signature)

**`-nodes`**
- NO DES (Data Encryption Standard)
- La clé privée ne sera PAS protégée par mot de passe
- Nécessaire pour qu'Apache puisse démarrer automatiquement
- Sinon, il faudrait entrer le mot de passe à chaque redémarrage

**`-days 365`**
- Validité du certificat : 365 jours (1 an)
- Après 1 an, le certificat expire
- Il faudra en créer un nouveau

**`-newkey rsa:2048`**
- Génère une NOUVELLE clé RSA de 2048 bits
- RSA = Algorithme de chiffrement asymétrique
- 2048 bits = Taille de la clé (sécurité suffisante)
- Aujourd'hui, on peut utiliser 4096 bits pour plus de sécurité

**`-keyout /etc/apache2/ssl/portfolio.key`**
- Chemin de sortie de la clé PRIVÉE
- Extension `.key` par convention

**`-out /etc/apache2/ssl/portfolio.crt`**
- Chemin de sortie du CERTIFICAT
- Extension `.crt` ou `.pem` par convention

---

**Tu seras interrogé :**

```
Country Name (2 letter code) [AU]:
```

**Entre le code pays (2 lettres) :**

```
SN
```

**Explication :**
- Code ISO 3166-1 alpha-2
- SN = Sénégal
- FR = France
- US = États-Unis
- etc.

---

```
State or Province Name (full name) [Some-State]:
```

**Entre l'état ou la région :**

```
Dakar
```

---

```
Locality Name (eg, city) []:
```

**Entre la ville :**

```
Dakar
```

---

```
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
```

**Entre le nom de ton organisation/entreprise :**

```
MonEntreprise
```

**Ou si personnel :**

```
Développeur Indépendant
```

---

```
Organizational Unit Name (eg, section) []:
```

**Entre le département (optionnel) :**

```
IT
```

**Ou laisse vide (appuie sur Entrée).**

---

```
Common Name (e.g. server FQDN or YOUR name) []:
```

**[ATTENTION] LE PLUS IMPORTANT !**

**Entre le nom de domaine EXACT :**

```
portfolio.local
```

**Explication :**
- **Common Name (CN)** = Nom de domaine du serveur
- DOIT correspondre EXACTEMENT au `ServerName` du Virtual Host
- DOIT correspondre à l'URL que tu tapes dans le navigateur

**Si tu tapes `https://portfolio.local`, le CN doit être `portfolio.local`**

**Si le CN ne correspond pas, le navigateur affichera une erreur de certificat.**

---

```
Email Address []:
```

**Entre ton email :**

```
contact@example.com
```

**Ou laisse vide.**

---

**Résultat :**

```
Generating a RSA private key
...........+++++
.................................+++++
writing new private key to '/etc/apache2/ssl/portfolio.key'
-----
```

**[OK] Certificat et clé privée créés !**

---

#### Vérifier les fichiers créés

```bash
ls -lh /etc/apache2/ssl/
```

**Résultat :**

```
-rw-r--r-- 1 root root 1.3K Dec 16 12:00 portfolio.crt
-rw------- 1 root root 1.7K Dec 16 12:00 portfolio.key
```

**Explications des permissions :**

**`portfolio.crt` : `-rw-r--r--` (644)**
- Certificat PUBLIC
- Lecture pour tout le monde OK
- Sera envoyé aux clients

**`portfolio.key` : `-rw-------` (600)**
- Clé PRIVÉE
- [ATTENTION] SEUL le propriétaire (root) peut lire
- JAMAIS partager cette clé !
- Si quelqu'un obtient cette clé, il peut se faire passer pour ton serveur

---

#### Examiner le contenu du certificat

**Voir les détails du certificat :**

```bash
sudo openssl x509 -in /etc/apache2/ssl/portfolio.crt -text -noout
```

**Résultat (extrait) :**

```
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            xx:xx:xx:xx:xx:xx:xx:xx
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = SN, ST = Dakar, L = Dakar, O = MonEntreprise, CN = portfolio.local
        Validity
            Not Before: Dec 16 12:00:00 2024 GMT
            Not After : Dec 16 12:00:00 2025 GMT
        Subject: C = SN, ST = Dakar, L = Dakar, O = MonEntreprise, CN = portfolio.local
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:xx:xx:xx:...
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha256WithRSAEncryption
         xx:xx:xx:...
```

**Points importants :**

**Issuer (Émetteur) :**
```
Issuer: ... CN = portfolio.local
```

C'est nous-mêmes ! (auto-signé)

**Subject (Sujet) :**
```
Subject: ... CN = portfolio.local
```

Identique à l'émetteur (certificat auto-signé)

**Validité :**
```
Not Before: Dec 16 12:00:00 2024 GMT
Not After : Dec 16 12:00:00 2025 GMT
```

Valide pendant 1 an.

**Clé publique :**
```
RSA Public-Key: (2048 bit)
```

Taille de la clé : 2048 bits (sécurisé)

---

### ÉTAPE 4 : Configurer le Virtual Host HTTPS

**Créer un nouveau fichier de configuration pour HTTPS :**

```bash
sudo nano /etc/apache2/sites-available/portfolio-ssl.conf
```

**Contenu :**

```apache
# ═══════════════════════════════════════════════════════════════
# VIRTUAL HOST - PORTFOLIO (HTTPS)
# ═══════════════════════════════════════════════════════════════
# Description : Site portfolio sécurisé avec SSL/TLS
# Port : 443 (HTTPS)
# ═══════════════════════════════════════════════════════════════

<VirtualHost *:443>
    # ───────────────────────────────────────────────────────────
    # IDENTIFICATION DU SERVEUR
    # ───────────────────────────────────────────────────────────
    
    ServerName portfolio.local
    ServerAdmin contact@example.com
    
    DocumentRoot /var/www/portfolio
    
    # ───────────────────────────────────────────────────────────
    # CONFIGURATION SSL/TLS
    # ───────────────────────────────────────────────────────────
    
    # Activer le moteur SSL pour ce Virtual Host
    SSLEngine on
    
    # Explication :
    # Sans cette directive, HTTPS ne fonctionnera pas
    # Même avec un certificat valide
    
    # ───────────────────────────────────────────────────────────
    # CERTIFICAT ET CLÉ PRIVÉE
    # ───────────────────────────────────────────────────────────
    
    # Chemin vers le certificat SSL (clé publique)
    SSLCertificateFile /etc/apache2/ssl/portfolio.crt
    
    # Chemin vers la clé privée
    SSLCertificateKeyFile /etc/apache2/ssl/portfolio.key
    
    # Explication :
    # SSLCertificateFile = Certificat (envoyé aux clients)
    # SSLCertificateKeyFile = Clé privée (reste sur le serveur)
    #
    # Ces deux fichiers DOIVENT correspondre !
    # La clé privée a généré la clé publique du certificat
    
    # ───────────────────────────────────────────────────────────
    # PROTOCOLES SSL/TLS
    # ───────────────────────────────────────────────────────────
    
    # Protocoles SSL/TLS autorisés
    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
    
    # Explication :
    # "all" = Tous les protocoles par défaut
    # "-SSLv2" = Désactiver SSLv2 (obsolète, vulnérable)
    # "-SSLv3" = Désactiver SSLv3 (vulnérable, attaque POODLE)
    # "-TLSv1" = Désactiver TLS 1.0 (obsolète)
    # "-TLSv1.1" = Désactiver TLS 1.1 (obsolète)
    #
    # Résultat : SEULEMENT TLS 1.2 et TLS 1.3 autorisés
    #
    # Pourquoi ?
    # - SSLv2/SSLv3 ont des failles de sécurité connues
    # - TLS 1.0/1.1 sont obsolètes et moins sécurisés
    # - TLS 1.2 (2008) = Minimum acceptable en 2024
    # - TLS 1.3 (2018) = Le plus récent et sécurisé
    #
    # Compatibilité :
    # - TLS 1.2 : Tous les navigateurs modernes (2014+)
    # - TLS 1.3 : Chrome 70+, Firefox 63+, Safari 12.1+ (2018+)
    
    # ───────────────────────────────────────────────────────────
    # SUITES DE CHIFFREMENT (CIPHER SUITES)
    # ───────────────────────────────────────────────────────────
    
    # Liste des algorithmes de chiffrement autorisés
    SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
    
    # Explication SIMPLE :
    # HIGH = Utiliser seulement les algorithmes "forts"
    # !aNULL = Interdire les algorithmes sans authentification
    # !MD5 = Interdire MD5 (obsolète, cassable)
    # !3DES = Interdire 3DES (lent et moins sécurisé)
    #
    # Résultat : Seulement AES, ChaCha20, etc. (sécurisés)
    
    # Configuration avancée (recommandée pour production) :
    # SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    
    # Décomposition (pour comprendre) :
    # ECDHE = Ephemeral Elliptic Curve Diffie-Hellman (échange de clés)
    # ECDSA/RSA = Algorithme de signature
    # AES128/AES256 = Algorithme de chiffrement symétrique
    # GCM = Galois/Counter Mode (mode de chiffrement)
    # SHA256/SHA384 = Fonction de hachage
    #
    # Exemple : ECDHE-RSA-AES128-GCM-SHA256
    # - Échange de clés : ECDHE
    # - Authentification : RSA
    # - Chiffrement : AES 128 bits en mode GCM
    # - Intégrité : SHA-256
    
    # ───────────────────────────────────────────────────────────
    # PRÉFÉRENCE DU SERVEUR
    # ───────────────────────────────────────────────────────────
    
    # Le serveur impose son ordre de préférence des algorithmes
    SSLHonorCipherOrder on
    
    # Explication :
    # Par défaut, c'est le CLIENT qui choisit l'algorithme
    # (parmi ceux proposés par le serveur)
    #
    # Avec SSLHonorCipherOrder on :
    # C'est le SERVEUR qui choisit le meilleur algorithme
    # Selon l'ordre de SSLCipherSuite
    #
    # Avantage : Force l'utilisation des algorithmes les plus sécurisés
    
    # ───────────────────────────────────────────────────────────
    # COMPRESSION SSL (DÉSACTIVÉE POUR SÉCURITÉ)
    # ───────────────────────────────────────────────────────────
    
    # Désactiver la compression SSL
    SSLCompression off
    
    # Explication :
    # La compression SSL est vulnérable à l'attaque CRIME
    # (Compression Ratio Info-leak Made Easy)
    #
    # L'attaque CRIME permet de deviner des secrets
    # en analysant la taille des données compressées
    #
    # Exemple simplifié :
    # Si "motdepasse123" compressé = 50 octets
    # Et "motdepasse124" compressé = 52 octets
    # Un attaquant peut deviner le mot de passe caractère par caractère
    #
    # Solution : Désactiver SSLCompression
    # Utiliser plutôt la compression HTTP (mod_deflate)
    # qui opère APRÈS le chiffrement SSL
    
    # ───────────────────────────────────────────────────────────
    # CONFIGURATION DU RÉPERTOIRE
    # ───────────────────────────────────────────────────────────
    
    <Directory /var/www/portfolio>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
        DirectoryIndex index.html
    </Directory>
    
    # ───────────────────────────────────────────────────────────
    # LOGS
    # ───────────────────────────────────────────────────────────
    
    ErrorLog ${APACHE_LOG_DIR}/portfolio-ssl-error.log
    CustomLog ${APACHE_LOG_DIR}/portfolio-ssl-access.log combined
    
    # Note : Logs séparés pour HTTPS
    # Facilite le monitoring et le debug
    
</VirtualHost>

# ═══════════════════════════════════════════════════════════════
# FIN DE LA CONFIGURATION HTTPS
# ═══════════════════════════════════════════════════════════════
```

**Sauvegarde : `Ctrl + O`, `Entrée`, `Ctrl + X`**

---

### ÉTAPE 5 : Configurer la redirection HTTP -> HTTPS

**Modifier le Virtual Host HTTP pour rediriger vers HTTPS :**

```bash
sudo nano /etc/apache2/sites-available/portfolio.conf
```

**Remplacer TOUT le contenu par :**

```apache
# ═══════════════════════════════════════════════════════════════
# VIRTUAL HOST - PORTFOLIO (HTTP -> HTTPS REDIRECT)
# ═══════════════════════════════════════════════════════════════
# Description : Redirige automatiquement HTTP vers HTTPS
# Port : 80 (HTTP)
# ═══════════════════════════════════════════════════════════════

<VirtualHost *:80>
    ServerName portfolio.local
    ServerAdmin contact@example.com
    
    # ───────────────────────────────────────────────────────────
    # REDIRECTION PERMANENTE VERS HTTPS
    # ───────────────────────────────────────────────────────────
    
    # Méthode 1 : Redirection simple avec la directive Redirect
    Redirect permanent / https://portfolio.local/
    
    # Explication :
    # "permanent" = Code HTTP 301 (redirection permanente)
    # "/" = Toutes les URLs
    # "https://portfolio.local/" = Destination
    #
    # Exemples de redirections :
    # http://portfolio.local/ -> https://portfolio.local/
    # http://portfolio.local/about.html -> https://portfolio.local/about.html
    # http://portfolio.local/css/style.css -> https://portfolio.local/css/style.css
    #
    # Le chemin est préservé automatiquement !
    
    # ───────────────────────────────────────────────────────────
    # MÉTHODE ALTERNATIVE : mod_rewrite (plus flexible)
    # ───────────────────────────────────────────────────────────
    
    # Décommenter si tu veux utiliser mod_rewrite à la place :
    
    # <IfModule mod_rewrite.c>
    #     RewriteEngine On
    #     
    #     # Vérifier si HTTPS est désactivé
    #     RewriteCond %{HTTPS} off
    #     
    #     # Rediriger vers HTTPS en préservant l'URL
    #     RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
    # </IfModule>
    
    # Explication de mod_rewrite :
    #
    # RewriteEngine On :
    # Active le moteur de réécriture d'URL
    #
    # RewriteCond %{HTTPS} off :
    # Condition : Si HTTPS est désactivé (= HTTP)
    # %{HTTPS} est une variable Apache
    # Valeurs possibles : "on" ou "off"
    #
    # RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] :
    # Règle de réécriture
    # ^(.*)$ = Capture TOUT ce qui suit le domaine
    # %{HTTP_HOST} = Le nom d'hôte (ex: portfolio.local)
    # %{REQUEST_URI} = L'URI complète (ex: /about.html)
    # [L] = Last (dernière règle, arrêter le traitement)
    # [R=301] = Redirect avec code 301 (permanent)
    #
    # Avantage de mod_rewrite :
    # Plus flexible, permet des règles complexes
    # Exemples :
    # - Rediriger seulement certaines pages
    # - Rediriger avec des conditions (IP, user-agent, etc.)
    
    # ───────────────────────────────────────────────────────────
    # LOGS (OPTIONNEL POUR LA REDIRECTION)
    # ───────────────────────────────────────────────────────────
    
    ErrorLog ${APACHE_LOG_DIR}/portfolio-http-error.log
    CustomLog ${APACHE_LOG_DIR}/portfolio-http-access.log combined
    
    # Ces logs enregistrent les tentatives d'accès en HTTP
    # Avant la redirection vers HTTPS
    # Utile pour le monitoring
    
</VirtualHost>

# ═══════════════════════════════════════════════════════════════
```

**Sauvegarde.**

---

### ÉTAPE 6 : Activer le site HTTPS

```bash
# Activer le site HTTPS
sudo a2ensite portfolio-ssl.conf

# Le site HTTP est déjà activé (exercice 1)
# Pas besoin de le réactiver
```

**Résultat :**

```
Enabling site portfolio-ssl.
To activate the new configuration, you need to run:
  systemctl reload apache2
```

---

**Vérifier la configuration :**

```bash
sudo apache2ctl configtest
```

**Résultat attendu :**

```
Syntax OK
```

**[OK] Configuration correcte !**

---

**Recharger Apache :**

```bash
sudo systemctl reload apache2
```

**Ou redémarrer :**

```bash
sudo systemctl restart apache2
```

---

**Vérifier qu'Apache écoute sur le port 443 :**

```bash
sudo netstat -tulpn | grep :443
```

**Résultat :**

```
tcp6  0  0 :::443  :::*  LISTEN  1234/apache2
```

**[OK] Apache écoute sur le port 443 (HTTPS) !**

---

### ÉTAPE 7 : Tester HTTPS dans le navigateur

**Ouvre ton navigateur et accède à :**

```
https://portfolio.local
```

**[ATTENTION] AVERTISSEMENT DE SÉCURITÉ**

**Tu verras un message comme :**

```
┌──────────────────────────────────────────────────┐
│  [ATTENTION] Votre connexion n'est pas privée             │
│                                                  │
│  Les pirates peuvent tenter de dérober vos      │
│  informations personnelles...                    │
│                                                  │
│  NET::ERR_CERT_AUTHORITY_INVALID                 │
│                                                  │
│  [Précédent]  [Paramètres avancés]               │
└──────────────────────────────────────────────────┘
```

**C'EST NORMAL ! [BRAVO]**

**Pourquoi cet avertissement ?**

Le navigateur ne fait pas confiance au certificat car :
- [X] Il n'est PAS signé par une CA reconnue (auto-signé)
- [X] Le navigateur ne connait pas l'"émetteur" (nous-mêmes)

**En production, tu utiliserais Let's Encrypt -> Pas d'avertissement.**

---

**Continuer malgré l'avertissement :**

**Chrome/Edge :**
1. Clique sur "Paramètres avancés"
2. Clique sur "Continuer vers portfolio.local (dangereux)"

**Firefox :**
1. Clique sur "Avancé"
2. Clique sur "Accepter le risque et poursuivre"

---

**[BRAVO] Tu devrais voir ton site en HTTPS ! [BRAVO]**

**Dans la barre d'adresse :**

```
[VERROUILLE] Pas sécurisé | https://portfolio.local
      ^
Cadenas barré ou triangle d'avertissement
```

**Clique sur le cadenas pour voir les détails du certificat :**

```
Certificat (invalide)
Émis à : portfolio.local
Émis par : portfolio.local (auto-signé)
Valide du : 16/12/2024
Valide jusqu'au : 16/12/2025
```

---

**Tester la redirection HTTP -> HTTPS :**

**Accède en HTTP :**

```
http://portfolio.local
```

**Tu devrais être automatiquement redirigé vers :**

```
https://portfolio.local
```

**[OK] Redirection fonctionnelle ! [BRAVO]**

---

### ÉTAPE 8 : Vérifier le chiffrement SSL

**Ouvrir les outils de développement (F12) -> Onglet "Security" (Sécurité)**

**Tu verras :**

```
┌────────────────────────────────────────┐
│  Connection                            │
│  Protocol version: TLS 1.3             │
│  Cipher suite: TLS_AES_128_GCM_SHA256  │
│  Key exchange: X25519                  │
│                                        │
│  Certificate                           │
│  Subject: portfolio.local              │
│  Issuer: portfolio.local               │
│  Valid from: Dec 16, 2024              │
│  Valid until: Dec 16, 2025             │
│  [ATTENTION] Self-signed                        │
└────────────────────────────────────────┘
```

**Explications :**

**Protocol version: TLS 1.3**
- Version du protocole TLS utilisée
- TLS 1.3 = La plus récente et sécurisée (2018)
- Si tu vois TLS 1.2, c'est OK aussi

**Cipher suite: TLS_AES_128_GCM_SHA256**
- Algorithmes de chiffrement utilisés
- AES_128_GCM = Chiffrement AES 128 bits en mode GCM
- SHA256 = Fonction de hachage pour l'intégrité

**Key exchange: X25519**
- Algorithme d'échange de clés
- X25519 = Courbe elliptique moderne et rapide

**[ATTENTION] Self-signed**
- Certificat auto-signé (pas de CA reconnue)

**[OK] La connexion est CHIFFRÉE, même avec un certificat auto-signé !**

---

### ÉTAPE 9 : Ajouter des headers de sécurité HTTPS

**Pour renforcer la sécurité, ajoutons des headers HTTP.**

**Éditer le Virtual Host HTTPS :**

```bash
sudo nano /etc/apache2/sites-available/portfolio-ssl.conf
```

**Ajouter AVANT `</VirtualHost>` :**

```apache
    # ───────────────────────────────────────────────────────────
    # HEADERS DE SÉCURITÉ HTTPS
    # ───────────────────────────────────────────────────────────
    
    <IfModule mod_headers.c>
        # HSTS : Force HTTPS pendant 1 an
        Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
        
        # Explication :
        # HSTS = HTTP Strict Transport Security
        # Force le navigateur à TOUJOURS utiliser HTTPS
        # Même si l'utilisateur tape http://
        #
        # max-age=31536000 = 365 jours (en secondes)
        # includeSubDomains = S'applique aussi aux sous-domaines
        #
        # Après la première visite en HTTPS :
        # Le navigateur se souvient pendant 1 an
        # Toute tentative d'accès en HTTP -> Automatiquement converti en HTTPS
        # AVANT même de contacter le serveur !
        #
        # [ATTENTION] Attention en développement :
        # Si tu actives HSTS puis désactives HTTPS,
        # le navigateur refusera de se connecter pendant 1 an
        # Solution : Vider le cache HSTS du navigateur
        
        # Protection contre le Clickjacking
        Header always set X-Frame-Options "SAMEORIGIN"
        
        # Explication :
        # Empêche d'afficher le site dans une <iframe> sur un autre domaine
        # Protège contre les attaques de type "clickjacking"
        #
        # Valeurs possibles :
        # DENY = Interdit toutes les iframes
        # SAMEORIGIN = Autorise seulement les iframes du même domaine
        # ALLOW-FROM https://example.com = Autorise un domaine spécifique (déprécié)
        
        # Protection XSS (Cross-Site Scripting)
        Header always set X-XSS-Protection "1; mode=block"
        
        # Explication :
        # Active le filtre anti-XSS du navigateur
        # 1 = Activé
        # mode=block = Bloque complètement la page si XSS détecté
        #
        # Note : Déprécié au profit de CSP, mais toujours utile
        # pour les vieux navigateurs
        
        # Empêcher le sniffing MIME
        Header always set X-Content-Type-Options "nosniff"
        
        # Explication :
        # Empêche le navigateur de "deviner" le type MIME
        # Le navigateur doit respecter le Content-Type envoyé
        #
        # Sans nosniff :
        # Si tu envoies un fichier .txt avec du JavaScript,
        # le navigateur pourrait l'exécuter (dangereux !)
        #
        # Avec nosniff :
        # Le navigateur respecte strictement le Content-Type
        
        # Content Security Policy (CSP)
        Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self';"
        
        # Explication :
        # CSP = Content Security Policy
        # Définie les sources autorisées pour chaque type de ressource
        #
        # default-src 'self' :
        # Par défaut, autoriser seulement les ressources du même domaine
        #
        # script-src 'self' 'unsafe-inline' :
        # Scripts : même domaine + scripts inline (<script>...</script>)
        # 'unsafe-inline' nécessaire pour les scripts dans le HTML
        # (Mieux vaut éviter en production)
        #
        # style-src 'self' 'unsafe-inline' :
        # CSS : même domaine + styles inline (<style>...</style>)
        #
        # img-src 'self' data: https: :
        # Images : même domaine + data: URLs + toutes les URLs HTTPS
        # data: pour les images base64
        # https: pour les images de CDN (ex: placeholder.com)
        #
        # font-src 'self' data: :
        # Polices : même domaine + data: URLs
        #
        # connect-src 'self' :
        # Connexions (fetch, XMLHttpRequest) : seulement même domaine
        #
        # frame-ancestors 'self' :
        # Qui peut afficher ce site dans une iframe
        # 'self' = Seulement nous-mêmes
        #
        # [ATTENTION] CSP est TRÈS strict !
        # Si quelque chose ne fonctionne pas,
        # regarde la console du navigateur (F12)
        # Elle te dira ce qui est bloqué
        
        # Referrer Policy
        Header always set Referrer-Policy "strict-origin-when-cross-origin"
        
        # Explication :
        # Contrôle les informations envoyées dans l'en-tête Referer
        #
        # strict-origin-when-cross-origin :
        # - Même origine : Envoie l'URL complète
        # - Origine différente (HTTPS -> HTTPS) : Envoie seulement l'origine
        # - Origine différente (HTTPS -> HTTP) : N'envoie RIEN
        #
        # Protège la vie privée en ne révélant pas trop d'infos
        
        # Permissions Policy (anciennement Feature Policy)
        Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
        
        # Explication :
        # Désactive certaines fonctionnalités du navigateur
        #
        # geolocation=() : Désactive la géolocalisation
        # microphone=() : Désactive l'accès au micro
        # camera=() : Désactive l'accès à la caméra
        #
        # Pourquoi ?
        # Un site portfolio n'a pas besoin de ces permissions
        # Renforce la sécurité et la vie privée
        #
        # Pour autoriser :
        # geolocation=(self) : Autoriser pour ce site
        # geolocation=(self "https://example.com") : Autoriser pour ce site et example.com
    </IfModule>
```

**Sauvegarde.**

---

**Activer le module headers (si pas déjà fait) :**

```bash
sudo a2enmod headers
sudo systemctl reload apache2
```

---

**Tester les headers :**

```bash
curl -I https://portfolio.local
```

**[ATTENTION] Erreur de certificat avec curl ! C'est normal.**

**Pour ignorer l'erreur de certificat :**

```bash
curl -Ik https://portfolio.local
```

**Résultat :**

```
HTTP/1.1 200 OK
Date: Mon, 16 Dec 2024 12:00:00 GMT
Server: Apache
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'; ...
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
Content-Type: text/html
```

**[OK] Tous les headers de sécurité sont présents !**

---

### [OK] TESTS DE VALIDATION

**1. HTTPS fonctionne**

- [ ] `https://portfolio.local` charge correctement
- [ ] Le cadenas (barré) s'affiche dans la barre d'adresse
- [ ] Les détails du certificat sont accessibles (clic sur le cadenas)
- [ ] Le certificat indique "auto-signé"

---

**2. Redirection HTTP -> HTTPS**

- [ ] `http://portfolio.local` redirige automatiquement vers `https://`
- [ ] Le chemin est préservé (ex: `http://.../about.html` -> `https://.../about.html`)

---

**3. Protocoles sécurisés**

**Dans les outils de développement (F12 -> Security) :**

- [ ] Protocol version: TLS 1.2 ou TLS 1.3
- [ ] Cipher suite: AES ou ChaCha20 (pas RC4, 3DES, etc.)

---

**4. Headers de sécurité**

```bash
curl -Ik https://portfolio.local | grep -E "Strict-Transport|X-Frame|X-XSS|X-Content-Type|Content-Security|Referrer|Permissions"
```

- [ ] Strict-Transport-Security présent
- [ ] X-Frame-Options présent
- [ ] X-XSS-Protection présent
- [ ] X-Content-Type-Options présent
- [ ] Content-Security-Policy présent
- [ ] Referrer-Policy présent
- [ ] Permissions-Policy présent

---

**5. Logs**

```bash
sudo tail -20 /var/log/apache2/portfolio-ssl-access.log
```

- [ ] Les requêtes HTTPS sont loguées
- [ ] Code de statut 200 (OK)

---

### [ROUGE] ERREURS COURANTES ET SOLUTIONS

#### Erreur 1 : "Connection refused" sur le port 443

**Symptôme :**

```
ERR_CONNECTION_REFUSED
```

**Cause : Apache n'écoute pas sur le port 443**

**Solution :**

**1. Vérifier qu'Apache tourne :**

```bash
sudo systemctl status apache2
```

---

**2. Vérifier que le module SSL est chargé :**

```bash
apache2ctl -M | grep ssl
```

**Si absent :**

```bash
sudo a2enmod ssl
sudo systemctl restart apache2
```

---

**3. Vérifier que le site HTTPS est activé :**

```bash
ls -la /etc/apache2/sites-enabled/ | grep portfolio-ssl
```

**Si absent :**

```bash
sudo a2ensite portfolio-ssl.conf
sudo systemctl reload apache2
```

---

**4. Vérifier qu'Apache écoute sur le port 443 :**

```bash
sudo netstat -tulpn | grep :443
```

**Si rien :**

Vérifie `/etc/apache2/ports.conf` :

```apache
<IfModule ssl_module>
    Listen 443
</IfModule>
```

---

**5. Vérifier le pare-feu :**

```bash
sudo ufw status
```

**Si le port 443 est bloqué :**

```bash
sudo ufw allow 443/tcp
```

---

#### Erreur 2 : "SSL received a record that exceeded the maximum permissible length"

**Symptôme :**

```
ERR_SSL_PROTOCOL_ERROR
```

**Cause : Apache écoute en HTTP sur le port 443 au lieu de HTTPS**

**Solution :**

**Vérifier que `SSLEngine on` est présent dans le Virtual Host :**

```bash
sudo grep -i "SSLEngine" /etc/apache2/sites-enabled/portfolio-ssl.conf
```

**Si absent, ajouter :**

```apache
SSLEngine on
```

---

#### Erreur 3 : "NET::ERR_CERT_COMMON_NAME_INVALID"

**Symptôme :**

```
Le nom du certificat ne correspond pas
```

**Cause : Le Common Name (CN) du certificat ne correspond pas au ServerName**

**Solution :**

**1. Vérifier le CN du certificat :**

```bash
sudo openssl x509 -in /etc/apache2/ssl/portfolio.crt -noout -subject
```

**Résultat :**

```
subject= /C=SN/ST=Dakar/L=Dakar/O=MonEntreprise/CN=portfolio.local
```

**Le CN doit être `portfolio.local`**

---

**2. Vérifier le ServerName du Virtual Host :**

```bash
sudo grep ServerName /etc/apache2/sites-enabled/portfolio-ssl.conf
```

**Résultat :**

```
ServerName portfolio.local
```

**Doivent être IDENTIQUES !**

---

**3. Si différents, recréer le certificat avec le bon CN**

---

#### Erreur 4 : La redirection HTTP -> HTTPS ne fonctionne pas

**Symptôme :**

`http://portfolio.local` ne redirige PAS vers `https://`

**Solutions :**

**1. Vérifier la directive Redirect :**

```bash
sudo grep -i redirect /etc/apache2/sites-enabled/portfolio.conf
```

**Doit contenir :**

```apache
Redirect permanent / https://portfolio.local/
```

---

**2. Vérifier que le site HTTP est activé :**

```bash
ls -la /etc/apache2/sites-enabled/ | grep portfolio.conf
```

**Si absent :**

```bash
sudo a2ensite portfolio.conf
sudo systemctl reload apache2
```

---

**3. Vider le cache du navigateur**

Parfois le navigateur garde en cache une ancienne version.

**Chrome/Edge : `Ctrl + Shift + R`**
**Firefox : `Ctrl + F5`**

---

#### Erreur 5 : Headers de sécurité absents

**Symptôme :**

```bash
curl -Ik https://portfolio.local | grep Strict-Transport
# Aucun résultat
```

**Cause : Module mod_headers pas chargé**

**Solution :**

```bash
sudo a2enmod headers
sudo systemctl reload apache2
```

---

### [IMPORTANT] POINTS CLÉS À RETENIR

**1. Différence HTTP vs HTTPS**
- HTTP = Non chiffré (port 80)
- HTTPS = Chiffré avec SSL/TLS (port 443)

**2. Certificat SSL**
- Certificat (.crt) = Clé publique (peut être partagé)
- Clé privée (.key) = SECRÈTE (ne JAMAIS partager)
- Common Name (CN) = DOIT correspondre au nom de domaine

**3. Certificat auto-signé vs CA**
- Auto-signé = Gratuit, avertissement navigateur, OK pour dev/intranet
- CA (Let's Encrypt, etc.) = Reconnu, pas d'avertissement, pour production

**4. Protocoles sécurisés**
- TLS 1.2 et TLS 1.3 = Sécurisés
- SSLv2, SSLv3, TLS 1.0, TLS 1.1 = Obsolètes, à désactiver

**5. Headers de sécurité**
- HSTS = Force HTTPS
- X-Frame-Options = Anti-clickjacking
- CSP = Contrôle des ressources autorisées
- X-XSS-Protection = Filtre anti-XSS

---

### [RAPIDE] POUR ALLER PLUS LOIN

**1. Passer à un vrai certificat Let's Encrypt**

**Si tu as un vrai nom de domaine :**

```bash
sudo apt install certbot python3-certbot-apache -y
sudo certbot --apache -d ton-domaine.com
```

**Renouvellement automatique tous les 90 jours :**

```bash
sudo systemctl list-timers | grep certbot
```

---

**2. Tester la sécurité SSL avec SSL Labs**

**https://www.ssllabs.com/ssltest/**

En production, ce site teste et note la configuration SSL (A+, A, B, etc.)

**Pour tester en local, utilise `testssl.sh` :**

```bash
git clone https://github.com/drwetter/testssl.sh.git
cd testssl.sh
./testssl.sh https://portfolio.local
```

---

**3. Certificat wildcard (*.domaine.com)**

Couvre tous les sous-domaines :
- `https://www.domaine.com`
- `https://blog.domaine.com`
- `https://shop.domaine.com`

**Avec Let's Encrypt :**

```bash
sudo certbot --apache -d domaine.com -d *.domaine.com --preferred-challenges dns
```

Nécessite de valider via DNS (ajouter un enregistrement TXT).

---

**4. HTTP/2 et HTTP/3**

**HTTP/2** = Multiplexage, meilleur que HTTP/1.1

**Activer HTTP/2 :**

```bash
sudo a2enmod http2
```

**Dans le Virtual Host HTTPS :**

```apache
Protocols h2 http/1.1
```

**HTTP/3** = Protocole QUIC (UDP), encore plus rapide
Nécessite des modules expérimentaux.

---

## [COURS] CONCLUSION DE L'EXERCICE 2

**[OK] Félicitations ! Tu as sécurisé ton site avec HTTPS !**

**Ce que tu as appris :**
- Créer un certificat SSL auto-signé avec OpenSSL
- Comprendre la structure d'un certificat (clé privée, clé publique, CN)
- Configurer Apache pour HTTPS (port 443)
- Désactiver les protocoles obsolètes (SSLv2, SSLv3, TLS 1.0/1.1)
- Configurer les suites de chiffrement sécurisées
- Rediriger HTTP vers HTTPS automatiquement
- Ajouter des headers de sécurité (HSTS, CSP, X-Frame-Options, etc.)
- Diagnostiquer et corriger les erreurs SSL courantes

**Compétences acquises :**
- [OK] Cryptographie SSL/TLS (niveau intermédiaire)
- [OK] Configuration Apache HTTPS
- [OK] Sécurité web (headers, protocoles)
- [OK] Certificats SSL (création, validation, CN)
- [OK] Redirection HTTP -> HTTPS

**Temps moyen de réalisation :** 1-2 heures

**Prochaine étape :** Exercice 3 - Site PHP avec MySQL (mini-blog) ! [POSTGRES]

---

---

# [JAUNE] EXERCICE 3 : SITE PHP AVEC MYSQL (MINI-BLOG)

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu es développeur dans une agence web. Un client te demande de créer un mini-blog pour publier des articles. Le blog doit permettre de :
- Afficher une liste d'articles
- Voir le détail d'un article
- Ajouter un nouvel article (interface d'administration simple)
- Tout doit être dynamique avec PHP et MySQL

### Objectifs techniques

- Installer et configurer PHP avec Apache (PHP-FPM)
- Installer et sécuriser MySQL/MariaDB
- Créer une base de données et des tables
- Développer un blog fonctionnel en PHP procédural
- Gérer les connexions à la base de données
- Afficher des données dynamiques
- Insérer des données via formulaire
- Protéger contre les injections SQL (PDO préparé)

### Contraintes

- PHP 8.1+ avec PHP-FPM (meilleure performance que mod_php)
- MySQL 8.0 ou MariaDB 10.6+
- Code PHP sécurisé (requêtes préparées, échappement HTML)
- Design simple mais fonctionnel
- Temps estimé : 3-4 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Installer et configurer PHP avec Apache (PHP-FPM)
- [OK] Installer et configurer MySQL/MariaDB
- [OK] Créer et gérer une base de données
- [OK] Écrire du code PHP pour interagir avec MySQL
- [OK] Utiliser PDO (PHP Data Objects) pour la sécurité
- [OK] Protéger contre les injections SQL
- [OK] Gérer les sessions PHP
- [OK] Structurer un projet PHP simple
- [OK] Déboguer les erreurs PHP et MySQL

---

## [DOCS] PRÉREQUIS

- Exercice 1 terminé (compréhension des Virtual Hosts)
- Connaissances de base en HTML/CSS
- Notions de programmation (variables, conditions, boucles)
- Compréhension basique de SQL (SELECT, INSERT)

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Installer PHP et ses extensions

```bash
# Mettre à jour la liste des paquets
sudo apt update

# Installer PHP 8.1 et PHP-FPM
sudo apt install php8.1 php8.1-fpm -y

# Installer les extensions PHP nécessaires
sudo apt install php8.1-mysql php8.1-mbstring php8.1-xml php8.1-curl php8.1-gd -y
```

**Explication des paquets :**

**php8.1**
- Paquet principal de PHP version 8.1
- Contient l'interpréteur PHP et les modules de base

**php8.1-fpm**
- FPM = FastCGI Process Manager
- Meilleure performance que mod_php (module Apache)
- Séparation des processus PHP et Apache
- Gestion avancée des pools de processus

**Pourquoi PHP-FPM au lieu de mod_php ?**

| Critère | mod_php | PHP-FPM |
|---------|---------|---------|
| **Performance** | Moyen | * Excellent |
| **Mémoire** | 1 processus PHP par worker Apache | Pool de processus dédiés |
| **Scaling** | Limité | * Très flexible |
| **Compatibilité** | Apache seulement | Apache, Nginx, etc. |
| **Configuration** | Dans httpd.conf | php-fpm.conf + pools |

**En prod : TOUJOURS utiliser PHP-FPM !**

---

**php8.1-mysql**
- Extension pour se connecter à MySQL/MariaDB
- Contient PDO_MySQL (recommandé) et mysqli

**php8.1-mbstring**
- Multi-Byte String
- Gestion des caractères multi-octets (UTF-8, etc.)
- Nécessaire pour manipuler du texte avec accents

**php8.1-xml**
- Parsing XML, XPath, SimpleXML
- Souvent requis par des frameworks

**php8.1-curl**
- Extension cURL pour faire des requêtes HTTP
- Utile pour consommer des API

**php8.1-gd**
- GD Graphics Library
- Manipulation d'images (resize, crop, watermark)
- Génération de thumbnails

---

**Vérifier l'installation :**

```bash
# Version de PHP
php -v
```

**Résultat :**

```
PHP 8.1.12 (cli) (built: Oct 28 2023 10:15:00) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.1.12, Copyright (c) Zend Technologies
    with Zend OPcache v8.1.12, Copyright (c), by Zend Technologies
```

**[OK] PHP 8.1 installé !**

---

**Vérifier que PHP-FPM tourne :**

```bash
sudo systemctl status php8.1-fpm
```

**Résultat :**

```
[BLACK_CIRCLE] php8.1-fpm.service - The PHP 8.1 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.1-fpm.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2024-12-16 14:00:00 UTC; 1min ago
   Main PID: 12345 (php-fpm8.1)
      Tasks: 3 (limit: 4915)
     Memory: 15.2M
        CPU: 234ms
```

**[OK] PHP-FPM actif et en cours d'exécution !**

---

### ÉTAPE 2 : Configurer Apache pour PHP-FPM

**Activer les modules nécessaires :**

```bash
# Module proxy_fcgi : Communiquer avec PHP-FPM via FastCGI
sudo a2enmod proxy_fcgi

# Module setenvif : Définir des variables d'environnement conditionnelles
sudo a2enmod setenvif
```

**Résultat :**

```
Considering dependency proxy for proxy_fcgi:
Enabling module proxy.
Enabling module proxy_fcgi.
Module setenvif already enabled
To activate the new configuration, you need to run:
  systemctl reload apache2
```

---

**Activer la configuration PHP-FPM pour Apache :**

```bash
sudo a2enconf php8.1-fpm
```

**Résultat :**

```
Enabling conf php8.1-fpm.
To activate the new configuration, you need to run:
  systemctl reload apache2
```

**Cette commande crée un lien symbolique :**

```
/etc/apache2/conf-enabled/php8.1-fpm.conf 
  -> /etc/apache2/conf-available/php8.1-fpm.conf
```

---

**Examiner la configuration PHP-FPM d'Apache :**

```bash
cat /etc/apache2/conf-available/php8.1-fpm.conf
```

**Contenu :**

```apache
<FilesMatch ".+\.ph(ar|p|tml)$">
    SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
</FilesMatch>
```

**Explication ligne par ligne :**

**`<FilesMatch ".+\.ph(ar|p|tml)$">`**
- Directive qui s'applique aux fichiers correspondant au pattern
- `.+` = Au moins 1 caractère
- `\.` = Point littéral (échappé car `.` a une signification spéciale en regex)
- `ph(ar|p|tml)` = "ph" suivi de "ar" OU "p" OU "tml"
- `$` = Fin de ligne

**Fichiers concernés :**
- `.phar` = PHP Archive (fichiers exécutables PHP)
- `.php` = Fichiers PHP standards
- `.phtml` = PHP HTML (ancien style)

---

**`SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"`**

Décortiquons cette directive complexe :

**SetHandler**
- Définit le gestionnaire (handler) pour traiter ces fichiers

**proxy:**
- Utilise le module mod_proxy

**unix:/run/php/php8.1-fpm.sock**
- Socket UNIX (fichier spécial pour communication inter-processus)
- `/run/php/php8.1-fpm.sock` = Socket de PHP-FPM
- Apache se connecte à PHP-FPM via ce socket

**|** (pipe)
- Séparateur

**fcgi://localhost**
- Protocole FastCGI
- "localhost" = Pas d'importance ici (le socket est utilisé)

**Flux complet :**

```
1. Client demande http://blog.local/index.php
2. Apache reçoit la requête
3. Apache voit que c'est un fichier .php
4. Applique <FilesMatch> -> SetHandler
5. Envoie à PHP-FPM via le socket /run/php/php8.1-fpm.sock
6. PHP-FPM exécute index.php
7. PHP-FPM renvoie le résultat HTML à Apache
8. Apache renvoie au client
```

---

**Pourquoi socket UNIX et pas TCP ?**

**Socket UNIX (`/run/php/php8.1-fpm.sock`) :**
- [OK] Plus rapide (pas de stack réseau)
- [OK] Plus sécurisé (pas d'accès réseau)
- [OK] Moins de ressources
- [X] Marche seulement en local

**Socket TCP (`127.0.0.1:9000`) :**
- [X] Un peu plus lent
- [OK] Peut être sur un serveur distant
- [OK] Utile pour l'architecture distribuée

**En local : Toujours utiliser socket UNIX !**

---

**Recharger Apache :**

```bash
sudo systemctl reload apache2
```

---

### ÉTAPE 3 : Tester PHP avec Apache

**Créer un fichier de test PHP :**

```bash
# Créer le dossier du blog
sudo mkdir -p /var/www/blog

# Créer un fichier phpinfo.php
sudo nano /var/www/blog/phpinfo.php
```

**Contenu :**

```php
<?php
phpinfo();
?>
```

**`phpinfo()` affiche toutes les informations PHP :**
- Version de PHP
- Modules chargés
- Configuration (php.ini)
- Variables d'environnement
- Etc.

**[ATTENTION] À supprimer en production (info sensibles) !**

---

**Définir les permissions :**

```bash
sudo chown -R www-data:www-data /var/www/blog
sudo find /var/www/blog -type d -exec chmod 755 {} \;
sudo find /var/www/blog -type f -exec chmod 644 {} \;
```

---

**Créer un Virtual Host pour le blog :**

```bash
sudo nano /etc/apache2/sites-available/blog.conf
```

**Contenu :**

```apache
<VirtualHost *:80>
    ServerName blog.local
    ServerAdmin contact@example.com
    DocumentRoot /var/www/blog
    
    <Directory /var/www/blog>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
        DirectoryIndex index.php index.html
    </Directory>
    
    # Logs
    ErrorLog ${APACHE_LOG_DIR}/blog-error.log
    CustomLog ${APACHE_LOG_DIR}/blog-access.log combined
    
    # PHP est déjà configuré globalement via php8.1-fpm.conf
    # Pas besoin de redéfinir ici
</VirtualHost>
```

**Sauvegarde.**

---

**Activer le site :**

```bash
sudo a2ensite blog.conf
sudo systemctl reload apache2
```

---

**Ajouter dans le fichier hosts (/etc/hosts sur ton PC) :**

```
192.168.1.100    blog.local
```

**Remplace `192.168.1.100` par l'IP de ton serveur.**

---

**Tester dans le navigateur :**

```
http://blog.local/phpinfo.php
```

**[BRAVO] Tu devrais voir la page phpinfo ! [BRAVO]**

**Vérifications importantes :**

**En haut de la page :**
```
PHP Version 8.1.12
```

**Section "Server API" :**
```
Server API: FPM/FastCGI
```

**[OK] Si tu vois "FPM/FastCGI", PHP-FPM fonctionne !**
**[X] Si tu vois "Apache 2.0 Handler", c'est mod_php (pas optimal)**

---

**Dans la section "PDO" :**

Cherche "PDO drivers" :

```
PDO drivers: mysql, sqlite
```

**[OK] Le driver MySQL est présent !**

---

**Supprimer phpinfo.php (sécurité) :**

```bash
sudo rm /var/www/blog/phpinfo.php
```

---

### ÉTAPE 4 : Installer et configurer MySQL/MariaDB

**Installer MariaDB (alternative open-source à MySQL) :**

```bash
sudo apt install mariadb-server mariadb-client -y
```

**MariaDB vs MySQL :**

| Critère | MySQL | MariaDB |
|---------|-------|---------|
| **Licence** | GPL (Oracle) | GPL (MariaDB Foundation) |
| **Performance** | Bon | * Meilleur |
| **Compatibilité** | MySQL uniquement | MySQL + extensions |
| **Open Source** | Oui (+ versions propriétaires) | * Totalement |

**MariaDB est un fork de MySQL créé par les créateurs originaux de MySQL.**

**Les deux sont très similaires et presque 100% compatibles.**

---

**Vérifier que MariaDB tourne :**

```bash
sudo systemctl status mariadb
```

**Résultat :**

```
[BLACK_CIRCLE] mariadb.service - MariaDB 10.6.12 database server
     Loaded: loaded (/lib/systemd/system/mariadb.service; enabled; vendor preset: enabled)
     Active: active (running) since Mon 2024-12-16 14:30:00 UTC; 1min ago
   Main PID: 23456 (mariadbd)
      Tasks: 9 (limit: 4915)
     Memory: 76.2M
```

**[OK] MariaDB actif !**

---

**Sécuriser l'installation de MariaDB :**

```bash
sudo mysql_secure_installation
```

**Ce script interactif va :**
1. Demander le mot de passe root actuel (vide par défaut)
2. Proposer de définir un mot de passe root
3. Proposer de supprimer les utilisateurs anonymes
4. Proposer de désactiver la connexion root à distance
5. Proposer de supprimer la base de données de test
6. Proposer de recharger les privilèges

---

**Réponses recommandées :**

```
Enter current password for root (enter for none):
```

**Appuie sur Entrée (pas de mot de passe par défaut).**

---

```
Switch to unix_socket authentication [Y/n]
```

**Tape `n` (non).**

**Explication :**
- unix_socket = Authentification via le user Linux
- On préfère utiliser un mot de passe pour plus de flexibilité

---

```
Change the root password? [Y/n]
```

**Tape `Y` (oui).**

**Entre un mot de passe fort :**

```
New password: MotDePasseSecurise123!
Re-enter new password: MotDePasseSecurise123!
```

**[ATTENTION] Note bien ce mot de passe ! Tu en auras besoin.**

---

```
Remove anonymous users? [Y/n]
```

**Tape `Y` (oui).**

**Par défaut, MariaDB crée un user anonyme. Dangereux !**

---

```
Disallow root login remotely? [Y/n]
```

**Tape `Y` (oui).**

**Root ne pourra se connecter qu'en local (depuis le serveur).**

---

```
Remove test database and access to it? [Y/n]
```

**Tape `Y` (oui).**

**La base de données "test" n'est pas nécessaire.**

---

```
Reload privilege tables now? [Y/n]
```

**Tape `Y` (oui).**

**Applique les changements immédiatement.**

---

**Résultat :**

```
All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Thanks for using MariaDB!
```

**[OK] MariaDB sécurisé !**

---

**Se connecter à MariaDB :**

```bash
sudo mysql -u root -p
```

**Entre le mot de passe root que tu as défini.**

**Tu devrais voir :**

```
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 34
Server version: 10.6.12-MariaDB-0ubuntu0.22.04.1 Ubuntu 22.04

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]>
```

**Prompt `MariaDB [(none)]>` = Tu es connecté !**

---

### ÉTAPE 5 : Créer la base de données et les tables

**Dans le prompt MariaDB :**

```sql
-- Créer la base de données
CREATE DATABASE blog_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

**Explication :**

**CREATE DATABASE blog_db**
- Crée une base de données nommée "blog_db"

**CHARACTER SET utf8mb4**
- Encodage UTF-8 (4 octets)
- Supporte TOUS les caractères Unicode (emojis, chinois, arabe, etc.)
- utf8mb4 > utf8 (utf8 = max 3 octets, pas d'emojis)

**COLLATE utf8mb4_unicode_ci**
- Règles de tri et comparaison
- unicode_ci = Case Insensitive (insensible à la casse)
- "ABC" = "abc" dans les comparaisons

---

**Vérifier que la base est créée :**

```sql
SHOW DATABASES;
```

**Résultat :**

```
+--------------------+
| Database           |
+--------------------+
| blog_db            |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
```

**[OK] blog_db créé !**

---

**Créer un utilisateur dédié pour le blog :**

```sql
CREATE USER 'blog_user'@'localhost' IDENTIFIED BY 'BlogPassword123!';
```

**Explication :**

**CREATE USER 'blog_user'@'localhost'**
- Crée un utilisateur "blog_user"
- `@'localhost'` = Peut se connecter SEULEMENT depuis le serveur local
- Pour autoriser à distance : `@'%'` (déconseillé en prod)

**IDENTIFIED BY 'BlogPassword123!'**
- Définit le mot de passe

**[ATTENTION] Note bien ce mot de passe !**

---

**Donner les droits sur la base blog_db à blog_user :**

```sql
GRANT ALL PRIVILEGES ON blog_db.* TO 'blog_user'@'localhost';
```

**Explication :**

**GRANT ALL PRIVILEGES**
- Donne TOUS les droits (SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, etc.)

**ON blog_db.\***
- Sur la base blog_db
- `.*` = Toutes les tables de cette base

**TO 'blog_user'@'localhost'**
- À l'utilisateur blog_user (depuis localhost)

---

**Appliquer les changements :**

```sql
FLUSH PRIVILEGES;
```

**FLUSH PRIVILEGES recharge les tables de privilèges.**

---

**Sélectionner la base de données :**

```sql
USE blog_db;
```

**Résultat :**

```
Database changed
```

**Le prompt devient :**

```
MariaDB [blog_db]>
```

---

**Créer la table "articles" :**

```sql
CREATE TABLE articles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    titre VARCHAR(255) NOT NULL,
    contenu TEXT NOT NULL,
    auteur VARCHAR(100) NOT NULL,
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    date_modification DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

**Explication DÉTAILLÉE :**

---

**`id INT AUTO_INCREMENT PRIMARY KEY`**

**INT**
- Type entier (integer)
- Valeurs : -2,147,483,648 à 2,147,483,647
- Pour l'ID, on utilise des entiers positifs : 1, 2, 3, ...

**AUTO_INCREMENT**
- Incrémentation automatique
- Pas besoin de spécifier l'ID lors de l'insertion
- MySQL génère automatiquement : 1, puis 2, puis 3, etc.

**PRIMARY KEY**
- Clé primaire = Identifiant unique de chaque ligne
- Pas de doublons autorisés
- Index automatique (recherche rapide)
- Une seule PRIMARY KEY par table

---

**`titre VARCHAR(255) NOT NULL`**

**VARCHAR(255)**
- VARCHAR = Variable Character (chaîne de caractères de longueur variable)
- (255) = Maximum 255 caractères
- Utilise seulement l'espace nécessaire + 1 ou 2 octets pour la longueur
- Exemple : "Bonjour" utilise 7 octets (pas 255)

**Pourquoi 255 ?**
- Historiquement, limite avant MySQL 5.0.3
- Aujourd'hui, on peut aller jusqu'à 65,535
- Mais 255 suffit amplement pour un titre

**NOT NULL**
- Ce champ est OBLIGATOIRE
- Pas de valeur NULL autorisée
- L'insertion échouera si titre est absent

**VARCHAR vs TEXT vs CHAR :**

| Type | Taille max | Usage | Indexation |
|------|------------|-------|------------|
| CHAR(n) | n caractères (fixe) | Codes, hash (taille fixe) | [OK] Rapide |
| VARCHAR(n) | n caractères | Titres, noms, emails | [OK] Rapide |
| TEXT | 65,535 chars | Articles, descriptions | [ATTENTION] Lent |

---

**`contenu TEXT NOT NULL`**

**TEXT**
- Chaîne de longueur variable jusqu'à 65,535 caractères
- Parfait pour du contenu long (articles de blog)
- Stocké différemment de VARCHAR (hors de la ligne)

**Variantes de TEXT :**

| Type | Taille max | Usage |
|------|------------|-------|
| TINYTEXT | 255 chars | Très courts textes |
| TEXT | 65,535 chars (≈64 KB) | Articles courts |
| MEDIUMTEXT | 16,777,215 chars (≈16 MB) | Articles longs |
| LONGTEXT | 4,294,967,295 chars (≈4 GB) | Livres entiers |

---

**`auteur VARCHAR(100) NOT NULL`**

Nom de l'auteur de l'article (max 100 caractères).

---

**`date_creation DATETIME DEFAULT CURRENT_TIMESTAMP`**

**DATETIME**
- Type date ET heure
- Format : `YYYY-MM-DD HH:MM:SS`
- Exemple : `2024-12-16 14:30:00`
- Plage : `1000-01-01 00:00:00` à `9999-12-31 23:59:59`

**DEFAULT CURRENT_TIMESTAMP**
- Valeur par défaut = Date/heure ACTUELLE lors de l'insertion
- Pas besoin de spécifier la date lors de l'INSERT
- MySQL la remplira automatiquement

**DATETIME vs DATE vs TIMESTAMP :**

| Type | Format | Utilisation |
|------|--------|-------------|
| DATE | YYYY-MM-DD | Dates sans heure |
| DATETIME | YYYY-MM-DD HH:MM:SS | Dates avec heure |
| TIMESTAMP | YYYY-MM-DD HH:MM:SS | Horodatage (converti en UTC) |

**TIMESTAMP auto-converti en UTC (fuseau horaire).**
**DATETIME stocke la valeur littérale (pas de conversion).**

---

**`date_modification DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`**

**DEFAULT CURRENT_TIMESTAMP**
- Valeur initiale = Date/heure de création

**ON UPDATE CURRENT_TIMESTAMP**
- * À chaque UPDATE, la date se met à jour automatiquement !
- Très pratique pour tracker les modifications

**Exemple :**

```sql
-- Insertion
INSERT INTO articles (titre, contenu, auteur) VALUES (...);
-- date_creation = 2024-12-16 14:00:00
-- date_modification = 2024-12-16 14:00:00

-- Modification (le lendemain)
UPDATE articles SET contenu = '...' WHERE id = 1;
-- date_creation = 2024-12-16 14:00:00 (inchangé)
-- date_modification = 2024-12-17 10:30:00 (mis à jour !)
```

---

**`ENGINE=InnoDB`**

**InnoDB** = Moteur de stockage MySQL

**Moteurs disponibles :**

| Moteur | Transactions | Clés étrangères | Performance | Usage |
|--------|--------------|-----------------|-------------|-------|
| InnoDB | [OK] Oui | [OK] Oui | Bon | * Standard (défaut) |
| MyISAM | [X] Non | [X] Non | Lecture rapide | Ancien (déconseillé) |
| MEMORY | [X] Non | [X] Non | * Très rapide | Cache temporaire |

**InnoDB supporte :**
- Transactions (BEGIN, COMMIT, ROLLBACK)
- Clés étrangères (FOREIGN KEY)
- Verrouillage au niveau ligne (meilleur concurrence)

**Toujours utiliser InnoDB en 2024 !**

---

**`DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`**

Encodage et règles de tri pour CETTE table.

---

**Vérifier que la table est créée :**

```sql
SHOW TABLES;
```

**Résultat :**

```
+-------------------+
| Tables_in_blog_db |
+-------------------+
| articles          |
+-------------------+
```

**[OK] Table créée !**

---

**Voir la structure de la table :**

```sql
DESCRIBE articles;
```

**Ou :**

```sql
SHOW COLUMNS FROM articles;
```

**Résultat :**

```
+-------------------+--------------+------+-----+---------------------+-------------------------------+
| Field             | Type         | Null | Key | Default             | Extra                         |
+-------------------+--------------+------+-----+---------------------+-------------------------------+
| id                | int(11)      | NO   | PRI | NULL                | auto_increment                |
| titre             | varchar(255) | NO   |     | NULL                |                               |
| contenu           | text         | NO   |     | NULL                |                               |
| auteur            | varchar(100) | NO   |     | NULL                |                               |
| date_creation     | datetime     | YES  |     | CURRENT_TIMESTAMP   | DEFAULT_GENERATED             |
| date_modification | datetime     | YES  |     | CURRENT_TIMESTAMP   | DEFAULT_GENERATED on update...|
+-------------------+--------------+------+-----+---------------------+-------------------------------+
```

**[OK] Structure correcte !**

---

**Insérer quelques articles de test :**

```sql
INSERT INTO articles (titre, contenu, auteur) VALUES
('Premier Article', 'Ceci est le contenu de mon premier article sur ce blog. Il parle de développement web et d''Apache.', 'Jean Dupont'),
('Guide PHP pour Débutants', 'PHP est un langage de programmation très populaire pour le web. Il permet de créer des sites dynamiques facilement.', 'Marie Martin'),
('MySQL vs MariaDB', 'MariaDB est un fork de MySQL créé par les créateurs originaux. Les deux sont très similaires mais MariaDB offre quelques améliorations.', 'Paul Durand');
```

**[ATTENTION] Note les guillemets doublés : `d''Apache`**

En SQL, pour échapper un guillemet simple dans une chaîne, on le double :
- `'d'Apache'` -> [X] Erreur de syntaxe
- `'d''Apache'` -> [OK] Correct

---

**Vérifier l'insertion :**

```sql
SELECT * FROM articles;
```

**Résultat :**

```
+----+----------------------------+----------------...+-------------+---------------------+---------------------+
| id | titre                      | contenu        ...| auteur      | date_creation       | date_modification   |
+----+----------------------------+----------------...+-------------+---------------------+---------------------+
|  1 | Premier Article            | Ceci est le... ...| Jean Dupont | 2024-12-16 14:45:00 | 2024-12-16 14:45:00 |
|  2 | Guide PHP pour Débutants   | PHP est un l...  | Marie Martin| 2024-12-16 14:45:00 | 2024-12-16 14:45:00 |
|  3 | MySQL vs MariaDB           | MariaDB est...   | Paul Durand | 2024-12-16 14:45:00 | 2024-12-16 14:45:00 |
+----+----------------------------+----------------...+-------------+---------------------+---------------------+
```

**[OK] 3 articles insérés !**

---

**Quitter MySQL :**

```sql
EXIT;
```

**Ou `QUIT;` ou `Ctrl + D`.**

---

### ÉTAPE 6 : Créer la page d'accueil du blog (liste des articles)

**Structure du projet :**

```
/var/www/blog/
├── index.php           <- Page d'accueil (liste articles)
├── article.php         <- Page détail d'un article
├── admin.php           <- Page d'administration (ajouter article)
├── config.php          <- Configuration base de données
└── css/
    └── style.css       <- Feuille de style
```

---

**Créer le fichier de configuration :**

```bash
sudo nano /var/www/blog/config.php
```

**Contenu :**

```php
<?php
// ═══════════════════════════════════════════════════════════════
// CONFIGURATION DE LA BASE DE DONNÉES
// ═══════════════════════════════════════════════════════════════

// Paramètres de connexion
define('DB_HOST', 'localhost');
define('DB_NAME', 'blog_db');
define('DB_USER', 'blog_user');
define('DB_PASS', 'BlogPassword123!');
define('DB_CHARSET', 'utf8mb4');

// ───────────────────────────────────────────────────────────────
// FONCTION DE CONNEXION À LA BASE DE DONNÉES
// ───────────────────────────────────────────────────────────────

function getDBConnection() {
    /*
     * Établit une connexion à la base de données avec PDO
     * 
     * PDO = PHP Data Objects
     * Interface d'accès aux bases de données en PHP
     * 
     * Avantages de PDO :
     * - Supporte plusieurs SGBD (MySQL, PostgreSQL, SQLite, etc.)
     * - Requêtes préparées (protection contre injections SQL)
     * - Gestion des erreurs avec exceptions
     * - Plus moderne que mysqli
     */
    
    try {
        // Construction du DSN (Data Source Name)
        $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
        
        /*
         * DSN = Chaîne de connexion
         * Format : "driver:parametre1=valeur1;parametre2=valeur2"
         * 
         * mysql: = Driver MySQL/MariaDB
         * host=localhost = Serveur de base de données
         * dbname=blog_db = Nom de la base
         * charset=utf8mb4 = Encodage des caractères
         */
        
        // Options de PDO
        $options = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ];
        
        /*
         * PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION :
         * En cas d'erreur SQL, lance une exception PDOException
         * Permet d'utiliser try/catch pour gérer les erreurs
         * 
         * Sans ça, les erreurs seraient silencieuses (dangereux !)
         * 
         * PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC :
         * Mode de récupération par défaut : tableau associatif
         * $row['titre'] au lieu de $row[0]
         * Plus lisible et maintenable
         * 
         * Autres modes :
         * - FETCH_NUM : Tableau numérique ($row[0], $row[1]...)
         * - FETCH_OBJ : Objet ($row->titre)
         * - FETCH_BOTH : Les deux (par défaut, gaspille de la mémoire)
         * 
         * PDO::ATTR_EMULATE_PREPARES => false :
         * Utilise les VRAIES requêtes préparées (côté serveur)
         * Plus sécurisé et performant
         * 
         * Avec emulation (true) :
         * PHP remplace les placeholders puis envoie au serveur
         * Moins sécurisé
         * 
         * Sans emulation (false) :
         * Le serveur MySQL prépare la requête
         * PHP envoie seulement les paramètres
         * * Recommandé !
         */
        
        // Créer la connexion PDO
        $pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
        
        /*
         * new PDO(...) crée une instance de PDO
         * $dsn = Chaîne de connexion
         * DB_USER = Nom d'utilisateur
         * DB_PASS = Mot de passe
         * $options = Tableau d'options
         * 
         * Si la connexion échoue, lance une PDOException
         */
        
        return $pdo;
        
    } catch (PDOException $e) {
        /*
         * PDOException = Exception spécifique à PDO
         * $e = Objet exception contenant les détails de l'erreur
         */
        
        // Log l'erreur (en production, utiliser error_log())
        echo "Erreur de connexion : " . $e->getMessage();
        
        /*
         * $e->getMessage() retourne le message d'erreur
         * Exemples :
         * - "SQLSTATE[HY000] [1045] Access denied for user 'blog_user'@'localhost'"
         * - "SQLSTATE[HY000] [2002] No such file or directory"
         * 
         * [ATTENTION] En production, NE PAS afficher les erreurs à l'utilisateur !
         * Risque de sécurité (révèle des infos sur la base)
         * 
         * Mieux :
         * error_log($e->getMessage());
         * die("Une erreur est survenue. Veuillez réessayer.");
         */
        
        // Arrêter l'exécution
        die();
    }
}

// ═══════════════════════════════════════════════════════════════
?>
```

**Sauvegarde.**

---

**Créer la feuille de style CSS :**

```bash
sudo mkdir /var/www/blog/css
sudo nano /var/www/blog/css/style.css
```

**Contenu (réutilisons le CSS de l'exercice 1, simplifié) :**

```css
/* Reset et styles globaux */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    line-height: 1.6;
    color: #333;
    background: #f4f4f4;
}

/* Header */
header {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 2rem 0;
    text-align: center;
}

header h1 {
    font-size: 2.5rem;
}

/* Container */
.container {
    max-width: 1000px;
    margin: 2rem auto;
    padding: 0 2rem;
}

/* Articles */
.article-card {
    background: white;
    padding: 2rem;
    margin-bottom: 2rem;
    border-radius: 10px;
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
    transition: transform 0.3s ease;
}

.article-card:hover {
    transform: translateY(-5px);
}

.article-card h2 {
    color: #667eea;
    margin-bottom: 0.5rem;
}

.article-card h2 a {
    color: #667eea;
    text-decoration: none;
}

.article-card h2 a:hover {
    text-decoration: underline;
}

.article-meta {
    color: #666;
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.article-excerpt {
    color: #444;
    margin-bottom: 1rem;
}

.read-more {
    display: inline-block;
    background: #667eea;
    color: white;
    padding: 0.5rem 1.5rem;
    text-decoration: none;
    border-radius: 5px;
    transition: background 0.3s ease;
}

.read-more:hover {
    background: #764ba2;
}

/* Article complet */
.article-full {
    background: white;
    padding: 3rem;
    border-radius: 10px;
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

.article-full h1 {
    color: #667eea;
    margin-bottom: 1rem;
}

.article-content {
    line-height: 1.8;
    text-align: justify;
}

/* Formulaire */
form {
    background: white;
    padding: 2rem;
    border-radius: 10px;
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}

.form-group {
    margin-bottom: 1.5rem;
}

.form-group label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: bold;
}

.form-group input[type="text"],
.form-group textarea {
    width: 100%;
    padding: 0.8rem;
    border: 2px solid #ddd;
    border-radius: 5px;
    font-size: 1rem;
}

.form-group textarea {
    min-height: 200px;
    font-family: inherit;
}

button[type="submit"] {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 1rem 2rem;
    border: none;
    border-radius: 5px;
    font-size: 1.1rem;
    cursor: pointer;
}

button[type="submit"]:hover {
    transform: translateY(-2px);
}

/* Messages */
.success-message {
    background: #d4edda;
    color: #155724;
    padding: 1rem;
    border-radius: 5px;
    margin-bottom: 1rem;
    border: 1px solid #c3e6cb;
}

.error-message {
    background: #f8d7da;
    color: #721c24;
    padding: 1rem;
    border-radius: 5px;
    margin-bottom: 1rem;
    border: 1px solid #f5c6cb;
}

/* Footer */
footer {
    background: #333;
    color: white;
    text-align: center;
    padding: 2rem 0;
    margin-top: 3rem;
}
```

**Sauvegarde.**

---

**Créer la page d'accueil (index.php) :**

```bash
sudo nano /var/www/blog/index.php
```

**Contenu :**

```php
<?php
// ═══════════════════════════════════════════════════════════════
// PAGE D'ACCUEIL DU BLOG - LISTE DES ARTICLES
// ═══════════════════════════════════════════════════════════════

// Inclure la configuration
require_once 'config.php';

/*
 * require_once vs require vs include vs include_once :
 * 
 * require : Inclut un fichier, erreur FATALE si absent
 * require_once : Inclut UNE SEULE FOIS, erreur fatale si absent
 * include : Inclut un fichier, WARNING si absent (continue)
 * include_once : Inclut UNE SEULE FOIS, warning si absent
 * 
 * Pour config.php : TOUJOURS require_once
 * (Le script ne peut pas fonctionner sans config)
 */

// Obtenir la connexion à la base de données
$pdo = getDBConnection();

// ───────────────────────────────────────────────────────────────
// RÉCUPÉRER LES ARTICLES
// ───────────────────────────────────────────────────────────────

try {
    // Préparer la requête SQL
    $sql = "SELECT id, titre, LEFT(contenu, 200) AS extrait, auteur, date_creation 
            FROM articles 
            ORDER BY date_creation DESC";
    
    /*
     * Explication de la requête :
     * 
     * SELECT id, titre, ... :
     * Sélectionne les colonnes spécifiées
     * 
     * LEFT(contenu, 200) AS extrait :
     * Fonction LEFT() : Prend les 200 premiers caractères de "contenu"
     * AS extrait : Renomme la colonne en "extrait"
     * 
     * Pourquoi ne pas prendre tout le contenu ?
     * - Économie de mémoire (articles longs)
     * - Optimisation (transfert moins de données)
     * - Affichage d'un aperçu seulement
     * 
     * FROM articles :
     * Table source
     * 
     * ORDER BY date_creation DESC :
     * Trier par date de création
     * DESC = Descendant (du plus récent au plus ancien)
     * ASC = Ascendant (du plus ancien au plus récent)
     */
    
    // Exécuter la requête
    $stmt = $pdo->query($sql);
    
    /*
     * $pdo->query() :
     * Exécute une requête SQL simple (sans paramètres)
     * Retourne un objet PDOStatement
     * 
     * Pour les requêtes avec paramètres, utiliser prepare() + execute()
     * (Voir plus bas)
     */
    
    // Récupérer tous les résultats
    $articles = $stmt->fetchAll();
    
    /*
     * fetchAll() :
     * Récupère TOUTES les lignes en une seule fois
     * Retourne un tableau de tableaux associatifs
     * 
     * Exemple de résultat :
     * [
     *     ['id' => 1, 'titre' => 'Premier Article', ...],
     *     ['id' => 2, 'titre' => 'Deuxième Article', ...],
     * ]
     * 
     * Alternatives :
     * 
     * fetch() :
     * Récupère UNE ligne à la fois
     * Utile dans une boucle while :
     * while ($row = $stmt->fetch()) { ... }
     * 
     * fetchColumn() :
     * Récupère une seule colonne (ex: COUNT(*))
     */
    
} catch (PDOException $e) {
    // En cas d'erreur SQL
    $error = "Erreur lors de la récupération des articles : " . $e->getMessage();
    $articles = [];
}

?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mon Blog PHP</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <h1>[NOTE] Mon Blog PHP</h1>
        <p>Développement web, tutoriels et astuces</p>
    </header>

    <div class="container">
        <?php if (isset($error)): ?>
            <!-- Afficher l'erreur si elle existe -->
            <div class="error-message">
                <?php echo htmlspecialchars($error); ?>
            </div>
            
            <!--
                htmlspecialchars() :
                Échappe les caractères spéciaux HTML
                
                Convertit :
                < en &lt;
                > en &gt;
                & en &amp;
                " en &quot;
                ' en &#039;
                
                Pourquoi ?
                Protège contre les attaques XSS (Cross-Site Scripting)
                
                Exemple d'attaque sans htmlspecialchars() :
                $error = "<script>alert('Attaque XSS!');</script>";
                echo $error; // [X] Le script s'exécute !
                
                Avec htmlspecialchars() :
                echo htmlspecialchars($error); // [OK] Affiché comme texte
                
                [ATTENTION] TOUJOURS utiliser htmlspecialchars() pour afficher
                des données provenant de :
                - Base de données
                - Formulaires ($_POST, $_GET)
                - Cookies
                - Toute source externe
            -->
        <?php endif; ?>

        <?php if (empty($articles)): ?>
            <!-- Si aucun article -->
            <div class="article-card">
                <p>Aucun article pour le moment. Revenez plus tard !</p>
            </div>
        <?php else: ?>
            <!-- Boucle sur les articles -->
            <?php foreach ($articles as $article): ?>
                <!--
                    foreach ($array as $item) :
                    Parcourt chaque élément du tableau
                    
                    $article contient :
                    [
                        'id' => 1,
                        'titre' => 'Premier Article',
                        'extrait' => 'Ceci est le contenu...',
                        'auteur' => 'Jean Dupont',
                        'date_creation' => '2024-12-16 14:45:00'
                    ]
                -->
                <div class="article-card">
                    <h2>
                        <a href="article.php?id=<?php echo $article['id']; ?>">
                            <?php echo htmlspecialchars($article['titre']); ?>
                        </a>
                    </h2>
                    
                    <!--
                        <a href="article.php?id=<?php echo $article['id']; ?>">
                        
                        Génère un lien vers article.php avec l'ID en paramètre
                        Exemple : article.php?id=1
                        
                        Dans article.php, on récupère l'ID avec $_GET['id']
                    -->
                    
                    <div class="article-meta">
                        Par <strong><?php echo htmlspecialchars($article['auteur']); ?></strong>
                        le <?php echo date('d/m/Y à H:i', strtotime($article['date_creation'])); ?>
                    </div>
                    
                    <!--
                        date('d/m/Y à H:i', strtotime($article['date_creation'])) :
                        
                        strtotime() :
                        Convertit une chaîne de date en timestamp UNIX
                        '2024-12-16 14:45:00' -> 1702737900 (nombre de secondes depuis 1970)
                        
                        date('d/m/Y à H:i', ...) :
                        Formate le timestamp
                        d = Jour (01-31)
                        m = Mois (01-12)
                        Y = Année (4 chiffres)
                        H = Heure (00-23)
                        i = Minutes (00-59)
                        
                        Résultat : 16/12/2024 à 14:45
                        
                        Autres formats courants :
                        'Y-m-d' -> 2024-12-16
                        'l, F j, Y' -> Monday, December 16, 2024
                        'd/m/Y H:i:s' -> 16/12/2024 14:45:00
                    -->
                    
                    <div class="article-excerpt">
                        <?php 
                        echo htmlspecialchars($article['extrait']); 
                        // Si le contenu dépasse 200 caractères, afficher "..."
                        if (strlen($article['extrait']) >= 200) {
                            echo '...';
                        }
                        ?>
                    </div>
                    
                    <a href="article.php?id=<?php echo $article['id']; ?>" class="read-more">
                        Lire la suite ->
                    </a>
                </div>
            <?php endforeach; ?>
            
            <!--
                Syntaxe alternative pour les structures de contrôle :
                
                foreach (...) :
                    ...
                endforeach;
                
                Plus lisible dans le HTML que les accolades :
                foreach (...) {
                    ...
                }
                
                Aussi disponible pour :
                if (...) : ... endif;
                while (...) : ... endwhile;
                for (...) : ... endfor;
            -->
        <?php endif; ?>

        <div style="text-align: center; margin-top: 2rem;">
            <a href="admin.php" class="read-more">
                + Ajouter un article
            </a>
        </div>
    </div>

    <footer>
        <p>&copy; 2024 Mon Blog PHP - Tous droits réservés</p>
    </footer>
</body>
</html>
```

**Sauvegarde.**

---

**Corriger les permissions :**

```bash
sudo chown -R www-data:www-data /var/www/blog
sudo find /var/www/blog -type d -exec chmod 755 {} \;
sudo find /var/www/blog -type f -exec chmod 644 {} \;
```

---

**Tester dans le navigateur :**

```
http://blog.local
```

**[BRAVO] Tu devrais voir la liste des 3 articles ! [BRAVO]**

---

(Le fichier est très long, je vais continuer dans la prochaine partie avec article.php, admin.php, puis les exercices 4 et 5...)

**Tester dans le navigateur :**

```
http://blog.local
```

**[BRAVO] Tu devrais voir la liste des 3 articles ! [BRAVO]**

---

### ÉTAPE 7 : Créer la page de détail d'un article

```bash
sudo nano /var/www/blog/article.php
```

**Contenu :**

```php
<?php
// ═══════════════════════════════════════════════════════════════
// PAGE DE DÉTAIL D'UN ARTICLE
// ═══════════════════════════════════════════════════════════════

require_once 'config.php';

$pdo = getDBConnection();

// ───────────────────────────────────────────────────────────────
// RÉCUPÉRER L'ID DE L'ARTICLE DEPUIS L'URL
// ───────────────────────────────────────────────────────────────

// Vérifier que l'ID est présent dans l'URL
if (!isset($_GET['id']) || empty($_GET['id'])) {
    /*
     * isset($_GET['id']) :
     * Vérifie si le paramètre 'id' existe dans l'URL
     * URL : article.php?id=1 -> isset($_GET['id']) = true
     * URL : article.php -> isset($_GET['id']) = false
     * 
     * empty($_GET['id']) :
     * Vérifie si la valeur est vide
     * article.php?id= -> empty($_GET['id']) = true
     * article.php?id=0 -> empty($_GET['id']) = true (0 = vide)
     * article.php?id=1 -> empty($_GET['id']) = false
     * 
     * ! = Négation (NON)
     * || = OU logique
     * 
     * Si l'ID n'existe pas OU est vide, rediriger
     */
    header('Location: index.php');
    exit();
    /*
     * header('Location: ...') :
     * Envoie un header HTTP de redirection
     * Code 302 (redirection temporaire) par défaut
     * 
     * [ATTENTION] DOIT être appelé AVANT tout echo/HTML !
     * Sinon erreur : "Headers already sent"
     * 
     * exit() ou die() :
     * Arrête l'exécution du script
     * Important après une redirection
     * Sinon, le code continue à s'exécuter
     */
}

// Récupérer et sécuriser l'ID
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
/*
 * filter_input() :
 * Filtre et valide une variable externe
 * 
 * INPUT_GET : Source des données ($_GET)
 * Autres sources :
 * - INPUT_POST : $_POST
 * - INPUT_COOKIE : $_COOKIE
 * - INPUT_SERVER : $_SERVER
 * 
 * 'id' : Nom de la variable
 * 
 * FILTER_VALIDATE_INT : Type de validation
 * Vérifie que c'est un entier valide
 * Retourne l'entier ou FALSE si invalide
 * 
 * Exemples :
 * article.php?id=1 -> $id = 1
 * article.php?id=abc -> $id = false
 * article.php?id=1.5 -> $id = false
 * article.php?id=-5 -> $id = -5
 * 
 * Autres filtres utiles :
 * FILTER_VALIDATE_EMAIL : Valide un email
 * FILTER_VALIDATE_URL : Valide une URL
 * FILTER_SANITIZE_STRING : Nettoie une chaîne
 */

if ($id === false) {
    // ID invalide (pas un entier)
    header('Location: index.php');
    exit();
}

// ───────────────────────────────────────────────────────────────
// RÉCUPÉRER L'ARTICLE DE LA BASE DE DONNÉES
// ───────────────────────────────────────────────────────────────

try {
    // Préparer la requête SQL avec placeholder
    $sql = "SELECT * FROM articles WHERE id = :id";
    
    /*
     * Requête préparée avec placeholder nommé :
     * 
     * :id = Placeholder (sera remplacé par la vraie valeur)
     * 
     * Pourquoi ne pas faire directement :
     * $sql = "SELECT * FROM articles WHERE id = $id"; // [X] DANGEREUX !
     * 
     * Parce que c'est VULNÉRABLE aux injections SQL !
     * 
     * === EXEMPLE D'INJECTION SQL ===
     * 
     * URL malveillante : article.php?id=1 OR 1=1
     * 
     * Sans requête préparée :
     * $id = $_GET['id']; // "1 OR 1=1"
     * $sql = "SELECT * FROM articles WHERE id = $id";
     * // Requête finale : SELECT * FROM articles WHERE id = 1 OR 1=1
     * // 1=1 est toujours vrai -> Retourne TOUS les articles !
     * 
     * Pire encore :
     * article.php?id=1; DROP TABLE articles; --
     * // Supprime la table articles ! [!]
     * 
     * Avec requête préparée :
     * $sql = "SELECT * FROM articles WHERE id = :id";
     * $stmt->execute([':id' => "1 OR 1=1"]);
     * // :id est traité comme une VALEUR littérale "1 OR 1=1"
     * // Pas comme du code SQL !
     * // Aucun article trouvé (aucun article n'a l'ID "1 OR 1=1")
     * // [OK] Protégé !
     * 
     * Les requêtes préparées = TOUJOURS obligatoires
     * pour les requêtes avec des paramètres utilisateurs !
     */
    
    // Préparer la requête
    $stmt = $pdo->prepare($sql);
    
    /*
     * $pdo->prepare($sql) :
     * Prépare la requête SQL
     * Retourne un objet PDOStatement
     * La requête n'est PAS encore exécutée
     */
    
    // Exécuter avec les paramètres
    $stmt->execute([':id' => $id]);
    
    /*
     * execute([':id' => $id]) :
     * Exécute la requête avec les paramètres
     * 
     * Format : [':placeholder' => $valeur]
     * 
     * PDO remplace :id par la valeur de $id
     * En toute sécurité (échappement automatique)
     * 
     * Syntaxes possibles :
     * 
     * 1. Tableau associatif (nommés) :
     * $stmt->execute([':id' => $id, ':auteur' => $auteur]);
     * 
     * 2. Tableau numérique (placeholders ?) :
     * $sql = "SELECT * FROM articles WHERE id = ? AND auteur = ?";
     * $stmt->execute([$id, $auteur]);
     * 
     * 3. Bind individuel :
     * $stmt->bindParam(':id', $id, PDO::PARAM_INT);
     * $stmt->execute();
     * 
     * Recommandation : Placeholders nommés (:id)
     * Plus lisible et moins sujet aux erreurs
     */
    
    // Récupérer l'article
    $article = $stmt->fetch();
    
    /*
     * fetch() :
     * Récupère UNE ligne (la première)
     * Retourne un tableau associatif ou FALSE si aucun résultat
     * 
     * Si plusieurs résultats, fetch() retourne seulement le premier
     * Pour tous les résultats : fetchAll()
     */
    
    if (!$article) {
        // Article non trouvé
        $error = "Article introuvable.";
    }
    
} catch (PDOException $e) {
    $error = "Erreur lors de la récupération de l'article : " . $e->getMessage();
}

?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo isset($article) ? htmlspecialchars($article['titre']) : 'Article'; ?> - Mon Blog</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <h1><a href="index.php" style="color: white; text-decoration: none;">[NOTE] Mon Blog PHP</a></h1>
    </header>

    <div class="container">
        <?php if (isset($error)): ?>
            <div class="error-message">
                <?php echo htmlspecialchars($error); ?>
            </div>
            <p><a href="index.php" class="read-more"><- Retour à l'accueil</a></p>
        <?php elseif ($article): ?>
            <div class="article-full">
                <h1><?php echo htmlspecialchars($article['titre']); ?></h1>
                
                <div class="article-meta">
                    Par <strong><?php echo htmlspecialchars($article['auteur']); ?></strong>
                    le <?php echo date('d/m/Y à H:i', strtotime($article['date_creation'])); ?>
                    
                    <?php if ($article['date_modification'] != $article['date_creation']): ?>
                        <br>
                        <em>Modifié le <?php echo date('d/m/Y à H:i', strtotime($article['date_modification'])); ?></em>
                    <?php endif; ?>
                </div>
                
                <hr style="margin: 2rem 0; border: none; border-top: 1px solid #ddd;">
                
                <div class="article-content">
                    <?php 
                    // Convertir les retours à la ligne en <br>
                    echo nl2br(htmlspecialchars($article['contenu'])); 
                    
                    /*
                     * nl2br() :
                     * NL = New Line (retour à la ligne)
                     * BR = <br> (balise HTML)
                     * 
                     * Convertit les retours à la ligne (\n) en <br>
                     * 
                     * Exemple :
                     * "Ligne 1\nLigne 2" devient "Ligne 1<br />\nLigne 2"
                     * 
                     * Dans la base de données :
                     * "Première ligne\nDeuxième ligne"
                     * 
                     * Sans nl2br() :
                     * Affichage : "Première ligne Deuxième ligne" (sur une ligne)
                     * 
                     * Avec nl2br() :
                     * Affichage :
                     * Première ligne
                     * Deuxième ligne
                     * 
                     * [ATTENTION] Appliquer htmlspecialchars() AVANT nl2br()
                     * Sinon les <br> insérés seraient échappés
                     */
                    ?>
                </div>
                
                <div style="margin-top: 2rem;">
                    <a href="index.php" class="read-more"><- Retour à l'accueil</a>
                </div>
            </div>
        <?php endif; ?>
    </div>

    <footer>
        <p>&copy; 2024 Mon Blog PHP - Tous droits réservés</p>
    </footer>
</body>
</html>
```

**Sauvegarde.**

---

### ÉTAPE 8 : Créer la page d'administration (ajouter un article)

```bash
sudo nano /var/www/blog/admin.php
```

**Contenu :**

```php
<?php
// ═══════════════════════════════════════════════════════════════
// PAGE D'ADMINISTRATION - AJOUTER UN ARTICLE
// ═══════════════════════════════════════════════════════════════

require_once 'config.php';

$pdo = getDBConnection();

// Variables pour le formulaire
$success = false;
$error = null;

// ───────────────────────────────────────────────────────────────
// TRAITER LE FORMULAIRE SI SOUMIS
// ───────────────────────────────────────────────────────────────

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    /*
     * $_SERVER['REQUEST_METHOD'] :
     * Méthode HTTP de la requête
     * Valeurs possibles : GET, POST, PUT, DELETE, etc.
     * 
     * Formulaire HTML avec method="POST" -> REQUEST_METHOD = 'POST'
     * Lien ou barre d'adresse -> REQUEST_METHOD = 'GET'
     * 
     * === 'POST' vérifie si le formulaire a été soumis
     */
    
    // Récupérer les données du formulaire
    $titre = $_POST['titre'] ?? '';
    $contenu = $_POST['contenu'] ?? '';
    $auteur = $_POST['auteur'] ?? '';
    
    /*
     * $_POST :
     * Tableau associatif contenant les données du formulaire
     * 
     * <input name="titre"> -> $_POST['titre']
     * <textarea name="contenu"> -> $_POST['contenu']
     * 
     * ?? '' :
     * Opérateur de coalescence nulle (PHP 7+)
     * Si $_POST['titre'] n'existe pas ou est null, utiliser ''
     * 
     * Équivalent à :
     * $titre = isset($_POST['titre']) ? $_POST['titre'] : '';
     */
    
    // Validation simple
    if (empty($titre) || empty($contenu) || empty($auteur)) {
        /*
         * empty() :
         * Vérifie si une variable est vide
         * 
         * Considéré comme vide :
         * - "" (chaîne vide)
         * - 0 (entier zéro)
         * - "0" (chaîne zéro)
         * - null
         * - false
         * - [] (tableau vide)
         * 
         * NON vide :
         * - "  " (espaces)
         * - "false" (chaîne)
         * - 1, -1, etc.
         */
        $error = "Tous les champs sont obligatoires.";
    } else {
        // Insérer l'article dans la base de données
        try {
            $sql = "INSERT INTO articles (titre, contenu, auteur) VALUES (:titre, :contenu, :auteur)";
            
            /*
             * INSERT INTO articles (...) VALUES (...) :
             * Insère une nouvelle ligne dans la table
             * 
             * Colonnes spécifiées : titre, contenu, auteur
             * PAS besoin de spécifier :
             * - id (AUTO_INCREMENT)
             * - date_creation (DEFAULT CURRENT_TIMESTAMP)
             * - date_modification (DEFAULT CURRENT_TIMESTAMP)
             * 
             * Ces colonnes sont remplies automatiquement par MySQL
             */
            
            $stmt = $pdo->prepare($sql);
            $stmt->execute([
                ':titre' => $titre,
                ':contenu' => $contenu,
                ':auteur' => $auteur
            ]);
            
            /*
             * [ATTENTION] TOUJOURS utiliser des requêtes préparées pour INSERT !
             * 
             * Protège contre les injections SQL
             * 
             * MAUVAIS (vulnérable) :
             * $sql = "INSERT INTO articles VALUES ('$titre', ...)"; // [X]
             * 
             * Si $titre = "'; DROP TABLE articles; --"
             * -> La table est supprimée ! [!]
             * 
             * BON (sécurisé) :
             * $sql = "INSERT ... VALUES (:titre, ...)";
             * $stmt->execute([':titre' => $titre]); // [OK]
             * 
             * PDO échappe automatiquement les caractères dangereux
             */
            
            $success = true;
            
            // Réinitialiser le formulaire
            $titre = '';
            $contenu = '';
            $auteur = '';
            
        } catch (PDOException $e) {
            $error = "Erreur lors de l'ajout de l'article : " . $e->getMessage();
        }
    }
}

?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ajouter un article - Mon Blog</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header>
        <h1><a href="index.php" style="color: white; text-decoration: none;">[NOTE] Mon Blog PHP</a></h1>
    </header>

    <div class="container">
        <h2>+ Ajouter un article</h2>
        
        <?php if ($success): ?>
            <div class="success-message">
                [OK] Article ajouté avec succès !
                <a href="index.php">Voir tous les articles</a>
            </div>
        <?php endif; ?>
        
        <?php if ($error): ?>
            <div class="error-message">
                [X] <?php echo htmlspecialchars($error); ?>
            </div>
        <?php endif; ?>
        
        <form method="POST" action="">
            <!--
                method="POST" :
                Envoie les données dans le corps de la requête
                Pas visible dans l'URL (contrairement à GET)
                
                action="" :
                URL de destination (vide = même page)
                Équivalent à action="admin.php"
                
                Le formulaire se soumet à lui-même
                -> La page recharge et traite les données
            -->
            
            <div class="form-group">
                <label for="titre">Titre de l'article *</label>
                <input type="text" id="titre" name="titre" 
                       value="<?php echo htmlspecialchars($titre ?? ''); ?>" 
                       required>
                
                <!--
                    value="<?php echo htmlspecialchars($titre ?? ''); ?>" :
                    
                    Pré-remplit le champ avec la valeur de $titre
                    Si le formulaire est invalide, les données sont conservées
                    (Meilleure UX)
                    
                    ?? '' : Si $titre n'existe pas, utiliser ''
                    
                    [ATTENTION] htmlspecialchars() obligatoire !
                    Sinon risque XSS si $titre contient du HTML
                    
                    required :
                    Attribut HTML5 de validation
                    Le navigateur empêche la soumission si vide
                    [ATTENTION] Validation côté client, PAS suffisante !
                    Toujours valider côté serveur aussi (PHP)
                -->
            </div>
            
            <div class="form-group">
                <label for="auteur">Auteur *</label>
                <input type="text" id="auteur" name="auteur" 
                       value="<?php echo htmlspecialchars($auteur ?? ''); ?>" 
                       required>
            </div>
            
            <div class="form-group">
                <label for="contenu">Contenu de l'article *</label>
                <textarea id="contenu" name="contenu" required><?php echo htmlspecialchars($contenu ?? ''); ?></textarea>
                
                <!--
                    Pour <textarea>, la valeur va entre les balises
                    Pas dans un attribut value comme <input>
                    
                    Correct :
                    <textarea>Contenu ici</textarea>
                    
                    Incorrect :
                    <textarea value="Contenu ici"></textarea> // [X]
                -->
            </div>
            
            <button type="submit">Publier l'article</button>
        </form>
        
        <div style="margin-top: 2rem;">
            <a href="index.php" class="read-more"><- Retour à l'accueil</a>
        </div>
    </div>

    <footer>
        <p>&copy; 2024 Mon Blog PHP - Tous droits réservés</p>
    </footer>
</body>
</html>
```

**Sauvegarde.**

---

**Corriger les permissions finales :**

```bash
sudo chown -R www-data:www-data /var/www/blog
sudo find /var/www/blog -type d -exec chmod 755 {} \;
sudo find /var/www/blog -type f -exec chmod 644 {} \;
```

---

### [OK] TESTS DE VALIDATION

**1. Page d'accueil**

- [ ] `http://blog.local` affiche les 3 articles
- [ ] Les titres, auteurs et dates s'affichent correctement
- [ ] L'extrait est limité à 200 caractères + "..."
- [ ] Les liens "Lire la suite" fonctionnent

---

**2. Page de détail**

- [ ] `http://blog.local/article.php?id=1` affiche l'article complet
- [ ] Le contenu entier est visible
- [ ] Si l'article a été modifié, "Modifié le..." s'affiche
- [ ] Tester avec un ID invalide : `article.php?id=999` -> Message d'erreur
- [ ] Tester avec un ID non-entier : `article.php?id=abc` -> Redirection vers l'accueil

---

**3. Page d'administration**

- [ ] `http://blog.local/admin.php` affiche le formulaire
- [ ] Remplir tous les champs et soumettre -> Message de succès
- [ ] L'article apparaît sur la page d'accueil
- [ ] Soumettre le formulaire vide -> Message d'erreur "Tous les champs obligatoires"
- [ ] Tester avec des caractères spéciaux dans le titre : `<script>alert('XSS')</script>`
  - [ ] Le titre s'affiche comme texte (pas exécuté)
  - [ ] Pas d'alerte JavaScript

---

**4. Connexion base de données**

```bash
# Vérifier que PHP peut se connecter à MySQL
php -r "new PDO('mysql:host=localhost;dbname=blog_db', 'blog_user', 'BlogPassword123!');" && echo "[OK] Connexion OK" || echo "[X] Erreur de connexion"
```

---

**5. Logs d'erreurs PHP**

```bash
# Consulter les erreurs PHP
sudo tail -20 /var/log/apache2/blog-error.log
```

**Si tout fonctionne, pas d'erreur PHP.**

---

### [ROUGE] ERREURS COURANTES ET SOLUTIONS

#### Erreur 1 : "Connection refused" ou "Can't connect to MySQL server"

**Symptôme :**

```
SQLSTATE[HY000] [2002] Connection refused
```

**Cause : MySQL/MariaDB ne tourne pas**

**Solution :**

```bash
sudo systemctl status mariadb
# Si inactif :
sudo systemctl start mariadb
```

---

#### Erreur 2 : "Access denied for user 'blog_user'@'localhost'"

**Symptôme :**

```
SQLSTATE[HY000] [1045] Access denied for user 'blog_user'@'localhost' (using password: YES)
```

**Causes possibles :**

**1. Mot de passe incorrect dans config.php**

Vérifier : `DB_PASS` dans `config.php`

---

**2. Utilisateur n'existe pas**

```bash
sudo mysql -u root -p
```

```sql
SELECT User, Host FROM mysql.user WHERE User = 'blog_user';
```

**Si aucun résultat, recréer l'utilisateur :**

```sql
CREATE USER 'blog_user'@'localhost' IDENTIFIED BY 'BlogPassword123!';
GRANT ALL PRIVILEGES ON blog_db.* TO 'blog_user'@'localhost';
FLUSH PRIVILEGES;
```

---

#### Erreur 3 : "Base table or view not found: 'blog_db.articles'"

**Symptôme :**

```
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'blog_db.articles' doesn't exist
```

**Cause : La table n'existe pas**

**Solution :**

```bash
sudo mysql -u root -p
```

```sql
USE blog_db;
SHOW TABLES;
```

**Si la table `articles` n'existe pas, la créer (voir ÉTAPE 5).**

---

#### Erreur 4 : Page blanche (aucun affichage)

**Symptôme :**

Page complètement blanche, aucun HTML.

**Cause : Erreur PHP fatale**

**Solution :**

**1. Activer l'affichage des erreurs PHP (développement uniquement) :**

```bash
sudo nano /etc/php/8.1/fpm/php.ini
```

**Chercher et modifier :**

```ini
display_errors = On
error_reporting = E_ALL
```

**Redémarrer PHP-FPM :**

```bash
sudo systemctl restart php8.1-fpm
```

---

**2. Consulter les logs :**

```bash
sudo tail -50 /var/log/apache2/blog-error.log
```

**Chercher les lignes avec "PHP Fatal error".**

---

#### Erreur 5 : Les données du formulaire ne s'insèrent pas

**Symptôme :**

Formulaire soumis, mais pas de message d'erreur ni de succès.

**Débogage :**

**Ajouter temporairement dans admin.php après `if ($_SERVER['REQUEST_METHOD'] === 'POST')` :**

```php
var_dump($_POST);
exit();
```

**Cela affichera le contenu de `$_POST`.**

**Si vide, le formulaire n'est pas soumis correctement.**

**Vérifier :**
- `<form method="POST">`
- Attributs `name` sur les champs
- Bouton `<button type="submit">`

---

**Supprimer le `var_dump()` après débogage !**

---

### [IMPORTANT] POINTS CLÉS À RETENIR

**1. PHP avec Apache**
- PHP-FPM > mod_php (performance)
- Configuration via socket UNIX (`/run/php/php8.1-fpm.sock`)
- Directive `SetHandler` pour les fichiers `.php`

**2. PDO (PHP Data Objects)**
- Interface moderne pour les bases de données
- Requêtes préparées OBLIGATOIRES (sécurité)
- Gestion des erreurs avec exceptions

**3. Sécurité**
- **Jamais** concaténer les variables dans les requêtes SQL
- Toujours utiliser `htmlspecialchars()` pour afficher du contenu
- Valider et filtrer toutes les entrées utilisateur (`filter_input()`)
- Requêtes préparées avec placeholders (`:id`, `:titre`, etc.)

**4. Gestion des formulaires**
- Vérifier `$_SERVER['REQUEST_METHOD']` pour détecter la soumission
- Valider côté serveur (ne jamais faire confiance au client)
- Réafficher les données en cas d'erreur (UX)
- Réinitialiser après succès

**5. Structure PHP**
- Séparer la configuration (`config.php`)
- Code PHP en haut, HTML en bas
- Syntaxe alternative pour les structures (`:` ... `endif;`)
- Utiliser `require_once` pour les fichiers essentiels

---

### [RAPIDE] POUR ALLER PLUS LOIN

**1. Améliorer la sécurité**

**Ajouter un système d'authentification :**

```php
// login.php : Page de connexion
// session.php : Vérifier si l'utilisateur est connecté
// Protéger admin.php : require_once 'session.php';
```

**Hacher les mots de passe avec `password_hash()` :**

```php
$hash = password_hash($password, PASSWORD_DEFAULT);
// Stocke $hash dans la base
// Vérification :
if (password_verify($password, $hash)) {
    // Connexion OK
}
```

---

**2. Pagination**

Pour éviter d'afficher 1000 articles sur une page :

```php
// Page 1 : Articles 1-10
// Page 2 : Articles 11-20
// etc.

$page = $_GET['page'] ?? 1;
$perPage = 10;
$offset = ($page - 1) * $perPage;

$sql = "SELECT * FROM articles ORDER BY date_creation DESC LIMIT :limit OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
```

---

**3. Éditeur WYSIWYG**

Remplacer le `<textarea>` par un éditeur riche (TinyMCE, CKEditor) :

```html
<script src="https://cdn.tiny.cloud/1/YOUR-API-KEY/tinymce/6/tinymce.min.js"></script>
<script>
  tinymce.init({
    selector: '#contenu'
  });
</script>
```

---

**4. Upload d'images**

Ajouter une image de couverture pour chaque article :

```php
// Nouvelle colonne dans la table :
ALTER TABLE articles ADD COLUMN image VARCHAR(255);

// Traiter l'upload :
if ($_FILES['image']['error'] === UPLOAD_ERR_OK) {
    $uploadDir = '/var/www/blog/uploads/';
    $filename = uniqid() . '_' . basename($_FILES['image']['name']);
    move_uploaded_file($_FILES['image']['tmp_name'], $uploadDir . $filename);
    // Stocker $filename dans la base
}
```

---

**5. Recherche**

Ajouter une barre de recherche :

```php
$search = $_GET['q'] ?? '';
$sql = "SELECT * FROM articles WHERE titre LIKE :search OR contenu LIKE :search";
$stmt->execute([':search' => '%' . $search . '%']);
// LIKE '%mot%' cherche "mot" n'importe où dans la chaîne
```

---

**6. Catégories et tags**

**Créer de nouvelles tables :**

```sql
CREATE TABLE categories (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nom VARCHAR(100) NOT NULL
);

CREATE TABLE article_categories (
    article_id INT,
    categorie_id INT,
    FOREIGN KEY (article_id) REFERENCES articles(id),
    FOREIGN KEY (categorie_id) REFERENCES categories(id)
);
```

---

**7. Commentaires**

**Ajouter une table de commentaires :**

```sql
CREATE TABLE commentaires (
    id INT AUTO_INCREMENT PRIMARY KEY,
    article_id INT NOT NULL,
    auteur VARCHAR(100) NOT NULL,
    contenu TEXT NOT NULL,
    date_creation DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (article_id) REFERENCES articles(id) ON DELETE CASCADE
);
```

**ON DELETE CASCADE : Si un article est supprimé, ses commentaires le sont aussi.**

---

## [COURS] CONCLUSION DE L'EXERCICE 3

**[OK] Félicitations ! Tu as créé un blog fonctionnel avec PHP et MySQL !**

**Ce que tu as appris :**
- Installer et configurer PHP-FPM avec Apache
- Installer et sécuriser MySQL/MariaDB
- Créer une base de données et des tables
- Se connecter à MySQL avec PDO
- Exécuter des requêtes SQL (SELECT, INSERT)
- Utiliser des requêtes préparées (protection injections SQL)
- Traiter des formulaires HTML avec PHP
- Valider et filtrer les entrées utilisateur
- Afficher des données dynamiques
- Gérer les erreurs avec try/catch
- Échapper le HTML avec `htmlspecialchars()` (protection XSS)
- Organiser un projet PHP

**Compétences acquises :**
- [OK] PHP procédural (niveau intermédiaire)
- [OK] MySQL/MariaDB (CRUD de base)
- [OK] PDO et requêtes préparées
- [OK] Sécurité web (SQL injection, XSS)
- [OK] Gestion de formulaires
- [OK] Architecture simple d'application PHP

**Temps moyen de réalisation :** 3-4 heures

**Prochaine étape :** Exercice 4 - Reverse Proxy pour app Node.js + Load Balancing ! [RAPIDE]

---

---

# [ROUGE] EXERCICE 4 : REVERSE PROXY + LOAD BALANCING

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu es DevOps dans une startup. L'équipe développe une API REST en Node.js qui doit supporter une forte charge. Ta mission :
1. Déployer plusieurs instances de l'app Node.js sur différents ports
2. Configurer Apache comme reverse proxy
3. Implémenter le load balancing (répartition de charge)
4. Assurer la haute disponibilité

### Objectifs techniques

- Créer une application Node.js simple (API REST)
- Configurer Apache comme reverse proxy
- Mettre en place le load balancing avec `mod_proxy_balancer`
- Gérer les sessions sticky (affectation client-serveur)
- Monitorer la santé des backends
- Gérer le failover automatique

### Architecture cible

```
Internet
   v
Apache (reverse proxy + load balancer)
   ├-> Node.js Instance 1 (port 3001)
   ├-> Node.js Instance 2 (port 3002)
   └-> Node.js Instance 3 (port 3003)
```

### Contraintes

- Apache 2.4
- Node.js 18+
- Load balancing par round-robin
- Health checks automatiques
- Temps estimé : 2-3 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Installer et utiliser Node.js
- [OK] Créer une API REST simple avec Express
- [OK] Configurer un reverse proxy Apache
- [OK] Implémenter le load balancing
- [OK] Gérer les sessions sticky
- [OK] Monitorer les backends
- [OK] Gérer la haute disponibilité
- [OK] Comprendre les headers HTTP de proxying

---

## [DOCS] PRÉREQUIS

- Exercices 1, 2 et 3 terminés
- Notions de base en HTTP
- Compréhension des architectures distribuées

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Installer Node.js

```bash
# Télécharger et installer NodeSource repository
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -

# Installer Node.js et npm
sudo apt install -y nodejs

# Vérifier l'installation
node --version  # v18.x.x
npm --version   # 9.x.x
```

**Explication :**

**NodeSource** = Repository officiel de Node.js pour Debian/Ubuntu
**setup_18.x** = Installe Node.js version 18 (LTS)
**npm** = Node Package Manager (gestionnaire de paquets)

---

### ÉTAPE 2 : Créer l'application Node.js

```bash
# Créer le dossier du projet
sudo mkdir -p /var/www/nodejs-app
cd /var/www/nodejs-app

# Initialiser le projet npm
sudo npm init -y
```

**`npm init -y`** crée un fichier `package.json` avec les valeurs par défaut.

---

**Installer Express (framework web) :**

```bash
sudo npm install express
```

**Express** = Framework minimaliste pour Node.js (comme Flask pour Python)

---

**Créer l'application (`server.js`) :**

```bash
sudo nano /var/www/nodejs-app/server.js
```

**Contenu :**

```javascript
// ═══════════════════════════════════════════════════════════════
// API REST NODE.JS SIMPLE
// ═══════════════════════════════════════════════════════════════

const express = require('express');
const app = express();

// Port défini par variable d'environnement (ou 3001 par défaut)
const PORT = process.env.PORT || 3001;

// ID de l'instance (pour identifier quel serveur répond)
const INSTANCE_ID = process.env.INSTANCE_ID || 'Instance-1';

// ───────────────────────────────────────────────────────────────
// ROUTES DE L'API
// ───────────────────────────────────────────────────────────────

// Route racine
app.get('/', (req, res) => {
    res.json({
        message: 'Bienvenue sur l\'API Node.js',
        instance: INSTANCE_ID,
        port: PORT,
        timestamp: new Date().toISOString()
    });
});

// Route de santé (health check)
app.get('/health', (req, res) => {
    res.status(200).json({
        status: 'OK',
        instance: INSTANCE_ID,
        uptime: process.uptime()
    });
});

// Route API exemple
app.get('/api/users', (req, res) => {
    res.json({
        users: [
            { id: 1, name: 'Alice' },
            { id: 2, name: 'Bob' },
            { id: 3, name: 'Charlie' }
        ],
        servedBy: INSTANCE_ID
    });
});

// ───────────────────────────────────────────────────────────────
// DÉMARRAGE DU SERVEUR
// ───────────────────────────────────────────────────────────────

app.listen(PORT, () => {
    console.log(`[${INSTANCE_ID}] Serveur démarré sur le port ${PORT}`);
    console.log(`[${INSTANCE_ID}] Health check: http://localhost:${PORT}/health`);
});
```

**Sauvegarde.**

---

### ÉTAPE 3 : Créer plusieurs instances avec PM2

**PM2** = Process Manager pour Node.js (gestion de processus en production)

```bash
# Installer PM2 globalement
sudo npm install -g pm2
```

---

**Créer le fichier de configuration PM2 :**

```bash
sudo nano /var/www/nodejs-app/ecosystem.config.js
```

**Contenu :**

```javascript
module.exports = {
    apps: [
        {
            name: 'node-api-1',
            script: './server.js',
            env: {
                PORT: 3001,
                INSTANCE_ID: 'Backend-1'
            }
        },
        {
            name: 'node-api-2',
            script: './server.js',
            env: {
                PORT: 3002,
                INSTANCE_ID: 'Backend-2'
            }
        },
        {
            name: 'node-api-3',
            script: './server.js',
            env: {
                PORT: 3003,
                INSTANCE_ID: 'Backend-3'
            }
        }
    ]
};
```

**Explication :**

3 instances de la même application :
- Instance 1 : Port 3001 (Backend-1)
- Instance 2 : Port 3002 (Backend-2)
- Instance 3 : Port 3003 (Backend-3)

---

**Démarrer les 3 instances :**

```bash
cd /var/www/nodejs-app
sudo pm2 start ecosystem.config.js
```

**Résultat :**

```
┌─────────────┬────┬─────────┬──────┬───────┬────────┐
│ App name    │ id │ mode    │ pid  │ status│ cpu    │
├─────────────┼────┼─────────┼──────┼───────┼────────┤
│ node-api-1  │ 0  │ fork    │ 1234 │ online│ 0%     │
│ node-api-2  │ 1  │ fork    │ 1235 │ online│ 0%     │
│ node-api-3  │ 2  │ fork    │ 1236 │ online│ 0%     │
└─────────────┴────┴─────────┴──────┴───────┴────────┘
```

---

**Vérifier que les 3 instances répondent :**

```bash
curl http://localhost:3001
curl http://localhost:3002
curl http://localhost:3003
```

**Chaque instance devrait renvoyer son ID.**

---

**Sauvegarder la configuration PM2 (redémarrage auto au boot) :**

```bash
sudo pm2 startup systemd
sudo pm2 save
```

---

### ÉTAPE 4 : Configurer Apache comme Reverse Proxy

**Activer les modules nécessaires :**

```bash
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_balancer
sudo a2enmod lbmethod_byrequests
sudo a2enmod headers
```

**Explication des modules :**

**proxy** : Module de base pour le proxying
**proxy_http** : Proxying HTTP/HTTPS
**proxy_balancer** : Load balancing
**lbmethod_byrequests** : Algorithme round-robin (répartition équitable)
**headers** : Manipulation des headers HTTP

---

**Créer le Virtual Host :**

```bash
sudo nano /etc/apache2/sites-available/nodejs-api.conf
```

**Contenu :**

```apache
<VirtualHost *:80>
    ServerName api.local
    ServerAdmin admin@example.com
    
    # ───────────────────────────────────────────────────────────
    # CONFIGURATION DU LOAD BALANCER
    # ───────────────────────────────────────────────────────────
    
    <Proxy "balancer://nodejs-cluster">
        # Backend 1
        BalancerMember http://localhost:3001 route=node1
        
        # Backend 2
        BalancerMember http://localhost:3002 route=node2
        
        # Backend 3
        BalancerMember http://localhost:3003 route=node3
        
        # Algorithme de répartition
        ProxySet lbmethod=byrequests
        
        # Session sticky (optionnel)
        ProxySet stickysession=ROUTEID
    </Proxy>
    
    # ───────────────────────────────────────────────────────────
    # CONFIGURATION DU REVERSE PROXY
    # ───────────────────────────────────────────────────────────
    
    # Proxier toutes les requêtes vers le cluster
    ProxyPass / balancer://nodejs-cluster/
    ProxyPassReverse / balancer://nodejs-cluster/
    
    # Préserver les headers d'origine
    ProxyPreserveHost On
    
    # Headers pour identifier le client original
    RequestHeader set X-Forwarded-Proto "http"
    RequestHeader set X-Forwarded-Port "80"
    
    # ───────────────────────────────────────────────────────────
    # LOGS
    # ───────────────────────────────────────────────────────────
    
    ErrorLog ${APACHE_LOG_DIR}/nodejs-api-error.log
    CustomLog ${APACHE_LOG_DIR}/nodejs-api-access.log combined
</VirtualHost>
```

**Sauvegarde.**

---

**Explications détaillées des directives :**

**`<Proxy "balancer://nodejs-cluster">`**

Définit un cluster (groupe) de backends nommé "nodejs-cluster"

---

**`BalancerMember http://localhost:3001 route=node1`**

Ajoute un membre au cluster

- `http://localhost:3001` = URL du backend
- `route=node1` = Identifiant de route (pour sticky sessions)

---

**`ProxySet lbmethod=byrequests`**

Définit l'algorithme de load balancing

**Algorithmes disponibles :**

| Méthode | Description | Usage |
|---------|-------------|-------|
| byrequests | Round-robin (à tour de rôle) | * Standard |
| bytraffic | Selon le volume de données | Fichiers lourds |
| bybusyness | Selon le nombre de requêtes actives | Requêtes lentes |
| heartbeat | Basé sur un monitoring externe | Avancé |

---

**`ProxySet stickysession=ROUTEID`**

**Session sticky** = Un client est toujours dirigé vers le même backend

**Pourquoi ?**
- Préserve les sessions (ex: panier e-commerce)
- Évite de perdre les données en mémoire

**Comment ça marche ?**
1. Le client fait une requête
2. Apache choisit un backend (ex: node1)
3. Apache ajoute un cookie `ROUTEID=node1`
4. Les requêtes suivantes vont toujours vers node1

**[ATTENTION] Désactive le vrai load balancing !**

En production, mieux vaut utiliser des sessions partagées (Redis, Memcached).

---

**`ProxyPass / balancer://nodejs-cluster/`**

Proxie toutes les requêtes (`/`) vers le cluster

Exemple :
- Requête client : `http://api.local/api/users`
- Apache proxie vers : `http://localhost:3001/api/users` (ou 3002 ou 3003)

---

**`ProxyPassReverse / balancer://nodejs-cluster/`**

Réécrit les headers `Location` et `Content-Location` dans les réponses

**Exemple concret :**

Backend répond avec :
```
Location: http://localhost:3001/redirect
```

Apache réécrit en :
```
Location: http://api.local/redirect
```

**Sans `ProxyPassReverse`, les redirections seraient cassées !**

---

**`ProxyPreserveHost On`**

Préserve le header `Host` original

**Sans ça :**
- Client envoie : `Host: api.local`
- Backend reçoit : `Host: localhost:3001`

**Avec :**
- Client envoie : `Host: api.local`
- Backend reçoit : `Host: api.local`

**Important pour les apps qui se basent sur le hostname !**

---

**`RequestHeader set X-Forwarded-Proto "http"`**

Ajoute un header indiquant le protocole original

Si Apache est en HTTP mais derrière un load balancer HTTPS :
```
X-Forwarded-Proto: https
```

Le backend sait que la requête originale était en HTTPS.

---

**Activer le site :**

```bash
sudo a2ensite nodejs-api.conf
sudo systemctl reload apache2
```

---

**Ajouter dans `/etc/hosts` (sur ton PC) :**

```
192.168.1.100    api.local
```

---

**Tester le reverse proxy :**

```bash
curl http://api.local/
```

**Résultat (exemple) :**

```json
{
  "message": "Bienvenue sur l'API Node.js",
  "instance": "Backend-1",
  "port": 3001,
  "timestamp": "2024-12-16T16:30:00.000Z"
}
```

---

**Tester le load balancing (faire 10 requêtes) :**

```bash
for i in {1..10}; do curl -s http://api.local/ | grep instance; done
```

**Résultat (round-robin) :**

```
"instance": "Backend-1",
"instance": "Backend-2",
"instance": "Backend-3",
"instance": "Backend-1",
"instance": "Backend-2",
"instance": "Backend-3",
...
```

**[OK] Les requêtes sont réparties équitablement entre les 3 backends !**

---

### ÉTAPE 5 : Ajouter le Balancer Manager (monitoring)

**Activer le gestionnaire de balancer :**

```bash
sudo a2enmod status
```

---

**Ajouter dans le Virtual Host (`nodejs-api.conf`) AVANT `</VirtualHost>` :**

```apache
    # ───────────────────────────────────────────────────────────
    # BALANCER MANAGER (MONITORING)
    # ───────────────────────────────────────────────────────────
    
    <Location "/balancer-manager">
        SetHandler balancer-manager
        Require ip 127.0.0.1 ::1
        # Pour autoriser ton IP :
        # Require ip 192.168.1.0/24
    </Location>
```

**Explication :**

- `/balancer-manager` = URL pour accéder au panneau de monitoring
- `SetHandler balancer-manager` = Active l'interface de gestion
- `Require ip 127.0.0.1` = Autorise seulement localhost

**[ATTENTION] En production, protéger avec un mot de passe !**

---

**Recharger Apache :**

```bash
sudo systemctl reload apache2
```

---

**Accéder au Balancer Manager :**

**Depuis le serveur :**

```bash
curl http://localhost/balancer-manager
```

**Depuis ton navigateur (si IP autorisée) :**

```
http://api.local/balancer-manager
```

**Interface web affichée :**

```
Load Balancer Manager for api.local

Balancer: balancer://nodejs-cluster
┌──────────────────┬────────┬────────┬─────────┬────────────┐
│ Worker URL       │ Route  │ Status │ Load    │ Elected    │
├──────────────────┼────────┼────────┼─────────┼────────────┤
│ http://localhost:3001 │ node1  │ Ok     │ 33.3%   │ 10 times   │
│ http://localhost:3002 │ node2  │ Ok     │ 33.3%   │ 10 times   │
│ http://localhost:3003 │ node3  │ Ok     │ 33.4%   │ 10 times   │
└──────────────────┴────────┴────────┴─────────┴────────────┘
```

**Tu peux :**
- Voir l'état de chaque backend (Ok, Error, Disabled)
- Désactiver temporairement un backend
- Voir les statistiques de charge

---

### ÉTAPE 6 : Tester le Failover (haute disponibilité)

**Failover** = Basculement automatique si un backend tombe

**Stopper une instance Node.js :**

```bash
sudo pm2 stop node-api-2
```

---

**Faire des requêtes :**

```bash
for i in {1..10}; do curl -s http://api.local/ | grep instance; done
```

**Résultat :**

```
"instance": "Backend-1",
"instance": "Backend-3",
"instance": "Backend-1",
"instance": "Backend-3",
...
```

**[OK] Backend-2 n'apparaît plus ! Apache a détecté qu'il était down.**

---

**Redémarrer Backend-2 :**

```bash
sudo pm2 start node-api-2
```

**Attendre quelques secondes, puis refaire des requêtes :**

```bash
for i in {1..10}; do curl -s http://api.local/ | grep instance; done
```

**Résultat :**

```
"instance": "Backend-1",
"instance": "Backend-2",  <- De retour !
"instance": "Backend-3",
...
```

**[OK] Failover automatique + retour automatique !**

---

### [OK] TESTS DE VALIDATION

**1. Load balancing fonctionne**

```bash
for i in {1..30}; do curl -s http://api.local/ | grep instance; done | sort | uniq -c
```

**Résultat attendu (répartition équitable) :**

```
     10 "instance": "Backend-1",
     10 "instance": "Backend-2",
     10 "instance": "Backend-3",
```

---

**2. Failover automatique**

- [ ] Stopper une instance : `pm2 stop node-api-1`
- [ ] Les requêtes vont vers les 2 autres instances
- [ ] Redémarrer : `pm2 start node-api-1`
- [ ] L'instance reprend du trafic automatiquement

---

**3. Headers de proxying**

```bash
curl -I http://api.local/
```

**Vérifier :**

```
X-Forwarded-For: ...
X-Forwarded-Proto: http
X-Forwarded-Port: 80
```

---

**4. Performance**

```bash
ab -n 1000 -c 10 http://api.local/
```

**Benchmarking avec ApacheBench :**
- `-n 1000` = 1000 requêtes
- `-c 10` = 10 requêtes concurrentes

**Résultat attendu : Répartition équitable dans les logs.**

---

### [IMPORTANT] POINTS CLÉS À RETENIR

**1. Reverse Proxy**
- Apache intercepte les requêtes et les transfère
- Cache en option (avec `mod_cache`)
- SSL termination possible

**2. Load Balancing**
- Répartition de charge entre plusieurs backends
- Algorithmes : round-robin, traffic, busyness
- Améliore performance et disponibilité

**3. Failover**
- Détection automatique des backends down
- Basculement automatique
- Retour automatique quand le backend revient

**4. Session Sticky**
- Un client toujours sur le même backend
- Utile pour sessions en mémoire
- Désactive le vrai load balancing

**5. Monitoring**
- Balancer Manager pour surveiller l'état
- Logs Apache pour audit
- PM2 pour monitorer Node.js

---

### [RAPIDE] POUR ALLER PLUS LOIN

**1. HTTPS avec Let's Encrypt**

```bash
sudo certbot --apache -d api.local
```

Certificat SSL automatique sur le reverse proxy.

---

**2. Cache avec mod_cache**

```apache
<IfModule mod_cache.c>
    CacheEnable disk /api/
    CacheRoot /var/cache/apache2/mod_cache_disk
</IfModule>
```

Mise en cache des réponses des backends.

---

**3. Compression**

```apache
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE application/json text/html
</IfModule>
```

Compression GZIP des réponses JSON.

---

**4. Rate Limiting**

```apache
<IfModule mod_ratelimit.c>
    SetOutputFilter RATE_LIMIT
    SetEnv rate-limit 400
</IfModule>
```

Limite le débit à 400 KB/s par connexion.

---

**5. WebSocket proxying**

```apache
ProxyPass /ws ws://localhost:3001/ws
ProxyPassReverse /ws ws://localhost:3001/ws
```

Proxying des connexions WebSocket.

---

## [COURS] CONCLUSION DE L'EXERCICE 4

**[OK] Félicitations ! Tu as configuré un reverse proxy avec load balancing !**

**Ce que tu as appris :**
- Installer et utiliser Node.js et PM2
- Créer une API REST simple avec Express
- Configurer Apache comme reverse proxy
- Mettre en place le load balancing
- Gérer le failover automatique
- Monitorer les backends avec Balancer Manager
- Comprendre les headers HTTP de proxying

**Compétences acquises :**
- [OK] Reverse proxy (niveau avancé)
- [OK] Load balancing et haute disponibilité
- [OK] Node.js et PM2 (niveau débutant)
- [OK] Architecture distribuée
- [OK] Monitoring et troubleshooting

**Temps moyen de réalisation :** 2-3 heures

**Prochaine étape :** Exercice 5 - Sécurisation complète (authentification, restrictions, headers) ! [VERROUILLE]

---

---

# [ROUGE] EXERCICE 5 : SÉCURISATION COMPLÈTE D'APACHE (EXPERT)

## [LISTE] ÉNONCÉ

### Contexte professionnel

Tu es consultant en sécurité pour une entreprise qui lance un nouveau service web. Le RSSI (Responsable Sécurité des Systèmes d'Information) exige un niveau de sécurité maximal avant la mise en production.

Ta mission : Auditer et sécuriser complètement un serveur Apache selon les meilleures pratiques de l'industrie.

### Objectifs

Implémenter une protection multi-niveaux :
1. **Authentification** : Protéger une zone admin avec mot de passe
2. **Restrictions d'accès** : IP whitelisting, géo-blocking basique
3. **Headers de sécurité** : HSTS, CSP, X-Frame-Options, etc.
4. **Protection anti-bot** : Rate limiting, blocage user-agents
5. **Durcissement Apache** : Masquer la version, désactiver modules inutiles
6. **Monitoring** : Alertes sur activités suspectes
7. **Conformité** : OWASP Top 10

### Contraintes

- Application de TOUTES les recommandations OWASP
- Conformité PCI-DSS niveau 1 (si e-commerce)
- Audit de sécurité avec outils automatisés
- Documentation complète
- Temps estimé : 3-5 heures

---

## [OBJECTIF] OBJECTIFS PÉDAGOGIQUES

À la fin de cet exercice, tu sauras :

- [OK] Mettre en place une authentification HTTP
- [OK] Restreindre l'accès par IP/réseau
- [OK] Configurer tous les headers de sécurité
- [OK] Implémenter du rate limiting
- [OK] Bloquer les bots malveillants
- [OK] Masquer les informations sensibles
- [OK] Monitorer et détecter les intrusions
- [OK] Auditer la sécurité avec Nikto/OpenVAS
- [OK] Appliquer le principe du moindre privilège

---

## [DOCS] PRÉREQUIS

- Tous les exercices précédents terminés
- Compréhension solide d'Apache
- Notions de sécurité web
- Familiarité avec les regex

---

## [OK] SOLUTION COMPLÈTE

### ÉTAPE 1 : Créer une zone admin protégée

**Créer le dossier admin :**

```bash
sudo mkdir -p /var/www/secure-site/admin
```

**Créer une page admin simple :**

```bash
sudo nano /var/www/secure-site/admin/index.html
```

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Zone Administration</title>
</head>
<body>
    <h1>[SECURISE] Zone Administration Sécurisée</h1>
    <p>Vous êtes authentifié. Bienvenue !</p>
    <ul>
        <li><a href="users.html">Gestion utilisateurs</a></li>
        <li><a href="settings.html">Paramètres</a></li>
        <li><a href="logs.html">Logs système</a></li>
    </ul>
</body>
</html>
```

---

**Créer le fichier de mots de passe :**

```bash
# Créer le dossier pour les fichiers d'auth
sudo mkdir -p /etc/apache2/auth

# Créer le fichier et ajouter un utilisateur
sudo htpasswd -c /etc/apache2/auth/.htpasswd admin
```

**`htpasswd` demande un mot de passe.**

**Entre un mot de passe fort :** `AdminSecure2024!`

**Explication de `htpasswd` :**

**`-c`** = Create (créer le fichier)
**[ATTENTION] N'utiliser -c que pour le PREMIER utilisateur !**

Pour ajouter d'autres utilisateurs :

```bash
# Ajouter "user2" (SANS -c)
sudo htpasswd /etc/apache2/auth/.htpasswd user2
```

---

**Sécuriser le fichier de mots de passe :**

```bash
sudo chown root:www-data /etc/apache2/auth/.htpasswd
sudo chmod 640 /etc/apache2/auth/.htpasswd
```

**Permissions 640 :**
- root : rw- (lecture/écriture)
- www-data : r-- (lecture seule)
- others : --- (aucun accès)

---

**Créer le Virtual Host sécurisé :**

```bash
sudo nano /etc/apache2/sites-available/secure-site.conf
```

**Contenu :**

```apache
# ═══════════════════════════════════════════════════════════════
# VIRTUAL HOST ULTRA-SÉCURISÉ
# ═══════════════════════════════════════════════════════════════

<VirtualHost *:443>
    ServerName secure.local
    ServerAdmin security@example.com
    DocumentRoot /var/www/secure-site
    
    # ───────────────────────────────────────────────────────────
    # SSL/TLS CONFIGURATION
    # ───────────────────────────────────────────────────────────
    
    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/secure.crt
    SSLCertificateKeyFile /etc/apache2/ssl/secure.key
    
    # Protocoles sécurisés uniquement
    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
    
    # Suites de chiffrement modernes et sécurisées
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
    SSLHonorCipherOrder on
    SSLCompression off
    
    # ───────────────────────────────────────────────────────────
    # HEADERS DE SÉCURITÉ (TOUS)
    # ───────────────────────────────────────────────────────────
    
    <IfModule mod_headers.c>
        # HSTS : Force HTTPS pendant 2 ans
        Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
        
        # Clickjacking protection
        Header always set X-Frame-Options "DENY"
        
        # XSS Protection
        Header always set X-XSS-Protection "1; mode=block"
        
        # MIME sniffing protection
        Header always set X-Content-Type-Options "nosniff"
        
        # Referrer Policy
        Header always set Referrer-Policy "strict-origin-when-cross-origin"
        
        # Content Security Policy (STRICT)
        Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self';"
        
        # Permissions Policy
        Header always set Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()"
        
        # Expect-CT (Certificate Transparency)
        Header always set Expect-CT "max-age=86400, enforce"
        
        # Masquer le serveur
        Header always unset X-Powered-By
        Header always unset Server
        Header always set Server "Webserver"
    </IfModule>
    
    # ───────────────────────────────────────────────────────────
    # CONFIGURATION RACINE DU SITE
    # ───────────────────────────────────────────────────────────
    
    <Directory /var/www/secure-site>
        Options -Indexes -FollowSymLinks
        AllowOverride None
        Require all granted
        
        # Bloquer l'accès aux fichiers cachés
        <FilesMatch "^\.">
            Require all denied
        </FilesMatch>
        
        # Bloquer les fichiers sensibles
        <FilesMatch "\.(bak|backup|sql|log|conf|ini|old)$">
            Require all denied
        </FilesMatch>
    </Directory>
    
    # ───────────────────────────────────────────────────────────
    # ZONE ADMIN PROTÉGÉE
    # ───────────────────────────────────────────────────────────
    
    <Directory /var/www/secure-site/admin>
        # Authentification HTTP Basic
        AuthType Basic
        AuthName "Zone Administration - Accès Restreint"
        AuthUserFile /etc/apache2/auth/.htpasswd
        Require valid-user
        
        # Restriction par IP (whitelist)
        <RequireAll>
            Require valid-user
            Require ip 192.168.1.0/24 127.0.0.1
        </RequireAll>
        
        # Rate limiting (limite de requêtes)
        <IfModule mod_ratelimit.c>
            SetOutputFilter RATE_LIMIT
            SetEnv rate-limit 500
        </IfModule>
    </Directory>
    
    # ───────────────────────────────────────────────────────────
    # BLOCAGE DES BOTS MALVEILLANTS
    # ───────────────────────────────────────────────────────────
    
    # Bloquer les User-Agents suspects
    SetEnvIfNoCase User-Agent "^-$" bad_bot
    SetEnvIfNoCase User-Agent "^$" bad_bot
    SetEnvIfNoCase User-Agent "masscan" bad_bot
    SetEnvIfNoCase User-Agent "nikto" bad_bot
    SetEnvIfNoCase User-Agent "sqlmap" bad_bot
    SetEnvIfNoCase User-Agent "nmap" bad_bot
    SetEnvIfNoCase User-Agent "python-requests" bad_bot
    SetEnvIfNoCase User-Agent "curl" bad_bot
    SetEnvIfNoCase User-Agent "wget" bad_bot
    
    <RequireAll>
        Require all granted
        Require not env bad_bot
    </RequireAll>
    
    # ───────────────────────────────────────────────────────────
    # PROTECTION CONTRE LES INJECTIONS SQL ET XSS
    # ───────────────────────────────────────────────────────────
    
    # Bloquer les patterns d'injection SQL
    <If "%{QUERY_STRING} =~ /(union|select|insert|update|delete|drop|create|alter|--|')/i">
        Require all denied
    </If>
    
    # Bloquer les patterns XSS
    <If "%{QUERY_STRING} =~ /(<script|<iframe|javascript:|onerror|onload)/i">
        Require all denied
    </If>
    
    # ───────────────────────────────────────────────────────────
    # LOGS DÉTAILLÉS
    # ───────────────────────────────────────────────────────────
    
    # Format de log personnalisé avec plus d'infos
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %T %D" combined_plus
    
    ErrorLog ${APACHE_LOG_DIR}/secure-site-error.log
    CustomLog ${APACHE_LOG_DIR}/secure-site-access.log combined_plus
    
</VirtualHost>

# ═══════════════════════════════════════════════════════════════
# REDIRECTION HTTP -> HTTPS
# ═══════════════════════════════════════════════════════════════

<VirtualHost *:80>
    ServerName secure.local
    Redirect permanent / https://secure.local/
</VirtualHost>
```

**Sauvegarde.**

---

**Créer le certificat SSL :**

```bash
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/apache2/ssl/secure.key \
  -out /etc/apache2/ssl/secure.crt \
  -subj "/C=SN/ST=Dakar/L=Dakar/O=SecureCompany/CN=secure.local"
```

---

**Activer le site :**

```bash
sudo a2ensite secure-site.conf
sudo systemctl reload apache2
```

---

**Ajouter dans `/etc/hosts` :**

```
192.168.1.100    secure.local
```

---

**Tester l'authentification :**

```
https://secure.local/admin/
```

**Une popup demandant login/mot de passe devrait apparaître.**

**Identifiants :** `admin` / `AdminSecure2024!`

---

### ÉTAPE 2 : Durcir la configuration globale d'Apache

**Éditer la configuration de sécurité :**

```bash
sudo nano /etc/apache2/conf-available/security.conf
```

**Modifier/ajouter :**

```apache
# Masquer la version d'Apache
ServerTokens Prod
ServerSignature Off

# Désactiver TRACE (protection CSRF)
TraceEnable Off

# Timeout réduit (anti-slowloris)
Timeout 30

# Limiter la taille des requêtes
LimitRequestBody 10485760

# Limiter le nombre de headers
LimitRequestFields 50

# Limiter la taille d'un header
LimitRequestFieldSize 8190

# Limiter la longueur de l'URL
LimitRequestLine 8190
```

---

**Activer la configuration :**

```bash
sudo a2enconf security
sudo systemctl reload apache2
```

---

### ÉTAPE 3 : Installer et configurer ModSecurity (WAF)

**ModSecurity** = Web Application Firewall (pare-feu applicatif)

```bash
sudo apt install libapache2-mod-security2 -y
```

---

**Activer ModSecurity :**

```bash
sudo mv /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
```

---

**Éditer la configuration :**

```bash
sudo nano /etc/modsecurity/modsecurity.conf
```

**Modifier :**

```
SecRuleEngine On
```

**Par défaut, c'est `DetectionOnly` (logs seulement, pas de blocage).**

---

**Installer les règles OWASP Core Rule Set (CRS) :**

```bash
sudo apt install modsecurity-crs -y
```

**Les règles CRS protègent contre :**
- Injections SQL
- XSS (Cross-Site Scripting)
- LFI/RFI (Local/Remote File Inclusion)
- Command Injection
- Session Fixation
- etc.

---

**Activer les règles :**

```bash
sudo ln -s /usr/share/modsecurity-crs /etc/modsecurity/crs
```

---

**Créer le fichier de configuration pour charger les règles :**

```bash
sudo nano /etc/apache2/mods-available/security2.conf
```

**Ajouter :**

```apache
<IfModule security2_module>
    SecDataDir /var/cache/modsecurity
    IncludeOptional /etc/modsecurity/*.conf
    IncludeOptional /usr/share/modsecurity-crs/owasp-crs.load
    IncludeOptional /usr/share/modsecurity-crs/rules/*.conf
</IfModule>
```

---

**Redémarrer Apache :**

```bash
sudo systemctl restart apache2
```

---

**Tester ModSecurity (injection SQL) :**

```bash
curl "https://secure.local/?id=1' OR '1'='1"
```

**Résultat attendu : 403 Forbidden**

**Les logs ModSecurity (`/var/log/apache2/modsec_audit.log`) contiennent les détails.**

---

### ÉTAPE 4 : Fail2Ban (protection brute-force)

**Installer Fail2Ban :**

```bash
sudo apt install fail2ban -y
```

---

**Créer la jail Apache :**

```bash
sudo nano /etc/fail2ban/jail.d/apache-auth.conf
```

**Contenu :**

```ini
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/apache2/*error.log
maxretry = 3
bantime = 3600
findtime = 600
```

**Explication :**

- **maxretry = 3** : 3 échecs d'authentification
- **bantime = 3600** : Bannir pendant 1 heure
- **findtime = 600** : Dans une fenêtre de 10 minutes

---

**Redémarrer Fail2Ban :**

```bash
sudo systemctl restart fail2ban
```

---

**Vérifier les jails actives :**

```bash
sudo fail2ban-client status
```

---

**Simuler une attaque (3 échecs d'auth) :**

```bash
for i in {1..5}; do curl -u wrong:wrong https://secure.local/admin/; done
```

---

**Vérifier que l'IP est bannie :**

```bash
sudo fail2ban-client status apache-auth
```

**Résultat :**

```
Status for the jail: apache-auth
|- Filter
|  |- Currently failed: 0
|  `- Total failed:     5
`- Actions
   |- Currently banned: 1
   |- Total banned:     1
   `- Banned IP list:   192.168.1.50
```

---

**Débannir manuellement (si nécessaire) :**

```bash
sudo fail2ban-client set apache-auth unbanip 192.168.1.50
```

---

### ÉTAPE 5 : Monitoring et alertes

**Installer Logwatch (rapports quotidiens par email) :**

```bash
sudo apt install logwatch -y
```

---

**Configuration :**

```bash
sudo nano /etc/logwatch/conf/logwatch.conf
```

**Modifier :**

```
MailTo = admin@example.com
Detail = High
Service = apache
```

---

**Tester manuellement :**

```bash
sudo logwatch --detail High --service apache --range today
```

---

**Script de monitoring personnalisé :**

```bash
sudo nano /usr/local/bin/apache-security-monitor.sh
```

**Contenu :**

```bash
#!/bin/bash

LOG_FILE="/var/log/apache2/secure-site-access.log"
ALERT_EMAIL="admin@example.com"

# Détecter les tentatives d'injection SQL
SQL_ATTACKS=$(grep -i "union\|select\|insert" $LOG_FILE | wc -l)

if [ $SQL_ATTACKS -gt 10 ]; then
    echo "ALERTE : $SQL_ATTACKS tentatives d'injection SQL détectées !" | \
      mail -s "Alerte Sécurité Apache" $ALERT_EMAIL
fi

# Détecter les scans de ports
SCAN_ATTEMPTS=$(grep "404" $LOG_FILE | wc -l)

if [ $SCAN_ATTEMPTS -gt 100 ]; then
    echo "ALERTE : $SCAN_ATTEMPTS erreurs 404 détectées (scan ?) !" | \
      mail -s "Alerte Sécurité Apache" $ALERT_EMAIL
fi
```

---

**Rendre exécutable :**

```bash
sudo chmod +x /usr/local/bin/apache-security-monitor.sh
```

---

**Programmer dans cron (toutes les heures) :**

```bash
sudo crontab -e
```

**Ajouter :**

```
0 * * * * /usr/local/bin/apache-security-monitor.sh
```

---

### ÉTAPE 6 : Audit de sécurité

**Installer Nikto (scanner de vulnérabilités) :**

```bash
sudo apt install nikto -y
```

---

**Scanner le site :**

```bash
nikto -h https://secure.local
```

**Nikto va tester :**
- Versions obsolètes
- Fichiers sensibles exposés
- Vulnérabilités connues
- Configurations dangereuses

---

**Analyser les résultats et corriger.**

---

**Tester SSL avec SSLLabs (en ligne) :**

En production : https://www.ssllabs.com/ssltest/

**Objectif : Note A ou A+**

---

### [OK] CHECKLIST DE SÉCURITÉ FINALE

**Configuration Apache :**
- [ ] ServerTokens Prod
- [ ] ServerSignature Off
- [ ] TraceEnable Off
- [ ] Timeouts configurés
- [ ] Limites de requêtes définies

**SSL/TLS :**
- [ ] HTTPS activé
- [ ] Protocoles obsolètes désactivés (SSLv2, SSLv3, TLS 1.0/1.1)
- [ ] Suites de chiffrement modernes
- [ ] Certificat valide

**Headers de sécurité :**
- [ ] HSTS
- [ ] X-Frame-Options
- [ ] X-XSS-Protection
- [ ] X-Content-Type-Options
- [ ] Content-Security-Policy
- [ ] Referrer-Policy
- [ ] Permissions-Policy

**Authentification :**
- [ ] Zone admin protégée par mot de passe
- [ ] Fichier .htpasswd sécurisé (permissions 640)
- [ ] Mots de passe forts

**Restrictions d'accès :**
- [ ] IP whitelisting sur zone sensible
- [ ] Fichiers cachés bloqués
- [ ] Fichiers sensibles bloqués (.bak, .sql, etc.)

**Protection anti-bot :**
- [ ] User-Agents malveillants bloqués
- [ ] Rate limiting configuré

**Protection injection :**
- [ ] Patterns SQL bloqués
- [ ] Patterns XSS bloqués
- [ ] ModSecurity + OWASP CRS activés

**Anti brute-force :**
- [ ] Fail2Ban configuré
- [ ] Bannissement automatique après échecs

**Monitoring :**
- [ ] Logs détaillés activés
- [ ] Logwatch configuré
- [ ] Script de surveillance personnalisé
- [ ] Alertes email configurées

**Audit :**
- [ ] Scan Nikto effectué
- [ ] Vulnérabilités corrigées
- [ ] Test SSL (grade A minimum)

---

## [COURS] CONCLUSION DE L'EXERCICE 5

**[BRAVO] BRAVO ! Tu maîtrises maintenant la sécurisation Apache au niveau expert ! [BRAVO]**

**Ce que tu as appris :**
- Authentification HTTP Basic
- Restrictions d'accès multi-niveaux
- Tous les headers de sécurité
- Web Application Firewall (ModSecurity)
- Protection anti-brute-force (Fail2Ban)
- Blocage de bots malveillants
- Rate limiting
- Monitoring et alertes
- Audit de sécurité

**Compétences acquises :**
- [OK] Sécurité Apache (niveau expert)
- [OK] Conformité OWASP Top 10
- [OK] ModSecurity et règles CRS
- [OK] Fail2Ban et détection d'intrusion
- [OK] Monitoring et réponse aux incidents
- [OK] Audit de sécurité

**Temps moyen de réalisation :** 3-5 heures

---

---

# [COURS] CONCLUSION GÉNÉRALE DES 5 EXERCICES

## [TROPHEE] FÉLICITATIONS !

**Tu as terminé les 5 exercices Apache avec succès !**

Tu es maintenant capable de :
- [OK] Créer et gérer des sites web statiques et dynamiques
- [OK] Sécuriser avec HTTPS et certificats SSL
- [OK] Développer des applications PHP avec MySQL
- [OK] Configurer des reverse proxies et load balancers
- [OK] Sécuriser complètement un serveur Apache

**Niveau atteint : EXPERT Apache ! [RAPIDE]**

## [GRAPHIQUE] RÉCAPITULATIF DES COMPÉTENCES

| Exercice | Niveau | Compétences clés |
|----------|--------|------------------|
| 1 - Portfolio | [VERT] Débutant | HTML/CSS, Virtual Hosts, structure web |
| 2 - HTTPS | [JAUNE] Intermédiaire | SSL/TLS, certificats, redirection, headers |
| 3 - Blog PHP | [JAUNE] Intermédiaire | PHP-FPM, MySQL, PDO, sécurité (SQL injection, XSS) |
| 4 - Reverse Proxy | [ROUGE] Avancé | Load balancing, haute disponibilité, Node.js |
| 5 - Sécurisation | [ROUGE] Expert | WAF, Fail2Ban, audit, conformité OWASP |

## [OBJECTIF] PROCHAINES ÉTAPES

**Pour continuer ta progression :**

1. **Projets réels** : Déploie un vrai site en production
2. **Certifications** : LPIC-2, RedHat RHCE
3. **Technologies** : Nginx, Kubernetes, Docker
4. **Cloud** : AWS, Azure, GCP
5. **Monitoring** : Prometheus, Grafana, ELK Stack

**Bon courage dans ta carrière de DevOps/SysAdmin ! [FORCE]**

---

**FIN DES EXERCICES APACHE**

═══════════════════════════════════════════════════════════════