diff options
Diffstat (limited to 'nixos/modules/services')
117 files changed, 3546 insertions, 846 deletions
diff --git a/nixos/modules/services/admin/pgadmin.nix b/nixos/modules/services/admin/pgadmin.nix index 390c80d1a2d4..ceb5655dc562 100644 --- a/nixos/modules/services/admin/pgadmin.nix +++ b/nixos/modules/services/admin/pgadmin.nix @@ -3,7 +3,6 @@ with lib; let - pkg = pkgs.pgadmin4; cfg = config.services.pgadmin; _base = with types; [ int bool str ]; @@ -36,6 +35,8 @@ in default = 5050; }; + package = mkPackageOptionMD pkgs "pgadmin4" { }; + initialEmail = mkOption { description = lib.mdDoc "Initial email for the pgAdmin account"; type = types.str; @@ -43,12 +44,19 @@ in initialPasswordFile = mkOption { description = lib.mdDoc '' - Initial password file for the pgAdmin account. + Initial password file for the pgAdmin account. Minimum length by default is 6. + Please see `services.pgadmin.minimumPasswordLength`. NOTE: Should be string not a store path, to prevent the password from being world readable ''; type = types.path; }; + minimumPasswordLength = mkOption { + description = lib.mdDoc "Minimum length of the password"; + type = types.int; + default = 6; + }; + emailServer = { enable = mkOption { description = lib.mdDoc '' @@ -115,7 +123,9 @@ in services.pgadmin.settings = { DEFAULT_SERVER_PORT = cfg.port; + PASSWORD_LENGTH_MIN = cfg.minimumPasswordLength; SERVER_MODE = true; + UPGRADE_CHECK_ENABLED = false; } // (optionalAttrs cfg.openFirewall { DEFAULT_SERVER = mkDefault "::"; }) // (optionalAttrs cfg.emailServer.enable { @@ -139,6 +149,14 @@ in preStart = '' # NOTE: this is idempotent (aka running it twice has no effect) + # Check here for password length to prevent pgadmin from starting + # and presenting a hard to find error message + # see https://github.com/NixOS/nixpkgs/issues/270624 + PW_LENGTH=$(wc -m < ${escapeShellArg cfg.initialPasswordFile}) + if [ $PW_LENGTH -lt ${toString cfg.minimumPasswordLength} ]; then + echo "Password must be at least ${toString cfg.minimumPasswordLength} characters long" + exit 1 + fi ( # Email address: echo ${escapeShellArg cfg.initialEmail} @@ -150,7 +168,7 @@ in echo "$PW" # Retype password: echo "$PW" - ) | ${pkg}/bin/pgadmin4-setup + ) | ${cfg.package}/bin/pgadmin4-setup ''; restartTriggers = [ @@ -162,7 +180,7 @@ in DynamicUser = true; LogsDirectory = "pgadmin"; StateDirectory = "pgadmin"; - ExecStart = "${pkg}/bin/pgadmin4"; + ExecStart = "${cfg.package}/bin/pgadmin4"; }; }; diff --git a/nixos/modules/services/audio/mopidy.nix b/nixos/modules/services/audio/mopidy.nix index 40e8679f53d7..9d8e67b0ea47 100644 --- a/nixos/modules/services/audio/mopidy.nix +++ b/nixos/modules/services/audio/mopidy.nix @@ -76,7 +76,7 @@ in { systemd.services.mopidy = { wantedBy = [ "multi-user.target" ]; - after = [ "network.target" "sound.target" ]; + after = [ "network-online.target" "sound.target" ]; description = "mopidy music player daemon"; serviceConfig = { ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)}"; diff --git a/nixos/modules/services/audio/mympd.nix b/nixos/modules/services/audio/mympd.nix new file mode 100644 index 000000000000..f1c7197085d7 --- /dev/null +++ b/nixos/modules/services/audio/mympd.nix @@ -0,0 +1,129 @@ +{ pkgs, config, lib, ... }: + +let + cfg = config.services.mympd; +in { + options = { + + services.mympd = { + + enable = lib.mkEnableOption (lib.mdDoc "MyMPD server"); + + package = lib.mkPackageOption pkgs "mympd" {}; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc '' + Open ports needed for the functionality of the program. + ''; + }; + + extraGroups = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "music" ]; + description = lib.mdDoc '' + Additional groups for the systemd service. + ''; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = with lib.types; attrsOf (nullOr (oneOf [ str bool int ])); + options = { + http_port = lib.mkOption { + type = lib.types.port; + description = lib.mdDoc '' + The HTTP port where mympd's web interface will be available. + + The HTTPS/SSL port can be configured via {option}`config`. + ''; + example = "8080"; + }; + + ssl = lib.mkOption { + type = lib.types.bool; + description = lib.mdDoc '' + Whether to enable listening on the SSL port. + + Refer to <https://jcorporation.github.io/myMPD/configuration/configuration-files#ssl-options> + for more information. + ''; + default = false; + }; + }; + }; + description = lib.mdDoc '' + Manages the configuration files declaratively. For all the configuration + options, see <https://jcorporation.github.io/myMPD/configuration/configuration-files>. + + Each key represents the "File" column from the upstream configuration table, and the + value is the content of that file. + ''; + }; + }; + + }; + + config = lib.mkIf cfg.enable { + systemd.services.mympd = { + # upstream service config: https://github.com/jcorporation/myMPD/blob/master/contrib/initscripts/mympd.service.in + after = [ "mpd.service" ]; + wantedBy = [ "multi-user.target" ]; + preStart = with lib; '' + config_dir="/var/lib/mympd/config" + mkdir -p "$config_dir" + + ${pipe cfg.settings [ + (mapAttrsToList (name: value: '' + echo -n "${if isBool value then boolToString value else toString value}" > "$config_dir/${name}" + '')) + (concatStringsSep "\n") + ]} + ''; + unitConfig = { + Description = "myMPD server daemon"; + Documentation = "man:mympd(1)"; + }; + serviceConfig = { + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; + DynamicUser = true; + ExecStart = lib.getExe cfg.package; + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + RestrictRealtime = true; + StateDirectory = "mympd"; + CacheDirectory = "mympd"; + RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK AF_UNIX"; + RestrictNamespaces = true; + SystemCallArchitectures = "native"; + SystemCallFilter = "@system-service"; + SupplementaryGroups = cfg.extraGroups; + }; + }; + + networking.firewall = lib.mkMerge [ + (lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.settings.http_port ]; + }) + (lib.mkIf (cfg.openFirewall && cfg.settings.ssl && cfg.settings.ssl_port != null) { + allowedTCPPorts = [ cfg.settings.ssl_port ]; + }) + ]; + + }; + + meta.maintainers = [ lib.maintainers.eliandoran ]; + +} diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix index 393fe83f493f..6f4455d3be60 100644 --- a/nixos/modules/services/backup/borgbackup.nix +++ b/nixos/modules/services/backup/borgbackup.nix @@ -143,20 +143,15 @@ let }; # Paths listed in ReadWritePaths must exist before service is started - mkActivationScript = name: cfg: + mkTmpfiles = name: cfg: let - install = "install -o ${cfg.user} -g ${cfg.group}"; - in - nameValuePair "borgbackup-job-${name}" (stringAfter [ "users" ] ('' - # Ensure that the home directory already exists - # We can't assert createHome == true because that's not the case for root - cd "${config.users.users.${cfg.user}.home}" - # Create each directory separately to prevent root owned parent dirs - ${install} -d .config .config/borg - ${install} -d .cache .cache/borg - '' + optionalString (isLocalPath cfg.repo && !cfg.removableDevice) '' - ${install} -d ${escapeShellArg cfg.repo} - '')); + settings = { inherit (cfg) user group; }; + in lib.nameValuePair "borgbackup-job-${name}" ({ + "${config.users.users."${cfg.user}".home}/.config/borg".d = settings; + "${config.users.users."${cfg.user}".home}/.cache/borg".d = settings; + } // optionalAttrs (isLocalPath cfg.repo && !cfg.removableDevice) { + "${cfg.repo}".d = settings; + }); mkPassAssertion = name: cfg: { assertion = with cfg.encryption; @@ -760,7 +755,7 @@ in { ++ mapAttrsToList mkSourceAssertions jobs ++ mapAttrsToList mkRemovableDeviceAssertions jobs; - system.activationScripts = mapAttrs' mkActivationScript jobs; + systemd.tmpfiles.settings = mapAttrs' mkTmpfiles jobs; systemd.services = # A job named "foo" is mapped to systemd.services.borgbackup-job-foo diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix index e3eb504e0adf..b222dd952d15 100644 --- a/nixos/modules/services/backup/restic.nix +++ b/nixos/modules/services/backup/restic.nix @@ -384,10 +384,11 @@ in ${lib.optionalString (backup.environmentFile != null) "source ${backup.environmentFile}"} # set same environment variables as the systemd service ${lib.pipe config.systemd.services."restic-backups-${name}".environment [ - (lib.filterAttrs (_: v: v != null)) + (lib.filterAttrs (n: v: v != null && n != "PATH")) (lib.mapAttrsToList (n: v: "${n}=${v}")) (lib.concatStringsSep "\n") ]} + PATH=${config.systemd.services."restic-backups-${name}".environment.PATH}:$PATH exec ${resticCmd} $@ '') (lib.filterAttrs (_: v: v.createWrapper) config.services.restic.backups); diff --git a/nixos/modules/services/backup/snapraid.nix b/nixos/modules/services/backup/snapraid.nix new file mode 100644 index 000000000000..c9b2550e80e8 --- /dev/null +++ b/nixos/modules/services/backup/snapraid.nix @@ -0,0 +1,239 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let cfg = config.services.snapraid; +in +{ + imports = [ + # Should have never been on the top-level. + (mkRenamedOptionModule [ "snapraid" ] [ "services" "snapraid" ]) + ]; + + options.services.snapraid = with types; { + enable = mkEnableOption (lib.mdDoc "SnapRAID"); + dataDisks = mkOption { + default = { }; + example = { + d1 = "/mnt/disk1/"; + d2 = "/mnt/disk2/"; + d3 = "/mnt/disk3/"; + }; + description = lib.mdDoc "SnapRAID data disks."; + type = attrsOf str; + }; + parityFiles = mkOption { + default = [ ]; + example = [ + "/mnt/diskp/snapraid.parity" + "/mnt/diskq/snapraid.2-parity" + "/mnt/diskr/snapraid.3-parity" + "/mnt/disks/snapraid.4-parity" + "/mnt/diskt/snapraid.5-parity" + "/mnt/disku/snapraid.6-parity" + ]; + description = lib.mdDoc "SnapRAID parity files."; + type = listOf str; + }; + contentFiles = mkOption { + default = [ ]; + example = [ + "/var/snapraid.content" + "/mnt/disk1/snapraid.content" + "/mnt/disk2/snapraid.content" + ]; + description = lib.mdDoc "SnapRAID content list files."; + type = listOf str; + }; + exclude = mkOption { + default = [ ]; + example = [ "*.unrecoverable" "/tmp/" "/lost+found/" ]; + description = lib.mdDoc "SnapRAID exclude directives."; + type = listOf str; + }; + touchBeforeSync = mkOption { + default = true; + example = false; + description = lib.mdDoc + "Whether {command}`snapraid touch` should be run before {command}`snapraid sync`."; + type = bool; + }; + sync.interval = mkOption { + default = "01:00"; + example = "daily"; + description = lib.mdDoc "How often to run {command}`snapraid sync`."; + type = str; + }; + scrub = { + interval = mkOption { + default = "Mon *-*-* 02:00:00"; + example = "weekly"; + description = lib.mdDoc "How often to run {command}`snapraid scrub`."; + type = str; + }; + plan = mkOption { + default = 8; + example = 5; + description = lib.mdDoc + "Percent of the array that should be checked by {command}`snapraid scrub`."; + type = int; + }; + olderThan = mkOption { + default = 10; + example = 20; + description = lib.mdDoc + "Number of days since data was last scrubbed before it can be scrubbed again."; + type = int; + }; + }; + extraConfig = mkOption { + default = ""; + example = '' + nohidden + blocksize 256 + hashsize 16 + autosave 500 + pool /pool + ''; + description = lib.mdDoc "Extra config options for SnapRAID."; + type = lines; + }; + }; + + config = + let + nParity = builtins.length cfg.parityFiles; + mkPrepend = pre: s: pre + s; + in + mkIf cfg.enable { + assertions = [ + { + assertion = nParity <= 6; + message = "You can have no more than six SnapRAID parity files."; + } + { + assertion = builtins.length cfg.contentFiles >= nParity + 1; + message = + "There must be at least one SnapRAID content file for each SnapRAID parity file plus one."; + } + ]; + + environment = { + systemPackages = with pkgs; [ snapraid ]; + + etc."snapraid.conf" = { + text = with cfg; + let + prependData = mkPrepend "data "; + prependContent = mkPrepend "content "; + prependExclude = mkPrepend "exclude "; + in + concatStringsSep "\n" + (map prependData + ((mapAttrsToList (name: value: name + " " + value)) dataDisks) + ++ zipListsWith (a: b: a + b) + ([ "parity " ] ++ map (i: toString i + "-parity ") (range 2 6)) + parityFiles ++ map prependContent contentFiles + ++ map prependExclude exclude) + "\n" + extraConfig; + }; + }; + + systemd.services = with cfg; { + snapraid-scrub = { + description = "Scrub the SnapRAID array"; + startAt = scrub.interval; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.snapraid}/bin/snapraid scrub -p ${ + toString scrub.plan + } -o ${toString scrub.olderThan}"; + Nice = 19; + IOSchedulingPriority = 7; + CPUSchedulingPolicy = "batch"; + + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = "none"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = "@system-service"; + SystemCallErrorNumber = "EPERM"; + CapabilityBoundingSet = "CAP_DAC_OVERRIDE"; + + ProtectSystem = "strict"; + ProtectHome = "read-only"; + ReadWritePaths = + # scrub requires access to directories containing content files + # to remove them if they are stale + let + contentDirs = map dirOf contentFiles; + in + unique ( + attrValues dataDisks ++ contentDirs + ); + }; + unitConfig.After = "snapraid-sync.service"; + }; + snapraid-sync = { + description = "Synchronize the state of the SnapRAID array"; + startAt = sync.interval; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${pkgs.snapraid}/bin/snapraid sync"; + Nice = 19; + IOSchedulingPriority = 7; + CPUSchedulingPolicy = "batch"; + + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateTmp = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + RestrictAddressFamilies = "none"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = "@system-service"; + SystemCallErrorNumber = "EPERM"; + CapabilityBoundingSet = "CAP_DAC_OVERRIDE" + + lib.optionalString cfg.touchBeforeSync " CAP_FOWNER"; + + ProtectSystem = "strict"; + ProtectHome = "read-only"; + ReadWritePaths = + # sync requires access to directories containing content files + # to remove them if they are stale + let + contentDirs = map dirOf contentFiles; + # Multiple "split" parity files can be specified in a single + # "parityFile", separated by a comma. + # https://www.snapraid.it/manual#7.1 + splitParityFiles = map (s: splitString "," s) parityFiles; + in + unique ( + attrValues dataDisks ++ splitParityFiles ++ contentDirs + ); + } // optionalAttrs touchBeforeSync { + ExecStartPre = "${pkgs.snapraid}/bin/snapraid touch"; + }; + }; + }; + }; +} diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix index 38682701ea15..35151ebd6bd7 100644 --- a/nixos/modules/services/cluster/kubernetes/pki.nix +++ b/nixos/modules/services/cluster/kubernetes/pki.nix @@ -212,7 +212,7 @@ in services.certmgr = { enable = true; - package = pkgs.certmgr-selfsigned; + package = pkgs.certmgr; svcManager = "command"; specs = let diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix index 56abeda3a5cd..446d19b8fd5a 100644 --- a/nixos/modules/services/continuous-integration/buildbot/master.nix +++ b/nixos/modules/services/continuous-integration/buildbot/master.nix @@ -305,5 +305,5 @@ in { '') ]; - meta.maintainers = with lib.maintainers; [ mic92 lopsided98 ]; + meta.maintainers = lib.teams.buildbot.members; } diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix index 2a836c24dda3..9c7b2bdd06e0 100644 --- a/nixos/modules/services/continuous-integration/buildbot/worker.nix +++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix @@ -188,6 +188,6 @@ in { }; }; - meta.maintainers = with lib.maintainers; [ ]; + meta.maintainers = lib.teams.buildbot.members; } diff --git a/nixos/modules/services/continuous-integration/buildkite-agents.nix b/nixos/modules/services/continuous-integration/buildkite-agents.nix index a35ca4168074..2e488f83d4c3 100644 --- a/nixos/modules/services/continuous-integration/buildkite-agents.nix +++ b/nixos/modules/services/continuous-integration/buildkite-agents.nix @@ -35,6 +35,12 @@ let type = lib.types.str; }; + extraGroups = lib.mkOption { + default = [ "keys" ]; + description = lib.mdDoc "Groups the user for this buildkite agent should belong to"; + type = lib.types.listOf lib.types.str; + }; + runtimePackages = lib.mkOption { default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]; defaultText = lib.literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]"; @@ -150,7 +156,7 @@ in home = cfg.dataDir; createHome = true; description = "Buildkite agent user"; - extraGroups = [ "keys" ]; + extraGroups = cfg.extraGroups; isSystemUser = true; group = "buildkite-agent-${name}"; }; diff --git a/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixos/modules/services/continuous-integration/jenkins/default.nix index e96743784e04..d69cf4587aab 100644 --- a/nixos/modules/services/continuous-integration/jenkins/default.nix +++ b/nixos/modules/services/continuous-integration/jenkins/default.nix @@ -236,6 +236,7 @@ in { serviceConfig = { User = cfg.user; + StateDirectory = mkIf (hasPrefix "/var/lib/jenkins" cfg.home) "jenkins"; }; }; }; diff --git a/nixos/modules/services/databases/aerospike.nix b/nixos/modules/services/databases/aerospike.nix index 373c8f4bffb0..4923c0f00ddb 100644 --- a/nixos/modules/services/databases/aerospike.nix +++ b/nixos/modules/services/databases/aerospike.nix @@ -108,6 +108,11 @@ in }; users.groups.aerospike.gid = config.ids.gids.aerospike; + boot.kernel.sysctl = { + "net.core.rmem_max" = mkDefault 15728640; + "net.core.wmem_max" = mkDefault 5242880; + }; + systemd.services.aerospike = rec { description = "Aerospike server"; @@ -131,14 +136,6 @@ in echo "kernel.shmmax too low, setting to 1GB" ${pkgs.procps}/bin/sysctl -w kernel.shmmax=1073741824 fi - if [ $(echo "$(cat /proc/sys/net/core/rmem_max) < 15728640" | ${pkgs.bc}/bin/bc) == "1" ]; then - echo "increasing socket buffer limit (/proc/sys/net/core/rmem_max): $(cat /proc/sys/net/core/rmem_max) -> 15728640" - echo 15728640 > /proc/sys/net/core/rmem_max - fi - if [ $(echo "$(cat /proc/sys/net/core/wmem_max) < 5242880" | ${pkgs.bc}/bin/bc) == "1" ]; then - echo "increasing socket buffer limit (/proc/sys/net/core/wmem_max): $(cat /proc/sys/net/core/wmem_max) -> 5242880" - echo 5242880 > /proc/sys/net/core/wmem_max - fi install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}" install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/smd" install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/udf" diff --git a/nixos/modules/services/databases/influxdb.nix b/nixos/modules/services/databases/influxdb.nix index 34b4139e7c58..adb212ab08d0 100644 --- a/nixos/modules/services/databases/influxdb.nix +++ b/nixos/modules/services/databases/influxdb.nix @@ -161,6 +161,7 @@ in ExecStart = ''${cfg.package}/bin/influxd -config "${configFile}"''; User = cfg.user; Group = cfg.group; + Restart = "on-failure"; }; postStart = let diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index d0058fd1948b..ed5915735730 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -404,7 +404,7 @@ in default = {}; description = lib.mdDoc '' PostgreSQL configuration. Refer to - <https://www.postgresql.org/docs/15/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE> + <https://www.postgresql.org/docs/current/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE> for an overview of `postgresql.conf`. ::: {.note} diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix index 04ac415c177c..da409030b3a3 100644 --- a/nixos/modules/services/desktops/pipewire/pipewire.nix +++ b/nixos/modules/services/desktops/pipewire/pipewire.nix @@ -4,6 +4,8 @@ with lib; let + json = pkgs.formats.json {}; + mapToFiles = location: config: concatMapAttrs (name: value: { "pipewire/${location}.conf.d/${name}.conf".source = json.generate "${name}" value;}) config; cfg = config.services.pipewire; enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 @@ -72,15 +74,140 @@ in { https://github.com/PipeWire/pipewire/blob/master/NEWS ''; }; + + extraConfig = { + pipewire = mkOption { + type = lib.types.attrsOf json.type; + default = {}; + example = { + "10-clock-rate" = { + "context.properties" = { + "default.clock.rate" = 44100; + }; + }; + "11-no-upmixing" = { + "stream.properties" = { + "channelmix.upmix" = false; + }; + }; + }; + description = lib.mdDoc '' + Additional configuration for the PipeWire server. + + Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/pipewire.conf.d`. + + See `man pipewire.conf` for details, and [the PipeWire wiki][wiki] for examples. + + See also: + - [PipeWire wiki - virtual devices][wiki-virtual-device] for creating virtual devices or remapping channels + - [PipeWire wiki - filter-chain][wiki-filter-chain] for creating more complex processing pipelines + - [PipeWire wiki - network][wiki-network] for streaming audio over a network + + [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PipeWire + [wiki-virtual-device]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices + [wiki-filter-chain]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Filter-Chain + [wiki-network]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Network + ''; + }; + client = mkOption { + type = lib.types.attrsOf json.type; + default = {}; + example = { + "10-no-resample" = { + "stream.properties" = { + "resample.disable" = true; + }; + }; + }; + description = lib.mdDoc '' + Additional configuration for the PipeWire client library, used by most applications. + + Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/client.conf.d`. + + See the [PipeWire wiki][wiki] for examples. + + [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-client + ''; + }; + client-rt = mkOption { + type = lib.types.attrsOf json.type; + default = {}; + example = { + "10-alsa-linear-volume" = { + "alsa.properties" = { + "alsa.volume-method" = "linear"; + }; + }; + }; + description = lib.mdDoc '' + Additional configuration for the PipeWire client library, used by real-time applications and legacy ALSA clients. + + Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/client-rt.conf.d`. + + See the [PipeWire wiki][wiki] for examples of general configuration, and [PipeWire wiki - ALSA][wiki-alsa] for ALSA clients. + + [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-client + [wiki-alsa]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-ALSA + ''; + }; + jack = mkOption { + type = lib.types.attrsOf json.type; + default = {}; + example = { + "20-hide-midi" = { + "jack.properties" = { + "jack.show-midi" = false; + }; + }; + }; + description = lib.mdDoc '' + Additional configuration for the PipeWire JACK server and client library. + + Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/jack.conf.d`. + + See the [PipeWire wiki][wiki] for examples. + + [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-JACK + ''; + }; + pipewire-pulse = mkOption { + type = lib.types.attrsOf json.type; + default = {}; + example = { + "15-force-s16-info" = { + "pulse.rules" = [{ + matches = [ + { "application.process.binary" = "my-broken-app"; } + ]; + actions = { + quirks = [ "force-s16-info" ]; + }; + }]; + }; + }; + description = lib.mdDoc '' + Additional configuration for the PipeWire PulseAudio server. + + Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/pipewire-pulse.conf.d`. + + See `man pipewire-pulse.conf` for details, and [the PipeWire wiki][wiki] for examples. + + See also: + - [PipeWire wiki - PulseAudio tricks guide][wiki-tricks] for more examples. + + [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PulseAudio + [wiki-tricks]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Guide-PulseAudio-Tricks + ''; + }; + }; }; }; imports = [ (lib.mkRemovedOptionModule ["services" "pipewire" "config"] '' - Overriding default Pipewire configuration through NixOS options never worked correctly and is no longer supported. - Please create drop-in files in /etc/pipewire/pipewire.conf.d/ to make the desired setting changes instead. + Overriding default PipeWire configuration through NixOS options never worked correctly and is no longer supported. + Please create drop-in configuration files via `services.pipewire.extraConfig` instead. '') - (lib.mkRemovedOptionModule ["services" "pipewire" "media-session"] '' pipewire-media-session is no longer supported upstream and has been removed. Please switch to `services.pipewire.wireplumber` instead. @@ -133,26 +260,35 @@ in { services.udev.packages = [ cfg.package ]; # If any paths are updated here they must also be updated in the package test. - environment.etc."alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable { - text = '' - pcm_type.pipewire { - libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_pcm_pipewire.so ; - ${optionalString enable32BitAlsaPlugins - "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"} - } - ctl_type.pipewire { - libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_ctl_pipewire.so ; - ${optionalString enable32BitAlsaPlugins - "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"} - } - ''; - }; - environment.etc."alsa/conf.d/50-pipewire.conf" = mkIf cfg.alsa.enable { - source = "${cfg.package}/share/alsa/alsa.conf.d/50-pipewire.conf"; - }; - environment.etc."alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable { - source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf"; - }; + environment.etc = { + "alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable { + text = '' + pcm_type.pipewire { + libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_pcm_pipewire.so ; + ${optionalString enable32BitAlsaPlugins + "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"} + } + ctl_type.pipewire { + libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_ctl_pipewire.so ; + ${optionalString enable32BitAlsaPlugins + "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"} + } + ''; + }; + + "alsa/conf.d/50-pipewire.conf" = mkIf cfg.alsa.enable { + source = "${cfg.package}/share/alsa/alsa.conf.d/50-pipewire.conf"; + }; + + "alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable { + source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf"; + }; + } + // mapToFiles "pipewire" cfg.extraConfig.pipewire + // mapToFiles "client" cfg.extraConfig.client + // mapToFiles "client-rt" cfg.extraConfig.client-rt + // mapToFiles "jack" cfg.extraConfig.jack + // mapToFiles "pipewire-pulse" cfg.extraConfig.pipewire-pulse; environment.sessionVariables.LD_LIBRARY_PATH = lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ]; diff --git a/nixos/modules/services/development/livebook.md b/nixos/modules/services/development/livebook.md index 73ddc57f6179..5012e977a4f7 100644 --- a/nixos/modules/services/development/livebook.md +++ b/nixos/modules/services/development/livebook.md @@ -18,7 +18,7 @@ which runs the server. port = 20123; # See note below about security environmentFile = pkgs.writeText "livebook.env" '' - LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ''; }; } @@ -37,3 +37,14 @@ A better approach would be to put the password in some secure user-readable location and set `environmentFile = /home/user/secure/livebook.env`. ::: + +### Extra dependencies {#module-services-livebook-extra-dependencies} + +By default, the Livebook service is run with minimum dependencies, but +some features require additional packages. For example, the machine +learning Kinos require `gcc` and `gnumake`. To add these, use +`extraPackages`: + +``` +services.livebook.extraPackages = with pkgs; [ gcc gnumake ]; +``` diff --git a/nixos/modules/services/development/livebook.nix b/nixos/modules/services/development/livebook.nix index 3991a4125ec3..75729ff28efa 100644 --- a/nixos/modules/services/development/livebook.nix +++ b/nixos/modules/services/development/livebook.nix @@ -12,6 +12,8 @@ in # future, this can be changed to a system service. enableUserService = mkEnableOption "a user service for Livebook"; + package = mkPackageOption pkgs "livebook" { }; + environmentFile = mkOption { type = types.path; description = lib.mdDoc '' @@ -63,6 +65,15 @@ in } ''; }; + + extraPackages = mkOption { + type = with types; listOf package; + default = [ ]; + description = lib.mdDoc '' + Extra packages to make available to the Livebook service. + ''; + example = literalExpression "with pkgs; [ gcc gnumake ]"; + }; }; config = mkIf cfg.enableUserService { @@ -79,9 +90,9 @@ in sname = cfg.erlang_node_short_name; } // cfg.options); in - "${pkgs.livebook}/bin/livebook server ${args}"; + "${cfg.package}/bin/livebook server ${args}"; }; - path = [ pkgs.bash ]; + path = [ pkgs.bash ] ++ cfg.extraPackages; wantedBy = [ "default.target" ]; }; }; diff --git a/nixos/modules/services/development/nixseparatedebuginfod.nix b/nixos/modules/services/development/nixseparatedebuginfod.nix new file mode 100644 index 000000000000..daf85153d339 --- /dev/null +++ b/nixos/modules/services/development/nixseparatedebuginfod.nix @@ -0,0 +1,105 @@ +{ pkgs, lib, config, ... }: +let + cfg = config.services.nixseparatedebuginfod; + url = "127.0.0.1:${toString cfg.port}"; +in +{ + options = { + services.nixseparatedebuginfod = { + enable = lib.mkEnableOption "separatedebuginfod, a debuginfod server providing source and debuginfo for nix packages"; + port = lib.mkOption { + description = "port to listen"; + default = 1949; + type = lib.types.port; + }; + nixPackage = lib.mkOption { + type = lib.types.package; + default = pkgs.nix; + defaultText = lib.literalExpression "pkgs.nix"; + description = '' + The version of nix that nixseparatedebuginfod should use as client for the nix daemon. It is strongly advised to use nix version >= 2.18, otherwise some debug info may go missing. + ''; + }; + allowOldNix = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Do not fail evaluation when {option}`services.nixseparatedebuginfod.nixPackage` is older than nix 2.18. + ''; + }; + }; + }; + config = lib.mkIf cfg.enable { + assertions = [ { + assertion = cfg.allowOldNix || (lib.versionAtLeast cfg.nixPackage.version "2.18"); + message = "nixseparatedebuginfod works better when `services.nixseparatedebuginfod.nixPackage` is set to nix >= 2.18 (instead of ${cfg.nixPackage.name}). Set `services.nixseparatedebuginfod.allowOldNix` to bypass."; + } ]; + + systemd.services.nixseparatedebuginfod = { + wantedBy = [ "multi-user.target" ]; + wants = [ "nix-daemon.service" ]; + after = [ "nix-daemon.service" ]; + path = [ cfg.nixPackage ]; + serviceConfig = { + ExecStart = [ "${pkgs.nixseparatedebuginfod}/bin/nixseparatedebuginfod -l ${url}" ]; + Restart = "on-failure"; + CacheDirectory = "nixseparatedebuginfod"; + # nix does not like DynamicUsers in allowed-users + User = "nixseparatedebuginfod"; + Group = "nixseparatedebuginfod"; + + # hardening + # Filesystem stuff + ProtectSystem = "strict"; # Prevent writing to most of / + ProtectHome = true; # Prevent accessing /home and /root + PrivateTmp = true; # Give an own directory under /tmp + PrivateDevices = true; # Deny access to most of /dev + ProtectKernelTunables = true; # Protect some parts of /sys + ProtectControlGroups = true; # Remount cgroups read-only + RestrictSUIDSGID = true; # Prevent creating SETUID/SETGID files + PrivateMounts = true; # Give an own mount namespace + RemoveIPC = true; + UMask = "0077"; + + # Capabilities + CapabilityBoundingSet = ""; # Allow no capabilities at all + NoNewPrivileges = true; # Disallow getting more capabilities. This is also implied by other options. + + # Kernel stuff + ProtectKernelModules = true; # Prevent loading of kernel modules + SystemCallArchitectures = "native"; # Usually no need to disable this + ProtectKernelLogs = true; # Prevent access to kernel logs + ProtectClock = true; # Prevent setting the RTC + + # Networking + RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; + + # Misc + LockPersonality = true; # Prevent change of the personality + ProtectHostname = true; # Give an own UTS namespace + RestrictRealtime = true; # Prevent switching to RT scheduling + MemoryDenyWriteExecute = true; # Maybe disable this for interpreters like python + RestrictNamespaces = true; + }; + }; + + users.users.nixseparatedebuginfod = { + isSystemUser = true; + group = "nixseparatedebuginfod"; + }; + + users.groups.nixseparatedebuginfod = { }; + + nix.settings.extra-allowed-users = [ "nixseparatedebuginfod" ]; + + environment.variables.DEBUGINFOD_URLS = "http://${url}"; + + environment.systemPackages = [ + # valgrind support requires debuginfod-find on PATH + (lib.getBin pkgs.elfutils) + ]; + + environment.etc."gdb/gdbinit.d/nixseparatedebuginfod.gdb".text = "set debuginfod enabled on"; + + }; +} diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix index 0b77bfbc33b3..05e76d843215 100644 --- a/nixos/modules/services/hardware/kanata.nix +++ b/nixos/modules/services/hardware/kanata.nix @@ -78,7 +78,13 @@ let mkName = name: "kanata-${name}"; mkDevices = devices: - optionalString ((length devices) > 0) "linux-dev ${concatStringsSep ":" devices}"; + let + devicesString = pipe devices [ + (map (device: "\"" + device + "\"")) + (concatStringsSep " ") + ]; + in + optionalString ((length devices) > 0) "linux-dev (${devicesString})"; mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" '' (defcfg diff --git a/nixos/modules/services/hardware/keyd.nix b/nixos/modules/services/hardware/keyd.nix index 724e9b956847..77297401a51c 100644 --- a/nixos/modules/services/hardware/keyd.nix +++ b/nixos/modules/services/hardware/keyd.nix @@ -143,7 +143,7 @@ in RuntimeDirectory = "keyd"; # Hardening - CapabilityBoundingSet = ""; + CapabilityBoundingSet = [ "CAP_SYS_NICE" ]; DeviceAllow = [ "char-input rw" "/dev/uinput rw" @@ -152,7 +152,7 @@ in PrivateNetwork = true; ProtectHome = true; ProtectHostname = true; - PrivateUsers = true; + PrivateUsers = false; PrivateMounts = true; PrivateTmp = true; RestrictNamespaces = true; @@ -165,9 +165,9 @@ in LockPersonality = true; ProtectProc = "invisible"; SystemCallFilter = [ + "nice" "@system-service" "~@privileged" - "~@resources" ]; RestrictAddressFamilies = [ "AF_UNIX" ]; RestrictSUIDSGID = true; diff --git a/nixos/modules/services/hardware/pcscd.nix b/nixos/modules/services/hardware/pcscd.nix index a9e4998efe37..b0a493c23899 100644 --- a/nixos/modules/services/hardware/pcscd.nix +++ b/nixos/modules/services/hardware/pcscd.nix @@ -16,9 +16,6 @@ let in { - - ###### interface - options.services.pcscd = { enable = mkEnableOption (lib.mdDoc "PCSC-Lite daemon"); @@ -46,10 +43,7 @@ in }; }; - ###### implementation - config = mkIf config.services.pcscd.enable { - environment.etc."reader.conf".source = cfgFile; environment.systemPackages = [ package ]; @@ -61,7 +55,6 @@ in systemd.services.pcscd = { environment.PCSCLITE_HP_DROPDIR = pluginEnv; - restartTriggers = [ "/etc/reader.conf" ]; # If the cfgFile is empty and not specified (in which case the default # /etc/reader.conf is assumed), pcscd will happily start going through the diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix index 8408844c4f94..8f64afe60734 100644 --- a/nixos/modules/services/hardware/sane.nix +++ b/nixos/modules/services/hardware/sane.nix @@ -4,7 +4,7 @@ with lib; let - pkg = pkgs.sane-backends.override { + pkg = config.hardware.sane.backends-package.override { scanSnapDriversUnfree = config.hardware.sane.drivers.scanSnap.enable; scanSnapDriversPackage = config.hardware.sane.drivers.scanSnap.package; }; @@ -57,6 +57,13 @@ in ''; }; + hardware.sane.backends-package = mkOption { + type = types.package; + default = pkgs.sane-backends; + defaultText = literalExpression "pkgs.sane-backends"; + description = lib.mdDoc "Backends driver package to use."; + }; + hardware.sane.snapshot = mkOption { type = types.bool; default = false; diff --git a/nixos/modules/services/hardware/thermald.nix b/nixos/modules/services/hardware/thermald.nix index 7ae602823cd6..a4839f326cc4 100644 --- a/nixos/modules/services/hardware/thermald.nix +++ b/nixos/modules/services/hardware/thermald.nix @@ -19,6 +19,12 @@ in ''; }; + ignoreCpuidCheck = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Whether to ignore the cpuid check to allow running on unsupported platforms"; + }; + configFile = mkOption { type = types.nullOr types.path; default = null; @@ -42,6 +48,7 @@ in ${cfg.package}/sbin/thermald \ --no-daemon \ ${optionalString cfg.debug "--loglevel=debug"} \ + ${optionalString cfg.ignoreCpuidCheck "--ignore-cpuid-check"} \ ${optionalString (cfg.configFile != null) "--config-file ${cfg.configFile}"} \ --dbus-enable \ --adaptive diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix index 08ca7a0d247d..670b9087f110 100644 --- a/nixos/modules/services/hardware/udev.nix +++ b/nixos/modules/services/hardware/udev.nix @@ -112,7 +112,8 @@ let echo "OK" filesToFixup="$(for i in "$out"/*; do - grep -l '\B\(/usr\)\?/s\?bin' "$i" || : + # list all files referring to (/usr)/bin paths, but allow references to /bin/sh. + grep -P -l '\B(?!\/bin\/sh\b)(\/usr)?\/bin(?:\/.*)?' "$i" || : done)" if [ -n "$filesToFixup" ]; then @@ -222,6 +223,9 @@ in description = lib.mdDoc '' Packages added to the {env}`PATH` environment variable when executing programs from Udev rules. + + coreutils, gnu{sed,grep}, util-linux and config.systemd.package are + automatically included. ''; }; diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix index afa64fa16c4a..689d83f7eedc 100644 --- a/nixos/modules/services/hardware/vdr.nix +++ b/nixos/modules/services/hardware/vdr.nix @@ -1,18 +1,15 @@ { config, lib, pkgs, ... }: - -with lib; - let cfg = config.services.vdr; - libDir = "/var/lib/vdr"; -in { - - ###### interface + inherit (lib) + mkEnableOption mkPackageOption mkOption types mkIf optional mdDoc; +in +{ options = { services.vdr = { - enable = mkEnableOption (lib.mdDoc "VDR. Please put config into ${libDir}"); + enable = mkEnableOption (mdDoc "Start VDR"); package = mkPackageOption pkgs "vdr" { example = "wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }"; @@ -21,58 +18,84 @@ in { videoDir = mkOption { type = types.path; default = "/srv/vdr/video"; - description = lib.mdDoc "Recording directory"; + description = mdDoc "Recording directory"; }; extraArguments = mkOption { type = types.listOf types.str; - default = []; - description = lib.mdDoc "Additional command line arguments to pass to VDR."; + default = [ ]; + description = mdDoc "Additional command line arguments to pass to VDR."; + }; + + enableLirc = mkEnableOption (mdDoc "LIRC"); + + user = mkOption { + type = types.str; + default = "vdr"; + description = mdDoc '' + User under which the VDR service runs. + ''; }; - enableLirc = mkEnableOption (lib.mdDoc "LIRC"); + group = mkOption { + type = types.str; + default = "vdr"; + description = mdDoc '' + Group under which the VDRvdr service runs. + ''; + }; }; + }; - ###### implementation + config = mkIf cfg.enable { - config = mkIf cfg.enable (mkMerge [{ systemd.tmpfiles.rules = [ - "d ${cfg.videoDir} 0755 vdr vdr -" - "Z ${cfg.videoDir} - vdr vdr -" + "d ${cfg.videoDir} 0755 ${cfg.user} ${cfg.group} -" + "Z ${cfg.videoDir} - ${cfg.user} ${cfg.group} -" ]; systemd.services.vdr = { description = "VDR"; wantedBy = [ "multi-user.target" ]; + wants = optional cfg.enableLirc "lircd.service"; + after = [ "network.target" ] + ++ optional cfg.enableLirc "lircd.service"; serviceConfig = { - ExecStart = '' - ${cfg.package}/bin/vdr \ - --video="${cfg.videoDir}" \ - --config="${libDir}" \ - ${escapeShellArgs cfg.extraArguments} - ''; - User = "vdr"; + ExecStart = + let + args = [ + "--video=${cfg.videoDir}" + ] + ++ optional cfg.enableLirc "--lirc=${config.passthru.lirc.socket}" + ++ cfg.extraArguments; + in + "${cfg.package}/bin/vdr ${lib.escapeShellArgs args}"; + User = cfg.user; + Group = cfg.group; CacheDirectory = "vdr"; StateDirectory = "vdr"; + RuntimeDirectory = "vdr"; Restart = "on-failure"; }; }; - users.users.vdr = { - group = "vdr"; - home = libDir; - isSystemUser = true; + environment.systemPackages = [ cfg.package ]; + + users.users = mkIf (cfg.user == "vdr") { + vdr = { + inherit (cfg) group; + home = "/run/vdr"; + isSystemUser = true; + extraGroups = [ + "video" + "audio" + ] + ++ optional cfg.enableLirc "lirc"; + }; }; - users.groups.vdr = {}; - } + users.groups = mkIf (cfg.group == "vdr") { vdr = { }; }; - (mkIf cfg.enableLirc { - services.lirc.enable = true; - users.users.vdr.extraGroups = [ "lirc" ]; - services.vdr.extraArguments = [ - "--lirc=${config.passthru.lirc.socket}" - ]; - })]); + }; } diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix index 2a6b07c6f1a6..bc470576b759 100644 --- a/nixos/modules/services/home-automation/home-assistant.nix +++ b/nixos/modules/services/home-automation/home-assistant.nix @@ -468,8 +468,8 @@ in { mkdir -p "${cfg.configDir}/custom_components" # remove components symlinked in from below the /nix/store - components="$(find "${cfg.configDir}/custom_components" -maxdepth 1 -type l)" - for component in "$components"; do + readarray -d "" components < <(find "${cfg.configDir}/custom_components" -maxdepth 1 -type l -print0) + for component in "''${components[@]}"; do if [[ "$(readlink "$component")" =~ ^${escapeShellArg builtins.storeDir} ]]; then rm "$component" fi diff --git a/nixos/modules/services/logging/logcheck.nix b/nixos/modules/services/logging/logcheck.nix index 8a277cea6e46..5d87fc87d416 100644 --- a/nixos/modules/services/logging/logcheck.nix +++ b/nixos/modules/services/logging/logcheck.nix @@ -220,10 +220,16 @@ in logcheck = {}; }; - system.activationScripts.logcheck = '' - mkdir -m 700 -p /var/{lib,lock}/logcheck - chown ${cfg.user} /var/{lib,lock}/logcheck - ''; + systemd.tmpfiles.settings.logcheck = { + "/var/lib/logcheck".d = { + mode = "700"; + inherit (cfg) user; + }; + "/var/lock/logcheck".d = { + mode = "700"; + inherit (cfg) user; + }; + }; services.cron.systemCronJobs = let withTime = name: {timeArgs, ...}: timeArgs != null; diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix index abbb2f32e6cc..25c7017a1d25 100644 --- a/nixos/modules/services/mail/dovecot.nix +++ b/nixos/modules/services/mail/dovecot.nix @@ -1,8 +1,11 @@ { options, config, lib, pkgs, ... }: -with lib; - let + inherit (lib) any attrValues concatMapStringsSep concatStrings + concatStringsSep flatten imap1 isList literalExpression mapAttrsToList + mkEnableOption mkIf mkOption mkRemovedOptionModule optional optionalAttrs + optionalString singleton types; + cfg = config.services.dovecot2; dovecotPkg = pkgs.dovecot; @@ -113,6 +116,36 @@ let '' ) + '' + plugin { + sieve_plugins = ${concatStringsSep " " cfg.sieve.plugins} + sieve_extensions = ${concatStringsSep " " (map (el: "+${el}") cfg.sieve.extensions)} + sieve_global_extensions = ${concatStringsSep " " (map (el: "+${el}") cfg.sieve.globalExtensions)} + '' + (optionalString (cfg.imapsieve.mailbox != []) '' + ${ + concatStringsSep "\n" (flatten (imap1 ( + idx: el: + singleton "imapsieve_mailbox${toString idx}_name = ${el.name}" + ++ optional (el.from != null) "imapsieve_mailbox${toString idx}_from = ${el.from}" + ++ optional (el.causes != null) "imapsieve_mailbox${toString idx}_causes = ${el.causes}" + ++ optional (el.before != null) "imapsieve_mailbox${toString idx}_before = file:${stateDir}/imapsieve/before/${baseNameOf el.before}" + ++ optional (el.after != null) "imapsieve_mailbox${toString idx}_after = file:${stateDir}/imapsieve/after/${baseNameOf el.after}" + ) + cfg.imapsieve.mailbox)) + } + '') + (optionalString (cfg.sieve.pipeBins != []) '' + sieve_pipe_bin_dir = ${pkgs.linkFarm "sieve-pipe-bins" (map (el: { + name = builtins.unsafeDiscardStringContext (baseNameOf el); + path = el; + }) + cfg.sieve.pipeBins)} + '') + '' + } + '' + cfg.extraConfig ]; @@ -343,6 +376,104 @@ in description = lib.mdDoc "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %."; }; + imapsieve.mailbox = mkOption { + default = []; + description = "Configure Sieve filtering rules on IMAP actions"; + type = types.listOf (types.submodule ({ config, ... }: { + options = { + name = mkOption { + description = '' + This setting configures the name of a mailbox for which administrator scripts are configured. + + The settings defined hereafter with matching sequence numbers apply to the mailbox named by this setting. + + This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes. + ''; + example = "Junk"; + type = types.str; + }; + + from = mkOption { + default = null; + description = '' + Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot2.imapsieve.mailbox.<name>.name when the message originates from the indicated mailbox. + + This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes. + ''; + example = "*"; + type = types.nullOr types.str; + }; + + causes = mkOption { + default = null; + description = '' + Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot2.imapsieve.mailbox.<name>.name when one of the listed IMAPSIEVE causes apply. + + This has no effect on the user script, which is always executed no matter the cause. + ''; + example = "COPY"; + type = types.nullOr (types.enum [ "APPEND" "COPY" "FLAG" ]); + }; + + before = mkOption { + default = null; + description = '' + When an IMAP event of interest occurs, this sieve script is executed before any user script respectively. + + This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_before: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed. + ''; + example = literalExpression "./report-spam.sieve"; + type = types.nullOr types.path; + }; + + after = mkOption { + default = null; + description = '' + When an IMAP event of interest occurs, this sieve script is executed after any user script respectively. + + This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_after: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed. + ''; + example = literalExpression "./report-spam.sieve"; + type = types.nullOr types.path; + }; + }; + })); + }; + + sieve = { + plugins = mkOption { + default = []; + example = [ "sieve_extprograms" ]; + description = "Sieve plugins to load"; + type = types.listOf types.str; + }; + + extensions = mkOption { + default = []; + description = "Sieve extensions for use in user scripts"; + example = [ "notify" "imapflags" "vnd.dovecot.filter" ]; + type = types.listOf types.str; + }; + + globalExtensions = mkOption { + default = []; + example = [ "vnd.dovecot.environment" ]; + description = "Sieve extensions for use in global scripts"; + type = types.listOf types.str; + }; + + pipeBins = mkOption { + default = []; + example = literalExpression '' + map lib.getExe [ + (pkgs.writeShellScriptBin "learn-ham.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_ham") + (pkgs.writeShellScriptBin "learn-spam.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_spam") + ] + ''; + description = "Programs available for use by the vnd.dovecot.pipe extension"; + type = types.listOf types.path; + }; + }; }; @@ -353,14 +484,23 @@ in enable = true; params.dovecot2 = {}; }; - services.dovecot2.protocols = - optional cfg.enableImap "imap" - ++ optional cfg.enablePop3 "pop3" - ++ optional cfg.enableLmtp "lmtp"; - - services.dovecot2.mailPlugins = mkIf cfg.enableQuota { - globally.enable = [ "quota" ]; - perProtocol.imap.enable = [ "imap_quota" ]; + + services.dovecot2 = { + protocols = + optional cfg.enableImap "imap" + ++ optional cfg.enablePop3 "pop3" + ++ optional cfg.enableLmtp "lmtp"; + + mailPlugins = mkIf cfg.enableQuota { + globally.enable = [ "quota" ]; + perProtocol.imap.enable = [ "imap_quota" ]; + }; + + sieve.plugins = + optional (cfg.imapsieve.mailbox != []) "sieve_imapsieve" + ++ optional (cfg.sieve.pipeBins != []) "sieve_extprograms"; + + sieve.globalExtensions = optional (cfg.sieve.pipeBins != []) "vnd.dovecot.pipe"; }; users.users = { @@ -415,7 +555,7 @@ in # (should be 0) so that the compiled sieve script is newer than # the source file and Dovecot won't try to compile it. preStart = '' - rm -rf ${stateDir}/sieve + rm -rf ${stateDir}/sieve ${stateDir}/imapsieve '' + optionalString (cfg.sieveScripts != {}) '' mkdir -p ${stateDir}/sieve ${concatStringsSep "\n" ( @@ -432,6 +572,29 @@ in ) cfg.sieveScripts )} chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve' + '' + + optionalString (cfg.imapsieve.mailbox != []) '' + mkdir -p ${stateDir}/imapsieve/{before,after} + + ${ + concatMapStringsSep "\n" + (el: + optionalString (el.before != null) '' + cp -p ${el.before} ${stateDir}/imapsieve/before/${baseNameOf el.before} + ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/before/${baseNameOf el.before}' + '' + + optionalString (el.after != null) '' + cp -p ${el.after} ${stateDir}/imapsieve/after/${baseNameOf el.after} + ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/after/${baseNameOf el.after}' + '' + ) + cfg.imapsieve.mailbox + } + + ${ + optionalString (cfg.mailUser != null && cfg.mailGroup != null) + "chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/imapsieve'" + } ''; }; @@ -459,4 +622,5 @@ in }; + meta.maintainers = [ lib.maintainers.dblsaiko ]; } diff --git a/nixos/modules/services/mail/nullmailer.nix b/nixos/modules/services/mail/nullmailer.nix index f6befe246b12..4fd0026dbe4e 100644 --- a/nixos/modules/services/mail/nullmailer.nix +++ b/nixos/modules/services/mail/nullmailer.nix @@ -120,7 +120,7 @@ with lib; }; maxpause = mkOption { - type = types.nullOr types.str; + type = with types; nullOr (oneOf [ str int ]); default = null; description = lib.mdDoc '' The maximum time to pause between successive queue runs, in seconds. @@ -138,7 +138,7 @@ with lib; }; pausetime = mkOption { - type = types.nullOr types.str; + type = with types; nullOr (oneOf [ str int ]); default = null; description = lib.mdDoc '' The minimum time to pause between successive queue runs when there @@ -168,7 +168,7 @@ with lib; }; sendtimeout = mkOption { - type = types.nullOr types.str; + type = with types; nullOr (oneOf [ str int ]); default = null; description = lib.mdDoc '' The time to wait for a remote module listed above to complete sending @@ -194,7 +194,7 @@ with lib; environment = { systemPackages = [ pkgs.nullmailer ]; etc = let - validAttrs = filterAttrs (name: value: value != null) cfg.config; + validAttrs = lib.mapAttrs (_: toString) (filterAttrs (_: value: value != null) cfg.config); in (foldl' (as: name: as // { "nullmailer/${name}".text = validAttrs.${name}; }) {} (attrNames validAttrs)) // optionalAttrs (cfg.remotesFile != null) { "nullmailer/remotes".source = cfg.remotesFile; }; diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix index 23c47aaca7e2..209e066a19ef 100644 --- a/nixos/modules/services/mail/postfix.nix +++ b/nixos/modules/services/mail/postfix.nix @@ -747,7 +747,7 @@ in ${concatStringsSep "\n" (mapAttrsToList (to: from: '' ln -sf ${from} /var/lib/postfix/conf/${to} - ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to} + ${pkgs.postfix}/bin/postalias -o -p /var/lib/postfix/conf/${to} '') cfg.aliasFiles)} ${concatStringsSep "\n" (mapAttrsToList (to: from: '' ln -sf ${from} /var/lib/postfix/conf/${to} @@ -779,6 +779,19 @@ in ExecStart = "${pkgs.postfix}/bin/postfix start"; ExecStop = "${pkgs.postfix}/bin/postfix stop"; ExecReload = "${pkgs.postfix}/bin/postfix reload"; + + # Hardening + PrivateTmp = true; + PrivateDevices = true; + ProtectSystem = "full"; + CapabilityBoundingSet = [ "~CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_MODULE" ]; + MemoryDenyWriteExecute = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ]; + RestrictNamespaces = true; + RestrictRealtime = true; }; }; diff --git a/nixos/modules/services/mail/rspamd-trainer.nix b/nixos/modules/services/mail/rspamd-trainer.nix new file mode 100644 index 000000000000..bb78ddf9dd47 --- /dev/null +++ b/nixos/modules/services/mail/rspamd-trainer.nix @@ -0,0 +1,76 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.rspamd-trainer; + format = pkgs.formats.toml { }; + +in { + options.services.rspamd-trainer = { + + enable = mkEnableOption (mdDoc "Spam/ham trainer for rspamd"); + + settings = mkOption { + default = { }; + description = mdDoc '' + IMAP authentication configuration for rspamd-trainer. For supplying + the IMAP password, use the `secrets` option. + ''; + type = types.submodule { + freeformType = format.type; + }; + example = literalExpression '' + { + HOST = "localhost"; + USERNAME = "spam@example.com"; + INBOXPREFIX = "INBOX/"; + } + ''; + }; + + secrets = lib.mkOption { + type = with types; listOf path; + description = lib.mdDoc '' + A list of files containing the various secrets. Should be in the + format expected by systemd's `EnvironmentFile` directory. For the + IMAP account password use `PASSWORD = mypassword`. + ''; + default = [ ]; + }; + + }; + + config = mkIf cfg.enable { + + systemd = { + services.rspamd-trainer = { + description = "Spam/ham trainer for rspamd"; + serviceConfig = { + ExecStart = "${pkgs.rspamd-trainer}/bin/rspamd-trainer"; + WorkingDirectory = "/var/lib/rspamd-trainer"; + StateDirectory = [ "rspamd-trainer/log" ]; + Type = "oneshot"; + DynamicUser = true; + EnvironmentFile = [ + ( format.generate "rspamd-trainer-env" cfg.settings ) + cfg.secrets + ]; + }; + }; + timers."rspamd-trainer" = { + wantedBy = [ "timers.target" ]; + timerConfig = { + OnBootSec = "10m"; + OnUnitActiveSec = "10m"; + Unit = "rspamd-trainer.service"; + }; + }; + }; + + }; + + meta.maintainers = with lib.maintainers; [ onny ]; + +} diff --git a/nixos/modules/services/matrix/matrix-sliding-sync.nix b/nixos/modules/services/matrix/matrix-sliding-sync.nix index 295be0c6bf16..8b22cd7dba80 100644 --- a/nixos/modules/services/matrix/matrix-sliding-sync.nix +++ b/nixos/modules/services/matrix/matrix-sliding-sync.nix @@ -1,10 +1,14 @@ { config, lib, pkgs, ... }: let - cfg = config.services.matrix-synapse.sliding-sync; + cfg = config.services.matrix-sliding-sync; in { - options.services.matrix-synapse.sliding-sync = { + imports = [ + (lib.mkRenamedOptionModule [ "services" "matrix-synapse" "sliding-sync" ] [ "services" "matrix-sliding-sync" ]) + ]; + + options.services.matrix-sliding-sync = { enable = lib.mkEnableOption (lib.mdDoc "sliding sync"); package = lib.mkPackageOption pkgs "matrix-sliding-sync" { }; @@ -83,6 +87,7 @@ in systemd.services.matrix-sliding-sync = rec { after = lib.optional cfg.createDatabase "postgresql.service" + ++ lib.optional config.services.dendrite.enable "dendrite.service" ++ lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit; wants = after; wantedBy = [ "multi-user.target" ]; diff --git a/nixos/modules/services/matrix/synapse.md b/nixos/modules/services/matrix/synapse.md index 58be24204fcf..f270be8c8d78 100644 --- a/nixos/modules/services/matrix/synapse.md +++ b/nixos/modules/services/matrix/synapse.md @@ -16,13 +16,13 @@ around Matrix. ## Synapse Homeserver {#module-services-matrix-synapse} -[Synapse](https://github.com/matrix-org/synapse) is +[Synapse](https://github.com/element-hq/synapse) is the reference homeserver implementation of Matrix from the core development team at matrix.org. The following configuration example will set up a synapse server for the `example.org` domain, served from the host `myhostname.example.org`. For more information, please refer to the -[installation instructions of Synapse](https://matrix-org.github.io/synapse/latest/setup/installation.html) . +[installation instructions of Synapse](https://element-hq.github.io/synapse/latest/setup/installation.html) . ``` { pkgs, lib, config, ... }: let @@ -70,7 +70,7 @@ in { # the domain (i.e. example.org from @foo:example.org) and the federation port # is 8448. # Further reference can be found in the docs about delegation under - # https://matrix-org.github.io/synapse/latest/delegate.html + # https://element-hq.github.io/synapse/latest/delegate.html locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig; # This is usually needed for homeserver discovery (from e.g. other Matrix clients). # Further reference can be found in the upstream docs at @@ -169,7 +169,7 @@ in an additional file like this: ::: {.note} It's also possible to user alternative authentication mechanism such as [LDAP (via `matrix-synapse-ldap3`)](https://github.com/matrix-org/matrix-synapse-ldap3) -or [OpenID](https://matrix-org.github.io/synapse/latest/openid.html). +or [OpenID](https://element-hq.github.io/synapse/latest/openid.html). ::: ## Element (formerly known as Riot) Web Client {#module-services-matrix-element-web} diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix index 9cc769c2d0db..50019d2a25cb 100644 --- a/nixos/modules/services/matrix/synapse.nix +++ b/nixos/modules/services/matrix/synapse.nix @@ -446,7 +446,7 @@ in { default = { }; description = mdDoc '' The primary synapse configuration. See the - [sample configuration](https://github.com/matrix-org/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_config.yaml) + [sample configuration](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_config.yaml) for possible values. Secrets should be passed in by using the `extraConfigFiles` option. @@ -749,7 +749,7 @@ in { by the module, but in practice it broke on runtime and as a result, no URL preview worked anywhere if this was set. - See https://matrix-org.github.io/synapse/latest/usage/configuration/config_documentation.html#url_preview_url_blacklist + See https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#url_preview_url_blacklist on how to configure it properly. '')) (types.attrsOf types.str)); @@ -873,7 +873,7 @@ in { Redis configuration for synapse. See the - [upstream documentation](https://github.com/matrix-org/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/usage/configuration/config_documentation.md#redis) + [upstream documentation](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/usage/configuration/config_documentation.md#redis) for available options. ''; }; @@ -886,7 +886,7 @@ in { description = lib.mdDoc '' Options for configuring workers. Worker support will be enabled if at least one worker is configured here. - See the [worker documention](https://matrix-org.github.io/synapse/latest/workers.html#worker-configuration) + See the [worker documention](https://element-hq.github.io/synapse/latest/workers.html#worker-configuration) for possible options for each worker. Worker-specific options overriding the shared homeserver configuration can be specified here for each worker. @@ -900,9 +900,9 @@ in { using [`services.matrix-synapse.configureRedisLocally`](#opt-services.matrix-synapse.configureRedisLocally). Workers also require a proper reverse proxy setup to direct incoming requests to the appropriate process. See - the [reverse proxy documentation](https://matrix-org.github.io/synapse/latest/reverse_proxy.html) for a + the [reverse proxy documentation](https://element-hq.github.io/synapse/latest/reverse_proxy.html) for a general reverse proxying setup and - the [worker documentation](https://matrix-org.github.io/synapse/latest/workers.html#available-worker-applications) + the [worker documentation](https://element-hq.github.io/synapse/latest/workers.html#available-worker-applications) for the available endpoints per worker application. ::: ''; @@ -932,7 +932,7 @@ in { The file for log configuration. See the [python documentation](https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema) - for the schema and the [upstream repository](https://github.com/matrix-org/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_log_config.yaml) + for the schema and the [upstream repository](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_log_config.yaml) for an example. ''; }; diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix index f4305bea2ad7..d0135b2ba7ac 100644 --- a/nixos/modules/services/misc/gitea.nix +++ b/nixos/modules/services/misc/gitea.nix @@ -234,6 +234,13 @@ in description = lib.mdDoc "Path to the git repositories."; }; + camoHmacKeyFile = mkOption { + type = types.nullOr types.str; + default = null; + example = "/var/lib/secrets/gitea/camoHmacKey"; + description = lib.mdDoc "Path to a file containing the camo HMAC key."; + }; + mailerPasswordFile = mkOption { type = types.nullOr types.str; default = null; @@ -429,6 +436,10 @@ in LFS_JWT_SECRET = "#lfsjwtsecret#"; }; + camo = mkIf (cfg.camoHmacKeyFile != null) { + HMAC_KEY = "#hmackey#"; + }; + session = { COOKIE_NAME = lib.mkDefault "session"; }; @@ -570,6 +581,10 @@ in ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}' ''} + ${lib.optionalString (cfg.camoHmacKeyFile != null) '' + ${replaceSecretBin} '#hmackey#' '${cfg.camoHmacKeyFile}' '${runConfig}' + ''} + ${lib.optionalString (cfg.mailerPasswordFile != null) '' ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}' ''} diff --git a/nixos/modules/services/misc/guix/default.nix b/nixos/modules/services/misc/guix/default.nix index 7b468e7067cc..7174ff36b709 100644 --- a/nixos/modules/services/misc/guix/default.nix +++ b/nixos/modules/services/misc/guix/default.nix @@ -22,11 +22,19 @@ let }) (builtins.genList guixBuildUser numberOfUsers)); - # A set of Guix user profiles to be linked at activation. + # A set of Guix user profiles to be linked at activation. All of these should + # be default profiles managed by Guix CLI and the profiles are located in + # `${cfg.stateDir}/profiles/per-user/$USER/$PROFILE`. guixUserProfiles = { - # The current Guix profile that is created through `guix pull`. + # The default Guix profile managed by `guix pull`. Take note this should be + # the profile with the most precedence in `PATH` env to let users use their + # updated versions of `guix` CLI. "current-guix" = "\${XDG_CONFIG_HOME}/guix/current"; + # The default Guix home profile. This profile contains more than exports + # such as an activation script at `$GUIX_HOME_PROFILE/activate`. + "guix-home" = "$HOME/.guix-home/profile"; + # The default Guix profile similar to $HOME/.nix-profile from Nix. "guix-profile" = "$HOME/.guix-profile"; }; @@ -228,14 +236,8 @@ in description = "Guix daemon socket"; before = [ "multi-user.target" ]; listenStreams = [ "${cfg.stateDir}/guix/daemon-socket/socket" ]; - unitConfig = { - RequiresMountsFor = [ - cfg.storeDir - cfg.stateDir - ]; - ConditionPathIsReadWrite = "${cfg.stateDir}/guix/daemon-socket"; - }; - wantedBy = [ "socket.target" ]; + unitConfig.RequiresMountsFor = [ cfg.storeDir cfg.stateDir ]; + wantedBy = [ "sockets.target" ]; }; systemd.mounts = [{ @@ -262,20 +264,31 @@ in # ephemeral setups where only certain part of the filesystem is # persistent (e.g., "Erase my darlings"-type of setup). system.userActivationScripts.guix-activate-user-profiles.text = let + guixProfile = profile: "${cfg.stateDir}/guix/profiles/per-user/\${USER}/${profile}"; + linkProfile = profile: location: let + userProfile = guixProfile profile; + in '' + [ -d "${userProfile}" ] && ln -sfn "${userProfile}" "${location}" + ''; linkProfileToPath = acc: profile: location: let - guixProfile = "${cfg.stateDir}/guix/profiles/per-user/\${USER}/${profile}"; - in acc + '' - [ -d "${guixProfile}" ] && [ -L "${location}" ] || ln -sf "${guixProfile}" "${location}" - ''; + in acc + (linkProfile profile location); + + # This should contain export-only Guix user profiles. The rest of it is + # handled manually in the activation script. + guixUserProfiles' = lib.attrsets.removeAttrs guixUserProfiles [ "guix-home" ]; - activationScript = lib.foldlAttrs linkProfileToPath "" guixUserProfiles; + linkExportsScript = lib.foldlAttrs linkProfileToPath "" guixUserProfiles'; in '' # Don't export this please! It is only expected to be used for this # activation script and nothing else. XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config} # Linking the usual Guix profiles into the home directory. - ${activationScript} + ${linkExportsScript} + + # Activate all of the default Guix non-exports profiles manually. + ${linkProfile "guix-home" "$HOME/.guix-home"} + [ -L "$HOME/.guix-home" ] && "$HOME/.guix-home/activate" ''; # GUIX_LOCPATH is basically LOCPATH but for Guix libc which in turn used by @@ -373,7 +386,6 @@ in serviceConfig = { Type = "oneshot"; - MemoryDenyWriteExecute = true; PrivateDevices = true; PrivateNetworks = true; ProtectControlGroups = true; diff --git a/nixos/modules/services/misc/llama-cpp.nix b/nixos/modules/services/misc/llama-cpp.nix new file mode 100644 index 000000000000..4d76456fb2fd --- /dev/null +++ b/nixos/modules/services/misc/llama-cpp.nix @@ -0,0 +1,111 @@ +{ config, lib, pkgs, utils, ... }: + +let + cfg = config.services.llama-cpp; +in { + + options = { + + services.llama-cpp = { + enable = lib.mkEnableOption "LLaMA C++ server"; + + package = lib.mkPackageOption pkgs "llama-cpp" { }; + + model = lib.mkOption { + type = lib.types.path; + example = "/models/mistral-instruct-7b/ggml-model-q4_0.gguf"; + description = "Model path."; + }; + + extraFlags = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "Extra flags passed to llama-cpp-server."; + example = ["-c" "4096" "-ngl" "32" "--numa"]; + default = []; + }; + + host = lib.mkOption { + type = lib.types.str; + default = "127.0.0.1"; + example = "0.0.0.0"; + description = "IP address the LLaMA C++ server listens on."; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Listen port for LLaMA C++ server."; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Open ports in the firewall for LLaMA C++ server."; + }; + }; + + }; + + config = lib.mkIf cfg.enable { + + systemd.services.llama-cpp = { + description = "LLaMA C++ server"; + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + serviceConfig = { + Type = "idle"; + KillSignal = "SIGINT"; + ExecStart = "${cfg.package}/bin/llama-cpp-server --log-disable --host ${cfg.host} --port ${builtins.toString cfg.port} -m ${cfg.model} ${utils.escapeSystemdExecArgs cfg.extraFlags}"; + Restart = "on-failure"; + RestartSec = 300; + + # for GPU acceleration + PrivateDevices = false; + + # hardening + DynamicUser = true; + CapabilityBoundingSet = ""; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + NoNewPrivileges = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectSystem = "strict"; + MemoryDenyWriteExecute = true; + LockPersonality = true; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + "~@resources" + ]; + SystemCallErrorNumber = "EPERM"; + ProtectProc = "invisible"; + ProtectHostname = true; + ProcSubset = "pid"; + }; + }; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.port ]; + }; + + }; + + meta.maintainers = with lib.maintainers; [ newam ]; +} diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix index 0ee7e898cf76..750dca9d0373 100644 --- a/nixos/modules/services/misc/moonraker.nix +++ b/nixos/modules/services/misc/moonraker.nix @@ -186,6 +186,12 @@ in { }; }; + # set this to false, otherwise we'll get a warning indicating that `/etc/klipper.cfg` + # is not located in the moonraker config directory. + services.moonraker.settings = lib.mkIf (!config.services.klipper.mutableConfig) { + file_manager.check_klipper_config_path = false; + }; + security.polkit.extraConfig = lib.optionalString cfg.allowSystemControl '' // nixos/moonraker: Allow Moonraker to perform system-level operations // diff --git a/nixos/modules/services/misc/nitter.nix b/nixos/modules/services/misc/nitter.nix index c2c462d46bb5..d2cf7c0de2b7 100644 --- a/nixos/modules/services/misc/nitter.nix +++ b/nixos/modules/services/misc/nitter.nix @@ -304,6 +304,23 @@ in ''; }; + guestAccounts = mkOption { + type = types.path; + default = "/var/lib/nitter/guest_accounts.jsonl"; + description = lib.mdDoc '' + Path to the guest accounts file. + + This file contains a list of guest accounts that can be used to + access the instance without logging in. The file is in JSONL format, + where each line is a JSON object with the following fields: + + {"oauth_token":"some_token","oauth_token_secret":"some_secret_key"} + + See https://github.com/zedeus/nitter/wiki/Guest-Account-Branch-Deployment + for more information on guest accounts and how to generate them. + ''; + }; + redisCreateLocally = mkOption { type = types.bool; default = true; @@ -333,8 +350,12 @@ in after = [ "network-online.target" ]; serviceConfig = { DynamicUser = true; + LoadCredential="guestAccountsFile:${cfg.guestAccounts}"; StateDirectory = "nitter"; - Environment = [ "NITTER_CONF_FILE=/var/lib/nitter/nitter.conf" ]; + Environment = [ + "NITTER_CONF_FILE=/var/lib/nitter/nitter.conf" + "NITTER_ACCOUNTS_FILE=%d/guestAccountsFile" + ]; # Some parts of Nitter expect `public` folder in working directory, # see https://github.com/zedeus/nitter/issues/414 WorkingDirectory = "${cfg.package}/share/nitter"; diff --git a/nixos/modules/services/misc/ntfy-sh.nix b/nixos/modules/services/misc/ntfy-sh.nix index 98134e94eeed..b8b077240115 100644 --- a/nixos/modules/services/misc/ntfy-sh.nix +++ b/nixos/modules/services/misc/ntfy-sh.nix @@ -79,12 +79,6 @@ in cache-file = mkDefault "/var/lib/ntfy-sh/cache-file.db"; }; - systemd.tmpfiles.rules = [ - "f ${cfg.settings.auth-file} 0600 ${cfg.user} ${cfg.group} - -" - "d ${cfg.settings.attachment-cache-dir} 0700 ${cfg.user} ${cfg.group} - -" - "f ${cfg.settings.cache-file} 0600 ${cfg.user} ${cfg.group} - -" - ]; - systemd.services.ntfy-sh = { description = "Push notifications server"; diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix new file mode 100644 index 000000000000..9794bbbec464 --- /dev/null +++ b/nixos/modules/services/misc/ollama.nix @@ -0,0 +1,42 @@ +{ config, lib, pkgs, ... }: let + + cfg = config.services.ollama; + +in { + + options = { + services.ollama = { + enable = lib.mkEnableOption ( + lib.mdDoc "Server for local large language models" + ); + package = lib.mkPackageOption pkgs "ollama" { }; + }; + }; + + config = lib.mkIf cfg.enable { + + systemd = { + services.ollama = { + wantedBy = [ "multi-user.target" ]; + description = "Server for local large language models"; + after = [ "network.target" ]; + environment = { + HOME = "%S/ollama"; + OLLAMA_MODELS = "%S/ollama/models"; + }; + serviceConfig = { + ExecStart = "${lib.getExe cfg.package} serve"; + WorkingDirectory = "/var/lib/ollama"; + StateDirectory = [ "ollama" ]; + DynamicUser = true; + }; + }; + }; + + environment.systemPackages = [ cfg.package ]; + + }; + + meta.maintainers = with lib.maintainers; [ onny ]; + +} diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix index b3bc7d89009d..3c6832958f59 100644 --- a/nixos/modules/services/misc/paperless.nix +++ b/nixos/modules/services/misc/paperless.nix @@ -10,7 +10,7 @@ let defaultFont = "${pkgs.liberation_ttf}/share/fonts/truetype/LiberationSerif-Regular.ttf"; # Don't start a redis instance if the user sets a custom redis connection - enableRedis = !hasAttr "PAPERLESS_REDIS" cfg.extraConfig; + enableRedis = !(cfg.settings ? PAPERLESS_REDIS); redisServer = config.services.redis.servers.paperless; env = { @@ -24,9 +24,11 @@ let PAPERLESS_TIME_ZONE = config.time.timeZone; } // optionalAttrs enableRedis { PAPERLESS_REDIS = "unix://${redisServer.unixSocket}"; - } // ( - lib.mapAttrs (_: toString) cfg.extraConfig - ); + } // (lib.mapAttrs (_: s: + if (lib.isAttrs s || lib.isList s) then builtins.toJSON s + else if lib.isBool s then lib.boolToString s + else toString s + ) cfg.settings); manage = pkgs.writeShellScript "manage" '' set -o allexport # Export the following env vars @@ -82,6 +84,7 @@ in imports = [ (mkRenamedOptionModule [ "services" "paperless-ng" ] [ "services" "paperless" ]) + (mkRenamedOptionModule [ "services" "paperless" "extraConfig" ] [ "services" "paperless" "settings" ]) ]; options.services.paperless = { @@ -160,32 +163,30 @@ in description = lib.mdDoc "Web interface port."; }; - # FIXME this should become an RFC42-style settings attr - extraConfig = mkOption { - type = types.attrs; + settings = mkOption { + type = lib.types.submodule { + freeformType = with lib.types; attrsOf (let + typeList = [ bool float int str path package ]; + in oneOf (typeList ++ [ (listOf (oneOf typeList)) (attrsOf (oneOf typeList)) ])); + }; default = { }; description = lib.mdDoc '' Extra paperless config options. - See [the documentation](https://docs.paperless-ngx.com/configuration/) - for available options. + See [the documentation](https://docs.paperless-ngx.com/configuration/) for available options. - Note that some options such as `PAPERLESS_CONSUMER_IGNORE_PATTERN` expect JSON values. Use `builtins.toJSON` to ensure proper quoting. + Note that some settings such as `PAPERLESS_CONSUMER_IGNORE_PATTERN` expect JSON values. + Settings declared as lists or attrsets will automatically be serialised into JSON strings for your convenience. ''; - example = literalExpression '' - { - PAPERLESS_OCR_LANGUAGE = "deu+eng"; - - PAPERLESS_DBHOST = "/run/postgresql"; - - PAPERLESS_CONSUMER_IGNORE_PATTERN = builtins.toJSON [ ".DS_STORE/*" "desktop.ini" ]; - - PAPERLESS_OCR_USER_ARGS = builtins.toJSON { - optimize = 1; - pdfa_image_compression = "lossless"; - }; + example = { + PAPERLESS_OCR_LANGUAGE = "deu+eng"; + PAPERLESS_DBHOST = "/run/postgresql"; + PAPERLESS_CONSUMER_IGNORE_PATTERN = [ ".DS_STORE/*" "desktop.ini" ]; + PAPERLESS_OCR_USER_ARGS = { + optimize = 1; + pdfa_image_compression = "lossless"; }; - ''; + }; }; user = mkOption { diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix index 3299b6404c2b..7036a372d1ea 100644 --- a/nixos/modules/services/misc/portunus.nix +++ b/nixos/modules/services/misc/portunus.nix @@ -102,7 +102,9 @@ in ldap = { package = mkOption { type = types.package; - # needs openldap built with a libxcrypt that support crypt sha256 until https://github.com/majewsky/portunus/issues/2 is solved + # needs openldap built with a libxcrypt that support crypt sha256 until users have had time to migrate to newer hashes + # Ref: <https://github.com/majewsky/portunus/issues/2> + # TODO: remove in NixOS 24.11 (cf. same note on pkgs/servers/portunus/default.nix) default = pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; }; defaultText = lib.literalExpression "pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; }"; description = lib.mdDoc "The OpenLDAP package to use."; @@ -247,6 +249,7 @@ in acmeDirectory = config.security.acme.certs."${cfg.domain}".directory; in { + PORTUNUS_SERVER_HTTP_SECURE = "true"; PORTUNUS_SLAPD_TLS_CA_CERTIFICATE = "/etc/ssl/certs/ca-certificates.crt"; PORTUNUS_SLAPD_TLS_CERTIFICATE = "${acmeDirectory}/cert.pem"; PORTUNUS_SLAPD_TLS_DOMAIN_NAME = cfg.domain; diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix index b517170cda21..c1209e34a92b 100644 --- a/nixos/modules/services/misc/redmine.nix +++ b/nixos/modules/services/misc/redmine.nix @@ -53,7 +53,7 @@ in enable = mkEnableOption (lib.mdDoc "Redmine"); package = mkPackageOption pkgs "redmine" { - example = "redmine.override { ruby = pkgs.ruby_2_7; }"; + example = "redmine.override { ruby = pkgs.ruby_3_2; }"; }; user = mkOption { diff --git a/nixos/modules/services/misc/tuxclocker.nix b/nixos/modules/services/misc/tuxclocker.nix new file mode 100644 index 000000000000..5969f75b8e30 --- /dev/null +++ b/nixos/modules/services/misc/tuxclocker.nix @@ -0,0 +1,71 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.programs.tuxclocker; +in +{ + options.programs.tuxclocker = { + enable = mkEnableOption (lib.mdDoc '' + TuxClocker, a hardware control and monitoring program + ''); + + enableAMD = mkEnableOption (lib.mdDoc '' + AMD GPU controls. + Sets the `amdgpu.ppfeaturemask` kernel parameter to 0xfffd7fff to enable all TuxClocker controls + ''); + + enabledNVIDIADevices = mkOption { + type = types.listOf types.int; + default = [ ]; + example = [ 0 1 ]; + description = lib.mdDoc '' + Enable NVIDIA GPU controls for a device by index. + Sets the `Coolbits` Xorg option to enable all TuxClocker controls. + ''; + }; + + useUnfree = mkOption { + type = types.bool; + default = false; + example = true; + description = lib.mdDoc '' + Whether to use components requiring unfree dependencies. + Disabling this allows you to get everything from the binary cache. + ''; + }; + }; + + config = let + package = if cfg.useUnfree then pkgs.tuxclocker else pkgs.tuxclocker-without-unfree; + in + mkIf cfg.enable { + environment.systemPackages = [ + package + ]; + + services.dbus.packages = [ + package + ]; + + # MSR is used for some features + boot.kernelModules = [ "msr" ]; + + # https://download.nvidia.com/XFree86/Linux-x86_64/430.14/README/xconfigoptions.html#Coolbits + services.xserver.config = let + configSection = (i: '' + Section "Device" + Driver "nvidia" + Option "Coolbits" "31" + Identifier "Device-nvidia[${toString i}]" + EndSection + ''); + in + concatStrings (map configSection cfg.enabledNVIDIADevices); + + # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/amd/include/amd_shared.h#n207 + # Enable everything modifiable in TuxClocker + boot.kernelParams = mkIf cfg.enableAMD [ "amdgpu.ppfeaturemask=0xfffd7fff" ]; + }; +} diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix index ec6aa5615039..5cf3c096397c 100644 --- a/nixos/modules/services/monitoring/netdata.nix +++ b/nixos/modules/services/monitoring/netdata.nix @@ -206,7 +206,15 @@ in { description = "Real time performance monitoring"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - path = (with pkgs; [ curl gawk iproute2 which procps bash ]) + path = (with pkgs; [ + curl + gawk + iproute2 + which + procps + bash + util-linux # provides logger command; required for syslog health alarms + ]) ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages) ++ lib.optional config.virtualisation.libvirtd.enable (config.virtualisation.libvirtd.package); environment = { diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix index e78cb4d01dc5..b4ac8e21451a 100644 --- a/nixos/modules/services/monitoring/prometheus/default.nix +++ b/nixos/modules/services/monitoring/prometheus/default.nix @@ -1435,6 +1435,10 @@ let remote_timeout = mkOpt types.str '' Timeout for requests to the remote write endpoint. ''; + headers = mkOpt (types.attrsOf types.str) '' + Custom HTTP headers to be sent along with each remote write request. + Be aware that headers that are set by Prometheus itself can't be overwritten. + ''; write_relabel_configs = mkOpt (types.listOf promTypes.relabel_config) '' List of remote write relabel configurations. ''; @@ -1530,6 +1534,10 @@ let remote_timeout = mkOpt types.str '' Timeout for requests to the remote read endpoint. ''; + headers = mkOpt (types.attrsOf types.str) '' + Custom HTTP headers to be sent along with each remote read request. + Be aware that headers that are set by Prometheus itself can't be overwritten. + ''; read_recent = mkOpt types.bool '' Whether reads should be made for queries for time ranges that the local storage should have complete data for. diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index 39abd293b2d1..35db8a7376b1 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -64,6 +64,7 @@ let "pgbouncer" "php-fpm" "pihole" + "ping" "postfix" "postgres" "process" diff --git a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix index 8b1cd47d0a40..3abb6ff6bdf8 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix @@ -15,8 +15,8 @@ in { type = types.listOf types.str; example = literalExpression '' [ - "/run/kea-dhcp4/kea-dhcp4.socket" - "/run/kea-dhcp6/kea-dhcp6.socket" + "/run/kea/kea-dhcp4.socket" + "/run/kea/kea-dhcp6.socket" ] ''; description = lib.mdDoc '' @@ -31,13 +31,15 @@ in { ]; serviceConfig = { User = "kea"; + DynamicUser = true; ExecStart = '' ${pkgs.prometheus-kea-exporter}/bin/kea-exporter \ --address ${cfg.listenAddress} \ --port ${toString cfg.port} \ ${concatStringsSep " " cfg.controlSocketPaths} ''; - SupplementaryGroups = [ "kea" ]; + RuntimeDirectory = "kea"; + RuntimeDirectoryPreserve = true; RestrictAddressFamilies = [ # Need AF_UNIX to collect data "AF_UNIX" diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix index 3158e71f0468..88dc79fc2503 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix @@ -43,14 +43,14 @@ in }; }; serviceOpts = mkMerge ([{ + environment.CONST_LABELS = concatStringsSep "," cfg.constLabels; serviceConfig = { ExecStart = '' ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \ --nginx.scrape-uri='${cfg.scrapeUri}' \ - --nginx.ssl-verify=${boolToString cfg.sslVerify} \ + --${lib.optionalString (!cfg.sslVerify) "no-"}nginx.ssl-verify \ --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \ --web.telemetry-path=${cfg.telemetryPath} \ - --prometheus.const-labels=${concatStringsSep "," cfg.constLabels} \ ${concatStringsSep " \\\n " cfg.extraFlags} ''; }; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/ping.nix b/nixos/modules/services/monitoring/prometheus/exporters/ping.nix new file mode 100644 index 000000000000..af78b6bef625 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/ping.nix @@ -0,0 +1,48 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.ping; + + settingsFormat = pkgs.formats.yaml {}; + configFile = settingsFormat.generate "config.yml" cfg.settings; +in +{ + port = 9427; + extraOpts = { + telemetryPath = mkOption { + type = types.str; + default = "/metrics"; + description = '' + Path under which to expose metrics. + ''; + }; + + settings = mkOption { + type = settingsFormat.type; + default = {}; + + description = lib.mdDoc '' + Configuration for ping_exporter, see + <https://github.com/czerwonk/ping_exporter> + for supported values. + ''; + }; + }; + + serviceOpts = { + serviceConfig = { + # ping-exporter needs `CAP_NET_RAW` to run as non root https://github.com/czerwonk/ping_exporter#running-as-non-root-user + CapabilityBoundingSet = [ "CAP_NET_RAW" ]; + AmbientCapabilities = [ "CAP_NET_RAW" ]; + ExecStart = '' + ${pkgs.prometheus-ping-exporter}/bin/ping_exporter \ + --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \ + --web.telemetry-path ${cfg.telemetryPath} \ + --config.path="${configFile}" \ + ${concatStringsSep " \\\n " cfg.extraFlags} + ''; + }; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix b/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix index 411277494013..b9ab305f7c08 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix @@ -19,7 +19,11 @@ in }; apiKeyFile = mkOption { type = types.str; - description = "File containing the API key."; + description = '' + The path to a file containing the API key. + The file is securely passed to the service by leveraging systemd credentials. + No special permissions need to be set on this file. + ''; example = "/run/secrets/sabnzbd_apikey"; }; }; @@ -30,18 +34,24 @@ in serviceOpts = let servers = lib.zipAttrs cfg.servers; - apiKeys = lib.concatStringsSep "," (builtins.map (file: "$(cat ${file})") servers.apiKeyFile); + credentials = lib.imap0 (i: v: { name = "apikey-${toString i}"; path = v; }) servers.apiKeyFile; in { + serviceConfig.LoadCredential = builtins.map ({ name, path }: "${name}:${path}") credentials; + environment = { METRICS_PORT = toString cfg.port; METRICS_ADDR = cfg.listenAddress; SABNZBD_BASEURLS = lib.concatStringsSep "," servers.baseUrl; }; - script = '' - export SABNZBD_APIKEYS="${apiKeys}" - exec ${lib.getExe pkgs.prometheus-sabnzbd-exporter} - ''; + script = + let + apiKeys = lib.concatStringsSep "," (builtins.map (cred: "$(< $CREDENTIALS_DIRECTORY/${cred.name})") credentials); + in + '' + export SABNZBD_APIKEYS="${apiKeys}" + exec ${lib.getExe pkgs.prometheus-sabnzbd-exporter} + ''; }; } diff --git a/nixos/modules/services/monitoring/snmpd.nix b/nixos/modules/services/monitoring/snmpd.nix new file mode 100644 index 000000000000..f2d3953e6a62 --- /dev/null +++ b/nixos/modules/services/monitoring/snmpd.nix @@ -0,0 +1,83 @@ +{ pkgs, config, lib, ... }: + +let + cfg = config.services.snmpd; + configFile = if cfg.configText != "" then + pkgs.writeText "snmpd.cfg" '' + ${cfg.configText} + '' else null; +in { + options.services.snmpd = { + enable = lib.mkEnableOption "snmpd"; + + package = lib.mkPackageOption pkgs "net-snmp" {}; + + listenAddress = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = lib.mdDoc '' + The address to listen on for SNMP and AgentX messages. + ''; + example = "127.0.0.1"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 161; + description = lib.mdDoc '' + The port to listen on for SNMP and AgentX messages. + ''; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc '' + Open port in firewall for snmpd. + ''; + }; + + configText = lib.mkOption { + type = lib.types.lines; + default = ""; + description = lib.mdDoc '' + The contents of the snmpd.conf. If the {option}`configFile` option + is set, this value will be ignored. + + Note that the contents of this option will be added to the Nix + store as world-readable plain text, {option}`configFile` can be used in + addition to a secret management tool to protect sensitive data. + ''; + }; + + configFile = lib.mkOption { + type = lib.types.path; + default = configFile; + defaultText = lib.literalMD "The value of {option}`configText`."; + description = lib.mdDoc '' + Path to the snmpd.conf file. By default, if {option}`configText` is set, + a config file will be automatically generated. + ''; + }; + + }; + + config = lib.mkIf cfg.enable { + systemd.services."snmpd" = { + description = "Simple Network Management Protocol (SNMP) daemon."; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + ExecStart = "${lib.getExe' cfg.package "snmpd"} -f -Lo -c ${cfg.configFile} ${cfg.listenAddress}:${toString cfg.port}"; + }; + }; + + networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [ + cfg.port + ]; + }; + + meta.maintainers = [ lib.maintainers.eliandoran ]; + +} diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix index 5baa0d8446e5..02502816ef5d 100644 --- a/nixos/modules/services/monitoring/thanos.nix +++ b/nixos/modules/services/monitoring/thanos.nix @@ -394,9 +394,8 @@ let Maximum number of queries processed concurrently by query node. ''; - query.replica-labels = mkAttrsParam "query.replica-label" '' + query.replica-labels = mkListParam "query.replica-label" '' Labels to treat as a replica indicator along which data is - deduplicated. Still you will be able to query without deduplication using diff --git a/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix index c9dda8a8c093..63afb5deb5bd 100644 --- a/nixos/modules/services/monitoring/ups.nix +++ b/nixos/modules/services/monitoring/ups.nix @@ -590,6 +590,7 @@ in "d /var/lib/nut 700" ]; + services.udev.packages = [ pkgs.nut ]; /* users.users.nut = diff --git a/nixos/modules/services/network-filesystems/drbd.nix b/nixos/modules/services/network-filesystems/drbd.nix index e74ed391d48e..79a1b768b461 100644 --- a/nixos/modules/services/network-filesystems/drbd.nix +++ b/nixos/modules/services/network-filesystems/drbd.nix @@ -55,8 +55,8 @@ let cfg = config.services.drbd; in wants = [ "systemd-udev.settle.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = "${pkgs.drbd}/sbin/drbdadm up all"; - ExecStop = "${pkgs.drbd}/sbin/drbdadm down all"; + ExecStart = "${pkgs.drbd}/bin/drbdadm up all"; + ExecStop = "${pkgs.drbd}/bin/drbdadm down all"; }; }; }; diff --git a/nixos/modules/services/network-filesystems/eris-server.nix b/nixos/modules/services/network-filesystems/eris-server.nix index 66eccfac408c..104676a52c61 100644 --- a/nixos/modules/services/network-filesystems/eris-server.nix +++ b/nixos/modules/services/network-filesystems/eris-server.nix @@ -3,6 +3,7 @@ let cfg = config.services.eris-server; stateDirectoryPath = "\${STATE_DIRECTORY}"; + nullOrStr = with lib.types; nullOr str; in { options.services.eris-server = { @@ -26,7 +27,7 @@ in { }; listenCoap = lib.mkOption { - type = lib.types.str; + type = nullOrStr; default = ":5683"; example = "[::1]:5683"; description = '' @@ -39,8 +40,8 @@ in { }; listenHttp = lib.mkOption { - type = lib.types.str; - default = ""; + type = nullOrStr; + default = null; example = "[::1]:8080"; description = "Server HTTP listen address. Do not listen by default."; }; @@ -58,8 +59,8 @@ in { }; mountpoint = lib.mkOption { - type = lib.types.str; - default = ""; + type = nullOrStr; + default = null; example = "/eris"; description = '' Mountpoint for FUSE namespace that exposes "urn:eris:…" files. @@ -69,33 +70,44 @@ in { }; config = lib.mkIf cfg.enable { + assertions = [{ + assertion = lib.strings.versionAtLeast cfg.package.version "20231219"; + message = + "Version of `config.services.eris-server.package` is incompatible with this module"; + }]; + systemd.services.eris-server = let - cmd = - "${cfg.package}/bin/eris-go server --coap '${cfg.listenCoap}' --http '${cfg.listenHttp}' ${ - lib.optionalString cfg.decode "--decode " - }${ - lib.optionalString (cfg.mountpoint != "") - ''--mountpoint "${cfg.mountpoint}" '' - }${lib.strings.escapeShellArgs cfg.backends}"; + cmd = "${cfg.package}/bin/eris-go server" + + (lib.optionalString (cfg.listenCoap != null) + " --coap '${cfg.listenCoap}'") + + (lib.optionalString (cfg.listenHttp != null) + " --http '${cfg.listenHttp}'") + + (lib.optionalString cfg.decode " --decode") + + (lib.optionalString (cfg.mountpoint != null) + " --mountpoint '${cfg.mountpoint}'"); in { description = "ERIS block server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - script = lib.mkIf (cfg.mountpoint != "") '' + environment.ERIS_STORE_URL = toString cfg.backends; + script = lib.mkIf (cfg.mountpoint != null) '' export PATH=${config.security.wrapperDir}:$PATH ${cmd} ''; serviceConfig = let - umounter = lib.mkIf (cfg.mountpoint != "") + umounter = lib.mkIf (cfg.mountpoint != null) "-${config.security.wrapperDir}/fusermount -uz ${cfg.mountpoint}"; - in { - ExecStartPre = umounter; - ExecStart = lib.mkIf (cfg.mountpoint == "") cmd; - ExecStopPost = umounter; - Restart = "always"; - RestartSec = 20; - AmbientCapabilities = "CAP_NET_BIND_SERVICE"; - }; + in if (cfg.mountpoint == null) then { + ExecStart = cmd; + } else + { + ExecStartPre = umounter; + ExecStopPost = umounter; + } // { + Restart = "always"; + RestartSec = 20; + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + }; }; }; diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix index fbf9b32a2b25..126e0902d5b4 100644 --- a/nixos/modules/services/network-filesystems/kubo.nix +++ b/nixos/modules/services/network-filesystems/kubo.nix @@ -147,18 +147,6 @@ in description = lib.mdDoc "Whether Kubo should try to run the fs-repo-migration at startup."; }; - ipfsMountDir = mkOption { - type = types.str; - default = "/ipfs"; - description = lib.mdDoc "Where to mount the IPFS namespace to"; - }; - - ipnsMountDir = mkOption { - type = types.str; - default = "/ipns"; - description = lib.mdDoc "Where to mount the IPNS namespace to"; - }; - enableGC = mkOption { type = types.bool; default = false; @@ -205,6 +193,18 @@ in ]; description = lib.mdDoc "Where Kubo listens for incoming p2p connections"; }; + + Mounts.IPFS = mkOption { + type = types.str; + default = "/ipfs"; + description = lib.mdDoc "Where to mount the IPFS namespace to"; + }; + + Mounts.IPNS = mkOption { + type = types.str; + default = "/ipns"; + description = lib.mdDoc "Where to mount the IPNS namespace to"; + }; }; }; description = lib.mdDoc '' @@ -282,8 +282,9 @@ in environment.systemPackages = [ cfg.package ]; environment.variables.IPFS_PATH = fakeKuboRepo; - # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size + # https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000; + boot.kernel.sysctl."net.core.wmem_max" = mkDefault 2500000; programs.fuse = mkIf cfg.autoMount { userAllowOther = true; @@ -309,8 +310,8 @@ in systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -" ] ++ optionals cfg.autoMount [ - "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -" - "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -" + "d '${cfg.settings.Mounts.IPFS}' - ${cfg.user} ${cfg.group} - -" + "d '${cfg.settings.Mounts.IPNS}' - ${cfg.user} ${cfg.group} - -" ]; # The hardened systemd unit breaks the fuse-mount function according to documentation in the unit file itself @@ -320,8 +321,6 @@ in services.kubo.settings = mkIf cfg.autoMount { Mounts.FuseAllowOther = lib.mkDefault true; - Mounts.IPFS = lib.mkDefault cfg.ipfsMountDir; - Mounts.IPNS = lib.mkDefault cfg.ipnsMountDir; }; systemd.services.ipfs = { @@ -352,8 +351,8 @@ in ipfs --offline config replace - ''; postStop = mkIf cfg.autoMount '' - # After an unclean shutdown the fuse mounts at cfg.ipnsMountDir and cfg.ipfsMountDir are locked - umount --quiet '${cfg.ipnsMountDir}' '${cfg.ipfsMountDir}' || true + # After an unclean shutdown the fuse mounts at cfg.settings.Mounts.IPFS and cfg.settings.Mounts.IPNS are locked + umount --quiet '${cfg.settings.Mounts.IPFS}' '${cfg.settings.Mounts.IPNS}' || true ''; serviceConfig = { ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ]; @@ -361,6 +360,8 @@ in Group = cfg.group; StateDirectory = ""; ReadWritePaths = optionals (!cfg.autoMount) [ "" cfg.dataDir ]; + # Make sure the socket units are started before ipfs.service + Sockets = [ "ipfs-gateway.socket" "ipfs-api.socket" ]; } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; }; } // optionalAttrs (!cfg.startWhenNeeded) { wantedBy = [ "default.target" ]; @@ -403,8 +404,8 @@ in (mkRenamedOptionModule [ "services" "ipfs" "defaultMode" ] [ "services" "kubo" "defaultMode" ]) (mkRenamedOptionModule [ "services" "ipfs" "autoMount" ] [ "services" "kubo" "autoMount" ]) (mkRenamedOptionModule [ "services" "ipfs" "autoMigrate" ] [ "services" "kubo" "autoMigrate" ]) - (mkRenamedOptionModule [ "services" "ipfs" "ipfsMountDir" ] [ "services" "kubo" "ipfsMountDir" ]) - (mkRenamedOptionModule [ "services" "ipfs" "ipnsMountDir" ] [ "services" "kubo" "ipnsMountDir" ]) + (mkRenamedOptionModule [ "services" "ipfs" "ipfsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPFS" ]) + (mkRenamedOptionModule [ "services" "ipfs" "ipnsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPNS" ]) (mkRenamedOptionModule [ "services" "ipfs" "gatewayAddress" ] [ "services" "kubo" "settings" "Addresses" "Gateway" ]) (mkRenamedOptionModule [ "services" "ipfs" "apiAddress" ] [ "services" "kubo" "settings" "Addresses" "API" ]) (mkRenamedOptionModule [ "services" "ipfs" "swarmAddress" ] [ "services" "kubo" "settings" "Addresses" "Swarm" ]) @@ -419,5 +420,7 @@ in (mkRenamedOptionModule [ "services" "kubo" "gatewayAddress" ] [ "services" "kubo" "settings" "Addresses" "Gateway" ]) (mkRenamedOptionModule [ "services" "kubo" "apiAddress" ] [ "services" "kubo" "settings" "Addresses" "API" ]) (mkRenamedOptionModule [ "services" "kubo" "swarmAddress" ] [ "services" "kubo" "settings" "Addresses" "Swarm" ]) + (mkRenamedOptionModule [ "services" "kubo" "ipfsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPFS" ]) + (mkRenamedOptionModule [ "services" "kubo" "ipnsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPNS" ]) ]; } diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix index 89b30996e8fa..782681018116 100644 --- a/nixos/modules/services/networking/avahi-daemon.nix +++ b/nixos/modules/services/networking/avahi-daemon.nix @@ -95,7 +95,6 @@ in ipv6 = mkOption { type = types.bool; default = false; - defaultText = literalExpression "config.networking.enableIPv6"; description = lib.mdDoc "Whether to use IPv6."; }; @@ -274,17 +273,17 @@ in system.nssModules = optional (cfg.nssmdns4 || cfg.nssmdns6) pkgs.nssmdns; system.nssDatabases.hosts = let - mdnsMinimal = if (cfg.nssmdns4 && cfg.nssmdns6) then - "mdns_minimal" + mdns = if (cfg.nssmdns4 && cfg.nssmdns6) then + "mdns" else if (!cfg.nssmdns4 && cfg.nssmdns6) then - "mdns6_minimal" + "mdns6" else if (cfg.nssmdns4 && !cfg.nssmdns6) then - "mdns4_minimal" + "mdns4" else ""; in optionals (cfg.nssmdns4 || cfg.nssmdns6) (mkMerge [ - (mkBefore [ "${mdnsMinimal} [NOTFOUND=return]" ]) # before resolve - (mkAfter [ "mdns" ]) # after dns + (mkBefore [ "${mdns}_minimal [NOTFOUND=return]" ]) # before resolve + (mkAfter [ "${mdns}" ]) # after dns ]); environment.systemPackages = [ cfg.package ]; diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix index a67f0c5de9ba..18f205b8d99e 100644 --- a/nixos/modules/services/networking/ddclient.nix +++ b/nixos/modules/services/networking/ddclient.nix @@ -217,7 +217,7 @@ with lib; inherit RuntimeDirectory; inherit StateDirectory; Type = "oneshot"; - ExecStartPre = "!${pkgs.writeShellScript "ddclient-prestart" preStart}"; + ExecStartPre = [ "!${pkgs.writeShellScript "ddclient-prestart" preStart}" ]; ExecStart = "${lib.getExe cfg.package} -file /run/${RuntimeDirectory}/ddclient.conf"; }; }; diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 8b6d3fc55f3e..2b59352ac616 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -98,7 +98,7 @@ let # anything ever again ("couldn't resolve ..., giving up on # it"), so we silently lose time synchronisation. This also # applies to openntpd. - /run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service || true + /run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service ntpd-rs.service || true fi ${cfg.runHook} diff --git a/nixos/modules/services/networking/dnsmasq.md b/nixos/modules/services/networking/dnsmasq.md new file mode 100644 index 000000000000..6fc9178b1c0d --- /dev/null +++ b/nixos/modules/services/networking/dnsmasq.md @@ -0,0 +1,68 @@ +# Dnsmasq {#module-services-networking-dnsmasq} + +Dnsmasq is an integrated DNS, DHCP and TFTP server for small networks. + +## Configuration {#module-services-networking-dnsmasq-configuration} + +### An authoritative DHCP and DNS server on a home network {#module-services-networking-dnsmasq-configuration-home} + +On a home network, you can use Dnsmasq as a DHCP and DNS server. New devices on +your network will be configured by Dnsmasq, and instructed to use it as the DNS +server by default. This allows you to rely on your own server to perform DNS +queries and caching, with DNSSEC enabled. + +The following example assumes that + +- you have disabled your router's integrated DHCP server, if it has one +- your router's address is set in [](#opt-networking.defaultGateway.address) +- your system's Ethernet interface is `eth0` +- you have configured the address(es) to forward DNS queries in [](#opt-networking.nameservers) + +```nix +{ + services.dnsmasq = { + enable = true; + settings = { + interface = "eth0"; + bind-interfaces = true; # Only bind to the specified interface + dhcp-authoritative = true; # Should be set when dnsmasq is definitely the only DHCP server on a network + + server = config.networking.nameservers; # Upstream dns servers to which requests should be forwarded + + dhcp-host = [ + # Give the current system a fixed address of 192.168.0.254 + "dc:a6:32:0b:ea:b9,192.168.0.254,${config.networking.hostName},infinite" + ]; + + dhcp-option = [ + # Address of the gateway, i.e. your router + "option:router,${config.networking.defaultGateway.address}" + ]; + + dhcp-range = [ + # Range of IPv4 addresses to give out + # <range start>,<range end>,<lease time> + "192.168.0.10,192.168.0.253,24h" + # Enable stateless IPv6 allocation + "::f,::ff,constructor:eth0,ra-stateless" + ]; + + dhcp-rapid-commit = true; # Faster DHCP negotiation for IPv6 + local-service = true; # Accept DNS queries only from hosts whose address is on a local subnet + log-queries = true; # Log results of all DNS queries + bogus-priv = true; # Don't forward requests for the local address ranges (192.168.x.x etc) to upstream nameservers + domain-needed = true; # Don't forward requests without dots or domain parts to upstream nameservers + + dnssec = true; # Enable DNSSEC + # DNSSEC trust anchor. Source: https://data.iana.org/root-anchors/root-anchors.xml + trust-anchor = ".,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D"; + }; + }; +} +``` + +## References {#module-services-networking-dnsmasq-references} + +- Upstream website: <https://dnsmasq.org> +- Manpage: <https://dnsmasq.org/docs/dnsmasq-man.html> +- FAQ: <https://dnsmasq.org/docs/FAQ> diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix index 8d1ca36c38ed..d01a1b6707a5 100644 --- a/nixos/modules/services/networking/dnsmasq.nix +++ b/nixos/modules/services/networking/dnsmasq.nix @@ -181,4 +181,6 @@ in restartTriggers = [ config.environment.etc.hosts.source ]; }; }; + + meta.doc = ./dnsmasq.md; } diff --git a/nixos/modules/services/networking/firewall-iptables.nix b/nixos/modules/services/networking/firewall-iptables.nix index e90086838720..2d1151770008 100644 --- a/nixos/modules/services/networking/firewall-iptables.nix +++ b/nixos/modules/services/networking/firewall-iptables.nix @@ -308,8 +308,9 @@ in description = "Firewall"; wantedBy = [ "sysinit.target" ]; wants = [ "network-pre.target" ]; - before = [ "network-pre.target" ]; after = [ "systemd-modules-load.service" ]; + before = [ "network-pre.target" "shutdown.target" ]; + conflicts = [ "shutdown.target" ]; path = [ cfg.package ] ++ cfg.extraPackages; diff --git a/nixos/modules/services/networking/gns3-server.md b/nixos/modules/services/networking/gns3-server.md new file mode 100644 index 000000000000..9320d914fbd3 --- /dev/null +++ b/nixos/modules/services/networking/gns3-server.md @@ -0,0 +1,31 @@ +# GNS3 Server {#module-services-gns3-server} + +[GNS3](https://www.gns3.com/), a network software emulator. + +## Basic Usage {#module-services-gns3-server-basic-usage} + +A minimal configuration looks like this: + +```nix +{ + services.gns3-server = { + enable = true; + + auth = { + enable = true; + user = "gns3"; + passwordFile = "/var/lib/secrets/gns3_password"; + }; + + ssl = { + enable = true; + certFile = "/var/lib/gns3/ssl/cert.pem"; + keyFile = "/var/lib/gns3/ssl/key.pem"; + }; + + dynamips.enable = true; + ubridge.enable = true; + vpcs.enable = true; + }; +} +``` diff --git a/nixos/modules/services/networking/gns3-server.nix b/nixos/modules/services/networking/gns3-server.nix new file mode 100644 index 000000000000..25583765de67 --- /dev/null +++ b/nixos/modules/services/networking/gns3-server.nix @@ -0,0 +1,263 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.gns3-server; + + settingsFormat = pkgs.formats.ini { }; + configFile = settingsFormat.generate "gns3-server.conf" cfg.settings; + +in { + meta = { + doc = ./gns3-server.md; + maintainers = [ lib.maintainers.anthonyroussel ]; + }; + + options = { + services.gns3-server = { + enable = lib.mkEnableOption (lib.mdDoc "GNS3 Server daemon"); + + package = lib.mkPackageOptionMD pkgs "gns3-server" { }; + + auth = { + enable = lib.mkEnableOption (lib.mdDoc "password based HTTP authentication to access the GNS3 Server"); + + user = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "gns3"; + description = lib.mdDoc ''Username used to access the GNS3 Server.''; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/secrets/gns3-server-password"; + description = lib.mdDoc '' + A file containing the password to access the GNS3 Server. + + ::: {.warning} + This should be a string, not a nix path, since nix paths + are copied into the world-readable nix store. + ::: + ''; + }; + }; + + settings = lib.mkOption { + type = lib.types.submodule { freeformType = settingsFormat.type; }; + default = {}; + example = { host = "127.0.0.1"; port = 3080; }; + description = lib.mdDoc '' + The global options in `config` file in ini format. + + Refer to <https://docs.gns3.com/docs/using-gns3/administration/gns3-server-configuration-file/> + for all available options. + ''; + }; + + log = { + file = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = "/var/log/gns3/server.log"; + description = lib.mdDoc ''Path of the file GNS3 Server should log to.''; + }; + + debug = lib.mkEnableOption (lib.mdDoc "debug logging"); + }; + + ssl = { + enable = lib.mkEnableOption (lib.mdDoc "SSL encryption"); + + certFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/var/lib/gns3/ssl/server.pem"; + description = lib.mdDoc '' + Path to the SSL certificate file. This certificate will + be offered to, and may be verified by, clients. + ''; + }; + + keyFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/var/lib/gns3/ssl/server.key"; + description = lib.mdDoc "Private key file for the certificate."; + }; + }; + + dynamips = { + enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable Dynamips support.''); + package = lib.mkPackageOptionMD pkgs "dynamips" { }; + }; + + ubridge = { + enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable uBridge support.''); + package = lib.mkPackageOptionMD pkgs "ubridge" { }; + }; + + vpcs = { + enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable VPCS support.''); + package = lib.mkPackageOptionMD pkgs "vpcs" { }; + }; + }; + }; + + config = let + flags = { + enableDocker = config.virtualisation.docker.enable; + enableLibvirtd = config.virtualisation.libvirtd.enable; + }; + + in lib.mkIf cfg.enable { + assertions = [ + { + assertion = cfg.ssl.enable -> cfg.ssl.certFile != null; + message = "Please provide a certificate to use for SSL encryption."; + } + { + assertion = cfg.ssl.enable -> cfg.ssl.keyFile != null; + message = "Please provide a private key to use for SSL encryption."; + } + { + assertion = cfg.auth.enable -> cfg.auth.user != null; + message = "Please provide a username to use for HTTP authentication."; + } + { + assertion = cfg.auth.enable -> cfg.auth.passwordFile != null; + message = "Please provide a password file to use for HTTP authentication."; + } + ]; + + users.groups.ubridge = lib.mkIf cfg.ubridge.enable { }; + + security.wrappers.ubridge = lib.mkIf cfg.ubridge.enable { + capabilities = "cap_net_raw,cap_net_admin=eip"; + group = "ubridge"; + owner = "root"; + permissions = "u=rwx,g=rx,o=r"; + source = lib.getExe cfg.ubridge.package; + }; + + services.gns3-server.settings = lib.mkMerge [ + { + Server = { + appliances_path = lib.mkDefault "/var/lib/gns3/appliances"; + configs_path = lib.mkDefault "/var/lib/gns3/configs"; + images_path = lib.mkDefault "/var/lib/gns3/images"; + projects_path = lib.mkDefault "/var/lib/gns3/projects"; + symbols_path = lib.mkDefault "/var/lib/gns3/symbols"; + }; + } + (lib.mkIf (cfg.ubridge.enable) { + Server.ubridge_path = lib.mkDefault (lib.getExe cfg.ubridge.package); + }) + (lib.mkIf (cfg.auth.enable) { + Server = { + auth = lib.mkDefault (lib.boolToString cfg.auth.enable); + user = lib.mkDefault cfg.auth.user; + password = lib.mkDefault "@AUTH_PASSWORD@"; + }; + }) + (lib.mkIf (cfg.vpcs.enable) { + VPCS.vpcs_path = lib.mkDefault (lib.getExe cfg.vpcs.package); + }) + (lib.mkIf (cfg.dynamips.enable) { + Dynamips.dynamips_path = lib.mkDefault (lib.getExe cfg.dynamips.package); + }) + ]; + + systemd.services.gns3-server = let + commandArgs = lib.cli.toGNUCommandLineShell { } { + config = "/etc/gns3/gns3_server.conf"; + pid = "/run/gns3/server.pid"; + log = cfg.log.file; + ssl = cfg.ssl.enable; + # These are implicitly not set if `null` + certfile = cfg.ssl.certFile; + certkey = cfg.ssl.keyFile; + }; + in + { + description = "GNS3 Server"; + + after = [ "network.target" "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + + # configFile cannot be stored in RuntimeDirectory, because GNS3 + # uses the `--config` base path to stores supplementary configuration files at runtime. + # + preStart = '' + install -m660 ${configFile} /etc/gns3/gns3_server.conf + + ${lib.optionalString cfg.auth.enable '' + ${pkgs.replace-secret}/bin/replace-secret \ + '@AUTH_PASSWORD@' \ + "''${CREDENTIALS_DIRECTORY}/AUTH_PASSWORD" \ + /etc/gns3/gns3_server.conf + ''} + ''; + + path = lib.optional flags.enableLibvirtd pkgs.qemu; + + reloadTriggers = [ configFile ]; + + serviceConfig = { + ConfigurationDirectory = "gns3"; + ConfigurationDirectoryMode = "0750"; + DynamicUser = true; + Environment = "HOME=%S/gns3"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStart = "${lib.getExe cfg.package} ${commandArgs}"; + Group = "gns3"; + LimitNOFILE = 16384; + LoadCredential = lib.mkIf cfg.auth.enable [ "AUTH_PASSWORD:${cfg.auth.passwordFile}" ]; + LogsDirectory = "gns3"; + LogsDirectoryMode = "0750"; + PIDFile = "/run/gns3/server.pid"; + Restart = "on-failure"; + RestartSec = 5; + RuntimeDirectory = "gns3"; + StateDirectory = "gns3"; + StateDirectoryMode = "0750"; + SupplementaryGroups = lib.optional flags.enableDocker "docker" + ++ lib.optional flags.enableLibvirtd "libvirtd" + ++ lib.optional cfg.ubridge.enable "ubridge"; + User = "gns3"; + WorkingDirectory = "%S/gns3"; + + # Hardening + DeviceAllow = lib.optional flags.enableLibvirtd "/dev/kvm"; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateTmp = true; + PrivateUsers = true; + # Don't restrict ProcSubset because python3Packages.psutil requires read access to /proc/stat + # ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_NETLINK" + "AF_UNIX" + "AF_PACKET" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + UMask = "0077"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/harmonia.nix b/nixos/modules/services/networking/harmonia.nix index d0f4a8a6e633..b384ac926137 100644 --- a/nixos/modules/services/networking/harmonia.nix +++ b/nixos/modules/services/networking/harmonia.nix @@ -55,6 +55,7 @@ in ExecStart = lib.getExe cfg.package; User = "harmonia"; Group = "harmonia"; + Restart = "on-failure"; PrivateUsers = true; DeviceAllow = [ "" ]; UMask = "0066"; diff --git a/nixos/modules/services/networking/jigasi.nix b/nixos/modules/services/networking/jigasi.nix index 8d2d25c6edfc..e701689031b1 100644 --- a/nixos/modules/services/networking/jigasi.nix +++ b/nixos/modules/services/networking/jigasi.nix @@ -96,7 +96,7 @@ in config = mkOption { type = attrsOf str; default = { }; - example = literalExample '' + example = literalExpression '' { "org.jitsi.jigasi.auth.URL" = "XMPP:jitsi-meet.example.com"; } diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix index 2f922a026a3a..5ca705976c41 100644 --- a/nixos/modules/services/networking/kea.nix +++ b/nixos/modules/services/networking/kea.nix @@ -254,6 +254,8 @@ in DynamicUser = true; User = "kea"; ConfigurationDirectory = "kea"; + RuntimeDirectory = "kea"; + RuntimeDirectoryPreserve = true; StateDirectory = "kea"; UMask = "0077"; }; @@ -288,8 +290,8 @@ in ]; environment = { - KEA_PIDFILE_DIR = "/run/kea-ctrl-agent"; - KEA_LOCKFILE_DIR = "/run/kea-ctrl-agent"; + KEA_PIDFILE_DIR = "/run/kea"; + KEA_LOCKFILE_DIR = "/run/kea"; }; restartTriggers = [ @@ -300,7 +302,6 @@ in ExecStart = "${package}/bin/kea-ctrl-agent -c /etc/kea/ctrl-agent.conf ${lib.escapeShellArgs cfg.ctrl-agent.extraArgs}"; KillMode = "process"; Restart = "on-failure"; - RuntimeDirectory = "kea-ctrl-agent"; } // commonServiceConfig; }; }) @@ -329,8 +330,8 @@ in ]; environment = { - KEA_PIDFILE_DIR = "/run/kea-dhcp4"; - KEA_LOCKFILE_DIR = "/run/kea-dhcp4"; + KEA_PIDFILE_DIR = "/run/kea"; + KEA_LOCKFILE_DIR = "/run/kea"; }; restartTriggers = [ @@ -348,7 +349,6 @@ in "CAP_NET_BIND_SERVICE" "CAP_NET_RAW" ]; - RuntimeDirectory = "kea-dhcp4"; } // commonServiceConfig; }; }) @@ -377,8 +377,8 @@ in ]; environment = { - KEA_PIDFILE_DIR = "/run/kea-dhcp6"; - KEA_LOCKFILE_DIR = "/run/kea-dhcp6"; + KEA_PIDFILE_DIR = "/run/kea"; + KEA_LOCKFILE_DIR = "/run/kea"; }; restartTriggers = [ @@ -394,7 +394,6 @@ in CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; - RuntimeDirectory = "kea-dhcp6"; } // commonServiceConfig; }; }) @@ -423,8 +422,8 @@ in ]; environment = { - KEA_PIDFILE_DIR = "/run/kea-dhcp-ddns"; - KEA_LOCKFILE_DIR = "/run/kea-dhcp-ddns"; + KEA_PIDFILE_DIR = "/run/kea"; + KEA_LOCKFILE_DIR = "/run/kea"; }; restartTriggers = [ @@ -439,7 +438,6 @@ in CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; - RuntimeDirectory = "kea-dhcp-ddns"; } // commonServiceConfig; }; }) diff --git a/nixos/modules/services/networking/miniupnpd.nix b/nixos/modules/services/networking/miniupnpd.nix index 64aacaf35040..116298dc6b1d 100644 --- a/nixos/modules/services/networking/miniupnpd.nix +++ b/nixos/modules/services/networking/miniupnpd.nix @@ -13,8 +13,17 @@ let listening_ip=${range} '') cfg.internalIPs} + ${lib.optionalString (firewall == "nftables") '' + upnp_table_name=miniupnpd + upnp_nat_table_name=miniupnpd + ''} + ${cfg.appendConfig} ''; + firewall = if config.networking.nftables.enable then "nftables" else "iptables"; + miniupnpd = pkgs.miniupnpd.override { inherit firewall; }; + firewallScripts = lib.optionals (firewall == "iptables") + ([ "iptables"] ++ lib.optional (config.networking.enableIPv6) "ip6tables"); in { options = { @@ -57,20 +66,50 @@ in }; config = mkIf cfg.enable { - networking.firewall.extraCommands = '' - ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_init.sh -i ${cfg.externalInterface} - ''; + networking.firewall.extraCommands = lib.mkIf (firewallScripts != []) (builtins.concatStringsSep "\n" (map (fw: '' + EXTIF=${cfg.externalInterface} ${pkgs.bash}/bin/bash -x ${miniupnpd}/etc/miniupnpd/${fw}_init.sh + '') firewallScripts)); + + networking.firewall.extraStopCommands = lib.mkIf (firewallScripts != []) (builtins.concatStringsSep "\n" (map (fw: '' + EXTIF=${cfg.externalInterface} ${pkgs.bash}/bin/bash -x ${miniupnpd}/etc/miniupnpd/${fw}_removeall.sh + '') firewallScripts)); - networking.firewall.extraStopCommands = '' - ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_removeall.sh -i ${cfg.externalInterface} - ''; + networking.nftables = lib.mkIf (firewall == "nftables") { + # see nft_init in ${miniupnpd-nftables}/etc/miniupnpd + tables.miniupnpd = { + family = "inet"; + # The following is omitted because it's expected that the firewall is to be responsible for it. + # + # chain forward { + # type filter hook forward priority filter; policy drop; + # jump miniupnpd + # } + # + # Otherwise, it quickly gets ugly with (potentially) two forward chains with "policy drop". + # This means the chain "miniupnpd" never actually gets triggered and is simply there to satisfy + # miniupnpd. If you're doing it yourself (without networking.firewall), the easiest way to get + # it to work is adding a rule "ct status dnat accept" - this is what networking.firewall does. + # If you don't want to simply accept forwarding for all "ct status dnat" packets, override + # upnp_table_name with whatever your table is, create a chain "miniupnpd" in your table and + # jump into it from your forward chain. + content = '' + chain miniupnpd {} + chain prerouting_miniupnpd { + type nat hook prerouting priority dstnat; policy accept; + } + chain postrouting_miniupnpd { + type nat hook postrouting priority srcnat; policy accept; + } + ''; + }; + }; systemd.services.miniupnpd = { description = "MiniUPnP daemon"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - ExecStart = "${pkgs.miniupnpd}/bin/miniupnpd -f ${configFile}"; + ExecStart = "${miniupnpd}/bin/miniupnpd -f ${configFile}"; PIDFile = "/run/miniupnpd.pid"; Type = "forking"; }; diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index b9ebbfbd9a29..e13876172dac 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -196,7 +196,7 @@ in before = [ "sshd.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { - Type = "simple"; + Type = "notify"; Restart = "always"; ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}"; UMask = "0027"; diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index d32712c8243d..c96439cf2641 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -565,7 +565,10 @@ in wantedBy = [ "network-online.target" ]; }; - systemd.services.ModemManager.aliases = [ "dbus-org.freedesktop.ModemManager1.service" ]; + systemd.services.ModemManager = { + aliases = [ "dbus-org.freedesktop.ModemManager1.service" ]; + path = lib.optionals (cfg.fccUnlockScripts != []) [ pkgs.libqmi pkgs.libmbim ]; + }; systemd.services.NetworkManager-dispatcher = { wantedBy = [ "network.target" ]; diff --git a/nixos/modules/services/networking/ntp/ntpd-rs.nix b/nixos/modules/services/networking/ntp/ntpd-rs.nix new file mode 100644 index 000000000000..a10b570f30bc --- /dev/null +++ b/nixos/modules/services/networking/ntp/ntpd-rs.nix @@ -0,0 +1,89 @@ +{ lib, config, pkgs, ... }: + +let + cfg = config.services.ntpd-rs; + format = pkgs.formats.toml { }; + configFile = format.generate "ntpd-rs.toml" cfg.settings; +in +{ + options.services.ntpd-rs = { + enable = lib.mkEnableOption "Network Time Service (ntpd-rs)"; + metrics.enable = lib.mkEnableOption "ntpd-rs Prometheus Metrics Exporter"; + + package = lib.mkPackageOption pkgs "ntpd-rs" { }; + + useNetworkingTimeServers = lib.mkOption { + type = lib.types.bool; + default = true; + description = lib.mdDoc '' + Use source time servers from {var}`networking.timeServers` in config. + ''; + }; + + settings = lib.mkOption { + type = lib.types.submodule { + freeformType = format.type; + }; + default = { }; + description = lib.mdDoc '' + Settings to write to {file}`ntp.toml` + + See <https://docs.ntpd-rs.pendulum-project.org/man/ntp.toml.5> + for more information about available options. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = !config.services.timesyncd.enable; + message = '' + `ntpd-rs` is not compatible with `services.timesyncd`. Please disable one of them. + ''; + } + ]; + + environment.systemPackages = [ cfg.package ]; + systemd.packages = [ cfg.package ]; + + services.timesyncd.enable = false; + systemd.services.systemd-timedated.environment = { + SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd-rs.service"; + }; + + services.ntpd-rs.settings = { + observability = { + observation-path = lib.mkDefault "/var/run/ntpd-rs/observe"; + }; + source = lib.mkIf cfg.useNetworkingTimeServers (map + (ts: { + mode = "server"; + address = ts; + }) + config.networking.timeServers); + }; + + systemd.services.ntpd-rs = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = ""; + Group = ""; + DynamicUser = true; + ExecStart = [ "" "${lib.makeBinPath [ cfg.package ]}/ntp-daemon --config=${configFile}" ]; + }; + }; + + systemd.services.ntp-rs-metrics = lib.mkIf cfg.metrics.enable { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = ""; + Group = ""; + DynamicUser = true; + ExecStart = [ "" "${lib.makeBinPath [ cfg.package ]}/bin/ntp-metrics-exporter --config=${configFile}" ]; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ fpletz ]; +} diff --git a/nixos/modules/services/networking/quicktun.nix b/nixos/modules/services/networking/quicktun.nix index 7aed972adc88..2d44659f2080 100644 --- a/nixos/modules/services/networking/quicktun.nix +++ b/nixos/modules/services/networking/quicktun.nix @@ -1,94 +1,153 @@ -{ config, pkgs, lib, ... }: +{ options, config, pkgs, lib, ... }: let + inherit (lib) mkOption mdDoc types mkIf; + opt = options.services.quicktun; cfg = config.services.quicktun; - in - -with lib; - { options = { - services.quicktun = mkOption { default = { }; - description = lib.mdDoc "QuickTun tunnels"; - type = types.attrsOf (types.submodule { + description = mdDoc '' + QuickTun tunnels. + + See <http://wiki.ucis.nl/QuickTun> for more information about available options. + ''; + type = types.attrsOf (types.submodule ({ name, ... }: let + qtcfg = cfg.${name}; + in { options = { tunMode = mkOption { - type = types.int; - default = 0; - example = 1; - description = lib.mdDoc ""; + type = with types; coercedTo bool (b: if b then 1 else 0) (ints.between 0 1); + default = false; + example = true; + description = mdDoc "Whether to operate in tun (IP) or tap (Ethernet) mode."; }; remoteAddress = mkOption { type = types.str; + default = "0.0.0.0"; example = "tunnel.example.com"; - description = lib.mdDoc ""; + description = mdDoc '' + IP address or hostname of the remote end (use `0.0.0.0` for a floating/dynamic remote endpoint). + ''; }; localAddress = mkOption { - type = types.str; + type = with types; nullOr str; + default = null; example = "0.0.0.0"; - description = lib.mdDoc ""; + description = mdDoc "IP address or hostname of the local end."; }; localPort = mkOption { - type = types.int; + type = types.port; default = 2998; - description = lib.mdDoc ""; + description = mdDoc "Local UDP port."; }; remotePort = mkOption { - type = types.int; - default = 2998; - description = lib.mdDoc ""; + type = types.port; + default = qtcfg.localPort; + defaultText = lib.literalExpression "config.services.quicktun.<name>.localPort"; + description = mdDoc " remote UDP port"; }; remoteFloat = mkOption { - type = types.int; - default = 0; - description = lib.mdDoc ""; + type = with types; coercedTo bool (b: if b then 1 else 0) (ints.between 0 1); + default = false; + example = true; + description = mdDoc '' + Whether to allow the remote address and port to change when properly encrypted packets are received. + ''; }; protocol = mkOption { - type = types.str; + type = types.enum [ "raw" "nacl0" "nacltai" "salty" ]; default = "nacltai"; - description = lib.mdDoc ""; + description = mdDoc "Which protocol to use."; }; privateKey = mkOption { - type = types.str; - description = lib.mdDoc ""; + type = with types; nullOr str; + default = null; + description = mdDoc '' + Local secret key in hexadecimal form. + + ::: {.warning} + This option is deprecated. Please use {var}`services.quicktun.<name>.privateKeyFile` instead. + ::: + + ::: {.note} + Not needed when {var}`services.quicktun.<name>.protocol` is set to `raw`. + ::: + ''; + }; + + privateKeyFile = mkOption { + type = with types; nullOr path; + # This is a hack to deprecate `privateKey` without using `mkChangedModuleOption` + default = if qtcfg.privateKey == null then null else pkgs.writeText "quickttun-key-${name}" qtcfg.privateKey; + defaultText = "null"; + description = mdDoc '' + Path to file containing local secret key in binary or hexadecimal form. + + ::: {.note} + Not needed when {var}`services.quicktun.<name>.protocol` is set to `raw`. + ::: + ''; }; publicKey = mkOption { - type = types.str; - description = lib.mdDoc ""; + type = with types; nullOr str; + default = null; + description = mdDoc '' + Remote public key in hexadecimal form. + + ::: {.note} + Not needed when {var}`services.quicktun.<name>.protocol` is set to `raw`. + ::: + ''; }; timeWindow = mkOption { - type = types.int; + type = types.ints.unsigned; default = 5; - description = lib.mdDoc ""; + description = mdDoc '' + Allowed time window for first received packet in seconds (positive number allows packets from history) + ''; }; upScript = mkOption { - type = types.lines; - default = ""; - description = lib.mdDoc ""; + type = with types; nullOr lines; + default = null; + description = mdDoc '' + Run specified command or script after the tunnel device has been opened. + ''; }; }; - }); + })); }; - }; - config = mkIf (cfg != []) { - systemd.services = foldr (a: b: a // b) {} ( - mapAttrsToList (name: qtcfg: { + config = { + warnings = lib.pipe cfg [ + (lib.mapAttrsToList (name: value: if value.privateKey != null then name else null)) + (builtins.filter (n: n != null)) + (map (n: " - services.quicktun.${n}.privateKey")) + (services: lib.optional (services != [ ]) '' + `services.quicktun.<name>.privateKey` is deprecated. + Please use `services.quicktun.<name>.privateKeyFile` instead. + + Offending options: + ${lib.concatStringsSep "\n" services} + '') + ]; + + systemd.services = lib.mkMerge ( + lib.mapAttrsToList (name: qtcfg: { "quicktun-${name}" = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; @@ -96,14 +155,14 @@ with lib; INTERFACE = name; TUN_MODE = toString qtcfg.tunMode; REMOTE_ADDRESS = qtcfg.remoteAddress; - LOCAL_ADDRESS = qtcfg.localAddress; + LOCAL_ADDRESS = mkIf (qtcfg.localAddress != null) (qtcfg.localAddress); LOCAL_PORT = toString qtcfg.localPort; REMOTE_PORT = toString qtcfg.remotePort; REMOTE_FLOAT = toString qtcfg.remoteFloat; - PRIVATE_KEY = qtcfg.privateKey; - PUBLIC_KEY = qtcfg.publicKey; + PRIVATE_KEY_FILE = mkIf (qtcfg.privateKeyFile != null) qtcfg.privateKeyFile; + PUBLIC_KEY = mkIf (qtcfg.publicKey != null) qtcfg.publicKey; TIME_WINDOW = toString qtcfg.timeWindow; - TUN_UP_SCRIPT = pkgs.writeScript "quicktun-${name}-up.sh" qtcfg.upScript; + TUN_UP_SCRIPT = mkIf (qtcfg.upScript != null) (pkgs.writeScript "quicktun-${name}-up.sh" qtcfg.upScript); SUID = "nobody"; }; serviceConfig = { @@ -114,5 +173,4 @@ with lib; }) cfg ); }; - } diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index f54ce5917438..39793922ab51 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixos/modules/services/networking/ssh/sshd.nix @@ -674,7 +674,11 @@ in (lport: "sshd -G -T -C lport=${toString lport} -f ${sshconf} > /dev/null") cfg.ports} ${concatMapStringsSep "\n" - (la: "sshd -G -T -C ${escapeShellArg "laddr=${la.addr},lport=${toString la.port}"} -f ${sshconf} > /dev/null") + (la: + concatMapStringsSep "\n" + (port: "sshd -G -T -C ${escapeShellArg "laddr=${la.addr},lport=${toString port}"} -f ${sshconf} > /dev/null") + (if la.port != null then [ la.port ] else cfg.ports) + ) cfg.listenAddresses} touch $out '') diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index 99d4d9eeffcc..e0425792431e 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -559,6 +559,15 @@ in { ''; }; + databaseDir = mkOption { + type = types.path; + description = lib.mdDoc '' + The directory containing the database and logs. + ''; + default = cfg.configDir; + defaultText = literalExpression "config.${opt.configDir}"; + }; + extraFlags = mkOption { type = types.listOf types.str; default = []; @@ -660,7 +669,7 @@ in { -no-browser \ -gui-address=${if isUnixGui then "unix://" else ""}${cfg.guiAddress} \ -config=${cfg.configDir} \ - -data=${cfg.dataDir} \ + -data=${cfg.databaseDir} \ ${escapeShellArgs cfg.extraFlags} ''; MemoryDenyWriteExecute = true; diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix index 3822df81063d..1070e4e25296 100644 --- a/nixos/modules/services/networking/tailscale.nix +++ b/nixos/modules/services/networking/tailscale.nix @@ -100,8 +100,8 @@ in { }; systemd.services.tailscaled-autoconnect = mkIf (cfg.authKeyFile != null) { - after = ["tailscale.service"]; - wants = ["tailscale.service"]; + after = ["tailscaled.service"]; + wants = ["tailscaled.service"]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; diff --git a/nixos/modules/services/networking/tinyproxy.nix b/nixos/modules/services/networking/tinyproxy.nix index 42d45c460c2e..8ff12b52f10c 100644 --- a/nixos/modules/services/networking/tinyproxy.nix +++ b/nixos/modules/services/networking/tinyproxy.nix @@ -85,7 +85,7 @@ in User = "tinyproxy"; Group = "tinyproxy"; Type = "simple"; - ExecStart = "${getExe pkgs.tinyproxy} -d -c ${configFile}"; + ExecStart = "${getExe cfg.package} -d -c ${configFile}"; ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID"; KillSignal = "SIGINT"; TimeoutStopSec = "30s"; diff --git a/nixos/modules/services/networking/vdirsyncer.nix b/nixos/modules/services/networking/vdirsyncer.nix index 6a069943434d..165dc70f0876 100644 --- a/nixos/modules/services/networking/vdirsyncer.nix +++ b/nixos/modules/services/networking/vdirsyncer.nix @@ -20,9 +20,11 @@ let else pkgs.writeText "vdirsyncer-${name}.conf" (toIniJson ( { - general = cfg'.config.general // (lib.optionalAttrs (cfg'.config.statusPath == null) { - status_path = "/var/lib/vdirsyncer/${name}"; - }); + general = cfg'.config.general // { + status_path = if cfg'.config.statusPath == null + then "/var/lib/vdirsyncer/${name}" + else cfg'.config.statusPath; + }; } // ( mapAttrs' (name: nameValuePair "pair ${name}") cfg'.config.pairs ) // ( diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix index 90d9c68433cf..4586550ed75e 100644 --- a/nixos/modules/services/networking/wpa_supplicant.nix +++ b/nixos/modules/services/networking/wpa_supplicant.nix @@ -107,6 +107,10 @@ let stopIfChanged = false; path = [ package ]; + # if `userControl.enable`, the supplicant automatically changes the permissions + # and owning group of the runtime dir; setting `umask` ensures the generated + # config file isn't readable (except to root); see nixpkgs#267693 + serviceConfig.UMask = "066"; serviceConfig.RuntimeDirectory = "wpa_supplicant"; serviceConfig.RuntimeDirectoryMode = "700"; serviceConfig.EnvironmentFile = mkIf (cfg.environmentFile != null) diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix index 514753687d69..9173e7eb3457 100644 --- a/nixos/modules/services/networking/yggdrasil.nix +++ b/nixos/modules/services/networking/yggdrasil.nix @@ -137,16 +137,24 @@ in message = "networking.enableIPv6 must be true for yggdrasil to work"; }]; - system.activationScripts.yggdrasil = mkIf cfg.persistentKeys '' - if [ ! -e ${keysPath} ] - then - mkdir --mode=700 -p ${builtins.dirOf keysPath} - ${binYggdrasil} -genconf -json \ - | ${pkgs.jq}/bin/jq \ - 'to_entries|map(select(.key|endswith("Key")))|from_entries' \ - > ${keysPath} - fi - ''; + # This needs to be a separate service. The yggdrasil service fails if + # this is put into its preStart. + systemd.services.yggdrasil-persistent-keys = lib.mkIf cfg.persistentKeys { + wantedBy = [ "multi-user.target" ]; + before = [ "yggdrasil.service" ]; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + script = '' + if [ ! -e ${keysPath} ] + then + mkdir --mode=700 -p ${builtins.dirOf keysPath} + ${binYggdrasil} -genconf -json \ + | ${pkgs.jq}/bin/jq \ + 'to_entries|map(select(.key|endswith("Key")))|from_entries' \ + > ${keysPath} + fi + ''; + }; systemd.services.yggdrasil = { description = "Yggdrasil Network Service"; diff --git a/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix index 994e01d4980e..60615d553041 100644 --- a/nixos/modules/services/networking/zerotierone.nix +++ b/nixos/modules/services/networking/zerotierone.nix @@ -4,6 +4,8 @@ with lib; let cfg = config.services.zerotierone; + localConfFile = pkgs.writeText "zt-local.conf" (builtins.toJSON cfg.localConf); + localConfFilePath = "/var/lib/zerotier-one/local.conf"; in { options.services.zerotierone.enable = mkEnableOption (lib.mdDoc "ZeroTierOne"); @@ -29,6 +31,19 @@ in options.services.zerotierone.package = mkPackageOption pkgs "zerotierone" { }; + options.services.zerotierone.localConf = mkOption { + default = null; + description = mdDoc '' + Optional configuration to be written to the Zerotier JSON-based local.conf. + If set, the configuration will be symlinked to `/var/lib/zerotier-one/local.conf` at build time. + To understand the configuration format, refer to https://docs.zerotier.com/config/#local-configuration-options. + ''; + example = { + settings.allowTcpFallbackRelay = false; + }; + type = types.nullOr types.attrs; + }; + config = mkIf cfg.enable { systemd.services.zerotierone = { description = "ZeroTierOne"; @@ -45,7 +60,17 @@ in chown -R root:root /var/lib/zerotier-one '' + (concatMapStrings (netId: '' touch "/var/lib/zerotier-one/networks.d/${netId}.conf" - '') cfg.joinNetworks); + '') cfg.joinNetworks) + optionalString (cfg.localConf != null) '' + if [ -L "${localConfFilePath}" ] + then + rm ${localConfFilePath} + elif [ -f "${localConfFilePath}" ] + then + mv ${localConfFilePath} ${localConfFilePath}.bak + fi + ln -s ${localConfFile} ${localConfFilePath} + ''; + serviceConfig = { ExecStart = "${cfg.package}/bin/zerotier-one -p${toString cfg.port}"; Restart = "always"; diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix index 3a2744303474..1f044384a5b8 100644 --- a/nixos/modules/services/printing/cupsd.nix +++ b/nixos/modules/services/printing/cupsd.nix @@ -4,9 +4,10 @@ with lib; let - inherit (pkgs) cups cups-pk-helper cups-filters xdg-utils; + inherit (pkgs) cups-pk-helper cups-filters xdg-utils; cfg = config.services.printing; + cups = cfg.package; avahiEnabled = config.services.avahi.enable; polkitEnabled = config.security.polkit.enable; @@ -140,6 +141,8 @@ in ''; }; + package = lib.mkPackageOption pkgs "cups" {}; + stateless = mkOption { type = types.bool; default = false; diff --git a/nixos/modules/services/security/bitwarden-directory-connector-cli.nix b/nixos/modules/services/security/bitwarden-directory-connector-cli.nix new file mode 100644 index 000000000000..18c02e22fd7e --- /dev/null +++ b/nixos/modules/services/security/bitwarden-directory-connector-cli.nix @@ -0,0 +1,323 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.services.bitwarden-directory-connector-cli; +in { + options.services.bitwarden-directory-connector-cli = { + enable = mkEnableOption "Bitwarden Directory Connector"; + + package = mkPackageOption pkgs "bitwarden-directory-connector-cli" {}; + + domain = mkOption { + type = types.str; + description = lib.mdDoc "The domain the Bitwarden/Vaultwarden is accessible on."; + example = "https://vaultwarden.example.com"; + }; + + user = mkOption { + type = types.str; + description = lib.mdDoc "User to run the program."; + default = "bwdc"; + }; + + interval = mkOption { + type = types.str; + default = "*:0,15,30,45"; + description = lib.mdDoc "The interval when to run the connector. This uses systemd's OnCalendar syntax."; + }; + + ldap = mkOption { + description = lib.mdDoc '' + Options to configure the LDAP connection. + If you used the desktop application to test the configuration you can find the settings by searching for `ldap` in `~/.config/Bitwarden\ Directory\ Connector/data.json`. + ''; + default = {}; + type = types.submodule ({ + config, + options, + ... + }: { + freeformType = types.attrsOf (pkgs.formats.json {}).type; + + config.finalJSON = builtins.toJSON (removeAttrs config (filter (x: x == "finalJSON" || ! options.${x}.isDefined or false) (attrNames options))); + + options = { + finalJSON = mkOption { + type = (pkgs.formats.json {}).type; + internal = true; + readOnly = true; + visible = false; + }; + + ssl = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Whether to use TLS."; + }; + startTls = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Whether to use STARTTLS."; + }; + + hostname = mkOption { + type = types.str; + description = lib.mdDoc "The host the LDAP is accessible on."; + example = "ldap.example.com"; + }; + + port = mkOption { + type = types.port; + default = 389; + description = lib.mdDoc "Port LDAP is accessible on."; + }; + + ad = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Whether the LDAP Server is an Active Directory."; + }; + + pagedSearch = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Whether the LDAP server paginates search results."; + }; + + rootPath = mkOption { + type = types.str; + description = lib.mdDoc "Root path for LDAP."; + example = "dc=example,dc=com"; + }; + + username = mkOption { + type = types.str; + description = lib.mdDoc "The user to authenticate as."; + example = "cn=admin,dc=example,dc=com"; + }; + }; + }); + }; + + sync = mkOption { + description = lib.mdDoc '' + Options to configure what gets synced. + If you used the desktop application to test the configuration you can find the settings by searching for `sync` in `~/.config/Bitwarden\ Directory\ Connector/data.json`. + ''; + default = {}; + type = types.submodule ({ + config, + options, + ... + }: { + freeformType = types.attrsOf (pkgs.formats.json {}).type; + + config.finalJSON = builtins.toJSON (removeAttrs config (filter (x: x == "finalJSON" || ! options.${x}.isDefined or false) (attrNames options))); + + options = { + finalJSON = mkOption { + type = (pkgs.formats.json {}).type; + internal = true; + readOnly = true; + visible = false; + }; + + removeDisabled = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc "Remove users from bitwarden groups if no longer in the ldap group."; + }; + + overwriteExisting = mkOption { + type = types.bool; + default = false; + description = + lib.mdDoc "Remove and re-add users/groups, See https://bitwarden.com/help/user-group-filters/#overwriting-syncs for more details."; + }; + + largeImport = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Enable if you are syncing more than 2000 users/groups."; + }; + + memberAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute that lists members in a LDAP group."; + example = "uniqueMember"; + }; + + creationDateAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute that lists a user's creation date."; + example = "whenCreated"; + }; + + useEmailPrefixSuffix = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "If a user has no email address, combine a username prefix with a suffix value to form an email."; + }; + emailPrefixAttribute = mkOption { + type = types.str; + description = lib.mdDoc "The attribute that contains the users username."; + example = "accountName"; + }; + emailSuffix = mkOption { + type = types.str; + description = lib.mdDoc "Suffix for the email, normally @example.com."; + example = "@example.com"; + }; + + users = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Sync users."; + }; + userPath = mkOption { + type = types.str; + description = lib.mdDoc "User directory, relative to root."; + default = "ou=users"; + }; + userObjectClass = mkOption { + type = types.str; + description = lib.mdDoc "Class that users must have."; + default = "inetOrgPerson"; + }; + userEmailAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute for a users email."; + default = "mail"; + }; + userFilter = mkOption { + type = types.str; + description = lib.mdDoc "LDAP filter for users."; + example = "(memberOf=cn=sales,ou=groups,dc=example,dc=com)"; + default = ""; + }; + + groups = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Whether to sync ldap groups into BitWarden."; + }; + groupPath = mkOption { + type = types.str; + description = lib.mdDoc "Group directory, relative to root."; + default = "ou=groups"; + }; + groupObjectClass = mkOption { + type = types.str; + description = lib.mdDoc "A class that groups will have."; + default = "groupOfNames"; + }; + groupNameAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute for a name of group."; + default = "cn"; + }; + groupFilter = mkOption { + type = types.str; + description = lib.mdDoc "LDAP filter for groups."; + example = "(cn=sales)"; + default = ""; + }; + }; + }); + }; + + secrets = { + ldap = mkOption { + type = types.str; + description = "Path to file that contains LDAP password for user in {option}`ldap.username"; + }; + + bitwarden = { + client_path_id = mkOption { + type = types.str; + description = "Path to file that contains Client ID."; + }; + client_path_secret = mkOption { + type = types.str; + description = "Path to file that contains Client Secret."; + }; + }; + }; + }; + + config = mkIf cfg.enable { + users.groups."${cfg.user}" = {}; + users.users."${cfg.user}" = { + isSystemUser = true; + group = cfg.user; + }; + + systemd = { + timers.bitwarden-directory-connector-cli = { + description = "Sync timer for Bitwarden Directory Connector"; + wantedBy = ["timers.target"]; + after = ["network-online.target"]; + timerConfig = { + OnCalendar = cfg.interval; + Unit = "bitwarden-directory-connector-cli.service"; + Persistent = true; + }; + }; + + services.bitwarden-directory-connector-cli = { + description = "Main process for Bitwarden Directory Connector"; + path = [pkgs.jq]; + + environment = { + BITWARDENCLI_CONNECTOR_APPDATA_DIR = "/tmp"; + BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS = "true"; + }; + + serviceConfig = { + Type = "oneshot"; + User = "${cfg.user}"; + PrivateTmp = true; + preStart = '' + set -eo pipefail + + # create the config file + ${lib.getExe cfg.package} data-file + touch /tmp/data.json.tmp + chmod 600 /tmp/data.json{,.tmp} + + ${lib.getExe cfg.package} config server ${cfg.domain} + + # now login to set credentials + export BW_CLIENTID="$(< ${escapeShellArg cfg.secrets.bitwarden.client_path_id})" + export BW_CLIENTSECRET="$(< ${escapeShellArg cfg.secrets.bitwarden.client_path_secret})" + ${lib.getExe cfg.package} login + + jq '.authenticatedAccounts[0] as $account + | .[$account].directoryConfigurations.ldap |= $ldap_data + | .[$account].directorySettings.organizationId |= $orgID + | .[$account].directorySettings.sync |= $sync_data' \ + --argjson ldap_data ${escapeShellArg cfg.ldap.finalJSON} \ + --arg orgID "''${BW_CLIENTID//organization.}" \ + --argjson sync_data ${escapeShellArg cfg.sync.finalJSON} \ + /tmp/data.json \ + > /tmp/data.json.tmp + + mv -f /tmp/data.json.tmp /tmp/data.json + + # final config + ${lib.getExe cfg.package} config directory 0 + ${lib.getExe cfg.package} config ldap.password --secretfile ${cfg.secrets.ldap} + ''; + + ExecStart = "${lib.getExe cfg.package} sync"; + }; + }; + }; + }; + + meta.maintainers = with maintainers; [Silver-Golden]; +} diff --git a/nixos/modules/services/security/munge.nix b/nixos/modules/services/security/munge.nix index 4d6fe33f697b..9d306c205f94 100644 --- a/nixos/modules/services/security/munge.nix +++ b/nixos/modules/services/security/munge.nix @@ -45,19 +45,25 @@ in systemd.services.munged = { wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; + wants = [ + "network-online.target" + "time-sync.target" + ]; + after = [ + "network-online.target" + "time-sync.target" + ]; path = [ pkgs.munge pkgs.coreutils ]; serviceConfig = { ExecStartPre = "+${pkgs.coreutils}/bin/chmod 0400 ${cfg.password}"; - ExecStart = "${pkgs.munge}/bin/munged --syslog --key-file ${cfg.password}"; - PIDFile = "/run/munge/munged.pid"; - ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStart = "${pkgs.munge}/bin/munged --foreground --key-file ${cfg.password}"; User = "munge"; Group = "munge"; StateDirectory = "munge"; StateDirectoryMode = "0711"; + Restart = "on-failure"; RuntimeDirectory = "munge"; }; diff --git a/nixos/modules/services/security/shibboleth-sp.nix b/nixos/modules/services/security/shibboleth-sp.nix index e7897c3324cf..975de1efa2f2 100644 --- a/nixos/modules/services/security/shibboleth-sp.nix +++ b/nixos/modules/services/security/shibboleth-sp.nix @@ -1,44 +1,43 @@ -{pkgs, config, lib, ...}: +{ config, lib, pkgs, ... }: -with lib; let cfg = config.services.shibboleth-sp; in { options = { services.shibboleth-sp = { - enable = mkOption { - type = types.bool; + enable = lib.mkOption { + type = lib.types.bool; default = false; description = lib.mdDoc "Whether to enable the shibboleth service"; }; - configFile = mkOption { - type = types.path; - example = literalExpression ''"''${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml"''; + configFile = lib.mkOption { + type = lib.types.path; + example = lib.literalExpression ''"''${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml"''; description = lib.mdDoc "Path to shibboleth config file"; }; - fastcgi.enable = mkOption { - type = types.bool; + fastcgi.enable = lib.mkOption { + type = lib.types.bool; default = false; description = lib.mdDoc "Whether to include the shibauthorizer and shibresponder FastCGI processes"; }; - fastcgi.shibAuthorizerPort = mkOption { - type = types.int; + fastcgi.shibAuthorizerPort = lib.mkOption { + type = lib.types.int; default = 9100; description = lib.mdDoc "Port for shibauthorizer FastCGI process to bind to"; }; - fastcgi.shibResponderPort = mkOption { - type = types.int; + fastcgi.shibResponderPort = lib.mkOption { + type = lib.types.int; default = 9101; description = lib.mdDoc "Port for shibauthorizer FastCGI process to bind to"; }; }; }; - config = mkIf cfg.enable { + config = lib.mkIf cfg.enable { systemd.services.shibboleth-sp = { description = "Provides SSO and federation for web applications"; after = lib.optionals cfg.fastcgi.enable [ "shibresponder.service" "shibauthorizer.service" ]; @@ -48,7 +47,7 @@ in { }; }; - systemd.services.shibresponder = mkIf cfg.fastcgi.enable { + systemd.services.shibresponder = lib.mkIf cfg.fastcgi.enable { description = "Provides SSO through Shibboleth via FastCGI"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; @@ -59,7 +58,7 @@ in { }; }; - systemd.services.shibauthorizer = mkIf cfg.fastcgi.enable { + systemd.services.shibauthorizer = lib.mkIf cfg.fastcgi.enable { description = "Provides SSO through Shibboleth via FastCGI"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; @@ -71,5 +70,5 @@ in { }; }; - meta.maintainers = with lib.maintainers; [ jammerful ]; + meta.maintainers = with lib.maintainers; [ ]; } diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix index 4ff941251c99..dea20dec1ab4 100644 --- a/nixos/modules/services/security/tor.nix +++ b/nixos/modules/services/security/tor.nix @@ -854,7 +854,7 @@ in BridgeRelay = true; ExtORPort.port = mkDefault "auto"; ServerTransportPlugin.transports = mkDefault ["obfs4"]; - ServerTransportPlugin.exec = mkDefault "${pkgs.obfs4}/bin/obfs4proxy managed"; + ServerTransportPlugin.exec = mkDefault "${lib.getExe pkgs.obfs4} managed"; } // optionalAttrs (cfg.relay.role == "private-bridge") { ExtraInfoStatistics = false; PublishServerDescriptor = false; diff --git a/nixos/modules/services/security/vaultwarden/backup.sh b/nixos/modules/services/security/vaultwarden/backup.sh index 2a3de0ab1dee..7668da5bc88f 100644 --- a/nixos/modules/services/security/vaultwarden/backup.sh +++ b/nixos/modules/services/security/vaultwarden/backup.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # Based on: https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault -if ! mkdir -p "$BACKUP_FOLDER"; then - echo "Could not create backup folder '$BACKUP_FOLDER'" >&2 +if [ ! -d "$BACKUP_FOLDER" ]; then + echo "Backup folder '$BACKUP_FOLDER' does not exist" >&2 exit 1 fi diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix index 14bbfa95a9ca..470db735bf64 100644 --- a/nixos/modules/services/security/vaultwarden/default.nix +++ b/nixos/modules/services/security/vaultwarden/default.nix @@ -55,6 +55,7 @@ in { description = lib.mdDoc '' The directory under which vaultwarden will backup its persistent data. ''; + example = "/var/backup/vaultwarden"; }; config = mkOption { @@ -230,6 +231,13 @@ in { }; wantedBy = [ "multi-user.target" ]; }; + + systemd.tmpfiles.settings = mkIf (cfg.backupDir != null) { + "10-vaultwarden".${cfg.backupDir}.d = { + inherit user group; + mode = "0770"; + }; + }; }; # uses attributes of the linked package diff --git a/nixos/modules/services/system/cachix-watch-store.nix b/nixos/modules/services/system/cachix-watch-store.nix index 992a59cbc075..8aa5f0358fa9 100644 --- a/nixos/modules/services/system/cachix-watch-store.nix +++ b/nixos/modules/services/system/cachix-watch-store.nix @@ -23,6 +23,14 @@ in ''; }; + signingKeyFile = mkOption { + type = types.nullOr types.path; + description = lib.mdDoc '' + Optional file containing a self-managed signing key to sign uploaded store paths. + ''; + default = null; + }; + compressionLevel = mkOption { type = types.nullOr types.int; description = lib.mdDoc "The compression level for ZSTD compression (between 0 and 16)"; @@ -69,7 +77,8 @@ in DynamicUser = true; LoadCredential = [ "cachix-token:${toString cfg.cachixTokenFile}" - ]; + ] + ++ lib.optional (cfg.signingKeyFile != null) "signing-key:${toString cfg.signingKeyFile}"; }; script = let @@ -80,6 +89,7 @@ in in '' export CACHIX_AUTH_TOKEN="$(<"$CREDENTIALS_DIRECTORY/cachix-token")" + ${lib.optionalString (cfg.signingKeyFile != null) ''export CACHIX_SIGNING_KEY="$(<"$CREDENTIALS_DIRECTORY/signing-key")"''} ${lib.escapeShellArgs command} ''; }; diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix index 8d5b25e61762..b47ebc92f93a 100644 --- a/nixos/modules/services/system/dbus.nix +++ b/nixos/modules/services/system/dbus.nix @@ -184,6 +184,11 @@ in aliases = [ "dbus.service" ]; + unitConfig = { + # We get errors when reloading the dbus-broker service + # if /tmp got remounted after this service started + RequiresMountsFor = [ "/tmp" ]; + }; # Don't restart dbus. Bad things tend to happen if we do. reloadIfChanged = true; restartTriggers = [ diff --git a/nixos/modules/services/system/kerberos/default.nix b/nixos/modules/services/system/kerberos/default.nix index 4ed48e463741..486d4b49c195 100644 --- a/nixos/modules/services/system/kerberos/default.nix +++ b/nixos/modules/services/system/kerberos/default.nix @@ -3,7 +3,7 @@ let inherit (lib) mkOption mkIf types length attrNames; cfg = config.services.kerberos_server; - kerberos = config.krb5.kerberos; + kerberos = config.security.krb5.package; aclEntry = { options = { diff --git a/nixos/modules/services/system/kerberos/heimdal.nix b/nixos/modules/services/system/kerberos/heimdal.nix index 816a2a8db061..ecafc9276670 100644 --- a/nixos/modules/services/system/kerberos/heimdal.nix +++ b/nixos/modules/services/system/kerberos/heimdal.nix @@ -4,7 +4,7 @@ let inherit (lib) mkIf concatStringsSep concatMapStrings toList mapAttrs mapAttrsToList; cfg = config.services.kerberos_server; - kerberos = config.krb5.kerberos; + kerberos = config.security.krb5.package; stateDir = "/var/heimdal"; aclFiles = mapAttrs (name: {acl, ...}: pkgs.writeText "${name}.acl" (concatMapStrings (( diff --git a/nixos/modules/services/system/kerberos/mit.nix b/nixos/modules/services/system/kerberos/mit.nix index 112000140453..a654bd1fe7e1 100644 --- a/nixos/modules/services/system/kerberos/mit.nix +++ b/nixos/modules/services/system/kerberos/mit.nix @@ -4,7 +4,7 @@ let inherit (lib) mkIf concatStrings concatStringsSep concatMapStrings toList mapAttrs mapAttrsToList; cfg = config.services.kerberos_server; - kerberos = config.krb5.kerberos; + kerberos = config.security.krb5.package; stateDir = "/var/lib/krb5kdc"; PIDFile = "/run/kdc.pid"; aclMap = { diff --git a/nixos/modules/services/system/zram-generator.nix b/nixos/modules/services/system/zram-generator.nix index 10b9992375cc..429531e5743d 100644 --- a/nixos/modules/services/system/zram-generator.nix +++ b/nixos/modules/services/system/zram-generator.nix @@ -27,7 +27,7 @@ in config = lib.mkIf cfg.enable { system.requiredKernelConfig = with config.lib.kernelConfig; [ - (isModule "ZRAM") + (isEnabled "ZRAM") ]; systemd.packages = [ cfg.package ]; diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix index 88537f8c4f7b..5dd02eb33163 100644 --- a/nixos/modules/services/torrent/transmission.nix +++ b/nixos/modules/services/torrent/transmission.nix @@ -251,6 +251,20 @@ in For instance, SSH sessions may time out more easily. ''; }; + + webHome = mkOption { + type = types.nullOr types.path; + default = null; + example = "pkgs.flood-for-transmission"; + description = lib.mdDoc '' + If not `null`, sets the value of the `TRANSMISSION_WEB_HOME` + environment variable used by the service. Useful for overriding + the web interface files, without overriding the transmission + package and thus requiring rebuilding it locally. Use this if + you want to use an alternative web interface, such as + `pkgs.flood-for-transmission`. + ''; + }; }; }; @@ -280,6 +294,7 @@ in requires = optional apparmor.enable "apparmor.service"; wantedBy = [ "multi-user.target" ]; environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source; + environment.TRANSMISSION_WEB_HOME = lib.mkIf (cfg.webHome != null) cfg.webHome; serviceConfig = { # Use "+" because credentialsFile may not be accessible to User= or Group=. @@ -314,6 +329,9 @@ in BindPaths = [ "${cfg.home}/${settingsDir}" cfg.settings.download-dir + # Transmission may need to read in the host's /run (eg. /run/systemd/resolve) + # or write in its private /run (eg. /run/host). + "/run" ] ++ optional cfg.settings.incomplete-dir-enabled cfg.settings.incomplete-dir ++ @@ -324,7 +342,6 @@ in # an AppArmor profile is provided to get a confinement based upon paths and rights. builtins.storeDir "/etc" - "/run" ] ++ optional (cfg.settings.script-torrent-done-enabled && cfg.settings.script-torrent-done-filename != null) @@ -349,10 +366,10 @@ in MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateDevices = true; - PrivateMounts = true; + PrivateMounts = mkDefault true; PrivateNetwork = mkDefault false; PrivateTmp = true; - PrivateUsers = true; + PrivateUsers = mkDefault true; ProtectClock = true; ProtectControlGroups = true; # ProtectHome=true would not allow BindPaths= to work across /home, @@ -434,7 +451,7 @@ in # at least up to the values hardcoded here: (mkIf cfg.settings.utp-enabled { "net.core.rmem_max" = mkDefault 4194304; # 4MB - "net.core.wmem_max" = mkDefault "1048576"; # 1MB + "net.core.wmem_max" = mkDefault 1048576; # 1MB }) (mkIf cfg.performanceNetParameters { # Increase the number of available source (local) TCP and UDP ports to 49151. @@ -490,6 +507,10 @@ in # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs px ${cfg.settings.script-torrent-done-filename} -> &@{dirs}, ''} + + ${optionalString (cfg.webHome != null) '' + r ${cfg.webHome}/**, + ''} ''; }; diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix index 146e968780c3..b7945282ba09 100644 --- a/nixos/modules/services/video/frigate.nix +++ b/nixos/modules/services/video/frigate.nix @@ -353,6 +353,7 @@ in ]; serviceConfig = { ExecStart = "${cfg.package.python.interpreter} -m frigate"; + Restart = "on-failure"; User = "frigate"; Group = "frigate"; diff --git a/nixos/modules/services/web-apps/c2fmzq-server.nix b/nixos/modules/services/web-apps/c2fmzq-server.nix index 2749c2a5a87a..87938fe160e1 100644 --- a/nixos/modules/services/web-apps/c2fmzq-server.nix +++ b/nixos/modules/services/web-apps/c2fmzq-server.nix @@ -6,8 +6,12 @@ let cfg = config.services.c2fmzq-server; argsFormat = { - type = with lib.types; nullOr (oneOf [ bool int str ]); - generate = lib.cli.toGNUCommandLineShell { }; + type = with lib.types; attrsOf (nullOr (oneOf [ bool int str ])); + generate = lib.cli.toGNUCommandLineShell { + mkBool = k: v: [ + "--${k}=${if v then "true" else "false"}" + ]; + }; }; in { options.services.c2fmzq-server = { diff --git a/nixos/modules/services/web-apps/code-server.nix b/nixos/modules/services/web-apps/code-server.nix new file mode 100644 index 000000000000..11601f6c3044 --- /dev/null +++ b/nixos/modules/services/web-apps/code-server.nix @@ -0,0 +1,259 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.code-server; + defaultUser = "code-server"; + defaultGroup = defaultUser; +in { + options = { + services.code-server = { + enable = lib.mkEnableOption (lib.mdDoc "code-server"); + + package = lib.mkPackageOptionMD pkgs "code-server" { + example = '' + pkgs.vscode-with-extensions.override { + vscode = pkgs.code-server; + vscodeExtensions = with pkgs.vscode-extensions; [ + bbenoist.nix + dracula-theme.theme-dracula + ]; + } + ''; + }; + + extraPackages = lib.mkOption { + default = [ ]; + description = lib.mdDoc '' + Additional packages to add to the code-server {env}`PATH`. + ''; + example = lib.literalExpression "[ pkgs.go ]"; + type = lib.types.listOf lib.types.package; + }; + + extraEnvironment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + description = lib.mdDoc '' + Additional environment variables to pass to code-server. + ''; + default = { }; + example = { PKG_CONFIG_PATH = "/run/current-system/sw/lib/pkgconfig"; }; + }; + + extraArguments = lib.mkOption { + default = [ ]; + description = lib.mdDoc '' + Additional arguments to pass to code-server. + ''; + example = lib.literalExpression ''[ "--log=info" ]''; + type = lib.types.listOf lib.types.str; + }; + + host = lib.mkOption { + default = "localhost"; + description = lib.mdDoc '' + The host name or IP address the server should listen to. + ''; + type = lib.types.str; + }; + + port = lib.mkOption { + default = 4444; + description = lib.mdDoc '' + The port the server should listen to. + ''; + type = lib.types.port; + }; + + auth = lib.mkOption { + default = "password"; + description = lib.mdDoc '' + The type of authentication to use. + ''; + type = lib.types.enum [ "none" "password" ]; + }; + + hashedPassword = lib.mkOption { + default = ""; + description = lib.mdDoc '' + Create the password with: `echo -n 'thisismypassword' | npx argon2-cli -e`. + ''; + type = lib.types.str; + }; + + user = lib.mkOption { + default = defaultUser; + example = "yourUser"; + description = lib.mdDoc '' + The user to run code-server as. + By default, a user named `${defaultUser}` will be created. + ''; + type = lib.types.str; + }; + + group = lib.mkOption { + default = defaultGroup; + example = "yourGroup"; + description = lib.mdDoc '' + The group to run code-server under. + By default, a group named `${defaultGroup}` will be created. + ''; + type = lib.types.str; + }; + + extraGroups = lib.mkOption { + default = [ ]; + description = lib.mdDoc '' + An array of additional groups for the `${defaultUser}` user. + ''; + example = [ "docker" ]; + type = lib.types.listOf lib.types.str; + }; + + socket = lib.mkOption { + default = null; + example = "/run/code-server/socket"; + description = lib.mdDoc '' + Path to a socket (bind-addr will be ignored). + ''; + type = lib.types.nullOr lib.types.str; + }; + + socketMode = lib.mkOption { + default = null; + description = lib.mdDoc '' + File mode of the socket. + ''; + type = lib.types.nullOr lib.types.str; + }; + + userDataDir = lib.mkOption { + default = null; + description = lib.mdDoc '' + Path to the user data directory. + ''; + type = lib.types.nullOr lib.types.str; + }; + + extensionsDir = lib.mkOption { + default = null; + description = lib.mdDoc '' + Path to the extensions directory. + ''; + type = lib.types.nullOr lib.types.str; + }; + + proxyDomain = lib.mkOption { + default = null; + example = "code-server.lan"; + description = lib.mdDoc '' + Domain used for proxying ports. + ''; + type = lib.types.nullOr lib.types.str; + }; + + disableTelemetry = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable telemetry. + ''; + type = lib.types.bool; + }; + + disableUpdateCheck = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable update check. + Without this flag, code-server checks every 6 hours against the latest github release and + then notifies you once every week that a new release is available. + ''; + type = lib.types.bool; + }; + + disableFileDownloads = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable file downloads from Code. + ''; + type = lib.types.bool; + }; + + disableWorkspaceTrust = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable Workspace Trust feature. + ''; + type = lib.types.bool; + }; + + disableGettingStartedOverride = lib.mkOption { + default = false; + example = true; + description = lib.mdDoc '' + Disable the coder/coder override in the Help: Getting Started page. + ''; + type = lib.types.bool; + }; + + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.code-server = { + description = "Code server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + path = cfg.extraPackages; + environment = { + HASHED_PASSWORD = cfg.hashedPassword; + } // cfg.extraEnvironment; + serviceConfig = { + ExecStart = '' + ${lib.getExe cfg.package} \ + --auth=${cfg.auth} \ + --bind-addr=${cfg.host}:${toString cfg.port} \ + '' + lib.optionalString (cfg.socket != null) '' + --socket=${cfg.socket} \ + '' + lib.optionalString (cfg.userDataDir != null) '' + --user-data-dir=${cfg.userDataDir} \ + '' + lib.optionalString (cfg.extensionsDir != null) '' + --extensions-dir=${cfg.extensionsDir} \ + '' + lib.optionalString (cfg.disableTelemetry == true) '' + --disable-telemetry \ + '' + lib.optionalString (cfg.disableUpdateCheck == true) '' + --disable-update-check \ + '' + lib.optionalString (cfg.disableFileDownloads == true) '' + --disable-file-downloads \ + '' + lib.optionalString (cfg.disableWorkspaceTrust == true) '' + --disable-workspace-trust \ + '' + lib.optionalString (cfg.disableGettingStartedOverride == true) '' + --disable-getting-started-override \ + '' + lib.escapeShellArgs cfg.extraArguments; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + RuntimeDirectory = cfg.user; + User = cfg.user; + Group = cfg.group; + Restart = "on-failure"; + }; + }; + + users.users."${cfg.user}" = lib.mkMerge [ + (lib.mkIf (cfg.user == defaultUser) { + isNormalUser = true; + description = "code-server user"; + inherit (cfg) group; + }) + { + packages = cfg.extraPackages; + inherit (cfg) extraGroups; + } + ]; + + users.groups."${defaultGroup}" = lib.mkIf (cfg.group == defaultGroup) { }; + }; + + meta.maintainers = [ lib.maintainers.stackshadow ]; +} diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix index 1df1cbf9f0e1..256ab3229ea6 100644 --- a/nixos/modules/services/web-apps/dokuwiki.nix +++ b/nixos/modules/services/web-apps/dokuwiki.nix @@ -122,62 +122,8 @@ let }; }; - # The current implementations of `doRename`, `mkRenamedOptionModule` do not provide the full options path when used with submodules. - # They would only show `settings.useacl' instead of `services.dokuwiki.sites."site1.local".settings.useacl' - # The partial re-implementation of these functions is done to help users in debugging by showing the full path. - mkRenamed = from: to: { config, options, name, ... }: let - pathPrefix = [ "services" "dokuwiki" "sites" name ]; - fromPath = pathPrefix ++ from; - fromOpt = getAttrFromPath from options; - toOp = getAttrsFromPath to config; - toPath = pathPrefix ++ to; - in { - options = setAttrByPath from (mkOption { - visible = false; - description = lib.mdDoc "Alias of {option}${showOption toPath}"; - apply = x: builtins.trace "Obsolete option `${showOption fromPath}' is used. It was renamed to ${showOption toPath}" toOp; - }); - config = mkMerge [ - { - warnings = optional fromOpt.isDefined - "The option `${showOption fromPath}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption toPath}'."; - } - (lib.modules.mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt) - ]; - }; - siteOpts = { options, config, lib, name, ... }: { - imports = [ - (mkRenamed [ "aclUse" ] [ "settings" "useacl" ]) - (mkRenamed [ "superUser" ] [ "settings" "superuser" ]) - (mkRenamed [ "disableActions" ] [ "settings" "disableactions" ]) - ({ config, options, ... }: let - showPath = suffix: lib.options.showOption ([ "services" "dokuwiki" "sites" name ] ++ suffix); - replaceExtraConfig = "Please use `${showPath ["settings"]}' to pass structured settings instead."; - ecOpt = options.extraConfig; - ecPath = showPath [ "extraConfig" ]; - in { - options.extraConfig = mkOption { - visible = false; - apply = x: throw "The option ${ecPath} can no longer be used since it's been removed.\n${replaceExtraConfig}"; - }; - config.assertions = [ - { - assertion = !ecOpt.isDefined; - message = "The option definition `${ecPath}' in ${showFiles ecOpt.files} no longer has any effect; please remove it.\n${replaceExtraConfig}"; - } - { - assertion = config.mergedConfig.useacl -> (config.acl != null || config.aclFile != null); - message = "Either ${showPath [ "acl" ]} or ${showPath [ "aclFile" ]} is mandatory if ${showPath [ "settings" "useacl" ]} is true"; - } - { - assertion = config.usersFile != null -> config.mergedConfig.useacl != false; - message = "${showPath [ "settings" "useacl" ]} is required when ${showPath [ "usersFile" ]} is set (Currently defined as `${config.usersFile}' in ${showFiles options.usersFile.files})."; - } - ]; - }) - ]; options = { enable = mkEnableOption (lib.mdDoc "DokuWiki web application"); @@ -392,21 +338,6 @@ let ''; }; - # Required for the mkRenamedOptionModule - # TODO: Remove me once https://github.com/NixOS/nixpkgs/issues/96006 is fixed - # or we don't have any more notes about the removal of extraConfig, ... - warnings = mkOption { - type = types.listOf types.unspecified; - default = [ ]; - visible = false; - internal = true; - }; - assertions = mkOption { - type = types.listOf types.unspecified; - default = [ ]; - visible = false; - internal = true; - }; }; }; in @@ -440,10 +371,6 @@ in # implementation config = mkIf (eachSite != {}) (mkMerge [{ - warnings = flatten (mapAttrsToList (_: cfg: cfg.warnings) eachSite); - - assertions = flatten (mapAttrsToList (_: cfg: cfg.assertions) eachSite); - services.phpfpm.pools = mapAttrs' (hostName: cfg: ( nameValuePair "dokuwiki-${hostName}" { inherit user; diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix index 9683730bbe1f..c8399143c37b 100644 --- a/nixos/modules/services/web-apps/freshrss.nix +++ b/nixos/modules/services/web-apps/freshrss.nix @@ -294,7 +294,6 @@ in systemd.services.freshrss-updater = { description = "FreshRSS feed updater"; after = [ "freshrss-config.service" ]; - wantedBy = [ "multi-user.target" ]; startAt = "*:0/5"; environment = { DATA_PATH = cfg.dataDir; diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix index cfba3c8a2970..359aaabfe673 100644 --- a/nixos/modules/services/web-apps/invidious.nix +++ b/nixos/modules/services/web-apps/invidious.nix @@ -10,82 +10,115 @@ let generatedHmacKeyFile = "/var/lib/invidious/hmac_key"; generateHmac = cfg.hmacKeyFile == null; - serviceConfig = { - systemd.services.invidious = { - description = "Invidious (An alternative YouTube front-end)"; - wants = [ "network-online.target" ]; - after = [ "network-online.target" ]; - wantedBy = [ "multi-user.target" ]; - - preStart = lib.optionalString generateHmac '' - if [[ ! -e "${generatedHmacKeyFile}" ]]; then - ${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}" - chmod 0600 "${generatedHmacKeyFile}" - fi - ''; - - script = '' - configParts=() - '' - # autogenerated hmac_key - + lib.optionalString generateHmac '' - configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")") - '' - # generated settings file - + '' - configParts+=("$(< ${lib.escapeShellArg settingsFile})") - '' - # optional database password file - + lib.optionalString (cfg.database.host != null) '' - configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})") - '' - # optional extra settings file - + lib.optionalString (cfg.extraSettingsFile != null) '' - configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})") - '' - # explicitly specified hmac key file - + lib.optionalString (cfg.hmacKeyFile != null) '' - configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})") - '' - # merge all parts into a single configuration with later elements overriding previous elements - + '' - export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")" - exec ${cfg.package}/bin/invidious - ''; - - serviceConfig = { - RestartSec = "2s"; - DynamicUser = true; - StateDirectory = "invidious"; - StateDirectoryMode = "0750"; - - CapabilityBoundingSet = ""; - PrivateDevices = true; - PrivateUsers = true; - ProtectHome = true; - ProtectKernelLogs = true; - ProtectProc = "invisible"; - RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; - RestrictNamespaces = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ]; - - # Because of various issues Invidious must be restarted often, at least once a day, ideally - # every hour. - # This option enables the automatic restarting of the Invidious instance. - Restart = lib.mkDefault "always"; - RuntimeMaxSec = lib.mkDefault "1h"; - }; + commonInvidousServiceConfig = { + description = "Invidious (An alternative YouTube front-end)"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service"; + requires = lib.optional cfg.database.createLocally "postgresql.service"; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + RestartSec = "2s"; + DynamicUser = true; + User = lib.mkIf (cfg.database.createLocally || cfg.serviceScale > 1) "invidious"; + StateDirectory = "invidious"; + StateDirectoryMode = "0750"; + + CapabilityBoundingSet = ""; + PrivateDevices = true; + PrivateUsers = true; + ProtectHome = true; + ProtectKernelLogs = true; + ProtectProc = "invisible"; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ]; + + # Because of various issues Invidious must be restarted often, at least once a day, ideally + # every hour. + # This option enables the automatic restarting of the Invidious instance. + # To ensure multiple instances of Invidious are not restarted at the exact same time, a + # randomized extra offset of up to 5 minutes is added. + Restart = lib.mkDefault "always"; + RuntimeMaxSec = lib.mkDefault "1h"; + RuntimeRandomizedExtraSec = lib.mkDefault "5min"; }; + }; + mkInvidiousService = scaleIndex: + lib.foldl' lib.recursiveUpdate commonInvidousServiceConfig [ + # only generate the hmac file in the first service + (lib.optionalAttrs (scaleIndex == 0) { + preStart = lib.optionalString generateHmac '' + if [[ ! -e "${generatedHmacKeyFile}" ]]; then + ${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}" + chmod 0600 "${generatedHmacKeyFile}" + fi + ''; + }) + # configure the secondary services to run after the first service + (lib.optionalAttrs (scaleIndex > 0) { + after = commonInvidousServiceConfig.after ++ [ "invidious.service" ]; + wants = commonInvidousServiceConfig.wants ++ [ "invidious.service" ]; + }) + { + script = '' + configParts=() + '' + # autogenerated hmac_key + + lib.optionalString generateHmac '' + configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")") + '' + # generated settings file + + '' + configParts+=("$(< ${lib.escapeShellArg settingsFile})") + '' + # optional database password file + + lib.optionalString (cfg.database.host != null) '' + configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})") + '' + # optional extra settings file + + lib.optionalString (cfg.extraSettingsFile != null) '' + configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})") + '' + # explicitly specified hmac key file + + lib.optionalString (cfg.hmacKeyFile != null) '' + configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})") + '' + # configure threads for secondary instances + + lib.optionalString (scaleIndex > 0) '' + configParts+=('{"channel_threads":0, "feed_threads":0}') + '' + # configure different ports for the instances + + '' + configParts+=('{"port":${toString (cfg.port + scaleIndex)}}') + '' + # merge all parts into a single configuration with later elements overriding previous elements + + '' + export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")" + exec ${cfg.package}/bin/invidious + ''; + } + ]; - services.invidious.settings = { - inherit (cfg) port; + serviceConfig = { + systemd.services = builtins.listToAttrs (builtins.genList + (scaleIndex: { + name = "invidious" + lib.optionalString (scaleIndex > 0) "-${builtins.toString scaleIndex}"; + value = mkInvidiousService scaleIndex; + }) + cfg.serviceScale); + services.invidious.settings = { # Automatically initialises and migrates the database if necessary check_tables = true; db = { - user = lib.mkDefault "kemal"; + user = lib.mkDefault ( + if (lib.versionAtLeast config.system.stateVersion "24.05") + then "invidious" + else "kemal" + ); dbname = lib.mkDefault "invidious"; port = cfg.database.port; # Blank for unix sockets, see @@ -94,67 +127,74 @@ let # Not needed because peer authentication is enabled password = lib.mkIf (cfg.database.host == null) ""; }; + + host_binding = cfg.address; } // (lib.optionalAttrs (cfg.domain != null) { inherit (cfg) domain; }); - assertions = [{ - assertion = cfg.database.host != null -> cfg.database.passwordFile != null; - message = "If database host isn't null, database password needs to be set"; - }]; + assertions = [ + { + assertion = cfg.database.host != null -> cfg.database.passwordFile != null; + message = "If database host isn't null, database password needs to be set"; + } + { + assertion = cfg.serviceScale >= 1; + message = "Service can't be scaled below one instance"; + } + ]; }; # Settings necessary for running with an automatically managed local database localDatabaseConfig = lib.mkIf cfg.database.createLocally { + assertions = [ + { + assertion = cfg.settings.db.user == cfg.settings.db.dbname; + message = '' + For local automatic database provisioning (services.invidious.database.createLocally == true) + to work, the username used to connect to PostgreSQL must match the database name, that is + services.invidious.settings.db.user must match services.invidious.settings.db.dbname. + This is the default since NixOS 24.05. For older systems, it is normally safe to manually set + the user to "invidious" as the new user will be created with permissions + for the existing database. `REASSIGN OWNED BY kemal TO invidious;` may also be needed, it can be + run as `sudo -u postgres env psql --user=postgres --dbname=invidious -c 'reassign OWNED BY kemal to invidious;'`. + ''; + } + ]; # Default to using the local database if we create it services.invidious.database.host = lib.mkDefault null; - - # TODO(raitobezarius to maintainers of invidious): I strongly advise to clean up the kemal specific - # thing for 24.05 and use `ensureDBOwnership`. - # See https://github.com/NixOS/nixpkgs/issues/216989 - systemd.services.postgresql.postStart = lib.mkAfter '' - $PSQL -tAc 'ALTER DATABASE "${cfg.settings.db.dbname}" OWNER TO "${cfg.settings.db.user}";' - ''; services.postgresql = { enable = true; - ensureUsers = lib.singleton { name = cfg.settings.db.user; ensureDBOwnership = false; }; + ensureUsers = lib.singleton { name = cfg.settings.db.user; ensureDBOwnership = true; }; ensureDatabases = lib.singleton cfg.settings.db.dbname; - # This is only needed because the unix user invidious isn't the same as - # the database user. This tells postgres to map one to the other. - identMap = '' - invidious invidious ${cfg.settings.db.user} - ''; - # And this specifically enables peer authentication for only this - # database, which allows passwordless authentication over the postgres - # unix socket for the user map given above. - authentication = '' - local ${cfg.settings.db.dbname} ${cfg.settings.db.user} peer map=invidious - ''; }; + }; + + ytproxyConfig = lib.mkIf cfg.http3-ytproxy.enable { + systemd.services.http3-ytproxy = { + description = "HTTP3 ytproxy for Invidious"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; - systemd.services.invidious-db-clean = { - description = "Invidious database cleanup"; - documentation = [ "https://docs.invidious.io/Database-Information-and-Maintenance.md" ]; - startAt = lib.mkDefault "weekly"; - path = [ config.services.postgresql.package ]; - after = [ "postgresql.service" ]; script = '' - psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "DELETE FROM nonces * WHERE expire < current_timestamp" - psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "TRUNCATE TABLE videos" + mkdir -p socket + exec ${lib.getExe cfg.http3-ytproxy.package}; ''; + serviceConfig = { + RestartSec = "2s"; DynamicUser = true; - User = "invidious"; + User = lib.mkIf cfg.nginx.enable config.services.nginx.user; + RuntimeDirectory = "http3-ytproxy"; + WorkingDirectory = "/run/http3-ytproxy"; }; }; - systemd.services.invidious = { - requires = [ "postgresql.service" ]; - after = [ "postgresql.service" ]; - - serviceConfig = { - User = "invidious"; + services.nginx.virtualHosts.${cfg.domain} = lib.mkIf cfg.nginx.enable { + locations."~ (^/videoplayback|^/vi/|^/ggpht/|^/sb/)" = { + proxyPass = "http://unix:/run/http3-ytproxy/socket/http-proxy.sock"; }; }; }; @@ -165,14 +205,28 @@ let external_port = 80; }; - services.nginx = { + services.nginx = let + ip = if cfg.address == "0.0.0.0" then "127.0.0.1" else cfg.address; + in + { enable = true; virtualHosts.${cfg.domain} = { - locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}"; + locations."/".proxyPass = + if cfg.serviceScale == 1 then + "http://${ip}:${toString cfg.port}" + else "http://upstream-invidious"; enableACME = lib.mkDefault true; forceSSL = lib.mkDefault true; }; + upstreams = lib.mkIf (cfg.serviceScale > 1) { + "upstream-invidious".servers = builtins.listToAttrs (builtins.genList + (scaleIndex: { + name = "${ip}:${toString (cfg.port + scaleIndex)}"; + value = { }; + }) + cfg.serviceScale); + }; }; assertions = [{ @@ -220,6 +274,20 @@ in ''; }; + serviceScale = lib.mkOption { + type = types.int; + default = 1; + description = lib.mdDoc '' + How many invidious instances to run. + + See https://docs.invidious.io/improve-public-instance/#2-multiple-invidious-processes for more details + on how this is intended to work. All instances beyond the first one have the options `channel_threads` + and `feed_threads` set to 0 to avoid conflicts with multiple instances refreshing subscriptions. Instances + will be configured to bind to consecutive ports starting with {option}`services.invidious.port` for the + first instance. + ''; + }; + # This needs to be outside of settings to avoid infinite recursion # (determining if nginx should be enabled and therefore the settings # modified). @@ -233,6 +301,16 @@ in ''; }; + address = lib.mkOption { + type = types.str; + # default from https://github.com/iv-org/invidious/blob/master/config/config.example.yml + default = if cfg.nginx.enable then "127.0.0.1" else "0.0.0.0"; + defaultText = lib.literalExpression ''if config.services.invidious.nginx.enable then "127.0.0.1" else "0.0.0.0"''; + description = lib.mdDoc '' + The IP address Invidious should bind to. + ''; + }; + port = lib.mkOption { type = types.port; # Default from https://docs.invidious.io/Configuration.md @@ -298,11 +376,28 @@ in which can also be used to disable AMCE and TLS. ''; }; + + http3-ytproxy = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc '' + Whether to enable http3-ytproxy for faster loading of images and video playback. + + If {option}`services.invidious.nginx.enable` is used, nginx will be configured automatically. If not, you + need to configure a reverse proxy yourself according to + https://docs.invidious.io/improve-public-instance/#3-speed-up-video-playback-with-http3-ytproxy. + ''; + }; + + package = lib.mkPackageOptionMD pkgs "http3-ytproxy" { }; + }; }; config = lib.mkIf cfg.enable (lib.mkMerge [ serviceConfig localDatabaseConfig nginxConfig + ytproxyConfig ]); } diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix index 7b00ce35eb1a..538e728fcc72 100644 --- a/nixos/modules/services/web-apps/mastodon.nix +++ b/nixos/modules/services/web-apps/mastodon.nix @@ -136,7 +136,7 @@ let # System Call Filtering SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ]; } // cfgService; - path = with pkgs; [ file imagemagick ffmpeg ]; + path = with pkgs; [ ffmpeg-headless file imagemagick ]; }) ) cfg.sidekiqProcesses; @@ -711,31 +711,28 @@ in { systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations { script = lib.optionalString (!databaseActuallyCreateLocally) '' umask 077 - - export PGPASSFILE - PGPASSFILE=$(mktemp) - cat > $PGPASSFILE <<EOF - ${cfg.database.host}:${toString cfg.database.port}:${cfg.database.name}:${cfg.database.user}:$(cat ${cfg.database.passwordFile}) - EOF - + export PGPASSWORD="$(cat '${cfg.database.passwordFile}')" '' + '' - if [ `psql ${cfg.database.name} -c \ + if [ `psql -c \ "select count(*) from pg_class c \ join pg_namespace s on s.oid = c.relnamespace \ where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \ and s.nspname not like 'pg_temp%';" | sed -n 3p` -eq 0 ]; then + echo "Seeding database" SAFETY_ASSURED=1 rails db:schema:load rails db:seed else + echo "Migrating database (this might be a noop)" rails db:migrate fi '' + lib.optionalString (!databaseActuallyCreateLocally) '' - rm $PGPASSFILE - unset PGPASSFILE + unset PGPASSWORD ''; path = [ cfg.package pkgs.postgresql ]; environment = env // lib.optionalAttrs (!databaseActuallyCreateLocally) { PGHOST = cfg.database.host; + PGPORT = toString cfg.database.port; + PGDATABASE = cfg.database.name; PGUSER = cfg.database.user; }; serviceConfig = { @@ -776,7 +773,7 @@ in { # System Call Filtering SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ]; } // cfgService; - path = with pkgs; [ file imagemagick ffmpeg ]; + path = with pkgs; [ ffmpeg-headless file imagemagick ]; }; systemd.services.mastodon-media-auto-remove = lib.mkIf cfg.mediaAutoRemove.enable { diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix index a500008fc792..1a5b7d0c24e9 100644 --- a/nixos/modules/services/web-apps/miniflux.nix +++ b/nixos/modules/services/web-apps/miniflux.nix @@ -21,10 +21,10 @@ in package = mkPackageOption pkgs "miniflux" { }; config = mkOption { - type = types.attrsOf types.str; + type = with types; attrsOf (oneOf [ str int ]); example = literalExpression '' { - CLEANUP_FREQUENCY = "48"; + CLEANUP_FREQUENCY = 48; LISTEN_ADDR = "localhost:8080"; } ''; @@ -51,12 +51,11 @@ in }; config = mkIf cfg.enable { - services.miniflux.config = { LISTEN_ADDR = mkDefault defaultAddress; DATABASE_URL = "user=miniflux host=/run/postgresql dbname=miniflux"; - RUN_MIGRATIONS = "1"; - CREATE_ADMIN = "1"; + RUN_MIGRATIONS = 1; + CREATE_ADMIN = 1; }; services.postgresql = { @@ -90,7 +89,7 @@ in User = "miniflux"; DynamicUser = true; RuntimeDirectory = "miniflux"; - RuntimeDirectoryMode = "0700"; + RuntimeDirectoryMode = "0750"; EnvironmentFile = cfg.adminCredentialsFile; # Hardening CapabilityBoundingSet = [ "" ]; @@ -117,7 +116,7 @@ in UMask = "0077"; }; - environment = cfg.config; + environment = lib.mapAttrs (_: toString) cfg.config; }; environment.systemPackages = [ cfg.package ]; diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md index ecc7f380592a..ce8f96a6a389 100644 --- a/nixos/modules/services/web-apps/nextcloud.md +++ b/nixos/modules/services/web-apps/nextcloud.md @@ -5,7 +5,7 @@ self-hostable cloud platform. The server setup can be automated using [services.nextcloud](#opt-services.nextcloud.enable). A desktop client is packaged at `pkgs.nextcloud-client`. -The current default by NixOS is `nextcloud27` which is also the latest +The current default by NixOS is `nextcloud28` which is also the latest major version available. ## Basic usage {#module-services-nextcloud-basic-usage} @@ -51,7 +51,7 @@ to ensure that changes can be applied by changing the module's options. In case the application serves multiple domains (those are checked with [`$_SERVER['HTTP_HOST']`](https://www.php.net/manual/en/reserved.variables.server.php)) it's needed to add them to -[`services.nextcloud.config.extraTrustedDomains`](#opt-services.nextcloud.config.extraTrustedDomains). +[`services.nextcloud.extraOptions.trusted_domains`](#opt-services.nextcloud.extraOptions.trusted_domains). Auto updates for Nextcloud apps can be enabled using [`services.nextcloud.autoUpdateApps`](#opt-services.nextcloud.autoUpdateApps.enable). diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index db6eacf30196..38c51251aac1 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -9,6 +9,7 @@ let jsonFormat = pkgs.formats.json {}; defaultPHPSettings = { + output_buffering = "0"; short_open_tag = "Off"; expose_php = "Off"; error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT"; @@ -23,11 +24,49 @@ let catch_workers_output = "yes"; }; + appStores = { + # default apps bundled with pkgs.nextcloudXX, e.g. files, contacts + apps = { + enabled = true; + writable = false; + }; + # apps installed via cfg.extraApps + nix-apps = { + enabled = cfg.extraApps != { }; + linkTarget = pkgs.linkFarm "nix-apps" + (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps); + writable = false; + }; + # apps installed via the app store. + store-apps = { + enabled = cfg.appstoreEnable == null || cfg.appstoreEnable; + linkTarget = "${cfg.home}/store-apps"; + writable = true; + }; + }; + + webroot = pkgs.runCommand + "${cfg.package.name or "nextcloud"}-with-apps" + { } + '' + mkdir $out + ln -sfv "${cfg.package}"/* "$out" + ${concatStrings + (mapAttrsToList (name: store: optionalString (store.enabled && store?linkTarget) '' + if [ -e "$out"/${name} ]; then + echo "Didn't expect ${name} already in $out!" + exit 1 + fi + ln -sfTv ${store.linkTarget} "$out"/${name} + '') appStores)} + ''; + inherit (cfg) datadir; phpPackage = cfg.phpPackage.buildEnv { extensions = { enabled, all }: (with all; enabled + ++ [ bz2 intl sodium ] # recommended ++ optional cfg.enableImagemagick imagick # Optionally enabled depending on caching settings ++ optional cfg.caching.apcu apcu @@ -44,7 +83,7 @@ let occ = pkgs.writeScriptBin "nextcloud-occ" '' #! ${pkgs.runtimeShell} - cd ${cfg.package} + cd ${webroot} sudo=exec if [[ "$USER" != nextcloud ]]; then sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS' @@ -68,31 +107,29 @@ let in { imports = [ - (mkRemovedOptionModule [ "services" "nextcloud" "config" "adminpass" ] '' - Please use `services.nextcloud.config.adminpassFile' instead! - '') - (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbpass" ] '' - Please use `services.nextcloud.config.dbpassFile' instead! - '') - (mkRemovedOptionModule [ "services" "nextcloud" "nginx" "enable" ] '' - The nextcloud module supports `nginx` as reverse-proxy by default and doesn't - support other reverse-proxies officially. - - However it's possible to use an alternative reverse-proxy by - - * disabling nginx - * setting `listen.owner` & `listen.group` in the phpfpm-pool to a different value - - Further details about this can be found in the `Nextcloud`-section of the NixOS-manual - (which can be opened e.g. by running `nixos-help`). - '') (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] '' This option has no effect since there's no supported Nextcloud version packaged here using OpenSSL for RC4 SSE. '') - (mkRemovedOptionModule [ "services" "nextcloud" "disableImagemagick" ] '' - Use services.nextcloud.enableImagemagick instead. + (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbport" ] '' + Add port to services.nextcloud.config.dbhost instead. '') + (mkRenamedOptionModule + [ "services" "nextcloud" "logLevel" ] [ "services" "nextcloud" "extraOptions" "loglevel" ]) + (mkRenamedOptionModule + [ "services" "nextcloud" "logType" ] [ "services" "nextcloud" "extraOptions" "log_type" ]) + (mkRenamedOptionModule + [ "services" "nextcloud" "config" "defaultPhoneRegion" ] [ "services" "nextcloud" "extraOptions" "default_phone_region" ]) + (mkRenamedOptionModule + [ "services" "nextcloud" "config" "overwriteProtocol" ] [ "services" "nextcloud" "extraOptions" "overwriteprotocol" ]) + (mkRenamedOptionModule + [ "services" "nextcloud" "skeletonDirectory" ] [ "services" "nextcloud" "extraOptions" "skeletondirectory" ]) + (mkRenamedOptionModule + [ "services" "nextcloud" "globalProfiles" ] [ "services" "nextcloud" "extraOptions" "profile.enabled" ]) + (mkRenamedOptionModule + [ "services" "nextcloud" "config" "extraTrustedDomains" ] [ "services" "nextcloud" "extraOptions" "trusted_domains" ]) + (mkRenamedOptionModule + [ "services" "nextcloud" "config" "trustedProxies" ] [ "services" "nextcloud" "extraOptions" "trusted_proxies" ]) ]; options.services.nextcloud = { @@ -156,32 +193,6 @@ in { Set this to false to disable the installation of apps from the global appstore. App management is always enabled regardless of this setting. ''; }; - logLevel = mkOption { - type = types.ints.between 0 4; - default = 2; - description = lib.mdDoc '' - Log level value between 0 (DEBUG) and 4 (FATAL). - - - 0 (debug): Log all activity. - - - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors. - - - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors. - - - 3 (error): Log failed operations and fatal errors. - - - 4 (fatal): Log only fatal errors that cause the server to stop. - ''; - }; - logType = mkOption { - type = types.enum [ "errorlog" "file" "syslog" "systemd" ]; - default = "syslog"; - description = lib.mdDoc '' - Logging backend to use. - systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions. - See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details. - ''; - }; https = mkOption { type = types.bool; default = false; @@ -190,7 +201,7 @@ in { package = mkOption { type = types.package; description = lib.mdDoc "Which package to use for the Nextcloud instance."; - relatedPackages = [ "nextcloud26" "nextcloud27" ]; + relatedPackages = [ "nextcloud26" "nextcloud27" "nextcloud28" ]; }; phpPackage = mkPackageOption pkgs "php" { example = "php82"; @@ -205,16 +216,6 @@ in { ''; }; - skeletonDirectory = mkOption { - default = ""; - type = types.str; - description = lib.mdDoc '' - The directory where the skeleton files are located. These files will be - copied to the data directory of new users. Leave empty to not copy any - skeleton files. - ''; - }; - webfinger = mkOption { type = types.bool; default = false; @@ -314,7 +315,6 @@ in { }; - config = { dbtype = mkOption { type = types.enum [ "sqlite" "pgsql" "mysql" ]; @@ -345,18 +345,14 @@ in { else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock" else "localhost"; defaultText = "localhost"; + example = "localhost:5000"; description = lib.mdDoc '' - Database host or socket path. + Database host (+port) or socket path. If [](#opt-services.nextcloud.database.createLocally) is true and [](#opt-services.nextcloud.config.dbtype) is either `pgsql` or `mysql`, defaults to the correct Unix socket instead. ''; }; - dbport = mkOption { - type = with types; nullOr (either int str); - default = null; - description = lib.mdDoc "Database port."; - }; dbtableprefix = mkOption { type = types.nullOr types.str; default = null; @@ -379,53 +375,6 @@ in { setup of Nextcloud by the systemd service `nextcloud-setup.service`. ''; }; - - extraTrustedDomains = mkOption { - type = types.listOf types.str; - default = []; - description = lib.mdDoc '' - Trusted domains from which the Nextcloud installation will be - accessible. You don't need to add - `services.nextcloud.hostname` here. - ''; - }; - - trustedProxies = mkOption { - type = types.listOf types.str; - default = []; - description = lib.mdDoc '' - Trusted proxies to provide if the Nextcloud installation is being - proxied to secure against, e.g. spoofing. - ''; - }; - - overwriteProtocol = mkOption { - type = types.nullOr (types.enum [ "http" "https" ]); - default = null; - example = "https"; - - description = lib.mdDoc '' - Force Nextcloud to always use HTTP or HTTPS i.e. for link generation. - Nextcloud uses the currently used protocol by default, but when - behind a reverse-proxy, it may use `http` for everything although - Nextcloud may be served via HTTPS. - ''; - }; - - defaultPhoneRegion = mkOption { - default = null; - type = types.nullOr types.str; - example = "DE"; - description = lib.mdDoc '' - An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html) - country code which replaces automatic phone-number detection - without a country code. - - As an example, with `DE` set as the default phone region, - the `+49` prefix can be omitted for phone numbers. - ''; - }; - objectstore = { s3 = { enable = mkEnableOption (lib.mdDoc '' @@ -608,30 +557,109 @@ in { The nextcloud-occ program preconfigured to target this Nextcloud instance. ''; }; - globalProfiles = mkEnableOption (lib.mdDoc "global profiles") // { - description = lib.mdDoc '' - Makes user-profiles globally available under `nextcloud.tld/u/user.name`. - Even though it's enabled by default in Nextcloud, it must be explicitly enabled - here because it has the side-effect that personal information is even accessible to - unauthenticated users by default. - - By default, the following properties are set to “Show to everyone” - if this flag is enabled: - - About - - Full name - - Headline - - Organisation - - Profile picture - - Role - - Twitter - - Website - - Only has an effect in Nextcloud 23 and later. - ''; - }; extraOptions = mkOption { - type = jsonFormat.type; + type = types.submodule { + freeformType = jsonFormat.type; + options = { + + loglevel = mkOption { + type = types.ints.between 0 4; + default = 2; + description = lib.mdDoc '' + Log level value between 0 (DEBUG) and 4 (FATAL). + + - 0 (debug): Log all activity. + + - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors. + + - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors. + + - 3 (error): Log failed operations and fatal errors. + + - 4 (fatal): Log only fatal errors that cause the server to stop. + ''; + }; + log_type = mkOption { + type = types.enum [ "errorlog" "file" "syslog" "systemd" ]; + default = "syslog"; + description = lib.mdDoc '' + Logging backend to use. + systemd requires the php-systemd package to be added to services.nextcloud.phpExtraExtensions. + See the [nextcloud documentation](https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/logging_configuration.html) for details. + ''; + }; + skeletondirectory = mkOption { + default = ""; + type = types.str; + description = lib.mdDoc '' + The directory where the skeleton files are located. These files will be + copied to the data directory of new users. Leave empty to not copy any + skeleton files. + ''; + }; + trusted_domains = mkOption { + type = types.listOf types.str; + default = []; + description = lib.mdDoc '' + Trusted domains, from which the nextcloud installation will be + accessible. You don't need to add + `services.nextcloud.hostname` here. + ''; + }; + trusted_proxies = mkOption { + type = types.listOf types.str; + default = []; + description = lib.mdDoc '' + Trusted proxies, to provide if the nextcloud installation is being + proxied to secure against e.g. spoofing. + ''; + }; + overwriteprotocol = mkOption { + type = types.enum [ "" "http" "https" ]; + default = ""; + example = "https"; + description = lib.mdDoc '' + Force Nextcloud to always use HTTP or HTTPS i.e. for link generation. + Nextcloud uses the currently used protocol by default, but when + behind a reverse-proxy, it may use `http` for everything although + Nextcloud may be served via HTTPS. + ''; + }; + default_phone_region = mkOption { + default = ""; + type = types.str; + example = "DE"; + description = lib.mdDoc '' + An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html) + country code which replaces automatic phone-number detection + without a country code. + + As an example, with `DE` set as the default phone region, + the `+49` prefix can be omitted for phone numbers. + ''; + }; + "profile.enabled" = mkEnableOption (lib.mdDoc "global profiles") // { + description = lib.mdDoc '' + Makes user-profiles globally available under `nextcloud.tld/u/user.name`. + Even though it's enabled by default in Nextcloud, it must be explicitly enabled + here because it has the side-effect that personal information is even accessible to + unauthenticated users by default. + By default, the following properties are set to “Show to everyone” + if this flag is enabled: + - About + - Full name + - Headline + - Organisation + - Profile picture + - Role + - Twitter + - Website + Only has an effect in Nextcloud 23 and later. + ''; + }; + }; + }; default = {}; description = lib.mdDoc '' Extra options which should be appended to Nextcloud's config.php file. @@ -679,7 +707,7 @@ in { config = mkIf cfg.enable (mkMerge [ { warnings = let - latest = 27; + latest = 28; upgradeWarning = major: nixos: '' A legacy Nextcloud install (from before NixOS ${nixos}) may be installed. @@ -700,7 +728,8 @@ in { '') ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11")) ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05")) - ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11")); + ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.11")) + ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05")); services.nextcloud.package = with pkgs; mkDefault ( @@ -710,15 +739,13 @@ in { nextcloud defined in an overlay, please set `services.nextcloud.package` to `pkgs.nextcloud`. '' - else if versionOlder stateVersion "22.11" then nextcloud24 else if versionOlder stateVersion "23.05" then nextcloud25 else if versionOlder stateVersion "23.11" then nextcloud26 - else nextcloud27 + else if versionOlder stateVersion "24.05" then nextcloud27 + else nextcloud28 ); - services.nextcloud.phpPackage = - if versionOlder cfg.package.version "26" then pkgs.php81 - else pkgs.php82; + services.nextcloud.phpPackage = pkgs.php82; services.nextcloud.phpOptions = mkMerge [ (mapAttrs (const mkOptionDefault) defaultPHPSettings) @@ -766,11 +793,10 @@ in { # When upgrading the Nextcloud package, Nextcloud can report errors such as # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly" # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround). - phpfpm-nextcloud.restartTriggers = [ cfg.package ]; + phpfpm-nextcloud.restartTriggers = [ webroot ]; nextcloud-setup = let c = cfg.config; - writePhpArray = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]"; requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable; objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable '' 'objectstore' => [ @@ -800,6 +826,10 @@ in { nextcloudGreaterOrEqualThan = req: versionAtLeast cfg.package.version req; + mkAppStoreConfig = name: { enabled, writable, ... }: optionalString enabled '' + [ 'path' => '${webroot}/${name}', 'url' => '/${name}', 'writable' => ${boolToString writable} ], + ''; + overrideConfig = pkgs.writeText "nextcloud-config.php" '' <?php ${optionalString requiresReadSecretFunction '' @@ -828,20 +858,12 @@ in { } $CONFIG = [ 'apps_paths' => [ - ${optionalString (cfg.extraApps != { }) "[ 'path' => '${cfg.home}/nix-apps', 'url' => '/nix-apps', 'writable' => false ],"} - [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ], - [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ], + ${concatStrings (mapAttrsToList mkAppStoreConfig appStores)} ], ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"} - 'datadirectory' => '${datadir}/data', - 'skeletondirectory' => '${cfg.skeletonDirectory}', ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"} - 'log_type' => '${cfg.logType}', - 'loglevel' => '${builtins.toString cfg.logLevel}', - ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"} ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"} ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"} - ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"} ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"} ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"} ${optionalString (c.dbpassFile != null) '' @@ -851,10 +873,6 @@ in { '' } 'dbtype' => '${c.dbtype}', - 'trusted_domains' => ${writePhpArray ([ cfg.hostName ] ++ c.extraTrustedDomains)}, - 'trusted_proxies' => ${writePhpArray (c.trustedProxies)}, - ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"} - ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"} ${objectstoreConfig} ]; @@ -890,7 +908,6 @@ in { # will be omitted. ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"''; ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"''; - ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"''; ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"''; "--database-pass" = "\"\$${dbpass.arg}\""; "--admin-user" = ''"${c.adminuser}"''; @@ -907,7 +924,7 @@ in { (i: v: '' ${occ}/bin/nextcloud-occ config:system:set trusted_domains \ ${toString i} --value="${toString v}" - '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains)); + '') ([ cfg.hostName ] ++ cfg.extraOptions.trusted_domains)); in { wantedBy = [ "multi-user.target" ]; @@ -935,17 +952,16 @@ in { exit 1 fi - ln -sf ${cfg.package}/apps ${cfg.home}/ - - # Install extra apps - ln -sfT \ - ${pkgs.linkFarm "nix-apps" - (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps)} \ - ${cfg.home}/nix-apps + ${concatMapStrings (name: '' + if [ -d "${cfg.home}"/${name} ]; then + echo "Cleaning up ${name}; these are now bundled in the webroot store-path!" + rm -r "${cfg.home}"/${name} + fi + '') [ "nix-apps" "apps" ]} # create nextcloud directories. # if the directories exist already with wrong permissions, we fix that - for dir in ${datadir}/config ${datadir}/data ${cfg.home}/store-apps ${cfg.home}/nix-apps; do + for dir in ${datadir}/config ${datadir}/data ${cfg.home}/store-apps; do if [ ! -e $dir ]; then install -o nextcloud -g nextcloud -d $dir elif [ $(stat -c "%G" $dir) != "nextcloud" ]; then @@ -982,7 +998,7 @@ in { environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config"; serviceConfig.Type = "oneshot"; serviceConfig.User = "nextcloud"; - serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${cfg.package}/cron.php"; + serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${webroot}/cron.php"; }; nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable { after = [ "nextcloud-setup.service" ]; @@ -1043,22 +1059,25 @@ in { user = "nextcloud"; }; - services.nextcloud = lib.mkIf cfg.configureRedis { - caching.redis = true; - extraOptions = { + services.nextcloud = { + caching.redis = lib.mkIf cfg.configureRedis true; + extraOptions = mkMerge [({ + datadirectory = lib.mkDefault "${datadir}/data"; + trusted_domains = [ cfg.hostName ]; + }) (lib.mkIf cfg.configureRedis { "memcache.distributed" = ''\OC\Memcache\Redis''; "memcache.locking" = ''\OC\Memcache\Redis''; redis = { host = config.services.redis.servers.nextcloud.unixSocket; port = 0; }; - }; + })]; }; services.nginx.enable = mkDefault true; services.nginx.virtualHosts.${cfg.hostName} = { - root = cfg.package; + root = webroot; locations = { "= /robots.txt" = { priority = 100; @@ -1075,14 +1094,6 @@ in { } ''; }; - "~ ^/store-apps" = { - priority = 201; - extraConfig = "root ${cfg.home};"; - }; - "~ ^/nix-apps" = { - priority = 201; - extraConfig = "root ${cfg.home};"; - }; "^~ /.well-known" = { priority = 210; extraConfig = '' @@ -1131,10 +1142,13 @@ in { fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s; ''; }; - "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm)$".extraConfig = '' + "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$".extraConfig = '' try_files $uri /index.php$request_uri; expires 6M; access_log off; + location ~ \.mjs$ { + default_type text/javascript; + } location ~ \.wasm$ { default_type application/wasm; } diff --git a/nixos/modules/services/web-apps/node-red.nix b/nixos/modules/services/web-apps/node-red.nix index de78f05a98ca..82f89783d778 100644 --- a/nixos/modules/services/web-apps/node-red.nix +++ b/nixos/modules/services/web-apps/node-red.nix @@ -19,7 +19,7 @@ in options.services.node-red = { enable = mkEnableOption (lib.mdDoc "the Node-RED service"); - package = mkPackageOption pkgs "nodePackages.node-red" { }; + package = mkPackageOption pkgs [ "nodePackages" "node-red" ] { }; openFirewall = mkOption { type = types.bool; diff --git a/nixos/modules/services/web-apps/outline.nix b/nixos/modules/services/web-apps/outline.nix index d97b45d62418..702755dfa2ab 100644 --- a/nixos/modules/services/web-apps/outline.nix +++ b/nixos/modules/services/web-apps/outline.nix @@ -586,6 +586,37 @@ in ensureDatabases = [ "outline" ]; }; + # Outline is unable to create the uuid-ossp extension when using postgresql 12, in later version this + # extension can be created without superuser permission. This services therefor this extension before + # outline starts and postgresql 12 is using on the host. + # + # Can be removed after postgresql 12 is dropped from nixos. + systemd.services.outline-postgresql = + let + pgsql = config.services.postgresql; + in + lib.mkIf (cfg.databaseUrl == "local" && pgsql.package == pkgs.postgresql_12) { + after = [ "postgresql.service" ]; + bindsTo = [ "postgresql.service" ]; + wantedBy = [ "outline.service" ]; + partOf = [ "outline.service" ]; + path = [ + pgsql.package + ]; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + psql outline -tAc 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"' + ''; + + serviceConfig = { + User = pgsql.superUser; + Type = "oneshot"; + RemainAfterExit = true; + }; + }; + services.redis.servers.outline = lib.mkIf (cfg.redisUrl == "local") { enable = true; user = config.services.outline.user; diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix index 9a544e98cfc4..95dc219d108c 100644 --- a/nixos/modules/services/web-servers/caddy/default.nix +++ b/nixos/modules/services/web-servers/caddy/default.nix @@ -342,8 +342,9 @@ in } ''; - # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size + # https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000; + boot.kernel.sysctl."net.core.wmem_max" = mkDefault 2500000; systemd.packages = [ cfg.package ]; systemd.services.caddy = { diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index 6ea24e65f220..6799de6c7d96 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -334,8 +334,8 @@ let + optionalString vhost.default "default_server " + optionalString vhost.reuseport "reuseport " + optionalString (extraParameters != []) (concatStringsSep " " - (let inCompatibleParameters = [ "ssl" "proxy_protocol" "http2" ]; - isCompatibleParameter = param: !(any (p: p == param) inCompatibleParameters); + (let inCompatibleParameters = [ "accept_filter" "backlog" "deferred" "fastopen" "http2" "proxy_protocol" "so_keepalive" "ssl" ]; + isCompatibleParameter = param: !(any (p: lib.hasPrefix p param) inCompatibleParameters); in filter isCompatibleParameter extraParameters)) + ";")) + " @@ -352,10 +352,11 @@ let # The acme-challenge location doesn't need to be added if we are not using any automated # certificate provisioning and can also be omitted when we use a certificate obtained via a DNS-01 challenge - acmeLocation = optionalString (vhost.enableACME || (vhost.useACMEHost != null && config.security.acme.certs.${vhost.useACMEHost}.dnsProvider == null)) '' + acmeLocation = optionalString (vhost.enableACME || (vhost.useACMEHost != null && config.security.acme.certs.${vhost.useACMEHost}.dnsProvider == null)) # Rule for legitimate ACME Challenge requests (like /.well-known/acme-challenge/xxxxxxxxx) # We use ^~ here, so that we don't check any regexes (which could # otherwise easily override this intended match accidentally). + '' location ^~ /.well-known/acme-challenge/ { ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"} ${optionalString (vhost.acmeRoot != null) "root ${vhost.acmeRoot};"} @@ -375,10 +376,11 @@ let ${concatMapStringsSep "\n" listenString redirectListen} server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; - ${acmeLocation} + location / { return ${toString vhost.redirectCode} https://$host$request_uri; } + ${acmeLocation} } ''} @@ -392,13 +394,6 @@ let http3 ${if vhost.http3 then "on" else "off"}; http3_hq ${if vhost.http3_hq then "on" else "off"}; ''} - ${acmeLocation} - ${optionalString (vhost.root != null) "root ${vhost.root};"} - ${optionalString (vhost.globalRedirect != null) '' - location / { - return ${toString vhost.redirectCode} http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri; - } - ''} ${optionalString hasSSL '' ssl_certificate ${vhost.sslCertificate}; ssl_certificate_key ${vhost.sslCertificateKey}; @@ -413,14 +408,16 @@ let ssl_conf_command Options KTLS; ''} - ${optionalString (hasSSL && vhost.quic && vhost.http3) - # Advertise that HTTP/3 is available - '' - add_header Alt-Svc 'h3=":$server_port"; ma=86400'; - ''} - ${mkBasicAuth vhostName vhost} + ${optionalString (vhost.root != null) "root ${vhost.root};"} + + ${optionalString (vhost.globalRedirect != null) '' + location / { + return ${toString vhost.redirectCode} http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri; + } + ''} + ${acmeLocation} ${mkLocations vhost.locations} ${vhost.extraConfig} @@ -472,7 +469,7 @@ let mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix; - oldHTTP2 = versionOlder cfg.package.version "1.25.1"; + oldHTTP2 = (versionOlder cfg.package.version "1.25.1" && !(cfg.package.pname == "angie" || cfg.package.pname == "angieQuic")); in { @@ -649,6 +646,8 @@ in Nginx package to use. This defaults to the stable version. Note that the nginx team recommends to use the mainline version which available in nixpkgs as `nginxMainline`. + Supported Nginx forks include `angie`, `openresty` and `tengine`. + For HTTP/3 support use `nginxQuic` or `angieQuic`. ''; }; @@ -1128,14 +1127,6 @@ in } { - assertion = any (host: host.kTLS) (attrValues virtualHosts) -> versionAtLeast cfg.package.version "1.21.4"; - message = '' - services.nginx.virtualHosts.<name>.kTLS requires nginx version - 1.21.4 or above; see the documentation for services.nginx.package. - ''; - } - - { assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts); message = '' Options services.nginx.service.virtualHosts.<name>.enableACME and @@ -1144,18 +1135,20 @@ in } { - assertion = cfg.package.pname != "nginxQuic" -> !(cfg.enableQuicBPF); + assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> !(cfg.enableQuicBPF); message = '' services.nginx.enableQuicBPF requires using nginxQuic package, - which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`. + which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or + `services.nginx.package = pkgs.angieQuic;`. ''; } { - assertion = cfg.package.pname != "nginxQuic" -> all (host: !host.quic) (attrValues virtualHosts); + assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> all (host: !host.quic) (attrValues virtualHosts); message = '' - services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic package, - which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`. + services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic or angie packages, + which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or + `services.nginx.package = pkgs.angieQuic;`. ''; } @@ -1341,6 +1334,8 @@ in nginx.gid = config.ids.gids.nginx; }; + boot.kernelModules = optional (versionAtLeast config.boot.kernelPackages.kernel.version "4.17") "tls"; + # do not delete the default temp directories created upon nginx startup systemd.tmpfiles.rules = [ "X /tmp/systemd-private-%b-nginx.service-*/tmp/nginx_*" diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix index 64a95afab9f4..ea98439d3823 100644 --- a/nixos/modules/services/web-servers/nginx/vhost-options.nix +++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -235,9 +235,9 @@ with lib; which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` and activate the QUIC transport protocol `services.nginx.virtualHosts.<name>.quic = true;`. - Note that HTTP/3 support is experimental and - *not* yet recommended for production. + Note that HTTP/3 support is experimental and *not* yet recommended for production. Read more at https://quic.nginx.org/ + HTTP/3 availability must be manually advertised, preferably in each location block. ''; }; @@ -250,8 +250,7 @@ with lib; which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` and activate the QUIC transport protocol `services.nginx.virtualHosts.<name>.quic = true;`. - Note that special application protocol support is experimental and - *not* yet recommended for production. + Note that special application protocol support is experimental and *not* yet recommended for production. Read more at https://quic.nginx.org/ ''; }; diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix index e9cadf219468..f5a6c05865c4 100644 --- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix +++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix @@ -79,20 +79,19 @@ in package = mkDefault pkgs.cinnamon.mint-cursor-themes; }; }; - services.xserver.displayManager.sessionCommands = '' - if test "$XDG_CURRENT_DESKTOP" = "Cinnamon"; then - true - ${concatMapStrings (p: '' - if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then - export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name} - fi - - if [ -d "${p}/lib/girepository-1.0" ]; then - export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0 - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib - fi - '') cfg.sessionPath} - fi + + # Have to take care of GDM + Cinnamon on Wayland users + environment.extraInit = '' + ${concatMapStrings (p: '' + if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then + export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name} + fi + + if [ -d "${p}/lib/girepository-1.0" ]; then + export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0 + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib + fi + '') cfg.sessionPath} ''; # Default services diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix index 20eca7746447..2cf9bc2eac37 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome.nix +++ b/nixos/modules/services/x11/desktop-managers/gnome.nix @@ -449,7 +449,6 @@ in gnome-color-manager gnome-control-center gnome-shell-extensions - gnome-themes-extra pkgs.gnome-tour # GNOME Shell detects the .desktop file on first log-in. pkgs.gnome-user-docs pkgs.orca diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix index 027479b1ce09..fc9de2500ba4 100644 --- a/nixos/modules/services/x11/desktop-managers/plasma5.nix +++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix @@ -26,10 +26,8 @@ let emptyValue.value = {}; }; - libsForQt5 = pkgs.plasma5Packages; - inherit (libsForQt5) kdeGear kdeFrameworks plasma5; inherit (lib) - getBin optionalAttrs optionalString literalExpression + getBin optionalAttrs literalExpression mkRemovedOptionModule mkRenamedOptionModule mkDefault mkIf mkMerge mkOption mkPackageOption types; @@ -65,7 +63,7 @@ let # recognize that software that has been removed. rm -fv $HOME/.cache/ksycoca* - ${libsForQt5.kservice}/bin/kbuildsycoca5 + ${pkgs.plasma5Packages.kservice}/bin/kbuildsycoca5 ''; set_XDG_CONFIG_HOME = '' @@ -176,20 +174,19 @@ in owner = "root"; group = "root"; capabilities = "cap_sys_nice+ep"; - source = "${getBin plasma5.kwin}/bin/kwin_wayland"; + source = "${getBin pkgs.plasma5Packages.kwin}/bin/kwin_wayland"; }; } // optionalAttrs (!cfg.runUsingSystemd) { start_kdeinit = { setuid = true; owner = "root"; group = "root"; - source = "${getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit"; + source = "${getBin pkgs.plasma5Packages.kinit}/libexec/kf5/start_kdeinit"; }; }; environment.systemPackages = - with libsForQt5; - with plasma5; with kdeGear; with kdeFrameworks; + with pkgs.plasma5Packages; let requiredPackages = [ frameworkintegration @@ -284,8 +281,8 @@ in ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages # Phonon audio backend - ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer - ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc + ++ lib.optional (cfg.phononBackend == "gstreamer") pkgs.plasma5Packages.phonon-backend-gstreamer + ++ lib.optional (cfg.phononBackend == "vlc") pkgs.plasma5Packages.phonon-backend-vlc # Optional hardware support features ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt pkgs.openobex pkgs.obexftp ] @@ -295,13 +292,13 @@ in ++ lib.optional config.powerManagement.enable powerdevil ++ lib.optional config.services.colord.enable pkgs.colord-kde ++ lib.optional config.services.hardware.bolt.enable pkgs.plasma5Packages.plasma-thunderbolt - ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ] + ++ lib.optional config.services.samba.enable kdenetwork-filesharing ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet ++ lib.optional config.services.flatpak.enable flatpak-kcm; # Extra services for D-Bus activation services.dbus.packages = [ - plasma5.kactivitymanagerd + pkgs.plasma5Packages.kactivitymanagerd ]; environment.pathsToLink = [ @@ -334,7 +331,7 @@ in serif = [ "Noto Serif" ]; }; - programs.ssh.askPassword = mkDefault "${plasma5.ksshaskpass.out}/bin/ksshaskpass"; + programs.ssh.askPassword = mkDefault "${pkgs.plasma5Packages.ksshaskpass.out}/bin/ksshaskpass"; # Enable helpful DBus services. services.accounts-daemon.enable = true; @@ -372,8 +369,8 @@ in }; xdg.portal.enable = true; - xdg.portal.extraPortals = [ plasma5.xdg-desktop-portal-kde ]; - xdg.portal.configPackages = mkDefault [ plasma5.xdg-desktop-portal-kde ]; + xdg.portal.extraPortals = [ pkgs.plasma5Packages.xdg-desktop-portal-kde ]; + xdg.portal.configPackages = mkDefault [ pkgs.plasma5Packages.xdg-desktop-portal-kde ]; # xdg-desktop-portal-kde expects PipeWire to be running. # This does not, by default, replace PulseAudio. services.pipewire.enable = mkDefault true; @@ -404,15 +401,14 @@ in '' ]; - services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-workspace ]; + services.xserver.displayManager.sessionPackages = [ pkgs.plasma5Packages.plasma-workspace ]; # Default to be `plasma` (X11) instead of `plasmawayland`, since plasma wayland currently has # many tiny bugs. # See: https://github.com/NixOS/nixpkgs/issues/143272 services.xserver.displayManager.defaultSession = mkDefault "plasma"; environment.systemPackages = - with libsForQt5; - with plasma5; with kdeGear; with kdeFrameworks; + with pkgs.plasma5Packages; let requiredPackages = [ ksystemstats @@ -448,7 +444,7 @@ in script = '' ${set_XDG_CONFIG_HOME} - ${kdeFrameworks.kconfig}/bin/kwriteconfig5 \ + ${pkgs.plasma5Packages.kconfig}/bin/kwriteconfig5 \ --file startkderc --group General --key systemdBoot ${lib.boolToString cfg.runUsingSystemd} ''; }; @@ -476,8 +472,7 @@ in ]; environment.systemPackages = - with libsForQt5; - with plasma5; with kdeApplications; with kdeFrameworks; + with pkgs.plasma5Packages; [ # Basic packages without which Plasma Mobile fails to work properly. plasma-mobile @@ -536,7 +531,7 @@ in }; }; - services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-mobile ]; + services.xserver.displayManager.sessionPackages = [ pkgs.plasma5Packages.plasma-mobile ]; }) # Plasma Bigscreen diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix index 16a7ff1a4bd5..3e2d5780a5cb 100644 --- a/nixos/modules/services/x11/display-managers/default.nix +++ b/nixos/modules/services/x11/display-managers/default.nix @@ -514,7 +514,7 @@ in # Make xsessions and wayland sessions available in XDG_DATA_DIRS # as some programs have behavior that depends on them being present - environment.sessionVariables.XDG_DATA_DIRS = [ + environment.sessionVariables.XDG_DATA_DIRS = lib.mkIf (cfg.displayManager.sessionPackages != [ ]) [ "${cfg.displayManager.sessionData.desktops}/share" ]; }; diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix index 6ca7a4425f89..0576619cc8d2 100644 --- a/nixos/modules/services/x11/display-managers/sddm.nix +++ b/nixos/modules/services/x11/display-managers/sddm.nix @@ -7,7 +7,7 @@ let cfg = dmcfg.sddm; xEnv = config.systemd.services.display-manager.environment; - sddm = pkgs.libsForQt5.sddm; + sddm = cfg.package; iniFmt = pkgs.formats.ini { }; @@ -108,6 +108,8 @@ in ''; }; + package = mkPackageOption pkgs [ "plasma5Packages" "sddm" ] {}; + enableHidpi = mkOption { type = types.bool; default = true; diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix index d2a5b5895e0a..0ea21eb1dce3 100644 --- a/nixos/modules/services/x11/hardware/libinput.nix +++ b/nixos/modules/services/x11/hardware/libinput.nix @@ -130,9 +130,9 @@ let cfg = config.services.xserver.libinput; default = true; description = lib.mdDoc '' - Disables horizontal scrolling. When disabled, this driver will discard any horizontal scroll - events from libinput. Note that this does not disable horizontal scrolling, it merely - discards the horizontal axis from any scroll events. + Enables or disables horizontal scrolling. When disabled, this driver will discard any + horizontal scroll events from libinput. This does not disable horizontal scroll events + from libinput; it merely discards the horizontal axis from any scroll events. ''; }; |