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..8cb2b406ee2
--- /dev/null
+++ b/nixos/modules/services/networking/inspircd.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.services.inspircd;
+
+ configFile = pkgs.writeText "inspircd.conf" cfg.config;
+
+in {
+ meta = {
+ maintainers = [ lib.maintainers.sternenseemann ];
+ };
+
+ 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 4c5a2b39bd9..fb45ec1a310 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..f4d82054011
--- /dev/null
+++ b/nixos/tests/inspircd.nix
@@ -0,0 +1,93 @@
+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;
+
+ # 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.concatStrings
+ (reduce
+ (lib.zipListsWith (cs: c: cs + c))
+ (builtins.map clientScript clients));
+})
diff --git a/pkgs/servers/irc/inspircd/default.nix b/pkgs/servers/irc/inspircd/default.nix
new file mode 100644
index 00000000000..f907e337ce6
--- /dev/null
+++ b/pkgs/servers/irc/inspircd/default.nix
@@ -0,0 +1,221 @@
+let
+ # inspircd ships a few extra modules that users can load
+ # via configuration. Upstream thus recommends to ship as
+ # many of them as possible. There is however a problem:
+ # inspircd is licensed under the GPL version 2 only and
+ # some modules link libraries that are incompatible with
+ # the GPL 2. Therefore we can't provide them as binaries
+ # via our binary-caches, but users should still be able
+ # to override this package and build the incompatible
+ # modules themselves.
+ #
+ # This means for us we need to a) prevent hydra from
+ # building a module set with a GPL incompatibility
+ # and b) dynamically figure out the largest possible
+ # set of modules to use depending on stdenv, because
+ # the used libc needs to be compatible as well.
+ #
+ # For an overview of all modules and their licensing
+ # situation, see https://docs.inspircd.org/packaging/
+
+ # Predicate for checking license compatibility with
+ # GPLv2. Since this is _only_ used for libc compatibility
+ # checking, only whitelist licenses used by notable
+ # libcs in nixpkgs (musl and glibc).
+ compatible = lib: drv:
+ lib.any (lic: lic == drv.meta.license) [
+ lib.licenses.mit # musl
+ lib.licenses.lgpl2Plus # glibc
+ ];
+
+ # compatible if libc is compatible
+ libcModules = [
+ "regex_posix"
+ "sslrehashsignal"
+ ];
+
+ # compatible if libc++ is compatible
+ # TODO(sternenseemann):
+ # we could enable "regex_stdlib" automatically, but only if
+ # we are using libcxxStdenv which is compatible with GPLv2,
+ # since the gcc libstdc++ license is GPLv2-incompatible
+ libcxxModules = [
+ "regex_stdlib"
+ ];
+
+ compatibleModules = lib: stdenv: [
+ # GPLv2 compatible dependencies
+ "argon2"
+ "ldap"
+ "mysql"
+ "pgsql"
+ "regex_pcre"
+ "regex_re2"
+ "regex_tre"
+ "sqlite3"
+ "ssl_gnutls"
+ ] ++ lib.optionals (compatible lib stdenv.cc.libc) libcModules;
+
+in
+
+{ lib
+, stdenv
+, fetchFromGitHub
+, nixosTests
+, perl
+, pkg-config
+, libargon2
+, openldap
+, postgresql
+, libmysqlclient
+, pcre
+, tre
+, re2
+, sqlite
+, gnutls
+, libmaxminddb
+, openssl
+, mbedtls
+# For a full list of module names, see https://docs.inspircd.org/packaging/
+, extraModules ? compatibleModules lib stdenv
+}:
+
+let
+ extras = {
+ # GPLv2 compatible
+ argon2 = [
+ (libargon2 // {
+ meta = libargon2.meta // {
+ # use libargon2 as CC0 since ASL20 is GPLv2-incompatible
+ # updating this here is important that meta.license is accurate
+ # libargon2 is licensed under either ASL20 or CC0.
+ license = lib.licenses.cc0;
+ };
+ })
+ ];
+ ldap = [ openldap ];
+ mysql = [ libmysqlclient ];
+ pgsql = [ postgresql ];
+ regex_pcre = [ pcre ];
+ regex_re2 = [ re2 ];
+ regex_tre = [ tre ];
+ sqlite3 = [ sqlite ];
+ ssl_gnutls = [ gnutls ];
+ # depends on stdenv.cc.libc
+ regex_posix = [];
+ sslrehashsignal = [];
+ # depends on used libc++
+ regex_stdlib = [];
+ # GPLv2 incompatible
+ geo_maxmind = [ libmaxminddb ];
+ ssl_mbedtls = [ mbedtls ];
+ ssl_openssl = [ openssl ];
+ };
+
+ # buildInputs necessary for the enabled extraModules
+ extraInputs = lib.concatMap
+ (m: extras."${m}" or (builtins.throw "Unknown extra module ${m}"))
+ extraModules;
+
+ # if true, we can't provide a binary version of this
+ # package without violating the GPL 2
+ gpl2Conflict =
+ let
+ allowed = compatibleModules lib stdenv;
+ in
+ !lib.all (lib.flip lib.elem allowed) extraModules;
+
+ # return list of the license(s) of the given derivation
+ getLicenses = drv:
+ let
+ lics = drv.meta.license or [];
+ in
+ if lib.isAttrs lics || lib.isString lics
+ then [ lics ]
+ else lics;
+
+ # Whether any member of list1 is also member of list2, i. e. set intersection.
+ anyMembers = list1: list2:
+ lib.any (m1: lib.elem m1 list2) list1;
+
+in
+
+stdenv.mkDerivation rec {
+ pname = "inspircd";
+ version = "3.9.0";
+
+ src = fetchFromGitHub {
+ owner = pname;
+ repo = pname;
+ rev = "v${version}";
+ sha256 = "0x3paasf4ynx4ddky2nq613vyirbhfnxzkjq148k7154pz3q426s";
+ };
+
+ outputs = [ "bin" "lib" "man" "doc" "out" ];
+
+ nativeBuildInputs = [
+ perl
+ pkg-config
+ ];
+ buildInputs = extraInputs;
+
+ configurePhase = ''
+ patchShebangs configure make/*.pl
+
+ # configure is executed twice, once to set the extras
+ # to use and once to do the Makefile setup
+ ./configure \
+ --enable-extras \
+ ${lib.escapeShellArg (lib.concatStringsSep " " extraModules)}
+
+ # this manually sets the flags instead of using configureFlags, because otherwise stdenv passes flags like --bindir, which make configure fail
+ ./configure \
+ --disable-auto-extras \
+ --distribution-label nixpkgs${version} \
+ --uid 0 \
+ --gid 0 \
+ --binary-dir ${placeholder "bin"}/bin \
+ --config-dir /etc/inspircd \
+ --data-dir ${placeholder "lib"}/lib/inspircd \
+ --example-dir ${placeholder "doc"}/share/doc/inspircd \
+ --log-dir /var/log/inspircd \
+ --manual-dir ${placeholder "man"}/share/man/man1 \
+ --module-dir ${placeholder "lib"}/lib/inspircd \
+ --runtime-dir /var/run \
+ --script-dir ${placeholder "bin"}/share/inspircd \
+ '';
+
+ postInstall = ''
+ # for some reasons the executables are not executable
+ chmod +x $bin/bin/*
+ '';
+
+ enableParallelBuilding = true;
+
+ passthru.tests = {
+ nixos-test = nixosTests.inspircd;
+ };
+
+ meta = {
+ description = "A modular C++ IRC server";
+ license = [ lib.licenses.gpl2Only ]
+ ++ lib.concatMap getLicenses extraInputs
+ ++ lib.optionals (anyMembers extraModules libcModules) (getLicenses stdenv.cc.libc)
+ # FIXME(sternenseemann): get license of used lib(std)c++ somehow
+ ++ lib.optional (anyMembers extraModules libcxxModules) "Unknown"
+ # Hack: Definitely prevent a hydra from building this package on
+ # a GPL 2 incompatibility even if it is not in a top-level attribute,
+ # but pulled in indirectly somehow.
+ ++ lib.optional gpl2Conflict lib.licenses.unfree;
+ maintainers = [ lib.maintainers.sternenseemann ];
+ # windows is theoretically possible, but requires extra work
+ # which I am not willing to do and can't test.
+ # https://github.com/inspircd/inspircd/blob/master/win/README.txt
+ platforms = lib.platforms.unix;
+ homepage = "https://www.inspircd.org/";
+ } // lib.optionalAttrs gpl2Conflict {
+ # make sure we never distribute a GPLv2-violating module
+ # in binary form. They can be built locally of course.
+ hydraPlatforms = [];
+ };
+}
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index eb66cdde0ee..6984b5d6539 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -18251,6 +18251,10 @@ in
theme-spring = callPackage ../servers/icingaweb2/theme-spring { };
};
+ inspircd = callPackage ../servers/irc/inspircd { };
+
+ inspircdMinimal = inspircd.override { extraModules = []; };
+
imgproxy = callPackage ../servers/imgproxy { };
ircdog = callPackage ../applications/networking/irc/ircdog { };