One of the communities I hang out at is The Server Room, a small but growing group of DevOps and adjacent professionals hosted by Jochen Lilich, aka “The Monospace Mentor”. We’ve been slowly incorporating IRC into The Server Room’s live comms structure and having a blast with it. When Jochen spearheaded the idea a while back, I found a new IRC client that I have come to enjoy quite a bit, Halloy. Obligatory “written in Rust”, but also I enjoy it’s old school vibes with modern creature comforts. What I missed with IRC though was the biggest creature comfort that modern chat apps like Slack and Discord originally hung their hat on - chat history.
If you’re a long time user of IRC, there’s two things probably true. 1, you’ve got some gray hairs (mine are in my beard) and 2, you’ve heard of an IRC bouncer. If you’re not familiar though, IRC bouncers are kind of like a proxy server with additional features. IRC bouncers, like proxies, are servers that talk the IRC protocol, but they establish a connection to an IRC server for you, and your client connects to the bouncer. This allows you to keep a persistent connection to a server and the channels you frequent. An additional feature of these bouncer servers is that they hold onto chat history for you, so when you reconnect, you can catch up on what you missed.
IRC version 3 includes a lot of really neat features, including an entire protocol extension for chat history, but the big popular IRC networks don’t have the resources to hold everyone’s offline messages, so even if they’re running an IRC daemon that supports IRCv3 extensions, they rarely support chathistory. But hey, I’m handy, and I know about IRC bouncers, so I figured I’d set something up.
The most popular bouncer has been ZNC, which is a venerable choice that I’ve used in the past. ZNC has some interesting features, including optional plugins to do some other shenanigans. However, when looking at the Halloy documentation, they mentioned a different bouncer called soju, and it looked quite interesting. soju, in addition to being a solid bouncer like ZNC, has support for IRCv3’s chathistory extension, as well as its own protocol extensions like bouncer-networks, allowing a client to connect to a bouncer once and communicate on all of the IRC networks the bouncer is managing for you, websocket hosting support for allowing web-based IRC clients even if the target network doesn’t, extensions for file transfer, and webpush, which would be great for a phone client that isn’t always connected (with a solid example also made by the soju developer called goguma.
Getting soju set up was pretty simple, but modern IRC, like the modern web, opts for encrypted connections by default, and my original idea for solving for this required more time than I could dedicate to solve the problem. I might come back to Cert Warden for managing this, but I ended up falling back to good ol’ Certbot, the EFF’s ACME client. It should be noted that I don’t think this is explicitly necessary for a bouncer, but I read some conflicting documentation that lead me to believe it might be valuable. You might be able to eschew this step altogether if you trust the network that you’re connecting to your bouncer on.
I set this up as an Alpine LXC container on a Proxmox host. I used the awesome Proxmox Community Scripts repo to get a container up and running quickly, and once I was in the root shell, getting my dependencies was pretty simple. soju and certbot both are in Alpine’s package repository, and given that I use DNS-01 validation for my certs in my home network (to avoid needing a publicly exposed webserver), I also need the certbot plugin for my DNS provider.
apk add soju certbot certbot-dns-cloudflare
Also, I need to edit some config. And I can’t use nano for more than editing a one-liner.
apk add neovim
First step, we use certbot to get a certificate cut for our eventual hostname of our bouncer. This involved following certbot and the certbot-dns-cloudflare plugin’s documentation. Once that’s done however, and I have a cert cut on my soju LXC, I need to ensure that soju can access the cert. Some handy hints were found in the soju documentation: Setting up Certbot for soju
Next, I configured soju with it’s incredibly difficult and lengthy configuration in order to get a baseline system going.
soju-bouncer:/etc/soju# cat config
# See soju(1) for supported directives
hostname soju.lab16.kajarlabs.com
db sqlite3 /var/lib/soju/main.db
tls /etc/letsencrypt/live/soju.lab16.kajarlabs.com/fullchain.pem /etc/letsencrypt/live/soju.lab16.kajarlabs.com/privkey.pem
listen ircs://
listen unix+admin://
Alpine uses rc-service to manage service daemons, a much lighter weight alternative to systemd, so we can start soju, verify that it runs correctly, and then stop it.
service soju start
service soju status
tail -n10 /var/log/soju.log
service soju stop
The listen unix+admin:// directive is for giving the sojudb account access to our user. With soju stopped, we can create our first admin account.
sojudb create-user <username> -admin
You’ll set a password for this user, as one would expect. Now, that the user is set up, we can connect our IRC client (in this case Halloy) to the bouncer.
# except from halloy's config.toml
[servers.soju]
nickname = "belthesar@gaspar" #username@client is a pattern you can use to identify clients. This allows each client to have their own scrollback / chathistory p
alt_nics = ["belthesar_@gaspar", ]
server = "soju.lab16.kajarlabs.com"
umodes = "+RG"
[servers.soju.sasl.plain]
# the credentials you setup with sojudb
username = "belthesar"
password = "supersecurepassword"
With Halloy configured, it can connect to your bouncer. Easy peasy.
Next, I had set up certificate based authentication with libera.chat’s network, which required me to cut a self signed certificate. soju can do the same! Once we’re connected to soju with our client, we can communicate with BouncerServ, a service used by users and admins of soju to manage their network connections. Halloy doesn’t have a network list for us to browse, so we need to setup our network connections here. Once set up and reconnected, Halloy will receive the network information over the previously mentioned bouncer-networks extension.
# commands sent to BouncerServ on soju
network create -name libera -addr ircs://irc.libera.chat
certfp generate -network libera
soju will now create the certificate and give you the fingerprint. You’ll need to give this to libera’s NickServ on a client that is actively connected to make this work. See libera’s documentation on using CertFP for more details.
After generating our cert, and updating libera to authenticate us with it, we’ll likely need to reconnect soju to libera to establish a clean connection workflow. A couple more commands to BouncerServ are all we need here:
network update -name libera -enabled false
network update -name libera -enabled true
Once you’ve done this, I restarted Halloy to establish a clean connection to soju. After connecting, Halloy presented me with my connection to soju, as well as a bounced connection to libera.chat.
[]
I’m pretty pleased with this setup so far! It took a little bit of finagling to figure out everything, and well, while soju’s man pages are quite thorough, I’ve certainly become spoiled by having nice formatted text documentation. That said, the spirit of the documentation definitely harkens back to the era of IRC, where man pages were the source of truth for service and command configuration, and adding additional clients to the bouncer was very straightforward. Next, I suppose I might have a look at connecting Goguma, giving me the same experience on my phone and tablet!