{ config, lib, pkgs, ...}: with lib; let cfg = config.services.znc; defaultUser = "znc"; # Default user to own process. # Default user and pass: # un=znc # pw=nixospass defaultUserName = "znc"; defaultPassBlock = " Method = sha256 Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93 Salt = l5Xryew4g*!oa(ECfX2o "; modules = pkgs.buildEnv { name = "znc-modules"; paths = cfg.modulePackages; }; # Keep znc.conf in nix store, then symlink or copy into `dataDir`, depending on `mutable`. mkZncConf = confOpts: '' Version = 1.6.3 ${concatMapStrings (n: "LoadModule = ${n}\n") confOpts.modules} Port = ${toString confOpts.port} IPv4 = true IPv6 = true SSL = ${boolToString confOpts.useSSL} ${lib.optionalString (confOpts.uriPrefix != null) "URIPrefix = ${confOpts.uriPrefix}"} ${confOpts.passBlock} Admin = true Nick = ${confOpts.nick} AltNick = ${confOpts.nick}_ Ident = ${confOpts.nick} RealName = ${confOpts.nick} ${concatMapStrings (n: "LoadModule = ${n}\n") confOpts.userModules} ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (name: net: '' ${concatMapStrings (m: "LoadModule = ${m}\n") net.modules} Server = ${net.server} ${lib.optionalString net.useSSL "+"}${toString net.port} ${net.password} ${concatMapStrings (c: "\n\n") net.channels} ${lib.optionalString net.hasBitlbeeControlChannel '' ''} ${net.extraConf} '') confOpts.networks) } ${confOpts.extraZncConf} ''; zncConfFile = pkgs.writeTextFile { name = "znc.conf"; text = if cfg.zncConf != "" then cfg.zncConf else mkZncConf cfg.confOptions; }; networkOpts = { ... }: { options = { server = mkOption { type = types.str; example = "chat.freenode.net"; description = '' IRC server address. ''; }; port = mkOption { type = types.int; default = 6697; example = 6697; description = '' IRC server port. ''; }; userName = mkOption { default = ""; example = "johntron"; type = types.string; description = '' A nick identity specific to the IRC server. ''; }; password = mkOption { type = types.str; default = ""; description = '' IRC server password, such as for a Slack gateway. ''; }; useSSL = mkOption { type = types.bool; default = true; description = '' Whether to use SSL to connect to the IRC server. ''; }; modulePackages = mkOption { type = types.listOf types.package; default = []; example = [ "pkgs.zncModules.push" "pkgs.zncModules.fish" ]; description = '' External ZNC modules to build. ''; }; modules = mkOption { type = types.listOf types.str; default = [ "simple_away" ]; example = literalExample "[ simple_away sasl ]"; description = '' ZNC modules to load. ''; }; channels = mkOption { type = types.listOf types.str; default = []; example = [ "nixos" ]; description = '' IRC channels to join. ''; }; hasBitlbeeControlChannel = mkOption { type = types.bool; default = false; description = '' Whether to add the special Bitlbee operations channel. ''; }; extraConf = mkOption { default = ""; type = types.lines; example = '' Encoding = ^UTF-8 FloodBurst = 4 FloodRate = 1.00 IRCConnectEnabled = true Ident = johntron JoinDelay = 0 Nick = johntron ''; description = '' Extra config for the network. ''; }; }; }; in { ###### Interface options = { services.znc = { enable = mkOption { default = false; type = types.bool; description = '' Enable a ZNC service for a user. ''; }; user = mkOption { default = "znc"; example = "john"; type = types.string; description = '' The name of an existing user account to use to own the ZNC server process. If not specified, a default user will be created to own the process. ''; }; group = mkOption { default = ""; example = "users"; type = types.string; description = '' Group to own the ZNCserver process. ''; }; dataDir = mkOption { default = "/var/lib/znc/"; example = "/home/john/.znc/"; type = types.path; description = '' The data directory. Used for configuration files and modules. ''; }; openFirewall = mkOption { type = types.bool; default = false; description = '' Whether to open ports in the firewall for ZNC. ''; }; zncConf = mkOption { default = ""; example = "See: http://wiki.znc.in/Configuration"; type = types.lines; description = '' Config file as generated with `znc --makeconf` to use for the whole ZNC configuration. If specified, `confOptions` will be ignored, and this value, as-is, will be used. If left empty, a conf file with default values will be used. ''; }; confOptions = { modules = mkOption { type = types.listOf types.str; default = [ "webadmin" "adminlog" ]; example = [ "partyline" "webadmin" "adminlog" "log" ]; description = '' A list of modules to include in the `znc.conf` file. ''; }; userModules = mkOption { type = types.listOf types.str; default = [ "chansaver" "controlpanel" ]; example = [ "chansaver" "controlpanel" "fish" "push" ]; description = '' A list of user modules to include in the `znc.conf` file. ''; }; userName = mkOption { default = defaultUserName; example = "johntron"; type = types.string; description = '' The user name used to log in to the ZNC web admin interface. ''; }; networks = mkOption { default = { }; type = with types; attrsOf (submodule networkOpts); description = '' IRC networks to connect the user to. ''; example = { "freenode" = { server = "chat.freenode.net"; port = 6697; useSSL = true; modules = [ "simple_away" ]; }; }; }; nick = mkOption { default = "znc-user"; example = "john"; type = types.string; description = '' The IRC nick. ''; }; passBlock = mkOption { example = defaultPassBlock; type = types.string; description = '' Generate with `nix-shell -p znc --command "znc --makepass"`. This is the password used to log in to the ZNC web admin interface. ''; }; port = mkOption { default = 5000; example = 5000; type = types.int; description = '' Specifies the port on which to listen. ''; }; useSSL = mkOption { default = true; type = types.bool; description = '' Indicates whether the ZNC server should use SSL when listening on the specified port. A self-signed certificate will be generated. ''; }; uriPrefix = mkOption { type = types.nullOr types.str; default = null; example = "/znc/"; description = '' An optional URI prefix for the ZNC web interface. Can be used to make ZNC available behind a reverse proxy. ''; }; extraZncConf = mkOption { default = ""; type = types.lines; description = '' Extra config to `znc.conf` file. ''; }; }; modulePackages = mkOption { type = types.listOf types.package; default = [ ]; example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]"; description = '' A list of global znc module packages to add to znc. ''; }; mutable = mkOption { default = true; type = types.bool; description = '' Indicates whether to allow the contents of the `dataDir` directory to be changed by the user at run-time. If true, modifications to the ZNC configuration after its initial creation are not overwritten by a NixOS system rebuild. If false, the ZNC configuration is rebuilt by every system rebuild. If the user wants to manage the ZNC service using the web admin interface, this value should be set to true. ''; }; extraFlags = mkOption { default = [ ]; example = [ "--debug" ]; type = types.listOf types.str; description = '' Extra flags to use when executing znc command. ''; }; }; }; ###### Implementation config = mkIf cfg.enable { networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.confOptions.port ]; }; systemd.services.znc = { description = "ZNC Server"; wantedBy = [ "multi-user.target" ]; after = [ "network.service" ]; serviceConfig = { User = cfg.user; Group = cfg.group; Restart = "always"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID"; }; preStart = '' ${pkgs.coreutils}/bin/mkdir -p ${cfg.dataDir}/configs # If mutable, regenerate conf file every time. ${optionalString (!cfg.mutable) '' ${pkgs.coreutils}/bin/echo "znc is set to be system-managed. Now deleting old znc.conf file to be regenerated." ${pkgs.coreutils}/bin/rm -f ${cfg.dataDir}/configs/znc.conf ''} # Ensure essential files exist. if [[ ! -f ${cfg.dataDir}/configs/znc.conf ]]; then ${pkgs.coreutils}/bin/echo "No znc.conf file found in ${cfg.dataDir}. Creating one now." ${pkgs.coreutils}/bin/cp --no-clobber ${zncConfFile} ${cfg.dataDir}/configs/znc.conf ${pkgs.coreutils}/bin/chmod u+rw ${cfg.dataDir}/configs/znc.conf ${pkgs.coreutils}/bin/chown ${cfg.user} ${cfg.dataDir}/configs/znc.conf fi if [[ ! -f ${cfg.dataDir}/znc.pem ]]; then ${pkgs.coreutils}/bin/echo "No znc.pem file found in ${cfg.dataDir}. Creating one now." ${pkgs.znc}/bin/znc --makepem --datadir ${cfg.dataDir} fi # Symlink modules rm ${cfg.dataDir}/modules || true ln -fs ${modules}/lib/znc ${cfg.dataDir}/modules ''; script = "${pkgs.znc}/bin/znc --foreground --datadir ${cfg.dataDir} ${toString cfg.extraFlags}"; }; users.users = optional (cfg.user == defaultUser) { name = defaultUser; description = "ZNC server daemon owner"; group = defaultUser; uid = config.ids.uids.znc; home = cfg.dataDir; createHome = true; }; users.groups = optional (cfg.user == defaultUser) { name = defaultUser; gid = config.ids.gids.znc; members = [ defaultUser ]; }; }; }