diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services')
29 files changed, 700 insertions, 117 deletions
diff --git a/nixpkgs/nixos/modules/services/audio/wyoming/faster-whisper.nix b/nixpkgs/nixos/modules/services/audio/wyoming/faster-whisper.nix index 1fb67ecfe506..205e05f2ed17 100644 --- a/nixpkgs/nixos/modules/services/audio/wyoming/faster-whisper.nix +++ b/nixpkgs/nixos/modules/services/audio/wyoming/faster-whisper.nix @@ -37,6 +37,9 @@ in enable = mkEnableOption (mdDoc "Wyoming faster-whisper server"); model = mkOption { + # Intersection between available and referenced models here: + # https://github.com/rhasspy/models/releases/tag/v1.0 + # https://github.com/rhasspy/rhasspy3/blob/wyoming-v1/programs/asr/faster-whisper/server/wyoming_faster_whisper/download.py#L17-L27 type = enum [ "tiny" "tiny-int8" @@ -44,7 +47,6 @@ in "base-int8" "small" "small-int8" - "medium" "medium-int8" ]; default = "tiny-int8"; diff --git a/nixpkgs/nixos/modules/services/backup/borgmatic.nix b/nixpkgs/nixos/modules/services/backup/borgmatic.nix index d3ba7628e85d..b27dd2817120 100644 --- a/nixpkgs/nixos/modules/services/backup/borgmatic.nix +++ b/nixpkgs/nixos/modules/services/backup/borgmatic.nix @@ -81,7 +81,7 @@ in config = mkIf cfg.enable { warnings = [] - ++ optional (cfg.settings != null && cfg.settings.location != null) + ++ optional (cfg.settings != null && cfg.settings ? location) "`services.borgmatic.settings.location` is deprecated, please move your options out of sections to the global scope" ++ optional (catAttrs "location" (attrValues cfg.configurations) != []) "`services.borgmatic.configurations.<name>.location` is deprecated, please move your options out of sections to the global scope" diff --git a/nixpkgs/nixos/modules/services/backup/restic.nix b/nixpkgs/nixos/modules/services/backup/restic.nix index 78220e99c3d1..49a55d056014 100644 --- a/nixpkgs/nixos/modules/services/backup/restic.nix +++ b/nixpkgs/nixos/modules/services/backup/restic.nix @@ -23,25 +23,13 @@ in environmentFile = mkOption { type = with types; nullOr str; - # added on 2021-08-28, s3CredentialsFile should - # be removed in the future (+ remember the warning) - default = config.s3CredentialsFile; + default = null; description = lib.mdDoc '' file containing the credentials to access the repository, in the format of an EnvironmentFile as described by systemd.exec(5) ''; }; - s3CredentialsFile = mkOption { - type = with types; nullOr str; - default = null; - description = lib.mdDoc '' - file containing the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY - for an S3-hosted repository, in the format of an EnvironmentFile - as described by systemd.exec(5) - ''; - }; - rcloneOptions = mkOption { type = with types; nullOr (attrsOf (oneOf [ str bool ])); default = null; @@ -113,12 +101,15 @@ in }; paths = mkOption { + # This is nullable for legacy reasons only. We should consider making it a pure listOf + # after some time has passed since this comment was added. type = types.nullOr (types.listOf types.str); - default = null; + default = [ ]; description = lib.mdDoc '' - Which paths to backup. If null or an empty array, no - backup command will be run. This can be used to create a - prune-only job. + Which paths to backup, in addition to ones specified via + `dynamicFilesFrom`. If null or an empty array and + `dynamicFilesFrom` is also null, no backup command will be run. + This can be used to create a prune-only job. ''; example = [ "/var/lib/postgresql" @@ -231,7 +222,7 @@ in description = lib.mdDoc '' A script that produces a list of files to back up. The results of this command are given to the '--files-from' - option. + option. The result is merged with paths specified via `paths`. ''; example = "find /home/matt/git -type d -name .git"; }; @@ -297,7 +288,6 @@ in }; config = { - warnings = mapAttrsToList (n: v: "services.restic.backups.${n}.s3CredentialsFile is deprecated, please use services.restic.backups.${n}.environmentFile instead.") (filterAttrs (n: v: v.s3CredentialsFile != null) config.services.restic.backups); assertions = mapAttrsToList (n: v: { assertion = (v.repository == null) != (v.repositoryFile == null); message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set"; @@ -310,10 +300,7 @@ in resticCmd = "${backup.package}/bin/restic${extraOptions}"; excludeFlags = optional (backup.exclude != []) "--exclude-file=${pkgs.writeText "exclude-patterns" (concatStringsSep "\n" backup.exclude)}"; filesFromTmpFile = "/run/restic-backups-${name}/includes"; - backupPaths = - if (backup.dynamicFilesFrom == null) - then optionalString (backup.paths != null) (concatStringsSep " " backup.paths) - else "--files-from ${filesFromTmpFile}"; + doBackup = (backup.dynamicFilesFrom != null) || (backup.paths != null && backup.paths != []); pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [ (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts)) (resticCmd + " check " + (concatStringsSep " " backup.checkOpts)) @@ -348,7 +335,7 @@ in after = [ "network-online.target" ]; serviceConfig = { Type = "oneshot"; - ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ]) + ExecStart = (optionals doBackup [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} --files-from=${filesFromTmpFile}" ]) ++ pruneCmd; User = backup.user; RuntimeDirectory = "restic-backups-${name}"; @@ -366,8 +353,11 @@ in ${optionalString (backup.initialize) '' ${resticCmd} snapshots || ${resticCmd} init ''} + ${optionalString (backup.paths != null && backup.paths != []) '' + cat ${pkgs.writeText "staticPaths" (concatStringsSep "\n" backup.paths)} >> ${filesFromTmpFile} + ''} ${optionalString (backup.dynamicFilesFrom != null) '' - ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile} + ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile} ''} ''; } // optionalAttrs (backup.dynamicFilesFrom != null || backup.backupCleanupCommand != null) { diff --git a/nixpkgs/nixos/modules/services/blockchain/ethereum/erigon.nix b/nixpkgs/nixos/modules/services/blockchain/ethereum/erigon.nix index 8ebe0fcaff54..945a373d1274 100644 --- a/nixpkgs/nixos/modules/services/blockchain/ethereum/erigon.nix +++ b/nixpkgs/nixos/modules/services/blockchain/ethereum/erigon.nix @@ -13,6 +13,8 @@ in { services.erigon = { enable = mkEnableOption (lib.mdDoc "Ethereum implementation on the efficiency frontier"); + package = mkPackageOptionMD pkgs "erigon" { }; + extraArgs = mkOption { type = types.listOf types.str; description = lib.mdDoc "Additional arguments passed to Erigon"; @@ -92,7 +94,7 @@ in { serviceConfig = { LoadCredential = "ERIGON_JWT:${cfg.secretJwtPath}"; - ExecStart = "${pkgs.erigon}/bin/erigon --config ${configFile} --authrpc.jwtsecret=%d/ERIGON_JWT ${lib.escapeShellArgs cfg.extraArgs}"; + ExecStart = "${cfg.package}/bin/erigon --config ${configFile} --authrpc.jwtsecret=%d/ERIGON_JWT ${lib.escapeShellArgs cfg.extraArgs}"; DynamicUser = true; Restart = "on-failure"; StateDirectory = "erigon"; diff --git a/nixpkgs/nixos/modules/services/logging/syslog-ng.nix b/nixpkgs/nixos/modules/services/logging/syslog-ng.nix index d22acbeaa70c..48d556b9459e 100644 --- a/nixpkgs/nixos/modules/services/logging/syslog-ng.nix +++ b/nixpkgs/nixos/modules/services/logging/syslog-ng.nix @@ -67,7 +67,7 @@ in { configHeader = mkOption { type = types.lines; default = '' - @version: 3.6 + @version: 4.4 @include "scl.conf" ''; description = lib.mdDoc '' diff --git a/nixpkgs/nixos/modules/services/mail/mlmmj.nix b/nixpkgs/nixos/modules/services/mail/mlmmj.nix index 642f8b20fe35..3f07fabcf177 100644 --- a/nixpkgs/nixos/modules/services/mail/mlmmj.nix +++ b/nixpkgs/nixos/modules/services/mail/mlmmj.nix @@ -143,13 +143,11 @@ in environment.systemPackages = [ pkgs.mlmmj ]; - system.activationScripts.mlmmj = '' - ${pkgs.coreutils}/bin/mkdir -p ${stateDir} ${spoolDir}/${cfg.listDomain} - ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${spoolDir} - ${concatMapLines (createList cfg.listDomain) cfg.mailLists} - ${pkgs.postfix}/bin/postmap /etc/postfix/virtual - ${pkgs.postfix}/bin/postmap /etc/postfix/transport - ''; + systemd.tmpfiles.rules = [ + ''d "${stateDir}" -'' + ''d "${spoolDir}/${cfg.listDomain}" -'' + ''Z "${spoolDir}" - "${cfg.user}" "${cfg.group}" -'' + ]; systemd.services.mlmmj-maintd = { description = "mlmmj maintenance daemon"; @@ -158,6 +156,11 @@ in Group = cfg.group; ExecStart = "${pkgs.mlmmj}/bin/mlmmj-maintd -F -d ${spoolDir}/${cfg.listDomain}"; }; + preStart = '' + ${concatMapLines (createList cfg.listDomain) cfg.mailLists} + ${pkgs.postfix}/bin/postmap /etc/postfix/virtual + ${pkgs.postfix}/bin/postmap /etc/postfix/transport + ''; }; systemd.timers.mlmmj-maintd = { diff --git a/nixpkgs/nixos/modules/services/monitoring/certspotter.md b/nixpkgs/nixos/modules/services/monitoring/certspotter.md new file mode 100644 index 000000000000..9bf6e1d946a0 --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/certspotter.md @@ -0,0 +1,74 @@ +# Cert Spotter {#module-services-certspotter} + +Cert Spotter is a tool for monitoring [Certificate Transparency](https://en.wikipedia.org/wiki/Certificate_Transparency) +logs. + +## Service Configuration {#modules-services-certspotter-service-configuration} + +A basic config that notifies you of all certificate changes for your +domain would look as follows: + +```nix +services.certspotter = { + enable = true; + # replace example.org with your domain name + watchlist = [ ".example.org" ]; + emailRecipients = [ "webmaster@example.org" ]; +}; + +# Configure an SMTP client +programs.msmtp.enable = true; +# Or you can use any other module that provides sendmail, like +# services.nullmailer, services.opensmtpd, services.postfix +``` + +In this case, the leading dot in `".example.org"` means that Cert +Spotter should monitor not only `example.org`, but also all of its +subdomains. + +## Operation {#modules-services-certspotter-operation} + +**By default, NixOS configures Cert Spotter to skip all certificates +issued before its first launch**, because checking the entire +Certificate Transparency logs requires downloading tens of terabytes of +data. If you want to check the *entire* logs for previously issued +certificates, you have to set `services.certspotter.startAtEnd` to +`false` and remove all previously saved log state in +`/var/lib/certspotter/logs`. The downloaded logs aren't saved, so if you +add a new domain to the watchlist and want Cert Spotter to go through +the logs again, you will have to remove `/var/lib/certspotter/logs` +again. + +After catching up with the logs, Cert Spotter will start monitoring live +logs. As of October 2023, it uses around **20 Mbps** of traffic on +average. + +## Hooks {#modules-services-certspotter-hooks} + +Cert Spotter supports running custom hooks instead of (or in addition +to) sending emails. Hooks are shell scripts that will be passed certain +environment variables. + +To see hook documentation, see Cert Spotter's man pages: + +```ShellSession +nix-shell -p certspotter --run 'man 8 certspotter-script' +``` + +For example, you can remove `emailRecipients` and send email +notifications manually using the following hook: + +```nix +services.certspotter.hooks = [ + (pkgs.writeShellScript "certspotter-hook" '' + function print_email() { + echo "Subject: [certspotter] $SUMMARY" + echo "Mime-Version: 1.0" + echo "Content-Type: text/plain; charset=US-ASCII" + echo + cat "$TEXT_FILENAME" + } + print_email | ${config.services.certspotter.sendmailPath} -i webmaster@example.org + '') +]; +``` diff --git a/nixpkgs/nixos/modules/services/monitoring/certspotter.nix b/nixpkgs/nixos/modules/services/monitoring/certspotter.nix new file mode 100644 index 000000000000..aafa29daa872 --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/certspotter.nix @@ -0,0 +1,143 @@ +{ config +, lib +, pkgs +, ... }: + +let + cfg = config.services.certspotter; + + configDir = pkgs.linkFarm "certspotter-config" ( + lib.toList { + name = "watchlist"; + path = pkgs.writeText "certspotter-watchlist" (builtins.concatStringsSep "\n" cfg.watchlist); + } + ++ lib.optional (cfg.emailRecipients != [ ]) { + name = "email_recipients"; + path = pkgs.writeText "certspotter-email_recipients" (builtins.concatStringsSep "\n" cfg.emailRecipients); + } + # always generate hooks dir when no emails are provided to allow running cert spotter with no hooks/emails + ++ lib.optional (cfg.emailRecipients == [ ] || cfg.hooks != [ ]) { + name = "hooks.d"; + path = pkgs.linkFarm "certspotter-hooks" (lib.imap1 (i: path: { + inherit path; + name = "hook${toString i}"; + }) cfg.hooks); + }); +in +{ + options.services.certspotter = { + enable = lib.mkEnableOption "Cert Spotter, a Certificate Transparency log monitor"; + + package = lib.mkPackageOptionMD pkgs "certspotter" { }; + + startAtEnd = lib.mkOption { + type = lib.types.bool; + description = '' + Whether to skip certificates issued before the first launch of Cert Spotter. + Setting this to `false` will cause Cert Spotter to download tens of terabytes of data. + ''; + default = true; + }; + + sendmailPath = lib.mkOption { + type = with lib.types; nullOr path; + description = '' + Path to the `sendmail` binary. By default, the local sendmail wrapper is used + (see {option}`services.mail.sendmailSetuidWrapper`}). + ''; + example = lib.literalExpression ''"''${pkgs.system-sendmail}/bin/sendmail"''; + }; + + watchlist = lib.mkOption { + type = with lib.types; listOf str; + description = "Domain names to watch. To monitor a domain with all subdomains, prefix its name with `.` (e.g. `.example.org`)."; + default = [ ]; + example = [ ".example.org" "another.example.com" ]; + }; + + emailRecipients = lib.mkOption { + type = with lib.types; listOf str; + description = "A list of email addresses to send certificate updates to."; + default = [ ]; + }; + + hooks = lib.mkOption { + type = with lib.types; listOf path; + description = '' + Scripts to run upon the detection of a new certificate. See `man 8 certspotter-script` or + [the GitHub page](https://github.com/SSLMate/certspotter/blob/${pkgs.certspotter.src.rev or "master"}/man/certspotter-script.md) + for more info. + ''; + default = [ ]; + example = lib.literalExpression '' + [ + (pkgs.writeShellScript "certspotter-hook" ''' + echo "Event summary: $SUMMARY." + ''') + ] + ''; + }; + + extraFlags = lib.mkOption { + type = with lib.types; listOf str; + description = "Extra command-line arguments to pass to Cert Spotter"; + example = [ "-no_save" ]; + default = [ ]; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = (cfg.emailRecipients != [ ]) -> (cfg.sendmailPath != null); + message = '' + You must configure the sendmail setuid wrapper (services.mail.sendmailSetuidWrapper) + or services.certspotter.sendmailPath + ''; + } + ]; + + services.certspotter.sendmailPath = let + inherit (config.security) wrapperDir; + inherit (config.services.mail) sendmailSetuidWrapper; + in lib.mkMerge [ + (lib.mkIf (sendmailSetuidWrapper != null) (lib.mkOptionDefault "${wrapperDir}/${sendmailSetuidWrapper.program}")) + (lib.mkIf (sendmailSetuidWrapper == null) (lib.mkOptionDefault null)) + ]; + + users.users.certspotter = { + description = "Cert Spotter user"; + group = "certspotter"; + home = "/var/lib/certspotter"; + isSystemUser = true; + }; + users.groups.certspotter = { }; + + systemd.services.certspotter = { + description = "Cert Spotter - Certificate Transparency Monitor"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment.CERTSPOTTER_CONFIG_DIR = configDir; + environment.SENDMAIL_PATH = if cfg.sendmailPath != null then cfg.sendmailPath else "/run/current-system/sw/bin/false"; + script = '' + export CERTSPOTTER_STATE_DIR="$STATE_DIRECTORY" + cd "$CERTSPOTTER_STATE_DIR" + ${lib.optionalString cfg.startAtEnd '' + if [[ ! -d logs ]]; then + # Don't download certificates issued before the first launch + exec ${cfg.package}/bin/certspotter -start_at_end ${lib.escapeShellArgs cfg.extraFlags} + fi + ''} + exec ${cfg.package}/bin/certspotter ${lib.escapeShellArgs cfg.extraFlags} + ''; + serviceConfig = { + User = "certspotter"; + Group = "certspotter"; + StateDirectory = "certspotter"; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ chayleaf ]; + meta.doc = ./certspotter.md; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/goss.md b/nixpkgs/nixos/modules/services/monitoring/goss.md new file mode 100644 index 000000000000..1e636aa3bdf3 --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/goss.md @@ -0,0 +1,44 @@ +# Goss {#module-services-goss} + +[goss](https://goss.rocks/) is a YAML based serverspec alternative tool +for validating a server's configuration. + +## Basic Usage {#module-services-goss-basic-usage} + +A minimal configuration looks like this: + +``` +{ + services.goss = { + enable = true; + + environment = { + GOSS_FMT = "json"; + GOSS_LOGLEVEL = "TRACE"; + }; + + settings = { + addr."tcp://localhost:8080" = { + reachable = true; + local-address = "127.0.0.1"; + }; + command."check-goss-version" = { + exec = "${lib.getExe pkgs.goss} --version"; + exit-status = 0; + }; + dns.localhost.resolvable = true; + file."/nix" = { + filetype = "directory"; + exists = true; + }; + group.root.exists = true; + kernel-param."kernel.ostype".value = "Linux"; + service.goss = { + enabled = true; + running = true; + }; + user.root.exists = true; + }; + }; +} +``` diff --git a/nixpkgs/nixos/modules/services/monitoring/goss.nix b/nixpkgs/nixos/modules/services/monitoring/goss.nix new file mode 100644 index 000000000000..64a8dad0703e --- /dev/null +++ b/nixpkgs/nixos/modules/services/monitoring/goss.nix @@ -0,0 +1,86 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.goss; + + settingsFormat = pkgs.formats.yaml { }; + configFile = settingsFormat.generate "goss.yaml" cfg.settings; + +in { + meta = { + doc = ./goss.md; + maintainers = [ lib.maintainers.anthonyroussel ]; + }; + + options = { + services.goss = { + enable = lib.mkEnableOption (lib.mdDoc "Goss daemon"); + + package = lib.mkPackageOptionMD pkgs "goss" { }; + + environment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + example = { + GOSS_FMT = "json"; + GOSS_LOGLEVEL = "FATAL"; + GOSS_LISTEN = ":8080"; + }; + description = lib.mdDoc '' + Environment variables to set for the goss service. + + See <https://github.com/goss-org/goss/blob/master/docs/manual.md> + ''; + }; + + settings = lib.mkOption { + type = lib.types.submodule { freeformType = settingsFormat.type; }; + default = { }; + example = { + addr."tcp://localhost:8080" = { + reachable = true; + local-address = "127.0.0.1"; + }; + service.goss = { + enabled = true; + running = true; + }; + }; + description = lib.mdDoc '' + The global options in `config` file in yaml format. + + Refer to <https://github.com/goss-org/goss/blob/master/docs/goss-json-schema.yaml> for schema. + ''; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + systemd.services.goss = { + description = "Goss - Quick and Easy server validation"; + unitConfig.Documentation = "https://github.com/goss-org/goss/blob/master/docs/manual.md"; + + after = [ "network-online.target" ]; + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + + environment = { + GOSS_FILE = configFile; + } // cfg.environment; + + reloadTriggers = [ configFile ]; + + serviceConfig = { + DynamicUser = true; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + ExecStart = "${cfg.package}/bin/goss serve"; + Group = "goss"; + Restart = "on-failure"; + RestartSec = 5; + User = "goss"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/monitoring/ups.nix b/nixpkgs/nixos/modules/services/monitoring/ups.nix index bb11b6a1c1d0..efef2d777acd 100644 --- a/nixpkgs/nixos/modules/services/monitoring/ups.nix +++ b/nixpkgs/nixos/modules/services/monitoring/ups.nix @@ -239,11 +239,9 @@ in power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample"; - system.activationScripts.upsSetup = stringAfter [ "users" "groups" ] - '' - # Used to store pid files of drivers. - mkdir -p /var/state/ups - ''; + systemd.tmpfiles.rules = [ + "d /var/state/ups -" + ]; /* diff --git a/nixpkgs/nixos/modules/services/networking/gvpe.nix b/nixpkgs/nixos/modules/services/networking/gvpe.nix index 2279ceee2f58..558f499022c8 100644 --- a/nixpkgs/nixos/modules/services/networking/gvpe.nix +++ b/nixpkgs/nixos/modules/services/networking/gvpe.nix @@ -29,7 +29,7 @@ let export PATH=$PATH:${pkgs.iproute2}/sbin - ip link set $IFNAME up + ip link set dev $IFNAME up ip address add ${cfg.ipAddress} dev $IFNAME ip route add ${cfg.subnet} dev $IFNAME diff --git a/nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix b/nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix index 9c71a988f29c..6c30f89b7968 100644 --- a/nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix +++ b/nixpkgs/nixos/modules/services/networking/iscsi/initiator.nix @@ -52,25 +52,27 @@ in ''; environment.etc."iscsi/initiatorname.iscsi".text = "InitiatorName=${cfg.name}"; - system.activationScripts.iscsid = let - extraCfgDumper = optionalString (cfg.extraConfigFile != null) '' - if [ -f "${cfg.extraConfigFile}" ]; then - printf "\n# The following is from ${cfg.extraConfigFile}:\n" - cat "${cfg.extraConfigFile}" - else - echo "Warning: services.openiscsi.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2 - fi - ''; - in '' - ( - cat ${config.environment.etc."iscsi/iscsid.conf.fragment".source} - ${extraCfgDumper} - ) > /etc/iscsi/iscsid.conf - ''; - systemd.packages = [ cfg.package ]; - systemd.services."iscsid".wantedBy = [ "multi-user.target" ]; + systemd.services."iscsid" = { + wantedBy = [ "multi-user.target" ]; + preStart = + let + extraCfgDumper = optionalString (cfg.extraConfigFile != null) '' + if [ -f "${cfg.extraConfigFile}" ]; then + printf "\n# The following is from ${cfg.extraConfigFile}:\n" + cat "${cfg.extraConfigFile}" + else + echo "Warning: services.openiscsi.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2 + fi + ''; + in '' + ( + cat ${config.environment.etc."iscsi/iscsid.conf.fragment".source} + ${extraCfgDumper} + ) > /etc/iscsi/iscsid.conf + ''; + }; systemd.sockets."iscsid".wantedBy = [ "sockets.target" ]; systemd.services."iscsi" = mkIf cfg.enableAutoLoginOut { diff --git a/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix b/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix index 82e68bf92af1..99ffbf56ccb0 100644 --- a/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix +++ b/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix @@ -76,5 +76,5 @@ with lib; }; }; - meta.maintainers = with maintainers; [ patricksjackson ymarkus ]; + meta.maintainers = with maintainers; [ arcuru ymarkus ]; } diff --git a/nixpkgs/nixos/modules/services/networking/spiped.nix b/nixpkgs/nixos/modules/services/networking/spiped.nix index 3e01ace54ad1..547317dbcbe2 100644 --- a/nixpkgs/nixos/modules/services/networking/spiped.nix +++ b/nixpkgs/nixos/modules/services/networking/spiped.nix @@ -197,8 +197,9 @@ in script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`"; }; - system.activationScripts.spiped = optionalString (cfg.config != {}) - "mkdir -p /var/lib/spiped"; + systemd.tmpfiles.rules = lib.mkIf (cfg.config != { }) [ + "d /var/lib/spiped -" + ]; # Setup spiped config files environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec" diff --git a/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix index 1946e9e79e98..14bc59089adf 100644 --- a/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix +++ b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix @@ -12,22 +12,44 @@ let then cfgc.package else pkgs.buildPackages.openssh; - # reports boolean as yes / no - mkValueStringSshd = with lib; v: - if isInt v then toString v - else if isString v then v - else if true == v then "yes" - else if false == v then "no" - else if isList v then concatStringsSep "," v - else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}"; - # dont use the "=" operator - settingsFormat = (pkgs.formats.keyValue { - mkKeyValue = lib.generators.mkKeyValueDefault { - mkValueString = mkValueStringSshd; - } " ";}); + settingsFormat = + let + # reports boolean as yes / no + mkValueString = with lib; v: + if isInt v then toString v + else if isString v then v + else if true == v then "yes" + else if false == v then "no" + else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}"; + + base = pkgs.formats.keyValue { + mkKeyValue = lib.generators.mkKeyValueDefault { inherit mkValueString; } " "; + }; + # OpenSSH is very inconsistent with options that can take multiple values. + # For some of them, they can simply appear multiple times and are appended, for others the + # values must be separated by whitespace or even commas. + # Consult either sshd_config(5) or, as last resort, the OpehSSH source for parsing + # the options at servconf.c:process_server_config_line_depth() to determine the right "mode" + # for each. But fortunaly this fact is documented for most of them in the manpage. + commaSeparated = [ "Ciphers" "KexAlgorithms" "Macs" ]; + spaceSeparated = [ "AuthorizedKeysFile" "AllowGroups" "AllowUsers" "DenyGroups" "DenyUsers" ]; + in { + inherit (base) type; + generate = name: value: + let transformedValue = mapAttrs (key: val: + if isList val then + if elem key commaSeparated then concatStringsSep "," val + else if elem key spaceSeparated then concatStringsSep " " val + else throw "list value for unknown key ${key}: ${(lib.generators.toPretty {}) val}" + else + val + ) value; + in + base.generate name transformedValue; + }; - configFile = settingsFormat.generate "sshd.conf-settings" cfg.settings; + configFile = settingsFormat.generate "sshd.conf-settings" (filterAttrs (n: v: v != null) cfg.settings); sshconf = pkgs.runCommand "sshd.conf-final" { } '' cat ${configFile} - >$out <<EOL ${cfg.extraConfig} @@ -431,6 +453,42 @@ in <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67> ''; }; + AllowUsers = mkOption { + type = with types; nullOr (listOf str); + default = null; + description = lib.mdDoc '' + If specified, login is allowed only for the listed users. + See {manpage}`sshd_config(5)` for details. + ''; + }; + DenyUsers = mkOption { + type = with types; nullOr (listOf str); + default = null; + description = lib.mdDoc '' + If specified, login is denied for all listed users. Takes + precedence over [](#opt-services.openssh.settings.AllowUsers). + See {manpage}`sshd_config(5)` for details. + ''; + }; + AllowGroups = mkOption { + type = with types; nullOr (listOf str); + default = null; + description = lib.mdDoc '' + If specified, login is allowed only for users part of the + listed groups. + See {manpage}`sshd_config(5)` for details. + ''; + }; + DenyGroups = mkOption { + type = with types; nullOr (listOf str); + default = null; + description = lib.mdDoc '' + If specified, login is denied for all users part of the listed + groups. Takes precedence over + [](#opt-services.openssh.settings.AllowGroups). See + {manpage}`sshd_config(5)` for details. + ''; + }; }; }); }; diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix index c51e8ad9f5fc..bfea89969728 100644 --- a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix +++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix @@ -43,21 +43,21 @@ in { # The swanctl command complains when the following directories don't exist: # See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctldirectory - system.activationScripts.strongswan-swanctl-etc = stringAfter ["etc"] '' - mkdir -p '/etc/swanctl/x509' # Trusted X.509 end entity certificates - mkdir -p '/etc/swanctl/x509ca' # Trusted X.509 Certificate Authority certificates - mkdir -p '/etc/swanctl/x509ocsp' - mkdir -p '/etc/swanctl/x509aa' # Trusted X.509 Attribute Authority certificates - mkdir -p '/etc/swanctl/x509ac' # Attribute Certificates - mkdir -p '/etc/swanctl/x509crl' # Certificate Revocation Lists - mkdir -p '/etc/swanctl/pubkey' # Raw public keys - mkdir -p '/etc/swanctl/private' # Private keys in any format - mkdir -p '/etc/swanctl/rsa' # PKCS#1 encoded RSA private keys - mkdir -p '/etc/swanctl/ecdsa' # Plain ECDSA private keys - mkdir -p '/etc/swanctl/bliss' - mkdir -p '/etc/swanctl/pkcs8' # PKCS#8 encoded private keys of any type - mkdir -p '/etc/swanctl/pkcs12' # PKCS#12 containers - ''; + systemd.tmpfiles.rules = [ + "d /etc/swanctl/x509 -" # Trusted X.509 end entity certificates + "d /etc/swanctl/x509ca -" # Trusted X.509 Certificate Authority certificates + "d /etc/swanctl/x509ocsp -" + "d /etc/swanctl/x509aa -" # Trusted X.509 Attribute Authority certificates + "d /etc/swanctl/x509ac -" # Attribute Certificates + "d /etc/swanctl/x509crl -" # Certificate Revocation Lists + "d /etc/swanctl/pubkey -" # Raw public keys + "d /etc/swanctl/private -" # Private keys in any format + "d /etc/swanctl/rsa -" # PKCS#1 encoded RSA private keys + "d /etc/swanctl/ecdsa -" # Plain ECDSA private keys + "d /etc/swanctl/bliss -" + "d /etc/swanctl/pkcs8 -" # PKCS#8 encoded private keys of any type + "d /etc/swanctl/pkcs12 -" # PKCS#12 containers + ]; systemd.services.strongswan-swanctl = { description = "strongSwan IPsec IKEv1/IKEv2 daemon using swanctl"; diff --git a/nixpkgs/nixos/modules/services/networking/tailscale.nix b/nixpkgs/nixos/modules/services/networking/tailscale.nix index 8b35cc8d6669..a5d171e0baab 100644 --- a/nixpkgs/nixos/modules/services/networking/tailscale.nix +++ b/nixpkgs/nixos/modules/services/networking/tailscale.nix @@ -31,6 +31,12 @@ in { package = lib.mkPackageOptionMD pkgs "tailscale" {}; + openFirewall = mkOption { + default = false; + type = types.bool; + description = lib.mdDoc "Whether to open the firewall for the specified port."; + }; + useRoutingFeatures = mkOption { type = types.enum [ "none" "client" "server" "both" ]; default = "none"; @@ -113,6 +119,8 @@ in { "net.ipv6.conf.all.forwarding" = mkOverride 97 true; }; + networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ cfg.port ]; + networking.firewall.checkReversePath = mkIf (cfg.useRoutingFeatures == "client" || cfg.useRoutingFeatures == "both") "loose"; networking.dhcpcd.denyInterfaces = [ cfg.interfaceName ]; diff --git a/nixpkgs/nixos/modules/services/networking/unifi.nix b/nixpkgs/nixos/modules/services/networking/unifi.nix index 37a739f41d48..6b6837109806 100644 --- a/nixpkgs/nixos/modules/services/networking/unifi.nix +++ b/nixpkgs/nixos/modules/services/networking/unifi.nix @@ -6,9 +6,9 @@ let cmd = '' @${cfg.jrePackage}/bin/java java \ ${optionalString (lib.versionAtLeast (lib.getVersion cfg.jrePackage) "16") - "--add-opens java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED " + ("--add-opens java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.time=ALL-UNNAMED " + "--add-opens java.base/sun.security.util=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED " - + "--add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED"} \ + + "--add-opens java.rmi/sun.rmi.transport=ALL-UNNAMED")} \ ${optionalString (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m"} \ ${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \ -jar ${stateDir}/lib/ace.jar diff --git a/nixpkgs/nixos/modules/services/system/nix-daemon.nix b/nixpkgs/nixos/modules/services/system/nix-daemon.nix index c9df20196dbd..ce255cd8d0a4 100644 --- a/nixpkgs/nixos/modules/services/system/nix-daemon.nix +++ b/nixpkgs/nixos/modules/services/system/nix-daemon.nix @@ -249,11 +249,6 @@ in services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers; - system.activationScripts.nix = stringAfter [ "etc" "users" ] - '' - install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user - ''; - # Legacy configuration conversion. nix.settings = mkMerge [ (mkIf (isNixAtLeast "2.3pre") { sandbox-fallback = false; }) diff --git a/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.md b/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.md new file mode 100644 index 000000000000..236953bd4ff7 --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.md @@ -0,0 +1,42 @@ +# c2FmZQ {#module-services-c2fmzq} + +c2FmZQ is an application that can securely encrypt, store, and share files, +including but not limited to pictures and videos. + +The service `c2fmzq-server` can be enabled by setting +``` +{ + services.c2fmzq-server.enable = true; +} +``` +This will spin up an instance of the server which is API-compatible with +[Stingle Photos](https://stingle.org) and an experimental Progressive Web App +(PWA) to interact with the storage via the browser. + +In principle the server can be exposed directly on a public interface and there +are command line options to manage HTTPS certificates directly, but the module +is designed to be served behind a reverse proxy or only accessed via localhost. + +``` +{ + services.c2fmzq-server = { + enable = true; + bindIP = "127.0.0.1"; # default + port = 8080; # default + }; + + services.nginx = { + enable = true; + recommendedProxySettings = true; + virtualHosts."example.com" = { + enableACME = true; + forceSSL = true; + locations."/" = { + proxyPass = "http://127.0.0.1:8080"; + }; + }; + }; +} +``` + +For more information, see <https://github.com/c2FmZQ/c2FmZQ/>. diff --git a/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.nix b/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.nix new file mode 100644 index 000000000000..2749c2a5a87a --- /dev/null +++ b/nixpkgs/nixos/modules/services/web-apps/c2fmzq-server.nix @@ -0,0 +1,125 @@ +{ lib, pkgs, config, ... }: + +let + inherit (lib) mkEnableOption mkPackageOption mkOption types; + + cfg = config.services.c2fmzq-server; + + argsFormat = { + type = with lib.types; nullOr (oneOf [ bool int str ]); + generate = lib.cli.toGNUCommandLineShell { }; + }; +in { + options.services.c2fmzq-server = { + enable = mkEnableOption "c2fmzq-server"; + + bindIP = mkOption { + type = types.str; + default = "127.0.0.1"; + description = "The local address to use."; + }; + + port = mkOption { + type = types.port; + default = 8080; + description = "The local port to use."; + }; + + passphraseFile = mkOption { + type = types.str; + example = "/run/secrets/c2fmzq/pwfile"; + description = "Path to file containing the database passphrase"; + }; + + package = mkPackageOption pkgs "c2fmzq" { }; + + settings = mkOption { + type = types.submodule { + freeformType = argsFormat.type; + + options = { + address = mkOption { + internal = true; + type = types.str; + default = "${cfg.bindIP}:${toString cfg.port}"; + }; + + database = mkOption { + type = types.str; + default = "%S/c2fmzq-server/data"; + description = "Path of the database"; + }; + + verbose = mkOption { + type = types.ints.between 1 3; + default = 2; + description = "The level of logging verbosity: 1:Error 2:Info 3:Debug"; + }; + }; + }; + description = '' + Configuration for c2FmZQ-server passed as CLI arguments. + Run {command}`c2FmZQ-server help` for supported values. + ''; + example = { + verbose = 3; + allow-new-accounts = true; + auto-approve-new-accounts = true; + encrypt-metadata = true; + enable-webapp = true; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.c2fmzq-server = { + description = "c2FmZQ-server"; + documentation = [ "https://github.com/c2FmZQ/c2FmZQ/blob/main/README.md" ]; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" "network-online.target" ]; + + serviceConfig = { + ExecStart = "${lib.getExe cfg.package} ${argsFormat.generate cfg.settings}"; + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + DynamicUser = true; + Environment = "C2FMZQ_PASSPHRASE_FILE=%d/passphrase-file"; + IPAccounting = true; + IPAddressAllow = cfg.bindIP; + IPAddressDeny = "any"; + LoadCredential = "passphrase-file:${cfg.passphraseFile}"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateIPC = true; + PrivateTmp = true; + PrivateUsers = true; + 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; + SocketBindAllow = cfg.port; + SocketBindDeny = "any"; + StateDirectory = "c2fmzq-server"; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "@system-service" "~@privileged @obsolete" ]; + }; + }; + }; + + meta = { + doc = ./c2fmzq-server.md; + maintainers = with lib.maintainers; [ hmenke ]; + }; +} diff --git a/nixpkgs/nixos/modules/services/web-apps/mattermost.nix b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix index 66e5f1695a15..24f3b3331845 100644 --- a/nixpkgs/nixos/modules/services/web-apps/mattermost.nix +++ b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix @@ -287,9 +287,9 @@ in # The systemd service will fail to execute the preStart hook # if the WorkingDirectory does not exist - system.activationScripts.mattermost = '' - mkdir -p "${cfg.statePath}" - ''; + systemd.tmpfiles.rules = [ + ''d "${cfg.statePath}" -'' + ]; systemd.services.mattermost = { description = "Mattermost chat service"; diff --git a/nixpkgs/nixos/modules/services/web-apps/plausible.nix b/nixpkgs/nixos/modules/services/web-apps/plausible.nix index e5deb6cf511f..576b54a7edf2 100644 --- a/nixpkgs/nixos/modules/services/web-apps/plausible.nix +++ b/nixpkgs/nixos/modules/services/web-apps/plausible.nix @@ -78,9 +78,9 @@ in { server = { disableRegistration = mkOption { default = true; - type = types.bool; + type = types.enum [true false "invite_only"]; description = lib.mdDoc '' - Whether to prohibit creating an account in plausible's UI. + Whether to prohibit creating an account in plausible's UI or allow on `invite_only`. ''; }; secretKeybaseFile = mkOption { @@ -209,7 +209,7 @@ in { # Configuration options from # https://plausible.io/docs/self-hosting-configuration PORT = toString cfg.server.port; - DISABLE_REGISTRATION = boolToString cfg.server.disableRegistration; + DISABLE_REGISTRATION = if isBool cfg.server.disableRegistration then boolToString cfg.server.disableRegistration else cfg.server.disableRegistration; RELEASE_TMP = "/var/lib/plausible/tmp"; # Home is needed to connect to the node with iex diff --git a/nixpkgs/nixos/modules/services/web-apps/shiori.nix b/nixpkgs/nixos/modules/services/web-apps/shiori.nix index f0505e052e1c..71b5ad4d4c06 100644 --- a/nixpkgs/nixos/modules/services/web-apps/shiori.nix +++ b/nixpkgs/nixos/modules/services/web-apps/shiori.nix @@ -29,6 +29,13 @@ in { default = 8080; description = lib.mdDoc "The port of the Shiori web application"; }; + + webRoot = mkOption { + type = types.str; + default = "/"; + example = "/shiori"; + description = lib.mdDoc "The root of the Shiori web application"; + }; }; }; @@ -40,7 +47,7 @@ in { environment.SHIORI_DIR = "/var/lib/shiori"; serviceConfig = { - ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}'"; + ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}' --webroot '${webRoot}'"; DynamicUser = true; StateDirectory = "shiori"; diff --git a/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix b/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix index 9cba5cb4fa9e..4fbf2bad750b 100644 --- a/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix +++ b/nixpkgs/nixos/modules/services/web-apps/snipe-it.nix @@ -18,15 +18,19 @@ let inherit (snipe-it.passthru) phpPackage; # shell script for local administration - artisan = pkgs.writeScriptBin "snipe-it" '' + artisan = (pkgs.writeScriptBin "snipe-it" '' #! ${pkgs.runtimeShell} - cd ${snipe-it} + cd "${snipe-it}/share/php/snipe-it" sudo=exec if [[ "$USER" != ${user} ]]; then sudo='exec /run/wrappers/bin/sudo -u ${user}' fi $sudo ${phpPackage}/bin/php artisan $* - ''; + '').overrideAttrs (old: { + meta = old.meta // { + mainProgram = "snipe-it"; + }; + }); in { options.services.snipe-it = { @@ -357,7 +361,7 @@ in { services.nginx = { enable = mkDefault true; virtualHosts."${cfg.hostName}" = mkMerge [ cfg.nginx { - root = mkForce "${snipe-it}/public"; + root = mkForce "${snipe-it}/share/php/snipe-it/public"; extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"; locations = { "/" = { @@ -394,7 +398,7 @@ in { RuntimeDirectory = "snipe-it/cache"; RuntimeDirectoryMode = "0700"; }; - path = [ pkgs.replace-secret ]; + path = [ pkgs.replace-secret artisan ]; script = let isSecret = v: isAttrs v && v ? _secret && (isString v._secret || builtins.isPath v._secret); @@ -451,7 +455,7 @@ in { rm "${cfg.dataDir}"/bootstrap/cache/*.php || true # migrate db - ${phpPackage}/bin/php artisan migrate --force + ${lib.getExe artisan} migrate --force # A placeholder file for invalid barcodes invalid_barcode_location="${cfg.dataDir}/public/uploads/barcodes/invalid_barcode.gif" diff --git a/nixpkgs/nixos/modules/services/web-servers/garage.nix b/nixpkgs/nixos/modules/services/web-servers/garage.nix index 731d5315f23a..47b4c6ab416e 100644 --- a/nixpkgs/nixos/modules/services/web-servers/garage.nix +++ b/nixpkgs/nixos/modules/services/web-servers/garage.nix @@ -86,7 +86,7 @@ in serviceConfig = { ExecStart = "${cfg.package}/bin/garage server"; - StateDirectory = mkIf (hasPrefix "/var/lib/garage" cfg.settings.data_dir && hasPrefix "/var/lib/garage" cfg.settings.metadata_dir) "garage"; + StateDirectory = mkIf (hasPrefix "/var/lib/garage" cfg.settings.data_dir || hasPrefix "/var/lib/garage" cfg.settings.metadata_dir) "garage"; DynamicUser = lib.mkDefault true; ProtectHome = true; NoNewPrivileges = true; diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix index 9eebd18855c7..f2e8585a9365 100644 --- a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix @@ -35,6 +35,7 @@ let compressMimeTypes = [ "application/atom+xml" "application/geo+json" + "application/javascript" # Deprecated by IETF RFC 9239, but still widely used "application/json" "application/ld+json" "application/manifest+json" diff --git a/nixpkgs/nixos/modules/services/web-servers/stargazer.nix b/nixpkgs/nixos/modules/services/web-servers/stargazer.nix index f0c3cf8787eb..18f57363137c 100644 --- a/nixpkgs/nixos/modules/services/web-servers/stargazer.nix +++ b/nixpkgs/nixos/modules/services/web-servers/stargazer.nix @@ -204,11 +204,9 @@ in }; # Create default cert store - system.activationScripts.makeStargazerCertDir = - lib.optionalAttrs (cfg.store == /var/lib/gemini/certs) '' - mkdir -p /var/lib/gemini/certs - chown -R ${cfg.user}:${cfg.group} /var/lib/gemini/certs - ''; + systemd.tmpfiles.rules = lib.mkIf (cfg.store == /var/lib/gemini/certs) [ + ''d /var/lib/gemini/certs - "${cfg.user}" "${cfg.group}" -'' + ]; users.users = lib.optionalAttrs (cfg.user == "stargazer") { stargazer = { |