#!/usr/bin/env bash # # ============================================================================ # nettoyage_stickeuse.sh — version 1.1 # # Retire l'ANCIENNE Stickeuse QL-570 d'un poste OP (Les Portes Logiques). # # Auteurice : Vitally LUBIN — Les Portes Logiques, 2026 # Licence : GNU Affero General Public License v3 (AGPL-3.0) # https://www.gnu.org/licenses/agpl-3.0.html # # POURQUOI # Faire place nette AVANT d'installer la version aboutie du dépôt : # https://github.com/lesporteslogiques/stickeuse # Sans ce ménage, les résidus de l'ancienne version (lanceurs orphelins, # agent fantôme, règle udev obsolète) parasitent le déploiement. # Ordre : nettoyer, puis installer. # # UTILISATION RAPIDE # su - # bash nettoyage_stickeuse.sh -n # inventaire seul, ne supprime rien # bash nettoyage_stickeuse.sh # inventorie, montre, demande, supprime # # OPTIONS # -n, --inventaire-seul affiche ce qui serait supprimé, puis s'arrête # -y, --oui ne demande pas confirmation (passage en série) # --avec-pip désinstalle aussi la bibliothèque Python brother_ql # -h, --aide affiche cette aide # # DÉROULÉ # 1. Inventaire : cherche les traces, ne touche à rien # 2. Confirmation : affiche la liste, attend un « o » # 3. Suppression : efface, recharge udev, rafraîchit les menus # 4. Vérification : relance la recherche et annonce ce qui subsiste # # GARDE-FOU # La NOUVELLE version (/opt/ql570 et son venv) n'est JAMAIS supprimée. # Tout lanceur .desktop pointant vers elle est conservé et signalé. # # Documentation complète : https://lesporteslogiques.net/wiki/materiel/logicos/start # ============================================================================ # set -u : une variable non définie devient une erreur au lieu d'un vide # silencieux. Sur un script qui fait des rm, c'est vital : « rm -rf # $CHEMIN » avec CHEMIN vide viserait la racine. # set -o pipefail : dans « a | b », le code retour est celui du premier # maillon qui échoue, pas seulement du dernier. # (pas de « set -e » : on veut continuer le nettoyage même si un rm échoue, # et le signaler dans le journal.) set -uo pipefail VERSION="1.1" HOTE="$(hostname)" # sert à nommer le journal par poste HORODATE="$(date +%Y%m%d-%H%M%S)" JOURNAL="$(pwd)/nettoyage_${HOTE}_${HORODATE}.log" # Drapeaux, modifiés plus bas par les options de la ligne de commande INVENTAIRE_SEUL=0 SANS_CONFIRMATION=0 AVEC_PIP=0 # Liste blanche : chemins de la NOUVELLE version, jamais supprimés. # Ajouter ici tout autre chemin à préserver. PROTEGES=("/opt/ql570") CIBLES=() # ce qui sera supprimé SIGNALES=() # ce qui est conservé volontairement, mais mérite d'être dit # ---------------------------------------------------------------- utilitaires # Tout ce qui s'affiche part aussi dans le journal : « tee -a » écrit sur la # sortie standard ET ajoute au fichier. Sur 30 postes, le journal est la seule # trace de ce qui a été trouvé et supprimé sur chacun. msg() { printf '%s\n' "$*" | tee -a "$JOURNAL"; } titre() { printf '\n=== %s ===\n' "$*" | tee -a "$JOURNAL"; } # Le chemin appartient-il à la nouvelle version ? # Le motif "$p"/* attrape aussi tout ce qui est DANS un dossier protégé. est_protege() { local chemin="$1" p for p in "${PROTEGES[@]}"; do [[ "$chemin" == "$p" || "$chemin" == "$p"/* ]] && return 0 done return 1 } # Ajoute un chemin à la liste de suppression, avec trois filtres : # 1. chemin vide -> ignoré (protection anti-« rm -rf » sur rien) # 2. n'existe pas -> ignoré (inutile d'encombrer la liste) # 3. protégé -> ignoré (nouvelle version) # 4. déjà présent -> ignoré (évite les doublons) ajouter() { local chemin="$1" [[ -z "$chemin" ]] && return [[ -e "$chemin" ]] || return est_protege "$chemin" && return local c # ${CIBLES[@]+"${CIBLES[@]}"} : formule qui évite l'erreur « variable non # définie » (set -u) quand le tableau est encore vide. for c in ${CIBLES[@]+"${CIBLES[@]}"}; do [[ "$c" == "$chemin" ]] && return done CIBLES+=("$chemin") } signaler() { SIGNALES+=("$1"); } # L'aide est l'en-tête du fichier lui-même : une seule source, jamais de # décalage entre la doc et le code. aide() { sed -n '4,38p' "$0" | sed 's/^# \{0,1\}//'; exit 0; } # ------------------------------------------------------------------ arguments while [[ $# -gt 0 ]]; do case "$1" in -n|--inventaire-seul) INVENTAIRE_SEUL=1 ;; -y|--oui) SANS_CONFIRMATION=1 ;; --avec-pip) AVEC_PIP=1 ;; -h|--aide) aide ;; *) echo "Option inconnue : $1 (voir --aide)" >&2; exit 2 ;; esac shift done # EUID = identifiant effectif de l'utilisateur ; 0 = root. # Nécessaire pour /opt et /etc, et pour parcourir tous les /home. if [[ "${EUID}" -ne 0 ]]; then echo "Ce script doit tourner en root : su - puis bash $0" >&2 exit 1 fi msg "nettoyage_stickeuse.sh v${VERSION} — poste ${HOTE} — ${HORODATE}" msg "Journal : ${JOURNAL}" # ============================================================== 1. INVENTAIRE # Rien n'est supprimé ici : on ne fait que remplir CIBLES et SIGNALES. titre "1. Inventaire" # --- côté système ----------------------------------------------------------- ajouter "/opt/stickeuse_ql570" # dossier de l'ancienne version ajouter "/usr/local/bin/ql570" # binaire C compilé de l'ancienne version # Lanceurs du menu général. C'est ici que se joue la distinction ancienne / # nouvelle : la ligne « Exec= » du fichier .desktop dit quel programme est # lancé. Si elle pointe vers /opt/ql570, c'est la NOUVELLE -> on conserve. for f in /usr/share/applications/*ql570* /usr/share/applications/*stickeuse*; do [[ -e "$f" ]] || continue # le motif non résolu reste littéral : on saute if grep -qs 'Exec=.*/opt/ql570' "$f"; then signaler "$f (lanceur de la NOUVELLE version — conservé)" else ajouter "$f" fi done # Règles udev : elles donnent à l'utilisateur le droit de parler à # l'imprimante USB. Absentes sur OP60, présentes sur OP42. for f in /etc/udev/rules.d/*ql570* /etc/udev/rules.d/*brother-ql*; do [[ -e "$f" ]] && ajouter "$f" done # --- côté utilisateurs ------------------------------------------------------ # On balaie /home/* plutôt que d'écrire un nom de compte en dur : il vaut # « xor » sur OP42, « lesporteslogiques » sur OP60, et sans doute autre chose # ailleurs. Le script s'adapte sans être modifié. for maison in /home/*; do [[ -d "$maison" ]] || continue ajouter "$maison/bin/stickeuse_ql570" # dossier de travail (OP60) ajouter "$maison/.gitdl/ql570" # sources téléchargées (OP60) ajouter "$maison/.ql570" # journal d'impression de l'appli # Recherche par motif dans les dossiers de configuration. # -maxdepth 1 : on ne descend pas dans les sous-dossiers, pour ne pas # ramasser un fichier sans rapport enfoui quelque part. for d in "$maison/.icons" "$maison/.config" "$maison/.cache" \ "$maison/.local/share" "$maison/.config/autostart"; do [[ -d "$d" ]] || continue # « while read < <(commande) » : on lit ligne par ligne sans sous-shell, # sinon les ajouts au tableau CIBLES seraient perdus. while IFS= read -r f; do ajouter "$f" done < <(find "$d" -maxdepth 1 \( -iname '*stickeuse*' -o -iname '*ql570*' \) 2>/dev/null) done # Lanceurs personnels : menu utilisateur, Bureau (session en français) et # Desktop (session en anglais). Même test Exec= que plus haut. for d in "$maison/.local/share/applications" "$maison/Bureau" "$maison/Desktop"; do [[ -d "$d" ]] || continue while IFS= read -r f; do if grep -qs 'Exec=.*/opt/ql570' "$f"; then signaler "$f (lanceur de la NOUVELLE version — conservé)" else ajouter "$f" fi done < <(find "$d" -maxdepth 1 \( -iname '*stickeuse*' -o -iname '*ql570*' \) 2>/dev/null) done done # --- bibliothèque Python brother_ql ----------------------------------------- # Installée par pip en root dans /usr/local, donc partagée par tout le poste. # Elle n'appartient PAS à l'ancienne version (qui utilisait un binaire C) : # quelqu'un a pu l'installer pour son propre usage. Conservée par défaut. PIP_PRESENT=0 if command -v brother_ql >/dev/null 2>&1 || \ ls -d /usr/local/lib/python3.*/dist-packages/brother_ql >/dev/null 2>&1; then PIP_PRESENT=1 fi # --- restitution de l'inventaire -------------------------------------------- if [[ ${#CIBLES[@]} -eq 0 ]]; then msg "Aucune trace de l'ancienne Stickeuse sur ce poste." else msg "${#CIBLES[@]} élément(s) à supprimer :" for c in "${CIBLES[@]}"; do msg " - $c"; done fi if [[ ${#SIGNALES[@]} -gt 0 ]]; then msg "" msg "Conservé (non concerné par l'ancienne version) :" for s in "${SIGNALES[@]}"; do msg " ! $s"; done fi if [[ $PIP_PRESENT -eq 1 ]]; then msg "" if [[ $AVEC_PIP -eq 1 ]]; then msg "brother_ql (pip) : présent — sera désinstallé (--avec-pip)." else msg "brother_ql (pip) : présent — CONSERVÉ. Relancer avec --avec-pip pour l'ôter." fi fi if [[ $INVENTAIRE_SEUL -eq 1 ]]; then msg "" msg "Mode inventaire seul : rien n'a été supprimé." exit 0 fi # Poste déjà propre : on s'arrête là. [[ ${#CIBLES[@]} -eq 0 && $PIP_PRESENT -eq 0 ]] && exit 0 # ============================================================ 2. CONFIRMATION if [[ $SANS_CONFIRMATION -eq 0 ]]; then printf '\nSupprimer les éléments ci-dessus ? [o/N] ' read -r reponse echo "Réponse : ${reponse:-N}" >> "$JOURNAL" case "${reponse}" in [oOyY]) ;; # o, O, y ou Y : on continue *) msg "Abandon : rien n'a été supprimé."; exit 0 ;; # tout le reste : on sort esac fi # ============================================================= 3. SUPPRESSION titre "2. Suppression" UDEV_TOUCHE=0 for c in ${CIBLES[@]+"${CIBLES[@]}"}; do est_protege "$c" && continue # second contrôle, au cas où la liste # blanche aurait évolué entre-temps [[ "$c" == /etc/udev/rules.d/* ]] && UDEV_TOUCHE=1 # « -- » marque la fin des options : un fichier nommé « -rf » serait traité # comme un nom, pas comme un drapeau. if rm -rf -- "$c"; then msg " supprimé : $c" else msg " ÉCHEC : $c" fi done # Sans rechargement, une règle supprimée reste active jusqu'au redémarrage. if [[ $UDEV_TOUCHE -eq 1 ]]; then udevadm control --reload-rules 2>/dev/null && msg " règles udev rechargées" fi # Debian 13 refuse pip en root sans --break-system-packages (PEP 668). # Sans risque ici : ces fichiers vivent dans /usr/local, hors du Python géré # par apt. Le « || » relance sans le drapeau sur les Debian plus anciennes. if [[ $PIP_PRESENT -eq 1 && $AVEC_PIP -eq 1 ]]; then msg " désinstallation de brother_ql (pip)" pip3 uninstall -y --break-system-packages brother_ql 2>&1 | tee -a "$JOURNAL" \ || pip3 uninstall -y brother_ql 2>&1 | tee -a "$JOURNAL" fi # Le menu lit un cache : sans ce rafraîchissement, l'entrée reste affichée # alors que le fichier a disparu (clic sans effet). if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database /usr/share/applications 2>/dev/null for maison in /home/*; do [[ -d "$maison/.local/share/applications" ]] && \ update-desktop-database "$maison/.local/share/applications" 2>/dev/null done msg " menus rafraîchis" fi # ============================================================ 4. VERIFICATION # On relance la recherche à blanc sur tout le disque pour confirmer. titre "3. Vérification" # -xdev : ne franchit pas les frontières de systèmes de fichiers. Évite de # partir dans /proc et /sys. Si /home est sur une autre partition, il faut # relancer la recherche dessus (voir README, section « Limites »). # # On écarte de la liste des « restes » : la nouvelle version, ce qui a été # volontairement conservé, le script lui-même et son journal — sinon le script # signalerait comme suspect ce qu'il vient de décider de garder. MOI="$(readlink -f "$0" 2>/dev/null || echo "$0")" RESTES="$(find / -xdev \( -iname '*stickeuse*' -o -iname '*ql570*' \) 2>/dev/null \ | grep -v '^/opt/ql570' \ | grep -Fxv "$MOI" \ | grep -Fv "$(basename "$MOI")" \ | grep -Fxv "$JOURNAL" || true)" for s in ${SIGNALES[@]+"${SIGNALES[@]}"}; do chemin="${s%% (*}" # retire le commentaire entre parenthèses RESTES="$(printf '%s\n' "$RESTES" | grep -Fxv "$chemin" || true)" done if [[ -z "$RESTES" ]]; then msg "Aucun reste. Poste ${HOTE} propre." else msg "Restes à examiner :" printf '%s\n' "$RESTES" | tee -a "$JOURNAL" fi if command -v brother_ql >/dev/null 2>&1; then msg "brother_ql (pip) toujours en place (conservé volontairement, ou --avec-pip non passé)." fi msg "" msg "Terminé. Fermer et rouvrir la session pour rafraîchir l'affichage du menu." msg "Journal : ${JOURNAL}"