Repository navigation
Releases: Pokled/nodyx
Release list
v2.13.0 : Solide, partout
Deux mois de travail depuis la 2.12.0, et un fil conducteur : que Nodyx tienne debout chez tout le monde, pas seulement sur nodyx.org. L'installeur a été relu ligne à ligne et en grande partie reconstruit, des défauts de sécurité ont été fermés, et une longue série de bugs trouvés en utilisant vraiment Nodyx au quotidien est corrigée. Avec, au passage, deux vraies nouveautés : les ambiances, et les extensions.
Si tu héberges une instance
Mets-la à jour. La mise à jour sauvegarde la base d'abord et ne coupe plus le site pendant la compilation :
sudo nodyx-updateSi ton instance a été installée avant cette version, lance aussi une fois la commande qui correspond à ton installation, pour appliquer les correctifs de sécurité qui touchent sa configuration (adresse réelle des visiteurs, secrets, tunnel) :
sudo bash /opt/nodyx/install.sh --upgrade # installation standard
sudo bash /opt/nodyx/install_tunnel.sh --repair # installation en tunnel (Cloudflare, Pangolin)Mot de passe administrateur perdu ? sudo nodyx-recover --reset <utilisateur> donne un lien de réinitialisation depuis le serveur. Un doute sur l'état de l'instance ? sudo nodyx-doctor.
Nouveau
- Les ambiances. L'apparence se règle en profondeur : 14 ambiances prêtes à l'emploi (Matrix, Cyberpunk, Gazette, Océan…), ou tes propres couleurs, avec un contraste vérifié et un brouillon que personne ne voit avant « Publier ». Sur grand écran, le contenu flotte sur le décor de ton instance, et un stylo permet de styler chaque zone directement sur le site.
- Les extensions. Nodyx peut être étendu par des widgets et des jeux tiers, installés en un clic depuis extensions.nodyx.org. Une extension tourne isolée : elle ne voit jamais les vrais comptes, son stockage est cloisonné, son accès au réseau est surveillé.
- Des jeux dans les salons vocaux. Une activité se lance depuis une galerie et s'installe dans le salon, pour jouer ensemble sans raccrocher.
- Le chinois simplifié, traduit à 100 %. Nodyx parle maintenant neuf langues, et les dates et les nombres suivent enfin la langue de chacun.
- Le thème clair va (presque) jusqu'au bout. Le chat, le fil d'actu, les membres, le forum entier, le wiki, l'agenda et les tâches suivent l'ambiance de l'instance au lieu de rester un bloc noir, en respectant les réglages de ta feuille (transparence, image). Les petits textes gris sont lisibles partout, et une couleur de pseudo trop pâle garde sa teinte, juste assez foncée pour se lire. La suite (profil, messages privés, accueil, administration) arrive.
- Une vitrine musicale,
/musique, gérée depuis l'administration, avec une attestation de provenance téléchargeable pour chaque morceau.
Sécurité
- Sur les installations standard, l'adresse réelle des visiteurs n'arrivait pas jusqu'à Nodyx : limitation de débit et bannissements ne fonctionnaient pas comme prévu. Corrigé, en standard comme en tunnel. C'est la raison de la commande ci-dessus.
- L'installeur ne peut plus enfermer l'administrateur hors de son serveur (pare-feu activé sans le bon port SSH), et n'efface plus les règles de pare-feu existantes.
- Les programmes téléchargés par l'installeur sont vérifiés par empreinte avant d'être installés.
- Plus aucun secret dans la ligne de commande des services ni en clair sur le disque : jeton de l'annuaire, secret TURN, jeton Cloudflare, mot de passe administrateur.
- Un chemin de fichier envoyé ne peut plus remonter hors de son dossier, et l'e-mail des membres bannis n'est plus renvoyé en clair à l'administration.
- Toutes les dépendances signalées sont à jour (Socket.IO, envoi de fichiers, e-mails, nettoyage du HTML, analyse des adresses…). Chaque alerte a été lue pour savoir si Nodyx était vraiment exposé, et les correctifs qui comptent sont prouvés par un test.
Installer et mettre à jour
- Une mise à jour ratée ne fait plus tomber le site : la nouvelle version se compile à côté, et le site ne bascule que si tout a réussi.
- La mise à jour installe Node.js 22 s'il manque : une instance installée en tunnel avant octobre pouvait être restée en Node 20, alors que le vocal exige 22.
- L'installeur fonctionne sans terminal (Ansible, cron) et sans secret en argument :
--yes,--network=relay,--admin-password-file. - « Non » veut dire non, un serveur web déjà présent n'est plus coupé d'office, le nom
ton-club.nodyx.orgest vérifié avant de commencer, Debian 13 minimale (sanssudo) est prise en charge. - La désinstallation est réécrite : elle enlève vraiment Nodyx, et seulement Nodyx.
Corrigé
- Canvas : les cadres, les formes avancées et les connecteurs disparaissaient au rechargement depuis la 2.2. Les retours à la ligne des notes sont respectés.
- Voix : un onglet fermé pendant un appel ne laisse plus de fantôme dans le salon, et quelqu'un qui arrive pendant un partage d'écran le reçoit enfin.
- Chat : un message supprimé disparaît vraiment du fil, au lieu de laisser une ligne vide.
- La première visite ne charge plus la page deux fois. Le forum n'écrase plus les noms de catégories sur écran de bureau. Le choix de la langue ramène là où on était.
- Éditeur, réglages audio, affichage sur téléphone : une longue liste de petites gênes, détaillée dans le journal des versions.
Et autour
Les README sont réécrits en anglais, en français et en espagnol, avec des captures récentes. nodyx.dev et start.nodyx.org accueillent les moteurs de recherche comme les assistants IA, et un lien partagé vers ces sites affiche enfin une vraie image.
Merci à davelowe1977, dont le signalement sur Debian 13 (#784) a lancé la relecture complète de l'installeur, à oorpheas pour ses signalements et pour le portugais du Brésil, et à toutes celles et ceux qui traduisent Nodyx.
Le détail complet, thème par thème : CHANGELOG.
v2.12.0 -- Personnalisation visuelle
Personnalisation visuelle : l'admin reprend la main sur ce qu'on voit en premier
Cette version sort d'un test grandeur nature : une vraie communauté multigaming, avec de vrais membres, déployée sur une instance Nodyx. Le premier réflexe de son admin a été de vouloir changer ce qu'un visiteur voit en arrivant, et de se cogner à des éléments non configurables. Tout ce qui suit vient de là.
Nouveau widget « En-tête » (Homepage Builder). Le widget Hero Banner reste tel quel pour qui veut du simple ; celui-ci apporte le contrôle qui manquait :
- Image de fond et logo téléversables, chacun positionnable (horizontal, vertical) et redimensionnable. Le fichier est stocké brut, sans recompression : pas de perte de qualité entre ce que vous envoyez et ce qui s'affiche
- Titre et sous-titre : texte, position, couleur, police (parmi les 10 réellement chargées par le thème, pas une liste décorative)
- Chaque élément s'affiche ou non : fond, logo, titre, sous-titre, bouton, statistiques, membres en ligne. Un admin qui ne veut que sa bannière et son logo coupe tout le reste
- Le bouton absent est vraiment absent. Hero Banner, lui, retombe sur des liens de navigation en dur quand on vide le champ : c'est le piège qu'on ne reproduit pas ici
Fond d'image pour la sidebar des membres. Elle est visible sur toutes les pages, son fond ne pouvait donc pas passer par le Homepage Builder : il vit désormais côté serveur, à côté du logo et de la bannière d'instance. Trois réglages nés de l'usage réel, pas de la théorie :
- Visibilité : visiteurs, membres connectés, ou tout le monde. Par défaut visiteurs seulement, parce qu'une fois connecté cette sidebar est un outil de travail et qu'une image derrière la rend illisible
- Assombrissement réglable : il était figé, et trop faible dès que l'image était chargée
- Dézoom (jusqu'à 0,4) : on pouvait zoomer, pas dézoomer, donc impossible de faire tenir une affiche entière dans le cadre
Sans image configurée, rien ne change, au pixel près : les deux fonctionnalités sont additives et éteintes par défaut.
Corrigé
- Le troisième widget à panneau personnalisé aurait affiché le panneau d'un autre. La chaîne de panneaux de configuration du builder n'avait aucune garde sur l'identifiant du plugin : invisible tant qu'il n'y en avait que deux, le suivant héritait silencieusement du panneau du Diaporama. Trouvé en ajoutant précisément ce troisième widget
- Dézoomer coupait l'image.
transform: scale()réduit autour du centre de la boîte, alors que le cadrage vise un autre point : en dessous de 1, le décalage entre les deux ouvrait un vide asymétrique, visible comme une coupure. L'origine de la transformation suit maintenant la position choisie - Positionner une image au glisser-déposer ne fonctionnait pas. Le glisser natif du navigateur prenait la main (curseur « sens interdit »), et l'image elle-même n'était pas cliquable. Remplacé par des curseurs, qui n'ont aucun de ces pièges
Exploitation
- La quatrième instance du serveur n'était sauvegardée nulle part, et n'aurait reçu aucune mise à jour, en silence. Elle est désormais couverte par les deux scripts. Sauvegarde et restauration vérifiées pour de vrai : base restaurée dans une base jetable, mêmes comptages, empreinte des fichiers identique à ce qui est servi en ligne
- Les cahiers des charges de communautés hébergées sont exclus du dépôt public : c'est le contenu privé de l'instance concernée, pas celui de la plateforme
v2.11.0 : le partage d'écran passe par le SFU, et Nodyx parle sept langues
Vidéo : le partage d'écran passe par le SFU
En 2.10.0 le SFU savait porter la voix. Il porte maintenant l'image. Un partageur envoie un seul flux, le serveur le recopie vers chaque spectateur : le mur du mesh (une copie par spectateur, ingérable au-delà de trois ou quatre) n'est plus le plafond du partage d'écran.
- Le partage d'écran des vrais canaux passe par le SFU dès que le canal est basculé, et partager son écran déclenche la bascule sans attendre de quorum. En mesh, le chemin d'avant n'a pas changé d'une ligne : c'est le filet
- Un partage en cours survit à la bascule : le flux local est capturé avant la démolition des connexions mesh, puis republié. Sans ça il aurait fallu rouvrir le sélecteur d'écran, ce que le navigateur refuse hors geste utilisateur, et le partage serait mort à chaque bascule
- Le son part avec l'écran (onglet, jeu, vidéo), avec volume et coupure côté spectateur, un rappel contextuel pour penser à cocher la case, et un indicateur qui dit si un partage diffuse du son
- Chemin TCP de secours pour l'ICE : sans lui, les réseaux qui bloquent l'UDP ne se connectaient pas du tout
- Pool de workers média réparti sur les cœurs, avec réserve, et
sfu-bench, un canon de charge qui révèle le plafond réel par router (modes multi-salons, watch-party, profil de flux configurable). Le banc a son propre site, auto-hébergé - Fantômes exorcisés : heartbeat, TTL côté daemon et réconciliation client. Un rejoin remplace au lieu d'être refusé, un onglet évincé s'arrête au lieu de boucler, la session survit à la veille mobile (wake lock), et le client se reconnecte seul après une coupure réseau
- Consumers et transports orphelins fermés : fuite mémoire sur instance longue
Quatre bugs vidéo qui se ressemblaient et n'avaient rien à voir, tous corrigés :
- La keyframe perdue. Les consumers vidéo étaient créés non pausés : mediasoup poussait la keyframe avant que le décodeur du navigateur n'existe, et sans keyframe rien ne se décode. Écran noir jusqu'à la suivante. La vidéo est désormais servie en pause puis reprise côté client. L'audio n'a pas de keyframe : voilà pourquoi il marchait depuis le début
- Le clignotement noir. Réassigner
srcObjectrelance l'algorithme de chargement du média même avec exactement le même objet. Le store étant republié au moindre frémissement du roster, la vidéo se réinitialisait en boucle. Les cinq actions concernées ne réassignent plus que si le flux change vraiment. Corrige le mesh autant que le SFU - L'écran invisible à l'arrivée. En rejoignant un canal déjà basculé, le miroir qui recopie les écrans vers l'interface ne démarrait pas : le flux partait bien au serveur, plus rien ne l'affichait
- Le simulcast, ajouté puis retiré : un
scalabilityModeinvalide côté navigateur privait le spectateur de toute vidéo. Remis ensuite, avec de quoi le prouver
La Scène, et un chat dans les salons vocaux
- Un canal vocal n'avait aucun fil de discussion. Pas un défaut d'affichage : le composeur et les messages n'existaient que pour les canaux texte. Le chat vit maintenant à droite du salon, ouvert par défaut et repliable, avec l'éditeur Nodyx et les outils de modération
- La Scène : le partage en plein écran, sans rognage ni sidebar, avec son chat, ses miniatures en bande basse et une fenêtre flottante qui survit à la navigation
- Contexte d'empilement : la Scène était montée sous un
asidepositionné avec unz-index, donc sonz-[500]ne valait qu'à l'intérieur de cet aside. L'en-tête et la sidebar des membres peignaient par-dessus, ce qui rognait l'écran à droite et rendait les boutons de la Scène inatteignables. Elle est montée à la racine du layout. Un overlay plein écran ne doit jamais vivre sous un ancêtre positionné
Le vocal au quotidien
- Un équaliseur réel par personne, fini les barres factices, et le même sur la Scène du salon, là où il se voit le plus
- Voir qui est dans un canal avant de le rejoindre, et les statuts dans le roster : muet, sourd, en train de partager
- Bouton haut-parleur (Android et desktop) via
setSinkId - Le son du partage sort par un
<audio>dédié, seul chemin fiable sur mobile - L'état est republié à chaque arrivée dans le vocal, et la croix de la Scène ne la rouvre plus toute seule
Nodyx parle sept langues
Le grand audit « plus rien en dur » est terminé. Toute l'interface, publique et administration, passe par des clés de traduction, et la CI empêche la moindre régression.
nodyx.org/translate: l'état des traductions, langue par langue, avec un lien direct vers le fichier à éditer. Les chiffres sont calculés depuis les fichiers de locale eux-mêmes, la page ne peut donc pas mentir. Un endpoint JSON alimente la même information sur nodyx.dev et start.nodyx.org- Anglais complet, à parité totale avec la source, et le repli d'exécution passe par l'anglais avant le français : une langue partiellement traduite montre de l'anglais, jamais une phrase française surprise
- Le cœur de l'interface est traduit dans les sept langues, et le vietnamien fait son entrée
- Sélecteur de langue accessible aux visiteurs, locale résolue côté serveur pour que le premier affichage soit déjà dans la bonne langue
- Drapeaux rendus en SVG : Chrome sous Windows n'affiche pas les emoji drapeaux, il écrit « FR » et « GB » à la place
- Quatre garde-fous en CI : aucune chaîne en dur, aucune clé référencée dans le vide, aucun placeholder corrompu par une traduction, et le tableau de couverture. Le contrôle des placeholders vérifie les variables
{{ }}, les jetons de gabarit{ }et l'équilibre des balises, en distinguant ce qu'une traduction invente (erreur, ça s'affiche en clair à l'écran) de ce qu'elle abandonne (avertissement, c'est souvent un choix de langue) - L'angle mort du scanner : il ne repérait que le français accentué, donc « Se connecter », « Rejoindre », « membres » avaient traversé toute l'extraction. Environ 25 chaînes corrigées, et le scanner durci
Forum, contenu, éditeur
- Un index de forum vivant : dernier message, compteurs et statistiques, avec les colonnes des sous-forums alignées sur celles des parents
- Embeds Twitch dans les posts, en deux iframes (lecteur et chat), sans toucher à la CSP, avec une taille dédiée quand le chat est présent. Le live ne se perd plus à la réédition d'un post
- Popups de l'éditeur portalées dans le
body: elles n'étaient plus rognées par unoverflow, y compris dans les modales - Tableau de tâches : cartes riches avec l'éditeur Nodyx à la place du champ texte brut, pleine largeur, et le crayon d'édition qui ne faisait rien (un
structuredClonesur un proxy réactif) - La page d'un événement ne déborde plus sous les sidebars
Interface
- Sidebar des membres repliable, badge de version centré en pied de colonne, typographie remontée d'un cran
- Pages d'authentification unifiées autour d'un thème sombre, panneaux redimensionnables dont l'état est lu côté serveur pour éviter tout clignotement au premier affichage, et la dépendance three.js retirée au passage
- La page d'accueil ne répète plus le nom de l'instance, qui semblait changer de taille en naviguant
- Le tableau de contributions du profil était vide pour tout le monde : l'API renvoie un tableau, la page attendait un objet indexé par date
Sécurité et robustesse
- Injection SQL latente dans
liked_by_me: l'identifiant du lecteur était interpolé dans la requête. Non exploitable en pratique, l'identifiant venant d'un JWT signé, mais la requête est désormais paramétrée comme ses voisines - OctoGuard : les effets de bord d'avertissement et de mise en sourdine quittent le chemin critique de la modération. Le pipeline échouait ouvert sous latence
adm-ziprelevé en 0.6.0 : déni de service par archive piégéeargon2ne sera jamais relevé automatiquement : c'est le hachage des mots de passe, il passe par une relecture humaine- L'heuristique de forme du pseudo qui recalait de vrais humains à l'inscription est supprimée
- Crash SSR sous Node 22.4 et plus :
localStoragegardé parbrowseret non partypeof
Installation et infrastructure
- Le SFU est empaqueté par l'installeur, il appartient au projet et non plus à un serveur précis
install_tunnel.shdit honnêtement que le vocal reste en mesh : demander d'ouvrir des ports trahirait la promesse zéro-port, tant que l'ICE complet n'existe pasnpm cidans l'installeur, pour des installations reproductibles au verrou près- Le serveur TURN Node historique, non déployé, est retiré
nodyx-sfud sfu-v0.1.0
Binaires précompilés du daemon SFU (vocal et partage d'écran scalables).
Compilés sur Debian 11 (glibc 2.31) : ils démarrent donc sur Debian 11/12/13
et Ubuntu 22.04/24.04, en amd64 et arm64.
Le worker mediasoup est compilé dans le binaire : aucune chaîne C++ à
installer chez l'utilisateur, aucun fichier annexe. install.sh les télécharge
automatiquement.
v2.10.0 · The SFU is born (experimental audio) · Ctrl+K Search
Vocal : le SFU est né (audio expérimental)
Quatre jours après le CDC (SPECS/NODYX_SFU_CDC.md), le premier son a traversé le SFU de Nodyx : un PC et un téléphone en 4G (une barre, presque zone blanche), audio dans les deux sens, via l'infrastructure de l'instance, sans un octet chez un tiers. Le mur mathématique du mesh (15 spectateurs × 3 Mbps = impossible) a désormais sa réponse.
nodyx-sfu(Rust, zéro dépendance, zérounsafe) : le cerveau vocal en trois étages hexagonaux,VoiceService(salons, sièges, kick, publish/subscribe, bascule hybride mesh↔SFU) → traitMediaEngine(le port, blobs de signaling opaques jamais lus par le métier) → adaptateur moteur. 25 tests contre unNullEngine: l'orchestration prouvée avant tout moteur réel, et le swap futur vers un moteur full-Rust garanti par constructionnodyx-sfu-mediasoup: l'adaptateur mediasoup (licence ISC, notice dansNOTICE) validé par spike (salon réel, pipe de fédération inter-workers, WebRtcTransport ICE/DTLS), et le daemonnodyx-sfud: API interne localhost à jeton obligatoire (comparaison en temps constant), HTTP minimal écrit main (~150 lignes), plage RTC UDP bornée (40000-40999, alignée sur le firewall, leçon nexus-turn), unité systemd durcie- Relais de signaling dans nodyx-core : events
voice:sfu_*ADDITIFS et DORMANTS (sansVOICE_SFU_URL, tout répondsfu_disabledsans toucher ni daemon ni base), mêmes gardes d'admission que le mesh, identité issue du JWT, payloads bornés, rollback complet sur échec partiel. Le vocal mesh existant : zéro ligne de comportement modifiée, ses 385+ tests en témoignent - Client
mediasoup-client+ laboratoire/admin/sfu-lab: Device → paire de transports send/recv → micro publié → flux consommés, machine à états journalisée en direct, timeout sur chaque échange, nettoyage idempotent - Passe de relecture post-MVP : quatre défauts d'états fantômes corrigés (course de création de router, participant orphelin côté daemon après échec partiel, échec de session silencieux côté client, double consume sur annonces croisées)
Recherche Ctrl+K : elle cherche enfin
- La palette n'appelait jamais le moteur de recherche, et son groupe FORUM interrogeait un endpoint inexistant : chercher un mot du contenu du site donnait toujours zéro résultat
- Désormais : recherche full-text live dès 2 caractères (titres ET contenu des posts, extraits surlignés, lien direct jusqu'au message), et l'arbre complet des catégories aplati, sous-sous-catégories navigables au clavier
v2.9.0 · SFU Foundations · Instance Themes · Custom Emojis
Vocal : le cap SFU est posé, et le mesh actuel respire mieux
Le constat est mathématique : en mesh P2P, celui qui partage son écran uploade son flux vers chaque spectateur (15 spectateurs à 3 Mbps = 42 Mbps montants, impossible sur une connexion résidentielle). Le cap est donc acté et outillé :
- CDC complet du SFU :
SPECS/NODYX_SFU_CDC.md, architecture hybride (mesh en petit comité, SFU au-delà ou dès qu'un partage d'écran démarre), moteur mediasoup derrière une abstraction swap-ready, fédération inter-instances par cascade (PipeTransport sur WireGuard), phasage P0 à P5 - Crate Rust
nodyx-sfu(le cerveau, déjà testé) : séparation hexagonale en trois étages,VoiceService(règles métier : salons, sièges, kick, bascule hybride, publish/subscribe) qui ne parle qu'au traitMediaEngine(transport pur), implémenté aujourd'hui par unNullEnginede test, demain par mediasoup, plus tard par un moteur Rust natif, sans réécrire le produit. Zéro dépendance, zérounsafe, 23 tests - Quick wins immédiats sur le mesh actuel : DTX Opus adaptatif (au-delà de 4 participants, le silence ne coûte quasi plus rien en upload ; en dessous, le réglage anti-jitter d'origine est conservé) et plafond de bitrate du partage d'écran selon qualité et fps (l'encodeur n'était pas borné : c'était le mur des watch-parties)
- Volume par participant enfin persistant (indexé par utilisateur + localStorage, il ne survivait pas au refresh)
TURN : le serveur possède sa plage de ports relais (fix de fond)
nexus-turn allouait ses ports relais dans la plage éphémère de l'OS, alors que le pare-feu n'ouvre que 49152-65535. Sur un OS au défaut kernel (32768-60999), plus de la moitié des allocations tombaient hors pare-feu : relais bloqué, vocal intermittent pour les auto-hébergés (NAT strict, mobile).
nexus-turnaccepte--min-port/--max-port(défauts alignés sur le pare-feu de l'installeur) et sonde sa plage lui-même : plus aucune dépendance àip_local_port_range- Réparation du build (hmac 0.13) et CI élargie à tout le workspace Rust : un crate cassé ne peut plus passer inaperçu
- Diagnostic réseau admin (
/admin/status) : le test TURN utilise désormais des credentials frais viaGET /api/v1/instance/ice-servers(les credentials figés au build expiraient en moins de 24h et la tuile RELAY mentait), et le STUN Google est retiré du testeur (nexus-turn répond au STUN, zéro service tiers)
Thème d'instance : l'owner impose son identité, le membre reste libre
- Nouveau système de thème d'instance : l'owner définit la base (
theme_varsstructuré,theme_csslibre, effet d'ambiance type Matrix avec pluie de caractères respectueuse deprefers-reduced-motion), chaque membre peut surcharger pour lui-même - Tokenisation de ~800 couleurs codées en dur vers des variables
--nx-*(indigo/violet/cyan et leurs glows) : les instances peuvent enfin re-teinter toute l'interface sans fork, nodyx.org reste identique par défaut - Fix critique multi-instances : le préfixe Redis lit désormais
REDIS_KEY_PREFIX(deux instances co-hébergées partageaient sessions et caches)
Emojis personnalisés (les émotes de la communauté)
- Upload d'emojis avec shortcode (
:mon_emote:), rendus dans les messages et les réactions du chat, protection des blocs de code - Picker enrichi : recherche, onglet Fréquents (usage local), onglet PERSO accentué, barre de réactions rapide au survol des messages alimentée par vos emojis les plus utilisés
- Aide au format à l'upload (taille idéale, formats) et déduplication des listeners socket du chat (réactions qui clignotaient)
Feed social : vrai temps réel
- Le fil d'actualité se met à jour en direct (posts, réponses imbriquées, réactions) sans rafraîchir
- Compteur de réponses = total des descendants (fil complet), compteur de posts du profil corrigé, posts du profil cliquables vers une page
/status/:idinteractive (composer, réactions) - Plus de notification de mention si vous êtes déjà sur le canal ; le compte de membres exclut les comptes système
- Mise à jour applicative transparente après déploiement (service worker + rechargement au premier clic) et
Cache-Control: no-cachesur le HTML SSR (fin des pages figées au retour d'une autre instance) - Édition d'un message du chat : tableaux et structure riches préservés
i18n : couverture massive et deux nouvelles langues
- Russe et portugais ajoutés (6 langues au total)
- Traduction du cœur membre qui restait codé en dur en français : découverte, wiki, tâches, fil d'actu et navigation, chat, édition de profil, DM (bandeaux E2E inclus), réglages, comptes connectés, sidebars, page membres, widgets de homepage
DM chiffrés : backup de clé (modèle Signal PIN)
- Sauvegarde de la clé E2E chiffrée par passphrase (PBKDF2 600k itérations puis AES-GCM), le serveur ne stocke qu'un blob opaque
- Restauration sur un nouvel appareil ou navigateur, générateur de phrase, bandeau pédagogique dans les DM, garde-fou avant envoi si la clé n'est pas sauvegardée
P2P Rust : découverte de pairs par gossip
- Nouveau crate
nodyx-gossip: découverte de pairs par anti-entropie épidémique, bibliothèque standard uniquement (zéro crate externe), auto-guérison par TTL, le réseau survit à la chute du nœud bootstrap - Records signés Ed25519 : node_id = clé publique, vérification stricte à la fusion (anti-spoofing, anti-rejeu), identité persistante par fichier de clé
Bibliothèque
- Lecteur audio intégré et bouton d'insertion de fichier
Dépendances
- Vagues Dependabot traitées (actions, groupes minor/patch npm et cargo)
v2.8.0 — Article Editor Overhaul · Playlists & OBS Scenes · Security Sweep
Éditeur d'articles : refonte de robustesse et d'UX (sprint juin)
Gros chantier sur l'éditeur riche (TipTap) après un audit complet. Cause racine identifiée et corrigée : certains blocs personnalisés se reconnaissaient via des attributs data-* que le sanitizer backend supprime, donc l'éditeur les déstructurait à la réédition (perte silencieuse de contenu). Principe établi : un bloc se reconnaît sur sa classe CSS (préservée), jamais sur un data-* volatil.
Robustesse round-trip (anti-perte de données)
- Colonnes et vidéos YouTube : ne disparaissent plus à la réédition (parse sur la classe + reparse des iframes nus)
- Console SSH des tutos (
.nodyx-term) : devient un nœud atomique protégé qui capture son HTML et le ré-émet tel quel, avec un toggle Rendu / Code façon CMS (la source est éditable, jamais corrompue au clavier) - Sommaire étanche : une image ne peut plus se faire aspirer dans la boîte sommaire
- Remplacement d'image réel : capture de la sélection à l'ouverture du menu
Nouvelles fonctionnalités d'édition
- Ancres à la sélection : barre flottante sur le texte surligné avec un bouton « Ancre » qui transforme la ligne en titre de section et l'ajoute à un menu Sommaire dérivé (recalculé, avec flash de feedback)
- Redimensionnement d'image : poignées de coin avec aimant aux largeurs clés (25 / 50 / 75 / 100 %), largeur stockée en attribut
width - Zone éditable à hauteur bornée + ascenseur interne : la barre d'outils reste toujours accessible sur les longs articles
- Console SSH stylée (
.nodyx-term) pour les tutoriels d'installation : fenêtre terminal avec barre de titre et coloration sémantique
Doc : audit complet de l'éditeur dans docs/audits/2026-06-15-editeur-rich.md
Streamer Hub : playlists audio et scènes OBS
- Playlists nommées dans la bibliothèque audio (Dev, Discussion, etc.), URL d'overlay OBS dédiée par playlist, contrôle depuis le Stream Deck
- Tab « Scènes OBS » : compositeur visuel inspiré d'OBS pour placer overlays et playlists, avec pickers et création inline
- Migrations 100 à 102 (playlists, jonction, scènes OBS)
Référencement (SEO / GEO) : fondations
- Correction du sitemap (les threads n'y apparaissaient jamais : passé de 14 à plus de 60 URLs)
- og:image unique par page = image de l'article (fini les doublons et la mauvaise image dans les partages Discord / Twitter), résolution rétroactive sur tous les articles
llms.txtréécrit,robots.txtrebrandé Nodyx, fichiers de vérification moteurs gitignorés (propres à chaque instance)- Guides publiés : comparatif honnête des alternatives (FR + EN) et guide d'installation débutant pas à pas (FR + EN)
Admin et performance
- Refonte sobre (style Linear / Vercel / Stripe) de la sidebar et du dashboard admin : palette zinc, un seul accent, emojis hors labels système
- SSR sans suspension sur le fetch directory + invalidation du cache showcase par clé de version (un article épinglé apparaît immédiatement)
- La home se rafraîchit après suppression d'un thread (invalidation des chargements, plus de refresh manuel)
- Grid Builder : scroll automatique vers la nouvelle ligne ajoutée
Sécurité et dépendances
- ws (high, DoS WebSocket) et tar (medium) corrigés sur core et frontend
- esbuild porté à
>= 0.28.1via override sur les 4 projets SvelteKit (sans montée vite 8 cassante) - nodemailer 8 → 9 (validé en production),
@types/nodeajouté à l'authenticator, groupes de dépendances minor/patch à jour, GitHub Actions checkout / setup-node à jour - js-yaml (turn-server legacy non déployé) : dismissée avec motif documenté
v2.7.0 — Nodyx Soundboard
La face audio du Streamer Hub est là.
Un soundboard intégré de bout en bout : tu uploades tes sons, tu les déclenches depuis ton téléphone ou ton chat Twitch, ils sortent sur OBS, tes viewers participent. Aucun SaaS, aucun bot tiers à câbler. Tout est chez toi.
Ce que ça fait pour le streamer
🎵 Bibliothèque audio personnelle
Drag & drop multi-fichiers (mp3, ogg, wav, flac jusqu'à 50 MB), extraction automatique des tags ID3 (titre, artiste, durée, cover art). Volume défaut, fade in/out, loop, indicateur royalty-free configurables par piste.
📺 Overlay OBS Browser Source
Une URL token-auth à coller dans OBS. Player Web Audio API avec fades, cross-fade automatique entre sons, OSD discret en coin (vignette + titre + barre de progression). Supporte plusieurs overlays par streamer pour des scènes OBS multiples.
🎛️ Stream Deck multi-pages
Le Deck mobile passe de 1 à 8 pages organisables. Une page pour les commandes, une page pour les sons, une page pour la modération. Nouvelles actions : jouer / couper / mettre en pause un son du soundboard, ou naviguer entre pages. Dock chips en bas du téléphone pour switcher en un tap.
🌐 Page publique pour les viewers
Tes viewers ouvrent `nodyx.org/soundboard`, voient ce qui joue en temps réel, parcourent ta bibliothèque, peuvent ajouter à la queue d'un clic. Preview audio au hover.
💬 Commande chat `!nextsound` (alias `!ns`)
`!ns ixion` dans le chat Twitch ajoute le son qui matche le mieux. Fuzzy matcher tolérant aux fautes de frappe et aux accents. Si le viewer est ambigu, le bot répond les 3 meilleurs candidats au lieu de deviner mal.
Pour les viewers qui demandent des sons
- Queue Redis FIFO (max 50 sons en attente)
- Auto-play : quand un son finit, le suivant démarre tout seul
- Rate-limit 30s par viewer + cap 3 sons simultanés (anti-spam basique)
- Déduplication globale (un même son ne peut pas être en queue 50 fois)
- Toggle admin "Viewers autorisés / Viewers bloqués" pour couper l'eau en cas de raid
Côté admin
- `+ Deck` modal : rattache un son à un bouton du Stream Deck en 1 clic sans naviguer (sélection de la page cible, mini-grille interactive)
- Queue viewers visible en temps réel dans le tab Soundboard avec skip / clear
- Multi-pages Deck avec pill bar (rename inline, color picker, drag-to-reorder)
Sous le capot
- 2 nouvelles migrations (098 `streamer_audio_library`, 099 cap asset 12→50 MB)
- 3 nouveaux services backend (`audioLibraryService`, `audioTrackMatcher`, `soundboardQueueService`)
- Bypass de la quota community 200 MB/24h pour les owner/admin
- Backwards-compat totale : les anciens decks (sans `pages`) sont automatiquement wrappés sans casser
Mise à niveau
```bash
cd /var/www/nodyx-core && git pull && npm run build && pm2 restart nodyx-core
cd /var/www/nodyx-frontend && npm run build && pm2 restart nodyx-frontend
```
Les migrations 098 et 099 s'appliquent automatiquement au boot du backend.
Changelog complet : CHANGELOG.md
Une instance, une communauté, ton propre toit.
v2.5.0 — Streamer Hub Phase 2 (Chat unifié Twitch ↔ Nodyx)
Streamer Hub Phase 2 : chat unifié Twitch ↔ Nodyx (spec 015)
Chat bidirectionnel temps réel entre une instance Nodyx et le chat Twitch du streamer principal. Pipeline 100% EventSub Chat + Helix Send Chat Message, zéro IRC.
Voir CHANGELOG.md et PHASE_2_REPORT.md pour le détail complet.
Highlights
Chat inbound (Twitch → Nodyx)
- Subscription EventSub `channel.chat.message` créée et persistée
- Channel `#twitch-chat` auto-créé, read-write par défaut pour tous les membres
- Messages mappés à des authors Nodyx (via `twitch_id` lié ou placeholder)
- Volume non persisté dans `streamer_events` (early-return), va directement dans `channel_messages`
Chat outbound (Nodyx → Twitch)
- Quand un membre écrit dans `#twitch-chat`, le message est relayé via Helix `POST /chat/messages`
- Préfixe `[username]` activé par défaut, désactivable via `STREAMER_CHAT_NO_PREFIX=1`
- Garde-fou : envoi UNIQUEMENT si une session est ouverte dans `streamer_sessions`
- Mode test bypass via `STREAMER_CHAT_TEST_MODE=1`
- Queue Redis `streamer:chat:send_queue` avec backoff exponentiel sur 429 (1s → 30s), TTL 60s, max 5 tentatives
- Alerte audit `chat_relay_queue_overflow` si la queue dépasse 50 messages
Lifecycle des sessions
- `stream.online` event → INSERT `streamer_sessions` (idempotent)
- `stream.offline` event → UPDATE `ended_at = NOW()`
Emotes et badges
- Emotes Twitch natives via fragments + CDN
- BTTV / FFZ / 7TV emotes, cache Redis 24h par channel
- Badges Twitch globaux + channel via Helix `/chat/badges/*` (caches 7j/24h)
- App Access Token caché 50j dans Redis
- Dégradation gracieuse : si Helix down, le message reste lisible sans emotes/badges
Fix versioning (critique)
Avant v2.5.0, 5 sources de vérité de version coexistaient et dérivaient (`package.json` à 2.1.0, hardcode `install.sh` à 2.2.0, etc.). Les instances affichaient "Mise à jour v1.8.1 → v2.4.0" alors qu'elles tournaient déjà v2.4.
Maintenant :
- Fichier `VERSION` à la racine = source unique
- Code TS lit `VERSION` au boot via `nodyx-core/src/utils/version.ts`, plus jamais `process.env.NODYX_VERSION`
- Installeurs (`install.sh`, `install_tunnel.sh`) résolvent en cascade : local → `raw.githubusercontent.com/.../main/VERSION` → GitHub API `releases/latest`
- Script `./scripts/bump-version.sh X.Y.Z` pour les futures releases
- Voir docs/RELEASE.md
Tests
269/269 verts (+65 vs v2.4.0). Nouveaux fichiers :
- `streamer-bridge.test.ts` (14 tests : gardes, refresh, formatting, 429 → queue)
- `streamer-emotes.test.ts` (11 tests : fragments, BTTV/FFZ/7TV, cache)
- `streamer-lifecycle.test.ts` (9 tests : online/offline, idempotence)
- `streamer-outbound-queue.test.ts` (17 tests : backoff, drop, worker)
- `streamer-badges.test.ts` (14 tests : global/channel, cache, dégradation)
Mise à jour
```bash
sudo nodyx-update
```
Après build + restart, l'instance affichera 2.5.0 sans intervention sur le `.env` (le code lit `VERSION` du repo cloné).
v2.4.0 — Backup System + Live Maintenance Mode
Backup System — Phase 1 MVP (spec 014)
First slice of the backup & restore system, fully wired end-to-end and smoke-tested on prod before merge — including a real-world incident that revealed and fixed a design bug live (see "The Yannick Story" below).
Backend
- New migration
077_backups.sqlwith three tables:backups,backup_settings(singleton),backup_audit_log backupService.tsshells out topg_dump --format=custom --compress=9andtar -czf, computes SHA-256 of the archive, and writes the row + audit entry. Restores usepg_restore --single-transaction --clean --if-existsso a half-failed restore rolls back atomicallypg_dumpexcludes the four meta-system tables (backups,backup_audit_log,backup_settings,schema_migrations) so a restore can never wipe its own safety net (see Yannick story)- Pre-restore snapshot is automatic: before any restore, a
source='pre-restore'backup is created, markedprotected=TRUEandexpires_at = NOW() + 24h, and cannot be deleted manually during that window - Redis lock
backup:lock(NX EX 3600) prevents two backups (or a backup-during-restore) from running concurrently. Released via Lua so a process can never delete a lock owned by someone else - 11 admin endpoints under
/api/v1/admin/backups: list, create, get, download, delete, restore, verify, diff, audit, storage, settings, reindex (recovery for orphan archives) path.basename()on the download path to defuse traversal attempts- 13 vitest tests added, full suite stays at 194 passing, zero regression
Frontend admin
- New page
/admin/backupswith storage indicator, table of backups, per-row Download / Verify / Restore / Delete actions - Linked from the admin sidebar in the Instance section (💾 Sauvegardes)
- Restore modal with diff preview (
+/- threads, posts, messages, users, uploads), pre-restore snapshot reassurance, type-to-confirm slug input, 5-second client countdown that only starts once the slug matches (server enforces the slug check too) - Dry-run button: verify the archive's checksum + format-version compat + tar structure WITHOUT touching the DB or filesystem. Result displayed inline (green ✓ "restorable" or red ✗ with the exact error). Audited as
metadata.dry_run = true - Ordered list of restore steps shown in the modal — no more black box on a destructive action
/admin/backups/auditlog viewer with action chips, success/failure pills, IP and user-agent shown — designed for post-compromise forensics (an attacker downloading a backup is an exfiltration event)
Docs
- Spec promoted from
.claude/ideas/BACKUP_SYSTEM.md(informal brainstorming) todocs/specs/014-backup-system/SPEC.MD(canonical, indexed by nodyx.dev like the other 8 specs)
Phase 1 deliberately omits Socket.IO progress (sync POST is fine for small instances), auto-backup scheduler, rotation, drag-and-drop upload of an external .tar.gz, AES-256-GCM encryption, and Docker bind-mount detection. Those land in Phase 2 / 3.
Note for self-hosters: the backup directory is nodyx-core/backups/ and must be writable by the nodyx user. On existing instances, run sudo install -d -o nodyx -g nodyx -m 0700 /var/www/nexus/nodyx-core/backups once. Phase 2 adds this step to install.sh automatically.
Live Maintenance Mode
A user (Yannick) registered on nodyx.org during the very first prod restore test, between the pg_dump (21:00) and the matching pg_restore (21:17). His user row got wiped by the restore — recovered manually via CLI restore of the pre-restore snapshot, but the lesson was clear: a critical write window has to be communicated to users, not silently swallow their actions.
Added in the same release:
maintenanceService.ts— single Redis flag (nodyx:maintenance:meta) with EX safety belt (auto-clear if the operation crashes). Set/cleared fromcreateBackupandrestoreBackuparound their critical sections (TTL: 30 min create, 60 min restore)maintenanceGuardmiddleware — global FastifyonRequesthook returning 503 with{error, code: 'MAINTENANCE_IN_PROGRESS', reason, since, label}on user-facing writes. Skips: GET/HEAD,/api/v1/admin/*(admin keeps working),/api/v1/instance/maintenance, static assets, Socket.IOGET /api/v1/instance/maintenance— public, lightweight, polled by the frontend bannerMaintenanceBanner.svelte— sticky amber banner at the top of every page when active. Self-managing 15 s polling, hidden when no operation is in flight
Backup System — post-incident hardening
Three commits triggered by the Yannick incident:
--exclude-tableonpg_dumpforbackups,backup_audit_log,backup_settings,schema_migrationsso a restore cannot wipe its own tracking metadata or the freshly-created pre-restore snapshot rowinstanceslug resolution now prefersprocess.env.NODYX_COMMUNITY_SLUGoverORDER BY created_at(mirrorsadminOnly.tsbehaviour, fixes filenamenodyx-backup-instance-...→nodyx-backup-nodyx-...)- Drop the non-existent
languagecolumn from the community SELECT — that field lives in the env, not the table - New
POST /admin/backups/reindexendpoint andreindexBackups()service helper that scansBACKUP_DIR, parses each.tar.gzmanifest in-memory (no full extraction), and INSERTs missing rows ON CONFLICT DO NOTHING. Idempotent. Use case: recovery after an incident, manual archive drop, or partial DB corruption that took out only the backups table
The Yannick Story
This is the one moment we want preserved in the changelog because it's a perfect case of "test in production reveals what unit tests can't". The full play-by-play:
- Phase 1 MVP backend + frontend merged on
main, smoke-tested via curl with admin tokens (all green) - The maintainer creates the first real production backup at 21:00 (manual, full, including uploads)
- Between 21:00 and 21:17, Yannick registers on nodyx.org (perfectly innocent action)
- The maintainer clicks "Restore" on the 21:00 backup at 21:17 to validate the round-trip mechanics
pg_restore --clean --if-existsrewinds the DB to its 21:00 state — Yannick's user row gets wiped- The pre-restore snapshot's own row in
backupsalso disappears, because that table is itself in the dump - The audit log can no longer reference any row in
backups(FK violation), errors silently - Yannick pings on Discord: "my profile is broken, I'm logged in but my page is dead"
- Diagnostic: 26 users in DB, 27 in the pre-restore snapshot file → diff = Yannick
- Recovery via CLI restore of the pre-restore snapshot — Yannick's account is back, no content lost
- Three commits ship in the next 90 minutes: the dump exclusion, the maintenance flag, the reindex endpoint
- Yannick gets credited as "The accidental contributor" — first user whose mere existence at the wrong second taught the system its own design flaws
Total cost of the incident on prod: 0 user lost, 0 thread lost, 0 post lost, 8 sessions to refresh, 1 design bug fixed forever, 1 new feature shipped (maintenance mode), 1 contributor immortalised in the repo.