{ config, lib, utils, pkgs, ... }: let dmcfg = config.services.displayManager; xcfg = config.services.xserver; xdmcfg = xcfg.displayManager; cfg = config.services.displayManager.ly; xEnv = config.systemd.services.display-manager.environment; ly = cfg.package.override { x11Support = cfg.x11Support; }; iniFmt = pkgs.formats.iniWithGlobalSection { }; inherit (lib) attrNames concatMapStrings getAttr mkIf mkOption mkEnableOption mkPackageOption optionalAttrs ; xserverWrapper = pkgs.writeShellScript "xserver-wrapper" '' ${concatMapStrings (n: '' export ${n}="${getAttr n xEnv}" '') (attrNames xEnv)} exec systemd-cat -t xserver-wrapper ${xdmcfg.xserverBin} ${toString xdmcfg.xserverArgs} "$@" ''; defaultConfig = { shutdown_cmd = "/run/current-system/systemd/bin/systemctl poweroff"; restart_cmd = "/run/current-system/systemd/bin/systemctl reboot"; service_name = "ly"; path = "/run/current-system/sw/bin"; term_reset_cmd = "${pkgs.ncurses}/bin/tput reset"; term_restore_cursor_cmd = "${pkgs.ncurses}/bin/tput cnorm"; waylandsessions = "${dmcfg.sessionData.desktops}/share/wayland-sessions"; xsessions = "${dmcfg.sessionData.desktops}/share/xsessions"; xauth_cmd = lib.optionalString xcfg.enable "${pkgs.xauth}/bin/xauth"; x_cmd = lib.optionalString xcfg.enable xserverWrapper; setup_cmd = dmcfg.sessionData.wrapper; brightness_up_cmd = "${lib.getExe pkgs.brightnessctl} -q -n s +10%"; brightness_down_cmd = "${lib.getExe pkgs.brightnessctl} -q -n s 10%-"; } // optionalAttrs dmcfg.autoLogin.enable { auto_login_service = "ly-autologin"; auto_login_session = dmcfg.sessionData.autologinSession; auto_login_user = dmcfg.autoLogin.user; }; finalConfig = defaultConfig // cfg.settings; cfgFile = iniFmt.generate "config.ini" { globalSection = finalConfig; }; in { options = { services.displayManager.ly = { enable = mkEnableOption "ly as the display manager"; x11Support = mkOption { description = "Whether to enable support for X11"; type = lib.types.bool; default = true; }; package = mkPackageOption pkgs [ "ly" ] { }; settings = mkOption { type = with lib.types; attrsOf iniFmt.lib.types.atom; default = { }; example = { load = false; save = false; }; description = '' Extra settings merged in and overwriting defaults in config.ini. ''; }; }; }; config = mkIf cfg.enable { assertions = [ { assertion = dmcfg.autoLogin.enable -> dmcfg.sessionData.autologinSession != null; message = '' ly auto-login requires that services.displayManager.defaultSession is set. ''; } ]; security.pam.services = { ly = { useDefaultRules = false; rules = { auth = utils.pam.autoOrderRules [ { name = "login"; control = "substack"; modulePath = "login"; } ]; account = utils.pam.autoOrderRules [ { name = "login"; control = "include"; modulePath = "login"; } ]; password = utils.pam.autoOrderRules [ { name = "login"; control = "substack"; modulePath = "login"; } ]; session = utils.pam.autoOrderRules [ { name = "login"; control = "include"; modulePath = "login"; } ]; }; }; } // optionalAttrs dmcfg.autoLogin.enable { ly-autologin = { useDefaultRules = false; rules = { auth = utils.pam.autoOrderRules [ { name = "nologin"; control = "requisite"; modulePath = "${config.security.pam.package}/lib/security/pam_nologin.so"; } { name = "ly-normal-user"; control = "required"; modulePath = "${config.security.pam.package}/lib/security/pam_succeed_if.so"; settings.quiet = true; args = lib.mkBefore [ "uid" ">=" "1000" ]; } { name = "permit"; control = "required"; modulePath = "${config.security.pam.package}/lib/security/pam_permit.so"; } ]; account = utils.pam.autoOrderRules [ { name = "ly"; control = "include"; modulePath = "ly"; } ]; password = utils.pam.autoOrderRules [ { name = "ly"; control = "include"; modulePath = "ly"; } ]; session = utils.pam.autoOrderRules [ { name = "ly"; control = "include"; modulePath = "ly"; } ]; }; }; }; environment = { etc."ly/config.ini".source = cfgFile; systemPackages = [ ly ]; pathsToLink = [ "/share/ly" ]; }; services = { dbus.packages = [ ly ]; displayManager = { enable = true; generic = { enable = true; execCmd = "exec /run/current-system/sw/bin/ly"; }; # Set this here instead of 'defaultConfig' so users get eval # errors when they change it. ly.settings.tty = 1; }; xserver = { # To enable user switching, allow ly to allocate displays dynamically. display = null; }; }; systemd = { # We're not using the upstream unit, so copy these: # https://github.com/fairyglade/ly/blob/master/res/ly.service services.display-manager = { after = [ "systemd-user-sessions.service" "plymouth-quit-wait.service" ]; serviceConfig = { Type = "idle"; StandardInput = "tty"; TTYPath = "/dev/tty${toString finalConfig.tty}"; TTYReset = "yes"; TTYVHangup = "yes"; }; }; }; }; meta.maintainers = with lib.maintainers; [ vonfry zacharyarnaise ]; }