L07 - Scripting Bash (et corrigé commenté du TP « déploiement de rôles »)
Objectif
Écrire des scripts Bash propres : variables, arguments, conditions, boucles, fonctions, codes de retour. Puis réaliser le TP B2 : un script qui déploie un rôle (
web,bdd,wordpress,firewall) sur une VM par SSH.
1. Premier script
nano bonjour.sh#!/usr/bin/env bash
# Mon premier script : dit bonjour
nom="${1:-inconnu}" # 1er argument, "inconnu" s'il est absent
echo "Bonjour $nom, nous sommes le $(date +%d/%m/%Y)."chmod +x bonjour.sh # rendre exécutable
./bonjour.sh Hugo # → Bonjour Hugo, nous sommes le 06/10/2026.
bash -n bonjour.sh # vérifier la syntaxe sans exécuter
shellcheck bonjour.sh # analyse les erreurs courantes (paquet shellcheck) — très recommandéLa 1re ligne (#! = shebang) indique quel interpréteur utiliser.
2. Variables et arguments
prenom="Alice" # PAS d'espace autour du =
echo "$prenom" # toujours entre guillemets doubles
echo '$prenom' # guillemets simples : rien n'est interprété → $prenom
date_jour=$(date +%F) # résultat d'une commande
nombre=$(( 3 + 4 * 2 )) # calcul entier → 11
readonly PI=3 # constante| Variable spéciale | Contenu |
|---|---|
$0 | Nom du script |
$1, $2… | Arguments |
$# | Nombre d’arguments |
$@ | Tous les arguments (à écrire "$@") |
$? | Code de retour de la dernière commande (0 = succès) |
$$ | PID du script |
$USER, $HOME, $PATH | Variables d’environnement |
${variable:-defaut} = valeur par défaut · ${variable:?message} = erreur si vide.
Lire une saisie : read -r -p "Ton nom : " nom. Mot de passe sans l’afficher : read -r -s -p "Mot de passe : " mdp (ou la méthode stty -echo du sujet).
3. Conditions
if $; then
echo "Usage : $0 <ip> <role>" >&2 # >&2 : message d'erreur
exit 1
elif "$2" == "web"; then
echo "Rôle web"
else
echo "Autre rôle"
fi| Test | Vrai si… |
|---|---|
-f fichier | Le fichier existe |
-d dossier | Le dossier existe |
-z "$v" / -n "$v" | Variable vide / non vide |
"$a" == "$b" / != | Chaînes égales / différentes |
$a -eq $b | Nombres égaux (-ne -lt -le -gt -ge) |
cond1 && cond2, ||, ! | ET, OU, NON |
if commande; then | La commande a réussi (code 0) - ex. if dpkg -s apache2 &>/dev/null; then |
case "$role" in
web) installer_web ;;
bdd|db) installer_bdd ;;
*) echo "Rôle inconnu" ;;
esac4. Boucles
for ip in 192.168.1.{10..20}; do
ping -c1 -W1 "$ip" &>/dev/null && echo "$ip répond"
done
for fichier in /var/log/*.log; do
echo "$fichier : $(wc -l < "$fichier") lignes"
done
compteur=0
while $compteur -lt 5; do
((compteur++))
done
while IFS=: read -r utilisateur _ uid _; do # lire un fichier ligne par ligne
$uid -ge 1000 && echo "Utilisateur humain : $utilisateur"
done < /etc/passwd5. Fonctions
verifier_paquet() {
local paquet="$1" # local : variable visible uniquement ici
if dpkg -s "$paquet" &>/dev/null; then
echo "$paquet est installé"
return 0
fi
return 1
}
verifier_paquet apache2 || echo "apache2 absent"6. Écrire des scripts robustes
set -euo pipefail # -e : stop à la 1re erreur ; -u : variable non définie = erreur ;
# pipefail : une erreur dans un tube est détectée
trap 'echo "Erreur ligne $LINENO"' ERR- Toujours mettre les variables entre guillemets (
"$fichier") - sinon un nom avec espace casse tout. - Tester les prérequis (droits root, arguments, commande présente :
command -v curl). - Commenter, et afficher un message d’aide.
7. TP B2 : déploiement de rôles sur une VM
Sujet (résumé)
Un script à deux arguments : l’IP de la VM et le rôle (web, bdd, wordpress, firewall). Une fonction par rôle, chacune vérifie que ce n’est pas déjà installé. Connexion SSH à la VM. Mot de passe sudo saisi sans être affiché ni stocké. Rendu en PDF + oral (10 min + 10 min de questions).
Logique du script
flowchart TD A["./deploiement_roles.sh IP ROLE"] --> B{"2 arguments ?"} B -- non --> AIDE[Afficher l'aide, exit 1] B -- oui --> C["Lire le mot de passe sudo<br/>(stty -echo)"] C --> D{ROLE ?} D -- web --> W["role_web : apache2 installé ?<br/>sinon apt install apache2 + php…"] D -- bdd --> BD["role_bdd : mariadb installé ?<br/>sinon install + bind-address 0.0.0.0"] D -- wordpress --> WP["role_wordpress : role_web puis<br/>/var/www/wordpress présent ?<br/>sinon télécharge + VirtualHost"] D -- firewall --> F["role_firewall : apache ? mariadb ?<br/>→ iptables : 22 + 80/443 + 3306"] W & BD & WP & F --> SSH["executer_distant :<br/>envoi du script par SSH,<br/>sudo -S côté VM"]
Préparer les VM
# Sur chaque VM Debian (console) : SSH + sudo
sudo apt install -y openssh-server sudo
sudo usermod -aG sudo etudiant
# Depuis ton PC : clé SSH (plus besoin du mot de passe SSH, seulement de celui de sudo)
ssh-copy-id etudiant@192.168.122.50Utilisation
chmod +x deploiement_roles.sh
./deploiement_roles.sh 192.168.122.50 web etudiant
./deploiement_roles.sh 192.168.122.51 bdd etudiant
./deploiement_roles.sh 192.168.122.50 wordpress etudiant
./deploiement_roles.sh 192.168.122.50 firewall etudiant
./deploiement_roles.sh 192.168.122.50 web etudiant # 2e fois : « déjà installé »Le script complet
Fichier : scripts/linux/deploiement_roles.sh
#!/usr/bin/env bash
# deploiement_roles.sh — Usage : ./deploiement_roles.sh <IP_VM> <web|bdd|wordpress|firewall> [utilisateur_ssh]
set -euo pipefail
ip_vm="${1:-}"
role="${2:-}"
utilisateur_ssh="${3:-$USER}"
afficher_aide() {
echo "Usage : $0 <IP_VM> <web|bdd|wordpress|firewall> [utilisateur_ssh]"
exit 1
}
-n "$ip_vm" && -n "$role" || afficher_aide
# Mot de passe sudo sans affichage (méthode du sujet)
ancien_mode=$(stty -g)
stty -echo
read -r -p "Mot de passe sudo de $utilisateur_ssh@$ip_vm (invisible) : " MOT_DE_PASSE
stty "$ancien_mode"
echo
# Envoie "mot de passe + script" sur l'entrée de SSH ; côté VM, le mot de passe
# est lu dans une variable puis donné à sudo -S par un tube (jamais en argument).
executer_distant() {
local script_distant="$1"
{ printf '%s\n' "$MOT_DE_PASSE"; printf '%s\n' "$script_distant"; } |
ssh -o ConnectTimeout=5 "$utilisateur_ssh@$ip_vm" \
'IFS= read -r mdp; script_recu=$(cat); printf "%s\n" "$mdp" | sudo -S -p "" bash -c "$script_recu"'
}
role_web() {
echo "==> Rôle WEB sur $ip_vm"
executer_distant '
set -e
if dpkg -s apache2 >/dev/null 2>&1; then
echo "Apache est déjà installé : rien à faire."
else
export DEBIAN_FRONTEND=noninteractive
apt-get update -q
apt-get install -y -q apache2 php libapache2-mod-php php-mysql php-curl php-gd \
php-mbstring php-xml php-zip php-intl
systemctl enable --now apache2
echo "Apache + PHP installés : $(php -v | head -n1)"
fi
'
}
role_bdd() {
echo "==> Rôle BDD sur $ip_vm"
executer_distant '
set -e
if dpkg -s mariadb-server >/dev/null 2>&1; then
echo "MariaDB est déjà installé : rien à faire."
else
export DEBIAN_FRONTEND=noninteractive
apt-get update -q
apt-get install -y -q mariadb-server
fichier_conf=/etc/mysql/mariadb.conf.d/50-server.cnf
sed -i "s/^bind-address.*/bind-address = 0.0.0.0/" "$fichier_conf"
systemctl enable mariadb
systemctl restart mariadb
ss -tlnp | grep 3306 || true
fi
'
}
role_wordpress() {
echo "==> Rôle WORDPRESS sur $ip_vm"
role_web
executer_distant '
set -e
dossier=/var/www/wordpress
if [ -f "$dossier/wp-settings.php" ]; then
echo "WordPress est déjà présent dans $dossier : rien à faire."
else
apt-get install -y -q wget
cd /tmp
wget -q https://wordpress.org/latest.tar.gz -O wordpress.tar.gz
tar -xzf wordpress.tar.gz -C /var/www/
chown -R www-data:www-data "$dossier"
find "$dossier" -type d -exec chmod 755 {} \;
find "$dossier" -type f -exec chmod 644 {} \;
cat > /etc/apache2/sites-available/wordpress.conf <<CONF
<VirtualHost *:80>
DocumentRoot $dossier
<Directory $dossier>
AllowOverride All
Require all granted
</Directory>
ErrorLog \${APACHE_LOG_DIR}/wordpress_error.log
CustomLog \${APACHE_LOG_DIR}/wordpress_access.log combined
</VirtualHost>
CONF
a2ensite wordpress.conf >/dev/null
a2dissite 000-default.conf >/dev/null
a2enmod rewrite >/dev/null
systemctl reload apache2
echo "WordPress installé : ouvre http://$(hostname -I | cut -d" " -f1)/"
fi
'
}
role_firewall() {
echo "==> Rôle FIREWALL sur $ip_vm"
executer_distant '
set -e
command -v iptables >/dev/null || { apt-get update -q; apt-get install -y -q iptables; }
iptables -F
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
if systemctl is-active --quiet apache2; then
iptables -A INPUT -p tcp -m multiport --dports 80,443 -j ACCEPT
echo "Apache détecté : 80 et 443 ouverts"
fi
if systemctl is-active --quiet mariadb; then
iptables -A INPUT -p tcp --dport 3306 -j ACCEPT
echo "MariaDB détecté : 3306 ouvert"
fi
export DEBIAN_FRONTEND=noninteractive
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections
dpkg -s iptables-persistent >/dev/null 2>&1 || apt-get install -y -q iptables-persistent
netfilter-persistent save
iptables -L INPUT -n --line-numbers
'
}
case "$role" in
web) role_web ;;
bdd) role_bdd ;;
wordpress) role_wordpress ;;
firewall) role_firewall ;;
*) echo "Rôle inconnu : $role"; afficher_aide ;;
esac
unset MOT_DE_PASSE
echo "Terminé."Questions probables à l’oral (et réponses)
| Question | Réponse |
|---|---|
Pourquoi stty -echo ? | Pour que le mot de passe ne s’affiche pas à l’écran pendant la saisie. |
Pourquoi ne pas mettre sudo avec le mot de passe en argument ? | Il serait visible dans ps et dans l’historique. On le passe par l’entrée standard (sudo -S). |
| Comment ton script sait qu’un rôle est déjà fait ? | dpkg -s paquet renvoie 0 si le paquet est installé ; pour WordPress on teste wp-settings.php. |
Pourquoi bind-address = 0.0.0.0 ? | Par défaut MariaDB n’écoute que sur 127.0.0.1 : les autres VM (serveur web) ne pourraient pas s’y connecter. |
Pourquoi DEBIAN_FRONTEND=noninteractive ? | Pour qu’apt ne pose aucune question (le script ne peut pas répondre). |
| Sécurité du firewall ? | Politique DROP par défaut, on n’ouvre que le nécessaire, conntrack pour les réponses. Amélioration : limiter 3306 à l’IP du serveur web. |
| Améliorations possibles ? | Clés SSH + sudo sans mot de passe pour un compte dédié, journalisation, Ansible (outil fait pour ça → P10 - Automatisation avec Ansible). |