Exploring the roots and future of Internet Relay Chat

systemd pour serveurs IRC : units, sandboxing et gestion d'IRCd, Anope et ZNC

Pendant des années, j'ai lancé mes serveurs IRC avec des scripts init.d bricolés, des screen en arrière-plan, ou pire — un nohup ./unrealircd start & dans un tmux. En 2024, si votre système utilise systemd (c'est-à-dire 95% des serveurs Linux en production), il n'y a aucune raison de ne pas en profiter. Voici comment configurer des fichiers unit propres pour chaque composant de votre infrastructure IRC.

Pourquoi gérer vos services IRC avec systemd

systemd offre des avantages concrets par rapport aux méthodes artisanales :

Ce que systemd apporte :
├── Démarrage automatique au boot
├── Redémarrage automatique en cas de crash
├── Gestion des dépendances (services lancés dans l'ordre)
├── Sandboxing (isolation du processus)
├── Logs centralisés via journald
├── Contrôle des ressources (CPU, RAM, I/O)
└── Interface unifiée (systemctl start/stop/status/restart)

La plupart des IRCd modernes ne fournissent pas de fichier unit dans leur distribution. UnrealIRCd a son propre wrapper (./unrealircd start), InspIRCd a le sien. Ces wrappers fonctionnent, mais ils n'offrent ni le sandboxing ni l'intégration journald de systemd.

Fichier unit pour un IRCd

UnrealIRCd :

# /etc/systemd/system/unrealircd.service
[Unit]
Description=UnrealIRCd IRC Server
After=network-online.target
Wants=network-online.target

[Service]
Type=forking
User=ircd
Group=ircd
WorkingDirectory=/opt/unrealircd
ExecStart=/opt/unrealircd/bin/unrealircd
ExecStop=/opt/unrealircd/bin/unrealircd stop
ExecReload=/opt/unrealircd/bin/unrealircd rehash
PIDFile=/opt/unrealircd/data/unrealircd.pid

Restart=on-failure
RestartSec=10
TimeoutStartSec=30
TimeoutStopSec=15

# Limites
LimitNOFILE=65536
LimitCORE=infinity

[Install]
WantedBy=multi-user.target

Le Type=forking est adapté parce qu'UnrealIRCd fork en arrière-plan par défaut. Le PIDFile permet à systemd de suivre le bon processus. LimitNOFILE=65536 est indispensable : un IRCd qui accepte des milliers de connexions a besoin de descripteurs de fichiers en conséquence.

InspIRCd :

# /etc/systemd/system/inspircd.service
[Unit]
Description=InspIRCd IRC Server
After=network-online.target
Wants=network-online.target

[Service]
Type=forking
User=ircd
Group=ircd
WorkingDirectory=/opt/inspircd
ExecStart=/opt/inspircd/bin/inspircd start
ExecStop=/opt/inspircd/bin/inspircd stop
ExecReload=/bin/kill -HUP $MAINPID
PIDFile=/opt/inspircd/data/inspircd.pid

Restart=on-failure
RestartSec=10
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Alternative avec Type=simple et l'option --nofork d'InspIRCd (recommandé pour une meilleure intégration systemd) :

[Service]
Type=simple
User=ircd
Group=ircd
ExecStart=/opt/inspircd/bin/inspircd --nofork --nopid
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
LimitNOFILE=65536

Le mode --nofork est préférable avec systemd : le processus reste en premier plan, systemd le supervise directement sans avoir besoin d'un PID file. Moins de points de défaillance.

Conseil : Utilisez Type=simple avec --nofork (ou équivalent) chaque fois que l'IRCd le supporte. Le type forking avec PID file est une source fréquente de bugs : PID file périmé, mauvais PID après un crash, systemd qui perd le suivi du processus.

Fichier unit pour Anope/Atheme

Anope :

# /etc/systemd/system/anope.service
[Unit]
Description=Anope IRC Services
After=unrealircd.service
Requires=unrealircd.service

[Service]
Type=forking
User=ircd
Group=ircd
WorkingDirectory=/opt/anope
ExecStart=/opt/anope/bin/services
ExecReload=/bin/kill -HUP $MAINPID
PIDFile=/opt/anope/data/services.pid

Restart=on-failure
RestartSec=15

[Install]
WantedBy=multi-user.target

Le After=unrealircd.service et Requires=unrealircd.service garantissent que les services IRC démarrent après l'IRCd. Si l'IRCd n'est pas lancé ou s'arrête, Anope sera arrêté aussi (Requires). Si vous voulez qu'Anope essaie de tourner même sans IRCd (il se reconnectera quand l'IRCd reviendra), utilisez Wants à la place de Requires.

Atheme :

# /etc/systemd/system/atheme.service
[Unit]
Description=Atheme IRC Services
After=network-online.target
Wants=unrealircd.service

[Service]
Type=forking
User=ircd
Group=ircd
WorkingDirectory=/opt/atheme
ExecStart=/opt/atheme/bin/atheme-services -c /opt/atheme/etc/atheme.conf
ExecReload=/bin/kill -HUP $MAINPID
PIDFile=/opt/atheme/var/atheme.pid

Restart=on-failure
RestartSec=15

# Atheme peut aussi tourner en foreground :
# Type=simple
# ExecStart=/opt/atheme/bin/atheme-services -n -c /opt/atheme/etc/atheme.conf

[Install]
WantedBy=multi-user.target

Fichier unit pour ZNC

ZNC supporte nativement le mode foreground avec --foreground, ce qui en fait un candidat idéal pour Type=simple :

# /etc/systemd/system/znc.service
[Unit]
Description=ZNC IRC Bouncer
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=znc
Group=znc
ExecStart=/usr/bin/znc --foreground --datadir /var/lib/znc
Restart=on-failure
RestartSec=10

# Sécurité
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/znc
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Si ZNC est installé depuis les paquets de votre distribution (apt install znc), un fichier unit est probablement déjà fourni. Vérifiez avec systemctl cat znc.

Sandboxing et hardening

C'est ici que systemd apporte le plus de valeur pour la sécurité. Les directives de sandboxing limitent ce que le processus peut faire, même en cas de compromission :

# Directives de hardening pour un IRCd
[Service]
# ...

# Empêcher l'élévation de privilèges
NoNewPrivileges=true

# Système de fichiers en lecture seule sauf les dossiers nécessaires
ProtectSystem=strict
ReadWritePaths=/opt/unrealircd/data /opt/unrealircd/logs /opt/unrealircd/tmp

# Interdire l'accès aux dossiers home
ProtectHome=true

# Répertoire /tmp privé
PrivateTmp=true

# Interdire le chargement de modules noyau
ProtectKernelModules=true

# Empêcher la modification des paramètres noyau
ProtectKernelTunables=true

# Filtrer les appels système (whitelist)
SystemCallFilter=@system-service
SystemCallFilter=~@mount @reboot @swap @raw-io
SystemCallErrorNumber=EPERM

# Interdire l'accès aux périphériques matériels
ProtectControlGroups=true
DevicePolicy=closed

# Restreindre les protocoles réseau
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# Empêcher la création de namespaces
RestrictNamespaces=true

Pour vérifier le niveau de sécurité de votre unit :

systemd-analyze security unrealircd.service

# Résultat type (avant hardening) :
# → Overall exposure level for unrealircd.service: 9.6 UNSAFE

# Après hardening :
# → Overall exposure level for unrealircd.service: 3.2 OK
Attention : L'option ProtectSystem=strict rend tout le système de fichiers en lecture seule. Si votre IRCd écrit des logs, des PID files ou des bases de données, vous devez lister explicitement ces chemins dans ReadWritePaths=. Testez toujours après avoir ajouté les directives de hardening — un IRCd qui ne peut pas écrire ses logs est un IRCd aveugle.

Logs avec journald

Quand un service tourne sous systemd, ses sorties stdout et stderr sont capturées par journald. Pour les IRCd qui loguent dans des fichiers, journald capture surtout les messages de démarrage et d'erreur. Pour ceux qui tournent en foreground (--nofork), journald capture tout.

# Voir les logs d'un service
journalctl -u unrealircd.service

# Logs en temps réel
journalctl -u unrealircd.service -f

# Logs depuis le dernier boot
journalctl -u unrealircd.service -b

# Logs d'erreur uniquement
journalctl -u unrealircd.service -p err

# Logs avec timestamp précis
journalctl -u unrealircd.service --since "2024-05-17 14:00" --until "2024-05-17 15:00"

# Combiner les logs de tous vos services IRC
journalctl -u unrealircd.service -u anope.service -u znc.service -f

Pour contrôler la rétention des logs :

# /etc/systemd/journald.conf
[Journal]
SystemMaxUse=500M
MaxRetentionSec=30d
Compress=yes

Si vous préférez envoyer les logs vers un fichier traditionnel (pour les ingérer dans un ELK ou Loki), ajoutez dans le fichier unit :

StandardOutput=append:/var/log/unrealircd/stdout.log
StandardError=append:/var/log/unrealircd/stderr.log

Orchestration et dépendances

Sur un serveur qui héberge plusieurs composants IRC, l'ordre de démarrage est déterminant. L'IRCd doit démarrer en premier, puis les services (Anope/Atheme), puis les bots et les bouncers.

# Ordre de démarrage :
# 1. IRCd (unrealircd.service)
# 2. Services IRC (anope.service) — After=unrealircd.service
# 3. ZNC (znc.service) — After=unrealircd.service
# 4. Bots divers — After=unrealircd.service anope.service

# Target personnalisée pour grouper vos services IRC
# /etc/systemd/system/irc.target
[Unit]
Description=IRC Server Stack
Requires=unrealircd.service
After=unrealircd.service
Wants=anope.service znc.service matterbridge.service

[Install]
WantedBy=multi-user.target

Avec cette target, un systemctl start irc.target lance toute votre stack IRC dans le bon ordre. Et systemctl status irc.target affiche l'état de tous les composants.

# Activer la target
sudo systemctl enable irc.target

# Démarrer tout
sudo systemctl start irc.target

# Voir l'état de la stack
sudo systemctl status irc.target
sudo systemctl list-dependencies irc.target

Pour le monitoring automatisé, systemd peut envoyer des notifications en cas de crash :

# /etc/systemd/system/unrealircd.service.d/notify.conf
[Unit]
OnFailure=notify-irc-crash@%n.service

# /etc/systemd/system/notify-irc-crash@.service
[Unit]
Description=Notify IRC crash for %i

[Service]
Type=oneshot
ExecStart=/usr/local/bin/notify-crash.sh %i

Questions fréquentes

Mon IRCd fonctionne déjà avec son propre script de démarrage. Pourquoi passer à systemd ?

Les scripts intégrés (comme ./unrealircd start) gèrent le lancement, mais pas le redémarrage automatique, le sandboxing, ni l'intégration journald. systemd vous donne la supervision du processus et l'isolation de sécurité sans effort supplémentaire. Le wrapper du IRCd reste utilisable pour les opérations manuelles — le fichier unit le remplace uniquement pour la gestion automatisée.

Faut-il utiliser Type=forking ou Type=simple ?

Préférez Type=simple avec l'option foreground du daemon (quand disponible). Type=forking avec PID file est plus fragile : si le PID file est périmé ou absent, systemd perd le suivi du processus. InspIRCd avec --nofork, ZNC avec --foreground, et Atheme avec -n sont tous adaptés au mode simple.

Le sandboxing systemd casse-t-il le fonctionnement de l'IRCd ?

Potentiellement, si vous activez des restrictions trop strictes sans les adapter. Le problème le plus fréquent : ProtectSystem=strict empêche l'écriture — ajoutez ReadWritePaths= pour les répertoires de données et de logs. Activez les directives une par une, testez après chaque ajout, et utilisez systemd-analyze security pour évaluer votre progression.