diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/networking/znc/default.nix')
-rw-r--r-- | nixpkgs/nixos/modules/services/networking/znc/default.nix | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/networking/znc/default.nix b/nixpkgs/nixos/modules/services/networking/znc/default.nix new file mode 100644 index 000000000000..1ad8855b86db --- /dev/null +++ b/nixpkgs/nixos/modules/services/networking/znc/default.nix @@ -0,0 +1,306 @@ +{ config, lib, pkgs, ...}: + +with lib; + +let + + cfg = config.services.znc; + + defaultUser = "znc"; + + modules = pkgs.buildEnv { + name = "znc-modules"; + paths = cfg.modulePackages; + }; + + listenerPorts = concatMap (l: optional (l ? Port) l.Port) + (attrValues (cfg.config.Listener or {})); + + # Converts the config option to a string + semanticString = let + + sortedAttrs = set: sort (l: r: + if l == "extraConfig" then false # Always put extraConfig last + else if isAttrs set.${l} == isAttrs set.${r} then l < r + else isAttrs set.${r} # Attrsets should be last, makes for a nice config + # This last case occurs when any side (but not both) is an attrset + # The order of these is correct when the attrset is on the right + # which we're just returning + ) (attrNames set); + + # Specifies an attrset that encodes the value according to its type + encode = name: value: { + null = []; + bool = [ "${name} = ${boolToString value}" ]; + int = [ "${name} = ${toString value}" ]; + + # extraConfig should be inserted verbatim + string = [ (if name == "extraConfig" then value else "${name} = ${value}") ]; + + # Values like `Foo = [ "bar" "baz" ];` should be transformed into + # Foo=bar + # Foo=baz + list = concatMap (encode name) value; + + # Values like `Foo = { bar = { Baz = "baz"; Qux = "qux"; Florps = null; }; };` should be transmed into + # <Foo bar> + # Baz=baz + # Qux=qux + # </Foo> + set = concatMap (subname: [ + "<${name} ${subname}>" + ] ++ map (line: "\t${line}") (toLines value.${subname}) ++ [ + "</${name}>" + ]) (filter (v: v != null) (attrNames value)); + + }.${builtins.typeOf value}; + + # One level "above" encode, acts upon a set and uses encode on each name,value pair + toLines = set: concatMap (name: encode name set.${name}) (sortedAttrs set); + + in + concatStringsSep "\n" (toLines cfg.config); + + semanticTypes = with types; rec { + zncAtom = nullOr (either (either int bool) str); + zncAttr = attrsOf (nullOr zncConf); + zncAll = either (either zncAtom (listOf zncAtom)) zncAttr; + zncConf = attrsOf (zncAll // { + # Since this is a recursive type and the description by default contains + # the description of its subtypes, infinite recursion would occur without + # explicitly breaking this cycle + description = "znc values (null, atoms (str, int, bool), list of atoms, or attrsets of znc values)"; + }); + }; + +in + +{ + + imports = [ ./options.nix ]; + + options = { + services.znc = { + enable = mkEnableOption "ZNC"; + + user = mkOption { + default = "znc"; + example = "john"; + type = types.str; + 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. + ''; + }; + + group = mkOption { + default = defaultUser; + example = "users"; + type = types.str; + description = '' + Group to own the ZNC process. + ''; + }; + + dataDir = mkOption { + default = "/var/lib/znc/"; + example = "/home/john/.znc/"; + type = types.path; + description = '' + The state directory for ZNC. The config and the modules will be linked + to from this directory as well. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Whether to open ports in the firewall for ZNC. Does work with + ports for listeners specified in + <option>services.znc.config.Listener</option>. + ''; + }; + + config = mkOption { + type = semanticTypes.zncConf; + default = {}; + example = literalExample '' + { + LoadModule = [ "webadmin" "adminlog" ]; + User.paul = { + Admin = true; + Nick = "paul"; + AltNick = "paul1"; + LoadModule = [ "chansaver" "controlpanel" ]; + Network.freenode = { + Server = "chat.freenode.net +6697"; + LoadModule = [ "simple_away" ]; + Chan = { + "#nixos" = { Detached = false; }; + "##linux" = { Disabled = true; }; + }; + }; + Pass.password = { + Method = "sha256"; + Hash = "e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93"; + Salt = "l5Xryew4g*!oa(ECfX2o"; + }; + }; + } + ''; + description = '' + Configuration for ZNC, see + <link xlink:href="https://wiki.znc.in/Configuration"/> for details. The + Nix value declared here will be translated directly to the xml-like + format ZNC expects. This is much more flexible than the legacy options + under <option>services.znc.confOptions.*</option>, but also can't do + any type checking. + </para> + <para> + You can use <command>nix-instantiate --eval --strict '<nixpkgs/nixos>' -A config.services.znc.config</command> + to view the current value. By default it contains a listener for port + 5000 with SSL enabled. + </para> + <para> + Nix attributes called <literal>extraConfig</literal> will be inserted + verbatim into the resulting config file. + </para> + <para> + If <option>services.znc.useLegacyConfig</option> is turned on, the + option values in <option>services.znc.confOptions.*</option> will be + gracefully be applied to this option. + </para> + <para> + If you intend to update the configuration through this option, be sure + to enable <option>services.znc.mutable</option>, otherwise none of the + changes here will be applied after the initial deploy. + ''; + }; + + configFile = mkOption { + type = types.path; + example = "~/.znc/configs/znc.conf"; + description = '' + Configuration file for ZNC. It is recommended to use the + <option>config</option> option instead. + </para> + <para> + Setting this option will override any auto-generated config file + through the <option>confOptions</option> or <option>config</option> + options. + ''; + }; + + 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; # TODO: Default to true when config is set, make sure to not delete the old config if present + type = types.bool; + description = '' + Indicates whether to allow the contents of the + <literal>dataDir</literal> directory to be changed by the user at + run-time. + </para> + <para> + If enabled, modifications to the ZNC configuration after its initial + creation are not overwritten by a NixOS rebuild. If disabled, the + ZNC configuration is rebuilt on every NixOS rebuild. + </para> + <para> + If the user wants to manage the ZNC service using the web admin + interface, this option should be enabled. + ''; + }; + + extraFlags = mkOption { + default = [ ]; + example = [ "--debug" ]; + type = types.listOf types.str; + description = '' + Extra arguments to use for executing znc. + ''; + }; + }; + }; + + + ###### Implementation + + config = mkIf cfg.enable { + + services.znc = { + configFile = mkDefault (pkgs.writeText "znc-generated.conf" semanticString); + config = { + Version = (builtins.parseDrvName pkgs.znc.name).version; + Listener.l.Port = mkDefault 5000; + Listener.l.SSL = mkDefault true; + }; + }; + + networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall listenerPorts; + + systemd.services.znc = { + description = "ZNC Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + Restart = "always"; + ExecStart = "${pkgs.znc}/bin/znc --foreground --datadir ${cfg.dataDir} ${escapeShellArgs cfg.extraFlags}"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID"; + }; + preStart = '' + mkdir -p ${cfg.dataDir}/configs + + # If mutable, regenerate conf file every time. + ${optionalString (!cfg.mutable) '' + echo "znc is set to be system-managed. Now deleting old znc.conf file to be regenerated." + rm -f ${cfg.dataDir}/configs/znc.conf + ''} + + # Ensure essential files exist. + if [[ ! -f ${cfg.dataDir}/configs/znc.conf ]]; then + echo "No znc.conf file found in ${cfg.dataDir}. Creating one now." + cp --no-clobber ${cfg.configFile} ${cfg.dataDir}/configs/znc.conf + chmod u+rw ${cfg.dataDir}/configs/znc.conf + chown ${cfg.user} ${cfg.dataDir}/configs/znc.conf + fi + + if [[ ! -f ${cfg.dataDir}/znc.pem ]]; then + 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 + ''; + }; + + 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 ]; + }; + + }; +} |