Migrer un nœud 4.x vers la 5.0¶
La 5.0 renomme mulewatch en p2pwatch et sépare le nœud en deux conteneurs : p2pwatch, le
crawler et son catalogue web, et ed2k, le client eMule avec son port-sync. Les dossiers d'aMule
passent sous ed2k/, et une ligne de compose.yml choisit entre la variante directe et la
variante VPN. Aucune donnée n'est perdue. Six étapes, depuis votre dossier de travail.
Sauvegardez d'abord votre configuration et votre catalogue
Le premier démarrage de la 5.0 réécrit les deux bases. Il en garde d'abord une
copie d'avant migration dans data/, mais votre
sauvegarde reste le retour le plus sûr vers la 4.x : elle garde aussi votre configuration. Les
deux demandent de la place : la sauvegarde la taille de data/, le premier démarrage jusqu'à
deux fois le catalogue dans data/. amule/ et downloads/ ne sont que déplacés, inutile de
les copier.
1. Arrêtez le nœud 4.x, avec ses anciens fichiers compose.
2. Rangez les dossiers d'aMule sous ed2k/. Un déplacement sur le même disque, immédiat quelle
que soit la taille de downloads/. data/ ne bouge pas.
3. Éditez crawler.yml et matcher.yml. Dans crawler.yml, retirez la section port_sync:
entière, et ces six clés de la racine : cycle_interval_seconds, decision_poll_interval_seconds,
search_poll_budget_seconds, search_poll_interval_seconds, keyword_pause_min_seconds et
keyword_pause_max_seconds. Tant que l'une reste, le crawler refuse de démarrer en la nommant :
port_sync: was removed, delete it from crawler.yml.
Retirez aussi enabled de la section webui:, et la clé metrics de la section
observability: : le catalogue web est toujours servi, et les métriques passent sur son port.
Restées, elles sont refusées de même : webui.enabled: was removed, delete it from crawler.yml et
observability.metrics: was removed, delete it from crawler.yml.
Déplacez ensuite la section backoff: sous search:, à côté de keywords, que la 5.0 rend
obligatoire. Laissée à la racine, elle est refusée de même :
backoff: moved to 'search.backoff', move it in crawler.yml.
search:
keywords: [keroro, titar]
backoff:
base_seconds: 2.0
cap_seconds: 300.0
factor: 2.0
jitter_ratio: 0.3
Dans chaque cible de observability.notifications, renommez enfin la clé tag en audience, sans
changer sa valeur. Laissée telle quelle, elle est refusée en nommant la nouvelle :
observability.notifications[0]: key 'tag' was renamed 'audience', rename it in crawler.yml.
observability:
notifications:
- url: "discord://${DISCORD_WEBHOOK_ID}/${DISCORD_WEBHOOK_TOKEN}"
audience: operations
Dans matcher.yml, min et fuzz deviennent min_coverage et min_similarity, sur un token
coverage comme sur une référence { token: … }, et le paramètre {segment} des regex devient
{letter}. Dans la politique livrée, cela touche deux lignes : la regex de segment_id finit par
\\s*{letter}, et title_hit s'écrit ainsi. Un ancien nom est refusé en nommant le nouveau :
token 'title_hit': key 'min' was renamed 'min_coverage', rename it in matcher.yml.
Si vous avez ajouté un token attr_between, ses attributs size_mb et duration_sec deviennent
size_mib et duration_seconds. Ses bornes min et max ne changent pas.
4. Remplacez les fichiers compose par ceux du dossier deploy de la 5.0 : compose.yml à la
racine, et les trois fichiers de son dossier ed2k/ dans le vôtre.
rm compose.yml base.compose.yml gluetun.compose.yml
cp /chemin/vers/deploy-5.0/compose.yml .
cp /chemin/vers/deploy-5.0/ed2k/*.compose.yml ed2k/
Sur la pile VPN, remplacez dans compose.yml la ligne - ed2k/direct.compose.yml par
- ed2k/vpn.compose.yml. Plus de -f ensuite : les mêmes commandes servent les deux variantes. Si
vous aviez changé un port, reportez-le : le 8080 est dans compose.yml, les ports d'aMule dans
ed2k/direct.compose.yml (le 4711 seul dans ed2k/vpn.compose.yml).
5. Réglez VPN_PORT_FORWARDING dans .env (variante VPN seulement). C'est désormais le seul
interrupteur du port-sync : si port_sync.enabled valait true, mettez on, sinon laissez off.
Si vous aviez changé gluetun_control_url ou l'un des deux intervalles, leurs variables sont dans
Devenir High-ID.
6. Démarrez.
docker compose up -d
docker compose ps # p2pwatch Up, ed2k Up (healthy) en ~30 s, plus ed2k-gluetun sous VPN
Un nœud direct dont la box redirige ses ports redirige désormais 4672/udp (Kad), et non plus
4662/udp. 4662/tcp ne change pas.
Ce qui change¶
- Le premier démarrage copie puis réécrit les deux bases : il garde
data/catalog.db.pre-0006.<heure>_UTCetdata/local.db.pre-0006.<heure>_UTC, puis migre. Une vingtaine de minutes sur un catalogue de 6 Go, dont huit pour la copie, pendant lesquelles le catalogue web ne répond pas. Ne redémarrez rien avant la fin, voir Limites connues. Faute de place dansdata/, le crawler refuse de démarrer sans rien écrire, voir Le déploiement bloque. - Deux journaux.
docker compose logs p2pwatchne montre plus que le crawler ; amuled et le port-sync écrivent dansdocker compose logs ed2k, amuleapi dansed2k/amule/amuleapi.log. - Sous la variante VPN, le crawler sort du tunnel. Seul
ed2kpasse pared2k-gluetun. Le crawler ne parle à aucun pair, et vos notifications partent de l'adresse de votre machine. - Le crawler joint amuleapi en direct, sans plus suivre
HTTP_PROXYniNO_PROXY: un proxy donné au conteneurp2pwatchne s'applique plus à cette connexion. - Une configuration invalide fait redémarrer
p2pwatchen boucle (Restarting) au lieu d'arrêter le conteneur. L'erreur est dansdocker compose logs p2pwatch. ${NOM}est résolu dans toute valeur texte decrawler.yml, et non plus seulement dansamule_api_password, les deux chemins de base,webui.amule_urlet les cibles de notification. Une variable absente de l'environnement est refusée où qu'elle soit, même dans une section désactivée, en nommant sa clé :search.keywords[0]: environment variable 'X' referenced but not set. Une valeur résolue reste du texte : un nombre ne peut pas venir de l'environnement.- La clé
node_iddecrawler.yml, jamais documentée, est refusée (node_id: was removed, ...) : l'identifiant du nœud est toujours celui quelocal.dbgarde. - Les métriques
emule_*deviennentp2pwatch_*. Les trois du port-sync (emule_port_sync_triggered,emule_port_mismatch,emule_high_id_recovered) disparaissent avec leurs notifications : un Low-ID se lit surp2pwatch_channel_connectable, et un canal non joignable depuis 5 minutes est notifié. - Le label
networkdevientchannelsurp2pwatch_searches,p2pwatch_observationsetp2pwatch_search_failures, qui gagnent aussi un labelclient. Adaptez les requêtes de vos tableaux de bord et de vos règles d'alerte. - Prometheus scrape le 8080.
/metricsest une route du catalogue web,http://<nœud>:8080/metrics, et plus rien n'écoute sur le 9090 : visez:8080dans votrescrape_config, et retirez"9090:9090"desports:si vous l'aviez publié. Le format passe d'OpenMetrics 1.0.0 au texte 0.0.4 : les séries gardent leurs noms, leurs labels et leurs valeurs, mais les échantillons_createddes compteurs disparaissent, et la famille d'un compteur prend son suffixe_total(# TYPE p2pwatch_searches_total counter). - Le catalogue web est toujours servi, et
/metricsavec lui. Retirer"8080:8080"desports:decompose.ymlle retire du réseau de l'hôte ; le réseau compose l'atteint toujours. S'il tombe, le crawl continue mais/metricsne répond plus. - La notification de démarrage devient
🟢 node online (download on), ou(download off), au lieu de🟢 instance online (mode full)ou(mode observer). - Dans
local.db, la tablebackfill_statedevientreevaluation_state, sa colonnepolicy_sha256devientpolicy_fingerprint, et la tablescheduler_statelaisse place àbackoff_states, une ligne par client ou canal en backoff. Les colonneslast_seen_atetlast_progress_atdedownloadsdeviennentlatest_seen_atetlatest_progress_at. Adaptez vos requêtes de la console SQL. Un backoff en cours à la mise à niveau est oublié : la recherche qu'il retenait repart aussitôt. - Dans le catalogue, la colonne
media_length_secdevientduration_seconds, désormais dansobservation_variants, et la colonneraw_metadeobservation_variantset desource_variantsdevientraw_metadata. Adaptez vos requêtes de la console SQL. - Dans le catalogue, le
raw_metadatad'un fichier devient un objet JSON, au lieu d'une liste de paires[nom, valeur]:codec,file_typeetcomplete_source_countà son premier niveau, et sousunmapped, toujours présent, ce que le réseau a envoyé sans qu'aucun champ ne le reprenne,mediacompris (media.titledevientunmapped.media.title). Adaptez vos requêtes de la console SQL :json_extract(raw_metadata, '$.unmapped.media.title'). - La taille d'un fichier n'est plus que dans
files.size_bytes:observation_variantsn'a pas de colonnesize_bytes, etdownloadsdelocal.dbperd la sienne. Adaptez vos requêtes de la console SQL : la taille d'une observation se lit parJOIN files USING (file_id). - Un instant stocké en entier de microsecondes depuis l'epoch porte son unité dans son nom :
observed_atdevientobserved_at_microsdansobservationsetsource_observationsdu catalogue, etstarted_atdevientstarted_at_microsdansbackoff_statesdelocal.db. L'instant d'une décision, jusqu'ici un texte ISO-8601 dansmatch_decisions.decided_at, devient lui aussi un entier de microsecondes,decided_at_micros, comme les quatre instants dedownloadsdanslocal.db:queued_at,completed_at,latest_seen_atetlatest_progress_atdeviennentqueued_at_micros,completed_at_micros,latest_seen_at_microsetlatest_progress_at_micros. Adaptez vos requêtes de la console SQL. - Chaque table du catalogue devient
STRICT: SQLite y refuse une valeur qui n'est pas du type de sa colonne. DesCHECKrefusent aussi une taille, une durée, un débit ou un nombre de sources négatif, untierautre quecatalog,notify,downloadetretracted, unconnectableautre que 0 ou 1, et un instant*_microshors de la plage que le crawler sait lire. Les migrations qui reconstruisent déjà ces tables les posent : la mise à niveau n'en reconstruit aucune de plus. Vos requêtes de la console SQL ne changent pas ;PRAGMA table_listmontre chaque tablestrict. La fusion refuse donc une source qui porte une telle valeur, voir Outils de catalogue. - Dans
local.db, la tablenode_runtimedevientnode_identity, une seule ligne : ses colonnesnode_idetcreated_at_microsremplacent les clésnode_idetcreated_at, l'instant devenant un entier de microsecondes. Adaptez vos requêtes de la console SQL. Unlocal.dbdontnode_runtimeporte unnode_idsans soncreated_atarrête la mise à niveau à sa migration0015(NOT NULL constraint failed: node_identity.created_at_micros) : les migrations d'avant restent appliquées, et le crawler ne démarre pas. Pour revenir à la 4.x, restaurez la copie delocal.dbqui porte le plus petit numéro (la copie d'avant migration). - Les paramètres d'URL de
/fileschangent de nom :qdevientsearch,targetdevienttarget_idetdirdevientdirection. Les anciens sont ignorés : mettez à jour vos favoris. Un?tier=inconnu est ignoré lui aussi, comme unsortou undirectioninconnu : la liste montre tous les fichiers au lieu de n'en montrer aucun. Après un redémarrage depuis le tableau de bord, l'adresse se termine par?done=restartedau lieu de?done=restart. - La recherche de
/fileset de/sourcesest littérale, et ignore la casse, les accents et les ligatures :%et_ne désignent plus qu'eux-mêmes, là où ils servaient de jokers (a_btrouvaitaxb.avi), etecolecommeÉCOLEtrouventécole,coeurtrouvecœur, là où seule la casse des lettres ASCII était ignorée. Un nom mal encodé (écolepourécole) ne se trouve que tel qu'il est écrit. - Le crawler se lance par sa sous-commande,
python -m p2pwatch crawl, que l'image lance d'elle-même. Sans sous-commande,python -m p2pwatch --config …sort en erreur (code 2) : adaptez lecommand:que vous auriez donné au servicep2pwatch. - La fusion des catalogues se lance par
python -m p2pwatch merge, au lieu depython -m p2pwatch.merge, qui n'existe plus. Ses options ne changent pas ; deux refus sont reformulés :destination already existsetdestination must be one of the source catalogs. Une sortie--outputqui est aussi l'une des sources est refusée (code 2) avant toute écriture, même avec--force, qui la fusionnait en elle-même : fusionnez dans ce catalogue par--into. validate-confignomme le fichier en cause :Invalid config: <fichier>: <clé>: <message>pour un contenu faux,error: <fichier>: <raison>sur une ligne pour un fichier absent, illisible ou qui n'est pas du YAML, les deux en code 1. Au démarrage, un tel fichier se litInvalid config, refusing to start: <fichier>: <raison>au lieu deunreadable YAML file: <fichier> (...).- Un texte qui n'est pas de l'UTF-8 valide est refusé, même écrit en échappement (
"\ud800"), où qu'il soit dans les trois fichiers, en nommant le fichier et sa clé :error: <fichier>: search.keywords[0] is not valid UTF-8. Une variable d'environnement quecrawler.ymlrésout est refusée en la nommant :Invalid config: <fichier>: search.keywords[0]: environment variable 'X' is not valid UTF-8. Un caractère de contrôle ("\t","\x1f","\x85") est refusé dans une lettre de segment detargets.ymlet dans un nom de règle dematcher.yml:Invalid config: <fichier>: segment letter 'a\x1fb' holds a control character. La 4.x les acceptait : un tel mot-clé arrêtait le crawler à sa première recherche, et un\x1fdans une lettre ou un nom de règle cassait la page des fichiers. - Toute erreur de
crawler.ymlcommence par sa clé,<clé>: <message>. Ces refus changent donc un peu de texte : une clé retirée ou déplacée (port_sync: was removed, ...), une clé absente (catalog_db_path: missingau lieu dekey 'catalog_db_path' missing,search.keywords: missingau lieu desearch: key 'keywords' missing), une section qui n'est pas un dictionnaire (search.backoff: mapping expected, got listau lieu desection 'search.backoff': ...),search.backoff.factor: must be ≥ 1 (growth), got 0.5etsearch.backoff.cap_seconds: 1.0 < base_seconds (10.0): cap below floor. - Quelques lignes de journal et noms de loggers changent : le crawler journalise sous le nom
du module qui écrit la ligne, qui change avec lui ; filtrez sur le message plutôt que sur le
logger. Les échecs d'écriture de la boucle de téléchargement disent
store failureau lieu derepo failure. Un catalogue web qui ne démarre pas le dit désormais parwebui degraded. Les lignes de démarragecrawler startedetdownload enabledjournalisent sousp2pwatch.application.crawler_lifecycle, la version au démarrage sousp2pwatch.composition.crawler_composition, le dispatcher des événements sousp2pwatch.application.steps.observability_dispatcher, la réévaluation du catalogue sousp2pwatch.application.steps.catalog_reevaluatoret les tâches de recherche sousp2pwatch.application.steps.search_task_runner. La recherche ne journalise plusclient <nom> connectedni l'avertissementclient <nom> unreachable (...): reconnect backoff ...: la session amuleapi s'ouvre d'elle-même au premier appel, et un amuled injoignable reste signalé parclient <nom> unreachable (...), client down, backoff .... Le port-sync,mulewatch.application.port_sync_loopen 4.x, journalise lui aussi sous le nom du module qui écrit la ligne :p2pwatch_amule.application.port_sync_lifecycle, ses commandes s6 sousp2pwatch_amule.adapters.driven.s6_service_supervisoret ses échecs à joindre gluetun sousp2pwatch_amule.adapters.driven.gluetun_forwarded_port_client. - La documentation est désormais sur https://mission-titar.github.io/p2pwatch/.
Retour arrière¶
docker compose down
mv ed2k/amule ed2k/downloads . && rm -r ed2k data
cp -a ../mulewatch.4x.bak/. .
docker compose up -d # ou -f gluetun.compose.yml
Votre ancien compose.yml tire toujours ghcr.io/mission-titar/mulewatch:latest, qui reste la
4.1.0. Le catalogue revient à son état d'avant la migration.
Sans sauvegarde de data/, les copies d'avant migration en tiennent lieu ; votre
configuration, elle, vient de votre sauvegarde. Gardez alors data/ : retirez-le du rm -r
ci-dessus. Puis, avant le docker compose up -d, cherchez les copies pre-0006 de chaque base :
Restaurez seulement une base qui a une copie pre-0006, la plus récente : renommez-la au nom de la
base, qu'elle remplace, puis supprimez les fichiers -wal et -shm de la base, qui seraient sinon
rejoués sur la copie. Une ligne par base, que vous ne lancez que si sa copie existe :
mv data/catalog.db.pre-0006.<heure>_UTC data/catalog.db && rm -f data/catalog.db-wal data/catalog.db-shm
mv data/local.db.pre-0006.<heure>_UTC data/local.db && rm -f data/local.db-wal data/local.db-shm
Une base sans copie pre-0006 n'a jamais été migrée : laissez-la telle quelle. C'est le cas de
local.db quand la migration du catalogue a échoué, car le crawler ouvre le catalogue d'abord.