diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 07774dd1d29..4a63a09ab84 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -682,6 +682,7 @@
./services/networking/i2p.nix
./services/networking/icecream/scheduler.nix
./services/networking/icecream/daemon.nix
+ ./services/networking/inspircd.nix
./services/networking/iodine.nix
./services/networking/iperf3.nix
./services/networking/ircd-hybrid/default.nix
diff --git a/nixos/modules/services/networking/inspircd.nix b/nixos/modules/services/networking/inspircd.nix
new file mode 100644
index 00000000000..fcac8d34cce
--- /dev/null
+++ b/nixos/modules/services/networking/inspircd.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.inspircd;
+
+ configFile = pkgs.writeText "inspircd.conf" cfg.config;
+
+in {
+ options = {
+ services.inspircd = {
+ enable = lib.mkEnableOption "InspIRCd";
+
+ package = lib.mkOption {
+ type = lib.types.package;
+ default = pkgs.inspircd;
+ defaultText = lib.literalExample "pkgs.inspircd";
+ example = lib.literalExample "pkgs.inspircdMinimal";
+ description = ''
+ The InspIRCd package to use. This is mainly useful
+ to specify an overridden version of the
+ pkgs.inspircd dervivation, for
+ example if you want to use a more minimal InspIRCd
+ distribution with less modules enabled or with
+ modules enabled which can't be distributed in binary
+ form due to licensing issues.
+ '';
+ };
+
+ config = lib.mkOption {
+ type = lib.types.lines;
+ description = ''
+ Verbatim inspircd.conf file.
+ For a list of options, consult the
+ InspIRCd documentation, the
+ Module documentation
+ and the example configuration files distributed
+ with pkgs.inspircd.doc
+ '';
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.services.inspircd = {
+ description = "InspIRCd - the stable, high-performance and modular Internet Relay Chat Daemon";
+ wantedBy = [ "multi-user.target" ];
+ requires = [ "network.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ ExecStart = ''
+ ${lib.getBin cfg.package}/bin/inspircd start --config ${configFile} --nofork --nopid
+ '';
+ DynamicUser = true;
+ };
+ };
+ };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 3ce71b0abe6..7d19e7309bc 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -176,6 +176,7 @@ in
initrd-network-ssh = handleTest ./initrd-network-ssh {};
initrdNetwork = handleTest ./initrd-network.nix {};
initrd-secrets = handleTest ./initrd-secrets.nix {};
+ inspircd = handleTest ./inspircd.nix {};
installer = handleTest ./installer.nix {};
iodine = handleTest ./iodine.nix {};
ipfs = handleTest ./ipfs.nix {};
diff --git a/nixos/tests/inspircd.nix b/nixos/tests/inspircd.nix
new file mode 100644
index 00000000000..b2975091d36
--- /dev/null
+++ b/nixos/tests/inspircd.nix
@@ -0,0 +1,96 @@
+let
+ clients = [
+ "ircclient1"
+ "ircclient2"
+ ];
+ server = "inspircd";
+ ircPort = 6667;
+ channel = "nixos-cat";
+ iiDir = "/tmp/irc";
+in
+
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+ name = "inspircd";
+ nodes = {
+ "${server}" = {
+ networking.firewall.allowedTCPPorts = [ ircPort ];
+ services.inspircd = {
+ enable = true;
+ package = pkgs.inspircdMinimal;
+ config = ''
+
+
+ '';
+ };
+ };
+ } // lib.listToAttrs (builtins.map (client: lib.nameValuePair client {
+ imports = [
+ ./common/user-account.nix
+ ];
+
+ systemd.services.ii = {
+ requires = [ "network.target" ];
+ wantedBy = [ "default.target" ];
+
+ serviceConfig = {
+ Type = "simple";
+ ExecPreStartPre = "mkdir -p ${iiDir}";
+ ExecStart = ''
+ ${lib.getBin pkgs.ii}/bin/ii -n ${client} -s ${server} -i ${iiDir}
+ '';
+ User = "alice";
+ };
+ };
+ }) clients);
+
+ testScript =
+ let
+ msg = client: "Hello, my name is ${client}";
+ clientScript = client: [
+ ''
+ ${client}.wait_for_unit("network.target")
+ ${client}.systemctl("start ii")
+ ${client}.wait_for_unit("ii")
+ ${client}.wait_for_file("${iiDir}/${server}/out")
+ ''
+ # wait until first PING from server arrives before joining,
+ # so we don't try it too early
+ ''
+ ${client}.wait_until_succeeds("grep 'PING' ${iiDir}/${server}/out")
+ ''
+ # join ${channel}
+ ''
+ ${client}.succeed("echo '/j #${channel}' > ${iiDir}/${server}/in")
+ ${client}.wait_for_file("${iiDir}/${server}/#${channel}/in")
+ ''
+ # send a greeting
+ ''
+ ${client}.succeed(
+ "echo '${msg client}' > ${iiDir}/${server}/#${channel}/in"
+ )
+ ''
+ # check that all greetings arrived on all clients
+ ] ++ builtins.map (other: ''
+ ${client}.succeed(
+ "grep '${msg other}$' ${iiDir}/${server}/#${channel}/out"
+ )
+ '') clients ++ [
+ "# trailing comment to please reformatter :)"
+ ];
+
+ # foldl', but requires a non-empty list instead of a start value
+ reduce = f: list:
+ builtins.foldl' f (builtins.head list) (builtins.tail list);
+ in ''
+ start_all()
+ ${server}.wait_for_open_port(${toString ircPort})
+
+ # run clientScript for all clients so that every list
+ # entry is executed by every client before advancing
+ # to the next one.
+ ${lib.concatStringsSep "\n"
+ (reduce
+ (a: b: lib.zipListsWith (cs: c: cs + "\n" + c) a b)
+ (builtins.map clientScript clients))}
+ '';
+})