From 6be2bfcc32d5ee203acf3b85354f5028c8bb50eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Tue, 27 Feb 2024 10:06:13 +0100 Subject: [PATCH] nixos/display-managers: extract generic display-manager bits Some settings which where before inside the xserver module, are generic and also required for SDDM under wayland. To make them easily re-usable lets extract them. --- nixos/modules/module-list.nix | 1 + .../services/display-managers/default.nix | 257 ++++++++++++++++++ .../services/x11/display-managers/default.nix | 213 +-------------- .../services/x11/display-managers/sddm.nix | 18 +- nixos/modules/services/x11/xserver.nix | 10 +- 5 files changed, 274 insertions(+), 225 deletions(-) create mode 100644 nixos/modules/services/display-managers/default.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index b361e9ee5e41..e6f3153d3b7e 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -508,6 +508,7 @@ ./services/development/nixseparatedebuginfod.nix ./services/development/rstudio-server/default.nix ./services/development/zammad.nix + ./services/display-managers/default.nix ./services/display-managers/greetd.nix ./services/editors/emacs.nix ./services/editors/haste.nix diff --git a/nixos/modules/services/display-managers/default.nix b/nixos/modules/services/display-managers/default.nix new file mode 100644 index 000000000000..93c4e481e530 --- /dev/null +++ b/nixos/modules/services/display-managers/default.nix @@ -0,0 +1,257 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.displayManager; + + installedSessions = pkgs.runCommand "desktops" + { # trivial derivation + preferLocalBuild = true; + allowSubstitutes = false; + } + '' + mkdir -p "$out/share/"{xsessions,wayland-sessions} + + ${lib.concatMapStrings (pkg: '' + for n in ${lib.concatStringsSep " " pkg.providedSessions}; do + if ! test -f ${pkg}/share/wayland-sessions/$n.desktop -o \ + -f ${pkg}/share/xsessions/$n.desktop; then + echo "Couldn't find provided session name, $n.desktop, in session package ${pkg.name}:" + echo " ${pkg}" + return 1 + fi + done + + if test -d ${pkg}/share/xsessions; then + ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions + fi + if test -d ${pkg}/share/wayland-sessions; then + ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions + fi + '') cfg.sessionPackages} + ''; + + dmDefault = config.services.xserver.desktopManager.default; + # fallback default for cases when only default wm is set + dmFallbackDefault = if dmDefault != null then dmDefault else "none"; + wmDefault = config.services.xserver.windowManager.default; + defaultSessionFromLegacyOptions = dmFallbackDefault + lib.optionalString (wmDefault != null && wmDefault != "none") "+${wmDefault}"; +in +{ + options = { + services.displayManager = { + enable = lib.mkEnableOption "systemd's display-manager service"; + + preStart = lib.mkOption { + type = lib.types.lines; + default = ""; + example = "rm -f /var/log/my-display-manager.log"; + description = lib.mdDoc "Script executed before the display manager is started."; + }; + + execCmd = lib.mkOption { + type = lib.types.str; + example = lib.literalExpression ''"''${pkgs.lightdm}/bin/lightdm"''; + description = lib.mdDoc "Command to start the display manager."; + }; + + environment = lib.mkOption { + type = with lib.types; attrsOf unspecified; + default = {}; + description = lib.mdDoc "Additional environment variables needed by the display manager."; + }; + + hiddenUsers = lib.mkOption { + type = with lib.types; listOf str; + default = [ "nobody" ]; + description = lib.mdDoc '' + A list of users which will not be shown in the display manager. + ''; + }; + + logToFile = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc '' + Whether the display manager redirects the output of the + session script to {file}`~/.xsession-errors`. + ''; + }; + + logToJournal = lib.mkOption { + type = lib.types.bool; + default = true; + description = lib.mdDoc '' + Whether the display manager redirects the output of the + session script to the systemd journal. + ''; + }; + + # Configuration for automatic login. Common for all DM. + autoLogin = lib.mkOption { + type = lib.types.submodule ({ config, options, ... }: { + options = { + enable = lib.mkOption { + type = lib.types.bool; + default = config.user != null; + defaultText = lib.literalExpression "config.${options.user} != null"; + description = lib.mdDoc '' + Automatically log in as {option}`autoLogin.user`. + ''; + }; + + user = lib.mkOption { + type = with lib.types; nullOr str; + default = null; + description = lib.mdDoc '' + User to be used for the automatic login. + ''; + }; + }; + }); + + default = {}; + description = lib.mdDoc '' + Auto login configuration attrset. + ''; + }; + + defaultSession = lib.mkOption { + type = lib.types.nullOr lib.types.str // { + description = "session name"; + check = d: + lib.assertMsg (d != null -> (lib.types.str.check d && lib.elem d config.services.displayManager.sessionData.sessionNames)) '' + Default graphical session, '${d}', not found. + Valid names for 'services.xserver.displayManager.defaultSession' are: + ${lib.concatStringsSep "\n " cfg.displayManager.sessionData.sessionNames} + ''; + }; + default = + if dmDefault != null || wmDefault != null then + defaultSessionFromLegacyOptions + else + null; + defaultText = lib.literalMD '' + Taken from display manager settings or window manager settings, if either is set. + ''; + example = "gnome"; + description = lib.mdDoc '' + Graphical session to pre-select in the session chooser (only effective for GDM, LightDM and SDDM). + + On GDM, LightDM and SDDM, it will also be used as a session for auto-login. + ''; + }; + + sessionData = lib.mkOption { + description = lib.mdDoc "Data exported for display managers’ convenience"; + internal = true; + default = {}; + }; + + sessionPackages = lib.mkOption { + type = lib.types.listOf (lib.types.package // { + description = "package with provided sessions"; + check = p: lib.assertMsg + (lib.types.package.check p && p ? providedSessions + && p.providedSessions != [] && lib.all lib.isString p.providedSessions) + '' + Package, '${p.name}', did not specify any session names, as strings, in + 'passthru.providedSessions'. This is required when used as a session package. + + The session names can be looked up in: + ${p}/share/xsessions + ${p}/share/wayland-sessions + ''; + }); + default = []; + description = lib.mdDoc '' + A list of packages containing x11 or wayland session files to be passed to the display manager. + ''; + }; + }; + }; + + imports = [ + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "autoLogin" ] [ "services" "displayManager" "autoLogin" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "defaultSession" ] [ "services" "displayManager" "defaultSession" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "environment" ] [ "services" "displayManager" "environment" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "execCmd" ] [ "services" "displayManager" "execCmd" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logToFile" ] [ "services" "displayManager" "logToFile" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logToJournal" ] [ "services" "displayManager" "logToJournal" ]) + (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "preStart" ] [ "services" "displayManager" "preStart" ]) + ]; + + config = lib.mkIf cfg.enable { + assertions = [ + { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null; + message = '' + services.displayManager.autoLogin.enable requires services.displayManager.autoLogin.user to be set + ''; + } + ]; + + warnings = + lib.mkIf (dmDefault != null || wmDefault != null) [ + '' + The following options are deprecated: + ${lib.concatStringsSep "\n " (map ({c, t}: t) (lib.filter ({c, t}: c != null) [ + { c = dmDefault; t = "- services.xserver.desktopManager.default"; } + { c = wmDefault; t = "- services.xserver.windowManager.default"; } + ]))} + Please use + services.displayManager.defaultSession = "${defaultSessionFromLegacyOptions}"; + instead. + '' + ]; + + # Make xsessions and wayland sessions available in XDG_DATA_DIRS + # as some programs have behavior that depends on them being present + environment.sessionVariables.XDG_DATA_DIRS = lib.mkIf (cfg.sessionPackages != [ ]) [ + "${cfg.sessionData.desktops}/share" + ]; + + services.displayManager.sessionData = { + desktops = installedSessions; + sessionNames = lib.concatMap (p: p.providedSessions) config.services.displayManager.sessionPackages; + # We do not want to force users to set defaultSession when they have only single DE. + autologinSession = + if cfg.defaultSession != null then + cfg.defaultSession + else if cfg.sessionData.sessionNames != [] then + lib.head cfg.sessionData.sessionNames + else + null; + }; + + # so that the service won't be enabled when only startx is used + systemd.services.display-manager.enable = + let dmConf = config.services.xserver.displayManager; + noDmUsed = !(dmConf.gdm.enable + || dmConf.sddm.enable + || dmConf.xpra.enable + || dmConf.lightdm.enable); + in lib.mkIf noDmUsed (lib.mkDefault false); + + systemd.services.display-manager = { + description = "Display Manager"; + after = [ "acpid.service" "systemd-logind.service" "systemd-user-sessions.service" ]; + restartIfChanged = false; + + environment = lib.optionalAttrs config.hardware.opengl.setLdLibraryPath { + LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.addOpenGLRunpath.driverLink ]; + } // cfg.environment; + + preStart = cfg.preStart; + script = lib.mkIf (config.systemd.services.display-manager.enable == true) cfg.execCmd; + + # Stop restarting if the display manager stops (crashes) 2 times + # in one minute. Starting X typically takes 3-4s. + startLimitIntervalSec = 30; + startLimitBurst = 3; + serviceConfig = { + Restart = "always"; + RestartSec = "200ms"; + SyslogIdentifier = "display-manager"; + }; + }; + }; +} diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix index c22048c6692e..5e2d1bf39abf 100644 --- a/nixos/modules/services/x11/display-managers/default.nix +++ b/nixos/modules/services/x11/display-managers/default.nix @@ -14,7 +14,6 @@ with lib; let cfg = config.services.xserver; - opt = options.services.xserver; xorg = pkgs.xorg; fontconfig = config.fonts.fontconfig; @@ -130,41 +129,6 @@ let exit 1 fi ''; - - installedSessions = pkgs.runCommand "desktops" - { # trivial derivation - preferLocalBuild = true; - allowSubstitutes = false; - } - '' - mkdir -p "$out/share/"{xsessions,wayland-sessions} - - ${concatMapStrings (pkg: '' - for n in ${concatStringsSep " " pkg.providedSessions}; do - if ! test -f ${pkg}/share/wayland-sessions/$n.desktop -o \ - -f ${pkg}/share/xsessions/$n.desktop; then - echo "Couldn't find provided session name, $n.desktop, in session package ${pkg.name}:" - echo " ${pkg}" - return 1 - fi - done - - if test -d ${pkg}/share/xsessions; then - ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions - fi - if test -d ${pkg}/share/wayland-sessions; then - ${pkgs.buildPackages.xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions - fi - '') cfg.displayManager.sessionPackages} - ''; - - dmDefault = cfg.desktopManager.default; - # fallback default for cases when only default wm is set - dmFallbackDefault = if dmDefault != null then dmDefault else "none"; - wmDefault = cfg.windowManager.default; - - defaultSessionFromLegacyOptions = dmFallbackDefault + optionalString (wmDefault != null && wmDefault != "none") "+${wmDefault}"; - in { @@ -215,35 +179,6 @@ in ''; }; - hiddenUsers = mkOption { - type = types.listOf types.str; - default = [ "nobody" ]; - description = lib.mdDoc '' - A list of users which will not be shown in the display manager. - ''; - }; - - sessionPackages = mkOption { - type = with types; listOf (package // { - description = "package with provided sessions"; - check = p: assertMsg - (package.check p && p ? providedSessions - && p.providedSessions != [] && all isString p.providedSessions) - '' - Package, '${p.name}', did not specify any session names, as strings, in - 'passthru.providedSessions'. This is required when used as a session package. - - The session names can be looked up in: - ${p}/share/xsessions - ${p}/share/wayland-sessions - ''; - }); - default = []; - description = lib.mdDoc '' - A list of packages containing x11 or wayland session files to be passed to the display manager. - ''; - }; - session = mkOption { default = []; type = types.listOf types.attrs; @@ -274,51 +209,6 @@ in ''; }; - sessionData = mkOption { - description = lib.mdDoc "Data exported for display managers’ convenience"; - internal = true; - default = {}; - apply = val: { - wrapper = xsessionWrapper; - desktops = installedSessions; - sessionNames = concatMap (p: p.providedSessions) cfg.displayManager.sessionPackages; - # We do not want to force users to set defaultSession when they have only single DE. - autologinSession = - if cfg.displayManager.defaultSession != null then - cfg.displayManager.defaultSession - else if cfg.displayManager.sessionData.sessionNames != [] then - head cfg.displayManager.sessionData.sessionNames - else - null; - }; - }; - - defaultSession = mkOption { - type = with types; nullOr str // { - description = "session name"; - check = d: - assertMsg (d != null -> (str.check d && elem d cfg.displayManager.sessionData.sessionNames)) '' - Default graphical session, '${d}', not found. - Valid names for 'services.xserver.displayManager.defaultSession' are: - ${concatStringsSep "\n " cfg.displayManager.sessionData.sessionNames} - ''; - }; - default = - if dmDefault != null || wmDefault != null then - defaultSessionFromLegacyOptions - else - null; - defaultText = literalMD '' - Taken from display manager settings or window manager settings, if either is set. - ''; - example = "gnome"; - description = lib.mdDoc '' - Graphical session to pre-select in the session chooser (only effective for GDM, LightDM and SDDM). - - On GDM, LightDM and SDDM, it will also be used as a session for auto-login. - ''; - }; - importedVariables = mkOption { type = types.listOf (types.strMatching "[a-zA-Z_][a-zA-Z0-9_]*"); visible = false; @@ -327,106 +217,19 @@ in ''; }; - job = { - - preStart = mkOption { - type = types.lines; - default = ""; - example = "rm -f /var/log/my-display-manager.log"; - description = lib.mdDoc "Script executed before the display manager is started."; - }; - - execCmd = mkOption { - type = types.str; - example = literalExpression ''"''${pkgs.lightdm}/bin/lightdm"''; - description = lib.mdDoc "Command to start the display manager."; - }; - - environment = mkOption { - type = types.attrsOf types.unspecified; - default = {}; - description = lib.mdDoc "Additional environment variables needed by the display manager."; - }; - - logToFile = mkOption { - type = types.bool; - default = false; - description = lib.mdDoc '' - Whether the display manager redirects the output of the - session script to {file}`~/.xsession-errors`. - ''; - }; - - logToJournal = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether the display manager redirects the output of the - session script to the systemd journal. - ''; - }; - - }; - - # Configuration for automatic login. Common for all DM. - autoLogin = mkOption { - type = types.submodule ({ config, options, ... }: { - options = { - enable = mkOption { - type = types.bool; - default = config.user != null; - defaultText = literalExpression "config.${options.user} != null"; - description = lib.mdDoc '' - Automatically log in as {option}`autoLogin.user`. - ''; - }; - - user = mkOption { - type = types.nullOr types.str; - default = null; - description = lib.mdDoc '' - User to be used for the automatic login. - ''; - }; - }; - }); - - default = {}; - description = lib.mdDoc '' - Auto login configuration attrset. - ''; - }; - }; }; config = { assertions = [ - { assertion = cfg.displayManager.autoLogin.enable -> cfg.displayManager.autoLogin.user != null; - message = '' - services.xserver.displayManager.autoLogin.enable requires services.xserver.displayManager.autoLogin.user to be set - ''; - } { assertion = cfg.desktopManager.default != null || cfg.windowManager.default != null -> cfg.displayManager.defaultSession == defaultSessionFromLegacyOptions; message = "You cannot use both services.xserver.displayManager.defaultSession option and legacy options (services.xserver.desktopManager.default and services.xserver.windowManager.default)."; } ]; - warnings = - mkIf (dmDefault != null || wmDefault != null) [ - '' - The following options are deprecated: - ${concatStringsSep "\n " (map ({c, t}: t) (filter ({c, t}: c != null) [ - { c = dmDefault; t = "- services.xserver.desktopManager.default"; } - { c = wmDefault; t = "- services.xserver.windowManager.default"; } - ]))} - Please use - services.xserver.displayManager.defaultSession = "${defaultSessionFromLegacyOptions}"; - instead. - '' - ]; + services.displayManager.sessionData.wrapper = xsessionWrapper; services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X"; @@ -449,7 +252,7 @@ in # Create desktop files and scripts for starting sessions for WMs/DMs # that do not have upstream session files (those defined using services.{display,desktop,window}Manager.session options). - services.xserver.displayManager.sessionPackages = + services.displayManager.sessionPackages = let dms = filter (s: s.manage == "desktop") cfg.displayManager.session; wms = filter (s: s.manage == "window") cfg.displayManager.session; @@ -511,20 +314,14 @@ in ) (cartesianProductOfSets { dm = dms; wm = wms; }) ); - - # Make xsessions and wayland sessions available in XDG_DATA_DIRS - # as some programs have behavior that depends on them being present - environment.sessionVariables.XDG_DATA_DIRS = lib.mkIf (cfg.displayManager.sessionPackages != [ ]) [ - "${cfg.displayManager.sessionData.desktops}/share" - ]; }; imports = [ (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ] "The option is no longer necessary because all display managers have already delegated lid management to systemd.") - (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "xserver" "displayManager" "job" "logToFile" ]) - (mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "xserver" "displayManager" "job" "logToJournal" ]) - (mkRenamedOptionModule [ "services" "xserver" "displayManager" "extraSessionFilesPackages" ] [ "services" "xserver" "displayManager" "sessionPackages" ]) + (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "displayManager" "logToFile" ]) + (mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "displayManager" "logToJournal" ]) + (mkRenamedOptionModule [ "services" "xserver" "displayManager" "extraSessionFilesPackages" ] [ "services" "displayManager" "sessionPackages" ]) ]; } diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix index a315a3ebf322..97351c5fd9fb 100644 --- a/nixos/modules/services/x11/display-managers/sddm.nix +++ b/nixos/modules/services/x11/display-managers/sddm.nix @@ -129,17 +129,17 @@ in { imports = [ (mkRemovedOptionModule - [ "services" "xserver" "displayManager" "sddm" "themes" ] - "Set the option `services.xserver.displayManager.sddm.package' instead.") + [ "services" "displayManager" "sddm" "themes" ] + "Set the option `services.displayManager.sddm.package' instead.") (mkRenamedOptionModule - [ "services" "xserver" "displayManager" "sddm" "autoLogin" "enable" ] - [ "services" "xserver" "displayManager" "autoLogin" "enable" ]) + [ "services" "displayManager" "sddm" "autoLogin" "enable" ] + [ "services" "displayManager" "autoLogin" "enable" ]) (mkRenamedOptionModule - [ "services" "xserver" "displayManager" "sddm" "autoLogin" "user" ] - [ "services" "xserver" "displayManager" "autoLogin" "user" ]) + [ "services" "displayManager" "sddm" "autoLogin" "user" ] + [ "services" "displayManager" "autoLogin" "user" ]) (mkRemovedOptionModule - [ "services" "xserver" "displayManager" "sddm" "extraConfig" ] - "Set the option `services.xserver.displayManager.sddm.settings' instead.") + [ "services" "displayManager" "sddm" "extraConfig" ] + "Set the option `services.displayManager.sddm.settings' instead.") ]; options = { @@ -281,6 +281,8 @@ in } ]; + services.displayManager.execCmd = "exec /run/current-system/sw/bin/sddm"; + security.pam.services = { sddm.text = '' auth substack login diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix index c5b168e608a4..c513bc64724f 100644 --- a/nixos/modules/services/x11/xserver.nix +++ b/nixos/modules/services/x11/xserver.nix @@ -639,6 +639,7 @@ in ###### implementation config = mkIf cfg.enable { + services.displayManager.enable = true; services.xserver.displayManager.lightdm.enable = let dmConf = cfg.displayManager; @@ -650,15 +651,6 @@ in || config.services.greetd.enable); in mkIf (default) (mkDefault true); - # so that the service won't be enabled when only startx is used - systemd.services.display-manager.enable = - let dmConf = cfg.displayManager; - noDmUsed = !(dmConf.gdm.enable - || dmConf.sddm.enable - || dmConf.xpra.enable - || dmConf.lightdm.enable); - in mkIf (noDmUsed) (mkDefault false); - hardware.opengl.enable = mkDefault true; services.xserver.videoDrivers = mkIf (cfg.videoDriver != null) [ cfg.videoDriver ];