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.