Compare commits

..

No commits in common. "main" and "doc-derniers-articles" have entirely different histories.

36 changed files with 75 additions and 1743 deletions

View file

@ -1,702 +0,0 @@
#!/bin/bash
# Relève la fiche d'une machine, et l'envoie au serveur de l'install party.
#
# 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
# 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é : le modèle, le processeur, la mémoire, les disques et
# leur santé, le système installé.
#
# 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 :
#
# · 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.
#
# 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
# 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
# Bash ne compte les caractères — et non les octets — que si la locale le dit.
# C'est ce qui aligne les colonnes du tableau des disques.
export LC_ALL="${LC_ALL:-C.UTF-8}"
# 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à.
PATH="$PATH:/usr/sbin:/sbin"
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
# 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/ *$//'
}
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.
#
# 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 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|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
# --- 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]"
TABLE_DISQUES=""
# Remplir une colonne à la bonne largeur.
#
# « printf %-10s » compte les octets, pas les caractères : « mécanique » en
# pèse dix pour neuf signes, et la colonne suivante glisse d'un cran. Bash,
# lui, sait compter les caractères quand la locale est en UTF-8 — on lui
# laisse ce travail, et l'on force la locale plutôt que d'espérer.
colonne() {
local texte="$1" largeur="$2" cote="${3:-gauche}" n
n=${#texte}
if [ "$n" -gt "$largeur" ]; then
printf '%s' "${texte:0:$largeur}"
return
fi
local blancs=""
while [ "$n" -lt "$largeur" ]; do blancs="$blancs "; n=$((n + 1)); done
if [ "$cote" = droite ]; then printf '%s%s' "$blancs" "$texte"
else printf '%s%s' "$texte" "$blancs"; fi
}
# --- 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=""
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$(
colonne "$nom" 7; printf ' '
colonne "${modele:-—}" 20; printf ' '
colonne "$([ "$rotatif" = 1 ] && echo mécanique || echo SSD)" 10; printf ' '
colonne "$taille_go Go" 10 droite; printf ' '
colonne "${sante:-—}" 8; printf ' '
colonne "$usure_dite" 16; printf ' '
printf '%s' "$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 pending "$pending"),$(champ illisibles "$illisibles"),$(champ heures "$heures"),\
$(champ usure "$usure"),$(champ ecrit "$ecrit")}"
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}"
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"
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
MOTIFS=""
# --- 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.
# 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
REMARQUES=""
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.
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
# 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
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
# 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]*) ;;
*) 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 "$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}$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."
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
if [ -n "$TABLE_DISQUES" ]; then
printf ' %s' "$D"
colonne "Disque" 7; printf ' '
colonne "Modèle" 20; printf ' '
colonne "Type" 10; printf ' '
colonne "Taille" 10 droite; printf ' '
colonne "SMART" 8; printf ' '
colonne "Marche / usure" 16; printf ' '
printf 'Secteurs R/P/I%s\n' "$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
# 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
# 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")"
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

View file

@ -1,27 +1,5 @@
#!/bin/bash #!/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 # Script de post-installation pour Linux Mint 22.3
# testé le 25/03/2026 # testé le 25/03/2026
# #
@ -249,7 +227,7 @@ install_logiciels() {
DEBIAN_FRONTEND=noninteractive apt install -y ttf-mscorefonts-installer || abort "problème lors de l'installation des polices" DEBIAN_FRONTEND=noninteractive apt install -y ttf-mscorefonts-installer || abort "problème lors de l'installation des polices"
# mise à jour du système # mise à jour du système
echo -e "==> Mise à jour du système\n" echo "==> Mise à jour du système\n"
apt update || abort "impossible de récupérer les informations des dépôts" apt update || abort "impossible de récupérer les informations des dépôts"
apt full-upgrade -y || abort "erreur lors de la mise à jour des paquets" apt full-upgrade -y || abort "erreur lors de la mise à jour des paquets"
@ -311,7 +289,7 @@ install_logiciels() {
# Mises à jour # # Mises à jour #
################ ################
mises_a_jour() { mises_a_jour() {
echo -e "==> Configuration de dconf\n" echo -e "Configuraiton de dconf\n"
# Configuration de dconf pour permettre de modifier certaines configurations # Configuration de dconf pour permettre de modifier certaines configurations
# par défaut # par défaut
mkdir -p /etc/dconf/profile mkdir -p /etc/dconf/profile
@ -359,32 +337,11 @@ configuration_apt() {
# GRUB : sélection automatique de la dernière entrée utilisée # # GRUB : sélection automatique de la dernière entrée utilisée #
############################################################### ###############################################################
modif_grub() { modif_grub() {
# Rien à faire si c'est déjà en place.
#
# update-grub lance os-prober, qui part sonder toutes les partitions de la
# machine à la recherche d'autres systèmes, puis régénère la configuration
# entière : vingt à trente secondes qu'on ne rattrape pas, et qu'on payait
# jusqu'ici à chaque exécution, y compris quand il n'y avait rien à
# changer. Sur une install party de quinze machines, cela se voit.
if grep -q '^GRUB_DEFAULT=saved' /etc/default/grub \
&& grep -q '^GRUB_SAVEDEFAULT=true' /etc/default/grub; then
echo -e "==> GRUB est déjà configuré, rien à changer\n"
return 0
fi
echo -e "==> Modification de GRUB\n" echo -e "==> Modification de GRUB\n"
# On commente la ligne d'origine, puis on ajoute les nôtres si elles # On comment la ligne "GRUB_DEFAULT=0" et on ajoute les "bonnes" options
# manquent. Les ajouter plutôt que les insérer règle deux choses : le sed -i '/^GRUB_DEFAULT=0/ i#GRUB_DEFAULT=0\
# fichier ne se retrouve plus avec des lignes indentées — GRUB les accepte, GRUB_DEFAULT=saved\
# mais elles trompent la lecture quand on vient déboguer —, et la fonction GRUB_SAVEDEFAULT=true' /etc/default/grub
# fait toujours son travail lorsque « GRUB_DEFAULT=0 » a disparu, ce qui
# arrive dès qu'un autre outil est passé avant nous.
sed -i 's/^GRUB_DEFAULT=0/#&/' /etc/default/grub
grep -q '^GRUB_DEFAULT=saved' /etc/default/grub \
|| echo 'GRUB_DEFAULT=saved' >> /etc/default/grub
grep -q '^GRUB_SAVEDEFAULT=true' /etc/default/grub \
|| echo 'GRUB_SAVEDEFAULT=true' >> /etc/default/grub
# On met à jour GRUB # On met à jour GRUB
update-grub update-grub
} }

View file

@ -70,13 +70,10 @@ occupe l'association. Il part du serveur de l'association.
**Qui la reçoit.** Les adhérents, et toute personne qui a demandé à la recevoir. Une **Qui la reçoit.** Les adhérents, et toute personne qui a demandé à la recevoir. Une
inscription à une install party ne vous y abonne pas : il faut l'avoir demandé. Pour inscription à une install party ne vous y abonne pas : il faut l'avoir demandé. Pour
la recevoir sans être adhérent, abonnez-vous vous-même sur la recevoir sans être adhérent, écrivez à **secretaire@alpinux.org**.
**[messagerie.alpinux.org](https://messagerie.alpinux.org)** — la marche à suivre est
décrite dans [S'abonner et se désabonner](messagerie-abonnement.md#inscription).
**Comment s'en aller.** Chaque envoi porte un lien de désinscription, en pied de page **Comment s'en aller.** Chaque envoi porte un lien de désinscription. Un clic suffit,
et dans ses en-têtes. Un clic suffit, il n'y a rien à justifier, et le retrait est il n'y a rien à justifier, et le retrait est immédiat. Vous continuerez à recevoir les
immédiat. Vous continuerez à recevoir les
messages liés à vos inscriptions en cours — ce sont deux choses distinctes. messages liés à vos inscriptions en cours — ce sont deux choses distinctes.
--- ---

View file

@ -58,7 +58,6 @@ Lors de nos **Install Party**, les bénévoles d'**Alpinux** installent Linux av
## 🧰 Après l'installation ## 🧰 Après l'installation
- 🩹 [**Trousse de premiers secours Linux Mint**](../guides/linux-mint-trousse.md) — les réflexes utiles des premiers jours. - 🩹 [**Trousse de premiers secours Linux Mint**](../guides/linux-mint-trousse.md) — les réflexes utiles des premiers jours.
- 🖧 [**Le serveur qu'on emporte**](../technique/serveur-install-party.md) — le réseau de la salle, le cache de paquets, et ce qu'un bénévole doit en savoir.
- 🗣️ [**Ateliers et rencontres**](evenements.md) — pour progresser et échanger avec la communauté. - 🗣️ [**Ateliers et rencontres**](evenements.md) — pour progresser et échanger avec la communauté.
--- ---

View file

@ -1,130 +0,0 @@
---
description: Comment s'abonner à une liste de diffusion d'Alpinux, confirmer son adresse, se désabonner, et lire les messages déjà envoyés.
---
# ✉️ S'abonner et se désabonner
Ce guide s'adresse à toute personne qui veut **recevoir les annonces de
l'association**, ou cesser de les recevoir. Aucun compte n'est nécessaire.
**En une phrase :** vous vous abonnez et vous vous désabonnez vous-même, personne ne
peut le faire à votre place, et rien ne part vers une adresse qui ne l'a pas
demandé.
---
## S'abonner { #inscription }
Rendez-vous sur la page d'accueil de la messagerie de l'association.
![La page d'accueil : votre adresse, la liste, et c'est tout](../assets/messagerie/abonnement-01-accueil.png)
Indiquez votre adresse de courriel, choisissez la liste qui vous intéresse, puis
**Continuer**.
L'écran suivant affiche un **code** — de la forme `ALPX-7K3M9Q` — et vous demande de
faire une chose, une seule :
!!! tip "Envoyez-nous un message depuis l'adresse que vous venez de saisir"
Destinataire : **messagerie@alpinux.org**
Objet : **le code affiché**, recopié tel quel
Corps du message : ce que vous voulez, il n'est pas lu
![Le code à recopier, l'adresse où écrire, et l'heure limite](../assets/messagerie/abonnement-02-code.png)
Un bouton **Ouvrir mon logiciel de messagerie** prépare le message pour vous. Il ne
l'envoie pas : c'est à vous de le faire.
Vous avez **trente minutes**. Passé ce délai, le code ne vaut plus rien et la demande
s'efface d'elle-même — sans aucune conséquence, vous pouvez recommencer.
Dans la minute qui suit votre envoi, vous recevez une réponse qui confirme
l'inscription, rappelle la liste, et porte déjà le lien pour partir.
![La réponse reçue une fois le code envoyé](../assets/messagerie/abonnement-03-confirmation.png)
### Pourquoi est-ce à moi d'écrire ?
D'habitude, un site vous envoie un courriel de confirmation. Ici, c'est l'inverse.
Trois raisons.
- **Rien ne part vers une adresse non vérifiée.** Écrire à des adresses non
confirmées est ce qui abîme le plus la réputation d'un expéditeur — et quand elle
est abîmée, ce sont les messages de toute l'association qui finissent en
indésirable, chez tout le monde.
- **Inscrire l'adresse de quelqu'un d'autre ne lui envoie rien.** La demande reste en
attente puis s'efface. Personne ne peut vous abonner à votre insu, ni se servir de
ce formulaire pour vous faire parvenir un message.
- **Un message venu de votre boîte prouve mieux que vous la possédez** qu'un clic sur
un lien : certains antivirus d'entreprise cliquent les liens tout seuls.
La contrepartie, c'est une manipulation à faire. Elle est assumée.
### Je me suis déjà abonné, ou désabonné, par le passé
Le parcours est le même. C'est d'ailleurs **le seul moyen de revenir** : l'association
ne réinscrit jamais personne de sa propre initiative.
### Je n'ai pas reçu la confirmation
Trois choses à vérifier, dans cet ordre :
1. **Avez-vous écrit depuis l'adresse que vous aviez saisie ?** Le code ne vaut que
présenté depuis celle-ci. Un message envoyé depuis une autre boîte est refusé.
2. **Les trente minutes sont-elles passées ?** Recommencez, c'est sans conséquence.
3. **Le code était-il bien dans l'objet ?** Il est aussi reconnu dans le corps du
message, mais l'objet est le plus sûr.
---
## Se désabonner { #desabonnement }
**Chaque message reçu porte un lien de désabonnement**, à deux endroits : en pied de
page, et dans les en-têtes techniques du message.
C'est ce second emplacement qui fait apparaître un bouton **Se désabonner** en haut
du message dans les logiciels modernes — Gmail, Outlook, Thunderbird, Apple Mail. Un
clic suffit.
![Le bouton « Se désabonner » qu'affiche votre logiciel de messagerie](../assets/messagerie/abonnement-04-bouton-desabonner.png)
La page qui s'ouvre vous laisse choisir :
- quitter **cette liste seulement** ;
- ou quitter **toutes les listes** de l'association d'un coup.
![Quitter cette liste, ou toutes](../assets/messagerie/abonnement-05-desabonnement.png)
Aucune justification n'est demandée. Le retrait est immédiat, et il n'y a personne à
qui écrire.
!!! note "Vous n'avez plus aucun message sous la main ?"
Reprenez simplement le parcours d'inscription : il vous renverra un message, qui
portera le lien.
---
## Lire ce qui a déjà été envoyé { #archives }
Les listes ouvertes au public ont des **archives** consultables sans compte. C'est
utile avant de s'abonner, pour voir à quoi on s'engage.
![Les envois passés d'une liste publique](../assets/messagerie/abonnement-06-archives.png)
Seuls y figurent les messages **réellement envoyés** : ni les brouillons, ni les
envois annulés.
!!! info "Aucune adresse de courriel n'apparaît dans les archives"
Les adresses citées dans le corps d'un message — signature, contact d'un
événement, adresse d'un partenaire — y sont masquées : `s****@a****.xxx`. On voit
qu'un contact existait, sans pouvoir le reconstituer ni même savoir chez quel
fournisseur il est hébergé. Leurs titulaires n'ont pas consenti à une publication
sur le web, où un robot les ramasserait pour en faire du spam.
---
## Voir aussi
- [Vos données, vos adresses, nos envois](donnees-personnelles.md)
- [Écrire et recevoir des messages](messagerie-membre.md) — la messagerie entre
membres, qui est autre chose que les listes.

View file

@ -1,300 +0,0 @@
---
description: Guide du gestionnaire de listes Alpinux — créer une liste, préparer et envoyer un message, gérer les abonnés, comprendre les adresses désactivées et surveiller la réputation d'envoi.
---
# 📣 Gérer une liste de diffusion
Ce guide s'adresse aux personnes qui **écrivent à toute une liste** : le bureau, et
les administrateurs de l'outil.
**En une phrase :** vous rédigez un brouillon, vous le mettez en file d'envoi, et un
automate le distribue en lissant le débit pendant que vous faites autre chose.
!!! warning "N'envoyez plus de message en copie cachée depuis votre logiciel de messagerie"
Les deux méthodes ne se coordonnent pas : l'outil ne saurait rien de ce qui est
parti par l'autre chemin, ni des rebonds que cela aurait provoqués. Et c'est
précisément ce mode d'envoi qui saturait le serveur — au-delà d'un certain
nombre de messages par heure, il refuse les suivants et une partie de l'envoi
est perdue sans que personne le voie.
---
## Envoyer un message à une liste { #envoyer }
### 1. Rédiger
Menu **Listes → Campagnes**, puis **Nouvelle campagne**.
![La rédaction, avec l'aperçu de ce qui partira](../assets/messagerie/gestionnaire-01-redaction.png)
Choisissez la liste destinataire, écrivez l'objet et le corps.
Le corps s'écrit en **Markdown** — la même syntaxe que ce wiki, voir
[Écrire en Markdown](../contribuer/markdown.md). Un aperçu s'affiche à côté ; le
bouton **Enregistrer et actualiser l'aperçu** le rafraîchit.
Trois variables sont remplacées à l'envoi, pour chaque destinataire :
| Variable | Remplacée par |
|---|---|
| `{{nom}}` | le nom de l'abonné, s'il est connu |
| `{{email}}` | son adresse |
| `{{desabonnement}}` | le lien de désabonnement qui lui est propre |
**Enregistrer le brouillon** vous permet de revenir plus tard : rien ne part tant que
vous n'avez rien demandé.
!!! danger "Le lien de désabonnement n'est pas optionnel"
Si vous ne placez pas `{{desabonnement}}` vous-même, un lien est ajouté
automatiquement en pied de message. Ce comportement ne se désactive pas : un
envoi de masse sans lien de désabonnement est signalé comme spam, et c'est
ensuite toute l'association qui n'arrive plus à écrire à personne.
### 2. Vérifier avant d'envoyer
Relisez l'aperçu. C'est le dernier moment confortable : une fois la file remplie, ce
qui est parti est parti.
Deux choses valent le coup d'œil :
- **les liens** — un lien faux dans un message envoyé à toute la liste ne se
rattrape pas ;
- **le nombre de destinataires**, affiché sur la fiche de la liste.
### 3. Envoyer
Bouton **Mettre en file d'envoi…**. Une confirmation vous est demandée, avec le
nombre de destinataires.
![Le dernier écran avant que la file se remplisse](../assets/messagerie/gestionnaire-02-confirmation.png)
L'envoi se fait ensuite tout seul, une minute sur l'autre, **sous un plafond horaire**
fixé pour ne pas saturer le serveur. Une liste de quelques centaines de personnes
part en une heure environ.
Vous pouvez fermer la page : l'envoi continue.
### 4. Suivre
La fiche de la campagne affiche la progression : envoyés, en attente, échecs.
![L'envoi en cours : remis, en attente, échecs](../assets/messagerie/gestionnaire-03-progression.png)
Si le compteur semble figé, c'est probablement le plafond horaire : l'envoi reprend
de lui-même à l'heure suivante.
### Arrêter, recommencer
- **Annuler les envois restants…** abandonne ce qui n'est pas encore parti. Ce qui
est déjà distribué ne revient pas.
- **Dupliquer en brouillon** recrée une copie modifiable d'un envoi passé — pratique
pour une lettre dont la structure ne change pas d'un mois sur l'autre.
- **Supprimer ce brouillon…** efface un brouillon abandonné. Une confirmation est
demandée, et il n'y a pas de corbeille. Seul un brouillon se supprime : une
campagne partie reste, puisque des gens l'ont reçue et qu'elle figure dans les
archives.
---
## Créer une liste { #creer }
Menu **Listes → Listes de diffusion**, puis le formulaire de création.
![Créer une liste : le nom et la description suffisent](../assets/messagerie/gestionnaire-04-creer-liste.png)
Seuls **le nom** et **la description** sont demandés. Le reste est automatique :
- l'**identifiant technique** se déduit du nom ;
- l'**expéditeur** est celui de l'association, suivi du nom de l'auteur à
l'envoi — « Alpinux (de la part de Prénom Nom) » ;
- les **réponses** reviennent à l'adresse d'expédition, qui est relevée.
Rien de cela ne se règle liste par liste : l'association écrit sous une seule
identité. Multiplier les expéditeurs éparpille la réputation du domaine autant que
cela désoriente les destinataires.
!!! note "Le nom d'une liste n'est pas modifiable après création"
Relisez-le avant de valider.
---
## Gérer les abonnés { #abonnes }
Menu **Listes → Listes de diffusion**, puis la liste voulue.
![La fiche d'une liste, adresses masquées par défaut](../assets/messagerie/gestionnaire-05-fiche-liste.png)
Quatre compteurs résument l'état : destinataires joignables, inscrits au total,
désabonnés, adresses mortes.
### Les adresses sont masquées
Elles s'affichent sous la forme `c••••••e@exemple.fr`. Le domaine reste lisible — il
suffit au diagnostic — sans étaler à l'écran un carnet d'adresses exploitable.
Un lien **Afficher les adresses en clair** lève le masque quand c'est nécessaire.
### Ajouter des abonnés
Deux moyens, dans le volet **Ajouter des abonnés** :
- **coller une liste d'adresses**, une par ligne. Les formes `nom@exemple.fr`,
`nom@exemple.fr;Prénom Nom` et `Prénom Nom <nom@exemple.fr>` sont acceptées ;
- **importer depuis Dolibarr** les adhérents à jour de cotisation.
!!! danger "Un import ne réactive jamais une adresse partie"
Désabonnée, morte, ou ayant signalé un spam : l'outil refuse de la remettre en
service, même si elle figure dans le fichier importé. Seule la personne
concernée peut revenir, par le parcours d'inscription.
Réinscrire quelqu'un qui est parti est le meilleur moyen de se faire signaler
comme spam — et cette fois définitivement.
---
## Ouvrir une liste au public { #publique }
*Réservé aux administrateurs.*
Sur la fiche de la liste, cochez **Liste publique** et enregistrez.
![Ouvrir une liste à l'inscription libre](../assets/messagerie/gestionnaire-06-liste-publique.png)
Trois conséquences, à peser avant de cocher :
- n'importe qui peut **demander à s'abonner** depuis la page d'accueil ;
- les envois passés deviennent **lisibles publiquement** dans les archives ;
- le **nom et la description** de la liste sont visibles de tout visiteur.
Ce dernier point n'est pas anodin pour une liste interne. Le geste est enregistré
dans le journal de l'outil.
### Les demandes en attente
Quand une liste est publique, la fiche affiche les **demandes d'inscription en
attente de confirmation**, avec leur heure d'expiration.
![Les demandes en attente de confirmation](../assets/messagerie/gestionnaire-07-demandes.png)
Ces adresses **ne sont pas inscrites** et ne le seront que si leurs titulaires
confirment depuis leur propre boîte. Il n'y a rien à faire : celles qui restent
s'effacent seules.
---
## Les adresses désactivées { #desactivees }
Menu **Listes → Adresses désactivées**.
L'écran comporte deux tableaux, et la différence entre les deux est importante.
### Rejets définitifs et plaintes
Ces adresses ont été retirées de **toutes** les listes : le serveur d'en face a
répondu que la boîte n'existait pas, ou la personne nous a signalés comme spam.
![Les rejets définitifs, avec le texte exact du refus](../assets/messagerie/gestionnaire-08-rejets.png)
La colonne de droite affiche **le texte exact du refus**, extrait du rapport reçu —
« utilisateur inconnu », « boîte pleine », le nom d'une liste noire. Le rapport
complet est dépliable si vous voulez tout lire.
Continuer à écrire à ces adresses abîmerait la réputation du serveur pour tous les
autres envois. C'est pourquoi l'outil les écarte sans demander.
### Blocages d'opérateur
**Ces adresses-là sont conservées**, et c'est délibéré.
Le message a été refusé non pas parce que la boîte a disparu, mais parce qu'un
opérateur refuse *notre* serveur — un grand fournisseur de messagerie qui nous a mis
sur une liste d'attente, ou un serveur qui interroge une liste noire.
![Les blocages d'opérateur : ces adresses restent inscrites](../assets/messagerie/gestionnaire-09-blocages.png)
La distinction est vitale. Une part notable des abonnés d'une association tient ses
adresses chez deux ou trois grands fournisseurs : les traiter comme mortes parce que
l'un d'eux nous bloque un jour ferait perdre d'un coup une fraction de la liste, en
silence, pour une faute qui n'est pas la leur.
Quand cette section se remplit, ce n'est donc pas un problème d'abonnés mais de
réputation d'envoi. Le délistage se demande auprès de l'opérateur concerné.
---
## Surveiller la réputation d'envoi { #postmaster }
*Réservé aux administrateurs.* Menu **Configuration → Postmaster**.
Les grands opérateurs de messagerie envoient chaque jour un rapport disant **quelles
adresses IP ont émis du courrier au nom du domaine de l'association**, et si ces
envois étaient correctement signés. L'outil les dépouille tout seul.
![Qui émet au nom du domaine, et si c'est aligné](../assets/messagerie/gestionnaire-10-postmaster.png)
Deux lectures valent le détour :
- **une adresse IP inconnue dans la liste** : quelqu'un émet en notre nom. Usurpation,
ou service de l'association qu'on avait oublié ;
- **une adresse IP connue mais jamais alignée** : elle émet sans être autorisée par
nos enregistrements, et ses messages tombent en indésirable chez tout le monde.
Le second tableau liste le courrier reçu à l'adresse *postmaster* que l'automate n'a
pas su traiter — avis d'opérateur, signalement — et qui attend une lecture humaine.
Le bouton **Marquer comme lu** le retire de la liste une fois traité.
---
## Attribuer les rôles { #droits }
*Réservé aux administrateurs.* Menu **Configuration → Droits**.
![Les trois rôles, cumulables](../assets/messagerie/gestionnaire-11-droits.png)
Trois rôles indépendants, cumulables :
| Rôle | Donne accès à |
|---|---|
| Rédacteur | la section **Listes** : campagnes et abonnés |
| Administrateur | les listes publiques, les imports, **Droits** et **Postmaster** |
| Messagerie interne | la section **Messagerie** |
Un administrateur ne peut pas se retirer lui-même l'administration : l'outil refuse
de se retrouver sans personne pour le gérer. Demandez-le à un autre administrateur.
---
## Questions fréquentes
??? question "Mon envoi semble arrêté à mi-parcours"
C'est le plafond horaire. L'envoi reprend de lui-même à l'heure suivante, et la
fiche de la campagne affiche ce qui reste en attente.
??? question "Une adresse apparaît comme désactivée alors qu'elle fonctionne"
Regardez dans quel tableau elle se trouve. Dans **Blocages d'opérateur**, elle
n'est pas désactivée et il n'y a rien à faire de ce côté. Dans **Rejets
définitifs**, lisez le texte du refus : il dit précisément ce que le serveur
distant a répondu.
??? question "Quelqu'un veut revenir après s'être désabonné"
Il doit refaire le parcours d'inscription lui-même. Ne le réinscrivez pas à la
main : l'outil l'interdit, et pour de bonnes raisons.
??? question "Puis-je renvoyer exactement le même message plus tard ?"
Oui, **Dupliquer en brouillon** sur la campagne passée. Vérifiez les dates et les
liens avant de renvoyer : ce qui était vrai il y a un mois ne l'est plus
forcément.
??? question "Comment savoir qui a ouvert le message ?"
On ne le sait pas, et c'est voulu. Les messages ne contiennent ni image de suivi
ni lien piégé. Ce que l'on sait, c'est ce qui a été **remis** et ce qui a
**rebondi**.
---
## Voir aussi
- [S'abonner et se désabonner](messagerie-abonnement.md) — ce que voient les
personnes à qui vous écrivez.
- [Vos données, vos adresses, nos envois](donnees-personnelles.md)
- [Écrire en Markdown](../contribuer/markdown.md)

View file

@ -1,120 +0,0 @@
---
description: La messagerie interne d'Alpinux — écrire à un autre membre sans connaître son adresse, lire ses messages, régler ses avis par courriel.
---
# 💬 Écrire et recevoir des messages
Ce guide s'adresse aux **membres** de l'association qui veulent s'écrire entre eux.
C'est autre chose que les listes de diffusion : ici, personne ne diffuse à tout le
monde. On écrit à une personne, et elle seule.
**En une phrase :** vous écrivez à quelqu'un sans connaître son adresse, et il vous
répond sans connaître la vôtre.
!!! info "Il faut être connecté"
La messagerie interne demande le **compte de l'association**, le même que pour
le portail. Le rôle correspondant est attribué par un administrateur.
---
## Lire mes messages { #boite }
Menu **Messagerie → Mes messages**.
![La boîte de réception ; les messages non lus sont en gras](../assets/messagerie/membre-01-boite.png)
Le nombre de messages non lus apparaît à côté du mot **Messagerie** dans le menu :
vous le voyez donc depuis n'importe quel écran, sans avoir à aller voir.
Un message s'ouvre d'un clic sur son objet. Il est marqué lu à ce moment-là.
---
## Écrire à quelqu'un { #ecrire }
Menu **Messagerie → Écrire**.
![Le destinataire se choisit dans l'annuaire, sans connaître son adresse](../assets/messagerie/membre-02-ecrire.png)
Choisissez le destinataire dans la liste proposée : c'est l'annuaire des membres.
**Vous n'avez pas besoin de son adresse** — vous ne la verrez pas, et il ne verra pas
la vôtre.
Écrivez l'objet et le message, puis envoyez.
### Ce que reçoit votre destinataire
Un **avis** par courriel, qui dit qu'un message l'attend, de la part de qui, et sur
quel sujet. **Le texte du message n'est pas recopié dans cet avis** : il faut se
connecter pour le lire.
C'est volontaire. Un avis qui recopierait le message rendrait la messagerie interne
inutile — autant s'écrire directement.
---
## Régler mes avis par courriel { #avis }
Menu **Configuration → Mon profil**.
![Les avis par courriel et le résumé quotidien se règlent ici](../assets/messagerie/membre-03-profil.png)
Vous y choisissez :
- **si** vous voulez être prévenu par courriel quand un message arrive ;
- **à quelle adresse** ces avis doivent partir.
### Le résumé quotidien
Si des messages restent non lus, un **résumé** part une fois par jour — et seulement
les jours où quelque chose est arrivé. Il dit qui vous a écrit et sur quels sujets.
Ni le corps des messages, ni les adresses des auteurs n'y figurent.
Il se coupe depuis le même écran que les avis.
---
## Emporter mes messages { #export }
Menu **Messagerie → Copie de mes messages**.
Vous téléchargez l'ensemble de vos messages — reçus et envoyés — dans une archive.
Chaque message y est au format standard des courriels, lisible par n'importe quel
logiciel de messagerie.
C'est ce qu'il faut utiliser pour garder une trace hors de l'outil, ou pour exercer
votre droit d'accès à vos données.
---
## Questions fréquentes
??? question "Mon correspondant verra-t-il mon adresse ?"
Non. Les messages ne circulent pas par courriel entre vous : ils restent dans
l'outil. Seuls les **avis** partent par courriel, et chacun part vers l'adresse
de son propre destinataire.
??? question "Puis-je répondre depuis mon logiciel de messagerie ?"
Non. L'avis est une notification, pas le message. Il faut se connecter pour lire
et répondre — c'est la contrepartie de ne pas divulguer les adresses.
??? question "Je ne reçois plus d'avis"
Vérifiez d'abord **Configuration → Mon profil** : les avis y sont peut-être
coupés. S'ils sont actifs et que rien n'arrive, c'est probablement que votre
adresse a rebondi ; l'outil suspend alors les avis pour ne pas s'acharner.
Signalez-le à un administrateur, qui le verra dans ses écrans.
??? question "Puis-je écrire à quelqu'un qui n'est pas dans l'annuaire ?"
Non. L'annuaire ne contient que les membres à qui un rôle a été attribué. Pour
écrire à quelqu'un d'autre, il faut son adresse et un logiciel de messagerie
ordinaire.
---
## Voir aussi
- [S'abonner et se désabonner](messagerie-abonnement.md) — les listes de diffusion,
qui sont autre chose.
- [Vos données, vos adresses, nos envois](donnees-personnelles.md)

View file

@ -1,50 +0,0 @@
---
description: La messagerie d'Alpinux — trois guides selon que vous voulez vous abonner, écrire aux membres, ou gérer une liste de diffusion.
---
# 📬 La messagerie d'Alpinux
La messagerie de l'association sert à deux choses qu'il ne faut pas confondre :
- **les listes de diffusion**, par lesquelles le bureau écrit à tout le monde ;
- **la messagerie interne**, par laquelle les membres s'écrivent entre eux sans se
communiquer leurs adresses.
Selon ce que vous voulez faire, la suite n'est pas la même.
---
## Je veux…
| Ce que vous voulez faire | Le guide qu'il vous faut |
|---|---|
| Recevoir les annonces de l'association, ou arrêter de les recevoir | [S'abonner et se désabonner](messagerie-abonnement.md) |
| Lire ce qui a déjà été envoyé | [S'abonner et se désabonner](messagerie-abonnement.md#archives) |
| Écrire à un autre membre, ou lire mes messages | [Écrire et recevoir des messages](messagerie-membre.md) |
| Envoyer un message à toute une liste | [Gérer une liste de diffusion](messagerie-gestionnaire.md) |
| Ajouter des abonnés, ouvrir une liste au public | [Gérer une liste de diffusion](messagerie-gestionnaire.md) |
| Comprendre pourquoi une adresse ne reçoit plus rien | [Gérer une liste de diffusion](messagerie-gestionnaire.md#desactivees) |
---
## Qui peut quoi
L'accès aux écrans dépend d'un rôle, attribué par un administrateur dans
**Configuration → Droits**.
| Vous êtes… | Vous voyez… |
|---|---|
| Une personne sans compte | l'inscription et les archives des listes publiques |
| Membre | la section **Messagerie** |
| Rédacteur | la section **Listes** |
| Administrateur | tout, plus **Configuration → Droits** et **Configuration → Postmaster** |
Une même personne peut cumuler les trois.
---
## Voir aussi
- [Vos données, vos adresses, nos envois](donnees-personnelles.md) — ce que
l'association recueille, combien de temps elle le garde, et ce que vous pouvez
exiger.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

View file

@ -1,23 +0,0 @@
# wiki.alpinux.org
Documentation publique Alpinux — MkDocs Material.
## Déploiement
```bash
cd org.alpinux.owni/wiki
mkdocs build --strict
rsync -rlcz --delete site/ alpinux.org:/var/www/wiki.alpinux.org/web/
```
## Développement local
```bash
pip install mkdocs-material
mkdocs serve # http://localhost:8000
```
## Notes
- Pas de service systemd — site statique servi directement par Apache
- Les docs publiques sont dans `wiki/docs/technique/`

View file

@ -104,6 +104,10 @@ Clic droit sur **Démarrer** → *Terminal (administrateur)*, puis :
powercfg /hibernate off powercfg /hibernate off
``` ```
!!! tip "Le dernier arrêt de Windows"
Au moment d'éteindre Windows pour insérer la clé USB, maintenez **Maj (Shift)**
enfoncée en cliquant sur *Démarrer → Arrêter*. Cela force un arrêt complet.
### Gestion de l'alimentation de la carte Wi-Fi ### Gestion de l'alimentation de la carte Wi-Fi
Certaines cartes Wi-Fi (puces Realtek ou Broadcom notamment) restent dans l'état de basse Certaines cartes Wi-Fi (puces Realtek ou Broadcom notamment) restent dans l'état de basse
@ -184,8 +188,6 @@ Linux. Il faut le basculer sur **AHCI**, dans la section *Storage*, *SATA Config
conserver Windows, faites le changement avec un bénévole. Si Windows est de toute conserver Windows, faites le changement avec un bénévole. Si Windows est de toute
façon remplacé, la bascule est sans conséquence. façon remplacé, la bascule est sans conséquence.
--- ---
## 2. Préparer son support d'installation ## 2. Préparer son support d'installation
@ -244,11 +246,8 @@ Avec [Ventoy](ventoy.md), la préparation tient en un copier-coller :
### Branchements ### Branchements
- Insérez la clé Ventoy dans un **port USB de la machine**, en évitant les hubs. - Insérez la clé Ventoy dans un **port USB de la machine**, en évitant les hubs.
- **Branchez un câble Ethernet.** Le script install.sh de Alpinux effectue des tests. - **Branchez un câble Ethernet.** Mint pourra télécharger mises à jour, codecs et paquets
de langue pendant l'installation — cela évite beaucoup de réglages ensuite.
!!! tip "Le dernier arrêt de Windows - Comment arrêter Windows pour démarrer sur la clé USB"
Au moment d'éteindre Windows pour insérer la clé USB, maintenez **Maj (Shift)**
enfoncée en cliquant sur *Démarrer → Arrêter*. Cela force un arrêt complet.
### Accéder au menu de démarrage ### Accéder au menu de démarrage
@ -342,27 +341,18 @@ fatigué. On ne le découvre pas au milieu du partitionnement.
En session live, taper les commandes une à une est fastidieux. Un script d'Alpinux fait En session live, taper les commandes une à une est fastidieux. Un script d'Alpinux fait
l'inventaire et rend un verdict : l'inventaire et rend un verdict :
**Sur le réseau de l'install party**, où les scripts sont servis par
[le serveur qu'Alpinux emporte](../technique/serveur-install-party.md) :
```bash ```bash
wget http://alpicache.lan/verif-disque.sh && chmod +x verif-disque.sh && ./verif-disque.sh wget https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-wiki/raw/branch/main/code/linux/linux-mint/verif-disque.sh
```
!!! tip "Si le nom ne répond pas"
Le serveur répond aussi à `alpicache`, `apt-proxy` et `10.0.0.1` — cette
dernière adresse fonctionne même quand la résolution de noms n'est pas encore
en place :
`wget http://10.0.0.1/verif-disque.sh`
**Ailleurs, depuis Internet :**
```bash
wget https://gitea.alpinux.org/alpinux.cedrica5l/alpinux.scripts/raw/branch/stable/poste/verif-disque.sh
chmod +x verif-disque.sh chmod +x verif-disque.sh
./verif-disque.sh ./verif-disque.sh
``` ```
Sur le réseau de l'install party, plus court :
```bash
wget http://10.0.0.1/verif-disque.sh && chmod +x verif-disque.sh && ./verif-disque.sh
```
Il **ne modifie rien** — il lit, il affiche, il conseille. Il demande le mot de passe pour Il **ne modifie rien** — il lit, il affiche, il conseille. Il demande le mot de passe pour
les vérifications qui le nécessitent, puis passe en revue : les vérifications qui le nécessitent, puis passe en revue :
@ -378,40 +368,6 @@ les vérifications qui le nécessitent, puis passe en revue :
Il finit par deux listes : *à régler avant d'installer*, et *à savoir*. 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://alpicache.lan/alpi-fiche.sh && chmod +x alpi-fiche.sh && sudo ./alpi-fiche.sh --check
```
Hors réseau de l'install party, il se prend sur Internet :
```bash
wget https://gitea.alpinux.org/alpinux.cedrica5l/alpinux.scripts/raw/branch/stable/poste/alpi-fiche.sh
```
Il n'aura alors aucun serveur à qui remettre la fiche : il l'affiche, et c'est tout.
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" !!! danger "BitLocker : ne touchez à rien"
Si le script annonce une partition chiffrée par BitLocker, **arrêtez-vous là**. Le 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 chiffrement doit être suspendu depuis Windows — *Paramètres → Confidentialité et
@ -612,34 +568,25 @@ Le système est installé ; quelques réglages le rendent réellement utilisable
### Le script de post-installation Alpinux ### Le script de post-installation Alpinux
Il automatise ce qui manque presque toujours après une installation standard. Ouvrez un Il automatise ce qui manque presque toujours après une installation standard. Ouvrez un
terminal (`Ctrl + Alt + T`) et récupérez-le — **sur le réseau local de l'Install Party**, terminal (`Ctrl + Alt + T`) et récupérez-le — **sur le réseau local de l'Install Party** :
où [le serveur d'Alpinux](../technique/serveur-install-party.md) le sert et met en cache
tout ce que la machine téléchargera ensuite :
```bash ```bash
wget http://alpicache.lan/install.sh http://alpicache.lan/alpi-fiche.sh wget http://10.0.0.1/install.sh
``` ```
**ou depuis Internet :** **ou depuis Internet :**
```bash ```bash
wget https://gitea.alpinux.org/alpinux.cedrica5l/alpinux.scripts/raw/branch/stable/poste/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.scripts/raw/branch/stable/poste/alpi-fiche.sh
``` ```
Puis, dans les deux cas : Puis, dans les deux cas :
```bash ```bash
chmod +x install.sh alpi-fiche.sh chmod +x install.sh
./install.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 Le mot de passe est demandé au passage (`alpinux73` en Install Party) : les caractères ne
s'affichent pas, c'est normal. s'affichent pas, c'est normal.

View file

@ -109,31 +109,16 @@ Les **Préférences** s'organisent en trois onglets :
!!! tip "Le piège du `/boot` plein" !!! tip "Le piège du `/boot` plein"
Les anciens noyaux s'accumulent et finissent par saturer la partition de démarrage, Les anciens noyaux s'accumulent et finissent par saturer la partition de démarrage,
ce qui bloque toute nouvelle mise à jour. L'onglet *Automatisation* permet de ce qui bloque toute nouvelle mise à jour. L'onglet *Automatisation* permet de
programmer leur suppression ; à défaut, passez de temps en temps par programmer leur suppression ; à défaut, retirez-les de temps en temps depuis
**Affichage → Noyaux Linux**, qui les liste et permet de retirer les plus anciens. l'*Analyseur de noyaux* du gestionnaire.
!!! warning "Changer de noyau n'est pas anodin"
La même fenêtre *Noyaux Linux* s'ouvre sur un avertissement qu'il vaut la peine de
lire : un noyau plus récent peut faire disparaître le Wi-Fi, l'accélération graphique,
ou donner un écran noir au démarrage — surtout avec des pilotes propriétaires.
La sortie de secours tient en quatre gestes, à connaître **avant** d'en installer un :
redémarrer, maintenir **Maj** pour afficher le menu GRUB, choisir *Advanced options*
puis un noyau antérieur, et désinstaller le plus récent une fois revenu.
### Activer le pare-feu ### Activer le pare-feu
Le pare-feu est installé, mais **désactivé par défaut**. Pour un ordinateur portable qui Le pare-feu est installé, mais **désactivé par défaut**. Pour un ordinateur portable qui
se connecte à des réseaux publics, il vaut mieux l'allumer : se connecte à des réseaux publics, il vaut mieux l'allumer :
**Paramètres du système → Pare-feu** (famille *Administration*). L'ouverture demande votre **Paramètres du système → Pare-feu** (famille *Administration*), puis basculez **État** sur *Activé*. Le profil *Public* convient :
mot de passe, puis il suffit de basculer **État** sur *Activé*. il bloque les connexions entrantes et laisse passer tout ce que vous initiez.
Le profil proposé par défaut, **Domestique**, convient : les deux lignes en dessous
annoncent la règle appliquée — *Entrant : Refuser*, *Sortant : Autoriser*. Autrement dit,
rien ne rentre sans y avoir été invité, tout ce que vous lancez sort normalement. Les
profils *Bureau* et *Public* servent à mémoriser d'autres jeux de règles ; sans besoin
précis, n'y touchez pas.
### Vérifier ses pilotes ### Vérifier ses pilotes

View file

@ -181,20 +181,14 @@ Une exécution réussie encadre le build par une ligne `Deploy started` et une l
--- ---
## Ce que le build publie pour alpinux.org ## Ce que le build publie pour la page d'accueil
Chaque build écrit deux fichiers à la racine du site, avec pour chaque page son titre, Chaque build écrit, à la racine du site, un fichier **`derniers-articles.json`** : les
son adresse et la date du commit qui a touché son contenu : vingt dernières pages créées ou modifiées, avec leur titre, leur adresse et la date du
commit qui a touché leur contenu. C'est ce fichier que lit la page d'accueil
d'alpinux.org pour afficher « Le wiki, fraîchement mis à jour ».
| Fichier | Ce qu'il porte | Qui le lit | Il est produit par le hook `hooks/derniers_articles.py`, déclaré dans `mkdocs.yml`.
|---|---|---|
| `derniers-articles.json` | Les vingt dernières pages créées ou modifiées | L'accueil d'alpinux.org, section « Le wiki, fraîchement mis à jour » |
| `toutes-les-pages.json` | Toutes les pages, sans limite, avec leur rubrique | La page des mises à jour d'alpinux.org |
Le second est la liste entière, le premier son début : un seul parcours de l'historique
git les produit tous les deux, et ils portent la même date de génération.
Ils sont produits par le hook `hooks/derniers_articles.py`, déclaré dans `mkdocs.yml`.
Trois choses à savoir : Trois choses à savoir :
- **Un déplacement n'est pas une mise à jour.** Les dates viennent de `git log` avec - **Un déplacement n'est pas une mise à jour.** Les dates viennent de `git log` avec
@ -204,15 +198,14 @@ Trois choses à savoir :
typiquement la redirection laissée derrière un article déplacé — n'apparaît jamais typiquement la redirection laissée derrière un article déplacé — n'apparaît jamais
dans la liste. dans la liste.
- **Le hook n'échoue jamais.** Sans historique git, il n'écrit rien et le build - **Le hook n'échoue jamais.** Sans historique git, il n'écrit rien et le build
continue ; alpinux.org masque alors simplement ce qu'il ne peut pas remplir. Ses continue ; l'accueil d'alpinux.org masque alors simplement sa section wiki. Ses
messages sont pour cela de niveau *info* et jamais *warning* : le déploiement messages sont pour cela de niveau *info* et jamais *warning* : le déploiement
construit avec `--strict`, où un seul avertissement suffit à interrompre le build, construit avec `--strict`, où un seul avertissement suffit à interrompre le build,
et donc la publication. et donc la publication.
Le même hook pose l'en-tête `Access-Control-Allow-Origin` sur ces deux fichiers, et Le même hook pose l'en-tête `Access-Control-Allow-Origin` sur ce seul fichier, au
sur eux seuls, au moyen d'un `.htaccess` déposé à la racine du site construit. Sans moyen d'un `.htaccess` déposé à la racine du site construit. Sans lui, le navigateur
lui, le navigateur refuserait à alpinux.org le droit de lire une réponse venue de refuserait à alpinux.org le droit de lire une réponse venue de wiki.alpinux.org.
wiki.alpinux.org.
!!! warning "Deux conditions" !!! warning "Deux conditions"
**Le clone doit avoir son historique.** Un clone `--depth 1` ne porte qu'un **Le clone doit avoir son historique.** Un clone `--depth 1` ne porte qu'un
@ -227,7 +220,6 @@ wiki.alpinux.org.
```bash ```bash
# vérifier ce que le dernier build a publié # vérifier ce que le dernier build a publié
curl -s https://wiki.alpinux.org/derniers-articles.json | head -20 curl -s https://wiki.alpinux.org/derniers-articles.json | head -20
curl -s https://wiki.alpinux.org/toutes-les-pages.json | head -20
``` ```
--- ---
@ -264,5 +256,4 @@ Ouvrez [http://localhost:8000](http://localhost:8000) — MkDocs recharge automa
| Déployer à la main (webhook en panne) | `deploy-wiki.sh`, sur le serveur | | Déployer à la main (webhook en panne) | `deploy-wiki.sh`, sur le serveur |
| Voir le journal des déploiements | `tail -20 /var/log/wiki-deploy.log` | | Voir le journal des déploiements | `tail -20 /var/log/wiki-deploy.log` |
| Vérifier la liste envoyée à l'accueil | `curl -s https://wiki.alpinux.org/derniers-articles.json` | | Vérifier la liste envoyée à l'accueil | `curl -s https://wiki.alpinux.org/derniers-articles.json` |
| Vérifier l'inventaire complet publié | `curl -s https://wiki.alpinux.org/toutes-les-pages.json` |
| Régénérer les fichiers du logo (poste local) | `python3 scripts/build-assets.py` | | Régénérer les fichiers du logo (poste local) | `python3 scripts/build-assets.py` |

View file

@ -1,169 +0,0 @@
---
description: Le serveur qu'Alpinux emporte en install party — partage de connexion, réseau local, cache de paquets APT, page de santé, et ce qu'un bénévole doit en savoir sur place.
---
# Le serveur qu'on emporte en install party
Quinze ordinateurs qui s'installent le même matin, c'est quinze fois les mêmes
cinq cents mégaoctets tirés par la ligne de la salle — quand il y en a une. Alpinux
apporte donc sa propre machine, **alpicache**, qui partage l'accès à Internet, sert un
réseau local aux postes en cours d'installation, et **garde en cache les paquets déjà
téléchargés**.
!!! note "Pour qui est cette page"
Pour les bénévoles qui installent, et pour qui prépare la salle. La configuration
complète de la machine — interfaces, services, dépannage détaillé — vit dans son
propre dépôt, `alpinux.alpicache`, et non ici.
---
## Le réseau de la salle
```mermaid
flowchart LR
A[Prise murale<br/>ou box de la salle] --> B[alpicache<br/>10.0.0.1]
B --> C[Switch]
C --> D[Poste 1]
C --> E[Poste 2]
C --> F[Poste n]
```
La machine tient les deux bouts : d'un côté elle demande un accès à la salle, de l'autre
elle distribue un réseau en **10.0.0.x** aux postes branchés sur le switch. Les rôles ne
sont pas figés à l'avance : elle désigne comme sortie la première prise où on lui répond,
et sert le réseau local sur l'autre.
!!! warning "Les postes se branchent sur le switch, jamais sur la box"
Un poste branché directement sur la box de la salle reçoit une adresse de la salle :
il ne voit plus le cache, ne trouve plus les scripts, et fait tomber les compteurs à
zéro. C'est l'erreur la plus fréquente, et elle ne se voit pas tout de suite.
---
## Ce que la machine offre, une fois branchée
| Adresse | À quoi ça sert |
| --- | --- |
| **<http://alpicache.lan/>** | La page de santé : réseau, disques, machines servies, cache |
| **`http://apt-proxy:3142`** | Le cache de paquets, à déclarer comme mandataire |
| **`http://10.0.0.1/`** | Les scripts d'Alpinux, servis sur place — dont `verif-disque.sh` et `install.sh` |
La page de santé se rafraîchit seule ; c'est le tableau de bord de la journée. Quatre
cartes à surveiller :
| Carte | Ce qu'on veut y voir |
| --- | --- |
| **Réseau** | une sortie *et* un réseau local nommés, « Internet joignable » |
| **Disques** | le disque du cache présent, avec de la place |
| **Machines servies** | le nombre grandit à mesure que les postes arrivent |
| **Cache de paquets** | « Économisé » qui monte au fil de la matinée |
**« Machines servies » qui reste à zéro** alors que des postes sont branchés, c'est le
signe que le réseau local ne distribue pas d'adresses : prévenez la personne qui tient la
machine plutôt que de chercher sur le poste du participant.
---
## Faire passer une installation par le cache
C'est là que tout se joue, et le geste n'est pas le même selon le système.
### Debian
L'installateur demande un mandataire à l'écran **« Configurer le miroir de l'archive
Debian »**. Saisissez-y :
```
http://apt-proxy:3142
```
C'est **le seul moment** où on peut le faire, et c'est celui qui compte : toute
l'installation passe alors par le cache.
### Linux Mint et Ubuntu
Leur installateur ne demande pas de mandataire : l'installation elle-même sortira sur
Internet. Le gain arrive juste après — sur les mises à jour et les logiciels ajoutés,
c'est-à-dire sur l'essentiel du volume.
Une fois la machine redémarrée sur son nouveau système :
```bash
sudo apt install auto-apt-proxy
```
Ce paquet cherche un cache sur le réseau local et s'en sert quand il en trouve un — sans
rien à configurer, et sans gêner la machine une fois rentrée chez son propriétaire.
!!! tip "Le script de post-installation le fait déjà"
Le [script d'Alpinux](../guides/linux-mint-depuis-windows.md#le-script-de-post-installation-alpinux)
installe `auto-apt-proxy` parmi ses réglages : si vous le lancez, c'est fait.
---
## Ce qui coince, et la parade
**La salle n'a pas de réseau.** Un téléphone Android en partage de connexion USB fait
l'affaire : branché sur la machine, il devient la sortie. Prévoyez le câble.
**Le disque du cache n'est pas vu.** Il doit être branché **avant** d'allumer la machine.
Après coup, le cache démarre sans rien dire et ne garde rien — la carte *Disques* de la
page de santé le montre tout de suite.
**Un poste n'obtient pas d'adresse.** Vérifiez d'abord le câble et le switch, puis la
carte *Machines servies*. Une adresse en `169.254.x.x` signifie que le poste n'a reçu
aucune réponse : il s'en est inventé une.
**`apt` renvoie des « 503 Connection closed » en série.** Le cache accepte la requête mais
ne sait pas où aller la chercher — c'est arrivé le 24/09/2026, et une installation Mint
s'y est arrêtée. Le piège est qu'**interroger le cache directement répond parfaitement** :
les deux chemins ne suivent pas la même route à l'intérieur du serveur. Ne cherchez donc
pas sur la machine du participant : prévenez la personne qui tient le serveur.
---
## Pour préparer une install party
La veille, la machine s'allume chez quelqu'un, sur une ligne rapide, et l'on garnit le
cache avec ce qu'on va installer — si la version de Mint a changé depuis la dernière
sortie, autant la tirer maintenant.
!!! warning "Le cache ne sert que ce qu'il connaît"
Le pré-chargement vise des versions nommées, et une version absente de la liste ne
profite à personne. Le 24/09/2026, la première Mint 22.3 de la soirée a tiré **1,3 Go
d'Internet pour 82 Mo servis par le cache** : le pré-chargeur couvrait encore les
versions précédentes.
Avant chaque sortie, vérifiez donc que la liste des versions pré-chargées correspond à
celle que vous allez réellement installer. C'est par le **nom de code** que le
pré-chargement les désigne, et un décalage d'un cran suffit à garnir le cache de la
mauvaise version :
| Version | Nom de code |
|---|---|
| Mint 22 | `wilma` |
| Mint 22.1 | `xia` |
| Mint 22.2 | `zara` |
| Mint 22.3 | `zena` |
Pour mémoire, la série précédente : `vanessa` (21), `vera` (21.1), `victoria` (21.2),
`virginia` (21.3).
Sur place, l'ordre de branchement n'est pas une superstition : le disque externe d'abord,
puis le câble Internet **seul**, puis la machine, et seulement ensuite le switch et les
postes. Brancher la sortie seule d'abord, c'est ôter à la machine toute occasion de se
tromper de rôle.
La procédure complète — ce qu'on emporte, les vérifications, le dépannage, la fiche à
cocher pour chaque participant et l'affiche à scotcher près du switch — est dans le dépôt
`alpinux.alpicache`. Demandez-la à un membre du bureau avant une sortie.
---
## Voir aussi
- [Install Party](../alpinux/install-party.md) — les dates, et comment y participer
- [Installer Linux Mint depuis Windows](../guides/linux-mint-depuis-windows.md) — le guide
suivi sur place
- [Préparation d'un serveur Debian 13](serveur-debian-13.md) — pour la machine, la base
système est la même

View file

@ -1,15 +1,8 @@
"""Publie ce que le wiki a écrit ou repris, pour qui l'affiche ailleurs. """Publie `derniers-articles.json` : ce que le wiki a écrit ou repris récemment.
Deux fichiers, écrits à chaque build à côté des pages et servis avec l'en-tête CORS La page d'accueil d'alpinux.org lit ce fichier pour afficher « Le wiki, fraîchement
sans lequel un autre domaine ne peut pas les lire : mis à jour ». Il est donc écrit à chaque build, à côté des pages, et servi avec
l'en-tête CORS sans lequel un autre domaine ne peut pas le lire.
- `derniers-articles.json` — les vingt dernières pages touchées, que la page d'accueil
d'alpinux.org lit pour sa section « Le wiki, fraîchement mis à jour » ;
- `toutes-les-pages.json` — le même inventaire, sans limite de nombre et avec la
rubrique de chaque page, pour la page des mises à jour d'alpinux.org.
Les deux sortent du même calcul : le second est la liste entière, le premier son
début.
Les dates viennent de git, pas du système de fichiers : un `git clone` repose tous les Les dates viennent de git, pas du système de fichiers : un `git clone` repose tous les
fichiers à la même seconde, et une page recopiée depuis Obsidian aurait l'air neuve. fichiers à la même seconde, et une page recopiée depuis Obsidian aurait l'air neuve.
@ -27,11 +20,10 @@ from urllib.parse import urljoin
from mkdocs.plugins import log from mkdocs.plugins import log
FICHIER_RECENTS = "derniers-articles.json" FICHIER = "derniers-articles.json"
FICHIER_TOUTES = "toutes-les-pages.json"
# Ce que le premier fichier porte au plus. L'accueil n'en affiche que six, mais le # Ce que le fichier porte au plus. L'accueil n'en affiche que six, mais le fichier
# fichier sert aussi à qui voudrait en faire autre chose. Le second ne plafonne pas. # sert aussi à qui voudrait en faire autre chose.
NB_MAX = 20 NB_MAX = 20
# En deçà, un article est annoncé comme « publié » plutôt que « mis à jour ». # En deçà, un article est annoncé comme « publié » plutôt que « mis à jour ».
@ -42,24 +34,10 @@ EXCLUS = {"index.md"}
_pages: dict[str, dict[str, str]] = {} _pages: dict[str, dict[str, str]] = {}
_dans_la_nav: set[str] = set() _dans_la_nav: set[str] = set()
_rubriques: dict[str, str] = {}
def _rubrique_par_page(items, rubrique=""):
"""Associe chaque page de la navigation au titre de la section qui la porte.
On garde la section de premier niveau — « Guides », « Contribuer » — plutôt que la
plus proche : c'est l'échelle à laquelle un lecteur range les pages.
"""
for item in items:
if item.is_section:
yield from _rubrique_par_page(item.children, rubrique or item.title)
elif item.is_page:
yield item.file.src_uri, rubrique
def on_nav(nav, config, files): def on_nav(nav, config, files):
"""Retient les pages de la navigation, et sous quelle rubrique elles vivent. """Retient les pages de la navigation.
Ce qui en est absent — les redirections laissées derrière un article déplacé, par Ce qui en est absent — les redirections laissées derrière un article déplacé, par
exemple — n'a pas à être annoncé comme une nouveauté. exemple — n'a pas à être annoncé comme une nouveauté.
@ -67,9 +45,6 @@ def on_nav(nav, config, files):
_dans_la_nav.clear() _dans_la_nav.clear()
_dans_la_nav.update(page.file.src_uri for page in nav.pages) _dans_la_nav.update(page.file.src_uri for page in nav.pages)
_rubriques.clear()
_rubriques.update(_rubrique_par_page(nav.items))
def on_page_context(context, page, config, nav): def on_page_context(context, page, config, nav):
"""Retient le titre et l'URL de chaque page rendue.""" """Retient le titre et l'URL de chaque page rendue."""
@ -131,30 +106,24 @@ def _historique(depot: Path, dossier: str) -> tuple[dict, dict]:
return modifiees, creees return modifiees, creees
def _autoriser_la_lecture_croisee(site_dir: Path, fichiers: list[str]) -> None: def _autoriser_la_lecture_croisee(site_dir: Path) -> None:
"""Pose l'en-tête CORS sur les seuls fichiers qui en ont besoin. """Pose l'en-tête CORS sur le seul fichier qui en a besoin.
Ils sont lus depuis alpinux.org : sans cet en-tête, le navigateur refuse la Le fichier est lu depuis alpinux.org : sans cet en-tête, le navigateur refuse la
réponse et la section reste masquée. Le bloc s'ajoute à un `.htaccess` déjà là réponse et la section reste masquée. Le bloc s'ajoute à un `.htaccess` déjà là
plutôt que de le remplacer — on ne sait pas ce qu'il porte d'autre — et n'est plutôt que de le remplacer — on ne sait pas ce qu'il porte d'autre.
écrit que pour les fichiers qui n'y figurent pas encore.
""" """
bloc = (
"<IfModule mod_headers.c>\n"
f' <Files "{FICHIER}">\n'
' Header set Access-Control-Allow-Origin "*"\n'
" </Files>\n"
"</IfModule>\n"
)
fichier = site_dir / ".htaccess" fichier = site_dir / ".htaccess"
existant = fichier.read_text(encoding="utf-8") if fichier.exists() else "" existant = fichier.read_text(encoding="utf-8") if fichier.exists() else ""
if FICHIER in existant:
manquants = [nom for nom in fichiers if nom not in existant]
if not manquants:
return return
bloc = "<IfModule mod_headers.c>\n"
for nom in manquants:
bloc += (
f' <Files "{nom}">\n'
' Header set Access-Control-Allow-Origin "*"\n'
" </Files>\n"
)
bloc += "</IfModule>\n"
separateur = "\n" if existant and not existant.endswith("\n") else "" separateur = "\n" if existant and not existant.endswith("\n") else ""
fichier.write_text(existant + separateur + bloc, encoding="utf-8") fichier.write_text(existant + separateur + bloc, encoding="utf-8")
@ -175,7 +144,7 @@ def on_post_build(config):
log.info("derniers-articles : clone superficiel, les dates ne sont pas fiables") log.info("derniers-articles : clone superficiel, les dates ne sont pas fiables")
seuil = datetime.now(timezone.utc) - timedelta(days=JOURS_NOUVEAU) seuil = datetime.now(timezone.utc) - timedelta(days=JOURS_NOUVEAU)
pages = [] articles = []
for chemin, modifiee in modifiees.items(): for chemin, modifiee in modifiees.items():
prefixe = docs_dir.name + "/" prefixe = docs_dir.name + "/"
@ -193,43 +162,30 @@ def on_post_build(config):
except ValueError: except ValueError:
nouveau = False nouveau = False
pages.append({ articles.append({
"titre": page["titre"], "titre": page["titre"],
"url": page["url"], "url": page["url"],
"date": creee if nouveau else modifiee, "date": creee if nouveau else modifiee,
"nouveau": nouveau, "nouveau": nouveau,
"rubrique": _rubriques.get(src_uri, ""),
}) })
if not pages: if not articles:
log.info("derniers-articles : aucune page datée, fichier non écrit") log.info("derniers-articles : aucune page datée, fichier non écrit")
return return
pages.sort(key=lambda page: page["date"], reverse=True) articles.sort(key=lambda article: article["date"], reverse=True)
articles = articles[:NB_MAX]
genere = datetime.now(timezone.utc).replace(microsecond=0).isoformat() genere = datetime.now(timezone.utc).replace(microsecond=0).isoformat()
contenu = json.dumps({"genere": genere, "articles": articles},
# L'accueil lit ce fichier depuis longtemps : il garde ses quatre clés, sans la ensure_ascii=False, indent=2) + "\n"
# rubrique dont il n'a que faire.
recents = [
{clef: page[clef] for clef in ("titre", "url", "date", "nouveau")}
for page in pages[:NB_MAX]
]
ecrits = {
FICHIER_RECENTS: {"genere": genere, "articles": recents},
FICHIER_TOUTES: {"genere": genere, "pages": pages},
}
try: try:
for nom, contenu in ecrits.items(): (site_dir / FICHIER).write_text(contenu, encoding="utf-8")
texte = json.dumps(contenu, ensure_ascii=False, indent=2) + "\n" _autoriser_la_lecture_croisee(site_dir)
(site_dir / nom).write_text(texte, encoding="utf-8")
_autoriser_la_lecture_croisee(site_dir, list(ecrits))
except OSError as erreur: except OSError as erreur:
log.info("derniers-articles : écriture impossible (%s), fichier non écrit", log.info("derniers-articles : écriture impossible (%s), fichier non écrit",
erreur) erreur)
return return
log.info("derniers-articles : %d page(s) publiée(s) dans %s, %d dans %s", log.info("derniers-articles : %d article(s) publié(s) dans %s", len(articles), FICHIER)
len(pages), FICHIER_TOUTES, len(recents), FICHIER_RECENTS)

View file

@ -89,11 +89,6 @@ nav:
- J'organise une install party: alpinux/installparty-organisateur.md - J'organise une install party: alpinux/installparty-organisateur.md
- Présentations passées: alpinux/presentations.md - Présentations passées: alpinux/presentations.md
- Événements & RDV: alpinux/evenements.md - Événements & RDV: alpinux/evenements.md
- Messagerie et listes:
- alpinux/messagerie.md
- S'abonner et se désabonner: alpinux/messagerie-abonnement.md
- Écrire et recevoir des messages: alpinux/messagerie-membre.md
- Gérer une liste de diffusion: alpinux/messagerie-gestionnaire.md
- Vos données, vos adresses, nos envois: alpinux/donnees-personnelles.md - Vos données, vos adresses, nos envois: alpinux/donnees-personnelles.md
- Présentations: - Présentations:
- presentations/libreoffice.md - presentations/libreoffice.md
@ -119,7 +114,6 @@ nav:
- technique/matrix.md - technique/matrix.md
- technique/arduino-ide.md - technique/arduino-ide.md
- technique/serveur-debian-13.md - technique/serveur-debian-13.md
- technique/serveur-install-party.md
- technique/bigbluebutton.md - technique/bigbluebutton.md
- technique/ateliers.md - technique/ateliers.md
- Communication: - Communication: