1 { config, lib, pkgs, options, ... }:
3 cfg = config.services.biboumi;
4 inherit (config.environment) etc;
5 rootDir = "/run/biboumi/mnt-root";
6 stateDir = "/var/lib/biboumi";
7 settingsFile = pkgs.writeText "biboumi.cfg" (
8 lib.generators.toKeyValue {
10 lib.optionalString (v != null) (lib.generators.mkKeyValueDefault {} "=" k v);
12 need_CAP_NET_BIND_SERVICE = cfg.settings.identd_port != 0 && cfg.settings.identd_port < 1024;
17 enable = lib.mkEnableOption "the Biboumi XMPP gateway to IRC";
19 settings = lib.mkOption {
21 See [biboumi 8.5](https://lab.louiz.org/louiz/biboumi/blob/8.5/doc/biboumi.1.rst)
25 type = lib.types.submodule {
26 freeformType = with lib.types;
27 (attrsOf (nullOr (oneOf [str int bool]))) // {
28 description = "settings option";
30 options.admin = lib.mkOption {
31 type = with lib.types; listOf str;
33 example = ["admin@example.org"];
34 apply = lib.concatStringsSep ":";
36 The bare JID of the gateway administrator. This JID will have more
37 privileges than other standard users, for example some administration
38 ad-hoc commands will only be available to that JID.
41 options.ca_file = lib.mkOption {
42 type = lib.types.path;
43 default = "/etc/ssl/certs/ca-certificates.crt";
45 Specifies which file should be used as the list of trusted CA
46 when negotiating a TLS session.
49 options.db_name = lib.mkOption {
50 type = with lib.types; either path str;
51 default = "${stateDir}/biboumi.sqlite";
53 The name of the database to use.
55 example = "postgresql://user:secret@localhost";
57 options.hostname = lib.mkOption {
59 example = "biboumi.example.org";
61 The hostname served by the XMPP gateway.
62 This domain must be configured in the XMPP server
63 as an external component.
66 options.identd_port = lib.mkOption {
67 type = lib.types.port;
71 The TCP port on which to listen for identd queries.
74 options.log_level = lib.mkOption {
75 type = lib.types.ints.between 0 3;
78 Indicate what type of log messages to write in the logs.
79 0 is debug, 1 is info, 2 is warning, 3 is error.
82 options.password = lib.mkOption {
83 type = with lib.types; nullOr str;
85 The password used to authenticate the XMPP component to your XMPP server.
86 This password must be configured in the XMPP server,
87 associated with the external component on
88 [hostname](#opt-services.biboumi.settings.hostname).
90 Set it to null and use [credentialsFile](#opt-services.biboumi.credentialsFile)
91 if you do not want this password to go into the Nix store.
94 options.persistent_by_default = lib.mkOption {
95 type = lib.types.bool;
98 Whether all rooms will be persistent by default:
99 the value of the “persistent” option in the global configuration of each
100 user will be “true”, but the value of each individual room will still
101 default to false. This means that a user just needs to change the global
102 “persistent” configuration option to false in order to override this.
105 options.policy_directory = lib.mkOption {
106 type = lib.types.path;
107 default = "${pkgs.biboumi}/etc/biboumi";
108 defaultText = lib.literalExpression ''"''${pkgs.biboumi}/etc/biboumi"'';
110 A directory that should contain the policy files,
111 used to customize Botan’s behaviour
112 when negotiating the TLS connections with the IRC servers.
115 options.port = lib.mkOption {
116 type = lib.types.port;
119 The TCP port to use to connect to the local XMPP component.
122 options.realname_customization = lib.mkOption {
123 type = lib.types.bool;
126 Whether the users will be able to use
127 the ad-hoc commands that lets them configure
128 their realname and username.
131 options.realname_from_jid = lib.mkOption {
132 type = lib.types.bool;
135 Whether the realname and username of each biboumi
136 user will be extracted from their JID.
137 Otherwise they will be set to the nick
138 they used to connect to the IRC server.
141 options.xmpp_server_ip = lib.mkOption {
142 type = lib.types.str;
143 default = "127.0.0.1";
145 The IP address to connect to the XMPP server on.
146 The connection to the XMPP server is unencrypted,
147 so the biboumi instance and the server should
148 normally be on the same host.
154 credentialsFile = lib.mkOption {
155 type = lib.types.path;
157 Path to a configuration file to be merged with the settings.
158 Beware not to surround "=" with spaces when setting biboumi's options in this file.
159 Useful to merge a file which is better kept out of the Nix store
160 because it contains sensible data like
161 [password](#opt-services.biboumi.settings.password).
163 default = "/dev/null";
164 example = "/run/keys/biboumi.cfg";
167 openFirewall = lib.mkEnableOption "opening of the identd port in the firewall";
171 config = lib.mkIf cfg.enable {
172 networking.firewall = lib.mkIf (cfg.openFirewall && cfg.settings.identd_port != 0)
173 { allowedTCPPorts = [ cfg.settings.identd_port ]; };
175 systemd.services.biboumi = {
176 description = "Biboumi, XMPP to IRC gateway";
177 after = [ "network.target" ];
178 wantedBy = [ "multi-user.target" ];
182 # Biboumi supports systemd's watchdog.
185 # Use "+" because credentialsFile may not be accessible to User= or Group=.
186 ExecStartPre = [("+" + pkgs.writeShellScript "biboumi-prestart" ''
188 cat ${settingsFile} '${cfg.credentialsFile}' |
189 install -m 644 /dev/stdin /run/biboumi/biboumi.cfg
191 ExecStart = "${pkgs.biboumi}/bin/biboumi /run/biboumi/biboumi.cfg";
192 ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID";
193 # Firewalls needing opening for output connections can still do that
194 # selectively for biboumi with:
195 # users.users.biboumi.isSystemUser = true;
197 # networking.nftables.ruleset = ''
198 # add rule inet filter output meta skuid biboumi tcp accept
201 RootDirectory = rootDir;
202 RootDirectoryStartOnly = true;
203 InaccessiblePaths = [ "-+${rootDir}" ];
204 RuntimeDirectory = [ "biboumi" (lib.removePrefix "/run/" rootDir) ];
205 RuntimeDirectoryMode = "700";
206 StateDirectory = "biboumi";
207 StateDirectoryMode = "700";
212 # This is for Type="notify"
213 # See https://github.com/systemd/systemd/issues/3544
214 "/run/systemd/notify"
215 "/run/systemd/journal/socket"
217 BindReadOnlyPaths = [
221 # The following options are only for optimizing:
222 # systemd-analyze security biboumi
223 AmbientCapabilities = [ (lib.optionalString need_CAP_NET_BIND_SERVICE "CAP_NET_BIND_SERVICE") ];
224 CapabilityBoundingSet = [ (lib.optionalString need_CAP_NET_BIND_SERVICE "CAP_NET_BIND_SERVICE") ];
225 # ProtectClock= adds DeviceAllow=char-rtc r
227 LockPersonality = true;
228 MemoryDenyWriteExecute = true;
229 NoNewPrivileges = true;
230 PrivateDevices = true;
231 PrivateMounts = true;
232 PrivateNetwork = lib.mkDefault false;
234 # PrivateUsers=true breaks AmbientCapabilities=CAP_NET_BIND_SERVICE
235 # See https://bugs.archlinux.org/task/65921
236 PrivateUsers = !need_CAP_NET_BIND_SERVICE;
238 ProtectControlGroups = true;
240 ProtectHostname = true;
241 ProtectKernelLogs = true;
242 ProtectKernelModules = true;
243 ProtectKernelTunables = true;
244 ProtectSystem = "strict";
246 # AF_UNIX is for /run/systemd/notify
247 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
248 RestrictNamespaces = true;
249 RestrictRealtime = true;
250 RestrictSUIDSGID = true;
253 # Groups in @system-service which do not contain a syscall
254 # listed by perf stat -e 'syscalls:sys_enter_*' biboumi biboumi.cfg
255 # in tests, and seem likely not necessary for biboumi.
256 # To run such a perf in ExecStart=, you have to:
257 # - AmbientCapabilities="CAP_SYS_ADMIN"
258 # - mount -o remount,mode=755 /sys/kernel/debug/{,tracing}
259 "~@aio" "~@chown" "~@ipc" "~@keyring" "~@resources" "~@setuid" "~@timer"
261 SystemCallArchitectures = "native";
262 SystemCallErrorNumber = "EPERM";
267 meta.maintainers = with lib.maintainers; [ julm ];