# [COURS] GUIDE LAMP ULTRA DÉTAILLÉ - PARTIE 1/3
## Linux + Apache + MySQL + PHP

## * INTRODUCTION

### Qu'est-ce que LAMP ?

**LAMP** est un acronyme désignant une **stack** (pile technologique) très populaire pour créer des sites web et applications web dynamiques.

**Décomposition :**

```
L = Linux         <- Système d'exploitation (Ubuntu, Debian, etc.)
A = Apache        <- Serveur web (sert les pages HTML)
M = MySQL/MariaDB <- Base de données (stocke les données)
P = PHP           <- Langage de programmation (génère du contenu dynamique)
```

**Analogie restaurant :**

```
┌─────────────────────────────────────────────────────┐
│  [ENTREPRISE] RESTAURANT (Serveur Web)                        │
├─────────────────────────────────────────────────────┤
│                                                     │
│  [FORK_AND_KNIFE_WITH_PLATE]  Apache = Le serveur qui apporte les plats     │
│       (distribue les pages web aux visiteurs)       │
│                                                     │
│  [PERSONNE][COOKING] PHP = Le cuisinier                              │
│       (prépare le contenu dynamique)                │
│                                                     │
│  [PACKAGE] MySQL = Le frigo/garde-manger                   │
│       (stocke les ingrédients/données)              │
│                                                     │
│  [LINUX] Linux = Le bâtiment du restaurant               │
│       (l'infrastructure de base)                    │
│                                                     │
└─────────────────────────────────────────────────────┘
```

**Flux d'une requête web :**

```
1. Utilisateur tape : www.monsite.com
   v
2. Linux route la requête
   v
3. Apache reçoit la demande
   v
4. Apache voit que c'est un fichier .php
   v
5. PHP exécute le code
   v
6. PHP demande des données à MySQL (si besoin)
   v
7. MySQL renvoie les données
   v
8. PHP génère le HTML final
   v
9. Apache envoie le HTML à l'utilisateur
   v
10. L'utilisateur voit la page web
```

---

### Pourquoi utiliser LAMP ?

**[OK] Avantages :**

|--------------------|------------------------------------------------------------------|
| Avantage           | Description                                                      |
|--------------------|------------------------------------------------------------------|
| **Gratuit**        | Tous les composants sont open-source (0€)                        |
| **Populaire**      | Énorme communauté, beaucoup de tutoriels                         |
| **Flexible**       | Fonctionne pour sites simples ou complexes                       |
| **Performant**     | Bien optimisé pour le web                                        |
| **Compatible**     | La plupart des CMS fonctionnent dessus (WordPress, Joomla, etc.) |
| **Bien documenté** | Des millions de pages de documentation                           |
| **Stable**         | Technologies éprouvées depuis 20+ ans                            |
|--------------------|------------------------------------------------------------------|

**[X] Alternatives modernes :**

|--------------|---------------------------------------|-----------------------------------------|
| Stack        | Description                           | Usage                                   |
|--------------|---------------------------------------|-----------------------------------------|
| **LEMP**     | Linux + Nginx + MySQL + PHP           | Plus rapide qu'Apache pour certains cas |
| **MEAN**     | MongoDB + Express + Angular + Node.js | Applications JavaScript full-stack      |
| **MERN**     | MongoDB + Express + React + Node.js   | Applications React modernes             |
| **JAMstack** | JavaScript + APIs + Markup            | Sites statiques ultra-rapides           |
|--------------|---------------------------------------|-----------------------------------------|

**[GRAPHIQUE] Parts de marché (2024) :**

```
Apache : ~30% des serveurs web
Nginx : ~35% des serveurs web
LAMP reste TRÈS utilisé pour WordPress, forums, e-commerce
```

---

### Quand utiliser LAMP ?

**[OK] OUI si tu veux créer :**

- Un blog (WordPress, Ghost)
- Un forum (phpBB, MyBB)
- Un site e-commerce (WooCommerce, PrestaShop)
- Un CMS personnalisé
- Une application web en PHP
- Un intranet d'entreprise
- Un site de gestion (CRM, ERP)

**[X] NON si tu veux créer :**

- Une application mobile native
- Un site statique simple (utilise Nginx + HTML)
- Une application en temps réel (utilise Node.js)
- Un jeu vidéo

---

## [LISTE] PRÉREQUIS

**Avant de commencer, assure-toi d'avoir :**

[OK] Une VM Linux (Ubuntu 22.04 ou 24.04 recommandé)
[OK] Au moins 2 GB de RAM
[OK] 20 GB d'espace disque
[OK] Connexion Internet
[OK] Accès root/sudo
[OK] Connaissances de base en ligne de commande Linux

**Configuration réseau recommandée :**

```
[WEB] Internet
   v
[RESEAU] Box/Routeur (192.168.1.1)
   │
   ├─ [CODE] VM Linux (192.168.1.100) <- Serveur LAMP
   │     └─ Apache écoute sur le port 80
   │
   └─ [ECRAN] Ton PC (192.168.1.X)
         └─ Accède au site via http://192.168.1.100
```

---

## [OUTIL] PARTIE 1 : INSTALLATION ET CONFIGURATION D'APACHE

### [ENTREE] Étape 1.1 : Installation d'Apache

**Apache** = Le serveur web (le "A" de LAMP)

**Rôle d'Apache :**
- Écoute sur le port 80 (HTTP) et 443 (HTTPS)
- Reçoit les requêtes des navigateurs
- Envoie les fichiers HTML/CSS/JS/images
- Exécute PHP quand nécessaire

**1. Mettre à jour les paquets :**

```bash
sudo apt update
```

**Explication :**
Rafraîchit la liste des paquets disponibles.

---

**2. Installer Apache :**

```bash
sudo apt install apache2 -y
```

**Explication :**
- `apache2` = Le paquet du serveur web Apache
- `-y` = Accepte automatiquement l'installation

**Résultat attendu :**

```
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  apache2 apache2-bin apache2-data apache2-utils ...
...
Setting up apache2 (2.4.52-1ubuntu4.12) ...
Created symlink /etc/systemd/system/multi-user.target.wants/apache2.service
```

**[OK] Installation terminée !**

---

**3. Vérifier la version :**

```bash
apache2 -v
```

**Résultat (exemple) :**

```
Server version: Apache/2.4.52 (Ubuntu)
Server built:   2024-10-10T19:24:20
```

---

**4. Vérifier que le service est actif :**

```bash
sudo systemctl status apache2
```

**Résultat attendu :**

```
[BLACK_CIRCLE] apache2.service - The Apache HTTP Server
     Loaded: loaded (/lib/systemd/system/apache2.service; enabled)
     Active: active (running) since Tue 2024-12-17 10:00:00 UTC; 2min ago
                     ^^^^^^^^^^^^^^^
                IL TOURNE ! [OK]
       Docs: https://httpd.apache.org/docs/2.4/
   Main PID: 1234 (apache2)
      Tasks: 55 (limit: 2312)
     Memory: 12.4M
        CPU: 245ms
     CGroup: /system.slice/apache2.service
             ├─1234 /usr/sbin/apache2 -k start
             ├─1235 /usr/sbin/apache2 -k start
             └─1236 /usr/sbin/apache2 -k start
```

**[OK] Si tu vois "active (running)", c'est parfait !**

---

**5. Tester depuis ton navigateur :**

**Ouvre ton navigateur sur ton PC et va à :**

```
http://192.168.1.100
```

**[ATTENTION] Remplace `192.168.1.100` par l'IP de ta VM !**

**Pour trouver l'IP de ta VM :**

```bash
ip a
```

**Cherche l'IP dans la section de ton interface réseau (ex: `enp0s3`).**

---

**Résultat attendu dans le navigateur :**

```
┌───────────────────────────────────────────────────┐
│  Apache2 Ubuntu Default Page                      │
│                                                   │
│  It works!                                        │
│                                                   │
│  This is the default welcome page used to test    │
│  the correct operation of the Apache2 server      │
│  after installation on Ubuntu systems.            │
└───────────────────────────────────────────────────┘
```

**[BRAVO] SI TU VOIS CETTE PAGE, APACHE FONCTIONNE ! [BRAVO]**

---

### [DOSSIER] Étape 1.2 : Comprendre la structure d'Apache

**Apache utilise plusieurs répertoires importants :**

```
/etc/apache2/
├── apache2.conf              <- Configuration principale
├── ports.conf                <- Ports d'écoute (80, 443)
├── sites-available/          <- Sites disponibles (configs)
│   ├── 000-default.conf      <- Site par défaut
│   └── default-ssl.conf      <- Site HTTPS
├── sites-enabled/            <- Sites activés (symlinks)
│   └── 000-default.conf      <- Lien vers sites-available
├── mods-available/           <- Modules disponibles
├── mods-enabled/             <- Modules activés
├── conf-available/           <- Configurations disponibles
└── conf-enabled/             <- Configurations activées

/var/www/
└── html/                     <- Répertoire web par défaut
    └── index.html            <- Page d'accueil par défaut

/var/log/apache2/
├── access.log                <- Journal des accès
└── error.log                 <- Journal des erreurs
```

**Fichiers clés à connaître :**

**1. `/etc/apache2/apache2.conf`** <- Configuration globale

**2. `/etc/apache2/sites-available/000-default.conf`** <- Config du site par défaut

**3. `/var/www/html/`** <- Où tu mets tes fichiers web

**4. `/var/log/apache2/access.log`** <- Qui a visité ton site

**5. `/var/log/apache2/error.log`** <- Les erreurs

---

### [WEB] Étape 1.3 : Créer ta première page web

**Remplaçons la page par défaut par notre propre page !**

**1. Éditer la page d'accueil :**

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

**2. Supprime TOUT le contenu et remplace par :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Mon Premier Serveur LAMP</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            text-align: center;
            padding: 50px;
            margin: 0;
        }
        .container {
            background: rgba(255, 255, 255, 0.1);
            border-radius: 20px;
            padding: 40px;
            max-width: 600px;
            margin: 0 auto;
            backdrop-filter: blur(10px);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
        }
        h1 {
            font-size: 3em;
            margin: 0;
            text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
        }
        p {
            font-size: 1.2em;
            margin-top: 20px;
        }
        .info {
            background: rgba(255, 255, 255, 0.2);
            border-radius: 10px;
            padding: 20px;
            margin-top: 30px;
        }
        .success {
            color: #4ade80;
            font-weight: bold;
            font-size: 1.5em;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>[RAPIDE] Ça marche !</h1>
        <p class="success">[OK] Apache est installé et fonctionne</p>
        <div class="info">
            <p><strong>[LINUX] Système :</strong> Linux</p>
            <p><strong>[WEB] Serveur Web :</strong> Apache 2.4</p>
            <p><strong>[IMPORTANT] Répertoire :</strong> /var/www/html</p>
            <p><strong>[OBJECTIF] Prochaine étape :</strong> Installer PHP et MySQL</p>
        </div>
        <p style="margin-top: 30px; font-size: 0.9em;">
            [COURS] Guide LAMP - Tutoriel ultra-détaillé pour débutants
        </p>
    </div>
</body>
</html>
```

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

---

**4. Rafraîchis ton navigateur :**

```
http://192.168.1.100
```

**Tu devrais voir une belle page avec un dégradé violet ! [DESIGN]***

---

### [CONFIG] Étape 1.4 : Comprendre les Virtual Hosts

**Virtual Host** = Héberger plusieurs sites sur un seul serveur

**Exemple :**

```
Un seul serveur (192.168.1.100) héberge :
- www.site1.com
- www.site2.com
- www.blog.com
```

**Apache utilise le nom de domaine demandé pour savoir quel site servir.**

---

**Architecture des Virtual Hosts :**

```
/var/www/
├── site1/
│   └── index.html          <- Site 1
├── site2/
│   └── index.html          <- Site 2
└── blog/
    └── index.html          <- Site 3

/etc/apache2/sites-available/
├── site1.conf              <- Config Virtual Host 1
├── site2.conf              <- Config Virtual Host 2
└── blog.conf               <- Config Virtual Host 3
```

---

**Créons un Virtual Host pour notre projet :**

**1. Créer un répertoire pour notre site :**

```bash
sudo mkdir -p /var/www/monsite
```

**Explication :**
- `mkdir` = Make directory (créer un dossier)
- `-p` = Créer les dossiers parents si nécessaire
- `/var/www/monsite` = Notre nouveau répertoire web

---

**2. Créer une page d'accueil :**

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

**Contenu :**

```html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Mon Site</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: #1a1a2e;
            color: #eee;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .card {
            background: #16213e;
            border-radius: 15px;
            padding: 40px;
            box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
            text-align: center;
        }
        h1 {
            color: #00d9ff;
            margin: 0 0 20px 0;
        }
        p {
            font-size: 1.1em;
            line-height: 1.6;
        }
    </style>
</head>
<body>
    <div class="card">
        <h1>* Mon Site Web</h1>
        <p>Ceci est mon premier Virtual Host Apache !</p>
        <p>Répertoire : <code>/var/www/monsite</code></p>
    </div>
</body>
</html>
```

**Sauvegarde.**

---

**3. Définir les permissions :**

```bash
sudo chown -R www-data:www-data /var/www/monsite
sudo chmod -R 755 /var/www/monsite
```

**Explication :**

- `chown` = Change owner (changer le propriétaire)
- `www-data` = Utilisateur sous lequel Apache tourne
- `-R` = Récursif (tous les fichiers et sous-dossiers)
- `chmod 755` = Permissions :
  - `7` = Propriétaire : lecture + écriture + exécution
  - `5` = Groupe : lecture + exécution
  - `5` = Autres : lecture + exécution

**Pourquoi `www-data` ?**

Apache tourne sous l'utilisateur `www-data` pour des raisons de sécurité. Si les fichiers appartiennent à `www-data`, Apache peut les lire/écrire.

---

**4. Créer un fichier de configuration Virtual Host :**

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

**Contenu :**

```apache
# ═══════════════════════════════════════════════════════════════
# VIRTUAL HOST - MON SITE
# ═══════════════════════════════════════════════════════════════

<VirtualHost *:80>
    # Informations générales
    ServerName monsite.local
    ServerAlias www.monsite.local
    ServerAdmin admin@monsite.local
    
    # Répertoire racine du site
    DocumentRoot /var/www/monsite
    
    # Configuration du répertoire
    <Directory /var/www/monsite>
        Options Indexes FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    # Logs
    ErrorLog ${APACHE_LOG_DIR}/monsite_error.log
    CustomLog ${APACHE_LOG_DIR}/monsite_access.log combined
</VirtualHost>

# Explication ligne par ligne :
#
# <VirtualHost *:80>
# Écouter sur TOUTES les interfaces (*) sur le port 80 (HTTP)
#
# ServerName monsite.local
# Nom de domaine principal du site
#
# ServerAlias www.monsite.local
# Alias (autre nom) pour accéder au site
#
# ServerAdmin admin@monsite.local
# Email de l'administrateur (affiché en cas d'erreur)
#
# DocumentRoot /var/www/monsite
# Répertoire racine où se trouvent les fichiers du site
#
# <Directory /var/www/monsite>
# Configuration spécifique à ce répertoire
#
# Options Indexes FollowSymLinks
# - Indexes : Afficher la liste des fichiers si pas de index.html
# - FollowSymLinks : Suivre les liens symboliques
#
# AllowOverride All
# Autoriser les fichiers .htaccess à modifier la config
#
# Require all granted
# Autoriser l'accès à tout le monde
#
# ErrorLog ${APACHE_LOG_DIR}/monsite_error.log
# Fichier de log des erreurs pour ce site
#
# CustomLog ${APACHE_LOG_DIR}/monsite_access.log combined
# Fichier de log des accès pour ce site
```

**Sauvegarde.**

---

**5. Activer le Virtual Host :**

```bash
sudo a2ensite monsite.conf
```

**Explication :**
- `a2ensite` = Apache 2 Enable Site
- Crée un lien symbolique dans `/etc/apache2/sites-enabled/`

**Résultat :**

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

---

**6. Désactiver le site par défaut (optionnel) :**

```bash
sudo a2dissite 000-default.conf
```

**Explication :**
- `a2dissite` = Apache 2 Disable Site
- Supprime le lien symbolique de `sites-enabled/`

---

**7. Recharger Apache :**

```bash
sudo systemctl reload apache2
```

**Ou redémarrer complètement :**

```bash
sudo systemctl restart apache2
```

**Différence :**
- `reload` = Recharge la config sans couper les connexions
- `restart` = Arrête et redémarre (coupe brièvement)

---

**8. Ajouter `monsite.local` dans le fichier hosts :**

**Sur ta VM :**

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

**Ajoute cette ligne :**

```
127.0.0.1    monsite.local www.monsite.local
```

**Sur ton PC Windows :**

```
1. Ouvre le Bloc-notes EN TANT QU'ADMINISTRATEUR
2. Fichier > Ouvrir : C:\Windows\System32\drivers\etc\hosts
3. Ajoute cette ligne :
   192.168.1.100    monsite.local www.monsite.local
4. Sauvegarde
```

---

**9. Tester dans le navigateur :**

```
http://monsite.local
```

**Tu devrais voir ta nouvelle page "Mon Site Web" ! [BRAVO]**

---

### [RECHERCHE] Étape 1.5 : Logs et débogage Apache

**Logs = Journaux = Historique de ce qui s'est passé**

**Où sont les logs ?**

```
/var/log/apache2/
├── access.log        <- Toutes les requêtes
├── error.log         <- Toutes les erreurs
├── monsite_access.log <- Requêtes de ton Virtual Host
└── monsite_error.log  <- Erreurs de ton Virtual Host
```

---

**Voir les logs en temps réel :**

```bash
# Logs généraux
sudo tail -f /var/log/apache2/access.log

# Logs d'erreur
sudo tail -f /var/log/apache2/error.log

# Logs de ton site
sudo tail -f /var/log/apache2/monsite_access.log
```

**Explication :**
- `tail` = Afficher la fin d'un fichier
- `-f` = Follow (suivre en temps réel)

**Pour arrêter : `Ctrl + C`**

---

**Exemple de log d'accès :**

```
192.168.1.50 - - [17/Dec/2024:10:30:45 +0000] "GET / HTTP/1.1" 200 1234 "-" "Mozilla/5.0..."
^            ^ ^ ^                            ^   ^       ^    ^   ^
│            │ │ │                            │   │       │    │   └─ User-Agent (navigateur)
│            │ │ │                            │   │       │    └───── Taille (bytes)
│            │ │ │                            │   │       └────────── Code HTTP (200 = OK)
│            │ │ │                            │   └────────────────── Version HTTP
│            │ │ │                            └────────────────────── Méthode (GET)
│            │ │ └───────────────────────────────────────────────── Date/Heure
│            │ └─────────────────────────────────────────────────── User (- = anonyme)
│            └───────────────────────────────────────────────────── Auth user
└────────────────────────────────────────────────────────────────── IP du visiteur
```

---

**Codes HTTP importants :**

| Code | Signification | Description |
|------|---------------|-------------|
| **200** | OK | Tout va bien |
| **301** | Moved Permanently | Redirection permanente |
| **302** | Found | Redirection temporaire |
| **304** | Not Modified | Contenu en cache (pas changé) |
| **400** | Bad Request | Requête invalide |
| **401** | Unauthorized | Authentification requise |
| **403** | Forbidden | Accès interdit |
| **404** | Not Found | Page introuvable |
| **500** | Internal Server Error | Erreur du serveur |
| **502** | Bad Gateway | Problème de proxy |
| **503** | Service Unavailable | Serveur surchargé |

---

**Analyser les logs :**

**Compter les visites uniques :**

```bash
awk '{print $1}' /var/log/apache2/access.log | sort -u | wc -l
```

**Voir les 10 IPs les plus actives :**

```bash
awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -10
```

**Voir les pages les plus visitées :**

```bash
awk '{print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -10
```

**Voir les erreurs 404 :**

```bash
grep " 404 " /var/log/apache2/access.log
```

---

### [OUTILS] Étape 1.6 : Modules Apache importants

**Apache est modulaire = fonctionnalités activables/désactivables**

**Lister les modules actifs :**

```bash
apache2ctl -M
```

**Ou :**

```bash
sudo apachectl -M
```

**Résultat (extrait) :**

```
 core_module (static)
 so_module (static)
 mpm_event_module (shared)
 rewrite_module (shared)
 ssl_module (shared)
 ...
```

---

**Modules essentiels à activer :**

**1. mod_rewrite (URL Rewriting) :**

```bash
sudo a2enmod rewrite
```

**Usage :**
- Réécrire les URLs (ex: `page.php?id=5` -> `/page/5`)
- Utilisé par WordPress, Joomla, etc.

---

**2. mod_ssl (HTTPS) :**

```bash
sudo a2enmod ssl
```

**Usage :**
- Activer HTTPS (connexions sécurisées)
- Nécessaire pour les certificats SSL/TLS

---

**3. mod_headers (Gestion des en-têtes HTTP) :**

```bash
sudo a2enmod headers
```

**Usage :**
- Ajouter/modifier des en-têtes HTTP
- Sécurité (CORS, CSP, etc.)

---

**4. mod_expires (Gestion du cache) :**

```bash
sudo a2enmod expires
```

**Usage :**
- Contrôler l'expiration du cache
- Améliorer les performances

---

**5. mod_deflate (Compression) :**

```bash
sudo a2enmod deflate
```

**Usage :**
- Compresser les fichiers envoyés
- Réduire la bande passante

---

**Activer tous ces modules en une fois :**

```bash
sudo a2enmod rewrite ssl headers expires deflate
```

**Puis recharger Apache :**

```bash
sudo systemctl reload apache2
```

---

**Désactiver un module :**

```bash
sudo a2dismod nom_du_module
sudo systemctl reload apache2
```

---

### [SECURISE] Étape 1.7 : Sécuriser Apache (Basique)

**Sécurité de base pour Apache :**

**1. Masquer la version d'Apache :**

```bash
sudo nano /etc/apache2/conf-available/security.conf
```

**Cherche et modifie ces lignes :**

```apache
ServerTokens Prod
ServerSignature Off
```

**Explication :**
- `ServerTokens Prod` = N'afficher que "Apache" (pas la version)
- `ServerSignature Off` = Ne pas afficher d'infos en bas des pages d'erreur

**Avant :**

```
Server: Apache/2.4.52 (Ubuntu) PHP/8.1.2
```

**Après :**

```
Server: Apache
```

**Activer la config :**

```bash
sudo a2enconf security
sudo systemctl reload apache2
```

---

**2. Désactiver le listing de répertoires :**

**Édite ton Virtual Host :**

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

**Modifie la ligne `Options` :**

**Avant :**

```apache
Options Indexes FollowSymLinks
```

**Après :**

```apache
Options -Indexes +FollowSymLinks
```

**Explication :**
- `-Indexes` = NE PAS lister les fichiers si pas de index.html
- `+FollowSymLinks` = Autoriser les liens symboliques

**Sans cette modification, si quelqu'un va sur `/images/` et qu'il n'y a pas de `index.html`, il verra la liste de tous tes fichiers !**

---

**3. Limiter la taille des requêtes :**

```bash
sudo nano /etc/apache2/apache2.conf
```

**Ajoute à la fin :**

```apache
# Limiter la taille des requêtes (protection DoS)
LimitRequestBody 10485760
```

**Explication :**
- `10485760` bytes = 10 MB
- Empêche quelqu'un d'uploader un fichier de 10 GB

---

**4. Configurer le pare-feu :**

```bash
# Autoriser HTTP
sudo ufw allow 80/tcp

# Autoriser HTTPS
sudo ufw allow 443/tcp

# Activer le pare-feu
sudo ufw enable

# Vérifier
sudo ufw status
```

---

### [GRAPHIQUE] Étape 1.8 : Optimiser les performances Apache

**MPM (Multi-Processing Module) = Comment Apache gère les connexions**

**3 types de MPM :**

|-------------|----------------------------|---------------------------------------------|------------------------------------------|
| MPM         | Description                | Avantages                                   | Inconvénients                            |
|-------------|----------------------------|---------------------------------------------|------------------------------------------|
| **prefork** | 1 processus par connexion  | Stable, compatible avec PHP non thread-safe | Consomme beaucoup de RAM                 |
| **worker**  | Multi-threads              | Moins de RAM, plus rapide                   | Pas compatible avec certains modules PHP |
| **event**   | Comme worker mais optimisé | Très performant, moins de RAM               | Nécessite PHP-FPM                        |
|-------------|----------------------------|---------------------------------------------|------------------------------------------|

**Par défaut, Apache utilise `mpm_event`.**

**Voir le MPM actif :**

```bash
apache2ctl -V | grep MPM
```

**Résultat :**

```
Server MPM: event
```

---

**Optimiser le MPM event :**

```bash
sudo nano /etc/apache2/mods-available/mpm_event.conf
```

**Configuration recommandée :**

```apache
<IfModule mpm_event_module>
    StartServers             2
    MinSpareThreads          25
    MaxSpareThreads          75
    ThreadLimit              64
    ThreadsPerChild          25
    MaxRequestWorkers        150
    MaxConnectionsPerChild   0
</IfModule>
```

**Explication :**

- `StartServers` = Nombre de processus au démarrage
- `MinSpareThreads` = Minimum de threads inactifs à garder
- `MaxSpareThreads` = Maximum de threads inactifs
- `ThreadsPerChild` = Threads par processus enfant
- `MaxRequestWorkers` = Maximum de connexions simultanées
- `MaxConnectionsPerChild` = Connexions avant de redémarrer un processus (0 = infini)

**Ces valeurs conviennent à un serveur avec 2-4 GB de RAM.**

---

**Activer la compression gzip :**

```bash
sudo nano /etc/apache2/mods-available/deflate.conf
```

**Ajoute :**

```apache
<IfModule mod_deflate.c>
    # Compresser HTML, CSS, JS, XML, JSON
    AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
    AddOutputFilterByType DEFLATE application/xml application/xhtml+xml
    AddOutputFilterByType DEFLATE application/rss+xml application/javascript
    AddOutputFilterByType DEFLATE application/x-javascript application/json
    
    # Ne pas compresser les images (déjà compressées)
    SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|ico)$ no-gzip
</IfModule>
```

**Redémarre Apache :**

```bash
sudo systemctl restart apache2
```

**Résultat : Les fichiers texte seront compressés (réduction de 70-80% de la taille) !**

---

### [TEST] Étape 1.9 : Tester et vérifier Apache

**Commandes utiles :**

```bash
# Tester la config (sans redémarrer)
sudo apache2ctl configtest

# Résultat attendu :
# Syntax OK

# Voir tous les Virtual Hosts
sudo apache2ctl -S

# Voir les modules chargés
sudo apache2ctl -M

# Voir la version et les options de compilation
sudo apache2ctl -V

# Redémarrer proprement
sudo systemctl restart apache2

# Recharger la config
sudo systemctl reload apache2

# Arrêter Apache
sudo systemctl stop apache2

# Démarrer Apache
sudo systemctl start apache2

# Voir le statut
sudo systemctl status apache2
```

---

**Tester depuis la ligne de commande :**

```bash
# Test simple
curl http://localhost

# Test avec en-têtes HTTP
curl -I http://localhost

# Test avec suivi des redirections
curl -L http://localhost

# Test d'un Virtual Host spécifique
curl -H "Host: monsite.local" http://localhost
```

---

**Tester depuis un autre PC sur le réseau :**

```cmd
# Windows
curl http://192.168.1.100

# Ou dans le navigateur
http://192.168.1.100
```

---

## [OBJECTIF] RÉSUMÉ DE LA PARTIE 1 - APACHE

**Ce que tu as appris :**

[OK] **Installation d'Apache** sur Ubuntu
[OK] **Comprendre la structure** des fichiers et dossiers
[OK] **Créer une page web HTML** personnalisée
[OK] **Configurer des Virtual Hosts** (plusieurs sites)
[OK] **Lire et analyser les logs** Apache
[OK] **Activer/désactiver des modules** (rewrite, ssl, etc.)
[OK] **Sécuriser Apache** (masquer version, désactiver indexes)
[OK] **Optimiser les performances** (MPM, compression)
[OK] **Tester et déboguer** la configuration

---

**Commandes clés à retenir :**

```bash
# Gestion du service
sudo systemctl start apache2
sudo systemctl stop apache2
sudo systemctl restart apache2
sudo systemctl reload apache2
sudo systemctl status apache2

# Gestion des sites
sudo a2ensite nom_du_site.conf
sudo a2dissite nom_du_site.conf

# Gestion des modules
sudo a2enmod nom_du_module
sudo a2dismod nom_du_module

# Test de configuration
sudo apache2ctl configtest

# Logs
sudo tail -f /var/log/apache2/access.log
sudo tail -f /var/log/apache2/error.log
```

---

## -> PROCHAINE ÉTAPE

**Dans la PARTIE 2, nous allons installer et configurer :**

[PACKAGE] **MySQL/MariaDB** - La base de données
[POSTGRES] **PHP** - Le langage de programmation
[LIEN] **L'intégration** Apache + PHP + MySQL
[DESIGN] **Un projet complet** pour tout tester

**Continue avec le fichier `lamp_partie2.txt` ! [RAPIDE]**

---

## [DOCS] RESSOURCES APACHE

**Documentation officielle :**
- https://httpd.apache.org/docs/2.4/
- https://httpd.apache.org/docs/2.4/vhosts/

**Tutoriels :**
- DigitalOcean : Guides Apache très détaillés
- LinuxConfig.org : Configurations avancées

**Outils de test :**
- GTmetrix : Tester les performances web
- SSL Labs : Tester la config SSL/TLS
- Apache Bench (ab) : Load testing

---

**Bravo d'avoir terminé la Partie 1 ! [BRAVO]**

**Apache est maintenant installé et configuré. Tu es prêt pour PHP et MySQL !**

# [COURS] GUIDE LAMP ULTRA DÉTAILLÉ - PARTIE 2/3
## MySQL/MariaDB + PHP

## [PACKAGE] PARTIE 2 : INSTALLATION ET CONFIGURATION DE MYSQL/MARIADB

### [REFLEXION] MySQL vs MariaDB : Quelle différence ?

**Histoire :**

```
1995 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     MySQL créé (Open Source)
     
2008 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     Sun Microsystems rachète MySQL
     
2010 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     Oracle rachète Sun (donc MySQL)
     v
     Inquiétudes sur l'avenir Open Source
     v
     Fork de MySQL = MariaDB (par le créateur original de MySQL)
     
2024 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
     MySQL et MariaDB coexistent
```

**Comparaison :**

| Critère                 | MySQL                        | MariaDB                             |
|-------------------------|------------------------------|-------------------------------------|
| **Propriétaire**        | Oracle                       | MariaDB Foundation (Open Source)    |
| **Licence**             | GPL (avec exceptions Oracle) | GPL (100% open)                     |
| **Performance**         | Excellent                    | Excellent (parfois meilleur)        |
| **Compatibilité**       | Compatible MariaDB [ATTENTION]        | Compatible MySQL [OK]                 |
| **Moteurs de stockage** | InnoDB, MyISAM               | InnoDB, Aria, ColumnStore, etc.     |
| **Extensions**          | Moins                        | Plus (JSON, Galera Cluster intégré) |
| **Communauté**          | Oracle + communauté          | 100% communauté                     |
| **Par défaut sur**      | Red Hat, CentOS              | Debian, Ubuntu, SUSE                |

**[ATTENTION] IMPORTANT :**

Ubuntu 22.04/24.04 installe **MariaDB** par défaut quand tu demandes MySQL.

**Dans ce guide, on utilisera MariaDB, mais tout fonctionne pareil avec MySQL.**

---

### [ENTREE] Étape 2.1 : Installation de MariaDB

**1. Installer MariaDB Server :**

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

**Explication :**
- `mariadb-server` = Le serveur de base de données
- Installe aussi `mariadb-client` (client en ligne de commande)

**Résultat attendu :**

```
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  mariadb-server mariadb-client-10.11 mariadb-server-10.11 ...
...
Setting up mariadb-server-10.11 (1:10.11.8-0ubuntu0.24.04.1) ...
Created symlink /etc/systemd/system/multi-user.target.wants/mariadb.service
```

---

**2. Vérifier que MariaDB tourne :**

```bash
sudo systemctl status mariadb
```

**Résultat attendu :**

```
[BLACK_CIRCLE] mariadb.service - MariaDB 10.11.8 database server
     Loaded: loaded (/lib/systemd/system/mariadb.service; enabled)
     Active: active (running) since Tue 2024-12-17 11:00:00 UTC; 1min ago
                     ^^^^^^^^^^^^^^^
                IL TOURNE ! [OK]
       Docs: man:mariadbd(8)
             https://mariadb.com/kb/en/library/systemd/
   Main PID: 5678 (mariadbd)
     Status: "Taking your SQL requests now..."
      Tasks: 9 (limit: 2312)
     Memory: 106.2M
        CPU: 1.234s
     CGroup: /system.slice/mariadb.service
             └─5678 /usr/sbin/mariadbd
```

**[OK] Si tu vois "active (running)", c'est parfait !**

---

**3. Vérifier la version :**

```bash
mariadb --version
```

**Ou :**

```bash
mysql --version
```

**Résultat (exemple) :**

```
mysql  Ver 15.1 Distrib 10.11.8-MariaDB, for debian-linux-gnu (x86_64) using  EditLine wrapper
```

**Note : La commande s'appelle `mysql` même si c'est MariaDB (compatibilité) !**

---

### [SECURISE] Étape 2.2 : Sécuriser MariaDB

**Par défaut, MariaDB est installé SANS MOT DE PASSE ROOT !**

**[ATTENTION] C'est un GROS risque de sécurité !**

**Script de sécurisation automatique :**

```bash
sudo mysql_secure_installation
```

**Ce script va poser plusieurs questions. Voici comment répondre :**

---

**Question 1 : Enter current password for root (enter for none):**

```
Appuie sur ENTRÉE (pas de mot de passe actuellement)
```

---

**Question 2 : Switch to unix_socket authentication [Y/n]**

```
Tape : n
Appuie sur ENTRÉE
```

**Explication :**

- `unix_socket` = Authentification par utilisateur système (plus sécurisé)
- On dit "non" pour pouvoir se connecter avec un mot de passe (plus simple pour débuter)

---

**Question 3 : Change the root password? [Y/n]**

```
Tape : Y
Appuie sur ENTRÉE
```

---

**Question 4 : New password:**

```
Tape un MOT DE PASSE FORT
Exemple : MonMotDePasse123!@#
Appuie sur ENTRÉE
```

**[ATTENTION] IMPORTANT : Note ce mot de passe quelque part !**

---

**Question 5 : Re-enter new password:**

```
Retape le MÊME mot de passe
Appuie sur ENTRÉE
```

---

**Question 6 : Remove anonymous users? [Y/n]**

```
Tape : Y
Appuie sur ENTRÉE
```

**Explication :**

Par défaut, MariaDB crée un utilisateur "anonyme" qui peut se connecter sans mot de passe. On le supprime pour la sécurité.

---

**Question 7 : Disallow root login remotely? [Y/n]**

```
Tape : Y
Appuie sur ENTRÉE
```

**Explication :**

Empêche de se connecter en root depuis un autre ordinateur (uniquement depuis localhost).

---

**Question 8 : Remove test database and access to it? [Y/n]**

```
Tape : Y
Appuie sur ENTRÉE
```

**Explication :**

Supprime la base de données de test (pas utile, risque de sécurité).

---

**Question 9 : Reload privilege tables now? [Y/n]**

```
Tape : Y
Appuie sur ENTRÉE
```

**Explication :**

Applique tous les changements immédiatement.

---

**Résultat final :**

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

Thanks for using MariaDB!
```

Tu as raison ! Sur les versions récentes d'Ubuntu (22.04+), `sudo mysql_secure_installation` peut ne pas fonctionner correctement car MariaDB utilise par défaut l'authentification `unix_socket` pour l'utilisateur root.

Voici la **correction complète** de la section de sécurisation :

---

## [SECURISE] Étape 2.2 : Sécuriser MariaDB (VERSION CORRIGÉE)

**Par défaut, MariaDB est installé avec l'authentification `unix_socket` pour root.**

**Cela signifie :**
- [OK] Seul `sudo` peut se connecter en tant que root (déjà sécurisé)
- [X] Pas de mot de passe root par défaut

**2 options pour sécuriser :**

---

### **OPTION A : Garder unix_socket (Recommandé pour serveurs locaux)**

**Avantages :**
- Plus sécurisé (pas de mot de passe à craquer)
- Seul l'utilisateur système peut se connecter

**1. Se connecter en root :**

```bash
sudo mysql
```

**2. Sécuriser manuellement :**

```sql
-- Supprimer les utilisateurs anonymes
DELETE FROM mysql.user WHERE User='';

-- Supprimer l'accès root distant
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');

-- Supprimer la base de données de test
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';

-- Appliquer les changements
FLUSH PRIVILEGES;

-- Vérifier
SELECT User, Host, plugin FROM mysql.user;
```

**Résultat attendu :**

```
+-------------+-----------+-------------+
| User        | Host      | plugin      |
+-------------+-----------+-------------+
| mariadb.sys | localhost | mysql_native_password |
| mysql       | localhost | mysql_native_password |
| root        | localhost | unix_socket |  <- unix_socket = sécurisé
+-------------+-----------+-------------+
```

**Quitter :**

```sql
EXIT;
```

**[OK] C'est tout ! MariaDB est sécurisé.**

**Pour te connecter :**

```bash
sudo mysql  # Pas de mot de passe nécessaire
```

---

### **OPTION B : Définir un mot de passe root (Pour compatibilité)**

**Si tu veux absolument un mot de passe root :**

**1. Se connecter :**

```bash
sudo mysql
```

**2. Changer le mode d'authentification :**

```sql
-- Passer de unix_socket à mysql_native_password
ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('TonMotDePasseRoot123!');

-- Appliquer
FLUSH PRIVILEGES;

-- Vérifier
SELECT User, Host, plugin FROM mysql.user WHERE User='root';
```

**Résultat attendu :**

```
+------+-----------+-----------------------+
| User | Host      | plugin                |
+------+-----------+-----------------------+
| root | localhost | mysql_native_password |  <- Maintenant avec mot de passe
+------+-----------+-----------------------+
```

**3. Sécuriser manuellement :**

```sql
-- Supprimer les utilisateurs anonymes
DELETE FROM mysql.user WHERE User='';

-- Supprimer l'accès root distant
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');

-- Supprimer la base de données de test
DROP DATABASE IF EXISTS test;
DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';

-- Appliquer
FLUSH PRIVILEGES;

EXIT;
```

**4. Tester la connexion avec mot de passe :**

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

**Entre ton mot de passe : `TonMotDePasseRoot123!`**

---

### **[CONFIG] Script automatique de sécurisation**

**Si tu veux automatiser (remplace `mysql_secure_installation`) :**

```bash
sudo nano /usr/local/bin/secure-mariadb.sh
```

**Contenu :**

```bash
#!/bin/bash
# ═══════════════════════════════════════════════════════════════
# SCRIPT DE SÉCURISATION MARIADB AUTOMATIQUE
# ═══════════════════════════════════════════════════════════════

echo "═══════════════════════════════════════════════════════════"
echo "  SÉCURISATION DE MARIADB"
echo "═══════════════════════════════════════════════════════════"
echo ""

# Demander le mot de passe root (optionnel)
read -p "Voulez-vous définir un mot de passe root ? (o/n) : " set_password

if [ "$set_password" = "o" ]; then
    read -sp "Nouveau mot de passe root : " root_password
    echo ""
    read -sp "Confirmer le mot de passe : " root_password_confirm
    echo ""
    
    if [ "$root_password" != "$root_password_confirm" ]; then
        echo "[X] Les mots de passe ne correspondent pas !"
        exit 1
    fi
    
    echo "[NOTE] Configuration du mot de passe root..."
    sudo mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('$root_password');"
    sudo mysql -e "FLUSH PRIVILEGES;"
    
    # Utiliser le nouveau mot de passe pour les commandes suivantes
    MYSQL_CMD="mysql -u root -p$root_password"
else
    echo "ℹ  Conservation de l'authentification unix_socket"
    MYSQL_CMD="sudo mysql"
fi

echo "[SUPPRIMER]  Suppression des utilisateurs anonymes..."
$MYSQL_CMD -e "DELETE FROM mysql.user WHERE User='';"

echo "[VERROUILLE] Désactivation de l'accès root distant..."
$MYSQL_CMD -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');"

echo "[SUPPRIMER]  Suppression de la base de données de test..."
$MYSQL_CMD -e "DROP DATABASE IF EXISTS test;"
$MYSQL_CMD -e "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%';"

echo "[SAUVEGARDE] Application des modifications..."
$MYSQL_CMD -e "FLUSH PRIVILEGES;"

echo ""
echo "[OK] MariaDB sécurisé avec succès !"
echo ""
echo "Pour vous connecter :"
if [ "$set_password" = "o" ]; then
    echo "  mysql -u root -p"
else
    echo "  sudo mysql"
fi
echo ""
```

**Rendre exécutable :**

```bash
sudo chmod +x /usr/local/bin/secure-mariadb.sh
```

**Lancer :**

```bash
sudo /usr/local/bin/secure-mariadb.sh
```

---

### **[LISTE] Résumé des changements**

**Remplacer dans tout le document :**

**[X] ANCIEN (ne fonctionne plus) :**
```bash
sudo mysql_secure_installation
```

**[OK] NOUVEAU (2 options) :**

**Option 1 (Recommandée) :**
```bash
# Sécurisation manuelle avec unix_socket
sudo mysql
# Puis exécuter les commandes SQL de nettoyage
```

**Option 2 (Avec script) :**
```bash
# Script automatique
sudo /usr/local/bin/secure-mariadb.sh
```

---

**Merci d'avoir signalé cette erreur ! La documentation est maintenant à jour. [BRAVO]**

**[BRAVO] MariaDB est maintenant sécurisé ! [BRAVO]**

---

### [CLE] Étape 2.3 : Se connecter à MariaDB

**Se connecter en tant que root :**

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

**Explication :**
- `mysql` = Client en ligne de commande
- `-u root` = User = root
- `-p` = Password (va demander le mot de passe)

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

---

**Tu devrais voir :**

```
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 42
Server version: 10.11.8-MariaDB-0ubuntu0.24.04.1 Ubuntu 24.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)]> _
                  ^
        Tu es connecté ! [OK]
```

---

**Commandes SQL de base :**

```sql
-- Voir toutes les bases de données
SHOW DATABASES;

-- Résultat :
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

-- Voir les utilisateurs
SELECT User, Host FROM mysql.user;

-- Résultat (exemple) :
+-------------+-----------+
| User        | Host      |
+-------------+-----------+
| mariadb.sys | localhost |
| mysql       | localhost |
| root        | localhost |
+-------------+-----------+

-- Quitter
EXIT;
```

**Ou :**

```sql
QUIT;
```

**Ou simplement :**

```sql
\q
```

---

### [ARCHIVE] Étape 2.4 : Créer une base de données et un utilisateur

**Scénario :**

Tu veux créer :
- Une base de données : `monsite_db`
- Un utilisateur : `monsite_user`
- Mot de passe : `motdepasse123`
- Privilèges : Tous les droits sur `monsite_db`

---

**1. Se connecter à MariaDB :**

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

**Entre le mot de passe root.**

---

**2. Créer la base de données :**

```sql
CREATE DATABASE monsite_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

**Explication :**

- `CREATE DATABASE` = Créer une base de données
- `monsite_db` = Nom de la base (choisis ce que tu veux)
- `CHARACTER SET utf8mb4` = Support des caractères internationaux + emojis [GRINNING_FACE]
- `COLLATE utf8mb4_unicode_ci` = Règles de tri (case-insensitive)

**Résultat :**

```
Query OK, 1 row affected (0.001 sec)
```

---

**3. Créer un utilisateur :**

```sql
CREATE USER 'monsite_user'@'localhost' IDENTIFIED BY 'motdepasse123';
```

**Explication :**

- `CREATE USER` = Créer un utilisateur
- `'monsite_user'` = Nom d'utilisateur
- `'localhost'` = Peut se connecter uniquement depuis localhost
- `IDENTIFIED BY 'motdepasse123'` = Mot de passe

**[ATTENTION] Remplace `motdepasse123` par un VRAI mot de passe fort !**

**Résultat :**

```
Query OK, 0 rows affected (0.002 sec)
```

---

**4. Donner tous les privilèges sur la base :**

```sql
GRANT ALL PRIVILEGES ON monsite_db.* TO 'monsite_user'@'localhost';
```

**Explication :**

- `GRANT` = Donner
- `ALL PRIVILEGES` = Tous les droits (SELECT, INSERT, UPDATE, DELETE, etc.)
- `ON monsite_db.*` = Sur toutes les tables de `monsite_db`
- `TO 'monsite_user'@'localhost'` = À cet utilisateur

**Résultat :**

```
Query OK, 0 rows affected (0.001 sec)
```

---

**5. Recharger les privilèges :**

```sql
FLUSH PRIVILEGES;
```

**Explication :**

Applique les changements immédiatement.

**Résultat :**

```
Query OK, 0 rows affected (0.000 sec)
```

---

**6. Vérifier :**

```sql
SHOW DATABASES;
```

**Tu devrais voir `monsite_db` dans la liste !**

```
+--------------------+
| Database           |
+--------------------+
| information_schema |
| monsite_db         |  <- Nouvelle base ! [OK]
| mysql              |
| performance_schema |
| sys                |
+--------------------+
```

---

**7. Quitter :**

```sql
EXIT;
```

---

**8. Tester la connexion avec le nouvel utilisateur :**

```bash
mysql -u monsite_user -p monsite_db
```

**Explication :**
- `-u monsite_user` = Connecter en tant que cet utilisateur
- `-p` = Demander le mot de passe
- `monsite_db` = Se connecter directement à cette base

**Entre le mot de passe : `motdepasse123`**

**Tu devrais voir :**

```
MariaDB [monsite_db]> _
         ^^^^^^^^^^
   Tu es dans la base ! [OK]
```

**Quitte avec `EXIT;`**

---

### [GRAPHIQUE] Étape 2.5 : Créer une table et insérer des données

**Créons une table simple pour tester.**

**1. Se connecter :**

```bash
mysql -u monsite_user -p monsite_db
```

---

**2. Créer une table `utilisateurs` :**

```sql
CREATE TABLE utilisateurs (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nom VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    date_inscription TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

**Explication ligne par ligne :**

```sql
CREATE TABLE utilisateurs (
    -- Créer une table nommée "utilisateurs"
    
    id INT AUTO_INCREMENT PRIMARY KEY,
    -- "id" : colonne de type entier
    -- AUTO_INCREMENT : s'incrémente automatiquement (1, 2, 3, ...)
    -- PRIMARY KEY : clé primaire (unique, identifie chaque ligne)
    
    nom VARCHAR(50) NOT NULL,
    -- "nom" : chaîne de caractères (max 50 caractères)
    -- NOT NULL : ne peut pas être vide
    
    email VARCHAR(100) NOT NULL UNIQUE,
    -- "email" : chaîne de caractères (max 100 caractères)
    -- NOT NULL : ne peut pas être vide
    -- UNIQUE : pas de doublons (chaque email doit être unique)
    
    date_inscription TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    -- "date_inscription" : horodatage (date + heure)
    -- DEFAULT CURRENT_TIMESTAMP : prend la date/heure actuelle automatiquement
);
```

**Résultat :**

```
Query OK, 0 rows affected (0.015 sec)
```

---

**3. Vérifier la structure de la table :**

```sql
DESCRIBE utilisateurs;
```

**Ou :**

```sql
SHOW COLUMNS FROM utilisateurs;
```

**Résultat :**

```
+------------------+--------------+------+-----+-------------------+-------------------+
| Field            | Type         | Null | Key | Default           | Extra             |
+------------------+--------------+------+-----+-------------------+-------------------+
| id               | int(11)      | NO   | PRI | NULL              | auto_increment    |
| nom              | varchar(50)  | NO   |     | NULL              |                   |
| email            | varchar(100) | NO   | UNI | NULL              |                   |
| date_inscription | timestamp    | NO   |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED |
+------------------+--------------+------+-----+-------------------+-------------------+
```

**[OK] La table est créée correctement !**

---

**4. Insérer des données :**

```sql
INSERT INTO utilisateurs (nom, email) VALUES ('Alice', 'alice@example.com');
INSERT INTO utilisateurs (nom, email) VALUES ('Bob', 'bob@example.com');
INSERT INTO utilisateurs (nom, email) VALUES ('Charlie', 'charlie@example.com');
```

**Explication :**

- `INSERT INTO utilisateurs` = Insérer dans la table "utilisateurs"
- `(nom, email)` = Dans ces colonnes
- `VALUES ('Alice', 'alice@example.com')` = Avec ces valeurs

**Note : Pas besoin de spécifier `id` (auto-increment) ni `date_inscription` (valeur par défaut).**

**Résultat (pour chaque INSERT) :**

```
Query OK, 1 row affected (0.002 sec)
```

---

**5. Voir toutes les données :**

```sql
SELECT * FROM utilisateurs;
```

**Explication :**
- `SELECT` = Sélectionner
- `*` = Toutes les colonnes
- `FROM utilisateurs` = Dans la table "utilisateurs"

**Résultat :**

```
+----+---------+---------------------+---------------------+
| id | nom     | email               | date_inscription    |
+----+---------+---------------------+---------------------+
|  1 | Alice   | alice@example.com   | 2024-12-17 11:30:00 |
|  2 | Bob     | bob@example.com     | 2024-12-17 11:30:15 |
|  3 | Charlie | charlie@example.com | 2024-12-17 11:30:30 |
+----+---------+---------------------+---------------------+
3 rows in set (0.000 sec)
```

**[BRAVO] Tes données sont dans la base ! [BRAVO]**

---

**6. Requêtes SQL utiles :**

**Sélectionner un utilisateur spécifique :**

```sql
SELECT * FROM utilisateurs WHERE nom = 'Alice';
```

**Résultat :**

```
+----+-------+-------------------+---------------------+
| id | nom   | email             | date_inscription    |
+----+-------+-------------------+---------------------+
|  1 | Alice | alice@example.com | 2024-12-17 11:30:00 |
+----+-------+-------------------+---------------------+
```

---

**Compter le nombre d'utilisateurs :**

```sql
SELECT COUNT(*) FROM utilisateurs;
```

**Résultat :**

```
+----------+
| COUNT(*) |
+----------+
|        3 |
+----------+
```

---

**Modifier un utilisateur :**

```sql
UPDATE utilisateurs SET nom = 'Alicia' WHERE id = 1;
```

**Vérifier :**

```sql
SELECT * FROM utilisateurs WHERE id = 1;
```

**Résultat :**

```
+----+--------+-------------------+---------------------+
| id | nom    | email             | date_inscription    |
+----+--------+-------------------+---------------------+
|  1 | Alicia | alice@example.com | 2024-12-17 11:30:00 |
+----+--------+-------------------+---------------------+
```

**Le nom est passé de "Alice" à "Alicia" ! [OK]**

---

**Supprimer un utilisateur :**

```sql
DELETE FROM utilisateurs WHERE id = 3;
```

**Vérifier :**

```sql
SELECT * FROM utilisateurs;
```

**Résultat :**

```
+----+--------+-------------------+---------------------+
| id | nom    | email             | date_inscription    |
+----+--------+-------------------+---------------------+
|  1 | Alicia | alice@example.com | 2024-12-17 11:30:00 |
|  2 | Bob    | bob@example.com   | 2024-12-17 11:30:15 |
+----+--------+-------------------+---------------------+
2 rows in set (0.000 sec)
```

**Charlie a été supprimé ! [ATTENTION]**

---

**7. Quitter :**

```sql
EXIT;
```

---

### [OUTIL] Étape 2.6 : Configuration avancée de MariaDB

**Fichier de configuration principal :**

```
/etc/mysql/mariadb.conf.d/50-server.cnf
```

**Ou :**

```
/etc/mysql/my.cnf
```

---

**Optimisations recommandées :**

```bash
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
```

**Cherche la section `[mysqld]` et modifie/ajoute ces paramètres :**

```ini
[mysqld]

# ═══════════════════════════════════════════════════════════════
# OPTIMISATIONS MARIADB POUR SERVEUR 2-4 GB RAM
# ═══════════════════════════════════════════════════════════════

# Charset et collation par défaut
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci

# Mémoire
innodb_buffer_pool_size = 512M
# ^ Taille du cache InnoDB (25-70% de la RAM disponible)
# Serveur 2GB -> 512M
# Serveur 4GB -> 1G

max_connections = 150
# ^ Nombre max de connexions simultanées

# Logs
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# ^ Enregistre les requêtes qui prennent plus de 2 secondes

# Performance
innodb_flush_log_at_trx_commit = 2
# ^ 1 = Sécurité max (lent)
#   2 = Compromis (recommandé)
#   0 = Performance max (risque de perte de données)

innodb_file_per_table = 1
# ^ Un fichier par table (facilite la gestion)

# Sécurité
bind-address = 127.0.0.1
# ^ N'écouter QUE sur localhost (pas de connexions externes)
# Pour autoriser connexions externes : bind-address = 0.0.0.0
```

**Sauvegarde et redémarre MariaDB :**

```bash
sudo systemctl restart mariadb
```

**Vérifier que ça marche :**

```bash
sudo systemctl status mariadb
```

---

### [GRAPHIQUE] Étape 2.7 : Sauvegarder et restaurer une base de données

**Sauvegarder une base de données :**

```bash
mysqldump -u monsite_user -p monsite_db > monsite_db_backup.sql
```

**Explication :**
- `mysqldump` = Outil de sauvegarde
- `-u monsite_user` = Utilisateur
- `-p` = Demander le mot de passe
- `monsite_db` = Base à sauvegarder
- `> monsite_db_backup.sql` = Rediriger vers ce fichier

**Entre le mot de passe.**

**Résultat :**

Un fichier `monsite_db_backup.sql` est créé dans le dossier actuel.

---

**Voir le contenu du backup :**

```bash
head -20 monsite_db_backup.sql
```

**Tu verras du SQL (les commandes pour recréer la base) :**

```sql
-- MySQL dump 10.19  Distrib 10.11.8-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost    Database: monsite_db
-- ------------------------------------------------------
-- Server version	10.11.8-MariaDB-0ubuntu0.24.04.1

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
...
CREATE TABLE `utilisateurs` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `nom` varchar(50) NOT NULL,
  ...
```

---

**Sauvegarder TOUTES les bases de données :**

```bash
mysqldump -u root -p --all-databases > all_databases_backup.sql
```

---

**Restaurer une base de données :**

**Méthode 1 : La base existe déjà**

```bash
mysql -u monsite_user -p monsite_db < monsite_db_backup.sql
```

---

**Méthode 2 : Recréer la base depuis zéro**

```bash
# 1. Se connecter en root
sudo mysql -u root -p

# 2. Supprimer l'ancienne base (ATTENTION !)
DROP DATABASE IF EXISTS monsite_db;

# 3. Recréer la base
CREATE DATABASE monsite_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

# 4. Quitter
EXIT;

# 5. Restaurer depuis le backup
mysql -u root -p monsite_db < monsite_db_backup.sql
```

---

**Automatiser les sauvegardes avec cron :**

```bash
# Créer un script de backup
sudo nano /usr/local/bin/backup-mysql.sh
```

**Contenu :**

```bash
#!/bin/bash
# ═══════════════════════════════════════════════════════════════
# SCRIPT DE SAUVEGARDE AUTOMATIQUE MYSQL
# ═══════════════════════════════════════════════════════════════

BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
DB_USER="root"
DB_PASSWORD="TON_MOT_DE_PASSE_ROOT"  # [ATTENTION] CHANGE ICI
DB_NAME="monsite_db"

# Créer le dossier de backup si inexistant
mkdir -p "$BACKUP_DIR"

# Sauvegarder
mysqldump -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" > "$BACKUP_DIR/${DB_NAME}_${DATE}.sql"

# Compresser
gzip "$BACKUP_DIR/${DB_NAME}_${DATE}.sql"

# Supprimer les backups de plus de 7 jours
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete

echo "Backup terminé : ${DB_NAME}_${DATE}.sql.gz"
```

**Rendre exécutable :**

```bash
sudo chmod +x /usr/local/bin/backup-mysql.sh
```

**Tester :**

```bash
sudo /usr/local/bin/backup-mysql.sh
```

---

**Automatiser avec cron (tous les jours à 2h du matin) :**

```bash
sudo crontab -e
```

**Ajoute :**

```bash
0 2 * * * /usr/local/bin/backup-mysql.sh >> /var/log/mysql-backup.log 2>&1
```

**Explication :**
- `0 2 * * *` = À 2h00 tous les jours
- `>> /var/log/mysql-backup.log` = Ajouter les logs à ce fichier
- `2>&1` = Rediriger les erreurs aussi

---

## [POSTGRES] PARTIE 3 : INSTALLATION ET CONFIGURATION DE PHP

### [ENTREE] Étape 3.1 : Installation de PHP

**PHP = Langage de programmation côté serveur (le "P" de LAMP)**

**Rôle de PHP :**
- Génère du HTML dynamique
- Communique avec MySQL
- Traite les formulaires
- Gère les sessions utilisateur
- Etc.

---

**1. Installer PHP et les modules essentiels :**

```bash
sudo apt install php php-mysql php-cli php-curl php-gd php-mbstring php-xml php-zip libapache2-mod-php -y
```

**Explication des paquets :**

|-------------------------|-----------------------------------------------|
| Paquet                  | Description                                   |
|-------------------------|-----------------------------------------------|
| `php`                   | PHP de base                                   |
| `php-mysql`             | Extension pour communiquer avec MySQL/MariaDB |
| `php-cli`               | PHP en ligne de commande                      |
| `php-curl`              | Extension pour requêtes HTTP                  |
| `php-gd`                | Extension pour traitement d'images            |
| `php-mbstring`          | Extension pour chaînes multi-bytes (UTF-8)    |
| `php-xml`               | Extension pour traiter XML                    |
| `php-zip`               | Extension pour ZIP                            |
| `libapache2-mod-php`    | Module Apache pour exécuter PHP               |
|-------------------------|-----------------------------------------------|

**Résultat attendu :**

```
Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  php php8.3-cli php8.3-common php8.3-curl php8.3-gd php8.3-mbstring
  php8.3-mysql php8.3-xml php8.3-zip libapache2-mod-php8.3 ...
...
Setting up php8.3 (8.3.6-0ubuntu0.24.04.2) ...
Setting up libapache2-mod-php8.3 (8.3.6-0ubuntu0.24.04.2) ...
```

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

---

**2. Vérifier la version :**

```bash
php --version
```

**Résultat (exemple) :**

```
PHP 8.3.6 (cli) (built: May  2 2024 10:40:49) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies
```

---

**3. Vérifier que le module Apache PHP est activé :**

```bash
sudo a2enmod php8.3
```

**Si déjà activé :**

```
Module php8.3 already enabled
```

**Si pas activé :**

```
Enabling module php8.3.
To activate the new configuration, you need to run:
  systemctl restart apache2
```

**Redémarre Apache :**

```bash
sudo systemctl restart apache2
```

---

### [TEST] Étape 3.2 : Tester PHP

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

```bash
sudo nano /var/www/html/info.php
```

**Contenu :**

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

**Explication :**
- `<?php` = Début du code PHP
- `phpinfo()` = Fonction qui affiche toutes les infos PHP
- `?>` = Fin du code PHP

**Sauvegarde.**

---

**Ouvre ton navigateur et va à :**

```
http://192.168.1.100/info.php
```

**Ou :**

```
http://monsite.local/info.php
```

---

**Tu devrais voir une GROSSE page avec :**

```
┌───────────────────────────────────────────────────────────┐
│  PHP Version 8.3.6                                        │
├───────────────────────────────────────────────────────────┤
│  System            Linux ubuntu 6.8.0-49-generic          │
│  Server API        Apache 2.0 Handler                     │
│  PHP API           20230831                               │
│  Zend Engine       v4.3.6                                 │
│  Configuration     /etc/php/8.3/apache2/php.ini           │
├───────────────────────────────────────────────────────────┤
│  [OK] MySQL Support  enabled                                │
│  [OK] GD Support     enabled                                │
│  [OK] cURL Support   enabled                                │
│  ...                                                      │
└───────────────────────────────────────────────────────────┘
```

**[BRAVO] SI TU VOIS CETTE PAGE, PHP FONCTIONNE ! [BRAVO]**

---

**[ATTENTION] IMPORTANT : Supprimer le fichier info.php après le test !**

```bash
sudo rm /var/www/html/info.php
```

**Pourquoi ?**

Le fichier `phpinfo()` révèle BEAUCOUP d'informations sur ton serveur (versions, chemins, config...). C'est un risque de sécurité si tu le laisses accessible publiquement.

---

### [LIEN] Étape 3.3 : Tester PHP avec MySQL

**Créer un script qui se connecte à MySQL :**

```bash
sudo nano /var/www/html/test_mysql.php
```

**Contenu :**

```php
<?php
// ═══════════════════════════════════════════════════════════════
// TEST DE CONNEXION MYSQL DEPUIS PHP
// ═══════════════════════════════════════════════════════════════

// Configuration de la connexion
$serveur = "localhost";
$utilisateur = "monsite_user";
$motdepasse = "motdepasse123";  // [ATTENTION] CHANGE PAR TON MOT DE PASSE
$base = "monsite_db";

// Tentative de connexion
$connexion = new mysqli($serveur, $utilisateur, $motdepasse, $base);

// Vérifier la connexion
if ($connexion->connect_error) {
    die("[X] Échec de la connexion : " . $connexion->connect_error);
}

echo "<h1>[OK] Connexion réussie à MySQL !</h1>";
echo "<p><strong>Serveur :</strong> $serveur</p>";
echo "<p><strong>Base de données :</strong> $base</p>";
echo "<p><strong>Version MySQL :</strong> " . $connexion->server_info . "</p>";

// Tester une requête
$resultat = $connexion->query("SELECT * FROM utilisateurs");

if ($resultat && $resultat->num_rows > 0) {
    echo "<h2>[GRAPHIQUE] Utilisateurs dans la base :</h2>";
    echo "<table border='1' cellpadding='10'>";
    echo "<tr><th>ID</th><th>Nom</th><th>Email</th><th>Date d'inscription</th></tr>";
    
    while ($ligne = $resultat->fetch_assoc()) {
        echo "<tr>";
        echo "<td>" . $ligne["id"] . "</td>";
        echo "<td>" . $ligne["nom"] . "</td>";
        echo "<td>" . $ligne["email"] . "</td>";
        echo "<td>" . $ligne["date_inscription"] . "</td>";
        echo "</tr>";
    }
    
    echo "</table>";
} else {
    echo "<p>Aucun utilisateur trouvé ou table vide.</p>";
}

// Fermer la connexion
$connexion->close();
?>

<style>
    body {
        font-family: Arial, sans-serif;
        background: #f0f0f0;
        padding: 20px;
    }
    h1 {
        color: #27ae60;
    }
    table {
        background: white;
        border-collapse: collapse;
        margin-top: 20px;
    }
    th {
        background: #3498db;
        color: white;
    }
</style>
```

**Sauvegarde.**

---

**Ouvre dans le navigateur :**

```
http://192.168.1.100/test_mysql.php
```

**Tu devrais voir :**

```
[OK] Connexion réussie à MySQL !

Serveur : localhost
Base de données : monsite_db
Version MySQL : 10.11.8-MariaDB-0ubuntu0.24.04.1

[GRAPHIQUE] Utilisateurs dans la base :

┌────┬────────┬───────────────────┬─────────────────────┐
│ ID │  Nom   │       Email       │ Date d'inscription  │
├────┼────────┼───────────────────┼─────────────────────┤
│ 1  │ Alicia │ alice@example.com │ 2024-12-17 11:30:00 │
│ 2  │ Bob    │ bob@example.com   │ 2024-12-17 11:30:15 │
└────┴────────┴───────────────────┴─────────────────────┘
```

**[BRAVO] PHP COMMUNIQUE AVEC MYSQL ! [BRAVO]**

---

**[ATTENTION] Supprimer le fichier de test (sécurité) :**

```bash
sudo rm /var/www/html/test_mysql.php
```

---

### [CONFIG] Étape 3.4 : Configuration de PHP

**Fichier de configuration principal :**

Non, le fichier ne contient **aucune mention de phpMyAdmin** ! [FACE_WITH_OPEN_MOUTH]

C'est une omission importante, car phpMyAdmin est un outil très populaire et utile pour gérer MySQL/MariaDB graphiquement.

Voici ce qu'il faudrait ajouter :

---

## [ECRAN] BONUS : INSTALLATION ET CONFIGURATION DE PHPMYADMIN

### [PACKAGE] Étape 1 : Installation de phpMyAdmin

```bash
sudo apt update
sudo apt install phpmyadmin -y
```

**Pendant l'installation, il y aura des questions :**

---

**Question 1 : Serveur web à reconfigurer automatiquement**

```
┌────────────────────┤ Configuration de phpmyadmin ├─────────────────────┐
│ Veuillez choisir le serveur web qui devrait être automatiquement       │
│ configuré pour exécuter phpMyAdmin.                                     │
│                                                                         │
│ Serveurs web à reconfigurer automatiquement :                          │
│                                                                         │
│    [ ] apache2                                                          │
│    [ ] lighttpd                                                         │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

**-> Sélectionne `apache2` avec la barre d'espace, puis `Entrée`**

---

**Question 2 : Configurer la base de données avec dbconfig-common ?**

```
┌────────────────────┤ Configuration de phpmyadmin ├─────────────────────┐
│                                                                         │
│ La base de données phpmyadmin est normalement gérée avec               │
│ dbconfig-common.                                                        │
│                                                                         │
│ Configurer la base de données pour phpmyadmin avec dbconfig-common ?   │
│                                                                         │
│                    <Oui>                   <Non>                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

**-> Sélectionne `Oui`**

---

**Question 3 : Mot de passe de l'utilisateur administrateur MySQL**

```
┌────────────────────┤ Configuration de phpmyadmin ├─────────────────────┐
│ Veuillez fournir le mot de passe administrateur du serveur de base     │
│ de données MySQL. Si ce mot de passe est vide, il ne sera pas pris     │
│ en compte.                                                              │
│                                                                         │
│ Mot de passe de l'administrateur MySQL :                               │
│                                                                         │
│ ________________________________________________________________        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
```

**-> Entre ton mot de passe root MySQL (ou laisse vide si tu utilises unix_socket)**

---

**Question 4 : Mot de passe pour phpMyAdmin**

```
┌────────────────────┤ Configuration de phpmyadmin ├─────────────────────┐
│ Veuillez choisir le mot de passe permettant à phpmyadmin de             │
│ s'enregistrer dans la base de données.                                  │
│                                                                          │
│ Mot de passe de connexion MySQL pour phpmyadmin :                       │
│                                                                          │
│ ________________________________________________________________         │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘
```

**-> Entre un mot de passe fort (ex: `phpMyAdmin123!`)**

---

**Question 5 : Confirmation du mot de passe**

```
Confirmation du mot de passe :

________________________________________________________________
```

**-> Re-tape le même mot de passe**

---

### [CONFIG] Étape 2 : Configuration manuelle (si nécessaire)

**Si l'installation ne configure pas automatiquement Apache :**

```bash
# Créer un lien symbolique
sudo ln -s /usr/share/phpmyadmin /var/www/html/phpmyadmin

# Ou configurer un Virtual Host dédié
sudo nano /etc/apache2/sites-available/phpmyadmin.conf
```

**Contenu du Virtual Host :**

```apache
Alias /phpmyadmin /usr/share/phpmyadmin

<Directory /usr/share/phpmyadmin>
    Options SymLinksIfOwnerMatch
    DirectoryIndex index.php

    # Limiter l'accès par IP (recommandé)
    # Require ip 192.168.1.0/24

    # Ou autoriser tout le monde
    Require all granted
</Directory>

# Désactiver l'accès aux dossiers de configuration
<Directory /usr/share/phpmyadmin/templates>
    Require all denied
</Directory>

<Directory /usr/share/phpmyadmin/libraries>
    Require all denied
</Directory>

<Directory /usr/share/phpmyadmin/setup>
    Require all denied
</Directory>
```

**Activer et recharger :**

```bash
sudo a2enconf phpmyadmin
sudo systemctl reload apache2
```

---

### [SECURISE] Étape 3 : Sécurisation de phpMyAdmin

**1. Créer un utilisateur MySQL dédié (recommandé) :**

```bash
sudo mysql
```

```sql
-- Créer un utilisateur pour phpMyAdmin
CREATE USER 'pma_admin'@'localhost' IDENTIFIED BY 'MotDePassePMA123!';

-- Donner tous les droits
GRANT ALL PRIVILEGES ON *.* TO 'pma_admin'@'localhost' WITH GRANT OPTION;

-- Appliquer
FLUSH PRIVILEGES;
EXIT;
```

---

**2. Activer l'authentification HTTP (couche supplémentaire) :**

```bash
sudo nano /etc/phpmyadmin/config.inc.php
```

**Ajoute à la fin :**

```php
// Sécurité supplémentaire
$cfg['Servers'][$i]['AllowNoPassword'] = false;
$cfg['LoginCookieValidity'] = 3600; // Session de 1 heure
$cfg['NavigationDisplayServers'] = false;
```

---

**3. Protéger avec .htaccess (authentification HTTP) :**

```bash
# Créer le fichier de mots de passe
sudo htpasswd -c /etc/phpmyadmin/.htpasswd admin
```

**Entre un mot de passe fort.**

**Créer le fichier .htaccess :**

```bash
sudo nano /usr/share/phpmyadmin/.htaccess
```

**Contenu :**

```apache
AuthType Basic
AuthName "Accès restreint à phpMyAdmin"
AuthUserFile /etc/phpmyadmin/.htpasswd
Require valid-user
```

---

**4. Changer l'URL d'accès (masquage) :**

```bash
sudo nano /etc/apache2/conf-available/phpmyadmin.conf
```

**Modifier :**

```apache
# Au lieu de /phpmyadmin, utiliser un nom aléatoire
Alias /mon-interface-secrete-2024 /usr/share/phpmyadmin
```

**Recharger :**

```bash
sudo systemctl reload apache2
```

**Maintenant, phpMyAdmin sera accessible sur :**

```
http://ton-serveur/mon-interface-secrete-2024
```

---

### [WEB] Étape 4 : Accéder à phpMyAdmin

**Dans ton navigateur, va à :**

```
http://192.168.1.100/phpmyadmin
```

**Ou si tu as changé l'URL :**

```
http://192.168.1.100/mon-interface-secrete-2024
```

---

**Écran de connexion :**

```
┌─────────────────────────────────────────────────┐
│  phpMyAdmin                                     │
├─────────────────────────────────────────────────┤
│                                                 │
│  Utilisateur : [root ou pma_admin]              │
│  Mot de passe : [ton mot de passe]              │
│                                                 │
│  Serveur : localhost                            │
│                                                 │
│            [ Exécuter ]                         │
│                                                 │
└─────────────────────────────────────────────────┘
```

**-> Entre les identifiants MySQL et connecte-toi !**

---

### [DESIGN] Interface phpMyAdmin

**Une fois connecté, tu verras :**

- **Bases de données** : Liste de toutes les bases
- **SQL** : Exécuter des requêtes SQL
- **Utilisateurs** : Gérer les utilisateurs MySQL
- **Import/Export** : Importer/Exporter des bases
- **Opérations** : Backup, réparation, optimisation

---

### [SECURITE] Étape 5 : Sécurité avancée (Production)

**1. Limiter l'accès par IP :**

```bash
sudo nano /etc/apache2/conf-available/phpmyadmin.conf
```

```apache
<Directory /usr/share/phpmyadmin>
    # Autoriser uniquement ton réseau local
    Require ip 192.168.1.0/24
    
    # Ou une IP spécifique
    # Require ip 192.168.1.50
</Directory>
```

---

**2. Désactiver phpMyAdmin en production :**

```bash
# Désactiver temporairement
sudo a2disconf phpmyadmin
sudo systemctl reload apache2

# Réactiver quand nécessaire
sudo a2enconf phpmyadmin
sudo systemctl reload apache2
```

---

**3. Utiliser HTTPS uniquement :**

```apache
<Directory /usr/share/phpmyadmin>
    # Forcer HTTPS
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</Directory>
```

---

### [ATTENTION] Erreurs courantes et solutions

**Erreur 1 : "Not Found" ou 404**

```bash
# Vérifier que le lien symbolique existe
ls -l /var/www/html/phpmyadmin

# Si absent, le créer
sudo ln -s /usr/share/phpmyadmin /var/www/html/phpmyadmin
```

---

**Erreur 2 : "Cannot start session without errors"**

```bash
# Créer le dossier de sessions
sudo mkdir -p /var/lib/phpmyadmin/tmp
sudo chown -R www-data:www-data /var/lib/phpmyadmin
```

**Éditer la config :**

```bash
sudo nano /etc/phpmyadmin/config.inc.php
```

**Ajouter :**

```php
$cfg['TempDir'] = '/var/lib/phpmyadmin/tmp';
```

---

**Erreur 3 : "The mbstring extension is missing"**

```bash
# Installer l'extension PHP manquante
sudo apt install php-mbstring
sudo systemctl restart apache2
```

---

**Erreur 4 : "Access denied for user 'root'@'localhost'"**

**Si tu utilises unix_socket :**

```bash
sudo mysql
```

```sql
-- Créer un utilisateur avec mot de passe pour phpMyAdmin
CREATE USER 'pma_admin'@'localhost' IDENTIFIED BY 'MotDePasse123!';
GRANT ALL PRIVILEGES ON *.* TO 'pma_admin'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EXIT;
```

**Puis utilise `pma_admin` pour te connecter à phpMyAdmin.**

---

### [GRAPHIQUE] Alternatives à phpMyAdmin

Si tu cherches d'autres outils :

| Outil | Description | URL |
|-------|-------------|-----|
| **Adminer** | Plus léger (1 fichier PHP) | https://www.adminer.org/ |
| **DBeaver** | Client lourd multiplateforme | https://dbeaver.io/ |
| **MySQL Workbench** | Client officiel MySQL | https://www.mysql.com/products/workbench/ |
| **phpMyAdmin** | Le plus populaire | https://www.phpmyadmin.net/ |

---

**Installation d'Adminer (alternative légère) :**

```bash
# Télécharger
cd /var/www/html
sudo wget https://github.com/vrana/adminer/releases/download/v4.8.1/adminer-4.8.1.php

# Renommer
sudo mv adminer-4.8.1.php adminer.php

# Permissions
sudo chown www-data:www-data adminer.php
sudo chmod 644 adminer.php
```

**Accès :**

```
http://192.168.1.100/adminer.php
```

---

**Voilà ! Cette section devrait être ajoutée au guide pour compléter la documentation. [BRAVO]**

```
/etc/php/8.3/apache2/php.ini
```

**Sauvegarder l'original :**

```bash
sudo cp /etc/php/8.3/apache2/php.ini /etc/php/8.3/apache2/php.ini.original
```

---

**Éditer la configuration :**

```bash
sudo nano /etc/php/8.3/apache2/php.ini
```

**C'est un GROS fichier (plus de 2000 lignes). Utilise `Ctrl + W` pour chercher.**

---

**Paramètres importants à modifier :**

**1. Taille maximale des fichiers uploadés :**

**Cherche : `upload_max_filesize`**

```ini
upload_max_filesize = 64M
```

**Par défaut : 2M (trop petit)**
**Recommandation : 64M ou plus**

---

**2. Taille maximale des requêtes POST :**

**Cherche : `post_max_size`**

```ini
post_max_size = 128M
```

**[ATTENTION] Doit être SUPÉRIEUR à `upload_max_filesize` !**

---

**3. Mémoire maximale pour un script :**

**Cherche : `memory_limit`**

```ini
memory_limit = 256M
```

**Par défaut : 128M**
**Pour WordPress / applications gourmandes : 256M ou 512M**

---

**4. Temps d'exécution maximal :**

**Cherche : `max_execution_time`**

```ini
max_execution_time = 300
```

**Par défaut : 30 secondes**
**Pour scripts longs (imports, exports) : 300 secondes (5 minutes)**

---

**5. Affichage des erreurs (DÉVELOPPEMENT uniquement) :**

**Cherche : `display_errors`**

```ini
display_errors = On
```

**[ATTENTION] En PRODUCTION, mets `Off` pour ne pas révéler d'infos sensibles !**

---

**6. Logs d'erreurs :**

**Cherche : `error_log`**

```ini
error_log = /var/log/php_errors.log
```

---

**7. Timezone :**

**Cherche : `date.timezone`**

```ini
date.timezone = Africa/Dakar
```

**Liste des timezones : https://www.php.net/manual/en/timezones.php**

---

**Sauvegarde et redémarre Apache :**

```bash
sudo systemctl restart apache2
```

---

**Vérifier que les changements sont appliqués :**

Recrée temporairement `info.php` :

```bash
sudo nano /var/www/html/info.php
```

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

**Va sur :**

```
http://192.168.1.100/info.php
```

**Cherche les paramètres que tu as modifiés dans la page.**

**Supprime ensuite le fichier :**

```bash
sudo rm /var/www/html/info.php
```

---

## [OBJECTIF] RÉSUMÉ DE LA PARTIE 2

**Ce que tu as appris :**

[OK] **Installation de MariaDB** sur Ubuntu
[OK] **Sécurisation de MariaDB** (mysql_secure_installation)
[OK] **Créer une base de données et un utilisateur**
[OK] **Créer des tables et insérer des données** (SQL)
[OK] **Requêtes SQL de base** (SELECT, INSERT, UPDATE, DELETE)
[OK] **Configuration avancée de MariaDB**
[OK] **Sauvegarder et restaurer** des bases de données
[OK] **Installation de PHP** et modules
[OK] **Tester PHP** avec phpinfo()
[OK] **Connecter PHP à MySQL**
[OK] **Configurer PHP** (php.ini)

---

**Commandes clés à retenir :**

```bash
# MariaDB
sudo systemctl start mariadb
sudo systemctl stop mariadb
sudo systemctl restart mariadb
sudo mysql -u root -p
sudo mysql_secure_installation

# Backup/Restore
mysqldump -u user -p database > backup.sql
mysql -u user -p database < backup.sql

# PHP
php --version
sudo systemctl restart apache2
```

---

## -> PROCHAINE ÉTAPE

**Dans la PARTIE 3, nous allons créer :**

[WEB] **Un projet web complet** (CRUD : Create, Read, Update, Delete)
[DESIGN] **Une interface utilisateur moderne**
[SECURISE] **Un système de connexion**
[PACKAGE] **Installer WordPress** (bonus)

**Continue avec le fichier `lamp_partie3.txt` ! [RAPIDE]**

---

**Bravo d'avoir terminé la Partie 2 ! [BRAVO]**

**Tu as maintenant une stack LAMP complètement fonctionnelle !**

# [COURS] GUIDE LAMP ULTRA DÉTAILLÉ - PARTIE 3/3
## Projets Pratiques + WordPress + Déploiement

## [RAPIDE] PARTIE 1 : PROJET CRUD COMPLET

### [NOTE] Qu'est-ce qu'un CRUD ?

**CRUD** = Acronyme des 4 opérations de base sur les données

```
C = CREATE  -> Créer (INSERT)
R = READ    -> Lire (SELECT)
U = UPDATE  -> Modifier (UPDATE)
D = DELETE  -> Supprimer (DELETE)
```

**Dans ce projet, nous allons créer :**

```
┌──────────────────────────────────────────────────┐
│  [LISTE] GESTIONNAIRE DE CONTACTS                     │
├──────────────────────────────────────────────────┤
│                                                  │
│  [OK] Ajouter un contact                           │
│  [GUIDE] Voir tous les contacts                       │
│  [EDIT]  Modifier un contact                         │
│  [SUPPRIMER]  Supprimer un contact                        │
│                                                  │
│  Interface moderne avec Bootstrap                │
│  PHP orienté objet                               │
│  Sécurité (injections SQL, XSS)                  │
│                                                  │
└──────────────────────────────────────────────────┘
```

---

### [DOSSIER] Étape 1.1 : Structure du projet

**Créer l'arborescence :**

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

# Créer la structure
sudo mkdir -p /var/www/contacts/{css,js,includes}

# Définir les permissions
sudo chown -R www-data:www-data /var/www/contacts
sudo chmod -R 755 /var/www/contacts
```

**Structure finale :**

```
/var/www/contacts/
├── index.php              <- Page d'accueil (liste des contacts)
├── ajouter.php            <- Formulaire d'ajout
├── modifier.php           <- Formulaire de modification
├── supprimer.php          <- Action de suppression
├── includes/
│   ├── config.php         <- Configuration (connexion DB)
│   ├── Database.php       <- Classe de gestion DB
│   └── Contact.php        <- Classe Contact
├── css/
│   └── style.css          <- Styles personnalisés
└── js/
    └── script.js          <- JavaScript
```

---

### [OUTIL] Étape 1.2 : Configuration du Virtual Host

**Créer la configuration :**

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

**Contenu :**

```apache
<VirtualHost *:80>
    ServerName contacts.local
    ServerAlias www.contacts.local
    ServerAdmin admin@contacts.local
    
    DocumentRoot /var/www/contacts
    
    <Directory /var/www/contacts>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    ErrorLog ${APACHE_LOG_DIR}/contacts_error.log
    CustomLog ${APACHE_LOG_DIR}/contacts_access.log combined
</VirtualHost>
```

**Activer le site :**

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

**Ajouter au fichier hosts :**

**Sur ta VM :**

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

**Ajoute :**

```
127.0.0.1    contacts.local www.contacts.local
```

**Sur ton PC Windows :**

```
C:\Windows\System32\drivers\etc\hosts

Ajoute :
192.168.1.100    contacts.local www.contacts.local
```

---

### [ARCHIVE] Étape 1.3 : Créer la base de données

**Se connecter à MySQL :**

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

**Créer la base et l'utilisateur :**

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

-- Créer un utilisateur dédié
CREATE USER 'contacts_user'@'localhost' IDENTIFIED BY 'MotDePasse123!';

-- Donner les droits
GRANT ALL PRIVILEGES ON contacts_db.* TO 'contacts_user'@'localhost';

-- Appliquer
FLUSH PRIVILEGES;

-- Utiliser la base
USE contacts_db;

-- Créer la table contacts
CREATE TABLE contacts (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nom VARCHAR(100) NOT NULL,
    prenom VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    telephone VARCHAR(20),
    adresse TEXT,
    date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    date_modification TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Insérer quelques données de test
INSERT INTO contacts (nom, prenom, email, telephone, adresse) VALUES
('Diop', 'Amadou', 'amadou.diop@example.com', '+221 77 123 45 67', 'Dakar, Sénégal'),
('Ndiaye', 'Fatou', 'fatou.ndiaye@example.com', '+221 76 234 56 78', 'Thiès, Sénégal'),
('Sarr', 'Moussa', 'moussa.sarr@example.com', '+221 78 345 67 89', 'Saint-Louis, Sénégal');

-- Vérifier
SELECT * FROM contacts;

-- Quitter
EXIT;
```

---

### [CODE] Étape 1.4 : Fichiers PHP du projet

**1. Configuration (includes/config.php) :**

```bash
sudo nano /var/www/contacts/includes/config.php
```

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

// Paramètres de connexion
define('DB_HOST', 'localhost');
define('DB_NAME', 'contacts_db');
define('DB_USER', 'contacts_user');
define('DB_PASS', 'MotDePasse123!');  // [ATTENTION] CHANGE ICI
define('DB_CHARSET', 'utf8mb4');

// Configuration de l'application
define('APP_NAME', 'Gestionnaire de Contacts');
define('APP_VERSION', '1.0.0');

// Timezone
date_default_timezone_set('Africa/Dakar');

// Démarrer la session
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Fonction pour échapper les données (sécurité)
function escape($data) {
    return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}

// Fonction pour afficher un message flash
function setFlashMessage($type, $message) {
    $_SESSION['flash'] = [
        'type' => $type,
        'message' => $message
    ];
}

// Fonction pour récupérer et supprimer un message flash
function getFlashMessage() {
    if (isset($_SESSION['flash'])) {
        $flash = $_SESSION['flash'];
        unset($_SESSION['flash']);
        return $flash;
    }
    return null;
}
?>
```

---

**2. Classe Database (includes/Database.php) :**

```bash
sudo nano /var/www/contacts/includes/Database.php
```

```php
<?php
// ═══════════════════════════════════════════════════════════════
// CLASSE DATABASE - GESTION DE LA CONNEXION
// ═══════════════════════════════════════════════════════════════

class Database {
    private $host = DB_HOST;
    private $db_name = DB_NAME;
    private $username = DB_USER;
    private $password = DB_PASS;
    private $charset = DB_CHARSET;
    private $pdo = null;
    
    /**
     * Obtenir la connexion PDO
     * @return PDO|null
     */
    public function getConnection() {
        // Si déjà connecté, retourner la connexion existante
        if ($this->pdo !== null) {
            return $this->pdo;
        }
        
        try {
            // DSN (Data Source Name)
            $dsn = "mysql:host={$this->host};dbname={$this->db_name};charset={$this->charset}";
            
            // Options PDO
            $options = [
                PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,  // Activer les exceptions
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,  // Fetch en tableau associatif
                PDO::ATTR_EMULATE_PREPARES => false,  // Désactiver l'émulation de requêtes préparées
            ];
            
            // Créer la connexion
            $this->pdo = new PDO($dsn, $this->username, $this->password, $options);
            
            return $this->pdo;
            
        } catch (PDOException $e) {
            // En cas d'erreur, afficher un message générique (pas les détails en production)
            die("Erreur de connexion à la base de données. Veuillez réessayer plus tard.");
            // En développement, tu peux afficher : echo $e->getMessage();
        }
    }
    
    /**
     * Fermer la connexion
     */
    public function closeConnection() {
        $this->pdo = null;
    }
}
?>
```

**Explication :**

- **PDO** (PHP Data Objects) = Interface pour accéder aux bases de données
- **Requêtes préparées** = Protection contre les injections SQL
- **Singleton pattern** = Une seule connexion réutilisée

---

**3. Classe Contact (includes/Contact.php) :**

```bash
sudo nano /var/www/contacts/includes/Contact.php
```

```php
<?php
// ═══════════════════════════════════════════════════════════════
// CLASSE CONTACT - GESTION DES CONTACTS
// ═══════════════════════════════════════════════════════════════

class Contact {
    private $db;
    private $table = 'contacts';
    
    // Propriétés
    public $id;
    public $nom;
    public $prenom;
    public $email;
    public $telephone;
    public $adresse;
    public $date_creation;
    public $date_modification;
    
    /**
     * Constructeur
     * @param PDO $db Connexion à la base de données
     */
    public function __construct($db) {
        $this->db = $db;
    }
    
    /**
     * Récupérer tous les contacts
     * @return array
     */
    public function readAll() {
        $query = "SELECT * FROM {$this->table} ORDER BY nom ASC, prenom ASC";
        $stmt = $this->db->prepare($query);
        $stmt->execute();
        return $stmt->fetchAll();
    }
    
    /**
     * Récupérer un contact par son ID
     * @param int $id
     * @return array|false
     */
    public function readOne($id) {
        $query = "SELECT * FROM {$this->table} WHERE id = :id LIMIT 1";
        $stmt = $this->db->prepare($query);
        $stmt->bindParam(':id', $id, PDO::PARAM_INT);
        $stmt->execute();
        return $stmt->fetch();
    }
    
    /**
     * Créer un nouveau contact
     * @return bool
     */
    public function create() {
        $query = "INSERT INTO {$this->table} 
                  (nom, prenom, email, telephone, adresse) 
                  VALUES 
                  (:nom, :prenom, :email, :telephone, :adresse)";
        
        $stmt = $this->db->prepare($query);
        
        // Nettoyer les données
        $this->nom = strip_tags($this->nom);
        $this->prenom = strip_tags($this->prenom);
        $this->email = strip_tags($this->email);
        $this->telephone = strip_tags($this->telephone);
        $this->adresse = strip_tags($this->adresse);
        
        // Lier les paramètres
        $stmt->bindParam(':nom', $this->nom);
        $stmt->bindParam(':prenom', $this->prenom);
        $stmt->bindParam(':email', $this->email);
        $stmt->bindParam(':telephone', $this->telephone);
        $stmt->bindParam(':adresse', $this->adresse);
        
        // Exécuter
        if ($stmt->execute()) {
            return true;
        }
        return false;
    }
    
    /**
     * Mettre à jour un contact
     * @return bool
     */
    public function update() {
        $query = "UPDATE {$this->table} SET 
                  nom = :nom, 
                  prenom = :prenom, 
                  email = :email, 
                  telephone = :telephone, 
                  adresse = :adresse 
                  WHERE id = :id";
        
        $stmt = $this->db->prepare($query);
        
        // Nettoyer les données
        $this->nom = strip_tags($this->nom);
        $this->prenom = strip_tags($this->prenom);
        $this->email = strip_tags($this->email);
        $this->telephone = strip_tags($this->telephone);
        $this->adresse = strip_tags($this->adresse);
        $this->id = intval($this->id);
        
        // Lier les paramètres
        $stmt->bindParam(':nom', $this->nom);
        $stmt->bindParam(':prenom', $this->prenom);
        $stmt->bindParam(':email', $this->email);
        $stmt->bindParam(':telephone', $this->telephone);
        $stmt->bindParam(':adresse', $this->adresse);
        $stmt->bindParam(':id', $this->id, PDO::PARAM_INT);
        
        // Exécuter
        if ($stmt->execute()) {
            return true;
        }
        return false;
    }
    
    /**
     * Supprimer un contact
     * @param int $id
     * @return bool
     */
    public function delete($id) {
        $query = "DELETE FROM {$this->table} WHERE id = :id";
        $stmt = $this->db->prepare($query);
        $stmt->bindParam(':id', $id, PDO::PARAM_INT);
        
        if ($stmt->execute()) {
            return true;
        }
        return false;
    }
    
    /**
     * Vérifier si un email existe déjà
     * @param string $email
     * @param int|null $excludeId ID à exclure (pour modification)
     * @return bool
     */
    public function emailExists($email, $excludeId = null) {
        $query = "SELECT id FROM {$this->table} WHERE email = :email";
        
        if ($excludeId !== null) {
            $query .= " AND id != :id";
        }
        
        $stmt = $this->db->prepare($query);
        $stmt->bindParam(':email', $email);
        
        if ($excludeId !== null) {
            $stmt->bindParam(':id', $excludeId, PDO::PARAM_INT);
        }
        
        $stmt->execute();
        return $stmt->rowCount() > 0;
    }
    
    /**
     * Rechercher des contacts
     * @param string $keyword
     * @return array
     */
    public function search($keyword) {
        $query = "SELECT * FROM {$this->table} 
                  WHERE nom LIKE :keyword 
                  OR prenom LIKE :keyword 
                  OR email LIKE :keyword 
                  OR telephone LIKE :keyword 
                  ORDER BY nom ASC, prenom ASC";
        
        $stmt = $this->db->prepare($query);
        $keyword = "%{$keyword}%";
        $stmt->bindParam(':keyword', $keyword);
        $stmt->execute();
        return $stmt->fetchAll();
    }
}
?>
```

---

**4. Page d'accueil (index.php) :**

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

```php
<?php
// ═══════════════════════════════════════════════════════════════
// PAGE D'ACCUEIL - LISTE DES CONTACTS
// ═══════════════════════════════════════════════════════════════

require_once 'includes/config.php';
require_once 'includes/Database.php';
require_once 'includes/Contact.php';

// Connexion à la base de données
$database = new Database();
$db = $database->getConnection();

// Instance de Contact
$contact = new Contact($db);

// Gestion de la recherche
$contacts = [];
$searchKeyword = '';

if (isset($_GET['search']) && !empty($_GET['search'])) {
    $searchKeyword = $_GET['search'];
    $contacts = $contact->search($searchKeyword);
} else {
    $contacts = $contact->readAll();
}

// Récupérer le message flash
$flash = getFlashMessage();
?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?php echo APP_NAME; ?></title>
    
    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <!-- Custom CSS -->
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <!-- Navigation -->
    <nav class="navbar navbar-dark bg-primary">
        <div class="container-fluid">
            <span class="navbar-brand mb-0 h1">
                <i class="fas fa-address-book"></i> <?php echo APP_NAME; ?>
            </span>
        </div>
    </nav>

    <div class="container mt-4">
        <!-- Message Flash -->
        <?php if ($flash): ?>
            <div class="alert alert-<?php echo $flash['type']; ?> alert-dismissible fade show" role="alert">
                <?php echo escape($flash['message']); ?>
                <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
            </div>
        <?php endif; ?>

        <!-- En-tête -->
        <div class="row mb-4">
            <div class="col-md-6">
                <h2><i class="fas fa-users"></i> Liste des Contacts</h2>
            </div>
            <div class="col-md-6 text-end">
                <a href="ajouter.php" class="btn btn-success">
                    <i class="fas fa-plus"></i> Nouveau Contact
                </a>
            </div>
        </div>

        <!-- Barre de recherche -->
        <div class="row mb-3">
            <div class="col-md-6">
                <form method="GET" action="">
                    <div class="input-group">
                        <input type="text" name="search" class="form-control" 
                               placeholder="Rechercher un contact..." 
                               value="<?php echo escape($searchKeyword); ?>">
                        <button class="btn btn-outline-primary" type="submit">
                            <i class="fas fa-search"></i> Rechercher
                        </button>
                        <?php if ($searchKeyword): ?>
                            <a href="index.php" class="btn btn-outline-secondary">
                                <i class="fas fa-times"></i> Réinitialiser
                            </a>
                        <?php endif; ?>
                    </div>
                </form>
            </div>
            <div class="col-md-6 text-end">
                <span class="badge bg-info text-dark">
                    <?php echo count($contacts); ?> contact(s) trouvé(s)
                </span>
            </div>
        </div>

        <!-- Tableau des contacts -->
        <?php if (empty($contacts)): ?>
            <div class="alert alert-info">
                <i class="fas fa-info-circle"></i> 
                <?php echo $searchKeyword ? 'Aucun contact trouvé.' : 'Aucun contact enregistré.'; ?>
            </div>
        <?php else: ?>
            <div class="table-responsive">
                <table class="table table-striped table-hover">
                    <thead class="table-dark">
                        <tr>
                            <th>Nom</th>
                            <th>Prénom</th>
                            <th>Email</th>
                            <th>Téléphone</th>
                            <th>Adresse</th>
                            <th width="150">Actions</th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($contacts as $c): ?>
                            <tr>
                                <td><?php echo escape($c['nom']); ?></td>
                                <td><?php echo escape($c['prenom']); ?></td>
                                <td><?php echo escape($c['email']); ?></td>
                                <td><?php echo escape($c['telephone'] ?? 'N/A'); ?></td>
                                <td><?php echo escape($c['adresse'] ?? 'N/A'); ?></td>
                                <td>
                                    <a href="modifier.php?id=<?php echo $c['id']; ?>" 
                                       class="btn btn-sm btn-warning" 
                                       title="Modifier">
                                        <i class="fas fa-edit"></i>
                                    </a>
                                    <a href="supprimer.php?id=<?php echo $c['id']; ?>" 
                                       class="btn btn-sm btn-danger" 
                                       onclick="return confirm('Êtes-vous sûr de vouloir supprimer ce contact ?');"
                                       title="Supprimer">
                                        <i class="fas fa-trash"></i>
                                    </a>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        <?php endif; ?>
    </div>

    <!-- Footer -->
    <footer class="mt-5 py-3 bg-light text-center">
        <p class="text-muted mb-0">
            <?php echo APP_NAME; ?> v<?php echo APP_VERSION; ?> | 
            Développé avec [HEAVY_BLACK_HEART] en LAMP
        </p>
    </footer>

    <!-- Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
```

---

**5. Page d'ajout (ajouter.php) :**

```bash
sudo nano /var/www/contacts/ajouter.php
```

```php
<?php
// ═══════════════════════════════════════════════════════════════
// PAGE AJOUT - CRÉER UN NOUVEAU CONTACT
// ═══════════════════════════════════════════════════════════════

require_once 'includes/config.php';
require_once 'includes/Database.php';
require_once 'includes/Contact.php';

// Connexion à la base de données
$database = new Database();
$db = $database->getConnection();

// Instance de Contact
$contact = new Contact($db);

// Variables pour le formulaire
$errors = [];
$nom = $prenom = $email = $telephone = $adresse = '';

// Traitement du formulaire
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Récupérer les données
    $nom = trim($_POST['nom'] ?? '');
    $prenom = trim($_POST['prenom'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $telephone = trim($_POST['telephone'] ?? '');
    $adresse = trim($_POST['adresse'] ?? '');
    
    // Validation
    if (empty($nom)) {
        $errors[] = "Le nom est obligatoire.";
    }
    
    if (empty($prenom)) {
        $errors[] = "Le prénom est obligatoire.";
    }
    
    if (empty($email)) {
        $errors[] = "L'email est obligatoire.";
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "L'email n'est pas valide.";
    } elseif ($contact->emailExists($email)) {
        $errors[] = "Cet email est déjà utilisé.";
    }
    
    // Si pas d'erreurs, créer le contact
    if (empty($errors)) {
        $contact->nom = $nom;
        $contact->prenom = $prenom;
        $contact->email = $email;
        $contact->telephone = $telephone;
        $contact->adresse = $adresse;
        
        if ($contact->create()) {
            setFlashMessage('success', 'Contact ajouté avec succès !');
            header('Location: index.php');
            exit;
        } else {
            $errors[] = "Erreur lors de l'ajout du contact.";
        }
    }
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Nouveau Contact - <?php echo APP_NAME; ?></title>
    
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <!-- Navigation -->
    <nav class="navbar navbar-dark bg-primary">
        <div class="container-fluid">
            <a href="index.php" class="navbar-brand">
                <i class="fas fa-address-book"></i> <?php echo APP_NAME; ?>
            </a>
        </div>
    </nav>

    <div class="container mt-4">
        <div class="row">
            <div class="col-md-8 offset-md-2">
                <!-- En-tête -->
                <div class="d-flex justify-content-between align-items-center mb-4">
                    <h2><i class="fas fa-user-plus"></i> Nouveau Contact</h2>
                    <a href="index.php" class="btn btn-secondary">
                        <i class="fas fa-arrow-left"></i> Retour
                    </a>
                </div>

                <!-- Erreurs -->
                <?php if (!empty($errors)): ?>
                    <div class="alert alert-danger">
                        <strong><i class="fas fa-exclamation-triangle"></i> Erreur(s) :</strong>
                        <ul class="mb-0 mt-2">
                            <?php foreach ($errors as $error): ?>
                                <li><?php echo escape($error); ?></li>
                            <?php endforeach; ?>
                        </ul>
                    </div>
                <?php endif; ?>

                <!-- Formulaire -->
                <div class="card">
                    <div class="card-body">
                        <form method="POST" action="">
                            <div class="row">
                                <div class="col-md-6 mb-3">
                                    <label for="nom" class="form-label">
                                        Nom <span class="text-danger">*</span>
                                    </label>
                                    <input type="text" class="form-control" id="nom" name="nom" 
                                           value="<?php echo escape($nom); ?>" required>
                                </div>

                                <div class="col-md-6 mb-3">
                                    <label for="prenom" class="form-label">
                                        Prénom <span class="text-danger">*</span>
                                    </label>
                                    <input type="text" class="form-control" id="prenom" name="prenom" 
                                           value="<?php echo escape($prenom); ?>" required>
                                </div>
                            </div>

                            <div class="mb-3">
                                <label for="email" class="form-label">
                                    Email <span class="text-danger">*</span>
                                </label>
                                <input type="email" class="form-control" id="email" name="email" 
                                       value="<?php echo escape($email); ?>" required>
                            </div>

                            <div class="mb-3">
                                <label for="telephone" class="form-label">Téléphone</label>
                                <input type="tel" class="form-control" id="telephone" name="telephone" 
                                       value="<?php echo escape($telephone); ?>"
                                       placeholder="+221 77 123 45 67">
                            </div>

                            <div class="mb-3">
                                <label for="adresse" class="form-label">Adresse</label>
                                <textarea class="form-control" id="adresse" name="adresse" 
                                          rows="3"><?php echo escape($adresse); ?></textarea>
                            </div>

                            <div class="d-grid gap-2 d-md-flex justify-content-md-end">
                                <a href="index.php" class="btn btn-secondary">
                                    <i class="fas fa-times"></i> Annuler
                                </a>
                                <button type="submit" class="btn btn-success">
                                    <i class="fas fa-save"></i> Enregistrer
                                </button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
```

---

**6. Page de modification (modifier.php) :**

```bash
sudo nano /var/www/contacts/modifier.php
```

```php
<?php
// ═══════════════════════════════════════════════════════════════
// PAGE MODIFICATION - MODIFIER UN CONTACT
// ═══════════════════════════════════════════════════════════════

require_once 'includes/config.php';
require_once 'includes/Database.php';
require_once 'includes/Contact.php';

// Vérifier l'ID
if (!isset($_GET['id']) || empty($_GET['id'])) {
    setFlashMessage('danger', 'Contact introuvable.');
    header('Location: index.php');
    exit;
}

$id = intval($_GET['id']);

// Connexion à la base de données
$database = new Database();
$db = $database->getConnection();

// Instance de Contact
$contact = new Contact($db);

// Récupérer le contact
$contactData = $contact->readOne($id);

if (!$contactData) {
    setFlashMessage('danger', 'Contact introuvable.');
    header('Location: index.php');
    exit;
}

// Variables pour le formulaire
$errors = [];
$nom = $contactData['nom'];
$prenom = $contactData['prenom'];
$email = $contactData['email'];
$telephone = $contactData['telephone'];
$adresse = $contactData['adresse'];

// Traitement du formulaire
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Récupérer les données
    $nom = trim($_POST['nom'] ?? '');
    $prenom = trim($_POST['prenom'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $telephone = trim($_POST['telephone'] ?? '');
    $adresse = trim($_POST['adresse'] ?? '');
    
    // Validation
    if (empty($nom)) {
        $errors[] = "Le nom est obligatoire.";
    }
    
    if (empty($prenom)) {
        $errors[] = "Le prénom est obligatoire.";
    }
    
    if (empty($email)) {
        $errors[] = "L'email est obligatoire.";
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = "L'email n'est pas valide.";
    } elseif ($contact->emailExists($email, $id)) {
        $errors[] = "Cet email est déjà utilisé par un autre contact.";
    }
    
    // Si pas d'erreurs, modifier le contact
    if (empty($errors)) {
        $contact->id = $id;
        $contact->nom = $nom;
        $contact->prenom = $prenom;
        $contact->email = $email;
        $contact->telephone = $telephone;
        $contact->adresse = $adresse;
        
        if ($contact->update()) {
            setFlashMessage('success', 'Contact modifié avec succès !');
            header('Location: index.php');
            exit;
        } else {
            $errors[] = "Erreur lors de la modification du contact.";
        }
    }
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Modifier Contact - <?php echo APP_NAME; ?></title>
    
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <!-- Navigation -->
    <nav class="navbar navbar-dark bg-primary">
        <div class="container-fluid">
            <a href="index.php" class="navbar-brand">
                <i class="fas fa-address-book"></i> <?php echo APP_NAME; ?>
            </a>
        </div>
    </nav>

    <div class="container mt-4">
        <div class="row">
            <div class="col-md-8 offset-md-2">
                <!-- En-tête -->
                <div class="d-flex justify-content-between align-items-center mb-4">
                    <h2><i class="fas fa-edit"></i> Modifier Contact</h2>
                    <a href="index.php" class="btn btn-secondary">
                        <i class="fas fa-arrow-left"></i> Retour
                    </a>
                </div>

                <!-- Erreurs -->
                <?php if (!empty($errors)): ?>
                    <div class="alert alert-danger">
                        <strong><i class="fas fa-exclamation-triangle"></i> Erreur(s) :</strong>
                        <ul class="mb-0 mt-2">
                            <?php foreach ($errors as $error): ?>
                                <li><?php echo escape($error); ?></li>
                            <?php endforeach; ?>
                        </ul>
                    </div>
                <?php endif; ?>

                <!-- Formulaire -->
                <div class="card">
                    <div class="card-body">
                        <form method="POST" action="">
                            <div class="row">
                                <div class="col-md-6 mb-3">
                                    <label for="nom" class="form-label">
                                        Nom <span class="text-danger">*</span>
                                    </label>
                                    <input type="text" class="form-control" id="nom" name="nom" 
                                           value="<?php echo escape($nom); ?>" required>
                                </div>

                                <div class="col-md-6 mb-3">
                                    <label for="prenom" class="form-label">
                                        Prénom <span class="text-danger">*</span>
                                    </label>
                                    <input type="text" class="form-control" id="prenom" name="prenom" 
                                           value="<?php echo escape($prenom); ?>" required>
                                </div>
                            </div>

                            <div class="mb-3">
                                <label for="email" class="form-label">
                                    Email <span class="text-danger">*</span>
                                </label>
                                <input type="email" class="form-control" id="email" name="email" 
                                       value="<?php echo escape($email); ?>" required>
                            </div>

                            <div class="mb-3">
                                <label for="telephone" class="form-label">Téléphone</label>
                                <input type="tel" class="form-control" id="telephone" name="telephone" 
                                       value="<?php echo escape($telephone); ?>"
                                       placeholder="+221 77 123 45 67">
                            </div>

                            <div class="mb-3">
                                <label for="adresse" class="form-label">Adresse</label>
                                <textarea class="form-control" id="adresse" name="adresse" 
                                          rows="3"><?php echo escape($adresse); ?></textarea>
                            </div>

                            <div class="d-grid gap-2 d-md-flex justify-content-md-end">
                                <a href="index.php" class="btn btn-secondary">
                                    <i class="fas fa-times"></i> Annuler
                                </a>
                                <button type="submit" class="btn btn-warning">
                                    <i class="fas fa-save"></i> Enregistrer les modifications
                                </button>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
```

---

**7. Page de suppression (supprimer.php) :**

```bash
sudo nano /var/www/contacts/supprimer.php
```

```php
<?php
// ═══════════════════════════════════════════════════════════════
// PAGE SUPPRESSION - SUPPRIMER UN CONTACT
// ═══════════════════════════════════════════════════════════════

require_once 'includes/config.php';
require_once 'includes/Database.php';
require_once 'includes/Contact.php';

// Vérifier l'ID
if (!isset($_GET['id']) || empty($_GET['id'])) {
    setFlashMessage('danger', 'Contact introuvable.');
    header('Location: index.php');
    exit;
}

$id = intval($_GET['id']);

// Connexion à la base de données
$database = new Database();
$db = $database->getConnection();

// Instance de Contact
$contact = new Contact($db);

// Supprimer le contact
if ($contact->delete($id)) {
    setFlashMessage('success', 'Contact supprimé avec succès !');
} else {
    setFlashMessage('danger', 'Erreur lors de la suppression du contact.');
}

header('Location: index.php');
exit;
?>
```

---

**8. CSS personnalisé (css/style.css) :**

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

```css
/* ═══════════════════════════════════════════════════════════════
   STYLES PERSONNALISÉS
   ═══════════════════════════════════════════════════════════════ */

body {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background-color: #f8f9fa;
}

footer {
    margin-top: auto;
}

.navbar-brand {
    font-size: 1.5rem;
    font-weight: bold;
}

.card {
    box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
    border: none;
}

.table {
    background-color: white;
}

.btn {
    border-radius: 0.375rem;
}

.btn-sm {
    padding: 0.25rem 0.5rem;
}

/* Animations */
.alert {
    animation: slideDown 0.3s ease-out;
}

@keyframes slideDown {
    from {
        opacity: 0;
        transform: translateY(-20px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Responsive */
@media (max-width: 768px) {
    .table {
        font-size: 0.875rem;
    }
    
    .btn-sm {
        padding: 0.15rem 0.3rem;
        font-size: 0.75rem;
    }
}

/* Amélioration du focus */
.form-control:focus,
.btn:focus {
    box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
}

/* Hover effects */
.table-hover tbody tr:hover {
    background-color: rgba(13, 110, 253, 0.05);
    cursor: pointer;
}
```

---

### [TEST] Étape 1.5 : Tester l'application

**1. Ouvre ton navigateur :**

```
http://contacts.local
```

**Tu devrais voir :**
- La liste des 3 contacts de test
- Un bouton "Nouveau Contact"
- Une barre de recherche

---

**2. Tester l'ajout :**

- Clique sur "Nouveau Contact"
- Remplis le formulaire
- Clique sur "Enregistrer"
- Tu devrais revenir sur la liste avec un message de succès

---

**3. Tester la modification :**

- Clique sur l'icône de crayon (modifier)
- Modifie les informations
- Clique sur "Enregistrer les modifications"
- Les changements sont appliqués

---

**4. Tester la recherche :**

- Entre un nom dans la barre de recherche
- Clique sur "Rechercher"
- Seuls les contacts correspondants s'affichent

---

**5. Tester la suppression :**

- Clique sur l'icône de poubelle (supprimer)
- Confirme la suppression
- Le contact est supprimé avec un message de confirmation

---

**[BRAVO] TON APPLICATION CRUD EST COMPLÈTE ! [BRAVO]**

---

## [PACKAGE] PARTIE 2 : INSTALLER WORDPRESS

### [REFLEXION] Qu'est-ce que WordPress ?

**WordPress** = CMS (Content Management System) le plus populaire au monde

**Statistiques 2024 :**
- 43% de tous les sites web utilisent WordPress
- 63% de part de marché des CMS
- 60+ millions de sites WordPress actifs

**Utilisations :**
- Blogs
- Sites d'entreprise
- E-commerce (avec WooCommerce)
- Forums
- Portfolios
- Magazines en ligne

---

### [ENTREE] Étape 2.1 : Télécharger WordPress

**1. Se placer dans le dossier temporaire :**

```bash
cd /tmp
```

---

**2. Télécharger WordPress (version française) :**

```bash
wget https://fr.wordpress.org/latest-fr_FR.tar.gz
```

**Explication :**
- `wget` = Télécharger un fichier depuis une URL
- `latest-fr_FR.tar.gz` = Dernière version en français

**Résultat :**

```
--2024-12-17 12:00:00--  https://fr.wordpress.org/latest-fr_FR.tar.gz
Resolving fr.wordpress.org... 192.0.78.13
Connecting to fr.wordpress.org|192.0.78.13|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 25123456 (24M) [application/octet-stream]
Saving to: 'latest-fr_FR.tar.gz'

latest-fr_FR.tar.gz   100%[===================>]  23.96M  5.23MB/s    in 5.2s

2024-12-17 12:00:05 (4.61 MB/s) - 'latest-fr_FR.tar.gz' saved [25123456/25123456]
```

---

**3. Extraire l'archive :**

```bash
tar -xzf latest-fr_FR.tar.gz
```

**Explication :**
- `tar` = Gestionnaire d'archives
- `-x` = Extraire
- `-z` = Décompresser (gzip)
- `-f` = Fichier

**Résultat :**

Un dossier `wordpress/` est créé.

---

**4. Déplacer dans /var/www/ :**

```bash
sudo mv wordpress /var/www/
```

---

**5. Définir les permissions :**

```bash
sudo chown -R www-data:www-data /var/www/wordpress
sudo chmod -R 755 /var/www/wordpress
```

---

### [ARCHIVE] Étape 2.2 : Créer la base de données WordPress

**Se connecter à MySQL :**

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

**Créer la base et l'utilisateur :**

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

-- Créer un utilisateur
CREATE USER 'wordpress_user'@'localhost' IDENTIFIED BY 'WP_MotDePasse123!';

-- Donner les droits
GRANT ALL PRIVILEGES ON wordpress_db.* TO 'wordpress_user'@'localhost';

-- Appliquer
FLUSH PRIVILEGES;

-- Quitter
EXIT;
```

---

### [WEB] Étape 2.3 : Configurer le Virtual Host

**Créer la configuration :**

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

**Contenu :**

```apache
<VirtualHost *:80>
    ServerName wordpress.local
    ServerAlias www.wordpress.local
    ServerAdmin admin@wordpress.local
    
    DocumentRoot /var/www/wordpress
    
    <Directory /var/www/wordpress>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    ErrorLog ${APACHE_LOG_DIR}/wordpress_error.log
    CustomLog ${APACHE_LOG_DIR}/wordpress_access.log combined
</VirtualHost>
```

**Activer le site :**

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

**Ajouter au fichier hosts :**

**VM :**

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

Ajoute :

```
127.0.0.1    wordpress.local www.wordpress.local
```

**PC Windows :**

```
C:\Windows\System32\drivers\etc\hosts

Ajoute :
192.168.1.100    wordpress.local www.wordpress.local
```

---

### [CONFIG] Étape 2.4 : Configuration de WordPress

**1. Ouvre ton navigateur :**

```
http://wordpress.local
```

**Tu verras l'écran d'installation de WordPress ! [BRAVO]**

---

**2. Clique sur "C'est parti !"**

---

**3. Remplis les informations de connexion :**

```
Nom de la base de données : wordpress_db
Identifiant : wordpress_user
Mot de passe : WP_MotDePasse123!
Adresse de la base de données : localhost
Préfixe des tables : wp_
```

**Clique sur "Envoyer"**

---

**4. Clique sur "Lancer l'installation"**

---

**5. Remplis les informations du site :**

```
Titre du site : Mon Site WordPress
Identifiant : admin
Mot de passe : (un mot de passe FORT)
Votre adresse e-mail : ton@email.com
Visibilité : [ ] Demander aux moteurs de recherche de ne pas indexer ce site
```

**[ATTENTION] Coche la case "Visibilité" si c'est juste un site de test !**

**Clique sur "Installer WordPress"**

---

**6. Connexion :**

**Tu seras redirigé vers la page de connexion :**

```
http://wordpress.local/wp-admin
```

**Entre tes identifiants et connecte-toi !**

---

**[BRAVO] WORDPRESS EST INSTALLÉ ! [BRAVO]**

**Tu peux maintenant :**
- Créer des articles
- Personnaliser le thème
- Installer des extensions (plugins)
- Gérer les utilisateurs
- Etc.

---

### [OUTIL] Étape 2.5 : Optimisations WordPress

**1. Augmenter la limite de mémoire :**

```bash
sudo nano /var/www/wordpress/wp-config.php
```

**Ajoute avant la ligne `/* C'est tout, ne touchez pas à ce qui suit ! */` :**

```php
// Augmenter la mémoire PHP
define('WP_MEMORY_LIMIT', '256M');
define('WP_MAX_MEMORY_LIMIT', '512M');

// Désactiver l'éditeur de fichiers (sécurité)
define('DISALLOW_FILE_EDIT', true);

// Activer le mode debug (développement uniquement)
// define('WP_DEBUG', true);
// define('WP_DEBUG_LOG', true);
// define('WP_DEBUG_DISPLAY', false);
```

---

**2. Configurer les permaliens (URLs propres) :**

**Dans l'admin WordPress :**

```
Réglages -> Permaliens -> Structure personnalisée
/%postname%/
```

**Sauvegarde.**

**Cette configuration nécessite `mod_rewrite` (déjà activé).**

---

**3. Installer des plugins essentiels :**

**Dans l'admin WordPress :**

```
Extensions -> Ajouter
```

**Plugins recommandés :**

| Plugin | Description |
|--------|-------------|
| **Yoast SEO** | Optimisation SEO |
| **Wordfence Security** | Sécurité et pare-feu |
| **WP Super Cache** | Mise en cache (performance) |
| **Contact Form 7** | Formulaires de contact |
| **Akismet** | Anti-spam (inclus par défaut) |

---

## [SECURISE] PARTIE 3 : SÉCURISER AVEC HTTPS (SSL/TLS)

### [VERROUILLE] Pourquoi HTTPS ?

**HTTP** (Hypertext Transfer Protocol) = Non sécurisé
- Les données circulent en **clair**
- Interception possible (man-in-the-middle)
- Navigateurs affichent "Non sécurisé"

**HTTPS** (HTTP Secure) = Sécurisé avec SSL/TLS
- Les données sont **chiffrées**
- Authentification du serveur
- Intégrité des données
- Navigateurs affichent un cadenas [VERROUILLE]
- **Nécessaire** pour les formulaires, paiements, connexions

---

### [DOC] Étape 3.1 : Obtenir un certificat SSL

**2 options :**

**Option A : Certificat auto-signé (pour développement local)**
- Gratuit
- Facile
- [ATTENTION] Navigateurs affichent un avertissement
- OK pour tests locaux

**Option B : Let's Encrypt (pour production)**
- Gratuit
- Valide et reconnu
- Renouvellement automatique
- Nécessite un nom de domaine public

**Dans ce guide, on va créer un certificat auto-signé pour le développement.**

---

### [SECURISE] Étape 3.2 : Créer un certificat auto-signé

**1. Activer le module SSL d'Apache :**

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

---

**2. Créer un dossier pour les certificats :**

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

---

**3. Générer le certificat :**

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

**Explication :**
- `openssl` = Outil de cryptographie
- `req` = Générer une requête de certificat
- `-x509` = Créer un certificat auto-signé
- `-nodes` = Pas de phrase de passe (no DES)
- `-days 365` = Valide 1 an
- `-newkey rsa:2048` = Clé RSA de 2048 bits
- `-keyout` = Fichier de la clé privée
- `-out` = Fichier du certificat

---

**Tu devras répondre à quelques questions :**

```
Country Name (2 letter code) [AU]: SN
State or Province Name (full name) [Some-State]: Dakar
Locality Name (eg, city) []: Dakar
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Mon Entreprise
Organizational Unit Name (eg, section) []: IT
Common Name (e.g. server FQDN or YOUR name) []: contacts.local
Email Address []: admin@contacts.local
```

**[ATTENTION] Le plus important : `Common Name` doit être le nom de domaine (`contacts.local`).**

---

**4. Vérifier les fichiers créés :**

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

**Résultat :**

```
-rw-r--r-- 1 root root 1273 Dec 17 12:30 contacts.crt
-rw------- 1 root root 1704 Dec 17 12:30 contacts.key
```

---

### [WEB] Étape 3.3 : Configurer le Virtual Host HTTPS

**Créer la configuration HTTPS :**

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

**Contenu :**

```apache
<VirtualHost *:443>
    ServerName contacts.local
    ServerAlias www.contacts.local
    ServerAdmin admin@contacts.local
    
    DocumentRoot /var/www/contacts
    
    <Directory /var/www/contacts>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    # SSL Configuration
    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/contacts.crt
    SSLCertificateKeyFile /etc/apache2/ssl/contacts.key
    
    # Sécurité SSL
    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
    SSLHonorCipherOrder on
    
    # Logs
    ErrorLog ${APACHE_LOG_DIR}/contacts_ssl_error.log
    CustomLog ${APACHE_LOG_DIR}/contacts_ssl_access.log combined
</VirtualHost>
```

**Explication :**

- `<VirtualHost *:443>` = Port HTTPS (443)
- `SSLEngine on` = Activer SSL
- `SSLCertificateFile` = Chemin du certificat
- `SSLCertificateKeyFile` = Chemin de la clé privée
- `SSLProtocol` = Protocoles autorisés (désactiver les vieux)
- `SSLCipherSuite` = Algorithmes de chiffrement autorisés

---

**Activer le site HTTPS :**

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

---

### [SYNC] Étape 3.4 : Rediriger HTTP vers HTTPS

**Modifier le Virtual Host HTTP :**

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

**Ajoute AVANT la balise `</VirtualHost>` :**

```apache
    # Rediriger tout le trafic HTTP vers HTTPS
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
```

**Configuration complète :**

```apache
<VirtualHost *:80>
    ServerName contacts.local
    ServerAlias www.contacts.local
    ServerAdmin admin@contacts.local
    
    DocumentRoot /var/www/contacts
    
    <Directory /var/www/contacts>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
    
    # Rediriger tout le trafic HTTP vers HTTPS
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
    
    ErrorLog ${APACHE_LOG_DIR}/contacts_error.log
    CustomLog ${APACHE_LOG_DIR}/contacts_access.log combined
</VirtualHost>
```

**Recharger Apache :**

```bash
sudo systemctl reload apache2
```

---

### [TEST] Étape 3.5 : Tester HTTPS

**Ouvre ton navigateur :**

```
http://contacts.local
```

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

```
https://contacts.local
```

---

**[ATTENTION] Le navigateur affichera un avertissement :**

```
Votre connexion n'est pas privée
Les pirates pourraient essayer de dérober vos informations...
```

**C'est NORMAL ! Le certificat est auto-signé.**

**Pour continuer :**
- Chrome/Edge : Clique sur "Paramètres avancés" -> "Continuer vers contacts.local (dangereux)"
- Firefox : "Avancé" -> "Accepter le risque et poursuivre"

---

**Tu verras un cadenas barré dans la barre d'adresse.**

**Si tu cliques dessus :**

```
La connexion n'est pas sécurisée
Certificat non valide
```

**C'est OK pour le développement local ! [BRAVO]**

---

### [MONDE] Étape 3.6 : Let's Encrypt (pour production)

**Si tu as un VRAI domaine public (ex: monsite.com), voici comment obtenir un certificat gratuit avec Let's Encrypt :**

**1. Installer Certbot :**

```bash
sudo apt install certbot python3-certbot-apache -y
```

---

**2. Obtenir un certificat :**

```bash
sudo certbot --apache -d monsite.com -d www.monsite.com
```

**Explication :**
- `--apache` = Plugin Apache (config automatique)
- `-d monsite.com` = Domaine principal
- `-d www.monsite.com` = Sous-domaine

---

**Certbot va :**
1. Vérifier que tu possèdes le domaine
2. Obtenir le certificat
3. Configurer Apache automatiquement
4. Mettre en place le renouvellement automatique

---

**3. Tester le renouvellement automatique :**

```bash
sudo certbot renew --dry-run
```

---

**4. Le certificat est renouvelé automatiquement tous les 90 jours !**

---

## [RAPIDE] PARTIE 4 : OPTIMISATION ET DÉPLOIEMENT

### [RAPIDE] Étape 4.1 : Activer la mise en cache

**1. Activer les modules de cache :**

```bash
sudo a2enmod cache
sudo a2enmod cache_disk
sudo a2enmod expires
sudo a2enmod headers
sudo systemctl restart apache2
```

---

**2. Configurer le cache :**

```bash
sudo nano /etc/apache2/mods-available/expires.conf
```

**Contenu :**

```apache
<IfModule mod_expires.c>
    ExpiresActive On
    
    # Images
    ExpiresByType image/jpg "access plus 1 year"
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType image/x-icon "access plus 1 year"
    
    # CSS et JavaScript
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType text/javascript "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
    
    # Fonts
    ExpiresByType font/ttf "access plus 1 year"
    ExpiresByType font/woff "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    
    # HTML
    ExpiresByType text/html "access plus 1 hour"
    
    # Par défaut
    ExpiresDefault "access plus 1 week"
</IfModule>
```

**Redémarrer Apache :**

```bash
sudo systemctl restart apache2
```

---

### [SECURITE] Étape 4.2 : Sécurité avancée

**1. En-têtes de sécurité HTTP :**

```bash
sudo nano /etc/apache2/conf-available/security-headers.conf
```

**Contenu :**

```apache
<IfModule mod_headers.c>
    # Empêcher le clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"
    
    # Empêcher le MIME sniffing
    Header always set X-Content-Type-Options "nosniff"
    
    # Politique de sécurité du contenu (CSP)
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; img-src 'self' data: https:; font-src 'self' data: https://cdnjs.cloudflare.com;"
    
    # Protection XSS
    Header always set X-XSS-Protection "1; mode=block"
    
    # Politique de référent
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    
    # Permissions Policy
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
    
    # HSTS (HTTPS uniquement - décommenter si tu as HTTPS en production)
    # Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
</IfModule>
```

**Activer :**

```bash
sudo a2enconf security-headers
sudo systemctl reload apache2
```

---

**2. Masquer les informations sensibles :**

```bash
sudo nano /etc/apache2/conf-available/security.conf
```

**Modifier :**

```apache
ServerTokens Prod
ServerSignature Off
TraceEnable Off
```

---

**3. Limiter la taille des requêtes :**

```apache
# Dans security.conf, ajoute :
LimitRequestBody 10485760
```

---

### [GRAPHIQUE] Étape 4.3 : Monitoring et logs

**1. Installer un outil de monitoring :**

```bash
sudo apt install htop iotop nethogs -y
```

**Explication :**
- `htop` = Moniteur de processus (comme top mais mieux)
- `iotop` = Moniteur d'I/O disque
- `nethogs` = Moniteur de bande passante réseau

**Utilisation :**

```bash
htop          # Voir les processus en temps réel
sudo iotop    # Voir l'utilisation disque
sudo nethogs  # Voir l'utilisation réseau
```

---

**2. Analyser les logs Apache :**

**Voir les 10 IPs les plus actives :**

```bash
awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -10
```

**Voir les pages les plus visitées :**

```bash
awk '{print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head -10
```

**Voir les erreurs 404 :**

```bash
grep " 404 " /var/log/apache2/access.log | awk '{print $7}' | sort | uniq -c | sort -rn
```

---

**3. Rotation des logs (automatique) :**

**Vérifier la configuration :**

```bash
cat /etc/logrotate.d/apache2
```

**Contenu (par défaut) :**

```
/var/log/apache2/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 640 root adm
    sharedscripts
    postrotate
        if invoke-rc.d apache2 status > /dev/null 2>&1; then \
            invoke-rc.d apache2 reload > /dev/null 2>&1; \
        fi
    endscript
}
```

**Explication :**
- `daily` = Rotation quotidienne
- `rotate 14` = Garder 14 jours d'historique
- `compress` = Compresser les anciens logs
- Les logs sont gérés automatiquement !

---

### [PACKAGE] Étape 4.4 : Sauvegarde complète

**Script de sauvegarde complet :**

```bash
sudo nano /usr/local/bin/backup-lamp.sh
```

**Contenu :**

```bash
#!/bin/bash
# ═══════════════════════════════════════════════════════════════
# SCRIPT DE SAUVEGARDE COMPLÈTE LAMP
# ═══════════════════════════════════════════════════════════════

BACKUP_DIR="/backup/lamp"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
MYSQL_USER="root"
MYSQL_PASSWORD="TON_MOT_DE_PASSE_ROOT"  # [ATTENTION] CHANGE ICI

# Créer le dossier de backup
mkdir -p "$BACKUP_DIR"

echo "═══════════════════════════════════════════════════════════"
echo "  SAUVEGARDE LAMP - $DATE"
echo "═══════════════════════════════════════════════════════════"

# 1. Sauvegarder les fichiers web
echo "[DOSSIER] Sauvegarde des fichiers web..."
tar -czf "$BACKUP_DIR/www_${DATE}.tar.gz" /var/www/

# 2. Sauvegarder les configurations Apache
echo "[CONFIG]  Sauvegarde des configurations Apache..."
tar -czf "$BACKUP_DIR/apache_config_${DATE}.tar.gz" /etc/apache2/

# 3. Sauvegarder toutes les bases de données
echo "[ARCHIVE]  Sauvegarde des bases de données..."
mysqldump -u "$MYSQL_USER" -p"$MYSQL_PASSWORD" --all-databases > "$BACKUP_DIR/all_databases_${DATE}.sql"
gzip "$BACKUP_DIR/all_databases_${DATE}.sql"

# 4. Sauvegarder les configurations PHP
echo "[POSTGRES] Sauvegarde des configurations PHP..."
tar -czf "$BACKUP_DIR/php_config_${DATE}.tar.gz" /etc/php/

# 5. Nettoyer les backups de plus de 30 jours
echo "[NETTOYAGE] Nettoyage des anciens backups..."
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete

echo ""
echo "[OK] Sauvegarde terminée !"
echo "[DOSSIER] Emplacement : $BACKUP_DIR"
echo ""

# Lister les backups
echo "Liste des backups :"
ls -lh "$BACKUP_DIR" | tail -20
```

**Rendre exécutable :**

```bash
sudo chmod +x /usr/local/bin/backup-lamp.sh
```

**Tester :**

```bash
sudo /usr/local/bin/backup-lamp.sh
```

---

**Automatiser avec cron (tous les jours à 3h du matin) :**

```bash
sudo crontab -e
```

**Ajoute :**

```bash
0 3 * * * /usr/local/bin/backup-lamp.sh >> /var/log/lamp-backup.log 2>&1
```

---

### [SYNC] Étape 4.5 : Restauration depuis une sauvegarde

**Restaurer les fichiers web :**

```bash
sudo tar -xzf /backup/lamp/www_2024-12-17_03-00-00.tar.gz -C /
```

---

**Restaurer les bases de données :**

```bash
gunzip -c /backup/lamp/all_databases_2024-12-17_03-00-00.sql.gz | sudo mysql -u root -p
```

---

**Restaurer les configurations Apache :**

```bash
sudo tar -xzf /backup/lamp/apache_config_2024-12-17_03-00-00.tar.gz -C /
sudo systemctl restart apache2
```

---

## [OBJECTIF] CHECKLIST DE DÉPLOIEMENT EN PRODUCTION

**Avant de mettre ton site en ligne :**

**Sécurité :**
- [ ] Certificat SSL/TLS valide (Let's Encrypt)
- [ ] Pare-feu configuré (ufw ou iptables)
- [ ] SSH sécurisé (clés SSH, désactiver root login)
- [ ] Mots de passe forts partout
- [ ] Fail2Ban installé (protection contre brute-force)
- [ ] Sauvegardes automatiques configurées
- [ ] En-têtes de sécurité HTTP activés

**Performance :**
- [ ] Cache activé (Apache + PHP)
- [ ] Compression gzip activée
- [ ] Images optimisées
- [ ] CDN configuré (optionnel)
- [ ] Base de données optimisée

**Monitoring :**
- [ ] Logs configurés et surveillés
- [ ] Monitoring des ressources (CPU, RAM, disque)
- [ ] Alertes email en cas de problème
- [ ] Uptime monitoring (ex: UptimeRobot)

**Maintenance :**
- [ ] Mises à jour automatiques de sécurité
- [ ] Plan de sauvegarde testé
- [ ] Documentation à jour
- [ ] Contacts d'urgence définis

---

## [DOCS] RESSOURCES COMPLÉMENTAIRES

### Documentation officielle

**Apache :**
- https://httpd.apache.org/docs/2.4/

**MySQL/MariaDB :**
- https://dev.mysql.com/doc/
- https://mariadb.org/documentation/

**PHP :**
- https://www.php.net/manual/fr/

**WordPress :**
- https://fr.wordpress.org/support/
- https://developer.wordpress.org/

---

### Outils utiles

**Sécurité :**
- Fail2Ban : Protection contre brute-force
- ModSecurity : Pare-feu applicatif web (WAF)
- Lynis : Audit de sécurité système

**Performance :**
- GTmetrix : Test de performance web
- PageSpeed Insights : Optimisation Google
- New Relic : Monitoring applicatif

**Développement :**
- Adminer : Interface web pour MySQL
- Git : Gestion de version
- Composer : Gestionnaire de dépendances PHP

---

## [BRAVO] FÉLICITATIONS !

**Tu as terminé le guide LAMP complet ! [BRAVO]**

**Tu sais maintenant :**

[OK] Installer et configurer **Apache**
[OK] Installer et gérer **MySQL/MariaDB**
[OK] Installer et configurer **PHP**
[OK] Créer une application **CRUD complète**
[OK] Installer et configurer **WordPress**
[OK] Sécuriser avec **HTTPS/SSL**
[OK] Optimiser les **performances**
[OK] Mettre en place des **sauvegardes**
[OK] **Déployer en production**

**Tu as acquis des compétences professionnelles en développement web ! [RAPIDE]**

---

## [COURS] PROCHAINES ÉTAPES

**Pour aller plus loin :**

1. **Apprendre un framework PHP**
   - Laravel (le plus populaire)
   - Symfony (pour grandes applications)
   - CodeIgniter (léger et rapide)

2. **Approfondir JavaScript**
   - Vue.js / React / Angular
   - Node.js (backend JavaScript)

3. **DevOps**
   - Docker et containers
   - Kubernetes pour orchestration
   - CI/CD avec GitLab/GitHub Actions

4. **Cloud**
   - AWS / Azure / Google Cloud
   - Déploiement scalable
   - Serverless

---

**Bon courage dans tes projets web ! [WEB]***

**N'oublie pas : La meilleure façon d'apprendre, c'est de PRATIQUER ! [FORCE]**

---

## [TEL] SUPPORT

**Si tu as des questions :**

- Stack Overflow : https://stackoverflow.com/
- Reddit : r/webdev, r/PHP, r/WordPress
- Discord : Serveurs de dev web
- Forums WordPress : https://fr.wordpress.org/support/forums/

**Partage tes projets et continue d'apprendre ! [RAPIDE]**


# [COURS] GUIDE LAMP ULTRA DÉTAILLÉ - PARTIE 4/4
## Haute Disponibilité, Scalabilité et Résilience

## [ALERTE] LE PROBLÈME : SINGLE POINT OF FAILURE (SPOF)

### [ROUGE] Architecture actuelle (NON résiliente)

```
┌─────────────────────────────────────────────────────────┐
│  ARCHITECTURE LAMP CLASSIQUE (FRAGILE)                  │
└─────────────────────────────────────────────────────────┘

    [UTILISATEURS] Utilisateurs
       v
    [WEB] Internet
       v
    [PACKAGE] Serveur LAMP Unique (192.168.1.100)
       ├─ Apache (Web Server)
       ├─ PHP (Application)
       └─ MySQL (Database)

[ROUGE] PROBLÈMES :

1. Si le serveur crash -> TOUT tombe
2. Si MySQL crash -> Application inutilisable
3. Si Apache crash -> Site inaccessible
4. Maintenance = Downtime total
5. Pas de scalabilité (1 serveur = limite fixe)
6. Backup uniquement = RTO (Recovery Time) long

[IMPACT] SPOF = Single Point Of Failure
   (Point unique de défaillance)
```

---

### [OK] Architecture haute disponibilité (RÉSILIENTE)

```
┌─────────────────────────────────────────────────────────┐
│  ARCHITECTURE LAMP HAUTE DISPONIBILITÉ                  │
└─────────────────────────────────────────────────────────┘

            [UTILISATEURS] Utilisateurs
            v
            [WEB] Internet
            v
            [SCALES]  LOAD BALANCER (HAProxy/Nginx)
            v
    ┌───────┴───────────┐
    v                   v
[PACKAGE] Web Server 1    [PACKAGE] Web Server 2    [PACKAGE] Web Server N...
   (Apache+PHP)       (Apache+PHP)       (Apache+PHP)
       v                   v                   v
       └───────────┬───────┘───────────────────┘
                   v
            [SCALES]  DB Load Balancer
                   v
       ┌───────────┴───────────┐
       v                       v
    [ARCHIVE] Master DB          [ARCHIVE] Slave DB (Read Replicas)
   (Read+Write)          (Read Only) x N
       v
    [ARCHIVE] Slave DB 2 (Replication)
   (Backup automatique)

[OK] AVANTAGES :

1. Un serveur web tombe -> Les autres continuent
2. DB Master tombe -> Promotion automatique d'un Slave
3. Scalabilité horizontale (ajouter des serveurs)
4. Maintenance sans downtime (rolling updates)
5. Cache distribué (Redis/Memcached)
6. Sessions partagées
7. Backups automatiques sur réplication
```

---

## [GRAPHIQUE] PARTIE 1 : RÉPLICATION MYSQL (MASTER-SLAVE)

### [OBJECTIF] Objectif

**Créer une réplication MySQL pour :**
- [OK] Haute disponibilité (si Master tombe, promouvoir un Slave)
- [OK] Répartition de charge (lectures sur Slaves)
- [OK] Backup automatique (Slave = copie temps réel)
- [OK] Disaster Recovery rapide

---

### [DOSSIER] Architecture de réplication

```
┌────────────────────────────────────────────────────────┐
│  RÉPLICATION MYSQL MASTER-SLAVE                        │
└────────────────────────────────────────────────────────┘

[NOTE] Application (PHP)
   │
   ├─ Écritures (INSERT, UPDATE, DELETE)
   │  v
   │  [ARCHIVE] MASTER (192.168.1.100)
   │     v (Binary Log Replication)
   │     ├─ [ARCHIVE] SLAVE 1 (192.168.1.101) <- Lecture
   │     ├─ [ARCHIVE] SLAVE 2 (192.168.1.102) <- Lecture
   │     └─ [ARCHIVE] SLAVE 3 (192.168.1.103) <- Lecture
   │
   └─ Lectures (SELECT)
      v
      Load Balancing entre SLAVE 1, 2, 3

FLUX DE DONNÉES :

1. Application écrit sur MASTER
2. MASTER enregistre dans Binary Log
3. SLAVES lisent le Binary Log
4. SLAVES appliquent les changements
5. Application lit depuis SLAVES (répartition)

DÉLAI DE RÉPLICATION : ~50-500ms
```

---

### [OUTIL] Étape 1.1 : Configuration du MASTER

**Sur le serveur MASTER (192.168.1.100) :**

**1. Éditer la configuration MySQL :**

```bash
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
```

**Cherche et modifie/ajoute dans la section `[mysqld]` :**

```ini
[mysqld]

# ═══════════════════════════════════════════════════════════════
# CONFIGURATION MASTER (RÉPLICATION)
# ═══════════════════════════════════════════════════════════════

# ID unique du serveur (doit être différent sur chaque serveur)
server-id = 1

# Activer le binary log
log-bin = /var/log/mysql/mysql-bin.log

# Durée de conservation des binary logs (jours)
expire_logs_days = 7

# Taille maximale d'un binary log avant rotation
max_binlog_size = 100M

# Format de réplication
# ROW = Plus sûr, enregistre les changements de lignes
# STATEMENT = Enregistre les requêtes SQL (plus compact)
# MIXED = Hybride (recommandé)
binlog_format = MIXED

# Bases de données à répliquer (optionnel)
# Si non spécifié, toutes les bases sont répliquées
# binlog-do-db = contacts_db
# binlog-do-db = wordpress_db

# Bases de données à IGNORER (optionnel)
# binlog-ignore-db = mysql
# binlog-ignore-db = information_schema
# binlog-ignore-db = performance_schema

# Synchronisation des logs (sécurité)
sync_binlog = 1

# InnoDB settings pour réplication
innodb_flush_log_at_trx_commit = 1
```

**Explication détaillée :**

```ini
server-id = 1
# ID UNIQUE pour chaque serveur MySQL dans le cluster
# Master = 1
# Slave 1 = 2
# Slave 2 = 3, etc.
# IMPORTANT : Ne JAMAIS avoir deux serveurs avec le même ID !

log-bin = /var/log/mysql/mysql-bin.log
# Active le Binary Log (journal binaire)
# Enregistre TOUTES les modifications de données
# Les slaves lisent ce fichier pour se synchroniser

expire_logs_days = 7
# Supprime automatiquement les binary logs de plus de 7 jours
# Évite de saturer le disque
# Ajuste selon ton espace disque et politique de backup

binlog_format = MIXED
# ROW : Enregistre les modifications ligne par ligne (sûr mais volumineux)
# STATEMENT : Enregistre les requêtes SQL (compact mais risqué)
# MIXED : Utilise ROW pour les requêtes ambiguës, STATEMENT sinon

sync_binlog = 1
# Force l'écriture du binary log sur disque à chaque transaction
# 0 = Rapide mais risque de perte de données en cas de crash
# 1 = Sécurité maximale (recommandé pour Master)
# N = Synchronise tous les N événements (compromis)
```

---

**2. Redémarrer MySQL :**

```bash
sudo systemctl restart mariadb
```

**Vérifier :**

```bash
sudo systemctl status mariadb
```

---

**3. Créer un utilisateur de réplication :**

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

```sql
-- Créer un utilisateur dédié à la réplication
CREATE USER 'replication_user'@'%' IDENTIFIED BY 'MotDePasseRéplication123!';

-> Pourquoi créer un utilisateur spécial ?
Parce que :
On ne veut pas utiliser root (trop dangereux)
On applique le principe de sécurité :
[VERROUILLE] "le moindre privilège nécessaire"
Cet utilisateur ne sert qu’à :
Lire les binlogs du master pour répliquer les données

-- Donner les droits de réplication
GRANT REPLICATION SLAVE ON *.* TO 'replication_user'@'%';

-- Appliquer
FLUSH PRIVILEGES;
```

**Explication :**

```sql
'replication_user'@'%'
                   ^
                   % = Depuis n'importe quelle IP
                   
Si tu veux restreindre à un réseau spécifique :
MySQL fonctionne comme ça :
Un utilisateur = nom + origine
'replication_user'@'192.168.1.%'  <- Seulement 192.168.1.x
'replication_user'@'192.168.1.101' <- Seulement cette IP
'replication_user'@'localhost' <- Connexion seulement depuis la machine locale

REPLICATION SLAVE
^
Droit spécial pour se connecter en tant que slave
Ce droit permet uniquement :
de lire les binary logs
de s’enregistrer comme esclave
de recevoir les événements de réplication
Il ne peut PAS :
lire les données
modifier les tables
supprimer des bases
faire du SELECT libre
C’est un droit ultra limité et sécurisé.
```

---

**4. Verrouiller les tables et obtenir la position du Master :**

```sql
-- Verrouiller TOUTES les tables en lecture seule
FLUSH TABLES WITH READ LOCK;

C’est une commande critique.
Elle fait 2 choses :
Elle force toutes les tables à écrire leurs données sur disque
Elle bloque toute écriture
-> Pourquoi ?
Parce qu’on veut capturer :
[IMPORTANT] un instant parfaitement figé de la base
Sinon :
Pendant que tu fais le dump
Des données pourraient changer
-> Incohérence entre Master et Slave

-- Voir la position actuelle du binary log
SHOW MASTER STATUS;
```

**Résultat (EXEMPLE - note ces valeurs) :**

```
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 |      154 |              |                  |
+------------------+----------+--------------+------------------+
```

**[ATTENTION] IMPORTANT : Note ces valeurs ! Tu en auras besoin pour configurer les slaves.**

```
File: mysql-bin.000001
Position: 154
```

[LOGIQUE] C’est quoi exactement ça ?
Le master écrit toutes les modifications dans un journal :
mysql-bin.000001
Et chaque événement est numéroté :
Position = 154 signifie :
"Le Slave doit commencer à lire à partir de l’octet 154 dans ce fichier"
-> C’est comme dire :
"Commence à lire le film à la minute 2:34"

---

**5. Faire un dump des données (optionnel mais recommandé) :**

**Ouvre un AUTRE terminal (garde le premier avec le lock actif) :**

```bash
mysqldump -u root -p --all-databases --master-data=2 > /tmp/master_backup.sql
```

**Explication :**
- `--all-databases` = Toutes les bases
- `--master-data=2` = Inclut la position du binary log en commentaire

Ce dump contient :
Les données
Les tables
Les bases
ET une ligne cachée comme :
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=154;
Donc même si tu oublies de noter la position -> elle est dans le fichier.
[IDEE] Très professionnel comme méthode.

[LOGIQUE] Différence entre --master-data=1 et --master-data=2
Valeur	Comportement
--master-data=1	Ajoute une vraie commande SQL active CHANGE MASTER TO dans le dump
--master-data=2	Ajoute la même commande mais en commentaire SQL (--)

Exemple avec =2 :
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=154;
Exemple avec =1 :
CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000001', MASTER_LOG_POS=154;
[ATTENTION] Pourquoi on préfère =2 (commenté) ?
Parce que c’est plus sûr.
Avec =1 :
Si tu importes le dump sur le mauvais serveur
Ou sur un environnement de test
Ou sur un autre master par erreur
-> La réplication peut se configurer automatiquement sans que tu t’en rendes compte
Avec =2 :
[OK] Tu vois la ligne
[OK] Tu copies les valeurs toi-même
[OK] Tu gardes le contrôle
[OK] Zéro surprise
-> C’est la pratique recommandée chez les pros.

---

**6. Déverrouiller les tables :**

**Retourne dans le premier terminal MySQL et déverrouille :**

```sql
UNLOCK TABLES;
EXIT;
```

Maintenant :
Le Master peut recommencer à accepter les écritures
Les utilisateurs peuvent travailler normalement

---

**7. Transférer le dump vers les slaves :**

```bash
# Vers Slave 1
scp /tmp/master_backup.sql user@192.168.1.101:/tmp/

# Vers Slave 2
scp /tmp/master_backup.sql user@192.168.1.102:/tmp/
```

---

### [OUTIL] Étape 1.2 : Configuration du SLAVE

**Sur CHAQUE serveur SLAVE (192.168.1.101, 102, etc.) :**

**1. Installer MariaDB :**

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

---

**2. Configurer le SLAVE :**

```bash
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
```

**Contenu (section `[mysqld]`) :**

```ini
[mysqld]

# ═══════════════════════════════════════════════════════════════
# CONFIGURATION SLAVE (RÉPLICATION)
# ═══════════════════════════════════════════════════════════════

# ID unique (DIFFÉRENT du Master et des autres Slaves !)
server-id = 2  # <- 2 pour Slave 1, 3 pour Slave 2, etc.

# Relay log (journal de relai)
relay-log = /var/log/mysql/relay-bin
relay-log-index = /var/log/mysql/relay-bin.index

# Binary log (si ce slave doit devenir master un jour)
log-bin = /var/log/mysql/mysql-bin.log
binlog_format = MIXED
expire_logs_days = 7

# Lecture seule (empêche les écritures locales)
# [ATTENTION] Le super-user (root) peut quand même écrire
read-only = 1

Pour bloquer même root (Utilisé en production très sérieuse.) :
super-read-only = 1

# Informations de réplication stockées dans une table (recommandé)
# Avant, MySQL stockait :
# IP du master
# mot de passe
# position
# dans un fichier texte
# Maintenant :
# -> dans des tables système -> plus robuste, crash-safe.

master-info-repository = TABLE
relay-log-info-repository = TABLE

# Crash-safe replication
relay-log-recovery = 1
```

**Explication :**

```ini
server-id = 2
# CHAQUE serveur doit avoir un ID UNIQUE
# Master = 1
# Slave 1 = 2
# Slave 2 = 3
# Slave 3 = 4, etc.

relay-log = /var/log/mysql/relay-bin
# Le Slave télécharge les binary logs du Master
# Les stocke localement dans des "relay logs"
# Puis les applique à sa propre base de données

read-only = 1
# Empêche les utilisateurs normaux d'écrire sur le Slave
# Seule la réplication peut écrire
# ATTENTION : root peut toujours écrire !
# Pour bloquer root aussi : super-read-only = 1

master-info-repository = TABLE
relay-log-info-repository = TABLE
# Au lieu de stocker les infos dans des fichiers,
# les stocke dans des tables MySQL
# Plus sûr en cas de crash

relay-log-recovery = 1
# En cas de crash, vérifie et répare les relay logs
# Évite les corruptions
```

---

**3. Redémarrer MySQL :**

```bash
sudo systemctl restart mariadb
```

---

**4. Importer le dump du Master :**

```bash
sudo mysql -u root -p < /tmp/master_backup.sql
```

**Cela va :**
1. Recréer toutes les bases de données
2. Recréer toutes les tables
3. Insérer toutes les données
4. Mettre le Slave au même état que le Master (au moment du dump)

---

**5. Configurer la connexion au Master :**

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

```sql
-- Arrêter la réplication (au cas où)
STOP SLAVE;

-- Configurer la connexion au Master
CHANGE MASTER TO
    MASTER_HOST='192.168.1.100',
    MASTER_USER='replication_user',
    MASTER_PASSWORD='MotDePasseRéplication123!',
    MASTER_LOG_FILE='mysql-bin.000001',  -- [ATTENTION] Valeur notée plus tôt
    MASTER_LOG_POS=154;                   -- [ATTENTION] Valeur notée plus tôt

Cette commande dit au Slave :
[PLUGIN] "Connecte-toi au master ici"
[UTILISATEUR] "Utilise cet utilisateur"
[FICHIER] "Commence à lire le journal à partir de ce point précis"

-- Démarrer la réplication
START SLAVE;

Deux threads démarrent :
IO_THREAD : télécharge les binlogs
SQL_THREAD : exécute les requêtes

-- Vérifier le statut
SHOW SLAVE STATUS\G
```

**Explication :**

```sql
CHANGE MASTER TO
    MASTER_HOST='192.168.1.100',
    # IP du serveur Master
    
    MASTER_USER='replication_user',
    # Utilisateur créé sur le Master
    
    MASTER_PASSWORD='MotDePasseRéplication123!',
    # Mot de passe de cet utilisateur
    
    MASTER_LOG_FILE='mysql-bin.000001',
    # Nom du binary log du Master (noté plus tôt)
    
    MASTER_LOG_POS=154;
    # Position dans le binary log (notée plus tôt)
    # Le Slave commencera à répliquer depuis cette position
```

---

**6. Vérifier la réplication :**

```sql
SHOW SLAVE STATUS\G
```

**Résultat attendu (extrait important) :**

```
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.1.100
                  Master_User: replication_user
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000001
          Read_Master_Log_Pos: 154
               Relay_Log_File: relay-bin.000002
                Relay_Log_Pos: 320
        Relay_Master_Log_File: mysql-bin.000001
             Slave_IO_Running: Yes  <- [OK] IMPORTANT
            Slave_SQL_Running: Yes  <- [OK] IMPORTANT
              Replicate_Do_DB:
          Replicate_Ignore_DB:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 154
              Relay_Log_Space: 528
...
        Seconds_Behind_Master: 0     <- [OK] Délai de réplication
...
```

[LOGIQUE] Architecture mentale finale

[ Client ] -> écrit -> [ MASTER ]
                       v écrit dans binlog
                   mysql-bin.000001
                       v
           [ SLAVE télécharge le binlog ]
                       v
                 relay-bin
                       v
            applique les requêtes localement


**[RECHERCHE] Indicateurs clés :**

|----------------------------|-----------------------|-----------------------------------|
| Indicateur                 | Valeur attendue       | Signification                     |
|----------------------------|-----------------------|-----------------------------------|
| **Slave_IO_Running**       | **Yes**               | [OK] Connexion au Master OK         |
| **Slave_SQL_Running**      | **Yes**               | [OK] Application des changements OK |
| **Seconds_Behind_Master**  | **0 ou petit nombre** | [OK] Pas de retard                  |
| **Last_Error**             | **Vide**              | [OK] Pas d'erreur                   |
|----------------------------|-----------------------|-----------------------------------|

**[X] Si `Slave_IO_Running` ou `Slave_SQL_Running` = No :**

```sql
-- Voir l'erreur
SHOW SLAVE STATUS\G

-- Réinitialiser et recommencer
STOP SLAVE;
RESET SLAVE;
-- Puis refaire CHANGE MASTER TO...
```

---

### [TEST] Étape 1.3 : Tester la réplication

**1. Sur le MASTER, créer une base de test :**

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

```sql
CREATE DATABASE test_replication;
USE test_replication;
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie');

SELECT * FROM users;
EXIT;
```

---

**2. Sur le SLAVE, vérifier que les données sont là :**

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

```sql
USE test_replication;
SELECT * FROM users;
```

**Tu devrais voir :**

```
+----+---------+---------------------+
| id | name    | created_at          |
+----+---------+---------------------+
|  1 | Alice   | 2024-12-17 14:00:00 |
|  2 | Bob     | 2024-12-17 14:00:00 |
|  3 | Charlie | 2024-12-17 14:00:00 |
+----+---------+---------------------+
```

**[BRAVO] LA RÉPLICATION FONCTIONNE ! [BRAVO]**

---

**3. Tester le délai de réplication :**

**Sur le MASTER :**

```sql
INSERT INTO users (name) VALUES ('David');
```

**Immédiatement sur le SLAVE :**

```sql
SELECT * FROM users;
```

**David devrait apparaître en moins d'1 seconde ! [RAPIDE]**

---

### [SYNC] Étape 1.4 : Promotion d'un Slave en Master (Failover)

**Scénario : Le Master tombe en panne. On veut promouvoir Slave 1 en nouveau Master.**

**Sur le SLAVE 1 (192.168.1.101) :**

**1. Arrêter la réplication :**

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

```sql
STOP SLAVE;
RESET SLAVE ALL;
```

---

**2. Désactiver le mode lecture seule :**

```sql
SET GLOBAL read_only = 0;
```

**Ou modifier la config :**

```bash
sudo nano /etc/mysql/mariadb.conf.d/50-server.cnf
```

**Commenter :**

```ini
# read-only = 1
```

**Redémarrer :**

```bash
sudo systemctl restart mariadb
```

---

**3. Créer l'utilisateur de réplication :**

```sql
CREATE USER 'replication_user'@'%' IDENTIFIED BY 'MotDePasseRéplication123!';
GRANT REPLICATION SLAVE ON *.* TO 'replication_user'@'%';
FLUSH PRIVILEGES;
```

---

**4. Sur les AUTRES SLAVES, les pointer vers le nouveau Master :**

**Sur Slave 2 (192.168.1.102) :**

```sql
STOP SLAVE;

CHANGE MASTER TO
    MASTER_HOST='192.168.1.101',  -- <- Nouveau Master
    MASTER_USER='replication_user',
    MASTER_PASSWORD='MotDePasseRéplication123!',
    MASTER_LOG_FILE='mysql-bin.000001',
    MASTER_LOG_POS=154;

START SLAVE;
SHOW SLAVE STATUS\G
```

---

**5. Mettre à jour l'application pour écrire sur le nouveau Master :**

**Dans ton code PHP :**

```php
// Avant
define('DB_MASTER_HOST', '192.168.1.100');  // Ancien Master

// Après
define('DB_MASTER_HOST', '192.168.1.101');  // Nouveau Master
```

---

**[BRAVO] Le Slave 1 est maintenant le nouveau Master ! [BRAVO]**

---

## [MELANGE] PARTIE 2 : LOAD BALANCING ET SCALABILITÉ HORIZONTALE

### [OBJECTIF] Objectif

**Problème :**
- Un seul serveur Web = Limite de capacité
- Si le serveur crash = Site down
- Maintenance = Downtime

**Solution : Load Balancing**

```
┌────────────────────────────────────────────────────────┐
│  LOAD BALANCING - RÉPARTITION DE CHARGE                │
└────────────────────────────────────────────────────────┘

    [UTILISATEURS] 1000 utilisateurs simultanés
       v
    [SCALES]  LOAD BALANCER (HAProxy / Nginx)
       v
    ┌──┴──────────┬──────────────┬──────────────┐
    v             v              v              v
[PACKAGE] Web 1      [PACKAGE] Web 2      [PACKAGE] Web 3      [PACKAGE] Web 4
   250 users     250 users     250 users     250 users
   Apache+PHP    Apache+PHP    Apache+PHP    Apache+PHP

AVANTAGES :

[OK] Haute disponibilité (si Web 1 crash, les autres continuent)
[OK] Scalabilité (ajouter des serveurs = plus de capacité)
[OK] Maintenance sans downtime (arrêter Web 1, les autres continuent)
[OK] Répartition géographique possible

ALGORITHMES DE RÉPARTITION :

1. Round Robin : Tour à tour (1->2->3->4->1->2...)
2. Least Connections : Vers le moins chargé
3. IP Hash : Même IP -> Même serveur (sessions)
4. Weighted : Poids différents (serveur puissant = plus de charge)
```

---

### [OUTIL] Étape 2.1 : Installation de HAProxy

**HAProxy** = Load Balancer open-source très performant

**Sur un serveur dédié (ou VM) qui sera le Load Balancer :**

```bash
sudo apt update
sudo apt install haproxy -y
```

**Vérifier :**

```bash
haproxy -v
```

**Résultat :**

```
HAProxy version 2.8.5-1ubuntu3 2024/04/01
```

---

### [CONFIG] Étape 2.2 : Configuration de HAProxy

**Sauvegarder la config par défaut :**

```bash
sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.original
```

---

**Éditer la configuration :**

```bash
sudo nano /etc/haproxy/haproxy.cfg
```

**Contenu :**

```
# ═══════════════════════════════════════════════════════════════
# CONFIGURATION HAPROXY - LOAD BALANCER LAMP
# ═══════════════════════════════════════════════════════════════

global
    # Logs
    log /dev/log local0
    log /dev/log local1 notice
    
    # Limites de connexions
    maxconn 4096
    
    # Utilisateur
    user haproxy
    group haproxy
    
    # Mode daemon
    daemon
    
    # Fichiers PID et sockets
    pidfile /var/run/haproxy.pid
    
    # Statistiques
    stats socket /run/haproxy/admin.sock mode 660 level admin
    stats timeout 30s

defaults
    # Mode (http ou tcp)
    mode http
    
    # Logs
    log global
    option httplog
    option dontlognull
    
    # Timeouts
    timeout connect 5000ms
    timeout client  50000ms
    timeout server  50000ms
    
    # Gestion des erreurs
    errorfile 400 /etc/haproxy/errors/400.http
    errorfile 403 /etc/haproxy/errors/403.http
    errorfile 408 /etc/haproxy/errors/408.http
    errorfile 500 /etc/haproxy/errors/500.http
    errorfile 502 /etc/haproxy/errors/502.http
    errorfile 503 /etc/haproxy/errors/503.http
    errorfile 504 /etc/haproxy/errors/504.http

# ───────────────────────────────────────────────────────────────
# FRONTEND - POINT D'ENTRÉE
# ───────────────────────────────────────────────────────────────

frontend web_frontend
    # Écouter sur le port 80 (HTTP)
    bind *:80
    
    # Mode HTTP
    mode http
    
    # Headers de sécurité
    http-response set-header X-Frame-Options SAMEORIGIN
    http-response set-header X-Content-Type-Options nosniff
    http-response set-header X-XSS-Protection "1; mode=block"
    
    # Compression
    compression algo gzip
    compression type text/html text/plain text/css text/javascript application/javascript
    
    # ACL (Access Control List) - Routage basé sur le domaine
    acl is_wordpress hdr(host) -i wordpress.local
    acl is_contacts hdr(host) -i contacts.local
    
    # Routage vers les backends
    use_backend wordpress_backend if is_wordpress
    use_backend contacts_backend if is_contacts
    
    # Backend par défaut
    default_backend web_backend

# ───────────────────────────────────────────────────────────────
# BACKEND - SERVEURS WEB (PAR DÉFAUT)
# ───────────────────────────────────────────────────────────────

backend web_backend
    # Algorithme de répartition
    balance roundrobin
    
    # Health checks (vérification de santé)
    option httpchk GET /
    
    # Sticky sessions (même IP -> même serveur)
    cookie SERVERID insert indirect nocache
    
    # Liste des serveurs
    server web1 192.168.1.110:80 check cookie web1
    server web2 192.168.1.111:80 check cookie web2
    server web3 192.168.1.112:80 check cookie web3
    
    # Si un serveur tombe, le retirer automatiquement
    # Réessayer toutes les 2 secondes
    default-server inter 2s fall 3 rise 2

# ───────────────────────────────────────────────────────────────
# BACKEND - WORDPRESS
# ───────────────────────────────────────────────────────────────

backend wordpress_backend
    balance roundrobin
    option httpchk GET /wp-admin/install.php
    cookie SERVERID insert indirect nocache
    
    server wp1 192.168.1.120:80 check cookie wp1
    server wp2 192.168.1.121:80 check cookie wp2

# ───────────────────────────────────────────────────────────────
# BACKEND - CONTACTS
# ───────────────────────────────────────────────────────────────

backend contacts_backend
    balance roundrobin
    option httpchk GET /index.php
    cookie SERVERID insert indirect nocache
    
    server contacts1 192.168.1.130:80 check cookie contacts1
    server contacts2 192.168.1.131:80 check cookie contacts2

# ───────────────────────────────────────────────────────────────
# STATISTIQUES - INTERFACE WEB
# ───────────────────────────────────────────────────────────────

listen stats
    bind *:8080
    mode http
    stats enable
    stats uri /stats
    stats realm HAProxy\ Statistics
    stats auth admin:MotDePasseStats123!  # [ATTENTION] CHANGE ICI
    stats refresh 30s
    stats show-node
    stats show-legends
    stats show-desc HAProxy Load Balancer
```

**Explication détaillée :**

```
frontend web_frontend
    bind *:80
    # Écoute sur TOUTES les interfaces (*) sur le port 80
    # C'est le point d'entrée unique

acl is_wordpress hdr(host) -i wordpress.local
# ACL = Access Control List (règle de routage)
# hdr(host) = Header "Host" de la requête HTTP
# -i = Case insensitive (majuscules/minuscules)
# Si Host: wordpress.local -> ACL is_wordpress = true

use_backend wordpress_backend if is_wordpress
# Si l'ACL is_wordpress est vraie,
# router vers le backend wordpress_backend

backend web_backend
    balance roundrobin
    # Algorithme Round Robin (tour à tour)
    # Requête 1 -> web1
    # Requête 2 -> web2
    # Requête 3 -> web3
    # Requête 4 -> web1 (recommence)

option httpchk GET /
# Health check : HAProxy fait un GET / toutes les X secondes
# Si le serveur répond 200 OK -> En ligne
# Si timeout ou erreur -> Hors ligne

cookie SERVERID insert indirect nocache
# Insère un cookie pour sticky sessions
# Même utilisateur -> Même serveur
# Important pour les sessions PHP !

server web1 192.168.1.110:80 check cookie web1
# Déclare un serveur backend
# Nom : web1
# IP : 192.168.1.110
# Port : 80
# check : Activer health checks
# cookie web1 : Valeur du cookie SERVERID

default-server inter 2s fall 3 rise 2
# inter 2s : Health check toutes les 2 secondes
# fall 3 : Marquer comme down après 3 échecs
# rise 2 : Marquer comme up après 2 succès
```

---

**Tester la configuration :**

```bash
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
```

**Résultat attendu :**

```
Configuration file is valid
```

---

**Démarrer HAProxy :**

```bash
sudo systemctl restart haproxy
sudo systemctl enable haproxy
sudo systemctl status haproxy
```

---

### [GRAPHIQUE] Étape 2.3 : Interface de statistiques

**Ouvre ton navigateur :**

```
http://192.168.1.X:8080/stats
```

**Identifiants :**
- User : `admin`
- Password : `MotDePasseStats123!`

**Tu verras un dashboard avec :**
- État de chaque serveur (UP/DOWN)
- Nombre de sessions
- Taux de requêtes
- Health checks
- Etc.

---

### [TEST] Étape 2.4 : Tester le Load Balancing

**1. Sur chaque serveur web, créer une page de test :**

**Web 1 (192.168.1.110) :**

```bash
echo "Serveur Web 1" | sudo tee /var/www/html/index.html
```

**Web 2 (192.168.1.111) :**

```bash
echo "Serveur Web 2" | sudo tee /var/www/html/index.html
```

**Web 3 (192.168.1.112) :**

```bash
echo "Serveur Web 3" | sudo tee /var/www/html/index.html
```

---

**2. Tester depuis ton PC :**

```bash
curl http://192.168.1.X  # IP du Load Balancer
```

**Répète la commande plusieurs fois. Tu devrais voir :**

```
Serveur Web 1
Serveur Web 2
Serveur Web 3
Serveur Web 1
Serveur Web 2
...
```

**[SCALES] La charge est répartie ! [SCALES]**

---

**3. Simuler une panne :**

**Arrête Apache sur Web 1 :**

```bash
# Sur Web 1
sudo systemctl stop apache2
```

**Refais les requêtes :**

```bash
curl http://192.168.1.X
```

**Résultat :**

```
Serveur Web 2
Serveur Web 3
Serveur Web 2
Serveur Web 3
...
```

**Web 1 n'apparaît plus ! HAProxy l'a automatiquement retiré ! [OBJECTIF]**

**Regarde les stats : Web 1 sera marqué comme DOWN.**

---

**Redémarre Web 1 :**

```bash
# Sur Web 1
sudo systemctl start apache2
```

**Après 4 secondes (2 health checks x 2), Web 1 revient dans le pool ! [OK]**

---

## [ARCHIVE] PARTIE 3 : SESSIONS PARTAGÉES (Redis)

### [ALERTE] Le problème des sessions

**Avec le Load Balancing, problème de sessions :**

```
Scénario :

1. User se connecte -> Requête va sur Web 1
   -> Session créée sur Web 1 (fichier local)

2. User rafraîchit -> Requête va sur Web 2 (round robin)
   -> Session introuvable sur Web 2
   -> User déconnecté !

[X] L'utilisateur est déconnecté à chaque changement de serveur !
```

**Solutions :**

**Option A : Sticky Sessions (HAProxy)**
- Même IP -> Même serveur (via cookie)
- [OK] Simple
- [X] Pas de vraie redondance (si serveur crash, session perdue)

**Option B : Sessions partagées (Redis/Memcached)**
- Sessions stockées dans une base centralisée
- [OK] Vraie redondance
- [OK] N'importe quel serveur peut lire la session
- [OK] Si un serveur crash, sessions préservées

**On va utiliser Redis ! [RAPIDE]**

---

### [PACKAGE] Étape 3.1 : Installation de Redis

**Sur un serveur dédié (ou sur le Load Balancer) :**

```bash
sudo apt update
sudo apt install redis-server -y
```

---

**Configurer Redis :**

```bash
sudo nano /etc/redis/redis.conf
```

**Cherche et modifie :**

```
# Écouter sur toutes les interfaces (au lieu de 127.0.0.1)
bind 0.0.0.0

# Désactiver le mode protégé (ou configurer un mot de passe)
protected-mode no

# OU mieux, configurer un mot de passe :
requirepass MotDePasseRedis123!
```

**Redémarrer Redis :**

```bash
sudo systemctl restart redis-server
sudo systemctl enable redis-server
```

---

**Tester Redis :**

```bash
redis-cli
```

**Si tu as configuré un mot de passe :**

```bash
redis-cli
AUTH MotDePasseRedis123!
PING
```

**Résultat :**

```
PONG
```

**[OK] Redis fonctionne !**

```bash
EXIT
```

---

### [POSTGRES] Étape 3.2 : Configurer PHP pour utiliser Redis

**Sur CHAQUE serveur web, installer l'extension PHP Redis :**

```bash
sudo apt install php-redis -y
sudo systemctl restart apache2
```

---

**Configurer PHP pour stocker les sessions dans Redis :**

```bash
sudo nano /etc/php/8.3/apache2/php.ini
```

**Cherche et modifie :**

```ini
session.save_handler = redis
session.save_path = "tcp://192.168.1.X:6379?auth=MotDePasseRedis123!"
```

**Explication :**

```ini
session.save_handler = redis
# Au lieu de "files" (fichiers locaux)

session.save_path = "tcp://192.168.1.X:6379?auth=MotDePasseRedis123!"
# tcp://IP_REDIS:PORT
# ?auth=MOT_DE_PASSE (si configuré)
```

**Redémarrer Apache :**

```bash
sudo systemctl restart apache2
```

---

### [TEST] Étape 3.3 : Tester les sessions partagées

**Créer un fichier de test sur TOUS les serveurs web :**

```bash
sudo nano /var/www/html/session_test.php
```

```php
<?php
session_start();

// Incrémenter un compteur à chaque visite
if (!isset($_SESSION['counter'])) {
    $_SESSION['counter'] = 1;
} else {
    $_SESSION['counter']++;
}

// Afficher les infos
echo "<h1>Test de session partagée (Redis)</h1>";
echo "<p><strong>Compteur de visites :</strong> " . $_SESSION['counter'] . "</p>";
echo "<p><strong>Session ID :</strong> " . session_id() . "</p>";
echo "<p><strong>Serveur :</strong> " . gethostname() . "</p>";
echo "<p><a href=''>Rafraîchir</a></p>";
?>
```

---

**Ouvre dans le navigateur (via le Load Balancer) :**

```
http://192.168.1.X/session_test.php
```

**Rafraîchis plusieurs fois.**

**Tu devrais voir :**
- Le compteur augmente (session maintenue)
- Le serveur change (Load Balancing fonctionne)
- Mais la session persiste ! [OK]

**Sans Redis, le compteur recommencerait à 1 à chaque changement de serveur.**

---

### [RECHERCHE] Voir les sessions dans Redis

**Sur le serveur Redis :**

```bash
redis-cli
AUTH MotDePasseRedis123!

# Lister toutes les clés (sessions)
KEYS *

# Voir le contenu d'une session
GET "PHPREDIS_SESSION:abcd1234..."

# Compter le nombre de sessions actives
DBSIZE
```

---

## [SAUVEGARDE] PARTIE 4 : BASE DE DONNÉES - STRATÉGIES AVANCÉES

### [OBJECTIF] Stratégie 1 : Read/Write Splitting

**Séparer les lectures et écritures :**

```php
<?php
// ═══════════════════════════════════════════════════════════════
// CONNEXIONS MULTIPLES - READ/WRITE SPLITTING
// ═══════════════════════════════════════════════════════════════

class Database {
    private $masterConnection = null;
    private $slaveConnection = null;
    
    // Configuration
    private $masterHost = '192.168.1.100';  // Master (Read+Write)
    private $slaveHost = '192.168.1.101';   // Slave (Read Only)
    private $username = 'app_user';
    private $password = 'MotDePasse123!';
    private $database = 'myapp_db';
    
    /**
     * Connexion au MASTER (pour écritures)
     */
    public function getMasterConnection() {
        if ($this->masterConnection === null) {
            try {
                $dsn = "mysql:host={$this->masterHost};dbname={$this->database};charset=utf8mb4";
                $this->masterConnection = new PDO($dsn, $this->username, $this->password);
                $this->masterConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            } catch (PDOException $e) {
                // En cas d'erreur Master, logger et lancer exception
                error_log("Master DB connection failed: " . $e->getMessage());
                throw new Exception("Database connection error");
            }
        }
        return $this->masterConnection;
    }
    
    /**
     * Connexion au SLAVE (pour lectures)
     */
    public function getSlaveConnection() {
        if ($this->slaveConnection === null) {
            try {
                $dsn = "mysql:host={$this->slaveHost};dbname={$this->database};charset=utf8mb4";
                $this->slaveConnection = new PDO($dsn, $this->username, $this->password);
                $this->slaveConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            } catch (PDOException $e) {
                // Si Slave inaccessible, fallback sur Master
                error_log("Slave DB connection failed, falling back to Master: " . $e->getMessage());
                return $this->getMasterConnection();
            }
        }
        return $this->slaveConnection;
    }
    
    /**
     * Exécuter une requête de LECTURE
     */
    public function query($sql, $params = []) {
        $db = $this->getSlaveConnection();  // <- Lecture sur Slave
        $stmt = $db->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }
    
    /**
     * Exécuter une requête d'ÉCRITURE
     */
    public function execute($sql, $params = []) {
        $db = $this->getMasterConnection();  // <- Écriture sur Master
        $stmt = $db->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }
}

// ═══════════════════════════════════════════════════════════════
// UTILISATION
// ═══════════════════════════════════════════════════════════════

$db = new Database();

// Lectures (sur Slave)
$contacts = $db->query("SELECT * FROM contacts")->fetchAll();

// Écritures (sur Master)
$db->execute("INSERT INTO contacts (nom, prenom, email) VALUES (?, ?, ?)", 
             ['Diop', 'Amadou', 'amadou@example.com']);

$db->execute("UPDATE contacts SET telephone = ? WHERE id = ?", 
             ['+221 77 123 45 67', 5]);
```

---

### [OBJECTIF] Stratégie 2 : Connection Pooling

**Éviter de créer/fermer des connexions à chaque requête :**

```php
<?php
// ═══════════════════════════════════════════════════════════════
// CONNECTION POOL - RÉUTILISATION DES CONNEXIONS
// ═══════════════════════════════════════════════════════════════

class DatabasePool {
    private static $instance = null;
    private $connections = [];
    private $maxConnections = 10;
    
    // Singleton
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    public function getConnection($host, $db, $user, $pass) {
        $key = md5($host . $db . $user);
        
        // Si connexion existe déjà, la réutiliser
        if (isset($this->connections[$key])) {
            return $this->connections[$key];
        }
        
        // Sinon, créer une nouvelle connexion
        if (count($this->connections) >= $this->maxConnections) {
            throw new Exception("Connection pool limit reached");
        }
        
        $dsn = "mysql:host=$host;dbname=$db;charset=utf8mb4";
        $pdo = new PDO($dsn, $user, $pass, [
            PDO::ATTR_PERSISTENT => true,  // <- Connexion persistante
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]);
        
        $this->connections[$key] = $pdo;
        return $pdo;
    }
}
```

---

### [OBJECTIF] Stratégie 3 : Failover automatique

**Détecter si le Master est down et basculer :**

```php
<?php
// ═══════════════════════════════════════════════════════════════
// AUTO-FAILOVER - BASCULEMENT AUTOMATIQUE
// ═══════════════════════════════════════════════════════════════

class ResilientDatabase {
    private $hosts = [
        'master' => '192.168.1.100',
        'slave1' => '192.168.1.101',
        'slave2' => '192.168.1.102',
    ];
    
    private $currentMaster = null;
    private $connection = null;
    
    public function __construct() {
        $this->currentMaster = $this->hosts['master'];
    }
    
    /**
     * Tenter de se connecter avec failover
     */
    public function getConnection() {
        // Essayer le Master actuel
        try {
            return $this->connect($this->currentMaster);
        } catch (Exception $e) {
            error_log("Master {$this->currentMaster} is down: " . $e->getMessage());
            
            // Essayer les autres serveurs
            foreach ($this->hosts as $name => $host) {
                if ($host === $this->currentMaster) continue;
                
                try {
                    error_log("Trying failover to $name ($host)");
                    $this->currentMaster = $host;  // Promouvoir ce serveur
                    return $this->connect($host);
                } catch (Exception $e) {
                    error_log("$name ($host) is also down");
                    continue;
                }
            }
            
            // Aucun serveur disponible
            throw new Exception("All database servers are down");
        }
    }
    
    /**
     * Se connecter à un host spécifique
     */
    private function connect($host) {
        $dsn = "mysql:host=$host;dbname=myapp_db;charset=utf8mb4";
        $pdo = new PDO($dsn, 'app_user', 'MotDePasse123!', [
            PDO::ATTR_TIMEOUT => 2,  // Timeout de 2 secondes
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
        ]);
        return $pdo;
    }
}
```

---

## [SYNC] PARTIE 5 : ARCHITECTURE COMPLÈTE HAUTE DISPONIBILITÉ

### [CONSTRUCTION] Architecture finale

```
┌─────────────────────────────────────────────────────────────────┐
│  ARCHITECTURE LAMP HAUTE DISPONIBILITÉ COMPLÈTE                 │
└─────────────────────────────────────────────────────────────────┘

                        [WEB] INTERNET
                            v
                    ┌───────────────┐
                    │   DNS / CDN   │  <- CloudFlare, AWS Route 53
                    └───────┬───────┘
                            v
                ┌───────────────────────┐
                │  Firewall / DDoS      │  <- Protection
                └───────────┬───────────┘
                            v
            ┌───────────────────────────────┐
            │  LOAD BALANCER (HAProxy x2)   │  <- Redondance
            │  IP Virtuelle (Keepalived)    │
            └───────────┬───────────────────┘
                        v
        ┌───────────────┼───────────────┐
        v               v               v
    [PACKAGE] Web 1        [PACKAGE] Web 2        [PACKAGE] Web N
    Apache+PHP      Apache+PHP      Apache+PHP
    Sessions->Redis  Sessions->Redis  Sessions->Redis
        v               v               v
        └───────────────┴───────────────┘
                        v
                ┌───────────────┐
                │  Redis Cache  │  <- Sessions partagées
                │  (Clustered)  │     + Cache applicatif
                └───────┬───────┘
                        v
            ┌───────────────────────┐
            │  DB Load Balancer     │  <- ProxySQL / MaxScale
            └───────────┬───────────┘
                        v
            ┌───────────┴───────────┐
            v                       v
        [ARCHIVE] Master DB          [ARCHIVE] Slave DB 1 (Read)
        (Write + Read)            v
            v                  [ARCHIVE] Slave DB 2 (Read)
            v                      v
        Replication ──────────-> Replication
            
[GRAPHIQUE] MONITORING :
   - Prometheus + Grafana (métriques)
   - ELK Stack (logs centralisés)
   - Uptime monitoring
   - Alerting (email, SMS)

[SAUVEGARDE] BACKUP :
   - Snapshot automatique toutes les heures
   - Backup complet journalier
   - Réplication géographique
   - Rétention 30 jours

[SECURISE] SÉCURITÉ :
   - WAF (ModSecurity)
   - Fail2Ban
   - SSL/TLS (Let's Encrypt)
   - Firewall (iptables / UFW)
   - VPN pour admin
```

---

## [GRAPHIQUE] RÉSUMÉ ET CHECKLIST

### [OK] Checklist Haute Disponibilité

**Base de données :**
- [ ] Réplication Master-Slave configurée
- [ ] Au moins 2 Slaves (redondance)
- [ ] Monitoring de la réplication (Seconds_Behind_Master)
- [ ] Procédure de failover documentée et testée
- [ ] Backups automatiques sur les Slaves
- [ ] Read/Write splitting dans l'application

**Serveurs Web :**
- [ ] Au moins 2 serveurs Web identiques
- [ ] Load Balancer configuré (HAProxy/Nginx)
- [ ] Health checks activés
- [ ] Sessions partagées (Redis/Memcached)
- [ ] Code déployé de façon identique sur tous les serveurs
- [ ] Déploiement automatisé (Ansible/Scripts)

**Cache et Performance :**
- [ ] Redis/Memcached pour sessions
- [ ] Redis pour cache applicatif (optionnel)
- [ ] OPcache PHP activé
- [ ] Compression gzip/brotli activée
- [ ] CDN configuré (CloudFlare, AWS CloudFront)

**Monitoring :**
- [ ] Logs centralisés (ELK, Graylog, Loki)
- [ ] Métriques système (CPU, RAM, Disk, Network)
- [ ] Métriques applicatives (temps de réponse, erreurs)
- [ ] Alertes configurées (email, SMS, Slack)
- [ ] Dashboards (Grafana, Kibana)

**Sécurité :**
- [ ] Pare-feu configuré sur tous les serveurs
- [ ] SSL/TLS avec certificats valides
- [ ] Accès SSH par clés uniquement
- [ ] Fail2Ban actif
- [ ] Séparation réseau (DMZ, private network)
- [ ] Mots de passe forts partout
- [ ] Principe du moindre privilège

**Backup et Disaster Recovery :**
- [ ] Backups automatiques quotidiens
- [ ] Backups testés régulièrement (DR drills)
- [ ] Backups géographiquement distribués
- [ ] RPO < 1 heure (Recovery Point Objective)
- [ ] RTO < 4 heures (Recovery Time Objective)
- [ ] Documentation de restauration

---

## [OBJECTIF] MÉTRIQUES DE DISPONIBILITÉ

**Calcul de la disponibilité :**

```
Disponibilité (%) = (Temps total - Temps d'arrêt) / Temps total × 100

Exemples :

99.9% (Three Nines)
-> Downtime acceptable : 8.76 heures/an
-> Bien pour la plupart des applications

99.99% (Four Nines)
-> Downtime acceptable : 52 minutes/an
-> Applications critiques

99.999% (Five Nines)
-> Downtime acceptable : 5.26 minutes/an
-> Systèmes bancaires, télécom
```

**Avec notre architecture :**

```
Composant unique : 99% disponible
-> 1% de downtime = 3.65 jours/an

Avec 2 composants en redondance :
-> 1 - (0.01 × 0.01) = 0.9999
-> 99.99% disponible
-> 52 minutes/an de downtime

Avec 3 composants :
-> 99.9999% disponible
-> 31 secondes/an de downtime
```

---

## [RAPIDE] PROCHAINES ÉTAPES

**Pour aller encore plus loin :**

1. **Containerisation (Docker/Kubernetes)**
   - Déploiement reproductible
   - Orchestration automatique
   - Scaling automatique (HPA)

2. **Infrastructure as Code**
   - Terraform pour provisionner
   - Ansible pour configurer
   - GitOps pour déployer

3. **Observabilité avancée**
   - Traces distribuées (Jaeger, Zipkin)
   - Métriques détaillées (Prometheus)
   - Logs structurés (ELK, Loki)

4. **CI/CD**
   - Intégration continue (GitHub Actions, GitLab CI)
   - Tests automatisés
   - Déploiement progressif (Canary, Blue/Green)

5. **Cloud Native**
   - AWS (RDS Multi-AZ, ELB, Auto Scaling)
   - GCP (Cloud SQL, Cloud Load Balancing)
   - Azure (Database for MySQL, Load Balancer)

---

## [COURS] CONCLUSION

**Tu as maintenant les connaissances pour créer une architecture LAMP :**

[OK] **Hautement disponible** (pas de SPOF)
[OK] **Scalable horizontalement** (ajouter des serveurs)
[OK] **Résiliente** (tolère les pannes)
[OK] **Performante** (cache, load balancing)
[OK] **Sécurisée** (bonnes pratiques)
[OK] **Observable** (logs, métriques)

**La haute disponibilité n'est pas un produit, c'est un processus continu d'amélioration ! [RAPIDE]**

**Bon courage dans la construction de tes infrastructures ! [FORCE]**