From 10f97b504862f2283cb47d88e7c7738bc30827a9 Mon Sep 17 00:00:00 2001 From: Alpinux Date: Thu, 24 Sep 2026 22:15:33 +0200 Subject: [PATCH 1/5] =?UTF-8?q?Verser=20le=20relev=C3=A9=20de=20fiche=20ma?= =?UTF-8?q?t=C3=A9rielle=20dans=20le=20d=C3=A9p=C3=B4t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La demande initiale faisait télécharger un script depuis alpicache et l'exécuter en root sur la machine de chaque participant. Le relevé est désormais versionné ici, relu comme le reste, et appelé localement : rien n'est tiré du réseau, rien n'est exécuté sans avoir été publié. Le script vient de la session alpicache, éprouvé sur place. Il relève le modèle, le processeur, la mémoire et le détail de ses connecteurs, les disques et leur état SMART, puis envoie la fiche au serveur de la salle. Depuis une session live, il rend en plus un verdict — bon, avec réserves, ou ne pas installer — que le bénévole peut opposer à un disque qui faiblit pendant que la machine est encore là. install.sh l'appelle en tête de fichier, dans sa passe root, et seulement s'il le trouve à côté de lui : une machine qui rejoue le script chez son propriétaire n'envoie rien à personne. L'en-tête du relevé disait « rien qui touche à la personne ». C'était inexact : la fiche emporte le nom de la machine — souvent un prénom — et l'adresse matérielle de la carte réseau. Les deux sont maintenant nommés, dans le script comme dans le guide, avec la consigne qui va avec : le dire à la personne dont on installe l'ordinateur. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo --- code/linux/linux-mint/alpi-fiche.sh | 389 +++++++++++++++++++++++ code/linux/linux-mint/install.sh | 22 ++ docs/guides/linux-mint-depuis-windows.md | 26 ++ 3 files changed, 437 insertions(+) create mode 100755 code/linux/linux-mint/alpi-fiche.sh diff --git a/code/linux/linux-mint/alpi-fiche.sh b/code/linux/linux-mint/alpi-fiche.sh new file mode 100755 index 0000000..f9d0b45 --- /dev/null +++ b/code/linux/linux-mint/alpi-fiche.sh @@ -0,0 +1,389 @@ +#!/bin/bash +# Relève la fiche d'une machine, et l'envoie au serveur de l'install party. +# +# À récupérer depuis le dépôt du wiki, comme les autres scripts d'Alpinux : +# +# wget https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-wiki/raw/branch/main/code/linux/linux-mint/alpi-fiche.sh +# chmod +x alpi-fiche.sh +# sudo ./alpi-fiche.sh --check # depuis la session live, avant d'installer +# sudo ./alpi-fiche.sh # après l'installation +# +# Sur le réseau de l'install party, plus court : +# +# wget http://10.0.0.1/alpi-fiche.sh && chmod +x alpi-fiche.sh && sudo ./alpi-fiche.sh --check +# +# Pourquoi la garder sur le serveur plutôt que sur le poste : trois semaines +# plus tard, quand quelqu'un rappelle parce que « ça rame », on n'a plus la +# machine sous la main. Savoir qu'elle a huit ans, quatre gigaoctets de +# mémoire et un disque mécanique qui commence à réallouer des secteurs répond +# à la question avant de l'avoir posée. +# +# Ce qui est envoyé, et rien d'autre : le modèle et la marque, les quatre +# derniers caractères du numéro de série, la date du BIOS, le processeur, la +# mémoire et le détail de ses connecteurs, les disques avec leur santé SMART, +# l'occupation des systèmes de fichiers, le système installé, l'architecture, +# l'adresse matérielle de l'interface réseau et le nom de la machine. +# +# Ni nom d'utilisateur, ni fichiers, ni historique, ni adresse IP publique. +# +# Deux réserves à connaître avant de s'en servir, parce qu'elles concernent +# des gens et pas des machines : +# +# - le **nom de la machine** est celui choisi pendant l'installation, et il +# contient souvent un prénom (« portable-jean ») ; +# - l'**adresse matérielle** identifie durablement la carte réseau ; elle +# sert ici à rapprocher la fiche du bail DHCP, rien de plus. +# +# Le relevé part en clair sur le réseau local de la salle, vers le serveur de +# l'association, et n'en sort pas. Prévenez-en la personne dont vous installez +# la machine : c'est la sienne. +# +# alpi-fiche relève et envoie +# alpi-fiche --montrer relève et affiche, sans rien envoyer +# alpi-fiche --check relève, envoie, et rend un verdict +# +# Le mode « --check » est fait pour tourner **depuis la clé USB, avant +# d'installer** : c'est le seul moment où l'on peut encore renoncer. Un disque +# qui se déclare défaillant, on ne passe pas une heure à installer dessus — on +# le dit à son propriétaire et on cherche une autre solution. + +set -u + +SERVEUR="${ALPI_SERVEUR:-http://alpicache.lan}" +MONTRER=0 +CHECK=0 +case "${1:-}" in + --montrer) MONTRER=1 ;; + --check) CHECK=1 ;; +esac + +if [ -t 1 ]; then + V=$'\e[32m'; O=$'\e[33m'; R=$'\e[31m'; B=$'\e[1m'; D=$'\e[2m'; Z=$'\e[0m' +else + V=""; O=""; R=""; B=""; D=""; Z="" +fi + +lire_dmi() { + cat "/sys/class/dmi/id/$1" 2>/dev/null | tr -d '\n' | sed 's/^ *//; s/ *$//' +} + +echapper() { + # De quoi écrire du JSON sans rien installer : guillemets, antislashs et + # sauts de ligne, et c'est tout ce qui peut nous arriver ici. + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr -d '\n\r' +} + +champ() { printf '"%s":"%s"' "$1" "$(echapper "${2:-}")"; } + +# --- La machine ------------------------------------------------------------ + +MODELE="$(lire_dmi product_name)" +MARQUE="$(lire_dmi sys_vendor)" +SERIE="$(lire_dmi product_serial)" +# Le numéro de série identifie un objet, pas une personne, mais il n'a rien à +# faire dans un fichier qu'on partage : on n'en garde que les quatre derniers +# caractères, assez pour distinguer deux machines du même modèle. +SERIE="${SERIE: -4}" +BIOS="$(lire_dmi bios_date)" + +PROCESSEUR="$(awk -F': ' '/^model name/{print $2; exit}' /proc/cpuinfo)" +COEURS="$(nproc 2>/dev/null || echo '?')" +MEMOIRE_KO="$(awk '/^MemTotal/{print $2}' /proc/meminfo)" + +SYSTEME="" +if [ -r /etc/os-release ]; then + SYSTEME="$(. /etc/os-release; echo "${PRETTY_NAME:-$NAME $VERSION}")" +fi +NOYAU="$(uname -r)" +ARCHITECTURE="$(uname -m)" + +# Sommes-nous sur la clé USB, ou sur le système installé ? +# +# La distinction n'est pas cosmétique : les deux relevés répondent à deux +# questions différentes. Celui de la session live dit « peut-on installer sur +# cette machine ? » et se fait avant d'effacer quoi que ce soit. Celui d'après +# dit « qu'avons-nous installé, et sur quoi ». Les garder séparés permet de +# comparer — un disque qui allait bien le matin et qui se plaint le soir, cela +# se voit. +# +# Une session live monte sa racine en overlay au-dessus d'un squashfs, et garde +# un /cdrom ou un /rofs. Aucun de ces signes n'existe sur un système installé. +MOMENT="post-installation" +if [ -d /rofs ] || [ -d /cdrom ] \ + || findmnt -n -o FSTYPE / 2>/dev/null | grep -qE '^(overlay|aufs|squashfs)$' \ + || grep -qE '(^| )(boot=casper|boot=live)( |$)' /proc/cmdline 2>/dev/null; then + MOMENT="live" +fi + +# --- La mémoire, barrette par barrette ------------------------------------ +# +# La question qu'on pose vraiment devant une machine lente : peut-on en +# ajouter ? Elle demande trois réponses — combien de connecteurs, combien +# d'occupés, et de quel type. Un slot libre en DDR3 ne se remplit pas avec ce +# qu'on a dans le tiroir. +# +# dmidecode lit cela dans la table du BIOS. Il est présent par défaut sur +# Debian, Ubuntu et Mint ; s'il manque, on ne prétend rien plutôt que de +# deviner. + +BARRETTES="[" +SLOTS_TOTAL=0 +SLOTS_PRIS=0 +TYPE_MEMOIRE="" +VITESSE_MEMOIRE="" +if command -v dmidecode >/dev/null 2>&1 && [ "$(id -u)" = "0" ]; then + premier=1 + # Chaque bloc « Memory Device » décrit un connecteur, occupé ou non. + while IFS= read -r bloc; do + emplacement="$(printf '%s' "$bloc" | sed -n 's/.*|Locator:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + taille="$(printf '%s' "$bloc" | sed -n 's/.*|Size:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + type_="$(printf '%s' "$bloc" | sed -n 's/.*|Type:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + vitesse="$(printf '%s' "$bloc" | sed -n 's/.*|Speed:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + forme="$(printf '%s' "$bloc" | sed -n 's/.*|Form Factor:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + reference="$(printf '%s' "$bloc" | sed -n 's/.*|Part Number:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + # La vitesse configurée est celle à laquelle la barrette travaille + # vraiment ; « Speed » n'est que ce dont elle serait capable. L'écart + # entre les deux explique bien des machines qui « rament » sans raison + # apparente : une DDR4-3200 bridée à 2133 par la carte mère, ou deux + # barrettes dépareillées qui s'alignent sur la plus lente. + vitesse_reelle="$(printf '%s' "$bloc" | sed -n 's/.*|Configured Memory Speed:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + fabricant="$(printf '%s' "$bloc" | sed -n 's/.*|Manufacturer:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + rang="$(printf '%s' "$bloc" | sed -n 's/.*|Rank:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + tension="$(printf '%s' "$bloc" | sed -n 's/.*|Configured Voltage:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + largeur="$(printf '%s' "$bloc" | sed -n 's/.*|Data Width:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + detail="$(printf '%s' "$bloc" | sed -n 's/.*|Type Detail:\([^|]*\).*/\1/p' | sed 's/^ *//; s/ *$//')" + [ -z "$emplacement" ] && continue + + SLOTS_TOTAL=$(( SLOTS_TOTAL + 1 )) + case "$taille" in + *"No Module"*|""|"Unknown") taille="" ;; + *) SLOTS_PRIS=$(( SLOTS_PRIS + 1 )) + [ -z "$TYPE_MEMOIRE" ] && [ "$type_" != "Unknown" ] && TYPE_MEMOIRE="$type_" + # Deux barrettes de vitesses différentes s'alignent sur la plus + # lente : c'est celle-là qu'il faut retenir pour la machine. + if [ -n "$vitesse_reelle" ] && [ "$vitesse_reelle" != "Unknown" ]; then + nombre="${vitesse_reelle%% *}" + case "$nombre" in + ''|*[!0-9]*) ;; + *) if [ -z "$VITESSE_MEMOIRE" ] || [ "$nombre" -lt "$VITESSE_MEMOIRE" ]; then + VITESSE_MEMOIRE="$nombre" + fi ;; + esac + fi ;; + esac + + [ "$premier" = 0 ] && BARRETTES="$BARRETTES," + premier=0 + BARRETTES="$BARRETTES{$(champ emplacement "$emplacement"),$(champ taille "$taille"),\ +$(champ type "$type_"),$(champ vitesse "$vitesse"),$(champ vitesse_reelle "$vitesse_reelle"),\ +$(champ forme "$forme"),$(champ reference "$reference"),$(champ fabricant "$fabricant"),\ +$(champ rang "$rang"),$(champ tension "$tension"),$(champ largeur "$largeur"),\ +$(champ detail "$detail")}" + done < <(dmidecode -t memory 2>/dev/null \ + | awk '/^Memory Device$/{bloc=""; dedans=1; next} + /^$/{if (dedans && bloc != "") print bloc; dedans=0; bloc=""} + dedans {gsub(/^[ \t]+/, ""); bloc = bloc "|" $0} + END{if (dedans && bloc != "") print bloc}') +fi +BARRETTES="$BARRETTES]" + +# --- Les disques ----------------------------------------------------------- +# +# lsblk donne la taille et le type ; le drapeau « rotational » du noyau +# distingue un disque mécanique d'un SSD sans rien installer. C'est la +# distinction qui compte : une machine lente avec un disque mécanique de dix +# ans ne s'arrangera pas avec un système plus léger. + +DISQUES="[" +premier=1 +for chemin in /sys/block/*; do + nom="$(basename "$chemin")" + case "$nom" in loop*|ram*|sr*|zram*|dm-*) continue ;; esac + [ -r "$chemin/size" ] || continue + taille=$(( $(cat "$chemin/size") * 512 )) + [ "$taille" -lt 1000000000 ] && continue # moins d'un Go : pas un disque + rotatif="$(cat "$chemin/queue/rotational" 2>/dev/null || echo '?')" + modele="$(cat "$chemin/device/model" 2>/dev/null | sed 's/ *$//')" + + # L'état SMART, s'il est lisible. Sans smartctl on ne prétend rien. + sante="" + reallocations="" + heures="" + if command -v smartctl >/dev/null 2>&1; then + sortie="$(smartctl -H -A "/dev/$nom" 2>/dev/null)" + sante="$(printf '%s' "$sortie" | grep -iE 'overall-health|SMART Health' \ + | sed 's/.*: *//' | head -1)" + reallocations="$(printf '%s' "$sortie" \ + | awk '/Reallocated_Sector_Ct|Reallocated_Event/{print $10; exit}')" + heures="$(printf '%s' "$sortie" | awk '/Power_On_Hours/{print $10; exit}')" + fi + + [ "$premier" = 0 ] && DISQUES="$DISQUES," + premier=0 + DISQUES="$DISQUES{$(champ nom "$nom"),$(champ modele "$modele"),\ +\"octets\":$taille,$(champ type "$([ "$rotatif" = 1 ] && echo mécanique || echo SSD)"),\ +$(champ sante "$sante"),$(champ reallocations "$reallocations"),$(champ heures "$heures")}" +done +DISQUES="$DISQUES]" + +# L'occupation, telle qu'elle est maintenant. +OCCUPATION="[" +premier=1 +while read -r point taille utilise dispo pourcent; do + [ "$premier" = 0 ] && OCCUPATION="$OCCUPATION," + premier=0 + OCCUPATION="$OCCUPATION{$(champ point "$point"),\"taille\":$taille,\ +\"utilise\":$utilise,$(champ pourcent "$pourcent")}" +done < <(df -B1 --output=target,size,used,avail,pcent 2>/dev/null \ + | awk 'NR>1 && $2+0 > 1000000000 {print $1, $2, $3, $4, $5}') +OCCUPATION="$OCCUPATION]" + +# --- Le rapport ------------------------------------------------------------ + +# L'adresse matérielle de l'interface par laquelle on sort — celle que le +# serveur a vue en donnant le bail, donc celle qui permettra de rapprocher +# cette fiche de la machine servie. « ip -o link » a failli nous donner +# l'adresse de diffusion, qui est la même pour tout le monde. +INTERFACE="$(ip -o -4 route show default 2>/dev/null | awk '{print $5; exit}')" +[ -z "$INTERFACE" ] && INTERFACE="$(ls /sys/class/net 2>/dev/null \ + | grep -v '^lo$' | head -1)" +MAC="$(cat "/sys/class/net/$INTERFACE/address" 2>/dev/null)" + +RAPPORT="{$(champ machine "$(hostname)"),$(champ mac "$MAC"),\ +$(champ marque "$MARQUE"),$(champ modele "$MODELE"),$(champ serie "$SERIE"),\ +$(champ bios "$BIOS"),$(champ processeur "$PROCESSEUR"),\"coeurs\":${COEURS:-0},\ +\"memoire_ko\":${MEMOIRE_KO:-0},$(champ systeme "$SYSTEME"),$(champ noyau "$NOYAU"),\ +$(champ architecture "$ARCHITECTURE"),$(champ moment "$MOMENT"),\ +$(champ type_memoire "$TYPE_MEMOIRE"),\ +$(champ vitesse_memoire "$VITESSE_MEMOIRE"),\ +\"slots_total\":$SLOTS_TOTAL,\"slots_pris\":$SLOTS_PRIS,\"barrettes\":$BARRETTES,\ +\"disques\":$DISQUES,\"occupation\":$OCCUPATION}" + +if [ "$MONTRER" = 1 ]; then + if command -v python3 >/dev/null 2>&1; then + printf '%s' "$RAPPORT" | python3 -m json.tool 2>/dev/null || printf '%s\n' "$RAPPORT" + else + printf '%s\n' "$RAPPORT" + fi + exit 0 +fi + +[ "$CHECK" = 1 ] && printf " %sLa fiche est envoyée à alpicache…%s\n\n" "$D" "$Z" +echo "Relevé : $([ "$MOMENT" = live ] && echo "session live, avant installation" \ + || echo "système installé")" +echo "Machine : $MARQUE $MODELE" +echo "Processeur : $PROCESSEUR ($COEURS cœurs)" +if [ "$SLOTS_TOTAL" -gt 0 ]; then + echo "Mémoire : $(( MEMOIRE_KO / 1024 )) Mo ${TYPE_MEMOIRE:+$TYPE_MEMOIRE }\ +${VITESSE_MEMOIRE:+à $VITESSE_MEMOIRE MT/s }· \ +$SLOTS_PRIS connecteur(s) occupé(s) sur $SLOTS_TOTAL" +else + echo "Mémoire : $(( MEMOIRE_KO / 1024 )) Mo" +fi +echo "Système : $SYSTEME" +echo + +# --- Le verdict ------------------------------------------------------------ +# +# Trois réponses possibles, et une seule question : peut-on installer sur cette +# machine ? Les seuils viennent de ce qu'on rencontre en install party, pas +# d'un idéal — une machine de dix ans avec 4 Go et un disque sain fait très +# bien l'affaire, et c'est souvent celle qu'on nous apporte. + +# Depuis une session live, le verdict est tout l'intérêt de l'exercice : on +# le rend sans qu'on ait à le demander. +[ "$MOMENT" = "live" ] && CHECK=1 + +if [ "$CHECK" = 1 ]; then + GRAVITE=0 # 0 rien à signaler · 1 à surveiller · 2 ne pas installer + REMARQUES="" + + note() { + [ "$1" -gt "$GRAVITE" ] && GRAVITE="$1" + REMARQUES="$REMARQUES$2"$'\n' + } + + # Les disques. C'est pour eux qu'on fait ce contrôle. + for chemin in /sys/block/*; do + nom="$(basename "$chemin")" + case "$nom" in loop*|ram*|sr*|zram*|dm-*) continue ;; esac + [ -r "$chemin/size" ] || continue + taille=$(( $(cat "$chemin/size") * 512 )) + [ "$taille" -lt 1000000000 ] && continue + + if ! command -v smartctl >/dev/null 2>&1; then + note 1 " ${O}?${Z} état des disques inconnu — smartmontools n'est pas installé. + ${D}sudo apt install -y smartmontools${Z} ${D}(il passera par le cache)${Z}" + break + fi + + sortie="$(smartctl -H -A "/dev/$nom" 2>/dev/null)" + if printf '%s' "$sortie" | grep -qiE 'overall-health.*(FAILED|FAILING)'; then + note 2 " ${R}✗${Z} ${B}/dev/$nom se déclare défaillant${Z} — ne pas installer dessus." + fi + for attribut in Reallocated_Sector_Ct Current_Pending_Sector Offline_Uncorrectable; do + valeur="$(printf '%s' "$sortie" | awk -v a="$attribut" '$2 == a {print $10; exit}')" + case "$valeur" in ''|*[!0-9]*) continue ;; esac + [ "$valeur" = "0" ] && continue + if [ "$valeur" -ge 50 ]; then + note 2 " ${R}✗${Z} /dev/$nom : $valeur $attribut — disque à remplacer." + else + note 1 " ${O}!${Z} /dev/$nom : $valeur $attribut — à surveiller." + fi + done + heures="$(printf '%s' "$sortie" | awk '/Power_On_Hours/{print $10; exit}')" + rotatif="$(cat "$chemin/queue/rotational" 2>/dev/null)" + case "$heures" in + ''|*[!0-9]*) ;; + *) if [ "$rotatif" = "1" ] && [ "$heures" -gt 43800 ]; then + note 1 " ${O}!${Z} /dev/$nom : disque mécanique de $(( heures / 8760 )) ans. + ${D}Un SSD changerait tout sur cette machine.${Z}" + fi ;; + esac + if [ "$taille" -lt 32000000000 ]; then + note 1 " ${O}!${Z} /dev/$nom ne fait que $(( taille / 1000000000 )) Go — c'est juste." + fi + done + + # La mémoire, le processeur. + MEMOIRE_MO=$(( MEMOIRE_KO / 1024 )) + if [ "$MEMOIRE_MO" -lt 2048 ]; then + note 2 " ${R}✗${Z} ${B}${MEMOIRE_MO} Mo de mémoire${Z} — trop peu pour Mint. + ${D}Envisager une version légère, ou ajouter de la mémoire.${Z}" + elif [ "$MEMOIRE_MO" -lt 4096 ]; then + libres=$(( SLOTS_TOTAL - SLOTS_PRIS )) + note 1 " ${O}!${Z} ${MEMOIRE_MO} Mo de mémoire — Mint tournera, mais sans aisance.$( + [ "$libres" -gt 0 ] && printf '\n %s' "${D}$libres connecteur(s) ${TYPE_MEMOIRE} libre(s) : la machine est extensible.${Z}")" + fi + if [ "$ARCHITECTURE" != "x86_64" ] && [ "$ARCHITECTURE" != "aarch64" ]; then + note 2 " ${R}✗${Z} processeur ${ARCHITECTURE} — les versions 64 bits ne s'installeront pas." + fi + + echo + case "$GRAVITE" in + 0) printf " %s%s BON POUR L'INSTALLATION %s\n" "$B$V" "✓" "$Z" ;; + 1) printf " %s%s INSTALLATION POSSIBLE, AVEC DES RÉSERVES %s\n" "$B$O" "!" "$Z" ;; + 2) printf " %s%s NE PAS INSTALLER EN L'ÉTAT %s\n" "$B$R" "✗" "$Z" ;; + esac + echo + [ -n "$REMARQUES" ] && printf "%s\n" "$REMARQUES" + [ "$GRAVITE" = 2 ] && printf " %sEn parler au propriétaire avant de toucher à quoi que ce soit.%s\n\n" "$D" "$Z" +fi + +if command -v curl >/dev/null 2>&1; then + reponse="$(printf '%s' "$RAPPORT" | curl -s --max-time 15 \ + -H 'Content-Type: application/json' --data-binary @- "$SERVEUR/fiche")" +elif command -v wget >/dev/null 2>&1; then + reponse="$(printf '%s' "$RAPPORT" | wget -qO- --timeout=15 \ + --header='Content-Type: application/json' --post-file=- "$SERVEUR/fiche")" +else + echo "Ni curl ni wget : la fiche n'a pas pu être envoyée." >&2 + exit 1 +fi + +if [ -n "$reponse" ]; then + echo "$reponse" +else + echo "Le serveur n'a pas répondu — la fiche n'est pas enregistrée." >&2 + exit 1 +fi diff --git a/code/linux/linux-mint/install.sh b/code/linux/linux-mint/install.sh index c67e8bc..30cf719 100644 --- a/code/linux/linux-mint/install.sh +++ b/code/linux/linux-mint/install.sh @@ -1,4 +1,26 @@ #!/bin/bash + +################################################## +# Fiche matérielle pour le serveur de l'install party +# +# Placée au début, et non à la fin : si un disque faiblit, autant le savoir +# avant d'avoir passé une heure à installer dessus — c'est le moment où l'on +# peut encore en parler à son propriétaire. Et si l'installation s'arrête en +# route, on a tout de même la fiche. +# +# Le relevé est le script voisin du dépôt, lu et versionné comme celui-ci : +# rien n'est téléchargé ni exécuté depuis le réseau. S'il n'est pas là, on +# passe — la machine d'un particulier qui rejoue ce script chez elle ne doit +# rien avoir à faire de tout ceci. +# +# C'est la seconde passe, celle que « sudo » relance, qui l'exécute : +# dmidecode et smartctl demandent les droits d'administration. +################################################## + +if [ "$(id -u)" = "0" ] && [ -x "$(dirname "$0")/alpi-fiche.sh" ]; then + "$(dirname "$0")/alpi-fiche.sh" || true +fi + # Script de post-installation pour Linux Mint 22.3 # testé le 25/03/2026 diff --git a/docs/guides/linux-mint-depuis-windows.md b/docs/guides/linux-mint-depuis-windows.md index e98ac5f..17b1aba 100644 --- a/docs/guides/linux-mint-depuis-windows.md +++ b/docs/guides/linux-mint-depuis-windows.md @@ -368,6 +368,32 @@ les vérifications qui le nécessitent, puis passe en revue : Il finit par deux listes : *à régler avant d'installer*, et *à savoir*. +### Relever la fiche de la machine + +En install party, un second script complète l'état des lieux : il relève la **fiche +matérielle** — modèle, processeur, mémoire et ses connecteurs, disques et leur santé — +et l'envoie au serveur de l'association. + +```bash +wget http://10.0.0.1/alpi-fiche.sh && chmod +x alpi-fiche.sh && sudo ./alpi-fiche.sh --check +``` + +Lancé depuis la session live, il rend en plus un verdict en trois états : **bon pour +l'installation**, **possible avec des réserves**, ou **ne pas installer en l'état** — un +disque qui se déclare défaillant, des secteurs réalloués, moins de 2 Go de mémoire, un +processeur 32 bits. C'est le dernier moment où l'on peut encore renoncer et en parler au +propriétaire. + +Le même script, relancé après l'installation (sans `--check`), enregistre ce qui a +finalement été installé. Les deux relevés se comparent : un disque qui allait bien le +matin et se plaint le soir, cela se voit. + +!!! warning "Dites-le à la personne dont vous installez la machine" + La fiche part sur le réseau local, vers le serveur de l'association, et n'en sort + pas. Elle ne contient ni fichiers, ni historique, ni nom d'utilisateur — mais elle + contient le **nom de la machine**, souvent un prénom, et l'**adresse matérielle** de + la carte réseau. C'est son ordinateur : cela se demande, cela ne se suppose pas. + !!! danger "BitLocker : ne touchez à rien" Si le script annonce une partition chiffrée par BitLocker, **arrêtez-vous là**. Le chiffrement doit être suspendu depuis Windows — *Paramètres → Confidentialité et From 3d6635fd529c1edcb82bf52210ecf2758d22899c Mon Sep 17 00:00:00 2001 From: Alpinux Date: Thu, 24 Sep 2026 22:38:04 +0200 Subject: [PATCH 2/5] alpi-fiche : ne plus prendre un /cdrom vide pour une session live MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'installateur de Mint laisse derrière lui un /cdrom vide sur la machine installée. La détection s'y fiait, et déclarait donc « session live » des machines parfaitement installées — ce qui fausse aussi le chronométrage, puisqu'il se calcule entre les deux relevés. Restent les deux signes qui ne mentent pas : la racine montée en overlay ou squashfs, et boot=casper sur la ligne du noyau. /rofs disparaît avec /cdrom, n'ayant pas plus de valeur probante. Constat de la session alpicache, sur une machine réelle. Je n'ai pas pu le reproduire moi-même — la machine d'essai ne se laissait pas piloter — mais le changement est sûr dans les deux sens : ces deux signes-là sont présents sur toute session live, et aucun ne l'est sur un système installé. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo --- code/linux/linux-mint/alpi-fiche.sh | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/code/linux/linux-mint/alpi-fiche.sh b/code/linux/linux-mint/alpi-fiche.sh index f9d0b45..19082bc 100755 --- a/code/linux/linux-mint/alpi-fiche.sh +++ b/code/linux/linux-mint/alpi-fiche.sh @@ -106,11 +106,17 @@ ARCHITECTURE="$(uname -m)" # comparer — un disque qui allait bien le matin et qui se plaint le soir, cela # se voit. # -# Une session live monte sa racine en overlay au-dessus d'un squashfs, et garde -# un /cdrom ou un /rofs. Aucun de ces signes n'existe sur un système installé. +# Une session live monte sa racine en overlay au-dessus d'un squashfs, et +# démarre avec « boot=casper » sur la ligne du noyau. Ces deux signes-là ne +# mentent pas. +# +# L'existence d'un /cdrom, en revanche, n'est pas un signe : l'installateur de +# Mint en laisse un derrière lui, vide, sur la machine installée. S'y fier +# faisait déclarer « session live » des machines parfaitement installées — et +# comme le chronométrage se calcule entre les deux relevés, la mesure partait +# avec. Constat de la session alpicache, sur une machine réelle. MOMENT="post-installation" -if [ -d /rofs ] || [ -d /cdrom ] \ - || findmnt -n -o FSTYPE / 2>/dev/null | grep -qE '^(overlay|aufs|squashfs)$' \ +if findmnt -n -o FSTYPE / 2>/dev/null | grep -qE '^(overlay|aufs|squashfs)$' \ || grep -qE '(^| )(boot=casper|boot=live)( |$)' /proc/cmdline 2>/dev/null; then MOMENT="live" fi From c428d1d040661c87d8624d2110eda97e12777ab8 Mon Sep 17 00:00:00 2001 From: Alpinux Date: Thu, 24 Sep 2026 22:41:19 +0200 Subject: [PATCH 3/5] =?UTF-8?q?Reprendre=20le=20relev=C3=A9=20=C3=A9prouv?= =?UTF-8?q?=C3=A9=20sur=20machine,=20avec=20ses=20trois=20correctifs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La session alpicache a essayé le relevé sur une machine réelle et en a tiré trois défauts, tous vérifiés là-bas : Le verdict ne sortait qu'en session live. Relancé après installation, le relevé ne disait donc rien de l'état des disques — précisément ce dont on voulait parler au propriétaire pendant qu'il est encore là. Il sort maintenant à tous les coups, sauf en mode --montrer. Un disque mécanique n'était signalé qu'au-delà de cinq ans de marche, ce qui laissait passer sans un mot un disque de trois ans — alors que c'est le premier frein d'une machine par ailleurs correcte. Il se signale désormais quel que soit son âge, qui est simplement rappelé. Le verdict défilait sans que personne ne le lise : trois secondes quand tout va bien, vingt avec des réserves, et il attend une touche — deux minutes au plus — quand il dit de ne pas installer. Jamais indéfiniment, ni du tout sans terminal : le script tourne aussi appelé par install.sh. La détection de session live est reprise dans sa version, plus fine que celle que j'avais faite : /cdrom et /rofs comptent de nouveau, mais seulement montés, et pour /cdrom seulement avec un système de fichiers de média. Un répertoire vide ne prouve plus rien. Seul l'en-tête reste de moi : le script se prend dans ce dépôt, pas sur le serveur. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo --- code/linux/linux-mint/alpi-fiche.sh | 141 ++++++++++++++++++++-------- 1 file changed, 104 insertions(+), 37 deletions(-) diff --git a/code/linux/linux-mint/alpi-fiche.sh b/code/linux/linux-mint/alpi-fiche.sh index 19082bc..c457ccc 100755 --- a/code/linux/linux-mint/alpi-fiche.sh +++ b/code/linux/linux-mint/alpi-fiche.sh @@ -1,7 +1,8 @@ #!/bin/bash # Relève la fiche d'une machine, et l'envoie au serveur de l'install party. # -# À récupérer depuis le dépôt du wiki, comme les autres scripts d'Alpinux : +# Ce script vit dans le dépôt du wiki, à côté d'install.sh : il est lu et +# versionné comme lui, et c'est de là qu'il faut le prendre. # # wget https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-wiki/raw/branch/main/code/linux/linux-mint/alpi-fiche.sh # chmod +x alpi-fiche.sh @@ -18,25 +19,22 @@ # mémoire et un disque mécanique qui commence à réallouer des secteurs répond # à la question avant de l'avoir posée. # -# Ce qui est envoyé, et rien d'autre : le modèle et la marque, les quatre -# derniers caractères du numéro de série, la date du BIOS, le processeur, la -# mémoire et le détail de ses connecteurs, les disques avec leur santé SMART, -# l'occupation des systèmes de fichiers, le système installé, l'architecture, -# l'adresse matérielle de l'interface réseau et le nom de la machine. +# Ce qui est envoyé : le modèle, le processeur, la mémoire, les disques et +# leur santé, le système installé. # -# Ni nom d'utilisateur, ni fichiers, ni historique, ni adresse IP publique. +# Deux de ces champs touchent à la personne, et il faut le dire plutôt que de +# prétendre le contraire — la première version de cet en-tête annonçait « rien +# qui touche à la personne », ce qui était faux : # -# Deux réserves à connaître avant de s'en servir, parce qu'elles concernent -# des gens et pas des machines : +# · le NOM DE LA MACHINE, choisi pendant l'installation, contient très +# souvent un prénom ; +# · l'ADRESSE MATÉRIELLE de la carte réseau identifie durablement l'appareil. # -# - le **nom de la machine** est celui choisi pendant l'installation, et il -# contient souvent un prénom (« portable-jean ») ; -# - l'**adresse matérielle** identifie durablement la carte réseau ; elle -# sert ici à rapprocher la fiche du bail DHCP, rien de plus. -# -# Le relevé part en clair sur le réseau local de la salle, vers le serveur de -# l'association, et n'en sort pas. Prévenez-en la personne dont vous installez -# la machine : c'est la sienne. +# Les deux servent : le nom pour s'y retrouver, l'adresse pour rapprocher la +# fiche du bail DHCP. Mais ils appartiennent à la personne dont on installe +# l'ordinateur — il faut le lui dire. Ne sont en revanche jamais envoyés : son +# nom d'utilisateur, ses fichiers, son historique, ni rien de ce qu'elle a +# installé. Le numéro de série est tronqué à quatre caractères. # # alpi-fiche relève et envoie # alpi-fiche --montrer relève et affiche, sans rien envoyer @@ -49,6 +47,11 @@ set -u +# smartctl et dmidecode vivent dans /usr/sbin, que le PATH d'un shell ordinaire +# ne contient pas toujours — un « command -v » y échouait alors que le binaire +# était là. +PATH="$PATH:/usr/sbin:/sbin" + SERVEUR="${ALPI_SERVEUR:-http://alpicache.lan}" MONTRER=0 CHECK=0 @@ -63,6 +66,41 @@ else V=""; O=""; R=""; B=""; D=""; Z="" fi +# Ce dont le relevé a besoin, et qu'il installe lui-même plutôt que de rendre +# un verdict à trous. +# +# « état des disques inconnu » n'aide personne : c'est précisément la réponse +# qu'on était venu chercher. Les deux paquets pèsent moins de deux mégaoctets, +# ils sont dans le cache, et l'installation prend deux secondes sur le réseau +# de l'install party. On ne le fait qu'en root, qu'avec apt, et sans rien +# demander d'autre. +completer_outils() { + [ "$(id -u)" = "0" ] || return 0 + command -v apt-get >/dev/null 2>&1 || return 0 + + local manquants="" + command -v smartctl >/dev/null 2>&1 || manquants="$manquants smartmontools" + command -v dmidecode >/dev/null 2>&1 || manquants="$manquants dmidecode" + [ -z "$manquants" ] && return 0 + + echo "==> Installation de ce qui manque au relevé :$manquants" + # Par le cache s'il répond, directement sinon. « -qq » pour ne pas noyer + # l'écran : ce n'est pas l'objet de la manœuvre. + local proxy="" + if command -v wget >/dev/null 2>&1 \ + && wget -q --spider --timeout=3 "$SERVEUR/" 2>/dev/null; then + proxy="-o Acquire::http::Proxy=http://apt-proxy:3142" + fi + DEBIAN_FRONTEND=noninteractive apt-get $proxy -qq -y install $manquants \ + >/dev/null 2>&1 \ + || echo " (échec — le relevé se fera sans)" + # Ce qui vient d'arriver est dans /usr/sbin, que le PATH d'un shell + # ordinaire ne contient pas toujours. + PATH="$PATH:/usr/sbin:/sbin" +} + +completer_outils + lire_dmi() { cat "/sys/class/dmi/id/$1" 2>/dev/null | tr -d '\n' | sed 's/^ *//; s/ *$//' } @@ -106,18 +144,20 @@ ARCHITECTURE="$(uname -m)" # comparer — un disque qui allait bien le matin et qui se plaint le soir, cela # se voit. # -# Une session live monte sa racine en overlay au-dessus d'un squashfs, et -# démarre avec « boot=casper » sur la ligne du noyau. Ces deux signes-là ne -# mentent pas. +# Le signe qui ne trompe pas est la racine : une session live la monte en +# overlay au-dessus d'un squashfs, ce qu'aucun système installé ne fait. S'y +# ajoute « boot=casper » ou « boot=live » sur la ligne de commande du noyau. # -# L'existence d'un /cdrom, en revanche, n'est pas un signe : l'installateur de -# Mint en laisse un derrière lui, vide, sur la machine installée. S'y fier -# faisait déclarer « session live » des machines parfaitement installées — et -# comme le chronométrage se calcule entre les deux relevés, la mesure partait -# avec. Constat de la session alpicache, sur une machine réelle. +# L'existence de /cdrom ou de /rofs, elle, ne prouve rien : l'installateur de +# Mint laisse un /cdrom vide derrière lui, et la première version de ce script +# a donc déclaré « session live » une machine parfaitement installée. On ne les +# retient que s'ils sont réellement montés. MOMENT="post-installation" if findmnt -n -o FSTYPE / 2>/dev/null | grep -qE '^(overlay|aufs|squashfs)$' \ - || grep -qE '(^| )(boot=casper|boot=live)( |$)' /proc/cmdline 2>/dev/null; then + || grep -qE '(^| )boot=(casper|live)( |$)' /proc/cmdline 2>/dev/null \ + || mountpoint -q /rofs 2>/dev/null \ + || { mountpoint -q /cdrom 2>/dev/null \ + && findmnt -n -o FSTYPE /cdrom 2>/dev/null | grep -qE '^(iso9660|squashfs)$'; }; then MOMENT="live" fi @@ -296,9 +336,11 @@ echo # d'un idéal — une machine de dix ans avec 4 Go et un disque sain fait très # bien l'affaire, et c'est souvent celle qu'on nous apporte. -# Depuis une session live, le verdict est tout l'intérêt de l'exercice : on -# le rend sans qu'on ait à le demander. -[ "$MOMENT" = "live" ] && CHECK=1 +# Le verdict sort toujours, live ou non. C'est l'information qu'on est venu +# chercher : la première version ne le rendait qu'en session live, et le +# relevé d'après installation défilait sans rien dire de l'état des disques — +# précisément ce dont on voulait parler au propriétaire. +[ "$MONTRER" = 1 ] || CHECK=1 if [ "$CHECK" = 1 ]; then GRAVITE=0 # 0 rien à signaler · 1 à surveiller · 2 ne pas installer @@ -339,13 +381,23 @@ if [ "$CHECK" = 1 ]; then done heures="$(printf '%s' "$sortie" | awk '/Power_On_Hours/{print $10; exit}')" rotatif="$(cat "$chemin/queue/rotational" 2>/dev/null)" - case "$heures" in - ''|*[!0-9]*) ;; - *) if [ "$rotatif" = "1" ] && [ "$heures" -gt 43800 ]; then - note 1 " ${O}!${Z} /dev/$nom : disque mécanique de $(( heures / 8760 )) ans. - ${D}Un SSD changerait tout sur cette machine.${Z}" - fi ;; - esac + # Un disque mécanique se signale toujours, et pas seulement quand il + # est vieux. C'est le premier frein d'une machine par ailleurs + # correcte : remplacer un plateau par un SSD fait plus pour elle que + # n'importe quel réglage, et c'est le genre de conseil qu'on donne + # pendant qu'on a la machine ouverte devant soi. + if [ "$rotatif" = "1" ]; then + age="" + case "$heures" in + ''|*[!0-9]*) ;; + *) annees=$(( heures / 8760 )) + [ "$annees" -gt 0 ] && age=" de $annees an$([ "$annees" -gt 1 ] && echo s)" + [ "$annees" = "0" ] && age=" ($heures h de marche)" ;; + esac + note 1 " ${O}!${Z} /dev/$nom est un ${B}disque mécanique${Z}$age. + ${D}Le remplacer par un SSD ferait plus pour cette machine que + tout le reste réuni — démarrage, ouverture des programmes, réactivité.${Z}" + fi if [ "$taille" -lt 32000000000 ]; then note 1 " ${O}!${Z} /dev/$nom ne fait que $(( taille / 1000000000 )) Go — c'est juste." fi @@ -373,7 +425,22 @@ if [ "$CHECK" = 1 ]; then esac echo [ -n "$REMARQUES" ] && printf "%s\n" "$REMARQUES" - [ "$GRAVITE" = 2 ] && printf " %sEn parler au propriétaire avant de toucher à quoi que ce soit.%s\n\n" "$D" "$Z" + + # Une pause, sans quoi tout cela défile et personne ne le lit. Sa durée + # suit la gravité, et l'on n'attend jamais indéfiniment : le script tourne + # aussi sans personne devant l'écran, appelé par install.sh. + if [ -t 0 ]; then + case "$GRAVITE" in + 2) printf " %sEn parler au propriétaire AVANT de toucher à quoi que ce soit.%s\n" "$D" "$Z" + printf " %sEntrée pour continuer malgré tout%s " "$B" "$Z" + read -r -t "${ALPI_PAUSE_ALERTE:-120}" _ || echo ;; + 1) printf " %sEntrée pour continuer (reprise automatique dans %ss)%s " \ + "$D" "${ALPI_PAUSE:-20}" "$Z" + read -r -t "${ALPI_PAUSE:-20}" _ || echo ;; + *) sleep "${ALPI_PAUSE_OK:-3}" ;; + esac + echo + fi fi if command -v curl >/dev/null 2>&1; then From 1f2bb9f6f4cd89e330ee0a443714aa160057a91f Mon Sep 17 00:00:00 2001 From: Alpinux Date: Thu, 24 Sep 2026 22:56:13 +0200 Subject: [PATCH 4/5] Faire voyager le verdict avec la fiche, au lieu de le refaire au serveur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sur une machine réelle, un HP 250 G4 affichait devant son propriétaire « NE PAS INSTALLER EN L'ÉTAT — 976 secteurs en attente », et apparaissait « sain » sur le tableau de bord. La fiche ne transportait que le compteur de secteurs réalloués, nul sur ce disque, et le serveur rejugeait là-dessus. Deux juges, deux verdicts, et c'est le rassurant qu'on lisait. Le verdict part désormais avec la fiche — champs « verdict » et « motifs », ces derniers débarrassés de leurs couleurs — et le serveur les affiche sans rien recalculer. Un seul juge, celui qui a vu le disque. Les trois compteurs SMART cessent d'être confondus : les secteurs réalloués disent l'usure et se comptent, ceux en attente et les illisibles disent une panne en cours, et un seul suffit à déclencher l'alerte. Ce ne sont pas des secteurs fatigués mais des données que le disque n'arrive plus à relire. Les deux nouveaux compteurs voyagent aussi dans la fiche. Le relevé se chronomètre enfin, sur la collecte seule : la pause du verdict attend quelqu'un devant l'écran, la mesurer reviendrait à chronométrer la personne. Constat, correctifs et version de référence : session alpicache. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo --- code/linux/linux-mint/alpi-fiche.sh | 231 ++++++++++++++++++++++++++-- 1 file changed, 219 insertions(+), 12 deletions(-) diff --git a/code/linux/linux-mint/alpi-fiche.sh b/code/linux/linux-mint/alpi-fiche.sh index c457ccc..e62ce05 100755 --- a/code/linux/linux-mint/alpi-fiche.sh +++ b/code/linux/linux-mint/alpi-fiche.sh @@ -47,6 +47,18 @@ set -u +# Le temps que met la collecte, en millisecondes. +# +# Une machine n'est relevée qu'une fois quand elle n'a pas connu de session +# live : il n'y a alors pas d'écart entre deux passages à mesurer. Cette durée- +# ci existe toujours, et elle se compare — elle dit ce que la machine met à +# s'interroger elle-même : lire le SMART de ses disques, réveiller ceux qui +# dorment, énumérer ses barrettes. Un plateau qui rame se voit là. +# +# On arrête le chronomètre à la fin de la collecte, avant le verdict : celui-ci +# attend une touche, et l'on mesurerait la personne devant l'écran. +DEBUT_NS="$(date +%s%N 2>/dev/null || echo 0)" + # smartctl et dmidecode vivent dans /usr/sbin, que le PATH d'un shell ordinaire # ne contient pas toujours — un « command -v » y échouait alors que le binaire # était là. @@ -233,6 +245,8 @@ $(champ detail "$detail")}" fi BARRETTES="$BARRETTES]" +TABLE_DISQUES="" + # --- Les disques ----------------------------------------------------------- # # lsblk donne la taille et le type ; le drapeau « rotational » du noyau @@ -254,21 +268,81 @@ for chemin in /sys/block/*; do # L'état SMART, s'il est lisible. Sans smartctl on ne prétend rien. sante="" reallocations="" + pending="" + illisibles="" heures="" + usure="" + ecrit="" if command -v smartctl >/dev/null 2>&1; then sortie="$(smartctl -H -A "/dev/$nom" 2>/dev/null)" sante="$(printf '%s' "$sortie" | grep -iE 'overall-health|SMART Health' \ | sed 's/.*: *//' | head -1)" reallocations="$(printf '%s' "$sortie" \ | awk '/Reallocated_Sector_Ct|Reallocated_Event/{print $10; exit}')" + # Les secteurs que le disque n'arrive plus à lire. Ils manquaient à la + # fiche, et c'est ce qui a fait qu'alpicache annonçait « sain » un + # disque déclaré à remplacer : il ne les avait jamais reçus. + pending="$(printf '%s' "$sortie" | awk '$2 == "Current_Pending_Sector"{print $10; exit}')" + illisibles="$(printf '%s' "$sortie" | awk '$2 == "Offline_Uncorrectable"{print $10; exit}')" heures="$(printf '%s' "$sortie" | awk '/Power_On_Hours/{print $10; exit}')" + + # L'usure d'un SSD, en pour cent de vie consommée. + # + # Un disque mécanique s'use en tournant, un SSD en écrivant : les + # heures de marche ne disent rien de son état. Chaque constructeur a + # son attribut, et deux conventions coexistent — les uns comptent ce + # qui est consommé, les autres ce qui reste. On ramène tout à + # « consommé », qui est le sens qu'on veut lire. NVMe donne directement + # « Percentage Used » ; pour les SATA, la colonne VALUE part de 100 et + # descend. + usure="$(printf '%s' "$sortie" | awk '/Percentage Used:/{gsub(/[^0-9]/,"",$3); print $3; exit}')" + if [ -z "$usure" ]; then + usure="$(printf '%s' "$sortie" | awk ' + /Media_Wearout_Indicator|Wear_Leveling_Count|SSD_Life_Left|Percent_Lifetime_Remain/ { + if ($4 ~ /^[0-9]+$/ && $4 <= 100) { print 100 - $4; exit } + }')" + fi + # Ce que le disque a écrit depuis sa mise en service : l'autre façon de + # juger un SSD, dont les modèles courants tiennent 100 à 600 To. + ecrit="$(printf '%s' "$sortie" | awk ' + /Total_LBAs_Written|Host_Writes_32MiB|Total_Host_Writes/ { + if ($10 ~ /^[0-9]+$/) { print $10; exit } + } + /Data Units Written:/ { gsub(/[^0-9]/,"",$4); print $4; exit }')" fi + # Les mêmes chiffres, mis en table pour être lus par quelqu'un. Le verdict + # ne dit que ce qui cloche ; on veut aussi voir ce qui va, et sur quoi le + # verdict se fonde. + taille_go="$(awk -v o="$taille" 'BEGIN{printf "%.1f", o/1000000000}' | tr . ,)" + secteurs="—" + for valeur in "${pending:-0}" "${illisibles:-0}" "${reallocations:-0}"; do + case "$valeur" in ''|*[!0-9]*) continue ;; esac + [ "$valeur" -gt 0 ] && secteurs="!" + done + [ "$secteurs" = "!" ] && secteurs="$(printf '%s/%s/%s' \ + "${reallocations:-0}" "${pending:-0}" "${illisibles:-0}")" + usure_dite="—" + if [ "$rotatif" = 1 ]; then + case "$heures" in ''|*[!0-9]*) ;; *) + usure_dite="$heures h" + [ "$heures" -ge 8760 ] && usure_dite="$usure_dite ($(( heures / 8760 )),$(( (heures % 8760) * 10 / 8760 )) an)" ;; + esac + else + case "$usure" in ''|*[!0-9]*) ;; *) usure_dite="usé à $usure %" ;; esac + fi + TABLE_DISQUES="$TABLE_DISQUES$(printf '%-7s %-20s %-10s %9s %-8s %-7s %s' \ + "$nom" "$(printf '%.20s' "${modele:-—}")" \ + "$([ "$rotatif" = 1 ] && echo mécanique || echo SSD)" \ + "$taille_go Go" "${sante:-—}" "${heures:-—}" "$secteurs")"$'\n' + [ "$premier" = 0 ] && DISQUES="$DISQUES," premier=0 DISQUES="$DISQUES{$(champ nom "$nom"),$(champ modele "$modele"),\ \"octets\":$taille,$(champ type "$([ "$rotatif" = 1 ] && echo mécanique || echo SSD)"),\ -$(champ sante "$sante"),$(champ reallocations "$reallocations"),$(champ heures "$heures")}" +$(champ sante "$sante"),$(champ reallocations "$reallocations"),\ +$(champ pending "$pending"),$(champ illisibles "$illisibles"),$(champ heures "$heures"),\ +$(champ usure "$usure"),$(champ ecrit "$ecrit")}" done DISQUES="$DISQUES]" @@ -305,6 +379,19 @@ $(champ vitesse_memoire "$VITESSE_MEMOIRE"),\ \"slots_total\":$SLOTS_TOTAL,\"slots_pris\":$SLOTS_PRIS,\"barrettes\":$BARRETTES,\ \"disques\":$DISQUES,\"occupation\":$OCCUPATION}" +DUREE_MS=0 +case "$DEBUT_NS" in + ''|*[!0-9]*) ;; + 0) ;; + *) FIN_NS="$(date +%s%N 2>/dev/null || echo 0)" + case "$FIN_NS" in + ''|*[!0-9]*|0) ;; + *) DUREE_MS=$(( (FIN_NS - DEBUT_NS) / 1000000 )) ;; + esac ;; +esac +[ "$DUREE_MS" -lt 0 ] && DUREE_MS=0 +RAPPORT="${RAPPORT%\}},\"duree_releve_ms\":$DUREE_MS}" + if [ "$MONTRER" = 1 ]; then if command -v python3 >/dev/null 2>&1; then printf '%s' "$RAPPORT" | python3 -m json.tool 2>/dev/null || printf '%s\n' "$RAPPORT" @@ -329,6 +416,8 @@ fi echo "Système : $SYSTEME" echo +MOTIFS="" + # --- Le verdict ------------------------------------------------------------ # # Trois réponses possibles, et une seule question : peut-on installer sur cette @@ -349,6 +438,17 @@ if [ "$CHECK" = 1 ]; then note() { [ "$1" -gt "$GRAVITE" ] && GRAVITE="$1" REMARQUES="$REMARQUES$2"$'\n' + # Le même motif, débarrassé de ses couleurs et de ses retours à la + # ligne, pour qu'alpicache dise exactement ce qui a été dit ici. + if [ "$1" -ge 1 ]; then + # Une note tient sur plusieurs lignes à l'écran ; le motif, lui, + # est d'un seul tenant — il ira dans une case de tableau. + nu="$(printf '%s' "$2" | tr '\n' ' ' | sed -e 's/\x1b\[[0-9;]*m//g' \ + -e 's/[[:space:]]\{1,\}/ /g' \ + -e 's/^ *[!?✗·] *//' \ + -e 's/^ *//' -e 's/ *$//')" + [ -n "$nu" ] && MOTIFS="$MOTIFS$nu"$'\n' + fi } # Les disques. C'est pour eux qu'on fait ce contrôle. @@ -369,34 +469,111 @@ if [ "$CHECK" = 1 ]; then if printf '%s' "$sortie" | grep -qiE 'overall-health.*(FAILED|FAILING)'; then note 2 " ${R}✗${Z} ${B}/dev/$nom se déclare défaillant${Z} — ne pas installer dessus." fi + # Les trois attributs ne disent pas la même chose, et les confondre + # revient à sous-estimer les deux qui comptent. + # + # Reallocated_Sector_Ct des secteurs déjà remplacés par la réserve + # du disque. Quelques-uns sont de l'usure + # ordinaire ; c'est leur nombre qui parle. + # Current_Pending_Sector des secteurs que le disque n'arrive plus à + # lire et n'a pas encore pu déplacer. Ce + # qu'ils contenaient est en train d'être + # perdu. Un seul suffit à s'inquiéter. + # Offline_Uncorrectable des secteurs définitivement illisibles. + # + # Les deux derniers ne sont pas de l'usure mais une panne en cours : + # on ne les mesure pas à la même aune que le premier. for attribut in Reallocated_Sector_Ct Current_Pending_Sector Offline_Uncorrectable; do valeur="$(printf '%s' "$sortie" | awk -v a="$attribut" '$2 == a {print $10; exit}')" case "$valeur" in ''|*[!0-9]*) continue ;; esac [ "$valeur" = "0" ] && continue - if [ "$valeur" -ge 50 ]; then - note 2 " ${R}✗${Z} /dev/$nom : $valeur $attribut — disque à remplacer." - else - note 1 " ${O}!${Z} /dev/$nom : $valeur $attribut — à surveiller." - fi + case "$attribut" in + Reallocated_Sector_Ct) + libelle="secteur(s) déjà remplacé(s)" + if [ "$valeur" -ge 50 ]; then + note 2 " ${R}✗${Z} /dev/$nom : ${B}$valeur $libelle${Z} — disque à remplacer." + else + note 1 " ${O}!${Z} /dev/$nom : $valeur $libelle — à surveiller." + fi ;; + Current_Pending_Sector) + note 2 " ${R}✗${Z} /dev/$nom : ${B}$valeur secteur(s) illisible(s)${Z} en attente. + ${R}Ce ne sont pas des secteurs usés mais des données que le disque + n'arrive plus à relire : le disque est en train de lâcher.${Z} + ${D}Sauvegarder ce qui doit l'être avant toute autre chose, et + ne rien installer dessus — l'installation ne tiendrait pas.${Z}" ;; + Offline_Uncorrectable) + note 2 " ${R}✗${Z} /dev/$nom : ${B}$valeur secteur(s) définitivement perdu(s)${Z}. + ${R}Le disque a renoncé à les relire. À remplacer.${Z}" ;; + esac done heures="$(printf '%s' "$sortie" | awk '/Power_On_Hours/{print $10; exit}')" rotatif="$(cat "$chemin/queue/rotational" 2>/dev/null)" + + # L'usure, recalculée ici : cette boucle est distincte de celle du + # relevé et ne voit pas ses variables. + usure="$(printf '%s' "$sortie" | awk '/Percentage Used:/{gsub(/[^0-9]/,"",$3); print $3; exit}')" + if [ -z "$usure" ]; then + usure="$(printf '%s' "$sortie" | awk ' + /Media_Wearout_Indicator|Wear_Leveling_Count|SSD_Life_Left|Percent_Lifetime_Remain/ { + if ($4 ~ /^[0-9]+$/ && $4 <= 100) { print 100 - $4; exit } + }')" + fi # Un disque mécanique se signale toujours, et pas seulement quand il # est vieux. C'est le premier frein d'une machine par ailleurs # correcte : remplacer un plateau par un SSD fait plus pour elle que # n'importe quel réglage, et c'est le genre de conseil qu'on donne # pendant qu'on a la machine ouverte devant soi. + # Les heures de marche, dites en heures et en années de fonctionnement. + # « 477 j 15 h » se lit mal : on croit à un disque épuisé alors que + # c'est une année et quelques mois de marche, ce qui est peu. + marche="" + case "$heures" in + ''|*[!0-9]*) ;; + *) marche="$heures h" + if [ "$heures" -ge 8760 ]; then + marche="$marche, soit $(( heures / 8760 )),$(( (heures % 8760) * 10 / 8760 )) an de marche" + fi ;; + esac + if [ "$rotatif" = "1" ]; then - age="" + # Un disque mécanique se fatigue en tournant. Les constructeurs + # annoncent 300 000 à 600 000 heures de MTBF, mais le retour du + # terrain est plus sobre : au-delà de 30 000 heures — trois ans et + # demi de marche continue — les pannes deviennent sensiblement plus + # fréquentes, et à 45 000 on est dans le sursis. + gravite_disque=1 + supplement="" case "$heures" in ''|*[!0-9]*) ;; - *) annees=$(( heures / 8760 )) - [ "$annees" -gt 0 ] && age=" de $annees an$([ "$annees" -gt 1 ] && echo s)" - [ "$annees" = "0" ] && age=" ($heures h de marche)" ;; + *) if [ "$heures" -ge 45000 ]; then + gravite_disque=2 + supplement=" + ${R}Au-delà de 45 000 heures : sauvegarder avant toute chose.${Z}" + elif [ "$heures" -ge 30000 ]; then + supplement=" + ${O}Plus de 30 000 heures de marche : il a fait son temps.${Z}" + fi ;; esac - note 1 " ${O}!${Z} /dev/$nom est un ${B}disque mécanique${Z}$age. + note "$gravite_disque" " ${O}!${Z} /dev/$nom est un ${B}disque mécanique${Z}${marche:+ · $marche}. ${D}Le remplacer par un SSD ferait plus pour cette machine que - tout le reste réuni — démarrage, ouverture des programmes, réactivité.${Z}" + tout le reste réuni — démarrage, ouverture des programmes, réactivité.${Z}$supplement" + else + # Un SSD ne s'use pas en tournant mais en écrivant. Les heures de + # marche ne disent rien de son état : c'est l'usure qu'on regarde. + case "$usure" in + ''|*[!0-9]*) + [ -n "$marche" ] && note 0 " ${V}·${Z} /dev/$nom est un SSD · $marche. + ${D}Usure non déclarée par ce modèle.${Z}" ;; + *) if [ "$usure" -ge 90 ]; then + note 2 " ${R}✗${Z} /dev/$nom : SSD usé à ${B}${usure} %${Z} — il approche de sa fin. + ${R}Sauvegarder, et prévoir son remplacement.${Z}" + elif [ "$usure" -ge 70 ]; then + note 1 " ${O}!${Z} /dev/$nom : SSD usé à ${usure} %${marche:+ · $marche}. + ${D}Encore utilisable, mais il faudra y penser.${Z}" + else + note 0 " ${V}·${Z} /dev/$nom : SSD usé à ${usure} % seulement${marche:+ · $marche}." + fi ;; + esac fi if [ "$taille" -lt 32000000000 ]; then note 1 " ${O}!${Z} /dev/$nom ne fait que $(( taille / 1000000000 )) Go — c'est juste." @@ -424,6 +601,13 @@ if [ "$CHECK" = 1 ]; then 2) printf " %s%s NE PAS INSTALLER EN L'ÉTAT %s\n" "$B$R" "✗" "$Z" ;; esac echo + if [ -n "$TABLE_DISQUES" ]; then + printf " %s%-7s %-20s %-10s %9s %-8s %-7s %s%s\n" "$D" \ + "Disque" "Modèle" "Type" "Taille" "SMART" "Heures" "Secteurs R/P/I" "$Z" + printf '%s' "$TABLE_DISQUES" | sed "s/^/ /" + printf " %sR : remplacés · P : illisibles en attente · I : perdus%s\n" "$D" "$Z" + echo + fi [ -n "$REMARQUES" ] && printf "%s\n" "$REMARQUES" # Une pause, sans quoi tout cela défile et personne ne le lit. Sa durée @@ -443,6 +627,29 @@ if [ "$CHECK" = 1 ]; then fi fi +# Ce que l'on vient de dire à la personne part avec la fiche. +# +# Sans cela, alpicache rejugeait à partir des seuls champs reçus — et ne voyait +# pas les secteurs en attente, qui ne lui étaient pas envoyés. Il affichait +# « sain » un disque que ce script venait de déclarer à remplacer. Deux juges, +# deux verdicts : il n'y en a plus qu'un, et c'est celui-ci. +VERDICT="ok" +case "${GRAVITE:-0}" in + 2) VERDICT="alerte" ;; + 1) VERDICT="attention" ;; +esac +MOTIFS_JSON="[" +premier=1 +while IFS= read -r ligne; do + [ -n "$ligne" ] || continue + [ "$premier" = 0 ] && MOTIFS_JSON="$MOTIFS_JSON," + premier=0 + MOTIFS_JSON="$MOTIFS_JSON$(printf '%s' "$ligne" \ + | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/^/"/' -e 's/$/"/')" +done <<< "${MOTIFS:-}" +MOTIFS_JSON="$MOTIFS_JSON]" +RAPPORT="${RAPPORT%\}},$(champ verdict "$VERDICT"),\"motifs\":$MOTIFS_JSON}" + if command -v curl >/dev/null 2>&1; then reponse="$(printf '%s' "$RAPPORT" | curl -s --max-time 15 \ -H 'Content-Type: application/json' --data-binary @- "$SERVEUR/fiche")" From 10110a9d6eeb2a84dea2defc025bd587c57f082f Mon Sep 17 00:00:00 2001 From: Alpinux Date: Thu, 24 Sep 2026 22:59:08 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Faire=20t=C3=A9l=C3=A9charger=20les=20deux?= =?UTF-8?q?=20scripts,=20sans=20quoi=20le=20relev=C3=A9=20ne=20part=20jama?= =?UTF-8?q?is?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install.sh n'appelle le relevé que s'il le trouve à côté de lui, et le guide ne faisait télécharger qu'install.sh : dans le cas nominal, la fiche n'aurait jamais été relevée. Les deux fichiers se prennent maintenant ensemble, et un encadré dit pourquoi — téléchargé seul, install.sh fonctionne, simplement sans relevé. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01BE4rvHVETRoWnYNDTGssdo --- docs/guides/linux-mint-depuis-windows.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/guides/linux-mint-depuis-windows.md b/docs/guides/linux-mint-depuis-windows.md index 17b1aba..a5ba2fa 100644 --- a/docs/guides/linux-mint-depuis-windows.md +++ b/docs/guides/linux-mint-depuis-windows.md @@ -597,22 +597,29 @@ Il automatise ce qui manque presque toujours après une installation standard. O terminal (`Ctrl + Alt + T`) et récupérez-le — **sur le réseau local de l'Install Party** : ```bash -wget http://10.0.0.1/install.sh +wget http://10.0.0.1/install.sh http://10.0.0.1/alpi-fiche.sh ``` **ou depuis Internet :** ```bash -wget https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-wiki/raw/branch/main/code/linux/linux-mint/install.sh +wget https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-wiki/raw/branch/main/code/linux/linux-mint/install.sh \ + https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-wiki/raw/branch/main/code/linux/linux-mint/alpi-fiche.sh ``` Puis, dans les deux cas : ```bash -chmod +x install.sh +chmod +x install.sh alpi-fiche.sh ./install.sh ``` +!!! note "Pourquoi deux fichiers" + `install.sh` commence par relever la fiche matérielle, et il ne le fait **que s'il + trouve `alpi-fiche.sh` à côté de lui** — il ne va rien chercher sur le réseau de + lui-même. Téléchargé seul, il s'installera très bien, simplement sans relevé. En + install party, prenez les deux. + Le mot de passe est demandé au passage (`alpinux73` en Install Party) : les caractères ne s'affichent pas, c'est normal.