diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index dea0ca13565..4f93e1f361d 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -806,6 +806,7 @@ ./services/networking/smartdns.nix ./services/networking/smokeping.nix ./services/networking/softether.nix + ./services/networking/solanum.nix ./services/networking/spacecookie.nix ./services/networking/spiped.nix ./services/networking/squid.nix diff --git a/nixos/modules/services/networking/solanum.nix b/nixos/modules/services/networking/solanum.nix new file mode 100644 index 00000000000..989621b204c --- /dev/null +++ b/nixos/modules/services/networking/solanum.nix @@ -0,0 +1,104 @@ +{ config, lib, pkgs, ... }: + +let + inherit (lib) mkEnableOption mkIf mkOption types; + inherit (pkgs) solanum; + cfg = config.services.solanum; + + configFile = pkgs.writeText "solanum.conf" cfg.config; +in + +{ + + ###### interface + + options = { + + services.solanum = { + + enable = mkEnableOption "Solanum IRC daemon"; + + config = mkOption { + type = types.str; + default = '' + serverinfo { + name = "irc.example.com"; + sid = "1ix"; + description = "irc!"; + + vhost = "0.0.0.0"; + vhost6 = "::"; + }; + + listen { + host = "0.0.0.0"; + port = 6667; + }; + + auth { + user = "*@*"; + class = "users"; + flags = exceed_limit; + }; + channel { + default_split_user_count = 0; + }; + ''; + description = '' + Solanum IRC daemon configuration file. + check for all options. + ''; + }; + + openFilesLimit = mkOption { + type = types.int; + default = 1024; + description = '' + Maximum number of open files. Limits the clients and server connections. + ''; + }; + + motd = mkOption { + type = types.nullOr types.lines; + default = null; + description = '' + Solanum MOTD text. + + Solanum will read its MOTD from /etc/solanum/ircd.motd. + If set, the value of this option will be written to this path. + ''; + }; + + }; + + }; + + + ###### implementation + + config = mkIf cfg.enable (lib.mkMerge [ + { + systemd.services.solanum = { + description = "Solanum IRC daemon"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + BANDB_DBPATH = "/var/lib/solanum/ban.db"; + }; + serviceConfig = { + ExecStart = "${solanum}/bin/solanum -foreground -logfile /dev/stdout -configfile ${configFile} -pidfile /run/solanum/ircd.pid"; + DynamicUser = true; + User = "solanum"; + StateDirectory = "solanum"; + RuntimeDirectory = "solanum"; + LimitNOFILE = "${toString cfg.openFilesLimit}"; + }; + }; + + } + + (mkIf (cfg.motd != null) { + environment.etc."solanum/ircd.motd".text = cfg.motd; + }) + ]); +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 420e6eaf2e8..4ada4a5de80 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -384,6 +384,7 @@ in snapcast = handleTest ./snapcast.nix {}; snapper = handleTest ./snapper.nix {}; sogo = handleTest ./sogo.nix {}; + solanum = handleTest ./solanum.nix {}; solr = handleTest ./solr.nix {}; sonarr = handleTest ./sonarr.nix {}; spacecookie = handleTest ./spacecookie.nix {}; diff --git a/nixos/tests/solanum.nix b/nixos/tests/solanum.nix new file mode 100644 index 00000000000..aabfb906aa8 --- /dev/null +++ b/nixos/tests/solanum.nix @@ -0,0 +1,89 @@ +let + clients = [ + "ircclient1" + "ircclient2" + ]; + server = "solanum"; + ircPort = 6667; + channel = "nixos-cat"; + iiDir = "/tmp/irc"; +in + +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "solanum"; + nodes = { + "${server}" = { + networking.firewall.allowedTCPPorts = [ ircPort ]; + services.solanum = { + enable = true; + }; + }; + } // 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}.systemctl("status solanum") + ${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)); +})