diff options
Diffstat (limited to 'nixos/modules')
51 files changed, 1085 insertions, 427 deletions
diff --git a/nixos/modules/config/mysql.nix b/nixos/modules/config/mysql.nix index 2f13c56f2ae5..95c9ba76663e 100644 --- a/nixos/modules/config/mysql.nix +++ b/nixos/modules/config/mysql.nix @@ -429,11 +429,11 @@ in ''; }; - # Activation script to append the password from the password file + # preStart script to append the password from the password file # to the configuration files. It also fixes the owner of the # libnss-mysql-root.cfg because it is changed to root after the # password is appended. - system.activationScripts.mysql-auth-passwords = '' + systemd.services.mysql.preStart = '' if [[ -r ${cfg.passwordFile} ]]; then org_umask=$(umask) umask 0077 diff --git a/nixos/modules/config/nix-channel.nix b/nixos/modules/config/nix-channel.nix index 3f8e088ede92..4abc846b0858 100644 --- a/nixos/modules/config/nix-channel.nix +++ b/nixos/modules/config/nix-channel.nix @@ -97,12 +97,9 @@ in nix.settings.nix-path = mkIf (! cfg.channel.enable) (mkDefault ""); - system.activationScripts.nix-channel = mkIf cfg.channel.enable - (stringAfter [ "etc" "users" ] '' - # Subscribe the root user to the NixOS channel by default. - if [ ! -e "/root/.nix-channels" ]; then - echo "${config.system.defaultChannel} nixos" > "/root/.nix-channels" - fi - ''); + systemd.tmpfiles.rules = lib.mkIf cfg.channel.enable [ + "f /root/.nix-channels -" + ''w "/root/.nix-channels" - - - - "${config.system.defaultChannel} nixos\n"'' + ]; }; } diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index 97268a8d83ef..b4251214876e 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -606,6 +606,14 @@ in { defaultText = literalExpression "config.users.users.\${name}.group"; default = cfg.users.${name}.group; }; + options.shell = mkOption { + type = types.passwdEntry types.path; + description = '' + The path to the user's shell in initrd. + ''; + default = "${pkgs.shadow}/bin/nologin"; + defaultText = literalExpression "\${pkgs.shadow}/bin/nologin"; + }; })); }; @@ -750,17 +758,20 @@ in { boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable { contents = { "/etc/passwd".text = '' - ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group }: let + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group, shell }: let g = config.boot.initrd.systemd.groups.${group}; - in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:") config.boot.initrd.systemd.users)} + in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:${shell}") config.boot.initrd.systemd.users)} ''; "/etc/group".text = '' ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)} ''; + "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n"; }; + storePaths = [ "${pkgs.shadow}/bin/nologin" ]; + users = { - root = {}; + root = { shell = lib.mkDefault "/bin/bash"; }; nobody = {}; }; diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix index d385e4a6b1c8..15e10128ac9a 100644 --- a/nixos/modules/installer/tools/tools.nix +++ b/nixos/modules/installer/tools/tools.nix @@ -224,12 +224,22 @@ in # accidentally delete configuration.nix. # system.copySystemConfiguration = true; - # This value determines the NixOS release from which the default - # settings for stateful data, like file locations and database versions - # on your system were taken. It's perfectly fine and recommended to leave - # this value at the release version of the first install of this system. - # Before changing this value read the documentation for this option - # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). + # This option defines the first version of NixOS you have installed on this particular machine, + # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. + # + # Most users should NEVER change this value after the initial install, for any reason, + # even if you've upgraded your system to a new NixOS release. + # + # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, + # so changing it will NOT upgrade your system. + # + # This value being lower than the current NixOS release does NOT mean your system is + # out of date, out of support, or vulnerable. + # + # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, + # and migrated your data accordingly. + # + # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment? } diff --git a/nixos/modules/installer/virtualbox-demo.nix b/nixos/modules/installer/virtualbox-demo.nix index 27a7651382b2..01931b2acfca 100644 --- a/nixos/modules/installer/virtualbox-demo.nix +++ b/nixos/modules/installer/virtualbox-demo.nix @@ -21,7 +21,7 @@ with lib; services.xserver.videoDrivers = mkOverride 40 [ "virtualbox" "vmware" "cirrus" "vesa" "modesetting" ]; powerManagement.enable = false; - system.stateVersion = mkDefault "18.03"; + system.stateVersion = lib.mkDefault lib.trivial.release; installer.cloneConfigExtra = '' # Let demo build as a trusted user. diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix index 0a66eafe933e..45dbf45b3ae7 100644 --- a/nixos/modules/misc/version.nix +++ b/nixos/modules/misc/version.nix @@ -121,22 +121,32 @@ in default = cfg.release; defaultText = literalExpression "config.${opt.release}"; description = lib.mdDoc '' - Every once in a while, a new NixOS release may change - configuration defaults in a way incompatible with stateful - data. For instance, if the default version of PostgreSQL - changes, the new version will probably be unable to read your - existing databases. To prevent such breakage, you should set the - value of this option to the NixOS release with which you want - to be compatible. The effect is that NixOS will use - defaults corresponding to the specified release (such as using - an older version of PostgreSQL). - It’s perfectly fine and recommended to leave this value at the - release version of the first install of this system. - Changing this option will not upgrade your system. In fact it - is meant to stay constant exactly when you upgrade your system. - You should only bump this option, if you are sure that you can - or have migrated all state on your system which is affected - by this option. + This option defines the first version of NixOS you have installed on this particular machine, + and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. + + For example, if NixOS version XX.YY ships with AwesomeDB version N by default, and is then + upgraded to version XX.YY+1, which ships AwesomeDB version N+1, the existing databases + may no longer be compatible, causing applications to fail, or even leading to data loss. + + The `stateVersion` mechanism avoids this situation by making the default version of such packages + conditional on the first version of NixOS you've installed (encoded in `stateVersion`), instead of + simply always using the latest one. + + Note that this generally only affects applications that can't upgrade their data automatically - + applications and services supporting automatic migrations will remain on latest versions when + you upgrade. + + Most users should **never** change this value after the initial install, for any reason, + even if you've upgraded your system to a new NixOS release. + + This value does **not** affect the Nixpkgs version your packages and OS are pulled from, + so changing it will **not** upgrade your system. + + This value being lower than the current NixOS release does **not** mean your system is + out of date, out of support, or vulnerable. + + Do **not** change this value unless you have manually inspected all the changes it would + make to your configuration, and migrated your data accordingly. ''; }; diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 4d8fa8159a89..bfac651f5a81 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -767,12 +767,14 @@ ./services/monitoring/below.nix ./services/monitoring/bosun.nix ./services/monitoring/cadvisor.nix + ./services/monitoring/certspotter.nix ./services/monitoring/cockpit.nix ./services/monitoring/collectd.nix ./services/monitoring/das_watchdog.nix ./services/monitoring/datadog-agent.nix ./services/monitoring/do-agent.nix ./services/monitoring/fusion-inventory.nix + ./services/monitoring/goss.nix ./services/monitoring/grafana-agent.nix ./services/monitoring/grafana-image-renderer.nix ./services/monitoring/grafana-reporter.nix @@ -1406,6 +1408,7 @@ ./system/activation/activatable-system.nix ./system/activation/activation-script.nix ./system/activation/specialisation.nix + ./system/activation/switchable-system.nix ./system/activation/bootspec.nix ./system/activation/top-level.nix ./system/boot/binfmt.nix diff --git a/nixos/modules/profiles/image-based-appliance.nix b/nixos/modules/profiles/image-based-appliance.nix new file mode 100644 index 000000000000..7e8b6f696d54 --- /dev/null +++ b/nixos/modules/profiles/image-based-appliance.nix @@ -0,0 +1,26 @@ +# This profile sets up a sytem for image based appliance usage. An appliance is +# installed as an image, cannot be re-built, has no Nix available, and is +# generally not meant for interactive use. Updates to such an appliance are +# handled by updating whole partition images via a tool like systemd-sysupdate. + +{ lib, modulesPath, ... }: + +{ + + # Appliances are always "minimal". + imports = [ + "${modulesPath}/profiles/minimal.nix" + ]; + + # The system cannot be rebuilt. + nix.enable = false; + system.switch.enable = false; + + # The system is static. + users.mutableUsers = false; + + # The system avoids interpreters as much as possible to reduce its attack + # surface. + boot.initrd.systemd.enable = lib.mkDefault true; + networking.useNetworkd = lib.mkDefault true; +} diff --git a/nixos/modules/profiles/minimal.nix b/nixos/modules/profiles/minimal.nix index bd1b2b452189..75f355b4a002 100644 --- a/nixos/modules/profiles/minimal.nix +++ b/nixos/modules/profiles/minimal.nix @@ -18,6 +18,15 @@ with lib; documentation.nixos.enable = mkDefault false; + # Perl is a default package. + environment.defaultPackages = mkDefault [ ]; + + # The lessopen package pulls in Perl. + programs.less.lessopen = mkDefault null; + + # This pulls in nixos-containers which depends on Perl. + boot.enableContainers = mkDefault false; + programs.command-not-found.enable = mkDefault false; services.logrotate.enable = mkDefault false; diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix index 02b11766b3c0..2a855a77e3a3 100644 --- a/nixos/modules/security/duosec.nix +++ b/nixos/modules/security/duosec.nix @@ -193,8 +193,11 @@ in source = "${pkgs.duo-unix.out}/bin/login_duo"; }; - system.activationScripts = { - login_duo = mkIf cfg.ssh.enable '' + systemd.services.login-duo = lib.mkIf cfg.ssh.enable { + wantedBy = [ "sysinit.target" ]; + before = [ "sysinit.target" ]; + unitConfig.DefaultDependencies = false; + script = '' if test -f "${cfg.secretKeyFile}"; then mkdir -m 0755 -p /etc/duo @@ -209,7 +212,13 @@ in mv -fT "$conf" /etc/duo/login_duo.conf fi ''; - pam_duo = mkIf cfg.pam.enable '' + }; + + systemd.services.pam-duo = lib.mkIf cfg.ssh.enable { + wantedBy = [ "sysinit.target" ]; + before = [ "sysinit.target" ]; + unitConfig.DefaultDependencies = false; + script = '' if test -f "${cfg.secretKeyFile}"; then mkdir -m 0755 -p /etc/duo diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix index a8bb0650b11a..250f9775be14 100644 --- a/nixos/modules/security/wrappers/default.nix +++ b/nixos/modules/security/wrappers/default.nix @@ -275,33 +275,38 @@ in mrpx ${wrap.source}, '') wrappers; - ###### wrappers activation script - system.activationScripts.wrappers = - lib.stringAfter [ "specialfs" "users" ] - '' - chmod 755 "${parentWrapperDir}" - - # We want to place the tmpdirs for the wrappers to the parent dir. - wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX) - chmod a+rx "$wrapperDir" - - ${lib.concatStringsSep "\n" mkWrappedPrograms} - - if [ -L ${wrapperDir} ]; then - # Atomically replace the symlink - # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ - old=$(readlink -f ${wrapperDir}) - if [ -e "${wrapperDir}-tmp" ]; then - rm --force --recursive "${wrapperDir}-tmp" - fi - ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp" - mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}" - rm --force --recursive "$old" - else - # For initial setup - ln --symbolic "$wrapperDir" "${wrapperDir}" + systemd.services.suid-sgid-wrappers = { + description = "Create SUID/SGID Wrappers"; + wantedBy = [ "sysinit.target" ]; + before = [ "sysinit.target" ]; + unitConfig.DefaultDependencies = false; + unitConfig.RequiresMountsFor = [ "/nix/store" "/run/wrappers" ]; + serviceConfig.Type = "oneshot"; + script = '' + chmod 755 "${parentWrapperDir}" + + # We want to place the tmpdirs for the wrappers to the parent dir. + wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX) + chmod a+rx "$wrapperDir" + + ${lib.concatStringsSep "\n" mkWrappedPrograms} + + if [ -L ${wrapperDir} ]; then + # Atomically replace the symlink + # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ + old=$(readlink -f ${wrapperDir}) + if [ -e "${wrapperDir}-tmp" ]; then + rm --force --recursive "${wrapperDir}-tmp" fi - ''; + ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp" + mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}" + rm --force --recursive "$old" + else + # For initial setup + ln --symbolic "$wrapperDir" "${wrapperDir}" + fi + ''; + }; ###### wrappers consistency checks system.checks = lib.singleton (pkgs.runCommandLocal diff --git a/nixos/modules/services/audio/wyoming/openwakeword.nix b/nixos/modules/services/audio/wyoming/openwakeword.nix index 06b7dd585fda..713945c1df62 100644 --- a/nixos/modules/services/audio/wyoming/openwakeword.nix +++ b/nixos/modules/services/audio/wyoming/openwakeword.nix @@ -22,15 +22,6 @@ let toString ; - models = [ - # wyoming_openwakeword/models/*.tflite - "alexa" - "hey_jarvis" - "hey_mycroft" - "hey_rhasspy" - "ok_nabu" - ]; - in { @@ -51,15 +42,22 @@ in }; models = mkOption { - type = listOf (enum models); - default = models; + type = listOf str; + default = [ + # wyoming_openwakeword/models/*.tflite + "alexa" + "hey_jarvis" + "hey_mycroft" + "hey_rhasspy" + "ok_nabu" + ]; description = mdDoc '' List of wake word models that should be made available. ''; }; preloadModels = mkOption { - type = listOf (enum models); + type = listOf str; default = [ "ok_nabu" ]; diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix index d3ba7628e85d..b27dd2817120 100644 --- a/nixos/modules/services/backup/borgmatic.nix +++ b/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/nixos/modules/services/blockchain/ethereum/erigon.nix b/nixos/modules/services/blockchain/ethereum/erigon.nix index 8ebe0fcaff54..945a373d1274 100644 --- a/nixos/modules/services/blockchain/ethereum/erigon.nix +++ b/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/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix index 56120094871c..24987374ab0d 100644 --- a/nixos/modules/services/hardware/udev.nix +++ b/nixos/modules/services/hardware/udev.nix @@ -350,7 +350,7 @@ in boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ]; - boot.initrd.extraUdevRulesCommands = optionalString (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "") + boot.initrd.extraUdevRulesCommands = mkIf (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "") '' cat <<'EOF' > $out/99-local.rules ${config.boot.initrd.services.udev.rules} diff --git a/nixos/modules/services/logging/syslog-ng.nix b/nixos/modules/services/logging/syslog-ng.nix index d22acbeaa70c..48d556b9459e 100644 --- a/nixos/modules/services/logging/syslog-ng.nix +++ b/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/nixos/modules/services/mail/mlmmj.nix b/nixos/modules/services/mail/mlmmj.nix index 642f8b20fe35..3f07fabcf177 100644 --- a/nixos/modules/services/mail/mlmmj.nix +++ b/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/nixos/modules/services/monitoring/certspotter.md b/nixos/modules/services/monitoring/certspotter.md new file mode 100644 index 000000000000..9bf6e1d946a0 --- /dev/null +++ b/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/nixos/modules/services/monitoring/certspotter.nix b/nixos/modules/services/monitoring/certspotter.nix new file mode 100644 index 000000000000..aafa29daa872 --- /dev/null +++ b/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/nixos/modules/services/monitoring/goss.md b/nixos/modules/services/monitoring/goss.md new file mode 100644 index 000000000000..1e636aa3bdf3 --- /dev/null +++ b/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/nixos/modules/services/monitoring/goss.nix b/nixos/modules/services/monitoring/goss.nix new file mode 100644 index 000000000000..64a8dad0703e --- /dev/null +++ b/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/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix index bb11b6a1c1d0..efef2d777acd 100644 --- a/nixos/modules/services/monitoring/ups.nix +++ b/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/nixos/modules/services/networking/gvpe.nix b/nixos/modules/services/networking/gvpe.nix index 2279ceee2f58..558f499022c8 100644 --- a/nixos/modules/services/networking/gvpe.nix +++ b/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/nixos/modules/services/networking/iscsi/initiator.nix b/nixos/modules/services/networking/iscsi/initiator.nix index 9c71a988f29c..6c30f89b7968 100644 --- a/nixos/modules/services/networking/iscsi/initiator.nix +++ b/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/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix index bd403e109c2a..9099cbe0cd32 100644 --- a/nixos/modules/services/networking/multipath.nix +++ b/nixos/modules/services/networking/multipath.nix @@ -546,8 +546,9 @@ in { # We do not have systemd in stage-1 boot so must invoke `multipathd` # with the `-1` argument which disables systemd calls. Invoke `multipath` # to display the multipath mappings in the output of `journalctl -b`. + # TODO: Implement for systemd stage 1 boot.initrd.kernelModules = [ "dm-multipath" "dm-service-time" ]; - boot.initrd.postDeviceCommands = '' + boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) '' modprobe -a dm-multipath dm-service-time multipathd -s (set -x && sleep 1 && multipath -ll) diff --git a/nixos/modules/services/networking/spiped.nix b/nixos/modules/services/networking/spiped.nix index 3e01ace54ad1..547317dbcbe2 100644 --- a/nixos/modules/services/networking/spiped.nix +++ b/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/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix index daa30fe09b89..f54ce5917438 100644 --- a/nixos/modules/services/networking/ssh/sshd.nix +++ b/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/nixos/modules/services/networking/sslh.nix b/nixos/modules/services/networking/sslh.nix index daf2f2f3668e..dd29db510020 100644 --- a/nixos/modules/services/networking/sslh.nix +++ b/nixos/modules/services/networking/sslh.nix @@ -5,81 +5,131 @@ with lib; let cfg = config.services.sslh; user = "sslh"; - configFile = pkgs.writeText "sslh.conf" '' - verbose: ${boolToString cfg.verbose}; - foreground: true; - inetd: false; - numeric: false; - transparent: ${boolToString cfg.transparent}; - timeout: "${toString cfg.timeout}"; - - listen: - ( - ${ - concatMapStringsSep ",\n" - (addr: ''{ host: "${addr}"; port: "${toString cfg.port}"; }'') - cfg.listenAddresses - } - ); - - ${cfg.appendConfig} - ''; - defaultAppendConfig = '' - protocols: - ( - { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; }, - { name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; }, - { name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; }, - { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; }, - { name: "tls"; host: "localhost"; port: "443"; probe: "builtin"; }, - { name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; } - ); - ''; + + configFormat = pkgs.formats.libconfig {}; + configFile = configFormat.generate "sslh.conf" cfg.settings; in + { imports = [ (mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ]) + (mkRenamedOptionModule [ "services" "sslh" "timeout" ] [ "services" "sslh" "settings" "timeout" ]) + (mkRenamedOptionModule [ "services" "sslh" "transparent" ] [ "services" "sslh" "settings" "transparent" ]) + (mkRemovedOptionModule [ "services" "sslh" "appendConfig" ] "Use services.sslh.settings instead") + (mkChangedOptionModule [ "services" "sslh" "verbose" ] [ "services" "sslh" "settings" "verbose-connections" ] + (config: if config.services.sslh.verbose then 1 else 0)) ]; - options = { - services.sslh = { - enable = mkEnableOption (lib.mdDoc "sslh"); + meta.buildDocsInSandbox = false; - verbose = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc "Verbose logs."; - }; + options.services.sslh = { + enable = mkEnableOption (lib.mdDoc "sslh, protocol demultiplexer"); - timeout = mkOption { - type = types.int; - default = 2; - description = lib.mdDoc "Timeout in seconds."; - }; + method = mkOption { + type = types.enum [ "fork" "select" "ev" ]; + default = "fork"; + description = lib.mdDoc '' + The method to use for handling connections: - transparent = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc "Will the services behind sslh (Apache, sshd and so on) see the external IP and ports as if the external world connected directly to them"; - }; + - `fork` forks a new process for each incoming connection. It is + well-tested and very reliable, but incurs the overhead of many + processes. - listenAddresses = mkOption { - type = types.coercedTo types.str singleton (types.listOf types.str); - default = [ "0.0.0.0" "[::]" ]; - description = lib.mdDoc "Listening addresses or hostnames."; - }; + - `select` uses only one thread, which monitors all connections at once. + It has lower overhead per connection, but if it stops, you'll lose all + connections. - port = mkOption { - type = types.port; - default = 443; - description = lib.mdDoc "Listening port."; - }; + - `ev` is implemented using libev, it's similar to `select` but + scales better to a large number of connections. + ''; + }; + + listenAddresses = mkOption { + type = with types; coercedTo str singleton (listOf str); + default = [ "0.0.0.0" "[::]" ]; + description = lib.mdDoc "Listening addresses or hostnames."; + }; + + port = mkOption { + type = types.port; + default = 443; + description = lib.mdDoc "Listening port."; + }; + + settings = mkOption { + type = types.submodule { + freeformType = configFormat.type; + + options.timeout = mkOption { + type = types.ints.unsigned; + default = 2; + description = lib.mdDoc "Timeout in seconds."; + }; + + options.transparent = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc '' + Whether the services behind sslh (Apache, sshd and so on) will see the + external IP and ports as if the external world connected directly to + them. + ''; + }; + + options.verbose-connections = mkOption { + type = types.ints.between 0 4; + default = 0; + description = lib.mdDoc '' + Where to log connections information. Possible values are: + + 0. don't log anything + 1. write log to stdout + 2. write log to syslog + 3. write log to both stdout and syslog + 4. write to a log file ({option}`sslh.settings.logfile`) + ''; + }; + + options.numeric = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether to disable reverse DNS lookups, thus keeping IP + address literals in the log. + ''; + }; + + options.protocols = mkOption { + type = types.listOf configFormat.type; + default = [ + { name = "ssh"; host = "localhost"; port = "22"; service= "ssh"; } + { name = "openvpn"; host = "localhost"; port = "1194"; } + { name = "xmpp"; host = "localhost"; port = "5222"; } + { name = "http"; host = "localhost"; port = "80"; } + { name = "tls"; host = "localhost"; port = "443"; } + { name = "anyprot"; host = "localhost"; port = "443"; } + ]; + description = lib.mdDoc '' + List of protocols sslh will probe for and redirect. + Each protocol entry consists of: + + - `name`: name of the probe. + + - `service`: libwrap service name (see {manpage}`hosts_access(5)`), - appendConfig = mkOption { - type = types.str; - default = defaultAppendConfig; - description = lib.mdDoc "Verbatim configuration file."; + - `host`, `port`: where to connect when this probe succeeds, + + - `log_level`: to log incoming connections, + + - `transparent`: proxy this protocol transparently, + + - etc. + + See the documentation for all options, including probe-specific ones. + ''; + }; }; + description = lib.mdDoc "sslh configuration. See {manpage}`sslh(8)` for available settings."; }; }; @@ -96,20 +146,29 @@ in PermissionsStartOnly = true; Restart = "always"; RestartSec = "1s"; - ExecStart = "${pkgs.sslh}/bin/sslh -F${configFile}"; + ExecStart = "${pkgs.sslh}/bin/sslh-${cfg.method} -F${configFile}"; KillMode = "process"; - AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID"; + AmbientCapabilities = ["CAP_NET_BIND_SERVICE" "CAP_NET_ADMIN" "CAP_SETGID" "CAP_SETUID"]; PrivateTmp = true; PrivateDevices = true; ProtectSystem = "full"; ProtectHome = true; }; }; + + services.sslh.settings = { + # Settings defined here are not supposed to be changed: doing so will + # break the module, as such you need `lib.mkForce` to override them. + foreground = true; + inetd = false; + listen = map (addr: { host = addr; port = toString cfg.port; }) cfg.listenAddresses; + }; + }) # code from https://github.com/yrutschle/sslh#transparent-proxy-support # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module - (mkIf (cfg.enable && cfg.transparent) { + (mkIf (cfg.enable && cfg.settings.transparent) { # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1; boot.kernel.sysctl."net.ipv4.conf.all.route_localnet" = 1; diff --git a/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixos/modules/services/networking/strongswan-swanctl/module.nix index c51e8ad9f5fc..bfea89969728 100644 --- a/nixos/modules/services/networking/strongswan-swanctl/module.nix +++ b/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/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix index 37a739f41d48..6b6837109806 100644 --- a/nixos/modules/services/networking/unifi.nix +++ b/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/nixos/modules/services/system/nix-daemon.nix b/nixos/modules/services/system/nix-daemon.nix index c9df20196dbd..ce255cd8d0a4 100644 --- a/nixos/modules/services/system/nix-daemon.nix +++ b/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/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix index 66e5f1695a15..24f3b3331845 100644 --- a/nixos/modules/services/web-apps/mattermost.nix +++ b/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/nixos/modules/services/web-servers/stargazer.nix b/nixos/modules/services/web-servers/stargazer.nix index f0c3cf8787eb..18f57363137c 100644 --- a/nixos/modules/services/web-servers/stargazer.nix +++ b/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 = { diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix index 7f6154794bd8..3d941596747b 100644 --- a/nixos/modules/system/activation/activatable-system.nix +++ b/nixos/modules/system/activation/activatable-system.nix @@ -1,52 +1,16 @@ -{ config, lib, pkgs, ... }: +{ options, config, lib, pkgs, ... }: let inherit (lib) mkOption - optionalString types ; - perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]); - systemBuilderArgs = { activationScript = config.system.activationScripts.script; dryActivationScript = config.system.dryActivationScript; }; - systemBuilderCommands = '' - echo "$activationScript" > $out/activate - echo "$dryActivationScript" > $out/dry-activate - substituteInPlace $out/activate --subst-var-by out ''${!toplevelVar} - substituteInPlace $out/dry-activate --subst-var-by out ''${!toplevelVar} - chmod u+x $out/activate $out/dry-activate - unset activationScript dryActivationScript - - mkdir $out/bin - substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ - --subst-var out \ - --subst-var-by toplevel ''${!toplevelVar} \ - --subst-var-by coreutils "${pkgs.coreutils}" \ - --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \ - --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \ - --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \ - --subst-var-by perl "${perlWrapped}" \ - --subst-var-by shell "${pkgs.bash}/bin/sh" \ - --subst-var-by su "${pkgs.shadow.su}/bin/su" \ - --subst-var-by systemd "${config.systemd.package}" \ - --subst-var-by utillinux "${pkgs.util-linux}" \ - ; - - chmod +x $out/bin/switch-to-configuration - ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) '' - if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then - echo "switch-to-configuration syntax is not valid:" - echo "$output" - exit 1 - fi - ''} - ''; - in { options = { @@ -60,6 +24,18 @@ in do, but for image based systems, this may not be needed or not be desirable. ''; }; + system.activatableSystemBuilderCommands = options.system.systemBuilderCommands // { + description = lib.mdDoc '' + Like `system.systemBuilderCommands`, but only for the commands that are + needed *both* when the system is activatable and when it isn't. + + Disclaimer: This option might go away in the future. It might be + superseded by separating switch-to-configuration into a separate script + which will make this option superfluous. See + https://github.com/NixOS/nixpkgs/pull/263462#discussion_r1373104845 for + a discussion. + ''; + }; system.build.separateActivationScript = mkOption { type = types.package; description = '' @@ -71,7 +47,18 @@ in }; }; config = { - system.systemBuilderCommands = lib.mkIf config.system.activatable systemBuilderCommands; + system.activatableSystemBuilderCommands = '' + echo "$activationScript" > $out/activate + echo "$dryActivationScript" > $out/dry-activate + substituteInPlace $out/activate --subst-var-by out ''${!toplevelVar} + substituteInPlace $out/dry-activate --subst-var-by out ''${!toplevelVar} + chmod u+x $out/activate $out/dry-activate + unset activationScript dryActivationScript + ''; + + system.systemBuilderCommands = lib.mkIf + config.system.activatable + config.system.activatableSystemBuilderCommands; system.systemBuilderArgs = lib.mkIf config.system.activatable (systemBuilderArgs // { toplevelVar = "out"; @@ -86,7 +73,7 @@ in }) '' mkdir $out - ${systemBuilderCommands} + ${config.system.activatableSystemBuilderCommands} ''; }; } diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix index c8407dd6779a..bc0b7266ce95 100644 --- a/nixos/modules/system/activation/activation-script.nix +++ b/nixos/modules/system/activation/activation-script.nix @@ -55,10 +55,6 @@ let # used as a garbage collection root. ln -sfn "$(readlink -f "$systemConfig")" /run/current-system - # Prevent the current configuration from being garbage-collected. - mkdir -p /nix/var/nix/gcroots - ln -sfn /run/current-system /nix/var/nix/gcroots/current-system - exit $_status ''; @@ -233,23 +229,15 @@ in config = { system.activationScripts.stdio = ""; # obsolete + system.activationScripts.var = ""; # obsolete - system.activationScripts.var = - '' - # Various log/runtime directories. - - mkdir -p /var/tmp - chmod 1777 /var/tmp - - # Empty, immutable home directory of many system accounts. - mkdir -p /var/empty - # Make sure it's really empty - ${pkgs.e2fsprogs}/bin/chattr -f -i /var/empty || true - find /var/empty -mindepth 1 -delete - chmod 0555 /var/empty - chown root:root /var/empty - ${pkgs.e2fsprogs}/bin/chattr -f +i /var/empty || true - ''; + systemd.tmpfiles.rules = [ + # Prevent the current configuration from being garbage-collected. + "d /nix/var/nix/gcroots -" + "L+ /nix/var/nix/gcroots/current-system - - - - /run/current-system" + "D /var/empty 0555 root root -" + "h /var/empty - - - - +i" + ]; system.activationScripts.usrbinenv = if config.environment.usrbinenv != null then '' diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index b3ff3ac0abf3..e2f66a287bc4 100755 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -22,6 +22,7 @@ use JSON::PP; use IPC::Cmd; use Sys::Syslog qw(:standard :macros); use Cwd qw(abs_path); +use Fcntl ':flock'; ## no critic(ControlStructures::ProhibitDeepNests) ## no critic(ErrorHandling::RequireCarping) @@ -91,6 +92,8 @@ if (!-f "/etc/NIXOS" && (read_file("/etc/os-release", err_mode => "quiet") // "" } make_path("/run/nixos", { mode => oct(755) }); +open(my $stc_lock, '>>', '/run/nixos/switch-to-configuration.lock') or die "Could not open lock - $!"; +flock($stc_lock, LOCK_EX) or die "Could not acquire lock - $!"; openlog("nixos", "", LOG_USER); # Install or update the bootloader. @@ -985,4 +988,5 @@ if ($res == 0) { syslog(LOG_ERR, "switching to system configuration $toplevel failed (status $res)"); } +close($stc_lock) or die "Could not close lock - $!"; exit($res); diff --git a/nixos/modules/system/activation/switchable-system.nix b/nixos/modules/system/activation/switchable-system.nix new file mode 100644 index 000000000000..00bc18e48d1f --- /dev/null +++ b/nixos/modules/system/activation/switchable-system.nix @@ -0,0 +1,55 @@ +{ config, lib, pkgs, ... }: + +let + + perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]); + +in + +{ + + options = { + system.switch.enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = lib.mdDoc '' + Whether to include the capability to switch configurations. + + Disabling this makes the system unable to be reconfigured via `nixos-rebuild`. + + This is good for image based appliances where updates are handled + outside the image. Reducing features makes the image lighter and + slightly more secure. + ''; + }; + }; + + config = lib.mkIf config.system.switch.enable { + system.activatableSystemBuilderCommands = '' + mkdir $out/bin + substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \ + --subst-var out \ + --subst-var-by toplevel ''${!toplevelVar} \ + --subst-var-by coreutils "${pkgs.coreutils}" \ + --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \ + --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \ + --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \ + --subst-var-by perl "${perlWrapped}" \ + --subst-var-by shell "${pkgs.bash}/bin/sh" \ + --subst-var-by su "${pkgs.shadow.su}/bin/su" \ + --subst-var-by systemd "${config.systemd.package}" \ + --subst-var-by utillinux "${pkgs.util-linux}" \ + ; + + chmod +x $out/bin/switch-to-configuration + ${lib.optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) '' + if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then + echo "switch-to-configuration syntax is not valid:" + echo "$output" + exit 1 + fi + ''} + ''; + }; + +} diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix index 8c9483f01c10..d16152ab9dec 100644 --- a/nixos/modules/system/boot/binfmt.nix +++ b/nixos/modules/system/boot/binfmt.nix @@ -20,17 +20,13 @@ let optionalString fixBinary "F"; in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}"; - activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then '' - rm -f /run/binfmt/${name} - cat > /run/binfmt/${name} << 'EOF' - #!${pkgs.bash}/bin/sh - exec -- ${interpreter} "$@" - EOF - chmod +x /run/binfmt/${name} - '' else '' - rm -f /run/binfmt/${name} - ln -s ${interpreter} /run/binfmt/${name} - ''; + mkInterpreter = name: { interpreter, wrapInterpreterInShell, ... }: + if wrapInterpreterInShell + then pkgs.writeShellScript "${name}-interpreter" '' + #!${pkgs.bash}/bin/sh + exec -- ${interpreter} "$@" + '' + else interpreter; getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs; getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch; @@ -318,18 +314,25 @@ in { environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf" (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations)); - system.activationScripts.binfmt = stringAfter [ "specialfs" ] '' - mkdir -p /run/binfmt - chmod 0755 /run/binfmt - ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)} - ''; - systemd = lib.mkIf (config.boot.binfmt.registrations != {}) { - additionalUpstreamSystemUnits = [ - "proc-sys-fs-binfmt_misc.automount" - "proc-sys-fs-binfmt_misc.mount" - "systemd-binfmt.service" - ]; - services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ]; - }; + + systemd = lib.mkMerge [ + ({ tmpfiles.rules = [ + "d /run/binfmt 0755 -" + ] ++ lib.mapAttrsToList + (name: interpreter: + "L+ /run/binfmt/${name} - - - - ${interpreter}" + ) + (lib.mapAttrs mkInterpreter config.boot.binfmt.registrations); + }) + + (lib.mkIf (config.boot.binfmt.registrations != {}) { + additionalUpstreamSystemUnits = [ + "proc-sys-fs-binfmt_misc.automount" + "proc-sys-fs-binfmt_misc.mount" + "systemd-binfmt.service" + ]; + services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ]; + }) + ]; }; } diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix index 5bf38b6fa200..88ba43caf003 100644 --- a/nixos/modules/system/boot/initrd-network.nix +++ b/nixos/modules/system/boot/initrd-network.nix @@ -116,11 +116,11 @@ in boot.initrd.kernelModules = [ "af_packet" ]; - boot.initrd.extraUtilsCommands = '' + boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' copy_bin_and_libs ${pkgs.klibc}/lib/klibc/bin.static/ipconfig ''; - boot.initrd.preLVMCommands = mkBefore ( + boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore ( # Search for interface definitions in command line. '' ifaces="" @@ -138,7 +138,7 @@ in # Bring up all interfaces. for iface in ${dhcpIfShellExpr}; do echo "bringing up network interface $iface..." - ip link set "$iface" up && ifaces="$ifaces $iface" + ip link set dev "$iface" up && ifaces="$ifaces $iface" done # Acquire DHCP leases. @@ -148,12 +148,12 @@ in done '' - + cfg.postCommands); + + cfg.postCommands)); - boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 '' + boot.initrd.postMountCommands = mkIf (cfg.flushBeforeStage2 && !config.boot.initrd.systemd.enable) '' for iface in $ifaces; do - ip address flush "$iface" - ip link set "$iface" down + ip address flush dev "$iface" + ip link set dev "$iface" down done ''; diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix index 60c5ff62ffff..3df14030ab68 100644 --- a/nixos/modules/system/boot/initrd-ssh.nix +++ b/nixos/modules/system/boot/initrd-ssh.nix @@ -164,13 +164,12 @@ in for instructions. ''; } - - { - assertion = config.boot.initrd.systemd.enable -> cfg.shell == null; - message = "systemd stage 1 does not support boot.initrd.network.ssh.shell"; - } ]; + warnings = lib.optional (config.boot.initrd.systemd.enable -> cfg.shell != null) '' + Please set 'boot.initrd.systemd.users.root.shell' instead of 'boot.initrd.network.ssh.shell' + ''; + boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) '' copy_bin_and_libs ${package}/bin/sshd cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib @@ -235,6 +234,8 @@ in users.sshd = { uid = 1; group = "sshd"; }; groups.sshd = { gid = 1; }; + users.root.shell = mkIf (config.boot.initrd.network.ssh.shell != null) config.boot.initrd.network.ssh.shell; + contents."/etc/ssh/authorized_keys.d/root".text = concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys; contents."/etc/ssh/sshd_config".text = sshdConfig; diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix index 9ea611919676..6b07686efcba 100644 --- a/nixos/modules/system/boot/kernel.nix +++ b/nixos/modules/system/boot/kernel.nix @@ -269,6 +269,9 @@ in "ata_piix" "pata_marvell" + # NVMe + "nvme" + # Standard SCSI stuff. "sd_mod" "sr_mod" diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix index 175e757cbbb6..be40b8e969a1 100644 --- a/nixos/modules/system/boot/systemd/initrd.nix +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -128,10 +128,6 @@ in { stage 2 counterparts such as {option}`systemd.services`, except that `restartTriggers` and `reloadTriggers` are not supported. - - Note: This is experimental. Some of the `boot.initrd` options - are not supported when this is enabled, and the options under - `boot.initrd.systemd` are subject to change. ''; }; @@ -348,6 +344,27 @@ in { }; config = mkIf (config.boot.initrd.enable && cfg.enable) { + assertions = map (name: { + assertion = lib.attrByPath name (throw "impossible") config.boot.initrd == ""; + message = '' + systemd stage 1 does not support 'boot.initrd.${lib.concatStringsSep "." name}'. Please + convert it to analogous systemd units in 'boot.initrd.systemd'. + + Definitions: + ${lib.concatMapStringsSep "\n" ({ file, ... }: " - ${file}") (lib.attrByPath name (throw "impossible") options.boot.initrd).definitionsWithLocations} + ''; + }) [ + [ "preFailCommands" ] + [ "preDeviceCommands" ] + [ "preLVMCommands" ] + [ "postDeviceCommands" ] + [ "postMountCommands" ] + [ "extraUdevRulesCommands" ] + [ "extraUtilsCommands" ] + [ "extraUtilsCommandsTest" ] + [ "network" "postCommands" ] + ]; + system.build = { inherit initialRamdisk; }; boot.initrd.availableKernelModules = [ diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix index a6604802c38c..7487cf97fe53 100644 --- a/nixos/modules/system/boot/timesyncd.nix +++ b/nixos/modules/system/boot/timesyncd.nix @@ -46,6 +46,28 @@ with lib; wantedBy = [ "sysinit.target" ]; aliases = [ "dbus-org.freedesktop.timesync1.service" ]; restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ]; + + preStart = ( + # Ensure that we have some stored time to prevent + # systemd-timesyncd to resort back to the fallback time. If + # the file doesn't exist we assume that our current system + # clock is good enough to provide an initial value. + '' + if ! [ -f /var/lib/systemd/timesync/clock ]; then + test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync + touch /var/lib/systemd/timesync/clock + fi + '' + + # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes + # - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742 + # - https://github.com/systemd/systemd/issues/12131 + (lib.optionalString (versionOlder config.system.stateVersion "19.09") '' + if [ -L /var/lib/systemd/timesync ]; then + rm /var/lib/systemd/timesync + mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync + fi + '') + ); }; environment.etc."systemd/timesyncd.conf".text = '' @@ -59,28 +81,5 @@ with lib; group = "systemd-timesync"; }; users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync; - - system.activationScripts.systemd-timesyncd-migration = - # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes - # - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742 - # - https://github.com/systemd/systemd/issues/12131 - mkIf (versionOlder config.system.stateVersion "19.09") '' - if [ -L /var/lib/systemd/timesync ]; then - rm /var/lib/systemd/timesync - mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync - fi - ''; - system.activationScripts.systemd-timesyncd-init-clock = - # Ensure that we have some stored time to prevent systemd-timesyncd to - # resort back to the fallback time. - # If the file doesn't exist we assume that our current system clock is - # good enough to provide an initial value. - '' - if ! [ -f /var/lib/systemd/timesync/clock ]; then - test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync - touch /var/lib/systemd/timesync/clock - fi - ''; }; - } diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix index 7837a34b4984..da9c83ba339c 100644 --- a/nixos/modules/tasks/encrypted-devices.nix +++ b/nixos/modules/tasks/encrypted-devices.nix @@ -5,8 +5,22 @@ with lib; let fileSystems = config.system.build.fileSystems ++ config.swapDevices; encDevs = filter (dev: dev.encrypted.enable) fileSystems; - keyedEncDevs = filter (dev: dev.encrypted.keyFile != null) encDevs; - keylessEncDevs = filter (dev: dev.encrypted.keyFile == null) encDevs; + + # With scripted initrd, devices with a keyFile have to be opened + # late, after file systems are mounted, because that could be where + # the keyFile is located. With systemd initrd, each individual + # systemd-cryptsetup@ unit has RequiresMountsFor= to delay until all + # the mount units for the key file are done; i.e. no special + # treatment is needed. + lateEncDevs = + if config.boot.initrd.systemd.enable + then { } + else filter (dev: dev.encrypted.keyFile != null) encDevs; + earlyEncDevs = + if config.boot.initrd.systemd.enable + then encDevs + else filter (dev: dev.encrypted.keyFile == null) encDevs; + anyEncrypted = foldr (j: v: v || j.encrypted.enable) false encDevs; @@ -39,11 +53,14 @@ let type = types.nullOr types.str; description = lib.mdDoc '' Path to a keyfile used to unlock the backing encrypted - device. At the time this keyfile is accessed, the - `neededForBoot` filesystems (see - `fileSystems.<name?>.neededForBoot`) - will have been mounted under `/mnt-root`, - so the keyfile path should usually start with "/mnt-root/". + device. When systemd stage 1 is not enabled, at the time + this keyfile is accessed, the `neededForBoot` filesystems + (see `utils.fsNeededForBoot`) will have been mounted under + `/mnt-root`, so the keyfile path should usually start with + "/mnt-root/". When systemd stage 1 is enabled, + `fsNeededForBoot` file systems will be mounted as needed + under `/sysroot`, and the keyfile will not be accessed until + its requisite mounts are done. ''; }; }; @@ -62,26 +79,42 @@ in }; config = mkIf anyEncrypted { - assertions = map (dev: { - assertion = dev.encrypted.label != null; - message = '' - The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set - ''; - }) encDevs; + assertions = concatMap (dev: [ + { + assertion = dev.encrypted.label != null; + message = '' + The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set + ''; + } + { + assertion = + config.boot.initrd.systemd.enable -> ( + dev.encrypted.keyFile == null + || !lib.any (x: lib.hasPrefix x dev.encrypted.keyFile) ["/mnt-root" "$targetRoot"] + ); + message = '' + Bad use of '/mnt-root' or '$targetRoot` in 'keyFile'. + + When 'boot.initrd.systemd.enable' is enabled, file systems + are mounted at '/sysroot' instead of '/mnt-root'. + ''; + } + ]) encDevs; boot.initrd = { luks = { devices = builtins.listToAttrs (map (dev: { name = dev.encrypted.label; - value = { device = dev.encrypted.blkDev; }; - }) keylessEncDevs); + value = { device = dev.encrypted.blkDev; inherit (dev.encrypted) keyFile; }; + }) earlyEncDevs); forceLuksSupportInInitrd = true; }; - postMountCommands = - concatMapStrings (dev: + # TODO: systemd stage 1 + postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable) + (concatMapStrings (dev: "cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n" - ) keyedEncDevs; + ) lateEncDevs); }; }; } diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix index 19ef188ce783..4eadec239e67 100644 --- a/nixos/modules/tasks/filesystems/bcachefs.nix +++ b/nixos/modules/tasks/filesystems/bcachefs.nix @@ -34,17 +34,43 @@ let } ''; - openCommand = name: fs: - let - # we need only unlock one device manually, and cannot pass multiple at once - # remove this adaptation when bcachefs implements mounting by filesystem uuid - # also, implement automatic waiting for the constituent devices when that happens - # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671) - firstDevice = head (splitString ":" fs.device); - in - '' - tryUnlock ${name} ${firstDevice} + # we need only unlock one device manually, and cannot pass multiple at once + # remove this adaptation when bcachefs implements mounting by filesystem uuid + # also, implement automatic waiting for the constituent devices when that happens + # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671) + firstDevice = fs: head (splitString ":" fs.device); + + openCommand = name: fs: '' + tryUnlock ${name} ${firstDevice fs} + ''; + + mkUnits = prefix: name: fs: let + mountUnit = "${utils.escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint))}.mount"; + device = firstDevice fs; + deviceUnit = "${utils.escapeSystemdPath device}.device"; + in { + name = "unlock-bcachefs-${utils.escapeSystemdPath fs.mountPoint}"; + value = { + description = "Unlock bcachefs for ${fs.mountPoint}"; + requiredBy = [ mountUnit ]; + before = [ mountUnit ]; + bindsTo = [ deviceUnit ]; + after = [ deviceUnit ]; + unitConfig.DefaultDependencies = false; + serviceConfig = { + Type = "oneshot"; + ExecCondition = "${pkgs.bcachefs-tools}/bin/bcachefs unlock -c \"${device}\""; + Restart = "on-failure"; + RestartMode = "direct"; + # Ideally, this service would lock the key on stop. + # As is, RemainAfterExit doesn't accomplish anything. + RemainAfterExit = true; + }; + script = '' + ${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${pkgs.bcachefs-tools}/bin/bcachefs unlock "${device}" ''; + }; + }; in @@ -59,6 +85,8 @@ in # use kernel package with bcachefs support until it's in mainline boot.kernelPackages = pkgs.linuxPackages_testing_bcachefs; + + systemd.services = lib.mapAttrs' (mkUnits "") (lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (!utils.fsNeededForBoot fs)) config.fileSystems); } (mkIf ((elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) { @@ -74,11 +102,13 @@ in copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs copy_bin_and_libs ${mountCommand}/bin/mount.bcachefs ''; - boot.initrd.extraUtilsCommandsTest = '' + boot.initrd.extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) '' $out/bin/bcachefs version ''; - boot.initrd.postDeviceCommands = commonFunctions + concatStrings (mapAttrsToList openCommand bootFs); + boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + concatStrings (mapAttrsToList openCommand bootFs)); + + boot.initrd.systemd.services = lib.mapAttrs' (mkUnits "/sysroot") bootFs; }) ]); } diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix index 082634ec9d01..4b6a5b6c12c1 100644 --- a/nixos/modules/tasks/filesystems/zfs.nix +++ b/nixos/modules/tasks/filesystems/zfs.nix @@ -584,17 +584,17 @@ in boot.initrd = mkIf inInitrd { kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl"; extraUtilsCommands = - '' + mkIf (!config.boot.initrd.systemd.enable) '' copy_bin_and_libs ${cfgZfs.package}/sbin/zfs copy_bin_and_libs ${cfgZfs.package}/sbin/zdb copy_bin_and_libs ${cfgZfs.package}/sbin/zpool ''; - extraUtilsCommandsTest = mkIf inInitrd - '' + extraUtilsCommandsTest = + mkIf (!config.boot.initrd.systemd.enable) '' $out/bin/zfs --help >/dev/null 2>&1 $out/bin/zpool --help >/dev/null 2>&1 ''; - postDeviceCommands = concatStringsSep "\n" (['' + postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (concatStringsSep "\n" (['' ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}" ''] ++ [(importLib { # See comments at importLib definition. @@ -623,10 +623,10 @@ in else concatMapStrings (fs: '' zfs load-key -- ${escapeShellArg fs} '') (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)} - '') rootPools)); + '') rootPools))); # Systemd in stage 1 - systemd = { + systemd = mkIf config.boot.initrd.systemd.enable { packages = [cfgZfs.package]; services = listToAttrs (map (pool: createImportService { inherit pool; diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix index da4aa916d655..e1ac7f24cb32 100644 --- a/nixos/modules/tasks/network-interfaces-scripted.nix +++ b/nixos/modules/tasks/network-interfaces-scripted.nix @@ -28,12 +28,12 @@ let SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}') for I in $SLAVES; do UPDATED=0 - ip link set "$I" nomaster + ip link set dev "$I" nomaster done [ "$UPDATED" -eq "1" ] && break done - ip link set "${i}" down 2>/dev/null || true - ip link del "${i}" 2>/dev/null || true + ip link set dev "${i}" down 2>/dev/null || true + ip link del dev "${i}" 2>/dev/null || true ''; # warn that these attributes are deprecated (2017-2-2) @@ -193,7 +193,7 @@ let state="/run/nixos/network/addresses/${i.name}" mkdir -p $(dirname "$state") - ip link set "${i.name}" up + ip link set dev "${i.name}" up ${flip concatMapStrings ips (ip: let @@ -270,7 +270,7 @@ let ip tuntap add dev "${i.name}" mode "${i.virtualType}" user "${i.virtualOwner}" ''; postStop = '' - ip link del ${i.name} || true + ip link del dev ${i.name} || true ''; }; @@ -291,15 +291,15 @@ let script = '' # Remove Dead Interfaces echo "Removing old bridge ${n}..." - ip link show dev "${n}" >/dev/null 2>&1 && ip link del "${n}" + ip link show dev "${n}" >/dev/null 2>&1 && ip link del dev "${n}" echo "Adding bridge ${n}..." ip link add name "${n}" type bridge # Enslave child interfaces ${flip concatMapStrings v.interfaces (i: '' - ip link set "${i}" master "${n}" - ip link set "${i}" up + ip link set dev "${i}" master "${n}" + ip link set dev "${i}" up '')} # Save list of enslaved interfaces echo "${flip concatMapStrings v.interfaces (i: '' @@ -316,7 +316,7 @@ let for uri in qemu:///system lxc:///; do for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \ - ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set ',target/@dev,' master ',source/@bridge,';')" | \ + ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set dev ',target/@dev,' master ',source/@bridge,';')" | \ ${pkgs.bash}/bin/bash done done @@ -328,23 +328,23 @@ let echo 2 >/sys/class/net/${n}/bridge/stp_state ''} - ip link set "${n}" up + ip link set dev "${n}" up ''; postStop = '' - ip link set "${n}" down || true - ip link del "${n}" || true + ip link set dev "${n}" down || true + ip link del dev "${n}" || true rm -f /run/${n}.interfaces ''; reload = '' # Un-enslave child interfaces (old list of interfaces) for interface in `cat /run/${n}.interfaces`; do - ip link set "$interface" nomaster up + ip link set dev "$interface" nomaster up done # Enslave child interfaces (new list of interfaces) ${flip concatMapStrings v.interfaces (i: '' - ip link set "${i}" master "${n}" - ip link set "${i}" up + ip link set dev "${i}" master "${n}" + ip link set dev "${i}" up '')} # Save list of enslaved interfaces echo "${flip concatMapStrings v.interfaces (i: '' @@ -395,7 +395,7 @@ let postStop = '' echo "Cleaning Open vSwitch ${n}" echo "Shutting down internal ${n} interface" - ip link set ${n} down || true + ip link set dev ${n} down || true echo "Deleting flows for ${n}" ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true echo "Deleting Open vSwitch ${n}" @@ -433,10 +433,10 @@ let while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done; # Bring up the bond and enslave the specified interfaces - ip link set "${n}" up + ip link set dev "${n}" up ${flip concatMapStrings v.interfaces (i: '' - ip link set "${i}" down - ip link set "${i}" master "${n}" + ip link set dev "${i}" down + ip link set dev "${i}" master "${n}" '')} ''; postStop = destroyBond n; @@ -457,13 +457,13 @@ let path = [ pkgs.iproute2 ]; script = '' # Remove Dead Interfaces - ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}" + ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}" ip link add link "${v.interface}" name "${n}" type macvlan \ ${optionalString (v.mode != null) "mode ${v.mode}"} - ip link set "${n}" up + ip link set dev "${n}" up ''; postStop = '' - ip link delete "${n}" || true + ip link delete dev "${n}" || true ''; }); @@ -515,7 +515,7 @@ let path = [ pkgs.iproute2 ]; script = '' # Remove Dead Interfaces - ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}" + ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}" ip link add name "${n}" type sit \ ${optionalString (v.remote != null) "remote \"${v.remote}\""} \ ${optionalString (v.local != null) "local \"${v.local}\""} \ @@ -526,10 +526,10 @@ let optionalString (v.encapsulation.sourcePort != null) "encap-sport ${toString v.encapsulation.sourcePort}" }"} - ip link set "${n}" up + ip link set dev "${n}" up ''; postStop = '' - ip link delete "${n}" || true + ip link delete dev "${n}" || true ''; }); @@ -549,16 +549,16 @@ let path = [ pkgs.iproute2 ]; script = '' # Remove Dead Interfaces - ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}" + ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}" ip link add name "${n}" type ${v.type} \ ${optionalString (v.remote != null) "remote \"${v.remote}\""} \ ${optionalString (v.local != null) "local \"${v.local}\""} \ ${optionalString (v.ttl != null) "${ttlarg} ${toString v.ttl}"} \ ${optionalString (v.dev != null) "dev \"${v.dev}\""} - ip link set "${n}" up + ip link set dev "${n}" up ''; postStop = '' - ip link delete "${n}" || true + ip link delete dev "${n}" || true ''; }); @@ -577,17 +577,17 @@ let path = [ pkgs.iproute2 ]; script = '' # Remove Dead Interfaces - ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}" + ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}" ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}" # We try to bring up the logical VLAN interface. If the master # interface the logical interface is dependent upon is not up yet we will # fail to immediately bring up the logical interface. The resulting logical # interface will brought up later when the master interface is up. - ip link set "${n}" up || true + ip link set dev "${n}" up || true ''; postStop = '' - ip link delete "${n}" || true + ip link delete dev "${n}" || true ''; }); diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix index cee23eb24406..2009c9a7e6e2 100644 --- a/nixos/modules/tasks/network-interfaces-systemd.nix +++ b/nixos/modules/tasks/network-interfaces-systemd.nix @@ -442,7 +442,7 @@ in postStop = '' echo "Cleaning Open vSwitch ${n}" echo "Shutting down internal ${n} interface" - ip link set ${n} down || true + ip link set dev ${n} down || true echo "Deleting flows for ${n}" ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true echo "Deleting Open vSwitch ${n}" diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 853a2cb31432..d976f9951bb5 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -1406,18 +1406,12 @@ in val = tempaddrValues.${opt}.sysctl; in nameValuePair "net.ipv6.conf.${replaceStrings ["."] ["/"] i.name}.use_tempaddr" val)); - # Set the host and domain names in the activation script. Don't - # clear it if it's not configured in the NixOS configuration, - # since it may have been set by dhcpcd in the meantime. - system.activationScripts.hostname = let - effectiveHostname = config.boot.kernel.sysctl."kernel.hostname" or cfg.hostName; - in optionalString (effectiveHostname != "") '' - hostname "${effectiveHostname}" - ''; - system.activationScripts.domain = - optionalString (cfg.domain != null) '' - domainname "${cfg.domain}" - ''; + systemd.services.domainname = lib.mkIf (cfg.domain != null) { + wantedBy = [ "sysinit.target" ]; + before = [ "sysinit.target" ]; + unitConfig.DefaultDependencies = false; + serviceConfig.ExecStart = ''${pkgs.nettools}/bin/domainname "${cfg.domain}"''; + }; environment.etc.hostid = mkIf (cfg.hostId != null) { source = hostidFile; }; boot.initrd.systemd.contents."/etc/hostid" = mkIf (cfg.hostId != null) { source = hostidFile; }; diff --git a/nixos/modules/tasks/swraid.nix b/nixos/modules/tasks/swraid.nix index 61b3682e0f68..249755bc0548 100644 --- a/nixos/modules/tasks/swraid.nix +++ b/nixos/modules/tasks/swraid.nix @@ -62,13 +62,13 @@ in { cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/ ''; - extraUtilsCommands = '' + extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) '' # Add RAID mdadm tool. copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon ''; - extraUtilsCommandsTest = '' + extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) '' $out/bin/mdadm --version ''; diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix index aa85665af695..6fdb177b968b 100644 --- a/nixos/modules/virtualisation/nixos-containers.nix +++ b/nixos/modules/virtualisation/nixos-containers.nix @@ -754,7 +754,7 @@ in { services.postgresql.enable = true; services.postgresql.package = pkgs.postgresql_14; - system.stateVersion = "21.05"; + system.stateVersion = "${lib.trivial.release}"; }; }; } @@ -906,4 +906,6 @@ in "tun" ]; }); + + meta.buildDocsInSandbox = false; } |