Mein WeeChat-Setup: Deklarativ mit Nix, eingeklinkt am Bouncer

Wie ich meinen IRC-Client WeeChat reproduzierbar über ein Nix-Home-Manager-Modul ausrolle und mich darüber in einem Rutsch über meinen Bouncer in alle IRC-Netze klinke
Table of contents

Mal etwas, das nichts mit dem Lab zu tun hat: mein IRC-Client. Ich nutze seit Jahren WeeChat , und wie bei fast allem auf meinen Maschinen gilt auch hier — nichts wird von Hand zusammengeklickt, alles ist reproduzierbar. WeeChat samt Konfiguration kommt deshalb aus einem Home-Manager -Modul und verbindet sich ausschließlich mit meinem Bouncer.

WeeChat als Nix-Modul

Der Client soll auf jeder meiner Maschinen identisch dastehen — gleiche Plugins, gleiche Skripte, gleiche Tastenbelegung. Genau dafür ist Nix gemacht. Mein Modul baut WeeChat über pkgs.weechat.override mit exakt den Bausteinen, die ich brauche, und nicht mehr:

weechat = pkgs.weechat.override {
  configure = { availablePlugins, ... }: {
    plugins = [ (availablePlugins.python.withPackages (_ps: [ ])) ];
    scripts = with pkgs.weechatScripts; [ autosort weechat-grep ];
  };
};

Das Python-Plugin und die beiden Skripte — autosort zum automatischen Einsortieren der Buffer und weechat-grep zum Durchsuchen der Logs — sind damit Teil des Build-Outputs, nicht etwas, das ich nach der Installation noch nachziehen müsste.

Die Konfiguration selbst lege ich deklarativ ab, indem das Modul den ./config-Baum nach ~/.config/weechat spiegelt:

xdg.configFile.weechat = {
  source = lib.cleanSourceWith { src = lib.cleanSource ./config; };
  recursive = true;
};

Das Ergebnis: Ein frisch ausgerolltes System hat sofort meinen kompletten WeeChat — Build und irc.conf, buflist.conf, trigger.conf und der ganze Rest. Aktiviert wird das Ganze über einen einzigen Schalter, fr0st.apps.weechat.enable.

Eingeklinkt am Bouncer

Hier kommt der zweite Teil ins Spiel. WeeChat verbindet sich nie direkt mit Libera.Chat oder hackint, sondern immer nur mit meinem ZNC-Bouncer. Dieser hält die eigentlichen Netzwerk-Verbindungen dauerhaft offen — und routet sie, wie im verlinkten Beitrag beschrieben, über Tor.

Der Trick steckt in der ZNC-typischen Syntax des Passwort-Feldes: Beide „Server“ in meiner irc.conf zeigen auf dieselbe Bouncer-Adresse, und allein das Passwort entscheidet, in welches Netz ZNC die Verbindung weiterreicht — <znc-user>/<netz>:<passwort>:

libera.addresses  = "${sec.data.bnc_addr}"
libera.password   = "ff0x/libera:${sec.data.bnc_pass}"
libera.capabilities = "server-time,znc.in/server-time-iso,znc.in/self-message"

hackint.addresses = "${sec.data.bnc_addr}"
hackint.password  = "ff0x/hackint:${sec.data.bnc_pass}"

Aus Sicht von WeeChat sind das zwei Server-Verbindungen; tatsächlich landen beide am selben Bouncer, der sie auf die richtigen Netze auffächert. Die znc.in-Capabilities sorgen dafür, dass beim Verbinden die gepufferte History samt korrekter Zeitstempel nachgespielt wird — ich steige also genau dort wieder ein, wo ich aufgehört habe.

flowchart LR WC["WeeChat (Nix)<br/>2× Server-Eintrag"] ZNC["ZNC-Bouncer<br/>ff0x/libera · ff0x/hackint"] LIB["Libera.Chat"] HAK["hackint"] WC -->|"TLS · password = user/netz:pass"| ZNC ZNC -->|"via Tor"| LIB ZNC -->|"via Tor"| HAK
Die eigentlichen Geheimnisse — Bouncer-Adresse und -Passwort — stehen nicht im Klartext in der eingecheckten irc.conf, sondern als ${sec.data.*} aus WeeChats eigenem secured-data-Store (sec.conf), der mit einer Master-Passphrase verschlüsselt ist. So kann der Config-Baum bedenkenlos versioniert werden.

Fazit

Zwei einfache Ideen, die zusammen erstaunlich bequem sind: Der Client ist ein reproduzierbares Nix-Artefakt, das auf jeder Maschine identisch erscheint, und die Netzwerk-Logik liegt nicht im Client, sondern im Bouncer. Ich starte WeeChat, eine einzige verschlüsselte Verbindung pro Eintrag geht zum Bouncer — und ich bin sofort wieder in allen Kanälen, als wäre ich nie weg gewesen.