diff options
author | Googlebot <googlebot@neet.dev> | 2022-03-21 11:24:33 -0400 |
---|---|---|
committer | Googlebot <googlebot@neet.dev> | 2022-03-21 11:32:36 -0400 |
commit | 4007aa201bf0eb20f651cb4ddfd38199eeebaa86 (patch) | |
tree | 2125273533afa209433ee571763c1813b5a95c9d /nixos/modules | |
parent | b2068c1248cd9f0b913b010ea37ec207131eefd5 (diff) | |
parent | da333c9c8202af773365c08ab10c4f2fd0e99a9e (diff) |
Merge remote-tracking branch 'upstream/master'
Diffstat (limited to 'nixos/modules')
60 files changed, 2592 insertions, 1659 deletions
diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix index 04952898cb761..adc6654afc79b 100644 --- a/nixos/modules/config/fonts/fonts.nix +++ b/nixos/modules/config/fonts/fonts.nix @@ -39,11 +39,6 @@ let defaultXFonts = [ (if hasHidpi then fontcursormisc_hidpi else pkgs.xorg.fontcursormisc) pkgs.xorg.fontmiscmisc - ] ++ optionals (config.nixpkgs.config.allowUnfree or false) - [ # these are unfree, and will make usage with xserver fail - pkgs.xorg.fontbhlucidatypewriter100dpi - pkgs.xorg.fontbhlucidatypewriter75dpi - pkgs.xorg.fontbh100dpi ]; in diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix index 9b274392096c3..6de5b99a1ee63 100644 --- a/nixos/modules/hardware/video/nvidia.nix +++ b/nixos/modules/hardware/video/nvidia.nix @@ -289,8 +289,14 @@ in environment.etc."egl/egl_external_platform.d".source = "/run/opengl-driver/share/egl/egl_external_platform.d/"; - hardware.opengl.extraPackages = [ nvidia_x11.out ]; - hardware.opengl.extraPackages32 = [ nvidia_x11.lib32 ]; + hardware.opengl.extraPackages = [ + nvidia_x11.out + pkgs.nvidia-vaapi-driver + ]; + hardware.opengl.extraPackages32 = [ + nvidia_x11.lib32 + pkgs.pkgsi686Linux.nvidia-vaapi-driver + ]; environment.systemPackages = [ nvidia_x11.bin ] ++ optionals cfg.nvidiaSettings [ nvidia_x11.settings ] diff --git a/nixos/modules/installer/tools/nixos-enter.sh b/nixos/modules/installer/tools/nixos-enter.sh index 115b3d7a7c5ee..89beeee7cf9e5 100644 --- a/nixos/modules/installer/tools/nixos-enter.sh +++ b/nixos/modules/installer/tools/nixos-enter.sh @@ -63,32 +63,32 @@ mount --rbind /sys "$mountPoint/sys" # modified from https://github.com/archlinux/arch-install-scripts/blob/bb04ab435a5a89cd5e5ee821783477bc80db797f/arch-chroot.in#L26-L52 chroot_add_resolv_conf() { - local chrootdir=$1 resolv_conf=$1/etc/resolv.conf + local chrootDir="$1" resolvConf="$1/etc/resolv.conf" [[ -e /etc/resolv.conf ]] || return 0 # Handle resolv.conf as a symlink to somewhere else. - if [[ -L $chrootdir/etc/resolv.conf ]]; then + if [[ -L "$resolvConf" ]]; then # readlink(1) should always give us *something* since we know at this point # it's a symlink. For simplicity, ignore the case of nested symlinks. - # We also ignore the possibility if `../`s escaping the root. - resolv_conf=$(readlink "$chrootdir/etc/resolv.conf") - if [[ $resolv_conf = /* ]]; then - resolv_conf=$chrootdir$resolv_conf + # We also ignore the possibility of `../`s escaping the root. + resolvConf="$(readlink "$resolvConf")" + if [[ "$resolvConf" = /* ]]; then + resolvConf="$chrootDir$resolvConf" else - resolv_conf=$chrootdir/etc/$resolv_conf + resolvConf="$chrootDir/etc/$resolvConf" fi fi # ensure file exists to bind mount over - if [[ ! -f $resolv_conf ]]; then - install -Dm644 /dev/null "$resolv_conf" || return 1 + if [[ ! -f "$resolvConf" ]]; then + install -Dm644 /dev/null "$resolvConf" || return 1 fi - mount --bind /etc/resolv.conf "$resolv_conf" + mount --bind /etc/resolv.conf "$resolvConf" } -chroot_add_resolv_conf "$mountPoint" || print "ERROR: failed to set up resolv.conf" +chroot_add_resolv_conf "$mountPoint" || echo "$0: failed to set up resolv.conf" >&2 ( # If silent, write both stdout and stderr of activation script to /dev/null diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix index 66a49b0b888f2..204a891430082 100644 --- a/nixos/modules/misc/locate.nix +++ b/nixos/modules/misc/locate.nix @@ -183,7 +183,11 @@ in pruneNames = mkOption { type = listOf str; - default = [ ".bzr" ".cache" ".git" ".hg" ".svn" ]; + default = lib.optionals (!isFindutils) [ ".bzr" ".cache" ".git" ".hg" ".svn" ]; + defaultText = literalDocBook '' + <literal>[ ".bzr" ".cache" ".git" ".hg" ".svn" ]</literal>, if + supported by the locate implementation (i.e. mlocate or plocate). + ''; description = '' Directory components which should exclude paths containing them from indexing ''; diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix index 6c072021ed834..d825f4beb301c 100644 --- a/nixos/modules/misc/version.nix +++ b/nixos/modules/misc/version.nix @@ -8,8 +8,12 @@ let concatStringsSep mapAttrsToList toLower literalExpression mkRenamedOptionModule mkDefault mkOption trivial types; + needsEscaping = s: null != builtins.match "[a-zA-Z0-9]+" s; + escapeIfNeccessary = s: if needsEscaping s then s else ''"${lib.escape [ "\$" "\"" "\\" "\`" ] s}"''; attrsToText = attrs: - concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}="${toString v}"'') attrs); + concatStringsSep "\n" ( + mapAttrsToList (n: v: ''${n}=${escapeIfNeccessary (toString v)}'') attrs + ); in { diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 6430993d5c638..c36c77874444d 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -180,6 +180,7 @@ ./programs/msmtp.nix ./programs/mtr.nix ./programs/nano.nix + ./programs/nbd.nix ./programs/neovim.nix ./programs/nm-applet.nix ./programs/npm.nix @@ -301,6 +302,7 @@ ./services/backup/znapzend.nix ./services/blockchain/ethereum/geth.nix ./services/backup/zrepl.nix + ./services/cluster/corosync/default.nix ./services/cluster/hadoop/default.nix ./services/cluster/k3s/default.nix ./services/cluster/kubernetes/addons/dns.nix @@ -313,6 +315,7 @@ ./services/cluster/kubernetes/pki.nix ./services/cluster/kubernetes/proxy.nix ./services/cluster/kubernetes/scheduler.nix + ./services/cluster/pacemaker/default.nix ./services/cluster/spark/default.nix ./services/computing/boinc/client.nix ./services/computing/foldingathome/client.nix @@ -819,6 +822,7 @@ ./services/networking/nar-serve.nix ./services/networking/nat.nix ./services/networking/nats.nix + ./services/networking/nbd.nix ./services/networking/ndppd.nix ./services/networking/nebula.nix ./services/networking/networkmanager.nix @@ -985,6 +989,7 @@ ./services/system/nscd.nix ./services/system/saslauthd.nix ./services/system/self-deploy.nix + ./services/system/systembus-notify.nix ./services/system/uptimed.nix ./services/torrent/deluge.nix ./services/torrent/flexget.nix @@ -1163,7 +1168,12 @@ ./system/boot/stage-1.nix ./system/boot/stage-2.nix ./system/boot/systemd.nix - ./system/boot/systemd-nspawn.nix + ./system/boot/systemd/coredump.nix + ./system/boot/systemd/journald.nix + ./system/boot/systemd/logind.nix + ./system/boot/systemd/nspawn.nix + ./system/boot/systemd/tmpfiles.nix + ./system/boot/systemd/user.nix ./system/boot/timesyncd.nix ./system/boot/tmp.nix ./system/etc/etc-activation.nix diff --git a/nixos/modules/programs/captive-browser.nix b/nixos/modules/programs/captive-browser.nix index dc054504ea48c..aad554c2bd66a 100644 --- a/nixos/modules/programs/captive-browser.nix +++ b/nixos/modules/programs/captive-browser.nix @@ -1,8 +1,12 @@ { config, lib, pkgs, ... }: -with lib; let cfg = config.programs.captive-browser; + + inherit (lib) + concatStringsSep escapeShellArgs optionalString + literalExpression mkEnableOption mkIf mkOption mkOptionDefault types; + browserDefault = chromium: concatStringsSep " " [ ''env XDG_CONFIG_HOME="$PREV_CONFIG_HOME"'' ''${chromium}/bin/chromium'' @@ -15,6 +19,15 @@ let ''-no-default-browser-check'' ''http://cache.nixos.org/'' ]; + + desktopItem = pkgs.makeDesktopItem { + name = "captive-browser"; + desktopName = "Captive Portal Browser"; + exec = "/run/wrappers/bin/captive-browser"; + icon = "nix-snowflake"; + categories = [ "Network" ]; + }; + in { ###### interface @@ -84,6 +97,11 @@ in ###### implementation config = mkIf cfg.enable { + environment.systemPackages = [ + (pkgs.runCommandNoCC "captive-browser-desktop-item" { } '' + install -Dm444 -t $out/share/applications ${desktopItem}/share/applications/*.desktop + '') + ]; programs.captive-browser.dhcp-dns = let diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix index d552c751afd73..a448727be778f 100644 --- a/nixos/modules/programs/environment.nix +++ b/nixos/modules/programs/environment.nix @@ -40,13 +40,15 @@ in KDEDIRS = [ "" ]; QT_PLUGIN_PATH = [ "/lib/qt4/plugins" "/lib/kde4/plugins" ]; QTWEBKIT_PLUGIN_PATH = [ "/lib/mozilla/plugins/" ]; - GTK_PATH = [ "/lib/gtk-2.0" "/lib/gtk-3.0" ]; + GTK_PATH = [ "/lib/gtk-2.0" "/lib/gtk-3.0" "/lib/gtk-4.0" ]; XDG_CONFIG_DIRS = [ "/etc/xdg" ]; XDG_DATA_DIRS = [ "/share" ]; MOZ_PLUGIN_PATH = [ "/lib/mozilla/plugins" ]; LIBEXEC_PATH = [ "/lib/libexec" ]; }; + environment.pathsToLink = [ "/lib/gtk-2.0" "/lib/gtk-3.0" "/lib/gtk-4.0" ]; + environment.extraInit = '' unset ASPELL_CONF diff --git a/nixos/modules/programs/nbd.nix b/nixos/modules/programs/nbd.nix new file mode 100644 index 0000000000000..fea9bc1ff71a1 --- /dev/null +++ b/nixos/modules/programs/nbd.nix @@ -0,0 +1,19 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.nbd; +in +{ + options = { + programs.nbd = { + enable = mkEnableOption "Network Block Device (nbd) support"; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ nbd ]; + boot.kernelModules = [ "nbd" ]; + }; +} diff --git a/nixos/modules/services/audio/squeezelite.nix b/nixos/modules/services/audio/squeezelite.nix index 05506f5bcc7ac..36295e21c60f9 100644 --- a/nixos/modules/services/audio/squeezelite.nix +++ b/nixos/modules/services/audio/squeezelite.nix @@ -1,50 +1,46 @@ { config, lib, pkgs, ... }: -with lib; - let + inherit (lib) mkEnableOption mkIf mkOption optionalString types; + dataDir = "/var/lib/squeezelite"; cfg = config.services.squeezelite; + pkg = if cfg.pulseAudio then pkgs.squeezelite-pulse else pkgs.squeezelite; + bin = "${pkg}/bin/${pkg.pname}"; -in { +in +{ ###### interface - options = { - - services.squeezelite= { + options.services.squeezelite = { + enable = mkEnableOption "Squeezelite, a software Squeezebox emulator"; - enable = mkEnableOption "Squeezelite, a software Squeezebox emulator"; - - extraArguments = mkOption { - default = ""; - type = types.str; - description = '' - Additional command line arguments to pass to Squeezelite. - ''; - }; + pulseAudio = mkEnableOption "pulseaudio support"; + extraArguments = mkOption { + default = ""; + type = types.str; + description = '' + Additional command line arguments to pass to Squeezelite. + ''; }; - }; ###### implementation config = mkIf cfg.enable { - - systemd.services.squeezelite= { + systemd.services.squeezelite = { wantedBy = [ "multi-user.target" ]; after = [ "network.target" "sound.target" ]; description = "Software Squeezebox emulator"; serviceConfig = { DynamicUser = true; - ExecStart = "${pkgs.squeezelite}/bin/squeezelite -N ${dataDir}/player-name ${cfg.extraArguments}"; + ExecStart = "${bin} -N ${dataDir}/player-name ${cfg.extraArguments}"; StateDirectory = builtins.baseNameOf dataDir; SupplementaryGroups = "audio"; }; }; - }; - } diff --git a/nixos/modules/services/cluster/corosync/default.nix b/nixos/modules/services/cluster/corosync/default.nix new file mode 100644 index 0000000000000..b4144917feea9 --- /dev/null +++ b/nixos/modules/services/cluster/corosync/default.nix @@ -0,0 +1,112 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.corosync; +in +{ + # interface + options.services.corosync = { + enable = mkEnableOption "corosync"; + + package = mkOption { + type = types.package; + default = pkgs.corosync; + defaultText = literalExpression "pkgs.corosync"; + description = "Package that should be used for corosync."; + }; + + clusterName = mkOption { + type = types.str; + default = "nixcluster"; + description = "Name of the corosync cluster."; + }; + + extraOptions = mkOption { + type = with types; listOf str; + default = []; + description = "Additional options with which to start corosync."; + }; + + nodelist = mkOption { + description = "Corosync nodelist: all cluster members."; + default = []; + type = with types; listOf (submodule { + options = { + nodeid = mkOption { + type = int; + description = "Node ID number"; + }; + name = mkOption { + type = str; + description = "Node name"; + }; + ring_addrs = mkOption { + type = listOf str; + description = "List of addresses, one for each ring."; + }; + }; + }); + }; + }; + + # implementation + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + environment.etc."corosync/corosync.conf".text = '' + totem { + version: 2 + secauth: on + cluster_name: ${cfg.clusterName} + transport: knet + } + + nodelist { + ${concatMapStrings ({ nodeid, name, ring_addrs }: '' + node { + nodeid: ${toString nodeid} + name: ${name} + ${concatStrings (imap0 (i: addr: '' + ring${toString i}_addr: ${addr} + '') ring_addrs)} + } + '') cfg.nodelist} + } + + quorum { + # only corosync_votequorum is supported + provider: corosync_votequorum + wait_for_all: 0 + ${optionalString (builtins.length cfg.nodelist < 3) '' + two_node: 1 + ''} + } + + logging { + to_syslog: yes + } + ''; + + environment.etc."corosync/uidgid.d/root".text = '' + # allow pacemaker connection by root + uidgid { + uid: 0 + gid: 0 + } + ''; + + systemd.packages = [ cfg.package ]; + systemd.services.corosync = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + StateDirectory = "corosync"; + StateDirectoryMode = "0700"; + }; + }; + + environment.etc."sysconfig/corosync".text = lib.optionalString (cfg.extraOptions != []) '' + COROSYNC_OPTIONS="${lib.escapeShellArgs cfg.extraOptions}" + ''; + }; +} diff --git a/nixos/modules/services/cluster/hadoop/conf.nix b/nixos/modules/services/cluster/hadoop/conf.nix index 0caec5cfc203f..e3c26a0d5505d 100644 --- a/nixos/modules/services/cluster/hadoop/conf.nix +++ b/nixos/modules/services/cluster/hadoop/conf.nix @@ -1,6 +1,6 @@ { cfg, pkgs, lib }: let - propertyXml = name: value: '' + propertyXml = name: value: lib.optionalString (value != null) '' <property> <name>${name}</name> <value>${builtins.toString value}</value> @@ -29,16 +29,16 @@ let export HADOOP_LOG_DIR=/tmp/hadoop/$USER ''; in -pkgs.runCommand "hadoop-conf" {} '' +pkgs.runCommand "hadoop-conf" {} (with cfg; '' mkdir -p $out/ - cp ${siteXml "core-site.xml" cfg.coreSite}/* $out/ - cp ${siteXml "hdfs-site.xml" cfg.hdfsSite}/* $out/ - cp ${siteXml "mapred-site.xml" cfg.mapredSite}/* $out/ - cp ${siteXml "yarn-site.xml" cfg.yarnSite}/* $out/ - cp ${siteXml "httpfs-site.xml" cfg.httpfsSite}/* $out/ - cp ${cfgFile "container-executor.cfg" cfg.containerExecutorCfg}/* $out/ + cp ${siteXml "core-site.xml" (coreSite // coreSiteInternal)}/* $out/ + cp ${siteXml "hdfs-site.xml" (hdfsSiteDefault // hdfsSite // hdfsSiteInternal)}/* $out/ + cp ${siteXml "mapred-site.xml" (mapredSiteDefault // mapredSite)}/* $out/ + cp ${siteXml "yarn-site.xml" (yarnSiteDefault // yarnSite // yarnSiteInternal)}/* $out/ + cp ${siteXml "httpfs-site.xml" httpfsSite}/* $out/ + cp ${cfgFile "container-executor.cfg" containerExecutorCfg}/* $out/ cp ${pkgs.writeTextDir "hadoop-user-functions.sh" userFunctions}/* $out/ cp ${pkgs.writeTextDir "hadoop-env.sh" hadoopEnv}/* $out/ - cp ${cfg.log4jProperties} $out/log4j.properties - ${lib.concatMapStringsSep "\n" (dir: "cp -r ${dir}/* $out/") cfg.extraConfDirs} -'' + cp ${log4jProperties} $out/log4j.properties + ${lib.concatMapStringsSep "\n" (dir: "cp -r ${dir}/* $out/") extraConfDirs} +'') diff --git a/nixos/modules/services/cluster/hadoop/default.nix b/nixos/modules/services/cluster/hadoop/default.nix index a1a95fe31cac5..a4fdea81037c7 100644 --- a/nixos/modules/services/cluster/hadoop/default.nix +++ b/nixos/modules/services/cluster/hadoop/default.nix @@ -21,24 +21,50 @@ with lib; <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/core-default.xml"/> ''; }; + coreSiteInternal = mkOption { + default = {}; + type = types.attrsOf types.anything; + internal = true; + description = '' + Internal option to add configs to core-site.xml based on module options + ''; + }; - hdfsSite = mkOption { + hdfsSiteDefault = mkOption { default = { "dfs.namenode.rpc-bind-host" = "0.0.0.0"; + "dfs.namenode.http-address" = "0.0.0.0:9870"; + "dfs.namenode.servicerpc-bind-host" = "0.0.0.0"; + "dfs.namenode.http-bind-host" = "0.0.0.0"; }; type = types.attrsOf types.anything; + description = '' + Default options for hdfs-site.xml + ''; + }; + hdfsSite = mkOption { + default = {}; + type = types.attrsOf types.anything; example = literalExpression '' { "dfs.nameservices" = "namenode1"; } ''; description = '' - Hadoop hdfs-site.xml definition + Additional options and overrides for hdfs-site.xml <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-hdfs/hdfs-default.xml"/> ''; }; + hdfsSiteInternal = mkOption { + default = {}; + type = types.attrsOf types.anything; + internal = true; + description = '' + Internal option to add configs to hdfs-site.xml based on module options + ''; + }; - mapredSite = mkOption { + mapredSiteDefault = mkOption { default = { "mapreduce.framework.name" = "yarn"; "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}"; @@ -54,18 +80,25 @@ with lib; } ''; type = types.attrsOf types.anything; + description = '' + Default options for mapred-site.xml + ''; + }; + mapredSite = mkOption { + default = {}; + type = types.attrsOf types.anything; example = literalExpression '' - options.services.hadoop.mapredSite.default // { + { "mapreduce.map.java.opts" = "-Xmx900m -XX:+UseParallelGC"; } ''; description = '' - Hadoop mapred-site.xml definition + Additional options and overrides for mapred-site.xml <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/mapred-default.xml"/> ''; }; - yarnSite = mkOption { + yarnSiteDefault = mkOption { default = { "yarn.nodemanager.admin-env" = "PATH=$PATH"; "yarn.nodemanager.aux-services" = "mapreduce_shuffle"; @@ -77,19 +110,34 @@ with lib; "yarn.nodemanager.linux-container-executor.path" = "/run/wrappers/yarn-nodemanager/bin/container-executor"; "yarn.nodemanager.log-dirs" = "/var/log/hadoop/yarn/nodemanager"; "yarn.resourcemanager.bind-host" = "0.0.0.0"; - "yarn.resourcemanager.scheduler.class" = "org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler"; + "yarn.resourcemanager.scheduler.class" = "org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler"; }; type = types.attrsOf types.anything; + description = '' + Default options for yarn-site.xml + ''; + }; + yarnSite = mkOption { + default = {}; + type = types.attrsOf types.anything; example = literalExpression '' - options.services.hadoop.yarnSite.default // { + { "yarn.resourcemanager.hostname" = "''${config.networking.hostName}"; } ''; description = '' - Hadoop yarn-site.xml definition + Additional options and overrides for yarn-site.xml <link xlink:href="https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-common/yarn-default.xml"/> ''; }; + yarnSiteInternal = mkOption { + default = {}; + type = types.attrsOf types.anything; + internal = true; + description = '' + Internal option to add configs to yarn-site.xml based on module options + ''; + }; httpfsSite = mkOption { default = { }; @@ -123,6 +171,7 @@ with lib; "yarn.nodemanager.linux-container-executor.group"="hadoop"; "min.user.id"=1000; "feature.terminal.enabled"=1; + "feature.mount-cgroup.enabled" = 1; }; type = types.attrsOf types.anything; example = literalExpression '' @@ -148,6 +197,8 @@ with lib; description = "Directories containing additional config files to be added to HADOOP_CONF_DIR"; }; + gatewayRole.enable = mkEnableOption "gateway role for deploying hadoop configs"; + package = mkOption { type = types.package; default = pkgs.hadoop; @@ -157,20 +208,16 @@ with lib; }; - config = mkMerge [ - (mkIf (builtins.hasAttr "yarn" config.users.users || - builtins.hasAttr "hdfs" config.users.users || - builtins.hasAttr "httpfs" config.users.users) { - users.groups.hadoop = { - gid = config.ids.gids.hadoop; - }; - environment = { - systemPackages = [ cfg.package ]; - etc."hadoop-conf".source = let - hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/"; - in "${hadoopConf}"; - }; - }) - - ]; + config = mkIf cfg.gatewayRole.enable { + users.groups.hadoop = { + gid = config.ids.gids.hadoop; + }; + environment = { + systemPackages = [ cfg.package ]; + etc."hadoop-conf".source = let + hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/"; + in "${hadoopConf}"; + variables.HADOOP_CONF_DIR = "/etc/hadoop-conf/"; + }; + }; } diff --git a/nixos/modules/services/cluster/hadoop/hdfs.nix b/nixos/modules/services/cluster/hadoop/hdfs.nix index be667aa82d8a6..325a002ad32fc 100644 --- a/nixos/modules/services/cluster/hadoop/hdfs.nix +++ b/nixos/modules/services/cluster/hadoop/hdfs.nix @@ -1,191 +1,191 @@ -{ config, lib, pkgs, ...}: +{ config, lib, pkgs, ... }: with lib; let cfg = config.services.hadoop; + + # Config files for hadoop services hadoopConf = "${import ./conf.nix { inherit cfg pkgs lib; }}/"; - restartIfChanged = mkOption { - type = types.bool; - description = '' - Automatically restart the service on config change. - This can be set to false to defer restarts on clusters running critical applications. - Please consider the security implications of inadvertently running an older version, - and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option. - ''; - default = false; - }; + + # Generator for HDFS service options + hadoopServiceOption = { serviceName, firewallOption ? true, extraOpts ? null }: { + enable = mkEnableOption serviceName; + restartIfChanged = mkOption { + type = types.bool; + description = '' + Automatically restart the service on config change. + This can be set to false to defer restarts on clusters running critical applications. + Please consider the security implications of inadvertently running an older version, + and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option. + ''; + default = false; + }; + extraFlags = mkOption{ + type = with types; listOf str; + default = []; + description = "Extra command line flags to pass to ${serviceName}"; + example = [ + "-Dcom.sun.management.jmxremote" + "-Dcom.sun.management.jmxremote.port=8010" + ]; + }; + extraEnv = mkOption{ + type = with types; attrsOf str; + default = {}; + description = "Extra environment variables for ${serviceName}"; + }; + } // (optionalAttrs firewallOption { + openFirewall = mkOption { + type = types.bool; + default = false; + description = "Open firewall ports for ${serviceName}."; + }; + }) // (optionalAttrs (extraOpts != null) extraOpts); + + # Generator for HDFS service configs + hadoopServiceConfig = + { name + , serviceOptions ? cfg.hdfs."${toLower name}" + , description ? "Hadoop HDFS ${name}" + , User ? "hdfs" + , allowedTCPPorts ? [ ] + , preStart ? "" + , environment ? { } + , extraConfig ? { } + }: ( + + mkIf serviceOptions.enable ( mkMerge [{ + systemd.services."hdfs-${toLower name}" = { + inherit description preStart; + environment = environment // serviceOptions.extraEnv; + wantedBy = [ "multi-user.target" ]; + inherit (serviceOptions) restartIfChanged; + serviceConfig = { + inherit User; + SyslogIdentifier = "hdfs-${toLower name}"; + ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} ${toLower name} ${escapeShellArgs serviceOptions.extraFlags}"; + Restart = "always"; + }; + }; + + services.hadoop.gatewayRole.enable = true; + + networking.firewall.allowedTCPPorts = mkIf + ((builtins.hasAttr "openFirewall" serviceOptions) && serviceOptions.openFirewall) + allowedTCPPorts; + } extraConfig]) + ); + in { options.services.hadoop.hdfs = { - namenode = { - enable = mkEnableOption "Whether to run the HDFS NameNode"; + + namenode = hadoopServiceOption { serviceName = "HDFS NameNode"; } // { formatOnInit = mkOption { type = types.bool; default = false; description = '' - Format HDFS namenode on first start. This is useful for quickly spinning up ephemeral HDFS clusters with a single namenode. - For HA clusters, initialization involves multiple steps across multiple nodes. Follow [this guide](https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html) - to initialize an HA cluster manually. - ''; - }; - inherit restartIfChanged; - openFirewall = mkOption { - type = types.bool; - default = true; - description = '' - Open firewall ports for namenode - ''; - }; - }; - datanode = { - enable = mkEnableOption "Whether to run the HDFS DataNode"; - inherit restartIfChanged; - openFirewall = mkOption { - type = types.bool; - default = true; - description = '' - Open firewall ports for datanode + Format HDFS namenode on first start. This is useful for quickly spinning up + ephemeral HDFS clusters with a single namenode. + For HA clusters, initialization involves multiple steps across multiple nodes. + Follow this guide to initialize an HA cluster manually: + <link xlink:href="https://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html"/> ''; }; }; - journalnode = { - enable = mkEnableOption "Whether to run the HDFS JournalNode"; - inherit restartIfChanged; - openFirewall = mkOption { - type = types.bool; - default = true; - description = '' - Open firewall ports for journalnode - ''; + + datanode = hadoopServiceOption { serviceName = "HDFS DataNode"; } // { + dataDirs = mkOption { + default = null; + description = "Tier and path definitions for datanode storage."; + type = with types; nullOr (listOf (submodule { + options = { + type = mkOption { + type = enum [ "SSD" "DISK" "ARCHIVE" "RAM_DISK" ]; + description = '' + Storage types ([SSD]/[DISK]/[ARCHIVE]/[RAM_DISK]) for HDFS storage policies. + ''; + }; + path = mkOption { + type = path; + example = [ "/var/lib/hadoop/hdfs/dn" ]; + description = "Determines where on the local filesystem a data node should store its blocks."; + }; + }; + })); }; }; - zkfc = { - enable = mkEnableOption "Whether to run the HDFS ZooKeeper failover controller"; - inherit restartIfChanged; + + journalnode = hadoopServiceOption { serviceName = "HDFS JournalNode"; }; + + zkfc = hadoopServiceOption { + serviceName = "HDFS ZooKeeper failover controller"; + firewallOption = false; }; - httpfs = { - enable = mkEnableOption "Whether to run the HDFS HTTPfs server"; + + httpfs = hadoopServiceOption { serviceName = "HDFS JournalNode"; } // { tempPath = mkOption { type = types.path; default = "/tmp/hadoop/httpfs"; - description = '' - HTTPFS_TEMP path used by HTTPFS - ''; - }; - inherit restartIfChanged; - openFirewall = mkOption { - type = types.bool; - default = true; - description = '' - Open firewall ports for HTTPFS - ''; + description = "HTTPFS_TEMP path used by HTTPFS"; }; }; + }; config = mkMerge [ - (mkIf cfg.hdfs.namenode.enable { - systemd.services.hdfs-namenode = { - description = "Hadoop HDFS NameNode"; - wantedBy = [ "multi-user.target" ]; - inherit (cfg.hdfs.namenode) restartIfChanged; - - preStart = (mkIf cfg.hdfs.namenode.formatOnInit '' - ${cfg.package}/bin/hdfs --config ${hadoopConf} namenode -format -nonInteractive || true - ''); - - serviceConfig = { - User = "hdfs"; - SyslogIdentifier = "hdfs-namenode"; - ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} namenode"; - Restart = "always"; - }; - }; - - networking.firewall.allowedTCPPorts = (mkIf cfg.hdfs.namenode.openFirewall [ + (hadoopServiceConfig { + name = "NameNode"; + allowedTCPPorts = [ 9870 # namenode.http-address 8020 # namenode.rpc-address - 8022 # namenode. servicerpc-address - ]); + 8022 # namenode.servicerpc-address + 8019 # dfs.ha.zkfc.port + ]; + preStart = (mkIf cfg.hdfs.namenode.formatOnInit + "${cfg.package}/bin/hdfs --config ${hadoopConf} namenode -format -nonInteractive || true" + ); }) - (mkIf cfg.hdfs.datanode.enable { - systemd.services.hdfs-datanode = { - description = "Hadoop HDFS DataNode"; - wantedBy = [ "multi-user.target" ]; - inherit (cfg.hdfs.datanode) restartIfChanged; - - serviceConfig = { - User = "hdfs"; - SyslogIdentifier = "hdfs-datanode"; - ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} datanode"; - Restart = "always"; - }; - }; - networking.firewall.allowedTCPPorts = (mkIf cfg.hdfs.datanode.openFirewall [ + (hadoopServiceConfig { + name = "DataNode"; + # port numbers for datanode changed between hadoop 2 and 3 + allowedTCPPorts = if versionAtLeast cfg.package.version "3" then [ 9864 # datanode.http.address 9866 # datanode.address 9867 # datanode.ipc.address - ]); + ] else [ + 50075 # datanode.http.address + 50010 # datanode.address + 50020 # datanode.ipc.address + ]; + extraConfig.services.hadoop.hdfsSiteInternal."dfs.datanode.data.dir" = let d = cfg.hdfs.datanode.dataDirs; in + if (d!= null) then (concatMapStringsSep "," (x: "["+x.type+"]file://"+x.path) cfg.hdfs.datanode.dataDirs) else d; }) - (mkIf cfg.hdfs.journalnode.enable { - systemd.services.hdfs-journalnode = { - description = "Hadoop HDFS JournalNode"; - wantedBy = [ "multi-user.target" ]; - inherit (cfg.hdfs.journalnode) restartIfChanged; - - serviceConfig = { - User = "hdfs"; - SyslogIdentifier = "hdfs-journalnode"; - ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} journalnode"; - Restart = "always"; - }; - }; - networking.firewall.allowedTCPPorts = (mkIf cfg.hdfs.journalnode.openFirewall [ + (hadoopServiceConfig { + name = "JournalNode"; + allowedTCPPorts = [ 8480 # dfs.journalnode.http-address 8485 # dfs.journalnode.rpc-address - ]); + ]; }) - (mkIf cfg.hdfs.zkfc.enable { - systemd.services.hdfs-zkfc = { - description = "Hadoop HDFS ZooKeeper failover controller"; - wantedBy = [ "multi-user.target" ]; - inherit (cfg.hdfs.zkfc) restartIfChanged; - - serviceConfig = { - User = "hdfs"; - SyslogIdentifier = "hdfs-zkfc"; - ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} zkfc"; - Restart = "always"; - }; - }; - }) - (mkIf cfg.hdfs.httpfs.enable { - systemd.services.hdfs-httpfs = { - description = "Hadoop httpfs"; - wantedBy = [ "multi-user.target" ]; - inherit (cfg.hdfs.httpfs) restartIfChanged; - - environment.HTTPFS_TEMP = cfg.hdfs.httpfs.tempPath; - preStart = '' - mkdir -p $HTTPFS_TEMP - ''; + (hadoopServiceConfig { + name = "zkfc"; + description = "Hadoop HDFS ZooKeeper failover controller"; + }) - serviceConfig = { - User = "httpfs"; - SyslogIdentifier = "hdfs-httpfs"; - ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} httpfs"; - Restart = "always"; - }; - }; - networking.firewall.allowedTCPPorts = (mkIf cfg.hdfs.httpfs.openFirewall [ + (hadoopServiceConfig { + name = "HTTPFS"; + environment.HTTPFS_TEMP = cfg.hdfs.httpfs.tempPath; + preStart = "mkdir -p $HTTPFS_TEMP"; + User = "httpfs"; + allowedTCPPorts = [ 14000 # httpfs.http.port - ]); + ]; }) - (mkIf ( - cfg.hdfs.namenode.enable || cfg.hdfs.datanode.enable || cfg.hdfs.journalnode.enable || cfg.hdfs.zkfc.enable - ) { + + (mkIf cfg.gatewayRole.enable { users.users.hdfs = { description = "Hadoop HDFS user"; group = "hadoop"; @@ -199,5 +199,6 @@ in isSystemUser = true; }; }) + ]; } diff --git a/nixos/modules/services/cluster/hadoop/yarn.nix b/nixos/modules/services/cluster/hadoop/yarn.nix index 37c26ea10f76f..74e16bdec687a 100644 --- a/nixos/modules/services/cluster/hadoop/yarn.nix +++ b/nixos/modules/services/cluster/hadoop/yarn.nix @@ -13,23 +13,77 @@ let ''; default = false; }; + extraFlags = mkOption{ + type = with types; listOf str; + default = []; + description = "Extra command line flags to pass to the service"; + example = [ + "-Dcom.sun.management.jmxremote" + "-Dcom.sun.management.jmxremote.port=8010" + ]; + }; + extraEnv = mkOption{ + type = with types; attrsOf str; + default = {}; + description = "Extra environment variables"; + }; in { options.services.hadoop.yarn = { resourcemanager = { - enable = mkEnableOption "Whether to run the Hadoop YARN ResourceManager"; - inherit restartIfChanged; + enable = mkEnableOption "Hadoop YARN ResourceManager"; + inherit restartIfChanged extraFlags extraEnv; + openFirewall = mkOption { type = types.bool; - default = true; + default = false; description = '' Open firewall ports for resourcemanager ''; }; }; nodemanager = { - enable = mkEnableOption "Whether to run the Hadoop YARN NodeManager"; - inherit restartIfChanged; + enable = mkEnableOption "Hadoop YARN NodeManager"; + inherit restartIfChanged extraFlags extraEnv; + + resource = { + cpuVCores = mkOption { + description = "Number of vcores that can be allocated for containers."; + type = with types; nullOr ints.positive; + default = null; + }; + maximumAllocationVCores = mkOption { + description = "The maximum virtual CPU cores any container can be allocated."; + type = with types; nullOr ints.positive; + default = null; + }; + memoryMB = mkOption { + description = "Amount of physical memory, in MB, that can be allocated for containers."; + type = with types; nullOr ints.positive; + default = null; + }; + maximumAllocationMB = mkOption { + description = "The maximum physical memory any container can be allocated."; + type = with types; nullOr ints.positive; + default = null; + }; + }; + + useCGroups = mkOption { + type = types.bool; + default = true; + description = '' + Use cgroups to enforce resource limits on containers + ''; + }; + + localDir = mkOption { + description = "List of directories to store localized files in."; + type = with types; nullOr (listOf path); + example = [ "/var/lib/hadoop/yarn/nm" ]; + default = null; + }; + addBinBash = mkOption { type = types.bool; default = true; @@ -39,7 +93,7 @@ in }; openFirewall = mkOption { type = types.bool; - default = true; + default = false; description = '' Open firewall ports for nodemanager. Because containers can listen on any ephemeral port, TCP ports 1024–65535 will be opened. @@ -49,10 +103,7 @@ in }; config = mkMerge [ - (mkIf ( - cfg.yarn.resourcemanager.enable || cfg.yarn.nodemanager.enable - ) { - + (mkIf cfg.gatewayRole.enable { users.users.yarn = { description = "Hadoop YARN user"; group = "hadoop"; @@ -65,15 +116,19 @@ in description = "Hadoop YARN ResourceManager"; wantedBy = [ "multi-user.target" ]; inherit (cfg.yarn.resourcemanager) restartIfChanged; + environment = cfg.yarn.resourcemanager.extraEnv; serviceConfig = { User = "yarn"; SyslogIdentifier = "yarn-resourcemanager"; ExecStart = "${cfg.package}/bin/yarn --config ${hadoopConf} " + - " resourcemanager"; + " resourcemanager ${escapeShellArgs cfg.yarn.resourcemanager.extraFlags}"; Restart = "always"; }; }; + + services.hadoop.gatewayRole.enable = true; + networking.firewall.allowedTCPPorts = (mkIf cfg.yarn.resourcemanager.openFirewall [ 8088 # resourcemanager.webapp.address 8030 # resourcemanager.scheduler.address @@ -94,6 +149,7 @@ in description = "Hadoop YARN NodeManager"; wantedBy = [ "multi-user.target" ]; inherit (cfg.yarn.nodemanager) restartIfChanged; + environment = cfg.yarn.nodemanager.extraEnv; preStart = '' # create log dir @@ -101,8 +157,9 @@ in chown yarn:hadoop /var/log/hadoop/yarn/nodemanager # set up setuid container executor binary + umount /run/wrappers/yarn-nodemanager/cgroup/cpu || true rm -rf /run/wrappers/yarn-nodemanager/ || true - mkdir -p /run/wrappers/yarn-nodemanager/{bin,etc/hadoop} + mkdir -p /run/wrappers/yarn-nodemanager/{bin,etc/hadoop,cgroup/cpu} cp ${cfg.package}/lib/${cfg.package.untarDir}/bin/container-executor /run/wrappers/yarn-nodemanager/bin/ chgrp hadoop /run/wrappers/yarn-nodemanager/bin/container-executor chmod 6050 /run/wrappers/yarn-nodemanager/bin/container-executor @@ -114,11 +171,26 @@ in SyslogIdentifier = "yarn-nodemanager"; PermissionsStartOnly = true; ExecStart = "${cfg.package}/bin/yarn --config ${hadoopConf} " + - " nodemanager"; + " nodemanager ${escapeShellArgs cfg.yarn.nodemanager.extraFlags}"; Restart = "always"; }; }; + services.hadoop.gatewayRole.enable = true; + + services.hadoop.yarnSiteInternal = with cfg.yarn.nodemanager; { + "yarn.nodemanager.local-dirs" = localDir; + "yarn.scheduler.maximum-allocation-vcores" = resource.maximumAllocationVCores; + "yarn.scheduler.maximum-allocation-mb" = resource.maximumAllocationMB; + "yarn.nodemanager.resource.cpu-vcores" = resource.cpuVCores; + "yarn.nodemanager.resource.memory-mb" = resource.memoryMB; + } // mkIf useCGroups { + "yarn.nodemanager.linux-container-executor.cgroups.hierarchy" = "/hadoop-yarn"; + "yarn.nodemanager.linux-container-executor.resources-handler.class" = "org.apache.hadoop.yarn.server.nodemanager.util.CgroupsLCEResourcesHandler"; + "yarn.nodemanager.linux-container-executor.cgroups.mount" = "true"; + "yarn.nodemanager.linux-container-executor.cgroups.mount-path" = "/run/wrappers/yarn-nodemanager/cgroup"; + }; + networking.firewall.allowedTCPPortRanges = [ (mkIf (cfg.yarn.nodemanager.openFirewall) {from = 1024; to = 65535;}) ]; diff --git a/nixos/modules/services/cluster/pacemaker/default.nix b/nixos/modules/services/cluster/pacemaker/default.nix new file mode 100644 index 0000000000000..7eeadffcc586b --- /dev/null +++ b/nixos/modules/services/cluster/pacemaker/default.nix @@ -0,0 +1,52 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.services.pacemaker; +in +{ + # interface + options.services.pacemaker = { + enable = mkEnableOption "pacemaker"; + + package = mkOption { + type = types.package; + default = pkgs.pacemaker; + defaultText = literalExpression "pkgs.pacemaker"; + description = "Package that should be used for pacemaker."; + }; + }; + + # implementation + config = mkIf cfg.enable { + assertions = [ { + assertion = config.services.corosync.enable; + message = '' + Enabling services.pacemaker requires a services.corosync configuration. + ''; + } ]; + + environment.systemPackages = [ cfg.package ]; + + # required by pacemaker + users.users.hacluster = { + isSystemUser = true; + group = "pacemaker"; + home = "/var/lib/pacemaker"; + }; + users.groups.pacemaker = {}; + + systemd.tmpfiles.rules = [ + "d /var/log/pacemaker 0700 hacluster pacemaker -" + ]; + + systemd.packages = [ cfg.package ]; + systemd.services.pacemaker = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + StateDirectory = "pacemaker"; + StateDirectoryMode = "0700"; + }; + }; + }; +} diff --git a/nixos/modules/services/continuous-integration/github-runner.nix b/nixos/modules/services/continuous-integration/github-runner.nix index c3bd8f99c57ff..a7645e1f56e93 100644 --- a/nixos/modules/services/continuous-integration/github-runner.nix +++ b/nixos/modules/services/continuous-integration/github-runner.nix @@ -34,6 +34,14 @@ in Repository to add the runner to. Changing this option triggers a new runner registration. + + IMPORTANT: If your token is org-wide (not per repository), you need to + provide a github org link, not a single repository, so do it like this + <literal>https://github.com/nixos</literal>, not like this + <literal>https://github.com/nixos/nixpkgs</literal>. + Otherwise, you are going to get a <literal>404 NotFound</literal> + from <literal>POST https://api.github.com/actions/runner-registration</literal> + in the configure script. ''; example = "https://github.com/nixos/nixpkgs"; }; diff --git a/nixos/modules/services/matrix/matrix-synapse.xml b/nixos/modules/services/matrix/matrix-synapse.xml index cdc4b4de1a739..cf33957d58ece 100644 --- a/nixos/modules/services/matrix/matrix-synapse.xml +++ b/nixos/modules/services/matrix/matrix-synapse.xml @@ -119,7 +119,7 @@ in { <link linkend="opt-services.matrix-synapse.settings.listeners">listeners</link> = [ { <link linkend="opt-services.matrix-synapse.settings.listeners._.port">port</link> = 8008; - <link linkend="opt-services.matrix-synapse.settings.listeners._.bind_addresses">bind_address</link> = [ "::1" ]; + <link linkend="opt-services.matrix-synapse.settings.listeners._.bind_addresses">bind_addresses</link> = [ "::1" ]; <link linkend="opt-services.matrix-synapse.settings.listeners._.type">type</link> = "http"; <link linkend="opt-services.matrix-synapse.settings.listeners._.tls">tls</link> = false; <link linkend="opt-services.matrix-synapse.settings.listeners._.x_forwarded">x_forwarded</link> = true; @@ -152,10 +152,10 @@ in { <para> If you want to run a server with public registration by anybody, you can - then enable <literal><link linkend="opt-services.matrix-synapse.settings.enable_registration">services.matrix-synapse.enable_registration</link> = + then enable <literal><link linkend="opt-services.matrix-synapse.settings.enable_registration">services.matrix-synapse.settings.enable_registration</link> = true;</literal>. Otherwise, or you can generate a registration secret with <command>pwgen -s 64 1</command> and set it with - <option><link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">services.matrix-synapse.registration_shared_secret</link></option>. + <option><link linkend="opt-services.matrix-synapse.settings.registration_shared_secret">services.matrix-synapse.settings.registration_shared_secret</link></option>. To create a new user or admin, run the following after you have set the secret and have rebuilt NixOS: <screen> diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix index b9d54f27edc21..04cf82f8a46bb 100644 --- a/nixos/modules/services/misc/jellyfin.nix +++ b/nixos/modules/services/misc/jellyfin.nix @@ -70,10 +70,12 @@ in LockPersonality = true; PrivateTmp = true; - PrivateDevices = true; + # Disabled to allow Jellyfin to access hw accel devices endpoints + # PrivateDevices = true; PrivateUsers = true; - ProtectClock = true; + # Disabled as it does not allow Jellyfin to interface with CUDA devices + # ProtectClock = true; ProtectControlGroups = true; ProtectHostname = true; ProtectKernelLogs = true; @@ -84,7 +86,7 @@ in RestrictNamespaces = true; # AF_NETLINK needed because Jellyfin monitors the network connection - RestrictAddressFamilies = [ "AF_NETLINK" "AF_INET" "AF_INET6" ]; + RestrictAddressFamilies = [ "AF_NETLINK" "AF_INET" "AF_INET6" "AF_UNIX" ]; RestrictRealtime = true; RestrictSUIDSGID = true; diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix index 2b21df91b82f7..d56808c7564ea 100644 --- a/nixos/modules/services/misc/nix-daemon.nix +++ b/nixos/modules/services/misc/nix-daemon.nix @@ -112,11 +112,11 @@ in { imports = [ - (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ]) - (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ]) - (mkRenamedOptionModule [ "nix" "daemonIONiceLevel" ] [ "nix" "daemonIOSchedPriority" ]) + (mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "useChroot" ]; to = [ "nix" "useSandbox" ]; }) + (mkRenamedOptionModuleWith { sinceRelease = 2003; from = [ "nix" "chrootDirs" ]; to = [ "nix" "sandboxPaths" ]; }) + (mkRenamedOptionModuleWith { sinceRelease = 2205; from = [ "nix" "daemonIONiceLevel" ]; to = [ "nix" "daemonIOSchedPriority" ]; }) (mkRemovedOptionModule [ "nix" "daemonNiceLevel" ] "Consider nix.daemonCPUSchedPolicy instead.") - ] ++ mapAttrsToList (oldConf: newConf: mkRenamedOptionModule [ "nix" oldConf ] [ "nix" "settings" newConf ]) legacyConfMappings; + ] ++ mapAttrsToList (oldConf: newConf: mkRenamedOptionModuleWith { sinceRelease = 2205; from = [ "nix" oldConf ]; to = [ "nix" "settings" newConf ]; }) legacyConfMappings; ###### interface diff --git a/nixos/modules/services/misc/paperless-ng.nix b/nixos/modules/services/misc/paperless-ng.nix index 44efc234a2b31..11e44f5ece575 100644 --- a/nixos/modules/services/misc/paperless-ng.nix +++ b/nixos/modules/services/misc/paperless-ng.nix @@ -214,6 +214,8 @@ in User = cfg.user; ExecStart = "${cfg.package}/bin/paperless-ng qcluster"; Restart = "on-failure"; + # The `mbind` syscall is needed for running the classifier. + SystemCallFilter = defaultServiceConfig.SystemCallFilter ++ [ "mbind" ]; }; environment = env; wantedBy = [ "multi-user.target" ]; diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix index d29d50706ef60..41302d6d3ceb3 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters.nix @@ -29,6 +29,7 @@ let "blackbox" "buildkite-agent" "collectd" + "dmarc" "dnsmasq" "domain" "dovecot" @@ -55,6 +56,7 @@ let "postfix" "postgres" "process" + "pve" "py-air-control" "redis" "rspamd" diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dmarc.nix b/nixos/modules/services/monitoring/prometheus/exporters/dmarc.nix new file mode 100644 index 0000000000000..330610a15d9e0 --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/dmarc.nix @@ -0,0 +1,117 @@ +{ config, lib, pkgs, options }: + +with lib; + +let + cfg = config.services.prometheus.exporters.dmarc; + + json = builtins.toJSON { + inherit (cfg) folders port; + listen_addr = cfg.listenAddress; + storage_path = "$STATE_DIRECTORY"; + imap = (builtins.removeAttrs cfg.imap [ "passwordFile" ]) // { password = "$IMAP_PASSWORD"; use_ssl = true; }; + poll_interval_seconds = cfg.pollIntervalSeconds; + deduplication_max_seconds = cfg.deduplicationMaxSeconds; + logging = { + version = 1; + disable_existing_loggers = false; + }; + }; +in { + port = 9797; + extraOpts = { + imap = { + host = mkOption { + type = types.str; + default = "localhost"; + description = '' + Hostname of IMAP server to connect to. + ''; + }; + port = mkOption { + type = types.port; + default = 993; + description = '' + Port of the IMAP server to connect to. + ''; + }; + username = mkOption { + type = types.str; + example = "postmaster@example.org"; + description = '' + Login username for the IMAP connection. + ''; + }; + passwordFile = mkOption { + type = types.str; + example = "/run/secrets/dovecot_pw"; + description = '' + File containing the login password for the IMAP connection. + ''; + }; + }; + folders = { + inbox = mkOption { + type = types.str; + default = "INBOX"; + description = '' + IMAP mailbox that is checked for incoming DMARC aggregate reports + ''; + }; + done = mkOption { + type = types.str; + default = "Archive"; + description = '' + IMAP mailbox that successfully processed reports are moved to. + ''; + }; + error = mkOption { + type = types.str; + default = "Invalid"; + description = '' + IMAP mailbox that emails are moved to that could not be processed. + ''; + }; + }; + pollIntervalSeconds = mkOption { + type = types.ints.unsigned; + default = 60; + description = '' + How often to poll the IMAP server in seconds. + ''; + }; + deduplicationMaxSeconds = mkOption { + type = types.ints.unsigned; + default = 604800; + defaultText = "7 days (in seconds)"; + description = '' + How long individual report IDs will be remembered to avoid + counting double delivered reports twice. + ''; + }; + debug = mkOption { + type = types.bool; + default = false; + description = '' + Whether to declare enable <literal>--debug</literal>. + ''; + }; + }; + serviceOpts = { + path = with pkgs; [ envsubst coreutils ]; + serviceConfig = { + StateDirectory = "prometheus-dmarc-exporter"; + WorkingDirectory = "/var/lib/prometheus-dmarc-exporter"; + ExecStart = "${pkgs.writeShellScript "setup-cfg" '' + export IMAP_PASSWORD="$(<${cfg.imap.passwordFile})" + envsubst \ + -i ${pkgs.writeText "dmarc-exporter.json.template" json} \ + -o ''${STATE_DIRECTORY}/dmarc-exporter.json + + exec ${pkgs.prometheus-dmarc-exporter}/bin/prometheus-dmarc-exporter \ + --configuration /var/lib/prometheus-dmarc-exporter/dmarc-exporter.json \ + ${optionalString cfg.debug "--debug"} + ''}"; + }; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix new file mode 100644 index 0000000000000..ef708414c95ef --- /dev/null +++ b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix @@ -0,0 +1,118 @@ +{ config, lib, pkgs, options }: + +with lib; +let + cfg = config.services.prometheus.exporters.pve; + + # pve exporter requires a config file so create an empty one if configFile is not provided + emptyConfigFile = pkgs.writeTextFile { + name = "pve.yml"; + text = "default:"; + }; + + computedConfigFile = "${if cfg.configFile == null then emptyConfigFile else cfg.configFile}"; +in +{ + port = 9221; + extraOpts = { + package = mkOption { + type = types.package; + default = pkgs.prometheus-pve-exporter; + defaultText = literalExpression "pkgs.prometheus-pve-exporter"; + example = literalExpression "pkgs.prometheus-pve-exporter"; + description = '' + The package to use for prometheus-pve-exporter + ''; + }; + + environmentFile = mkOption { + type = with types; nullOr path; + default = null; + example = "/etc/prometheus-pve-exporter/pve.env"; + description = '' + Path to the service's environment file. This path can either be a computed path in /nix/store or a path in the local filesystem. + + The environment file should NOT be stored in /nix/store as it contains passwords and/or keys in plain text. + + Environment reference: https://github.com/prometheus-pve/prometheus-pve-exporter#authentication + ''; + }; + + configFile = mkOption { + type = with types; nullOr path; + default = null; + example = "/etc/prometheus-pve-exporter/pve.yml"; + description = '' + Path to the service's config file. This path can either be a computed path in /nix/store or a path in the local filesystem. + + The config file should NOT be stored in /nix/store as it will contain passwords and/or keys in plain text. + + If both configFile and environmentFile are provided, the configFile option will be ignored. + + Configuration reference: https://github.com/prometheus-pve/prometheus-pve-exporter/#authentication + ''; + }; + + collectors = { + status = mkOption { + type = types.bool; + default = true; + description = '' + Collect Node/VM/CT status + ''; + }; + version = mkOption { + type = types.bool; + default = true; + description = '' + Collect PVE version info + ''; + }; + node = mkOption { + type = types.bool; + default = true; + description = '' + Collect PVE node info + ''; + }; + cluster = mkOption { + type = types.bool; + default = true; + description = '' + Collect PVE cluster info + ''; + }; + resources = mkOption { + type = types.bool; + default = true; + description = '' + Collect PVE resources info + ''; + }; + config = mkOption { + type = types.bool; + default = true; + description = '' + Collect PVE onboot status + ''; + }; + }; + }; + serviceOpts = { + serviceConfig = { + ExecStart = '' + ${cfg.package}/bin/pve_exporter \ + --${if cfg.collectors.status == true then "" else "no-"}collector.status \ + --${if cfg.collectors.version == true then "" else "no-"}collector.version \ + --${if cfg.collectors.node == true then "" else "no-"}collector.node \ + --${if cfg.collectors.cluster == true then "" else "no-"}collector.cluster \ + --${if cfg.collectors.resources == true then "" else "no-"}collector.resources \ + --${if cfg.collectors.config == true then "" else "no-"}collector.config \ + ${computedConfigFile} \ + ${toString cfg.port} ${cfg.listenAddress} + ''; + } // optionalAttrs (cfg.environmentFile != null) { + EnvironmentFile = cfg.environmentFile; + }; + }; +} diff --git a/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix b/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix index c0a50f07d7171..2edd1de83e1bf 100644 --- a/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix +++ b/nixos/modules/services/monitoring/prometheus/exporters/systemd.nix @@ -11,7 +11,7 @@ in { serviceConfig = { ExecStart = '' ${pkgs.prometheus-systemd-exporter}/bin/systemd_exporter \ - --web.listen-address ${cfg.listenAddress}:${toString cfg.port} + --web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags} ''; RestrictAddressFamilies = [ # Need AF_UNIX to collect data diff --git a/nixos/modules/services/networking/amuled.nix b/nixos/modules/services/networking/amuled.nix index e55ac7a6b18b7..aa72a047526b0 100644 --- a/nixos/modules/services/networking/amuled.nix +++ b/nixos/modules/services/networking/amuled.nix @@ -76,7 +76,7 @@ in script = '' ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${user} \ - -c 'HOME="${cfg.dataDir}" ${pkgs.amuleDaemon}/bin/amuled' + -c 'HOME="${cfg.dataDir}" ${pkgs.amule-daemon}/bin/amuled' ''; }; }; diff --git a/nixos/modules/services/networking/nbd.nix b/nixos/modules/services/networking/nbd.nix new file mode 100644 index 0000000000000..87f8c41a8e5cb --- /dev/null +++ b/nixos/modules/services/networking/nbd.nix @@ -0,0 +1,146 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.nbd; + configFormat = pkgs.formats.ini { }; + iniFields = with types; attrsOf (oneOf [ bool int float str ]); + serverConfig = configFormat.generate "nbd-server-config" + ({ + generic = + (cfg.server.extraOptions // { + user = "root"; + group = "root"; + port = cfg.server.listenPort; + } // (optionalAttrs (cfg.server.listenAddress != null) { + listenaddr = cfg.server.listenAddress; + })); + } + // (mapAttrs + (_: { path, allowAddresses, extraOptions }: + extraOptions // { + exportname = path; + } // (optionalAttrs (allowAddresses != null) { + authfile = pkgs.writeText "authfile" (concatStringsSep "\n" allowAddresses); + })) + cfg.server.exports) + ); + splitLists = + partition + (path: hasPrefix "/dev/" path) + (mapAttrsToList (_: { path, ... }: path) cfg.server.exports); + allowedDevices = splitLists.right; + boundPaths = splitLists.wrong; +in +{ + options = { + services.nbd = { + server = { + enable = mkEnableOption "the Network Block Device (nbd) server"; + + listenPort = mkOption { + type = types.port; + default = 10809; + description = "Port to listen on. The port is NOT automatically opened in the firewall."; + }; + + extraOptions = mkOption { + type = iniFields; + default = { + allowlist = false; + }; + description = '' + Extra options for the server. See + <citerefentry><refentrytitle>nbd-server</refentrytitle> + <manvolnum>5</manvolnum></citerefentry>. + ''; + }; + + exports = mkOption { + description = "Files or block devices to make available over the network."; + default = { }; + type = with types; attrsOf + (submodule { + options = { + path = mkOption { + type = str; + description = "File or block device to export."; + example = "/dev/sdb1"; + }; + + allowAddresses = mkOption { + type = nullOr (listOf str); + default = null; + example = [ "10.10.0.0/24" "127.0.0.1" ]; + description = "IPs and subnets that are authorized to connect for this device. If not specified, the server will allow all connections."; + }; + + extraOptions = mkOption { + type = iniFields; + default = { + flush = true; + fua = true; + }; + description = '' + Extra options for this export. See + <citerefentry><refentrytitle>nbd-server</refentrytitle> + <manvolnum>5</manvolnum></citerefentry>. + ''; + }; + }; + }); + }; + + listenAddress = mkOption { + type = with types; nullOr str; + description = "Address to listen on. If not specified, the server will listen on all interfaces."; + default = null; + example = "10.10.0.1"; + }; + }; + }; + }; + + config = mkIf cfg.server.enable { + boot.kernelModules = [ "nbd" ]; + + systemd.services.nbd-server = { + after = [ "network-online.target" ]; + before = [ "multi-user.target" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${pkgs.nbd}/bin/nbd-server -C ${serverConfig}"; + Type = "forking"; + + DeviceAllow = map (path: "${path} rw") allowedDevices; + BindPaths = boundPaths; + + CapabilityBoundingSet = ""; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = false; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "noaccess"; + ProtectSystem = "strict"; + RestrictAddressFamilies = "AF_INET AF_INET6"; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + UMask = "0077"; + }; + }; + }; +} diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix index cf6c9661dc1b0..a51fc53453424 100644 --- a/nixos/modules/services/networking/nsd.nix +++ b/nixos/modules/services/networking/nsd.nix @@ -194,19 +194,8 @@ let zone.children ); - # fighting infinite recursion - zoneOptions = zoneOptionsRaw // childConfig zoneOptions1 true; - zoneOptions1 = zoneOptionsRaw // childConfig zoneOptions2 false; - zoneOptions2 = zoneOptionsRaw // childConfig zoneOptions3 false; - zoneOptions3 = zoneOptionsRaw // childConfig zoneOptions4 false; - zoneOptions4 = zoneOptionsRaw // childConfig zoneOptions5 false; - zoneOptions5 = zoneOptionsRaw // childConfig zoneOptions6 false; - zoneOptions6 = zoneOptionsRaw // childConfig null false; - - childConfig = x: v: { options.children = { type = types.attrsOf x; visible = v; }; }; - # options are ordered alphanumerically - zoneOptionsRaw = types.submodule { + zoneOptions = types.submodule { options = { allowAXFRFallback = mkOption { @@ -246,6 +235,13 @@ let }; children = mkOption { + # TODO: This relies on the fact that `types.anything` doesn't set any + # values of its own to any defaults, because in the above zoneConfigs', + # values from children override ones from parents, but only if the + # attributes are defined. Because of this, we can't replace the element + # type here with `zoneConfigs`, since that would set all the attributes + # to default values, breaking the parent inheriting function. + type = types.attrsOf types.anything; default = {}; description = '' Children zones inherit all options of their parents. Attributes diff --git a/nixos/modules/services/networking/pleroma.nix b/nixos/modules/services/networking/pleroma.nix index 9b8382392c0a7..c6d4c14dcb7e2 100644 --- a/nixos/modules/services/networking/pleroma.nix +++ b/nixos/modules/services/networking/pleroma.nix @@ -1,6 +1,7 @@ { config, options, lib, pkgs, stdenv, ... }: let cfg = config.services.pleroma; + cookieFile = "/var/lib/pleroma/.cookie"; in { options = { services.pleroma = with lib; { @@ -8,7 +9,7 @@ in { package = mkOption { type = types.package; - default = pkgs.pleroma; + default = pkgs.pleroma.override { inherit cookieFile; }; defaultText = literalExpression "pkgs.pleroma"; description = "Pleroma package to use."; }; @@ -100,7 +101,6 @@ in { after = [ "network-online.target" "postgresql.service" ]; wantedBy = [ "multi-user.target" ]; restartTriggers = [ config.environment.etc."/pleroma/config.exs".source ]; - environment.RELEASE_COOKIE = "/var/lib/pleroma/.cookie"; serviceConfig = { User = cfg.user; Group = cfg.group; @@ -118,10 +118,10 @@ in { # Better be safe than sorry migration-wise. ExecStartPre = let preScript = pkgs.writers.writeBashBin "pleromaStartPre" '' - if [ ! -f /var/lib/pleroma/.cookie ] + if [ ! -f "${cookieFile}" ] || [ ! -s "${cookieFile}" ] then echo "Creating cookie file" - dd if=/dev/urandom bs=1 count=16 | hexdump -e '16/1 "%02x"' > /var/lib/pleroma/.cookie + dd if=/dev/urandom bs=1 count=16 | ${pkgs.hexdump}/bin/hexdump -e '16/1 "%02x"' > "${cookieFile}" fi ${cfg.package}/bin/pleroma_ctl migrate ''; diff --git a/nixos/modules/services/networking/tox-node.nix b/nixos/modules/services/networking/tox-node.nix index c24e7fd128508..c6e5c2d6e8190 100644 --- a/nixos/modules/services/networking/tox-node.nix +++ b/nixos/modules/services/networking/tox-node.nix @@ -8,12 +8,7 @@ let homeDir = "/var/lib/tox-node"; configFile = let - # fetchurl should be switched to getting this file from tox-node.src once - # the dpkg directory is in a release - src = pkgs.fetchurl { - url = "https://raw.githubusercontent.com/tox-rs/tox-node/master/dpkg/config.yml"; - sha256 = "1431wzpzm786mcvyzk1rp7ar418n45dr75hdggxvlm7pkpam31xa"; - }; + src = "${pkg.src}/dpkg/config.yml"; confJSON = pkgs.writeText "config.json" ( builtins.toJSON { log-type = cfg.logType; diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix index f6e9634909241..87873c8c1e83b 100644 --- a/nixos/modules/services/networking/unbound.nix +++ b/nixos/modules/services/networking/unbound.nix @@ -62,6 +62,7 @@ in { }; stateDir = mkOption { + type = types.path; default = "/var/lib/unbound"; description = "Directory holding all state for unbound to run."; }; diff --git a/nixos/modules/services/networking/vsftpd.nix b/nixos/modules/services/networking/vsftpd.nix index 710c2d9ca17b6..d205302051e14 100644 --- a/nixos/modules/services/networking/vsftpd.nix +++ b/nixos/modules/services/networking/vsftpd.nix @@ -153,6 +153,7 @@ in userlist = mkOption { default = []; + type = types.listOf types.str; description = "See <option>userlistFile</option>."; }; diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix index 4d35624241708..ce295bd4ba3bb 100644 --- a/nixos/modules/services/security/oauth2_proxy.nix +++ b/nixos/modules/services/security/oauth2_proxy.nix @@ -102,17 +102,19 @@ in # Taken from: https://github.com/oauth2-proxy/oauth2-proxy/blob/master/providers/providers.go provider = mkOption { type = types.enum [ - "google" + "adfs" "azure" + "bitbucket" + "digitalocean" "facebook" "github" - "keycloak" "gitlab" + "google" + "keycloak" + "keycloak-oidc" "linkedin" "login.gov" - "bitbucket" "nextcloud" - "digitalocean" "oidc" ]; default = "google"; diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix index cafb44e124298..a5822c02794d9 100644 --- a/nixos/modules/services/security/tor.nix +++ b/nixos/modules/services/security/tor.nix @@ -910,6 +910,11 @@ in ORPort = mkForce []; PublishServerDescriptor = mkForce false; }) + (mkIf (!cfg.client.enable) { + # Make sure application connections via SOCKS are disabled + # when services.tor.client.enable is false + SOCKSPort = mkForce [ 0 ]; + }) (mkIf cfg.client.enable ( { SOCKSPort = [ cfg.client.socksListenAddress ]; } // optionalAttrs cfg.client.transparentProxy.enable { @@ -962,7 +967,7 @@ in '') onion.authorizedClients ++ optional (onion.secretKey != null) '' install -d -o tor -g tor -m 0700 ${escapeShellArg onion.path} - key="$(cut -f1 -d: ${escapeShellArg onion.secretKey})" + key="$(cut -f1 -d: ${escapeShellArg onion.secretKey} | head -1)" case "$key" in ("== ed25519v"*"-secret") install -o tor -g tor -m 0400 ${escapeShellArg onion.secretKey} ${escapeShellArg onion.path}/hs_ed25519_secret_key;; @@ -1008,7 +1013,11 @@ in #InaccessiblePaths = [ "-+${runDir}/root" ]; UMask = "0066"; BindPaths = [ stateDir ]; - BindReadOnlyPaths = [ storeDir "/etc" ]; + BindReadOnlyPaths = [ storeDir "/etc" ] ++ + optionals config.services.resolved.enable [ + "/run/systemd/resolve/stub-resolv.conf" + "/run/systemd/resolve/resolv.conf" + ]; AmbientCapabilities = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE"; CapabilityBoundingSet = [""] ++ lib.optional bindsPrivilegedPort "CAP_NET_BIND_SERVICE"; # ProtectClock= adds DeviceAllow=char-rtc r diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix index b355df056bc19..ddd5bcebcdd5f 100644 --- a/nixos/modules/services/system/earlyoom.nix +++ b/nixos/modules/services/system/earlyoom.nix @@ -1,81 +1,73 @@ { config, lib, pkgs, ... }: -with lib; - let - ecfg = config.services.earlyoom; + cfg = config.services.earlyoom; + + inherit (lib) + mkDefault mkEnableOption mkIf mkOption types + mkRemovedOptionModule + concatStringsSep optional; + in { - options = { - services.earlyoom = { + options.services.earlyoom = { + enable = mkEnableOption "Early out of memory killing"; - enable = mkOption { - type = types.bool; - default = false; - description = '' - Enable early out of memory killing. - ''; - }; + freeMemThreshold = mkOption { + type = types.ints.between 1 100; + default = 10; + description = '' + Minimum of availabe memory (in percent). + If the free memory falls below this threshold and the analog is true for + <option>services.earlyoom.freeSwapThreshold</option> + the killing begins. + ''; + }; - freeMemThreshold = mkOption { - type = types.int; - default = 10; - description = '' - Minimum of availabe memory (in percent). - If the free memory falls below this threshold and the analog is true for - <option>services.earlyoom.freeSwapThreshold</option> - the killing begins. - ''; - }; + freeSwapThreshold = mkOption { + type = types.ints.between 1 100; + default = 10; + description = '' + Minimum of availabe swap space (in percent). + If the available swap space falls below this threshold and the analog + is true for <option>services.earlyoom.freeMemThreshold</option> + the killing begins. + ''; + }; - freeSwapThreshold = mkOption { - type = types.int; - default = 10; - description = '' - Minimum of availabe swap space (in percent). - If the available swap space falls below this threshold and the analog - is true for <option>services.earlyoom.freeMemThreshold</option> - the killing begins. - ''; - }; + # TODO: remove or warn after 1.7 (https://github.com/rfjakob/earlyoom/commit/7ebc4554) + ignoreOOMScoreAdjust = mkOption { + type = types.bool; + default = false; + description = '' + Ignore oom_score_adjust values of processes. + ''; + }; - # TODO: remove or warn after 1.7 (https://github.com/rfjakob/earlyoom/commit/7ebc4554) - ignoreOOMScoreAdjust = mkOption { - type = types.bool; - default = false; - description = '' - Ignore oom_score_adjust values of processes. - ''; - }; + enableDebugInfo = mkOption { + type = types.bool; + default = false; + description = '' + Enable debugging messages. + ''; + }; - enableDebugInfo = mkOption { - type = types.bool; - default = false; - description = '' - Enable debugging messages. - ''; - }; + enableNotifications = mkOption { + type = types.bool; + default = false; + description = '' + Send notifications about killed processes via the system d-bus. - notificationsCommand = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - This option is deprecated and ignored by earlyoom since 1.6. - Use <option>services.earlyoom.enableNotifications</option> instead. - ''; - }; + WARNING: enabling this option (while convenient) should *not* be done on a + machine where you do not trust the other users as it allows any other + local user to DoS your session by spamming notifications. - enableNotifications = mkOption { - type = types.bool; - default = false; - description = '' - Send notifications about killed processes via the system d-bus. - To actually see the notifications in your GUI session, you need to have - <literal>systembus-notify</literal> running as your user. + To actually see the notifications in your GUI session, you need to have + <literal>systembus-notify</literal> running as your user which this + option handles. - See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details. - ''; - }; + See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details. + ''; }; }; @@ -83,37 +75,30 @@ in (mkRemovedOptionModule [ "services" "earlyoom" "useKernelOOMKiller" ] '' This option is deprecated and ignored by earlyoom since 1.2. '') + (mkRemovedOptionModule [ "services" "earlyoom" "notificationsCommand" ] '' + This option is deprecated and ignored by earlyoom since 1.6. + '') ]; - config = mkIf ecfg.enable { - assertions = [ - { assertion = ecfg.freeMemThreshold > 0 && ecfg.freeMemThreshold <= 100; - message = "Needs to be a positive percentage"; } - { assertion = ecfg.freeSwapThreshold > 0 && ecfg.freeSwapThreshold <= 100; - message = "Needs to be a positive percentage"; } - ]; - - # TODO: reimplement this option as -N after 1.7 (https://github.com/rfjakob/earlyoom/commit/afe03606) - warnings = optional (ecfg.notificationsCommand != null) - "`services.earlyoom.notificationsCommand` is deprecated and ignored by earlyoom since 1.6."; + config = mkIf cfg.enable { + services.systembus-notify.enable = mkDefault cfg.enableNotifications; systemd.services.earlyoom = { description = "Early OOM Daemon for Linux"; wantedBy = [ "multi-user.target" ]; - path = optional ecfg.enableNotifications pkgs.dbus; + path = optional cfg.enableNotifications pkgs.dbus; serviceConfig = { - StandardOutput = "null"; StandardError = "journal"; ExecStart = concatStringsSep " " ([ "${pkgs.earlyoom}/bin/earlyoom" - "-m ${toString ecfg.freeMemThreshold}" - "-s ${toString ecfg.freeSwapThreshold}" - ] ++ optional ecfg.ignoreOOMScoreAdjust "-i" - ++ optional ecfg.enableDebugInfo "-d" - ++ optional ecfg.enableNotifications "-n"); + "-m ${toString cfg.freeMemThreshold}" + "-s ${toString cfg.freeSwapThreshold}" + ] + ++ optional cfg.ignoreOOMScoreAdjust "-i" + ++ optional cfg.enableDebugInfo "-d" + ++ optional cfg.enableNotifications "-n" + ); }; }; - - environment.systemPackages = optional ecfg.enableNotifications pkgs.systembus-notify; }; } diff --git a/nixos/modules/services/system/systembus-notify.nix b/nixos/modules/services/system/systembus-notify.nix new file mode 100644 index 0000000000000..e918bc552ece9 --- /dev/null +++ b/nixos/modules/services/system/systembus-notify.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.systembus-notify; + + inherit (lib) mkEnableOption mkIf; + +in +{ + options.services.systembus-notify = { + enable = mkEnableOption '' + System bus notification support + + WARNING: enabling this option (while convenient) should *not* be done on a + machine where you do not trust the other users as it allows any other + local user to DoS your session by spamming notifications. + ''; + }; + + config = mkIf cfg.enable { + systemd = { + packages = with pkgs; [ systembus-notify ]; + + user.services.systembus-notify.wantedBy = [ "graphical-session.target" ]; + }; + }; +} diff --git a/nixos/modules/services/video/epgstation/default.nix b/nixos/modules/services/video/epgstation/default.nix index 41613dcbb3ba4..191f6eb52e57e 100644 --- a/nixos/modules/services/video/epgstation/default.nix +++ b/nixos/modules/services/video/epgstation/default.nix @@ -1,30 +1,40 @@ { config, lib, options, pkgs, ... }: -with lib; - let cfg = config.services.epgstation; opt = options.services.epgstation; + description = "EPGStation: DVR system for Mirakurun-managed TV tuners"; + username = config.users.users.epgstation.name; groupname = config.users.users.epgstation.group; + mirakurun = { + sock = config.services.mirakurun.unixSocket; + option = options.services.mirakurun.unixSocket; + }; - settingsFmt = pkgs.formats.json {}; - settingsTemplate = settingsFmt.generate "config.json" cfg.settings; + yaml = pkgs.formats.yaml { }; + settingsTemplate = yaml.generate "config.yml" cfg.settings; preStartScript = pkgs.writeScript "epgstation-prestart" '' #!${pkgs.runtimeShell} - PASSWORD="$(head -n1 "${cfg.basicAuth.passwordFile}")" - DB_PASSWORD="$(head -n1 "${cfg.database.passwordFile}")" + DB_PASSWORD_FILE=${lib.escapeShellArg cfg.database.passwordFile} + + if [[ ! -f "$DB_PASSWORD_FILE" ]]; then + printf "[FATAL] File containing the DB password was not found in '%s'. Double check the NixOS option '%s'." \ + "$DB_PASSWORD_FILE" ${lib.escapeShellArg opt.database.passwordFile} >&2 + exit 1 + fi + + DB_PASSWORD="$(head -n1 ${lib.escapeShellArg cfg.database.passwordFile})" # setup configuration - touch /etc/epgstation/config.json - chmod 640 /etc/epgstation/config.json + touch /etc/epgstation/config.yml + chmod 640 /etc/epgstation/config.yml sed \ - -e "s,@password@,$PASSWORD,g" \ -e "s,@dbPassword@,$DB_PASSWORD,g" \ - ${settingsTemplate} > /etc/epgstation/config.json - chown "${username}:${groupname}" /etc/epgstation/config.json + ${settingsTemplate} > /etc/epgstation/config.yml + chown "${username}:${groupname}" /etc/epgstation/config.yml # NOTE: Use password authentication, since mysqljs does not yet support auth_socket if [ ! -e /var/lib/epgstation/db-created ]; then @@ -35,7 +45,7 @@ let ''; streamingConfig = lib.importJSON ./streaming.json; - logConfig = { + logConfig = yaml.generate "logConfig.yml" { appenders.stdout.type = "stdout"; categories = { default = { appenders = [ "stdout" ]; level = "info"; }; @@ -45,53 +55,51 @@ let }; }; - defaultPassword = "INSECURE_GO_CHECK_CONFIGURATION_NIX\n"; + # Deprecate top level options that are redundant. + deprecateTopLevelOption = config: + lib.mkRenamedOptionModule + ([ "services" "epgstation" ] ++ config) + ([ "services" "epgstation" "settings" ] ++ config); + + removeOption = config: instruction: + lib.mkRemovedOptionModule + ([ "services" "epgstation" ] ++ config) + instruction; in { - options.services.epgstation = { - enable = mkEnableOption "EPGStation: DTV Software in Japan"; + meta.maintainers = with lib.maintainers; [ midchildan ]; - usePreconfiguredStreaming = mkOption { - type = types.bool; - default = true; - description = '' - Use preconfigured default streaming options. + imports = [ + (deprecateTopLevelOption [ "port" ]) + (deprecateTopLevelOption [ "socketioPort" ]) + (deprecateTopLevelOption [ "clientSocketioPort" ]) + (removeOption [ "basicAuth" ] + "Use a TLS-terminated reverse proxy with authentication instead.") + ]; - Upstream defaults: - <link xlink:href="https://github.com/l3tnun/EPGStation/blob/master/config/config.sample.json"/> - ''; - }; + options.services.epgstation = { + enable = lib.mkEnableOption description; - port = mkOption { - type = types.port; - default = 20772; - description = '' - HTTP port for EPGStation to listen on. - ''; + package = lib.mkOption { + default = pkgs.epgstation; + type = lib.types.package; + defaultText = lib.literalExpression "pkgs.epgstation"; + description = "epgstation package to use"; }; - socketioPort = mkOption { - type = types.port; - default = cfg.port + 1; - defaultText = literalExpression "config.${opt.port} + 1"; + usePreconfiguredStreaming = lib.mkOption { + type = lib.types.bool; + default = true; description = '' - Socket.io port for EPGStation to listen on. - ''; - }; + Use preconfigured default streaming options. - clientSocketioPort = mkOption { - type = types.port; - default = cfg.socketioPort; - defaultText = literalExpression "config.${opt.socketioPort}"; - description = '' - Socket.io port that the web client is going to connect to. This may be - different from <option>socketioPort</option> if EPGStation is hidden - behind a reverse proxy. + Upstream defaults: + <link xlink:href="https://github.com/l3tnun/EPGStation/blob/master/config/config.yml.template"/> ''; }; - openFirewall = mkOption { - type = types.bool; + openFirewall = lib.mkOption { + type = lib.types.bool; default = false; description = '' Open ports in the firewall for the EPGStation web interface. @@ -106,50 +114,17 @@ in ''; }; - basicAuth = { - user = mkOption { - type = with types; nullOr str; - default = null; - example = "epgstation"; - description = '' - Basic auth username for EPGStation. If <literal>null</literal>, basic - auth will be disabled. - - <warning> - <para> - Basic authentication has known weaknesses, the most critical being - that it sends passwords over the network in clear text. Use this - feature to control access to EPGStation within your family and - friends, but don't rely on it for security. - </para> - </warning> - ''; - }; - - passwordFile = mkOption { - type = types.path; - default = pkgs.writeText "epgstation-password" defaultPassword; - defaultText = literalDocBook ''a file containing <literal>${defaultPassword}</literal>''; - example = "/run/keys/epgstation-password"; - description = '' - A file containing the password for <option>basicAuth.user</option>. - ''; - }; - }; - - database = { - name = mkOption { - type = types.str; + database = { + name = lib.mkOption { + type = lib.types.str; default = "epgstation"; description = '' Name of the MySQL database that holds EPGStation's data. ''; }; - passwordFile = mkOption { - type = types.path; - default = pkgs.writeText "epgstation-db-password" defaultPassword; - defaultText = literalDocBook ''a file containing <literal>${defaultPassword}</literal>''; + passwordFile = lib.mkOption { + type = lib.types.path; example = "/run/keys/epgstation-db-password"; description = '' A file containing the password for the database named @@ -158,69 +133,106 @@ in }; }; - settings = mkOption { + # The defaults for some options come from the upstream template + # configuration, which is the one that users would get if they follow the + # upstream instructions. This is, in some cases, different from the + # application defaults. Some options like encodeProcessNum and + # concurrentEncodeNum doesn't have an optimal default value that works for + # all hardware setups and/or performance requirements. For those kind of + # options, the application default wouldn't always result in the expected + # out-of-the-box behavior because it's the responsibility of the user to + # configure them according to their needs. In these cases, the value in the + # upstream template configuration should serve as a "good enough" default. + settings = lib.mkOption { description = '' - Options to add to config.json. + Options to add to config.yml. Documentation: <link xlink:href="https://github.com/l3tnun/EPGStation/blob/master/doc/conf-manual.md"/> ''; - default = {}; + default = { }; example = { recPriority = 20; conflictPriority = 10; }; - type = types.submodule { - freeformType = settingsFmt.type; + type = lib.types.submodule { + freeformType = yaml.type; + + options.port = lib.mkOption { + type = lib.types.port; + default = 20772; + description = '' + HTTP port for EPGStation to listen on. + ''; + }; - options.readOnlyOnce = mkOption { - type = types.bool; - default = false; - description = "Don't reload configuration files at runtime."; + options.socketioPort = lib.mkOption { + type = lib.types.port; + default = cfg.settings.port + 1; + defaultText = lib.literalExpression "config.${opt.settings}.port + 1"; + description = '' + Socket.io port for EPGStation to listen on. It is valid to share + ports with <option>${opt.settings}.port</option>. + ''; }; - options.mirakurunPath = mkOption (let - sockPath = config.services.mirakurun.unixSocket; - in { - type = types.str; - default = "http+unix://${replaceStrings ["/"] ["%2F"] sockPath}"; - defaultText = literalExpression '' - "http+unix://''${replaceStrings ["/"] ["%2F"] config.${options.services.mirakurun.unixSocket}}" + options.clientSocketioPort = lib.mkOption { + type = lib.types.port; + default = cfg.settings.socketioPort; + defaultText = lib.literalExpression "config.${opt.settings}.socketioPort"; + description = '' + Socket.io port that the web client is going to connect to. This may + be different from <option>${opt.settings}.socketioPort</option> if + EPGStation is hidden behind a reverse proxy. + ''; + }; + + options.mirakurunPath = with mirakurun; lib.mkOption { + type = lib.types.str; + default = "http+unix://${lib.replaceStrings ["/"] ["%2F"] sock}"; + defaultText = lib.literalExpression '' + "http+unix://''${lib.replaceStrings ["/"] ["%2F"] config.${option}}" ''; example = "http://localhost:40772"; description = "URL to connect to Mirakurun."; - }); + }; + + options.encodeProcessNum = lib.mkOption { + type = lib.types.ints.positive; + default = 4; + description = '' + The maximum number of processes that EPGStation would allow to run + at the same time for encoding or streaming videos. + ''; + }; + + options.concurrentEncodeNum = lib.mkOption { + type = lib.types.ints.positive; + default = 1; + description = '' + The maximum number of encoding jobs that EPGStation would run at the + same time. + ''; + }; - options.encode = mkOption { - type = with types; listOf attrs; + options.encode = lib.mkOption { + type = with lib.types; listOf attrs; description = "Encoding presets for recorded videos."; default = [ { - name = "H264"; - cmd = "${pkgs.epgstation}/libexec/enc.sh main"; + name = "H.264"; + cmd = "%NODE% ${cfg.package}/libexec/enc.js"; suffix = ".mp4"; - default = true; - } - { - name = "H264-sub"; - cmd = "${pkgs.epgstation}/libexec/enc.sh sub"; - suffix = "-sub.mp4"; } ]; - defaultText = literalExpression '' + defaultText = lib.literalExpression '' [ { - name = "H264"; - cmd = "''${pkgs.epgstation}/libexec/enc.sh main"; + name = "H.264"; + cmd = "%NODE% config.${opt.package}/libexec/enc.js"; suffix = ".mp4"; - default = true; - } - { - name = "H264-sub"; - cmd = "''${pkgs.epgstation}/libexec/enc.sh sub"; - suffix = "-sub.mp4"; } ] ''; @@ -229,14 +241,25 @@ in }; }; - config = mkIf cfg.enable { + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = !(lib.hasAttr "readOnlyOnce" cfg.settings); + message = '' + The option config.${opt.settings}.readOnlyOnce can no longer be used + since it's been removed. No replacements are available. + ''; + } + ]; + environment.etc = { - "epgstation/operatorLogConfig.json".text = builtins.toJSON logConfig; - "epgstation/serviceLogConfig.json".text = builtins.toJSON logConfig; + "epgstation/epgUpdaterLogConfig.yml".source = logConfig; + "epgstation/operatorLogConfig.yml".source = logConfig; + "epgstation/serviceLogConfig.yml".source = logConfig; }; - networking.firewall = mkIf cfg.openFirewall { - allowedTCPPorts = with cfg; [ port socketioPort ]; + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = with cfg.settings; [ port socketioPort ]; }; users.users.epgstation = { @@ -245,13 +268,13 @@ in isSystemUser = true; }; - users.groups.epgstation = {}; + users.groups.epgstation = { }; - services.mirakurun.enable = mkDefault true; + services.mirakurun.enable = lib.mkDefault true; services.mysql = { - enable = mkDefault true; - package = mkDefault pkgs.mariadb; + enable = lib.mkDefault true; + package = lib.mkDefault pkgs.mariadb; ensureDatabases = [ cfg.database.name ]; # FIXME: enable once mysqljs supports auth_socket # ensureUsers = [ { @@ -260,39 +283,28 @@ in # } ]; }; - services.epgstation.settings = let - defaultSettings = { - serverPort = cfg.port; - socketioPort = cfg.socketioPort; - clientSocketioPort = cfg.clientSocketioPort; - - dbType = mkDefault "mysql"; - mysql = { - user = username; - database = cfg.database.name; - socketPath = mkDefault "/run/mysqld/mysqld.sock"; - password = mkDefault "@dbPassword@"; - connectTimeout = mkDefault 1000; - connectionLimit = mkDefault 10; + services.epgstation.settings = + let + defaultSettings = { + dbtype = lib.mkDefault "mysql"; + mysql = { + socketPath = lib.mkDefault "/run/mysqld/mysqld.sock"; + user = username; + password = lib.mkDefault "@dbPassword@"; + database = cfg.database.name; + }; + + ffmpeg = lib.mkDefault "${pkgs.ffmpeg-full}/bin/ffmpeg"; + ffprobe = lib.mkDefault "${pkgs.ffmpeg-full}/bin/ffprobe"; + + # for disambiguation with TypeScript files + recordedFileExtension = lib.mkDefault ".m2ts"; }; - - basicAuth = mkIf (cfg.basicAuth.user != null) { - user = mkDefault cfg.basicAuth.user; - password = mkDefault "@password@"; - }; - - ffmpeg = mkDefault "${pkgs.ffmpeg-full}/bin/ffmpeg"; - ffprobe = mkDefault "${pkgs.ffmpeg-full}/bin/ffprobe"; - - fileExtension = mkDefault ".m2ts"; - maxEncode = mkDefault 2; - maxStreaming = mkDefault 2; - }; - in - mkMerge [ - defaultSettings - (mkIf cfg.usePreconfiguredStreaming streamingConfig) - ]; + in + lib.mkMerge [ + defaultSettings + (lib.mkIf cfg.usePreconfiguredStreaming streamingConfig) + ]; systemd.tmpfiles.rules = [ "d '/var/lib/epgstation/streamfiles' - ${username} ${groupname} - -" @@ -301,15 +313,15 @@ in ]; systemd.services.epgstation = { - description = pkgs.epgstation.meta.description; + inherit description; + wantedBy = [ "multi-user.target" ]; - after = [ - "network.target" - ] ++ optional config.services.mirakurun.enable "mirakurun.service" - ++ optional config.services.mysql.enable "mysql.service"; + after = [ "network.target" ] + ++ lib.optional config.services.mirakurun.enable "mirakurun.service" + ++ lib.optional config.services.mysql.enable "mysql.service"; serviceConfig = { - ExecStart = "${pkgs.epgstation}/bin/epgstation start"; + ExecStart = "${cfg.package}/bin/epgstation start"; ExecStartPre = "+${preStartScript}"; User = username; Group = groupname; diff --git a/nixos/modules/services/video/epgstation/streaming.json b/nixos/modules/services/video/epgstation/streaming.json index 8eb99cf85584b..7f8df0817fc3d 100644 --- a/nixos/modules/services/video/epgstation/streaming.json +++ b/nixos/modules/services/video/epgstation/streaming.json @@ -1,119 +1,140 @@ { - "liveHLS": [ - { - "name": "720p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -flags +loop-global_header %OUTPUT%" + "urlscheme": { + "m2ts": { + "ios": "vlc-x-callback://x-callback-url/stream?url=PROTOCOL://ADDRESS", + "android": "intent://ADDRESS#Intent;package=org.videolan.vlc;type=video;scheme=PROTOCOL;end" }, - { - "name": "480p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -flags +loop-global_header %OUTPUT%" + "video": { + "ios": "infuse://x-callback-url/play?url=PROTOCOL://ADDRESS", + "android": "intent://ADDRESS#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=PROTOCOL;end" }, - { - "name": "180p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 48k -ac 2 -c:v libx264 -vf yadif,scale=-2:180 -b:v 100k -preset veryfast -maxrate 110k -bufsize 1000k -flags +loop-global_header %OUTPUT%" + "download": { + "ios": "vlc-x-callback://x-callback-url/download?url=PROTOCOL://ADDRESS&filename=FILENAME" } - ], - "liveMP4": [ - { - "name": "720p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1" - }, - { - "name": "480p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1" - } - ], - "liveWebM": [ - { - "name": "720p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 3 -c:a libvorbis -ar 48000 -b:a 192k -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:720 -b:v 3000k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1" - }, - { - "name": "480p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 2 -c:a libvorbis -ar 48000 -b:a 128k -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:480 -b:v 1500k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1" - } - ], - "mpegTsStreaming": [ - { - "name": "720p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -y -f mpegts pipe:1" - }, - { - "name": "480p", - "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -y -f mpegts pipe:1" - }, - { - "name": "Original" - } - ], - "mpegTsViewer": { - "ios": "vlc-x-callback://x-callback-url/stream?url=http://ADDRESS", - "android": "intent://ADDRESS#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=http;end" - }, - "recordedDownloader": { - "ios": "vlc-x-callback://x-callback-url/download?url=http://ADDRESS&filename=FILENAME", - "android": "intent://ADDRESS#Intent;package=com.dv.adm;type=video;scheme=http;end" }, - "recordedStreaming": { - "webm": [ - { - "name": "720p", - "cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 3 -c:a libvorbis -ar 48000 -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:720 %VB% %VBUFFER% %AB% %ABUFFER% -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1", - "vb": "3000k", - "ab": "192k" - }, - { - "name": "360p", - "cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 2 -c:a libvorbis -ar 48000 -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:360 %VB% %VBUFFER% %AB% %ABUFFER% -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1", - "vb": "1500k", - "ab": "128k" - } - ], - "mp4": [ - { - "name": "720p", - "cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -ac 2 -c:v libx264 -vf yadif,scale=-2:720 %VB% %VBUFFER% %AB% %ABUFFER% -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1", - "vb": "3000k", - "ab": "192k" - }, - { - "name": "360p", - "cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -ac 2 -c:v libx264 -vf yadif,scale=-2:360 %VB% %VBUFFER% %AB% %ABUFFER% -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1", - "vb": "1500k", - "ab": "128k" + "stream": { + "live": { + "ts": { + "m2ts": [ + { + "name": "720p", + "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -y -f mpegts pipe:1" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -y -f mpegts pipe:1" + }, + { + "name": "無変換" + } + ], + "m2tsll": [ + { + "name": "720p", + "cmd": "%FFMPEG% -dual_mono_mode main -f mpegts -analyzeduration 500000 -i pipe:0 -map 0 -c:s copy -c:d copy -ignore_unknown -fflags nobuffer -flags low_delay -max_delay 250000 -max_interleave_delta 1 -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -flags +cgop -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -y -f mpegts pipe:1" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -dual_mono_mode main -f mpegts -analyzeduration 500000 -i pipe:0 -map 0 -c:s copy -c:d copy -ignore_unknown -fflags nobuffer -flags low_delay -max_delay 250000 -max_interleave_delta 1 -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -flags +cgop -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -y -f mpegts pipe:1" + } + ], + "webm": [ + { + "name": "720p", + "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 3 -c:a libvorbis -ar 48000 -b:a 192k -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:720 -b:v 3000k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 2 -c:a libvorbis -ar 48000 -b:a 128k -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:480 -b:v 1500k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1" + } + ], + "mp4": [ + { + "name": "720p", + "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1" + } + ], + "hls": [ + { + "name": "720p", + "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -map 0 -threads 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -flags +loop-global_header %OUTPUT%" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -re -dual_mono_mode main -i pipe:0 -sn -map 0 -threads 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 17 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -flags +loop-global_header %OUTPUT%" + } + ] } - ], - "mpegTs": [ - { - "name": "720p (H.264)", - "cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -ac 2 -c:v libx264 -vf yadif,scale=-2:720 %VB% %VBUFFER% %AB% %ABUFFER% -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -y -f mpegts pipe:1", - "vb": "3000k", - "ab": "192k" + }, + "recorded": { + "ts": { + "webm": [ + { + "name": "720p", + "cmd": "%FFMPEG% -dual_mono_mode main -i pipe:0 -sn -threads 3 -c:a libvorbis -ar 48000 -b:a 192k -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:720 -b:v 3000k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -dual_mono_mode main -i pipe:0 -sn -threads 3 -c:a libvorbis -ar 48000 -b:a 128k -ac 2 -c:v libvpx-vp9 -vf yadif,scale=-2:480 -b:v 1500k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1" + } + ], + "mp4": [ + { + "name": "720p", + "cmd": "%FFMPEG% -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -dual_mono_mode main -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1" + } + ], + "hls": [ + { + "name": "720p", + "cmd": "%FFMPEG% -dual_mono_mode main -i pipe:0 -sn -map 0 -threads 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -flags +loop-global_header %OUTPUT%" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -dual_mono_mode main -i pipe:0 -sn -map 0 -threads 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -flags +loop-global_header %OUTPUT%" + } + ] }, - { - "name": "360p (H.264)", - "cmd": "%FFMPEG% -dual_mono_mode main %RE% -i pipe:0 -sn -threads 0 -c:a aac -ar 48000 -ac 2 -c:v libx264 -vf yadif,scale=-2:360 %VB% %VBUFFER% %AB% %ABUFFER% -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -y -f mpegts pipe:1", - "vb": "1500k", - "ab": "128k" + "encoded": { + "webm": [ + { + "name": "720p", + "cmd": "%FFMPEG% -dual_mono_mode main -ss %SS% -i %INPUT% -sn -threads 3 -c:a libvorbis -ar 48000 -b:a 192k -ac 2 -c:v libvpx-vp9 -vf scale=-2:720 -b:v 3000k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -dual_mono_mode main -ss %SS% -i %INPUT% -sn -threads 3 -c:a libvorbis -ar 48000 -b:a 128k -ac 2 -c:v libvpx-vp9 -vf scale=-2:480 -b:v 1500k -deadline realtime -speed 4 -cpu-used -8 -y -f webm pipe:1" + } + ], + "mp4": [ + { + "name": "720p", + "cmd": "%FFMPEG% -dual_mono_mode main -ss %SS% -i %INPUT% -sn -threads 0 -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf scale=-2:720 -b:v 3000k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -dual_mono_mode main -ss %SS% -i %INPUT% -sn -threads 0 -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf scale=-2:480 -b:v 1500k -profile:v baseline -preset veryfast -tune fastdecode,zerolatency -movflags frag_keyframe+empty_moov+faststart+default_base_moof -y -f mp4 pipe:1" + } + ], + "hls": [ + { + "name": "720p", + "cmd": "%FFMPEG% -dual_mono_mode main -ss %SS% -i %INPUT% -sn -threads 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf scale=-2:720 -b:v 3000k -preset veryfast -flags +loop-global_header %OUTPUT%" + }, + { + "name": "480p", + "cmd": "%FFMPEG% -dual_mono_mode main -ss %SS% -i %INPUT% -sn -threads 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -hls_flags delete_segments -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf scale=-2:480 -b:v 3000k -preset veryfast -flags +loop-global_header %OUTPUT%" + } + ] } - ] - }, - "recordedHLS": [ - { - "name": "720p", - "cmd": "%FFMPEG% -dual_mono_mode main -i %INPUT% -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 192k -ac 2 -c:v libx264 -vf yadif,scale=-2:720 -b:v 3000k -preset veryfast -flags +loop-global_header %OUTPUT%" - }, - { - "name": "480p", - "cmd": "%FFMPEG% -dual_mono_mode main -i %INPUT% -sn -threads 0 -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_filename %streamFileDir%/stream%streamNum%-%09d.ts -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx264 -vf yadif,scale=-2:480 -b:v 1500k -preset veryfast -flags +loop-global_header %OUTPUT%" - }, - { - "name": "480p(h265)", - "cmd": "%FFMPEG% -dual_mono_mode main -i %INPUT% -sn -map 0 -ignore_unknown -max_muxing_queue_size 1024 -f hls -hls_time 3 -hls_list_size 0 -hls_allow_cache 1 -hls_segment_type fmp4 -hls_fmp4_init_filename stream%streamNum%-init.mp4 -hls_segment_filename stream%streamNum%-%09d.m4s -c:a aac -ar 48000 -b:a 128k -ac 2 -c:v libx265 -vf yadif,scale=-2:480 -b:v 350k -preset veryfast -tag:v hvc1 %OUTPUT%" } - ], - "recordedViewer": { - "ios": "infuse://x-callback-url/play?url=http://ADDRESS", - "android": "intent://ADDRESS#Intent;package=com.mxtech.videoplayer.ad;type=video;scheme=http;end" } } diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix index 141ab98e29bfc..b32220a5e5790 100644 --- a/nixos/modules/services/web-apps/nextcloud.nix +++ b/nixos/modules/services/web-apps/nextcloud.nix @@ -153,7 +153,7 @@ in { package = mkOption { type = types.package; description = "Which package to use for the Nextcloud instance."; - relatedPackages = [ "nextcloud21" "nextcloud22" "nextcloud23" ]; + relatedPackages = [ "nextcloud22" "nextcloud23" ]; }; phpPackage = mkOption { type = types.package; @@ -571,15 +571,6 @@ in { nextcloud defined in an overlay, please set `services.nextcloud.package` to `pkgs.nextcloud`. '' - # 21.03 will not be an official release - it was instead 21.05. - # This versionOlder statement remains set to 21.03 for backwards compatibility. - # See https://github.com/NixOS/nixpkgs/pull/108899 and - # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md. - # FIXME(@Ma27) remove this else-if as soon as 21.05 is EOL! This is only here - # to ensure that users who are on Nextcloud 19 with a stateVersion <21.05 with - # no explicit services.nextcloud.package don't upgrade to v21 by accident ( - # nextcloud20 throws an eval-error because it's dropped). - else if versionOlder stateVersion "21.03" then nextcloud20 else if versionOlder stateVersion "21.11" then nextcloud21 else if versionOlder stateVersion "22.05" then nextcloud22 else nextcloud23 diff --git a/nixos/modules/services/web-apps/plantuml-server.nix b/nixos/modules/services/web-apps/plantuml-server.nix index f4bf43f56b98a..9ea37b8a4cad4 100644 --- a/nixos/modules/services/web-apps/plantuml-server.nix +++ b/nixos/modules/services/web-apps/plantuml-server.nix @@ -20,6 +20,21 @@ in description = "PlantUML server package to use"; }; + packages = { + jdk = mkOption { + type = types.package; + default = pkgs.jdk; + defaultText = literalExpression "pkgs.jdk"; + description = "JDK package to use for the server"; + }; + jetty = mkOption { + type = types.package; + default = pkgs.jetty; + defaultText = literalExpression "pkgs.jetty"; + description = "Jetty package to use for the server"; + }; + }; + user = mkOption { type = types.str; default = "plantuml"; @@ -105,10 +120,10 @@ in ALLOW_PLANTUML_INCLUDE = if cfg.allowPlantumlInclude then "true" else "false"; }; script = '' - ${pkgs.jre}/bin/java \ - -jar ${pkgs.jetty}/start.jar \ + ${cfg.packages.jdk}/bin/java \ + -jar ${cfg.packages.jetty}/start.jar \ --module=deploy,http,jsp \ - jetty.home=${pkgs.jetty} \ + jetty.home=${cfg.packages.jetty} \ jetty.base=${cfg.package} \ jetty.http.host=${cfg.listenHost} \ jetty.http.port=${builtins.toString cfg.listenPort} diff --git a/nixos/modules/services/web-servers/pomerium.nix b/nixos/modules/services/web-servers/pomerium.nix index 2bc7d01c7c287..0b460755f50ef 100644 --- a/nixos/modules/services/web-servers/pomerium.nix +++ b/nixos/modules/services/web-servers/pomerium.nix @@ -69,11 +69,16 @@ in CERTIFICATE_KEY_FILE = "key.pem"; }; startLimitIntervalSec = 60; + script = '' + if [[ -v CREDENTIALS_DIRECTORY ]]; then + cd "$CREDENTIALS_DIRECTORY" + fi + exec "${pkgs.pomerium}/bin/pomerium" -config "${cfgFile}" + ''; serviceConfig = { DynamicUser = true; StateDirectory = [ "pomerium" ]; - ExecStart = "${pkgs.pomerium}/bin/pomerium -config ${cfgFile}"; PrivateUsers = false; # breaks CAP_NET_BIND_SERVICE MemoryDenyWriteExecute = false; # breaks LuaJIT @@ -99,7 +104,6 @@ in AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; - WorkingDirectory = mkIf (cfg.useACMEHost != null) "$CREDENTIALS_DIRECTORY"; LoadCredential = optionals (cfg.useACMEHost != null) [ "fullchain.pem:/var/lib/acme/${cfg.useACMEHost}/fullchain.pem" "key.pem:/var/lib/acme/${cfg.useACMEHost}/key.pem" @@ -124,7 +128,7 @@ in Type = "oneshot"; TimeoutSec = 60; ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active pomerium.service"; - ExecStart = "/run/current-system/systemd/bin/systemctl restart pomerium.service"; + ExecStart = "/run/current-system/systemd/bin/systemctl --no-block restart pomerium.service"; }; }; }); diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix index f9446fe125a34..877097cf37813 100644 --- a/nixos/modules/services/web-servers/tomcat.nix +++ b/nixos/modules/services/web-servers/tomcat.nix @@ -23,8 +23,8 @@ in package = mkOption { type = types.package; - default = pkgs.tomcat85; - defaultText = literalExpression "pkgs.tomcat85"; + default = pkgs.tomcat9; + defaultText = literalExpression "pkgs.tomcat9"; example = lib.literalExpression "pkgs.tomcat9"; description = '' Which tomcat package to use. @@ -127,7 +127,7 @@ in webapps = mkOption { type = types.listOf types.path; default = [ tomcat.webapps ]; - defaultText = literalExpression "[ pkgs.tomcat85.webapps ]"; + defaultText = literalExpression "[ config.services.tomcat.package.webapps ]"; description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat"; }; @@ -201,6 +201,7 @@ in { uid = config.ids.uids.tomcat; description = "Tomcat user"; home = "/homeless-shelter"; + group = "tomcat"; extraGroups = cfg.extraGroups; }; diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix index f8f47a0614524..a7fda4be97968 100644 --- a/nixos/modules/services/x11/desktop-managers/mate.nix +++ b/nixos/modules/services/x11/desktop-managers/mate.nix @@ -74,11 +74,9 @@ in # Debugging environment.sessionVariables.MATE_SESSION_DEBUG = mkIf cfg.debug "1"; - environment.systemPackages = - pkgs.mate.basePackages ++ - (pkgs.gnome.removePackagesByName - pkgs.mate.extraPackages - config.environment.mate.excludePackages) ++ + environment.systemPackages = pkgs.gnome.removePackagesByName + (pkgs.mate.basePackages ++ + pkgs.mate.extraPackages ++ [ pkgs.desktop-file-utils pkgs.glib @@ -87,7 +85,8 @@ in pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/ pkgs.mate.mate-settings-daemon pkgs.yelp # for 'Contents' in 'Help' menus - ]; + ]) + config.environment.mate.excludePackages; programs.dconf.enable = true; # Shell integration for VTE terminals diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix index 6a7d2a8aa6cd9..8ff9b0b756d3d 100644 --- a/nixos/modules/services/x11/desktop-managers/pantheon.nix +++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix @@ -227,6 +227,7 @@ in # Settings from elementary-default-settings environment.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini"; + xdg.portal.enable = true; xdg.portal.extraPortals = with pkgs.pantheon; [ elementary-files elementary-settings-daemon diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix index 92b3af8527f1b..a5db3dd5dd453 100644 --- a/nixos/modules/services/x11/display-managers/default.nix +++ b/nixos/modules/services/x11/display-managers/default.nix @@ -219,6 +219,7 @@ in session = mkOption { default = []; + type = types.listOf types.attrs; example = literalExpression '' [ { manage = "desktop"; diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index a1653d451feaf..9e5b760434a05 100644..100755 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -8,16 +8,29 @@ use File::Basename; use File::Slurp qw(read_file write_file edit_file); use Net::DBus; use Sys::Syslog qw(:standard :macros); -use Cwd 'abs_path'; +use Cwd qw(abs_path); -my $out = "@out@"; +## no critic(ControlStructures::ProhibitDeepNests) +## no critic(ErrorHandling::RequireCarping) +## no critic(CodeLayout::ProhibitParensWithBuiltins) +## no critic(Variables::ProhibitPunctuationVars, Variables::RequireLocalizedPunctuationVars) +## no critic(InputOutput::RequireCheckedSyscalls, InputOutput::RequireBracedFileHandleWithPrint, InputOutput::RequireBriefOpen) +## no critic(ValuesAndExpressions::ProhibitNoisyQuotes, ValuesAndExpressions::ProhibitMagicNumbers, ValuesAndExpressions::ProhibitEmptyQuotes, ValuesAndExpressions::ProhibitInterpolationOfLiterals) +## no critic(RegularExpressions::ProhibitEscapedMetacharacters) -my $curSystemd = abs_path("/run/current-system/sw/bin"); +# System closure path to switch to +my $out = "@out@"; +# Path to the directory containing systemd tools of the old system +my $cur_systemd = abs_path("/run/current-system/sw/bin"); +# Path to the systemd store path of the new system +my $new_systemd = "@systemd@"; # To be robust against interruption, record what units need to be started etc. -my $startListFile = "/run/nixos/start-list"; -my $restartListFile = "/run/nixos/restart-list"; -my $reloadListFile = "/run/nixos/reload-list"; +# We read these files again every time this script starts to make sure we continue +# where the old (interrupted) script left off. +my $start_list_file = "/run/nixos/start-list"; +my $restart_list_file = "/run/nixos/restart-list"; +my $reload_list_file = "/run/nixos/reload-list"; # Parse restart/reload requests by the activation script. # Activation scripts may write newline-separated units to the restart @@ -29,21 +42,23 @@ my $reloadListFile = "/run/nixos/reload-list"; # The reload file asks the script to reload a unit. This is the same as # specifying a reload trigger in the NixOS module and can be ignored if # the unit is restarted in this activation. -my $restartByActivationFile = "/run/nixos/activation-restart-list"; -my $reloadByActivationFile = "/run/nixos/activation-reload-list"; -my $dryRestartByActivationFile = "/run/nixos/dry-activation-restart-list"; -my $dryReloadByActivationFile = "/run/nixos/dry-activation-reload-list"; - -make_path("/run/nixos", { mode => oct(755) }); - -my $action = shift @ARGV; +my $restart_by_activation_file = "/run/nixos/activation-restart-list"; +my $reload_by_activation_file = "/run/nixos/activation-reload-list"; +my $dry_restart_by_activation_file = "/run/nixos/dry-activation-restart-list"; +my $dry_reload_by_activation_file = "/run/nixos/dry-activation-reload-list"; + +# The action that is to be performed (like switch, boot, test, dry-activate) +# Also exposed via environment variable from now on +my $action = shift(@ARGV); +$ENV{NIXOS_ACTION} = $action; +# Expose the locale archive as an environment variable for systemctl and the activation script if ("@localeArchive@" ne "") { $ENV{LOCALE_ARCHIVE} = "@localeArchive@"; } -if (!defined $action || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) { - print STDERR <<EOF; +if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) { + print STDERR <<"EOF"; Usage: $0 [switch|boot|test] switch: make the configuration the boot default and activate now @@ -51,67 +66,102 @@ boot: make the configuration the boot default test: activate the configuration, but don\'t make it the boot default dry-activate: show what would be done if this configuration were activated EOF - exit 1; + exit(1); } -$ENV{NIXOS_ACTION} = $action; - # This is a NixOS installation if it has /etc/NIXOS or a proper # /etc/os-release. -die "This is not a NixOS installation!\n" unless - -f "/etc/NIXOS" || (read_file("/etc/os-release", err_mode => 'quiet') // "") =~ /ID="?nixos"?/s; +if (!-f "/etc/NIXOS" && (read_file("/etc/os-release", err_mode => "quiet") // "") !~ /^ID="?nixos"?/msx) { + die("This is not a NixOS installation!\n"); +} +make_path("/run/nixos", { mode => oct(755) }); openlog("nixos", "", LOG_USER); # Install or update the bootloader. if ($action eq "switch" || $action eq "boot") { - system("@installBootLoader@ $out") == 0 or exit 1; + chomp(my $install_boot_loader = <<'EOFBOOTLOADER'); +@installBootLoader@ +EOFBOOTLOADER + system("$install_boot_loader $out") == 0 or exit 1; } # Just in case the new configuration hangs the system, do a sync now. -system("@coreutils@/bin/sync", "-f", "/nix/store") unless ($ENV{"NIXOS_NO_SYNC"} // "") eq "1"; +if (($ENV{"NIXOS_NO_SYNC"} // "") ne "1") { + system("@coreutils@/bin/sync", "-f", "/nix/store"); +} -exit 0 if $action eq "boot"; +if ($action eq "boot") { + exit(0); +} # Check if we can activate the new configuration. -my $oldVersion = read_file("/run/current-system/init-interface-version", err_mode => 'quiet') // ""; -my $newVersion = read_file("$out/init-interface-version"); +my $cur_init_interface_version = read_file("/run/current-system/init-interface-version", err_mode => "quiet") // ""; +my $new_init_interface_version = read_file("$out/init-interface-version"); -if ($newVersion ne $oldVersion) { - print STDERR <<EOF; +if ($new_init_interface_version ne $cur_init_interface_version) { + print STDERR <<'EOF'; Warning: the new NixOS configuration has an ‘init’ that is incompatible with the current configuration. The new configuration -won\'t take effect until you reboot the system. +won't take effect until you reboot the system. EOF - exit 100; + exit(100); } # Ignore SIGHUP so that we're not killed if we're running on (say) # virtual console 1 and we restart the "tty1" unit. $SIG{PIPE} = "IGNORE"; -sub getActiveUnits { +# Asks the currently running systemd instance via dbus which units are active. +# Returns a hash where the key is the name of each unit and the value a hash +# of load, state, substate. +sub get_active_units { my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1"); my $units = $mgr->ListUnitsByPatterns([], []); my $res = {}; - for my $item (@$units) { + for my $item (@{$units}) { my ($id, $description, $load_state, $active_state, $sub_state, - $following, $unit_path, $job_id, $job_type, $job_path) = @$item; - next unless $following eq ''; - next if $job_id == 0 and $active_state eq 'inactive'; + $following, $unit_path, $job_id, $job_type, $job_path) = @{$item}; + if ($following ne "") { + next; + } + if ($job_id == 0 and $active_state eq "inactive") { + next; + } $res->{$id} = { load => $load_state, state => $active_state, substate => $sub_state }; } return $res; } -sub parseFstab { +# Asks the currently running systemd instance whether a unit is currently active. +# Takes the name of the unit as an argument and returns a bool whether the unit is active or not. +sub unit_is_active { + my ($unit_name) = @_; + + my $mgr = Net::DBus->system->get_service("org.freedesktop.systemd1")->get_object("/org/freedesktop/systemd1"); + my $units = $mgr->ListUnitsByNames([$unit_name]); + if (scalar(@{$units}) == 0) { + return 0; + } + my $active_state = $units->[0]->[3]; + return $active_state eq "active" || $active_state eq "activating"; +} + +# Parse a fstab file, given its path. +# Returns a tuple of filesystems and swaps. +# +# Filesystems is a hash of mountpoint and { device, fsType, options } +# Swaps is a hash of device and { options } +sub parse_fstab { my ($filename) = @_; my ($fss, $swaps); - foreach my $line (read_file($filename, err_mode => 'quiet')) { - chomp $line; - $line =~ s/^\s*#.*//; - next if $line =~ /^\s*$/; - my @xs = split / /, $line; + foreach my $line (read_file($filename, err_mode => "quiet")) { + chomp($line); + $line =~ s/^\s*\#.*//msx; + if ($line =~ /^\s*$/msx) { + next; + } + my @xs = split(/\s+/msx, $line); if ($xs[2] eq "swap") { $swaps->{$xs[0]} = { options => $xs[3] // "" }; } else { @@ -130,36 +180,35 @@ sub parseFstab { # # Instead of returning the hash, this subroutine takes a hashref to return the data in. This # allows calling the subroutine multiple times with the same hash to parse override files. -sub parseSystemdIni { - my ($unitContents, $path) = @_; +sub parse_systemd_ini { + my ($unit_contents, $path) = @_; # Tie the ini file to a hash for easier access - my %fileContents; - tie %fileContents, "Config::IniFiles", (-file => $path, -allowempty => 1, -allowcontinue => 1); + tie(my %file_contents, "Config::IniFiles", (-file => $path, -allowempty => 1, -allowcontinue => 1)); ## no critic(Miscellanea::ProhibitTies) # Copy over all sections - foreach my $sectionName (keys %fileContents) { - if ($sectionName eq "Install") { + foreach my $section_name (keys(%file_contents)) { + if ($section_name eq "Install") { # Skip the [Install] section because it has no relevant keys for us next; } # Copy over all keys - foreach my $iniKey (keys %{$fileContents{$sectionName}}) { + foreach my $ini_key (keys(%{$file_contents{$section_name}})) { # Ensure the value is an array so it's easier to work with - my $iniValue = $fileContents{$sectionName}{$iniKey}; - my @iniValues; - if (ref($iniValue) eq "ARRAY") { - @iniValues = @{$iniValue}; + my $ini_value = $file_contents{$section_name}{$ini_key}; + my @ini_values; + if (ref($ini_value) eq "ARRAY") { + @ini_values = @{$ini_value}; } else { - @iniValues = $iniValue; + @ini_values = $ini_value; } # Go over all values - for my $iniValue (@iniValues) { + for my $ini_value (@ini_values) { # If a value is empty, it's an override that tells us to clean the value - if ($iniValue eq "") { - delete $unitContents->{$sectionName}->{$iniKey}; + if ($ini_value eq "") { + delete $unit_contents->{$section_name}->{$ini_key}; next; } - push(@{$unitContents->{$sectionName}->{$iniKey}}, $iniValue); + push(@{$unit_contents->{$section_name}->{$ini_key}}, $ini_value); } } } @@ -168,7 +217,7 @@ sub parseSystemdIni { # This subroutine takes the path to a systemd configuration file (like a unit configuration), # parses it, and returns a hash that contains the contents. The contents of this hash are -# explained in the `parseSystemdIni` subroutine. Neither the sections nor the keys inside +# explained in the `parse_systemd_ini` subroutine. Neither the sections nor the keys inside # the sections are consistently sorted. # # If a directory with the same basename ending in .d exists next to the unit file, it will be @@ -181,37 +230,45 @@ sub parse_unit { # Replace \ with \\ so glob() still works with units that have a \ in them # Valid characters in unit names are ASCII letters, digits, ":", "-", "_", ".", and "\" $unit_path =~ s/\\/\\\\/gmsx; - foreach (glob "${unit_path}{,.d/*.conf}") { - parseSystemdIni(\%unit_data, "$_") + foreach (glob("${unit_path}{,.d/*.conf}")) { + parse_systemd_ini(\%unit_data, "$_") } return %unit_data; } # Checks whether a specified boolean in a systemd unit is true # or false, with a default that is applied when the value is not set. -sub parseSystemdBool { - my ($unitConfig, $sectionName, $boolName, $default) = @_; +sub parse_systemd_bool { + my ($unit_config, $section_name, $bool_name, $default) = @_; - my @values = @{$unitConfig->{$sectionName}{$boolName} // []}; + my @values = @{$unit_config->{$section_name}{$bool_name} // []}; # Return default if value is not set - if (scalar @values lt 1 || not defined $values[-1]) { + if ((scalar(@values) < 1) || (not defined($values[-1]))) { return $default; } # If value is defined multiple times, use the last definition - my $last = $values[-1]; + my $last_value = $values[-1]; # These are valid values as of systemd.syntax(7) - return $last eq "1" || $last eq "yes" || $last eq "true" || $last eq "on"; + return $last_value eq "1" || $last_value eq "yes" || $last_value eq "true" || $last_value eq "on"; } -sub recordUnit { +# Writes a unit name into a given file to be more resilient against +# crashes of the script. Does nothing when the action is dry-activate. +sub record_unit { my ($fn, $unit) = @_; - write_file($fn, { append => 1 }, "$unit\n") if $action ne "dry-activate"; + if ($action ne "dry-activate") { + write_file($fn, { append => 1 }, "$unit\n"); + } + return; } -# The opposite of recordUnit, removes a unit name from a file +# The opposite of record_unit, removes a unit name from a file sub unrecord_unit { my ($fn, $unit) = @_; - edit_file { s/^$unit\n//msx } $fn if $action ne "dry-activate"; + if ($action ne "dry-activate") { + edit_file(sub { s/^$unit\n//msx }, $fn); + } + return; } # Compare the contents of two unit files and return whether the unit @@ -223,9 +280,19 @@ sub unrecord_unit { # - 0 if the units are equal # - 1 if the units are different and a restart action is required # - 2 if the units are different and a reload action is required -sub compare_units { - my ($old_unit, $new_unit) = @_; +sub compare_units { ## no critic(Subroutines::ProhibitExcessComplexity) + my ($cur_unit, $new_unit) = @_; my $ret = 0; + # Keys to ignore in the [Unit] section + my %unit_section_ignores = map { $_ => 1 } qw( + X-Reload-Triggers + Description Documentation + OnFailure OnSuccess OnFailureJobMode + IgnoreOnIsolate StopWhenUnneeded + RefuseManualStart RefuseManualStop + AllowIsolate CollectMode + SourcePath + ); my $comp_array = sub { my ($a, $b) = @_; @@ -233,12 +300,24 @@ sub compare_units { }; # Comparison hash for the sections - my %section_cmp = map { $_ => 1 } keys %{$new_unit}; + my %section_cmp = map { $_ => 1 } keys(%{$new_unit}); # Iterate over the sections - foreach my $section_name (keys %{$old_unit}) { + foreach my $section_name (keys(%{$cur_unit})) { # Missing section in the new unit? - if (not exists $section_cmp{$section_name}) { - if ($section_name eq 'Unit' and %{$old_unit->{'Unit'}} == 1 and defined(%{$old_unit->{'Unit'}}{'X-Reload-Triggers'})) { + if (not exists($section_cmp{$section_name})) { + # If the [Unit] section was removed, make sure that only keys + # were in it that are ignored + if ($section_name eq "Unit") { + foreach my $ini_key (keys(%{$cur_unit->{"Unit"}})) { + if (not defined($unit_section_ignores{$ini_key})) { + return 1; + } + } + next; # check the next section + } else { + return 1; + } + if ($section_name eq "Unit" and %{$cur_unit->{"Unit"}} == 1 and defined(%{$cur_unit->{"Unit"}}{"X-Reload-Triggers"})) { # If a new [Unit] section was removed that only contained X-Reload-Triggers, # do nothing. next; @@ -248,46 +327,61 @@ sub compare_units { } delete $section_cmp{$section_name}; # Comparison hash for the section contents - my %ini_cmp = map { $_ => 1 } keys %{$new_unit->{$section_name}}; + my %ini_cmp = map { $_ => 1 } keys(%{$new_unit->{$section_name}}); # Iterate over the keys of the section - foreach my $ini_key (keys %{$old_unit->{$section_name}}) { + foreach my $ini_key (keys(%{$cur_unit->{$section_name}})) { delete $ini_cmp{$ini_key}; - my @old_value = @{$old_unit->{$section_name}{$ini_key}}; + my @cur_value = @{$cur_unit->{$section_name}{$ini_key}}; # If the key is missing in the new unit, they are different... if (not $new_unit->{$section_name}{$ini_key}) { - # ... unless the key that is now missing was the reload trigger - if ($section_name eq 'Unit' and $ini_key eq 'X-Reload-Triggers') { + # ... unless the key that is now missing is one of the ignored keys + if ($section_name eq "Unit" and defined($unit_section_ignores{$ini_key})) { next; } return 1; } my @new_value = @{$new_unit->{$section_name}{$ini_key}}; # If the contents are different, the units are different - if (not $comp_array->(\@old_value, \@new_value)) { - # Check if only the reload triggers changed - if ($section_name eq 'Unit' and $ini_key eq 'X-Reload-Triggers') { - $ret = 2; - } else { - return 1; + if (not $comp_array->(\@cur_value, \@new_value)) { + # Check if only the reload triggers changed or one of the ignored keys + if ($section_name eq "Unit") { + if ($ini_key eq "X-Reload-Triggers") { + $ret = 2; + next; + } elsif (defined($unit_section_ignores{$ini_key})) { + next; + } } + return 1; } } - # A key was introduced that was missing in the old unit + # A key was introduced that was missing in the previous unit if (%ini_cmp) { - if ($section_name eq 'Unit' and %ini_cmp == 1 and defined($ini_cmp{'X-Reload-Triggers'})) { - # If the newly introduced key was the reload triggers, reload the unit - $ret = 2; + if ($section_name eq "Unit") { + foreach my $ini_key (keys(%ini_cmp)) { + if ($ini_key eq "X-Reload-Triggers") { + $ret = 2; + } elsif (defined($unit_section_ignores{$ini_key})) { + next; + } else { + return 1; + } + } } else { return 1; } }; } - # A section was introduced that was missing in the old unit + # A section was introduced that was missing in the previous unit if (%section_cmp) { - if (%section_cmp == 1 and defined($section_cmp{'Unit'}) and %{$new_unit->{'Unit'}} == 1 and defined(%{$new_unit->{'Unit'}}{'X-Reload-Triggers'})) { - # If a new [Unit] section was introduced that only contains X-Reload-Triggers, - # reload instead of restarting - $ret = 2; + if (%section_cmp == 1 and defined($section_cmp{"Unit"})) { + foreach my $ini_key (keys(%{$new_unit->{"Unit"}})) { + if (not defined($unit_section_ignores{$ini_key})) { + return 1; + } elsif ($ini_key eq "X-Reload-Triggers") { + $ret = 2; + } + } } else { return 1; } @@ -296,72 +390,78 @@ sub compare_units { return $ret; } -sub handleModifiedUnit { - my ($unit, $baseName, $newUnitFile, $newUnitInfo, $activePrev, $unitsToStop, $unitsToStart, $unitsToReload, $unitsToRestart, $unitsToSkip) = @_; +# Called when a unit exists in both the old systemd and the new system and the units +# differ. This figures out of what units are to be stopped, restarted, reloaded, started, and skipped. +sub handle_modified_unit { ## no critic(Subroutines::ProhibitManyArgs, Subroutines::ProhibitExcessComplexity) + my ($unit, $base_name, $new_unit_file, $new_unit_info, $active_cur, $units_to_stop, $units_to_start, $units_to_reload, $units_to_restart, $units_to_skip) = @_; - if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/ || $unit =~ /\.slice$/) { + if ($unit eq "sysinit.target" || $unit eq "basic.target" || $unit eq "multi-user.target" || $unit eq "graphical.target" || $unit =~ /\.path$/msx || $unit =~ /\.slice$/msx) { # Do nothing. These cannot be restarted directly. # Slices and Paths don't have to be restarted since # properties (resource limits and inotify watches) # seem to get applied on daemon-reload. - } elsif ($unit =~ /\.mount$/) { + } elsif ($unit =~ /\.mount$/msx) { # Reload the changed mount unit to force a remount. # FIXME: only reload when Options= changed, restart otherwise - $unitsToReload->{$unit} = 1; - recordUnit($reloadListFile, $unit); - } elsif ($unit =~ /\.socket$/) { + $units_to_reload->{$unit} = 1; + record_unit($reload_list_file, $unit); + } elsif ($unit =~ /\.socket$/msx) { # FIXME: do something? # Attempt to fix this: https://github.com/NixOS/nixpkgs/pull/141192 # Revert of the attempt: https://github.com/NixOS/nixpkgs/pull/147609 # More details: https://github.com/NixOS/nixpkgs/issues/74899#issuecomment-981142430 } else { - my %unitInfo = $newUnitInfo ? %{$newUnitInfo} : parse_unit($newUnitFile); - if (parseSystemdBool(\%unitInfo, "Service", "X-ReloadIfChanged", 0) and not $unitsToRestart->{$unit} and not $unitsToStop->{$unit}) { - $unitsToReload->{$unit} = 1; - recordUnit($reloadListFile, $unit); + my %new_unit_info = $new_unit_info ? %{$new_unit_info} : parse_unit($new_unit_file); + if (parse_systemd_bool(\%new_unit_info, "Service", "X-ReloadIfChanged", 0) and not $units_to_restart->{$unit} and not $units_to_stop->{$unit}) { + $units_to_reload->{$unit} = 1; + record_unit($reload_list_file, $unit); } - elsif (!parseSystemdBool(\%unitInfo, "Service", "X-RestartIfChanged", 1) || parseSystemdBool(\%unitInfo, "Unit", "RefuseManualStop", 0) || parseSystemdBool(\%unitInfo, "Unit", "X-OnlyManualStart", 0)) { - $unitsToSkip->{$unit} = 1; + elsif (!parse_systemd_bool(\%new_unit_info, "Service", "X-RestartIfChanged", 1) || parse_systemd_bool(\%new_unit_info, "Unit", "RefuseManualStop", 0) || parse_systemd_bool(\%new_unit_info, "Unit", "X-OnlyManualStart", 0)) { + $units_to_skip->{$unit} = 1; } else { # It doesn't make sense to stop and start non-services because # they can't have ExecStop= - if (!parseSystemdBool(\%unitInfo, "Service", "X-StopIfChanged", 1) || $unit !~ /\.service$/) { + if (!parse_systemd_bool(\%new_unit_info, "Service", "X-StopIfChanged", 1) || $unit !~ /\.service$/msx) { # This unit should be restarted instead of # stopped and started. - $unitsToRestart->{$unit} = 1; - recordUnit($restartListFile, $unit); + $units_to_restart->{$unit} = 1; + record_unit($restart_list_file, $unit); # Remove from units to reload so we don't restart and reload - if ($unitsToReload->{$unit}) { - delete $unitsToReload->{$unit}; - unrecord_unit($reloadListFile, $unit); + if ($units_to_reload->{$unit}) { + delete $units_to_reload->{$unit}; + unrecord_unit($reload_list_file, $unit); } } else { # If this unit is socket-activated, then stop the # socket unit(s) as well, and restart the # socket(s) instead of the service. my $socket_activated = 0; - if ($unit =~ /\.service$/) { - my @sockets = split(/ /, join(" ", @{$unitInfo{Service}{Sockets} // []})); - if (scalar @sockets == 0) { - @sockets = ("$baseName.socket"); + if ($unit =~ /\.service$/msx) { + my @sockets = split(/\s+/msx, join(" ", @{$new_unit_info{Service}{Sockets} // []})); + if (scalar(@sockets) == 0) { + @sockets = ("$base_name.socket"); } foreach my $socket (@sockets) { - if (defined $activePrev->{$socket}) { + if (defined($active_cur->{$socket})) { # We can now be sure this is a socket-activate unit - $unitsToStop->{$socket} = 1; + $units_to_stop->{$socket} = 1; # Only restart sockets that actually # exist in new configuration: if (-e "$out/etc/systemd/system/$socket") { - $unitsToStart->{$socket} = 1; - recordUnit($startListFile, $socket); + $units_to_start->{$socket} = 1; + if ($units_to_start eq $units_to_restart) { + record_unit($restart_list_file, $socket); + } else { + record_unit($start_list_file, $socket); + } $socket_activated = 1; } # Remove from units to reload so we don't restart and reload - if ($unitsToReload->{$unit}) { - delete $unitsToReload->{$unit}; - unrecord_unit($reloadListFile, $unit); + if ($units_to_reload->{$unit}) { + delete $units_to_reload->{$unit}; + unrecord_unit($reload_list_file, $unit); } } } @@ -372,60 +472,67 @@ sub handleModifiedUnit { # We write this to a file to ensure that the # service gets restarted if we're interrupted. if (!$socket_activated) { - $unitsToStart->{$unit} = 1; - recordUnit($startListFile, $unit); + $units_to_start->{$unit} = 1; + if ($units_to_start eq $units_to_restart) { + record_unit($restart_list_file, $unit); + } else { + record_unit($start_list_file, $unit); + } } - $unitsToStop->{$unit} = 1; + $units_to_stop->{$unit} = 1; # Remove from units to reload so we don't restart and reload - if ($unitsToReload->{$unit}) { - delete $unitsToReload->{$unit}; - unrecord_unit($reloadListFile, $unit); + if ($units_to_reload->{$unit}) { + delete $units_to_reload->{$unit}; + unrecord_unit($reload_list_file, $unit); } } } } + return; } # Figure out what units need to be stopped, started, restarted or reloaded. -my (%unitsToStop, %unitsToSkip, %unitsToStart, %unitsToRestart, %unitsToReload); +my (%units_to_stop, %units_to_skip, %units_to_start, %units_to_restart, %units_to_reload); -my %unitsToFilter; # units not shown +my %units_to_filter; # units not shown -$unitsToStart{$_} = 1 foreach - split('\n', read_file($startListFile, err_mode => 'quiet') // ""); +%units_to_start = map { $_ => 1 } + split(/\n/msx, read_file($start_list_file, err_mode => "quiet") // ""); -$unitsToRestart{$_} = 1 foreach - split('\n', read_file($restartListFile, err_mode => 'quiet') // ""); +%units_to_restart = map { $_ => 1 } + split(/\n/msx, read_file($restart_list_file, err_mode => "quiet") // ""); -$unitsToReload{$_} = 1 foreach - split('\n', read_file($reloadListFile, err_mode => 'quiet') // ""); +%units_to_reload = map { $_ => 1 } + split(/\n/msx, read_file($reload_list_file, err_mode => "quiet") // ""); -my $activePrev = getActiveUnits; -while (my ($unit, $state) = each %{$activePrev}) { - my $baseUnit = $unit; +my $active_cur = get_active_units(); +while (my ($unit, $state) = each(%{$active_cur})) { + my $base_unit = $unit; - my $prevUnitFile = "/etc/systemd/system/$baseUnit"; - my $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + my $cur_unit_file = "/etc/systemd/system/$base_unit"; + my $new_unit_file = "$out/etc/systemd/system/$base_unit"; # Detect template instances. - if (!-e $prevUnitFile && !-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) { - $baseUnit = "$1\@.$2"; - $prevUnitFile = "/etc/systemd/system/$baseUnit"; - $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + if (!-e $cur_unit_file && !-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { + $base_unit = "$1\@.$2"; + $cur_unit_file = "/etc/systemd/system/$base_unit"; + $new_unit_file = "$out/etc/systemd/system/$base_unit"; } - my $baseName = $baseUnit; - $baseName =~ s/\.[a-z]*$//; + my $base_name = $base_unit; + $base_name =~ s/\.[[:lower:]]*$//msx; - if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) { - if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") { - my %unitInfo = parse_unit($prevUnitFile); - $unitsToStop{$unit} = 1 if parseSystemdBool(\%unitInfo, "Unit", "X-StopOnRemoval", 1); + if (-e $cur_unit_file && ($state->{state} eq "active" || $state->{state} eq "activating")) { + if (! -e $new_unit_file || abs_path($new_unit_file) eq "/dev/null") { + my %cur_unit_info = parse_unit($cur_unit_file); + if (parse_systemd_bool(\%cur_unit_info, "Unit", "X-StopOnRemoval", 1)) { + $units_to_stop{$unit} = 1; + } } - elsif ($unit =~ /\.target$/) { - my %unitInfo = parse_unit($newUnitFile); + elsif ($unit =~ /\.target$/msx) { + my %new_unit_info = parse_unit($new_unit_file); # Cause all active target units to be restarted below. # This should start most changed units we stop here as @@ -434,11 +541,11 @@ while (my ($unit, $state) = each %{$activePrev}) { # active after the system has resumed, which probably # should not be the case. Just ignore it. if ($unit ne "suspend.target" && $unit ne "hibernate.target" && $unit ne "hybrid-sleep.target") { - unless (parseSystemdBool(\%unitInfo, "Unit", "RefuseManualStart", 0) || parseSystemdBool(\%unitInfo, "Unit", "X-OnlyManualStart", 0)) { - $unitsToStart{$unit} = 1; - recordUnit($startListFile, $unit); + if (!(parse_systemd_bool(\%new_unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%new_unit_info, "Unit", "X-OnlyManualStart", 0))) { + $units_to_start{$unit} = 1; + record_unit($start_list_file, $unit); # Don't spam the user with target units that always get started. - $unitsToFilter{$unit} = 1; + $units_to_filter{$unit} = 1; } } @@ -453,33 +560,35 @@ while (my ($unit, $state) = each %{$activePrev}) { # Stopping a target generally has no effect on other units # (unless there is a PartOf dependency), so this is just a # bookkeeping thing to get systemd to do the right thing. - if (parseSystemdBool(\%unitInfo, "Unit", "X-StopOnReconfiguration", 0)) { - $unitsToStop{$unit} = 1; + if (parse_systemd_bool(\%new_unit_info, "Unit", "X-StopOnReconfiguration", 0)) { + $units_to_stop{$unit} = 1; } } else { - my %old_unit_info = parse_unit($prevUnitFile); - my %new_unit_info = parse_unit($newUnitFile); - my $diff = compare_units(\%old_unit_info, \%new_unit_info); - if ($diff eq 1) { - handleModifiedUnit($unit, $baseName, $newUnitFile, \%new_unit_info, $activePrev, \%unitsToStop, \%unitsToStart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); - } elsif ($diff eq 2 and not $unitsToRestart{$unit}) { - $unitsToReload{$unit} = 1; - recordUnit($reloadListFile, $unit); + my %cur_unit_info = parse_unit($cur_unit_file); + my %new_unit_info = parse_unit($new_unit_file); + my $diff = compare_units(\%cur_unit_info, \%new_unit_info); + if ($diff == 1) { + handle_modified_unit($unit, $base_name, $new_unit_file, \%new_unit_info, $active_cur, \%units_to_stop, \%units_to_start, \%units_to_reload, \%units_to_restart, \%units_to_skip); + } elsif ($diff == 2 and not $units_to_restart{$unit}) { + $units_to_reload{$unit} = 1; + record_unit($reload_list_file, $unit); } } } } -sub pathToUnitName { +# Converts a path to the name of a systemd mount unit that would be responsible +# for mounting this path. +sub path_to_unit_name { my ($path) = @_; # Use current version of systemctl binary before daemon is reexeced. - open my $cmd, "-|", "$curSystemd/systemd-escape", "--suffix=mount", "-p", $path + open(my $cmd, "-|", "$cur_systemd/systemd-escape", "--suffix=mount", "-p", $path) or die "Unable to escape $path!\n"; - my $escaped = join "", <$cmd>; - chomp $escaped; - close $cmd or die; + my $escaped = do { local $/ = undef; <$cmd> }; + chomp($escaped); + close($cmd) or die("Unable to close systemd-escape pipe"); return $escaped; } @@ -488,32 +597,32 @@ sub pathToUnitName { # automatically by starting local-fs.target. FIXME: might be nicer if # we generated units for all mounts; then we could unify this with the # unit checking code above. -my ($prevFss, $prevSwaps) = parseFstab "/etc/fstab"; -my ($newFss, $newSwaps) = parseFstab "$out/etc/fstab"; -foreach my $mountPoint (keys %$prevFss) { - my $prev = $prevFss->{$mountPoint}; - my $new = $newFss->{$mountPoint}; - my $unit = pathToUnitName($mountPoint); - if (!defined $new) { +my ($cur_fss, $cur_swaps) = parse_fstab("/etc/fstab"); +my ($new_fss, $new_swaps) = parse_fstab("$out/etc/fstab"); +foreach my $mount_point (keys(%{$cur_fss})) { + my $cur = $cur_fss->{$mount_point}; + my $new = $new_fss->{$mount_point}; + my $unit = path_to_unit_name($mount_point); + if (!defined($new)) { # Filesystem entry disappeared, so unmount it. - $unitsToStop{$unit} = 1; - } elsif ($prev->{fsType} ne $new->{fsType} || $prev->{device} ne $new->{device}) { + $units_to_stop{$unit} = 1; + } elsif ($cur->{fsType} ne $new->{fsType} || $cur->{device} ne $new->{device}) { # Filesystem type or device changed, so unmount and mount it. - $unitsToStop{$unit} = 1; - $unitsToStart{$unit} = 1; - recordUnit($startListFile, $unit); - } elsif ($prev->{options} ne $new->{options}) { + $units_to_stop{$unit} = 1; + $units_to_start{$unit} = 1; + record_unit($start_list_file, $unit); + } elsif ($cur->{options} ne $new->{options}) { # Mount options changes, so remount it. - $unitsToReload{$unit} = 1; - recordUnit($reloadListFile, $unit); + $units_to_reload{$unit} = 1; + record_unit($reload_list_file, $unit); } } # Also handles swap devices. -foreach my $device (keys %$prevSwaps) { - my $prev = $prevSwaps->{$device}; - my $new = $newSwaps->{$device}; - if (!defined $new) { +foreach my $device (keys(%{$cur_swaps})) { + my $cur = $cur_swaps->{$device}; + my $new = $new_swaps->{$device}; + if (!defined($new)) { # Swap entry disappeared, so turn it off. Can't use # "systemctl stop" here because systemd has lots of alias # units that prevent a stop from actually calling @@ -530,97 +639,109 @@ foreach my $device (keys %$prevSwaps) { # Should we have systemd re-exec itself? -my $prevSystemd = abs_path("/proc/1/exe") // "/unknown"; -my $prevSystemdSystemConfig = abs_path("/etc/systemd/system.conf") // "/unknown"; -my $newSystemd = abs_path("@systemd@/lib/systemd/systemd") or die; -my $newSystemdSystemConfig = abs_path("$out/etc/systemd/system.conf") // "/unknown"; - -my $restartSystemd = $prevSystemd ne $newSystemd; -if ($prevSystemdSystemConfig ne $newSystemdSystemConfig) { - $restartSystemd = 1; +my $cur_pid1_path = abs_path("/proc/1/exe") // "/unknown"; +my $cur_systemd_system_config = abs_path("/etc/systemd/system.conf") // "/unknown"; +my $new_pid1_path = abs_path("$new_systemd/lib/systemd/systemd") or die; +my $new_systemd_system_config = abs_path("$out/etc/systemd/system.conf") // "/unknown"; + +my $restart_systemd = $cur_pid1_path ne $new_pid1_path; +if ($cur_systemd_system_config ne $new_systemd_system_config) { + $restart_systemd = 1; } - -sub filterUnits { +# Takes an array of unit names and returns an array with the same elements, +# except all units that are also in the global variable `unitsToFilter`. +sub filter_units { my ($units) = @_; my @res; - foreach my $unit (sort(keys %{$units})) { - push @res, $unit if !defined $unitsToFilter{$unit}; + foreach my $unit (sort(keys(%{$units}))) { + if (!defined($units_to_filter{$unit})) { + push(@res, $unit); + } } return @res; } -my @unitsToStopFiltered = filterUnits(\%unitsToStop); +my @units_to_stop_filtered = filter_units(\%units_to_stop); # Show dry-run actions. if ($action eq "dry-activate") { - print STDERR "would stop the following units: ", join(", ", @unitsToStopFiltered), "\n" - if scalar @unitsToStopFiltered > 0; - print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n" - if scalar(keys %unitsToSkip) > 0; + if (scalar(@units_to_stop_filtered) > 0) { + print STDERR "would stop the following units: ", join(", ", @units_to_stop_filtered), "\n"; + } + if (scalar(keys(%units_to_skip)) > 0) { + print STDERR "would NOT stop the following changed units: ", join(", ", sort(keys(%units_to_skip))), "\n"; + } print STDERR "would activate the configuration...\n"; system("$out/dry-activate", "$out"); # Handle the activation script requesting the restart or reload of a unit. - foreach (split('\n', read_file($dryRestartByActivationFile, err_mode => 'quiet') // "")) { + foreach (split(/\n/msx, read_file($dry_restart_by_activation_file, err_mode => "quiet") // "")) { my $unit = $_; - my $baseUnit = $unit; - my $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + my $base_unit = $unit; + my $new_unit_file = "$out/etc/systemd/system/$base_unit"; # Detect template instances. - if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) { - $baseUnit = "$1\@.$2"; - $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { + $base_unit = "$1\@.$2"; + $new_unit_file = "$out/etc/systemd/system/$base_unit"; } - my $baseName = $baseUnit; - $baseName =~ s/\.[a-z]*$//; + my $base_name = $base_unit; + $base_name =~ s/\.[[:lower:]]*$//msx; # Start units if they were not active previously - if (not defined $activePrev->{$unit}) { - $unitsToStart{$unit} = 1; + if (not defined($active_cur->{$unit})) { + $units_to_start{$unit} = 1; next; } - handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); + handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip); } - unlink($dryRestartByActivationFile); + unlink($dry_restart_by_activation_file); - foreach (split('\n', read_file($dryReloadByActivationFile, err_mode => 'quiet') // "")) { + foreach (split(/\n/msx, read_file($dry_reload_by_activation_file, err_mode => "quiet") // "")) { my $unit = $_; - if (defined($activePrev->{$unit}) and not $unitsToRestart{$unit} and not $unitsToStop{$unit}) { - $unitsToReload{$unit} = 1; - recordUnit($reloadListFile, $unit); + if (defined($active_cur->{$unit}) and not $units_to_restart{$unit} and not $units_to_stop{$unit}) { + $units_to_reload{$unit} = 1; + record_unit($reload_list_file, $unit); } } - unlink($dryReloadByActivationFile); + unlink($dry_reload_by_activation_file); - print STDERR "would restart systemd\n" if $restartSystemd; - print STDERR "would reload the following units: ", join(", ", sort(keys %unitsToReload)), "\n" - if scalar(keys %unitsToReload) > 0; - print STDERR "would restart the following units: ", join(", ", sort(keys %unitsToRestart)), "\n" - if scalar(keys %unitsToRestart) > 0; - my @unitsToStartFiltered = filterUnits(\%unitsToStart); - print STDERR "would start the following units: ", join(", ", @unitsToStartFiltered), "\n" - if scalar @unitsToStartFiltered; + if ($restart_systemd) { + print STDERR "would restart systemd\n"; + } + if (scalar(keys(%units_to_reload)) > 0) { + print STDERR "would reload the following units: ", join(", ", sort(keys(%units_to_reload))), "\n"; + } + if (scalar(keys(%units_to_restart)) > 0) { + print STDERR "would restart the following units: ", join(", ", sort(keys(%units_to_restart))), "\n"; + } + my @units_to_start_filtered = filter_units(\%units_to_start); + if (scalar(@units_to_start_filtered)) { + print STDERR "would start the following units: ", join(", ", @units_to_start_filtered), "\n"; + } exit 0; } syslog(LOG_NOTICE, "switching to system configuration $out"); -if (scalar (keys %unitsToStop) > 0) { - print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n" - if scalar @unitsToStopFiltered; +if (scalar(keys(%units_to_stop)) > 0) { + if (scalar(@units_to_stop_filtered)) { + print STDERR "stopping the following units: ", join(", ", @units_to_stop_filtered), "\n"; + } # Use current version of systemctl binary before daemon is reexeced. - system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop)); + system("$cur_systemd/systemctl", "stop", "--", sort(keys(%units_to_stop))); } -print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n" - if scalar(keys %unitsToSkip) > 0; +if (scalar(keys(%units_to_skip)) > 0) { + print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys(%units_to_skip))), "\n"; +} # Activate the new configuration (i.e., update /etc, make accounts, # and so on). @@ -629,90 +750,110 @@ print STDERR "activating the configuration...\n"; system("$out/activate", "$out") == 0 or $res = 2; # Handle the activation script requesting the restart or reload of a unit. -foreach (split('\n', read_file($restartByActivationFile, err_mode => 'quiet') // "")) { +foreach (split(/\n/msx, read_file($restart_by_activation_file, err_mode => "quiet") // "")) { my $unit = $_; - my $baseUnit = $unit; - my $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + my $base_unit = $unit; + my $new_unit_file = "$out/etc/systemd/system/$base_unit"; # Detect template instances. - if (!-e $newUnitFile && $unit =~ /^(.*)@[^\.]*\.(.*)$/) { - $baseUnit = "$1\@.$2"; - $newUnitFile = "$out/etc/systemd/system/$baseUnit"; + if (!-e $new_unit_file && $unit =~ /^(.*)@[^\.]*\.(.*)$/msx) { + $base_unit = "$1\@.$2"; + $new_unit_file = "$out/etc/systemd/system/$base_unit"; } - my $baseName = $baseUnit; - $baseName =~ s/\.[a-z]*$//; + my $base_name = $base_unit; + $base_name =~ s/\.[[:lower:]]*$//msx; # Start units if they were not active previously - if (not defined $activePrev->{$unit}) { - $unitsToStart{$unit} = 1; - recordUnit($startListFile, $unit); + if (not defined($active_cur->{$unit})) { + $units_to_start{$unit} = 1; + record_unit($start_list_file, $unit); next; } - handleModifiedUnit($unit, $baseName, $newUnitFile, undef, $activePrev, \%unitsToRestart, \%unitsToRestart, \%unitsToReload, \%unitsToRestart, \%unitsToSkip); + handle_modified_unit($unit, $base_name, $new_unit_file, undef, $active_cur, \%units_to_restart, \%units_to_restart, \%units_to_reload, \%units_to_restart, \%units_to_skip); } # We can remove the file now because it has been propagated to the other restart/reload files -unlink($restartByActivationFile); +unlink($restart_by_activation_file); -foreach (split('\n', read_file($reloadByActivationFile, err_mode => 'quiet') // "")) { +foreach (split(/\n/msx, read_file($reload_by_activation_file, err_mode => "quiet") // "")) { my $unit = $_; - if (defined($activePrev->{$unit}) and not $unitsToRestart{$unit} and not $unitsToStop{$unit}) { - $unitsToReload{$unit} = 1; - recordUnit($reloadListFile, $unit); + if (defined($active_cur->{$unit}) and not $units_to_restart{$unit} and not $units_to_stop{$unit}) { + $units_to_reload{$unit} = 1; + record_unit($reload_list_file, $unit); } } # We can remove the file now because it has been propagated to the other reload file -unlink($reloadByActivationFile); +unlink($reload_by_activation_file); # Restart systemd if necessary. Note that this is done using the # current version of systemd, just in case the new one has trouble # communicating with the running pid 1. -if ($restartSystemd) { +if ($restart_systemd) { print STDERR "restarting systemd...\n"; - system("$curSystemd/systemctl", "daemon-reexec") == 0 or $res = 2; + system("$cur_systemd/systemctl", "daemon-reexec") == 0 or $res = 2; } # Forget about previously failed services. -system("@systemd@/bin/systemctl", "reset-failed"); +system("$new_systemd/bin/systemctl", "reset-failed"); # Make systemd reload its units. -system("@systemd@/bin/systemctl", "daemon-reload") == 0 or $res = 3; +system("$new_systemd/bin/systemctl", "daemon-reload") == 0 or $res = 3; # Reload user units -open my $listActiveUsers, '-|', '@systemd@/bin/loginctl', 'list-users', '--no-legend'; -while (my $f = <$listActiveUsers>) { - next unless $f =~ /^\s*(?<uid>\d+)\s+(?<user>\S+)/; +open(my $list_active_users, "-|", "$new_systemd/bin/loginctl", "list-users", "--no-legend") || die("Unable to call loginctl"); +while (my $f = <$list_active_users>) { + if ($f !~ /^\s*(?<uid>\d+)\s+(?<user>\S+)/msx) { + next; + } my ($uid, $name) = ($+{uid}, $+{user}); print STDERR "reloading user units for $name...\n"; system("@su@", "-s", "@shell@", "-l", $name, "-c", "export XDG_RUNTIME_DIR=/run/user/$uid; " . - "$curSystemd/systemctl --user daemon-reexec; " . - "@systemd@/bin/systemctl --user start nixos-activation.service"); + "$cur_systemd/systemctl --user daemon-reexec; " . + "$new_systemd/bin/systemctl --user start nixos-activation.service"); } -close $listActiveUsers; +close($list_active_users) || die("Unable to close the file handle to loginctl"); # Set the new tmpfiles print STDERR "setting up tmpfiles\n"; -system("@systemd@/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3; - +system("$new_systemd/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3; + +# Before reloading we need to ensure that the units are still active. They may have been +# deactivated because one of their requirements got stopped. If they are inactive +# but should have been reloaded, the user probably expects them to be started. +if (scalar(keys(%units_to_reload)) > 0) { + for my $unit (keys(%units_to_reload)) { + if (!unit_is_active($unit)) { + # Figure out if we need to start the unit + my %unit_info = parse_unit("$out/etc/systemd/system/$unit"); + if (!(parse_systemd_bool(\%unit_info, "Unit", "RefuseManualStart", 0) || parse_systemd_bool(\%unit_info, "Unit", "X-OnlyManualStart", 0))) { + $units_to_start{$unit} = 1; + record_unit($start_list_file, $unit); + } + # Don't reload the unit, reloading would fail + delete %units_to_reload{$unit}; + unrecord_unit($reload_list_file, $unit); + } + } +} # Reload units that need it. This includes remounting changed mount # units. -if (scalar(keys %unitsToReload) > 0) { - print STDERR "reloading the following units: ", join(", ", sort(keys %unitsToReload)), "\n"; - system("@systemd@/bin/systemctl", "reload", "--", sort(keys %unitsToReload)) == 0 or $res = 4; - unlink($reloadListFile); +if (scalar(keys(%units_to_reload)) > 0) { + print STDERR "reloading the following units: ", join(", ", sort(keys(%units_to_reload))), "\n"; + system("$new_systemd/bin/systemctl", "reload", "--", sort(keys(%units_to_reload))) == 0 or $res = 4; + unlink($reload_list_file); } # Restart changed services (those that have to be restarted rather # than stopped and started). -if (scalar(keys %unitsToRestart) > 0) { - print STDERR "restarting the following units: ", join(", ", sort(keys %unitsToRestart)), "\n"; - system("@systemd@/bin/systemctl", "restart", "--", sort(keys %unitsToRestart)) == 0 or $res = 4; - unlink($restartListFile); +if (scalar(keys(%units_to_restart)) > 0) { + print STDERR "restarting the following units: ", join(", ", sort(keys(%units_to_restart))), "\n"; + system("$new_systemd/bin/systemctl", "restart", "--", sort(keys(%units_to_restart))) == 0 or $res = 4; + unlink($restart_list_file); } # Start all active targets, as well as changed units we stopped above. @@ -721,29 +862,32 @@ if (scalar(keys %unitsToRestart) > 0) { # that are symlinks to other units. We shouldn't start both at the # same time because we'll get a "Failed to add path to set" error from # systemd. -my @unitsToStartFiltered = filterUnits(\%unitsToStart); -print STDERR "starting the following units: ", join(", ", @unitsToStartFiltered), "\n" - if scalar @unitsToStartFiltered; -system("@systemd@/bin/systemctl", "start", "--", sort(keys %unitsToStart)) == 0 or $res = 4; -unlink($startListFile); +my @units_to_start_filtered = filter_units(\%units_to_start); +if (scalar(@units_to_start_filtered)) { + print STDERR "starting the following units: ", join(", ", @units_to_start_filtered), "\n" +} +system("$new_systemd/bin/systemctl", "start", "--", sort(keys(%units_to_start))) == 0 or $res = 4; +unlink($start_list_file); # Print failed and new units. my (@failed, @new); -my $activeNew = getActiveUnits; -while (my ($unit, $state) = each %{$activeNew}) { +my $active_new = get_active_units(); +while (my ($unit, $state) = each(%{$active_new})) { if ($state->{state} eq "failed") { - push @failed, $unit; + push(@failed, $unit); next; } if ($state->{substate} eq "auto-restart") { # A unit in auto-restart substate is a failure *if* it previously failed to start - my $main_status = `@systemd@/bin/systemctl show --value --property=ExecMainStatus '$unit'`; + open(my $main_status_fd, "-|", "$new_systemd/bin/systemctl", "show", "--value", "--property=ExecMainStatus", $unit) || die("Unable to call 'systemctl show'"); + my $main_status = do { local $/ = undef; <$main_status_fd> }; + close($main_status_fd) || die("Unable to close 'systemctl show' fd"); chomp($main_status); if ($main_status ne "0") { - push @failed, $unit; + push(@failed, $unit); next; } } @@ -751,19 +895,19 @@ while (my ($unit, $state) = each %{$activeNew}) { # Ignore scopes since they are not managed by this script but rather # created and managed by third-party services via the systemd dbus API. # This only lists units that are not failed (including ones that are in auto-restart but have not failed previously) - if ($state->{state} ne "failed" && !defined $activePrev->{$unit} && $unit !~ /\.scope$/msx) { - push @new, $unit; + if ($state->{state} ne "failed" && !defined($active_cur->{$unit}) && $unit !~ /\.scope$/msx) { + push(@new, $unit); } } -if (scalar @new > 0) { +if (scalar(@new) > 0) { print STDERR "the following new units were started: ", join(", ", sort(@new)), "\n" } -if (scalar @failed > 0) { - my @failed_sorted = sort @failed; +if (scalar(@failed) > 0) { + my @failed_sorted = sort(@failed); print STDERR "warning: the following units failed: ", join(", ", @failed_sorted), "\n\n"; - system "@systemd@/bin/systemctl status --no-pager --full '" . join("' '", @failed_sorted) . "' >&2"; + system("$new_systemd/bin/systemctl status --no-pager --full '" . join("' '", @failed_sorted) . "' >&2"); $res = 4; } @@ -773,4 +917,4 @@ if ($res == 0) { syslog(LOG_ERR, "switching to system configuration $out failed (status $res)"); } -exit $res; +exit($res); diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix index d147155d796c1..db00244ca0afa 100644 --- a/nixos/modules/system/boot/kernel.nix +++ b/nixos/modules/system/boot/kernel.nix @@ -36,7 +36,7 @@ in boot.kernelPackages = mkOption { default = pkgs.linuxPackages; - type = types.unspecified // { merge = mergeEqualOption; }; + type = types.raw; apply = kernelPackages: kernelPackages.extend (self: super: { kernel = super.kernel.override (originalArgs: { inherit randstructSeed; diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index adc8930630981..fa879437fd810 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -15,9 +15,12 @@ import re import datetime import glob import os.path -from typing import Tuple, List, Optional +from typing import NamedTuple, List, Optional -SystemIdentifier = Tuple[Optional[str], int, Optional[str]] +class SystemIdentifier(NamedTuple): + profile: Optional[str] + generation: int + specialisation: Optional[str] def copy_if_not_exists(source: str, dest: str) -> None: @@ -151,7 +154,14 @@ def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]: gen_lines.pop() configurationLimit = @configurationLimit@ - configurations: List[SystemIdentifier] = [ (profile, int(line.split()[0]), None) for line in gen_lines ] + configurations = [ + SystemIdentifier( + profile=profile, + generation=int(line.split()[0]), + specialisation=None + ) + for line in gen_lines + ] return configurations[-configurationLimit:] @@ -160,7 +170,7 @@ def get_specialisations(profile: Optional[str], generation: int, _: Optional[str system_dir(profile, generation, None), "specialisation") if not os.path.exists(specialisations_dir): return [] - return [(profile, generation, spec) for spec in os.listdir(specialisations_dir)] + return [SystemIdentifier(profile, generation, spec) for spec in os.listdir(specialisations_dir)] def remove_old_entries(gens: List[SystemIdentifier]) -> None: @@ -271,7 +281,8 @@ def main() -> None: if os.readlink(system_dir(*gen)) == args.default_config: write_loader_conf(*gen) except OSError as e: - print("ignoring generation '{}' in the list of boot entries because of the following error:\n{}".format(*gen, e), file=sys.stderr) + profile = f"profile '{gen.profile}'" if gen.profile else "default profile" + print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr) for root, _, files in os.walk('@efiSysMountPoint@/efi/nixos/.extra-files', topdown=False): relative_root = root.removeprefix("@efiSysMountPoint@/efi/nixos/.extra-files").removeprefix("/") diff --git a/nixos/modules/system/boot/modprobe.nix b/nixos/modules/system/boot/modprobe.nix index 27f78835adb2e..e683d1817297a 100644 --- a/nixos/modules/system/boot/modprobe.nix +++ b/nixos/modules/system/boot/modprobe.nix @@ -34,23 +34,6 @@ with lib; type = types.lines; }; - boot.initrd.extraModprobeConfig = mkOption { - default = ""; - example = - '' - options zfs zfs_arc_max=1073741824 - ''; - description = '' - Does exactly the same thing as - <option>boot.extraModprobeConfig</option>, except - that the generated <filename>modprobe.conf</filename> - file is also included in the initrd. - This is useful for setting module options for kernel - modules that are loaded during early boot in the initrd. - ''; - type = types.lines; - }; - }; @@ -67,9 +50,6 @@ with lib; '')} ${config.boot.extraModprobeConfig} ''; - environment.etc."modprobe.d/nixos-initrd.conf".text = '' - ${config.boot.initrd.extraModprobeConfig} - ''; environment.etc."modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases; environment.etc."modprobe.d/systemd.conf".source = "${pkgs.systemd}/lib/modprobe.d/systemd.conf"; diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix index 1575c0257d1c6..8b011d91563f0 100644 --- a/nixos/modules/system/boot/stage-1.nix +++ b/nixos/modules/system/boot/stage-1.nix @@ -338,9 +338,6 @@ let { object = pkgs.writeText "mdadm.conf" config.boot.initrd.mdadmConf; symlink = "/etc/mdadm.conf"; } - { object = config.environment.etc."modprobe.d/nixos-initrd.conf".source; - symlink = "/etc/modprobe.d/nixos-initrd.conf"; - } { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf"; preferLocalBuild = true; @@ -581,7 +578,7 @@ in else "gzip" ); defaultText = literalDocBook "<literal>zstd</literal> if the kernel supports it (5.9+), <literal>gzip</literal> if not"; - type = types.unspecified; # We don't have a function type... + type = types.either types.str (types.functionTo types.str); description = '' The compressor to use on the initrd image. May be any of: diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 4019af63ad355..057474c607ac8 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -2,7 +2,6 @@ with utils; with systemdUtils.unitOptions; -with systemdUtils.lib; with lib; let @@ -11,6 +10,24 @@ let systemd = cfg.package; + inherit (systemdUtils.lib) + makeUnit + generateUnits + makeJobScript + unitConfig + serviceConfig + mountConfig + automountConfig + commonUnitText + targetToUnit + serviceToUnit + socketToUnit + timerToUnit + pathToUnit + mountToUnit + automountToUnit + sliceToUnit; + upstreamSystemUnits = [ # Targets. "basic.target" @@ -63,32 +80,6 @@ let "printer.target" "smartcard.target" - # Login stuff. - "systemd-logind.service" - "autovt@.service" - "systemd-user-sessions.service" - "dbus-org.freedesktop.import1.service" - "dbus-org.freedesktop.machine1.service" - "dbus-org.freedesktop.login1.service" - "user@.service" - "user-runtime-dir@.service" - - # Journal. - "systemd-journald.socket" - "systemd-journald@.socket" - "systemd-journald-varlink@.socket" - "systemd-journald.service" - "systemd-journald@.service" - "systemd-journal-flush.service" - "systemd-journal-catalog-update.service" - ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [ - "systemd-journald-dev-log.socket" - "syslog.socket" - - # Coredumps. - "systemd-coredump.socket" - "systemd-coredump@.service" - # Kernel module loading. "systemd-modules-load.service" "kmod-static-nodes.service" @@ -149,19 +140,12 @@ let # Slices / containers. "slices.target" - "user.slice" "machine.slice" "machines.target" "systemd-importd.service" "systemd-machined.service" "systemd-nspawn@.service" - # Temporary file creation / cleanup. - "systemd-tmpfiles-clean.service" - "systemd-tmpfiles-clean.timer" - "systemd-tmpfiles-setup.service" - "systemd-tmpfiles-setup-dev.service" - # Misc. "systemd-sysctl.service" "dbus-org.freedesktop.timedate1.service" @@ -172,9 +156,6 @@ let "systemd-hostnamed.service" "systemd-exit.service" "systemd-update-done.service" - ] ++ optionals config.services.journald.enableHttpGateway [ - "systemd-journal-gatewayd.socket" - "systemd-journal-gatewayd.service" ] ++ cfg.additionalUpstreamSystemUnits; upstreamSystemWants = @@ -185,237 +166,6 @@ let "timers.target.wants" ]; - upstreamUserUnits = [ - "app.slice" - "background.slice" - "basic.target" - "bluetooth.target" - "default.target" - "exit.target" - "graphical-session-pre.target" - "graphical-session.target" - "paths.target" - "printer.target" - "session.slice" - "shutdown.target" - "smartcard.target" - "sockets.target" - "sound.target" - "systemd-exit.service" - "systemd-tmpfiles-clean.service" - "systemd-tmpfiles-clean.timer" - "systemd-tmpfiles-setup.service" - "timers.target" - "xdg-desktop-autostart.target" - ]; - - makeJobScript = name: text: - let - scriptName = replaceChars [ "\\" "@" ] [ "-" "_" ] (shellEscape name); - out = (pkgs.writeShellScriptBin scriptName '' - set -e - ${text} - '').overrideAttrs (_: { - # The derivation name is different from the script file name - # to keep the script file name short to avoid cluttering logs. - name = "unit-script-${scriptName}"; - }); - in "${out}/bin/${scriptName}"; - - unitConfig = { config, options, ... }: { - config = { - unitConfig = - optionalAttrs (config.requires != []) - { Requires = toString config.requires; } - // optionalAttrs (config.wants != []) - { Wants = toString config.wants; } - // optionalAttrs (config.after != []) - { After = toString config.after; } - // optionalAttrs (config.before != []) - { Before = toString config.before; } - // optionalAttrs (config.bindsTo != []) - { BindsTo = toString config.bindsTo; } - // optionalAttrs (config.partOf != []) - { PartOf = toString config.partOf; } - // optionalAttrs (config.conflicts != []) - { Conflicts = toString config.conflicts; } - // optionalAttrs (config.requisite != []) - { Requisite = toString config.requisite; } - // optionalAttrs (config.restartTriggers != []) - { X-Restart-Triggers = toString config.restartTriggers; } - // optionalAttrs (config.reloadTriggers != []) - { X-Reload-Triggers = toString config.reloadTriggers; } - // optionalAttrs (config.description != "") { - Description = config.description; } - // optionalAttrs (config.documentation != []) { - Documentation = toString config.documentation; } - // optionalAttrs (config.onFailure != []) { - OnFailure = toString config.onFailure; } - // optionalAttrs (options.startLimitIntervalSec.isDefined) { - StartLimitIntervalSec = toString config.startLimitIntervalSec; - } // optionalAttrs (options.startLimitBurst.isDefined) { - StartLimitBurst = toString config.startLimitBurst; - }; - }; - }; - - serviceConfig = { name, config, ... }: { - config = mkMerge - [ { # Default path for systemd services. Should be quite minimal. - path = mkAfter - [ pkgs.coreutils - pkgs.findutils - pkgs.gnugrep - pkgs.gnused - systemd - ]; - environment.PATH = "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}"; - } - (mkIf (config.preStart != "") - { serviceConfig.ExecStartPre = - [ (makeJobScript "${name}-pre-start" config.preStart) ]; - }) - (mkIf (config.script != "") - { serviceConfig.ExecStart = - makeJobScript "${name}-start" config.script + " " + config.scriptArgs; - }) - (mkIf (config.postStart != "") - { serviceConfig.ExecStartPost = - [ (makeJobScript "${name}-post-start" config.postStart) ]; - }) - (mkIf (config.reload != "") - { serviceConfig.ExecReload = - makeJobScript "${name}-reload" config.reload; - }) - (mkIf (config.preStop != "") - { serviceConfig.ExecStop = - makeJobScript "${name}-pre-stop" config.preStop; - }) - (mkIf (config.postStop != "") - { serviceConfig.ExecStopPost = - makeJobScript "${name}-post-stop" config.postStop; - }) - ]; - }; - - mountConfig = { config, ... }: { - config = { - mountConfig = - { What = config.what; - Where = config.where; - } // optionalAttrs (config.type != "") { - Type = config.type; - } // optionalAttrs (config.options != "") { - Options = config.options; - }; - }; - }; - - automountConfig = { config, ... }: { - config = { - automountConfig = - { Where = config.where; - }; - }; - }; - - commonUnitText = def: '' - [Unit] - ${attrsToSection def.unitConfig} - ''; - - targetToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable; - text = - '' - [Unit] - ${attrsToSection def.unitConfig} - ''; - }; - - serviceToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable; - text = commonUnitText def + - '' - [Service] - ${let env = cfg.globalEnvironment // def.environment; - in concatMapStrings (n: - let s = optionalString (env.${n} != null) - "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n"; - # systemd max line length is now 1MiB - # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af - in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)} - ${if def.reloadIfChanged then '' - X-ReloadIfChanged=true - '' else if !def.restartIfChanged then '' - X-RestartIfChanged=false - '' else ""} - ${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"} - ${attrsToSection def.serviceConfig} - ''; - }; - - socketToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable; - text = commonUnitText def + - '' - [Socket] - ${attrsToSection def.socketConfig} - ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)} - ${concatStringsSep "\n" (map (s: "ListenDatagram=${s}") def.listenDatagrams)} - ''; - }; - - timerToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable; - text = commonUnitText def + - '' - [Timer] - ${attrsToSection def.timerConfig} - ''; - }; - - pathToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable; - text = commonUnitText def + - '' - [Path] - ${attrsToSection def.pathConfig} - ''; - }; - - mountToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable; - text = commonUnitText def + - '' - [Mount] - ${attrsToSection def.mountConfig} - ''; - }; - - automountToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable; - text = commonUnitText def + - '' - [Automount] - ${attrsToSection def.automountConfig} - ''; - }; - - sliceToUnit = name: def: - { inherit (def) aliases wantedBy requiredBy enable; - text = commonUnitText def + - '' - [Slice] - ${attrsToSection def.sliceConfig} - ''; - }; - - logindHandlerType = types.enum [ - "ignore" "poweroff" "reboot" "halt" "kexec" "suspend" - "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock" - ]; - proxy_env = config.networking.proxy.envVars; in @@ -568,26 +318,6 @@ in ''; }; - systemd.coredump.enable = mkOption { - default = true; - type = types.bool; - description = '' - Whether core dumps should be processed by - <command>systemd-coredump</command>. If disabled, core dumps - appear in the current directory of the crashing process. - ''; - }; - - systemd.coredump.extraConfig = mkOption { - default = ""; - type = types.lines; - example = "Storage=journal"; - description = '' - Extra config options for systemd-coredump. See coredump.conf(5) man page - for available options. - ''; - }; - systemd.extraConfig = mkOption { default = ""; type = types.lines; @@ -598,142 +328,6 @@ in ''; }; - services.journald.console = mkOption { - default = ""; - type = types.str; - description = "If non-empty, write log messages to the specified TTY device."; - }; - - services.journald.rateLimitInterval = mkOption { - default = "30s"; - type = types.str; - description = '' - Configures the rate limiting interval that is applied to all - messages generated on the system. This rate limiting is applied - per-service, so that two services which log do not interfere with - each other's limit. The value may be specified in the following - units: s, min, h, ms, us. To turn off any kind of rate limiting, - set either value to 0. - - See <option>services.journald.rateLimitBurst</option> for important - considerations when setting this value. - ''; - }; - - services.journald.rateLimitBurst = mkOption { - default = 10000; - type = types.int; - description = '' - Configures the rate limiting burst limit (number of messages per - interval) that is applied to all messages generated on the system. - This rate limiting is applied per-service, so that two services - which log do not interfere with each other's limit. - - Note that the effective rate limit is multiplied by a factor derived - from the available free disk space for the journal as described on - <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html"> - journald.conf(5)</link>. - - Note that the total amount of logs stored is limited by journald settings - such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap. - - It is thus recommended to compute what period of time that you will be - able to store logs for when an application logs at full burst rate. - With default settings for log lines that are 100 Bytes long, this can - amount to just a few hours. - ''; - }; - - services.journald.extraConfig = mkOption { - default = ""; - type = types.lines; - example = "Storage=volatile"; - description = '' - Extra config options for systemd-journald. See man journald.conf - for available options. - ''; - }; - - services.journald.enableHttpGateway = mkOption { - default = false; - type = types.bool; - description = '' - Whether to enable the HTTP gateway to the journal. - ''; - }; - - services.journald.forwardToSyslog = mkOption { - default = config.services.rsyslogd.enable || config.services.syslog-ng.enable; - defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable"; - type = types.bool; - description = '' - Whether to forward log messages to syslog. - ''; - }; - - services.logind.extraConfig = mkOption { - default = ""; - type = types.lines; - example = "IdleAction=lock"; - description = '' - Extra config options for systemd-logind. See - <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html"> - logind.conf(5)</link> for available options. - ''; - }; - - services.logind.killUserProcesses = mkOption { - default = false; - type = types.bool; - description = '' - Specifies whether the processes of a user should be killed - when the user logs out. If true, the scope unit corresponding - to the session and all processes inside that scope will be - terminated. If false, the scope is "abandoned" (see - <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#"> - systemd.scope(5)</link>), and processes are not killed. - </para> - - <para> - See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link> - for more details. - ''; - }; - - services.logind.lidSwitch = mkOption { - default = "suspend"; - example = "ignore"; - type = logindHandlerType; - - description = '' - Specifies what to be done when the laptop lid is closed. - ''; - }; - - services.logind.lidSwitchDocked = mkOption { - default = "ignore"; - example = "suspend"; - type = logindHandlerType; - - description = '' - Specifies what to be done when the laptop lid is closed - and another screen is added. - ''; - }; - - services.logind.lidSwitchExternalPower = mkOption { - default = config.services.logind.lidSwitch; - defaultText = literalExpression "services.logind.lidSwitch"; - example = "ignore"; - type = logindHandlerType; - - description = '' - Specifies what to do when the laptop lid is closed and the system is - on external power. By default use the same action as specified in - services.logind.lidSwitch. - ''; - }; - systemd.sleep.extraConfig = mkOption { default = ""; type = types.lines; @@ -744,95 +338,6 @@ in ''; }; - systemd.user.extraConfig = mkOption { - default = ""; - type = types.lines; - example = "DefaultCPUAccounting=yes"; - description = '' - Extra config options for systemd user instances. See man systemd-user.conf for - available options. - ''; - }; - - systemd.tmpfiles.rules = mkOption { - type = types.listOf types.str; - default = []; - example = [ "d /tmp 1777 root root 10d" ]; - description = '' - Rules for creation, deletion and cleaning of volatile and temporary files - automatically. See - <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> - for the exact format. - ''; - }; - - systemd.tmpfiles.packages = mkOption { - type = types.listOf types.package; - default = []; - example = literalExpression "[ pkgs.lvm2 ]"; - apply = map getLib; - description = '' - List of packages containing <command>systemd-tmpfiles</command> rules. - - All files ending in .conf found in - <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename> - will be included. - If this folder does not exist or does not contain any files an error will be returned instead. - - If a <filename>lib</filename> output is available, rules are searched there and only there. - If there is no <filename>lib</filename> output it will fall back to <filename>out</filename> - and if that does not exist either, the default output will be used. - ''; - }; - - systemd.user.units = mkOption { - description = "Definition of systemd per-user units."; - default = {}; - type = with types; attrsOf (submodule ( - { name, config, ... }: - { options = concreteUnitOptions; - config = { - unit = mkDefault (makeUnit name config); - }; - })); - }; - - systemd.user.paths = mkOption { - default = {}; - type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]); - description = "Definition of systemd per-user path units."; - }; - - systemd.user.services = mkOption { - default = {}; - type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] ); - description = "Definition of systemd per-user service units."; - }; - - systemd.user.slices = mkOption { - default = {}; - type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] ); - description = "Definition of systemd per-user slice units."; - }; - - systemd.user.sockets = mkOption { - default = {}; - type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] ); - description = "Definition of systemd per-user socket units."; - }; - - systemd.user.targets = mkOption { - default = {}; - type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] ); - description = "Definition of systemd per-user target units."; - }; - - systemd.user.timers = mkOption { - default = {}; - type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] ); - description = "Definition of systemd per-user timer units."; - }; - systemd.additionalUpstreamSystemUnits = mkOption { default = [ ]; type = types.listOf types.str; @@ -968,8 +473,6 @@ in in ({ "systemd/system".source = generateUnits "system" enabledUnits enabledUpstreamSystemUnits upstreamSystemWants; - "systemd/user".source = generateUnits "user" cfg.user.units upstreamUserUnits []; - "systemd/system.conf".text = '' [Manager] ${optionalString config.systemd.enableCgroupAccounting '' @@ -995,76 +498,17 @@ in ${config.systemd.extraConfig} ''; - "systemd/user.conf".text = '' - [Manager] - ${config.systemd.user.extraConfig} - ''; - - "systemd/journald.conf".text = '' - [Journal] - Storage=persistent - RateLimitInterval=${config.services.journald.rateLimitInterval} - RateLimitBurst=${toString config.services.journald.rateLimitBurst} - ${optionalString (config.services.journald.console != "") '' - ForwardToConsole=yes - TTYPath=${config.services.journald.console} - ''} - ${optionalString (config.services.journald.forwardToSyslog) '' - ForwardToSyslog=yes - ''} - ${config.services.journald.extraConfig} - ''; - - "systemd/coredump.conf".text = - '' - [Coredump] - ${config.systemd.coredump.extraConfig} - ''; - - "systemd/logind.conf".text = '' - [Login] - KillUserProcesses=${if config.services.logind.killUserProcesses then "yes" else "no"} - HandleLidSwitch=${config.services.logind.lidSwitch} - HandleLidSwitchDocked=${config.services.logind.lidSwitchDocked} - HandleLidSwitchExternalPower=${config.services.logind.lidSwitchExternalPower} - ${config.services.logind.extraConfig} - ''; - "systemd/sleep.conf".text = '' [Sleep] ${config.systemd.sleep.extraConfig} ''; - # install provided sysctl snippets - "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf"; - "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf"; - - "tmpfiles.d".source = (pkgs.symlinkJoin { - name = "tmpfiles.d"; - paths = map (p: p + "/lib/tmpfiles.d") cfg.tmpfiles.packages; - postBuild = '' - for i in $(cat $pathsPath); do - (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || ( - echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files." - exit 1 - ) - done - '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) '' - rm -f $out/${removePrefix "tmpfiles.d/" name} - '') config.system.build.etc.passthru.targets; - }) + "/*"; - "systemd/system-generators" = { source = hooks "generators" cfg.generators; }; "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; }; }); services.dbus.enable = true; - users.users.systemd-coredump = { - uid = config.ids.uids.systemd-coredump; - group = "systemd-coredump"; - }; - users.groups.systemd-coredump = {}; users.users.systemd-network = { uid = config.ids.uids.systemd-network; group = "systemd-network"; @@ -1084,36 +528,6 @@ in unitConfig.X-StopOnReconfiguration = true; }; - systemd.tmpfiles.packages = [ - # Default tmpfiles rules provided by systemd - (pkgs.runCommand "systemd-default-tmpfiles" {} '' - mkdir -p $out/lib/tmpfiles.d - cd $out/lib/tmpfiles.d - - ln -s "${systemd}/example/tmpfiles.d/home.conf" - ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf" - ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf" - ln -s "${systemd}/example/tmpfiles.d/systemd.conf" - ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf" - ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf" - ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf" - ln -s "${systemd}/example/tmpfiles.d/tmp.conf" - ln -s "${systemd}/example/tmpfiles.d/var.conf" - ln -s "${systemd}/example/tmpfiles.d/x11.conf" - '') - # User-specified tmpfiles rules - (pkgs.writeTextFile { - name = "nixos-tmpfiles.d"; - destination = "/lib/tmpfiles.d/00-nixos.conf"; - text = '' - # This file is created automatically and should not be modified. - # Please change the option ‘systemd.tmpfiles.rules’ instead. - - ${concatStringsSep "\n" cfg.tmpfiles.rules} - ''; - }) - ]; - systemd.units = mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services @@ -1128,14 +542,6 @@ in (v: let n = escapeSystemdPath v.where; in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts); - systemd.user.units = - mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.user.paths - // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services - // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.user.slices - // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.user.sockets - // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.user.targets - // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.user.timers; - system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET" "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC" @@ -1143,11 +549,6 @@ in "TMPFS_XATTR" "SECCOMP" ]; - users.groups.systemd-journal.gid = config.ids.gids.systemd-journal; - users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway; - users.users.systemd-journal-gateway.group = "systemd-journal-gateway"; - users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway; - # Generate timer units for all services that have a ‘startAt’ value. systemd.timers = mapAttrs (name: service: @@ -1164,42 +565,14 @@ in }) (filterAttrs (name: service: service.startAt != []) cfg.user.services); - systemd.sockets.systemd-journal-gatewayd.wantedBy = - optional config.services.journald.enableHttpGateway "sockets.target"; - - # Provide the systemd-user PAM service, required to run systemd - # user instances. - security.pam.services.systemd-user = - { # Ensure that pam_systemd gets included. This is special-cased - # in systemd to provide XDG_RUNTIME_DIR. - startSession = true; - }; - # Some overrides to upstream units. systemd.services."systemd-backlight@".restartIfChanged = false; systemd.services."systemd-fsck@".restartIfChanged = false; systemd.services."systemd-fsck@".path = [ config.system.path ]; - systemd.services."user@".restartIfChanged = false; - systemd.services.systemd-journal-flush.restartIfChanged = false; systemd.services.systemd-random-seed.restartIfChanged = false; systemd.services.systemd-remount-fs.restartIfChanged = false; systemd.services.systemd-update-utmp.restartIfChanged = false; - systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions. systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild - # Restarting systemd-logind breaks X11 - # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101 - # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112 - # - this might be addressed in the future by xorg - #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ]; - systemd.services.systemd-logind.restartIfChanged = false; - systemd.services.systemd-logind.stopIfChanged = false; - # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions. - systemd.services."user-runtime-dir@".stopIfChanged = false; - systemd.services."user-runtime-dir@".restartIfChanged = false; - systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ]; - systemd.services.systemd-journald.stopIfChanged = false; - systemd.services."systemd-journald@".restartTriggers = [ config.environment.etc."systemd/journald.conf".source ]; - systemd.services."systemd-journald@".stopIfChanged = false; systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true; systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true; systemd.targets.network-online.wantedBy = [ "multi-user.target" ]; @@ -1210,8 +583,6 @@ in systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container"; systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container"; - boot.kernel.sysctl."kernel.core_pattern" = mkIf (!cfg.coredump.enable) "core"; - # Increase numeric PID range (set directly instead of copying a one-line file from systemd) # https://github.com/systemd/systemd/pull/12226 boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304); diff --git a/nixos/modules/system/boot/systemd/coredump.nix b/nixos/modules/system/boot/systemd/coredump.nix new file mode 100644 index 0000000000000..b6ee2cff1f9af --- /dev/null +++ b/nixos/modules/system/boot/systemd/coredump.nix @@ -0,0 +1,57 @@ +{ config, lib, pkgs, utils, ... }: + +with lib; + +let + cfg = config.systemd.coredump; + systemd = config.systemd.package; +in { + options = { + systemd.coredump.enable = mkOption { + default = true; + type = types.bool; + description = '' + Whether core dumps should be processed by + <command>systemd-coredump</command>. If disabled, core dumps + appear in the current directory of the crashing process. + ''; + }; + + systemd.coredump.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "Storage=journal"; + description = '' + Extra config options for systemd-coredump. See coredump.conf(5) man page + for available options. + ''; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "systemd-coredump.socket" + "systemd-coredump@.service" + ]; + + environment.etc = { + "systemd/coredump.conf".text = + '' + [Coredump] + ${cfg.extraConfig} + ''; + + # install provided sysctl snippets + "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf"; + "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf"; + }; + + users.users.systemd-coredump = { + uid = config.ids.uids.systemd-coredump; + group = "systemd-coredump"; + }; + users.groups.systemd-coredump = {}; + + boot.kernel.sysctl."kernel.core_pattern" = mkIf (!cfg.enable) "core"; + }; +} diff --git a/nixos/modules/system/boot/systemd/journald.nix b/nixos/modules/system/boot/systemd/journald.nix new file mode 100644 index 0000000000000..7e14c8ae4077f --- /dev/null +++ b/nixos/modules/system/boot/systemd/journald.nix @@ -0,0 +1,131 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.journald; +in { + options = { + services.journald.console = mkOption { + default = ""; + type = types.str; + description = "If non-empty, write log messages to the specified TTY device."; + }; + + services.journald.rateLimitInterval = mkOption { + default = "30s"; + type = types.str; + description = '' + Configures the rate limiting interval that is applied to all + messages generated on the system. This rate limiting is applied + per-service, so that two services which log do not interfere with + each other's limit. The value may be specified in the following + units: s, min, h, ms, us. To turn off any kind of rate limiting, + set either value to 0. + + See <option>services.journald.rateLimitBurst</option> for important + considerations when setting this value. + ''; + }; + + services.journald.rateLimitBurst = mkOption { + default = 10000; + type = types.int; + description = '' + Configures the rate limiting burst limit (number of messages per + interval) that is applied to all messages generated on the system. + This rate limiting is applied per-service, so that two services + which log do not interfere with each other's limit. + + Note that the effective rate limit is multiplied by a factor derived + from the available free disk space for the journal as described on + <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html"> + journald.conf(5)</link>. + + Note that the total amount of logs stored is limited by journald settings + such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap. + + It is thus recommended to compute what period of time that you will be + able to store logs for when an application logs at full burst rate. + With default settings for log lines that are 100 Bytes long, this can + amount to just a few hours. + ''; + }; + + services.journald.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "Storage=volatile"; + description = '' + Extra config options for systemd-journald. See man journald.conf + for available options. + ''; + }; + + services.journald.enableHttpGateway = mkOption { + default = false; + type = types.bool; + description = '' + Whether to enable the HTTP gateway to the journal. + ''; + }; + + services.journald.forwardToSyslog = mkOption { + default = config.services.rsyslogd.enable || config.services.syslog-ng.enable; + defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable"; + type = types.bool; + description = '' + Whether to forward log messages to syslog. + ''; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "systemd-journald.socket" + "systemd-journald@.socket" + "systemd-journald-varlink@.socket" + "systemd-journald.service" + "systemd-journald@.service" + "systemd-journal-flush.service" + "systemd-journal-catalog-update.service" + ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [ + "systemd-journald-dev-log.socket" + "syslog.socket" + ] ++ optionals cfg.enableHttpGateway [ + "systemd-journal-gatewayd.socket" + "systemd-journal-gatewayd.service" + ]; + + environment.etc = { + "systemd/journald.conf".text = '' + [Journal] + Storage=persistent + RateLimitInterval=${cfg.rateLimitInterval} + RateLimitBurst=${toString cfg.rateLimitBurst} + ${optionalString (cfg.console != "") '' + ForwardToConsole=yes + TTYPath=${cfg.console} + ''} + ${optionalString (cfg.forwardToSyslog) '' + ForwardToSyslog=yes + ''} + ${cfg.extraConfig} + ''; + }; + + users.groups.systemd-journal.gid = config.ids.gids.systemd-journal; + users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway; + users.users.systemd-journal-gateway.group = "systemd-journal-gateway"; + users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway; + + systemd.sockets.systemd-journal-gatewayd.wantedBy = + optional cfg.enableHttpGateway "sockets.target"; + + systemd.services.systemd-journal-flush.restartIfChanged = false; + systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ]; + systemd.services.systemd-journald.stopIfChanged = false; + systemd.services."systemd-journald@".restartTriggers = [ config.environment.etc."systemd/journald.conf".source ]; + systemd.services."systemd-journald@".stopIfChanged = false; + }; +} diff --git a/nixos/modules/system/boot/systemd/logind.nix b/nixos/modules/system/boot/systemd/logind.nix new file mode 100644 index 0000000000000..c1e6cfe61d041 --- /dev/null +++ b/nixos/modules/system/boot/systemd/logind.nix @@ -0,0 +1,114 @@ +{ config, lib, pkgs, utils, ... }: + +with lib; + +let + cfg = config.services.logind; + + logindHandlerType = types.enum [ + "ignore" "poweroff" "reboot" "halt" "kexec" "suspend" + "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock" + ]; +in +{ + options = { + services.logind.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "IdleAction=lock"; + description = '' + Extra config options for systemd-logind. See + <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html"> + logind.conf(5)</link> for available options. + ''; + }; + + services.logind.killUserProcesses = mkOption { + default = false; + type = types.bool; + description = '' + Specifies whether the processes of a user should be killed + when the user logs out. If true, the scope unit corresponding + to the session and all processes inside that scope will be + terminated. If false, the scope is "abandoned" (see + <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#"> + systemd.scope(5)</link>), and processes are not killed. + </para> + + <para> + See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link> + for more details. + ''; + }; + + services.logind.lidSwitch = mkOption { + default = "suspend"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to be done when the laptop lid is closed. + ''; + }; + + services.logind.lidSwitchDocked = mkOption { + default = "ignore"; + example = "suspend"; + type = logindHandlerType; + + description = '' + Specifies what to be done when the laptop lid is closed + and another screen is added. + ''; + }; + + services.logind.lidSwitchExternalPower = mkOption { + default = cfg.lidSwitch; + defaultText = literalExpression "services.logind.lidSwitch"; + example = "ignore"; + type = logindHandlerType; + + description = '' + Specifies what to do when the laptop lid is closed and the system is + on external power. By default use the same action as specified in + services.logind.lidSwitch. + ''; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "systemd-logind.service" + "autovt@.service" + "systemd-user-sessions.service" + "dbus-org.freedesktop.import1.service" + "dbus-org.freedesktop.machine1.service" + "dbus-org.freedesktop.login1.service" + "user@.service" + "user-runtime-dir@.service" + ]; + + environment.etc = { + "systemd/logind.conf".text = '' + [Login] + KillUserProcesses=${if cfg.killUserProcesses then "yes" else "no"} + HandleLidSwitch=${cfg.lidSwitch} + HandleLidSwitchDocked=${cfg.lidSwitchDocked} + HandleLidSwitchExternalPower=${cfg.lidSwitchExternalPower} + ${cfg.extraConfig} + ''; + }; + + # Restarting systemd-logind breaks X11 + # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101 + # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112 + # - this might be addressed in the future by xorg + #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ]; + systemd.services.systemd-logind.restartIfChanged = false; + systemd.services.systemd-logind.stopIfChanged = false; + + # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions. + systemd.services."user-runtime-dir@".stopIfChanged = false; + systemd.services."user-runtime-dir@".restartIfChanged = false; + }; +} diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd/nspawn.nix index 0c6822319a5b0..0c6822319a5b0 100644 --- a/nixos/modules/system/boot/systemd-nspawn.nix +++ b/nixos/modules/system/boot/systemd/nspawn.nix diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix new file mode 100644 index 0000000000000..57d44c8591ed1 --- /dev/null +++ b/nixos/modules/system/boot/systemd/tmpfiles.nix @@ -0,0 +1,104 @@ +{ config, lib, pkgs, utils, ... }: + +with lib; + +let + cfg = config.systemd.tmpfiles; + systemd = config.systemd.package; +in +{ + options = { + systemd.tmpfiles.rules = mkOption { + type = types.listOf types.str; + default = []; + example = [ "d /tmp 1777 root root 10d" ]; + description = '' + Rules for creation, deletion and cleaning of volatile and temporary files + automatically. See + <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> + for the exact format. + ''; + }; + + systemd.tmpfiles.packages = mkOption { + type = types.listOf types.package; + default = []; + example = literalExpression "[ pkgs.lvm2 ]"; + apply = map getLib; + description = '' + List of packages containing <command>systemd-tmpfiles</command> rules. + + All files ending in .conf found in + <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename> + will be included. + If this folder does not exist or does not contain any files an error will be returned instead. + + If a <filename>lib</filename> output is available, rules are searched there and only there. + If there is no <filename>lib</filename> output it will fall back to <filename>out</filename> + and if that does not exist either, the default output will be used. + ''; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "systemd-tmpfiles-clean.service" + "systemd-tmpfiles-clean.timer" + "systemd-tmpfiles-setup.service" + "systemd-tmpfiles-setup-dev.service" + ]; + + systemd.additionalUpstreamUserUnits = [ + "systemd-tmpfiles-clean.service" + "systemd-tmpfiles-clean.timer" + "systemd-tmpfiles-setup.service" + ]; + + environment.etc = { + "tmpfiles.d".source = (pkgs.symlinkJoin { + name = "tmpfiles.d"; + paths = map (p: p + "/lib/tmpfiles.d") cfg.packages; + postBuild = '' + for i in $(cat $pathsPath); do + (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || ( + echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files." + exit 1 + ) + done + '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) '' + rm -f $out/${removePrefix "tmpfiles.d/" name} + '') config.system.build.etc.passthru.targets; + }) + "/*"; + }; + + systemd.tmpfiles.packages = [ + # Default tmpfiles rules provided by systemd + (pkgs.runCommand "systemd-default-tmpfiles" {} '' + mkdir -p $out/lib/tmpfiles.d + cd $out/lib/tmpfiles.d + + ln -s "${systemd}/example/tmpfiles.d/home.conf" + ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf" + ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf" + ln -s "${systemd}/example/tmpfiles.d/systemd.conf" + ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf" + ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf" + ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf" + ln -s "${systemd}/example/tmpfiles.d/tmp.conf" + ln -s "${systemd}/example/tmpfiles.d/var.conf" + ln -s "${systemd}/example/tmpfiles.d/x11.conf" + '') + # User-specified tmpfiles rules + (pkgs.writeTextFile { + name = "nixos-tmpfiles.d"; + destination = "/lib/tmpfiles.d/00-nixos.conf"; + text = '' + # This file is created automatically and should not be modified. + # Please change the option ‘systemd.tmpfiles.rules’ instead. + + ${concatStringsSep "\n" cfg.rules} + ''; + }) + ]; + }; +} diff --git a/nixos/modules/system/boot/systemd/user.nix b/nixos/modules/system/boot/systemd/user.nix new file mode 100644 index 0000000000000..e30f83f3457f8 --- /dev/null +++ b/nixos/modules/system/boot/systemd/user.nix @@ -0,0 +1,158 @@ +{ config, lib, pkgs, utils, ... }: +with utils; +with systemdUtils.unitOptions; +with lib; + +let + cfg = config.systemd.user; + + systemd = config.systemd.package; + + inherit + (systemdUtils.lib) + makeUnit + generateUnits + makeJobScript + unitConfig + serviceConfig + commonUnitText + targetToUnit + serviceToUnit + socketToUnit + timerToUnit + pathToUnit; + + upstreamUserUnits = [ + "app.slice" + "background.slice" + "basic.target" + "bluetooth.target" + "default.target" + "exit.target" + "graphical-session-pre.target" + "graphical-session.target" + "paths.target" + "printer.target" + "session.slice" + "shutdown.target" + "smartcard.target" + "sockets.target" + "sound.target" + "systemd-exit.service" + "timers.target" + "xdg-desktop-autostart.target" + ] ++ config.systemd.additionalUpstreamUserUnits; +in { + options = { + systemd.user.extraConfig = mkOption { + default = ""; + type = types.lines; + example = "DefaultCPUAccounting=yes"; + description = '' + Extra config options for systemd user instances. See man systemd-user.conf for + available options. + ''; + }; + + systemd.user.units = mkOption { + description = "Definition of systemd per-user units."; + default = {}; + type = with types; attrsOf (submodule ( + { name, config, ... }: + { options = concreteUnitOptions; + config = { + unit = mkDefault (makeUnit name config); + }; + })); + }; + + systemd.user.paths = mkOption { + default = {}; + type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]); + description = "Definition of systemd per-user path units."; + }; + + systemd.user.services = mkOption { + default = {}; + type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] ); + description = "Definition of systemd per-user service units."; + }; + + systemd.user.slices = mkOption { + default = {}; + type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] ); + description = "Definition of systemd per-user slice units."; + }; + + systemd.user.sockets = mkOption { + default = {}; + type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] ); + description = "Definition of systemd per-user socket units."; + }; + + systemd.user.targets = mkOption { + default = {}; + type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] ); + description = "Definition of systemd per-user target units."; + }; + + systemd.user.timers = mkOption { + default = {}; + type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] ); + description = "Definition of systemd per-user timer units."; + }; + + systemd.additionalUpstreamUserUnits = mkOption { + default = []; + type = types.listOf types.str; + example = []; + description = '' + Additional units shipped with systemd that should be enabled for per-user systemd instances. + ''; + internal = true; + }; + }; + + config = { + systemd.additionalUpstreamSystemUnits = [ + "user.slice" + ]; + + environment.etc = { + "systemd/user".source = generateUnits "user" cfg.units upstreamUserUnits []; + + "systemd/user.conf".text = '' + [Manager] + ${cfg.extraConfig} + ''; + }; + + systemd.user.units = + mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths + // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services + // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices + // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets + // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets + // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers; + + # Generate timer units for all services that have a ‘startAt’ value. + systemd.user.timers = + mapAttrs (name: service: { + wantedBy = ["timers.target"]; + timerConfig.OnCalendar = service.startAt; + }) + (filterAttrs (name: service: service.startAt != []) cfg.services); + + # Provide the systemd-user PAM service, required to run systemd + # user instances. + security.pam.services.systemd-user = + { # Ensure that pam_systemd gets included. This is special-cased + # in systemd to provide XDG_RUNTIME_DIR. + startSession = true; + }; + + # Some overrides to upstream units. + systemd.services."user@".restartIfChanged = false; + systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions. + }; +} diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix index b931b27ad8170..1404dcbaf7c0f 100644 --- a/nixos/modules/tasks/auto-upgrade.nix +++ b/nixos/modules/tasks/auto-upgrade.nix @@ -80,6 +80,7 @@ in { Reboot the system into the new generation instead of a switch if the new generation uses a different kernel, kernel modules or initrd than the booted system. + See <option>rebootWindow</option> for configuring the times at which a reboot is allowed. ''; }; @@ -96,6 +97,32 @@ in { ''; }; + rebootWindow = mkOption { + description = '' + Define a lower and upper time value (in HH:MM format) which + constitute a time window during which reboots are allowed after an upgrade. + This option only has an effect when <option>allowReboot</option> is enabled. + The default value of <literal>null</literal> means that reboots are allowed at any time. + ''; + default = null; + example = { lower = "01:00"; upper = "05:00"; }; + type = with types; nullOr (submodule { + options = { + lower = mkOption { + description = "Lower limit of the reboot window"; + type = types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}"; + example = "01:00"; + }; + + upper = mkOption { + description = "Upper limit of the reboot window"; + type = types.strMatching "[[:digit:]]{2}:[[:digit:]]{2}"; + example = "05:00"; + }; + }; + }); + }; + }; }; @@ -110,12 +137,10 @@ in { }]; system.autoUpgrade.flags = (if cfg.flake == null then - [ "--no-build-output" ] ++ (if cfg.channel == null then - [ "--upgrade" ] - else [ + [ "--no-build-output" ] ++ optionals (cfg.channel != null) [ "-I" "nixpkgs=${cfg.channel}/nixexprs.tar.xz" - ]) + ] else [ "--flake ${cfg.flake}" ]); @@ -143,19 +168,52 @@ in { ]; script = let - nixos-rebuild = - "${config.system.build.nixos-rebuild}/bin/nixos-rebuild"; + nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild"; + date = "${pkgs.coreutils}/bin/date"; + readlink = "${pkgs.coreutils}/bin/readlink"; + shutdown = "${pkgs.systemd}/bin/shutdown"; + upgradeFlag = optional (cfg.channel == null) "--upgrade"; in if cfg.allowReboot then '' - ${nixos-rebuild} boot ${toString cfg.flags} - booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})" - built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})" - if [ "$booted" = "$built" ]; then + ${nixos-rebuild} boot ${toString (cfg.flags ++ upgradeFlag)} + booted="$(${readlink} /run/booted-system/{initrd,kernel,kernel-modules})" + built="$(${readlink} /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})" + + ${optionalString (cfg.rebootWindow != null) '' + current_time="$(${date} +%H:%M)" + + lower="${cfg.rebootWindow.lower}" + upper="${cfg.rebootWindow.upper}" + + if [[ "''${lower}" < "''${upper}" ]]; then + if [[ "''${current_time}" > "''${lower}" ]] && \ + [[ "''${current_time}" < "''${upper}" ]]; then + do_reboot="true" + else + do_reboot="false" + fi + else + # lower > upper, so we are crossing midnight (e.g. lower=23h, upper=6h) + # we want to reboot if cur > 23h or cur < 6h + if [[ "''${current_time}" < "''${upper}" ]] || \ + [[ "''${current_time}" > "''${lower}" ]]; then + do_reboot="true" + else + do_reboot="false" + fi + fi + ''} + + if [ "''${booted}" = "''${built}" ]; then ${nixos-rebuild} switch ${toString cfg.flags} + ${optionalString (cfg.rebootWindow != null) '' + elif [ "''${do_reboot}" != true ]; then + echo "Outside of configured reboot window, skipping." + ''} else - /run/current-system/sw/bin/shutdown -r +1 + ${shutdown} -r +1 fi '' else '' - ${nixos-rebuild} switch ${toString cfg.flags} + ${nixos-rebuild} switch ${toString (cfg.flags ++ upgradeFlag)} ''; startAt = cfg.dates; @@ -167,3 +225,4 @@ in { }; } + diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index 06117ab451d3c..01980b80f1cfd 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -1021,6 +1021,12 @@ in dev = "enp4s0f0"; type = "tap"; }; + gre6Tunnel = { + remote = "fd7a:5634::1"; + local = "fd7a:5634::2"; + dev = "enp4s0f0"; + type = "tun6"; + }; } ''; description = '' @@ -1058,10 +1064,15 @@ in }; type = mkOption { - type = with types; enum [ "tun" "tap" ]; + type = with types; enum [ "tun" "tap" "tun6" "tap6" ]; default = "tap"; example = "tap"; - apply = v: if v == "tun" then "gre" else "gretap"; + apply = v: { + tun = "gre"; + tap = "gretap"; + tun6 = "ip6gre"; + tap6 = "ip6gretap"; + }.${v}; description = '' Whether the tunnel routes layer 2 (tap) or layer 3 (tun) traffic. ''; diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix index 5af9baff8bc1b..f40481727830b 100644 --- a/nixos/modules/virtualisation/oci-containers.nix +++ b/nixos/modules/virtualisation/oci-containers.nix @@ -22,11 +22,13 @@ let type = with types; nullOr package; default = null; description = '' - Path to an image file to load instead of pulling from a registry. - If defined, do not pull from registry. + Path to an image file to load before running the image. This can + be used to bypass pulling the image from the registry. - You still need to set the <literal>image</literal> attribute, as it - will be used as the image name for docker to start a container. + The <literal>image</literal> attribute must match the name and + tag of the image contained in this file, as they will be used to + run the container with that image. If they do not match, the + image will be pulled from the registry as usual. ''; example = literalExpression "pkgs.dockerTools.buildImage {...};"; }; |