ITSkillsCenter
Business Digital

Bash scripting pour sysadmin : sauvegardes, monitoring, déploiements — tutoriel 2026

15 min de lecture

📍 Article principal du cluster : Automatisation pour PME ouest-africaines : le guide complet 2026
Ce tutoriel fait partie du cluster « Automatisation ». Pour la vue d’ensemble (no-code, n8n, scripts, IA agents), commencer par le pilier.

Introduction

Tout sysadmin Linux finit par écrire du Bash. Pas du Bash de hacker mythique — du Bash utilitaire, qui sauvegarde une base de données, qui ping un service tierce et alerte sur Telegram en cas de panne, qui déploie un site WordPress en SSH après un push Git. Ce tutoriel pas-à-pas vous donne le squelette mental et les bonnes pratiques pour produire des scripts Bash de production — qui tiennent dix ans, qui ne réveillent personne à 3 h du matin, et qui sont compréhensibles par votre successeur.

L’angle est délibérément pratique : trois cas d’usage concrets pour une PME ouest-africaine — sauvegarde de base de données, monitoring d’un service web critique, déploiement automatisé d’un site — accompagnés des patterns transverses (gestion d’erreurs, logs, secrets, notifications) qui font la différence entre un script jetable et un script de production. Toutes les commandes ont été testées sur Ubuntu 24.04 LTS et Debian 12.

Prérequis

  • Serveur Linux (Ubuntu 22.04+, Debian 12, AlmaLinux 9 ou équivalent)
  • Accès SSH avec utilisateur sudo
  • Connaissance de base de la ligne de commande (cd, cat, nano/vim, redirections)
  • Bash 5.x (présent par défaut sur toute distribution moderne)
  • Temps estimé : 2 h pour le tutoriel, 4 à 6 h pour adapter et déployer en production

Étape 1 — Le squelette d’un script Bash robuste

Avant d’écrire la moindre logique métier, on adopte un squelette unique pour tous les scripts. Ce squelette élimine 80 % des bugs classiques (variables non définies, échec silencieux d’une commande dans un pipe, accumulation d’effets de bord après une erreur). Créez le fichier /home/admin/scripts/template.sh :

#!/usr/bin/env bash
#
# Description : décrit en une phrase ce que fait le script
# Auteur      : votre nom — votre email
# Dernière MAJ : 2026-04-30
# Usage       : ./template.sh [arg1] [arg2]
#

set -Eeuo pipefail
IFS=$'\n\t'

# --- Configuration ---
readonly SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
readonly LOG_FILE="/var/log/$(basename "$0" .sh).log"
readonly LOCK_FILE="/var/run/$(basename "$0" .sh).lock"

# --- Fonctions utilitaires ---
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

die() {
  log "ERREUR : $*"
  exit 1
}

cleanup() {
  rm -f "$LOCK_FILE"
}

trap cleanup EXIT
trap 'die "Interruption à la ligne $LINENO"' ERR

# --- Verrou anti-double-exécution ---
[[ -f "$LOCK_FILE" ]] && die "Script déjà en cours (lock $LOCK_FILE existe)"
echo "$$" > "$LOCK_FILE"

# --- Logique métier ---
log "Démarrage"
# ... votre logique ici ...
log "Terminé avec succès"

Ce squelette concentre les bonnes pratiques en moins de 30 lignes. set -Eeuo pipefail est le quatuor magique : -E propage les trap ERR dans les fonctions, -e stoppe au premier échec, -u empêche l’usage de variables non définies, -o pipefail capture les échecs au milieu d’un pipeline. IFS=$'\n\t' évite les surprises de mot-splitting sur les chemins avec espaces. Les fonctions log et die centralisent la sortie. Le trap cleanup EXIT garantit le nettoyage du fichier lock même en cas d’arrêt brutal. Le verrou empêche deux exécutions concurrentes — précieux quand un cron lance un script qui prend parfois plus de temps que son intervalle.

Adoptez ce squelette comme base de tous vos nouveaux scripts. Vous gagnerez littéralement des semaines de débuggage sur la durée.

Étape 2 — Cas d’usage 1 : sauvegarde de base de données avec rapport email

Voici un script complet qui sauvegarde une base PostgreSQL, vérifie le résultat, et envoie un email de rapport — qu’il y ait succès ou échec. C’est le job typique qui doit tourner toutes les nuits sur n’importe quel serveur de PME hébergeant une application métier.

#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'

readonly DB_NAME="appmetier"
readonly DB_USER="backup"
readonly BACKUP_DIR="/var/backups/postgres"
readonly RETENTION_DAYS=14
readonly DATE=$(date +%Y%m%d-%H%M%S)
readonly BACKUP_FILE="$BACKUP_DIR/${DB_NAME}-${DATE}.sql.gz"
readonly LOG_FILE="/var/log/backup-postgres.log"
readonly ALERT_EMAIL="admin@votre-pme.sn"

# Source des secrets (PGPASSWORD)
[[ -f /etc/default/backup-postgres ]] && source /etc/default/backup-postgres

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }

send_alert() {
  local subject="$1"
  local body="$2"
  echo -e "$body" | mail -s "$subject" "$ALERT_EMAIL"
}

trap 'send_alert "[BACKUP ERREUR] $(hostname) — $DB_NAME" "Échec à la ligne $LINENO. Voir $LOG_FILE"' ERR

mkdir -p "$BACKUP_DIR"
log "Démarrage backup $DB_NAME"

PGPASSWORD="$PGPASSWORD" pg_dump --clean --if-exists --no-owner -h localhost -U "$DB_USER" "$DB_NAME" \
  | gzip > "$BACKUP_FILE"

# Vérification : taille du fichier > 1 Ko (sinon dump suspect)
size=$(stat -c%s "$BACKUP_FILE")
if (( size < 1024 )); then
  log "Backup suspect : taille $size octets"
  send_alert "[BACKUP SUSPECT] $(hostname) — $DB_NAME" "Fichier de seulement $size octets"
  exit 1
fi

# Purge des anciennes sauvegardes
find "$BACKUP_DIR" -name "${DB_NAME}-*.sql.gz" -mtime +$RETENTION_DAYS -delete
log "Backup OK : $BACKUP_FILE ($(numfmt --to=iec $size))"

# Rapport quotidien (uniquement le lundi matin pour ne pas saturer)
if [[ "$(date +%u)" == "1" ]]; then
  count=$(find "$BACKUP_DIR" -name "${DB_NAME}-*.sql.gz" | wc -l)
  send_alert "[BACKUP HEBDO] $(hostname) — $DB_NAME" "$count sauvegardes présentes. Dernière : $BACKUP_FILE"
fi

Ce script va plus loin que le simple pg_dump | gzip. Il vérifie que le fichier produit a une taille raisonnable (un dump de quelques octets indique que pg_dump a échoué silencieusement après le pipe), il alerte par email uniquement en cas d’erreur (pas de spam quotidien), et il envoie un récapitulatif hebdomadaire le lundi. Le mot de passe est isolé dans /etc/default/backup-postgres en chmod 600. Pour la planification, voir le tutoriel frère Cron et systemd timers Linux.

Étape 3 — Cas d’usage 2 : monitoring d’un service web avec alerte Telegram

Vérifier toutes les 5 minutes qu’un site WordPress répond, et alerter sur Telegram en cas de panne. Telegram est devenu le canal d’alerte le plus pratique pour un sysadmin africain : gratuit, fiable même sur connexion 3G, notifications push instantanées sur mobile.

#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'

readonly URL="https://votre-site.com"
readonly TIMEOUT=10
readonly STATE_FILE="/var/lib/monitor-web.state"
readonly LOG_FILE="/var/log/monitor-web.log"

[[ -f /etc/default/monitor-web ]] && source /etc/default/monitor-web
# /etc/default/monitor-web doit définir TG_BOT_TOKEN et TG_CHAT_ID

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }

notify_telegram() {
  local message="$1"
  curl -sS --max-time 10 \
    -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
    -d "chat_id=${TG_CHAT_ID}" \
    -d "text=${message}" \
    -d "parse_mode=Markdown" > /dev/null
}

# Test HTTP
http_code=$(curl -sS -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT" "$URL" || echo "000")

previous_state="OK"
[[ -f "$STATE_FILE" ]] && previous_state=$(cat "$STATE_FILE")

if [[ "$http_code" =~ ^[23] ]]; then
  current_state="OK"
  if [[ "$previous_state" == "DOWN" ]]; then
    notify_telegram "✅ *$URL* est de retour ($http_code)"
    log "Récupération : $URL répond $http_code"
  fi
else
  current_state="DOWN"
  if [[ "$previous_state" == "OK" ]]; then
    notify_telegram "🔴 *$URL* est DOWN — code HTTP : $http_code"
    log "Panne détectée : $URL répond $http_code"
  fi
fi

echo "$current_state" > "$STATE_FILE"

La logique d’état (lecture du précédent état, comparaison, écriture du nouveau) évite le piège classique du monitoring : envoyer une alerte toutes les 5 minutes pendant 4 heures parce que le service reste down. Ici, on alerte une fois sur la transition OK → DOWN, et une fois sur la transition DOWN → OK. Le destinataire reçoit deux messages au total pour un incident de 4 heures, pas 48.

Pour créer le bot Telegram et obtenir le TG_BOT_TOKEN, parlez à @BotFather sur Telegram et suivez les étapes (5 minutes). Le TG_CHAT_ID s’obtient en envoyant un message à votre nouveau bot puis en consultant https://api.telegram.org/bot<TOKEN>/getUpdates. Planifiez ensuite ce script avec un cron */5 * * * * ou un systemd timer OnCalendar=*:0/5.

Étape 4 — Cas d’usage 3 : déploiement automatisé d’un site WordPress

Sur un VPS qui héberge plusieurs sites clients, on veut un script qui prend un nom de site en argument, met à jour le code depuis Git, ajuste les permissions, vide le cache, et redémarre le service nginx si nécessaire.

#!/usr/bin/env bash
set -Eeuo pipefail
IFS=$'\n\t'

usage() {
  echo "Usage : $(basename "$0") <nom-du-site>"
  echo "Exemple : $(basename "$0") site-client-acme"
  exit 1
}

[[ $# -eq 1 ]] || usage
readonly SITE="$1"
readonly SITE_DIR="/var/www/${SITE}"
readonly LOG_FILE="/var/log/deploy-${SITE}.log"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
die() { log "ERREUR : $*"; exit 1; }

[[ -d "$SITE_DIR" ]] || die "Le site $SITE n'existe pas dans /var/www"
[[ -d "$SITE_DIR/.git" ]] || die "$SITE_DIR n'est pas un dépôt Git"

cd "$SITE_DIR"

log "Déploiement de $SITE"
log "Sauvegarde de l'état avant déploiement"
git stash push -u -m "Avant déploiement automatisé $(date +%Y%m%d-%H%M%S)" || true

log "Récupération des dernières modifications"
git fetch --quiet
git reset --hard origin/main

log "Ajustement des permissions"
chown -R www-data:www-data "$SITE_DIR"
find "$SITE_DIR" -type d -exec chmod 755 {} \;
find "$SITE_DIR" -type f -exec chmod 644 {} \;
chmod 600 "$SITE_DIR/wp-config.php" 2>/dev/null || true

log "Vidage cache OPcache (PHP-FPM)"
systemctl reload php8.3-fpm 2>/dev/null || systemctl reload php-fpm 2>/dev/null || true

log "Test config nginx"
nginx -t 2>&1 | tee -a "$LOG_FILE"

log "Déploiement terminé pour $SITE"

Ce script gère les cas concrets : le site doit exister et être un dépôt Git, on sauvegarde tout changement local non committé via git stash avant le reset (sans cela un changement urgent fait en SSH serait perdu), on remet les bonnes permissions WordPress (755 dossiers, 644 fichiers, 600 pour wp-config.php qui contient les credentials base), on rafraîchit l’opcode cache PHP, et on valide la config nginx avant de la considérer valide. Le déploiement complet d’un site moyen prend 5 à 15 secondes.

Étape 5 — Patterns transverses indispensables

Quatre patterns reviennent dans 90 % des scripts Bash sérieux que vous écrirez. Les maîtriser vous évite de réinventer la roue à chaque projet.

Gestion robuste des arguments. Pour les scripts qui acceptent plusieurs flags, utilisez getopts plutôt que de parser $1, $2 à la main. getopts gère les options courtes (-v), la combinaison (-vf), les arguments d’option (-o fichier.txt), et signale proprement les options inconnues.

Vérifications préalables explicites. Au début de chaque script de production, listez les prérequis et faites-les échouer fort avec un message clair : binaire absent (command -v psql >/dev/null || die "psql introuvable"), fichier de config manquant, variable d’environnement non définie. Cela évite les erreurs cryptiques en pleine exécution.

Verrouillage avec flock. Pour les scripts cron qui peuvent durer plus longtemps que leur intervalle (sauvegardes lentes, traitements de gros volumes), utilisez flock pour empêcher l’empilement : flock -n /var/run/mon-job.lock /home/admin/scripts/mon-job.sh. Si une instance tourne déjà, la nouvelle exit immédiatement sans bruit.

Signaux et nettoyage. Si votre script crée des fichiers temporaires, des dossiers de travail, ou démarre des processus en arrière-plan, utilisez trap cleanup EXIT pour garantir le nettoyage même en cas de Ctrl+C ou de kill. Sans cela, votre /tmp se remplit silencieusement.

Étape 6 — Vérification finale

Avant de mettre un script Bash en production, déroulez ce checklist en 6 points :

  1. Shellcheck propre : shellcheck votre-script.sh ne doit remonter aucune erreur (les warnings, à juger).
  2. Test en mode dry-run : ajoutez une option --dry-run qui simule sans écrire, et exécutez-la d’abord.
  3. Test du chemin d’erreur : provoquez délibérément un échec (mauvais mot de passe, fichier inexistant) et vérifiez que l’alerte arrive bien.
  4. Permissions cohérentes : chmod 750 pour les scripts contenant des secrets indirects, chmod 700 pour les fichiers d’environnement.
  5. Logs lisibles : un tail -f du log doit permettre de comprendre ce qui se passe sans relire le code.
  6. Documentation : un commentaire en tête expliquant quoi, pourquoi, comment relancer. Votre successeur (ou votre futur vous) vous remerciera.

Erreurs fréquentes

Erreur Cause Solution
Script qui marche en interactif mais pas en cron PATH minimaliste, pas de TTY, pas de variables d’environnement utilisateur Mettre les chemins complets, exporter explicitement les variables, sourcer un fichier d’env
Échec silencieux d’une commande dans un pipe set -e seul ne capture pas le milieu d’un pipe Ajouter set -o pipefail en plus
Variables non définies utilisées par erreur Faute de frappe non détectée set -u : toute variable non définie fait crasher le script immédiatement
Mots-clés Bash interprétés dans des chemins avec espaces IFS par défaut découpe sur l’espace IFS=$'\n\t' en début de script + toujours quoter "$variable"
Spam d’alertes lors d’incidents prolongés Pas de gestion d’état Fichier d’état + alerte uniquement sur transition
Exécutions concurrentes qui se marchent dessus Pas de verrou flock ou fichier lock manuel + trap cleanup EXIT
Mots de passe dans le script Praticité Fichier EnvironmentFile en chmod 600, ou gestionnaire de secrets

Adaptation au contexte ouest-africain

Trois adaptations spécifiques pour un sysadmin sénégalais, ivoirien ou malien. Première : favoriser Telegram pour les alertes plutôt que les SMS payants ou les emails qui peuvent traîner. Le bot Telegram est gratuit, instantané, et fonctionne même en 3G dégradée. Deuxième : prévoir des timeouts généreux sur les appels HTTP sortants. Une connexion satellite Starlink rurale ou une fibre Sonatel un mauvais jour peut faire monter la latence à plusieurs secondes — un timeout de 2 secondes par défaut produira de fausses alertes. Allez plutôt sur 10-15 secondes pour les checks externes. Troisième : journaliser systématiquement les jobs critiques vers un serveur tiers (Loki self-hosted, Logtail, ou simplement un syslog distant) plutôt que sur le seul disque local — en cas de crash du VPS, vous gardez l’historique pour comprendre ce qui s’est passé.

Tutoriels frères du cluster automatisation

FAQ

Bash ou Python pour scripter en 2026 ?

Bash dès qu’on chaîne des commandes shell (cp, mv, rsync, curl, jq, systemctl). Python dès qu’on manipule des structures de données complexes (JSON imbriqué, CSV/Excel, XML), qu’on fait du calcul, ou qu’on utilise des bibliothèques riches (pandas, requests, paramiko). Règle simple : si votre script Bash dépasse 200 lignes ou contient plus de 3 niveaux d’imbrication, basculez en Python.

Comment debugger un script Bash qui ne marche pas ?

Trois techniques. bash -x votre-script.sh active la trace : chaque commande exécutée est affichée. set -x en haut du script fait pareil. Pour cibler une zone : set -x avant la zone, set +x après. Et toujours utiliser shellcheck en amont — il détecte 80 % des bugs avant exécution.

Comment partager des secrets entre scripts sans les écrire en clair ?

Trois niveaux selon la sensibilité. Niveau 1 : fichier /etc/default/votre-app en chmod 600, propriétaire l’utilisateur applicatif. Niveau 2 : utilisation de pass (gestionnaire CLI basé sur GPG). Niveau 3 : HashiCorp Vault avec authentification AppRole, le script récupère le secret à l’exécution via vault kv get. Pour une PME, le niveau 1 suffit largement pour 90 % des cas.

Faut-il versionner ses scripts Bash sur Git ?

Oui, sans hésiter — un dépôt Git interne (Gitea self-hosted, GitHub privé, GitLab) avec un dossier scripts/ par serveur. Avantage : historique des modifications, retour arrière facile, revue de code entre admins, déploiement via git pull. Ne jamais committer les fichiers de secrets — utilisez .gitignore agressivement et un fichier .env.example sans valeurs.

Quel est le piège le plus courant en Bash ?

Oublier de quoter les variables. cp $source $dest casse dès qu’un nom de fichier contient un espace. La règle : toujours cp "$source" "$dest", sauf rare cas où on veut explicitement le mot-splitting. Combiné avec set -u (qui crashe sur variable non définie) et shellcheck (qui détecte les variables non quotées), 90 % des bugs Bash classiques disparaissent.

Sources et références

Pour aller plus loin

  • 🔝 Retour au pilier : Automatisation pour PME ouest-africaines : le guide complet 2026
  • Tutoriel suivant recommandé du cluster : GitHub Actions : CI/CD automatique pour projets web — pour pousser vos scripts Bash dans un pipeline d’intégration continue.
  • Pratique : reprendre un script « jetable » écrit il y a six mois et le retourner via le squelette de l’étape 1. Mesure de qualité immédiate.

Mots-clés secondaires : Bash scripting, sysadmin Linux, automatisation serveur, sauvegarde PostgreSQL, monitoring web, Telegram bot alertes, déploiement Git, ShellCheck, set -e pipefail.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité