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
- Le script affiche l'usage et sort avec le code 1 (erreur d'usage), sans rien créer.
- À vérifier que l'archive est lisible :
tarla parcourt sans rien extraire. Une archive corrompue fait échouer le script. - Seulement si le script sort avec un code non nul (le
trap … EXITs'exécute à toute sortie, et la fonction teste$?) : l'archive incomplète est supprimée et le code devient 2. - Il supprime, dans le dossier de destination, les archives du même nom vieilles de plus de 7 jours. C'est la rotation.
- 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 :
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)
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) avecPersistent=true. - Ajoute à
sauvegarde.shune vérification d'espace disque avant de sauvegarder. - Écris le script Ansible (S08) qui déploie
sauvegarde.shet 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 pipefailettrap; - 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 : ______________________