Aller au contenu

TP – Écrire, tester et planifier des scripts d'exploitation

BTS SIO SISR 2e année – Bloc 2 – S09 – binôme : un tape, l'autre lit ; on échange les rôles à chaque partie

Note

But. Tu lis un script de sauvegarde, tu testes ses cas d'erreur, tu écris un rapport d'état avec seuils, tu les planifies avec cron et tu vérifies leur exécution. Une dernière partie transpose le principe en PowerShell.

Durée : 2 h 35 (parties A à E), plus le bilan. Prérequis : cours S09 lu ; scripts Bash et PowerShell de la première année ; S08 (Git) ; une VM Debian, une VM Windows Server pour la partie E.

Note

Comment ce TP fonctionne. Avant de manipuler, tu écris ce que tu anticipes, puis tu testes et tu notes ce que tu constates. Un bilan de compétences clôt la séance.

Binôme : ____________ / ____________

Préparation

mkdir -p ~/scripts ~/donnees/docs ~/sauvegardes
for i in 1 2 3; do echo "fichier $i" > ~/donnees/docs/f$i.txt; done
cd ~/scripts && git init

Partie A – Lire un script de sauvegarde (≈ 30 min)

Crée ~/scripts/sauvegarde.sh avec le contenu suivant, puis chmod +x sauvegarde.sh.

#!/usr/bin/env bash
# sauvegarde.sh - archive un dossier, vérifie l'archive, supprime les anciennes
# Usage : sauvegarde.sh SOURCE DESTINATION [JOURS_DE_RETENTION]
# Codes de retour : 0 succès, 1 erreur d'usage, 2 échec de sauvegarde

set -euo pipefail

SOURCE="${1:-}"
DEST="${2:-}"
RETENTION="${3:-7}"
NOM="$(basename "$SOURCE" 2>/dev/null || true)"
HORODATAGE="$(date +%Y%m%d-%H%M%S)"
ARCHIVE="${DEST}/${NOM}-${HORODATAGE}.tar.gz"
JOURNAL="${DEST}/sauvegarde.log"

journal() {
    local msg
    msg="$(date '+%F %T') $*"
    echo "$msg" | tee -a "$JOURNAL" >&2
    logger -t sauvegarde -- "$*" 2>/dev/null || true
}

# --- vérifications préalables
if [[ -z "$SOURCE" || -z "$DEST" ]]; then
    echo "Usage : $0 SOURCE DESTINATION [JOURS]" >&2
    exit 1
fi
if [[ ! -d "$SOURCE" ]]; then
    echo "Erreur : $SOURCE n'est pas un dossier" >&2
    exit 1
fi
mkdir -p "$DEST"

# --- en cas d'échec : supprimer l'archive incomplète
nettoyer() {
    local code=$?
    if [[ $code -ne 0 ]]; then
        rm -f "$ARCHIVE"
        journal "ECHEC (code $code) : $SOURCE"
        exit 2
    fi
}
trap nettoyer EXIT

journal "DEBUT : $SOURCE -> $ARCHIVE"
tar -czf "$ARCHIVE" -C "$(dirname "$SOURCE")" "$NOM"
tar -tzf "$ARCHIVE" > /dev/null          # vérifie que l'archive est lisible
TAILLE="$(du -h "$ARCHIVE" | cut -f1)"
journal "OK : $ARCHIVE ($TAILLE)"

# --- rotation : supprime les archives plus vieilles que RETENTION jours
SUPPRIMEES="$(find "$DEST" -maxdepth 1 -name "${NOM}-*.tar.gz" -mtime "+${RETENTION}" -print -delete | wc -l)"
journal "ROTATION : $SUPPRIMEES archive(s) supprimée(s) (> ${RETENTION} j)"

Réponds sans l'exécuter.

Question Réponse
1. Que se passe-t-il si l'on oublie le deuxième argument ? Quel code de retour ?
2. À quoi sert tar -tzf "$ARCHIVE" > /dev/null ?
3. Quand la fonction nettoyer supprime-t-elle l'archive ?
4. Que fait find … -mtime "+7" -delete ?
5. Pourquoi set -u protège-t-il ici ?
Éléments de réponse
  1. Le script affiche l'usage et sort avec le code 1 (erreur d'usage), sans rien créer.
  2. À vérifier que l'archive est lisible : tar la parcourt sans rien extraire. Une archive corrompue fait échouer le script.
  3. Seulement si le script sort avec un code non nul (le trap … EXIT s'exécute à toute sortie, et la fonction teste $?) : l'archive incomplète est supprimée et le code devient 2.
  4. Il supprime, dans le dossier de destination, les archives du même nom vieilles de plus de 7 jours. C'est la rotation.
  5. Si une variable (par exemple DEST) est vide ou mal orthographiée, le script s'arrête au lieu de travailler sur un chemin inattendu comme /.

Partie B – Tester les cas nominal et d'erreur (≈ 35 min)

1. Cas nominal :

./sauvegarde.sh ~/donnees/docs ~/sauvegardes 7 ; echo "code = $?"
ls ~/sauvegardes ; cat ~/sauvegardes/sauvegarde.log

2. Cas d'erreur d'usage :

./sauvegarde.sh ; echo "code = $?"
./sauvegarde.sh /inexistant ~/sauvegardes ; echo "code = $?"

3. Rotation. Fabrique une vieille archive, relance, observe :

touch -d '10 days ago' ~/sauvegardes/docs-20200101-000000.tar.gz
./sauvegarde.sh ~/donnees/docs ~/sauvegardes 7
ls ~/sauvegardes

4. Échec en cours de route : simule un tar défaillant et observe le nettoyage.

mkdir -p /tmp/faux && printf '#!/bin/sh\nexit 3\n' > /tmp/faux/tar && chmod +x /tmp/faux/tar
PATH=/tmp/faux:$PATH ./sauvegarde.sh ~/donnees/docs ~/sauvegardes 7 ; echo "code = $?"
ls ~/sauvegardes
Test Code observé Constat
Cas nominal
Sans argument
Dossier inexistant
Vieille archive
tar en échec

J'anticipe. Au test 4, restera-t-il une archive incomplète dans ~/sauvegardes ? Quel code sera renvoyé ? ______________________

Éléments de réponse

Nominal 0 ; sans argument 1 ; dossier inexistant 1 ; la vieille archive est supprimée et le journal indique « 1 archive(s) supprimée(s) » ; tar en échec : code 2, et aucune archive incomplète n'est conservée (le trap l'a supprimée). Le journal contient la ligne ECHEC (code 3).

Je comprends. Pourquoi est-il important que le script renvoie un code non nul en cas d'échec, plutôt que d'afficher seulement un message ? ______________________

Éléments de réponse

Un message n'est lu que par quelqu'un qui regarde l'écran. Un code de retour peut être lu par le planificateur, la supervision ou un autre script, qui peuvent alors alerter ou enchaîner. Un script qui « réussit » toujours donne un faux sentiment de sécurité.

5. Enregistre : git add sauvegarde.sh && git commit -m "Ajouter le script de sauvegarde avec rotation".


Partie C – Écrire un rapport d'état avec seuils (≈ 35 min)

But : produire un rapport lisible et un code de retour 1 si un seuil est dépassé.

Crée ~/scripts/rapport.sh en complétant la partie mémoire :

#!/usr/bin/env bash
# rapport.sh - rapport d'état de la machine ; code de retour 1 si un seuil est dépassé
# Usage : rapport.sh [SEUIL_DISQUE_%] [SEUIL_MEMOIRE_%]

set -uo pipefail

SEUIL_DISQUE="${1:-80}"
SEUIL_MEM="${2:-90}"
ALERTE=0

echo "=== Rapport d'état de $(hostname) - $(date '+%F %T') ==="
echo "Système : $(. /etc/os-release && echo "$PRETTY_NAME")"
echo "Disponibilité : $(uptime -p)"
echo "Charge (1, 5, 15 min) : $(cut -d' ' -f1-3 /proc/loadavg)"

echo
echo "--- Disques (seuil ${SEUIL_DISQUE} %) ---"
while read -r point pct; do
    pct="${pct%\%}"
    if (( pct >= SEUIL_DISQUE )); then
        echo "ALERTE : $point utilisé à ${pct} %"
        ALERTE=1
    else
        echo "ok : $point utilisé à ${pct} %"
    fi
done < <(df -P -x tmpfs -x devtmpfs | awk 'NR>1 {print $6, $5}')

echo
echo "--- Mémoire (seuil ${SEUIL_MEM} %) ---"
# A COMPLETER : calculer le pourcentage de mémoire utilisée avec `free`,
# afficher "ok" ou "ALERTE" selon le seuil, et positionner ALERTE=1 si besoin

exit "$ALERTE"
Éléments de réponse (la partie à compléter)
MEM_PCT="$(free | awk '/^Mem:/ {printf "%d", ($3/$2)*100}')"
if (( MEM_PCT >= SEUIL_MEM )); then
    echo "ALERTE : mémoire utilisée à ${MEM_PCT} %"
    ALERTE=1
else
    echo "ok : mémoire utilisée à ${MEM_PCT} %"
fi

Tests.

./rapport.sh ; echo "code = $?"        # seuils par défaut : 80 % et 90 %
./rapport.sh 1 90 ; echo "code = $?"   # seuil disque volontairement très bas

J'anticipe. Quel code obtiens-tu avec le seuil de disque à 1 % ? Pourquoi ? ______________________

Éléments de réponse

1 : tout système de fichiers utilisé à plus de 1 % déclenche une ALERTE ; la variable ALERTE vaut 1 et le script sort avec ce code. Avec les seuils par défaut et un système sain : 0.

Je comprends. Pourquoi le paramètre « seuil » est-il un argument et non une valeur écrite dans le script ? ______________________

Éléments de réponse

Pour réutiliser le script sur des machines différentes (un serveur de fichiers n'a pas le même seuil qu'un serveur web) et pour le tester (on abaisse le seuil pour provoquer l'alerte).

Commit : git add rapport.sh && git commit -m "Ajouter le rapport d'état avec seuils".


Partie D – Planifier et vérifier (≈ 25 min)

1. Ouvre ta crontab (crontab -e) et ajoute deux lignes (adapte /home/utilisateur) :

*/2 * * * * /home/utilisateur/scripts/sauvegarde.sh /home/utilisateur/donnees/docs /home/utilisateur/sauvegardes 7 >> /home/utilisateur/cron.log 2>&1
*/5 * * * * /home/utilisateur/scripts/rapport.sh 80 90 >> /home/utilisateur/rapport.log 2>&1

2. Attends deux à cinq minutes, puis vérifie que l'exécution planifiée a eu lieu et qu'elle a réussi :

crontab -l
ls -l ~/sauvegardes
tail ~/cron.log ~/rapport.log
journalctl -t sauvegarde --since "10 minutes ago"
journalctl -t CRON --since "10 minutes ago" | tail

(si journalctl -t CRON est vide, essaie journalctl -u cron.)

J'anticipe. Que se passerait-il si tu écrivais sauvegarde.sh sans chemin absolu dans la crontab ? ______________________

Éléments de réponse

Cron utilise un environnement minimal (PATH réduit, répertoire courant différent) : la commande ne serait pas trouvée. La sortie d'erreur irait dans le courrier local ou serait perdue si elle n'est pas redirigée. D'où les chemins absolus et la redirection >> … 2>&1.

3. Retire les deux lignes de la crontab à la fin du TP (sinon la sauvegarde continue toutes les deux minutes).

Je comprends. Cite deux limites de cron que ne règle pas ce TP. ______________________

Éléments de réponse

Pas d'alerte si le script échoue (il faut la supervision, S11) ; pas de rattrapage si la machine était éteinte à l'heure prévue (un minuteur systemd avec Persistent=true le permet) ; journalisation dispersée.


Partie E – Transposer en PowerShell (≈ 30 min)

Sur la VM Windows Server, dans PowerShell, crée C:\scripts\rapport.ps1 :

param([int]$Seuil = 80)
$ErrorActionPreference = "Stop"
$alerte = 0
$disques = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3"
foreach ($d in $disques) {
    $pct = [math]::Round((($d.Size - $d.FreeSpace) / $d.Size) * 100)
    if ($pct -ge $Seuil) {
        Write-Output "ALERTE : $($d.DeviceID) utilisé à $pct %"
        $alerte = 1
    } else {
        Write-Output "ok : $($d.DeviceID) utilisé à $pct %"
    }
}
exit $alerte
powershell -File C:\scripts\rapport.ps1 -Seuil 1 ; $LASTEXITCODE
powershell -File C:\scripts\rapport.ps1 ; $LASTEXITCODE

Planification (exemple à adapter) :

schtasks /create /tn "RapportDisques" /tr "powershell -File C:\scripts\rapport.ps1" /sc daily /st 07:00
schtasks /query /tn "RapportDisques"

Je comprends. Retrouve dans le script PowerShell les quatre principes vus en Bash : paramètre, vérification, code de retour, journal. Que manque-t-il pour le journal ? ______________________

Éléments de réponse

Paramètre : param([int]$Seuil = 80) ; vérification : lecture des disques et comparaison au seuil ; code de retour : exit $alerte. Il manque le journal : ajouter Add-Content C:\scripts\rapport.log "$(Get-Date -Format s) …" ou une écriture dans le journal d'événements.

Pour aller plus loin

  • Remplace la tâche cron par un minuteur systemd (.service + .timer) avec Persistent=true.
  • Ajoute à sauvegarde.sh une vérification d'espace disque avant de sauvegarder.
  • Écris le script Ansible (S08) qui déploie sauvegarde.sh et la ligne de cron sur les serveurs.

Retour en entreprise (si ta situation le permet)

  • Observer : quelles tâches répétitives sont déjà automatisées ? Comment sait-on qu'elles ont réussi ?
  • Documenter : choisis une tâche manuelle de ton entreprise et décris le script qui la remplacerait (paramètres, vérifications, codes de retour).

Rendu

Compte rendu PDF, nommé RENDU-B2A2-S09-NOM.pdf, contenant : le tableau des tests de la partie B, les scripts finaux, le git log --oneline, la preuve d'une exécution planifiée réussie (capture de cron.log et de journalctl), le script PowerShell et ses essais.

Bilan de la séance

Niveau : Non maîtrisé, Maîtrise partielle, Bonne maîtrise ou Excellente maîtrise, ou Non évalué.

Compétence du référentiel Ce que j'ai fait dans cette séance Niveau
B2.2 – Installer, tester et déployer une solution d'infrastructure réseau
B2.3 – Exploiter, dépanner et superviser une solution d'infrastructure réseau

Note

Les codes sont ceux du référentiel.

Je sais faire, sans aide :

  • Écrire un script paramétrable avec vérifications et codes de retour ;
  • Utiliser set -euo pipefail et trap ;
  • Tester les cas nominal et d'erreur ;
  • Planifier avec cron et vérifier l'exécution ;
  • Transposer le principe en PowerShell ;
  • Versionner et documenter un script.

Ce que je dois reprendre : ______________________

Ma question à poser à l'enseignant : ______________________