diff options
Diffstat (limited to 'nixos/modules/services')
94 files changed, 1604 insertions, 527 deletions
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix index a3c0715c9e607..abb7925e0935f 100644 --- a/nixos/modules/services/backup/borgbackup.nix +++ b/nixos/modules/services/backup/borgbackup.nix @@ -104,6 +104,9 @@ let --what="sleep" \ --why="Scheduled backup" \ '' + backupScript; + unitConfig = optionalAttrs (isLocalPath cfg.repo) { + RequiresMountsFor = [ cfg.repo ]; + }; serviceConfig = { User = cfg.user; Group = cfg.group; diff --git a/nixos/modules/services/backup/duplicity.nix b/nixos/modules/services/backup/duplicity.nix index 033d0cffd8d6e..46625ec5460e4 100644 --- a/nixos/modules/services/backup/duplicity.nix +++ b/nixos/modules/services/backup/duplicity.nix @@ -42,6 +42,28 @@ in ''; }; + includeFileList = mkOption { + type = types.nullOr types.path; + default = null; + example = /path/to/fileList.txt; + description = '' + File containing newline-separated list of paths to include into the + backups. See the FILE SELECTION section in {manpage}`duplicity(1)` for + details on the syntax. + ''; + }; + + excludeFileList = mkOption { + type = types.nullOr types.path; + default = null; + example = /path/to/fileList.txt; + description = '' + File containing newline-separated list of paths to exclude into the + backups. See the FILE SELECTION section in {manpage}`duplicity(1)` for + details on the syntax. + ''; + }; + targetUrl = mkOption { type = types.str; example = "s3://host:port/prefix"; @@ -154,6 +176,8 @@ in ${lib.optionalString (cfg.cleanup.maxIncr != null) "${dup} remove-all-inc-of-but-n-full ${toString cfg.cleanup.maxIncr} ${target} --force ${extra}"} exec ${dup} ${if cfg.fullIfOlderThan == "always" then "full" else "incr"} ${lib.escapeShellArgs ( [ cfg.root cfg.targetUrl ] + ++ lib.optionals (cfg.includeFileList != null) [ "--include-filelist" cfg.includeFileList ] + ++ lib.optionals (cfg.excludeFileList != null) [ "--exclude-filelist" cfg.excludeFileList ] ++ concatMap (p: [ "--include" p ]) cfg.include ++ concatMap (p: [ "--exclude" p ]) cfg.exclude ++ (lib.optionals (cfg.fullIfOlderThan != "never" && cfg.fullIfOlderThan != "always") [ "--full-if-older-than" cfg.fullIfOlderThan ]) diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix index dc5d8f09e069b..9e1abb85bfe54 100644 --- a/nixos/modules/services/backup/tsm.nix +++ b/nixos/modules/services/backup/tsm.nix @@ -90,7 +90,7 @@ in environment.HOME = "/var/lib/tsm-backup"; serviceConfig = { # for exit status description see - # https://www.ibm.com/docs/en/storage-protect/8.1.22?topic=clients-client-return-codes + # https://www.ibm.com/docs/en/storage-protect/8.1.23?topic=clients-client-return-codes SuccessExitStatus = "4 8"; # The `-se` option must come after the command. # The `-optfile` option suppresses a `dsm.opt`-not-found warning. diff --git a/nixos/modules/services/cluster/patroni/default.nix b/nixos/modules/services/cluster/patroni/default.nix index d1a165603fdaa..3b563bb89fffb 100644 --- a/nixos/modules/services/cluster/patroni/default.nix +++ b/nixos/modules/services/cluster/patroni/default.nix @@ -10,6 +10,15 @@ let configFile = format.generate configFileName cfg.settings; in { + imports = [ + (lib.mkRemovedOptionModule [ "services" "patroni" "raft" ] '' + Raft has been deprecated by upstream. + '') + (lib.mkRemovedOptionModule [ "services" "patroni" "raftPort" ] '' + Raft has been deprecated by upstream. + '') + ]; + options.services.patroni = { enable = mkEnableOption "Patroni"; @@ -68,7 +77,7 @@ in type = types.path; default = "/var/lib/patroni"; description = '' - Folder where Patroni data will be written, used by Raft as well if enabled. + Folder where Patroni data will be written, this is where the pgpass password file will be written. ''; }; @@ -120,22 +129,6 @@ in ''; }; - raft = mkOption { - type = types.bool; - default = false; - description = '' - This will configure Patroni to use its own RAFT implementation instead of using a dedicated DCS. - ''; - }; - - raftPort = mkOption { - type = types.port; - default = 5010; - description = '' - The port on which RAFT listens. - ''; - }; - softwareWatchdog = mkOption { type = types.bool; default = false; @@ -178,12 +171,6 @@ in connect_address = "${cfg.nodeIp}:${toString cfg.restApiPort}"; }; - raft = mkIf cfg.raft { - data_dir = "${cfg.dataDir}/raft"; - self_addr = "${cfg.nodeIp}:5010"; - partner_addrs = map (ip: ip + ":5010") cfg.otherNodesIps; - }; - postgresql = { listen = "${cfg.nodeIp}:${toString cfg.postgresqlPort}"; connect_address = "${cfg.nodeIp}:${toString cfg.postgresqlPort}"; @@ -235,7 +222,7 @@ in KillMode = "process"; } (mkIf (cfg.postgresqlDataDir == "/var/lib/postgresql/${cfg.postgresqlPackage.psqlSchema}" && cfg.dataDir == "/var/lib/patroni") { - StateDirectory = "patroni patroni/raft postgresql postgresql/${cfg.postgresqlPackage.psqlSchema}"; + StateDirectory = "patroni postgresql postgresql/${cfg.postgresqlPackage.psqlSchema}"; StateDirectoryMode = "0750"; }) ]; @@ -251,7 +238,6 @@ in environment.systemPackages = [ pkgs.patroni cfg.postgresqlPackage - (mkIf cfg.raft pkgs.python310Packages.pysyncobj) ]; environment.etc."${configFileName}".source = configFile; diff --git a/nixos/modules/services/cluster/rke2/default.nix b/nixos/modules/services/cluster/rke2/default.nix index 9ddbd299fdf8d..51b849ebcc802 100644 --- a/nixos/modules/services/cluster/rke2/default.nix +++ b/nixos/modules/services/cluster/rke2/default.nix @@ -241,7 +241,7 @@ in "kernel.panic_on_oops" = 1; }; - systemd.services.rke2 = { + systemd.services."rke2-${cfg.role}" = { description = "Rancher Kubernetes Engine v2"; documentation = [ "https://github.com/rancher/rke2#readme" ]; after = [ "network-online.target" ]; diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix index 1771ca0b980b9..c603aff38038a 100644 --- a/nixos/modules/services/continuous-integration/gitlab-runner.nix +++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix @@ -137,8 +137,10 @@ let "--builds-dir ${service.buildsDir}" ++ optional (service.cloneUrl != null) "--clone-url ${service.cloneUrl}" - ++ optional (service.preCloneScript != null) - "--pre-clone-script ${service.preCloneScript}" + ++ optional (service.preGetSourcesScript != null) + "--pre-get-sources-script ${service.preGetSourcesScript}" + ++ optional (service.postGetSourcesScript != null) + "--post-get-sources-script ${service.postGetSourcesScript}" ++ optional (service.preBuildScript != null) "--pre-build-script ${service.preBuildScript}" ++ optional (service.postBuildScript != null) @@ -495,13 +497,20 @@ in { Whitelist allowed services. ''; }; - preCloneScript = mkOption { + preGetSourcesScript = mkOption { type = types.nullOr types.path; default = null; description = '' Runner-specific command script executed before code is pulled. ''; }; + postGetSourcesScript = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Runner-specific command script executed after code is pulled. + ''; + }; preBuildScript = mkOption { type = types.nullOr types.path; default = null; diff --git a/nixos/modules/services/continuous-integration/woodpecker/agents.nix b/nixos/modules/services/continuous-integration/woodpecker/agents.nix index ce5926a246bbe..b88bc6a0ccac0 100644 --- a/nixos/modules/services/continuous-integration/woodpecker/agents.nix +++ b/nixos/modules/services/continuous-integration/woodpecker/agents.nix @@ -109,7 +109,7 @@ let }; in { - meta.maintainers = with lib.maintainers; [ janik ambroisie ]; + meta.maintainers = with lib.maintainers; [ ambroisie ]; options = { services.woodpecker-agents = { diff --git a/nixos/modules/services/continuous-integration/woodpecker/server.nix b/nixos/modules/services/continuous-integration/woodpecker/server.nix index 54d8da8a59e5e..6e3cfb0b0114c 100644 --- a/nixos/modules/services/continuous-integration/woodpecker/server.nix +++ b/nixos/modules/services/continuous-integration/woodpecker/server.nix @@ -8,7 +8,7 @@ let cfg = config.services.woodpecker-server; in { - meta.maintainers = with lib.maintainers; [ janik ambroisie ]; + meta.maintainers = with lib.maintainers; [ ambroisie ]; options = { diff --git a/nixos/modules/services/databases/monetdb.nix b/nixos/modules/services/databases/monetdb.nix index 5025eb30369b4..ee24cf2b0fc20 100644 --- a/nixos/modules/services/databases/monetdb.nix +++ b/nixos/modules/services/databases/monetdb.nix @@ -6,7 +6,7 @@ let cfg = config.services.monetdb; in { - meta.maintainers = with maintainers; [ StillerHarpo primeos ]; + meta.maintainers = with maintainers; [ StillerHarpo ]; ###### interface options = { diff --git a/nixos/modules/services/desktop-managers/lomiri.nix b/nixos/modules/services/desktop-managers/lomiri.nix index 0b871aa38183e..e1de29ba3b597 100644 --- a/nixos/modules/services/desktop-managers/lomiri.nix +++ b/nixos/modules/services/desktop-managers/lomiri.nix @@ -21,8 +21,11 @@ in { history-service libusermetrics lomiri + lomiri-calculator-app + lomiri-clock-app lomiri-download-manager lomiri-filemanager-app + lomiri-polkit-agent lomiri-schemas # exposes some required dbus interfaces lomiri-session # wrappers to properly launch the session lomiri-sounds @@ -35,7 +38,7 @@ in { morph-browser qtmir # not having its desktop file for Xwayland available causes any X11 application to crash the session suru-icon-theme - # telephony-service # currently broken: https://github.com/NixOS/nixpkgs/pull/314043 + telephony-service ]); variables = { # To override the keyboard layouts in Lomiri @@ -84,7 +87,7 @@ in { ] ++ lib.optionals (config.hardware.pulseaudio.enable || config.services.pipewire.pulse.enable) [ ayatana-indicator-sound ]) ++ (with pkgs.lomiri; [ - # telephony-service # currently broken: https://github.com/NixOS/nixpkgs/pull/314043 + telephony-service ] ++ lib.optionals config.networking.networkmanager.enable [ lomiri-indicator-network ]); @@ -145,6 +148,18 @@ in { ExecStart = "${pkgs.lomiri.lomiri-url-dispatcher}/libexec/lomiri-url-dispatcher/lomiri-update-directory /run/current-system/sw/share/lomiri-url-dispatcher/urls/"; }; }; + + "lomiri-polkit-agent" = rec { + description = "Lomiri Polkit agent"; + wantedBy = [ "lomiri.service" "lomiri-full-greeter.service" "lomiri-full-shell.service" "lomiri-greeter.service" "lomiri-shell.service" ]; + after = [ "graphical-session.target" ]; + partOf = wantedBy; + serviceConfig = { + Type = "simple"; + Restart = "always"; + ExecStart = "${pkgs.lomiri.lomiri-polkit-agent}/libexec/lomiri-polkit-agent/policykit-agent"; + }; + }; }; systemd.services = { diff --git a/nixos/modules/services/desktops/espanso.nix b/nixos/modules/services/desktops/espanso.nix index a6b8a078247b1..a2c4b77b90464 100644 --- a/nixos/modules/services/desktops/espanso.nix +++ b/nixos/modules/services/desktops/espanso.nix @@ -8,6 +8,7 @@ in { options = { services.espanso = { enable = mkEnableOption "Espanso"; + wayland = mkEnableOption "use the Wayland compatible espanso package"; package = mkPackageOption pkgs "espanso" { example = "pkgs.espanso-wayland"; }; diff --git a/nixos/modules/services/desktops/gnome/gnome-keyring.nix b/nixos/modules/services/desktops/gnome/gnome-keyring.nix index 02b198fd81cb9..550c6ba8eff54 100644 --- a/nixos/modules/services/desktops/gnome/gnome-keyring.nix +++ b/nixos/modules/services/desktops/gnome/gnome-keyring.nix @@ -26,33 +26,22 @@ in }; config = lib.mkIf cfg.enable { - environment.systemPackages = [ pkgs.gnome.gnome-keyring ]; + environment.systemPackages = [ pkgs.gnome-keyring ]; services.dbus.packages = [ - pkgs.gnome.gnome-keyring + pkgs.gnome-keyring pkgs.gcr ]; - xdg.portal.extraPortals = [ pkgs.gnome.gnome-keyring ]; - - security.pam.services = lib.mkMerge [ - { - login.enableGnomeKeyring = true; - } - (lib.mkIf config.services.xserver.displayManager.gdm.enable { - gdm-password.enableGnomeKeyring = true; - gdm-autologin.enableGnomeKeyring = true; - }) - (lib.mkIf (config.services.xserver.displayManager.gdm.enable && config.services.fprintd.enable) { - gdm-fingerprint.enableGnomeKeyring = true; - }) - ]; + xdg.portal.extraPortals = [ pkgs.gnome-keyring ]; + + security.pam.services.login.enableGnomeKeyring = true; security.wrappers.gnome-keyring-daemon = { owner = "root"; group = "root"; capabilities = "cap_ipc_lock=ep"; - source = "${pkgs.gnome.gnome-keyring}/bin/gnome-keyring-daemon"; + source = "${pkgs.gnome-keyring}/bin/gnome-keyring-daemon"; }; }; } diff --git a/nixos/modules/services/desktops/gnome/gnome-user-share.nix b/nixos/modules/services/desktops/gnome/gnome-user-share.nix index 2c6d94b7bdfc6..518beb80419a9 100644 --- a/nixos/modules/services/desktops/gnome/gnome-user-share.nix +++ b/nixos/modules/services/desktops/gnome/gnome-user-share.nix @@ -26,11 +26,11 @@ config = lib.mkIf config.services.gnome.gnome-user-share.enable { environment.systemPackages = [ - pkgs.gnome.gnome-user-share + pkgs.gnome-user-share ]; systemd.packages = [ - pkgs.gnome.gnome-user-share + pkgs.gnome-user-share ]; }; diff --git a/nixos/modules/services/desktops/gnome/rygel.nix b/nixos/modules/services/desktops/gnome/rygel.nix index c980b239d521e..7ce7e079b6af6 100644 --- a/nixos/modules/services/desktops/gnome/rygel.nix +++ b/nixos/modules/services/desktops/gnome/rygel.nix @@ -23,12 +23,12 @@ ###### implementation config = lib.mkIf config.services.gnome.rygel.enable { - environment.systemPackages = [ pkgs.gnome.rygel ]; + environment.systemPackages = [ pkgs.rygel ]; - services.dbus.packages = [ pkgs.gnome.rygel ]; + services.dbus.packages = [ pkgs.rygel ]; - systemd.packages = [ pkgs.gnome.rygel ]; + systemd.packages = [ pkgs.rygel ]; - environment.etc."rygel.conf".source = "${pkgs.gnome.rygel}/etc/rygel.conf"; + environment.etc."rygel.conf".source = "${pkgs.rygel}/etc/rygel.conf"; }; } diff --git a/nixos/modules/services/desktops/gnome/sushi.nix b/nixos/modules/services/desktops/gnome/sushi.nix index 946030e4bb229..7f7360488eb41 100644 --- a/nixos/modules/services/desktops/gnome/sushi.nix +++ b/nixos/modules/services/desktops/gnome/sushi.nix @@ -31,9 +31,9 @@ config = lib.mkIf config.services.gnome.sushi.enable { - environment.systemPackages = [ pkgs.gnome.sushi ]; + environment.systemPackages = [ pkgs.sushi ]; - services.dbus.packages = [ pkgs.gnome.sushi ]; + services.dbus.packages = [ pkgs.sushi ]; }; diff --git a/nixos/modules/services/desktops/playerctld.nix b/nixos/modules/services/desktops/playerctld.nix new file mode 100644 index 0000000000000..ef4866d75715d --- /dev/null +++ b/nixos/modules/services/desktops/playerctld.nix @@ -0,0 +1,32 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.services.playerctld; +in +{ + options.services.playerctld = { + enable = lib.mkEnableOption "the playerctld daemon"; + + package = lib.mkPackageOption pkgs "playerctl" { }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + systemd.user.services.playerctld = { + description = "Playerctld daemon to track media player activity"; + wantedBy = [ "default.target" ]; + + serviceConfig = { + Type = "exec"; + ExecStart = "${cfg.package}/bin/playerctld"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ aacebedo ]; +} diff --git a/nixos/modules/services/games/terraria.nix b/nixos/modules/services/games/terraria.nix index 57417b614f713..4c32cc92a1b94 100644 --- a/nixos/modules/services/games/terraria.nix +++ b/nixos/modules/services/games/terraria.nix @@ -19,15 +19,28 @@ let (boolFlag "secure" cfg.secure) (boolFlag "noupnp" cfg.noUPnP) ]; - stopScript = pkgs.writeScript "terraria-stop" '' - #!${pkgs.runtimeShell} + tmuxCmd = "${lib.getExe pkgs.tmux} -S ${lib.escapeShellArg cfg.dataDir}/terraria.sock"; + + stopScript = pkgs.writeShellScript "terraria-stop" '' if ! [ -d "/proc/$1" ]; then exit 0 fi - ${getBin pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/terraria.sock send-keys Enter exit Enter - ${getBin pkgs.coreutils}/bin/tail --pid="$1" -f /dev/null + lastline=$(${tmuxCmd} capture-pane -p | grep . | tail -n1) + + # If the service is not configured to auto-start a world, it will show the world selection prompt + # If the last non-empty line on-screen starts with "Choose World", we know the prompt is open + if [[ "$lastline" =~ ^'Choose World' ]]; then + # In this case, nothing needs to be saved, so we can kill the process + ${tmuxCmd} kill-session + else + # Otherwise, we send the `exit` command + ${tmuxCmd} send-keys Enter exit Enter + fi + + # Wait for the process to stop + tail --pid="$1" -f /dev/null ''; in { @@ -152,7 +165,7 @@ in Type = "forking"; GuessMainPID = true; UMask = 007; - ExecStart = "${getBin pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/terraria.sock new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}"; + ExecStart = "${tmuxCmd} new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}"; ExecStop = "${stopScript} $MAINPID"; }; }; diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix index 78f627d33e2df..2914877bdccde 100644 --- a/nixos/modules/services/mail/roundcube.nix +++ b/nixos/modules/services/mail/roundcube.nix @@ -93,13 +93,17 @@ in maxAttachmentSize = mkOption { type = types.int; default = 18; + apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.37)}M"; description = '' The maximum attachment size in MB. - - Note: Since roundcube only uses 70% of max upload values configured in php - 30% is added automatically to [](#opt-services.roundcube.maxAttachmentSize). + [upstream issue comment]: https://github.com/roundcube/roundcubemail/issues/7979#issuecomment-808879209 + ::: {.note} + Since there is some overhead in base64 encoding applied to attachments, + 37% will be added + to the value set in this option in order to offset the overhead. For example, setting + `maxAttachmentSize` to `100` would result in `137M` being the real value in the configuration. + See [upstream issue comment] for more details on the motivations behind this. + ::: ''; - apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M"; }; configureNginx = lib.mkOption { diff --git a/nixos/modules/services/mail/stalwart-mail.nix b/nixos/modules/services/mail/stalwart-mail.nix index 776243a68af53..1025788f0d84d 100644 --- a/nixos/modules/services/mail/stalwart-mail.nix +++ b/nixos/modules/services/mail/stalwart-mail.nix @@ -9,12 +9,28 @@ let dataDir = "/var/lib/stalwart-mail"; useLegacyStorage = versionOlder config.system.stateVersion "24.11"; + parsePorts = listeners: let + parseAddresses = listeners: lib.flatten(lib.mapAttrsToList (name: value: value.bind) listeners); + splitAddress = addr: strings.splitString ":" addr; + extractPort = addr: strings.toInt(builtins.foldl' (a: b: b) "" (splitAddress addr)); + in + builtins.map(address: extractPort address) (parseAddresses listeners); + in { options.services.stalwart-mail = { enable = mkEnableOption "the Stalwart all-in-one email server"; package = mkPackageOption pkgs "stalwart-mail" { }; + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Whether to open TCP firewall ports, which are specified in + {option}`services.stalwart-mail.settings.listener` on all interfaces. + ''; + }; + settings = mkOption { inherit (configFormat) type; default = { }; @@ -138,6 +154,11 @@ in { # Make admin commands available in the shell environment.systemPackages = [ cfg.package ]; + + networking.firewall = mkIf (cfg.openFirewall + && (builtins.hasAttr "listener" cfg.settings.server)) { + allowedTCPPorts = parsePorts cfg.settings.server.listener; + }; }; meta = { diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix index 8cb51bb0b7a7f..6660d5e977ffb 100644 --- a/nixos/modules/services/misc/dictd.nix +++ b/nixos/modules/services/misc/dictd.nix @@ -62,6 +62,9 @@ in description = "DICT.org Dictionary Server"; wantedBy = [ "multi-user.target" ]; environment = { LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; }; + # Work around the fact that dictd doesn't handle SIGTERM; it terminates + # with code 143 instead of exiting with code 0. + serviceConfig.SuccessExitStatus = [ 143 ]; serviceConfig.Type = "forking"; script = "${pkgs.dict}/sbin/dictd -s -c ${dictdb}/share/dictd/dictd.conf --locale en_US.UTF-8"; }; diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix index 7b96a182f0d94..492c669f180a9 100644 --- a/nixos/modules/services/misc/gitlab.nix +++ b/nixos/modules/services/misc/gitlab.nix @@ -12,7 +12,7 @@ let postgresqlPackage = if config.services.postgresql.enable then config.services.postgresql.package else - pkgs.postgresql_13; + pkgs.postgresql_14; gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket"; gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket"; @@ -1119,8 +1119,8 @@ in { message = "services.gitlab.secrets.jwsFile must be set!"; } { - assertion = versionAtLeast postgresqlPackage.version "13.6.0"; - message = "PostgreSQL >=13.6 is required to run GitLab 16. Follow the instructions in the manual section for upgrading PostgreSQL here: https://nixos.org/manual/nixos/stable/index.html#module-services-postgres-upgrading"; + assertion = versionAtLeast postgresqlPackage.version "14.9"; + message = "PostgreSQL >= 14.9 is required to run GitLab 17. Follow the instructions in the manual section for upgrading PostgreSQL here: https://nixos.org/manual/nixos/stable/index.html#module-services-postgres-upgrading"; } ]; @@ -1282,6 +1282,7 @@ in { "d ${gitlabConfig.production.shared.path}/registry 0750 ${cfg.user} ${cfg.group} -" "d ${gitlabConfig.production.shared.path}/terraform_state 0750 ${cfg.user} ${cfg.group} -" "d ${gitlabConfig.production.shared.path}/ci_secure_files 0750 ${cfg.user} ${cfg.group} -" + "d ${gitlabConfig.production.shared.path}/external-diffs 0750 ${cfg.user} ${cfg.group} -" "L+ /run/gitlab/config - - - - ${cfg.statePath}/config" "L+ /run/gitlab/log - - - - ${cfg.statePath}/log" "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp" diff --git a/nixos/modules/services/misc/jellyseerr.nix b/nixos/modules/services/misc/jellyseerr.nix index 7599a1af33840..9aab517e0493b 100644 --- a/nixos/modules/services/misc/jellyseerr.nix +++ b/nixos/modules/services/misc/jellyseerr.nix @@ -9,6 +9,7 @@ in options.services.jellyseerr = { enable = mkEnableOption ''Jellyseerr, a requests manager for Jellyfin''; + package = mkPackageOption pkgs "jellyseerr" { }; openFirewall = mkOption { type = types.bool; @@ -32,10 +33,10 @@ in serviceConfig = { Type = "exec"; StateDirectory = "jellyseerr"; - WorkingDirectory = "${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr"; + WorkingDirectory = "${cfg.package}/libexec/jellyseerr/deps/jellyseerr"; DynamicUser = true; - ExecStart = "${pkgs.jellyseerr}/bin/jellyseerr"; - BindPaths = [ "/var/lib/jellyseerr/:${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr/config/" ]; + ExecStart = lib.getExe cfg.package; + BindPaths = [ "/var/lib/jellyseerr/:${cfg.package}/libexec/jellyseerr/deps/jellyseerr/config/" ]; Restart = "on-failure"; ProtectHome = true; ProtectSystem = "strict"; diff --git a/nixos/modules/services/misc/languagetool.nix b/nixos/modules/services/misc/languagetool.nix index ba563dace4737..2a7e68c9053a3 100644 --- a/nixos/modules/services/misc/languagetool.nix +++ b/nixos/modules/services/misc/languagetool.nix @@ -1,14 +1,17 @@ -{ config, lib, options, pkgs, ... }: +{ config, lib, pkgs, ... }: with lib; let cfg = config.services.languagetool; - settingsFormat = pkgs.formats.javaProperties {}; -in { + settingsFormat = pkgs.formats.javaProperties { }; +in +{ options.services.languagetool = { enable = mkEnableOption "the LanguageTool server, a multilingual spelling, style, and grammar checker that helps correct or paraphrase texts"; + package = mkPackageOption pkgs "languagetool" { }; + port = mkOption { type = types.port; default = 8081; @@ -31,7 +34,7 @@ in { ''; }; - settings = lib.mkOption { + settings = mkOption { type = types.submodule { freeformType = settingsFormat.type; @@ -49,11 +52,25 @@ in { for supported settings. ''; }; + + jrePackage = mkPackageOption pkgs "jre" { }; + + jvmOptions = mkOption { + description = '' + Extra command line options for the JVM running languagetool. + More information can be found here: https://docs.oracle.com/en/java/javase/19/docs/specs/man/java.html#standard-options-for-java + ''; + default = [ ]; + type = types.listOf types.str; + example = [ + "-Xmx512m" + ]; + }; }; config = mkIf cfg.enable { - systemd.services.languagetool = { + systemd.services.languagetool = { description = "LanguageTool HTTP server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; @@ -65,13 +82,17 @@ in { RestrictNamespaces = [ "" ]; SystemCallFilter = [ "@system-service" "~ @privileged" ]; ProtectHome = "yes"; + Restart = "on-failure"; ExecStart = '' - ${pkgs.languagetool}/bin/languagetool-http-server \ - --port ${toString cfg.port} \ - ${optionalString cfg.public "--public"} \ - ${optionalString (cfg.allowOrigin != null) "--allow-origin ${cfg.allowOrigin}"} \ - "--config" ${settingsFormat.generate "languagetool.conf" cfg.settings} - ''; + ${cfg.jrePackage}/bin/java \ + -cp ${cfg.package}/share/languagetool-server.jar \ + ${toString cfg.jvmOptions} \ + org.languagetool.server.HTTPServer \ + --port ${toString cfg.port} \ + ${optionalString cfg.public "--public"} \ + ${optionalString (cfg.allowOrigin != null) "--allow-origin ${cfg.allowOrigin}"} \ + "--config" ${settingsFormat.generate "languagetool.conf" cfg.settings} + ''; }; }; }; diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix index c460514783efc..a0a32f1702bf3 100644 --- a/nixos/modules/services/misc/ollama.nix +++ b/nixos/modules/services/misc/ollama.nix @@ -5,9 +5,6 @@ let cfg = config.services.ollama; ollamaPackage = cfg.package.override { inherit (cfg) acceleration; - linuxPackages = config.boot.kernelPackages // { - nvidia_x11 = config.hardware.nvidia.package; - }; }; in { diff --git a/nixos/modules/services/misc/renovate.nix b/nixos/modules/services/misc/renovate.nix index 25a719c91cbd8..9062b7424b681 100644 --- a/nixos/modules/services/misc/renovate.nix +++ b/nixos/modules/services/misc/renovate.nix @@ -128,6 +128,7 @@ in RestrictAddressFamilies = [ "AF_INET" "AF_INET6" + "AF_UNIX" ]; RestrictNamespaces = true; RestrictRealtime = true; diff --git a/nixos/modules/services/misc/rkvm.nix b/nixos/modules/services/misc/rkvm.nix index 9d41669e00f61..b149c3d3979f5 100644 --- a/nixos/modules/services/misc/rkvm.nix +++ b/nixos/modules/services/misc/rkvm.nix @@ -7,7 +7,7 @@ let toml = pkgs.formats.toml { }; in { - meta.maintainers = with maintainers; [ ckie ]; + meta.maintainers = with maintainers; [ ]; options.services.rkvm = { enable = mkOption { diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix index 1b16ef7958ad2..fc57683de3280 100644 --- a/nixos/modules/services/misc/snapper.nix +++ b/nixos/modules/services/misc/snapper.nix @@ -96,48 +96,48 @@ let }; TIMELINE_LIMIT_HOURLY = mkOption { - type = types.str; - default = "10"; + type = types.int; + default = 10; description = '' Limits for timeline cleanup. ''; }; TIMELINE_LIMIT_DAILY = mkOption { - type = types.str; - default = "10"; + type = types.int; + default = 10; description = '' Limits for timeline cleanup. ''; }; TIMELINE_LIMIT_WEEKLY = mkOption { - type = types.str; - default = "0"; + type = types.int; + default = 0; description = '' Limits for timeline cleanup. ''; }; TIMELINE_LIMIT_MONTHLY = mkOption { - type = types.str; - default = "10"; + type = types.int; + default = 10; description = '' Limits for timeline cleanup. ''; }; TIMELINE_LIMIT_QUARTERLY = mkOption { - type = types.str; - default = "0"; + type = types.int; + default = 0; description = '' Limits for timeline cleanup. ''; }; TIMELINE_LIMIT_YEARLY = mkOption { - type = types.str; - default = "10"; + type = types.int; + default = 10; description = '' Limits for timeline cleanup. ''; @@ -353,4 +353,6 @@ in ) (attrNames cfg.configs); } ); + + meta.maintainers = with lib.maintainers; [ Djabx ]; } diff --git a/nixos/modules/services/misc/sonarr.nix b/nixos/modules/services/misc/sonarr.nix index 228a2d48f5a9c..60e73198d60de 100644 --- a/nixos/modules/services/misc/sonarr.nix +++ b/nixos/modules/services/misc/sonarr.nix @@ -1,4 +1,4 @@ -{ config, pkgs, lib, ... }: +{ config, pkgs, lib, utils, ... }: with lib; @@ -54,7 +54,11 @@ in Type = "simple"; User = cfg.user; Group = cfg.group; - ExecStart = "${cfg.package}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'"; + ExecStart = utils.escapeSystemdExecArgs [ + (lib.getExe cfg.package) + "-nobrowser" + "-data=${cfg.dataDir}" + ]; Restart = "on-failure"; }; }; diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix index d09cd87febfff..8db63d5386332 100644 --- a/nixos/modules/services/misc/zoneminder.nix +++ b/nixos/modules/services/misc/zoneminder.nix @@ -202,10 +202,10 @@ in { ]; services = { - fcgiwrap = lib.mkIf useNginx { - enable = true; - preforkProcesses = cfg.cameras; - inherit user group; + fcgiwrap.zoneminder = lib.mkIf useNginx { + process.prefork = cfg.cameras; + process.user = user; + process.group = group; }; mysql = lib.mkIf cfg.database.createLocally { @@ -225,9 +225,7 @@ in { default = true; root = "${pkg}/share/zoneminder/www"; listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ]; - extraConfig = let - fcgi = config.services.fcgiwrap; - in '' + extraConfig = '' index index.php; location / { @@ -257,7 +255,7 @@ in { fastcgi_param HTTP_PROXY ""; fastcgi_intercept_errors on; - fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress}; + fastcgi_pass unix:${config.services.fcgiwrap.zoneminder.socket.address}; } location /cache/ { diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 32919950adc1e..eae2658b7ffb8 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -105,7 +105,7 @@ let }; url = mkOption { type = types.str; - default = "localhost"; + default = ""; description = "Url of the datasource."; }; editable = mkOption { diff --git a/nixos/modules/services/monitoring/opentelemetry-collector.nix b/nixos/modules/services/monitoring/opentelemetry-collector.nix index 459cc85324902..d9b8c27ccdfe3 100644 --- a/nixos/modules/services/monitoring/opentelemetry-collector.nix +++ b/nixos/modules/services/monitoring/opentelemetry-collector.nix @@ -6,8 +6,9 @@ let cfg = config.services.opentelemetry-collector; opentelemetry-collector = cfg.package; - settingsFormat = pkgs.formats.yaml {}; -in { + settingsFormat = pkgs.formats.yaml { }; +in +{ options.services.opentelemetry-collector = { enable = mkEnableOption "Opentelemetry Collector"; @@ -15,7 +16,7 @@ in { settings = mkOption { type = settingsFormat.type; - default = {}; + default = { }; description = '' Specify the configuration for Opentelemetry Collector in Nix. @@ -35,9 +36,9 @@ in { config = mkIf cfg.enable { assertions = [{ assertion = ( - (cfg.settings == {}) != (cfg.configFile == null) + (cfg.settings == { }) != (cfg.configFile == null) ); - message = '' + message = '' Please specify a configuration for Opentelemetry Collector with either 'services.opentelemetry-collector.settings' or 'services.opentelemetry-collector.configFile'. @@ -48,21 +49,27 @@ in { description = "Opentelemetry Collector Service Daemon"; wantedBy = [ "multi-user.target" ]; - serviceConfig = let - conf = if cfg.configFile == null - then settingsFormat.generate "config.yaml" cfg.settings - else cfg.configFile; - in - { - ExecStart = "${getExe opentelemetry-collector} --config=file:${conf}"; - DynamicUser = true; - Restart = "always"; - ProtectSystem = "full"; - DevicePolicy = "closed"; - NoNewPrivileges = true; - WorkingDirectory = "/var/lib/opentelemetry-collector"; - StateDirectory = "opentelemetry-collector"; - }; + serviceConfig = + let + conf = + if cfg.configFile == null + then settingsFormat.generate "config.yaml" cfg.settings + else cfg.configFile; + in + { + ExecStart = "${getExe opentelemetry-collector} --config=file:${conf}"; + DynamicUser = true; + Restart = "always"; + ProtectSystem = "full"; + DevicePolicy = "closed"; + NoNewPrivileges = true; + WorkingDirectory = "%S/opentelemetry-collector"; + StateDirectory = "opentelemetry-collector"; + SupplementaryGroups = [ + # allow to read the systemd journal for opentelemetry-collector + "systemd-journal" + ]; + }; }; }; } diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix b/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix index b4307a76e1b02..b3665b66ba406 100644 --- a/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix +++ b/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix @@ -32,9 +32,15 @@ in ${escapeShellArgs cfg.extraFlags} ''; + CapabilityBoundingSet = [ "" ]; + DeviceAllow = [ "" ]; DynamicUser = true; NoNewPrivileges = true; + MemoryDenyWriteExecute = true; + + LockPersonality = true; + ProtectProc = "invisible"; ProtectSystem = "strict"; ProtectHome = "tmpfs"; @@ -43,6 +49,8 @@ in PrivateDevices = true; PrivateIPC = true; + ProcSubset = "pid"; + ProtectHostname = true; ProtectClock = true; ProtectKernelTunables = true; @@ -50,7 +58,10 @@ in ProtectKernelLogs = true; ProtectControlGroups = true; + Restart = "on-failure"; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix index d1d8f2caaf63d..f40ac3c9138ff 100644 --- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix +++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix @@ -181,15 +181,57 @@ in { -i "${alertmanagerYml}" ''; serviceConfig = { - Restart = "always"; - StateDirectory = "alertmanager"; - DynamicUser = true; # implies PrivateTmp - EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; - WorkingDirectory = "/tmp"; ExecStart = "${cfg.package}/bin/alertmanager" + optionalString (length cmdlineArgs != 0) (" \\\n " + concatStringsSep " \\\n " cmdlineArgs); ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; + + CapabilityBoundingSet = [ "" ]; + DeviceAllow = [ "" ]; + DynamicUser = true; + NoNewPrivileges = true; + + MemoryDenyWriteExecute = true; + + LockPersonality = true; + + ProtectProc = "invisible"; + ProtectSystem = "strict"; + ProtectHome = "tmpfs"; + + PrivateTmp = true; + PrivateDevices = true; + PrivateIPC = true; + + ProcSubset = "pid"; + + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + + Restart = "always"; + + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + + StateDirectory = "alertmanager"; + SystemCallFilter = [ + "@system-service" + "~@cpu-emulation" + "~@privileged" + "~@reboot" + "~@setuid" + "~@swap" + ]; + + WorkingDirectory = "/tmp"; }; }; }) diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index dc357f6cc5fb3..0a9d4ef985227 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -29,6 +29,7 @@ let "blackbox" "buildkite-agent" "collectd" + "deluge" "dmarc" "dnsmasq" "dnssec" @@ -408,6 +409,14 @@ in Please ensure you have either `services.prometheus.exporters.idrac.configuration' or `services.prometheus.exporters.idrac.configurationPath' set! ''; + } { + assertion = cfg.deluge.enable -> ( + (cfg.deluge.delugePassword == null) != (cfg.deluge.delugePasswordFile == null) + ); + message = '' + Please ensure you have either `services.prometheus.exporters.deluge.delugePassword' + or `services.prometheus.exporters.deluge.delugePasswordFile' set! + ''; } ] ++ (flip map (attrNames exporterOpts) (exporter: { assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall; message = '' @@ -437,6 +446,13 @@ in hardware.rtl-sdr.enable = mkDefault true; })] ++ [(mkIf config.services.postfix.enable { services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup; + })] ++ [(mkIf config.services.prometheus.exporters.deluge.enable { + system.activationScripts = { + deluge-exported.text = '' + mkdir -p /etc/deluge-exporter + echo "DELUGE_PASSWORD=$(cat ${config.services.prometheus.exporters.deluge.delugePasswordFile})" > /etc/deluge-exporter/password + ''; + }; })] ++ (mapAttrsToList (name: conf: mkExporterConf { inherit name; diff --git a/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix b/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix new file mode 100644 index 0000000000000..5943b46eeb5fc --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix @@ -0,0 +1,85 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.prometheus.exporters.deluge; + inherit (lib) mkOption types concatStringsSep; +in +{ + port = 9354; + + extraOpts = { + delugeHost = mkOption { + type = types.str; + default = "localhost"; + description = '' + Hostname where deluge server is running. + ''; + }; + + delugePort = mkOption { + type = types.port; + default = 58846; + description = '' + Port where deluge server is listening. + ''; + }; + + delugeUser = mkOption { + type = types.str; + default = "localclient"; + description = '' + User to connect to deluge server. + ''; + }; + + delugePassword = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Password to connect to deluge server. + + This stores the password unencrypted in the nix store and is thus considered unsafe. Prefer + using the delugePasswordFile option. + ''; + }; + + delugePasswordFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing the password to connect to deluge server. + ''; + }; + + exportPerTorrentMetrics = mkOption { + type = types.bool; + default = false; + description = '' + Enable per-torrent metrics. + + This may significantly increase the number of time series depending on the number of + torrents in your Deluge instance. + ''; + }; + }; + serviceOpts = { + serviceConfig = { + ExecStart = '' + ${pkgs.prometheus-deluge-exporter}/bin/deluge-exporter + ''; + Environment = [ + "LISTEN_PORT=${toString cfg.port}" + "LISTEN_ADDRESS=${toString cfg.listenAddress}" + + "DELUGE_HOST=${cfg.delugeHost}" + "DELUGE_USER=${cfg.delugeUser}" + "DELUGE_PORT=${toString cfg.delugePort}" + ] ++ lib.optionals (cfg.delugePassword != null) [ + "DELUGE_PASSWORD=${cfg.delugePassword}" + ] ++ lib.optionals cfg.exportPerTorrentMetrics [ + "PER_TORRENT_METRICS=1" + ]; + EnvironmentFile = lib.optionalString (cfg.delugePasswordFile != null) "/etc/deluge-exporter/password"; + }; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix b/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix index 097ea39594788..e470ebe2eb592 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/fastly.nix @@ -1,17 +1,20 @@ -{ config -, lib -, pkgs -, options -, ... +{ + config, + lib, + pkgs, + utils, + ... }: let inherit (lib) - escapeShellArgs + getExe mkOption optionals types - ; + ; + + inherit (utils) escapeSystemdExecArgs; cfg = config.services.prometheus.exporters.fastly; in @@ -39,17 +42,19 @@ in serviceOpts = { serviceConfig = { LoadCredential = "fastly-api-token:${cfg.tokenPath}"; + Environment = [ "FASTLY_API_TOKEN=%d/fastly-api-token" ]; + ExecStart = escapeSystemdExecArgs ( + [ + (getExe pkgs.prometheus-fastly-exporter) + "-listen" + "${cfg.listenAddress}:${toString cfg.port}" + ] + ++ optionals (cfg.configFile != null) [ + "--config-file" + cfg.configFile + ] + ++ cfg.extraFlags + ); }; - script = let - call = escapeShellArgs ([ - "${pkgs.prometheus-fastly-exporter}/bin/fastly-exporter" - "-listen" "${cfg.listenAddress}:${toString cfg.port}" - ] ++ optionals (cfg.configFile != null) [ - "--config-file" cfg.configFile - ] ++ cfg.extraFlags); - in '' - export FASTLY_API_TOKEN="$(cat $CREDENTIALS_DIRECTORY/fastly-api-token)" - ${call} - ''; }; } diff --git a/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixos/modules/services/monitoring/prometheus/pushgateway.nix index 80e2339f59256..d4f9c4a29f386 100644 --- a/nixos/modules/services/monitoring/prometheus/pushgateway.nix +++ b/nixos/modules/services/monitoring/prometheus/pushgateway.nix @@ -147,12 +147,52 @@ in { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { - Restart = "always"; - DynamicUser = true; ExecStart = "${cfg.package}/bin/pushgateway" + optionalString (length cmdlineArgs != 0) (" \\\n " + concatStringsSep " \\\n " cmdlineArgs); + + CapabilityBoundingSet = [ "" ]; + DeviceAllow = [ "" ]; + DynamicUser = true; + NoNewPrivileges = true; + + MemoryDenyWriteExecute = true; + + LockPersonality = true; + + ProtectProc = "invisible"; + ProtectSystem = "strict"; + ProtectHome = "tmpfs"; + + PrivateTmp = true; + PrivateDevices = true; + PrivateIPC = true; + + ProcSubset = "pid"; + + ProtectHostname = true; + ProtectClock = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectKernelLogs = true; + ProtectControlGroups = true; + + Restart = "always"; + + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + StateDirectory = if cfg.persistMetrics then cfg.stateDir else null; + SystemCallFilter = [ + "@system-service" + "~@cpu-emulation" + "~@privileged" + "~@reboot" + "~@setuid" + "~@swap" + ]; }; }; }; diff --git a/nixos/modules/services/monitoring/smartd.nix b/nixos/modules/services/monitoring/smartd.nix index 2c05eaad25ace..6fd3b5707ab67 100644 --- a/nixos/modules/services/monitoring/smartd.nix +++ b/nixos/modules/services/monitoring/smartd.nix @@ -10,6 +10,7 @@ let opt = options.services.smartd; nm = cfg.notifications.mail; + ns = cfg.notifications.systembus-notify; nw = cfg.notifications.wall; nx = cfg.notifications.x11; @@ -28,6 +29,12 @@ let ${pkgs.smartmontools}/sbin/smartctl -a -d "$SMARTD_DEVICETYPE" "$SMARTD_DEVICE" } | ${nm.mailer} -i "${nm.recipient}" ''} + ${optionalString ns.enable '' + ${pkgs.dbus}/bin/dbus-send --system \ + / net.nuetzlich.SystemNotifications.Notify \ + "string:Problem detected with disk: $SMARTD_DEVICESTRING" \ + "string:Warning message from smartd is: $SMARTD_MESSAGE" + ''} ${optionalString nw.enable '' { ${pkgs.coreutils}/bin/cat << EOF @@ -159,6 +166,24 @@ in }; }; + systembus-notify = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + Whenever to send systembus-notify notifications. + + WARNING: enabling this option (while convenient) should *not* be done on a + machine where you do not trust the other users as it allows any other + local user to DoS your session by spamming notifications. + + To actually see the notifications in your GUI session, you need to have + `systembus-notify` running as your user, which this + option handles by enabling {option}`services.systembus-notify`. + ''; + }; + }; + wall = { enable = mkOption { default = true; @@ -247,6 +272,8 @@ in serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd ${lib.concatStringsSep " " cfg.extraOptions} --no-fork --configfile=${smartdConf}"; }; + services.systembus-notify.enable = mkDefault ns.enable; + }; } diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix index c70d0cf7beac3..a887f964ca428 100644 --- a/nixos/modules/services/network-filesystems/samba.nix +++ b/nixos/modules/services/network-filesystems/samba.nix @@ -55,6 +55,7 @@ let PIDFile = "/run/${appName}.pid"; Type = "notify"; NotifyAccess = "all"; #may not do anything... + Slice = "system-samba.slice"; }; unitConfig.RequiresMountsFor = "/var/lib/samba"; @@ -216,6 +217,11 @@ in wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; }; + + slices.system-samba = { + description = "Samba slice"; + }; + # Refer to https://github.com/samba-team/samba/tree/master/packaging/systemd # for correct use with systemd services = { diff --git a/nixos/modules/services/networking/bee.nix b/nixos/modules/services/networking/bee.nix index da11ac9399abd..72483c41d02d1 100644 --- a/nixos/modules/services/networking/bee.nix +++ b/nixos/modules/services/networking/bee.nix @@ -101,8 +101,7 @@ in { preStart = with cfg.settings; '' if ! test -f ${password-file}; then - < /dev/urandom tr -dc _A-Z-a-z-0-9 2> /dev/null | head -c32 > ${password-file} - chmod 0600 ${password-file} + < /dev/urandom tr -dc _A-Z-a-z-0-9 2> /dev/null | head -c32 | install -m 600 /dev/stdin ${password-file} echo "Initialized ${password-file} from /dev/urandom" fi if [ ! -f ${data-dir}/keys/libp2p.key ]; then diff --git a/nixos/modules/services/networking/blocky.nix b/nixos/modules/services/networking/blocky.nix index b98c8b7bdb730..4bc6ffa3f46ab 100644 --- a/nixos/modules/services/networking/blocky.nix +++ b/nixos/modules/services/networking/blocky.nix @@ -12,6 +12,8 @@ in options.services.blocky = { enable = mkEnableOption "blocky, a fast and lightweight DNS proxy as ad-blocker for local network with many features"; + package = mkPackageOption pkgs "blocky" { }; + settings = mkOption { type = format.type; default = { }; @@ -30,7 +32,7 @@ in serviceConfig = { DynamicUser = true; - ExecStart = "${pkgs.blocky}/bin/blocky --config ${configFile}"; + ExecStart = "${getExe cfg.package} --config ${configFile}"; Restart = "on-failure"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; diff --git a/nixos/modules/services/networking/cgit.nix b/nixos/modules/services/networking/cgit.nix index 0ccbef756812e..de8128ed5a59c 100644 --- a/nixos/modules/services/networking/cgit.nix +++ b/nixos/modules/services/networking/cgit.nix @@ -25,14 +25,14 @@ let regexLocation = cfg: regexEscape (stripLocation cfg); - mkFastcgiPass = cfg: '' + mkFastcgiPass = name: cfg: '' ${if cfg.nginx.location == "/" then '' fastcgi_param PATH_INFO $uri; '' else '' fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$; fastcgi_param PATH_INFO $fastcgi_path_info; '' - }fastcgi_pass unix:${config.services.fcgiwrap.socketAddress}; + }fastcgi_pass unix:${config.services.fcgiwrap."cgit-${name}".socket.address}; ''; cgitrcLine = name: value: "${name}=${ @@ -72,25 +72,11 @@ let ${cfg.extraConfig} ''; - mkCgitReposDir = cfg: - if cfg.scanPath != null then - cfg.scanPath - else - pkgs.runCommand "cgit-repos" { - preferLocalBuild = true; - allowSubstitutes = false; - } '' - mkdir -p "$out" - ${ - concatStrings ( - mapAttrsToList - (name: value: '' - ln -s ${escapeShellArg value.path} "$out"/${escapeShellArg name} - '') - cfg.repos - ) - } - ''; + fcgiwrapUnitName = name: "fcgiwrap-cgit-${name}"; + fcgiwrapRuntimeDir = name: "/run/${fcgiwrapUnitName name}"; + gitProjectRoot = name: cfg: if cfg.scanPath != null + then cfg.scanPath + else "${fcgiwrapRuntimeDir name}/repos"; in { @@ -154,6 +140,18 @@ in type = types.lines; default = ""; }; + + user = mkOption { + description = "User to run the cgit service as."; + type = types.str; + default = "cgit"; + }; + + group = mkOption { + description = "Group to run the cgit service as."; + type = types.str; + default = "cgit"; + }; }; })); }; @@ -165,18 +163,46 @@ in message = "Exactly one of services.cgit.${vhost}.scanPath or services.cgit.${vhost}.repos must be set."; }) cfgs; - services.fcgiwrap.enable = true; + users = mkMerge (flip mapAttrsToList cfgs (_: cfg: { + users.${cfg.user} = { + isSystemUser = true; + inherit (cfg) group; + }; + groups.${cfg.group} = { }; + })); + + services.fcgiwrap = flip mapAttrs' cfgs (name: cfg: + nameValuePair "cgit-${name}" { + process = { inherit (cfg) user group; }; + socket = { inherit (config.services.nginx) user group; }; + } + ); + + systemd.services = flip mapAttrs' cfgs (name: cfg: + nameValuePair (fcgiwrapUnitName name) + (mkIf (cfg.repos != { }) { + serviceConfig.RuntimeDirectory = fcgiwrapUnitName name; + preStart = '' + GIT_PROJECT_ROOT=${escapeShellArg (gitProjectRoot name cfg)} + mkdir -p "$GIT_PROJECT_ROOT" + cd "$GIT_PROJECT_ROOT" + ${concatLines (flip mapAttrsToList cfg.repos (name: repo: '' + ln -s ${escapeShellArg repo.path} ${escapeShellArg name} + ''))} + ''; + } + )); services.nginx.enable = true; - services.nginx.virtualHosts = mkMerge (mapAttrsToList (_: cfg: { + services.nginx.virtualHosts = mkMerge (mapAttrsToList (name: cfg: { ${cfg.nginx.virtualHost} = { locations = ( genAttrs' [ "cgit.css" "cgit.png" "favicon.ico" "robots.txt" ] - (name: nameValuePair "= ${stripLocation cfg}/${name}" { + (fileName: nameValuePair "= ${stripLocation cfg}/${fileName}" { extraConfig = '' - alias ${cfg.package}/cgit/${name}; + alias ${cfg.package}/cgit/${fileName}; ''; }) ) // { @@ -184,10 +210,10 @@ in fastcgiParams = rec { SCRIPT_FILENAME = "${pkgs.git}/libexec/git-core/git-http-backend"; GIT_HTTP_EXPORT_ALL = "1"; - GIT_PROJECT_ROOT = mkCgitReposDir cfg; + GIT_PROJECT_ROOT = gitProjectRoot name cfg; HOME = GIT_PROJECT_ROOT; }; - extraConfig = mkFastcgiPass cfg; + extraConfig = mkFastcgiPass name cfg; }; "${stripLocation cfg}/" = { fastcgiParams = { @@ -196,7 +222,7 @@ in HTTP_HOST = "$server_name"; CGIT_CONFIG = mkCgitrc cfg; }; - extraConfig = mkFastcgiPass cfg; + extraConfig = mkFastcgiPass name cfg; }; }; }; diff --git a/nixos/modules/services/networking/cloudflare-dyndns.nix b/nixos/modules/services/networking/cloudflare-dyndns.nix index ab5b1a08539a5..a15de2ddd861b 100644 --- a/nixos/modules/services/networking/cloudflare-dyndns.nix +++ b/nixos/modules/services/networking/cloudflare-dyndns.nix @@ -28,6 +28,16 @@ in ''; }; + frequency = mkOption { + type = types.nullOr types.str; + default = "*:0/5"; + description = '' + Run cloudflare-dyndns with the given frequency (see + {manpage}`systemd.time(7)` for the format). + If null, do not run automatically. + ''; + }; + proxied = mkOption { type = types.bool; default = false; @@ -67,7 +77,6 @@ in description = "CloudFlare Dynamic DNS Client"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - startAt = "*:0/5"; environment = { CLOUDFLARE_DOMAINS = toString cfg.domains; @@ -88,6 +97,8 @@ in in "${pkgs.cloudflare-dyndns}/bin/cloudflare-dyndns ${toString args}"; }; + } // optionalAttrs (cfg.frequency != null) { + startAt = cfg.frequency; }; }; } diff --git a/nixos/modules/services/networking/cloudflare-warp.nix b/nixos/modules/services/networking/cloudflare-warp.nix new file mode 100644 index 0000000000000..2ab5f287ac496 --- /dev/null +++ b/nixos/modules/services/networking/cloudflare-warp.nix @@ -0,0 +1,91 @@ +{ config, lib, pkgs, ... }: +let + cfg = config.services.cloudflare-warp; +in +{ + options.services.cloudflare-warp = { + enable = lib.mkEnableOption "Cloudflare Zero Trust client daemon"; + + package = lib.mkPackageOption pkgs "cloudflare-warp" { }; + + rootDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/cloudflare-warp"; + description = '' + Working directory for the warp-svc daemon. + ''; + }; + + udpPort = lib.mkOption { + type = lib.types.port; + default = 2408; + description = '' + The UDP port to open in the firewall. Warp uses port 2408 by default, but fallback ports can be used + if that conflicts with another service. See the [firewall documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/deployment/firewall#warp-udp-ports) + for the pre-configured available fallback ports. + ''; + }; + + openFirewall = lib.mkEnableOption "opening UDP ports in the firewall" // { + default = true; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedUDPPorts = [ cfg.udpPort ]; + }; + + systemd.tmpfiles.rules = [ + "d ${cfg.rootDir} - root root" + "z ${cfg.rootDir} - root root" + ]; + + systemd.services.cloudflare-warp = { + enable = true; + description = "Cloudflare Zero Trust Client Daemon"; + + # lsof is used by the service to determine which UDP port to bind to + # in the case that it detects collisions. + path = [ pkgs.lsof ]; + requires = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = + let + caps = [ + "CAP_NET_ADMIN" + "CAP_NET_BIND_SERVICE" + "CAP_SYS_PTRACE" + ]; + in + { + Type = "simple"; + ExecStart = "${cfg.package}/bin/warp-svc"; + ReadWritePaths = [ "${cfg.rootDir}" "/etc/resolv.conf" ]; + CapabilityBoundingSet = caps; + AmbientCapabilities = caps; + Restart = "always"; + RestartSec = 5; + Environment = [ "RUST_BACKTRACE=full" ]; + WorkingDirectory = cfg.rootDir; + + # See the systemd.exec docs for the canonicalized paths, the service + # makes use of them for logging, and account state info tracking. + # https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#RuntimeDirectory= + StateDirectory = "cloudflare-warp"; + RuntimeDirectory = "cloudflare-warp"; + LogsDirectory = "cloudflare-warp"; + + # The service needs to write to /etc/resolv.conf to configure DNS, so that file would have to + # be world read/writable to run as anything other than root. + User = "root"; + Group = "root"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ treyfortmuller ]; +} diff --git a/nixos/modules/services/networking/cloudflared.nix b/nixos/modules/services/networking/cloudflared.nix index 60f6b7c466892..c0d1012ffb80d 100644 --- a/nixos/modules/services/networking/cloudflared.nix +++ b/nixos/modules/services/networking/cloudflared.nix @@ -131,7 +131,7 @@ let `cloudflared` starts a proxy server to translate HTTP traffic into TCP when proxying, for example, SSH or RDP. This configures what type of proxy will be started. Valid options are: - `""` for the regular proxy - - `"socks"` for a SOCKS5 proxy. Refer to the [https://developers.cloudflare.com/cloudflare-one/tutorials/kubectl/](tutorial on connecting through Cloudflare Access using kubectl) for more information. + - `"socks"` for a SOCKS5 proxy. Refer to the [tutorial on connecting through Cloudflare Access using kubectl](https://developers.cloudflare.com/cloudflare-one/tutorials/kubectl/) for more information. ''; }; }; @@ -167,7 +167,7 @@ in description = '' Credential file. - See [https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-useful-terms/#credentials-file](Credentials file). + See [Credentials file](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-useful-terms/#credentials-file). ''; }; @@ -178,7 +178,7 @@ in description = '' Enable warp routing. - See [https://developers.cloudflare.com/cloudflare-one/tutorials/warp-to-tunnel/](Connect from WARP to a private network on Cloudflare using Cloudflare Tunnel). + See [Connect from WARP to a private network on Cloudflare using Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/tutorials/warp-to-tunnel/). ''; }; }; @@ -204,7 +204,7 @@ in description = '' Service to pass the traffic. - See [https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/local-management/ingress/#supported-protocols](Supported protocols). + See [Supported protocols](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/local-management/ingress/#supported-protocols). ''; example = "http://localhost:80, tcp://localhost:8000, unix:/home/production/echo.sock, hello_world or http_status:404"; }; @@ -226,7 +226,7 @@ in description = '' Ingress rules. - See [https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/local-management/ingress/](Ingress rules). + See [Ingress rules](https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/local-management/ingress/). ''; example = { "*.domain.com" = "http://localhost:80"; diff --git a/nixos/modules/services/networking/deconz.nix b/nixos/modules/services/networking/deconz.nix index 88b0ee612d871..e023f8e866c0d 100644 --- a/nixos/modules/services/networking/deconz.nix +++ b/nixos/modules/services/networking/deconz.nix @@ -122,6 +122,7 @@ in RuntimeDirectory = name; RuntimeDirectoryMode = "0700"; StateDirectory = name; + SuccessExitStatus = [ 143 ]; WorkingDirectory = stateDir; # For access to /dev/ttyACM0 (ConBee). SupplementaryGroups = [ "dialout" ]; diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix index a9fcd883beb07..674a424fb0a42 100644 --- a/nixos/modules/services/networking/firefox-syncserver.nix +++ b/nixos/modules/services/networking/firefox-syncserver.nix @@ -316,7 +316,7 @@ in }; meta = { - maintainers = with lib.maintainers; [ pennae ]; + maintainers = with lib.maintainers; [ ]; doc = ./firefox-syncserver.md; }; } diff --git a/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix index 03210bca371cf..5b700269037c2 100644 --- a/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix +++ b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix @@ -1,18 +1,27 @@ -{ config, lib, pkgs, ... }: - -with lib; +{ + config, + lib, + pkgs, + ... +}: let cfg = config.services.magic-wormhole-mailbox-server; + # keep semicolon in dataDir for backward compatibility dataDir = "/var/lib/magic-wormhole-mailbox-server;"; - python = pkgs.python3.withPackages (py: [ py.magic-wormhole-mailbox-server py.twisted ]); + python = pkgs.python311.withPackages ( + py: with py; [ + magic-wormhole-mailbox-server + twisted + ] + ); in { options.services.magic-wormhole-mailbox-server = { - enable = mkEnableOption "Magic Wormhole Mailbox Server"; + enable = lib.mkEnableOption "Magic Wormhole Mailbox Server"; }; - config = mkIf cfg.enable { + config = lib.mkIf cfg.enable { systemd.services.magic-wormhole-mailbox-server = { after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; @@ -23,6 +32,7 @@ in StateDirectory = baseNameOf dataDir; }; }; - }; + + meta.maintainers = [ lib.maintainers.mjoerg ]; } diff --git a/nixos/modules/services/networking/mihomo.nix b/nixos/modules/services/networking/mihomo.nix index d4bb10496279d..a425952b54ced 100644 --- a/nixos/modules/services/networking/mihomo.nix +++ b/nixos/modules/services/networking/mihomo.nix @@ -2,10 +2,11 @@ # cfg.configFile contains secrets such as proxy servers' credential! # we dont want plaintext secrets in world-readable `/nix/store`. -{ lib -, config -, pkgs -, ... +{ + lib, + config, + pkgs, + ... }: let cfg = config.services.mihomo; @@ -17,7 +18,6 @@ in package = lib.mkPackageOption pkgs "mihomo" { }; configFile = lib.mkOption { - default = null; type = lib.types.nullOr lib.types.path; description = "Configuration file to use."; }; @@ -67,7 +67,7 @@ in ExecStart = lib.concatStringsSep " " [ (lib.getExe cfg.package) "-d /var/lib/private/mihomo" - (lib.optionalString (cfg.configFile != null) "-f \${CREDENTIALS_DIRECTORY}/config.yaml") + "-f \${CREDENTIALS_DIRECTORY}/config.yaml" (lib.optionalString (cfg.webui != null) "-ext-ui ${cfg.webui}") (lib.optionalString (cfg.extraOpts != null) cfg.extraOpts) ]; diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix index 9825af47777e5..7baaf93a1bcfa 100644 --- a/nixos/modules/services/networking/mosquitto.nix +++ b/nixos/modules/services/networking/mosquitto.nix @@ -721,7 +721,7 @@ in }; meta = { - maintainers = with lib.maintainers; [ pennae ]; + maintainers = with lib.maintainers; [ ]; doc = ./mosquitto.md; }; } diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix index 56eed04c3e8d9..d69af8d16b52c 100644 --- a/nixos/modules/services/networking/nebula.nix +++ b/nixos/modules/services/networking/nebula.nix @@ -51,8 +51,8 @@ in }; key = mkOption { - type = types.path; - description = "Path to the host key."; + type = types.oneOf [types.nonEmptyStr types.path]; + description = "Path or reference to the host key."; example = "/etc/nebula/host.key"; }; @@ -241,7 +241,7 @@ in ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; - ProtectSystem = "strict"; + ProtectSystem = true; RestrictNamespaces = true; RestrictSUIDSGID = true; User = networkId; @@ -269,4 +269,6 @@ in ${nameToId netName} = {}; }) enabledNetworks); }; + + meta.maintainers = [ numinit ]; } diff --git a/nixos/modules/services/networking/networkd-dispatcher.nix b/nixos/modules/services/networking/networkd-dispatcher.nix index 427835870e59f..cced406934c1a 100644 --- a/nixos/modules/services/networking/networkd-dispatcher.nix +++ b/nixos/modules/services/networking/networkd-dispatcher.nix @@ -13,7 +13,7 @@ in { enable = mkEnableOption '' Networkd-dispatcher service for systemd-networkd connection status - change. See [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions) + change. See [upstream instructions](https://gitlab.com/craftyguy/networkd-dispatcher) for usage ''; @@ -35,7 +35,7 @@ in { ''; description = '' Declarative configuration of networkd-dispatcher rules. See - [https://gitlab.com/craftyguy/networkd-dispatcher](upstream instructions) + [upstream instructions](https://gitlab.com/craftyguy/networkd-dispatcher) for an introduction and example scripts. ''; type = types.attrsOf (types.submodule { diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix index b7143cf520f96..fda8245ba97d1 100644 --- a/nixos/modules/services/networking/networkmanager.nix +++ b/nixos/modules/services/networking/networkmanager.nix @@ -127,7 +127,7 @@ in { meta = { - maintainers = teams.freedesktop.members ++ [ lib.maintainers.janik ]; + maintainers = teams.freedesktop.members; }; ###### interface diff --git a/nixos/modules/services/networking/oink.nix b/nixos/modules/services/networking/oink.nix index cd0fdf172331d..3497ca9220a80 100644 --- a/nixos/modules/services/networking/oink.nix +++ b/nixos/modules/services/networking/oink.nix @@ -77,6 +77,7 @@ in config = mkIf cfg.enable { systemd.services.oink = { description = "Dynamic DNS client for Porkbun"; + after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; script = "${cfg.package}/bin/oink -c ${oinkConfig}"; }; diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix index 0de07a9b870c6..9e54dfc17dfe7 100644 --- a/nixos/modules/services/networking/prosody.nix +++ b/nixos/modules/services/networking/prosody.nix @@ -266,6 +266,13 @@ let else if builtins.isList x then "{ ${lib.concatMapStringsSep ", " toLua x} }" else throw "Invalid Lua value"; + settingsToLua = prefix: settings: generators.toKeyValue { + listsAsDuplicateKeys = false; + mkKeyValue = k: generators.mkKeyValueDefault { + mkValueString = toLua; + } " = " (prefix + k); + } (filterAttrs (k: v: v != null) settings); + createSSLOptsStr = o: '' ssl = { cafile = "/etc/ssl/certs/ca-bundle.crt"; @@ -418,15 +425,26 @@ let httpUploadPath = mkOption { type = types.str; description = '' - Directory where the uploaded files will be stored. By - default, uploaded files are put in a sub-directory of the - default Prosody storage path (usually /var/lib/prosody). + Directory where the uploaded files will be stored when the http_upload module is used. + By default, uploaded files are put in a sub-directory of the default Prosody storage path (usually /var/lib/prosody). ''; default = "/var/lib/prosody"; }; }; }; + httpFileShareOpts = { ... }: { + freeformType = with types; + let atom = oneOf [ int bool str (listOf atom) ]; in + attrsOf (nullOr atom) // { + description = "int, bool, string or list of them"; + }; + options.domain = mkOption { + type = with types; nullOr str; + description = "Domain name for a http_file_share service."; + }; + }; + vHostOpts = { ... }: { options = { @@ -650,7 +668,7 @@ in uploadHttp = mkOption { description = '' - Configures the Prosody builtin HTTP server to handle user uploads. + Configures the old Prosody builtin HTTP server to handle user uploads. ''; type = types.nullOr (types.submodule uploadHttpOpts); default = null; @@ -659,6 +677,17 @@ in }; }; + httpFileShare = mkOption { + description = '' + Configures the http_file_share module to handle user uploads. + ''; + type = types.nullOr (types.submodule httpFileShareOpts); + default = null; + example = { + domain = "uploads.my-xmpp-example-host.org"; + }; + }; + muc = mkOption { type = types.listOf (types.submodule mucOpts); default = [ ]; @@ -716,6 +745,17 @@ in description = "Additional prosody configuration"; }; + log = mkOption { + type = types.lines; + default = ''"*syslog"''; + description = "Logging configuration. See [](https://prosody.im/doc/logging) for more details"; + example = '' + { + { min = "warn"; to = "*syslog"; }; + } + ''; + }; + }; }; @@ -740,11 +780,10 @@ in You need to setup at least a MUC domain to comply with XEP-0423. '' + genericErrMsg;} - { assertion = cfg.uploadHttp != null || !cfg.xmppComplianceSuite; + { assertion = cfg.uploadHttp != null || cfg.httpFileShare != null || !cfg.xmppComplianceSuite; message = '' - You need to setup the uploadHttp module through - config.services.prosody.uploadHttp to comply with - XEP-0423. + You need to setup the http_upload or http_file_share modules through config.services.prosody.uploadHttp + or config.services.prosody.httpFileShare to comply with XEP-0423. '' + genericErrMsg;} ]; in errors; @@ -753,8 +792,11 @@ in environment.etc."prosody/prosody.cfg.lua".text = let - httpDiscoItems = optionals (cfg.uploadHttp != null) - [{ url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";}]; + httpDiscoItems = optional (cfg.uploadHttp != null) { + url = cfg.uploadHttp.domain; description = "HTTP upload endpoint"; + } ++ optional (cfg.httpFileShare != null) { + url = cfg.httpFileShare.domain; description = "HTTP file share endpoint"; + }; mucDiscoItems = builtins.foldl' (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc) [] @@ -764,7 +806,7 @@ in pidfile = "/run/prosody/prosody.pid" - log = "*syslog" + log = ${cfg.log} data_path = "${cfg.dataDir}" plugin_paths = { @@ -833,7 +875,6 @@ in '') cfg.muc} ${ lib.optionalString (cfg.uploadHttp != null) '' - -- TODO: think about migrating this to mod-http_file_share instead. Component ${toLua cfg.uploadHttp.domain} "http_upload" http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit} http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter} @@ -841,6 +882,11 @@ in http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath} ''} + ${lib.optionalString (cfg.httpFileShare != null) '' + Component ${toLua cfg.httpFileShare.domain} "http_file_share" + ${settingsToLua " http_file_share_" (cfg.httpFileShare // { domain = null; })} + ''} + ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: '' VirtualHost "${v.domain}" enabled = ${boolToString v.enabled}; diff --git a/nixos/modules/services/networking/realm.nix b/nixos/modules/services/networking/realm.nix new file mode 100644 index 0000000000000..5b0c34ac825ff --- /dev/null +++ b/nixos/modules/services/networking/realm.nix @@ -0,0 +1,50 @@ +{ config +, lib +, pkgs +, ... +}: +let + cfg = config.services.realm; + configFormat = pkgs.formats.json { }; + configFile = configFormat.generate "config.json" cfg.config; + inherit (lib) + mkEnableOption mkPackageOption mkOption mkIf types getExe; +in +{ + + meta.maintainers = with lib.maintainers; [ ocfox ]; + + options = { + services.realm = { + enable = mkEnableOption "A simple, high performance relay server written in rust"; + package = mkPackageOption pkgs "realm" { }; + config = mkOption { + type = types.submodule { + freeformType = configFormat.type; + }; + default = { }; + description = '' + The realm configuration, see <https://github.com/zhboner/realm#overview> for documentation. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.realm = { + serviceConfig = { + DynamicUser = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + ProtectClock = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectProc = "invisible"; + ProtectKernelTunables = true; + ExecStart = "${getExe cfg.package} --config ${configFile}"; + AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" ]; + }; + wantedBy = [ "multi-user.target" ]; + }; + }; +} diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix index 395796d39db8e..c788069fbaa55 100644 --- a/nixos/modules/services/networking/resilio.nix +++ b/nixos/modules/services/networking/resilio.nix @@ -291,5 +291,5 @@ in }; }; - meta.maintainers = with maintainers; [ jwoudenberg ]; + meta.maintainers = with maintainers; [ ]; } diff --git a/nixos/modules/services/networking/scion/scion-control.nix b/nixos/modules/services/networking/scion/scion-control.nix index 95d78a87ac859..0cc190d619b6f 100644 --- a/nixos/modules/services/networking/scion/scion-control.nix +++ b/nixos/modules/services/networking/scion/scion-control.nix @@ -3,8 +3,10 @@ with lib; let + globalCfg = config.services.scion; cfg = config.services.scion.scion-control; toml = pkgs.formats.toml { }; + connectionDir = if globalCfg.stateless then "/run" else "/var/lib"; defaultConfig = { general = { id = "cs"; @@ -12,13 +14,13 @@ let reconnect_to_dispatcher = true; }; beacon_db = { - connection = "/run/scion-control/control.beacon.db"; + connection = "${connectionDir}/scion-control/control.beacon.db"; }; path_db = { - connection = "/run/scion-control/control.path.db"; + connection = "${connectionDir}/scion-control/control.path.db"; }; trust_db = { - connection = "/run/scion-control/control.trust.db"; + connection = "${connectionDir}/scion-control/control.trust.db"; }; log.console = { level = "info"; @@ -62,7 +64,7 @@ in DynamicUser = true; Restart = "on-failure"; BindPaths = [ "/dev/shm:/run/shm" ]; - RuntimeDirectory = "scion-control"; + ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-control"; }; }; }; diff --git a/nixos/modules/services/networking/scion/scion-daemon.nix b/nixos/modules/services/networking/scion/scion-daemon.nix index 8528bec1d52eb..6ee2a4b505f26 100644 --- a/nixos/modules/services/networking/scion/scion-daemon.nix +++ b/nixos/modules/services/networking/scion/scion-daemon.nix @@ -3,8 +3,10 @@ with lib; let + globalCfg = config.services.scion; cfg = config.services.scion.scion-daemon; toml = pkgs.formats.toml { }; + connectionDir = if globalCfg.stateless then "/run" else "/var/lib"; defaultConfig = { general = { id = "sd"; @@ -12,10 +14,10 @@ let reconnect_to_dispatcher = true; }; path_db = { - connection = "/run/scion-daemon/sd.path.db"; + connection = "${connectionDir}/scion-daemon/sd.path.db"; }; trust_db = { - connection = "/run/scion-daemon/sd.trust.db"; + connection = "${connectionDir}/scion-daemon/sd.trust.db"; }; log.console = { level = "info"; @@ -57,7 +59,7 @@ in ExecStart = "${pkgs.scion}/bin/scion-daemon --config ${configFile}"; Restart = "on-failure"; DynamicUser = true; - RuntimeDirectory = "scion-daemon"; + ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-daemon"; }; }; }; diff --git a/nixos/modules/services/networking/scion/scion-dispatcher.nix b/nixos/modules/services/networking/scion/scion-dispatcher.nix index 7c9f5e6a385ee..1f4193fae79e5 100644 --- a/nixos/modules/services/networking/scion/scion-dispatcher.nix +++ b/nixos/modules/services/networking/scion/scion-dispatcher.nix @@ -3,6 +3,7 @@ with lib; let + globalCfg = config.services.scion; cfg = config.services.scion.scion-dispatcher; toml = pkgs.formats.toml { }; defaultConfig = { @@ -66,7 +67,7 @@ in ExecStartPre = "${pkgs.coreutils}/bin/rm -rf /run/shm/dispatcher"; ExecStart = "${pkgs.scion}/bin/scion-dispatcher --config ${configFile}"; Restart = "on-failure"; - RuntimeDirectory = "scion-dispatcher"; + ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-dispatcher"; }; }; }; diff --git a/nixos/modules/services/networking/scion/scion-router.nix b/nixos/modules/services/networking/scion/scion-router.nix index 2cac44ab767ef..47ff320b6a8ff 100644 --- a/nixos/modules/services/networking/scion/scion-router.nix +++ b/nixos/modules/services/networking/scion/scion-router.nix @@ -3,6 +3,7 @@ with lib; let + globalCfg = config.services.scion; cfg = config.services.scion.scion-router; toml = pkgs.formats.toml { }; defaultConfig = { @@ -42,7 +43,7 @@ in ExecStart = "${pkgs.scion}/bin/scion-router --config ${configFile}"; Restart = "on-failure"; DynamicUser = true; - RuntimeDirectory = "scion-router"; + ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-router"; }; }; }; diff --git a/nixos/modules/services/networking/scion/scion.nix b/nixos/modules/services/networking/scion/scion.nix index b8bfef8b93b58..0ae959484d326 100644 --- a/nixos/modules/services/networking/scion/scion.nix +++ b/nixos/modules/services/networking/scion/scion.nix @@ -8,6 +8,22 @@ in { options.services.scion = { enable = mkEnableOption "all of the scion components and services"; + stateless = mkOption { + type = types.bool; + default = true; + description = '' + Setting this value to false (stateful) can lead to improved caching and + performance. + + This option decides whether to persist the SCION path sqlite databases + on disk or not. Persisting this data can lead to database corruption in + extreme cases such as power outage, meaning SCION fails to work on the + next boot. This is being investigated. + + If true, /run/scion-* is used for data + If false, use /var/lib/scion-* is used for data + ''; + }; bypassBootstrapWarning = mkOption { type = types.bool; default = false; diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix index 3fb3eac45cc82..a07cde847cf69 100644 --- a/nixos/modules/services/networking/smokeping.nix +++ b/nixos/modules/services/networking/smokeping.nix @@ -337,7 +337,11 @@ in }; # use nginx to serve the smokeping web service - services.fcgiwrap.enable = mkIf cfg.webService true; + services.fcgiwrap.smokeping = mkIf cfg.webService { + process.user = cfg.user; + process.group = cfg.user; + socket = { inherit (config.services.nginx) user group; }; + }; services.nginx = mkIf cfg.webService { enable = true; virtualHosts."smokeping" = { @@ -349,7 +353,7 @@ in locations."/smokeping.fcgi" = { extraConfig = '' include ${config.services.nginx.package}/conf/fastcgi_params; - fastcgi_pass unix:${config.services.fcgiwrap.socketAddress}; + fastcgi_pass unix:${config.services.fcgiwrap.smokeping.socket.address}; fastcgi_param SCRIPT_FILENAME ${smokepingHome}/smokeping.fcgi; fastcgi_param DOCUMENT_ROOT ${smokepingHome}; ''; diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index 45503ef89aaa1..94ff838b50e04 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -368,6 +368,15 @@ in { ''; }; + type = mkOption { + type = types.enum [ "sendreceive" "sendonly" "receiveonly" "receiveencrypted" ]; + default = "sendreceive"; + description = '' + Controls how the folder is handled by Syncthing. + See <https://docs.syncthing.net/users/config.html#config-option-folder.type>. + ''; + }; + devices = mkOption { type = types.listOf types.str; default = []; diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix index b3598606d8be7..a23c19f685d2d 100644 --- a/nixos/modules/services/security/clamav.nix +++ b/nixos/modules/services/security/clamav.nix @@ -5,7 +5,6 @@ let stateDir = "/var/lib/clamav"; clamavGroup = clamavUser; cfg = config.services.clamav; - pkg = pkgs.clamav; toKeyValue = generators.toKeyValue { mkKeyValue = generators.mkKeyValueDefault { } " "; @@ -27,6 +26,7 @@ in options = { services.clamav = { + package = mkPackageOption pkgs "clamav" { }; daemon = { enable = mkEnableOption "ClamAV clamd daemon"; @@ -125,7 +125,7 @@ in }; config = mkIf (cfg.updater.enable || cfg.daemon.enable) { - environment.systemPackages = [ pkg ]; + environment.systemPackages = [ cfg.package ]; users.users.${clamavUser} = { uid = config.ids.uids.clamav; @@ -172,7 +172,7 @@ in restartTriggers = [ clamdConfigFile ]; serviceConfig = { - ExecStart = "${pkg}/bin/clamd"; + ExecStart = "${cfg.package}/bin/clamd"; ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"; User = clamavUser; Group = clamavGroup; @@ -201,7 +201,7 @@ in serviceConfig = { Type = "oneshot"; - ExecStart = "${pkg}/bin/freshclam"; + ExecStart = "${cfg.package}/bin/freshclam"; SuccessExitStatus = "1"; # if databases are up to date StateDirectory = "clamav"; User = clamavUser; @@ -274,7 +274,7 @@ in serviceConfig = { Type = "oneshot"; - ExecStart = "${pkg}/bin/clamdscan --multiscan --fdpass --infected --allmatch ${lib.concatStringsSep " " cfg.scanner.scanDirectories}"; + ExecStart = "${cfg.package}/bin/clamdscan --multiscan --fdpass --infected --allmatch ${lib.concatStringsSep " " cfg.scanner.scanDirectories}"; }; }; }; diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix index 520da45c94e2f..20d2dadbb7e22 100644 --- a/nixos/modules/services/security/sks.nix +++ b/nixos/modules/services/security/sks.nix @@ -10,7 +10,7 @@ let ''; in { - meta.maintainers = with maintainers; [ primeos calbrecht jcumming ]; + meta.maintainers = with maintainers; [ calbrecht jcumming ]; options = { diff --git a/nixos/modules/services/security/yubikey-agent.nix b/nixos/modules/services/security/yubikey-agent.nix index 991f6a5595451..6a4dc7014970a 100644 --- a/nixos/modules/services/security/yubikey-agent.nix +++ b/nixos/modules/services/security/yubikey-agent.nix @@ -10,7 +10,7 @@ in { ###### interface - meta.maintainers = with maintainers; [ philandstuff rawkode jwoudenberg ]; + meta.maintainers = with maintainers; [ philandstuff rawkode ]; options = { diff --git a/nixos/modules/services/system/cloud-init.nix b/nixos/modules/services/system/cloud-init.nix index 5d7258cac7780..7148d62b90842 100644 --- a/nixos/modules/services/system/cloud-init.nix +++ b/nixos/modules/services/system/cloud-init.nix @@ -16,6 +16,7 @@ let ++ optional cfg.btrfs.enable btrfs-progs ++ optional cfg.ext4.enable e2fsprogs ++ optional cfg.xfs.enable xfsprogs + ++ cfg.extraPackages ; hasFs = fsName: lib.any (fs: fs.fsType == fsName) (lib.attrValues config.fileSystems); settingsFormat = pkgs.formats.yaml { }; @@ -79,6 +80,14 @@ in ''; }; + extraPackages = mkOption { + type = types.listOf types.package; + default = [ ]; + description = '' + List of additional packages to be available within cloud-init jobs. + ''; + }; + settings = mkOption { description = '' Structured cloud-init configuration. @@ -163,7 +172,7 @@ in { text = cfg.config; } ; - systemd.network.enable = cfg.network.enable; + systemd.network.enable = mkIf cfg.network.enable true; systemd.services.cloud-init-local = { description = "Initial cloud-init job (pre-networking)"; diff --git a/nixos/modules/services/torrent/rtorrent.nix b/nixos/modules/services/torrent/rtorrent.nix index e0ce33d13462e..609b06b5e7066 100644 --- a/nixos/modules/services/torrent/rtorrent.nix +++ b/nixos/modules/services/torrent/rtorrent.nix @@ -182,7 +182,7 @@ in { # XMLRPC scgi_local = (cfg.rpcsock) - schedule = scgi_group,0,0,"execute.nothrow=chown,\":rtorrent\",(cfg.rpcsock)" + schedule = scgi_group,0,0,"execute.nothrow=chown,\":${cfg.group}\",(cfg.rpcsock)" schedule = scgi_permission,0,0,"execute.nothrow=chmod,\"g+w,o=\",(cfg.rpcsock)" ''; diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix index 4803d577b94b1..6b05886756fe2 100644 --- a/nixos/modules/services/ttys/kmscon.nix +++ b/nixos/modules/services/ttys/kmscon.nix @@ -41,6 +41,12 @@ in { }; in nullOr (nonEmptyListOf fontType); }; + useXkbConfig = mkOption { + description = "Configure keymap from xserver keyboard settings."; + type = types.bool; + default = false; + }; + extraConfig = mkOption { description = "Extra contents of the kmscon.conf file."; type = types.lines; @@ -91,9 +97,15 @@ in { services.kmscon.extraConfig = let + xkb = optionals cfg.useXkbConfig + (lib.mapAttrsToList (n: v: "xkb-${n}=${v}") ( + lib.filterAttrs + (n: v: builtins.elem n ["layout" "model" "options" "variant"] && v != "") + config.services.xserver.xkb + )); render = optionals cfg.hwRender [ "drm" "hwaccel" ]; fonts = optional (cfg.fonts != null) "font-name=${lib.concatMapStringsSep ", " (f: f.name) cfg.fonts}"; - in lib.concatStringsSep "\n" (render ++ fonts); + in lib.concatStringsSep "\n" (xkb ++ render ++ fonts); hardware.graphics.enable = mkIf cfg.hwRender true; diff --git a/nixos/modules/services/web-apps/eintopf.nix b/nixos/modules/services/web-apps/eintopf.nix new file mode 100644 index 0000000000000..a2b304445597e --- /dev/null +++ b/nixos/modules/services/web-apps/eintopf.nix @@ -0,0 +1,92 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.eintopf; + +in { + options.services.eintopf = { + + enable = mkEnableOption "Eintopf community event calendar web app"; + + settings = mkOption { + type = types.attrsOf types.str; + default = { }; + description = '' + Settings to configure web service. See + <https://codeberg.org/Klasse-Methode/eintopf/src/branch/main/DEPLOYMENT.md> + for available options. + ''; + example = literalExpression '' + { + EINTOPF_ADDR = ":1234"; + EINTOPF_ADMIN_EMAIL = "admin@example.org"; + EINTOPF_TIMEZONE = "Europe/Berlin"; + } + ''; + }; + + secrets = lib.mkOption { + type = with types; listOf path; + description = '' + A list of files containing the various secrets. Should be in the + format expected by systemd's `EnvironmentFile` directory. + ''; + default = [ ]; + }; + + }; + + config = mkIf cfg.enable { + + systemd.services.eintopf = { + description = "Community event calendar web app"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + environment = cfg.settings; + serviceConfig = { + ExecStart = "${pkgs.eintopf}/bin/eintopf"; + WorkingDirectory = "/var/lib/eintopf"; + StateDirectory = "eintopf" ; + EnvironmentFile = [ cfg.secrets ]; + + # hardening + AmbientCapabilities = ""; + CapabilityBoundingSet = "" ; + DevicePolicy = "closed"; + DynamicUser = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged" ]; + UMask = "0077"; + }; + }; + + }; + + meta.maintainers = with lib.maintainers; [ onny ]; + +} diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix index 021101fecaa48..7a22e15231923 100644 --- a/nixos/modules/services/web-apps/freshrss.nix +++ b/nixos/modules/services/web-apps/freshrss.nix @@ -5,6 +5,15 @@ let cfg = config.services.freshrss; poolName = "freshrss"; + + extension-env = pkgs.buildEnv { + name = "freshrss-extensions"; + paths = cfg.extensions; + }; + env-vars = { + DATA_PATH = cfg.dataDir; + THIRDPARTY_EXTENSIONS_PATH = "${extension-env}/share/freshrss/"; + }; in { meta.maintainers = with maintainers; [ etu stunkymonkey mattchrist ]; @@ -14,6 +23,31 @@ in package = mkPackageOption pkgs "freshrss" { }; + extensions = mkOption { + type = types.listOf types.package; + default = [ ]; + defaultText = literalExpression "[]"; + example = literalExpression '' + with freshrss-extensions; [ + youtube + ] ++ [ + (freshrss-extensions.buildFreshRssExtension { + FreshRssExtUniqueId = "ReadingTime"; + pname = "reading-time"; + version = "1.5"; + src = pkgs.fetchFromGitLab { + domain = "framagit.org"; + owner = "Lapineige"; + repo = "FreshRSS_Extension-ReadingTime"; + rev = "fb6e9e944ef6c5299fa56ffddbe04c41e5a34ebf"; + hash = "sha256-C5cRfaphx4Qz2xg2z+v5qRji8WVSIpvzMbethTdSqsk="; + }; + }) + ] + ''; + description = "Additional extensions to be used."; + }; + defaultUser = mkOption { type = types.str; default = "admin"; @@ -214,9 +248,7 @@ in "pm.max_spare_servers" = 5; "catch_workers_output" = true; }; - phpEnv = { - DATA_PATH = "${cfg.dataDir}"; - }; + phpEnv = env-vars; }; }; @@ -259,9 +291,7 @@ in RemainAfterExit = true; }; restartIfChanged = true; - environment = { - DATA_PATH = cfg.dataDir; - }; + environment = env-vars; script = let @@ -293,9 +323,7 @@ in description = "FreshRSS feed updater"; after = [ "freshrss-config.service" ]; startAt = "*:0/5"; - environment = { - DATA_PATH = cfg.dataDir; - }; + environment = env-vars; serviceConfig = defaultServiceConfig // { ExecStart = "${cfg.package}/app/actualize_script.php"; }; diff --git a/nixos/modules/services/web-apps/glance.md b/nixos/modules/services/web-apps/glance.md new file mode 100644 index 0000000000000..f65b32b3ba914 --- /dev/null +++ b/nixos/modules/services/web-apps/glance.md @@ -0,0 +1,39 @@ +# Glance {#module-services-glance} + +Glance is a self-hosted dashboard that puts all your feeds in one place. + +Visit [the Glance project page](https://github.com/glanceapp/glance) to learn +more about it. + +## Quickstart {#module-services-glance-quickstart} + +Checkout the [configuration docs](https://github.com/glanceapp/glance/blob/main/docs/configuration.md) to learn more. +Use the following configuration to start a public instance of Glance locally: + +```nix +{ + services.glance = { + enable = true; + settings = { + pages = [ + { + name = "Home"; + columns = [ + { + size = "full"; + widgets = [ + { type = "calendar"; } + { + type = "weather"; + location = "Nivelles, Belgium"; + } + ]; + } + ]; + } + ]; + }; + openFirewall = true; + }; +} +``` diff --git a/nixos/modules/services/web-apps/glance.nix b/nixos/modules/services/web-apps/glance.nix new file mode 100644 index 0000000000000..fbc310daea770 --- /dev/null +++ b/nixos/modules/services/web-apps/glance.nix @@ -0,0 +1,141 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.glance; + + inherit (lib) + mkEnableOption + mkPackageOption + mkOption + mkIf + getExe + types + ; + + settingsFormat = pkgs.formats.yaml { }; +in +{ + options.services.glance = { + enable = mkEnableOption "glance"; + package = mkPackageOption pkgs "glance" { }; + + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + options = { + server = { + host = mkOption { + description = "Glance bind address"; + default = "127.0.0.1"; + example = "0.0.0.0"; + type = types.str; + }; + port = mkOption { + description = "Glance port to listen on"; + default = 8080; + example = 5678; + type = types.port; + }; + }; + pages = mkOption { + type = settingsFormat.type; + description = '' + List of pages to be present on the dashboard. + + See <https://github.com/glanceapp/glance/blob/main/docs/configuration.md#pages--columns> + ''; + default = [ + { + name = "Calendar"; + columns = [ + { + size = "full"; + widgets = [ { type = "calendar"; } ]; + } + ]; + } + ]; + example = [ + { + name = "Home"; + columns = [ + { + size = "full"; + widgets = [ + { type = "calendar"; } + { + type = "weather"; + location = "Nivelles, Belgium"; + } + ]; + } + ]; + } + ]; + }; + }; + }; + default = { }; + description = '' + Configuration written to a yaml file that is read by glance. See + <https://github.com/glanceapp/glance/blob/main/docs/configuration.md> + for more. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Whether to open the firewall for Glance. + This adds `services.glance.settings.server.port` to `networking.firewall.allowedTCPPorts`. + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.glance = { + description = "Glance feed dashboard server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + ExecStart = + let + glance-yaml = settingsFormat.generate "glance.yaml" cfg.settings; + in + "${getExe cfg.package} --config ${glance-yaml}"; + WorkingDirectory = "/var/lib/glance"; + StateDirectory = "glance"; + RuntimeDirectory = "glance"; + RuntimeDirectoryMode = "0755"; + PrivateTmp = true; + DynamicUser = true; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + PrivateUsers = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + ProcSubset = "pid"; + RestrictNamespaces = true; + RestrictRealtime = true; + SystemCallArchitectures = "native"; + UMask = "0077"; + }; + }; + + networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.settings.server.port ]; }; + }; + + meta.doc = ./glance.md; + meta.maintainers = [ lib.maintainers.drupol ]; +} diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix index 247b65c786636..39aa7379c0edf 100644 --- a/nixos/modules/services/web-apps/jitsi-meet.nix +++ b/nixos/modules/services/web-apps/jitsi-meet.nix @@ -398,30 +398,29 @@ in before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service") ++ (optional cfg.jigasi.enable "jigasi.service"); serviceConfig = { Type = "oneshot"; + UMask = "027"; + User = "root"; + Group = "jitsi-meet"; + WorkingDirectory = "/var/lib/jitsi-meet"; }; script = let secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optionals cfg.jigasi.enable [ "jigasi-user-secret" "jigasi-component-secret" ]) ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret"); in '' - cd /var/lib/jitsi-meet ${concatMapStringsSep "\n" (s: '' if [ ! -f ${s} ]; then tr -dc a-zA-Z0-9 </dev/urandom | head -c 64 > ${s} - chown root:jitsi-meet ${s} - chmod 640 ${s} fi '') secrets} # for easy access in prosody echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env echo "JIGASI_COMPONENT_SECRET=$(cat jigasi-component-secret)" >> secrets-env - chown root:jitsi-meet secrets-env - chmod 640 secrets-env '' + optionalString cfg.prosody.enable '' # generate self-signed certificates - if [ ! -f /var/lib/jitsi-meet.crt ]; then + if [ ! -f /var/lib/jitsi-meet/jitsi-meet.crt ]; then ${getBin pkgs.openssl}/bin/openssl req \ -x509 \ -newkey rsa:4096 \ @@ -430,8 +429,7 @@ in -days 36500 \ -nodes \ -subj '/CN=${cfg.hostName}/CN=auth.${cfg.hostName}' - chmod 640 /var/lib/jitsi-meet/jitsi-meet.{crt,key} - chown root:jitsi-meet /var/lib/jitsi-meet/jitsi-meet.{crt,key} + chmod 640 /var/lib/jitsi-meet/jitsi-meet.key fi ''; }; diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix index b11626ec2dc3b..600ad3053605d 100644 --- a/nixos/modules/services/web-apps/mediawiki.nix +++ b/nixos/modules/services/web-apps/mediawiki.nix @@ -64,129 +64,135 @@ let else throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}"; - mediawikiConfig = pkgs.writeText "LocalSettings.php" '' - <?php - # Protect against web entry - if ( !defined( 'MEDIAWIKI' ) ) { - exit; - } + mediawikiConfig = pkgs.writeTextFile { + name = "LocalSettings.php"; + checkPhase = '' + ${php}/bin/php --syntax-check "$target" + ''; + text = '' + <?php + # Protect against web entry + if ( !defined( 'MEDIAWIKI' ) ) { + exit; + } - $wgSitename = "${cfg.name}"; - $wgMetaNamespace = false; - - ## The URL base path to the directory containing the wiki; - ## defaults for all runtime URL paths are based off of this. - ## For more information on customizing the URLs - ## (like /w/index.php/Page_title to /wiki/Page_title) please see: - ## https://www.mediawiki.org/wiki/Manual:Short_URL - $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}"; + $wgSitename = "${cfg.name}"; + $wgMetaNamespace = false; + + ## The URL base path to the directory containing the wiki; + ## defaults for all runtime URL paths are based off of this. + ## For more information on customizing the URLs + ## (like /w/index.php/Page_title to /wiki/Page_title) please see: + ## https://www.mediawiki.org/wiki/Manual:Short_URL + $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}"; - ## The protocol and server name to use in fully-qualified URLs - $wgServer = "${cfg.url}"; + ## The protocol and server name to use in fully-qualified URLs + $wgServer = "${cfg.url}"; - ## The URL path to static resources (images, scripts, etc.) - $wgResourceBasePath = $wgScriptPath; + ## The URL path to static resources (images, scripts, etc.) + $wgResourceBasePath = $wgScriptPath; - ${lib.optionalString (cfg.webserver == "nginx") '' - $wgArticlePath = "/wiki/$1"; - $wgUsePathInfo = true; - ''} + ${lib.optionalString (cfg.webserver == "nginx") '' + $wgArticlePath = "/wiki/$1"; + $wgUsePathInfo = true; + ''} - ## The URL path to the logo. Make sure you change this from the default, - ## or else you'll overwrite your logo when you upgrade! - $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png"; + ## The URL path to the logo. Make sure you change this from the default, + ## or else you'll overwrite your logo when you upgrade! + $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png"; - ## UPO means: this is also a user preference option + ## UPO means: this is also a user preference option - $wgEnableEmail = true; - $wgEnableUserEmail = true; # UPO + $wgEnableEmail = true; + $wgEnableUserEmail = true; # UPO - $wgPasswordSender = "${cfg.passwordSender}"; + $wgPasswordSender = "${cfg.passwordSender}"; - $wgEnotifUserTalk = false; # UPO - $wgEnotifWatchlist = false; # UPO - $wgEmailAuthentication = true; + $wgEnotifUserTalk = false; # UPO + $wgEnotifWatchlist = false; # UPO + $wgEmailAuthentication = true; - ## Database settings - $wgDBtype = "${cfg.database.type}"; - $wgDBserver = "${dbAddr}"; - $wgDBport = "${toString cfg.database.port}"; - $wgDBname = "${cfg.database.name}"; - $wgDBuser = "${cfg.database.user}"; - ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"} + ## Database settings + $wgDBtype = "${cfg.database.type}"; + $wgDBserver = "${dbAddr}"; + $wgDBport = "${toString cfg.database.port}"; + $wgDBname = "${cfg.database.name}"; + $wgDBuser = "${cfg.database.user}"; + ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"} - ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) '' - # MySQL specific settings - $wgDBprefix = "${cfg.database.tablePrefix}"; - ''} + ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) '' + # MySQL specific settings + $wgDBprefix = "${cfg.database.tablePrefix}"; + ''} - ${optionalString (cfg.database.type == "mysql") '' - # MySQL table options to use during installation or update - $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary"; - ''} + ${optionalString (cfg.database.type == "mysql") '' + # MySQL table options to use during installation or update + $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary"; + ''} - ## Shared memory settings - $wgMainCacheType = CACHE_NONE; - $wgMemCachedServers = []; + ## Shared memory settings + $wgMainCacheType = CACHE_NONE; + $wgMemCachedServers = []; - ${optionalString (cfg.uploadsDir != null) '' - $wgEnableUploads = true; - $wgUploadDirectory = "${cfg.uploadsDir}"; - ''} + ${optionalString (cfg.uploadsDir != null) '' + $wgEnableUploads = true; + $wgUploadDirectory = "${cfg.uploadsDir}"; + ''} - $wgUseImageMagick = true; - $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert"; + $wgUseImageMagick = true; + $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert"; - # InstantCommons allows wiki to use images from https://commons.wikimedia.org - $wgUseInstantCommons = false; + # InstantCommons allows wiki to use images from https://commons.wikimedia.org + $wgUseInstantCommons = false; - # Periodically send a pingback to https://www.mediawiki.org/ with basic data - # about this MediaWiki instance. The Wikimedia Foundation shares this data - # with MediaWiki developers to help guide future development efforts. - $wgPingback = true; + # Periodically send a pingback to https://www.mediawiki.org/ with basic data + # about this MediaWiki instance. The Wikimedia Foundation shares this data + # with MediaWiki developers to help guide future development efforts. + $wgPingback = true; - ## If you use ImageMagick (or any other shell command) on a - ## Linux server, this will need to be set to the name of an - ## available UTF-8 locale - $wgShellLocale = "C.UTF-8"; + ## If you use ImageMagick (or any other shell command) on a + ## Linux server, this will need to be set to the name of an + ## available UTF-8 locale + $wgShellLocale = "C.UTF-8"; - ## Set $wgCacheDirectory to a writable directory on the web server - ## to make your wiki go slightly faster. The directory should not - ## be publicly accessible from the web. - $wgCacheDirectory = "${cacheDir}"; + ## Set $wgCacheDirectory to a writable directory on the web server + ## to make your wiki go slightly faster. The directory should not + ## be publicly accessible from the web. + $wgCacheDirectory = "${cacheDir}"; - # Site language code, should be one of the list in ./languages/data/Names.php - $wgLanguageCode = "en"; + # Site language code, should be one of the list in ./languages/data/Names.php + $wgLanguageCode = "en"; - $wgSecretKey = file_get_contents("${stateDir}/secret.key"); + $wgSecretKey = file_get_contents("${stateDir}/secret.key"); - # Changing this will log out all existing sessions. - $wgAuthenticationTokenVersion = ""; + # Changing this will log out all existing sessions. + $wgAuthenticationTokenVersion = ""; - ## For attaching licensing metadata to pages, and displaying an - ## appropriate copyright notice / icon. GNU Free Documentation - ## License and Creative Commons licenses are supported so far. - $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright - $wgRightsUrl = ""; - $wgRightsText = ""; - $wgRightsIcon = ""; + ## For attaching licensing metadata to pages, and displaying an + ## appropriate copyright notice / icon. GNU Free Documentation + ## License and Creative Commons licenses are supported so far. + $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright + $wgRightsUrl = ""; + $wgRightsText = ""; + $wgRightsIcon = ""; - # Path to the GNU diff3 utility. Used for conflict resolution. - $wgDiff = "${pkgs.diffutils}/bin/diff"; - $wgDiff3 = "${pkgs.diffutils}/bin/diff3"; + # Path to the GNU diff3 utility. Used for conflict resolution. + $wgDiff = "${pkgs.diffutils}/bin/diff"; + $wgDiff3 = "${pkgs.diffutils}/bin/diff3"; - # Enabled skins. - ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)} + # Enabled skins. + ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)} - # Enabled extensions. - ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)} + # Enabled extensions. + ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)} - # End of automatically generated settings. - # Add more configuration options below. + # End of automatically generated settings. + # Add more configuration options below. - ${cfg.extraConfig} - ''; + ${cfg.extraConfig} + ''; + }; withTrailingSlash = str: if lib.hasSuffix "/" str then str else "${str}/"; in diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md index 0b615deae44be..c433be5350b53 100644 --- a/nixos/modules/services/web-apps/nextcloud.md +++ b/nixos/modules/services/web-apps/nextcloud.md @@ -121,6 +121,29 @@ Auto updates for Nextcloud apps can be enabled using This is not an end-to-end encryption, but can be used to encrypt files that will be persisted to external storage such as S3. + - **Issues with file permissions / unsafe path transitions** + + {manpage}`systemd-tmpfiles(8)` makes sure that the paths for + + * configuration (including declarative config) + * data + * app store + * home directory itself (usually `/var/lib/nextcloud`) + + are properly set up. However, `systemd-tmpfiles` will refuse to do so + if it detects an unsafe path transition, i.e. creating files/directories + within a directory that is neither owned by `root` nor by `nextcloud`, the + owning user of the files/directories to be created. + + Symptoms of that include + + * `config/override.config.php` not being updated (and the config file + eventually being garbage-collected). + * failure to read from application data. + + To work around that, please make sure that all directories in question + are owned by `nextcloud:nextcloud`. + ## Using an alternative webserver as reverse-proxy (e.g. `httpd`) {#module-services-nextcloud-httpd} By default, `nginx` is used as reverse-proxy for `nextcloud`. diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index bfb3e73e65102..c8c4fe4b4d612 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -91,10 +91,10 @@ let 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' + sudo='exec /run/wrappers/bin/sudo -u nextcloud' fi - export NEXTCLOUD_CONFIG_DIR="${datadir}/config" - $sudo \ + $sudo ${pkgs.coreutils}/bin/env \ + NEXTCLOUD_CONFIG_DIR="${datadir}/config" \ ${phpCli} \ occ "$@" ''; diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix index 545ca68ccaac2..0d0e01d4f7bc0 100644 --- a/nixos/modules/services/web-apps/onlyoffice.nix +++ b/nixos/modules/services/web-apps/onlyoffice.nix @@ -1,24 +1,22 @@ { lib, config, pkgs, ... }: -with lib; - let cfg = config.services.onlyoffice; in { options.services.onlyoffice = { - enable = mkEnableOption "OnlyOffice DocumentServer"; + enable = lib.mkEnableOption "OnlyOffice DocumentServer"; - enableExampleServer = mkEnableOption "OnlyOffice example server"; + enableExampleServer = lib.mkEnableOption "OnlyOffice example server"; - hostname = mkOption { - type = types.str; + hostname = lib.mkOption { + type = lib.types.str; default = "localhost"; - description = "FQDN for the onlyoffice instance."; + description = "FQDN for the OnlyOffice instance."; }; - jwtSecretFile = mkOption { - type = types.nullOr types.str; + jwtSecretFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; default = null; description = '' Path to a file that contains the secret to sign web requests using JSON Web Tokens. @@ -26,34 +24,34 @@ in ''; }; - package = mkPackageOption pkgs "onlyoffice-documentserver" { }; + package = lib.mkPackageOption pkgs "onlyoffice-documentserver" { }; - port = mkOption { - type = types.port; + port = lib.mkOption { + type = lib.types.port; default = 8000; - description = "Port the OnlyOffice DocumentServer should listens on."; + description = "Port the OnlyOffice document server should listen on."; }; - examplePort = mkOption { - type = types.port; + examplePort = lib.mkOption { + type = lib.types.port; default = null; - description = "Port the OnlyOffice Example server should listens on."; + description = "Port the OnlyOffice example server should listen on."; }; - postgresHost = mkOption { - type = types.str; + postgresHost = lib.mkOption { + type = lib.types.str; default = "/run/postgresql"; description = "The Postgresql hostname or socket path OnlyOffice should connect to."; }; - postgresName = mkOption { - type = types.str; + postgresName = lib.mkOption { + type = lib.types.str; default = "onlyoffice"; - description = "The name of database OnlyOffice should user."; + description = "The name of database OnlyOffice should use."; }; - postgresPasswordFile = mkOption { - type = types.nullOr types.str; + postgresPasswordFile = lib.mkOption { + type = lib.types.nullOr lib.types.str; default = null; description = '' Path to a file that contains the password OnlyOffice should use to connect to Postgresql. @@ -61,8 +59,8 @@ in ''; }; - postgresUser = mkOption { - type = types.str; + postgresUser = lib.mkOption { + type = lib.types.str; default = "onlyoffice"; description = '' The username OnlyOffice should use to connect to Postgresql. @@ -70,8 +68,8 @@ in ''; }; - rabbitmqUrl = mkOption { - type = types.str; + rabbitmqUrl = lib.mkOption { + type = lib.types.str; default = "amqp://guest:guest@localhost:5672"; description = "The Rabbitmq in amqp URI style OnlyOffice should connect to."; }; @@ -80,10 +78,10 @@ in config = lib.mkIf cfg.enable { services = { nginx = { - enable = mkDefault true; + enable = lib.mkDefault true; # misses text/csv, font/ttf, application/x-font-ttf, application/rtf, application/wasm - recommendedGzipSettings = mkDefault true; - recommendedProxySettings = mkDefault true; + recommendedGzipSettings = lib.mkDefault true; + recommendedProxySettings = lib.mkDefault true; upstreams = { # /etc/nginx/includes/http-common.conf diff --git a/nixos/modules/services/web-apps/pixelfed.nix b/nixos/modules/services/web-apps/pixelfed.nix index cd0e8f62b65c8..46d671f8504b6 100644 --- a/nixos/modules/services/web-apps/pixelfed.nix +++ b/nixos/modules/services/web-apps/pixelfed.nix @@ -40,7 +40,7 @@ in { pixelfed = { enable = mkEnableOption "a Pixelfed instance"; package = mkPackageOption pkgs "pixelfed" { }; - phpPackage = mkPackageOption pkgs "php81" { }; + phpPackage = mkPackageOption pkgs "php82" { }; user = mkOption { type = types.str; diff --git a/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix index 022bb5e438812..df3eeaef16183 100644 --- a/nixos/modules/services/web-apps/shiori.nix +++ b/nixos/modules/services/web-apps/shiori.nix @@ -1,17 +1,15 @@ { config, lib, pkgs, ... }: -with lib; -let - cfg = config.services.shiori; +let cfg = config.services.shiori; in { options = { services.shiori = { - enable = mkEnableOption "Shiori simple bookmarks manager"; + enable = lib.mkEnableOption "Shiori simple bookmarks manager"; - package = mkPackageOption pkgs "shiori" { }; + package = lib.mkPackageOption pkgs "shiori" { }; - address = mkOption { - type = types.str; + address = lib.mkOption { + type = lib.types.str; default = ""; description = '' The IP address on which Shiori will listen. @@ -19,30 +17,55 @@ in { ''; }; - port = mkOption { - type = types.port; + port = lib.mkOption { + type = lib.types.port; default = 8080; description = "The port of the Shiori web application"; }; - webRoot = mkOption { - type = types.str; + webRoot = lib.mkOption { + type = lib.types.str; default = "/"; example = "/shiori"; description = "The root of the Shiori web application"; }; + + environmentFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/path/to/environmentFile"; + description = '' + Path to file containing environment variables. + Useful for passing down secrets. + <https://github.com/go-shiori/shiori/blob/master/docs/Configuration.md#overall-configuration> + ''; + }; + + databaseUrl = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "postgres:///shiori?host=/run/postgresql"; + description = "The connection URL to connect to MySQL or PostgreSQL"; + }; }; }; - config = mkIf cfg.enable { - systemd.services.shiori = with cfg; { + config = lib.mkIf cfg.enable { + systemd.services.shiori = { description = "Shiori simple bookmarks manager"; wantedBy = [ "multi-user.target" ]; - - environment.SHIORI_DIR = "/var/lib/shiori"; + after = [ "postgresql.service" "mysql.service" ]; + environment = { + SHIORI_DIR = "/var/lib/shiori"; + } // lib.optionalAttrs (cfg.databaseUrl != null) { + SHIORI_DATABASE_URL = cfg.databaseUrl; + }; serviceConfig = { - ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}' --webroot '${webRoot}'"; + ExecStart = + "${cfg.package}/bin/shiori server --address '${cfg.address}' --port '${ + toString cfg.port + }' --webroot '${cfg.webRoot}'"; DynamicUser = true; StateDirectory = "shiori"; @@ -50,15 +73,24 @@ in { RuntimeDirectory = "shiori"; # Security options - + EnvironmentFile = + lib.optional (cfg.environmentFile != null) cfg.environmentFile; BindReadOnlyPaths = [ "/nix/store" # For SSL certificates, and the resolv.conf "/etc" - ]; + ] ++ lib.optional (config.services.postgresql.enable && + cfg.databaseUrl != null && + lib.strings.hasPrefix "postgres://" cfg.databaseUrl) + "/run/postgresql" + ++ lib.optional (config.services.mysql.enable && + cfg.databaseUrl != null && + lib.strings.hasPrefix "mysql://" cfg.databaseUrl) + "/var/run/mysqld"; CapabilityBoundingSet = ""; + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; DeviceAllow = ""; @@ -78,7 +110,7 @@ in { ProtectKernelTunables = true; RestrictNamespaces = true; - RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ]; RestrictRealtime = true; RestrictSUIDSGID = true; @@ -88,11 +120,17 @@ in { SystemCallErrorNumber = "EPERM"; SystemCallFilter = [ "@system-service" - "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid" + "~@cpu-emulation" + "~@debug" + "~@keyring" + "~@memlock" + "~@obsolete" + "~@privileged" + "~@setuid" ]; }; }; }; - meta.maintainers = with maintainers; [ minijackson ]; + meta.maintainers = with lib.maintainers; [ minijackson CaptainJawZ ]; } diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index 4d49b29efff69..46cb099595794 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -435,7 +435,7 @@ in example = literalExpression '' [ "proxy_connect" - { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; } + { name = "jk"; path = "''${pkgs.apacheHttpdPackages.mod_jk}/modules/mod_jk.so"; } ] ''; description = '' @@ -538,25 +538,13 @@ in ''; }; - enableMellon = mkOption { - type = types.bool; - default = false; - description = "Whether to enable the mod_auth_mellon module."; - }; + enableMellon = mkEnableOption "the mod_auth_mellon module"; - enablePHP = mkOption { - type = types.bool; - default = false; - description = "Whether to enable the PHP module."; - }; + enablePHP = mkEnableOption "the PHP module"; phpPackage = mkPackageOption pkgs "php" { }; - enablePerl = mkOption { - type = types.bool; - default = false; - description = "Whether to enable the Perl module (mod_perl)."; - }; + enablePerl = mkEnableOption "the Perl module (mod_perl)"; phpOptions = mkOption { type = types.lines; diff --git a/nixos/modules/services/web-servers/fcgiwrap.nix b/nixos/modules/services/web-servers/fcgiwrap.nix index 3250e9c05ed66..29ddd39942c60 100644 --- a/nixos/modules/services/web-servers/fcgiwrap.nix +++ b/nixos/modules/services/web-servers/fcgiwrap.nix @@ -3,70 +3,128 @@ with lib; let - cfg = config.services.fcgiwrap; + forEachInstance = f: flip mapAttrs' config.services.fcgiwrap (name: cfg: + nameValuePair "fcgiwrap-${name}" (f cfg) + ); + in { + options.services.fcgiwrap = mkOption { + description = "Configuration for fcgiwrap instances."; + default = { }; + type = types.attrsOf (types.submodule ({ config, ... }: { options = { + process.prefork = mkOption { + type = types.ints.positive; + default = 1; + description = "Number of processes to prefork."; + }; - options = { - services.fcgiwrap = { - enable = mkOption { - type = types.bool; - default = false; - description = "Whether to enable fcgiwrap, a server for running CGI applications over FastCGI."; + process.user = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + User as which this instance of fcgiwrap will be run. + Set to `null` (the default) to use a dynamically allocated user. + ''; }; - preforkProcesses = mkOption { - type = types.int; - default = 1; - description = "Number of processes to prefork."; + process.group = mkOption { + type = types.nullOr types.str; + default = null; + description = "Group as which this instance of fcgiwrap will be run."; }; - socketType = mkOption { + socket.type = mkOption { type = types.enum [ "unix" "tcp" "tcp6" ]; default = "unix"; description = "Socket type: 'unix', 'tcp' or 'tcp6'."; }; - socketAddress = mkOption { + socket.address = mkOption { type = types.str; - default = "/run/fcgiwrap.sock"; + default = "/run/fcgiwrap-${config._module.args.name}.sock"; example = "1.2.3.4:5678"; - description = "Socket address. In case of a UNIX socket, this should be its filesystem path."; + description = '' + Socket address. + In case of a UNIX socket, this should be its filesystem path. + ''; }; - user = mkOption { + socket.user = mkOption { type = types.nullOr types.str; default = null; - description = "User permissions for the socket."; + description = '' + User to be set as owner of the UNIX socket. + Defaults to the process running user. + ''; }; - group = mkOption { + socket.group = mkOption { type = types.nullOr types.str; default = null; - description = "Group permissions for the socket."; + description = '' + Group to be set as owner of the UNIX socket. + Defaults to the process running group. + ''; }; - }; + + socket.mode = mkOption { + type = types.nullOr types.str; + default = if config.socket.type == "unix" then "0600" else null; + defaultText = literalExpression '' + if config.socket.type == "unix" then "0600" else null + ''; + description = '' + Mode to be set on the UNIX socket. + Defaults to private to the socket's owner. + ''; + }; + }; })); }; - config = mkIf cfg.enable { - systemd.services.fcgiwrap = { + config = { + assertions = concatLists (mapAttrsToList (name: cfg: [ + { + assertion = cfg.socket.user != null -> cfg.socket.type == "unix"; + message = "Socket owner can only be set for the UNIX socket type."; + } + { + assertion = cfg.socket.group != null -> cfg.socket.type == "unix"; + message = "Socket owner can only be set for the UNIX socket type."; + } + { + assertion = cfg.socket.mode != null -> cfg.socket.type == "unix"; + message = "Socket mode can only be set for the UNIX socket type."; + } + ]) config.services.fcgiwrap); + + systemd.services = forEachInstance (cfg: { after = [ "nss-user-lookup.target" ]; - wantedBy = optional (cfg.socketType != "unix") "multi-user.target"; + wantedBy = optional (cfg.socket.type != "unix") "multi-user.target"; serviceConfig = { - ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${builtins.toString cfg.preforkProcesses} ${ - optionalString (cfg.socketType != "unix") "-s ${cfg.socketType}:${cfg.socketAddress}" - }"; - } // (if cfg.user != null && cfg.group != null then { - User = cfg.user; - Group = cfg.group; - } else { } ); - }; + ExecStart = '' + ${pkgs.fcgiwrap}/sbin/fcgiwrap ${cli.toGNUCommandLineShell {} ({ + c = cfg.process.prefork; + } // (optionalAttrs (cfg.socket.type != "unix") { + s = "${cfg.socket.type}:${cfg.socket.address}"; + }))} + ''; + } // (if cfg.process.user != null then { + User = cfg.process.user; + Group = cfg.process.group; + } else { + DynamicUser = true; + }); + }); - systemd.sockets = if (cfg.socketType == "unix") then { - fcgiwrap = { - wantedBy = [ "sockets.target" ]; - socketConfig.ListenStream = cfg.socketAddress; + systemd.sockets = forEachInstance (cfg: mkIf (cfg.socket.type == "unix") { + wantedBy = [ "sockets.target" ]; + socketConfig = { + ListenStream = cfg.socket.address; + SocketUser = cfg.socket.user; + SocketGroup = cfg.socket.group; + SocketMode = cfg.socket.mode; }; - } else { }; + }); }; } diff --git a/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixos/modules/services/x11/desktop-managers/budgie.nix index b4e7390293351..7f43a939970b8 100644 --- a/nixos/modules/services/x11/desktop-managers/budgie.nix +++ b/nixos/modules/services/x11/desktop-managers/budgie.nix @@ -61,7 +61,7 @@ in { ''; type = types.listOf types.package; default = []; - example = literalExpression "[ pkgs.gnome.gpaste ]"; + example = literalExpression "[ pkgs.gpaste ]"; }; extraGSettingsOverrides = mkOption { @@ -132,7 +132,7 @@ in { gnome-menus # Required by Budgie Control Center. - gnome.zenity + zenity # Provides `gsettings`. glib @@ -162,7 +162,7 @@ in { ++ cfg.sessionPath; # Both budgie-desktop-view and nemo defaults to this emulator. - programs.gnome-terminal.enable = mkDefault (notExcluded pkgs.gnome.gnome-terminal); + programs.gnome-terminal.enable = mkDefault (notExcluded pkgs.gnome-terminal); # Fonts. fonts.packages = [ diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix index fa67441e7ac49..0dc21862d8348 100644 --- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix +++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix @@ -27,7 +27,7 @@ in sessionPath = mkOption { default = []; type = types.listOf types.package; - example = literalExpression "[ pkgs.gnome.gpaste ]"; + example = literalExpression "[ pkgs.gpaste ]"; description = '' Additional list of packages to be added to the session search path. Useful for GSettings-conditional autostart. @@ -163,8 +163,8 @@ in libgnomekbd # theme - gnome.adwaita-icon-theme - gnome.gnome-themes-extra + adwaita-icon-theme + gnome-themes-extra gtk3.out # other @@ -229,11 +229,11 @@ in }) (mkIf serviceCfg.apps.enable { - programs.gnome-disks.enable = mkDefault (notExcluded pkgs.gnome.gnome-disk-utility); - programs.gnome-terminal.enable = mkDefault (notExcluded pkgs.gnome.gnome-terminal); - programs.file-roller.enable = mkDefault (notExcluded pkgs.gnome.file-roller); + programs.gnome-disks.enable = mkDefault (notExcluded pkgs.gnome-disk-utility); + programs.gnome-terminal.enable = mkDefault (notExcluded pkgs.gnome-terminal); + programs.file-roller.enable = mkDefault (notExcluded pkgs.file-roller); - environment.systemPackages = with pkgs // pkgs.gnome // pkgs.cinnamon; utils.removePackagesByName [ + environment.systemPackages = with pkgs // pkgs.cinnamon; utils.removePackagesByName [ # cinnamon team apps bulky warpinator diff --git a/nixos/modules/services/x11/desktop-managers/gnome.md b/nixos/modules/services/x11/desktop-managers/gnome.md index 2b4bd06df04f2..9943f138dc60c 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome.md +++ b/nixos/modules/services/x11/desktop-managers/gnome.md @@ -114,7 +114,7 @@ in `dconf-editor` ## Shell Extensions {#sec-gnome-shell-extensions} Most Shell extensions are packaged under the `gnomeExtensions` attribute. -Some packages that include Shell extensions, like `gnome.gpaste`, don’t have their extension decoupled under this attribute. +Some packages that include Shell extensions, like `gpaste`, don’t have their extension decoupled under this attribute. You can install them like any other package: diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix index fe50d930b5af0..11d5fcbfee23d 100644 --- a/nixos/modules/services/x11/desktop-managers/gnome.nix +++ b/nixos/modules/services/x11/desktop-managers/gnome.nix @@ -89,7 +89,7 @@ in sessionPath = mkOption { default = []; type = types.listOf types.package; - example = literalExpression "[ pkgs.gnome.gpaste ]"; + example = literalExpression "[ pkgs.gpaste ]"; description = '' Additional list of packages to be added to the session search path. Useful for GNOME Shell extensions or GSettings-conditional autostart. @@ -176,7 +176,7 @@ in environment.gnome.excludePackages = mkOption { default = []; - example = literalExpression "[ pkgs.gnome.totem ]"; + example = literalExpression "[ pkgs.totem ]"; type = types.listOf types.package; description = "Which packages gnome should exclude from the default environment"; }; @@ -373,7 +373,7 @@ in gnome-shell ]; optionalPackages = with pkgs.gnome; [ - adwaita-icon-theme + pkgs.adwaita-icon-theme nixos-background-info gnome-backgrounds gnome-bluetooth @@ -399,28 +399,28 @@ in with pkgs.gnome; utils.removePackagesByName ([ - baobab - epiphany + pkgs.baobab + pkgs.epiphany pkgs.gnome-text-editor - gnome-calculator - gnome-calendar + pkgs.gnome-calculator + pkgs.gnome-calendar gnome-characters gnome-clocks pkgs.gnome-console gnome-contacts - gnome-font-viewer + pkgs.gnome-font-viewer gnome-logs gnome-maps gnome-music - gnome-system-monitor + pkgs.gnome-system-monitor gnome-weather pkgs.loupe - nautilus + pkgs.nautilus pkgs.gnome-connections - simple-scan + pkgs.simple-scan pkgs.snapshot - totem - yelp + pkgs.totem + pkgs.yelp ] ++ lib.optionals config.services.flatpak.enable [ # Since PackageKit Nix support is not there yet, # only install gnome-software if flatpak is enabled. @@ -432,12 +432,12 @@ in # Since some of these have a corresponding package, we only # enable that program module if the package hasn't been excluded # through `environment.gnome.excludePackages` - programs.evince.enable = notExcluded pkgs.gnome.evince; - programs.file-roller.enable = notExcluded pkgs.gnome.file-roller; - programs.geary.enable = notExcluded pkgs.gnome.geary; - programs.gnome-disks.enable = notExcluded pkgs.gnome.gnome-disk-utility; - programs.seahorse.enable = notExcluded pkgs.gnome.seahorse; - services.gnome.sushi.enable = notExcluded pkgs.gnome.sushi; + programs.evince.enable = notExcluded pkgs.evince; + programs.file-roller.enable = notExcluded pkgs.file-roller; + programs.geary.enable = notExcluded pkgs.geary; + programs.gnome-disks.enable = notExcluded pkgs.gnome-disk-utility; + programs.seahorse.enable = notExcluded pkgs.seahorse; + services.gnome.sushi.enable = notExcluded pkgs.sushi; # VTE shell integration for gnome-console programs.bash.vteIntegration = mkDefault true; @@ -482,9 +482,9 @@ in # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/-/blob/3.38.0/elements/core/meta-gnome-core-developer-tools.bst (lib.mkIf serviceCfg.core-developer-tools.enable { - environment.systemPackages = with pkgs.gnome; utils.removePackagesByName [ - dconf-editor - devhelp + environment.systemPackages = utils.removePackagesByName [ + pkgs.dconf-editor + pkgs.devhelp pkgs.gnome-builder # boxes would make sense in this option, however # it doesn't function well enough to be included diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix index 0e9a06706d4f6..01bf3aad202ad 100644 --- a/nixos/modules/services/x11/desktop-managers/pantheon.nix +++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix @@ -44,7 +44,7 @@ in sessionPath = mkOption { default = []; type = types.listOf types.package; - example = literalExpression "[ pkgs.gnome.gpaste ]"; + example = literalExpression "[ pkgs.gpaste ]"; description = '' Additional list of packages to be added to the session search path. Useful for GSettings-conditional autostart. @@ -207,7 +207,7 @@ in desktop-file-utils glib # for gsettings program gnome-menus - gnome.adwaita-icon-theme + adwaita-icon-theme gtk3.out # for gtk-launch program onboard orca # elementary/greeter#668 @@ -284,11 +284,11 @@ in }) (mkIf serviceCfg.apps.enable { - programs.evince.enable = mkDefault (notExcluded pkgs.gnome.evince); - programs.file-roller.enable = mkDefault (notExcluded pkgs.gnome.file-roller); + programs.evince.enable = mkDefault (notExcluded pkgs.evince); + programs.file-roller.enable = mkDefault (notExcluded pkgs.file-roller); environment.systemPackages = utils.removePackagesByName ([ - pkgs.gnome.gnome-font-viewer + pkgs.gnome-font-viewer ] ++ (with pkgs.pantheon; [ elementary-calculator elementary-calendar diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix index aee2f5b35db2e..98d3555ccbc5e 100644 --- a/nixos/modules/services/x11/desktop-managers/xfce.nix +++ b/nixos/modules/services/x11/desktop-managers/xfce.nix @@ -84,8 +84,8 @@ in glib # for gsettings gtk3.out # gtk-update-icon-cache - gnome.gnome-themes-extra - gnome.adwaita-icon-theme + gnome-themes-extra + adwaita-icon-theme hicolor-icon-theme tango-icon-theme xfce4-icon-theme diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix index 51ab08e74f864..1a39b365db5f3 100644 --- a/nixos/modules/services/x11/display-managers/gdm.nix +++ b/nixos/modules/services/x11/display-managers/gdm.nix @@ -6,6 +6,7 @@ let cfg = config.services.xserver.displayManager; gdm = pkgs.gnome.gdm; + pamCfg = config.security.pam.services; settingsFormat = pkgs.formats.ini { }; configFile = settingsFormat.generate "custom.conf" cfg.gdm.settings; @@ -155,7 +156,7 @@ in gdm # for gnome-login.session config.services.displayManager.sessionData.desktops pkgs.gnome.gnome-control-center # for accessibility icon - pkgs.gnome.adwaita-icon-theme + pkgs.adwaita-icon-theme pkgs.hicolor-icon-theme # empty icon theme as a base ]; } // optionalAttrs (xSessionWrapper != null) { @@ -183,7 +184,7 @@ in # Otherwise GDM will not be able to start correctly and display Wayland sessions systemd.packages = with pkgs.gnome; [ gdm gnome-session gnome-shell ]; - environment.systemPackages = [ pkgs.gnome.adwaita-icon-theme ]; + environment.systemPackages = [ pkgs.adwaita-icon-theme ]; # We dont use the upstream gdm service # it has to be disabled since the gdm package has it @@ -321,15 +322,20 @@ in session include login ''; + # This would block password prompt when included by gdm-password. + # GDM will instead run gdm-fingerprint in parallel. login.fprintAuth = mkIf config.services.fprintd.enable false; + gdm-fingerprint.text = mkIf config.services.fprintd.enable '' auth required pam_shells.so auth requisite pam_nologin.so auth requisite pam_faillock.so preauth auth required ${pkgs.fprintd}/lib/security/pam_fprintd.so - auth optional pam_permit.so auth required pam_env.so - auth [success=ok default=1] ${pkgs.gnome.gdm}/lib/security/pam_gdm.so + ${lib.optionalString pamCfg.login.enableGnomeKeyring '' + auth [success=ok default=1] ${pkgs.gnome.gdm}/lib/security/pam_gdm.so + auth optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so + ''} account include login diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix index 930ee96b384d5..8975d6fb9f0f7 100644 --- a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix +++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix @@ -34,8 +34,8 @@ in { theme = { package = mkOption { type = types.package; - default = pkgs.gnome.gnome-themes-extra; - defaultText = literalExpression "pkgs.gnome.gnome-themes-extra"; + default = pkgs.gnome-themes-extra; + defaultText = literalExpression "pkgs.gnome-themes-extra"; description = '' The package path that contains the theme given in the name option. ''; diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix index 30940da103a96..0907aeb839ba3 100644 --- a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix +++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix @@ -47,8 +47,8 @@ in package = mkOption { type = types.package; - default = pkgs.gnome.gnome-themes-extra; - defaultText = literalExpression "pkgs.gnome.gnome-themes-extra"; + default = pkgs.gnome-themes-extra; + defaultText = literalExpression "pkgs.gnome-themes-extra"; description = '' The package path that contains the theme given in the name option. ''; @@ -68,8 +68,8 @@ in package = mkOption { type = types.package; - default = pkgs.gnome.adwaita-icon-theme; - defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme"; + default = pkgs.adwaita-icon-theme; + defaultText = literalExpression "pkgs.adwaita-icon-theme"; description = '' The package path that contains the icon theme given in the name option. ''; @@ -89,8 +89,8 @@ in package = mkOption { type = types.package; - default = pkgs.gnome.adwaita-icon-theme; - defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme"; + default = pkgs.adwaita-icon-theme; + defaultText = literalExpression "pkgs.adwaita-icon-theme"; description = '' The package path that contains the cursor theme given in the name option. ''; diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix index 299d3bae5f06f..d20b26491aee1 100644 --- a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix +++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix @@ -33,8 +33,8 @@ in theme = { package = mkOption { type = types.package; - default = pkgs.gnome.gnome-themes-extra; - defaultText = literalExpression "pkgs.gnome.gnome-themes-extra"; + default = pkgs.gnome-themes-extra; + defaultText = literalExpression "pkgs.gnome-themes-extra"; description = '' The package path that contains the theme given in the name option. ''; @@ -52,8 +52,8 @@ in iconTheme = { package = mkOption { type = types.package; - default = pkgs.gnome.adwaita-icon-theme; - defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme"; + default = pkgs.adwaita-icon-theme; + defaultText = literalExpression "pkgs.adwaita-icon-theme"; description = '' The package path that contains the icon theme given in the name option. ''; @@ -90,8 +90,8 @@ in cursorTheme = { package = mkOption { type = types.package; - default = pkgs.gnome.adwaita-icon-theme; - defaultText = literalExpression "pkgs.gnome.adwaita-icon-theme"; + default = pkgs.adwaita-icon-theme; + defaultText = literalExpression "pkgs.adwaita-icon-theme"; description = '' The package path that contains the cursor theme given in the name option. ''; diff --git a/nixos/modules/services/x11/window-managers/herbstluftwm.nix b/nixos/modules/services/x11/window-managers/herbstluftwm.nix index 7edaf4e980ec4..94c9f4aa91c8f 100644 --- a/nixos/modules/services/x11/window-managers/herbstluftwm.nix +++ b/nixos/modules/services/x11/window-managers/herbstluftwm.nix @@ -33,7 +33,10 @@ in (cfg.configFile != null) ''-c "${cfg.configFile}"'' ; - in "${cfg.package}/bin/herbstluftwm ${configFileClause} &"; + in '' + ${cfg.package}/bin/herbstluftwm ${configFileClause} & + waitPID=$! + ''; }; environment.systemPackages = [ cfg.package ]; }; |