Deploying Nested ZNC Services with Ansible

The OpenStack community, like other open source communities, relies on IRC for a lot of our brief and interactive communication. The community is large, though, and spans the globe, so we are not all online simultaneously. Many of us also travel, meaning we are offline at times even when we are otherwise “working.” The result is lags in communication, or missed messages entirely.

ZNC is an IRC “bouncer”, a tool for maintaining a presence on an Internet Relay Chat network even when you yourself are not connected to the Internet. ZNC maintains a scroll-back buffer for each channel you join, letting you replay missed messages after reconnecting to the service for a while. Having access to the messages you’ve missed makes IRC more useful as an asynchronous communication tool.

Advanced ZNC Configuration

Using a single ZNC server is a good solution if you use just one computer to connect to IRC. For anyone with both a desktop and laptop, or even a phone or tablet, though, using a single shared ZNC server can actually result in losing messages. If your desktop computer is on, it consumes the scroll-back so you can’t see them when your laptop comes online, for example.

ZNC supports multiple users, so it is possible to configure it so that each device has its own buffers. However, that means each user needs to be connected to each channel in order to have a buffer saved. Managing that by hand, by subscribing each user, is a pain.

Last year Sean Dague wrote a blog post describing a puppet module he and Dan Smith created for managing multiple ZNC servers. The idea they came up with is actually to nest the servers, so that one base server talks to the IRC network(s) and the others serve the scroll-back for each client (the diagram in the blog post explains the configuration very well). They dubbed this configuration “ZNC on ZNC”.

znc-on-znc Ansible Role

I use Ansible, instead of puppet, for managing my development server configurations. As a way to expand my Ansible knowledge, I decided to build an Ansible role to implement the ZNC-on-ZNC configuration that Sean and Dan had started. I ended up going a little further and adding features to make it possible to connect the ZNC services to more than one upstream network – so you can connect to a Bitlbee instance that proxies to a Jabber network, for example.

The role can be installed from the git repository or with ansible-galaxy:

$ ansible-galaxy install dhellmann.znc-on-znc

The README file describes all of the options in detail, and gives an example configuration. The most important values are the znc_user settings for securing the ZNC servers, the znc_networks for managing the IRC networks to which the server connects upstream, and the znc_clients list for defining how many and which clients will be connecting to the servers.

What You Get

Each entry in znc_networks results in an upstream connection, and each entry in znc_clients results in a separate ZNC process to which a client can connect as the znc_user to access the private scroll-back buffers.

Each ZNC instance is configured with a self-signed SSL certificate, generated as part of the deployment. The base instance is configured to connect to all of the networks with a set of channels to join, so if the ZNC server loses the connection is can rejoin all of your channels automatically.

The child instances are configured to connect to the server and trust its SSL certificate. The child connects to the base server separately for each network, and the subscribed channels are configured so that all child instances have buffers for all channels.

The amount of buffering in each child is configurable separately, so that small mobile devices don’t have an excessive amount of playback every time they connect.

monitd is configured to keep all of the ZNC instance running, in case any die or the server is rebooted.

Another useful feature is triggered by the znc_firewall_bypass_port setting, which connects port 443 to one of the selected clients to allow you to use your ZNC instance through a firewall that blocks traffic on the normal IRC ports. The clients don’t run as root, so they can’t bind directly to the privileged port. Instead, rinetd connects port 443 with the unprivileged port the client is configured to use normally.

Hosting ZNC

I use a small cloud instance to host the ZNC service, so it is accessible from anywhere I might travel. If you don’t have a cloud account, or don’t want to pay for a server to run ZNC, any machine that maintains an Internet connection and to which you can connect remotely will work as well.