BIG FAT WARNING
One thing to note, and the reason why I’m no longer using this setup: screen sessions started from within X cannot survive X restarts. If you don’t know what that means, don’t worry about it; if you do, you’ve been warned.
A while back, Arch switched to systemd for its init system. It’s pretty enjoyable from an end-user perspective, unit files are far easier to write and maintain than the old rc-scripts, the process groups are conceptually consistent and robust, and the centralized logging via
journalctl is pretty sweet.
With a recent patch to dbus, it’s now possible to run a second, user-specific instance of systemd to manage your login session. In order to describe why we might want to do this, and before we go into detail on how, it’d be useful to first talk about how a minimal graphical login session can be managed without it.
When my machine first boots, I get a dead simple, tty-based login prompt. I don’t use a display manager and consider this my graphical login.
When I enter my credentials, normal shell initialization proceeds no differently than any other time. When ZSH (my shell) gets to the end of
~/.zshenv it finds the following:
[[ $TTY == /dev/tty1 ]] \ && (( $UID )) \ && [[ -z $DISPLAY ]] \ && exec startx
Translation: if I’m logging into the first physical tty, I’m not the root user, and there’s no display already running, then start X.
More specifically, due to the
exec there, it replaces itself with X. Without this, someone would find themselves at a logged-in shell if they were to kill X – something you can do even in the presence of most screen locks.
startx command eventually sources
~/.xinitrc where we find commands for initializing my X environment: wallpaper setting, a few
xset commands, starting up
urxvtd, etc. After all that, my window manager is started.
This is all well and good, but there are a few improvements we can make by letting systemd manage this process.
First of all, the output of All The Things is hard to find. It used to be that calling
startx on tty1 would start an X session on tty7 and the output of starting up X and any applications launched in
xinitrc would at least be spammed back on tty1. That seems to no longer be the case and
startx starts X right there on tty1 hiding any output from those programs.
It’s also hard to see all your X-related processes together as one cohesive group. Some processes would remain owned by
xmonad (my window manager) but some would fork off and end up a direct child of
init. Other “one shot” commands would run (or not) and exit without any visibility about their success or failure.
Using something like systemd can address these points.
Using systemd means setting up your own targets and service files under
~/.config/systemd/user just as you do for your main system. With these service files in place, we can simply execute
systemd --user and everything will be started up (in parallel) and managed by the new instance of
We’ll be able to get useful status info about all the processes, manage them like system services, and see any output from them by using
user-session-units from the AUR, it’ll also pull in
xorg-launch-helper. This will provide us with a
xorg.target which will handle getting X running.
Now, there’s a bit of a chicken-and-the-egg problem we have to deal with. I ran into it when I first moved to systemd at the system level too. In order to have your services start automatically when you start systemd, you have to
enable them. In order to
enable them, you need systemd to be running. In this case it’s a bit trickier since the user session can’t start without one of those services we’re going to enable, but we can’t enable it without starting the user session…
The recommended way around this is to (temporarily) add
systemd --user & to the top of your current
.xinitrc and restart X.
It’s unclear to me if you could get away with just running that command from some terminal right where you are – feel free to try that first.
Now that we’re back with a user session running, we can set up our “services”.
First, we’ll write a target and service file for your window manager. I use XMonad, so mine looks like this:
[Unit] Description=XMonad Wants=xorg.target Wants=xinit.target Requires=dbus.socket AllowIsolate=true [Install] Alias=default.target
[Unit] Description=xmonad After=xorg.target [Service] ExecStart=/home/you/.cabal/bin/xmonad Environment=DISPLAY=:0 [Install] WantedBy=xmonad.target
You can see we reference
xinit.target as a
Want, this target will hold all the services we used to start as part of
xinitrc. Let’s create the target for now, we’ll worry about the services later:
[Unit] Description=Xinit Requires=xorg.target
Then, enable our main target:
$ systemctl --user enable xmonad.target
This should drop a symlink at
default.target setting that as the target to be run when you execute
At this point, if you were to quit X and run that command, it should successfully start X and load XMonad (or whatever WM you’re using). The next thing we’ll do is write service files for all the stuff you currently have in
Here are some of the ones I’m using as examples:
[Unit] Description=Wallpaper setter After=xorg.target [Service] Type=oneshot ExecStart=/usr/bin/feh --bg-tile %h/Pictures/wallpaper.png Environment=DISPLAY=:0 [Install] WantedBy=xinit.target
It appears that we can use
%h to represent our home directory, but only in certain ways. The above works, but trying to use
%h in the path to the xmonad binary does not. Sigh.
[Unit] Description=Synergy Server After=xorg.target [Service] Type=forking ExecStart=/usr/bin/synergys --debug ERROR [Install] WantedBy=xinit.target
[Unit] Description=Urxvt Daemon After=xorg.target [Service] Type=simple ExecStart=/usr/bin/urxvtd [Install] WantedBy=xinit.target
With these in place, you can enable them all. I used the following shortcut:
$ cd .config/systemd/user $ for s in *.service; do systemctl --user enable $s; done
Now, finally, simply running
systemd --user should start X, bring up all your X-related services, and start your window manager.
How you do this going forward is up to you, but in my case I simply updated the last line in my
[[ $TTY == /dev/tty1 ]] \ && (( $UID )) \ && [[ -z $DISPLAY ]] \ && exec systemd --user
Arguably, we’ve complected what used to be a pretty simple system – so what do we gain?
Well, my OCD loves seeing a nice, isolated process group of everything X-related:
We can also now use a consistent interface for working with services at both the system and X level. Included in this interface is the super useful
Finally, we get the benefits of
journalctl – running it as our non-root user will show us all the messages from our X-related processes.
There are probably a number of additional
systemd features that we can now leverage for our graphical environment, but I’m still in the process of figuring that all out.
Many thanks to gtmanfred for putting this idea in my head and going through the hard work of figuring it out and writing it up. The information has also been added to the Arch wiki.