Set up an IRC server
___ _
/ __| ___ | |_
\__ \ / -_) | _|
|___/ \___| \__|
_ _ _ __
| || | | '_ \
\_,_| | .__/
|_|
__ _ _ _
/ _` | | ' \
\__,_| |_||_|
___ ___ ___
|_ _| | _ \ / __|
| | | / | (__
|___| |_|_\ \___|
___ ___ _ _ __ __ ___ _ _
(_-< / -_) | '_| \ V / / -_) | '_|
/__/ \___| |_| \_/ \___| |_|
╔─*──*──*──*──*──*──*──*──*──*──*──*──*──*──*──*─╗
║1 ........................................ 1║
║2* ........................................ *2║
║3 ........................................ 3║
║1 ...........Posted: 2024-03-30........... 1║
║2* .........Tags: linux sysadmin .......... *2║
║3 ........................................ 3║
║1 ........................................ 1║
╚────────────────────────────────────────────────╝
My journey setting up a little IRC server, with SSL support and services, on
Debian 12.
I feel like I went through a lot of struggles and research to get to the point
of having a functional server. I tried various IRC daemons services, but
ultimately decided to go with ngircd and atheme. I feel these are well
supported, maintained and current. I also thought they were easier and simpler
to understand and set up. An almost out-of-the-box experience.
This is the setup I use for my IRC server[1]. Try joining!
If you're struggling feel free to contact me using info from my about page[2].
# ngircd
I think ngircd is very simple and easy to work with.
## Basic ngircd installation and configuration
Install:
```
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install ngircd
```
Now edit `/etc/ngircd/ngircd.conf`. We will come back to this config file later,
but there are some preliminary configurations you can test out and configure:
* `[Global]` settings* `Name`: I set mine to `irc.someodd.zip`
* The admin info and email
* `Listen`: You might want to set which IP addresses the server listens on.
For
example, I listen on `0.0.0.0`.
* `MotdFile` (at `/etc/ngircd/motd.txt`)
* `Ports`: I think actually resolved a connection issue for `atheme-services`
by
explicitly setting this to `6667`
* `ServerGID` & `ServerUID`: I have these set to `irc` (user and group server
runs under, be aware of file permissions to things ngircd needs to access)
* Set an `[Oper]` block.* I have mine set to the same username I have
registered
with atheme services
Try `sudo service ngircd restart`.
Handy commands:
* A handy command for debugging ngircd is `sudo journalctl -xeu ngircd.service`.
* Reload config (including letsencrypt/ssl cert) without restarting
service/interrupting:
```
pgrep ngircd
sudo kill -HUP <PID>
```
## ngircd + LetsEncrypt (SSL)
Get SSL connections working on the IRC server by using LetsEncrypt to manage the
certificate automatically.
I was able to specify my icecast2 web root (selecting 2, place in web root when
asked) since I already have that in order to do the ACME verification or
whatever! Otherwise you might wanna do something like `sudo certbot certonly
--standalone -d irc.someodd.zip` (which will start a web server for you).
```
sudo certbot certonly --webroot-path="/usr/share/icecast2/web" -d 'irc.someodd.zip'
```
Do a bunch of file management for ngircd's permissions sake, but also create a
`dhparams.pem`:
```
sudo mkdir /etc/ngircd/ssl
sudo openssl dhparam -out /etc/letsencrypt/live/irc.someodd.zip/dhparams.pem 2048
sudo cp /etc/letsencrypt/live/irc.someodd.zip/fullchain.pem /etc/ngircd/ssl/
sudo cp /etc/letsencrypt/live/irc.someodd.zip/dhparams.pem /etc/ngircd/ssl/
sudo cp /etc/letsencrypt/live/irc.someodd.zip/privkey.pem /etc/ngircd/ssl/
sudo chown -R irc:irc /etc/ngircd/ssl/
```
Point to those files in `/etc/ngircd/ngircd.conf`, in the `[SSL]` section. In
fact here's basically my entire `[SSL]` block, with comments removed:
```
[SSL]
CertFile = /etc/ngircd/ssl/fullchain.pem
CipherList = SECURE128:-VERS-SSL3.0
DHFile = /etc/ngircd/ssl/dhparams.pem
KeyFile = /etc/ngircd/ssl/privkey.pem
Ports = 6697
```
Note I have SSL connections accepted on port 6697.
Allow connections to the configured SSL port with:
```
sudo ufw allow 6697/tcp comment 'SSL IRC'
```
Of course if you want to test external connections, do some port forwarding on
your router. Finally, test it out by first restarting `ngircd` (`sudo service
ngircd restart`) and then connecting to your server on that port with SSL!
### Handling LetsEncrypt renewals
Certificates expire or something, I guess. Luckily LetsEncrypt makes automation
fairly simple.
Configure this in `/etc/letsencrypt/renewal/irc.someodd.zip.conf` under
`[renewalparams]`:
```
renew_hook = cp /etc/letsencrypt/live/irc.someodd.zip/fullchain.pem /etc/ngircd/ssl/ && cp /etc/letsencrypt/live/irc.someodd.zip/privkey.pem /etc/ngircd/ssl/ && chown -R irc:irc /etc/ngircd/ssl/ && kill -HUP $(pidof ngircd)
```
Validate that the above command works correctly:
```
sudo certbot renew --dry-run --cert-name irc.someodd.zip
```
Please note, something I don't like about this script and would like to update
in the future, is that I think it should use Atheme's global notice module.
# Atheme
I like Atheme because it seems to have an active community, basically works well
out-of-the-box, and is relatively straightforward.
I strongly recommend reading the official ngircd's services.txt for service
configuration instructions[3].
Install Atheme:
```
sudo apt-get update
sudo apt-get install atheme-services
```
A handy command for debugging `atheme-services` is `sudo journalctl -xeu
atheme-services.service`.
## Preparing ngircd for Atheme
Add a new `[Server]` block in `ngircd.conf` for Atheme:
```
[Server]
Name = services.irc.someodd.zip
Pass = abc
MyPassword = abc
Type = Service
SSLConnect = no
MyPassword = abc
PeerPassword = abc
ServiceMask = *Serv
```
Caveat: in this server block, don't define host and port, because that'll make
ngircd try to connect to said host/port. My mistake was thinking that I was
using such to define what the block would listen on. You make Atheme just
connect to your server like a regular client almost. I made this mistake and it
lead to a lot of confusion. The idea is Atheme connects to ngircd, not the other
way around!
Go ahead and `sudo service ngircd restart`.
## Actually configuring Atheme
Copy an example configuration to the expected config file location:
```
sudo cp /usr/share/doc/atheme-services/examples/atheme.conf.example /etc/atheme/atheme.conf
```
A few things you really should set in `atheme.conf`:
* Ensure `loadmodule "modules/protocol/ngircd";` is set (protocol module)
* In `serverinfo` ensure the `name` setting reflects the `Name` setting
configured earlier when configuring the Atheme `[Server]` block in ngircd. For
me that was `name = "services.irc.someodd.zip";`
* Edit the uplink:* For me, because of my `ngircd` server name, I have `uplink
"irc.someodd.zip"
{`
* Set `password` to `abc` (or whatever!) as reflected in the Atheme `[Server]`
block made in `ngircd.conf` as shown earlier in this document.
* Connect on port `6667` with `port = 6667` (non-SSL).
Now have fun and test out with `sudo service atheme-services restart`! Try to
`/msg nickserv help`.
## Make sure to back up the database!
My database, I think, was at `/var/lib/atheme/services.db`. I added it to the
list of things that restic backs up.
## Known issues
**I think it's not saving nicknames/registration after restart? That's severe!**
or maybe it's that i need to verify by email for it to work right? i should
disable that? i have to follow up on this.
# Bonus
Stability, backup tips
## ZNC
Install:
```
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install znc
```
Run the configuration wizard:
```
znc --makeconf
```
Since I already have port 6697 for SSL on the main IRC server I'll use `6669`.
This just makes it run as the current user you have it set up as. I probably
will make a better set up in the future, but this is fine right now. Also the
SSL cert it generates is self-signed and doesn't use letsencrypt.
Let's make it start at startup:
```
sudo systemctl enable znc
```
And run:
```
sudo service znc start
```
UFW:
```
sudo ufw allow proto tcp from any to any port 6669 comment 'ZNC SSL'
```
You can access the web UI through the same port, but firefox will require
setting `network.security.ports.banned.override` to the string value of `6669`
in `about:config`. Also forward port on router.
Only edit `~/.znc/configs/znc.conf` if ZNC not running.
Honestly, somehow I'm just running it as my regular user so I just launch `znc`
as that regular user.
You may want this in your nginx for znc if you setup ZNC:
```
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/znc.someodd.zip;
}
```
### SSL + more
Create the certificate or whatever (note I'm using the web root from my Whisper
Radio setup[4] but you may wanna just use the `--standalone` option instead; I
selected *webroot* when prompted):
```
sudo certbot certonly --webroot-path="/usr/share/icecast2/web" -d 'znc.someodd.zip'
```
Copy the files, ensure ownership, create a directory (we will automate this with
a hook, too):
```
mkdir ~/.znc/ssl
sudo cp /etc/letsencrypt/live/znc.someodd.zip/fullchain.pem /home/someuser/.znc/ssl/fullchain.pem
sudo cp /etc/letsencrypt/live/znc.someodd.zip/privkey.pem /home/someuser/.znc/ssl/privkey.pem
sudo chown -R someuser:someuser ~/.znc/ssl
```
Now in `~/.znc/configs/znc.conf` (make sure ZNC isn't running):
```
SSLCertFile = /home/someuser/.znc/ssl/fullchain.pem
SSLKeyFile = /home/someuser/.znc/ssl/privkey.pem
<Listener l>
Port = 6669
IPv4 = true
IPv6 = true
SSL = true
</Listener>
```
check service file see which user runs as or make znc run as a user...
in hex chat i have `znc.someodd.zip/6669` and default login method with my admin
password and I mamke sure the username is the right username (don't use default
info)
NEEDS TO USE HOOK POST HOOK FOR RENEWAL... LETSENCRYPT
something is wrong with the ZNC configuration (nginx)
### ZNC user config, tips
To get messages on all devices (like if you have a laptop and a phone) and want
to make sure you don't miss anything, even if you receive on one device:
```
/msg *controlpanel set AutoClearChanBuffer $me False
/msg *controlpanel set AutoClearQueryBuffer $me False
```
Maybe I should make this for all users by default!
switching...
```
/znc JumpNetwork netname
```
you can login using the default login message with username/pasword, but you can
also specify the network to connect by default to make connecting to multiple
networks easier:
Client configs:
* You can set an IRC password like `username/network:password` to connect to a
specific network by default. This will let you have different ZNC connections
(say one for someodd and one for libera.chat).
* I think you can even set `username/network` as the username and just have
your
ZNC password as the password.
* HexChat - ZNC[5]
* Add your ZNC server’s address and port to the server list, like
`znc.example.com/6667` for non-SSL connections or `znc.example.com/+6697`
for
SSL connections (prepend the port number with a `+` for SSL).
* In the `Password` field, input your ZNC credentials in the format
`username/network:password`. This tells HexChat how to log in to ZNC and
specifies which of your ZNC-configured networks to connect to.
### Playback, Clientbuffer Modules
I feel this is kind of required in the modern times where you may have phone and
laptop connected. You may want to have the same playback/buffer for all clients.
*
https://wiki.znc.in/Playback[6]
*
https://wiki.znc.in/Clientbuffer[7]
*
https://wiki.znc.in/Modules[8]
*
https://wiki.znc.in/Compiling_modules[9]
```
sudo apt-get install znc-buildmod
git clone
https://github.com/jpnurmi/znc-playback
cd znc-playback
znc-buildmod playback.cpp
mkdir -p ~/.znc/modules
mv playback.so ~/.znc/modules
```
Then send `LoadMod playback` to `*status`.
Now `clientbuffer`:
```
git clone
https://github.com/CyberShadow/znc-clientbuffer
cd znc-clientbuffer
znc-buildmod clientbuffer.cpp
mv clientbuffer.so ~/.znc/modules
```
Use the autoadd argument.
```
/msg *status Broadcast about to restart ZNC for maintenance. Please reconnect in a few minutes.
/msg *status SaveConfig
```
now `pkill znc` and `znc`.
now when i checked global modules, playback was set. `clientbuffer` seems to
only show up in editnetwork.
It may be very handy to use the ClientBuffer module. In which case you can set
an identifier either way:
* `username@identifier/network:password` as the password and your username as
the username
* `username@identifier/network` as the username, and your password as the
password
### tor
..
### tips
debug with:
```
znc -D
```
### backing up
..
## XMLRPC
Enable the httpd/XMLRPC in server. Here are some examples...
I had to read the source code and use chatgpt to figure this out.
```
curl -X POST -H 'Content-Type: text/xml' -d '<?xml version="1.0"?>
<methodCall>
<methodName>atheme.login</methodName>
<params>
<param>
<value><string>username</string></value>
</param>
<param>
<value><string>password</string></value>
</param>
</params>
</methodCall>'
http://localhost:8080/xmlrpc
```
ideally i want to access statistics without logging in.
finally found this?
https://raw.githubusercontent.com/atheme/atheme/master/doc/XMLRPC
```
curl -X POST -H 'Content-Type: text/xml' -d '<?xml version="1.0"?>
<methodCall>
<methodName>atheme.command</methodName>
<params>
<param><value><string>authcookie</string></value></param>
<param><value><string>someodd</string></value></param>
<param><value><string>sourceIP</string></value></param>
<param><value><string>ChanServ</string></value></param>
<param><value><string>info</string></value></param>
<param><value><string>#channel</string></value></param>
</params>
</methodCall>'
http://localhost:8080/xmlrpc
```
maybe set up a dummy user that will do this.
## tor
..
## setup to share statistics
I think the easiest way to set up statistics haring is just to have nginx share
a web root and then have a cron job run a script to echo the contents to the web
root:
```
sudo apt-get install nginx
```
then edit `sudo vi /etc/nginx/sites-available/irc.someodd.zip.conf` (we also
want to set cors header or whatever so it's easier to fetch the site using
javascript):
```
server {
listen 8765;
listen 8888 ssl;
server_name irc.someodd.zip;
root /var/www/irc.someodd.zip;
ssl_certificate /etc/letsencrypt/live/irc.someodd.zip/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/irc.someodd.zip/privkey.pem;
location /stats.json {
# Add CORS headers
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept';
add_header 'Access-Control-Allow-Credentials' 'true';
}
location / {
try_files $uri $uri/ =404;
}
}
```
link the config to make it active
```
sudo ln -s /etc/nginx/sites-available/irc.someodd.zip.conf /etc/nginx/sites-enabled/
```
..
make the web root:
```
mkdir /var/www/irc.someodd.zip/
```
finally create this bash script to `/usr/local/bin/irc_stats.sh` with these
contents, and you must install `ii` to get it to work: ...
```
#!/bin/bash
# Configuration variables
SERVER="127.0.0.1" # Change to your IRC server
PORT=6667 # Default IRC port
NICK="iistats" # Change to your desired nickname
IIDIR="/tmp/ii_$$" # Temporary directory for ii's output
# Start ii in the background
ii -s "$SERVER" -p "$PORT" -n "$NICK" -f "ii User" -i "$IIDIR" >/dev/null 2>&1 &
IIPID=$!
# Allow some time for ii to connect
sleep 4
input=$(cat "$IIDIR/$SERVER/out")
# Parsing the information using grep and regex
number_of_services=$(echo "$input" | grep -oP '(?<=and )\d+(?= services on)' | head -1)
current_number_of_users=$(echo "$input" | grep -oP '(?<=I have )\d+(?= users,)' | head -1)
highest_connection_count=$(echo "$input" | grep -oP '(?<=Highest connection count: )\d+')
number_of_channels_formed=$(echo "$input" | grep -oP '\d+(?= channels formed)' | head -1)
operators_online=$(echo "$input" | grep -oP '\d+(?= operator\(s\) online)')
# ngIRCd uptime (days)
ngircd_pid=$(pgrep ngircd)
if [ -n "$ngircd_pid" ]; then
ngircd_uptime_seconds=$(ps -p "$ngircd_pid" -o etimes= | tail -n 1 | tr -d ' ')
ngircd_uptime=$(echo "$ngircd_uptime_seconds / 86400" | bc)
else
ngircd_uptime="N/A"
fi
# Atheme uptime (days)
atheme_pid=$(pgrep atheme-services)
if [ -n "$atheme_pid" ]; then
atheme_uptime_seconds=$(ps -p "$atheme_pid" -o etimes= | tail -n 1 | tr -d ' ')
atheme_uptime=$(echo "$atheme_uptime_seconds / 86400" | bc)
else
atheme_uptime="N/A"
fi
# output json
echo "{
\"number of channels formed\": ${number_of_channels_formed:-0},
\"number of services\": ${number_of_services:-0},
\"current number of users\": ${current_number_of_users:-0},
\"highest connection count\": ${highest_connection_count:-0},
\"operators online\": ${operators_online:-0},
\"ngircd uptime days\": \"$ngircd_uptime\",
\"atheme uptime days\": \"$atheme_uptime\"
}"
# Clean up
kill $IIPID
rm -rf "$IIDIR"
```
chmod:
```
sudo chmod +x /usr/local/bin/irc_stats.sh
```
you can test it
```
sudo /usr/local/bin/irc_stats.sh > /var/www/irc.someodd.zip/stats.json
```
crontab it `crontab -e`:
```
*/30 * * * * /usr/local/bin/irc_stats.sh > /var/www/irc.someodd.zip/stats.json 2>&1
```
then
`sudo service nginx restart`
now change the web root path in the letsencrypt file...
ufw for nginx:
```
sudo ufw allow 8888/tcp comment 'general nginx https'
```
then you can just fetch this:
https://irc.someodd.zip/stats.txt (port forward
443 -> 8888) for stats line-by-line it'll look like:
```
number of channels formed: 2
number of services: 8
current number of users: 5
highest connection count: 9
operators online: 1
ngircd uptime: 5 days
atheme uptime: 1 days
```
the example javascript:
```
fetch('
https://irc.someodd.zip/stats.json')
.then(response => response.json())
.then(data => {
// Parse the JSON object
const numberOfChannelsFormed = data['number of channels formed'];
const numberOfServices = data['number of services'];
const currentNumberOfUsers = data['current number of users'];
const highestConnectionCount = data['highest connection count'];
const operatorsOnline = data['operators online'];
const ngircdUptimeDays = data['ngircd uptime days'];
const athemeUptimeDays = data['atheme uptime days'];
// Display the parsed data
console.log('Number of channels formed:', numberOfChannelsFormed);
console.log('Number of services:', numberOfServices);
console.log('Current number of users:', currentNumberOfUsers);
console.log('Highest connection count:', highestConnectionCount);
console.log('Operators online:', operatorsOnline);
console.log('ngIRCd uptime days:', ngircdUptimeDays);
console.log('Atheme uptime days:', athemeUptimeDays);
})
.catch(error => {
console.error('Error fetching data:', error);
});
```
you can see a demo of irc stats in action here[10]
gotta modify for letsencrypt `sudo vi
/etc/letsencrypt/renewal/irc.someodd.zip.conf`:
```
webroot_path = /var/www/irc.someodd.zip,
```
you may also want to set the `[[webroot_map]]`
test with `sudo certbot renew --dry-run`
### ZNC BEHIND NGINX (stats continued)
this setup is kinda broken so use
https://stackoverflow.com/questions/34236949/znc-on-a-subdomain-with-nginx-reverse-proxy
?
https://walkergriggs.com/2021/10/13/znc_the_right_way/
https://wiki.znc.in/Reverse_Proxy
edit the `~/.znc/configs/znc.conf`:
```
TrustedProxy = 127.0.0.1
<Listener listener1>
AllowIRC = false
AllowWeb = true
IPv4 = true
IPv6 = true
Port = 6666
SSL = false
URIPrefix = /
</Listener>
```
you can safely shut down:
```
znc --quit
znc
```
HONESTLY DON'T EVEN NEED TO LISTNE ON 6666.. COULD JUST SSL TWICE.
for the sake of cerbot you may also want to add a config for znc, which you can
use as an opportunity to use it through a better port, edit
`/etc/nginx/sites-available/znc.someodd.zip.conf`:
```
server {
listen 8888 ssl http2;
listen 8765;
server_name znc.someodd.zip;
access_log /var/log/nginx/irc.log combined;
ssl_certificate /etc/letsencrypt/live/znc.someodd.zip/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/znc.someodd.zip/privkey.pem;
location / {
proxy_pass
http://127.0.0.1:6666;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Client-Verify SUCCESS;
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-SSL-Subject $ssl_client_s_dn;
proxy_set_header X-SSL-Issuer $ssl_client_i_dn;
proxy_read_timeout 1800;
proxy_connect_timeout 1800;
}
}
```
make the dir:
`sudo mkdir /var/www/znc.someodd.zip`
```
sudo ln -s /etc/nginx/sites-available/znc.someodd.zip.conf /etc/nginx/sites-enabled/
```
restart nginx.
you should be able to access znc on the regular
https://znc.someodd.zip/
edit the znc letsencrypt:
```
webroot_path = /var/www/znc.someodd.zip,
[[webroot_map]]
znc.someodd.zip = /var/www/znc.someodd.zip
```
try:
```
sudo certbot renew --dry-run
```
## Footnotes
[1]: my IRC server: /services/irc-server.md
[2]: my about page: /about
[3]: the official ngircd's services.txt for service configuration instructions:
https://github.com/ngircd/ngircd/blob/master/doc/Services.txt
[4]: my Whisper Radio setup: /showcase/whisper-radio
[5]: HexChat - ZNC:
https://wiki.znc.in/HexChat
[6]:
https://wiki.znc.in/Playback:
https://wiki.znc.in/Playback
[7]:
https://wiki.znc.in/Clientbuffer:
https://wiki.znc.in/Clientbuffer
[8]:
https://wiki.znc.in/Modules:
https://wiki.znc.in/Modules
[9]:
https://wiki.znc.in/Compiling_modules:
https://wiki.znc.in/Compiling_modules
[10]: here: /showcase/irc-server/#statistics