Les goélands lents

Idées, textes et images entre ciel, terre et mer

social: [ mastodon logo @ololduck@vit.am | github logo github | sourcehut logo sourcehut | radicle logo radicle ]

lang: [ en | fr ]

Installer Soju, un bouncer IRC moderne

2026-06-24

J'ai troqué mon ZNC historique contre Soju. Voici comment s'est passé le remplacement.

J’utilise ZNC depuis des années. Il est fiable, solide, et fait le taf de me garder connecté à mes potes quand je ne suis pas devant mon ordinateur, et de n’avoir qu’un seul utilisateur réellement connecté au réseau irc même si je suis à la fois connecté via mon téléphone et mon ordinateur. Mais le protocole IRC a évolué avec les fonctionnalités IRCv3 que ZNC ne supporte pas.

J’avais déjà mentionné Soju dans mes notes du 3 mai, je me suis enfin décidé à le déployer sur chat.vit.am.

Pourquoi Soju?🔗

Soju est un bouncer IRC plus récent que ZNC, avec le support natif des extensions IRCv3 comme chathistory, sasl, et message-tags. Mais ce qui m’a vraiment décidé, c’est la gestion de l’upload de fichiers. ZNC ne le propose pas nativement, alors que Soju l’intègre directement: on peut envoyer un fichier avec les clients supportant l’extension IRC soju.im/file-uploads. Le lien est public, n’importe qui peut le télécharger.

Le déploiement🔗

J’ai un petit stack Docker Compose sur chat.vit.am qui fait tourner TheLounge (webchat) et ZNC (l’ancien bouncer). J’y ai ajouté un service Soju.

Configuration Docker Compose🔗

# docker-compose.yml
services:
  soju:
    image: codeberg.org/emersion/soju:latest
    container_name: soju
    restart: unless-stopped
    ports:
      - "127.0.0.1:6668:6667"
      - "127.0.0.1:4002:80"
    configs:
      - source: soju
        target: /soju-config
    volumes:
      - soju-db:/db
      - /var/www/chat.vit.am/uploads/:/uploads/

configs:
  soju:
    file: soju/config

volumes:
  soju-db:

J’expose deux ports, tous les deux en boucle locale:

Le dossier /var/www/chat.vit.am/uploads/ est monté dans le conteneur pour le stockage des fichiers uploadés.

Configuration de Soju🔗

# soju/config
db sqlite3 /db/main.db
message-store db
file-upload fs uploads/

listen irc+insecure://
listen http+insecure://:80
listen unix+admin://

http-ingress https://chat.vit.am/soju

accept-proxy-ip localhost

Quelques explications:

Le reverse proxy nginx🔗

J’avais déjà une configuration HTTPS pour chat.vit.am (certificat Let’s Encrypt géré par Certbot, utilisé pour TheLounge et ZNC). J’ai simplement ajouté un bloc location /soju/ dans le serveur existant, et une section stream dans nginx.conf pour le TCP TLS.

C’est là que les choses deviennent intéressantes. nginx doit remplir deux rôles: servir de proxy HTTP pour l’interface Soju (WebSocket et uploads), et faire du TCP stream pour les connexions IRC directes avec TLS.

Configuration HTTP (proxy inverse)🔗

# /etc/nginx/sites-available/chat.vit.am
location /soju/ {
    proxy_pass http://127.0.0.1:4002/;
    proxy_http_version 1.1;
    proxy_redirect ~^(/uploads/.*)$ /soju$1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1d;
    client_max_body_size 15m;
}

Le proxy_pass avec le slash final est important: il retire le préfixe /soju/ avant de passer la requête à Soju.

La directive proxy_redirect a été la clé pour faire fonctionner l’upload de fichiers. Quand Soju reçoit un fichier, il répond avec un en-tête Location contenant un chemin comme /uploads/ololduck/monfichier.png. Sans réécriture, le client reçoit une redirection vers chat.vit.am/uploads/... qui n’est pas servi correctement. La réécriture transforme ça en /soju/uploads/....

J’ai aussi défini proxy_read_timeout 1d pour les connexions WebSocket qui restent ouvertes longtemps, et client_max_body_size 15m pour pouvoir uploader des fichiers jusqu’à 15 Mo.

Configuration du stream IRC (TCP/TLS)🔗

# /etc/nginx/nginx.conf (section stream)
stream {
    server {
        listen 6698 ssl;
        ssl_certificate     /etc/letsencrypt/live/chat.vit.am/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/chat.vit.am/privkey.pem;
        ssl_protocols       TLSv1.2 TLSv1.3;

        proxy_pass 127.0.0.1:6668;
    }
}

Ce bloc réutilise le certificat Let’s Encrypt existant (les mêmes chemins que le serveur HTTPS). Plus besoin de gérer un certificat supplémentaire ou de configurer le TLS du côté de Soju.

Le stream écoute sur le port 6698 en TLS, et forwarde le trafic vers le port 6668 de Soju (lui-même mappé vers le 6667 du conteneur). Cela permet de se connecter avec n’importe quel client IRC à chat.vit.am:6698 en TLS.

J’ai laissé le port 6697 pour ZNC, qui continue de tourner en parallèle le temps que tout le monde migre.

Créer un utilisateur🔗

Soju ne crée pas d’utilisateur au premier lancement. Il faut le faire via l’outil sojudb:

sujuctl create-user ololduck -admin

Comme Soju tourne dans Docker, il faut passer par le conteneur:

docker exec -it soju sojuctl create-user ololduck -admin

L’option -admin donne les droits d’administration, nécessaires pour pouvoir créer d’autres utilisateurs et administrer le bouncer.

Connexion des clients🔗

Clients supportant les réseaux multiples🔗

Si votre client gère l’extension soju.im/bouncer-networks (c’est le cas de Halloy que j’utilise), il suffit de se connecter à chat.vit.am:6698 avec son nom d’utilisateur et mot de passe Soju. Les réseaux IRC se configurent ensuite directement dans le client.

Autres clients🔗

Pour les clients qui ne gèrent pas les réseaux multiples, on peut spécifier le réseau directement dans le nom d’utilisateur: <utilisateur>/<serveur>. Par exemple, pour se connecter à Libera Chat:

Le bouncer va automatiquement créer le réseau et s’y connecter.

Si on utilise plusieurs clients en même temps (un PC fixe et un portable), on peut ajouter un nom de client: ololduck/irc.libera.chat@bureau et ololduck/irc.libera.chat@portable. Chaque client aura son propre buffer d’historique.

Connexion WebSocket🔗

Pour les clients web qui supportent le WebSocket IRC, l’URL est wss://chat.vit.am/soju/socket.

Gérer les réseaux avec BouncerServ🔗

BouncerServ est le robot interne de Soju qui permet de tout configurer. On lui parle en message privé: /msg BouncerServ <commande>.

Les commandes peuvent être abrégées: network devient net, create devient c, etc.

Créer un réseau🔗

/msg BouncerServ network create -addr irc.libera.chat -name libera -nick ololduck

Ça crée le réseau et Soju s’y connecte directement. Si le serveur nécessite un mot de passe (serveur ou NickServ):

/msg BouncerServ net create -addr irc.libera.chat -name libera -nick ololduck -connect-command "PRIVMSG NickServ :IDENTIFY monmotdepasse"

Le -connect-command envoie une commande IRC brute juste après la connexion. Pratique pour les serveurs qui ne supportent pas SASL.

Certificats et serveurs non standard🔗

Tous les serveurs IRC ne sont pas derrière un certificat signé par une autorité reconnue. Pour ceux-là, on peut spécifier une empreinte de certificat (certfp) pour que Soju vérifie le certificat par empreinte plutôt que par chaîne de confiance.

Pour récupérer l’empreinte d’un serveur:

openssl s_client -connect ssl.netrusk.net:6697 -verify_quiet </dev/null | openssl x509 -fingerprint -sha512 -noout -in /dev/stdin

Ensuite on l’utilise à la création:

/msg BouncerServ net create -addr ssl.netrusk.net -name netrusk -nick ololduck -certfp 12:71:11:E2:E7:BC:ED:B4:38:74:74:49:FA:B1:42:20:4B:6C:9A:0F:31:14:69:BF:01:99:4B:BD:80:A5:4C:E8:8A:BC:27:AE:63:0C:56:33:51:72:73:A3:FE:90:63:D7:98:35:79:CC:B8:80:16:0A:A0:DF:10:45:C7:12:44:F2

On peut aussi mettre à jour un réseau existant plutôt que de le supprimer et le recréer:

/msg BouncerServ net update netrusk -certfp <empreinte>

Ou changer son adresse:

/msg BouncerServ net update netrusk -addr ssl.netrusk.net

Quand on modifie un réseau, Soju se déconnecte et se reconnecte automatiquement.

Lister et supprimer🔗

/msg BouncerServ network status

Affiche tous les réseaux configurés, leur état (connecté, déconnecté, erreur) et le nombre de salons.

/msg BouncerServ network delete libera

Supprime le réseau et s’en déconnecte.

SASL🔗

Pour les serveurs qui supportent SASL (c’est le cas de Libera Chat):

/msg BouncerServ sasl set-plain <identifiant> <motdepasse>

Si le réseau n’est pas celui par défaut (parce qu’on a plusieurs réseaux), on précise avec -network:

/msg BouncerServ sasl set-plain -network libera ololduck monmotdepasse

Certificat client (CertFP)🔗

Si le réseau supporte CertFP (comme BitcoinNet), on peut générer une paire de clés directement depuis Soju:

/msg BouncerServ certfp generate -network BitcoinNet

Soju génère un certificat auto-signé et le présente au serveur lors de la connexion. Il faut ensuite enregistrer l’empreinte auprès de NickServ sur le serveur concerné.

Le résultat🔗

Au final, j’ai:

La migration s’est bien passée, et je peux maintenant profiter des fonctionnalités IRCv3 et de l’upload de fichiers. ZNC tourne encore en parallèle pour les copains qui ne sont pas encore migrés, mais ça viendra (ou pas).