The jj IRC client

jj is an Internet Relay Chat (IRC) client based on the principles of ii, using FIFO (named pipe) files to read commands from the user, and simply logs the output of each channels to plain files.

Instead of being an ncurse program that runs into tmux(1), jj works as an applicative router. A router for ISO layer 7. You may already know "applicative routers" for different protocols already:

IRC itself is much of a routing protocol server-side: routing messages to the right client. Another aspect of applicative routing is client-side.

Much like ii or sic, and all the modular bots in the wild, the jj acts as a is a client-side IRC router through hooks and log files.

How jj works

As opposed to ii, which is plain C, jj uses an awk script to do the heavy lifting of IRC parsing, called from a compiled binary coded in C, that only does the fifo creation, and multiplexing of user and network input, and write both to the awk script.

It reads its entire configuration from environment variables, described on the the project's, such as $IRC_DIR, in which it create a direcTory with the channel as a name.

I set IRC_DIR to /var/irc, which gives us (full list on the

There is one instance of jj per server conexion, which greatly simplifies the software, makes debugging much easier, and permit to adapt and configure it specifically for the requirements of each host to connect to.

Recently, jj acquired the UCSPI connexion interface, which is a way to write programs without handling the network protocols into the program itself, but instead expect an running connexion (TCP, TLS, SSH, SOCKS5...) from standard input and output (for servers) or file descripTor 6 and 7 (for clients).

This permits to run it through s6, ucspi, ucspi-ssl, curvecp, or any protocol that has an UCSPI adapter for it.

State of IRC client <=> server connexions

Because it is run by different people and projects, the connexion to IRC servers varies greatly through the different cases:

From this plethora of security fine tuning, it is necessary to have an irc client with a good TLS implementation (lots of lines of code), and a socks proxy (more lines of code), with a configuration interface (many many lines of code).

How UCSPI helps

I use jj under the s6 and s6-rc supervision tree on a VPS (until I get real hardware home).

jj having one instance per host to connect to, and jj supporting UCSPI, all of these are of a great fit:

By using ucspi, we are entirely avoiding the problemm, as we can compose a socks client to talk to the Tor daemon (one line of scripts), and once the connexion has started, it is possible to start a tlsclient program that uses the active connexion and start a TLS session within that Tor socket, which can be configured as well to use a client certificate.

All of the UCSPI tools work by performing their startup work (opening a TCP connexion, initiating a TLS/SOCKS/... session), and starting a child program, which was pased as argument to it, with a pipe, or executing them directly.

This gives more or less long chain, aka chainloading, that djb used and popularized through its set of programs.

How I use jj and s6/s6-rc and UCSPI together

The s6 and s6-rc package come with an execline shell-like language that makes this style of pipling as natural

This is how it looks in an execline ./run script in practice, as povided by s6 for configuration of daemon (jj) startup scripts:

Boilerplate I have everywhere (sorry sk., #!/usr/bin/env...):

#!/usr/bin/env execlineb
fdmove -c 2 1

This command reads the content of each file in ./env/ and export environment variables to their content: env/IRC_HOST, env/IRC_USER, env/IRC_PASSWORD, ...

s6-envdir "env"

I use the "irc" user and the /var/irc/ direcTory:

s6-setuidgid irc

This starts a TCP connexion to the local Tor daemon running:

s6-tcpclient 9050

This starts a SOCK5 proxy session for communicating with the hackint

sockc "5ogdsfyoqk47ompu.onion" "6667"

At that point, the program called by sockc will be able to communicate directly with IRC commands, as the TCP connexion is ready, the socks session is set, and the raw IRC protocol follows.

It is now possible to call jjd directly, which will recognize the UCSPI environment through the $PROTO environment variable.


In case there is another context, situation, other UCSPI commands can be inserted or moved, including the setup of client certifiates, that often goes through environment variables.

This is the same script while run as #!/bin/sh. Yes, this is a single command!

fdmove -c 2 1 \
  s6-envdir "env" \
  s6-setuidgid irc \
  s6-tcpclient 9050 \
  sockc "5ogdsfyoqk47ompu.onion" "6667" \

Making jj useable interactively

Being log-based, wrappers tools are necessary for making jj useable as a main IRC client.


similar to jji from the jj package

Take lines from the user stdin with a prompt set to the name of the channel. A better line-editing tool could be used, such as rlwrap around this.


similar to jjp in the jj package

Get a human-readable feed out of the jj log: Filter the log to add colors. One way to use it is: tail -f chan.log | jj-log


not in original jj package

Print the multiple files sorted as if tail -f was printing them since the beginning, with the same "==> filename <==" header.


not in original jj package

Instead of going with one window per chatroom and switching between chatrooms, jj-tail gives a combined feed with the output of all channels in a same stream showing a headers for each channel.

== ==
00:12    ikityik: it's like having a bad fever dream with this ISP
== ==
00:12       cicl: when usb is enabled sounds like a vm?
00:12       cicl: whats the context?
== ==
00:12      thrig: Logan Runners?
00:12       knot: with blackjack and hookers

To avoid being bothered with too many channels at once, it takes a list of find(1) -path $filters as argument that will match the name of the server/channels:

$ jj-tail
<log of all channels of all servers ...>
$ jj-tail freenode
<log of all channels of freenode/* ...>
$ jj-tail qmail smtp
<log of freenode/#qmail freenode/#opensmtpd ...>
$ jj-tail server
<server logs of all channels follows (wich hilights) ...>

This way, you can reduce the scope of the conversation to one server, similar channels on multiple servers, just the selected channels...