Nodes and Multiple Machines FR - Kheopsian/Hydranos GitHub Wiki

🇬🇧 English · 🇫🇷 Français

Nœuds et plusieurs machines

S'applique à Hydranos 4.4.

Cette page vous concerne si vous faites tourner Hydranos sur plusieurs machines et voulez voir et déplacer les torrents entre elles depuis une seule interface web. Elle explique comment ajouter une machine (enrôler ou déclarer), ce que le script d'enrôlement lui fait, et quelles actions fonctionnent sur les torrents d'une autre machine. Pour savoir si vous avez vraiment besoin de plusieurs machines : Topologies de déploiement.

Ce qu'est un nœud

Un nœud est une autre instance Hydranos complète : le même programme hydranos, avec son propre fichier de configuration, son store, ses moteurs, sa clé API et son interface web. Il n'existe pas de mode nœud particulier ni de démon de nœud séparé.

  • L'instance sur laquelle vous ajoutez un nœud est son contrôleur. Le contrôleur conserve le nom, l'URL et la clé API du nœud dans son propre store (table nodes dans hydra.db), pas dans default.toml. La clé est stockée en clair.
  • La relation est à sens unique. Le contrôleur appelle l'API HTTP normale du nœud avec la clé API du nœud (en-tête X-API-Key). Le nœud n'appelle jamais le contrôleur après l'enrôlement, et il ne stocke ni l'adresse ni la clé du contrôleur.
  • Un nœud continue de tourner seul. Si le contrôleur est arrêté ou le supprime, le nœud continue de seeder et sa propre interface continue de fonctionner. Retirer un nœud sur le contrôleur supprime seulement la ligne (Remove node {name}? Its own data is untouched.).
  • Les données des torrents ne passent jamais par l'API. Quand un torrent change de machine, seul le fichier .torrent passe par HTTP. Les octets passent par BitTorrent entre les ports d'écoute des deux moteurs, et chaque pièce est vérifiée par hash.
  • Chaque appel du contrôleur vers un nœud (sonde de santé, liste, action relayée, handoff, rapatriement, rotation de clé) a un délai d'expiration HTTP de 4 secondes.
  • Ces appels n'utilisent jamais la route egress du démon, son réglage [proxy] ni un tunnel, même kill switch armé (un HTTP_PROXY/HTTPS_PROXY défini dans l'environnement du démon n'est pas désactivé, le comportement par défaut du client HTTP peut donc encore l'appliquer) : ils sortent par la route normale de l'hôte (VPN & sortie réseau).

Il existe deux façons d'ajouter un nœud :

+ Enrol a machine + Declare an existing one
À utiliser pour une machine Linux neuve sans rien d'installé une instance qui tourne déjà (tout OS, Windows compris)
Ce que vous tapez une commande curl … | sh, sur la nouvelle machine nom, URL et clé API du nœud, sur le contrôleur
Qui installe Hydranos le script (Docker, un binaire avec une unité système, ou un binaire dans le home d'un utilisateur) vous, au préalable (Docker, Linux, Windows)
Le contrôleur teste-t-il d'abord le nœud non oui, il refuse un nœud qui ne répond pas

Enrôler une nouvelle machine

Sur le contrôleur, ouvrez l'onglet Nodes et cliquez sur + Enrol a machine. Le contrôleur crée un jeton à usage unique et affiche une commande comme celle-ci :

curl -fsSL http://192.0.2.10:8199/install.sh | sh -s -- --register-to http://192.0.2.10:8199 --token 0123456789abcdef0123456789abcdef
  • Le jeton fait 32 caractères hexadécimaux, est valable 30 minutes et utilisable une seule fois. La durée est fixée dans le code et ne peut pas être modifiée.
  • L'adresse dans la commande est celle que votre navigateur a utilisée pour joindre le contrôleur (en-tête HTTP Host). Si c'est localhost ou un nom que la nouvelle machine ne sait pas résoudre, ouvrez l'interface par une adresse qu'elle peut joindre et cliquez à nouveau.
  • La commande utilise toujours http://, même derrière un reverse proxy HTTPS : le jeton et la nouvelle clé API du nœud circulent en clair.
  • Copier (Copy) copie la commande ; Fermer (Close) masque la carte (le jeton reste valable).

Lisez ceci avant de rediriger le script vers sh

Le script est le fichier install.sh à la racine du dépôt, intégré au binaire et servi sans authentification sur /install.sh. Exécutez-le sur une machine Linux. Il installe Hydranos de l'une de trois façons, choisie dans cet ordre :

Mode Quand Ce qu'il installe
Docker docker est dans le PATH un conteneur
système pas de Docker, lancé en root /usr/local/bin/hydranos et une unité systemd
utilisateur pas de Docker et pas root (un compte de seedbox), ou --user ~/.local/bin/hydranos et une unité systemd --user

Le mode Docker écrit par défaut dans /opt/hydranos : lancez-le en root, ou passez un --dir où vous pouvez écrire. Voici ce que fait le script :

  1. Il lit ses options : --register-to et --token (toutes deux obligatoires), --name (par défaut : la sortie de hostname -s), --port (par défaut 8199), --dir (voir l'étape 4), --container (Docker seulement, par défaut hydranos), --image (Docker seulement, voir l'étape 5), --user (force le mode utilisateur). Toute autre option l'arrête avec le code de sortie 2.

  2. Il trouve l'adresse que le contrôleur utilisera pour joindre cette machine : l'adresse source de ip route get <controller>. En cas d'échec, il s'arrête.

  3. Il choisit la version à installer : celle du contrôleur, lue sur le /health du contrôleur (tag v<version>), pour qu'un nœud tourne sur la même version que son contrôleur même s'il est enrôlé des mois plus tard. La variable d'environnement HYDRANOS_VERSION_PIN la remplace par un tag comme v4.4.0 (curl … | HYDRANOS_VERSION_PIN=v4.4.0 sh -s -- …). Si aucune des deux ne donne de version, Docker utilise l'image :latest et les modes binaires demandent à GitHub la dernière version.

  4. Il crée les dossiers de configuration et de données :

    Mode Fichier de configuration Dossier de données data_dir écrit dans le fichier
    Docker <dir>/default.toml (/opt/hydranos) <dir>/data /configs, le dossier tel que le conteneur le voit
    système <dir>/default.toml (/opt/hydranos) <dir>/data <dir>/data
    utilisateur $XDG_CONFIG_HOME/hydranos/default.toml (par défaut $HOME/.config/hydranos) $XDG_DATA_HOME/hydranos (par défaut $HOME/.local/share/hydranos) le dossier de données

    En mode utilisateur, --dir remplace les deux : <dir>/default.toml et <dir>/data. Si le fichier de configuration n'existe pas, il en écrit un avec une nouvelle clé API aléatoire (48 caractères hexadécimaux) :

    [daemon]
    api_host = "0.0.0.0"
    api_port = 8199
    api_key = "<48 hex characters>"
    data_dir = "<see the table>"
    
    [race]
    listen_port = 16371
    enable_ipv6 = true
    
    [hoard]
    listen_port = 16372
    enable_ipv6 = true

    Un fichier existant est conservé et son api_key réutilisée. En mode utilisateur, un nouveau fichier n'est lisible que par son propriétaire (mode 600) : sur une seedbox partagée, la clé est toute l'autorité du nœud. Les ports BitTorrent sont 16371 (race) et 16372 (hoard), pas les habituels 16171/16172 : ouvrez-les (TCP et UDP).

  5. Mode Docker. Si un conteneur portant le nom --container existe déjà, le script ne le remplace que s'il porte le label io.hydranos.enrolled=true, c'est-à-dire si ce script l'a créé. Tout autre conteneur arrête le script avec a container named hydranos already exists and was not made by this script; pass --container <another-name>, or remove it yourself. Ensuite il exécute :

    docker run -d --name hydranos --label io.hydranos.enrolled=true --restart unless-stopped --network host \
      --stop-timeout 150 \
      -v /opt/hydranos:/configs -v /opt/hydranos/data:/data \
      ghcr.io/kheopsian/hydranos:v<controller version> --config /configs/default.toml

    --image remplace le nom d'image entier. Le conteneur utilise le réseau de l'hôte, donc les ports 8199, 16371 et 16372 sont ceux de l'hôte. Le dossier de configuration est monté sur /configs, et le store se trouve dans /opt/hydranos sur l'hôte parce que data_dir = "/configs".

  6. Mode système (root, pas de Docker). Il télécharge hydranos-<tag>-linux-amd64.tar.gz (ou -arm64 ; toute autre architecture l'arrête) depuis la release GitHub, installe /usr/local/bin/hydranos, écrit /etc/systemd/system/hydranos.service (réécrit à chaque exécution ; l'unité tourne en root, avec Restart=always, TimeoutStopSec=150 et LimitNOFILE=1000000), puis lance systemctl enable et systemctl restart. Sans systemctl, il affiche la commande pour démarrer Hydranos et se termine en erreur, avant l'enregistrement.

  7. Mode utilisateur. Il installe le binaire de la même archive dans $HOME/.local/bin/hydranos, puis :

    • si systemctl --user fonctionne dans cette session, il écrit $XDG_CONFIG_HOME/systemd/user/hydranos.service (par défaut $HOME/.config/systemd/user ; Restart=always, TimeoutStopSec=150 ; pas de LimitNOFILE, l'unité relève à la place la limite de fichiers ouverts au maximum du compte), l'active et la redémarre. Il tente ensuite loginctl enable-linger pour le compte. Si le lingering reste désactivé, il avertit lingering is off for <user>: the node stops when you log out and stays down after a reboot. et vous demande de faire lancer loginctl enable-linger <user> par l'administrateur de l'hôte ;
    • sinon (pas de gestionnaire utilisateur, par exemple une session ssh sans pam_systemd), il démarre Hydranos avec nohup, écrit son PID dans <data>/hydranos.pid et affiche la commande pour le relancer : rien ne le redémarre après un plantage ou un redémarrage de la machine. Une nouvelle exécution arrête le processus précédent (même binaire seulement) et l'attend jusqu'à 150 s.
  8. Il interroge http://127.0.0.1:<port>/health jusqu'à 60 fois, à une seconde d'intervalle, chaque essai disposant de 2 s : entre 1 et 3 minutes environ. Un essai ne compte que si /health répond et n'indique plus "status": "starting" (le port s'ouvre alors que le catalogue se charge encore). Si le nœud n'y arrive jamais, il s'arrête sans s'enregistrer et indique où se trouvent les journaux.

  9. Il envoie {token, name, url, api_key} à /api/nodes/register sur le contrôleur, avec url = http://<address from step 2>:<port>.

  10. Si l'enregistrement échoue, il affiche l'URL et la clé API pour que vous puissiez ajouter le nœud à la main avec + Declare an existing one, et se termine en erreur. Le nœud reste installé et en marche.

Un nom déjà déclaré sur le contrôleur est refusé (HTTP 409) avant que le jeton soit consommé, avec a node named <name> already exists; the token is still valid, try --name : relancez la même commande avec --name <another name>.

Autres limites du script :

  • Linux uniquement (sh, ip, getent, curl, /dev/urandom, et tar hors Docker). Il n'existe pas de script d'enrôlement pour Windows ni macOS ; déclarez plutôt ces instances.
  • Une route uniquement IPv6 vers le contrôleur produirait une URL où l'adresse IPv6 est écrite sans crochets, que le contrôleur ne peut pas utiliser. Utilisez l'IPv4, ou déclarez le nœud à la main.
  • Relancer la commande sur une machine déjà enrôlée conserve sa configuration et sa clé. Elle réinstalle à la version du contrôleur (Docker : recrée son propre conteneur ; système : remplace le binaire et réécrit l'unité), puis l'enregistrement se termine par un 409 (nom déjà pris), sans consommer le jeton : la ligne du nœud existe déjà.

Noms des nœuds

Un nom peut contenir - (seedbox-de, l'exemple affiché dans le formulaire de déclaration, ou une valeur par défaut de hostname -s). Une ligne distante est étiquetée <node>-<engine>, et l'interface comme le serveur comparent cette étiquette aux noms déclarés, du plus long au plus court : avec un nœud nas-2, la ligne nas-2-hoard est le nœud nas-2, moteur hoard. Un nom ne peut pas contenir / ni .. : c'est un segment de chemin du lien Open.

La séquence d'enrôlement

sequenceDiagram
    actor Admin
    participant C as Contrôleur
    participant N as Nouvelle machine
    Admin->>C: onglet Nodes, + Enrol a machine
    C->>C: POST /api/nodes/enrol, jeton valable 30 min, usage unique
    C-->>Admin: commande curl construite depuis l'en-tête Host du navigateur
    Admin->>N: colle la commande dans un shell
    N->>C: GET /install.sh (sans authentification)
    C-->>N: le script
    N->>N: adresse propre = source de ip route get contrôleur
    N->>C: GET /health (sans authentification)
    C-->>N: la version du contrôleur, la version à installer
    N->>N: écrit default.toml avec une nouvelle clé API (si absent)
    N->>N: conteneur Docker (ne remplace qu'un conteneur étiqueté par le script),<br/>ou binaire + unité système (root), ou binaire + unité utilisateur (--user)
    N->>N: interroge GET /health sur 127.0.0.1 jusqu'à ce qu'il ne soit plus starting, 60 essais
    N->>C: POST /api/nodes/register avec token, name, url, api_key
    alt token, name ou url manquant, URL loopback, ou / ou .. dans le nom
        C-->>N: 400, jeton non consommé
    else nom déjà connu (vérifié avant le jeton)
        C-->>N: 409, jeton non consommé, essayez --name
    else jeton inconnu, déjà utilisé ou expiré
        C-->>N: 401
    else nom pris entre-temps (revérifié après le jeton)
        C->>C: jeton consommé
        C-->>N: 409, nœud existant non écrasé
    else accepté
        C->>C: jeton consommé, nœud enregistré, nœud non sondé<br/>une api_key vide est acceptée
        C-->>N: 200
    end
    opt enregistrement échoué
        N-->>Admin: affiche URL et clé API pour + Declare an existing one
    end
    Admin->>C: ouvre l'onglet Nodes plus tard
    C->>N: GET /api/status et GET /api/engines (sonde en direct)
Loading

Déclarer une instance existante

Utilisez + Declare an existing one pour une instance déjà installée : Docker, Windows, une machine où le script a échoué, ou une machine 3.x mise à jour.

  1. Sur le nœud, trouvez sa clé API (Config › Paramètres avancés (default.toml) (Advanced settings (default.toml)) › Général (General) › api_key, ou [daemon] api_key dans son default.toml). Voir Sécurité & accès.
  2. Sur le contrôleur, onglet Nodes, + Declare an existing one, puis remplissez :
    • Nom (Name) : sans / ni .. (voir Noms des nœuds plus haut).
    • URL : l'origine du nœud, telle que les autres machines la joignent, par exemple http://198.51.100.5:8199. Un / final est retiré.
    • API key (the remote's own) : la clé du nœud, pas celle du contrôleur.
  3. Cliquez sur Test. Il sonde le nœud sans rien enregistrer. En cas de succès, il affiche Hydranos <version>, <engines>, <n> torrents. En cas d'échec, il affiche la raison (voir le tableau des statuts ci-dessous).
  4. Cliquez sur Ajouter (Add). Le contrôleur sonde à nouveau et refuse (HTTP 400) si :
    • le nom ou l'URL est vide, ou le nom contient / ou .. ;
    • l'hôte de l'URL est une adresse loopback (127.x.x.x, localhost, ::1, 0.0.0.0) : Open y envoie le navigateur, donc les autres machines doivent pouvoir l'utiliser ;
    • la sonde échoue.

Ajouter (Add) avec un nom qui existe déjà remplace quand même son URL et sa clé, sans demander. Pour modifier un nœud, utilisez plutôt Modifier (Edit) sur sa ligne (voir plus bas).

Une URL https:// est acceptée (pour un nœud derrière un reverse proxy). Ce cas n'a pas été testé pour la 4.4, et un handoff vers un tel nœud risque de ne pas l'atteindre (voir Handoff ci-dessous).

L'onglet Nodes

L'onglet se charge une fois à son ouverture. Il ne se rafraîchit pas tout seul.

  • This node : les moteurs locaux (colonnes Engine, Rôle (Role), Listen port, Interface, IPv6, Socket, Torrents, Actions) et + Add an engine. Les moteurs sont décrits dans Moteurs : race & hoard.
  • Other nodes : une ligne par nœud déclaré, sondé en direct à chaque chargement de l'onglet (tous les nœuds en même temps). Colonnes : Nom (Name), URL, Version, Engines (d'après GET /api/engines du nœud), Torrents (somme sur tous ses moteurs), Statut (Status), Actions. Sans aucun nœud : No other node.
    • Open (seulement quand le nœud est en ligne) ouvre l'interface du nœud dans un nouvel onglet, déjà connectée : le contrôleur redirige le navigateur vers <node URL>/#key=<node API key>, et la page du nœud déplace la clé dans le stockage de son propre navigateur.
    • Modifier (Edit) change le nom, l'URL ou la clé du nœud (voir Modifier un nœud).
    • Changer la clé (Rotate key) (grisé tant que le nœud est hors ligne) donne une nouvelle clé API au nœud (voir Changer la clé d'un nœud).
    • Remove supprime la ligne sur le contrôleur. Rien ne se passe sur le nœud. Un déplacement vers ce nœud encore en attente dans l'onglet Jobs échoue alors, et la copie locale est conservée.
    • Version est seulement affichée. Rien ne compare la version du contrôleur à celle du nœud, et aucune route ne refuse un écart. Les versions mélangées ne sont pas testées : gardez toutes les machines sur la même release.

Valeurs de statut (les messages d'erreur viennent du serveur et s'affichent tels quels, en français sans accents) :

Statut Signification Ce qu'il faut vérifier
online /api/status a répondu en JSON —
timeout pas de réponse en 4 s nœud arrêté, pare-feu, mauvais port
connexion refusee la connexion n'a pas pu être établie : refusée, échec DNS, pas de route, négociation TLS port, nom d'hôte, pare-feu, https ou http
injoignable la requête a échoué après l'établissement de la connexion reverse proxy, nœud en redémarrage
cle API refusee le nœud a répondu 401 ou 403 la clé du nœud a changé : déclarez-le à nouveau avec la nouvelle clé
HTTP <code> tout autre statut d'échec un reverse proxy entre le contrôleur et le nœud
une erreur indiquant que la réponse ne vient pas d'Hydranos 200 mais pas du JSON autre chose écoute sur cette URL

Il n'existe pas d'interrupteur « activé » : l'indicateur enabled du store n'est jamais mis à false.

Modifier un nœud

Modifier (Edit) ouvre le formulaire de déclaration rempli avec le nom et l'URL du nœud. Le bouton s'intitule Enregistrer (Save), et le champ API key est vide avec l'indication inchangée (unchanged) : la clé stockée n'est jamais envoyée au navigateur, et un champ vide la conserve. Enregistrer envoie PATCH /api/nodes/<current name> :

  • Une nouvelle URL ou une nouvelle clé est d'abord sondée, comme pour Ajouter : refusée (400) si la sonde échoue ou si l'hôte est une adresse loopback. Le formulaire envoie toujours l'URL, donc la sonde a toujours lieu : un nœud hors ligne ne peut pas être renommé depuis l'interface (l'API accepte {"name": …} seul, sans sonde).
  • Un nouveau nom suit les mêmes règles que pour Ajouter (pas de / ni ..). Un nom déjà pris est refusé avec un 409 a node named <name> already exists. Si l'URL ou la clé a changé dans le même enregistrement, elles sont déjà stockées quand ce 409 revient.
  • Les déplacements vers ce nœud encore en attente dans l'onglet Jobs suivent le nouveau nom.
  • Test dans ce formulaire sonde avec le champ de clé tel que saisi : laissé vide, il teste sans clé.

Changer la clé d'un nœud

Changer la clé (Rotate key) demande Changer la clé API de {name} ? (Rotate the API key of {name}?). Ensuite :

  1. Le contrôleur demande une nouvelle clé au nœud (POST /api/auth/api-key/rotate sur le nœud, avec la clé actuelle). Le nœud la génère et la garde en attente, en mémoire, pendant 10 minutes ; la clé actuelle fonctionne toujours.
  2. Le contrôleur confirme en présentant la nouvelle clé (POST /api/auth/api-key/confirm). Le nœud l'écrit dans [daemon] api_key de son default.toml et refuse l'ancienne clé dès la requête suivante. Les sessions de navigateur connectées par mot de passe sur le nœud restent valables.
  3. Le contrôleur stocke la nouvelle clé et l'affiche une fois (Nouvelle clé API de {name} (New API key of {name}) — Affichée une seule fois. Copiez-la pour les clients de ce node qui utilisaient l'ancienne clé.). Donnez-la à tout ce qui utilisait l'ancienne clé : Sonarr, Radarr ou autobrr pointés sur le nœud, scripts. Open remet la nouvelle clé au navigateur.

Erreurs : the node is unreachable, the node refused the current key: declare it again with its key (corrigez avec Modifier), the node is too old to rotate its key (needs 4.4). Si le contrôleur s'arrête entre les deux étapes, le nœud garde son ancienne clé et oublie celle en attente après 10 minutes ou un redémarrage.

Ce qui fonctionne entre nœuds

Ce que le contrôleur fusionne :

  • La liste Hoard (/api/hoard/page) affiche les lignes de chaque nœud à côté des lignes locales. La colonne Emplacement (Location) affiche local-<engine> pour une ligne locale et <node>-<engine> pour une ligne distante (le champ de l'API s'appelle toujours agent, un nom hérité). La fusion propre au nœud est incluse, donc les moteurs supplémentaires de rôle hoard d'un nœud apparaissent aussi. Un nœud en erreur ou qui met plus de 4 s est écarté de la page sans avertissement. Les compteurs des puces de filtre sont additionnés sur tous les nœuds.
  • Ctrl+A sur la liste Hoard (« tout ce que le filtre retient ») inclut les lignes distantes.
  • L'onglet Race n'affiche pas les lignes distantes : il lit uniquement le moteur race local.

Le tableau ci-dessous donne le résultat de chaque action du menu contextuel depuis l'interface du contrôleur. « Relayée » signifie que le contrôleur envoie l'action au nœud qui détient la ligne, sur la route du nœud lui-même pour la copie de ce moteur ; un refus revient sous la forme <node>: <the node's error>. « Ignorée » signifie que la tâche groupée indique {n} absents de ce nœud ({n} not on this node) (ou {n} non concernés ({n} not applicable) pour une ligne race) et ne fait rien. Les tâches groupées et leur panneau de progression sont décrits sur Jobs, actions groupées & déplacement.

Action Ligne locale Ligne distante (<node>-<engine>)
Arrêter (Stop) / Démarrer (Start) fonctionne relayée (POST /api/engines/<engine>/pause sur le nœud)
Re-annoncer (Reannounce) fonctionne relayée
Vérifier à nouveau (Recheck) (affichée quand la sélection contient une ligne hoard) fonctionne, lignes race comprises relayée
Retirer le torrent (Remove torrent) / Retirer + supprimer les fichiers (Remove + delete files) fonctionne, sur la copie sélectionnée seulement relayée : retire la copie du nœud dans ce moteur
Changer de catégorie › (Change category ›) (étiquette seulement) fonctionne relayée
Changer de catégorie + déplacer les fichiers › (Change category + move files ›) fonctionne (job ou graduation) relayée : le nœud déplace ses propres fichiers (un job sur le nœud). Un refus pour cause de liens physiques revient comme une erreur, sans la demande de consentement
Déplacer vers un dossier… (Set location…) fonctionne relayée, comme ci-dessus
Limiter le débit… (Limit rate…), Téléchargement séquentiel (Sequential download), Première/dernière pièce d'abord (First/last piece first) fonctionne relayée
Modifier les tags › (Edit tags ›), Forcer le téléchargement (Force download) / Ne plus forcer (Stop forcing) fonctionne sur les lignes hoard ignorée
Limites de partage… (Share limits…) fonctionne ignorée
Duplicate to engine › local-<id> (same files, second identity) fonctionne non proposée
Move to engine › local-<id> (here, no copy) fonctionne non proposée
Duplicate to engine › / Move to engine › <node>-<engine> (handoff) fonctionne non proposée
local-<id> (bring it here) (rapatriement) non proposée fonctionne ; proposée sous Duplicate comme sous Move, et la copie distante est toujours conservée
Move to engine › <node>-<engine> (there, no copy) non proposée fonctionne, entre deux moteurs du nœud qui détient la ligne
Panneau de détail, Edit trackers fonctionne le détail est cherché localement et ne se charge pas ; Edit trackers modifie la copie de cette instance pour le même hash, s'il y en a une
Exporter (Export) (zip, liste de hashs, CSV) fonctionne les fichiers .torrent viennent uniquement du store local ; les hashs présents seulement à distance vont dans missing.txt dans le zip

Pour les éléments ignorés, cliquez sur Open dans l'onglet Nodes et faites-le dans l'interface du nœud. Une ligne distante dont le nœud n'est plus déclaré échoue avec no declared node holds <label>.

Bug connu en 4.4 : Edit trackers dans le panneau de détail d'une ligne d'un autre nœud semble modifier le torrent de ce nœud — le détail d'une ligne distante ne se charge pas, et le bouton modifie la copie de cette instance pour le même hash, s'il y en a une — cliquez sur Open et modifiez les trackers dans l'interface du nœud.

Le groupe Node (Duplicate to engine ›, Move to engine ›) apparaît dès qu'un nœud est déclaré ou que cette instance a deux moteurs locaux (toute installation par défaut a race et hoard). Le sélecteur de moteur liste d'abord les moteurs de cette instance, puis une section par nœud ; un nœud hors ligne affiche offline, un nœud sans moteur no engine, et si rien ne s'applique vous voyez Nowhere else to send it. Une sélection qui couvre plusieurs nœuds, ou qui mélange lignes locales et distantes, n'obtient aucune cible.

Handoff, rapatriement et déplacement

Handoff : pousser un torrent local vers un nœud

Sélectionnez des lignes locales, puis Duplicate to engine › ou Move to engine › et choisissez <node>-<engine>.

  1. Si le torrent a une catégorie, le contrôleur demande au nœud ses catégories (un torrent sans catégorie, ou un nœud dont la liste de catégories ne peut pas être lue, saute cette vérification). Si le nœud en a, mais pas celle-ci, le handoff échoue et les liste : créez la catégorie sur le nœud (avec son propre chemin de sauvegarde) et réessayez.
  2. Il envoie le .torrent au nœud (POST /api/torrents/upload, avec la catégorie et le moteur choisi). Le nœud le place comme pour tout ajout (Catégories & routage).
  3. Il indique au nœud où se trouvent les données (POST /api/torrents/<hash>/peers avec auto:<port>). Le nœud remplace auto par l'IP d'où vient la requête et se connecte au moteur du contrôleur sur ce port. Si le nœud est derrière un reverse proxy, cette IP est celle du proxy et la connexion ne mène nulle part, alors que la tâche indique quand même un succès.
  4. Le nœud télécharge les données depuis le contrôleur par BitTorrent.

Duplicate s'arrête là : les deux instances gardent une copie, et la tâche groupée indique un succès. Move (then: remove) crée en plus un job handoff sur le contrôleur, et la tâche groupée indique {n} envoyé(s) ; la copie locale part quand le node l'a en entier (Jobs) ({n} sent; the local copy goes once the node holds it (Jobs)). Dans l'onglet Jobs (Jobs, actions groupées & déplacement), le job affiche l'état En attente du node (Waiting for the node), la destination <node>-<engine> et la progression du nœud :

  • Toutes les 30 s, le contrôleur demande au nœud la progression du torrent (GET /api/engines/<engine>/page?search=<hash> sur le nœud ; tous les moteurs du nœud quand aucun n'est nommé). N'importe quel moteur convient, y compris les supplémentaires comme vpn1.
  • Quand le nœud indique 100 %, le job est Terminé (Done) et le contrôleur retire le torrent de tous les moteurs locaux, avec ses fichiers (les fichiers qu'un autre torrent lit encore sont conservés).
  • Une interrogation échouée est ignorée et les interrogations continuent. L'échéance, 6 heures après le handoff, est stockée dans le job : un redémarrage du contrôleur reprend l'attente. Passé ce délai, le job échoue avec the node did not report a full copy within 6 hours; the local copy is kept.
  • Si le nœud est retiré entre-temps, le job échoue avec the node <name> was removed before it confirmed; the local copy is kept. Un renommage par Modifier (Edit) est suivi.
  • Annuler (Cancel) dans l'onglet Jobs arrête l'attente : les deux copies sont conservées.
  • Un second déplacement du même torrent vers un nœud pendant qu'un autre attend est refusé (409) : a move of this torrent to a node is already waiting; see Jobs.
sequenceDiagram
    actor Admin
    participant C as Contrôleur
    participant N as Nœud
    Admin->>C: Move to engine, choix de node-hoard
    opt le torrent a une catégorie
        C->>N: GET /api/categories
    end
    alt le nœud a des catégories, mais pas celle-ci
        C-->>Admin: erreur listant les catégories du nœud
    else catégorie connue, nœud sans catégorie, pas de catégorie, ou appel des catégories échoué
        C->>N: POST /api/torrents/upload avec .torrent, catégorie, moteur
        C->>N: POST /api/torrents/HASH/peers avec auto:PORT
        C->>C: job handoff, état en attente, échéance dans 6 h
        C-->>Admin: tâche groupée : envoyé, la copie locale part quand le nœud l'a (Jobs)
        N->>N: auto = IP de l'appelant, connexion mise en file
        N->>C: connexion BitTorrent au moteur du contrôleur
        C-->>N: pièces par BitTorrent, vérifiées par hash sur le nœud
        loop toutes les 30 s tant que le job attend, reprise après un redémarrage
            C->>N: GET /api/engines/ENGINE/page?search=HASH
            Note over C,N: une interrogation échouée est ignorée, les interrogations continuent
        end
        alt le nœud indique une progression de 1
            C->>C: job terminé, retire le torrent de tous les moteurs locaux, avec ses fichiers
        else échéance passée
            C->>C: job échoué, copie locale gardée
        else nœud retiré sur le contrôleur
            C->>C: job échoué, copie locale gardée
        else Annuler dans l'onglet Jobs
            C->>C: job annulé, les deux copies gardées
        end
    end
Loading

Rapatriement : ramener ici un torrent distant

Sélectionnez des lignes d'un seul nœud, puis Duplicate to engine › ou Move to engine › et choisissez local-<id> (bring it here). Le contrôleur :

  1. refuse si le moteur n'est pas un moteur local (400) ou si cette instance a déjà le torrent (409) ;
  2. télécharge le .torrent depuis le nœud (GET /api/torrents/<hash>/torrent) et lit le port d'écoute du moteur du nœud qui le détient (GET /api/engines) ;
  3. ajoute le torrent au moteur local choisi, sans catégorie, et se connecte au nœud sur ce port.

Les données arrivent ensuite par BitTorrent. Un rapatriement ne retire jamais rien sur le nœud : retirez sa copie dans l'interface du nœud (Open) si vous le souhaitez.

Déplacer entre les moteurs d'un nœud

Sur des lignes distantes, Move to engine › <node>-<engine> (there, no copy) demande au nœud de déplacer le torrent vers son autre moteur (POST /api/torrents/<hash>/engine sur le nœud). Rien ne traverse le réseau ; la réponse du nœud est transmise telle quelle.

Par l'API REST

Les mêmes opérations existent sous forme de routes sur le contrôleur : POST /api/nodes/:name/handoff ({info_hash, engine, category, then, from} ; avec then: "remove", la réponse porte l'identifiant du job), POST /api/nodes/:name/fetch ({info_hash, engine, from_engine, category}, from_engine vaut hoard par défaut), POST /api/nodes/:name/move-engine ({info_hash, engine}). Voir API REST.

Gestion des nœuds :

  • PATCH /api/nodes/:name : n'importe lesquels de {name, url, api_key}, au moins un (sinon 400 nothing to change: send name, url and/or api_key). Répond {"status":"ok","name":…,"health":…} ; 404 pour un nœud inconnu.
  • POST /api/nodes/:name/rotate-key : répond {"status":"ok","name":…,"api_key":<new key>,"health":…} ; 502 avec la raison quand le nœud ne change pas de clé.
  • Sur toute instance, les deux étapes d'une rotation : POST /api/auth/api-key/rotate (clé actuelle dans X-Api-Key ; répond {"pending_key", "expires_in": 600, "confirm"}), puis POST /api/auth/api-key/confirm avec la clé en attente dans X-Api-Key. Elles changent aussi la clé d'une instance qui n'est pas un nœud (Sécurité & accès).
curl -X POST http://192.0.2.10:8199/api/nodes/seedboxde/handoff \
  -H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"info_hash":"<40-hex info hash>","engine":"hoard","then":"keep"}'

Ce qui n'est jamais agrégé

Ces éléments restent propres à chaque instance, même avec des nœuds déclarés :

  • L'ajout de torrents : l'onglet + Ajouter un torrent (+ Add Torrent), les dossiers surveillés et le shim ajoutent uniquement aux moteurs locaux. Pour mettre un torrent sur un nœud depuis ici, faites le handoff d'un torrent que le contrôleur possède déjà.
  • Les totaux de l'en-tête et de la Vue d'ensemble, les débits, les records et les chiffres de performances.
  • L'onglet Race, l'onglet Trackers et les statistiques des trackers.
  • Le shim qBittorrent (/api/v2/*) : Sonarr, Radarr, autobrr et cross-seed ne voient que les torrents du contrôleur. Faites pointer chaque outil vers l'instance qui doit détenir ses torrents (Shim qBittorrent & automatisation).
  • Les workflows : ils s'exécutent sur l'instance où ils sont définis, sur ses propres torrents.
  • Les jobs, le drain, les catégories et la configuration.
  • MCP : les outils de lecture listent les lignes fusionnées, mais les outils d'écriture n'agissent que sur les torrents locaux (Serveur MCP).

Sécurité

  • Le contrôleur détient la clé API de chaque nœud, en clair dans son store. Quiconque possède la clé API ou l'identifiant de l'interface du contrôleur peut agir sur tous les nœuds, et Open remet la clé d'un nœud à tout navigateur connecté au contrôleur. La carte d'enrôlement affirme que le contrôleur « never holds a credential » (ne détient jamais d'identifiant) pour le nœud : c'est faux en 4.4. Ce qui est vrai : le contrôleur n'obtient jamais de shell ni d'accès SSH au nœud. Si la clé ou le store du contrôleur fuit, changez la clé de chaque nœud avec Changer la clé (Rotate key), ainsi que la clé du contrôleur lui-même (Sécurité & accès).
  • /install.sh et /api/nodes/register ne demandent pas de clé API ; register n'accepte qu'un jeton valide, non expiré et non utilisé (les jetons expirés restent dans le store, sans danger).
  • L'enrôlement et les appels aux nœuds passent en HTTP simple par défaut. Gardez les nœuds sur un réseau privé ou un VPN entre vos machines. Pour du HTTPS, placez le nœud derrière un reverse proxy et déclarez-le avec son URL https:// (voir la réserve sur le handoff plus haut). Voir Sécurité & accès.
  • Chaque nœud expose sa propre API sur le port 8199 (api_host = "0.0.0.0" avec le script d'enrôlement). Protégez-le par un pare-feu comme n'importe quelle autre instance.

Voir aussi

⚠️ **GitHub.com Fallback** ⚠️