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écialeContenu
$0Nom 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, $PATHVariables 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
TestVrai si…
-f fichierLe fichier existe
-d dossierLe dossier existe
-z "$v" / -n "$v"Variable vide / non vide
"$a" == "$b" / !=Chaînes égales / différentes
$a -eq $bNombres égaux (-ne -lt -le -gt -ge)
cond1 && cond2, ||, !ET, OU, NON
if commande; thenLa 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" ;;
esac

4. 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/passwd

5. 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.50

Utilisation

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)

QuestionRé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).

Voir aussi