02_les petites procédures de Claude A - synthèse des procédures et état de fonctionnement Rédigé le 13/09/2026, ligne Seedbox-Films ajoutée le 14/09. La colonne « Dernière utilisation connue » est figée à cette date : la valeur vivante est relue chaque matin par le canari et affichée dans le tableau « Procédures » en pied de la page de veille du jour. Ce que cette page fait Depuis juin, les procédures se sont multipliées : compression de PDF et de photos, conversion de musique, sauvegardes, synchronisations, tunnels. Chacune a sa page détaillée. Celle-ci les rassemble en une ligne chacune — quoi, où, quand, dernière trace, comment vérifier — pour répondre vite à « est-ce que ça tourne encore ? ». Trois familles selon qui peut les voir : le VPS observe directement ce qui tourne chez lui et, par le tunnel, le NAS ; il ne voit pas le PC. Les procédures du PC laissent donc un petit fichier témoin dans l'arbre Syncthing ( Jux-scripts/Etat-Infra/temoins/) à chaque passe réelle, et c'est lui que le canari lit. Automatiques sur le VPS Tournent sans intervention. Le canari du matin les relit chaque jour ; toute dérive apparaît dans la page de veille. Procédure Ce qu'elle fait Où / quand Dernière utilisation connue Vérification Sauvegardes VPS → kDrive page 176 vps_backup.sh : BookStack, Immich, Joplin, Mealie, Readeck, Baïkal + ~/Documents (Syncthing) en 7z vers kDrive. Rotation 7 jours ; copie mensuelle de Documents sur 12 mois. VPS · cron 2h UTC (14 min, Joplin en tête) 13/09 02:13 UTC — BILAN 8/8 tail -3 /opt/backups/backup.log → BILAN 8/8 OK. Première ligne de la veille. Komga-PDF page 220 Un PDF déposé dans D:\Syncthing\komga-pdf\ revient compressé par StirlingPDF dans komga-compressed/ (−57 à −66 %). Écriture atomique, 3 tentatives, journal persistant. VPS · service komga-watch (inotify + rebalayage 10 min) 09/09 — Cuisine et Vins de France 09-10.2026, 59,9 → 20,5 Mio en 62 s journalctl -u komga-watch -n 20 ; /home/debian/logs/komga_compress.log. Le canari teste le login Stirling chaque matin (panne muette du 7 au 9/09). Komga-Cache page 168 komga_scan.sh : vfs/refresh du montage kDrive puis scan des 23 bibliothèques, avant le scan interne de Komga à 4 h. Sans lui un fichier déposé reste invisible jusqu'à 72 h. VPS · cron 3h45 UTC 13/09 — 23 scans, aucun échec tail /home/debian/logs/komga_scan.log → aucun echec. Seedbox-Films page 318 seedbox_films.sh : les films du dossier FILMS de la seedbox (WebDAV) copiés vers kDrive Videos_NAS/video/movies sans toucher le disque du VPS, puis vfs/refresh du montage et scan de la seule bibliothèque Jellyfin « Films récents ». copy jamais sync ; mémoire locale des copies (kDrive liste les gros fichiers frais avec retard). VPS · cron toutes les 5 min, muet si rien de nouveau 14/09 — 7 films, 13,4 Gio en 196 s (~73 Mo/s) tail /home/debian/logs/seedbox_films.log ; sonde « Seedbox-Films » des tâches nocturnes et ligne « Seedbox » du tableau des procédures de la veille. Scan Navidrome page 170 Quick scan quotidien (titres existants) et full scan hebdomadaire par redémarrage du conteneur (nouveaux albums), après vfs/refresh du montage music. VPS · cron 4h30 UTC quotidien, lundi 3h UTC complet 13/09 04:30 UTC tail /var/log/navidrome_scan.log ; API getScanStatus → scanning:false. Scan Audiobookshelf page 155 Scan des 36 bibliothèques par l'API (JWT). VPS · cron 4h UTC 13/09 — SCAN OK 36/36 tail /var/log/abs_scan.log → SCAN OK. Compression Joplin page 222 Images stockées en base PostgreSQL recompressées en JPEG 85 % ; observatoire réécrit sur la page 166. Terminée depuis juin, ne traite que les nouvelles ressources. VPS · timer systemd 3h UTC 13/09 — 893 ressources, 396,5 Mo économisés (−77,7 %) sudo grep -c Traceback /var/log/joplin-compress.log doit rester stable ; page 166 datée du jour. Veille FreshRSS → BookStack page 252 Page « Veille AAAA-MM-JJ » : articles du jour par thème, dédoublonnés, critères lus dans la page 252. Porte aussi l'état de l'infrastructure et le tableau des procédures. VPS · cron 5h30 UTC 13/09 07:30 — page 314, 13 432 caractères La page du jour existe dans le chapitre 251 ; tail /home/debian/logs/veille_freshrss.log. Canari Etat-Infra page 251 etat_infra.py : sauvegardes, 17 services web, conteneurs, certificats servis, montages, Syncthing, WireGuard, NAS, Komga-PDF, journaux nocturnes, disque, agenda Baïkal, procédures → JSON du jour, rendu dans la veille. Muet quand tout va bien. VPS · cron 5h15 UTC 13/09 07:15 — vert, 0 anomalie Ligne du haut de la veille (Sonde absente = le collecteur n'a pas tourné) ; /home/debian/logs/etat_infra.log ; python3 etat_infra.py --dry-run. monitor-rclone page 170 Vérifie les 7 montages FUSE ; si l'un est mort : restart du service rclone puis des conteneurs qui le lisent. Silencieux quand tout va bien. VPS · cron toutes les 5 min 12/09 00:20 UTC — recovery de /home/debian/photo (Immich redémarré) tail /home/debian/logs/rclone-monitor.log ; ligne « interventions depuis hier » de la veille. Montages rclone kDrive page 165 Six montages media (photo, romans, komga, livres, music, audiobooks) + /mnt/nas_videos pour Jellyfin. Cache VFS full plafonné, sauf video en minimal. RC sur 127.0.0.1 uniquement. VPS · services systemd permanents permanent — 7/7 répondent le 13/09 ls de chaque point de montage ; rclone rc … vfs/stats ; ligne « Montages rclone » de la veille. disk_monitor Alerte au-delà de 85 % de disque, purge /tmp/rclone-spool* de plus d'une heure (incident du 24/06). VPS · cron toutes les 30 min 13/09 20:00 — 78 % tail /var/log/disk_monitor.log. Sur le NAS sasnexte Le VPS y accède par le tunnel WireGuard (10.0.0.2). Les journaux vivent dans /volume1/homes/SAS_NEXTE/logs/. Procédure Ce qu'elle fait Où / quand Dernière utilisation connue Vérification Synchro NAS → kDrive page 238 sync_kdrive_complete.sh : miroir rclone sync de audiobooks, music, photo/2026 et Komga vers SYNC-pour_VPS/Sync-SASNEXTE. Version durcie du 28/08 (config absolue, refus de source vide, --max-delete). NAS · planificateur DSM, quotidien 13/09 — 4/4 sans CRITICAL grep -l CRITICAL logs/sync_*_$(date +%Y%m%d)*.log doit être vide ; ligne « NAS sasnexte » de la veille (lecture par ssh). Photos NAS — compression par lots page 285 Inventaire ( photos.sqlite) → lot par album ( traiter_lot_nas.py, originaux intacts) → validation dans Synology Photos (supprimer = refuser) → bascule en root ( basculer_lot_nas.py --go). Photos < 2 Mo exclues, EXIF/mtime vérifiés. NAS · manuel, lot par lot 28/08 — lot 2022 (359 photos, 1,47 Go) en validation ; doublons : 75 fichiers en quarantaine python3 traiter_lot_nas.py --lot X --lister ; colonne statut de photos.sqlite. Navidrome-Paroles (Beets) page 312 Conteneur beets-paroles : beet lyrics (LRCLIB) → ecrire_paroles.py (SYLT/USLT seulement) → propager_paroles.sh vers kDrive → full scan Navidrome. Beets n'écrit jamais dans les fichiers. NAS · manuel, chaîne finir_paroles.sh en setsid 11-12/09 — 13 139 fichiers écrits, 10 998 paroles synchronisées en base (avant : 15) Navidrome : compte des titres avec paroles ; conteneur à l'arrêt entre deux passes. Point ouvert : décalage 2-3 s dans le lecteur web. Video → kDrive page 290 Les 694 Go du partage video copiés dans Private/Videos_NAS/ et vérifiés par rclone check --download ; Jellyfin lit kDrive (×13 plus rapide que le CIFS via WireGuard). fait le 30/08 · reste : suppression sur le NAS après observation 30/08 — 0 différence sur 4 056 fichiers, 629 films dans Jellyfin systemctl status kdrive-video ; montage /mnt/nas_videos dans le canari. Sur le poste julie (PC) Le VPS ne voit pas le PC. Ces procédures déposent un témoin ( Jux-scripts/Etat-Infra/temoins/.json, dans l'arbre Syncthing) à chaque passe réelle ; c'est ce témoin que la veille affiche. « Non observé » = aucune passe depuis l'ajout du témoin (13/09). Procédure Ce qu'elle fait Où / quand Dernière utilisation connue Vérification Photos-Caesium page 284 Dépôt dans D:\Procedures photos\Tofs a compresse\, résultat dans Tofs-compressed\ (caesiumclt q80, EXIF et GPS conservés, −64 % mesuré). Dossiers hors Syncthing volontairement. PC · tâche planifiée Photos-Caesium (ONLOGON, pythonw), sondage 15 s 27/08 — 71 photos, 251 → 90 Mo en 22 s Get-CimInstance Win32_Process -Filter "Name='pythonw.exe'" ; D:\Procedures photos\logs\ ; témoin photos-caesium. Musique-FLAC page 298 Dépôt dans D:\Musique\FLAC à encoder\, MP3 V0 dans MP3 fait\. Tags lus dans le FLAC, réécrits, relus dans le MP3 et comparés ; durée vérifiée (un FLAC tronqué se convertit sans erreur). PC · tâche planifiée Musique-FLAC à créer par Julien ; sinon flac_mp3.py à la main 04/09 — Dry Cleaning, 11 pistes, 824 → 81 Mo en 50 s D:\Musique\logs\flac_mp3.log ; témoin musique-flac. Livres-ISBN page 300 inventaire_livres.py (métadonnées lues dans les fichiers) puis resoudre_livres.py (BnF, OpenLibrary) → CSV rangé par rayon, à relire. Lecture seule sur \\NASMAISON\foxy : Julien saisit. PC · manuel, par lots déposés dans D:\Procedures Calibre\Dossier à renseigner\ 04-05/09 — lot Cuisine Géo : 619 fichiers, 233 ISBN (41 pré-cochés) CSV dans D:\Procedures Calibre\ISBN\ ; témoin livres-isbn. Calibre-Metadonnees Pour les 1 572 livres déjà dans metadata.db : corriger_auteurs.py, resoudre_isbn.py, appliquer_isbn.py (rien sans --go). La source Google de Calibre est morte : seul l'ISBN fonctionne. PC · manuel, Calibre fermé pour écrire 31/08 — 76 auteurs corrigeables, 121 ISBN proposés ; relecture en attente propositions.csv ; fetch-ebook-metadata.exe -I isbn:… rend une fiche complète. Mealie-Recettes page 315 Note Joplin publiée (carnet « A transcrire dans Mealie » obligatoire) → Claude structure → recette Mealie avec photo, catégories = cuisine, tags = ingrédients. PC · à la demande, outils MCP lire_note_joplin / creer_recette 13/09 — köttbullar, brochettes d'espadon Recette visible dans Mealie ; témoin mealie. Surveillance Blink page 275 Sonde le module Sync toutes les 30 s et balaie le /24 toutes les 5 min ; CSV dans D:\Logs\Blink\. Mesure seulement, ne réveille rien. PC · dossier Démarrage ( Surveillance Blink.cmd, pythonw) en service depuis le 30/08 pythonw.exe présent ; surveiller_blink.py --resume. Non relayé dans la veille (hors Syncthing). Scan LAN page 275 scan_lan.py --ports : balayage ping, ARP, OUI, ports, DNS inverse du réseau maison. PC · à la demande 23/08 — 9 appareils — PC-Health page 249 collect_bsod.ps1 (élevé) : arrêts anormaux, codes bugcheck, pilote AMD, SMART → ligne ajoutée au journal de la page 249. PC · manuel, toutes les 1-2 semaines 25/08 — 4 épisodes inexpliqués ; action en attente : pilote AMD Journal de la page 249 ; smartctl -A /dev/pd0 (Unsafe_Shutdown_Count). Kobo — dépôt d'epub page 271 Un epub déposé dans D:\Syncthing\Kobo\ est servi par nginx à juxjux.ovh/56ifciz, à taper dans le navigateur bêta de la liseuse (USB en panne). Syncthing + vhost nginx VPS · à la demande 22/08 — 1 fichier en ligne curl -I https://juxjux.ovh/56ifciz/ → 200 ; ligne « Kobo » de la veille (date du dernier fichier). Liaisons permanentes Pas des procédures à proprement parler, mais tout le reste repose dessus. Procédure Ce qu'elle fait Où / quand Dernière utilisation connue Vérification Maillage Syncthing page 174 Un seul dossier afltj-njyuy partagé par 8 appareils, hub VPS. Réplication, pas sauvegarde : la sauvegarde de ~/Documents couvre ce trou depuis le 30/08. permanent · SyncTrayzor sur le PC (home D:\SyncthingHome) 13/09 — 3 appareils vus depuis hier GUI http://127.0.0.1:8384 ; ligne « Syncthing » de la veille (tolérances par appareil dans config.json). Tunnel WireGuard page 245 VPS 10.0.0.1 (hub) ↔ NAS 10.0.0.2 ↔ PC 10.0.0.3. Lecteur S: → \\10.0.0.2\NEXTE. Le service Windows meurt après un plantage sec ; relance automatique posée le 13/09. permanent · service Windows + wg0 sur le VPS 13/09 — poignées de main NAS et PC à la minute Test-NetConnection 10.0.0.2 -Port 445 côté PC ; sudo wg show côté VPS ; ligne « WireGuard » de la veille. Baïkal + DAVx5 page 247 Contacts et agenda : Baïkal source de vérité, DAVx5 (15 min) sur le téléphone, InfCloud et Roundcube en web. Sauvegardé chaque nuit. permanent · conteneurs baikal, roundcube permanent — agenda affiché chaque matin Bloc « Agenda » sous la ligne du haut de la veille (lecture directe de db.sqlite) ; curl -X PROPFIND sur /dav.php/. Serveurs MCP BookStack et kDrive (npm), et les serveurs Python stdlib de Jux-scripts/MCP/ : Portainer, Syncthing, Komga, Navidrome, Readeck, Mealie. PC · au démarrage de chaque session Claude Code 13/09 — mcp_mealie.py ajouté Outils listés en début de session ; un MCP en échec n'apparaît pas. Chantiers en attente (relevé du 13/09) Musique-FLAC : tâche planifiée Musique-FLAC à créer par Julien dans sa propre console (piège MSIX). Video → kDrive : suppression des 694 Go sur le NAS, après période d'observation. Photos NAS : valider ou refuser le lot 2022 dans Synology Photos, puis basculer_lot_nas.py --go. Calibre / Livres-ISBN : relecture des CSV de propositions, saisie manuelle. PC : réinstaller le pilote AMD Radeon (cause des 0x50), réappliquer HiberbootEnabled=0 et les réglages de vidage. Joplin : EVENTS_AUTO_DELETE_ENABLED=1 sur la stack 16 (table events à 1 Go) — à appliquer par Julien. Syncthing : corbeille trashcan 30 j non tranchée. Immich-SERVER redémarre la nuit — vu par le canari, cause à établir (monitor-rclone est intervenu sur /home/debian/photo le 12/09 à 00:20 UTC). Ubuntu : désactiver l'ancien service komga-watch utilisateur, cassé depuis le 23/08. Ajouter une procédure au tableau Une entrée dans Jux-scripts/Etat-Infra/procedures.json : id, nom, page BookStack, source ( journal avec chemin et motif, dossier, domaine d'une sonde existante, temoin, ou manuel avec une date), et cadence_heures si elle doit tourner régulièrement — au-delà la ligne passe « En retard ». Pour une procédure du PC : appeler ecrire_temoin("", "résumé") en fin de passe (fonction de dix lignes, déjà présente dans compress_photos.py, flac_mp3.py, isbn_commun.py, joplin_mealie.py). Le témoin ne doit jamais faire échouer la procédure. python3 etat_infra.py --dry-run sur le VPS montre le tableau sans rien publier. Le tableau de la veille ne lève pas d'alerte en tête de page : c'est un récapitulatif. Les alertes viennent des sondes dédiées (sauvegardes, journaux nocturnes, Komga-PDF, NAS…), qui couvrent déjà ce qui est critique. 260801 - Mon Linux OS sur SASNEXTE Claude prend la main en MCP pour monter et manager une distribution Linux ubiquiste - disponible  260528_Essai sur \\NASMAISON\foxy\Géographie\ Workflow geo_loud — Optimisation des fichiers lourds Contexte Le dossier \\NASMAISON\foxy\Géographie\ contient 1 312 fichiers (PDF et EPUB) pour un total de 32,9 Go. Parmi eux, 127 fichiers dépassent 75 Mo et représentent 13,6 Go — soit 41 % du volume total concentré sur moins de 10 % des fichiers. L'objectif est de sortir ces fichiers lourds pour les optimiser (compression PDF), puis de les remettre exactement à leur emplacement d'origine sans perdre l'arborescence. Architecture du système Fichier     Rôle nas_indexer.py     Scanne le NAS et crée la base SQLite nas_geographie.db     Référentiel de tous les fichiers avec métadonnées et statut nas_geo_loud.py     Script checkout/checkin — déplace et restaure les fichiers lourds La base SQLite est la source de vérité : elle connaît à tout moment l'emplacement original de chaque fichier et son statut. Colonnes de traçabilité (table fichiers) Colonne     Description chemin_absolu     Chemin d'origine complet sur le NAS chemin_geo_loud     Chemin dans le dossier de travail après déplacement statut     original / deplace / restaure date_deplacement     Horodatage du déplacement date_restauration     Horodatage de la restauration Workflow complet Étape 1 — Indexation initiale 1 python nas_indexer.py "\\NASMAISON\foxy\Géographie" --output nas_geographie.db Résultat : 1 312 fichiers indexés en ~22 secondes, 0 erreur. Étape 2 — Export (checkout) Déplace les 127 fichiers > 75 Mo vers \\NASMAISON\foxy\geo_loud\ : 1 2 3 4 python nas_geo_loud.py export \   --db nas_geographie.db \   --dest \\NASMAISON\foxy\geo_loud \   --confirmer Ce que fait le script :     Interroge la base pour tous les fichiers statut = 'original' et taille > 75 Mo     Renomme chaque fichier {id:06d}_{nom_original} pour éviter les collisions de noms     Déplace physiquement le fichier vers geo_loud/     Met à jour la base : statut = 'deplace', chemin_geo_loud, date_deplacement     Le préfixe numérique garantit l'unicité même si deux sous-dossiers contiennent un fichier de même nom. Étape 3 — Optimisation PDF Traiter les fichiers dans \\NASMAISON\foxy\geo_loud\ avec l'outil d'optimisation. Les fichiers peuvent être traités dans n'importe quel ordre et en plusieurs sessions. Étape 4 — Restauration (checkin) 1 2 3 python nas_geo_loud.py restaurer \   --db nas_geographie.db \   --dest \\NASMAISON\foxy\geo_loud Ce que fait le script :     Lit tous les enregistrements statut = 'deplace' dans la base     Pour chaque fichier présent dans geo_loud/ : déplace vers son chemin_absolu d'origine     Recrée les sous-dossiers si nécessaire     Met à jour : statut = 'restaure', date_restauration     Ignore les fichiers absents de geo_loud/ (pas encore optimisés) Étape 5 — Vérification du statut 1 python nas_geo_loud.py statut --db nas_geographie.db Affiche : 1 2 3 4 Statut         Fichiers     Total Go --------------------------------------   deplace           127       13.577   original        1 185       19.275 Vues SQL utiles (Metabase / DB Browser) 1 2 3 4 5 6 7 8 -- Fichiers encore dans geo_loud SELECT nom, ROUND(taille_octets/1048576.0,1) AS mo, date_deplacement, chemin_absolu FROM fichiers WHERE statut = 'deplace' ORDER BY taille_octets DESC; -- Bilan par statut SELECT statut, COUNT(*) AS nb, ROUND(SUM(taille_octets)/1073741824.0,3) AS go FROM fichiers GROUP BY statut; Paramètres disponibles Paramètre     Défaut     Description --db     nas_geographie.db     Chemin vers la base SQLite --dest     \\NASMAISON\foxy\geo_loud     Dossier de travail --seuil     75     Seuil en Mo pour la sélection --confirmer     (off)     Bypass la confirmation interactive Ré-indexation après optimisation 1 python nas_indexer.py "\\NASMAISON\foxy\Géographie" --output nas_geographie.db --vider Fichiers du projet Fichier     Emplacement nas_indexer.py     C:\Users\eliob\Documents\nas_indexer.py nas_geo_loud.py     C:\Users\eliob\Documents\nas_geo_loud.py nas_rapport.py     C:\Users\eliob\Documents\nas_rapport.py nas_geographie.db     C:\Users\eliob\Documents\nas_geographie.db 260611_procédure Komga-PDF par Claude sur StirlingPDF Présentation La procédure Komga-PDF est un script de compression de fichiers PDF pour réduire considérablement la taille du stockage des bibliothèques d'ouvrages de la collection de Julien. L'acquisition de fichiers issus de bases de données Internet accumule des fichiers PDF en haute résolution (images surtout). Ces fichiers sont synchronisés entre les NAS Synology et un disque dur kDrive Infomaniak de 6 To. Contexte Des points de montage Rclone relient les répertoires kDrive et les containers Docker installés sur le VPS OVH juxjux.ovh. Ces containers lisent les PDF à la volée — permettant ainsi de construire une infrastructure de très forte densité de connaissances digitales (epub, pdf, images, vidéos, musiques...) avec une solution serveur d'entrée de gamme (10 €/mois tout inclus). La lecture réseau est multi-support : PC, tablette, téléphone. Elle est aussi nomade, séquentielle et pressée. La compacité des fichiers PDF devient donc nécessaire pour diffuser rapidement sur les réseaux. Un magazine ou un ouvrage informatique ne doit pas peser 130 Mo sur un écran de 10 pouces. Procédure Les nouveaux PDF sont déposés dans le dossier Syncthing : Syncthing/komga-pdf/ depuis n'importe quelle machine (PC Elio+Jux, PC Nexte, Ubuntu, Xiaomi...) Syncthing synchronise automatiquement vers le VPS : /home/debian/Documents/komga-pdf/ Le service komga-watch (systemd VPS) détecte l'arrivée via inotifywait local Dès détection, komga_compress_vps.py compresse le PDF via StirlingPDF et dépose le résultat dans /home/debian/Documents/komga-compressed/ Julien range manuellement les PDF compressés vers sasnexte puis dans le dossier thématique de son choix Les originaux ne sont jamais supprimés du VPS — Julien valide et conserve si besoin (haute résolution souhaitée) Architecture technique Élément Chemin / URL Dépôt (toutes machines) Syncthing/komga-pdf/ Dossier VPS (Syncthing) /home/debian/Documents/komga-pdf/ Destination VPS /home/debian/Documents/komga-compressed/ API compression https://spdf.juxjux.ovh/api/v1/misc/compress-pdf Scripts VPS /home/debian/komga_compress_vps.py + /home/debian/komga_watch_vps.sh Logs journalctl -u komga-watch -f (sur le VPS) + journal persistant /home/debian/logs/komga_compress.log Scripts Le service tourne entièrement sur le VPS — plus de dépendance Ubuntu ni de SSH persistant. Fichier Rôle komga_compress_vps.py Compression : liste les PDFs locaux (extension insensible à la casse), envoie à StirlingPDF, écrit dans komga-compressed/ de façon atomique. Idempotent. Vérifie que la réponse est bien un PDF et met à l'écart un fichier après 3 échecs. komga_watch_vps.sh Surveillance : traite le backlog au démarrage, puis boucle inotifywait -t 600. Le délai impose un rebalayage périodique, indispensable car les événements survenant pendant une compression sont perdus (voir REX 2026-08-23). /etc/systemd/system/komga-watch.service Service system (pas user), actif au boot, Restart=always. Gérer le service (VPS) sudo systemctl status komga-watch sudo systemctl restart komga-watch journalctl -u komga-watch -f Retour d'expérience (2026-08-23) — Deux fichiers bloqués sans la moindre trace Deux PDF déposés le 22/08 stagnaient dans komga-pdf/ sans aucune ligne au journal : ni erreur, ni tentative. Le service était pourtant active, et deux autres fichiers étaient passés normalement le matin même à 06:13. La cause — une course perdue sur les événements inotify komga_watch_vps.sh appelait inotifywait une seule fois par tour de boucle, puis lançait la compression. Pendant toute la durée du traitement, plus personne n'écoutait le dossier. Horodatage Événement 06:13:07 inotifywait rend la main (dépôt de « Le Grand Livre… ») 06:13:10 le script Python liste le dossier — 2 PDF trouvés 06:13:10 → 06:15:16 2 min 06 s de compression, aucune écoute inotify 06:15:16 inotifywait redémarre — les événements de la fenêtre sont perdus Les deux fichiers « Les secrets de… » sont arrivés dans cette fenêtre. Leur close_write et leur moved_to sont tombés dans l'angle mort, et le glob() du script était déjà passé. Ils sont devenus invisibles pour de bon — jusqu'au dépôt d'un autre fichier, qui aurait relancé un balayage complet. Preuves relevées au moment du diagnostic : inotifywait (PID 1902285) en attente depuis 32 min, soit exactement depuis 06:15:16 mtime du dossier à 06:45:22 — une suppression (les originaux déjà traités), non surveillée donc sans événement les deux fichiers parfaitement valides : %PDF- … %%EOF, 148,4 et 51,4 Mio Le défaut est structurel et se reproduit dès que plusieurs fichiers arrivent ensemble — précisément le cas où la compression dure le plus longtemps. Le correctif — un délai sur inotifywait inotifywait -q -t 600 -e close_write,moved_to "$SOURCE_DIR" -t rend la main au bout de 600 s même sans événement, ce qui garantit un rebalayage périodique. Le traitement étant idempotent — et désormais silencieux quand il n'y a rien de neuf — un balayage à vide ne coûte rien. Plus aucun fichier ne peut rester orphelin : au pire 10 minutes de latence. Vérification du correctif, en reproduisant le bug Le scénario a été rejoué volontairement. Un PDF a été déposé dans le dossier par lien matériel ( ln), qui ne produit qu'un IN_CREATE — événement non surveillé par le script. C'est l'équivalent exact d'un événement perdu. Horodatage Constat 07:51:09 démarrage du guetteur, balayage initial 07:51:56 dépôt du fichier — aucune ligne au journal, donc aucun événement émis 08:01:10 le rebalayage périodique le trouve : « 1 PDF(s) à traiter (sur 1 présents) » 600 secondes pile après le démarrage. Avec l'ancien script, ce fichier serait resté indéfiniment invisible. La compression a ensuite échoué en HTTP 400 — le PDF de test faisait 193 octets sans table xref, StirlingPDF le refuse à raison. Cet échec a validé trois autres correctifs du même coup : le compteur de tentatives s'est incrémenté ( ERREUR (1/3)) et la signature a été mémorisée dans ~/.komga_compress_state.json chaque ligne est bien apparue dans le journal persistant rien n'a été écrit en destination : ni fichier corrompu, ni .part orphelin Artefacts de test supprimés après contrôle. Les autres défauts corrigés dans la même passe # Défaut Correctif 1 glob("*.pdf") ignorait les .PDF — régression par rapport à l'ancien find -iname comparaison sur suffix.lower() 2 write_bytes() écrivait le fichier final directement dans un dossier Syncthing : propagation possible d'un PDF incomplet écriture en .part puis os.replace(), atomique 3 aucune validation de la réponse : une page d'erreur HTML renvoyée en HTTP 200 était enregistrée en .pdf vérification de la signature %PDF- 4 timeout=300 trop juste — un fichier de 148 Mio prend environ 3 min porté à 900 s 5 si la compression ne gagnait rien, le résultat était écrit quand même l'original est recopié tel quel, ce qui clôt proprement le traitement 6 un fichier en échec était retenté sans fin — grave avec le rebalayage toutes les 10 min, soit 144 envois par jour mémoire des échecs dans ~/.komga_compress_state.json, mise à l'écart après 3 tentatives 7 get_token() sans protection : Stirling indisponible = traceback, lot entier perdu message d'erreur explicite, sortie propre 8 mkdir(exist_ok=True) sans parents=True corrigé 9 aucun journal persistant — journald seul, tourné régulièrement /home/debian/logs/komga_compress.log, en plus de stdout La mise à l'écart après trois tentatives s'appuie sur la signature du fichier (taille + date de modification) : redéposer une version corrigée portant le même nom relance donc le traitement normalement. Déblocage des deux fichiers et mesure mémoire Fichier Original Compressé Gain Les secrets de la photo lifestyle — Baptiste Dulac 148,4 Mio 30,1 Mio -80 % Les secrets de la série photo — Frédéric Landragin 51,4 Mio 38,6 Mio -25 % Traitement en 4 min 05 s. Pic mémoire de stirling-pdf : 1 426 Mio sur les 2 Go du plafond (71 %), aucun OOM kill — c'est le plus gros fichier jamais passé dans la chaîne. Troisième confirmation que porter la limite à 2 Go était le bon choix : le plafond de 1 Go envisagé le 2026-08-02 aurait tué le container. Sauvegardes et versionnement Versions précédentes conservées sur le VPS : komga_compress_vps.py.bak-260823 et komga_watch_vps.sh.bak-260823 Les scripts de production sont désormais versionnés dans Jux-scripts/Komga-PDF/ sous leurs noms de production. Jusqu'ici ils n'existaient que sur le VPS, hors de tout périmètre Syncthing — exactement le scénario qui a fait perdre les MCP Python avec le profil Windows eliob Les anciens komga_compress.py et komga_watch.sh du même dossier correspondent à l'architecture Ubuntu abandonnée en juin 2026 (pilotage par SSH depuis Ubuntu, destination /mnt/sasnexte/Komga/pdf_compressed). Ils ne doivent plus servir de référence Retour d'expérience (2026-08-22) — Test de bout en bout depuis le poste julie Première validation complète de la chaîne depuis le redémarrage du service le 2026-07-08, et premier passage depuis le PC Windows julie (entré dans le maillage Syncthing le 2026-08-10). Aucune intervention manuelle : dépôt du fichier dans D:\Syncthing\komga-pdf\, tout le reste s'est enchaîné seul. Chronologie mesurée — 1 min 50 s entre le dépôt sur le VPS et le fichier compressé : Étape Horodatage (UTC) Syncthing dépose le .tmp sur le VPS, inotify déclenche 11:24:50 Fin de l'attente de 3 s, le script détecte le PDF 11:24:53 StirlingPDF rend le fichier compressé 11:26:40 Retour du fichier compressé sur le poste julie (Syncthing) 11:26 Résultat de compression : Fichier Original Compressé Gain La Revue du Vin de France - Septembre 2026.pdf 129,2 Mo 55,4 Mo -57% Même taux que le numéro de juin 2026 du même magazine (-57 %) — la compression est reproductible d'un numéro à l'autre pour une source identique. Mesure mémoire — marge plus étroite qu'attendu C'est le point neuf de ce test. Le fichier de 129 Mo est le plus gros passé dans la chaîne à ce jour (précédent record : 96,6 Mo le 2026-06-13). Consommation du container stirling-pdf, relevée toutes les 15 s : t+ Mémoire 15 s 701 Mio 30 s 1001 Mio 45 s 1,036 Gio 60 s 1,069 Gio (pic) 75 s 1,047 Gio 90 s 1,039 Gio (terminé) Pic à 1,069 Gio sur le plafond de 2 Gio posé le 2026-08-02 → il reste ~45 % de marge Le plafond de 1 Go initialement envisagé aurait tué ce traitement en OOM kill. La décision de le porter à 2 Go (motivée alors par l'OCR, qui monte à ~969 Mio) se trouve validée une seconde fois, pour une raison indépendante : la compression d'un gros PDF La consommation ne suit pas la taille du fichier de façon linéaire — elle plafonne vers 1 Gio et s'y maintient. Mais une compression concurrente d'un OCR ferait dépasser les 2 Gio. Ne pas lancer les deux en parallèle, et ne pas redescendre la limite Point d'attention — aucun log persistant (résolu le 2026-08-23) Les logs partent uniquement dans journald ( komga_watch_vps.sh écrit sur stdout, capté par systemd). Le journal a été tourné depuis le démarrage du service : tous les traitements de juin et juillet sont irrécupérables, journalctl -u komga-watch ne montrait rien avant ce test. Pour conserver un historique consultable, ajouter une redirection vers un fichier dans le script, ou poser un journalctl --unit komga-watch en rotation propre. Non fait à ce jour. Corrigé le 2026-08-23 : le script Python écrit désormais chaque ligne dans /home/debian/logs/komga_compress.log en plus de stdout. Voir le REX du 2026-08-23 ci-dessus. Retour d'expérience (2026-06-13) — Refactorisation VPS Problème de l'architecture Ubuntu : le service tournait sur Ubuntu avec un SSH persistant vers le VPS. Deux défauts structurels : inotifywait ne détecte pas les fichiers déjà présents au redémarrage du service (backlog silencieux) Impossible à contrôler depuis Windows/Claude Code Solution : service systemd sur le VPS lui-même. inotifywait local, pas de SSH persistant. Le script traite le backlog à chaque démarrage avant d'entrer dans la boucle de surveillance. Résultats de compression (2026-06-13) : Fichier Original Compressé Gain Beaux Arts - Juin 2026.pdf 75.8 Mo 61.1 Mo -19% Connaissance des Arts - Juin 2026.pdf 57.3 Mo 35.2 Mo -39% La Revue du Vin de France - Juin 2026.pdf 93.3 Mo 39.9 Mo -57% Livre lacto fermentation.pdf 96.6 Mo 19.6 Mo -80% Monde Gourmand N°93 - Juin 2026.pdf 41.2 Mo 17.4 Mo -58% Points techniques : Dossier VPS avec D majuscule : /home/debian/Documents/ (Syncthing sensible à la casse) Race condition inotifywait/Syncthing : attente 3s après événement, script idempotent Python : /usr/bin/python3 (système, requests 2.28.1 disponible) StirlingPDF auth : JWT via POST /api/v1/auth/login → session.access_token, valable 24h Fichiers sync-conflict Syncthing filtrés automatiquement par le script Retour d'expérience (2026-06-12) Résultats de compression : Fichier Original Compressé Gain Monde Gourmand N°93 - Juin 2026.pdf 42 Mo 17 Mo -58% Connaissance des Arts - Juin 2026.pdf 57 Mo 35 Mo -39% 260614 - procedure Joplin-compression Contexte — instructions de Julien (260614) J'utilise l'application Joplin sur tous mes appareils. C'est ma mémoire de tout (travail, hobbies, maison...) et donc à force, la base de données se remplit. Elle se compose de notes à l'intérieur desquelles on trouve des images. Même en n'important que des captures d'écran, le poids de ces images est devenu important — de l'ordre de 300 Mo initialement estimé (476 Mo constatés en réalité, voir ci-dessous). Appareils synchronisés sous Joplin : VPS Juxjux.OVH (serveur de sync) PC Nexte (PC du travail de Julien) PC Elio+Jux (PC maison de Julien) Mobile Xiaomi T15pro de Julien Tablette Samsung S5 de Julien Session Ubuntu sur clé SSD de Julien Objectif de la procédure : Compresser le stock d'images contenues dans la base de données Joplin via un process fiable de remplacement Monter un système de compression automatique quotidienne des nouvelles images Principes définis par Julien : Tenir un carnet de bord (observatoire) du poids digital de la BDD Joplin dans la page BookStack Joplin, avec les 15 pièces jointes les plus lourdes Mettre en place sur le VPS une procédure duplication → compression → remplacement d'images, qui se propage via la sync Joplin sur tous les appareils Exploration technique — Claude, 14/06/2026 Infrastructure Joplin sur le VPS Contrairement à ce qu'on pourrait attendre, Joplin Server n'utilise pas SQLite mais PostgreSQL. Container Image Rôle joplin joplin/server:latest Serveur de sync Joplin joplin-db postgres:15-alpine Base de données joplin-nginx nginx:alpine Reverse proxy interne joplin_to_obsidian image custom Container migration (actif, sans impact) Volume de données : /home/debian/joplin-data → /home/joplin/.config/joplin (bind mount) Connexion DB : POSTGRES_HOST=joplin-db, POSTGRES_DATABASE=joplin, POSTGRES_USER=joplin Important : le port 5432 de joplin-db n'est pas exposé à l'extérieur du réseau Docker. Tout script de manipulation doit tourner sur le VPS et se connecter via l'IP interne Docker. Structure de la base de données La table centrale est items (23 tables au total). Chaque note, ressource et paramètre Joplin est une ligne dans cette table. Colonnes clés : content (bytea) — données binaires brutes content_size (integer) — taille en octets content_storage_id = 1 → stockage de type Database (tout est dans PostgreSQL, pas de fichiers externes) jop_type — type d'item Joplin updated_time — timestamp de dernière modification (utilisé par les clients pour détecter les changements à sync) Répartition par type : jop_type Signification Nombre Poids total 0 Ressource (image/fichier joint) 736 476 MB 1 Note 699 8,7 MB 4 Tag 733 4,5 MB 13 NoteTag (relation note↔tag) 250 3,9 MB 2 Carnet (Folder) 94 707 KB 6 Master Key 87 19 KB 5 Setting 39 — Analyse des ressources (jop_type = 0) Les ressources sont stockées comme bytes bruts dans la colonne content. Le format est détectable via les magic bytes : PNG ( \x89PNG) — majorité des ressources JPEG ( \xff\xd8) — portion significative ZIP ( PK\x03\x04) — cas particulier : ZIP contenant plusieurs images page_1.png, page_2.png... (documents multi-pages) La colonne mime_type est vide pour toutes les ressources — le type est implicite dans les bytes du contenu. Top 15 ressources les plus lourdes (état initial) : Rang ID Taille Format 1 0xh87pWrRt82z0DbO9n3yQ 12,2 MB ZIP (page_1.png 7MB + page_2.png 5MB) 2 M0C32hKC2hMqPAxpRGiVgp 7,2 MB PNG 3 UbAqQY3cEbH62vioIHU6Pj 6,0 MB PNG 4 ZPQJVSjptMSxE71pV7JbAt 5,9 MB PNG 5 h4Lhw1Trx9wgmD7doX9NyZ 5,9 MB PNG 6 sbktcNb4IUj0yIovgDt0fL 5,3 MB PNG 7 jtzuGpvrRTR12iLzwhcSNn 5,2 MB PNG 8 VasIoF2e9EGuNQt38aOFMx 4,9 MB JPEG 9 ZACDcQWzQzDLdnV7Qnf933 4,3 MB PNG 10 hExoJEEWT2tHz1demE5Nhm 4,3 MB PNG 11 veu4HT3bStx07gRUlAEYvG 4,2 MB PNG 12 bz9Twmb2F5lj0mPIQi48IB 4,2 MB JPEG 13 TzWK21r4n0yvbEtJh02DGB 4,2 MB JPEG 14 eD165w1bdEHJg0tq3qWok5 4,2 MB PNG 15 HD4SeqIx252nP9nh8HDVnH 4,1 MB PNG Architecture retenue Choix de compression — décision Julien, 14/06/2026 PNG → JPEG 85% (lossy). Toutes les images, qu'elles soient PNG ou JPEG à l'origine, sont converties/re-sauvegardées en JPEG qualité 85. Gain estimé : 50–75% par image. Acceptable pour des captures d'écran. Pour les ZIP multi-pages : chaque image interne est convertie en JPEG 85%, le ZIP est reconstruit. Phase 1 — Observatoire Script joplin_observatoire.py sur le VPS : Connexion psycopg2 à joplin-db via IP réseau Docker interne Calcul des stats globales (taille totale, nombre d'items par format) Liste des 15 ressources les plus lourdes avec format et taille Mise à jour de la page BookStack Joplin (page 166) avec ces informations Phase 2 — Compression (script principal) Script joplin_compress.py sur le VPS : Connexion : psycopg2 → IP Docker interne de joplin-db : 5432 Traitement par ressource : Lire le blob content depuis items (jop_type=0) Détecter le format (magic bytes) Ouvrir avec Pillow, convertir en JPEG 85 ( quality=85, optimize=True) Pour les ZIP multi-pages : dézipper → compresser chaque image → reconstruire le ZIP Si le gain est > 5% : mettre à jour content, content_size, updated_time en base Logger le résultat (ID, taille avant, taille après, ratio) Tracking des items traités : fichier JSON local /home/debian/joplin_compress_log.json — évite de retraiter les ressources déjà compressées lors des passages quotidiens. Propagation sync : Joplin détecte les changements via updated_time. Lors de la prochaine synchronisation de chaque client, les ressources compressées sont re-téléchargées automatiquement. Phase 3 — Service systemd (cron quotidien) Timer systemd joplin-compress.timer → joplin-compress.service : Déclenchement quotidien (3h du matin) Traite uniquement les nouvelles ressources (non présentes dans le log JSON) Met à jour l'observatoire BookStack après chaque passage Mise en production — 14/06/2026 Résultat du premier run (stock complet) Ressources traitées 736 au total Compressées 424 Ignorées (gain < 5%) 312 Erreurs 0 Poids avant 476 Mo Poids après 119,7 Mo Économie 356 Mo (-78,9%) Scripts déployés sur le VPS Script Rôle Options /home/debian/joplin_compress.py Compression Pillow PNG/JPEG → JPEG 85%, ZIP multi-pages, mise à jour PostgreSQL --dry-run (simulation) / --limit N (N ressources max) /home/debian/joplin_observatoire.py Stats DB + top 15 → mise à jour page BookStack Joplin (ID 166) — Log tracking : /home/debian/joplin_compress_log.json — liste des ressources déjà traitées, évite les doublons aux runs suivants. Timer systemd Service joplin-compress.service Timer joplin-compress.timer Déclenchement Chaque nuit à 3h UTC ( OnCalendar=*-*-* 03:00:00) Logs /var/log/joplin-compress.log Statut active (waiting) — prochain run : 15/06/2026 03:00 UTC Notes techniques joplin-db IP Docker interne : 172.27.0.4 (peut changer si le container est recréé — vérifier avec docker inspect joplin-db -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') La compression modifie content, content_size et updated_time dans la table items Les clients Joplin re-téléchargent les ressources modifiées lors de la prochaine synchronisation (détection via updated_time) Les images à fond transparent (RGBA/LA/P) sont aplaties sur fond blanc avant conversion JPEG Réexamen du 2026-08-27 — Caesium n'apporte rien, mais la base pèse 1 263 Mo Question posée : la procédure Photos-Caesium (page 284), qui compresse les photos du PC avec caesiumclt, pourrait-elle profiter aussi à Joplin ? Réponse mesurée : non pour les images, mais l'examen a mis au jour un problème dix fois plus lourd. 1. Le gisement image est épuisé État au 2026-08-27 : 729 ressources, 117,2 Mo (contre 476 Mo avant la procédure de juin, soit -79,3 %). Répartition par signature : Signature Format Nombre Poids ffd8ffe0 JPEG/JFIF — produits par Pillow 551 105 Mo 25504446 PDF 5 5,0 Mo 89504e47 PNG restants 42 4,3 Mo ffd8ffe1 JPEG avec EXIF (originaux) 24 1,7 Mo autres WebP, SVG, HTML, GIF, MP4 107 1,3 Mo Test réalisé sur deux échantillons extraits de la base (20 JPEG tirés au hasard, 5,20 Mo ; les 20 plus gros PNG, 3,84 Mo), compressés avec caesiumclt 1.4.0 sur le PC — aucune installation sur le VPS : Population Passe Caesium Gain Coût qualité 551 JPEG (105 Mo) --lossless -5,0 % aucun, pixels intacts -q 85 (mozjpeg vs libjpeg) -14,8 % 2e génération de perte -q 80 -32,0 % perte visible sur du texte 42 PNG (4,3 Mo) --lossless -27,7 % aucun --lossless --zopfli -29,8 % aucun (mais très lent) -q 80 -69,1 % quantification, mauvais pour du texte Conclusion : un passage Caesium sans risque récupérerait environ 6,5 Mo sur 117, soit -5,5 %. Le jeu n'en vaut pas la chandelle. La procédure de juin a pris l'essentiel du gain, et une seconde passe lossy sur des captures d'écran dégraderait le texte pour un bénéfice marginal. Réserve méthodologique : la procédure actuelle convertit les PNG en JPEG q85. Pour des captures d'écran, le JPEG est un mauvais format — il crée du crénelage autour du texte. Une optimisation PNG sans perte ( -27,7 % mesurés) serait meilleure en qualité pour un gain comparable. Priorité faible : il n'arrive que 3 à 4 images par semaine. Vérification que le script quotidien fonctionne toujours : les 12 ressources les plus récentes sont toutes en ffd8ffe0 dès qu'elles ont une taille significative, donc bien passées par Pillow. L'image du 27/08 (113 ko) correspond exactement à la ligne de log [OK] NkiVrkTlkYIp033uedJsdd PNG 900KB -> 113KB (87.4%). Le dispositif est vivant. 2. Le vrai poids : la table events, 1 072 Mo pour 7,28 millions de lignes La base joplin pèse 1 263 Mo. Les images, TOAST compris, n'en représentent que 172. Table Total (avec index) Table seule Lignes vivantes events 1 072 Mo 500 Mo 7 287 073 items (notes + images) 172 Mo 2 048 ko 2 642 changes 8 080 ko 3 992 ko 0 tout le reste ~2,5 Mo — — Les index d' events pèsent à eux seuls 573 Mo ( events_id_unique 284 Mo, events_pkey 156 Mo, plus deux index secondaires). Origine TaskService.runTask() écrit un événement au début et à la fin de chaque tâche de fond ( EventType.TaskStarted puis TaskCompleted). Une de ces tâches tourne toutes les ~10 secondes. Cadence mesurée : 23 400 lignes par jour, sans interruption depuis le 30/09/2025. Soit environ 1 Go par an. Ces lignes ne servent à rien : EventModel ne lit jamais que le dernier événement par (type, nom), via lastEventByTypeAndName ( orderBy('counter','desc').first()). Les 7,28 millions d'autres sont mortes. Cause : un interrupteur laissé sur « off » Joplin Server livre sa propre tâche de purge, désactivée par défaut. Dans l'image installée ( joplin/server 3.7.1) : dist/env.js:122 EVENTS_AUTO_DELETE_ENABLED: false dist/env.js:123 EVENTS_AUTO_DELETE_AFTER_DAYS: 30 dist/utils/setupTaskService.js:105 if (config.EVENTS_AUTO_DELETE_ENABLED) { tasks.push({ id: TaskId.DeleteOldEvents, schedule: '0 0 * * *', run: (models) => models.event().deleteOldEvents( config.EVENTS_AUTO_DELETE_AFTER_DAYS * Day), }); } Le container joplin ne définit pas la variable (seul MAX_ITEM_SIZE figure parmi les réglages non standard). La tâche DeleteOldEvents n'a donc jamais été enregistrée, et rien n'a jamais été purgé. Ce que ça coûte La sauvegarde nocturne. L'archive Joplin est la plus grosse du VPS (276 Mo) et le poste le plus lent du run (~14 min sur 21). D'après la largeur des lignes (~65 octets de texte par événement), les événements morts représentent grossièrement la moitié de cette archive. Le disque du VPS, à 65 % (61 Go utilisés sur 99), après l'incident de saturation du 24/06/2026. La mémoire et le swap de joplin-db, qui figurait parmi les principaux détenteurs de swap au relevé du 03/08. Correction — à appliquer Purge à 30 jours : il resterait ~702 000 lignes au lieu de 7 282 457, soit -90,4 %. La sauvegarde maigrit immédiatement ( pg_dump n'exporte que les lignes vivantes) ; le disque n'est rendu qu'après un VACUUM FULL. Activer le mécanisme officiel — stack Portainer 16, service joplin, ajouter aux variables d'environnement : - EVENTS_AUTO_DELETE_ENABLED=1 - EVENTS_AUTO_DELETE_AFTER_DAYS=30 puis mettre la stack à jour. La tâche s'exécute alors chaque nuit à minuit UTC — sans conflit avec la sauvegarde de 2 h. Rendre l'espace au disque, une fois le premier passage effectué : sudo docker exec joplin-db psql -U joplin -d joplin -c "VACUUM FULL events;" Verrou exclusif, mais bref une fois la table réduite. Variante plus douce, si l'on préfère éviter que le premier passage supprime 6,58 millions de lignes en une seule instruction : faire le rattrapage par lots avant d'activer la tâche — créer CREATE INDEX CONCURRENTLY tmp_events_created ON events(created_time), boucler des DELETE … WHERE ctid IN (SELECT ctid … LIMIT 500000), retirer l'index, puis VACUUM FULL. 3. Bug corrigé le 2026-08-27 — l'observatoire ne tournait plus depuis 72 jours joplin_observatoire.py plantait chaque nuit depuis la mi-juin : psycopg2.OperationalError: connection to server at "172.27.0.4" … Connection refused. L'IP Docker de joplin-db était passée à 172.27.0.3 lors d'une recréation du container. Le correctif du 2026-06-19, qui remplaçait l'IP figée par une résolution dynamique, n'avait été appliqué qu'à joplin_compress.py — l'observatoire avait été oublié. La compression, elle, a continué de fonctionner normalement pendant toute la période. Même correctif appliqué ( docker inspect joplin-db au démarrage), sauvegarde joplin_observatoire.py.bak-260827. Exécution de contrôle réussie : page 166 réalimentée — 729 ressources, 117,2 Mo, -79,3 %. Leçon : le compteur de tracebacks du log est un bon canari. sudo grep -c Traceback /var/log/joplin-compress.log renvoyait 72 — une par nuit — sans que personne ne s'en aperçoive, parce que la partie compression, elle, écrivait des lignes normales. 260615 - Immich performance 260615 -instructions pour la procédure Immich Performance Les photos de famille (et des vidéos) sont un patrimoine important pour notre famille. Donc elles font l'objet de beaucoup d'attention....et de volume de stockage. Les photos sont stockées sur les 2 NAS - Nasmaison et Sasnexte synchronisées à travers les outils Synology. A partir de Sasnexte, un script journalier Synology les propulse sur le VPS Kdrive pour être lues en lecture seule par toute la famille (elles sont également sur deux autres disques durs USB....).  Aujourd'hui, le dossier photos du Sasnexte que l'on doit considérer comme le cœur de ce dispositif nécessite une observation un peu plus fine car :  - des compressions vidéos doivent être faites pour les rendre plus faciles en lecture sur les portables en mobilité - des compressions images lorsque le poids est aberrant pour l'usage - des doublons existent forcément. Immich performance est une procédure importante du livre de Claude.  Réponse de Claude Inventaire par Claude Code le 01/10/2025 SAS_NEXTE@NEXTE:~$ sudo du -h --max-depth=2 /volume1/photo/ | sort -h   0 /volume1/photo/1968-1994 - photos famille dont Noumea/1982   0 /volume1/photo/2009-mariage Zymny   0 /volume1/photo/2014- été/140714-Lyon chez Pierre   0 /volume1/photo/2020 - Automne Noel/noel2020   0 /volume1/photo/2024/202401 - Hiver Automne   0 /volume1/photo/2025/DCIM   0 /volume1/photo/@eaDir/clientd.tmp.dir   0 /volume1/photo/@eaDir/cloud.tmp.dir   0 /volume1/photo/@eaDir/@recycle   0 /volume1/photo/@eaDir/@tmp   4.0K /volume1/photo/@eaDir/@drive.queues   24K /volume1/photo/@eaDir/sharesnap_share_configuration   32K /volume1/photo/2010 - été/@eaDir   40K /volume1/photo/2011- Ete/@eaDir   52K /volume1/photo/2021-été covidé/@eaDir   84K /volume1/photo/@eaDir/980710-photo CV julien.jpg   264K /volume1/photo/@eaDir/131124-elio pas content.jpg   352K /volume1/photo/2009 Nouvel an Réveillon/@eaDir   432K /volume1/photo/@eaDir/2015-soirée mexicaine.jpg   440K /volume1/photo/1968-1994 - photos famille dont Noumea/Givet   548K /volume1/photo/@eaDir/150314-Zoee en 18X24.png   616K /volume1/photo/2009 Nouvel an Réveillon   656K /volume1/photo/1968-1994 - photos famille dont Noumea/1977   656K /volume1/photo/@eaDir/7b06435c-6bad-4eb0-aa38-af03bdfbfbc8.jpg   672K /volume1/photo/@eaDir/PhotoLab445379.jpg   792K /volume1/photo/@eaDir/160220-elio en 18X24.png   848K /volume1/photo/2011 - printemps/@eaDir   900K /volume1/photo/2016 ACDC/@eaDir   1.5M /volume1/photo/1968-1994 - photos famille dont Noumea/1976   1.7M /volume1/photo/1968-1994 - photos famille dont Noumea/Pierre-001   2.1M /volume1/photo/1968-1994 - photos famille dont Noumea/1969   3.0M /volume1/photo/1968-1994 - photos famille dont Noumea/1978   3.4M /volume1/photo/1968-1994 - photos famille dont Noumea/1968   3.4M /volume1/photo/1968-1994 - photos famille dont Noumea/1980   3.5M /volume1/photo/2014-mars-repas chez maman/@eaDir   4.1M /volume1/photo/1999/@eaDir   4.3M /volume1/photo/2014 - 4 ans Zoée/@eaDir   4.9M /volume1/photo/1968-1994 - photos famille dont Noumea/1979   4.9M /volume1/photo/2016 ACDC   5.3M /volume1/photo/1986 - Californie/@eaDir   5.3M /volume1/photo/Cartes de voeux/@eaDir   5.5M /volume1/photo/2010-hiver/@eaDir   6.0M /volume1/photo/1968-1994 - photos famille dont Noumea/1992   6.6M /volume1/photo/1930-2016 - Photos BERTRAND MER/Elio   6.6M /volume1/photo/2016-best photos pour 70 ans/Elio   6.7M /volume1/photo/1999   6.9M /volume1/photo/1968-1994 - photos famille dont Noumea/Etats-Unis 1986   7.4M /volume1/photo/2015 - Noël/@eaDir   7.6M /volume1/photo/1930-2016 - Photos BERTRAND MER/Les communions solennelle s   7.6M /volume1/photo/2016-best photos pour 70 ans/Les communions solennelles   7.8M /volume1/photo/1968-1994 - photos famille dont Noumea/1994   7.9M /volume1/photo/2013-Serbie/@eaDir   8.0M /volume1/photo/2014/@eaDir   8.4M /volume1/photo/1968-1994 - photos famille dont Noumea/1990   8.5M /volume1/photo/2004 - été/@eaDir   8.5M /volume1/photo/2022-Noel/@eaDir   8.6M /volume1/photo/2015 - Automne/@eaDir   8.9M /volume1/photo/1930-2016 - Photos BERTRAND MER/Les amis, la famille en n oir et blanc   8.9M /volume1/photo/2016-best photos pour 70 ans/Les amis, la famille en noir et blanc   9.5M /volume1/photo/1968-1994 - photos famille dont Noumea/1981   9.9M /volume1/photo/2018 - Noel/@eaDir   10M /volume1/photo/#recycle/2016 - 70 ans maman_DiskStation_Nov-04-1745-2020 _CaseConflict   11M /volume1/photo/1930-2016 - Photos BERTRAND MER/Album de la famille de Re née Campanella   11M /volume1/photo/2010 - naissance Zoée/@eaDir   11M /volume1/photo/2014/Noël 2014   11M /volume1/photo/2016-best photos pour 70 ans/Album de la famille de Renée Campanella   12M /volume1/photo/2013 Marseille/@eaDir   12M /volume1/photo/2014-mars-repas chez maman   12M /volume1/photo/2015-Hiver/@eaDir   12M /volume1/photo/2015-inventaire maison assurances/@eaDir   13M /volume1/photo/1968-1994 - photos famille dont Noumea/1975   14M /volume1/photo/1968-1994 - photos famille dont Noumea/1983   15M /volume1/photo/2005 - été/@eaDir   16M /volume1/photo/2016-mai juin/@eaDir   16M /volume1/photo/2016 - Valras/@eaDir   17M /volume1/photo/2004 - été   17M /volume1/photo/2010 - naissance Zoée   18M /volume1/photo/1968-1977/@eaDir   18M /volume1/photo/2014 - 4 ans Zoée   18M /volume1/photo/2014/Anniversaire_Elio_2014   18M /volume1/photo/2016-avril avant crète/@eaDir   19M /volume1/photo/1930-2016 - Photos BERTRAND MER/Grand-père Emile et mamie Rosa   19M /volume1/photo/2016-best photos pour 70 ans/Grand-père Emile et mamie Ro sa   21M /volume1/photo/1930-2016 - Photos BERTRAND MER/@eaDir   21M /volume1/photo/1930-2016 - Photos BERTRAND MER/L'album de Papa et Maman   21M /volume1/photo/2016-best photos pour 70 ans/L'album de Papa et Maman   21M /volume1/photo/2016- photos Maman Papa/@eaDir   22M /volume1/photo/2011 - Automne/@eaDir   22M /volume1/photo/2013 Noël/@eaDir   22M /volume1/photo/2014-Hiver Printemps/@eaDir   23M /volume1/photo/2014 - Automne Noel/@eaDir   24M /volume1/photo/2011 - jour de l'an/@eaDir   24M /volume1/photo/2014-mur effondré - Ville de Toulon/@eaDir   25M /volume1/photo/2009 - Papa/@eaDir   25M /volume1/photo/2010-hiver   25M /volume1/photo/2021-été covidé/2021 - après vacances   25M /volume1/photo/Cartes de voeux   26M /volume1/photo/1968-1994 - photos famille dont Noumea/1984   27M /volume1/photo/1930-2016 - Photos BERTRAND MER/Enfants Mer et cousins   27M /volume1/photo/2008 Toscane/@eaDir   27M /volume1/photo/2016 - 70 ans Maman/@eaDir   27M /volume1/photo/2016-best photos pour 70 ans/Enfants Mer et cousins   27M /volume1/photo/2022-automne a Noel/@eaDir   29M /volume1/photo/2018 - Noel   30M /volume1/photo/2008 - mariage Héléna et Jérome/@eaDir   30M /volume1/photo/2012 - Eté/@eaDir   30M /volume1/photo/#recycle/2016 - 70 ans maman_DiskStation_Nov-04-1745-2020 _CaseConflict_1   31M /volume1/photo/2025/2501 à 2502   32M /volume1/photo/2015 - Noël   33M /volume1/photo/2014/Carnaval 2014   34M /volume1/photo/2016-best photos pour 70 ans/@eaDir   35M /volume1/photo/2014-Barcelone/@eaDir   36M /volume1/photo/2013/@eaDir   36M /volume1/photo/2016-Parc Vert Coteau/soirée janvier 2016   36M /volume1/photo/2024/2024 - Nouvel an Arriège   37M /volume1/photo/2015 - Automne   38M /volume1/photo/2014/Février 2014   39M /volume1/photo/2011 - Noel/@eaDir   39M /volume1/photo/2020 - Automne Noel/@eaDir   40M /volume1/photo/2014/Sept_2014_4ans_Zoée   40M /volume1/photo/@eaDir/SYNO@.fileindexdb   41M /volume1/photo/2016-Parc Vert Coteau/avril 2016 état   41M /volume1/photo/2022-Noel   43M /volume1/photo/@eaDir   44M /volume1/photo/2009 - Papa   44M /volume1/photo/2012/@eaDir   45M /volume1/photo/1930-2016 - Photos BERTRAND MER/Papa juin 2009 - Le livre d'Alain   45M /volume1/photo/2016-best photos pour 70 ans/Papa juin 2009 - Le livre d' Alain   47M /volume1/photo/2005 - été   47M /volume1/photo/2008 Toscane   50M /volume1/photo/2019-Cabaret Vert/@eaDir   51M /volume1/photo/2015 - Juillet Valras/@eaDir   52M /volume1/photo/2021-hiver printemps/@eaDir   53M /volume1/photo/2015-Hiver   53M /volume1/photo/2018-défi Genes/@eaDir   54M /volume1/photo/2008 - été Hautes Alpes/@eaDir   54M /volume1/photo/2009 - Elio/@eaDir   55M /volume1/photo/2013-Serbie   58M /volume1/photo/2013 Marseille   58M /volume1/photo/2015-inventaire maison assurances   62M /volume1/photo/2007-1er janvier/@eaDir   62M /volume1/photo/2016 - Valras   64M /volume1/photo/2014- été/@eaDir   67M /volume1/photo/2009 - été Irlande/@eaDir   67M /volume1/photo/2016-mai juin   70M /volume1/photo/1968-1994 - photos famille dont Noumea/Nouméa   70M /volume1/photo/2011- Ete/GARD juillet 2011   71M /volume1/photo/2020 - Hiver Confinement COVID/@eaDir   73M /volume1/photo/2022-hiver à Paques/@eaDir   73M /volume1/photo/2024/202412 - Automne Noel   75M /volume1/photo/2013 - Printemps/@eaDir   76M /volume1/photo/2016-Parc Vert Coteau   76M /volume1/photo/2024/@eaDir   79M /volume1/photo/2012 - Eté/2012 - Bretagne   79M /volume1/photo/2014-Hiver Printemps   81M /volume1/photo/2013 Noël   82M /volume1/photo/2017-Voyage à Turin/@eaDir   84M /volume1/photo/2016-Venasque/@eaDir   86M /volume1/photo/1986 - Californie   88M /volume1/photo/2023-ete/@eaDir   91M /volume1/photo/2009 - été Ardèche/@eaDir   93M /volume1/photo/2016-avril avant crète   94M /volume1/photo/2007-1er janvier   96M /volume1/photo/2021-Automne et Noel/@eaDir   97M /volume1/photo/2008 - été Hautes Alpes   99M /volume1/photo/PhotoLibrary/2025   100M /volume1/photo/2014- Porto - Primavera/@eaDir   107M /volume1/photo/2014-mur effondré - Ville de Toulon   108M /volume1/photo/2023 - Automne Noel/@eaDir   111M /volume1/photo/2009 - été Irlande   111M /volume1/photo/2015- printemps/@eaDir   114M /volume1/photo/2025/2504 et 2505   125M /volume1/photo/2010 - été   125M /volume1/photo/2010 - été/Beauduc   125M /volume1/photo/2014 - Automne Noel   130M /volume1/photo/2016-Crete/@eaDir   131M /volume1/photo/2011 - jour de l'an   138M /volume1/photo/2021-été covidé/2021 avant vacances   139M /volume1/photo/2013   140M /volume1/photo/2018 - Valras/@eaDir   143M /volume1/photo/2016-Automne Noel/@eaDir   145M /volume1/photo/2013-Pays Bas/@eaDir   146M /volume1/photo/2014-Barcelone   150M /volume1/photo/2023-Hiver à Paques/@eaDir   152M /volume1/photo/2008 - mariage Héléna et Jérome   153M /volume1/photo/2017-Automne Noel/@eaDir   154M /volume1/photo/2014/Mai_Juin 2014   157M /volume1/photo/2014/Avril 2014   159M /volume1/photo/2022-automne a Noel   162M /volume1/photo/2009 - été Ardèche   173M /volume1/photo/2014 - 5 jours en Corse/@eaDir   178M /volume1/photo/2016-best photos pour 70 ans/Ma famille pour mes 70 ans   178M /volume1/photo/2020 -Printemps/@eaDir   179M /volume1/photo/1930-2016 - Photos BERTRAND MER   180M /volume1/photo/1968-1994 - photos famille dont Noumea   181M /volume1/photo/2019- Hiver Printemps/@eaDir   181M /volume1/photo/2022 - été Flandres NL Paris/@eaDir   183M /volume1/photo/2012   184M /volume1/photo/2009 - Elio   186M /volume1/photo/2014/2014-été   192M /volume1/photo/2016- photos Maman Papa   203M /volume1/photo/1968-1977   206M /volume1/photo/1967 à 2000 - photos famille Bertrand/@eaDir   213M /volume1/photo/2023-Mai à Vacances Eté/@eaDir   214M /volume1/photo/2019-Automne/@eaDir   222M /volume1/photo/2015 - Juillet Valras   229M /volume1/photo/2020 - Automne Noel   230M /volume1/photo/2018-printemps/@eaDir   258M /volume1/photo/2021-hiver printemps   281M /volume1/photo/2014- été   282M /volume1/photo/2018 - automne noel/@eaDir   323M /volume1/photo/2011 - Noel   325M /volume1/photo/2018-défi Genes   348M /volume1/photo/2005 - naissance Elio/@eaDir   354M /volume1/photo/2011 - printemps   368M /volume1/photo/2011 - Automne   381M /volume1/photo/2017-Hiver-Printemps/@eaDir   384M /volume1/photo/2016-Venasque   407M /volume1/photo/2020 -Printemps   409M /volume1/photo/2016-best photos pour 70 ans   419M /volume1/photo/2016 - 70 ans Maman   434M /volume1/photo/2020 - Hiver Confinement COVID   439M /volume1/photo/2017-Voyage à Turin   460M /volume1/photo/2023-Mai à Vacances Eté   462M /volume1/photo/2011- Ete/LIGURIE aout 2011   500M /volume1/photo/2013 - Printemps   511M /volume1/photo/2023-ete   515M /volume1/photo/2022-hiver à Paques   520M /volume1/photo/2012 - Eté/2012 - été Buis   525M /volume1/photo/2016-Crete   528M /volume1/photo/2014- Porto - Primavera   531M /volume1/photo/2011- Ete   535M /volume1/photo/2023-Vacances D NL B/@eaDir   573M /volume1/photo/2023 - Automne Noel   575M /volume1/photo/2016-été Ré Gers/@eaDir   580M /volume1/photo/2018-Barcelone Primavera Sound/@eaDir   618M /volume1/photo/2019-Cabaret Vert   629M /volume1/photo/2019 Bretagne Alpes/@eaDir   635M /volume1/photo/2013-Pays Bas   663M /volume1/photo/2021-Automne et Noel   667M /volume1/photo/2014   685M /volume1/photo/2015- printemps   700M /volume1/photo/2017-été Scandinavie/@eaDir   718M /volume1/photo/2024/2024 - Eté Italie Autriche   759M /volume1/photo/2018 - Valras   809M /volume1/photo/#recycle/2025   817M /volume1/photo/2016-Automne Noel   846M /volume1/photo/2025/2506 à 2509   860M /volume1/photo/#recycle   907M /volume1/photo/2019-Automne   920M /volume1/photo/2017-Automne Noel   935M /volume1/photo/2023-Hiver à Paques   941M /volume1/photo/2024/2024-Porto   944M /volume1/photo/cecile tri 2022/@eaDir   945M /volume1/photo/PhotoLibrary/DCIM   956M /volume1/photo/2005 - naissance Elio   964M /volume1/photo/2020 - été Vienne Venise/@eaDir   991M /volume1/photo/2025   1.1G /volume1/photo/2014 - 5 jours en Corse   1.1G /volume1/photo/2015-Croatie Autriche/@eaDir   1.1G /volume1/photo/2018-the Alpen/@eaDir   1.1G /volume1/photo/PhotoLibrary   1.2G /volume1/photo/2021-été covidé/2021 - vacances Aout Mont Blanc Cantal   1.2G /volume1/photo/2022 - été Flandres NL Paris   1.3G /volume1/photo/2019- Hiver Printemps   1.3G /volume1/photo/2021-été covidé   1.4G /volume1/photo/2018-printemps   1.5G /volume1/photo/2016-été Ré Gers/Vidéo Aveyron Ré et Gers   1.5G /volume1/photo/2024/2024-Via Reggio   1.8G /volume1/photo/2018 - automne noel   1.9G /volume1/photo/2012 - Eté/2012- été Italia   2.0G /volume1/photo/2017-Hiver-Printemps   2.7G /volume1/photo/2012 - Eté   2.8G /volume1/photo/2019 Bretagne Alpes   2.8G /volume1/photo/2020 - été Vienne Venise   2.9G /volume1/photo/1967 à 2000 - photos famille Bertrand   3.2G /volume1/photo/2023-Vacances D NL B   3.6G /volume1/photo/2018-Barcelone Primavera Sound   3.7G /volume1/photo/cecile tri 2022   3.8G /volume1/photo/2024   4.1G /volume1/photo/2016-été Ré Gers   4.2G /volume1/photo/2017-été Scandinavie   5.7G /volume1/photo/2018-the Alpen   6.4G /volume1/photo/2015-Croatie Autriche   79G /volume1/photo/ Montages Rclone Sasnexte et Kdrive Architecture NAS Sasnexte (Synology) └── rclone sync (WebDAV) ──► kDrive Infomaniak └── rclone mount (FUSE) ──► VPS Jux └── containers Docker Le NAS pousse les médias vers kDrive une fois par jour via le planificateur DSM. Le VPS monte kDrive en FUSE en permanence — les containers accèdent aux fichiers en lecture. Remote rclone (NAS sasnexte) Fichier : /volume1/homes/SAS_NEXTE/.config/rclone/rclone.conf [kdrive_music] type = webdav url = https://591617.connect.kdrive.infomaniak.com vendor = other user = julien.bertrand@nexte.fr pass = [obfusqué rclone] Note : le remote s'appelle kdrive_music mais sert à synchroniser tous les contenus (musique, photos, audiobooks, Komga). Nom historique à ne pas confondre avec un remote dédié musique. Mot de passe WebDAV : régénéré le 2026-06-23 (compte julien.bertrand@nexte.fr, espace kDrive 591617). Mise à jour via rclone config update kdrive_music pass $(rclone obscure NOUVEAU_MDP). Script unifié (NAS sasnexte) Emplacement : /volume1/homes/SAS_NEXTE/scripts/sync_kdrive_complete.sh Planificateur DSM : tâche déclenchée quotidiennement — commande : /volume1/homes/SAS_NEXTE/scripts/sync_kdrive_complete.sh Note sudo : le script contient sudo -u SAS_NEXTE rclone .... Si la tâche DSM est configurée pour tourner en tant que SAS_NEXTE, retirer le sudo -u SAS_NEXTE (inutile et peut bloquer). Si elle tourne en root, le garder. Le script n'est plus recopie ici : il est versionne dans l'arbre Syncthing, seule source de verite — Jux-scripts/NAS-Sync/sync_kdrive_complete.sh, deploye sur le NAS dans /volume1/homes/SAS_NEXTE/scripts/. La version en ligne ci-dessous datait du 2026-06-23 et n'avait aucune protection ; elle a ete remplacee le 2026-08-28 par la version durcie decrite plus bas. Sauvegarde de l'ancienne sur le NAS : sync_kdrive_complete.sh.bak-260828. Déploiement : écrire via vi depuis SSH — le heredoc et les éditeurs Windows introduisent des CRLF qui cassent bash. Après édition : sed -i 's/\r//' script.sh pour vérifier. Syncs actifs Nom Source NAS Destination kDrive Notes audiobooks /volume1/audiobooks SYNC-pour_VPS/Sync-SASNEXTE/audiobooks   music /volume1/music SYNC-pour_VPS/Sync-SASNEXTE/music   photo /volume1/photo/2026 SYNC-pour_VPS/Sync-SASNEXTE/photo/2026 Année courante seulement komga /volume1/Komga SYNC-pour_VPS/Sync-SASNEXTE/Komga   Non géré ici : Romans : abandonné Foxy : sur NAS Maison (pas sasnexte) Options rclone — justifications Option Rôle --delete-excluded Supprime sur kDrive les fichiers exclus qui auraient pu y être uploadés --ignore-errors Poursuit la sync si un fichier est inaccessible (NAS Synology parfois occupé) --delete-during Supprime les fichiers obsolètes au fil du scan, pas en fin de run --fast-list Réduit les appels API WebDAV (un seul listing récursif) @eaDir/** Dossiers de métadonnées Synology (miniatures) #recycle/** Corbeille Synology @__thumb/** Miniatures Synology *.@SynoResource / *.@SynoEAStream Flux de ressources étendues Synology --transfers=4 4 fichiers en parallèle — équilibre entre perf et charge WebDAV --checkers=8 8 vérifications de hash en parallèle --timeout=5m Timeout par opération I/O --contimeout=2m Timeout de connexion initiale Côté VPS — montages FUSE correspondants Les services systemd sur le VPS montent les dossiers kDrive en FUSE : Service systemd Point de montage Source kDrive rclone-audiobookshelf.service /home/debian/audiobookshelf/audiobooks SYNC-pour_VPS/Sync-SASNEXTE/audiobooks kdrive-music.service /home/debian/music SYNC-pour_VPS/Sync-SASNEXTE/music rclone-photo.service /home/debian/photo SYNC-pour_VPS/Sync-SASNEXTE/photo kdrive-komga.service /home/debian/komga SYNC-pour_VPS/Sync-SASNEXTE/Komga Remote VPS : kdrive: — remote natif Infomaniak ( @infomaniak/mcp-server-kdrive), compte julien.bertrand@live.fr, kDrive ID 591617. Logs Un fichier de log par sync par run : /volume1/homes/SAS_NEXTE/logs/sync_{nom}_{date}.log Bilan global : /volume1/homes/SAS_NEXTE/logs/sync_{date}.log Commande de suivi : tail -f /volume1/homes/SAS_NEXTE/logs/sync_music_*.log Panne du 2026-08-28 — les 4 syncs a l'arret, cause : rclone.conf deplace Symptome : les photos de l'ete 2026 deposees la veille sur le NAS ne remontaient pas sur kDrive. Ni par la tache planifiee, ni par un lancement manuel du script — ce qui excluait d'emblee un probleme de reseau ou de quota. Message dans le journal du jour ( /volume1/homes/SAS_NEXTE/logs/sync_photo_AAAAMMJJ_*.log) : NOTICE: Config file "/var/services/homes/SAS_NEXTE/.config/rclone/rclone.conf" not found - using defaults CRITICAL: Failed to create file system for "kdrive_music:...": didn't find section in config file ("kdrive_music") Cause : le 2026-08-27 a 10:16, le dossier .config du home a ete range dans /volume1/homes/SAS_NEXTE/Fichiers techniques/. Le script ne passait pas --config : rclone cherchait donc $HOME/.config/rclone/rclone.conf et ne trouvait plus le remote. Les quatre syncs sont mortes d'un coup (photo, music, komga, audiobooks), des le run suivant. Correction : fichier recopie a sa place ( chmod 600) ; une copie subsiste dans Fichiers techniques et sert desormais de secours automatique (voir plus bas). Ce que cet incident apprend Devant un didn't find section in config file, verifier l'emplacement du rclone.conf avant tout le reste. Un simple rangement par SMB ou File Station suffit a tout casser, sans que rien ne soit "casse" au sens habituel. L'echec etait totalement silencieux : aucune alerte, aucun mail, l'ancien script retournait toujours 0. Seul le journal du jour le disait. C'est le vrai defaut, plus encore que le fichier deplace. Piege d'analyse au moment de reparer : le dossier avait ete renomme cote NAS ( 2026_Eté UK → 2026_Eté UK et Italia), ce qui faisait voir a rclone 308 suppressions et 351 copies. En miroir --delete-during c'est correct, mais toujours faire un --dry-run puis comparer les listes avant de relancer une sync apres un incident : rclone lsf -R --files-only kdrive_music:.../photo/2026/ANCIEN_NOM | sort > /tmp/k.txt (cd /volume1/photo/2026/NOUVEAU_NOM && ls -p | grep -v /) | sort > /tmp/n.txt comm -23 /tmp/k.txt /tmp/n.txt # doit etre vide : rien sur kDrive qui manque au NAS Trou du 20 au 24/08 dans tous les journaux : NAS eteint (coupures secteur / orages), sans rapport avec cette panne. Durcissement du script (2026-08-28) Source de verite : Jux-scripts/NAS-Sync/sync_kdrive_complete.sh (arbre Syncthing, disponible sur toutes les machines). Le comportement nominal est inchange — memes 4 syncs, memes options rclone, meme emplacement, la tache DSM n'a pas besoin d'etre modifiee. Protection Ce qu'elle evite --config en chemin absolu La panne ci-dessus : le script ne depend plus de $HOME ni de l'utilisateur qui le lance Restauration automatique de la config depuis Fichiers techniques/.config/rclone/rclone.conf Un nouveau deplacement du fichier ne coupe plus la synchro ; l'evenement est journalise Controles prealables : rclone present, section [kdrive_music] presente, remote reellement joignable Partir en synchro avec un mot de passe WebDAV revoque ou kDrive injoignable Refus de synchroniser une source absente ou vide Le scenario catastrophe : un volume non monte ou un dossier vide, qui en mode miroir viderait la destination kDrive --max-delete par service (photo 500, les autres 1000) Une suppression de masse imprevue : la sync s'interrompt et attend un oeil humain Ligne === BILAN: X/4 OK === et fichier d'etat logs/etat_sync.txt Avoir a lire quatre journaux pour savoir si la nuit s'est bien passee Code de sortie non nul en cas d'echec + entree dans le journal systeme DSM L'echec silencieux Purge des journaux de plus de 90 jours L'accumulation dans logs/ Mode d'emploi S=/volume1/homes/SAS_NEXTE/scripts/sync_kdrive_complete.sh $S # les 4 syncs (ce que lance la tache DSM) DRY_RUN=1 $S # essai a blanc, rien n'est ecrit sur kDrive SERVICES="photo music" $S # un sous-ensemble seulement SERVICES="photo" FORCE_MAX_DELETE=1 $S # apres verification, lever le garde-fou cat /volume1/homes/SAS_NEXTE/logs/etat_sync.txt # resultat du dernier run Canari — savoir en une commande si la nuit s'est bien passee cat /volume1/homes/SAS_NEXTE/logs/etat_sync.txt grep -l CRITICAL /volume1/homes/SAS_NEXTE/logs/sync_*_$(date +%Y%m%d)*.log Notifications — pourquoi pas synodsmnotify Sur DSM 7.4, synodsmnotify n'accepte pas de titre libre : tout libelle est rejete par title: '...' is neither mail string key nor i18n format, y compris les formes i18n:... essayees. Le script utilise donc synologset1 sys err 0x11100000, qui ecrit dans le journal systeme DSM (Centre de journalisation). Action restante cote DSM (a faire par Julien, compte admin) : dans Planificateur de taches → tache de synchronisation → Parametres, cocher « Envoyer les details d'execution par e-mail » et « uniquement lorsque le script se termine anormalement ». C'est maintenant efficace : l'ancien script retournait toujours 0, la version durcie retourne 1 des qu'un service echoue. Tests de validation passes le 2026-08-28 Marche nominale en essai a blanc : BILAN: 1/1 OK, code retour 0 Panne rejouee (config deplacee) : restauration automatique, puis sync normale — code retour 0 Source vide : sync non lancee, ECHEC photo (source-vide), code retour 1 Seuil de suppressions depasse : sync interrompue, ECHEC photo (seuil-suppressions), code retour 1, avec la commande de reprise affichee REX déploiement 2026-06-23 Problème 1 : CRLF dans le script (édition Windows) → bash -x montrait \r comme commande inconnue. Fix : réécrire via vi sur le NAS. Problème 2 : mot de passe WebDAV périmé (401 Unauthorized) → régénéré le 2026-06-09 côté VPS mais pas mis à jour sur le NAS. Fix : rclone config update kdrive_music pass $(rclone obscure MDP). SSH bloqué : pare-feu NAS + 2FA rendent le SSH depuis l'extérieur inaccessible. Passer par une session SSH locale ou DSM. Résultat : 4 syncs validés — audiobooks 48.9 GiB / 23 min, music 1 GiB / 45s, photo et komga déjà à jour. 260727 - Claude en MCP sur le NASmaison Le NASmaison est un petit Synology DS218j installé à la maison de Julien. IL sert surtout de stockage des livres et des films. Egalement des cartes rasters, le dossier personnel de Cécile, Elio et Zoée. IL a une copie miroir du dossier musique et komga, mais plus photos. Il est synchronisé avec le NAS SASNEXTE pour musique et Komga. Il compose au quotidien 6 procédures hyperbacups de la bibliothèque de livres en direction du Kdrive : les livres de Foxy répartis par grands thèmes les livres de la nouvelle bibliothèque - biblio-calibre Claude a désormais une connexion MCP privilégiée sur le NASmaison. Et peut notamment garantir le transfert sécurisé des dossiers            260731-Claude et SASNEXTE sur le PC Elio+Jux WireGuard sasnexte sur le PC Elio+Jux — diagnostic et résolution Date : 2026-07-31 | Machine : PC Windows eliob | NAS cible : sasnexte (82.66.244.248) Statut : RÉSOLU — tunnel activé, lecteur S: mappé sur \\10.0.0.2\NEXTE Question initiale WireGuard a été monté sur ce PC et sur le NAS sasnexte, mais le disque du NAS n'apparaissait pas comme lecteur dans l'explorateur de fichiers Windows. Pourquoi ? Réponse courte Deux causes cumulées : Le tunnel n'était pas activé sur le PC : la config vps_maisonpc_sasnexte était importée dans l'application WireGuard, mais jamais activée (aucun service WireGuardTunnel$*, aucun adaptateur réseau, wg show vide). Un lecteur réseau ne se crée jamais tout seul : la découverte réseau Windows (broadcast/mDNS/WS-Discovery) ne traverse pas un tunnel de couche 3 — le NAS n'apparaîtra jamais spontanément dans « Réseau ». Il faut mapper manuellement une lettre avec net use. Topologie du tunnel vps_maisonpc_sasnexte Réseau 10.0.0.0/24, hub-and-spoke avec le VPS Jux comme serveur ( wg0, port UDP 51820) : IP Machine État (2026-07-31) 10.0.0.1 VPS Jux (51.77.141.54) — hub Serveur, forwarding OK ( ACCEPT in wg0 dans FORWARD) 10.0.0.2 NAS sasnexte (endpoint 82.66.244.248) Connecté, handshake actif, 485 Gio transférés 10.0.0.3 PC Windows eliob Connecté (après activation du tunnel) 10.0.0.4 PC maison (peer MkzKqDe…) Jamais connecté (pas d'endpoint) Clé publique serveur VPS : zbsY/bl4fHU1eR29PjTruK0Nrmp/BQORSlBGSh3Nkyo= Diagnostic détaillé Côté PC (avant activation) Application WireGuard installée, service WireGuardManager Running Config importée (dossier C:\Program Files\WireGuard\Data\Configurations présent, chiffré DPAPI, admin uniquement) mais tunnel inactif Aucun .conf en clair dans le profil utilisateur — pour l'exporter : app WireGuard en admin → « Exporter les tunnels » Après activation du tunnel Adaptateur vps_maisonpc_sasnexte Up, IP 10.0.0.3/24 wg.exe show échoue en non-admin ( Permission denied) — normal, utiliser Get-NetAdapter / Get-NetIPAddress pour vérifier sans élévation Piège ICMP : le NAS ne répond PAS au ping (pare-feu Synology) alors que le tunnel fonctionne. Ne pas diagnostiquer au ping — tester en TCP : Test-NetConnection 10.0.0.2 -Port 445 Ports NAS via tunnel : 445/139 (SMB) OK, 22 (SSH) OK, 5000/5001 (DSM) bloqués SSH sasnexte depuis l'extérieur Le SSH sas_nexte@82.66.244.248 (IP publique) refusait le mot de passe le 2026-07-31 alors que le même mot de passe fonctionne en SMB via le tunnel → le mot de passe est bon, c'est le SSH public qui est filtré (fail2ban/whitelist). Passer par le tunnel : ssh sas_nexte@10.0.0.2 (port 22 ouvert). Résolution appliquée (2026-07-31) Tunnel activé dans l'app WireGuard (fait par Julien) → handshake OK avec le VPS Identifiants SMB enregistrés dans le gestionnaire d'identification Windows : cmdkey /add:10.0.0.2 /user:sas_nexte /pass:"xAPIJU5108§" Lecteur mappé : net use S: \\10.0.0.2\NEXTE /persistent:yes Vérifié : S: liste bien le contenu du partage NEXTE Important : le lecteur S: ne fonctionne que si le tunnel WireGuard est actif. Après un reboot, le tunnel se réactive automatiquement (service WireGuardTunnel$vps_maisonpc_sasnexte en démarrage auto) et Windows reconnecte S: grâce à /persistent:yes + identifiants cmdkey. Partages SMB disponibles sur \10.0.0.2 ActiveBackupforBusiness, audiobooks, chat, home (dossier perso SAS_NEXTE), homes, Iso VM, music, NetBackup, NEXTE (mappé sur S:), Public_s, RAW, Ressources_NEXTE, retro, web, web_packages Pour mapper un partage supplémentaire (les identifiants sont déjà enregistrés) : net use R: \\10.0.0.2\Ressources_NEXTE /persistent:yes Points ouverts Finaliser la config Ubuntu : exporter le .conf depuis l'app WireGuard Windows (admin → Exporter les tunnels) — le peer 10.0.0.4 libre pourrait aussi servir pour Ubuntu Identifier le peer 10.0.0.4 ( MkzKqDeVoIX9R9oMlCK7c76e3c3p/DJe/ryd0eqEV20=) : prévu pour le PC maison, jamais connecté Restauration du 2026-08-27 — après la réinstallation de Windows Statut : RÉSOLU. Tunnel rétabli, S: remonté. Le poste s'appelle désormais julie — c'est la même machine que « PC Elio+Jux », réinstallée le 2026-08-08 (voir CLAUDE.md). Ce qui s'était passé La réinstallation de Windows a emporté WireGuard et, avec lui, la clé privée du peer 10.0.0.3. Elle vivait dans C:\Program Files\WireGuard\Data\Configurations, chiffrée par DPAPI — irrécupérable, exactement comme l'identité Syncthing PC-Elio+Jux perdue au même moment. Le diagnostic se lit d'une seule commande sur le VPS : peer: cMDglLZZrrZUL4QdtB5nHQFe0U5fdzXzaWnmihw1b0o= endpoint: 83.195.45.93:63884 allowed ips: 10.0.0.3/32 latest handshake: 19 days, 23 hours, 41 minutes ago 19 jours et 23 heures avant le 27/08 à 21 h ramènent au 7 août en soirée, soit la veille de la réinstallation (8 août à 12:21). Le NAS, lui, faisait des handshakes toutes les quelques secondes : côté VPS et côté NAS, rien n'était cassé. Seul le poste avait disparu. Recherche préalable infructueuse, mais qui valait la peine : aucun .conf exporté nulle part sur D:, ni dans l'arbre Syncthing, ni dans le profil. Le point ouvert « exporter le .conf pour Ubuntu » de juillet n'avait jamais été fait — sans quoi l'identité aurait pu être restaurée telle quelle. Décision : réutiliser l'adresse 10.0.0.3, pas le peer libre 10.0.0.4 Le peer 10.0.0.4 était disponible, mais changer d'adresse aurait invalidé la documentation et, surtout, aurait risqué de buter sur d'éventuelles règles du pare-feu Synology référençant 10.0.0.3. On garde donc l'adresse et on ne remplace que la clé publique — le NAS n'a pas été touché. Nouvelle identité du poste Clé publique (nouvelle) UfqCwYB3T/XFl9qbpFR/8YSibq3Yta83fB9D2GAiW2A= Clé publique (ancienne, morte) cMDglLZZrrZUL4QdtB5nHQFe0U5fdzXzaWnmihw1b0o= Adresse 10.0.0.3/24, inchangée Fichier client D:\WireGuard\vps_maisonpc_sasnexte.conf — hors de l'arbre Syncthing, une clé privée n'a rien à faire sur les 7 appareils du maillage Paire générée par une implémentation X25519 en Python pur (aucune dépendance à installer), validée au préalable contre le vecteur de test de la RFC 7748 § 6.1 avant d'être utilisée. Configuration cliente : [Interface] PrivateKey = Address = 10.0.0.3/24 [Peer] PublicKey = zbsY/bl4fHU1eR29PjTruK0Nrmp/BQORSlBGSh3Nkyo= Endpoint = 51.77.141.54:51820 AllowedIPs = 10.0.0.0/24 PersistentKeepalive = 25 AllowedIPs = 10.0.0.0/24 et non 0.0.0.0/0 : seul le trafic du VPN passe par le tunnel, la navigation ordinaire n'est pas détournée par le VPS. PersistentKeepalive = 25 maintient l'association NAT de la Livebox. Côté VPS Sauvegarde ‌/etc/wireguard/wg0.conf.bak-260827, remplacement de la clé publique du peer 10.0.0.3 et suppression de son Endpoint périmé, puis application sans redémarrer l'interface : sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)' wg syncconf plutôt que wg-quick down/up : c'est le point important. Un redémarrage de l'interface aurait coupé le peer du NAS et remis ses compteurs à zéro. Après syncconf, le NAS a conservé son handshake et ses 962,99 Gio de compteur, sans la moindre interruption. Vérifié au passage : -A FORWARD -i wg0 -j ACCEPT bien présent (posé par le PostUp), ip_forward = 1, 51820/udp ouvert dans ufw. Côté poste winget install --id WireGuard.WireGuard -e & "C:\Program Files\WireGuard\wireguard.exe" /installtunnelservice "D:\WireGuard\vps_maisonpc_sasnexte.conf" cmdkey /add:10.0.0.2 /user:sas_nexte /pass:"xAPIJU5108§" net use S: \\10.0.0.2\NEXTE /persistent:yes /installtunnelservice évite entièrement l'interface graphique : il crée le service WireGuardTunnel$vps_maisonpc_sasnexte, le met en démarrage automatique et l'active dans la foulée. Le nom du tunnel est celui du fichier — d'où le choix de conserver vps_maisonpc_sasnexte. ⚠ Tout cela doit être tapé par Julien dans sa propre console, jamais depuis Claude Code. Le piège MSIX (page 282) ne concerne pas que les installeurs : cmdkey écrit dans %APPDATA%\Microsoft\Credentials et net use /persistent dans HKCU\Network, deux emplacements virtualisés par le conteneur. Lancées depuis une session Claude, ces commandes auraient annoncé un succès sans rien créer pour l'utilisateur. Vérifié en direct le 2026-08-27 : une fois S: monté depuis la console de Julien, Test-Path S:\ répond False depuis la session Claude Code. Le cloisonnement joue aussi sur les lettres de lecteur — c'est un cas de plus à ajouter à la liste de la page 282. Vérification finale Contrôle Résultat Test-NetConnection 10.0.0.2 -Port 445 True net use S: « La commande s'est terminée correctement » wg show sur le VPS — peer 10.0.0.3 endpoint 83.195.45.93:60519, handshake 36 s, 10,47 Kio reçus / 2,76 Kio envoyés wg show sur le VPS — peer 10.0.0.2 (NAS) intact, handshake 30 s, 962,99 Gio — non perturbé Le rappel de juillet reste valable : ne pas diagnostiquer au ping, le NAS ignore l'ICMP même tunnel actif. Test-NetConnection 10.0.0.2 -Port 445 est le seul témoin fiable. Point ouvert, reformulé Le peer 10.0.0.4 ( MkzKqDe…) est toujours libre et n'a jamais servi. Pour raccorder la session Ubuntu, lui générer sa propre paire de clés — ne pas recopier le .conf du poste Windows, une clé privée ne se partage pas entre deux machines. Piege du 2026-08-27 — un lecteur mappe en console administrateur reste invisible de l'explorateur Apres la restauration du tunnel, S: n'apparaissait nulle part dans l'explorateur, et le premier reflexe (« le VPN ne marche pas ») etait faux : le service tournait, le VPS enregistrait des handshakes et du trafic. Cause : Windows attribue deux jetons au compte administrateur — un filtre (normal) et un complet (eleve) — et les lecteurs reseau ne traversent pas cette frontiere. Les commandes d'installation ( winget, /installtunnelservice) exigeant l'elevation, tout avait ete tape dans la meme console admin : net use avait donc cree S: pour le seul jeton eleve. L'explorateur, qui tourne en jeton normal, ne pouvait pas le voir. Symptomes trompeurs : net use S: ... rejoue dans la console admin renvoie erreur systeme 85, « Nom de peripherique local deja utilise » — ce qui laisse croire que le lecteur existe bien le meme net use S: /delete en session normale repond « La connexion reseau est introuvable » — preuve que le lecteur n'y a jamais existe Correctif : rejouer le mappage dans une console non elevee (Win+R → powershell ; l'invite doit afficher C:\Users\julie et non C:\WINDOWS\system32). [Security.Principal.WindowsPrincipal]::new([Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('Administrators') # doit repondre False net use S: \10.0.0.2\NEXTE /persistent:yes Les identifiants poses par cmdkey sont, eux, communs aux deux jetons (stockes par utilisateur) : inutile de les refaire. Alternative permanente, si le cloisonnement gene : poser EnableLinkedConnections a 1, ce qui fait partager les lecteurs entre les deux jetons. Necessite un redemarrage. New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' -Name EnableLinkedConnections -PropertyType DWord -Value 1 -Force Second point de confusion, sans gravite : WireGuard n'affiche ni fenetre ni icone dans la zone de notification. C'est la consequence directe de /installtunnelservice, qui installe le tunnel comme service Windows — precisement ce qui garantit qu'il remonte au demarrage sans intervention. Pour le voir, lancer WireGuard depuis le menu Demarrer : le tunnel y figure, actif, avec ses compteurs. Regle a retenir : ne jamais diagnostiquer un tunnel a l'absence d'un lecteur dans l'explorateur. Les deux temoins fiables sont Test-NetConnection 10.0.0.2 -Port 445 cote poste, et sudo wg show cote VPS. 260827-Tofs à compresse sur le PC Windows On dépose des photos dans un dossier, on les récupère compressées dans un autre. Rien à lancer, rien à régler au coup par coup. C'est le principe de la procédure Komga-PDF appliqué aux images, mais entièrement sur le PC julie — le VPS n'intervient pas. Dépôt D:\Procedures photos\Tofs a compresse\ Résultat D:\Procedures photos\Tofs-compressed\ Moteur caesiumclt v1.4.0 — binaire Rust, aucune dépendance Script Jux-scripts/Photos-Caesium/compress_photos.py — stdlib seule Profil actif q80 — environ -62 %, sans redimensionnement Délai 43 secondes entre le dépôt et la livraison La seule chose à ne jamais toucher : l'option -e dans options_communes de config.json. Elle conserve l'EXIF, et ce n'est pas le comportement par défaut de Caesium. Sans elle, dates de prise de vue et coordonnées GPS sont purement supprimées — silencieusement. Ce n'est pas une précaution théorique : c'est déjà arrivé, le 14 juillet 2026, et 275 photos y ont laissé leur GPS définitivement. Au quotidien Déposez les photos dans Tofs a compresse\ et n'attendez rien d'autre : la tâche Photos-Caesium tourne en permanence et les prend d'elle-même. Les fichiers compressés apparaissent dans Tofs-compressed\ avec la même arborescence — un album déposé revient en album. 20:43:51 dépôt dans Tofs a compresse\ 20:44:32 déclenchement (après 20 s sans changement) 20:44:34 livraison dans Tofs-compressed\ Le délai tient au garde-fou : la surveillance attend 20 secondes sans le moindre changement avant de traiter, ce qui évite d'attraper un fichier en cours de copie. Si vous videz une carte SD de 300 photos, elle patiente jusqu'à la fin du transfert. Ce qui devient de vos originaux : ils partent dans originaux-traites\ — jamais effacés, c'est le dossier à vider de temps en temps ; un HEIC ou un RAW, que Caesium ne sait pas lire, est écarté dans non-traites\ plutôt que traité ; une photo qui ne gagnerait rien à être compressée est recopiée telle quelle en sortie : un fichier déposé ressort toujours. À la main Trois lanceurs à double-cliquer dans D:\Procedures photos\ : Lanceur Effet Compresser maintenant.cmd une passe, puis sortie Simuler le gain.cmd compresse en zone tampon, annonce le gain, jette le résultat Surveillance (fenetre visible).cmd la boucle, console à l'écran py -3.12 compress_photos.py --une-fois py -3.12 compress_photos.py --simulation py -3.12 compress_photos.py --profil sans-perte --une-fois py -3.12 compress_photos.py --profils py -3.12 compress_photos.py # surveillance continue La surveillance automatique Tâche planifiée Photos-Caesium (ONLOGON), en place depuis le 2026-08-27 : schtasks /Create /TN "Photos-Caesium" /TR '"C:\Users\julie\AppData\Local\Programs\Python\Python312\pythonw.exe" "D:\Syncthing\Jux_univers\Jux-scripts\Photos-Caesium\compress_photos.py"' /SC ONLOGON /RL LIMITED /F pythonw.exe et non python.exe, sans quoi une console s'ouvre à chaque ouverture de session. Vérification par schtasks /Query /TN "Photos-Caesium" /V /FO LIST ou taskschd.msc ; retrait par /Delete. Cette tâche doit être créée depuis la console de Julien, jamais depuis une session Claude Code. Une session Claude tourne dans un conteneur MSIX : la tâche figerait des chemins virtualisés et ne ferait rien, sans le moindre message. Voir la page 282. Profils de compression Définis dans config.json, modifiables sans toucher au code. --profil surcharge ponctuellement. Profil Options Gain sur photos de téléphone q80 (actif) -q 80 -59 à -72 % q90 -q 90 environ -45 % sans-perte --lossless -23 % web-3000px -q 80 --long-edge 3000 --no-upscale environ -85 % Options communes à tous les profils : -e --keep-dates --min-savings 1%. Le profil actif ne redimensionne pas. q80 ne touche ni aux dimensions ni au nombre d'étiquettes EXIF. web-3000px, qui ramènerait le grand côté à 3 000 pixels, figure dans config.json mais n'est pas sélectionné — à n'activer que sur demande explicite. « Sans altérer la qualité » coûte les deux tiers du gain C'était la demande initiale, et elle mérite d'être précisée : les deux modes de Caesium n'ont rien de comparable en rendement. --lossless ne touche pas un seul pixel. Il reconstruit les tables de Huffman et réoptimise le conteneur. Sur 71 photos réelles : -23 %. -q 80 ré-encode l'image. La différence est invisible à l'œil sur une photographie, et c'est le réglage par défaut de l'interface graphique Caesium — celui qui donne l'impression que « Caesium compresse admirablement bien ». Sur les mêmes fichiers : -64 %. D'où q80 par défaut. Pour des originaux irremplaçables, basculer sur sans-perte dans config.json, ou traiter ce lot-là avec --profil sans-perte. Ce qui est garanti : EXIF, GPS, dates, dimensions Vérifié deux fois, par la mesure et non par relecture du code. Contrôle du 2026-09-28 — la chaîne réelle, de bout en bout Trois photos du Xiaomi 15T Pro, dont deux géolocalisées, déposées sans rien lancer : c'est la surveillance qui les a prises, comme pour un dépôt ordinaire. Contrôle Résultat Bloc EXIF identique à l'octet près — 52 334 / 52 740 / 52 782 octets Coordonnées GPS 43.675676, 4.634059 avant et après, au chiffre près Date de prise de vue identique à la seconde Étiquettes EXIF 72 → 72 (58 → 58 pour la photo sans GPS) Dimensions 4096×3072 inchangées Date de fichier ( mtime) identique — --keep-dates tient Poids 13,1 Mo → 5,1 Mo, -61 % Les 72 étiquettes conservées couvrent tout ce que l'appareil écrit : marque, modèle, objectif, ouverture, temps de pose, ISO, orientation, balance des blancs, et le bloc GPS complet. Rien n'est aplati ni simplifié — Caesium ne réécrit que les données d'image. Mesure de référence du 2026-08-27 71 photos de téléphone (lot « Ligurie 2026 »), Ryzen 5 2600X, --threads 0 : Taille Durée Originaux 251,1 Mo — q80 90,4 Mo (-64,0 %) 22 s sans-perte -22,8 % — Soit environ 12 Mo/s de débit : un vidage de carte SD de 8 Go se traite en une dizaine de minutes. Le contrôle EXIF de ce jour-là portait sur trois fichiers et donnait déjà un bloc EXIF identique à l'octet (1 819 / 52 665 / 52 597 octets), IFD GPS présent, dimensions inchangées. L'outil de contrôle Jux-scripts/Photos-Caesium/comparer_exif.py — stdlib seule. Prend deux dossiers et compare fichier par fichier : bloc EXIF octet par octet, GPS converti en degrés décimaux, DateTimeOriginal, appareil, dimensions, nombre d'étiquettes, mtime, poids. py -3.12 comparer_exif.py Il rend un verdict global, et se prête au contrôle de sanité : lancé avec le même dossier des deux côtés, il doit tout déclarer conforme. À rejouer après toute modification de profil, de config.json ou du binaire. ⚠ L'incident du 2026-07-14 — le -e manquant Une passe antérieure à la mise en place de cette procédure a été lancée sans -e. Dégâts constatés le 2026-08-28, sur les photos de l'été 2026 : 275 fichiers dépouillés de tout bloc EXIF — date et GPS. Signature nette : 493 Ko de moyenne pour les fichiers traités, contre 1 103 Ko pour les 76 intacts du même voyage. Conséquence différée, invisible sur le moment. Sans DateTimeOriginal, Immich se rabat sur la date du fichier. Or le WebDAV kDrive ne préserve pas les mtime : à la première resynchronisation, six semaines plus tard, les 275 photos sont allées se ranger au jour de la synchro et la chronologie du voyage s'est effondrée. Le GPS est définitivement perdu. Seule la date a pu être reconstruite, depuis le nom des fichiers, par dater_photos_nas.py. Récit complet : page 164. À faire avant toute passe de compression : vérifier -e dans la ligne de commande, et contrôler l'EXIF d'un fichier de sortie avant de traiter le lot. Un fichier de sortie sans bloc EXIF doit faire arrêter la passe immédiatement — les originaux ne sont pas toujours récupérables. PC et NAS : le même moteur, une garantie de plus sur le NAS Le stock photo du NAS suit sa propre procédure, en quatre étapes avec validation humaine (page 285). Le moteur, lui, est rigoureusement le même. PC ( compress_photos.py) NAS ( traiter_lot_nas.py) Binaire caesiumclt 1.4.0, identique Profil -q 80, identique Options communes -e --keep-dates --min-savings 1%, identiques Date et propriétaire réimposés puis revérifiés non oui ( os.utime, os.chown, puis relecture) La différence ne porte pas sur l'EXIF, protégé à l'identique par -e des deux côtés. Elle porte sur la date de fichier et le propriétaire, pour une raison propre au NAS : les photos y appartiennent à admin:users, et une compression lancée sans privilèges les réattribuerait toutes en cassant SMB et Synology Photos. Sur le PC il n'y a qu'un utilisateur, et la mesure montre que --keep-dates suffit. Formats Pris en charge : JPEG, PNG, WebP, GIF, TIFF. Non pris en charge : HEIC (iPhone) et RAW. Ces fichiers partent dans non-traites\ — jamais supprimés — avec une ligne au journal. Si le besoin HEIC se présente, il faudra une étape de conversion en amont : Caesium ne sait pas le lire. Arborescence D:\Procedures photos\ ├── Tofs a compresse\ ← dépôt (sous-dossiers acceptés) ├── Tofs-compressed\ ← résultat, même arborescence ├── originaux-traites\ ← originaux après traitement ├── non-traites\ ← formats non pris en charge ├── echecs\ ← abandons après 3 tentatives ├── bin\caesiumclt.exe ├── logs\photos_compress.log ├── config.json ├── Compresser maintenant.cmd ├── Simuler le gain.cmd └── Surveillance (fenetre visible).cmd Le script, lui, vit dans Jux-scripts/Photos-Caesium/, donc dans l'arbre Syncthing : versionné et disponible sur toutes les machines. Seules les données restent locales. Pourquoi les dossiers sont hors de Syncthing Ils avaient d'abord été créés dans D:\Syncthing\, puis déplacés. Ne pas les y remettre. Il n'existe qu'un seul dossier Syncthing dans tout le maillage ( afltj-njyuy), partagé avec 7 appareils dont le Xiaomi et la tablette SM-T720. Tout ce qui y tombe se réplique partout. C'est supportable pour Komga-PDF — un PDF de 130 Mo de temps en temps — mais un vidage de carte SD descendrait intégralement sur les téléphones, avant même que la compression ait commencé. Conséquence assumée : le dépôt à distance n'est pas possible, on dépose depuis le PC. Maintenance Vider originaux-traites\ de temps en temps — les originaux y sont conservés, pas effacés. C'est le dossier qui grossit. Surveiller non-traites\ — s'il se remplit, des HEIC ou des RAW arrivent dans le flux. echecs\ doit rester vide. Toute entrée signale un fichier corrompu ou un cas non prévu : consulter le journal. Journal : logs\photos_compress.log, rotation automatique à 5 Mo. Reste à faire : décider du sort du lot de démonstration laissé dans Tofs-compressed\Ligurie 2026\ (71 photos, 90 Mo), qui sert à juger la qualité q80 à l'œil. Annexe — fonctionnement interne Sondage toutes les 15 s du dossier de dépôt. Il n'y a pas d'inotify sous Windows ; le sondage est de toute façon la partie robuste du dispositif. Déclenchement après 20 s sans le moindre changement dans l'arborescence — taille ou date d'un fichier quelconque. Plafond de sécurité à 15 min, au-delà duquel on traite quand même. Le lot entier part en un seul appel à caesiumclt, qui parallélise sur tous les cœurs ( --threads 0). Écriture d'abord dans .staging, puis déplacement : jamais de fichier partiel visible dans Tofs-compressed\. Structure préservée ( -R -S). Collision de nom en sortie : suffixe (2), (3)… Rien n'est jamais écrasé. Gain inférieur à 1 % ( --min-savings 1%) : Caesium n'écrit rien, le script recopie l'original tel quel. Mémoire des échecs : 3 tentatives (clé taille + date, dans .etat.json), puis mise à l'écart dans echecs\. Journal persistant avec rotation à 5 Mo. Les points 2, 4, 7, 8 et 9 reprennent d'emblée les correctifs apportés à komga_watch_vps.sh le 2026-08-23 (page 220) : événements perdus pendant le traitement, écriture non atomique, absence de mémoire des échecs, journal volatil. Autant ne pas refaire les mêmes erreurs. config.json, clé apres_traitement : deplacer (défaut), supprimer, ou laisser. laisser fait retraiter le dépôt en boucle à chaque passe — à réserver aux tests. Annexe — pièges rencontrés à l'installation --dry-run de Caesium ne mesure rien. L'option existe et s'exécute sans erreur, mais le rapport JSON renvoie compressed_size == original_size, soit 0,0 % de gain : elle simule l'écriture, pas la compression. Le mode --simulation du script compresse donc réellement dans .staging, relève le chiffre, puis jette le résultat. -e n'est pas le défaut — perte silencieuse des dates et du GPS. Voir l'incident ci-dessus. --json est la bonne interface de pilotage. Caesium renvoie un rapport structuré, une entrée par fichier ( original_path, compressed_size, status…) plus un summary. Le script s'appuie dessus plutôt que de parser la sortie texte. --min-savings rend inutile le garde-fou maison. Sur Komga-PDF il avait fallu écrire à la main la règle « si la compression ne gagne rien, recopier l'original » ; Caesium l'implémente nativement. Créer la tâche planifiée depuis Claude Code : à éviter (piège MSIX). La création avait d'ailleurs été refusée par le garde-fou de session — ce qui tombait bien. Le binaire ne s'installe pas. caesiumclt.exe est un exécutable Rust statique de 5,7 Mo, sans dépendance ni installeur : on le dépose dans bin\ et on le met à jour en le remplaçant. Annexe — le binaire Version caesiumclt v1.4.0, publiée le 8 juillet 2026 Source github.com/Lymphatus/caesium-clt/releases Archive caesiumclt-v1.4.0-x86_64-pc-windows-msvc.zip — 2 296 760 octets SHA-256 a56454a83207fc25830f4d12679d7385c0906060f2ce5f54345ac5a4cf94b00f Licence GPL / MPL selon les composants ( bin\LICENSE.md) Le même dépôt publie caesiumclt-v1.4.0-x86_64-unknown-linux-musl.tar.gz, statique — c'est lui qui tourne sur le NAS, sans Docker ni Entware. Annexe — pourquoi 100 % local, contrairement à Komga-PDF Komga-PDF passe par le VPS. Photos-Caesium non, et c'est délibéré. Trois raisons, par ordre d'importance : Il n'y a pas de service à réutiliser. Komga-PDF s'appuie sur StirlingPDF, déjà déployé et qui expose compress-pdf en HTTP. Pour les images, aucun équivalent : Caesium est un binaire en ligne de commande, pas un service. L'héberger sur le VPS n'apporterait pas une API, juste un exécutable — qu'on peut aussi bien poser sur le PC. Le CPU. La compression JPEG est du calcul pur, massivement parallélisable. Le Ryzen 5 2600X offre 12 threads et ne fait généralement rien ; le VPS fait tourner une trentaine de conteneurs, avec un swap déjà occupé et un antécédent de saturation disque à 100 %. Le transfert. Les photos sont déjà sur place. Les envoyer au VPS pour les faire revenir consommerait deux fois le débit montant de la Livebox sans contrepartie. Le seul cas qui justifierait le VPS serait de pouvoir déposer depuis le téléphone pendant que le PC est éteint — c'est précisément ce qui avait fait choisir le VPS pour Komga-PDF. Le portage serait trivial (binaire linux-musl statique, script en stdlib seule). À reconsidérer si le besoin apparaît. Voir aussi Page 285 — les photos du NAS : procédure en quatre étapes avec validation humaine Page 220 — Komga-PDF, dont celle-ci reprend le principe et les correctifs Page 164 — Immich : réparation des dates après l'incident du -e manquant Page 282 — le piège MSIX de Claude Code sur le poste julie Copie de référence de cette page : Jux-scripts/Photos-Caesium/doc_bookstack_284.md. Mise en place le 2026-08-27, page réécrite intégralement le 2026-09-28. 260828-Tofs à compresse sur SASNEXTE Compression des photos du NAS SASNEXTE Procédure en service depuis le 2026-08-28, première bascule réelle le 2026-09-28. Même moteur que la procédure Photos-Caesium du PC (page 284), mais avec une validation humaine avant tout écrasement. Copie de référence : Jux-scripts/Photos-Caesium/doc_bookstack_285.md. En bref Les photos du NAS sont déjà à leur place dans /volume1/photo : on ne dépose rien. La procédure les prend là où elles sont, les compresse dans un dossier d'attente, et n'écrase l'original que ce que vous avez validé. Ce qui déclenche rien — c'est vous qui choisissez un lot Où valider photos_reprise dans Synology Photos Comment refuser supprimer la photo du dossier de validation Moteur caesiumclt v1.4.0 musl, natif sur DSM Gain constaté -50 à -65 % selon les albums Débit sur le DS220+ 1,55 à 1,75 Mo/s Le refus ne demande aucune action. Une photo absente du dossier de validation au moment de la bascule voit son original conservé, sans rien à cocher ni saisir. L'inaction est le comportement sûr : un lot abandonné en route, une session interrompue, un oubli — rien ne se perd par négligence, seulement par décision explicite. La procédure, en quatre commandes 1. Voir les lots disponibles python3 /volume1/homes/SAS_NEXTE/scripts/traiter_lot_nas.py --lister Affiche chaque sous-dossier avec son nombre de photos à traiter et son poids, du plus lourd au plus léger. C'est ce qu'on passe ensuite à --lot. 2. Compresser un lot — les originaux ne sont pas touchés python3 /volume1/homes/SAS_NEXTE/scripts/traiter_lot_nas.py --lot "2026/2026-Automne" --lot accepte un préfixe d'album ou de chemin : 2022 prend toute l'année, 2026/2026-Automne un seul sous-album. Le préfixe suffit, inutile de taper les accents. Les photos compressées arrivent dans /volume1/homes/SAS_NEXTE/Photos/photos_reprise, en reproduisant l'arborescence. 3. Valider dans Synology Photos Ouvrez photos_reprise dans l'application, en plein écran, et supprimez les photos dont la qualité ne convient pas. Ce qui reste vaut validation. 4. Basculer python3 /volume1/homes/SAS_NEXTE/scripts/basculer_lot_nas.py --lot "2026/2026-Automne" sudo python3 /volume1/homes/SAS_NEXTE/scripts/basculer_lot_nas.py --lot "2026/2026-Automne" --go La première ligne est une simulation : elle annonce combien de photos seront remplacées et combien sont refusées. La seconde écrase pour de vrai. ⚠ Le sudo n'est pas décoratif. Les photos appartiennent à admin:users. Lancée sous SAS_NEXTE, la bascule les réattribuerait toutes et casserait l'accès SMB et Synology Photos. Le script refuse de s'exécuter sans root ( geteuid), avec un message explicite. La règle : on ne compresse pas une photo de moins de 2 Mo Aucun bénéfice à recompresser une numérisation ancienne de 700 Ko pour lui gagner 80 Ko, au prix de la réécriture d'un fichier irremplaçable. Seuil Fichiers à traiter Poids couvert Gain estimé Durée 1 Mo 12 253 (74,6 %) 95,6 % 21,6 Go 6,6 h 2 Mo 9 077 (55,3 %) 82,4 % 18,6 Go 5,7 h 3 Mo 4 839 (29,5 %) 55,4 % 12,5 Go 3,8 h À 2 Mo on écarte 45 % des fichiers pour seulement 18 % du poids. Descendre à 1 Mo ajouterait 3 Go de gain contre 3 176 fichiers supplémentaires ; monter à 3 Mo sacrifierait 6 Go pour n'économiser que deux heures. Effet secondaire heureux : les albums de numérisations anciennes sortent d'eux-mêmes du périmètre. 1967 à 2000 - photos famille Bertrand (536 images, 2,62 Go) et 2005 - naissance Elio (869 images) ont zéro fichier à traiter. La règle protège le patrimoine le plus fragile sans qu'on ait eu à le désigner. Ce qui est garanti EXIF, GPS, dimensions Vérifié sur les fichiers du NAS puis, après la bascule du 2026-09-28, sur les photos réellement écrasées : bloc EXIF identique à l'octet près, IFD GPS présent et coordonnées au chiffre près, dimensions inchangées, jeu complet d'étiquettes (72 sur un Xiaomi 15T Pro, 54 et 45 sur des photos plus anciennes). Cela dépend entièrement de l'option -e, qui n'est pas le défaut de Caesium — voir l'incident du 14 juillet en page 284, qui a coûté le GPS de 275 photos. La date de modification, et pourquoi elle compte Immich construit sa chronologie à partir du DateTimeOriginal EXIF quand il existe, et du mtime du fichier quand il n'existe pas — cas des images WhatsApp, des captures d'écran et des scans. 1 853 photos, soit 10,5 % du stock, sont dans ce cas (colonne origine_date de la base). Les deux scripts ne se reposent donc pas sur --keep-dates : ils relèvent mtime, atime, uid, gid et permissions avant traitement, les réimposent par os.utime / os.chown / os.chmod, puis relisent le fichier et lèvent une exception si le mtime a bougé d'une seconde. Assertion vérifiée fichier par fichier. Preuve : 26 photos témoins, dates identiques à la seconde, propriétaire restauré, originaux intacts. Le contrôle intégré a d'ailleurs détecté 26 écarts lors d'un essai en non-privilégié, contre 0 en root — c'est lui qui a établi l'obligation du sudo. Ce que la procédure apporte face à une compression au fil de l'eau Auditable : chaque décision est tracée dans une base interrogeable, pas dispersée dans un journal. Réversible tant que la bascule n'a pas eu lieu : l'original reste intact pendant toute la validation. Interruptible : la procédure est faite pour s'arrêter entre deux lots et reprendre des jours plus tard. Le refus est passif, donc sûr par construction. Où en est le chantier Inventaire du 2026-09-28 : 17 715 images, 41,41 Go, dont 9 014 à retraiter (30,80 Go) et 8 701 écartées (10,61 Go). 417 vidéos (21,22 Go) sont mises de côté pour un chantier ultérieur. Lots basculés Lot Photos Avant Après Gain 2022 (4 albums) 359 1 501 Mo 518 Mo 983 Mo 2026/2026-Automne 22 (+1 refusée) 94,05 Mo 46,44 Mo 47,6 Mo Total 381 1 031 Mo Reste à faire 8 632 photos, 29,25 Go encore en a_traiter — le gros du gisement 1,75 Go de doublons à arbitrer à la main (252 photos renommées + 1 018 inter-albums) Décider du sort des 624 PNG (2,95 Go) : le sans-perte y rend environ -28 % sans aucune altération Trancher le remplacement des versions dégradées par les versions « (conflit) », qui sont les meilleures (voir l'annexe) Faire aboutir l'authentification par clé SSH, pour cesser de passer le mot de passe La base est actualisée chaque nuit Une photo ajoutée après le dernier inventaire est invisible de traiter_lot_nas.py, qui travaille à partir de la base. C'est exactement ce qui s'est produit le 2026-09-28 avec l'album 2026-Automne à Noel, créé après l'inventaire du 28 août : il a fallu reconstruire la base avant de pouvoir le traiter. D'où une tâche nocturne, posée le 2026-09-28. Script /volume1/homes/SAS_NEXTE/scripts/inventaire_nuit.sh Tâche DSM quotidienne, 2 h, utilisateur SAS_NEXTE Commande bash /volume1/homes/SAS_NEXTE/scripts/inventaire_nuit.sh Journal /volume1/homes/SAS_NEXTE/logs/inventaire_nuit.log, rotation à 2 Mo Durée 9 min 20 pour 17 715 images Utilisateur SAS_NEXTE, surtout pas root : l'inventaire ne fait que lire /volume1/photo, et en root les fichiers produits appartiendraient à root — un lancement manuel ultérieur échouerait alors à les écraser. Trois protections, parce que personne ne regarde tourner une tâche de nuit Verrou par PID — un inventaire qui déborde ne se fait pas doubler. Le test porte sur le PID enregistré ( kill -0) et non sur ps, dont la sortie est tronquée sur DSM. La base n'est plus supprimée avant reconstruction. Elle est bâtie dans photos.sqlite.tmp puis remplacée à la fin par os.replace, après sauvegarde rotative sur 7 jours ( photos_j1.sqlite à photos_j7.sqlite). Vérifié : un plantage en cours de route laisse la base et le suivi de validation intacts, sans .tmp résiduel. ⚠⚠ Refus d'écrire sur source vide. Zéro image trouvée = refus et code 2. Un soir où /volume1/photo ne serait pas monté, un inventaire vide écraserait sinon la base — et avec elle le suivi des lots en cours de validation. Même principe que le refus de source vide de sync_kdrive_complete.sh. Le canari du matin la surveille Entrée inventaire dans la section nas de Etat-Infra/config.json ( max_heures: 30, attendu BILAN NUIT: OK). La sonde est greffée sur la session SSH que la vérification NAS ouvre déjà — aucune connexion supplémentaire. Elle alerte si le journal est absent, vieux de plus de 30 h, ou si le dernier bilan est en échec ; muette le reste du temps. L'inventaire conserve le suivi des lots lors d'une reconstruction. Statuts, lots, tailles obtenues et dates de traitement sont reportés depuis la base précédente pour toute photo dont le chemin n'a pas changé. Sans cela, le passage nocturne effacerait chaque nuit l'avancement des lots en validation. Annexe — la base et les statuts Sorties dans /volume1/homes/SAS_NEXTE/inventaire/, accessibles en SMB sous \\10.0.0.2\home\inventaire\ : photos.csv (tableur ou Metabase), photos.sqlite (SQL), videos.csv, resume.txt. Colonnes de la table photos : album, chemin, nom, format, octets, mo, date_prise_vue, origine_date, date_modif, largeur, hauteur, appareil, gps_lat, gps_lon, a_traiter, motif_ecart, empreinte, doublon_de, quasi_doublon, statut, lot, octets_apres, date_traitement. ⚠ Les chemins sont relatifs à /volume1/photo ( 2022-hiver à Paques/IMG….jpg), et l'album est le premier segment du chemin : toutes les photos de /volume1/photo/2026// portent donc l'album 2026. C'est pourquoi --lot accepte aussi un chemin. Statut Signification a_traiter retenue par le seuil, pas encore compressée ecarte sous le seuil, ou format non pris en charge en_validation compressée, en attente du jugement dans Synology Photos bascule validée et écrasée — octets porte la nouvelle taille refuse supprimée du dossier de validation : original conservé sans_gain la compression n'apportait rien, original conservé Requêtes utiles : -- ce qui sera traité, par album SELECT album, count(*) AS n, round(sum(mo)/1024, 2) AS go FROM photos WHERE a_traiter = 1 GROUP BY album ORDER BY go DESC; -- où en sont les lots SELECT lot, statut, count(*) FROM photos WHERE lot IS NOT NULL AND lot != '' GROUP BY lot, statut; -- les photos dont la chronologie Immich dépend du mtime SELECT album, count(*) FROM photos WHERE origine_date = 'mtime' GROUP BY album; -- exclure un album entier du traitement UPDATE photos SET a_traiter = 0, motif_ecart = 'exclu manuellement' WHERE album = '...'; La chaîne d'outils Six scripts dans Jux-scripts/Photos-Caesium/, stdlib seule, déployés sur le NAS dans /volume1/homes/SAS_NEXTE/scripts/. Script Étape Rôle inventaire_photos_nas.py 1 recense tout, détecte les doublons, propose la sélection inventaire_nuit.sh 1 lanceur nocturne : verrou, journal, bilan pour le canari nettoyer_doublons_nas.py 1 bis écarte les doublons certains, en quarantaine traiter_lot_nas.py 3 compresse un lot vers le dossier de validation basculer_lot_nas.py 4 écrase les originaux validés, constate les refus comparer_versions.py — compare la qualité de deux versions d'une même photo (DQT) Garde-fous intégrés : traiter_lot_nas.py appelle caesiumclt sans -R, dossier par dossier — aucun @eaDir ne peut être touché, sans avoir besoin d'option d'exclusion. Liste de fichiers explicite limitée aux JPEG : passer un dossier ferait traiter les PNG et TIFF en lossy. Sortie validée ( FF D8, taille inférieure) avant dépôt. Budget --minutes, reprise en relançant la même commande. Annexe — les correctifs du 2026-09-28 La chaîne n'avait jamais été jouée jusqu'à la bascule. Six défauts, tous découverts en conditions réelles, tous dormant depuis le 28 août. ⚠⚠ os.replace() ne traverse pas deux partages Synology. Errno 18, Invalid cross-device link — 359 échecs d'un coup. Chaque partage DSM est un sous-volume Btrfs distinct : /volume1/homes et /volume1/photo sont deux devices pour le noyau, malgré le /volume1 commun. remplacer() copie désormais vers un tampon .nouveau créé à côté de l'original (même sous-volume), y pose permissions, propriétaire et dates, puis os.replace — atomique. Le fichier de validation n'est supprimé qu'après succès et vérification du mtime. Aucun dégât : le try/except a tenu, les 359 originaux et la base sont restés intacts. ⚠ Le bilan annonçait un succès après un échec total : « 983,47 Mo récupérés » s'affichait sous « 0 original remplacé, 359 échecs », parce qu'il additionnait le gain théorique du lot. Un bilan qui ment ainsi est pire qu'une absence de bilan. Le gain est désormais compté photo par photo au moment du succès, et tout échec déclenche une ligne disant explicitement que les originaux sont inchangés. ⚠ --lot ne savait viser qu'une année entière : les cinq sous-albums de 2026 étaient fondus dans un lot 2026 de 115 photos. Le filtre porte maintenant sur l'album ou le chemin, et --lister groupe sur les deux premiers segments. Rétrocompatible. ⚠ --lister exigeait --lot, alors qu'il sert précisément à découvrir quoi passer à --lot. ⚠ Les messages de fin donnaient des commandes inutilisables — python3 basculer_lot_nas.py … sans chemin, sans sudo, sans --go, d'où un « No such file » depuis le répertoire personnel. Chemins absolus et trois étapes numérotées désormais. ⚠ purger_vides() ne supprimait jamais rien : Synology dépose un @eaDir de vignettes dans chaque dossier, compté comme du contenu par os.listdir(). photos_reprise gardait donc l'apparence de lots encore en attente de validation. @eaDir et Thumbs.db sont maintenant ignorés, puis emportés avec le dossier. Annexe — les doublons L'inventaire a trouvé 1 334 doublons exacts (1,93 Go) et 1 278 quasi-doublons. Méthode : SHA-256 intégral mais calculé uniquement entre fichiers de taille identique — 2 360 candidats sur 17 835, ce qui évite de hacher 41 Go. Les quasi-doublons sont repérés par DateTimeOriginal + dimensions identiques. Sur les 1 334, seuls 75 ont été écartés (190 Mo, en quarantaine dans /volume1/homes/SAS_NEXTE/doublons_ecartes, jamais effacés directement). ⚠ Un doublon inter-albums n'est pas une erreur, c'est souvent une intention 1930-2016 - Photos BERTRAND MER et 2016-best photos pour 70 ans partagent 516 fichiers identiques : c'est une sélection faite pour un anniversaire. Les supprimer aurait vidé l'album. Ne jamais dédoublonner sur la seule empreinte — 1 018 fichiers laissés intacts pour cette raison. Le critère de conservation : la relation de préfixe Un suffixe de copie est toujours ajouté au nom d'origine. Si un nom du groupe est préfixe strict de tous les autres, c'est l'original. Structurel, sans heuristique. Couvre _1, - Copie, et les cascades de conflits Syncthing (un fichier existait en 7 exemplaires). Deux pièges à ne pas refaire : _\d+$ est un mauvais marqueur de copie — il prend les numéros d'appareil pour des suffixes ( IMG_0322, 20190830_221854). Il ne donnait le bon résultat que par accident. Retiré. Le suffixe Windows français est « - Copie » avec des espaces autour du tiret : [-_](copie|copy)$ ne l'attrape pas. Motif correct : \s*[-_]\s*(copie|copy)(\s*\(?\d+\)?)?\s*$. Mode prudent par défaut (décision de Julien : « la règle la plus prudente, tant pis s'il reste des doublons ») : un groupe sans relation de préfixe n'est pas traité. 264 groupes de photos renommées ( 20150808_185538.jpg ↔ Croatie_août_2015_142.jpg, 0,60 Go) laissés en l'état — choisir le nom qui survit est une décision humaine. Seconde passe --conflits : 24 fichiers de plus. Le nommage Google Drive français Copie de P1060905 (conflit du 19-03-2018 à 00h18).jpg encadre l'original au lieu de le suffixer, donc le critère de préfixe ne pouvait pas le voir. Garde-fou : on n'écarte un conflit que s'il subsiste un exemplaire propre dans le groupe — 6 fichiers laissés en place car tous les exemplaires de leur groupe étaient des copies de conflit. Annexe — ⚠⚠ les fichiers « (conflit) » sont les ORIGINAUX Contre-intuitif, et vérifié : sur ce stock, un Copie de X (conflit du …).jpg est la meilleure version, et le fichier au nom propre portant le même radical est une recompression dégradée. Mesuré sur 12 paires par la table de quantification JPEG (DQT — somme basse = moins dégradé) : 10 cas sur 12 donnent la version conflit gagnante, avec un DQT de 292 contre 700 à 1 000, une taille 2 à 3 fois supérieure et un bloc EXIF trois fois plus riche, à dimensions rigoureusement identiques. Un événement de mars 2018 a réécrit ces photos en qualité réduite. La consigne « supprimer tout ce qui porte (conflit) » aurait détruit la meilleure version de 10 photos. Ce qui les a sauvées : le garde-fou « n'écarter un conflit que s'il subsiste un exemplaire propre dans le groupe » — et il les a écartées pour une raison qui n'était pas la bonne (leurs octets diffèrent, elles n'étaient donc pas des doublons exacts). Un garde-fou conservateur protège aussi contre les erreurs qu'on n'avait pas prévues. Décision en attente : remplacer les versions dégradées par les versions de conflit et leur rendre leur nom. Outils prêts, jamais exécutés en écriture : comparer_versions.py (comparaison DQT) et renommer_conflits.py (retire l'encadrement, ne renomme que si le nom cible est libre). Annexe — pièges SSH et DSM Une bannière SSH qui répond ne prouve pas que l'accès soit ouvert. Le port 22 renvoyait SSH-2.0-OpenSSH_8.2, annonçait publickey,password et évaluait les identifiants avant de les rejeter — ce qui a conduit à soupçonner à tort le groupe administrators, puis la vérification en deux étapes. Les deux hypothèses étaient fausses : le service était désactivé et le port fermé par les règles DSM. Le compte Unix est SAS_NEXTE en majuscules (uid 1026), alors qu'on s'y connecte en sas_nexte. DSM est insensible à la casse à l'ouverture de session, mais le dossier personnel et le propriétaire des fichiers portent la forme majuscule. Le § du mot de passe ne survit pas à Git Bash → plink. Le construire côté cible en octets explicites : printf 'xAPIJU5108\xc2\xa7' > /tmp/pw sshpass -f /tmp/pw ssh -o NumberOfPasswordPrompts=1 sas_nexte@10.0.0.2 -o NumberOfPasswordPrompts=1 est important : sans lui, un échec compte pour trois tentatives auprès du blocage automatique de DSM. Autres pièges : scp échoue ( dest open: No such file or directory) — DSM n'expose pas de sous-système SFTP. Transférer par un tube : ssh nas 'cat > cible' < source. /tmp est monté noexec : un binaire y est marqué exécutable mais refuse de démarrer. Les exécutables vivent sur /volume1. ps w tronque sa sortie sur DSM : un grep dessus a répondu 0 alors que le processus tournait, ce qui a conduit à lancer une seconde copie sur la même destination. Tester la vivacité par ps -eo pid,sid,args, ou par PID. nohup ne suffit pas à détacher un script lancé par SSH : utiliser setsid script.sh < /dev/null > /dev/null 2>&1 &. Authentification par clé non aboutie : clé publique déposée dans ~/.ssh/authorized_keys (intacte, vérifiée), toujours refusée — probablement StrictModes, qui rejette un dossier personnel accessible en écriture au groupe, ce que Synology crée par défaut. Clé privée en D:\SSH\nas_sasnexte, hors arbre Syncthing. Annexe — Immich, ce qu'il faut savoir d'ici Immich lit /home/debian/photo sur le VPS — le miroir kDrive de /volume1/photo — et ignore les dossiers personnels : le dossier de validation ne l'affecte donc pas. Bibliothèque externe 89bfcdc1-1604-47a1-90a3-975d2f6c4639, chemin /mnt/media/photo. Relecture après traitement : resynchronisation puis POST /api/libraries/{id}/scan. Il n'existe pas de MCP Immich dans ces sessions (perdu avec le profil eliob). Passer par l'API : POST /api/auth/login → JWT, puis POST /api/search/metadata ( withExif: true, accepte isOffline, originalFileName, takenAfter/ takenBefore, pagine par nextPage) et GET /api/assets/statistics. ⚠ GET /api/timeline/buckets?size=MONTH est le seul moyen fiable d'obtenir des totaux : le champ total de la recherche ne renvoie que le compte de la page courante. Bibliothèque au 2026-08-28 : 18 156 éléments, dont 2 datés de l'an 4501 — des scans du Nikon LS-5000 dont le fichier portait une date aberrante, corrigés depuis à 1979. Récit complet : page 164. Annexe — la machine et le stock Modèle Synology DS220+ Processeur Intel Celeron J4025 @ 2,00 GHz — 2 cœurs, pas d'HyperThreading Mémoire 5,8 Go Volume btrfs, 5,3 To, 1,4 To libres Binaire /volume1/homes/SAS_NEXTE/bin/caesiumclt — v1.4.0, x86_64-unknown-linux-musl Le binaire musl est statique et sans aucune dépendance : il s'exécute nativement sur DSM, sans Docker ni Entware. Composition de /volume1/photo au relevé initial : Contenu Fichiers Poids Traité ? JPEG 16 422 37,59 Go oui — la cible MP4 + MOV 413 21,11 Go non PNG 624 2,95 Go oui, en sans-perte HEIC 691 0,79 Go non pris en charge TIFF, PSD, divers ~157 0,76 Go TIFF oui, PSD non @eaDir — vignettes Synology 94 791 15,36 Go à ne jamais toucher ⚠ Attention au recensement naïf : un find … -iname '*.jpg' sans exclure @eaDir compte 71 802 fichiers au lieu de 16 422, parce qu'il ramasse les vignettes. Un échantillon constitué ainsi fausse gravement la taille moyenne. Annexe — pourquoi le NAS et pas le PC Le PC est sept fois plus rapide (11,4 Mo/s contre 1,55 sur le J4025), mais le tunnel WireGuard est en étoile via le VPS et plafonne à 4,01 Mo/s. Faire passer 53 Go de lecture et de réécriture y prendrait plus de quatre heures en saturant le lien, PC allumé. Le NAS travaille la nuit, gratuitement. Cet arbitrage vaut pour le stock complet. Sur un lot de quelques dizaines de mégaoctets, le calcul s'inverse — mais la procédure de validation, elle, reste la même. Points de vigilance permanents 1. La sauvegarde. sync_kdrive_complete.sh ne synchronise que /volume1/photo/2026 vers kDrive, et en miroir ( rclone sync --delete-during). Les JPEG de 1967 à 2025 n'ont aucune copie hors du NAS par cette voie. Une archive Hyper Backup ( NEXTE_1.hbk) tourne chaque nuit, mais son périmètre exact n'a pas été confirmé. À établir avant toute réécriture de masse. 2. La compression sur place est irréversible. Le profil q80 est un ré-encodage. C'est toute la raison d'être de l'étape de validation. 3. Ne jamais toucher aux @eaDir. Les compresser casserait Synology Photos, qui les régénérerait aussitôt. 4. Synology Photos indexe le dossier de validation et génère ses vignettes : comptez du temps machine et quelques gigaoctets de @eaDir supplémentaires, restitués dès le dossier vidé. Hyper Backup le sauvegardera aussi la nuit suivante — gonflement temporaire, résorbé après la bascule. 5. HEIC et vidéos restent hors de portée. Pour les vidéos, le levier serait un ré-encodage H.265 : un tout autre chantier, très au-delà d'un J4025. Voir aussi Page 284 — la procédure Photos-Caesium sur le PC, dont celle-ci reprend le moteur et les réglages Page 164 — Immich : réparation des dates après l'incident du -e manquant Page 245 — le tunnel WireGuard qui donne accès au NAS Page 238 — la synchronisation NAS → kDrive 260830 - Basculement des videos de SASNEXTE vers kDrive 260830 - Basculement des vidéos de SASNEXTE vers kDrive Objectif : libérer 694 Go sur le NAS sasnexte en déplaçant /volume1/video vers kDrive, en répliquant l'arborescence à l'identique. Jellyfin lira ensuite depuis kDrive — procédure 2, documentée sur la page 165 (14_Jellyfin). Statut au 2026-08-30 : étapes 1 à 5 terminées. Les 694 Go sont sur kDrive, vérifiés, et Jellyfin les lit déjà. Seule l'étape 6 reste — la suppression sur le NAS — volontairement en attente. Mesures du 2026-08-30 Le dossier video df est trompeur : ses 4,0 To sont le volume /volume1 entier, pas le partage vidéo. Dossier Taille movies 163,1 Go cinema_italie 111,0 Go fantastique_et_SF 84,4 Go series 83,6 Go documentaires 60,6 Go cinema_realisateurs 47,1 Go culture_cuisine 31,7 Go animes 27,7 Go cinema_europe 24,2 Go historiques 21,7 Go cinema_asie 12,0 Go spectacles 11,6 Go palettes 8,9 Go cinema_UK 7,3 Go alladomanda 2,0 Go #recycle (corbeille DSM, non transférée) 10,5 Go Total transféré 694,55 Go — 4 044 fichiers Seulement ~423 vidéos réelles (266 mkv, 94 mp4, 54 avi, 7 vob). Le reste est de l'habillage Jellyfin : 2 989 jpg (vignettes trickplay), 408 nfo, 170 png, 40 srt. Autres partages du volume, pour situer video 707 Go · Komga 552 Go · audiobooks 374 Go · music 133 Go · photo 84 Go · homes 36 Go. Le solde des 4,0 To est constitué d' @ActiveBackup, @docker et des snapshots. Débits mesurés Trajet Débit NAS vers kDrive (rclone WebDAV, 1 flux) 44 Mo/s NAS vers kDrive (3 transferts parallèles) 62 à 78 Mio/s kDrive vers VPS 97,6 Mo/s kDrive vers NAS 89,5 Mo/s NAS vers VPS aujourd'hui (CIFS via WireGuard) 5,0 Mo/s Disque local NAS (lecture) 49 à 58 Mo/s Conséquence contre-intuitive : la bascule améliore la lecture au lieu de la dégrader. Jellyfin plafonne aujourd'hui à 5,0 Mo/s (40 Mbit/s) : suffisant pour un 1080p courant, juste pour un remux, et incompatible avec deux flux simultanés. Via kDrive, le VPS descend à 780 Mbit/s. Le goulot était le tunnel WireGuard depuis Toulon, pas kDrive. Espace kDrive Capacité 6,60 To · utilisé 4,21 To · libre 2,39 To. Après bascule : environ 1,69 To restants. Renouvellement de l'abonnement le 2026-09-20. Le dossier video n'avait AUCUNE sauvegarde Vérifié le 2026-08-30 : aucune des 4 tâches Hyper Backup ( 2509NEXTE, Toojux_Home, Alteris, Bckup_Vero_photos) ne mentionne video, pas de snapshot ( #snapshot absent), et il ne fait pas partie des 4 dossiers de sync_kdrive_complete.sh. Le déplacement ne fait donc pas passer de deux copies à une. Il déplace l'unique copie d'un DS220+ à deux baies sans sauvegarde vers une infrastructure redondée avec corbeille. C'est une amélioration, pas une prise de risque. Les deux risques réellement nouveaux : un rclone sync mal dirigé côté kDrive effacerait tout — le renommage du 2026-08-28 avait déjà fait voir 308 suppressions à rclone ; l'abonnement kDrive se renouvelle le 20/09. Procédure # Étape Durée Réversible 0 Analyse de doublons NAS contre Videos_NAS 10 min oui 1 rclone copy — fait, 08:35 → 11:03 (2 h 28) 2 h 28 oui 2 rclone check --download — fait, 11:05 → 13:21, 0 différence sur 4 056 fichiers 2 h 16 oui 3 à 5 Bascule Jellyfin — faite, voir page 165 30 min oui 6 Suppression sur le NAS : 694 Go libérés, volume de 75 % à ~62 % — EN ATTENTE 5 min non Script : /volume1/homes/SAS_NEXTE/scripts/basculement_video.sh (source de vérité dans Jux-scripts/). Journal : /volume1/homes/SAS_NEXTE/logs/basculement_video_260830.log. Verrou : /volume1/homes/SAS_NEXTE/logs/basculement_video.pid. rclone --config /volume1/homes/SAS_NEXTE/.config/rclone/rclone.conf copy \ /volume1/video kdrive_music:Videos_NAS/video \ --exclude '/#recycle/**' --exclude '**/@eaDir/**' \ --exclude '**/.DS_Store' --exclude '**/Thumbs.db' \ --create-empty-src-dirs \ --transfers 3 --checkers 8 --retries 3 --low-level-retries 10 \ --stats 5m --stats-one-line -v copy, jamais sync : la source n'est jamais modifiée par le script. La suppression est un geste manuel séparé, en étape 6. Étape 2 — pourquoi la vérification par téléchargement est obligatoire Le WebDAV kDrive n'expose aucun hash ( rclone backend features kdrive: donne Hashes: []). Après transfert, rclone ne peut comparer que les tailles. Pour un déplacement qui se termine par une suppression de la source, c'est insuffisant : il faut rclone check --download, qui retélécharge tout et compare les empreintes calculées à la volée. 694 Go à 89,5 Mo/s = environ 2 h 10. Non négociable avant l'étape 6. Étape 0 — résultat de l'analyse de doublons Videos_NAS (racine kDrive) pèse 246,7 Go : Video Culture Arts et Peinture 130,80 · Video Cuisine 83,10 · Video Architecture et Urbanisme 29,38 · Video concerts 3,46. culture_cuisine et Video Cuisine ne se recoupent pas du tout. Le NAS porte Chef's Table et Man vs Food, kDrive porte Rick Stein, Jamie Oliver, Entre les Bras. Zéro collision de nom. Seuls 2 vrais doublons, 1,60 Go, tous deux contre Videos_NAS et non à l'intérieur du périmètre transféré : palettes/De Cimabue a Giotto, les premiers maitres italiens.mp4 (0,87 Go) contre Video Culture Arts et Peinture/ documentaires/Justice - a Cross Universe/....avi (0,73 Go) contre Video concerts/ Décision : ils ont été transférés quand même. Ces deux fichiers appartiennent à des bibliothèques Jellyfin distinctes ; 1,6 Go ne justifiait pas de casser la réplication. Emplacement final : Private/Videos_NAS/video/ La destination initiale SYNC-pour_VPS/Sync-SASNEXTE/video était une erreur de conception, corrigée le jour même sur remarque de Julien. Cet espace est celui des copies miroir du NAS : quatre dossiers que sync_kdrive_complete.sh synchronise chaque jour en rclone sync --delete-during. Or video n'est pas une copie — après l'étape 6, c'est l'original, lu directement par Jellyfin. Le nom promettait une synchronisation qui n'existe pas, et surtout il tendait un piège : le jour où quelqu'un ajoute video à ce script — geste naturel vu l'emplacement — un sync depuis une source NAS vidée effacerait les 694 Go. Le déplacement a pris 2 secondes : rclone moveto, Server side directory move succeeded, aucun octet retransféré. Le remote kDrive annonce DirMove: True — toute réorganisation de l'arborescence kDrive est donc gratuite, quelle que soit la volumétrie. Deux fusions décidées dans la foulée Source Destination Volume video/series Videos_NAS/Séries (qui était vide) 399 objets, 77,8 Gio video/culture_cuisine Videos_NAS/Video Cuisine (77,4 Gio existants) 286 objets, 29,6 Gio Vérification de non-collision faite avant : Séries était vide, et les deux ensembles cuisine n'avaient aucun nom commun au premier niveau ( Chef's table, Man VS Food d'un côté ; France, India, Globe cooker, Cuisines des Terroirs de l'autre). 126 s en tout, côté serveur. Les comptes tombent juste : 4 056 − 399 − 286 = 3 371 objets restants dans video/, et 77,393 + 29,566 = 106,959 Gio dans Video Cuisine. Arborescence finale Private/Videos_NAS/ 878,7 Gio, 4 781 objets <- monté sur /mnt/nas_videos ├── Séries/ 77,8 Gio -> /data/series ├── Video Cuisine/ 107,0 Gio -> /data/culture_cuisine ├── Video Culture Arts et Peinture/ 130,8 Gio (aucune bibliothèque) ├── Video Architecture et Urbanisme/ 29,4 Gio (aucune bibliothèque) ├── Video concerts/ 3,5 Gio (aucune bibliothèque) └── video/ 541,6 Gio, 3 371 objets ├── movies, cinema_italie, fantastique_et_SF, documentaires, ├── cinema_realisateurs, animes, cinema_europe, historiques, └── cinema_asie, spectacles, palettes, cinema_UK, alladomanda ⚠ video/@eaDir subsiste (11 objets) : l'exclusion **/@eaDir/** écarte le contenu du dossier, pas le dossier lui-même, et --create-empty-src-dirs l'a recréé. Sans conséquence, mais à ne pas confondre avec un oubli. Pièges rencontrés — à ne pas re-découvrir ps w tronque sa sortie sur DSM. Un test de vivacité ps w | grep -c a renvoyé 0 alors que le processus tournait — ce qui a conduit à lancer une seconde copie sur la même destination. Les deux ont été arrêtées par SIGTERM, sans corruption (rclone compare les tailles et réécrit tout fichier tronqué). Toujours utiliser ps -eo pid,sid,args. nohup ne suffit pas à détacher sur DSM. Utiliser setsid script.sh < /dev/null > /dev/null 2>&1 &. Un verrou par PID a été ajouté au script pour rendre le double-lancement impossible. Faux doublons .VOB de 1,07 Go. Les segments DVD-Video sont tous plafonnés à 1 073 739 776 octets : la taille identique ne prouve rien. Cinq faux positifs relevés entre un Scola du NAS et un DVD Architectures de kDrive. Même leçon que le dédoublonnage photo du 2026-08-28 — une signature faible ne fait pas un doublon. Le quota-used-bytes WebDAV de kDrive est peu fiable : il renvoie 0 pour certains dossiers pourtant pleins (il donnait 102,6 Go pour Videos_NAS, dont la taille réelle est 246,7 Go). Passer par rclone size, ou par l'API du drive pour l'espace global. df sur un partage Synology rapporte le volume entier, pas le partage. Toujours confirmer par du -sb. find -iname '*.jpg' sans exclure @eaDir fausse tout recensement (rappel de la page 285). Pièges d'accès SSH au NAS (rappel page 285) Le compte Unix est SAS_NEXTE en majuscules (uid 1026), la connexion se fait en sas_nexte. Le caractère § du mot de passe ne survit pas à Git Bash puis plink. Le construire sur le VPS avec printf et les octets \xc2\xa7, puis sshpass -f /tmp/pw ssh -o NumberOfPasswordPrompts=1 sas_nexte@10.0.0.2. Sans NumberOfPasswordPrompts=1, un échec compte pour 3 auprès du blocage automatique DSM. scp échoue (pas de sous-système SFTP) — transférer par ssh nas 'cat > cible' < source, ou passer par base64 -d. /tmp est monté noexec : les exécutables vont sur /volume1. 260901-reprise des règles du pare-feu de SASNEXTE .....en profondeur !  Durcissement du NAS : de 28 ports exposés à 8 Constat de départ, mesuré depuis le VPS (hors LAN, hors VPN) : tous les ports en écoute du NAS répondaient depuis Internet, y compris NFS, SMB, FTP et le démon rsync. La méthode qui a marché Plutôt que de raisonner sur les règles du pare-feu, mesurer la surface réelle : lister les ports en écoute côté NAS, puis tester chacun depuis une machine extérieure. L'écart entre ce qu'on croit fermé et ce qui répond est la seule donnée qui compte. Trois pièges rencontrés 1. L'ordre des règles. Le pare-feu Synology applique la première règle qui correspond, puis s'arrête. Une règle « Service terminal chiffré → Tous → Autoriser » placée en tête annulait complètement les règles « port 22 → Refuser » situées plus bas. Toute autorisation doit précéder le refus qu'elle contourne ; une règle placée après un refus qui la couvre est du décor. 2. Les libellés tronqués. « Interface de Gestion, File Station, Audio Statio… » masquait une longue liste d'applications — dont FTP et VPN Server. C'est ce qui expliquait qu'un port reste ouvert alors que la règle qui le concernait avait été supprimée. Toujours dérouler la liste complète derrière les points de suspension. 3. Éteindre un service est plus robuste que le filtrer. NFS a résisté à toutes les manipulations de règles ; il s'est fermé instantanément une fois le service désactivé. Un service éteint ne dépend d'aucune règle, d'aucun ordre, d'aucun profil actif. Résultat Fermés : SMB (445/139), NFS (2049, 111, 662, 892, 4045), FTP (21), rsync daemon (873), Note Station, File Station, WS-Discovery et une dizaine d'autres. Restent : 22 (voulu — les deux VPS), 80/443/6690 (filtrés par pays), 5108/5109 (DSM), 5510 et 6281 (en cours d'identification). Deux subtilités utiles Le pare-feu Synology ne filtre que l'entrant. Le NAS va chercher les fichiers sur une seedbox en FTP : c'est une connexion sortante, soumise à aucune règle. Le port 21 du NAS n'y jouait aucun rôle et a pu être fermé sans rien casser. Bien distinguer « la machine se connecte » de « on se connecte à la machine » avant d'écrire une règle. Le VPN rend la restriction indolore. En passant par le tunnel, le trafic arrive avec l'adresse source du VPN (10.8.0.0/24) et non l'IP publique du poste. Autoriser ce sous-réseau suffit, depuis n'importe où. Corollaire : le sous-réseau du VPN doit être explicitement autorisé sur le port 22 — il n'est couvert ni par 192.168.0.0/16 ni par 172.16.0.0/12. Sans cette règle, activer le pare-feu coupe l'accès SSH par le tunnel. Point vérifié au passage : le VPN est OpenVPN (paquet VPNCenter, UDP 1194), et non WireGuard comme supposé. Testé par un vrai handshake — le serveur répond. Erreurs commises pendant l'intervention Consignées parce qu'elles sont instructives : Une image supprimée à tort (page « Calcul Patterns Route Commune »), faute d'avoir compris que les pages pointent parfois sur l'URL scaled-. Restaurée depuis l'archive. Diagnostic erroné : « vos suppressions de règles ne prennent pas effet ». La vraie cause était la règle englobante au libellé tronqué. Test sur les mauvais ports : DSM déclaré « non exposé » après un test sur 5000/5001, alors qu'il écoute sur 5108/5109 — ports personnalisés. Accès DSM supposé indisponible sans l'avoir vérifié. L'accès SMB au partage administrateur homes existait, et a permis de lire authorized_keys. Les deux garde-fous qui ont évité des dégâts : l'archive systématique avant suppression, et la vérification après chaque action plutôt que la confiance dans l'effet attendu. fail2ban sur le VPS juxjux (23 septembre 2026) Même sujet, autre machine. Le durcissement ci-dessus portait sur le NAS ; ce volet concerne le VPS juxjux.ovh, dont la porte SSH était restée battante. Le constat. Le fichier /var/log/btmp, qui enregistre les tentatives de connexion échouées, pesait 251 Mo. Il ne s'agissait pas d'un problème de journaux : 364 743 tentatives depuis le 1er septembre, le compte root visé 163 300 fois, admin 13 219, ubuntu 12 568. Une seule adresse en comptait 87 745 — c'est elle qui gonflait la moyenne à 17 000 par jour, alors que le rythme de fond est d'environ 3 000. Purger ce fichier aurait fait disparaître la mesure, pas les attaques : il est le thermomètre, pas la fièvre. Le piège qui aurait rendu l'installation inutile. Sur ce Debian 12, /var/log/auth.log n'existe pas — les connexions ne sont écrites que dans journald. Avec sa configuration par défaut, fail2ban surveille ce fichier absent. Ici l'échec a été franc : le service est tombé en failed dès l'installation, sur ERROR Have not found any log file for sshd jail. Le vrai danger était la variante silencieuse — une jail qui démarre, affiche « 0 banni », et laisse conclure que les attaques ont cessé. La ligne backend = systemd règle le problème, et le contrôle à faire est double : Journal matches: _SYSTEMD_UNIT=sshd.service dans le statut de la jail, et un compteur de bannis non nul. La même leçon que le VPN du NAS, sur une autre infrastructure. La question de la liste blanche est celle qui pouvait coûter le plus cher : une erreur ici ferme la porte de l'extérieur, y compris pour nous. Les adresses publiques semblaient l'évidence, mais l'historique des connexions du VPS montrait des accès depuis six adresses 92.184.113.x différentes et une adresse italienne — des connexions en partage 4G et en vacances, pas la box de la maison. La réponse solide est ailleurs : le VPS est le hub du maillage WireGuard, et une connexion par le tunnel lui arrive avec l'adresse source 10.0.0.3, privée et immuable, indépendante de tout opérateur. C'est exactement ce qui avait été constaté sur le NAS avec son OpenVPN en 10.8.0.0/24. Deux machines, deux VPN différents, la même conclusion : autoriser le sous-réseau du tunnel vaut mieux que courir après des adresses publiques. Vérifié avant d'armer la jail, pas après : ssh debian@10.0.0.1 fonctionne et le serveur voit bien 10.0.0.3. La liste blanche retenue compte donc trois filets, du plus sûr au moins sûr : 10.0.0.0/24 (le tunnel, qui couvre aussi le NAS en 10.0.0.2), puis 83.195.45.93 pour la maison et 82.66.244.248 pour le bureau. Nuance utile entre ces deux dernières : l'adresse du bureau est fixe par construction — Free en attribue une à chaque Freebox, et celle-ci n'a pas bougé en quatre ans — tandis qu'Orange ne garantit rien en offre grand public. C'est aussi pourquoi le point de contact WireGuard du NAS n'a jamais eu besoin d'être retouché. Ce que fail2ban n'est pas. La crainte naturelle — « et si mon PC ou le NAS tombe en panne ? » — repose sur un malentendu qu'il vaut la peine de lever : ce n'est pas une liste d'accès. Le port 22 reste ouvert à tous ; fail2ban compte les échecs et ne ferme qu'après cinq ratés en dix minutes. Depuis n'importe quelle machine et n'importe quelle adresse, une connexion avec le bon mot de passe passe du premier coup. La liste blanche ne sert qu'à une chose : éviter de se bannir soi-même en se trompant plusieurs fois. Et un bannissement dure une heure, pas davantage. Restent, en dernier recours, le tunnel puis la console KVM de l'hébergeur, qui ne dépend ni du réseau ni de SSH. Réglages et résultat. Jail sshd, cinq échecs tolérés sur dix minutes, bannissement d'une heure, configuration dans /etc/fail2ban/jail.local (jamais jail.conf, écrasé aux mises à jour). L'action de bannissement passe par ufw plutôt que par une chaîne iptables parallèle, puisque le pare-feu est déjà en place : les blocages apparaissent bien en tête de ufw status numbered, avant la règle qui autorise le port 22. Dans les secondes suivant le démarrage : 131 échecs détectés, trois adresses bannies. Une erreur commise au passage, dans l'esprit de la section précédente : la sauvegarde de l'ancienne configuration avait été déposée dans /etc/logrotate.d/. Or logrotate lit tous les fichiers de ce dossier, sans considération d'extension — d'où un duplicate log entry for /var/log/btmp et un fichier entier ignoré. Une sauvegarde ne se range jamais dans un dossier que lit un service ; elle a été déplacée vers /root/logrotate-sauvegardes/. Ce qui reste ouvert Sur la sauvegarde Destination unique : si le NAS refuse, plus rien ne sort du VPS. Aucune rétention sur le VPS (staging vidé après transfert). Restauration jamais testée — une sauvegarde non restaurée n'est pas une sauvegarde. Disque VPS à 87 % (9,3 Go libres). HyperBackup non surveillé : toute la profondeur d'historique en dépend. Sur le NAS Identifier et fermer 5510 et 6281. DSM (5108/5109) reste accessible depuis Internet — si maintenu, activer 2FA et le blocage automatique. Inverser le sens du transfert : que le NAS tire depuis le VPS, ce qui permettrait de fermer SSH sur le NAS définitivement plutôt que provisoirement. Le compte synobackup existe déjà pour cela. Sur le VPS (ajouté le 23/09/2026) Passer à une clé SSH plutôt qu'un mot de passe : cela supprimerait à la fois le risque de se bannir sur une faute de frappe et tout intérêt aux 3 000 tentatives quotidiennes. Retirer la règle du pare-feu qui ouvre le port 2222, derrière lequel plus rien n'écoute. Et adosser au canari du matin une sonde sur la jail, pour que son extinction ne passe pas inaperçue. 260903-Capture du texte sur applications Android vers Joplin Récupérer le texte affiché par une application Android — un article de presse, typiquement — et le déposer dans Joplin ou dans le coffre Obsidian, depuis le PC. Mis en place le 2026-09-03 sur le poste julie. Reproductible sur un autre PC, la procédure d'installation figure plus bas. Le principe Le script appelle uiautomator dump, un outil livré avec Android, qui exporte l'arbre d'accessibilité de l'écran en XML. On en tire les attributs text et content-desc. Ce n'est PAS de la reconnaissance optique. On récupère le texte réel de l'application, avec ses accents, sa ponctuation et sa casse exacts. Aucune erreur de lecture possible, contrairement à un OCR sur capture d'écran. Rien n'est installé sur la tablette. Tout passe par adb, déjà présent avec le SDK Android. Ce qu'il faut sur le PC Élément Rôle Python 3.12 le script n'utilise que la bibliothèque standard — rien à installer en plus adb fourni par platform-tools du SDK Android Un appareil joignable l'émulateur emulator-5554, ou un vrai téléphone en débogage USB Le script est dans l'arbre Syncthing, donc déjà présent sur toutes les machines : Jux-scripts/Android-Texte/capturer_texte.py Jux-scripts/Android-Texte/README.md Usage # ecran courant, affiche dans la console py -3.12 capturer_texte.py # vers un fichier / le presse-papiers py -3.12 capturer_texte.py -o article.txt py -3.12 capturer_texte.py --presse-papiers # vers Joplin ou Obsidian py -3.12 capturer_texte.py --joplin py -3.12 capturer_texte.py --obsidian Option Défaut Rôle -o, --fichier — fichier UTF-8 --presse-papiers — copie dans le presse-papiers Windows --joplin — crée une note Joplin --carnet Captures Android carnet Joplin, créé s'il manque --obsidian — dépose un .md dans le coffre --coffre D:\Syncthing\Jux_Obsidian racine du coffre --dossier Captures Android sous-dossier du coffre --titre deviné titre de la note --defiler — fait défiler et accumule --serie emulator-5554 identifiant adb --adb — chemin d' adb.exe --tri-position — trier par coordonnées (voir pièges) Codes de sortie : 0 succès, 1 erreur adb, 2 échec Joplin, 3 échec Obsidian. Les deux destinations Obsidian — la plus simple Écrit un .md dans \Captures Android\, avec l'en-tête YAML des notes existantes : --- title: ... created: 2026-09-03 04:54:54Z updated: 2026-09-03 04:54:54Z source: capture Android --- Ni jeton, ni application ouverte, ni service à activer. Le coffre étant dans Syncthing, la note part sur tous les appareils par la synchronisation habituelle. Les noms de fichier sont assainis pour Windows et un suffixe (2), (3)… évite d'écraser. Joplin — via le Web Clipper, en local Mise en place, une seule fois par PC : Joplin → Outils → Options → Web Clipper → Activer le service Copier le jeton affiché dans D:\Android\joplin_token.txt La variable d'environnement JOPLIN_TOKEN est prioritaire si elle existe. ⚠ Le jeton doit être HORS de l'arbre Syncthing. Déposé par erreur dans D:\Syncthing\Jux_univers\Mes Notepads\ le 2026-09-03, il s'est répliqué sur les sept appareils du maillage avant d'être déplacé. Le risque reste modéré — l'API n'écoute que sur 127.0.0.1 du PC, détenir le jeton ailleurs ne donne aucun accès distant — mais la régénération est immédiate depuis les options de Joplin. Même principe que /home/debian/.komga_scan.env côté VPS. Pourquoi pas le Joplin Server du VPS joplin.juxjux.ovh est un serveur de synchronisation, pas une API de notes : les items y sont sérialisés pour la synchro, on ne peut pas y créer une note proprement. La seule voie praticable est l'API locale du client de bureau, sur http://127.0.0.1:41184. Corollaire : Joplin doit être ouvert sur le PC au moment de la capture. Les lanceurs du bureau Deux fichiers .cmd sur le bureau : Capturer vers Joplin et Capturer vers Obsidian. Ils vérifient la présence de Python et du script, écrivent une copie du texte dans %TEMP%\derniere_capture.txt, consignent les erreurs dans D:\Android\capture_texte.log et affichent les dernières lignes du journal en cas d'échec. Pourquoi des .cmd et pas des raccourcis .lnk : un .lnk créé depuis Claude Code vers une cible sous %LOCALAPPDATA% fige un chemin virtualisé et ne fait rien du tout (voir page 282, section 6). Un .cmd est du texte brut : ses chemins sont littéraux et fonctionnent depuis l'explorateur. Pièges rencontrés ⚠ 1. L'ordre du document, pas les coordonnées Une première version triait les fragments par coordonnées bounds. Faux : dès qu'une partie du contenu est hors écran — le cas courant avec une WebView — ses bounds deviennent aberrants et les paragraphes ressortent mélangés. L'ordre de l'arbre XML est l'ordre du document, donc l'ordre de lecture. C'est le comportement par défaut. --tri-position rétablit l'ancien, pour de rares interfaces natives dont l'arbre ne suit pas la mise en page. ⚠ 2. --defiler est souvent inutile Une WebView expose l'intégralité du document chargé à l'accessibilité, pas seulement la partie visible. Un article de presse entier est capturé en un seul appel. --defiler ne sert vraiment que pour les listes à chargement progressif — fil d'actualité, boîte de réception — où le contenu n'existe pas tant qu'on n'y est pas descendu. ⚠ 3. Le titre est deviné, et pas toujours trouvable Sans --titre, le script prend la plus longue des huit premières lignes, entre 30 et 120 caractères, en écartant dates, heures, signatures ( Par X), Mis à jour, Lire aussi. Trois versions ont été nécessaires : Règle essayée Résultat obtenu première ligne substantielle « Éditos & Analyses » — la rubrique la plus longue des premières « Le 01/09/2026 à 06h45 | Mis à jour… » — la date + filtre, plancher à 20 caractères « trottinettes spatiales » — une bribe de phrase + plancher à 30 caractères correct, ou repli sur la date Il faut capturer depuis le HAUT de l'article. Une fois qu'on a défilé loin, l'en-tête sort de l'arbre et aucun titre n'est trouvable : la note prend alors la date ( Capture Android du 03-09-2026 a 07h18). C'est délibéré — mieux vaut une date qu'un titre faux. Sinon, --titre "...". ⚠ 4. cmd clipboard n'existe pas Sur les images « Google Play », adb shell cmd clipboard répond No shell command implementation. D'où le passage par clip.exe côté Windows, en UTF-16LE. ⚠ 5. Ne pas faire défiler la tablette par adb à l'aveugle Sept input swipe enchaînés pour « remonter en haut » ont fait sortir l'application Les Échos de l'article et atterrir sur un écran de recherche. Les gestes longs déclenchent des navigations imprévues. Laisser l'utilisateur positionner la vue lui-même. ⚠ 6. Vérifier Joplin sur TOUTES les pages GET /folders est paginé. Avec 106 carnets, une vérification limitée à la première page conclut à tort que le carnet n'existe pas. Le script, lui, suit has_more correctement. # faux : ne lit que la page 1 Invoke-RestMethod "http://127.0.0.1:41184/folders?token=$j" Installer sur un autre PC Python 3.12 — winget install --id Python.Python.3.12 --scope user platform-tools — via le SDK Manager d'Android Studio, ou l'archive seule de Google Adapter ADB_DEFAUT en tête du script, ou passer --adb Pour Obsidian : adapter COFFRE_DEFAUT, ou passer --coffre Pour Joplin : activer le Web Clipper et déposer le jeton hors de Syncthing Recopier les deux .cmd du bureau en corrigeant les chemins en tête de fichier Le script lui-même n'a rien à installer : stdlib seule. Limites Le texte dessiné en image n'apparaît pas : infographies, PDF rastérisés, certains lecteurs. Il faudrait de l'OCR — tesseract est déjà disponible dans StirlingPDF sur le VPS si le besoin se présente. Une application déclarant ses vues importantForAccessibility="no" reste muette. Les fenêtres FLAG_SECURE (applications bancaires, vidéo protégée) ne sont pas exportées. Vérifié le 2026-09-03 capture d'un article des Échos : 6 563 caractères, texte exact, ordre correct note Joplin créée dans un carnet auto-créé, contenu vérifié côté serveur note Obsidian déposée avec l'en-tête YAML conforme au coffre 4 tests unitaires sur le choix du titre Si ça ne marche pas Le journal D:\\Android\\capture_texte.log est le point d'entrée : les lanceurs y écrivent la sortie d'erreur et en affichent les dernières lignes en cas d'échec. Une exécution saine ressemble à ceci : ===== 03/09/2026 7:42:55 - Joplin ===== ecran 1 : 29 nouveaux fragments 29 fragments -> C:\\Users\\julie\\AppData\\Local\\Temp\\derniere_capture.txt Joplin : note creee dans « Captures Android » — e4e99c3f... Échec transitoire observé le 2026-09-03 à 07:23. La capture avait réussi — fichier de secours de 6,7 Ko écrit, article complet — mais aucune note n'avait été créée. Rejoué à l'identique quelques minutes plus tard : succès, sans qu'aucune modification n'ait été faite entre-temps. Cause non établie. Deux hypothèses restent ouvertes : Joplin indisponible ou en cours de synchronisation à cet instant, ou un problème d'encodage sur la sortie d'erreur sous console .cmd. Une piste a été écartée par la mesure : le chemin Python codé en dur dans les .cmd pointe sous %LOCALAPPDATA%, mais l'installation y est bien réelle — le chemin conteneur …\\Packages\\Claude_pzs8sxrjxfjjc\\LocalCache\\Local\\Programs\\Python\\… n'existe pas. C'est d'ailleurs le test à retenir pour trancher ce type de doute (page 282, section 6). C'est pour cette raison que les lanceurs journalisent : si le cas se reproduit, le message sera conservé. En cas d'échec, le texte n'est jamais perdu : il reste dans %TEMP%\\derniere_capture.txt. Contrôles rapides # la tablette repond-elle ? & 'D:\Android\Sdk\platform-tools\adb.exe' devices # le service Web Clipper est-il actif ? Invoke-WebRequest 'http://127.0.0.1:41184/ping' -UseBasicParsing # -> JoplinClipperServer # le jeton est-il en place ? Test-Path 'D:\Android\joplin_token.txt' Voir aussi Page 282 — l'émulateur Android, son installation et ses pièges Jux-scripts/Android-Texte/README.md — documentation technique du script 260904-conversion des fichiers FLAC en MP3 avec Tags vérifiés Conversion des fichiers FLAC en MP3, avec tags vérifiés Mise en place le 2026-09-04 sur le poste Windows julie. Script versionné dans Jux-scripts/Musique-FLAC/, documentation de référence : le README.md du même dossier. On dépose des FLAC dans un dossier, on les récupère encodés en MP3 dans un autre, sans rien lancer à la main. C'est la troisième procédure bâtie sur ce moule, après Komga-PDF (compression de PDF) et Photos-Caesium (compression de photos), dont elle reprend les garde-fous éprouvés : écriture par fichier provisoire, mémoire des échecs, journal persistant, attente d'une période de calme avant de toucher au dépôt. Sa particularité : elle ne fait pas confiance au convertisseur. Les tags sont lus dans le FLAC par le script lui-même, réécrits explicitement, puis relus dans le MP3 produit et comparés champ par champ. Un MP3 auquel il manque un tag n'est pas livré. Pourquoi localement, et pourquoi ffmpeg Le VPS n'intervient pas. L'encodage MP3 est du calcul pur : le Ryzen 5 2600X (12 fils) est très largement supérieur au VPS pour cet usage, et les fichiers sont déjà sur le poste. Aucun aller-retour réseau, aucune API à maintenir — même raisonnement que pour Photos-Caesium. Aucun encodeur n'était présent sur ce poste : ni ffmpeg, ni lame, ni flac, et Python n'a pas Emplacements Rôle Chemin Dépôt D:\Musique\FLAC à encoder\ Résultat D:\Musique\MP3 fait\ Originaux après conversion D:\Musique\FLAC traités\ Échecs (après 3 tentatives) D:\Musique\echecs\ Binaire D:\Musique\bin\ffmpeg.exe Configuration D:\Musique\config.json Journal D:\Musique\logs\flac_mp3.log Mémoire des traitements D:\Musique\etat.json Script Jux-scripts\Musique-FLAC\flac_mp3.py (synchronisé, donc versionné) Les dossiers de travail sont volontairement hors de l'arbre Syncthing, pour la même raison que Photos-Caesium : il n'existe qu'un seul dossier Syncthing dans tout le maillage ( afltj-njyuy), partagé avec 7 appareils. Un album FLAC de 400 Mo déposé dedans descendrait intégralement sur le Xiaomi et la SM-T720. L'arborescence du dépôt est reproduite à l'identique en sortie : on dépose le dossier de l'album, on récupère le dossier de l'album. Usage Trois lanceurs à double-cliquer dans D:\Musique\ : Convertir maintenant.cmd — une passe puis sortie Simuler (lire les tags).cmd — n'encode rien, annonce les tags lus dans chaque FLAC Surveillance (fenetre visible).cmd — boucle de surveillance avec la console à l'écran En ligne de commande : py -3.12 flac_mp3.py --une-fois py -3.12 flac_mp3.py --simulation py -3.12 flac_mp3.py --profil 320 --une-fois py -3.12 flac_mp3.py --profils py -3.12 flac_mp3.py --verifier "D:\Musique\MP3 fait\album\01 - titre.mp3" py -3.12 flac_mp3.py --oublier-echecs py -3.12 flac_mp3.py # surveillance continue --verifier relit les tags d'un MP3 déjà produit et les affiche : c'est l'outil de contrôle à la main, indépendant de toute conversion. Profils de qualité Définis dans config.json, V0 par défaut. Profil Options LAME Débit moyen V0 -q:a 0 ~245 kbps VBR — transparent, réglage de référence V2 -q:a 2 ~190 kbps VBR — nettement plus léger 320 -b:a 320k 320 kbps CBR — compatibilité matérielle maximale 256 / 192 -b:a … débits fixes intermédiaires Ce que fait le script, dans l'ordre Attend que le dépôt soit calme. Sondage toutes les 15 s ; le traitement ne démarre qu'après 20 s sans le moindre changement dans l'arborescence (nombre de fichiers, volume total, date la plus récente). C'est ce qui évite d'attraper un fichier en cours de copie. Plafond de 15 min, après quoi le traitement est forcé. Lit le FLAC lui-même : bloc STREAMINFO (durée, fréquence, canaux) et bloc VORBIS_COMMENT (tous les tags), présence d'un bloc PICTURE. Refuse d'emblée un FLAC dépourvu de title, artist ou album — liste réglable par tags_obligatoires. Encode en écrivant chaque tag explicitement (voir le piège n°1 plus bas), pochette comprise, vers un fichier .staging-… et non directement vers la destination. Relit les trames ID3v2 du MP3 produit et les compare aux tags attendus. Recompare la durée du MP3 à celle du FLAC (voir le piège n°3 — c'est le contrôle le plus utile de la chaîne). Ne publie le MP3 que si tout passe. Le fichier provisoire est renommé en place (opération atomique) ; en cas d'échec il est supprimé et rien n'apparaît en sortie. Déplace l'original dans FLAC traités\. Les fichiers d'accompagnement ( cover.jpg, .cue, .log, .m3u…) suivent l'album ; les images sont en plus copiées en sortie. Les dossiers vidés sont élagués, de sorte que le dépôt se vide de lui-même. En cas d'échec, le fichier est retenté 3 fois (clé taille + date de modification, mémorisée dans etat.json), puis écarté dans echecs\ accompagné d'un .erreur.txt qui dit pourquoi. Correspondance des tags Les tags Vorbis du FLAC sont traduits en trames ID3v2 standard : Vorbis ID3v2 Remarque TITLE TIT2 ARTIST TPE1 ALBUM TALB ALBUMARTIST / ALBUM ARTIST TPE2 DATE / YEAR TYER (v2.3) ou TDRC (v2.4) voir piège n°2 TRACKNUMBER + TRACKTOTAL TRCK = 3/12 recomposé, voir piège n°1 DISCNUMBER + DISCTOTAL TPOS = 1/2 recomposé GENRE TCON COMPOSER TCOM ORGANIZATION / LABEL / PUBLISHER TPUB COPYRIGHT TCOP ISRC TSRC BPM TBPM COMMENT / DESCRIPTION COMM LYRICS / UNSYNCEDLYRICS USLT bloc PICTURE APIC pochette tout le reste TXXX: REPLAYGAIN_*, MUSICBRAINZ_*… conservés tels quels Aucun tag n'est perdu : ce qui n'a pas de trame dédiée part en TXXX sous son propre nom, et c'est relu tel quel à la vérification. Pièges — à ne pas re-découvrir 1. ffmpeg -map_metadata 0 perd les totaux de piste et de disque. C'est la raison d'être de la réécriture explicite ( -map_metadata -1 puis un -metadata par champ). Mesuré sur le même FLAC source : ffmpeg nu ce script piste track 3 + TXXX:tracktotal 12 TRCK 3/12 disque disc 1 + TXXX:disctotal 2 TPOS 1/2 éditeur TXXX:organization TPUB Un lecteur qui affiche « piste 3 sur 12 » lit TRCK, pas un TXXX non standard. La conversion naïve produit donc des albums qui s'affichent mal sans que rien ne le signale. 2. ID3v2.3 ne sait pas stocker une date complète. Sa trame TYER ne contient que l'année : un DATE=2024-03-17 devient 2024. Ce n'est pas une perte de tag mais une limite du format — le script le classe en écart toléré et ne bloque pas. "id3v2_version": 4 dans config.json conserve la date entière (trame TDRC), au prix de la compatibilité avec les vieux lecteurs matériels. La vérification fonctionne dans les deux cas. v2.3 est le défaut retenu. 3. ⚠ Un FLAC tronqué se convertit sans la moindre erreur. Vérifié : sur un FLAC coupé au tiers, ffmpeg sort en code 0 et produit un MP3 parfaitement valide… de 0,27 s au lieu de 2,00 s. Ni le code de retour, ni la validité du MP3, ni les tags ne trahissent quoi que ce soit — les tags sont même intacts, puisqu'ils vivent en tête de fichier. Seule la comparaison des durées l'attrape, d'où verifier_duree activé par défaut. C'est le contrôle le plus utile de toute la chaîne, et le seul qui protège d'une livraison silencieusement mutilée. 4. Le bloc VORBIS_COMMENT est en petit-boutiste, alors que tout le reste du format FLAC est en gros-boutiste. Se tromper de sens donne des longueurs de champ aberrantes et un parseur qui part dans le décor. 5. Les tailles ID3 ne se lisent pas toutes pareil. L'en-tête ID3v2 et les trames v2.4 utilisent des entiers syncsafe (7 bits utiles par octet, le 8ᵉ toujours à 0) ; les trames v2.3, elles, portent une taille sur 32 bits ordinaires. Un seul mode de lecture pour les deux versions donne des trames décalées. 6. Valeurs multiples. Vorbis autorise plusieurs ARTIST dans un même fichier, ID3v2.3 non. Elles sont jointes par ; — et c'est cette même chaîne qui sert de valeur attendue à la vérification, donc la comparaison reste exacte. 7. pythonw.exe + binaire console = cascade de fenêtres noires. Le script passe creationflags=CREATE_NO_WINDOW à tous ses appels ffmpeg. Sans ce drapeau, un lancement en tâche planifiée par pythonw.exe (qui n'a pas de console) fait allouer une fenêtre par appel. Même piège que la surveillance Blink du 2026-08-30. 8. Pas d'inotify sous Windows : la surveillance se fait par sondage, pas par événements. Surveillance automatique à l'ouverture de session À créer par Julien dans sa propre console, pas depuis Claude Code — une tâche créée depuis le conteneur MSIX risquerait de figer un chemin virtualisé (page BookStack 282) : schtasks /Create /TN "Musique-FLAC" /TR "\"C:\Users\julie\AppData\Local\Programs\Python\Python312\pythonw.exe\" \"D:\Syncthing\Jux_univers\Jux-scripts\Musique-FLAC\flac_mp3.py\"" /SC ONLOGON /RL LIMITED /F pythonw.exe (et non python.exe) pour qu'aucune fenêtre n'apparaisse. Vérification : schtasks /Query /TN "Musique-FLAC" /V /FO LIST, ou taskschd.msc. Pour retirer : schtasks /Delete /TN "Musique-FLAC" /F. Contrôle depuis une session Claude : Get-CimInstance Win32_Process -Filter "Name='pythonw.exe'". Mesures de mise en place (2026-09-04) ffmpeg 9.0.1 (build essentials gyan.dev) : archive de 106,1 Mo, seul ffmpeg.exe conservé → 98,1 Mo dans D:\Musique\bin\. ffprobe.exe et ffplay.exe écartés : le script analyse les tags lui-même, il n'en a pas besoin. Délai de prise en charge : fichier déposé à 07:06:44, MP3 livré à 07:07:43, soit 59 s (15 s de sondage + 20 s de calme requis + encodage). Chemins d'échec vérifiés un par un : fichier qui n'est pas un FLAC, FLAC sans tags obligatoires, FLAC tronqué → 3 tentatives chacun puis mise à l'écart dans echecs\ avec le motif. Dépôt vidé, aucun MP3 partiel livré. Aller-retour des tags vérifié sur 15 tags dont accents, guillemets français et exposant ( Prélude n°1 — «Nuit», Élodie Ström), totaux de piste et de disque, ISRC, REPLAYGAIN_* et MUSICBRAINZ_* : tous restitués, pochette 600×600 conservée. Premier album réel — Dry Cleaning, Secret Love (2026-09-04) Source FLAC 24 bits / 96 kHz, 11 pistes, 41 min 10 s d'audio. Volume 823,7 Mo → 81,0 Mo, soit 9,8 % du FLAC Durée 50 s pour tout l'album, environ 49 × le temps réel Tags 18 par piste, tous vérifiés, aucun écart — pas même sur la date Pochette conservée sur les 11 pistes (JPEG 600×600) Dépôt vidé de lui-même ; Cover.jpg copiée en sortie, .m3u et .nfo suivis dans FLAC traités\ Tags exotiques restitués sans perte en TXXX : INVOLVEDPEOPLE (la liste complète des crédits, plus de 500 caractères), UPC, ITUNESADVISORY, MEDIATYPE, PRODUCER. Totaux recomposés en TRCK 3/11 et TPOS 1/1. Le rééchantillonnage est automatique et inévitable : MP3 plafonne à 48 kHz, un FLAC 96 kHz est donc ramené à 48 kHz et 16 bits par ffmpeg, sans intervention. La tolérance d'une seconde sur la durée absorbe l'écart sans difficulté (mesuré : aucun signalement sur les 11 pistes). Ne pas se fier au ratio relevé sur l'essai de synthèse (55 %) : une onde sinusoïdale ne compresse comme rien de réel. Le chiffre utile est celui de ce tableau. Reste à faire Créer la tâche planifiée (commande ci-dessus, à taper par Julien). Décider si les MP3 produits ont vocation à rejoindre la chaîne Navidrome ( music sur kDrive) ou restent un export local vers téléphone et autoradio. 260904 - ISBN des livres pour indexation Calibre ISBN des livres pour indexation Calibre Procédure en trois étapes, arrêtée le 2026-09-20. Copie de référence : Jux-scripts/Livres-ISBN/doc_bookstack_300.md. Le processus 1. Julien dépose les EPUB et PDF à identifier dans D:\Procedures Calibre\Dossier à renseigner avec leurs sous-dossiers s'il veut retrouver ses rayons dans le CSV. 2. Claude lance le script (ou Julien, c'est une seule commande) : py -3.12 D:\Syncthing\Jux_univers\Jux-scripts\Livres-ISBN\renseigner.py Il produit un seul fichier, dans le dépôt lui-même, daté du jour, dans l'ordre des fichiers : D:\Procedures Calibre\Dossier à renseigner\260920-livres-isbn.csv 3. Claude écrit dans les fichiers déposés ce qu'il a trouvé — l'ISBN, et le titre/auteur du catalogue quand la proposition est sûre : py -3.12 D:\Syncthing\Jux_univers\Jux-scripts\Livres-ISBN\ecrire_metadonnees.py --go L'outil est ebook-meta.exe, celui de Calibre : ce qu'il écrit, Calibre le lit à l'ajout, par construction. Décision de Julien (2026-09-20) : écrire même quand la recherche peut être erronée — « c'est moi qui valide les livres un par un ensuite ; si l'ISBN est faux, je le vois. Ça me fait gagner un temps fou. » Chaque fichier est copié à côté avant l'écriture, contrôlé après, restauré si le contrôle échoue. Le CSV reçoit une colonne ecrit. 4. Julien ajoute les livres un à un dans Calibre, vérifie, valide, corrige, le CSV ouvert à côté (LibreOffice Calc l'ouvre directement, séparateur ;). Une fois le lot fait, il vide le dépôt — Calibre a copié les fichiers dans sa bibliothèque, les originaux sont dans Foxy. Aucun script n'écrit dans Calibre ni dans Foxy. Le dépôt est une copie de travail ; les deux scripts refusent tout dossier réseau. Lire le CSV Une ligne d'intitulé === rayon === (n) précède chaque sous-dossier. Puis, par livre : Colonne Ce qu'elle dit confiance o : ISBN sûr, à saisir tel quel. ? : trouvé en catalogue mais à vérifier (comparer titre/ auteur à la couverture). Vide : rien trouvé a_saisir Ce que Julien devra taper dans Calibre — rien, isbn, titre, auteur ou une combinaison. Le reste, Calibre le lira tout seul dans le fichier à l'ajout isbn, titre, auteur Les valeurs à reporter provenance fichier (lu dans le livre), BnF, OpenLibrary calibre_lira isbn si Calibre lira l'ISBN de lui-même remarque Les avertissements : fichier que Calibre ne sait pas ouvrir, nom à renommer, doublon, titre suspect Dans Calibre, l'ISBN se saisit dans Modifier les métadonnées → Identifiants sous la forme isbn:9782…. Ensuite Télécharger les métadonnées complète la fiche — mais uniquement le résumé : le réglage Préférences → Partage → Téléchargement des métadonnées → Champs à télécharger ne doit garder que Résumé. Les étiquettes sont le classement de Julien, jamais écrasées. Ce que fait le script, et pourquoi Il lit les métadonnées dans les fichiers — l'OPF d'un EPUB, le DocInfo, le XMP et la page de copyright d'un PDF, les enregistrements EXTH d'un MOBI. Sur Foxy, 37 % des EPUB portent déjà leur ISBN : autant de livres identifiés sans une requête. Le nom de fichier ne sert qu'en dernier recours. Il demande à Calibre ce que lui en lira ( ebook-meta.exe, le lecteur même de l'import). C'est mesuré, pas deviné : Calibre lit l'ISBN d'un EPUB 14 fois sur 14 quand il est dans l'OPF, d'un PDF une fois sur deux — il ne lit jamais la page de copyright. D'où la colonne a_saisir. Il interroge la BnF puis OpenLibrary pour ce qui n'a pas d'ISBN. Ni Amazon (la source Calibre est morte, HTTP 500) ni Google Books (429 depuis cette IP). Il ne pré-coche ( o) que sur une preuve : ISBN lu dans le fichier, ou score ≥ 0,90 avec auteur concordant. Jamais sur un résultat obtenu sans filtre auteur, jamais sur un titre de moins de 25 caractères sans auteur ( Wraps, Whoopies : plusieurs livres portent ce titre). Il met en cache lectures Calibre et réponses en ligne dans D:\Procedures Calibre\ISBN\ : relancer ne réinterroge pas. Pièges connus Un ISBN se retrouve, il ne se déduit pas. Un ISBN inventé passe la clé de contrôle une fois sur dix et pointe un autre livre. Tout numéro est vérifié (clé ISBN-10/13), et dans un PDF n'est retenu que précédé du mot ISBN ou EAN. Le DocInfo d'un PDF est souvent du bruit d'atelier : PDF Reducer Demo version (203 fois sur le lot Cuisine), ~1, Microsoft Word - doc1. Règle : un titre d'ouvrage ne se répète pas à l'identique dans cinq fichiers. Un nom de fichier bien formé porte légitimement l'auteur ( Appetites - Anthony Bourdain.epub). La règle qui rejette un auteur « copié du nom de fichier » ne vise que les blocs sans espace ( SaveursAmericainesChristineFleurent). Un nom avec accent décomposé (NFD, fichier passé par un Mac) fait échouer Calibre sur « fichier introuvable » alors que l'EPUB est sain. Le script le lit quand même et note « à renommer avant l'ajout ». Sans auteur, un titre identique ne prouve rien. Signal d'alerte : zéro « à vérifier » sur 200 résultats n'est jamais naturel. X - Y ambigu ( Cioran - Exercices Négatifs vs Constantin - Bertrand Lançon) : le titre lu dans le fichier tranche ; sinon le catalogue est interrogé dans les deux sens. Brochures, magazines, compilations de recettes n'ont pas d'ISBN — le script ne s'y acharne pas. \\NASMAISON\foxy n'est dans aucune sauvegarde connue. Vrai des 16 616 fichiers comme de la bibliothèque Calibre. Historique 2026-09-04 — chaîne initiale ( inventaire_livres.py + resoudre_livres.py), lot Cuisine Géo : 619 fichiers, 233 ISBN (38 %). 2026-09-05 — les CSV suivent l'ordre du répertoire, plus le score : on vérifie un ISBN en voyant ses voisins d'étagère. 2026-09-20 — processus ramené à trois étapes, renseigner.py comme point d'entrée unique, un seul CSV dans le dépôt, lecture par Calibre mesurée. Le soir même : Claude écrit l'ISBN dans les fichiers déposés ( ecrire_metadonnees.py), Julien valide dans Calibre. Premier lot : Cuisine Géo Asie/Europe/Monde, ~480 fichiers. 260911- Paroles dans ma musique / paroles sur Navidrome En une phrase Navidrome n'affiche dans son lecteur web que les paroles synchronisées et embarquées dans les fichiers ; on les fait chercher sur LRCLIB par Beets (en conteneur sur le NAS), un script les écrit dans les tags sans rien toucher d'autre, on pousse les fichiers modifiés vers kDrive, Navidrome les relit. 1. Le diagnostic — pourquoi « Absence de paroles » était normal Le symptôme : Dancing Queen d'ABBA affichait « Absence de paroles » dans le lecteur web, alors qu'un fichier Dancing Queen.lrc synchronisé était posé à côté du MP3 et que l'API Subsonic ( getLyricsBySongId) renvoyait bien ces paroles horodatées. Vérifié dans le code de Navidrome 0.63.2 : Le lecteur web ne passe pas par l'API Subsonic. Il lit le champ lyrics de l'objet chanson ( ui/src/reducers/playerReducer.js), c'est-à-dire la colonne media_file.lyrics de la base, remplie au scan, depuis les tags embarqués seulement (USLT/SYLT pour les MP3, LYRICS pour FLAC, ©lyr pour M4A). Il n'affiche que les entrées synced: true. Un tag USLT en texte brut donne « Absence de paroles ». Les fichiers .lrc/ .txt posés à côté ne sont lus qu'à la volée, par core/lyrics au moment d'une requête getLyricsBySongId, selon LyricsPriority ( .lrc avant embedded). Donc Symfonium, Feishin, DSub… voient les 58 .lrc de la bibliothèque, le web non. ND_LRCLIB_ENABLED=true, posé sur le conteneur de la stack 42, est inerte : le binaire ne contient aucune chaîne lrclib. Navidrome ne va rien chercher en ligne sans plugin LyricsProvider. Navidrome lit bien la trame SYLT avec ses horodatages (ses tests gotaglib_test.go le montrent : [00:02.50]English SYLT), ainsi qu'un USLT contenant du texte LRC. État de la bibliothèque le 2026-09-11 avant travaux : 16 077 titres, 485 avec paroles en base, 15 synchronisées — donc 15 titres affichables dans l'interface web, sur 16 000. 2. Architecture retenue Élément Choix Pourquoi Où ça tourne NAS sasnexte, Container Manager, sur /volume1/music C'est l'original. Le VPS ne lit qu'un miroir kDrive ( /home/debian/music) que sync_kdrive_complete.sh écrase chaque nuit : y écrire serait perdu Recherche Beets 2.14.0 ( lscr.io/linuxserver/beets), plugin lyrics, source LRCLIB seule Gratuit, sans clé, la seule source qui fournit des paroles synchronisées. Genius/Google ne donnent que du texte brut, invisible du web Écriture dans les fichiers ecrire_paroles.py (mutagen), pas Beets Voir le piège n°1 : Beets réécrit tous ses champs Propagation propager_paroles.sh (rclone forcé) Voir le piège n°2 : le padding ID3 Enchaînement finir_paroles.sh en setsid 7 h de traitement, le NAS finit seul Les fichiers : docker-compose.yml, config.yaml (Beets), ecrire_paroles.py, propager_paroles.sh, finir_paroles.sh, README.md — dans Jux-scripts/Navidrome-Paroles/, déployés sur le NAS dans /volume1/homes/SAS_NEXTE/scripts/ et /volume1/docker/beets-paroles/config/. Configuration Beets, l'essentiel : import: autotag: no # jamais de re-identification MusicBrainz copy: no move: no # jamais de deplacement / renommage write: no # Beets N'ECRIT JAMAIS dans les fichiers (piege n°1) incremental: yes duplicate_action: keep # piege n°4 ignore: ['#recycle', '@eaDir', '@__thumb', '.*', '*~', 'System Volume Information', 'lost+found'] plugins: lyrics web lyrics: auto: no sources: [lrclib] synced: yes # preferer les paroles horodatees keep_synced: yes # ne jamais retoucher un titre deja synchronise force: yes # re-interroger les 485 titres qui n'ont que du texte brut fallback: null # rien trouve -> fichier intact 3. La procédure, telle qu'elle a été jouée le 2026-09-11 Sur le NAS (SSH sas_nexte, docker exige sudo et vit dans /usr/local/bin/) : D=/usr/local/bin/docker cd /volume1/docker/beets-paroles && sudo $D compose up -d sudo $D exec beets-paroles beet version # 2.14.0 sudo $D exec beets-paroles beet lyrics --help | grep keep-synced # l'option doit exister Inventaire — Beets indexe la bibliothèque sans rien écrire ( -A = tel quel, -W = aucun tag réécrit) : sudo $D exec beets-paroles beet import -A -W /music # 25 min, 15 770 titres # + 25 dossiers reimportes avec -I (piege n°4) -> 16 069 titres sudo $D exec beets-paroles beet stats Essai sur un album, avec repère posé avant : /volume1/homes/SAS_NEXTE/scripts/propager_paroles.sh --repere sudo $D exec beets-paroles beet lyrics 'album:Gold - Greatest Hits' # 19/19 en 20 s sudo $D exec beets-paroles python3 /config/ecrire_paroles.py --simulation --filtre "ABBA/Gold" sudo $D exec beets-paroles python3 /config/ecrire_paroles.py --filtre "ABBA/Gold" --controle 19 /volume1/homes/SAS_NEXTE/scripts/propager_paroles.sh # 19 fichiers, 4 s Côté VPS, rafraîchir le listing du montage et scanner : rclone rc --rc-addr 127.0.0.1:5576 --rc-user=rcadmin --rc-pass='RcMusic2026!' vfs/refresh recursive=true dir='ABBA/Gold - Greatest Hits' curl "https://navidrome.juxjux.ovh/rest/startScan?u=julien&p=…&v=1.16.0&c=claude&f=json" Résultat : Dancing Queen synced: true en base Navidrome, start: 20320 (20,32 s), après un simple quick scan — la date du dossier ayant changé, Navidrome relit ses fichiers. Toute la bibliothèque, sans surveillance : /volume1/homes/SAS_NEXTE/scripts/propager_paroles.sh --repere sudo $D exec -d beets-paroles sh -c 'beet lyrics >> /config/lyrics_run.log 2>&1; echo "FIN rc=$?" >> /config/lyrics_run.log' setsid /volume1/homes/SAS_NEXTE/scripts/finir_paroles.sh < /dev/null > /dev/null 2>&1 & finir_paroles.sh attend la ligne FIN, lance ecrire_paroles.py --controle 50 (50 fichiers tirés au hasard dont l'empreinte audio est comparée avant/après — le moindre écart arrête tout), puis propager_paroles.sh. Il n'y a pas de propagation si l'écriture a le moindre échec : on préfère un état non poussé à un état partiel. Suivi : tail /volume1/homes/SAS_NEXTE/logs/paroles_finir.log sudo $D exec beets-paroles sh -c 'echo "$(grep -c "Found lyrics" /config/lyrics_run.log) / $(grep -c "Fetching lyrics" /config/lyrics_run.log)"' Mesures : lancé à 18h08, terminé à 0h34, 0,64 titre/s, 86 % de paroles trouvées (voir le bilan en §7) ; le cron VPS de 4h30 ( vfs/refresh + quick scan) relit ensuite la bibliothèque. Pour ne pas attendre : bash /home/debian/navidrome_fullscan.sh. Contrôle final, sur le VPS : sudo cp /home/debian/docker/navidrome/data/navidrome.db /tmp/nd.db sudo cp /home/debian/docker/navidrome/data/navidrome.db-wal /tmp/nd.db-wal sudo sqlite3 /tmp/nd.db "select count(*), sum(lyrics like '%\"synced\":true%') from media_file where lyrics not in ('','[]')" Avant : 485|15. Dans le lecteur web, recharger la page et remettre le titre dans la file : la file de lecture conserve l'objet chanson tel qu'il était au moment de l'ajout, sans les paroles. 4. Les pièges — dans l'ordre où ils ont mordu 1. Beets écrit tous ses champs, pas seulement les paroles. Premier essai avec import.write: yes : sur Dancing Queen, Beets a réencodé toutes les trames texte et ajouté TRCK 0/0, TPOS 0/0, TDRC 0000, TDOR 0000, TBPM 0, TCMP 0 et un UFID vide — des valeurs qu'il n'avait pas, écrites comme des défauts. Sur 16 000 fichiers, une pollution durable des tags. Les 19 originaux d'ABBA ont été restaurés depuis kDrive ( rclone copy … --ignore-times), Beets passé en write: no, et ecrire_paroles.py écrit lui-même, en ne touchant qu'aux trames de paroles : SYLT (horodatée) + USLT (texte brut) pour les MP3, ©lyr pour M4A, LYRICS pour FLAC. Vérifié à l'octet sur ABBA : trames d'origine de tailles identiques, empreinte audio identique, ID3v1 conservé, propriétaire admin:users conservé. 2. Le padding ID3 rend les modifications invisibles à la synchro. Le WebDAV kDrive n'expose ni empreinte ni date fiable : sync_kdrive_complete.sh ne compare que les tailles. Or, mesuré sur 40 MP3 au hasard, 30 ont ≥ 2 048 octets de padding — des paroles y tiennent souvent sans changer la taille du fichier. Le NAS aurait eu les paroles et Navidrome ne les aurait jamais vues. propager_paroles.sh liste les fichiers audio postérieurs à un repère et les pousse avec rclone copy --files-from --ignore-times, taille identique ou non. Le ré-envoi donne au fichier une nouvelle date côté kDrive, ce qui invalide aussi le cache VFS du VPS. 3. Les ACL Synology ne laissent passer que root dans le conteneur. docker exec -u 1024:100 (l'uid admin, propriétaire des fichiers) échoue en Permission denied sur /music : le partage est en mode 000, ses droits sont dans les ACL DSM, que le conteneur n'évalue pas pour un utilisateur ordinaire. Tout tourne donc en root dans le conteneur. Sans conséquence sur les fichiers : mutagen réécrit en place, même inode, le propriétaire reste admin:users. En revanche, un rclone copy de restauration lancé par sas_nexte recrée les fichiers sous cet utilisateur → chown admin:users derrière. 4. Vingt-deux albums sautés comme « doublons ». Même artiste et même titre d'album dans deux dossiers (The Suburbs et The Suburbs _ Month Of May, les disques 2 et 3 d'un best of, une édition deluxe…) : en mode silencieux Beets les saute (« This album is already in the library! »), et l'état incrémental les note comme traités — une relance ne les reprend pas. Correctif : duplicate_action: keep, puis réimport explicite des dossiers avec -I. Pour les extraire du journal, prendre la ligne qui précède chaque « already in the library » ; les multi-disques y sont regroupés sur une seule ligne séparée par ; , à découper. 15 770 → 16 069 titres. 5. Beets 2.x stocke les chemins relatifs à directory. items.path contient ABBA/Gold - Greatest Hits/…, pas /music/… : première passe de ecrire_paroles.py en 19 échecs « No such file ». Le script préfixe /music. 6. Deux fois du LRC = chaque ligne en double. Beets met du texte LRC dans SYLT et dans USLT ; Navidrome en fait deux entrées synchronisées, et le lecteur web concatène toutes les entrées synchronisées. ecrire_paroles.py écrit le LRC dans SYLT et le texte brut dans USLT. 7. nohup ne détache pas sur DSM : setsid … < /dev/null > /dev/null 2>&1 &, et vérifier par ps -eo pid,sid,args — ps w tronque et fait croire à un processus mort. 5. Ce que la chaîne ne fait pas Elle ignore les 58 .lrc et 12 .txt déjà présents (LRCLIB retrouve ABBA lui-même). Les laisser : ils servent encore aux clients Subsonic. Elle ne renomme, ne déplace, ne ré-identifie rien. Elle n'efface jamais des paroles existantes et ne retouche pas un titre déjà synchronisé. Un titre absent de LRCLIB reste tel quel : normal pour les mixes, les instrumentaux et les raretés. Ne pas ajouter genius/ google : texte brut seulement, invisible du web. 6. Pour un nouvel album, plus tard D=/usr/local/bin/docker sudo $D exec beets-paroles beet import -A -W /music # incremental : seuls les nouveaux dossiers /volume1/homes/SAS_NEXTE/scripts/propager_paroles.sh --repere sudo $D exec beets-paroles beet lyrics 'added:-1w..' # ou 'album:Titre' sudo $D exec beets-paroles python3 /config/ecrire_paroles.py /volume1/homes/SAS_NEXTE/scripts/propager_paroles.sh Puis le cron VPS de 4h30 fait le reste. Le conteneur beets-paroles peut rester arrêté entre deux usages ( sudo $D compose stop) : sa base /config/beets.db et l'état paroles_ecrites.json survivent. 7. Bilan du 2026-09-12 Étape Heure Résultat beet lyrics (16 069 titres) 18h08 → 0h34 13 830 paroles trouvées (86 %), 0,64 titre/s ecrire_paroles.py --controle 50 0h34 → 1h51 13 139 fichiers écrits, 10 979 synchronisés, 50 contrôles audio OK, 1 échec : un .mp3.part (téléchargement inachevé) — la chaîne s'est arrêtée avant propagation, comme prévu propager_paroles.sh 6h05 → 6h46 13 139 fichiers, 102,7 Gio, 0 erreur, 50 Mio/s — en session setsid, a survécu à une coupure SSH navidrome_fullscan.sh 8h10 → 8h32 21 min Base Navidrome 16 077 titres, 13 160 avec paroles, 10 998 synchronisées (avant : 485 / 15) Poids ajouté : 48,6 Mio sur 102,6 Gio (+0,046 %), 3,9 Ko par fichier en moyenne. 8 815 fichiers sur 13 137 n'ont pas changé de taille — les paroles ont tenu dans le padding ID3 : sans la propagation forcée, 67 % des fichiers seraient restés invisibles pour la synchro quotidienne. Le piège n°2 n'était pas théorique. Le fichier .mp3.part de Suki Waterhouse est à supprimer à la main (ou à ajouter à ignore dans config.yaml). Point ouvert : Julien constate 2–3 s de retard des paroles dans le lecteur web. Ce n'est ni le scan (l'affichage est piloté côté navigateur par la position de lecture) ni les données (LRCLIB et le .lrc d'origine s'accordent à 0,3 s près sur Dancing Queen). À tester dans Symfonium sur le même titre : en phase = le rendu du lecteur web ( react-jinke-music-player) est en cause, et le modèle Navidrome connaît un offset LRC que le lecteur web ignore. 260913-recettes de Joplin vers MEALIE par Claude Mise en place le 2026-09-13. Mealie est utilisé tous les jours par toute la famille, mais renseigner une recette à la main est fastidieux : sections d'ingrédients, étapes, catégories, tags, photo. La procédure confie ce travail à Claude à partir d'une simple note Joplin. 1. La procédure, côté Julien Capturer la recette dans une note Joplin, dans le carnet « A transcrire dans Mealie » — texte libre, copié-collé d'un site, clip web, réponse d'un assistant, dictée, photos du téléphone. Aucune mise en forme n'est exigée. Publier la note : clic droit sur la note → Publier la note → copier le lien ( https://joplin.juxjux.ovh/shares/xxxxxxxx). Coller le lien à Claude. C'est tout. Le carnet est le garde-fou (demandé par Julien le 2026-09-13) : publier une note ne vaut pas consigne, seul le carnet le fait. Une note publiée pour une autre raison — un résumé de match de hockey, une liste, un article — est refusée par le script même si Claude se trompe sur son contenu. La page publiée ne dit pas dans quel carnet vit la note ; la base Joplin Server sur le VPS le sait ( items.jop_parent_id, interrogée par SSH). Si la base est injoignable, le doute vaut refus. Claude lit la note, la structure, crée la recette avec sa photo, et rend le lien Mealie. En retour il signale ce qu'il a dû décider seul (catégorie ou tag créé, quantités absentes, photo de remplacement), pour que Julien puisse corriger d'un mot. Où est le travail de Claude Une note de recette est rarement au carré : un copié-collé de site mêle recette et bavardage ; une réponse d'assistant argumente (« je choisirais l'espadon… ») au lieu de lister ; une photo de carnet manuscrit ne dit rien à un parseur. Un script ne peut pas trier ça. Claude, lui, lit la note comme un lecteur et en tire : Champ Mealie Ce que Claude en fait Nom reformulé en titre de recette (le titre Joplin est souvent une note de travail) Description l'esprit du plat, les choix qui comptent — pas les étapes Personnes, temps repris s'ils sont dans la note, estimés sinon (et signalés comme estimés) Ingrédients listés en sections ( Marinade, Sauce, Riz coco…) — Mealie les affiche titrées Étapes rédigées à l'impératif, titrées, sans le bavardage Notes ce qui ne rentre ni dans les ingrédients ni dans les étapes : variantes, choix du poisson, conseils Catégories cuisine / pays / type de plat — c'est la convention de la base (Turquie, Italia, Poisson, Dessert…) Tags ingrédients principaux — c'est l'autre convention de la base (Courgettes, Feta, Lait de coco…) Source l'URL de la note Joplin, dans le champ URL d'origine de Mealie Image la première vraie photo de la note ; les autres en photos secondaires Claude réutilise les catégories et tags existants avant d'en créer, et signale toute création. Les temps sont en minutes nues (« 30 »), comme le reste de la base. 2. L'outillage Tout est dans Jux-scripts/Mealie-Recettes/ et Jux-scripts/MCP/, stdlib Python seule — rien à installer, réplicable sur toutes les machines par Syncthing. joplin_mealie.py — la mécanique py -3.12 joplin_mealie.py lire # texte + images, avec verdict "photo ou pas" py -3.12 joplin_mealie.py creer fiche.json [--simulation] # creation, image, photos secondaires py -3.12 joplin_mealie.py image # poser / remplacer l'image principale py -3.12 joplin_mealie.py organiseurs # categories et tags existants py -3.12 joplin_mealie.py supprimer lire annonce d'abord le carnet de la note ( A TRANSCRIRE ou PAS a transcrire), puis transforme le bloc
de la page publiée en texte proche du markdown (titres, listes, gras, tableaux), remplace chaque image par un marqueur [image N] et les liste à part avec leurs dimensions. Une image de moins de 200 px de côté est déclarée PAS une photo : c'est un favicon ou une vignette, jamais un plat. creer prend une fiche JSON (celle que Claude rédige), refuse si la note de source n'est pas dans le carnet attendu ( --forcer uniquement sur demande explicite), puis fait le reste : POST /api/recipes (nom) → PUT /api/recipes/{slug} (corps complet) → PUT /api/recipes/{slug}/image (multipart) → POST /api/recipes/{slug}/assets pour les photos secondaires. Catégories et tags sont résolus par nom, insensible à la casse et aux accents, créés s'ils manquent. Refuse un doublon de nom sauf --doublon-ok. --simulation montre le corps sans rien écrire. Le carnet se lit par plink (Windows, clé d'hôte épinglée) ou ssh (Linux, clé attendue) : psql dans le conteneur joplin-db, jointure shares → items (note) → items (carnet), titre dans le JSON de content. Nom du carnet surchargeable par MEALIE_CARNET. Fiche JSON minimale : { "nom": "Brochettes d'espadon satay, riz coco", "description": "…", "personnes": 4, "temps_preparation": "25", "temps_cuisson": "20", "temps_total": "60", "categories": ["Asiatique", "Poisson"], "tags": ["Espadon", "Lait de coco", "Cacahouetes"], "ingredients": [{"titre": "Marinade", "items": ["2 c. à soupe de sauce soja", "…"]}, "600 g d'espadon"], "etapes": [{"titre": "Marinade", "texte": "…"}, "Servir avec le riz."], "notes": [{"titre": "Quel poisson ?", "texte": "…"}], "source": "https://joplin.juxjux.ovh/shares/…", "image": "https://joplin.juxjux.ovh/shares/…?resource_id=…", "images_secondaires": ["https://…"] } mcp_mealie.py — les mêmes gestes en outils MCP Enregistré le 2026-09-13 dans ~/.claude.json du poste julie (projet Claude-pcelio+jux), comme les autres MCP Python : command = chemin absolu de python.exe, args = chemin du script. 13 outils : Joplin → Mealie : lire_note_joplin, creer_recette, poser_image, poser_photo_secondaire consultation : lister_recettes, lire_recette, lister_organiseurs, supprimer_recette au quotidien : plan_repas, ajouter_au_plan, listes_courses, ajouter_courses, recette_vers_courses Le MCP importe joplin_mealie.py depuis le dossier voisin : une seule implémentation, deux façons de l'appeler (le CLI reste utile quand le MCP n'est pas chargé, ou depuis Ubuntu / la VM). 3. Première recette réelle — l'exemple du 2026-09-13 Note : https://joplin.juxjux.ovh/shares/EqODt1SvmX6OKzdr867bpZ — Brochette de poisson Satay sauce. Une réponse d'assistant collée telle quelle : classement de cinq poissons avec des étoiles, « je choisirais l'espadon », marinade en liste, sauce satay sans quantités, riz coco et accompagnement en prose. Résultat : Brochettes d'espadon satay, riz coco — 5 sections d'ingrédients, 5 étapes titrées, 2 notes, catégories Asiatique + Poisson, 8 tags dont un créé ( Espadon), photo. Le classement des poissons est allé dans une note, pas dans les étapes. Ce qu'a révélé ce premier passage : La seule image de la note faisait 16 × 16 px : le favicon d'un site cité par l'assistant, pas une photo. Sans le contrôle des dimensions, Mealie aurait reçu un carré illisible. D'où la règle des 200 px. Sans photo dans la note, Claude en cherche une libre de droits sur Wikimedia Commons (API commons.wikimedia.org, licence lue dans les métadonnées). Ici : Brochette d'espadon à Georgioúpoli (Crète), CC0. Elle est signalée comme provisoire dans une note de la recette, à remplacer par une photo du plat. Wikimedia refuse le User-Agent vide de urllib (HTTP 403) et n'accepte que des tailles de vignette fixes ( 1280px-, pas 800px- → HTTP 400 « Use thumbnail sizes listed »). Le script envoie un User-Agent nommé. La note de Julien n'avait ni temps ni nombre de personnes explicites hors « pour 4 personnes / 600–700 g » : les temps ont été estimés (25 + 20, total 60 marinade comprise) et le sont dits. Deuxième recette — un clip web (même jour) Note Uhp5DRRLdhLh2IlmAv8Bkg : la page entière du blog L'instant nordique clippée dans Joplin — menus, encarts, articles liés, formulaire de commentaire, pied de page, 19 images dont 3 fois le logo. La recette des köttbullar tient au milieu. Résultat : Köttbullar, boulettes de viande suédoises à la crème — 3 sections d'ingrédients, 7 étapes titrées, photo de la poêle en principale, l'assiette en photo secondaire. Ce que Claude a dû arbitrer, consigné dans une note « Incohérences de la source » de la recette : la liste du site dit « 5 ml de lait » mais l'étape en met 4 cuillères à soupe (retenu) ; la sauce soja apparaît à la dernière étape sans figurer dans la liste (ajoutée) ; « 2 piments » est reproduit tel quel mais signalé comme douteux dans une recette suédoise ; nombre de personnes absent (4 pour 450 g, estimé). Le contrôle du carnet a fonctionné dans les deux sens le même jour : la note du pâté Lamourette, dans « Trucs et astuces », est refusée par creer. 4. Pièges relevés Un clip web embarque tout le site : navigation, articles liés, formulaire, 19 images. La recette est au milieu ; tout le reste est à ignorer, et les images du site ne sont pas toutes des photos du plat (logo, vignettes d'autres articles, portrait de l'auteur). Regarder les candidates avant de choisir. Une note publiée est publique — l'URL suffit, sans identifiant. Ne pas y mettre autre chose que la recette. Le champ URL d'origine de Mealie la garde : dépublier la note casse ce lien, sans effet sur la recette. Les images de la note se téléchargent par ?resource_id= sur l'URL du partage, anonymement. Les photos de téléphone arrivent en pleine taille (1 920 × 1 440, 500 Ko à 800 Ko) — Mealie les redimensionne lui-même. Mealie n'analyse pas les ingrédients : ils sont stockés en texte libre ( note / display), comme les 141 recettes existantes. Le champ title du premier ingrédient d'une section porte le titre de section — c'est ainsi que Mealie fait ses en-têtes. POST /api/recipes ne prend que le nom et renvoie le slug ; tout le reste passe par un PUT du corps complet relu ( GET puis mise à jour), sinon l'API efface ce qu'on n'a pas renvoyé. Les temps sont des chaînes libres dans Mealie. La base utilise des minutes nues (« 60 », « 30 ») — 31 recettes sur 141 n'en ont aucun. S'y tenir. 59 recettes sur 141 n'ont pas de catégorie au 2026-09-13 : les plus récentes ont été saisies vite. Rien n'empêche de les reprendre une par une avec lire_recette + mise à jour — chantier possible. Le CLI claude n'est pas dans le PATH du poste (application de bureau) : le MCP a été inscrit à la main dans .claude.json, sauvegarde .claude.json.bak-260913-mealie. Il faut relancer la session pour qu'il soit chargé. Piège d'atelier, sans rapport avec Mealie : l'outil Bash de Claude Code abîme les \x.. dans un heredoc Python — deux fichiers réparés en cours de route. Écrire les scripts avec l'outil d'édition, les exécuter avec Bash. 5. Identifiants Mealie : jubertrand@gmail.com / mot de passe dans CLAUDE.md (OAuth2 password, POST /api/auth/token, formulaire username/ password, jeton valable 48 h). Surchargeables par MEALIE_URL, MEALIE_USER, MEALIE_PASS. 260914-les films de la Seedbox sur le Kdrive et indexation automatique Jellyfin Mise en place le 14/09/2026. Scripts et README dans Jux-scripts/Seedbox-Films/ (arbre Syncthing). Tableau de bord : ligne « Seedbox » du tableau des procédures en pied de la veille du jour, et sonde « Seedbox-Films » des tâches nocturnes. Le besoin, et ce qui a été écarté Faire passer un film de la seedbox ( pool372.seedbox.fr, accessible en WebDAV — c'est un Nextcloud) jusqu'à Jellyfin, sans le faire transiter par le PC, et que Jellyfin l'indexe tout seul. FileZilla, l'idée de départ : impossible. La version gratuite ne parle pas WebDAV (FTP/SFTP seulement) et, même en Pro, FileZilla ne fait jamais de transfert serveur → serveur : tout octet passe par le PC, deux fois le volume sur la ligne, PC allumé. Le mode direct FXP est propre au FTP et FileZilla ne l'a jamais implémenté. Un « dossier film du VPS » : le VPS n'a que 22 Go libres sur 99 (78 %) et aucune bibliothèque Jellyfin n'y vit — les 15 lisent toutes kDrive via /mnt/nas_videos depuis la bascule du 30/08. Cinq films et le disque était plein, BookStack avec. Le streaming direct depuis la seedbox (montage rclone de son WebDAV dans Jellyfin) : techniquement possible, Nextcloud gère les requêtes par plage. Mais la seedbox est un espace de transit : le film disparaîtrait de Jellyfin — historique de visionnage compris — le jour du ménage. Écarté, la copie vers kDrive reste la voie durable. Retenu : rclone sur le VPS, seedbox déclarée comme second remote à côté de kdrive. Le VPS pilote, mais le flux WebDAV de la seedbox est réécrit tel quel dans le flux WebDAV de kDrive — le disque du VPS n'est pas touché. La seedbox et kDrive restent « deux mondes » : le seul point de contact est le dossier FILMS de l'une vers le dossier movies de l'autre. Le circuit seedbox (WebDAV Nextcloud) kDrive (WebDAV) VPS Seedbox/FILMS/*.mkv --rclone copy--> Videos_NAS/video/movies --FUSE--> /mnt/nas_videos/video/movies (id 1482703) = /data/movies dans Jellyfin bibliothèque « Films récents » Un film terminé sur la seedbox est dans Jellyfin 5 à 11 minutes plus tard : jusqu'à 5 min pour qu'il soit tenu pour stable, jusqu'à 5 min avant le passage du cron, ~1 min de copie par film, ~30 s de scan. Ce que fait seedbox_films.sh (cron VPS toutes les 5 min) Verrou par PID — un double lancement copierait deux fois les mêmes fichiers. Liste la seedbox ( rclone lsjson) : c'est la seule source de vérité (voir le piège kDrive ci-dessous). Retient ce qui est présent sur la seedbox, stable depuis 5 min, et absent de sa mémoire ( /home/debian/.seedbox_films.copies, une ligne taille ⇥ nom par fichier copié). Exclut *.part, *.!qB, *.aria2 et les dossiers cachés. Rien à faire → sort en silence, le journal ne grossit que quand il se passe quelque chose. Vérifie que kDrive répond, puis rclone copy --files-from-raw vers kdrive:…/Videos_NAS/video/movies, en --size-only (le WebDAV kDrive n'expose ni hash ni date). copy, jamais sync : la seedbox n'est jamais modifiée, et une suppression sur la seedbox ne supprime rien sur kDrive. Si la copie a réussi, les fichiers entrent en mémoire, puis : rclone rc vfs/refresh dir=video/movies sur le montage kdrive-video (port 5578). Sans cela le montage garde son listing 72 h et Jellyfin ne voit rien — le même piège que Komga (page 168) ; POST /Items/{id}/Refresh sur la seule bibliothèque « Films récents » ( a8c97a7ab1c7545488876ca753a24ab3), avec une clé API Jellyfin dédiée seedbox-films — pas un scan général des 15 bibliothèques sur kDrive. Si la copie a échoué : rien n'entre en mémoire, tout est retenté au passage suivant. --simulation passe --dry-run à rclone et s'arrête avant la mémoire et le rafraîchissement. Première exécution — 14/09/2026 Étape Mesure Copie seedbox → kDrive 7 films, 13,4 Gio en 196 s (~73 Mo/s), disque du VPS intact Rafraîchissement du montage 3 s Scan Jellyfin 30 s plus tard, .nfo, affiches, fonds et logos produits pour 6 films sur 7 Identification La Corde au cou (Dead Man's Wire), Dreams, Michael, La Vallée des fous, Que la bête meure, Vivaldi et moi (Primavera) — et un raté : The.Mandalorian.And.Grogu.2026.Multi.Truefrench.Imax.WEBRIP.mp4, resté sous son nom brut (trop de jetons après l'année). À identifier à la main, ou nommer Titre (Année).ext sur la seedbox avant copie Pièges rencontrés — à ne pas redécouvrir ⚠ kDrive met plusieurs minutes à lister un gros fichier frais en WebDAV. Première version du script : un rclone copy classique, qui compare source et destination. Le passage suivant a recopié 5 films sur 7 — 10 Gio, 143 s — copiés 3 à 5 min plus tôt : kDrive ne les listait pas encore (à 8 min, rclone check donnait 0 différence ; retard d'indexation, pas perte). Avec un cron toutes les 5 min, chaque film aurait été copié deux fois. D'où la mémoire locale des copies : le script ne demande plus à kDrive ce qu'il possède. Conséquence à connaître : un film supprimé sur kDrive mais toujours dans FILMS n'est pas recopié — retirer sa ligne de .seedbox_films.copies pour le ravoir. La mémoire se purge seule des fichiers retirés de la seedbox, et un fichier remplacé par une version de taille différente est recopié (clé = taille + nom). La racine WebDAV de la seedbox ne contient que Seedbox/ : le dossier est Seedbox/FILMS, pas FILMS. rclone 1.74 dit « Skipped copy as --dry-run is set », plus « Not copying » : un comptage sur l'ancien motif fait croire à une simulation vide. grep -c renvoie 1 quand il ne trouve rien — || true derrière, toujours. Trois noms pour la même chose : dossier kDrive movies (id 1482703), bind Jellyfin /data/movies, bibliothèque « Films récents ». Le garde-fou de session Claude Code a refusé de faire transiter le mot de passe de la seedbox dans une commande — heureusement : Julien l'a posé lui-même par rclone config password seedbox pass '…', depuis sa console, en 30 secondes. Voir Sécurité. Où sont les choses sur le VPS Quoi Où Script (propagé par Syncthing) /home/debian/Documents/Jux_univers/Jux-scripts/Seedbox-Films/seedbox_films.sh Cron */5 * * * *, crontab de debian (sauvegarde crontab.bak-260914) Journal /home/debian/logs/seedbox_films.log (+ seedbox_films.cron.log, sortie brute) Mémoire des copies /home/debian/.seedbox_films.copies, amorcée le 14/09 avec les 7 premiers films Secrets /home/debian/.seedbox_films.env (chmod 600, hors Syncthing) : clé API Jellyfin, id de la bibliothèque, mot de passe RC du montage video Remote rclone seedbox /home/debian/.config/rclone/rclone.conf (sauvegarde .bak-260914) — type = webdav, vendor = nextcloud, url = https://pool372.seedbox.fr/files/remote.php/dav/files/seedbox4f65a4ad8f8a0 Sécurité — le mot de passe de la seedbox Il n'existe qu'à un seul endroit : rclone.conf du VPS, obscurci, permissions 600. Consigne explicite de Julien : il n'est ni dans CLAUDE.md, ni dans le README, ni dans cette page, ni dans la mémoire de Claude — contrairement aux autres identifiants de l'infrastructure. Il a été saisi par Julien depuis sa propre console. S'il change : rclone config password seedbox pass 'nouveau' sur le VPS, à taper par Julien. Canari du matin Deux entrées ajoutées le 14/09 dans Jux-scripts/Etat-Infra/ : config.json, bloc journaux : sonde « Seedbox-Films », alerte sur ERREUR|AVERTISSEMENT parmi les 60 dernières lignes datées de moins de 24 h ( erreur_heures, nouveau), et sans max_heures : ce journal est muet quand rien n'est à copier, un silence n'est pas une panne. max_heures est devenu facultatif dans etat_infra.py pour ce cas (sauvegarde .bak-260914). procedures.json : ligne « Seedbox → kDrive movies → Jellyfin », dernière copie lue sur le motif fichier(s) copie(s) en, sans cadence — « OK — à la demande ». Lecture : ERREUR : seedbox injoignable toutes les 5 min = mot de passe changé ou seedbox arrêtée ; AVERTISSEMENT : vfs/refresh a echoue = montage kdrive-video mort, monitor-rclone.sh s'en occupe. Débits mesurés Trajet Débit seedbox.fr → kDrive, piloté par le VPS, 2 flux ~73 Mo/s (13,4 Gio en 196 s ; 10 Gio en 143 s) kDrive → conteneur Jellyfin (mesure du 30/08) 65,6 Mo/s Pour mémoire : ce qu'aurait fait FileZilla 2 × le volume sur la ligne Orange du PC Non fait, à décider Déclenchement à l'événement plutôt qu'au sondage. Le VPS ne peut pas être notifié par WebDAV. L'événement « téléchargement terminé » existe côté seedbox : le crochet « exécuter une commande à la fin » du client torrent (ruTorrent, qBittorrent, Deluge…) appellerait une URL du VPS (nginx, chemin secret, jeton) qui pose un drapeau lu par un systemd.path. Le délai tomberait à la seule durée de la copie, et les 5 min de stabilité disparaîtraient — le client sait, lui, que le fichier est complet. Les identifiants kDrive ne quitteraient pas le VPS. Il faut savoir quel client torrent la seedbox propose et si ce réglage est accessible. Ménage de la seedbox : rien ne supprime les fichiers copiés. Julien vide FILMS quand il veut ; rien ne bouge sur kDrive. Renommer les fichiers en Titre (Année).ext sur la seedbox avant copie, pour que Jellyfin identifie tout du premier coup. 260916-Proxy pour IP étrangères — les proxys de la seedbox dans Firefox 1. Le besoin Ma Seedbox inclut des proxys — j'aimerais bien m'en servir : par défaut le proxy France, car il intègre Adblock (donc plus de pub, normalement) ; les autres proxys pour regarder les TV publiques étrangères (surtout l'Italie). Pays Serveur Port Note France proxy-fr.seedbox.fr 3128 Adblock inclus Italie proxy-it.seedbox.fr 3128 Royaume-Uni proxy-uk.seedbox.fr 3128 Allemagne proxy-de.seedbox.fr 3128 Espagne proxy-es.seedbox.fr 3128 Portugal proxy-pt.seedbox.fr 3128 Belgique proxy-be.seedbox.fr 3128 Pays-Bas proxy-nl.seedbox.fr 3128 Irlande proxy-ie.seedbox.fr 3128 Pologne proxy-pl.seedbox.fr 3128 République Tchèque proxy-cz.seedbox.fr 3128 Finlande proxy-fi.seedbox.fr 3128 Lituanie proxy-lt.seedbox.fr 3128 2. Ce qui a été vérifié avant de choisir (2026-09-16) Sondage depuis le poste julie, sans identifiants, sur 8 des 13 serveurs : tous répondent sur le port 3128 ; ce sont des Squid 3.5.20 ; ils exigent une authentification Basic ( 407 Proxy Authentication Required, realm Web-Proxy) — ce sont les identifiants de la seedbox ; la méthode CONNECT est acceptée → le HTTPS passe (indispensable, toutes les TV sont en HTTPS) ; toutes les adresses sont des plages OVH (87.98.x, 94.23.x, 178.32.x, 91.121.x) : des IP de datacenter géolocalisées dans chaque pays, pas des IP résidentielles. Conséquence en §6. 3. Solution retenue : FoxyProxy dans Firefox, en mode « par motifs » Une extension Firefox qui choisit le proxy selon le site visité : France par défaut pour tout le trafic du navigateur (Adblock) ; bascule automatique par pays : raiplay.it part par l'Italie, zdf.de par l'Allemagne, rtve.es par l'Espagne… sans rien toucher ; bascule manuelle en un clic dans la barre d'outils pour forcer un pays ; identifiants saisis une fois, mémorisés par l'extension ; exclusions : réseau local, tunnel WireGuard, nos propres services ( juxjux.ovh, alteris.ovh, infomaniak.com) et les sites sensibles ne passent jamais par un proxy. Pourquoi pas le proxy système de Windows : il s'appliquerait à tout (Syncthing, Steam, mises à jour, WireGuard…), Windows gère mal l'authentification proxy, et un fichier PAC sait router par site mais ne peut pas porter d'identifiants. Il est fourni en variante (§7), à ne retenir que pour faire passer une autre application que Firefox. 4. Les fichiers — Jux-scripts/Seedbox-Proxy/ Fichier Rôle generer_config.py Source de vérité : une table (pays, serveur, code ISO, couleur, domaines TV) produit les deux fichiers ci-dessous. Stdlib seule : py -3.12 generer_config.py foxyproxy_seedbox.json Fichier d'import FoxyProxy : 13 proxys, 27 domaines de télévision publique, liste d'exclusion, champs identifiants vides seedbox.pac Variante « proxy système », même routage, sans identifiants README.md Résumé de cette page Le mot de passe seedbox ne s'écrit nulle part dans l'arbre Syncthing (règle en vigueur depuis la procédure Seedbox-Films) : le JSON versionné a ses champs username/ password vides, et c'est Julien qui les renseigne, hors Syncthing. Domaines routés par pays (télévisions publiques ; s'ajoutent dans la table du script) : Pays Domaines Italie rai.it, raiplay.it, raiplaysound.it, rainews.it Royaume-Uni bbc.co.uk, bbc.com, bbci.co.uk, channel4.com, itv.com Allemagne zdf.de, ardmediathek.de, ard.de, daserste.de, tagesschau.de Espagne rtve.es — Portugal : rtp.pt — Belgique : rtbf.be, vrt.be Pays-Bas npo.nl, npostart.nl, nos.nl — Irlande : rte.ie — Pologne : tvp.pl Rép. Tchèque ceskatelevize.cz, ivysilani.cz — Finlande : yle.fi — Lituanie : lrt.lt Les motifs sont des expressions régulières sur l'URL complète ( ^https?://([^/]+\.)?rai\.it(:\d+)?(/|$)) : le domaine nu et ses sous-domaines, mais ni boulevard.de pour ard.de, ni une URL qui ne ferait que citer rai.it dans son chemin. FoxyProxy évalue les proxys dans l'ordre et retient le premier dont un motif correspond : les pays sont en tête, la France (motif *) en dernier. Sans ce motif final, FoxyProxy enverrait tout le reste en direct — c'est son comportement par défaut en mode motifs. 5. Mise en place — pas à pas Installer FoxyProxy Standard dans Firefox (menu ⋮ → Extensions et thèmes → chercher « FoxyProxy Standard », éditeur eric.h.jung / foxyproxy). Épingler son icône dans la barre d'outils. Copier foxyproxy_seedbox.json hors de l'arbre Syncthing (par exemple D:\Seedbox\) et y mettre les identifiants dans un éditeur de texte : Remplacer tout "username": "" par "username": "", idem pour "password". Treize proxys, deux remplacements. Supprimer cette copie après l'import. Variante sans toucher au fichier : importer tel quel, puis saisir identifiant et mot de passe dans chacun des 13 proxys de la page d'options (FoxyProxy propose aussi un Bulk Edit). Importer : icône FoxyProxy → Options → onglet Import → choisir le fichier. L'import remplace la configuration existante. Vérifier que le sélecteur en haut de la page d'options est bien sur « Proxy by Patterns » (le fichier le pose, mais c'est ce réglage qui fait tout). Contrôle : https://ifconfig.me doit afficher 87.98.174.34 (le proxy France) — et non l'IP Orange de la maison ; ouvrir https://www.raiplay.it puis l'onglet Log des options de FoxyProxy : les requêtes raiplay.it doivent être marquées Italie, les autres France ; lancer une vidéo RaiPlay. Compléter la liste d'exclusion (options → Global Exclude, ou la liste PASSTHROUGH du script) avec les sites sensibles : banque, assurance, santé. Voir §6, point 3. Portmaster : rien à faire, le sortant est en permit par défaut. Firefox ouvrira des connexions vers proxy-*.seedbox.fr:3128, c'est tout. 6. Ce qu'il faut savoir — limites L'Adblock du proxy ne travaille qu'au niveau des domaines sur HTTPS. Un proxy ne lit pas l'intérieur d'une page chiffrée ; il ne peut que refuser les connexions vers les domaines publicitaires connus. Garder uBlock Origin dans Firefox — les deux se complètent, ils ne se remplacent pas. Les IP sont des adresses de datacenter OVH — et la Rai les refuse (constaté le 2026-09-16). Premier essai sur Diretta Rai 2 par le proxy Italie : « RAI detiene i diritti per lo streaming del contenuto esclusivamente per connessioni dall'Italia ». Géolocalisation des 13 IP (base ip-api) : Proxy annoncé IP Vu comme Hébergeur Italie 94.23.65.245 IT Milan OVH AS16276, hosting Espagne, Portugal, Belgique, Pologne, Tchéquie 87.98.225.34, 94.23.74.75, 91.121.216.35, 87.98.235.69, 94.23.168.27 pays annoncé OVH, hosting Royaume-Uni, Allemagne, Pays-Bas, Irlande, Finlande, Lituanie 178.32.60.249, 87.98.243.154, 94.23.144.159, 188.165.0.96, 188.165.136.66, 188.165.24.74 France (Paris, Strasbourg, Roubaix) OVH, hosting Toutes les machines sont physiquement chez OVH en France ; seule la géolocalisation déclarée change, et six « pays » sur treize ne sont même pas repris par cette base. La Rai diffuse via Akamai, dont la base (EdgeScape) place vraisemblablement 94.23.x à Roubaix. Conclusion : les proxys seedbox ne servent pas pour les télévisions étrangères — ils restent utiles pour la France/Adblock. Pour une vraie IP italienne : un Raspberry Pi chez un proche en Italie, raccordé au hub WireGuard du VPS comme peer 10.0.0.5 avec un petit Squid, déclaré dans FoxyProxy comme « Italie maison » ; à défaut, un VPN grand public avec extension Firefox dans un profil séparé (ses IP sont aussi en datacenter mais renouvelées contre les blocages). Les proxys résidentiels payants (facturés au Go, 10-30 € le film) et les listes gratuites (machines compromises) sont écartés. Le proxy voit passer tout le trafic HTTP en clair, et l'identifiant en Basic (encodé, pas chiffré, entre Firefox et le proxy). Le HTTPS reste chiffré de bout en bout, mais le proxy connaît les sites visités. D'où la liste d'exclusion — nos propres services et les sites sensibles n'ont aucune raison de transiter par un Squid OVH. Firefox seulement. VLC, Jellyfin, l'émulateur Android… ne sont pas concernés. C'est voulu. Une vidéo qui démarre puis échoue : le lecteur charge parfois son flux depuis un CDN sur un autre domaine que le site (Akamai, etc.), qui part alors par la France. Repérer ce domaine dans l'onglet Log de FoxyProxy (ou l'outil Réseau de Firefox, F12) et l'ajouter au pays concerné. Réimporter le JSON efface les identifiants saisis (l'import remplace tout). Pour ajouter un site durablement : modifier la table du script, regénérer, réimporter la copie avec identifiants. Pour un essai rapide : ajouter le motif directement dans le proxy, depuis les options. Squid 3.5.20 date de 2016 — c'est le choix de seedbox.fr, pas le nôtre. Sans conséquence pour l'usage. 7. Variante « proxy système » — seedbox.pac Même routage, sans extension : un fichier PAC lu par le navigateur ou le système. Firefox : Paramètres → Réseau → Paramètres de connexion → Adresse de configuration automatique du proxy : file:///D:/Syncthing/Jux_univers/Jux-scripts/Seedbox-Proxy/seedbox.pac. Firefox demande les identifiants une fois par proxy (cocher « mémoriser »). Perd la bascule manuelle et le journal de FoxyProxy. Windows (Paramètres → Réseau → Proxy → Utiliser un script d'installation) : Windows 10 n'accepte plus un chemin file:// — il faudrait servir le fichier en http:// depuis le VPS, comme le dossier Kobo. Non fait, et non recommandé : le proxy s'appliquerait alors à toutes les applications. La France est déclarée avec repli ; DIRECT : si le proxy ne répond pas, on navigue sans lui plutôt que de rester bloqué. Les proxys pays, eux, n'ont pas de repli — mieux vaut voir l'échec que croire regarder « depuis l'Italie ». 8. Voies écartées Proxy système Windows : tout le poste y passerait (Syncthing, Steam, WireGuard…), authentification mal gérée — §3. Un profil Firefox par pays : marche, mais oblige à changer de fenêtre et à dupliquer les extensions et mots de passe. Conteneurs Firefox + proxy par conteneur : FoxyProxy sait le faire ( tabProxy), c'est une évolution possible si la bascule automatique par domaine ne suffit pas — un onglet « Italie » où tout passerait par l'Italie. 9. Suite — la réponse pour la télévision : IPTV-Publiques (2026-09-16, le soir même) Ni proxy ni VPN : les diffuseurs publics publient leurs flux HLS, le projet iptv-org les recense, et Jellyfin les joue via un tuner M3U testé toutes les 6 h. Rai 1/2/3, News 24, Storia, Scuola, Südtirol, TVE Internacional, Das Erste, Tagesschau 24, arte, RTP… — 21 chaînes vivantes au premier relevé. Page BookStack 323, dossier Jux-scripts/IPTV-Publiques/. Les proxys seedbox restent utiles pour la France/Adblock ; le VPN n'a pas été souscrit. 260916-IPTV-Publiques — la télévision publique européenne dans Jellyfin 1. Le besoin, et pourquoi pas les proxys ni un VPN Regarder les télévisions publiques européennes (Rai en tête) depuis la maison. La page 322 raconte les deux impasses de la journée : les proxys seedbox.fr sont des IP de datacenter OVH que la Rai refuse, et un VPN grand public aurait coûté un abonnement pour un résultat incertain. La troisième voie est la bonne : les diffuseurs publics publient eux-mêmes leurs flux HLS, et le projet communautaire iptv-org les recense par pays. Un flux HLS est une simple URL .m3u8. Il n'y a rien à contourner — seulement à trier ce qui répond réellement depuis le VPS, et à le servir proprement. Décision de Julien : c'est Jellyfin qui joue la TV. Le VPS porte déjà Jellyfin ; une liste M3U branchée en tuner y ajoute une section « TV en direct » visible sur la télé, la tablette et les téléphones de toute la famille, avec logos, numéros de chaînes et groupes par pays. 2. Ce que fait generer_playlist.py Toutes les 6 heures sur le VPS (cron 15 */6 * * *), stdlib seule : lit les listes iptv-org des pays concernés ; n'en garde que les chaînes de la liste blanche config.json (regex sur le nom) ; ajoute les candidats supplémentaires connus hors liste (les flux CloudFront de Rai 1 et Rai 3, le flux officiel d'arte) ; écarte d'office les hôtes en adresse IP nue et streamhostingcdn (rediffusions pirates) ; teste chaque candidat : playlist HLS → variante la plus haute → téléchargement d'un vrai segment, avec mesure du débit. Un segment qui n'arrive pas, une playlist vide, un débit sous 3 Mbit/s = KO ; retient le candidat le plus rapide par chaîne, avec une préférence de stabilité pour celui de la fois précédente ; écrit tv_publiques.m3u (tvg-id, logo, tvg-chno, group-title = pays) dans /home/debian/jellyfin/config/ (= /config/ dans le conteneur, lu par le tuner) et dans /home/debian/Documents/IPTV/ (= D:\Syncthing\IPTV\, pour VLC depuis n'importe quelle machine) ; si la liste a changé, demande à Jellyfin de relire son guide ( POST /ScheduledTasks/Running/{RefreshGuide}, clé API dédiée iptv-publiques dans /home/debian/.iptv_publiques.env, hors Syncthing) ; termine par la ligne que lit le canari : === BILAN: 21/41 chaînes vivantes, 0 perdue — absentes : … ===. « Perdue » ≠ « absente ». Une chaîne géobloquée depuis toujours est absente et ne fait pas parler le canari ; une chaîne vivante au passage précédent et morte maintenant est perdue, et là le canari chante ( attendu: "BILAN: .*0 perdue", journal iptv_publiques.log, cadence 8 h). Hystérésis : au premier échec la chaîne est conservée dans la M3U avec son flux précédent ( ? au journal) ; elle n'est déclarée perdue qu'au second passage raté. Sans cela, les flux RTP « Not 24/7 » faisaient chanter le canari pour rien — constaté dès le second passage. --simulation teste et affiche sans rien écrire, --verbeux détaille chaque candidat. explorer.py it de … liste et teste toutes les chaînes publiques d'un pays : c'est l'outil pour enrichir la liste blanche. 3. Relevé du 2026-09-16 depuis le VPS — 21 chaînes vivantes sur 41 demandées Pays Vivantes Absentes et pourquoi Italie Rai 1, Rai 2, Rai 3, Rai News 24, Rai Storia, Rai Scuola (CloudFront tiers, 80-95 Mbit/s), Rai Südtirol (flux officiel wzstreaming.rai.it) Rai 4, Gulp, Sport : flux officiels géobloqués Espagne TVE Internacional (le flux officiel RTVE pour l'Europe), Teledeporte, Clan La 1, La 2, 24 Horas : HTTP 451 — RTVE bloque les IP d'hébergeur (mais répond depuis la maison) Allemagne Das Erste HD, Tagesschau 24, KiKA, hr-fernsehen, SR Fernsehen — tous flux officiels ARD, ouverts au monde ZDF, 3sat : n'existent que via antik.sk, trop lent ; Phoenix, ZDFinfo/neo : géobloqués ; BR, NDR, WDR, ARD-alpha, MDR : géoblocage doux (voir §4) Arte arte (flux officiel Akamai, 140 Mbit/s) — Belgique BX1 (Bruxelles) La Une, Tipik : uniquement via des IP nues (écartées) ; VRT absente des listes Portugal RTP 1, RTP Notícias, RTP Açores (flux officiels) — intermittents, [Not 24/7] RTP 2 Danemark Folketinget (le parlement, Kaltura) DR1, DR2 : géobloqués Suisse aucune SRF/RTS/RSI : absentes des listes ou géobloquées Autriche aucune ORF 1/2 : IP nues ; ORF III : géoblocage doux Luxembourg aucune RTL Télé Lëtzebuerg : géoblocage doux Pays-Bas aucune NPO : géobloqués Vérifié de bout en bout : tuner créé par l'API ( POST /LiveTv/TunerHosts, type m3u, URL /config/tv_publiques.m3u, User-Agent navigateur), guide rafraîchi, 21 chaînes vues par Jellyfin, puis lecture de Rai 2 par le vrai enchaînement client ( PlaybackInfo → LiveStreams/Open) : Jellyfin sonde le flux ( hls, h264 1080p, aac) et répond SupportsDirectPlay: true — aucun transcodage, le VPS ne fait que relayer. 4. Pièges rencontrés Les listes iptv-org ne sont pas la Rai. Rai 1/2/3 y viennent de rediffuseurs tiers (un CloudFront anonyme, un opérateur slovaque). Ils peuvent disparaître : d'où le test à chaque passage et l'hystérésis, plutôt qu'une liste figée. Un flux « OK » n'est pas forcément jouable : dash2/dash4.antik.sk répond 200 mais sert ses segments au rythme du direct (~1 Mbit/s), mesuré depuis le VPS et depuis la maison. En lecture ça coupe. D'où la mesure de débit et le seuil debit_min_mbit: 3. ZDF et 3sat n'ont pas d'autre source : absentes. Géoblocage doux : ARD régionales, RTL.lu et ORF III resservent la playlist master à la place de la variante — HTTP 200, aucune erreur, et un lecteur naïf tourne en rond. Détecté par « la variante contient encore #EXT-X-STREAM-INF ». Le dernier segment d'un direct n'est pas toujours servi (404 fugace sur Das Erste) : le test prend l'avant-avant-dernier et réessaie une fois. La géographie n'est pas la même depuis la maison et depuis le VPS : RTVE La 2 joue depuis la Livebox (IP Orange) et répond 451 depuis OVH. Jellyfin étant sur le VPS, c'est le VPS qui fait foi — la M3U de Syncthing est la même, VLC à la maison n'aura pas RTVE La 2 même si elle marcherait. PlaybackInfo seul fait croire à un transcodage ( ContainerNotSupported, VideoCodecNotSupported, ffmpeg à 170-290 % CPU) : Jellyfin ne sonde un tuner M3U qu'à l'ouverture ( RequiresOpening: true → LiveStreams/Open). Après l'ouverture, direct play. Les ffmpeg de mes premiers essais ont tourné 2 min à 3 cœurs — tués à la main ( pgrep -x ffmpeg). pkill -f 'motif' lancé par plink -m script tue la session elle-même : le motif figure dans la ligne de commande du bash qui l'exécute. Passer par pgrep -x sur le nom du processus. streaming-live.rtp.pt répond 204 No Content hors antenne ( [Not 24/7]) : un 204 est traité comme vide, pas comme un succès. arte : l'entrée « arte » de la liste France est une IP nue ; le flux officiel est artesimulcast.akamaized.net/hls/live/2030993/artelive_de/index.m3u8 (celui de la liste allemande, marqué à tort [Geo-blocked] — il répond depuis la France). Le chemin artelive_fr n'existe pas sous cet identifiant : le flux français a le sien, 2031003/artelive_fr (trouvé le lendemain dans la liste belge de Free-TV, voir §7). 5. Utilisation et entretien Regarder : Jellyfin → TV en direct (ou Live TV) → les chaînes par groupe de pays. Sur la télé, les numéros 1-7 sont l'Italie. VLC ailleurs : Média → Ouvrir un flux réseau → D:\Syncthing\IPTV\tv_publiques.m3u (ou son équivalent sur chaque machine). Ajouter une chaîne : python3 explorer.py sur le VPS pour voir ce qui répond, puis une ligne dans chaines de config.json (nom, pays, motif, groupe, éventuels candidats). Le passage suivant l'inclut si elle vit. Voir l'état : tail -3 /home/debian/logs/iptv_publiques.log, ou la page de veille du matin (« Tâches nocturnes »). Retour arrière : supprimer le tuner dans Jellyfin (Tableau de bord → TV en direct), retirer la ligne de cron (sauvegarde /home/debian/crontab.bak-260916), effacer les deux tv_publiques.m3u. Pas encore fait — le guide des programmes (EPG). Les tvg-id iptv-org ( Rai1.it, DasErste.de…) sont ceux du projet iptv-org/epg, qui sait produire un XMLTV par chaîne ; Jellyfin l'accepte en fournisseur de guide XMLTV. Un grabber Node à faire tourner, à évaluer si le « qu'est-ce qui passe ? » manque à l'usage. 6. Fichiers Jux-scripts/IPTV-Publiques/ : generer_playlist.py (production), config.json (liste blanche, sorties, seuils), explorer.py (exploration), README.md, doc_bookstack.md (cette page). Sur le VPS : journal /home/debian/logs/iptv_publiques.log, état /home/debian/logs/iptv_publiques.etat.json, clé API dans /home/debian/.iptv_publiques.env (chmod 600). Canari : entrée IPTV-Publiques dans Etat-Infra/config.json. 7. Le 2026-09-17 — seconde source (Free-TV) et guide des programmes Julien a signalé la liste Free-TV/IPTV (via l'article de Korben sur les chaînes IPTV gratuites, qui ne cite que ces deux listes). Elle est complémentaire d'iptv-org : un seul fichier de 2 080 chaînes, le pays dans group-title, des flux officiels plus souvent, des marqueurs Ⓖ (géobloquée), Ⓢ (pas 24/7), Ⓨ (YouTube) accolés aux noms, et surtout un en-tête x-tvg-url qui désigne les guides XMLTV d'epgshare01 par pays. Le script lit désormais les deux sources ( sources dans config.json, mode pays pour iptv-org, mode groupe pour Free-TV), fusionne les candidats d'une même chaîne et garde le plus rapide. Résultat depuis le VPS : 55 chaînes vivantes sur 87 demandées (21 la veille). Ce que Free-TV a apporté : Pays Nouvelles chaînes vivantes Italie Rai Italia Europa (le canal international de la Rai, fait pour l'étranger), Rai World Premium, La7 (flux officiel), Senato TV, Camera dei Deputati Espagne régionales officielles : Telemadrid, Canal Sur, Canal Extremadura, Televisión Canaria, ETB 1/2, TV3 Catalunya, 3/24, À Punt Allemagne NDR International, WDR Fernsehen Arte le flux français ( artelive_fr, id Akamai 2031003 — celui de la liste belge) remplace le flux allemand Portugal RTP 2, RTP Mundo (international), RTP Madeira ; RTP 3/Notícias Autriche W24 (la chaîne de la ville de Vienne — ORF reste géobloqué) Danemark TV 2/Bornholm Pays-Bas 13 régionales publiques (NH Nieuws, RTV Rijnmond, Utrecht, Omroep Brabant, Gelderland, RTV Noord, Oost, Drenthe, Omrop Fryslân, Flevoland, West, Zeeland, L1) — NPO reste géobloqué Toujours rien pour la Suisse (SRF/RTS/RSI : IP nues ou géobloqués), le Luxembourg (RTL.lu en géoblocage doux) et les nationales RTVE, ZDF, ORF, DR, NPO, RTBF. Guide des programmes — generer_guide.py (cron 5 3 * * *, 9 s) : lit en flux les guides epgshare01 des pays utiles (18 à 52 Mo de XML chacun, jusqu'à 751 chaînes), n'en garde que nos chaînes, réécrit leurs identifiants sur les nôtres et produit /config/guide.xml (5 Mo, ~7 500 programmes) pour le fournisseur XMLTV de Jellyfin (créé par POST /LiveTv/ListingProviders). 50 chaînes sur 55 ont un programme ; les 5 sans (Rai Italia, Rai World Premium, Rai Südtirol, Senato, Camera) n'existent dans aucun guide. Les identifiants ne concordent pas d'une liste à l'autre ( Rai1.it, Rai.1.HD..101.it, Das.Erste.de vs DasErste.de, RTP.1.HD.pt vs RTP1.pt, HR.de pour hr-fernsehen, SWR/SR.de pour SR). D'où : la M3U porte désormais notre propre tvg-id stable ( rai1.tvpub), le guide est réécrit avec ces id, et l'appariement se fait sur les noms normalisés (sans ponctuation, sans « HD », sans suffixe pays) plus des alias epg dans config.json pour les récalcitrants ( "epg": ["SWR/SR"]). Le tvg-id d'origine reste dans tvg-id-source TVE Internacional n'est que dans le guide suisse ( TVE.Internacional.ch) : epg_pays: "ch" sur la chaîne, arte dans le guide français Jellyfin affiche « en cours » et la grille sur la télé ; sa tâche Refresh Guide relit le fichier chaque nuit Pièges du jour : L'outil Bash abîme \b et \n dans un heredoc — un \b de regex est devenu un caractère backspace (0x08) dans explorer.py : la regex ^(Rai\b|…) ne trouvait plus une seule Rai, sans erreur. Éditer avec Edit/Write, jamais par heredoc (déjà noté en mémoire, re-mordu) Free-TV marque le géoblocage d'un Ⓖ dans le nom : sans le retirer, ^Rai 1$ ne matche pas « Rai 1 Ⓖ ». Et un flux marqué Ⓖ répond parfois depuis le VPS (arte, UniNettuno) : le marqueur trie les candidats, il ne les écarte pas Segments RTP aux noms hors ASCII → UnicodeEncodeError/ InvalidURL : l'URL du segment est désormais encodée ( urllib.parse.quote) antik.sk répond 200 mais sert au rythme du direct : ZDF et 3sat n'ont aucune autre source vivante, ils restent absents 8. État au 2026-09-17 55 chaînes dans Jellyfin, 50 avec guide, cron M3U toutes les 6 h et guide à 3h05 UTC, deux entrées canari ( 0 perdue, GUIDE: [1-9]). Ajouter une chaîne = python3 explorer.py puis une ligne dans chaines (motif sur le nom tel qu'il apparaît dans l'une des deux listes). 9. Le 2026-09-27 — l'Europe du Nord et de l'Est, et la Grèce Julien demandait la télévision publique grecque, ERT 1 surtout, en donnant l'URL https://ertflix.s.llnwi.net/ertlive/ert1/clrdef24723b/playlist.m3u8. Cette URL est morte : llnwi.net est le CDN Limelight, retiré du service — le nom ne résout plus du tout (NXDOMAIN, depuis le PC comme depuis le VPS). Le flux vivant d'ERT est ailleurs, et il fallait le trouver. Ce que les deux listes donnaient, et pourquoi ça ne suffisait pas : iptv-org n'a que ERT 3, ERT News et ERT Cosmos ; Free-TV a bien ERT 1/2/3 mais en .mpd (DASH), que notre chaîne ne sait ni tester ni servir à Jellyfin. En remplaçant l'extension par index.m3u8 sur le même paquetiseur, tout ERT répond en HLS 1080p, audio grec et sous-titres inclus : https://ert-live.siliconweb.com/bpk-tv/{ERT1,ERT2,ERT3,ERTNews,ERTCosmos,ERTKids}/default/index.m3u8. Ces six URL sont posées en candidats dans config.json ; le script les préfère aux .mpd. Exploration de 18 pays dans la foulée ( explorer.py gr ie pl cz fi se no hr ro hu bg sk si cy is ee lv lt), 34 chaînes ajoutées à la liste blanche : Pays Ajouté Source Grèce ERT 1, ERT 2, ERT 3, ERT News, ERT Cosmos, ERT Kids, Vouli TV (le parlement) siliconweb, officiel Irlande RTÉ One, RTÉ 2, RTÉ News, TG4 live.rte.ie, officiel Roumanie TVR 1, 2, 3, International, Cultural, Info, Folclor CDN officiel mncdn Finlande Yle TV1, Yle TV2, Yle Teema & Fem Akamai, officiel Islande RÚV, RÚV 2 Akamai, officiel Pays baltes ETV, ETV2, ETV+ (Estonie), LTV7 (Lettonie), LRT Lituanica, LRT Plius, LRT Klasika err.ee et lrt.lt, officiels Chypre RIK Sat — Slovaquie STVR Live, STVR :24 hôte tiers, intermittent Pologne Belsat (la chaîne biélorusse d'opposition, émise depuis la Pologne) — Écartées faute de source propre — à ne pas rechercher inutilement : les ČT tchèques, les M1/M2/Duna hongroises et Jednotka/Dvojka slovaques n'existent que derrière l'IP nue 88.212.15.19, un rediffuseur pirate que hotes_exclus refuse par principe. SVT (Suède), NRK (Norvège), HRT (Croatie), BNT (Bulgarie), RTV SLO (Slovénie) et TVP (Pologne) n'ont aucun flux vivant dans les deux listes. Résultat : 83 chaînes vivantes sur 121, 65 avec programmes, 41 émissions en cours vues par Jellyfin au moment du contrôle. Guide : onze guides epgshare01 ajoutés. Deux n'existent pas ( IS1, EE1 renvoient une page HTML de 52 octets) — RÚV et ETV n'auront donc pas de programmes. ERT 1/2/3/News et Vouli sont absents du guide grec alors que ERT Cosmos y est : rien à corriger de notre côté. RIK Sat, lui, a été rattaché au guide grec et non chypriote ( epg_pays: "gr"), et les irlandaises ont reçu leurs alias ( RTE Two HD, RTE News Now). Pièges du jour : Une URL de flux fournie de bonne foi peut pointer un CDN mort : vérifier la résolution DNS avant de chercher plus loin ( host, ou un curl qui répond 000 en 0 s) .mpd ≠ .m3u8 : Free-TV donne du DASH pour certaines chaînes ; le même paquetiseur Broadpeak sert du HLS au même chemin, il suffit de changer l'extension Deux échecs passagers (ERT Kids, LRT Plius) ont été rattrapés par l'hystérésis puis se sont remis à répondre au passage suivant : c'est exactement ce pour quoi elle a été écrite 261001-les playlistes de Claude sur Navidrome En une phrase AudioMuse-AI écoute réellement les morceaux (analyse sonore, pas les tags), les regroupe par parenté musicale, fait nommer les groupes par Gemini, et dépose le résultat en playlists dans Navidrome — où elles restent, même PC éteint. 1. Pourquoi sur le PC, et nulle part ailleurs AudioMuse n'est pas un greffon : c'est une pile d'analyse (Flask + worker ONNX + PostgreSQL embarqué, 2,4 Go installés) qui exige AVX2 et 8 Go de RAM. Machine AVX2 RAM disponible Verdict NAS sasnexte non — Celeron J4025 0,6 Go éliminé, ne démarrera jamais jux-debian non — même CPU hôte 3 Go éliminé, même raison VPS Jux oui (Haswell, 6 cœurs) 2,5 Go, swap à 4,9/8 Go trop juste PC julie oui — Ryzen 5 2600X, 12 threads 16 Go retenu L'absence d'AVX2 sur le J4025 est définitive : ni le NAS ni la VM qui tourne dessus ne pourront héberger AudioMuse, quelle que soit la version. 2. Ce que le PC éteint change — et ne change pas Point vérifié dans le code ( tasks/mediaserver/navidrome.py) et non supposé : AudioMuse crée les playlists dans Navidrome, par l'API Subsonic ( createPlaylist, puis updatePlaylist public=true). Elles vivent donc dans la base du VPS, comme si elles avaient été faites à la main. PC allumé PC éteint Écouter les playlists (web, Symfonium, Feishin) oui oui Fabriquer / rafraîchir les playlists oui non Instant Mix & Radio (greffon .ndp) oui non Le greffon Navidrome audiomuseai.ndp n'a pas été installé : il interroge AudioMuse en direct à chaque clic, donc il exige un service permanent — ce que le PC n'est pas. Seules les playlists nous intéressent, et elles n'en ont pas besoin. 3. Emplacement, et les deux pièges du poste D:\AudioMuse-AI\ ├── app\ le bundle (PostgreSQL embarqué compris), 2,4 Go ├── donnees\AudioMuse-AI\ base, audio temporaire, modèles, journaux, secrets ├── sauvegardes\ dumps produits par sauvegarder_base.py ├── Demarrer AudioMuse.cmd ├── Arreter AudioMuse.cmd └── sauvegarder_base.py Hors de l'arbre Syncthing, volontairement : 1,5 Go d'archive et plusieurs Go de données n'ont rien à faire sur les 7 appareils du maillage, dont un téléphone. Même raison que D:\Procedures photos\ et D:\Musique\. ⚠ Piège n°1 — MSIX native-build/windows/paths.py construit les chemins de données à partir de la variable d'environnement LOCALAPPDATA. Or Claude Code tourne dans un conteneur MSIX : tout processus lancé depuis une session Claude voit son %LOCALAPPDATA% redirigé vers …\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Local\. Lancé ainsi, AudioMuse installerait sa base dans un chemin fantôme — exactement l'incident Android Studio du 2026-08-26. Demarrer AudioMuse.cmd force donc LOCALAPPDATA=D:\AudioMuse-AI\donnees avant d'appeler l'exécutable. Double bénéfice : hors périmètre de virtualisation, et hors du SSD système (95 Go libres, 146 arrêts brutaux au compteur, contre 596 Go sur D:). C'est Julien qui lance, jamais Claude Code. ⚠ Piège n°2 — l'espace dans le chemin Toujours dans paths.py : si le chemin de données contient une espace, AudioMuse bascule en silence vers %PROGRAMDATA%. Le lanceur vérifie et refuse de démarrer le cas échéant, plutôt que d'écrire ailleurs sans rien dire. 4. Mise en route Double-cliquer Demarrer AudioMuse.cmd. Au premier lancement, PostgreSQL s'initialise : compter 1 à 2 minutes. Le lanceur attend que le port 8000 réponde, puis ouvre le navigateur. Une icône apparaît dans la zone de notification (état, journal, arrêt). Dans l'assistant, serveur de musique : Navidrome, https://navidrome.juxjux.ovh, utilisateur julien, mot de passe Navidrome. Authentification AudioMuse : un identifiant, un mot de passe, un jeton d'API. Le jeton ne sert qu'au greffon (non installé) mais l'assistant l'exige. Première analyse — page Analysis and Clustering → Start Analysis. Le coût de la première analyse AudioMuse télécharge chaque morceau en entier par l'API Subsonic ( download_track → stream), l'analyse, puis passe au suivant. Un seul fichier à la fois : rien ne s'accumule sur le disque. Mais au total ~133 Go transitent depuis le VPS (donc depuis kDrive), une seule fois. Durée annoncée par le projet : « de quelques heures à plusieurs jours ». L'analyse est reprenable — les titres déjà traités sont en base, une interruption ne fait rien perdre — et incrémentale ensuite : un nouvel album ne coûte que sa propre taille. 5. Noms de playlists par Gemini Il n'y a aucun greffon à installer : c'est de la configuration. Le modèle ne reçoit que des étiquettes de genre et d'humeur, jamais la musique ni les fichiers. Créer la clé (gratuite, sans facturation) sur aistudio.google.com → Clés API → Créer une clé API. C'est à Julien de le faire : Claude ne crée pas d'identifiants. AudioMuse → Administration → Configuration → Configuration avancée → Fournisseur d'IA & Nommage de playlist : AI_MODEL_PROVIDER = GEMINI GEMINI_API_KEY = la clé GEMINI_MODEL_NAME = gemini-flash-latest — alias toujours à jour ; le défaut livré est gemini-2.5-pro, qui échoue souvent en version gratuite AI Prompt → Clustering Naming → style Titre complet de l'IA, et ajouter en fin de prompt la consigne de langue et de longueur (2 à 4 mots, sans underscore, jamais le mot « automatic »). Aperçu des titres teste sans rien enregistrer. Si les noms restent des étiquettes ( no AI title so the tag name is kept), la clé ou le nom de modèle est faux. Si Gemini cesse un jour de répondre, les playlists sont quand même créées, avec les anciens noms à base d'étiquettes. 6. ⚠ Le suffixe _automatic — à savoir avant de s'attacher à une playlist Toute playlist produite se termine par _automatic. Au début de chaque clustering, AudioMuse appelle delete_automatic_playlists() qui supprime dans Navidrome toutes les playlists dont le nom finit par ce suffixe — y compris celles d'une exécution précédente. C'est voulu : il les recalcule. Pour garder une playlist définitivement : la renommer dans Navidrome en retirant _automatic. Elle devient invisible de ce ménage et survit à tout, y compris à une réinstallation. 7. La base d'analyse, et sa sauvegarde sur kDrive Son poids — calculé sur le schéma réel Trois empreintes par titre, toutes en BYTEA float32 : Empreinte Dimensions Octets/titre MusiCNN ( EMBEDDING_DIMENSION) 200 800 CLAP ( CLAP_EMBEDDING_DIMENSION) 512 2 048 Paroles GTE ( LYRICS_EMBEDDING_DIMENSION) 768 3 072 Métadonnées ( score) — ~1 000 ~7 Ko/titre × 16 069 ≈ 113 Mo de données brutes, que l'index de recherche ( ivf_cell) double à peu près. Base vivante attendue : 250 à 350 Mo ; dump : 150 à 250 Mo. ⚠⚠ Pourquoi la base VIVANTE n'est pas sur kDrive Demande initiale de Julien : « on mettra la base d'analyse dans le kDrive, on y a de la place, c'est pérenne ». L'objectif est le bon — cette base vaut des heures de calcul et 133 Go téléchargés — mais une base PostgreSQL vivante ne peut pas être posée sur kDrive : WebDAV n'offre ni fsync fiable, ni verrouillage de fichier, ni renommage atomique, et un fichier de base synchronisé pendant qu'il est ouvert se corrompt. C'est le mode de panne classique, pas une précaution théorique. La pérennité s'obtient par un dump poussé sur kDrive — exactement le schéma de vps_backup.sh pour les 7 autres services. Restaurable sur n'importe quelle machine, et survit à un formatage. Le script py -3.12 D:\AudioMuse-AI\sauvegarder_base.py REM dump + envoi kDrive py -3.12 D:\AudioMuse-AI\sauvegarder_base.py --simulation REM dump local seulement py -3.12 D:\AudioMuse-AI\sauvegarder_base.py --restaurer REM recupere et reinjecte AudioMuse doit tourner : le PostgreSQL embarqué n'écoute que pendant ce temps. Le script refuse poliment sinon. Destination kDrive : SYNC-pour_VPS/backups/audiomuse, dossier id 1517484, créé le 2026-10-01 à côté des sept autres sauvegardes. Un seul nom, audiomuse_base.dump, avec conflict=version : kDrive conserve lui-même les versions précédentes. pg_dump -Fc (format personnalisé, déjà compressé), écriture atomique .partiel puis os.replace, et refus d'envoyer un dump de moins de 1 Ko — un dump vide ne doit jamais écraser un bon. Au-delà de 900 Mo il bascule sur l'envoi par session et morceaux : l'upload simple de kDrive refuse au-delà de 1 000 000 000 octets (leçon du 2026-09-27). Ce seuil ne sera probablement jamais atteint ici, mais le chemin existe. Identifiants PostgreSQL : utilisateur et base postgres sur 127.0.0.1:5432, mot de passe aléatoire lu dans donnees\AudioMuse-AI\secrets\pg_password. 8. Désinstallation Arreter AudioMuse.cmd Supprimer D:\AudioMuse-AI\ C'est tout : archive portable, rien dans Program Files, rien dans le registre, aucun service, aucun résidu dans %LOCALAPPDATA% puisque les données sont sur D:. Les playlists déjà créées restent dans Navidrome — elles ne lui appartiennent pas. Seule disparaît la base d'analyse ; d'où le dump kDrive, qui permet de revenir sans refaire les 133 Go. 9. Repères Version installée : v3.6.3 (2026-09-26), AudioMuse-AI-amd64-windows.zip, 1 588 225 001 octets, téléchargé en 187 s Ports locaux : 8000 (interface), 8001 (contrôle), 5432 (PostgreSQL embarqué) — les trois vérifiés libres avant installation, tous sur 127.0.0.1 Portmaster : l'interface est en 127.0.0.1, couverte par la règle Allow Localhost. Rien à ouvrir, aucune connexion entrante Le PC appelle navidrome.juxjux.ovh en sortant : rien à ouvrir sur la Livebox Dépôt : https://github.com/NeptuneHub/AudioMuse-AI (AGPL-3.0) 10. Mesures réelles du 2026-10-01 (première analyse) Analyse lancée à 20h55 (heure de Paris) sur les 1 161 albums / 16 069 titres. Grandeur Mesure Durée par piste médiane 19,5 s, moyenne 20,6 s (min 4,8 — max 93,1) dont étape paroles médiane 1,6 s, moyenne 3,1 s (max 78,7) Part des paroles 15 % du temps CPU pendant l'analyse 90 %, le worker à lui seul ~6 cœurs, 713 Mo Durée totale projetée ~92 h, soit 3,8 jours Trois méthodes indépendantes concordent sur ~90 h : comptage des lignes de score en base, horodatage du journal piste par piste, et la progression « Albums N/1161 ». Le goulot est le CPU, pas le réseau : les 133 Go ne ralentissent rien, c'est l'inférence ONNX (CLAP + MusiCNN + GTE) qui limite. Désactiver les paroles : mesuré, et non retenu Question posée par Julien le 2026-10-01. Mesure faite sans rien désactiver, en horodatant l'étape dans le journal sur 216 pistes réelles : gain de 14 h sur 92 (92 h → 78 h, 3,8 → 3,2 jours). Non retenu : 15 % de gain contre la perte du regroupement par le sens des textes (ce qui distingue AudioMuse d'un classeur purement acoustique), de la page Lyrics Search et de la fonction Album Creation (conditionnée à lyrics_enabled and clap_enabled). Comme l'analyse est suspendable, les 0,6 jour gagnés ne changent pas la nature du problème. Détail : Whisper se déclenche bien (74 pistes sur 216 en échec d'API externe, repli whisper_small, timeout 300 s) mais seules 23 transcriptions aboutissent — le reste est reconnu instrumental avant. D'où une médiane basse avec quelques pointes à 78 s. ⚠ Suspendre et reprendre : sans perte Vérifié dans le code, pas seulement dans la FAQ : tasks/analysis/main.py saute les albums entièrement analysés ( Skipping album … all N tracks already analyzed) et album.py saute les étapes déjà faites piste par piste ( SKIPPED MusiCNN … already analyzed). Moyen Coût Bouton Cancel Current Task (Analysis and Clustering) rien, la tâche passe en REVOKED Arreter AudioMuse.cmd ou le menu de l'icône la piste en cours Extinction du PC la piste en cours Pour reprendre : relancer AudioMuse si besoin, puis Start Analysis — il repart où il s'était arrêté. Le PC n'a donc pas à rester immobilisé 3,8 jours d'affilée. Et plus tard, l'analyse reste incrémentale : un nouvel album ne coûte que ses pistes (~5 min pour 15 titres). Le clustering, lui, recalcule toutes les playlists à chaque exécution, mais à partir des empreintes déjà en base — aucun téléchargement, aucune ré-analyse. 11. Deux corrections à la configuration documentée ⚠ PostgreSQL n'écoute PAS sur 5432. native-build/windows/paths.py annonce pg_port() = 5432, mais le serveur embarqué prend un port libre au hasard (56290 au premier démarrage) qui change à chaque redémarrage. Le seul endroit fiable est la 4ᵉ ligne de pgdata/postmaster.pid. sauvegarder_base.py le lit de là ; coder 5432 en dur donne un « Connection refused » trompeur. ⚠ Les horodatages de la base et de l'interface sont en UTC, pas en heure de Paris (le build natif ne reçoit pas de TZ). Deux heures d'écart en été : une analyse démarrée à 20h55 s'affiche à 18h55. Ne pas en conclure à un blocage. ⚠ Le port 8000 écoute sur 0.0.0.0, pas sur 127.0.0.1 comme écrit plus haut au §9 : l'interface est donc joignable depuis le réseau local. Portmaster l'autorise (règle Allow LAN) et la bloque depuis l'extérieur ( Block *), et l'interface est protégée par le compte créé dans l'assistant — mais ce n'est pas une écoute purement locale. 12. ⚠⚠ Le choix du modèle Gemini — testé le 2026-10-01 Les deux modèles conseillés (par AudioMuse et par le billet Reddit d'origine) ne fonctionnent pas. Testé en appelant directement l'API Google avec la clé de Julien : Modèle Résultat gemini-2.5-pro — défaut livré par AudioMuse ( config.py) 404 — « no longer available to new users » gemini-flash-latest — conseillé par le billet Reddit 503 — « currently experiencing high demand », deux fois à 96 s d'écart, puis encore 3 min plus tard gemini-2.5-flash ✅ 200, réponse réelle gemini-2.5-flash-lite 404 — plus ouvert aux nouveaux comptes gemini-2.0-flash 404 — retiré Valeur à retenir : GEMINI_MODEL_NAME = gemini-2.5-flash. Son seul inconvénient face à un alias -latest est qu'il faudra le changer à la main le jour où Google sortira un Flash plus récent — bien moindre mal qu'un modèle qui ne répond jamais. Comment distinguer les trois pannes Le code HTTP dit tout, et évite de soupçonner la clé à tort : Code Sens 401 / 403 la clé est mauvaise 404 le nom de modèle n'existe pas (ou plus) pour ce compte 429 quota dépassé 503 modèle surchargé chez Google — ni la clé ni le quota ne sont en cause Méthode de diagnostic, sans rien modifier dans l'interface : grep -E "generativelanguage" .../logs/audiomuse.log | head Chaque ligne donne le modèle appelé et le code obtenu. ⚠ Aucune relance automatique sur erreur HTTP tasks/ai/providers/gemini.py : les 3 tentatives de EMPTY_RESPONSE_RETRIES ne couvrent que les réponses vides. Sur une erreur SDK (503 comprise), la fonction renvoie directement "Error: AI service is currently unavailable." sans réessayer. Un délai de 7 s précède chaque appel ( GEMINI_API_CALL_DELAY_SECONDS). Conséquence le jour du clustering : si Google est surchargé à cet instant, les playlists concernées garderont leur nom d'étiquette ( Electronic_Pop_Indie_Medium_Relaxed_Danceable). Ce n'est pas perdu : relancer le clustering suffit — il travaille sur les empreintes déjà en base, donc quelques minutes, sans téléchargement ni ré-analyse, et Gemini est réinterrogé. Valider la clé sans attendre l'analyse complète La page Instant Playlist appelle Gemini directement ( app_chat.py → config.AI_MODEL_PROVIDER). C'est le test le plus rapide : une demande en langage naturel, et le journal dit si l'IA a planifié ou si AudioMuse est retombé sur rescue: matching your words directly (= l'IA n'a pas répondu). Validé le 2026-10-01 à 22h29 : deux appels gemini-2.5-flash en 200, playlist de 50 titres cohérente, aucun repli. 13. ⚠ Pourquoi « Preview titles » échoue en début d'analyse Message : « No playlist was large enough to keep. » Ce n'est ni Gemini ni une erreur de réglage, c'est de l'arithmétique : Paramètre Valeur NUM_CLUSTERS_MIN / MAX 40 à 100 grappes MIN_PLAYLIST_SIZE_FOR_TOP_N 20 titres minimum pour qu'une playlist soit retenue TOP_N_CLUSTERING_PLAYLIST 10 playlists conservées au final CLUSTERING_MAX_PLAYLIST_SONGS 200 titres maximum MAX_SONGS_PER_ARTIST 3 par playlist Avec 237 titres analysés et 40 grappes au minimum, cela fait 6 titres par grappe au mieux. Le journal l'a confirmé au titre près : 5 playlists de 1, 4, 6, 9 et 16 titres, toutes écartées. Il faut donc au moins ~800 titres pour que l'aperçu donne quelque chose, et en pratique davantage. Attendre ~2 000 titres (une dizaine d'heures d'analyse) avant de refaire l'essai. Note sur le résultat final : seules 10 playlists seront conservées sur 40 à 100 grappes. Pour en avoir plus, augmenter TOP_N_CLUSTERING_PLAYLIST.