# Déployer un site Web dynamique connecté à Azure MySQL
> **Guide pas à pas** — VM Ubuntu + Nginx/Apache + PHP + Azure Database for MySQL Flexible Server
>
> Ce guide documente chaque étape du déploiement, de la création des ressources Azure jusqu'à l'interface finale du site.
---
## Table des matières
1. [Créer la base de données Azure MySQL](#1--créer-la-base-de-données-azure-mysql)
2. [Créer la machine virtuelle (VM)](#2--créer-la-machine-virtuelle-vm)
3. [Résoudre le problème de connexion SSH](#3--résoudre-le-problème-de-connexion-ssh)
4. [Installer et configurer le serveur web](#4--installer-et-configurer-le-serveur-web)
5. [Configurer le réseau (NSG / pare-feu)](#5--configurer-le-réseau-nsg--pare-feu)
6. [Déployer l'application PHP](#6--déployer-lapplication-php)
7. [Configurer la base de données](#7--configurer-la-base-de-données)
8. [Construire le dashboard (interface finale)](#8--construire-le-dashboard-interface-finale)
---
## 1 — Créer la base de données Azure MySQL
### 1.1 — Accéder au portail Azure et rechercher le service
Dans le portail Azure, recherchez **"Azure Database for MySQL"** puis cliquez sur **Créer**.

> Ici, on accède au service Azure Database for MySQL depuis la barre de recherche du portail Azure.

> On choisit le mode **Serveur flexible**, qui est la version recommandée par Azure (plus économique et plus performante).
### 1.2 — Configurer le serveur MySQL

> Remplissez les champs principaux : nom du serveur (ex: `mysqlwebapp123`), la région, et les identifiants admin (utilisateur + mot de passe).
#### Création avancée

> Dans les options avancées, vous pouvez ajuster le tier de calcul, le stockage et la version MySQL. Pour un projet de test, le tier **Burstable (B1ms)** est suffisant.

> Sélectionnez la taille de calcul et le stockage adaptés à votre budget. Le mode Burstable est le plus économique.
### 1.3 — Configuration réseau

> Configuration de l'accès réseau au serveur de base de données.

> On choisit l'accès public pour pouvoir se connecter depuis notre VM.

> Les règles de pare-feu définissent quelles adresses IP peuvent accéder au serveur MySQL.

> **Important :** On clique sur **"Créer un serveur sans règle de pare-feu"** car la VM n'est pas encore créée. On ajoutera la règle plus tard avec l'IP publique de la VM.
### 1.4 — Validation et création

> Vérifiez le récapitulatif de toutes les options avant de cliquer sur **Créer**.

> Le déploiement prend environ 2-5 minutes. Une fois terminé, le serveur est prêt.
---
## 2 — Créer la machine virtuelle (VM)
### 2.1 — Configuration de base

> On crée une VM Ubuntu Server. Choisissez la même région que votre serveur MySQL pour minimiser la latence.

> Sélectionnez **Ubuntu Server 22.04 LTS** comme image. Pour la taille, une **B1s** ou **B2s** suffit largement pour un site web léger.
### 2.2 — Configuration du disque

> **Astuce économique :** Changez le disque de **Premium SSD** à **Standard SSD** pour réduire les coûts. La différence de performance est négligeable pour notre usage.
### 2.3 — Configuration réseau

> Azure crée automatiquement un réseau virtuel, un sous-réseau et une IP publique. Laissez les valeurs par défaut.

> Autorisez le **port SSH (22)** dans les règles de sécurité. On ajoutera le port HTTP (80) après.

> Options de monitoring et de gestion. Les valeurs par défaut conviennent pour un projet de test.
### 2.4 — Authentification SSH

> Choisissez l'authentification par **clé SSH** (plus sécurisé qu'un mot de passe). Azure peut générer la paire de clés pour vous.

> **Téléchargez et conservez la clé privée** (fichier `.pem`). Vous en aurez besoin pour chaque connexion SSH. Ne la partagez jamais.
### 2.5 — Validation et création

> Vérifiez le récapitulatif puis cliquez sur **Créer**. Le déploiement prend environ 1-2 minutes.
---
## 3 — Résoudre le problème de connexion SSH
### 3.1 — Le problème
Après la création de la VM, la connexion SSH peut échouer avec un timeout.
> ❌ **Rien n'atteint la VM depuis Internet**
> (`TcpTestSucceeded = False` + Ping timeout)
>
> Ce n'est PAS un problème de clé SSH, ni de Linux, ni du port 22.
> 👉 **C'est le routage public Azure qui est cassé** — symptôme classique d'une IP publique mal attachée (fréquent après la création d'Azure Bastion).
### 3.2 — La solution : recréer l'IP publique
Dissociez l'IP publique actuelle de la carte réseau de la VM, puis créez-en une nouvelle.

> Dans les paramètres de la carte réseau (NIC), on dissocie l'ancienne IP publique et on en attache une nouvelle.
### 3.3 — Connexion réussie

> La connexion SSH fonctionne maintenant avec la nouvelle IP publique.

> On est connecté à la VM via SSH. On peut maintenant installer le serveur web.
---
## 4 — Installer et configurer le serveur web
### 4.1 — Mettre Ubuntu à jour
Dans le terminal SSH :
```bash
sudo apt update && sudo apt upgrade -y
```
### 4.2 — Installer Nginx
```bash
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx
```
### 4.3 — Tester l'accès web
Ouvrez dans votre navigateur : `http://VOTRE_IP_PUBLIQUE`
Vous devriez voir la page **"Welcome to nginx"**.

> ✅ Si vous voyez cette page, le serveur web est opérationnel.
### 4.4 — Si la page ne s'affiche pas

> ❌ Si la page ne s'affiche pas, c'est un problème de règles réseau (NSG). Le port 80 n'est pas ouvert.
---
## 5 — Configurer le réseau (NSG / pare-feu)
### 5.1 — Ouvrir le port HTTP (80) dans le NSG

> Allez dans les paramètres réseau de la VM pour modifier les règles du NSG (Network Security Group).

> Après avoir changé d'IP publique, il faut aussi **mettre à jour les règles NSG** pour autoriser le trafic HTTP sur le port 80.
### 5.2 — Ajouter la règle de pare-feu pour MySQL

> Créez une nouvelle règle entrante autorisant le **port 80 (HTTP)** depuis n'importe quelle source.
### 5.3 — Mettre à jour le pare-feu de la base de données

> N'oubliez pas d'ajouter l'**IP publique de la VM** dans les règles de pare-feu du serveur Azure MySQL. Sans ça, PHP ne pourra pas se connecter à la base.
### 5.4 — Vérification — Nginx accessible

> ✅ Après la configuration correcte du NSG, la page Nginx s'affiche dans le navigateur.
---
## 6 — Déployer l'application PHP
### 6.1 — Préparer le dossier du site
```bash
cd /var/www/html
sudo mkdir abder-blog
sudo chown -R $USER:$USER abder-blog
cd abder-blog
```
### 6.2 — Créer le fichier de configuration de la base de données
```bash
nano config.php
```
Collez le contenu suivant (adaptez avec vos identifiants) :
```php
<?php
$host = "mysqlwebapp123.mysql.database.azure.com";
$db = "abder_blog";
$user = "abderapp";
$pass = "VotreMotDePasse";
$ssl_ca = "/etc/mysql/certs/DigiCertGlobalRootCA.crt.pem";
try {
$pdo = new PDO(
"mysql:host=$host;dbname=$db;charset=utf8mb4",
$user,
$pass,
[
PDO::MYSQL_ATTR_SSL_CA => $ssl_ca,
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]
);
} catch (PDOException $e) {
die("Erreur DB: " . $e->getMessage());
}
```
> 🔒 **Sécurité** : ne publiez jamais ce fichier sur un dépôt public. Il contient vos identifiants.
### 6.3 — Créer la page d'accueil (test de connexion)
```bash
nano index.php
```
```php
<?php
require "config.php";
$stmt = $pdo->query("SELECT COUNT(*) FROM posts");
$posts = $stmt->fetchColumn();
?>
<!DOCTYPE html>
<html>
<head><title>Abder Blog</title></head>
<body>
<h1>Bienvenue sur Abder Blog 🚀</h1>
<p>Connexion MySQL : OK</p>
<p>Articles dans la base : <?= $posts ?></p>
</body>
</html>
```
### 6.4 — Test dans le navigateur
Ouvrez : `http://VOTRE_IP_PUBLIQUE/abder-blog/`

> ✅ La page affiche le nombre d'articles dans la base. La connexion entre PHP et Azure MySQL fonctionne.
---
## 7 — Configurer la base de données
### 7.1 — Vérifications dans le portail Azure

> On peut vérifier l'état de notre serveur MySQL depuis le portail Azure.

> Le dashboard du serveur montre les métriques de connexion, le statut et les paramètres.

> Vérifiez que l'IP de votre VM est bien autorisée dans les règles de pare-feu du serveur.

> Les paramètres de configuration du serveur (version MySQL, SSL, etc.).

> Confirmation que la base de données est bien accessible et fonctionnelle.
### 7.2 — Se connecter à MySQL depuis la VM
```bash
mysql -h mysqlwebapp123.mysql.database.azure.com -u abderapp -p abder_blog
```
### 7.3 — Créer la table `messages` et insérer des données
```sql
CREATE TABLE messages (
id INT PRIMARY KEY AUTO_INCREMENT,
contenu VARCHAR(255),
date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO messages (contenu) VALUES
('Message 1'),
('Message 2'),
('Message 3'),
('Message 4'),
('Message 5');
SELECT * FROM messages;
```
Puis quittez : `exit;`
### 7.4 — Vérifier les données sur le site

> ✅ Les messages insérés en base apparaissent maintenant sur le site web.

> L'interface commence à prendre forme avec les données dynamiques provenant d'Azure MySQL.

> Vue de l'interface du site affichant les messages. On peut voir le CRUD basique fonctionner.
### 7.5 — Installer l'extension PHP mbstring
Si vous rencontrez une erreur `mb_strlen()` :
```bash
sudo apt update
sudo apt install -y php-mbstring
sudo systemctl restart apache2
```
Vérification :
```bash
php -m | grep mbstring
```

> ✅ L'extension `mbstring` est active. Les fonctions multibyte PHP fonctionnent correctement.
### 7.6 — Recréer la table avec l'encodage correct
Si nécessaire, recréez la table avec l'encodage `utf8mb4` pour supporter les emojis et caractères spéciaux :
```bash
mysql -h mysqlwebapp123.mysql.database.azure.com -u abderapp -p
```
```sql
SHOW DATABASES;
USE abder_blog;
SHOW TABLES;
CREATE TABLE IF NOT EXISTS messages (
id INT PRIMARY KEY AUTO_INCREMENT,
contenu VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
date_creation TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

> ✅ La table `messages` est maintenant créée avec l'encodage `utf8mb4_unicode_ci`, compatible avec les emojis.
---
## 8 — Construire le dashboard (interface finale)
### 8.1 — Architecture des fichiers
```
/var/www/html/abder-blog/
├── public/
│ ├── index.php ← Dashboard principal
│ ├── add.php ← Ajout de messages (POST + CSRF)
│ └── delete.php ← Suppression (POST + CSRF + bulk)
├── src/
│ └── db.php ← Connexion PDO
└── config.php ← Configuration BDD
```
### 8.2 — Créer le dashboard (`public/index.php`)
```bash
sudo nano /var/www/html/abder-blog/public/index.php
```
Le dashboard comprend les fonctionnalités suivantes :
| Fonctionnalité | Description |
|---|---|
| KPIs | Total des messages, dernières 24h, 7 jours |
| Composer | Formulaire d'ajout avec protection CSRF |
| Analytics | Graphique Chart.js (messages/jour sur 7 jours) |
| Intelligence | Analyse des termes les plus fréquents |
| Activity Feed | Liste paginée avec vue cartes ou tableau |
| Bulk Actions | Sélection et suppression en masse |
| Command Palette | Navigation rapide avec `Ctrl+K` |
| Thème | Bascule clair/sombre |
**Technologies utilisées :**
| Technologie | Usage |
|---|---|
| Bootstrap 5.3 | Mise en page et composants |
| Bootstrap Icons | Iconographie |
| Chart.js | Graphiques analytics |
| Google Fonts (Inter) | Typographie |
| PHP PDO | Requêtes SQL sécurisées |
| CSRF Token | Protection des actions POST |
### 8.3 — Créer `add.php` (ajout de messages)
```bash
sudo nano /var/www/html/abder-blog/public/add.php
```
```php
<?php
session_start();
require __DIR__ . '/../src/db.php';
$pdo = db();
$csrf = $_POST['csrf'] ?? '';
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $csrf)) {
header("Location: index.php?err=" . urlencode("CSRF invalide."));
exit;
}
$contenu = trim($_POST['contenu'] ?? '');
if ($contenu === '') {
header("Location: index.php?err=" . urlencode("Le contenu est vide."));
exit;
}
if (mb_strlen($contenu) > 255) {
header("Location: index.php?err=" . urlencode("Max 255 caractères."));
exit;
}
$stmt = $pdo->prepare("INSERT INTO messages (contenu) VALUES (:c)");
$stmt->execute([':c' => $contenu]);
header("Location: index.php?ok=1");
exit;
```
### 8.4 — Créer `delete.php` (suppression individuelle et en masse)
```bash
sudo nano /var/www/html/abder-blog/public/delete.php
```
```php
<?php
session_start();
require __DIR__ . '/../src/db.php';
$pdo = db();
$csrf = $_POST['csrf'] ?? '';
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $csrf)) {
header("Location: index.php?err=" . urlencode("CSRF invalide."));
exit;
}
// Bulk delete
$bulk = trim($_POST['bulk_ids'] ?? '');
if ($bulk !== '') {
$ids = array_filter(array_map('intval', explode(',', $bulk)));
$ids = array_values(array_unique(array_filter($ids, fn($x) => $x > 0)));
if (count($ids) > 0) {
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("DELETE FROM messages WHERE id IN ($placeholders)");
$stmt->execute($ids);
}
header("Location: index.php");
exit;
}
// Single delete
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
$stmt = $pdo->prepare("DELETE FROM messages WHERE id = :id");
$stmt->execute([':id' => $id]);
}
header("Location: index.php");
exit;
```
### 8.5 — Recharger le serveur web
```bash
sudo systemctl reload apache2
```
### 8.6 — Résultat final

> L'interface finale du dashboard **"Lux Enterprise Console"**. On voit la sidebar avec le statut système, les KPIs (total messages, dernières 24h, 7 jours) et la navigation par sections.

> La partie inférieure du dashboard avec le flux d'activité (liste des messages), les contrôles de filtrage/recherche, et le graphique analytics Chart.js sur 7 jours.
---
## Récapitulatif de l'architecture
```
┌─────────────────┐ ┌──────────────────────────┐
│ Navigateur │ ──HTTP──▶│ VM Ubuntu (Azure) │
│ (Client) │ │ ├── Nginx / Apache │
└─────────────────┘ │ ├── PHP-FPM │
│ └── Application PHP │
└──────────┬───────────────┘
│ SSL/TLS
┌──────────▼───────────────┐
│ Azure Database for MySQL │
│ (Flexible Server) │
└──────────────────────────┘
```
### Sécurité mise en place
| Mesure | Description |
|---|---|
| CSRF Token | Protection de toutes les actions POST |
| PDO Prepared Statements | Protection contre les injections SQL |
| `htmlspecialchars()` | Échappement de toutes les sorties HTML |
| SSL/TLS | Connexion chiffrée entre PHP et MySQL |
| NSG | Filtrage réseau au niveau de la VM |
| Pare-feu MySQL | Restriction d'accès par IP |
---
> **Guide réalisé par Abder** — Déploiement Azure VM + MySQL Flexible Server