../

NixOS without display manager

Konstantinos Foutzopoulos

A display manager or login manager is a graphical interface shown at the end of boot. It presents the user with a login screen, and when credentials are entered it starts a session on an X server. Examples of such software can be found in Debian, Arch, and Gentoo wiki. The default on a NixOS installation is LightDM, a relatively lightweight and highly customizable display manager with various front-ends (greeters) written in a variety of toolkits (NixOS defaults to the GTK one). Though a good choice, for an one-user system where user wants to just run their session without much else its features are mostly unneeded.

Disabling the display manager

NixOS allows decleratively setting various options, among them is the default session and autologin which are dependent on the display manager. For example if the user named user (cleverly thought, right?) wants to login automatically at their i3 session the following configuration may be used.

...
  services.xserver = {
      displayManager = {
        defaultSession = "none+i3";
        autoLogin = {
          enable = true;
          user = user;
        };
      };
      windowManager.i3.enable = true;
  };
...

In order for the display manager to be suppressed the following option exists. According to the documentation, this enables a dummy pseudo-display manager. Basically it disables LightDM, the display-manager systemd service and sets Xorg’s log file to null (see <nixpkgs/…/start.nix>). Most importantly it enables the xinit and startx (wrapper to xinit) commands at the global environment.

...
  services.xserver = {
      displayManager = {
        startx.enable = true;
      };
  };
...

Unfortunately it also means losing autologin functionality and some set up done by the display manager. In case anyone wonders, autologin is useful when full-disk encryption is used. As a password is entered during the boot the need for a second password just few seconds later is mostly pointless.

Autologin

Automatical login of a user can be done creating a systemd (system) service. For this the following configuration can used, adapted from an @caadar’s gist. Specifically a new target is created, the kernel logging is suppressed, and the service logs in the user named user after the multi-user target has been reached.

...
  systemd.targets = {
    "autologin-tty1" = {
      requires = [ "multi-user.target" ];
      after = [ "multi-user.target" ];
      unitConfig.AllowIsolate = "yes";
    };
  };

  systemd.services = {
    "autovt@tty1" = {
      enable = true;
      restartIfChanged = false;
      description = "autologin service at tty1";
      after = [ "suppress-kernel-logging.service" ];
      wantedBy = [ "autologin-tty1.target" ];
      serviceConfig = {
        ExecStart =  builtins.concatStringsSep " " ([
          "@${pkgs.utillinux}/sbin/agetty"
          "agetty --login-program ${pkgs.shadow}/bin/login"
          "--autologin user --noclear %I $TERM"
        ]);
        Restart = "always";
        Type = "idle";
      };
    };
    "suppress-kernel-logging" = {
      enable = true;
      restartIfChanged = false;
      description = "suppress kernel logging to the console";
      after = [ "multi-user.target" ];
      wantedBy = [ "autologin-tty1.target" ];
      serviceConfig = {
        ExecStart = "${pkgs.utillinux}/sbin/dmesg -n 1";
        Type = "oneshot";
      };
    };
...

The restartIfChange is set to false so, if a nixos-rebuild takes place and the service has changed, the session won’t absurdly restart. Nevertheless it will restart if user decides to exit the sesssion.

Autostarting X

There’re two ways to autostart X on console login. Either using the profile, or as a systemd user service. Theoretically the optimal will be the later since the profile is for shell configuration and environment set up rather running services. This is for the service manager to do (systemd in NixOS case). But it is also substantially more complicated (see Pitt’s slides) and moreover requires running a Xorg server as root (though the session will be run as user). In contrast the former way is mostly trivial and also runs Xorg as user.

First, we need to source the ~/.profile. This isn’t done by default. Rather the following configuration has to be added. This makes an /etc/profile.local sourced by /etc/profile which sources ~/.profile.

...
  environment.etc = {
      "profile.local".text = ''
        # /etc/profile.local: DO NOT EDIT -- this file has been generated automatically.
        if [ -f "$HOME/.profile" ]; then
          . "$HOME/.profile"
        fi
      '';
  };
...

The startx will run the ~/.xinitrc file. Therefore to run i3 adding the following will be enough.

exec i3

Then at the end of ~/.profile the following will run startx if no display has been set and only on logging at tty1. Therefore it won’t run on other consoles or when the shell opens in a terminal.

if [ -z "$DISPLAY" ] && [ $TTY == "/dev/tty1" ]; then
  exec startx
fi

Someone may argue that it could be added in /etc/profile.local directly. But in that case X will run before any user configuration takes place.

Set-up X

The display manager loads ~/.xprofile which is used to execute commands at the beginning of the user session, and ~/.Xresources which sets parameters for X applications. Therefore simply add the following before executing the window manager.

[ -f ~/.xprofile ] && . ~/.xprofile
[ -f ~/.Xresources ] && xrdb -merge ~/.Xresources

A display manager also loads the ~/.pam_environment. The variables used in it have to be moved to ~/.profile before starting up X. It should be noted that on NixOS there’s no /etc/environment or or /etc/security/pam_env.conf file, rather environment variables set in configuration.nix will be added in /etc/profile. This is the place where most of set up takes place making a display manager even less necessary.

Also the user’s dbus daemon has to be set. This is done adding the following to ~/.xinitrc, taken from nixos.wiki.

if test -z "$DBUS_SESSION_BUS_ADDRESS"; then
  eval $(dbus-launch --exit-with-session --sh-syntax)
fi
systemctl --user import-environment DISPLAY XAUTHORITY

if command -v dbus-update-activation-environment >/dev/null 2>&1; then
  dbus-update-activation-environment DISPLAY XAUTHORITY
fi

The wiki also shows how to run X without installing it system-wide. This could be useful in a multi-user environment or/and if running Nix on another system (non-NixOS). For both cases some modifications are required.

Finally start the graphical-session target in systemd.

systemctl --user start graphical-session.target

The graphical-session target was made for services that require a graphical session to be running. As many services are starting at that run level, without the previous some of them will never run.

TODO: Add systemd-based X autostart

TODO: Add Wayland (sway) instructions