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 { };