diff options
Diffstat (limited to 'nixos/modules/services/networking')
33 files changed, 911 insertions, 435 deletions
diff --git a/nixos/modules/services/networking/adguardhome.nix b/nixos/modules/services/networking/adguardhome.nix index df9927351edc3..5be3e0bea224a 100644 --- a/nixos/modules/services/networking/adguardhome.nix +++ b/nixos/modules/services/networking/adguardhome.nix @@ -140,7 +140,7 @@ in { { assertion = cfg.settings != null -> !(hasAttrByPath [ "bind_port" ] cfg.settings); - message = "AdGuard option `settings.bind_host' has been superseded by `services.adguardhome.port'"; + message = "AdGuard option `settings.bind_port' has been superseded by `services.adguardhome.port'"; } { assertion = settings != null -> cfg.mutableSettings @@ -167,8 +167,13 @@ in { preStart = optionalString (settings != null) '' if [ -e "$STATE_DIRECTORY/AdGuardHome.yaml" ] \ && [ "${toString cfg.mutableSettings}" = "1" ]; then + # First run a schema_version update on the existing configuration + # This ensures that both the new config and the existing one have the same schema_version + # Note: --check-config has the side effect of modifying the file at rest! + ${lib.getExe cfg.package} -c "$STATE_DIRECTORY/AdGuardHome.yaml" --check-config + # Writing directly to AdGuardHome.yaml results in empty file - ${pkgs.yaml-merge}/bin/yaml-merge "$STATE_DIRECTORY/AdGuardHome.yaml" "${configFile}" > "$STATE_DIRECTORY/AdGuardHome.yaml.tmp" + ${lib.getExe pkgs.yaml-merge} "$STATE_DIRECTORY/AdGuardHome.yaml" "${configFile}" > "$STATE_DIRECTORY/AdGuardHome.yaml.tmp" mv "$STATE_DIRECTORY/AdGuardHome.yaml.tmp" "$STATE_DIRECTORY/AdGuardHome.yaml" else cp --force "${configFile}" "$STATE_DIRECTORY/AdGuardHome.yaml" @@ -178,7 +183,7 @@ in { serviceConfig = { DynamicUser = true; - ExecStart = "${cfg.package}/bin/adguardhome ${args}"; + ExecStart = "${lib.getExe cfg.package} ${args}"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ] ++ optionals cfg.allowDHCP [ "CAP_NET_RAW" ]; Restart = "always"; diff --git a/nixos/modules/services/networking/antennas.nix b/nixos/modules/services/networking/antennas.nix index ef98af22f20f2..a37df953fc923 100644 --- a/nixos/modules/services/networking/antennas.nix +++ b/nixos/modules/services/networking/antennas.nix @@ -50,10 +50,7 @@ in }; serviceConfig = { - ExecStart = "${pkgs.antennas}/bin/antennas"; - - # Antennas expects all resources like html and config to be relative to it's working directory - WorkingDirectory = "${pkgs.antennas}/libexec/antennas/deps/antennas/"; + ExecStart = "${pkgs.antennas}/bin/antennas"; # Hardening CapabilityBoundingSet = [ "" ]; diff --git a/nixos/modules/services/networking/aria2.nix b/nixos/modules/services/networking/aria2.nix index f32f5682c9801..f0d5c5c8a21e3 100644 --- a/nixos/modules/services/networking/aria2.nix +++ b/nixos/modules/services/networking/aria2.nix @@ -1,98 +1,132 @@ { config, lib, pkgs, ... }: -with lib; - let cfg = config.services.aria2; homeDir = "/var/lib/aria2"; - - settingsDir = "${homeDir}"; - sessionFile = "${homeDir}/aria2.session"; - downloadDir = "${homeDir}/Downloads"; - - rangesToStringList = map (x: builtins.toString x.from +"-"+ builtins.toString x.to); - - settingsFile = pkgs.writeText "aria2.conf" - '' - dir=${cfg.downloadDir} - listen-port=${concatStringsSep "," (rangesToStringList cfg.listenPortRange)} - rpc-listen-port=${toString cfg.rpcListenPort} - ''; - + defaultRpcListenPort = 6800; + defaultDir = "${homeDir}/Downloads"; + + portRangesToString = ranges: lib.concatStringsSep "," (map + (x: + if x.from == x.to + then builtins.toString x.from + else builtins.toString x.from + "-" + builtins.toString x.to + ) + ranges); + + customToKeyValue = lib.generators.toKeyValue { + mkKeyValue = lib.generators.mkKeyValueDefault + { + mkValueString = v: + if builtins.isList v then portRangesToString v + else lib.generators.mkValueStringDefault { } v; + } "="; + }; in { imports = [ - (mkRemovedOptionModule [ "services" "aria2" "rpcSecret" ] "Use services.aria2.rpcSecretFile instead") + (lib.mkRemovedOptionModule [ "services" "aria2" "rpcSecret" ] "Use services.aria2.rpcSecretFile instead") + (lib.mkRemovedOptionModule [ "services" "aria2" "extraArguments" ] "Use services.aria2.settings instead") + (lib.mkRenamedOptionModule [ "services" "aria2" "downloadDir" ] [ "services" "aria2" "settings" "dir" ]) + (lib.mkRenamedOptionModule [ "services" "aria2" "listenPortRange" ] [ "services" "aria2" "settings" "listen-port" ]) + (lib.mkRenamedOptionModule [ "services" "aria2" "rpcListenPort" ] [ "services" "aria2" "settings" "rpc-listen-port" ]) ]; options = { services.aria2 = { - enable = mkOption { - type = types.bool; + enable = lib.mkOption { + type = lib.types.bool; default = false; description = '' Whether or not to enable the headless Aria2 daemon service. - Aria2 daemon can be controlled via the RPC interface using - one of many WebUI (http://localhost:6800/ by default). + Aria2 daemon can be controlled via the RPC interface using one of many + WebUIs (http://localhost:${toString defaultRpcListenPort}/ by default). - Targets are downloaded to ${downloadDir} by default and are - accessible to users in the "aria2" group. + Targets are downloaded to `${defaultDir}` by default and are + accessible to users in the `aria2` group. ''; }; - openPorts = mkOption { - type = types.bool; + openPorts = lib.mkOption { + type = lib.types.bool; default = false; description = '' - Open listen and RPC ports found in listenPortRange and rpcListenPort - options in the firewall. - ''; - }; - downloadDir = mkOption { - type = types.path; - default = downloadDir; - description = '' - Directory to store downloaded files. - ''; - }; - listenPortRange = mkOption { - type = types.listOf types.attrs; - default = [ { from = 6881; to = 6999; } ]; - description = '' - Set UDP listening port range used by DHT(IPv4, IPv6) and UDP tracker. + Open listen and RPC ports found in `settings.listen-port` and + `settings.rpc-listen-port` options in the firewall. ''; }; - rpcListenPort = mkOption { - type = types.int; - default = 6800; - description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535"; - }; - rpcSecretFile = mkOption { - type = types.path; + rpcSecretFile = lib.mkOption { + type = lib.types.path; example = "/run/secrets/aria2-rpc-token.txt"; description = '' A file containing the RPC secret authorization token. Read https://aria2.github.io/manual/en/html/aria2c.html#rpc-auth to know how this option value is used. ''; }; - extraArguments = mkOption { - type = types.separatedString " "; - example = "--rpc-listen-all --remote-time=true"; - default = ""; + settings = lib.mkOption { description = '' - Additional arguments to be passed to Aria2. + Generates the `aria2.conf` file. Refer to [the documentation][0] for + all possible settings. + + [0]: https://aria2.github.io/manual/en/html/aria2c.html#synopsis ''; + default = { }; + type = lib.types.submodule { + freeformType = with lib.types; attrsOf (oneOf [ bool int float singleLineStr ]); + options = { + save-session = lib.mkOption { + type = lib.types.singleLineStr; + default = "${homeDir}/aria2.session"; + description = "Save error/unfinished downloads to FILE on exit."; + }; + dir = lib.mkOption { + type = lib.types.singleLineStr; + default = defaultDir; + description = "Directory to store downloaded files."; + }; + conf-path = lib.mkOption { + type = lib.types.singleLineStr; + default = "${homeDir}/aria2.conf"; + description = "Configuration file path."; + }; + enable-rpc = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Enable JSON-RPC/XML-RPC server."; + }; + listen-port = lib.mkOption { + type = with lib.types; listOf (attrsOf port); + default = [{ from = 6881; to = 6999; }]; + description = "Set UDP listening port range used by DHT(IPv4, IPv6) and UDP tracker."; + }; + rpc-listen-port = lib.mkOption { + type = lib.types.port; + default = defaultRpcListenPort; + description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535"; + }; + }; + }; }; }; }; - config = mkIf cfg.enable { + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.settings.enable-rpc; + message = "RPC has to be enabled, the default module option takes care of that."; + } + { + assertion = !(cfg.settings ? rpc-secret); + message = "Set the RPC secret through services.aria2.rpcSecretFile so it will not end up in the world-readable nix store."; + } + ]; # Need to open ports for proper functioning - networking.firewall = mkIf cfg.openPorts { - allowedUDPPortRanges = config.services.aria2.listenPortRange; - allowedTCPPorts = [ config.services.aria2.rpcListenPort ]; + networking.firewall = lib.mkIf cfg.openPorts { + allowedUDPPortRanges = config.services.aria2.settings.listen-port; + allowedTCPPorts = [ config.services.aria2.settings.rpc-listen-port ]; }; users.users.aria2 = { @@ -107,7 +141,7 @@ in systemd.tmpfiles.rules = [ "d '${homeDir}' 0770 aria2 aria2 - -" - "d '${config.services.aria2.downloadDir}' 0770 aria2 aria2 - -" + "d '${config.services.aria2.settings.dir}' 0770 aria2 aria2 - -" ]; systemd.services.aria2 = { @@ -115,22 +149,25 @@ in after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; preStart = '' - if [[ ! -e "${sessionFile}" ]] + if [[ ! -e "${cfg.settings.save-session}" ]] then - touch "${sessionFile}" + touch "${cfg.settings.save-session}" fi - cp -f "${settingsFile}" "${settingsDir}/aria2.conf" - echo "rpc-secret=$(cat "$CREDENTIALS_DIRECTORY/rpcSecretFile")" >> "${settingsDir}/aria2.conf" + cp -f "${pkgs.writeText "aria2.conf" (customToKeyValue cfg.settings)}" "${cfg.settings.conf-path}" + chmod +w "${cfg.settings.conf-path}" + echo "rpc-secret=$(cat "$CREDENTIALS_DIRECTORY/rpcSecretFile")" >> "${cfg.settings.conf-path}" ''; serviceConfig = { Restart = "on-abort"; - ExecStart = "${pkgs.aria2}/bin/aria2c --enable-rpc --conf-path=${settingsDir}/aria2.conf ${config.services.aria2.extraArguments} --save-session=${sessionFile}"; + ExecStart = "${pkgs.aria2}/bin/aria2c --conf-path=${cfg.settings.conf-path}"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; User = "aria2"; Group = "aria2"; - LoadCredential="rpcSecretFile:${cfg.rpcSecretFile}"; + LoadCredential = "rpcSecretFile:${cfg.rpcSecretFile}"; }; }; }; + + meta.maintainers = [ lib.maintainers.timhae ]; } diff --git a/nixos/modules/services/networking/clatd.nix b/nixos/modules/services/networking/clatd.nix new file mode 100644 index 0000000000000..de6cde4e979c0 --- /dev/null +++ b/nixos/modules/services/networking/clatd.nix @@ -0,0 +1,81 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.clatd; + + settingsFormat = pkgs.formats.keyValue {}; + + configFile = settingsFormat.generate "clatd.conf" cfg.settings; +in +{ + options = { + services.clatd = { + enable = mkEnableOption "clatd"; + + package = mkPackageOption pkgs "clatd" { }; + + settings = mkOption { + type = types.submodule ({ name, ... }: { + freeformType = settingsFormat.type; + }); + default = { }; + example = literalExpression '' + { + plat-prefix = "64:ff9b::/96"; + } + ''; + description = '' + Configuration of clatd. See [clatd Documentation](https://github.com/toreanderson/clatd/blob/master/README.pod#configuration). + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.clatd = { + description = "464XLAT CLAT daemon"; + documentation = [ "man:clatd(8)" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + startLimitIntervalSec = 0; + + serviceConfig = { + ExecStart = "${cfg.package}/bin/clatd -c ${configFile}"; + + # Hardening + CapabilityBoundingSet = [ + "CAP_NET_ADMIN" + ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectProc = "invisible"; + ProtectSystem = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@network-io" + "@system-service" + "~@privileged" + "~@resources" + ]; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix index b912550e1155e..272a50eb92de8 100644 --- a/nixos/modules/services/networking/ddclient.nix +++ b/nixos/modules/services/networking/ddclient.nix @@ -11,7 +11,9 @@ let # This file can be used as a template for configFile or is automatically generated by Nix options. cache=${dataDir}/ddclient.cache foreground=YES - use=${cfg.use} + ${lib.optionalString (cfg.use != "") "use=${cfg.use}"} + ${lib.optionalString (cfg.use == "" && cfg.usev4 != "") "usev4=${cfg.usev4}"} + ${lib.optionalString (cfg.use == "" && cfg.usev6 != "") "usev6=${cfg.usev6}"} login=${cfg.username} password=${if cfg.protocol == "nsupdate" then "/run/${RuntimeDirectory}/ddclient.key" else "@password_placeholder@"} protocol=${cfg.protocol} @@ -163,12 +165,26 @@ with lib; }; use = mkOption { - default = "web, web=checkip.dyndns.com/, web-skip='Current IP Address: '"; + default = ""; type = str; description = '' Method to determine the IP address to send to the dynamic DNS provider. ''; }; + usev4 = mkOption { + default = "webv4, webv4=checkip.dyndns.com/, webv4-skip='Current IP Address: '"; + type = str; + description = '' + Method to determine the IPv4 address to send to the dynamic DNS provider. Only used if `use` is not set. + ''; + }; + usev6 = mkOption { + default = "webv6, webv6=checkipv6.dyndns.com/, webv6-skip='Current IP Address: '"; + type = str; + description = '' + Method to determine the IPv6 address to send to the dynamic DNS provider. Only used if `use` is not set. + ''; + }; verbose = mkOption { default = false; @@ -204,6 +220,8 @@ with lib; ###### implementation config = mkIf config.services.ddclient.enable { + warnings = lib.optional (cfg.use != "") "Setting `use` is deprecated, ddclient now supports `usev4` and `usev6` for separate IPv4/IPv6 configuration."; + systemd.services.ddclient = { description = "Dynamic DNS Client"; wantedBy = [ "multi-user.target" ]; diff --git a/nixos/modules/services/networking/frr.nix b/nixos/modules/services/networking/frr.nix index 7f611ce7b1c7d..df2b4035d2f07 100644 --- a/nixos/modules/services/networking/frr.nix +++ b/nixos/modules/services/networking/frr.nix @@ -23,10 +23,9 @@ let "pbr" "bfd" "fabric" - "mgmt" ]; - allServices = services ++ [ "zebra" ]; + allServices = services ++ [ "zebra" "mgmt" ]; isEnabled = service: cfg.${service}.enable; @@ -137,6 +136,20 @@ in ''; }; }; + mgmt = (serviceOptions "mgmt") // { + enable = mkOption { + type = types.bool; + default = isEnabled "static"; + defaultText = lib.literalExpression "config.services.frr.static.enable"; + description = '' + Whether to enable the Configuration management daemon. + + The Configuration management daemon is automatically + enabled if needed, at the moment this is when staticd + is enabled. + ''; + }; + }; }; } { options.services.frr = (genAttrs services serviceOptions); } @@ -164,7 +177,7 @@ in environment.etc = let mkEtcLink = service: { - name = "frr/${service}.conf"; + name = "frr/${daemonName service}.conf"; value.source = configFile service; }; in @@ -196,18 +209,18 @@ in unitConfig.Documentation = if service == "zebra" then "man:zebra(8)" else "man:${daemon}(8) man:zebra(8)"; - restartTriggers = [ + restartTriggers = mkIf (service != "mgmt") [ (configFile service) ]; - reloadIfChanged = true; + reloadIfChanged = (service != "mgmt"); serviceConfig = { PIDFile = "frr/${daemon}.pid"; - ExecStart = "${pkgs.frr}/libexec/frr/${daemon} -f /etc/frr/${service}.conf" + ExecStart = "${pkgs.frr}/libexec/frr/${daemon}" + optionalString (scfg.vtyListenAddress != "") " -A ${scfg.vtyListenAddress}" + optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}" + " " + (concatStringsSep " " scfg.extraOptions); - ExecReload = "${pkgs.python3.interpreter} ${pkgs.frr}/libexec/frr/frr-reload.py --reload --daemon ${daemonName service} --bindir ${pkgs.frr}/bin --rundir /run/frr /etc/frr/${service}.conf"; + ExecReload = mkIf (service != "mgmt") "${pkgs.python3.interpreter} ${pkgs.frr}/libexec/frr/frr-reload.py --reload --daemon ${daemon} --bindir ${pkgs.frr}/bin --rundir /run/frr /etc/frr/${daemon}.conf"; Restart = "on-abnormal"; }; }); diff --git a/nixos/modules/services/networking/git-daemon.nix b/nixos/modules/services/networking/git-daemon.nix index 6be72505c216e..522e6b14f868f 100644 --- a/nixos/modules/services/networking/git-daemon.nix +++ b/nixos/modules/services/networking/git-daemon.nix @@ -27,6 +27,8 @@ in ''; }; + package = mkPackageOption pkgs "git" { }; + basePath = mkOption { type = types.str; default = ""; @@ -119,7 +121,7 @@ in systemd.services.git-daemon = { after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - script = "${pkgs.git}/bin/git daemon --reuseaddr " + script = "${getExe cfg.package} daemon --reuseaddr " + (optionalString (cfg.basePath != "") "--base-path=${cfg.basePath} ") + (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ") + "--port=${toString cfg.port} --user=${cfg.user} --group=${cfg.group} ${cfg.options} " diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix index 1bef5a1f0a9e8..b678656f2e046 100644 --- a/nixos/modules/services/networking/hostapd.nix +++ b/nixos/modules/services/networking/hostapd.nix @@ -687,7 +687,7 @@ in { authentication = { mode = mkOption { default = "wpa3-sae"; - type = types.enum ["none" "wpa2-sha256" "wpa3-sae-transition" "wpa3-sae"]; + type = types.enum ["none" "wpa2-sha1" "wpa2-sha256" "wpa3-sae-transition" "wpa3-sae"]; description = '' Selects the authentication mode for this AP. @@ -695,7 +695,9 @@ in { and create an open AP. Use {option}`settings` together with this option if you want to configure the authentication manually. Any password options will still be effective, if set. - - {var}`"wpa2-sha256"`: WPA2-Personal using SHA256 (IEEE 802.11i/RSN). Passwords are set + - {var}`"wpa2-sha1"`: Not recommended. WPA2-Personal using HMAC-SHA1. Passwords are set + using {option}`wpaPassword` or preferably by {option}`wpaPasswordFile` or {option}`wpaPskFile`. + - {var}`"wpa2-sha256"`: WPA2-Personal using HMAC-SHA256 (IEEE 802.11i/RSN). Passwords are set using {option}`wpaPassword` or preferably by {option}`wpaPasswordFile` or {option}`wpaPskFile`. - {var}`"wpa3-sae-transition"`: Use WPA3-Personal (SAE) if possible, otherwise fallback to WPA2-SHA256. Only use if necessary and switch to the newer WPA3-SAE when possible. @@ -812,7 +814,7 @@ in { Warning: These entries will get put into a world-readable file in the Nix store! Using {option}`saePasswordFile` instead is recommended. - Not used when {option}`mode` is {var}`"wpa2-sha256"`. + Not used when {option}`mode` is {var}`"wpa2-sha1"` or {var}`"wpa2-sha256"`. ''; type = types.listOf (types.submodule { options = { @@ -884,7 +886,7 @@ in { parameters doesn't matter: `<password>[|mac=<peer mac>][|vlanid=<VLAN ID>][|pk=<m:ECPrivateKey-base64>][|id=<identifier>]` - Not used when {option}`mode` is {var}`"wpa2-sha256"`. + Not used when {option}`mode` is {var}`"wpa2-sha1"` or {var}`"wpa2-sha256"`. ''; }; @@ -959,6 +961,9 @@ in { } // optionalAttrs (bssCfg.authentication.mode == "wpa3-sae-transition") { wpa = 2; wpa_key_mgmt = "WPA-PSK-SHA256 SAE"; + } // optionalAttrs (bssCfg.authentication.mode == "wpa2-sha1") { + wpa = 2; + wpa_key_mgmt = "WPA-PSK"; } // optionalAttrs (bssCfg.authentication.mode == "wpa2-sha256") { wpa = 2; wpa_key_mgmt = "WPA-PSK-SHA256"; @@ -1186,8 +1191,8 @@ in { message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE in transition mode requires defining both a wpa password option and a sae password option''; } { - assertion = auth.mode == "wpa2-sha256" -> countWpaPasswordDefinitions == 1; - message = ''hostapd radio ${radio} bss ${bss}: uses WPA2-SHA256 which requires defining a wpa password option''; + assertion = (auth.mode == "wpa2-sha1" || auth.mode == "wpa2-sha256") -> countWpaPasswordDefinitions == 1; + message = ''hostapd radio ${radio} bss ${bss}: uses WPA2-PSK which requires defining a wpa password option''; } ]) radioCfg.networks)) diff --git a/nixos/modules/services/networking/inadyn.nix b/nixos/modules/services/networking/inadyn.nix index baa4302096c2c..7022673538c8a 100644 --- a/nixos/modules/services/networking/inadyn.nix +++ b/nixos/modules/services/networking/inadyn.nix @@ -202,7 +202,7 @@ in startAt = cfg.interval; serviceConfig = { Type = "oneshot"; - ExecStart = ''${lib.getExe pkgs.inadyn} -f ${configFile} --cache-dir ''${CACHE_DIRECTORY}/inadyn -1 --foreground -l ${cfg.logLevel}''; + ExecStart = ''${lib.getExe pkgs.inadyn} -f ${configFile} --cache-dir ''${CACHE_DIRECTORY} -1 --foreground -l ${cfg.logLevel}''; LoadCredential = "config:${configFile}"; CacheDirectory = "inadyn"; diff --git a/nixos/modules/services/networking/jotta-cli.md b/nixos/modules/services/networking/jotta-cli.md new file mode 100644 index 0000000000000..335e5c8e38563 --- /dev/null +++ b/nixos/modules/services/networking/jotta-cli.md @@ -0,0 +1,27 @@ +# Jottacloud Command-line Tool {#module-services-jotta-cli} + +The [Jottacloud Command-line Tool](https://docs.jottacloud.com/en/articles/1436834-jottacloud-command-line-tool) is a headless [Jottacloud](https://jottacloud.com) client. + +## Quick Start {#module-services-jotta-cli-quick-start} + +```nix +{ + services.jotta-cli.enable = true; +} +``` + +This adds `jotta-cli` to `environment.systemPackages` and starts a user service that runs `jottad` with the default options. + +## Example Configuration {#module-services-jotta-cli-example-configuration} + +```nix +services.jotta-cli = { + enable = true; + options = [ "slow" ]; + package = pkgs.jotta-cli; +}; +``` + +This uses `jotta-cli` and `jottad` from the `pkgs.jotta-cli` package and starts `jottad` in low memory mode. + +`jottad` is also added to `environment.systemPackages`, so `jottad --help` can be used to explore options. diff --git a/nixos/modules/services/networking/jotta-cli.nix b/nixos/modules/services/networking/jotta-cli.nix new file mode 100644 index 0000000000000..e0fa1ef332fe6 --- /dev/null +++ b/nixos/modules/services/networking/jotta-cli.nix @@ -0,0 +1,43 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.services.jotta-cli; +in { + options = { + services.jotta-cli = { + + enable = mkEnableOption "Jottacloud Command-line Tool"; + + options = mkOption { + default = [ "stdoutlog" "datadir" "%h/.jottad/" ]; + example = [ ]; + type = with types; listOf str; + description = "Command-line options passed to jottad."; + }; + + package = lib.mkPackageOption pkgs "jotta-cli" { }; + }; + }; + config = mkIf cfg.enable { + systemd.user.services.jottad = { + + description = "Jottacloud Command-line Tool daemon"; + + serviceConfig = { + Type = "notify"; + EnvironmentFile = "-%h/.config/jotta-cli/jotta-cli.env"; + ExecStart = "${lib.getExe' cfg.package "jottad"} ${concatStringsSep " " cfg.options}"; + Restart = "on-failure"; + }; + + wantedBy = [ "default.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + }; + environment.systemPackages = [ pkgs.jotta-cli ]; + }; + + meta.maintainers = with lib.maintainers; [ evenbrenden ]; + meta.doc = ./jotta-cli.md; +} diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix index 66173c145d16a..11add600b66fb 100644 --- a/nixos/modules/services/networking/kea.nix +++ b/nixos/modules/services/networking/kea.nix @@ -278,6 +278,9 @@ in "https://kea.readthedocs.io/en/kea-${package.version}/arm/agent.html" ]; + wants = [ + "network-online.target" + ]; after = [ "network-online.target" "time-sync.target" diff --git a/nixos/modules/services/networking/mihomo.nix b/nixos/modules/services/networking/mihomo.nix index 312530caeaade..d4bb10496279d 100644 --- a/nixos/modules/services/networking/mihomo.nix +++ b/nixos/modules/services/networking/mihomo.nix @@ -25,6 +25,7 @@ in webui = lib.mkOption { default = null; type = lib.types.nullOr lib.types.path; + example = lib.literalExpression "pkgs.metacubexd"; description = '' Local web interface to use. diff --git a/nixos/modules/services/networking/mycelium.nix b/nixos/modules/services/networking/mycelium.nix index 9487a5daafee0..0d0b2945af4c1 100644 --- a/nixos/modules/services/networking/mycelium.nix +++ b/nixos/modules/services/networking/mycelium.nix @@ -60,6 +60,8 @@ in networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ 9651 ]; networking.firewall.allowedUDPPorts = lib.optionals cfg.openFirewall [ 9650 9651 ]; + environment.systemPackages = [ cfg.package ]; + systemd.services.mycelium = { description = "Mycelium network"; after = [ "network.target" ]; diff --git a/nixos/modules/services/networking/netbird.nix b/nixos/modules/services/networking/netbird.nix index 7add377896cab..e68c39946fe3b 100644 --- a/nixos/modules/services/networking/netbird.nix +++ b/nixos/modules/services/networking/netbird.nix @@ -37,7 +37,6 @@ in { meta.maintainers = with maintainers; [ misuzu - thubrecht ]; meta.doc = ./netbird.md; diff --git a/nixos/modules/services/networking/netbird/coturn.nix b/nixos/modules/services/networking/netbird/coturn.nix index 746d70a07250d..29ff1e8fc15ee 100644 --- a/nixos/modules/services/networking/netbird/coturn.nix +++ b/nixos/modules/services/networking/netbird/coturn.nix @@ -60,6 +60,7 @@ in default = null; description = '' The password of the user used by netbird to connect to the coturn server. + Be advised this will be world readable in the nix store. ''; }; @@ -142,7 +143,11 @@ in ]; }); - security.acme.certs.${cfg.domain}.postRun = optionalString cfg.useAcmeCertificates "systemctl restart coturn.service"; + security.acme.certs = mkIf cfg.useAcmeCertificates { + ${cfg.domain}.postRun = '' + systemctl restart coturn.service + ''; + }; networking.firewall = { allowedUDPPorts = cfg.openPorts; diff --git a/nixos/modules/services/networking/netbird/server.nix b/nixos/modules/services/networking/netbird/server.nix index a4de0fda6a134..e3de286a04fa4 100644 --- a/nixos/modules/services/networking/netbird/server.nix +++ b/nixos/modules/services/networking/netbird/server.nix @@ -2,6 +2,7 @@ let inherit (lib) + mkDefault mkEnableOption mkIf mkOption @@ -15,7 +16,7 @@ in { meta = { - maintainers = with lib.maintainers; [ thubrecht ]; + maintainers = with lib.maintainers; [patrickdag]; doc = ./server.md; }; @@ -41,26 +42,46 @@ in config = mkIf cfg.enable { services.netbird.server = { dashboard = { - inherit (cfg) enable domain enableNginx; + domain = mkDefault cfg.domain; + enable = mkDefault cfg.enable; + enableNginx = mkDefault cfg.enableNginx; managementServer = "https://${cfg.domain}"; }; management = { - inherit (cfg) enable domain enableNginx; + domain = mkDefault cfg.domain; + enable = mkDefault cfg.enable; + enableNginx = mkDefault cfg.enableNginx; } - // (optionalAttrs cfg.coturn.enable { + // (optionalAttrs cfg.coturn.enable rec { turnDomain = cfg.domain; turnPort = config.services.coturn.tls-listening-port; + # We cannot merge a list of attrsets so we have to redefine the whole list + settings = { + TURNConfig.Turns = mkDefault [ + { + Proto = "udp"; + URI = "turn:${turnDomain}:${builtins.toString turnPort}"; + Username = "netbird"; + Password = + if (cfg.coturn.password != null) + then cfg.coturn.password + else {_secret = cfg.coturn.passwordFile;}; + } + ]; + }; }); signal = { - inherit (cfg) enable domain enableNginx; + domain = mkDefault cfg.domain; + enable = mkDefault cfg.enable; + enableNginx = mkDefault cfg.enableNginx; }; coturn = { - inherit (cfg) domain; + domain = mkDefault cfg.domain; }; }; }; diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index e33bbb2af178f..b7143cf520f96 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -470,7 +470,7 @@ in - [main] - no-auto-default=* - ''' - + extraConfig.main.no-auto-default = "*"; + + settings.main.no-auto-default = "*"; }; ``` '' diff --git a/nixos/modules/services/networking/oink.nix b/nixos/modules/services/networking/oink.nix new file mode 100644 index 0000000000000..cd0fdf172331d --- /dev/null +++ b/nixos/modules/services/networking/oink.nix @@ -0,0 +1,84 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.oink; + makeOinkConfig = attrs: (pkgs.formats.json { }).generate + "oink.json" (mapAttrs' (k: v: nameValuePair (toLower k) v) attrs); + oinkConfig = makeOinkConfig { + global = cfg.settings; + domains = cfg.domains; + }; +in +{ + options.services.oink = { + enable = mkEnableOption "Oink, a dynamic DNS client for Porkbun"; + package = mkPackageOption pkgs "oink" { }; + settings = { + apiKey = mkOption { + type = types.str; + description = "API key to use when modifying DNS records."; + }; + secretApiKey = mkOption { + type = types.str; + description = "Secret API key to use when modifying DNS records."; + }; + interval = mkOption { + # https://github.com/rlado/oink/blob/v1.1.1/src/main.go#L364 + type = types.ints.between 60 172800; # 48 hours + default = 900; + description = "Seconds to wait before sending another request."; + }; + ttl = mkOption { + type = types.ints.between 600 172800; + default = 600; + description = '' + The TTL ("Time to Live") value to set for your DNS records. + + The TTL controls how long in seconds your records will be cached + for. A smaller value will allow the record to update quicker. + ''; + }; + }; + domains = mkOption { + type = with types; listOf (attrsOf anything); + default = []; + example = [ + { + domain = "nixos.org"; + subdomain = ""; + ttl = 1200; + } + { + domain = "nixos.org"; + subdomain = "hydra"; + } + ]; + description = '' + List of attribute sets containing configuration for each domain. + + Each attribute set must have two attributes, one named *domain* + and another named *subdomain*. The domain attribute must specify + the root domain that you want to configure, and the subdomain + attribute must specify its subdomain if any. If you want to + configure the root domain rather than a subdomain, leave the + subdomain attribute as an empty string. + + Additionally, you can use attributes from *services.oink.settings* + to override settings per-domain. + + Every domain listed here *must* have API access enabled in + Porkbun's control panel. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.oink = { + description = "Dynamic DNS client for Porkbun"; + wantedBy = [ "multi-user.target" ]; + script = "${cfg.package}/bin/oink -c ${oinkConfig}"; + }; + }; +} diff --git a/nixos/modules/services/networking/pixiecore.nix b/nixos/modules/services/networking/pixiecore.nix index cfdb8014136ed..111cb7e355040 100644 --- a/nixos/modules/services/networking/pixiecore.nix +++ b/nixos/modules/services/networking/pixiecore.nix @@ -82,8 +82,8 @@ in apiServer = mkOption { type = types.str; - example = "localhost:8080"; - description = "host:port to connect to the API. Ignored unless mode is set to 'api'"; + example = "http://localhost:8080"; + description = "URI to connect to the API. Ignored unless mode is set to 'api'"; }; extraArguments = mkOption { diff --git a/nixos/modules/services/networking/radvd.nix b/nixos/modules/services/networking/radvd.nix index 4e3e501d2f593..0143324a78155 100644 --- a/nixos/modules/services/networking/radvd.nix +++ b/nixos/modules/services/networking/radvd.nix @@ -33,6 +33,17 @@ in package = mkPackageOption pkgs "radvd" { }; + debugLevel = mkOption { + type = types.int; + default = 0; + example = 5; + description = '' + The debugging level is an integer in the range from 1 to 5, + from quiet to very verbose. A debugging level of 0 completely + turns off debugging. + ''; + }; + config = mkOption { type = types.lines; example = @@ -67,7 +78,7 @@ in wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = - { ExecStart = "@${cfg.package}/bin/radvd radvd -n -u radvd -C ${confFile}"; + { ExecStart = "@${cfg.package}/bin/radvd radvd -n -u radvd -d ${toString cfg.debugLevel} -C ${confFile}"; Restart = "always"; }; }; diff --git a/nixos/modules/services/networking/rosenpass.nix b/nixos/modules/services/networking/rosenpass.nix index 373a6c7690799..92ecc1cb31a36 100644 --- a/nixos/modules/services/networking/rosenpass.nix +++ b/nixos/modules/services/networking/rosenpass.nix @@ -130,8 +130,8 @@ in relevant = config.systemd.network.enable; root = config.systemd.network.netdevs; peer = (x: x.wireguardPeers); - key = (x: if x.wireguardPeerConfig ? PublicKey then x.wireguardPeerConfig.PublicKey else null); - description = "${options.systemd.network.netdevs}.\"<name>\".wireguardPeers.*.wireguardPeerConfig.PublicKey"; + key = x: x.PublicKey or null; + description = "${options.systemd.network.netdevs}.\"<name>\".wireguardPeers.*.PublicKey"; } { relevant = config.networking.wireguard.enable; @@ -225,8 +225,10 @@ in # See <https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers> environment.CONFIG = "%t/${serviceConfig.RuntimeDirectory}/config.toml"; - preStart = "${getExe pkgs.envsubst} -i ${config} -o \"$CONFIG\""; - script = "rosenpass exchange-config \"$CONFIG\""; + script = '' + ${getExe pkgs.envsubst} -i ${config} -o "$CONFIG" + rosenpass exchange-config "$CONFIG" + ''; }; }; } diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix index 38d6e4452c97b..3fb3eac45cc82 100644 --- a/nixos/modules/services/networking/smokeping.nix +++ b/nixos/modules/services/networking/smokeping.nix @@ -47,6 +47,13 @@ let in { + imports = [ + (mkRemovedOptionModule [ "services" "smokeping" "port" ] '' + The smokeping web service is now served by nginx. + In order to change the port, you need to change the nginx configuration under `services.nginx.virtualHosts.smokeping.listen.*.port`. + '') + ]; + options = { services.smokeping = { enable = mkEnableOption "smokeping service"; @@ -71,8 +78,8 @@ in }; cgiUrl = mkOption { type = types.str; - default = "http://${cfg.hostName}:${toString cfg.port}/smokeping.cgi"; - defaultText = literalExpression ''"http://''${hostName}:''${toString port}/smokeping.cgi"''; + default = "http://${cfg.hostName}/smokeping.cgi"; + defaultText = literalExpression ''"http://''${hostName}/smokeping.cgi"''; example = "https://somewhere.example.com/smokeping.cgi"; description = "URL to the smokeping cgi."; }; @@ -177,11 +184,6 @@ in which makes it bind to all interfaces. ''; }; - port = mkOption { - type = types.port; - default = 8081; - description = "TCP port to use for the web server."; - }; presentationConfig = mkOption { type = types.lines; default = '' @@ -312,17 +314,8 @@ in description = "smokeping daemon user"; home = smokepingHome; createHome = true; - # When `cfg.webService` is enabled, `thttpd` makes SmokePing available - # under `${cfg.host}:${cfg.port}/smokeping.fcgi` as per the `ln -s` below. - # We also want that going to `${cfg.host}:${cfg.port}` without `smokeping.fcgi` - # makes it easy for the user to find SmokePing. - # However `thttpd` does not seem to support easy redirections from `/` to `smokeping.fcgi` - # and only allows directory listings or `/` -> `index.html` resolution if the directory - # has `chmod 755` (see https://acme.com/software/thttpd/thttpd_man.html#PERMISSIONS, - # " directories should be 755 if you want to allow indexing"). - # Otherwise it shows `403 Forbidden` on `/`. - # Thus, we need to make `smokepingHome` (which is given to `thttpd -d` below) `755`. - homeMode = "755"; + # When `cfg.webService` is enabled, `nginx` requires read permissions on the home directory. + homeMode = "711"; }; users.groups.${cfg.user} = { }; systemd.services.smokeping = { @@ -342,21 +335,25 @@ in ${cfg.package}/bin/smokeping --static --config=${configPath} ''; }; - systemd.services.thttpd = mkIf cfg.webService { - requiredBy = [ "multi-user.target" ]; - requires = [ "smokeping.service" ]; - path = with pkgs; [ bash rrdtool smokeping thttpd ]; - serviceConfig = { - Restart = "always"; - ExecStart = lib.concatStringsSep " " (lib.concatLists [ - [ "${pkgs.thttpd}/bin/thttpd" ] - [ "-u ${cfg.user}" ] - [ ''-c "**.fcgi"'' ] - [ "-d ${smokepingHome}" ] - (lib.optional (cfg.host != null) "-h ${cfg.host}") - [ "-p ${builtins.toString cfg.port}" ] - [ "-D -nos" ] - ]); + + # use nginx to serve the smokeping web service + services.fcgiwrap.enable = mkIf cfg.webService true; + services.nginx = mkIf cfg.webService { + enable = true; + virtualHosts."smokeping" = { + serverName = mkDefault cfg.host; + locations."/" = { + root = smokepingHome; + index = "smokeping.fcgi"; + }; + locations."/smokeping.fcgi" = { + extraConfig = '' + include ${config.services.nginx.package}/conf/fastcgi_params; + fastcgi_pass unix:${config.services.fcgiwrap.socketAddress}; + fastcgi_param SCRIPT_FILENAME ${smokepingHome}/smokeping.fcgi; + fastcgi_param DOCUMENT_ROOT ${smokepingHome}; + ''; + }; }; }; }; diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index c62bccd462d33..1e4e34a4f1675 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -5,11 +5,11 @@ with lib; let # The splicing information needed for nativeBuildInputs isn't available - # on the derivations likely to be used as `cfgc.package`. + # on the derivations likely to be used as `cfg.package`. # This middle-ground solution ensures *an* sshd can do their basic validation # on the configuration. validationPackage = if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform - then cfgc.package + then cfg.package else pkgs.buildPackages.openssh; # dont use the "=" operator @@ -169,6 +169,13 @@ in ''; }; + package = mkOption { + type = types.package; + default = config.programs.ssh.package; + defaultText = literalExpression "programs.ssh.package"; + description = "OpenSSH package to use for sshd."; + }; + startWhenNeeded = mkOption { type = types.bool; default = false; @@ -296,6 +303,17 @@ in ''; }; + authorizedKeysInHomedir = mkOption { + type = types.bool; + default = true; + description = '' + Enables the use of the `~/.ssh/authorized_keys` file. + + Otherwise, the only files trusted by default are those in `/etc/ssh/authorized_keys.d`, + *i.e.* SSH keys from [](#opt-users.users._name_.openssh.authorizedKeys.keys). + ''; + }; + authorizedKeysCommand = mkOption { type = types.str; default = "none"; @@ -331,7 +349,7 @@ in freeformType = settingsFormat.type; options = { AuthorizedPrincipalsFile = mkOption { - type = types.str; + type = types.nullOr types.str; default = "none"; # upstream default description = '' Specifies a file that lists principal names that are accepted for certificate authentication. The default @@ -339,16 +357,18 @@ in ''; }; LogLevel = mkOption { - type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ]; + type = types.nullOr (types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ]); default = "INFO"; # upstream default description = '' Gives the verbosity level that is used when logging messages from sshd(8). Logging with a DEBUG level violates the privacy of users and is not recommended. ''; }; - UsePAM = mkEnableOption "PAM authentication" // { default = true; }; + UsePAM = + mkEnableOption "PAM authentication" + // { default = true; type = types.nullOr types.bool; }; UseDns = mkOption { - type = types.bool; + type = types.nullOr types.bool; # apply if cfg.useDns then "yes" else "no" default = false; description = '' @@ -359,14 +379,14 @@ in ''; }; X11Forwarding = mkOption { - type = types.bool; + type = types.nullOr types.bool; default = false; description = '' Whether to allow X11 connections to be forwarded. ''; }; PasswordAuthentication = mkOption { - type = types.bool; + type = types.nullOr types.bool; default = true; description = '' Specifies whether password authentication is allowed. @@ -374,20 +394,20 @@ in }; PermitRootLogin = mkOption { default = "prohibit-password"; - type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"]; + type = types.nullOr (types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"]); description = '' Whether the root user can login using ssh. ''; }; KbdInteractiveAuthentication = mkOption { - type = types.bool; + type = types.nullOr types.bool; default = true; description = '' Specifies whether keyboard-interactive authentication is allowed. ''; }; GatewayPorts = mkOption { - type = types.str; + type = types.nullOr types.str; default = "no"; description = '' Specifies whether remote hosts are allowed to connect to @@ -396,7 +416,7 @@ in ''; }; KexAlgorithms = mkOption { - type = types.listOf types.str; + type = types.nullOr (types.listOf types.str); default = [ "sntrup761x25519-sha512@openssh.com" "curve25519-sha256" @@ -413,7 +433,7 @@ in ''; }; Macs = mkOption { - type = types.listOf types.str; + type = types.nullOr (types.listOf types.str); default = [ "hmac-sha2-512-etm@openssh.com" "hmac-sha2-256-etm@openssh.com" @@ -429,14 +449,14 @@ in ''; }; StrictModes = mkOption { - type = types.bool; + type = types.nullOr (types.bool); default = true; description = '' Whether sshd should check file modes and ownership of directories ''; }; Ciphers = mkOption { - type = types.listOf types.str; + type = types.nullOr (types.listOf types.str); default = [ "chacha20-poly1305@openssh.com" "aes256-gcm@openssh.com" @@ -491,7 +511,9 @@ in ''; }; # Disabled by default, since pam_motd handles this. - PrintMotd = mkEnableOption "printing /etc/motd when a user logs in interactively"; + PrintMotd = + mkEnableOption "printing /etc/motd when a user logs in interactively" + // { type = types.nullOr types.bool; }; }; }); }; @@ -533,8 +555,8 @@ in }; users.groups.sshd = {}; - services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli"; - services.openssh.sftpServerExecutable = mkDefault "${cfgc.package}/libexec/sftp-server"; + services.openssh.moduliFile = mkDefault "${cfg.package}/etc/ssh/moduli"; + services.openssh.sftpServerExecutable = mkDefault "${cfg.package}/libexec/sftp-server"; environment.etc = authKeysFiles // authPrincipalsFiles // { "ssh/moduli".source = cfg.moduliFile; @@ -548,7 +570,7 @@ in wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target"; after = [ "network.target" ]; stopIfChanged = false; - path = [ cfgc.package pkgs.gawk ]; + path = [ cfg.package pkgs.gawk ]; environment.LD_LIBRARY_PATH = nssModulesPath; restartTriggers = optionals (!cfg.startWhenNeeded) [ @@ -582,7 +604,7 @@ in serviceConfig = { ExecStart = (optionalString cfg.startWhenNeeded "-") + - "${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") + + "${cfg.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") + "-D " + # don't detach into a daemon process "-f /etc/ssh/sshd_config"; KillMode = "process"; @@ -628,14 +650,17 @@ in security.pam.services.sshd = lib.mkIf cfg.settings.UsePAM { startSession = true; showMotd = true; - unixAuth = cfg.settings.PasswordAuthentication; + unixAuth = + if cfg.settings.PasswordAuthentication == true + then true + else false; }; # These values are merged with the ones defined externally, see: # https://github.com/NixOS/nixpkgs/pull/10155 # https://github.com/NixOS/nixpkgs/pull/41745 services.openssh.authorizedKeysFiles = - [ "%h/.ssh/authorized_keys" "/etc/ssh/authorized_keys.d/%u" ]; + lib.optional cfg.authorizedKeysInHomedir "%h/.ssh/authorized_keys" ++ [ "/etc/ssh/authorized_keys.d/%u" ]; services.openssh.settings.AuthorizedPrincipalsFile = mkIf (authPrincipalsFiles != {}) "/etc/ssh/authorized_principals.d/%u"; @@ -690,6 +715,10 @@ in assertions = [{ assertion = if cfg.settings.X11Forwarding then cfgc.setXAuthLocation else true; message = "cannot enable X11 forwarding without setting xauth location";} + { assertion = (builtins.match "(.*\n)?(\t )*[Kk][Ee][Rr][Bb][Ee][Rr][Oo][Ss][Aa][Uu][Tt][Hh][Ee][Nn][Tt][Ii][Cc][Aa][Tt][Ii][Oo][Nn][ |\t|=|\"]+yes.*" "${configFile}\n${cfg.extraConfig}") != null -> cfgc.package.withKerberos; + message = "cannot enable Kerberos authentication without using a package with Kerberos support";} + { assertion = (builtins.match "(.*\n)?(\t )*[Gg][Ss][Ss][Aa][Pp][Ii][Aa][Uu][Tt][Hh][Ee][Nn][Tt][Ii][Cc][Aa][Tt][Ii][Oo][Nn][ |\t|=|\"]+yes.*" "${configFile}\n${cfg.extraConfig}") != null -> cfgc.package.withKerberos; + message = "cannot enable GSSAPI authentication without using a package with Kerberos support";} (let duplicates = # Filter out the groups with more than 1 element diff --git a/nixos/modules/services/networking/sunshine.nix b/nixos/modules/services/networking/sunshine.nix index 0749eaee95d8a..ec78db1f3f8e9 100644 --- a/nixos/modules/services/networking/sunshine.nix +++ b/nixos/modules/services/networking/sunshine.nix @@ -1,6 +1,6 @@ { config, lib, pkgs, utils, ... }: let - inherit (lib) mkEnableOption mkPackageOption mkOption mkIf mkDefault types optionals getExe; + inherit (lib) mkEnableOption mkPackageOption mkOption literalExpression mkIf mkDefault types optionals getExe; inherit (utils) escapeSystemdExecArgs; cfg = config.services.sunshine; @@ -46,7 +46,7 @@ in See https://docs.lizardbyte.dev/projects/sunshine/en/latest/about/advanced_usage.html#configuration for syntax. ''; - example = '' + example = literalExpression '' { sunshine_name = "nixos"; } @@ -67,7 +67,7 @@ in description = '' Configuration for applications to be exposed to Moonlight. If this is set, no configuration is possible from the web UI, and must be by the `settings` option. ''; - example = '' + example = literalExpression '' { env = { PATH = "$(PATH):$(HOME)/.local/bin"; diff --git a/nixos/modules/services/networking/tailscale-auth.nix b/nixos/modules/services/networking/tailscale-auth.nix index c3a515212e782..f21d1f108911c 100644 --- a/nixos/modules/services/networking/tailscale-auth.nix +++ b/nixos/modules/services/networking/tailscale-auth.nix @@ -14,7 +14,7 @@ let in { options.services.tailscaleAuth = { - enable = mkEnableOption "Enable tailscale.nginx-auth, to authenticate users via tailscale."; + enable = mkEnableOption "tailscale.nginx-auth, to authenticate users via tailscale"; package = mkPackageOption pkgs "tailscale-nginx-auth" {}; diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix index a79e47d8491b8..a690dc610e825 100644 --- a/nixos/modules/services/networking/tailscale.nix +++ b/nixos/modules/services/networking/tailscale.nix @@ -61,12 +61,21 @@ in { }; extraUpFlags = mkOption { - description = "Extra flags to pass to {command}`tailscale up`."; + description = '' + Extra flags to pass to {command}`tailscale up`. Only applied if `authKeyFile` is specified."; + ''; type = types.listOf types.str; default = []; example = ["--ssh"]; }; + extraSetFlags = mkOption { + description = "Extra flags to pass to {command}`tailscale set`."; + type = types.listOf types.str; + default = []; + example = ["--advertise-exit-node"]; + }; + extraDaemonFlags = mkOption { description = "Extra flags to pass to {command}`tailscaled`."; type = types.listOf types.str; @@ -120,6 +129,18 @@ in { ''; }; + systemd.services.tailscaled-set = mkIf (cfg.extraSetFlags != []) { + after = ["tailscaled.service"]; + wants = ["tailscaled.service"]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "oneshot"; + }; + script = '' + ${cfg.package}/bin/tailscale set ${escapeShellArgs cfg.extraSetFlags} + ''; + }; + boot.kernel.sysctl = mkIf (cfg.useRoutingFeatures == "server" || cfg.useRoutingFeatures == "both") { "net.ipv4.conf.all.forwarding" = mkOverride 97 true; "net.ipv6.conf.all.forwarding" = mkOverride 97 true; diff --git a/nixos/modules/services/networking/tayga.nix b/nixos/modules/services/networking/tayga.nix index 1a0df33fe883d..9f118b243e90c 100644 --- a/nixos/modules/services/networking/tayga.nix +++ b/nixos/modules/services/networking/tayga.nix @@ -16,6 +16,8 @@ let prefix ${strAddr cfg.ipv6.pool} dynamic-pool ${strAddr cfg.ipv4.pool} data-dir ${cfg.dataDir} + + ${concatStringsSep "\n" (mapAttrsToList (ipv4: ipv6: "map " + ipv4 + " " + ipv6) cfg.mappings)} ''; addrOpts = v: @@ -103,18 +105,38 @@ in dataDir = mkOption { type = types.path; default = "/var/lib/tayga"; - description = "Directory for persistent data"; + description = "Directory for persistent data."; }; tunDevice = mkOption { type = types.str; default = "nat64"; - description = "Name of the nat64 tun device"; + description = "Name of the nat64 tun device."; + }; + + mappings = mkOption { + type = types.attrsOf types.str; + default = {}; + description = "Static IPv4 -> IPv6 host mappings."; + example = literalExpression '' + { + "192.168.5.42" = "2001:db8:1:4444::1"; + "192.168.5.43" = "2001:db8:1:4444::2"; + "192.168.255.2" = "2001:db8:1:569::143"; + } + ''; }; }; }; config = mkIf cfg.enable { + assertions = [ + { + assertion = allUnique (attrValues cfg.mappings); + message = "Neither the IPv4 nor the IPv6 addresses must be entered twice in the mappings."; + } + ]; + networking.interfaces."${cfg.tunDevice}" = { virtual = true; virtualType = "tun"; diff --git a/nixos/modules/services/networking/trust-dns.nix b/nixos/modules/services/networking/trust-dns.nix index e6f8cc15819f6..039b7de263504 100644 --- a/nixos/modules/services/networking/trust-dns.nix +++ b/nixos/modules/services/networking/trust-dns.nix @@ -51,7 +51,7 @@ in package = mkPackageOption pkgs "trust-dns" { extraDescription = '' ::: {.note} - The package must provide `meta.mainProgram` which names the server binayr; any other utilities (client, resolver) are not needed. + The package must provide `meta.mainProgram` which names the server binary; any other utilities (client, resolver) are not needed. ::: ''; }; @@ -86,7 +86,7 @@ in type = types.listOf types.str; default = [ "0.0.0.0" ]; description = '' - List of ipv4 addresses on which to listen for DNS queries. + List of ipv4 addresses on which to listen for DNS queries. ''; }; listen_addrs_ipv6 = mkOption { @@ -114,7 +114,7 @@ in }; zones = mkOption { description = "List of zones to serve."; - default = {}; + default = []; type = types.listOf (types.coercedTo types.str (zone: { inherit zone; }) zoneType); }; }; diff --git a/nixos/modules/services/networking/vsftpd.nix b/nixos/modules/services/networking/vsftpd.nix index 25f950600b91c..07b93e92a7509 100644 --- a/nixos/modules/services/networking/vsftpd.nix +++ b/nixos/modules/services/networking/vsftpd.nix @@ -278,7 +278,7 @@ in } { assertion = (cfg.enableVirtualUsers -> cfg.userDbPath != null) - && (cfg.enableVirtualUsers -> cfg.localUsers != null); + && (cfg.enableVirtualUsers -> cfg.localUsers); message = "vsftpd: If enableVirtualUsers is true, you need to setup both the userDbPath and localUsers options."; }]; diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix index 3f68af3a86c96..81abae2c9303d 100644 --- a/nixos/modules/services/networking/wireguard.nix +++ b/nixos/modules/services/networking/wireguard.nix @@ -80,6 +80,15 @@ let description = "Commands called at the end of the interface setup."; }; + preShutdown = mkOption { + example = literalExpression ''"''${pkgs.iproute2}/bin/ip netns del foo"''; + default = ""; + type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines; + description = '' + Commands called before shutting down the interface. + ''; + }; + postShutdown = mkOption { example = literalExpression ''"''${pkgs.openresolv}/bin/resolvconf -d wg0"''; default = ""; @@ -497,6 +506,7 @@ let ''; postStop = '' + ${values.preShutdown} ${ipPostMove} link del dev "${name}" ${values.postShutdown} ''; diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix index efb65aead116a..bd7536351955a 100644 --- a/nixos/modules/services/networking/wstunnel.nix +++ b/nixos/modules/services/networking/wstunnel.nix @@ -1,95 +1,94 @@ -{ config, lib, options, pkgs, utils, ... }: -with lib; +{ config +, lib +, pkgs +, ... +}: + let cfg = config.services.wstunnel; - attrsToArgs = attrs: utils.escapeSystemdExecArgs ( - mapAttrsToList - (name: value: if value == true then "--${name}" else "--${name}=${value}") - attrs - ); + + hostPortToString = { host, port }: "${host}:${toString port}"; + hostPortSubmodule = { options = { - host = mkOption { + host = lib.mkOption { description = "The hostname."; - type = types.str; + type = lib.types.str; }; - port = mkOption { + port = lib.mkOption { description = "The port."; - type = types.port; - }; - }; - }; - localRemoteSubmodule = { - options = { - local = mkOption { - description = "Local address and port to listen on."; - type = types.submodule hostPortSubmodule; - example = { - host = "127.0.0.1"; - port = 51820; - }; - }; - remote = mkOption { - description = "Address and port on remote to forward traffic to."; - type = types.submodule hostPortSubmodule; - example = { - host = "127.0.0.1"; - port = 51820; - }; + type = lib.types.port; }; }; }; - hostPortToString = { host, port }: "${host}:${builtins.toString port}"; - localRemoteToString = { local, remote }: utils.escapeSystemdExecArg "${hostPortToString local}:${hostPortToString remote}"; + commonOptions = { - enable = mkOption { - description = "Whether to enable this `wstunnel` instance."; - type = types.bool; + enable = lib.mkEnableOption "this `wstunnel` instance." // { default = true; }; - package = mkPackageOption pkgs "wstunnel" {}; + package = lib.mkPackageOption pkgs "wstunnel" { }; - autoStart = mkOption { - description = "Whether this tunnel server should be started automatically."; - type = types.bool; - default = true; - }; + autoStart = + lib.mkEnableOption "starting this wstunnel instance automatically." // { + default = true; + }; - extraArgs = mkOption { - description = "Extra command line arguments to pass to `wstunnel`. Attributes of the form `argName = true;` will be translated to `--argName`, and `argName = \"value\"` to `--argName=value`."; - type = with types; attrsOf (either str bool); - default = {}; + extraArgs = lib.mkOption { + description = '' + Extra command line arguments to pass to `wstunnel`. + Attributes of the form `argName = true;` will be translated to `--argName`, + and `argName = \"value\"` to `--argName value`. + ''; + type = with lib.types; attrsOf (either str bool); + default = { }; example = { "someNewOption" = true; "someNewOptionWithValue" = "someValue"; }; }; - verboseLogging = mkOption { - description = "Enable verbose logging."; - type = types.bool; - default = false; + loggingLevel = lib.mkOption { + description = '' + Passed to --log-lvl + + Control the log verbosity. i.e: TRACE, DEBUG, INFO, WARN, ERROR, OFF + For more details, checkout [EnvFilter](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#example-syntax) + ''; + type = lib.types.nullOr lib.types.str; + example = "INFO"; + default = null; }; - environmentFile = mkOption { - description = "Environment file to be passed to the systemd service. Useful for passing secrets to the service to prevent them from being world-readable in the Nix store. Note however that the secrets are passed to `wstunnel` through the command line, which makes them locally readable for all users of the system at runtime."; - type = types.nullOr types.path; + environmentFile = lib.mkOption { + description = '' + Environment file to be passed to the systemd service. + Useful for passing secrets to the service to prevent them from being + world-readable in the Nix store. + Note however that the secrets are passed to `wstunnel` through + the command line, which makes them locally readable for all users of + the system at runtime. + ''; + type = lib.types.nullOr lib.types.path; default = null; example = "/var/lib/secrets/wstunnelSecrets"; }; }; - serverSubmodule = { config, ...}: { + serverSubmodule = { config, ... }: { options = commonOptions // { - listen = mkOption { - description = "Address and port to listen on. Setting the port to a value below 1024 will also give the process the required `CAP_NET_BIND_SERVICE` capability."; - type = types.submodule hostPortSubmodule; + listen = lib.mkOption { + description = '' + Address and port to listen on. + Setting the port to a value below 1024 will also give the process + the required `CAP_NET_BIND_SERVICE` capability. + ''; + type = lib.types.submodule hostPortSubmodule; default = { host = "0.0.0.0"; port = if config.enableHTTPS then 443 else 80; }; - defaultText = literalExpression '' + defaultText = lib.literalExpression '' { host = "0.0.0.0"; port = if enableHTTPS then 443 else 80; @@ -97,250 +96,252 @@ let ''; }; - restrictTo = mkOption { - description = "Accepted traffic will be forwarded only to this service. Set to `null` to allow forwarding to arbitrary addresses."; - type = types.nullOr (types.submodule hostPortSubmodule); - example = { + restrictTo = lib.mkOption { + description = '' + Accepted traffic will be forwarded only to this service. + ''; + type = lib.types.listOf (lib.types.submodule hostPortSubmodule); + default = [ ]; + example = [{ host = "127.0.0.1"; port = 51820; - }; + }]; }; - enableHTTPS = mkOption { + enableHTTPS = lib.mkOption { description = "Use HTTPS for the tunnel server."; - type = types.bool; + type = lib.types.bool; default = true; }; - tlsCertificate = mkOption { - description = "TLS certificate to use instead of the hardcoded one in case of HTTPS connections. Use together with `tlsKey`."; - type = types.nullOr types.path; + tlsCertificate = lib.mkOption { + description = '' + TLS certificate to use instead of the hardcoded one in case of HTTPS connections. + Use together with `tlsKey`. + ''; + type = lib.types.nullOr lib.types.path; default = null; example = "/var/lib/secrets/cert.pem"; }; - tlsKey = mkOption { - description = "TLS key to use instead of the hardcoded on in case of HTTPS connections. Use together with `tlsCertificate`."; - type = types.nullOr types.path; + tlsKey = lib.mkOption { + description = '' + TLS key to use instead of the hardcoded on in case of HTTPS connections. + Use together with `tlsCertificate`. + ''; + type = lib.types.nullOr lib.types.path; default = null; example = "/var/lib/secrets/key.pem"; }; - useACMEHost = mkOption { - description = "Use a certificate generated by the NixOS ACME module for the given host. Note that this will not generate a new certificate - you will need to do so with `security.acme.certs`."; - type = types.nullOr types.str; + useACMEHost = lib.mkOption { + description = '' + Use a certificate generated by the NixOS ACME module for the given host. + Note that this will not generate a new certificate - you will need to do so with `security.acme.certs`. + ''; + type = lib.types.nullOr lib.types.str; default = null; example = "example.com"; }; }; }; + clientSubmodule = { config, ... }: { options = commonOptions // { - connectTo = mkOption { + connectTo = lib.mkOption { description = "Server address and port to connect to."; - type = types.submodule hostPortSubmodule; - example = { - host = "example.com"; - }; - }; - - enableHTTPS = mkOption { - description = "Enable HTTPS when connecting to the server."; - type = types.bool; - default = true; - }; - - localToRemote = mkOption { - description = "Local hosts and ports to listen on, plus the hosts and ports on remote to forward traffic to. Setting a local port to a value less than 1024 will additionally give the process the required CAP_NET_BIND_SERVICE capability."; - type = types.listOf (types.submodule localRemoteSubmodule); - default = []; - example = [ { - local = { - host = "127.0.0.1"; - port = 8080; - }; - remote = { - host = "127.0.0.1"; - port = 8080; - }; - } ]; + type = lib.types.str; + example = "https://wstunnel.server.com:8443"; }; - dynamicToRemote = mkOption { - description = "Host and port for the SOCKS5 proxy to dynamically forward traffic to. Leave this at `null` to disable the SOCKS5 proxy. Setting the port to a value less than 1024 will additionally give the service the required CAP_NET_BIND_SERVICE capability."; - type = types.nullOr (types.submodule hostPortSubmodule); - default = null; - example = { - host = "127.0.0.1"; - port = 1080; - }; + localToRemote = lib.mkOption { + description = ''Listen on local and forwards traffic from remote.''; + type = lib.types.listOf (lib.types.str); + default = [ ]; + example = [ + "tcp://1212:google.com:443" + "unix:///tmp/wstunnel.sock:g.com:443" + ]; }; - udp = mkOption { - description = "Whether to forward UDP instead of TCP traffic."; - type = types.bool; - default = false; + remoteToLocal = lib.mkOption { + description = "Listen on remote and forwards traffic from local. Only tcp is supported"; + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ + "tcp://1212:google.com:443" + "unix://wstunnel.sock:g.com:443" + ]; }; - udpTimeout = mkOption { - description = "When using UDP forwarding, timeout in seconds after which the tunnel connection is closed. `-1` means no timeout."; - type = types.int; - default = 30; - }; + addNetBind = lib.mkEnableOption "Whether add CAP_NET_BIND_SERVICE to the tunnel service, this should be enabled if you want to bind port < 1024"; - httpProxy = mkOption { + httpProxy = lib.mkOption { description = '' Proxy to use to connect to the wstunnel server (`USER:PASS@HOST:PORT`). ::: {.warning} - Passwords specified here will be world-readable in the Nix store! To pass a password to the service, point the `environmentFile` option to a file containing `PROXY_PASSWORD=<your-password-here>` and set this option to `<user>:$PROXY_PASSWORD@<host>:<port>`. Note however that this will also locally leak the passwords at runtime via e.g. /proc/<pid>/cmdline. - + Passwords specified here will be world-readable in the Nix store! + To pass a password to the service, point the `environmentFile` option + to a file containing `PROXY_PASSWORD=<your-password-here>` and set + this option to `<user>:$PROXY_PASSWORD@<host>:<port>`. + Note however that this will also locally leak the passwords at + runtime via e.g. /proc/<pid>/cmdline. ::: ''; - type = types.nullOr types.str; + type = lib.types.nullOr lib.types.str; default = null; }; - soMark = mkOption { - description = "Mark network packets with the SO_MARK sockoption with the specified value. Setting this option will also enable the required `CAP_NET_ADMIN` capability for the systemd service."; - type = types.nullOr types.int; + soMark = lib.mkOption { + description = '' + Mark network packets with the SO_MARK sockoption with the specified value. + Setting this option will also enable the required `CAP_NET_ADMIN` capability + for the systemd service. + ''; + type = lib.types.nullOr lib.types.ints.unsigned; default = null; }; - upgradePathPrefix = mkOption { - description = "Use a specific HTTP path prefix that will show up in the upgrade request to the `wstunnel` server. Useful when running `wstunnel` behind a reverse proxy."; - type = types.nullOr types.str; + upgradePathPrefix = lib.mkOption { + description = '' + Use a specific HTTP path prefix that will show up in the upgrade + request to the `wstunnel` server. + Useful when running `wstunnel` behind a reverse proxy. + ''; + type = lib.types.nullOr lib.types.str; default = null; example = "wstunnel"; }; - hostHeader = mkOption { - description = "Use this as the HTTP host header instead of the real hostname. Useful for circumventing hostname-based firewalls."; - type = types.nullOr types.str; - default = null; - }; - - tlsSNI = mkOption { + tlsSNI = lib.mkOption { description = "Use this as the SNI while connecting via TLS. Useful for circumventing hostname-based firewalls."; - type = types.nullOr types.str; + type = lib.types.nullOr lib.types.str; default = null; }; - tlsVerifyCertificate = mkOption { + tlsVerifyCertificate = lib.mkOption { description = "Whether to verify the TLS certificate of the server. It might be useful to set this to `false` when working with the `tlsSNI` option."; - type = types.bool; + type = lib.types.bool; default = true; }; # The original argument name `websocketPingFrequency` is a misnomer, as the frequency is the inverse of the interval. - websocketPingInterval = mkOption { - description = "Do a heartbeat ping every N seconds to keep up the websocket connection."; - type = types.nullOr types.ints.unsigned; + websocketPingInterval = lib.mkOption { + description = "Frequency at which the client will send websocket ping to the server."; + type = lib.types.nullOr lib.types.ints.unsigned; default = null; }; - upgradeCredentials = mkOption { + upgradeCredentials = lib.mkOption { description = '' - Use these credentials to authenticate during the HTTP upgrade request (Basic authorization type, `USER:[PASS]`). + Use these credentials to authenticate during the HTTP upgrade request + (Basic authorization type, `USER:[PASS]`). ::: {.warning} - Passwords specified here will be world-readable in the Nix store! To pass a password to the service, point the `environmentFile` option to a file containing `HTTP_PASSWORD=<your-password-here>` and set this option to `<user>:$HTTP_PASSWORD`. Note however that this will also locally leak the passwords at runtime via e.g. /proc/<pid>/cmdline. + Passwords specified here will be world-readable in the Nix store! + To pass a password to the service, point the `environmentFile` option + to a file containing `HTTP_PASSWORD=<your-password-here>` and set this + option to `<user>:$HTTP_PASSWORD`. + Note however that this will also locally leak the passwords at runtime + via e.g. /proc/<pid>/cmdline. ::: ''; - type = types.nullOr types.str; + type = lib.types.nullOr lib.types.str; default = null; }; - customHeaders = mkOption { + customHeaders = lib.mkOption { description = "Custom HTTP headers to send during the upgrade request."; - type = types.attrsOf types.str; - default = {}; + type = lib.types.attrsOf lib.types.str; + default = { }; example = { "X-Some-Header" = "some-value"; }; }; }; }; + generateServerUnit = name: serverCfg: { name = "wstunnel-server-${name}"; - value = { - description = "wstunnel server - ${name}"; - requires = [ "network.target" "network-online.target" ]; - after = [ "network.target" "network-online.target" ]; - wantedBy = optional serverCfg.autoStart "multi-user.target"; - - serviceConfig = let - certConfig = config.security.acme.certs."${serverCfg.useACMEHost}"; - in { - Type = "simple"; - ExecStart = with serverCfg; let - resolvedTlsCertificate = if useACMEHost != null - then "${certConfig.directory}/fullchain.pem" - else tlsCertificate; - resolvedTlsKey = if useACMEHost != null - then "${certConfig.directory}/key.pem" - else tlsKey; - in '' - ${package}/bin/wstunnel \ - --server \ - ${optionalString (restrictTo != null) "--restrictTo=${utils.escapeSystemdExecArg (hostPortToString restrictTo)}"} \ - ${optionalString (resolvedTlsCertificate != null) "--tlsCertificate=${utils.escapeSystemdExecArg resolvedTlsCertificate}"} \ - ${optionalString (resolvedTlsKey != null) "--tlsKey=${utils.escapeSystemdExecArg resolvedTlsKey}"} \ - ${optionalString verboseLogging "--verbose"} \ - ${attrsToArgs extraArgs} \ - ${utils.escapeSystemdExecArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString listen}"} - ''; - EnvironmentFile = optional (serverCfg.environmentFile != null) serverCfg.environmentFile; - DynamicUser = true; - SupplementaryGroups = optional (serverCfg.useACMEHost != null) certConfig.group; - PrivateTmp = true; - AmbientCapabilities = optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; - NoNewPrivileges = true; - RestrictNamespaces = "uts ipc pid user cgroup"; - ProtectSystem = "strict"; - ProtectHome = true; - ProtectKernelTunables = true; - ProtectKernelModules = true; - ProtectControlGroups = true; - PrivateDevices = true; - RestrictSUIDSGID = true; + value = + let + certConfig = config.security.acme.certs.${serverCfg.useACMEHost}; + in + { + description = "wstunnel server - ${name}"; + requires = [ "network.target" "network-online.target" ]; + after = [ "network.target" "network-online.target" ]; + wantedBy = lib.optional serverCfg.autoStart "multi-user.target"; + + environment.RUST_LOG = serverCfg.loggingLevel; + + serviceConfig = { + Type = "exec"; + EnvironmentFile = + lib.optional (serverCfg.environmentFile != null) serverCfg.environmentFile; + DynamicUser = true; + SupplementaryGroups = + lib.optional (serverCfg.useACMEHost != null) certConfig.group; + PrivateTmp = true; + AmbientCapabilities = + lib.optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ]; + NoNewPrivileges = true; + RestrictNamespaces = "uts ipc pid user cgroup"; + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + PrivateDevices = true; + RestrictSUIDSGID = true; + + Restart = "on-failure"; + RestartSec = 2; + RestartSteps = 20; + RestartMaxDelaySec = "5min"; + }; + script = with serverCfg; '' + ${lib.getExe package} \ + server \ + ${lib.cli.toGNUCommandLineShell { } ( + lib.recursiveUpdate + { + restrict-to = map hostPortToString restrictTo; + tls-certificate = if useACMEHost != null + then "${certConfig.directory}/fullchain.pem" + else "${tlsCertificate}"; + tls-private-key = if useACMEHost != null + then "${certConfig.directory}/key.pem" + else "${tlsKey}"; + } + extraArgs + )} \ + ${lib.escapeShellArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString listen}"} + ''; }; - }; }; + generateClientUnit = name: clientCfg: { name = "wstunnel-client-${name}"; value = { description = "wstunnel client - ${name}"; requires = [ "network.target" "network-online.target" ]; after = [ "network.target" "network-online.target" ]; - wantedBy = optional clientCfg.autoStart "multi-user.target"; + wantedBy = lib.optional clientCfg.autoStart "multi-user.target"; + + environment.RUST_LOG = clientCfg.loggingLevel; serviceConfig = { - Type = "simple"; - ExecStart = with clientCfg; '' - ${package}/bin/wstunnel \ - ${concatStringsSep " " (builtins.map (x: "--localToRemote=${localRemoteToString x}") localToRemote)} \ - ${concatStringsSep " " (mapAttrsToList (n: v: "--customHeaders=\"${n}: ${v}\"") customHeaders)} \ - ${optionalString (dynamicToRemote != null) "--dynamicToRemote=${utils.escapeSystemdExecArg (hostPortToString dynamicToRemote)}"} \ - ${optionalString udp "--udp"} \ - ${optionalString (httpProxy != null) "--httpProxy=${httpProxy}"} \ - ${optionalString (soMark != null) "--soMark=${toString soMark}"} \ - ${optionalString (upgradePathPrefix != null) "--upgradePathPrefix=${upgradePathPrefix}"} \ - ${optionalString (hostHeader != null) "--hostHeader=${hostHeader}"} \ - ${optionalString (tlsSNI != null) "--tlsSNI=${tlsSNI}"} \ - ${optionalString tlsVerifyCertificate "--tlsVerifyCertificate"} \ - ${optionalString (websocketPingInterval != null) "--websocketPingFrequency=${toString websocketPingInterval}"} \ - ${optionalString (upgradeCredentials != null) "--upgradeCredentials=${upgradeCredentials}"} \ - --udpTimeoutSec=${toString udpTimeout} \ - ${optionalString verboseLogging "--verbose"} \ - ${attrsToArgs extraArgs} \ - ${utils.escapeSystemdExecArg "${if enableHTTPS then "wss" else "ws"}://${hostPortToString connectTo}"} - ''; - EnvironmentFile = optional (clientCfg.environmentFile != null) clientCfg.environmentFile; + Type = "exec"; + EnvironmentFile = + lib.optional (clientCfg.environmentFile != null) clientCfg.environmentFile; DynamicUser = true; PrivateTmp = true; - AmbientCapabilities = (optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]) ++ (optionals ((clientCfg.dynamicToRemote.port or 1024) < 1024 || (any (x: x.local.port < 1024) clientCfg.localToRemote)) [ "CAP_NET_BIND_SERVICE" ]); + AmbientCapabilities = + (lib.optionals clientCfg.addNetBind [ "CAP_NET_BIND_SERVICE" ]) ++ + (lib.optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]); NoNewPrivileges = true; RestrictNamespaces = "uts ipc pid user cgroup"; ProtectSystem = "strict"; @@ -350,80 +351,118 @@ let ProtectControlGroups = true; PrivateDevices = true; RestrictSUIDSGID = true; + + Restart = "on-failure"; + RestartSec = 2; + RestartSteps = 20; + RestartMaxDelaySec = "5min"; }; + + script = with clientCfg; '' + ${lib.getExe package} \ + client \ + ${lib.cli.toGNUCommandLineShell { } ( + lib.recursiveUpdate + { + local-to-remote = localToRemote; + remote-to-local = remoteToLocal; + http-headers = lib.mapAttrsToList (n: v: "${n}:${v}") customHeaders; + http-proxy = httpProxy; + socket-so-mark = soMark; + http-upgrade-path-prefix = upgradePathPrefix; + tls-sni-override = tlsSNI; + tls-verify-certificate = tlsVerifyCertificate; + websocket-ping-frequency-sec = websocketPingInterval; + http-upgrade-credentials = upgradeCredentials; + } + extraArgs + )} \ + ${lib.escapeShellArg connectTo} + ''; }; }; -in { +in +{ options.services.wstunnel = { - enable = mkEnableOption "wstunnel"; + enable = lib.mkEnableOption "wstunnel"; - servers = mkOption { + servers = lib.mkOption { description = "`wstunnel` servers to set up."; - type = types.attrsOf (types.submodule serverSubmodule); - default = {}; + type = lib.types.attrsOf (lib.types.submodule serverSubmodule); + default = { }; example = { "wg-tunnel" = { - listen.port = 8080; + listen = { + host = "0.0.0.0"; + port = 8080; + }; enableHTTPS = true; tlsCertificate = "/var/lib/secrets/fullchain.pem"; tlsKey = "/var/lib/secrets/key.pem"; - restrictTo = { + restrictTo = [{ host = "127.0.0.1"; port = 51820; - }; + }]; }; }; }; - clients = mkOption { + clients = lib.mkOption { description = "`wstunnel` clients to set up."; - type = types.attrsOf (types.submodule clientSubmodule); - default = {}; + type = lib.types.attrsOf (lib.types.submodule clientSubmodule); + default = { }; example = { "wg-tunnel" = { - connectTo = { - host = "example.com"; - port = 8080; - }; - enableHTTPS = true; - localToRemote = { - local = { - host = "127.0.0.1"; - port = 51820; - }; - remote = { - host = "127.0.0.1"; - port = 51820; - }; - }; - udp = true; + connectTo = "wss://wstunnel.server.com:8443"; + localToRemote = [ + "tcp://1212:google.com:443" + "tcp://2:n.lan:4?proxy_protocol" + ]; + remoteToLocal = [ + "socks5://[::1]:1212" + "unix://wstunnel.sock:g.com:443" + ]; }; }; }; }; - config = mkIf cfg.enable { - systemd.services = (mapAttrs' generateServerUnit (filterAttrs (n: v: v.enable) cfg.servers)) // (mapAttrs' generateClientUnit (filterAttrs (n: v: v.enable) cfg.clients)); - - assertions = (mapAttrsToList (name: serverCfg: { - assertion = !(serverCfg.useACMEHost != null && (serverCfg.tlsCertificate != null || serverCfg.tlsKey != null)); - message = '' - Options services.wstunnel.servers."${name}".useACMEHost and services.wstunnel.servers."${name}".{tlsCertificate, tlsKey} are mutually exclusive. - ''; - }) cfg.servers) ++ - (mapAttrsToList (name: serverCfg: { - assertion = !((serverCfg.tlsCertificate != null || serverCfg.tlsKey != null) && !(serverCfg.tlsCertificate != null && serverCfg.tlsKey != null)); - message = '' - services.wstunnel.servers."${name}".tlsCertificate and services.wstunnel.servers."${name}".tlsKey need to be set together. - ''; - }) cfg.servers) ++ - (mapAttrsToList (name: clientCfg: { - assertion = !(clientCfg.localToRemote == [] && clientCfg.dynamicToRemote == null); - message = '' - Either one of services.wstunnel.clients."${name}".localToRemote or services.wstunnel.clients."${name}".dynamicToRemote must be set. - ''; - }) cfg.clients); + config = lib.mkIf cfg.enable { + systemd.services = + (lib.mapAttrs' generateServerUnit (lib.filterAttrs (n: v: v.enable) cfg.servers)) // + (lib.mapAttrs' generateClientUnit (lib.filterAttrs (n: v: v.enable) cfg.clients)); + + assertions = + (lib.mapAttrsToList + (name: serverCfg: { + assertion = + !(serverCfg.useACMEHost != null && serverCfg.tlsCertificate != null); + message = '' + Options services.wstunnel.servers."${name}".useACMEHost and services.wstunnel.servers."${name}".{tlsCertificate, tlsKey} are mutually exclusive. + ''; + }) + cfg.servers) ++ + + (lib.mapAttrsToList + (name: serverCfg: { + assertion = + (serverCfg.tlsCertificate == null && serverCfg.tlsKey == null) || + (serverCfg.tlsCertificate != null && serverCfg.tlsKey != null); + message = '' + services.wstunnel.servers."${name}".tlsCertificate and services.wstunnel.servers."${name}".tlsKey need to be set together. + ''; + }) + cfg.servers) ++ + + (lib.mapAttrsToList + (name: clientCfg: { + assertion = !(clientCfg.localToRemote == [ ] && clientCfg.remoteToLocal == [ ]); + message = '' + Either one of services.wstunnel.clients."${name}".localToRemote or services.wstunnel.clients."${name}".remoteToLocal must be set. + ''; + }) + cfg.clients); }; - meta.maintainers = with maintainers; [ alyaeanyx ]; + meta.maintainers = with lib.maintainers; [ alyaeanyx rvdp neverbehave ]; } diff --git a/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix index 86c1efc629a98..68c04118fdd58 100644 --- a/nixos/modules/services/networking/zerotierone.nix +++ b/nixos/modules/services/networking/zerotierone.nix @@ -4,7 +4,9 @@ with lib; let cfg = config.services.zerotierone; - localConfFile = pkgs.writeText "zt-local.conf" (builtins.toJSON cfg.localConf); + + settingsFormat = pkgs.formats.json {}; + localConfFile = settingsFormat.generate "zt-local.conf" cfg.localConf; localConfFilePath = "/var/lib/zerotier-one/local.conf"; in { @@ -41,7 +43,7 @@ in example = { settings.allowTcpFallbackRelay = false; }; - type = types.nullOr types.attrs; + type = settingsFormat.type; }; config = mkIf cfg.enable { @@ -60,7 +62,7 @@ in chown -R root:root /var/lib/zerotier-one '' + (concatMapStrings (netId: '' touch "/var/lib/zerotier-one/networks.d/${netId}.conf" - '') cfg.joinNetworks) + optionalString (cfg.localConf != null) '' + '') cfg.joinNetworks) + optionalString (cfg.localConf != {}) '' if [ -L "${localConfFilePath}" ] then rm ${localConfFilePath} |