Writing a service that depends on Xorg

I'm trying to write a user level service for redshift, and it needs to wait until Xorg is up and running. My current service file looks like this:

[Unit]
Description=Redshift
After=graphical.target

[Service]
Environment=DISPLAY=:0
ExecStart=/bin/redshift -l 28:-13 -t 5300:3300 -b 0.80:0.91 -m randr
Restart=always

[Install]
WantedBy=default.target

However, it seems that it attempts to start before Xorg is up, and I have to manually start the service afterwards. I guess I'm using the wrong After= target. Any hints?


Solution 1:

I've been researching this and grawity's answer seems out of date. You can now setup user services with systemd that run with as part of the user's session. They can have DISPLAY and XAUTHORITY set (currently in Arch, Debian Stretch+ and Ubuntu).

This makes sense over the previous recommendations of using desktop autostart files, as you get process management just like you would a system level app (restart, etc).

Best docs right now is the Arch wiki; Systemd/User

TLDR version;

  1. Create desired *.service file in ~/.config/systemd/user/
  2. Run systemctl --user enable [service] (exclude .service suffix)
  3. Optionally run systemctl --user start [service] to start now
  4. Use systemctl --user status [service] to check how it's doing

A couple other useful commands.

  • systemctl --user list-unit-files - view all user units
  • systemctl --user daemon-reload - if you edit a .service file

-- Later...

I upgraded and converted most of my session daemons to systemd .service files. So I can add a couple of additional notes.

There was no default hook to run the services at login, so you must trigger it yourself. I do it from my ~/.xsession file.

systemctl --user import-environment PATH DBUS_SESSION_BUS_ADDRESS
systemctl --no-block --user start xsession.target

The first line imports some environment variables into the systemd user session and the second kicks off the target. My xsession.target file;

[Unit]
Description=Xsession running
BindsTo=graphical-session.target

My xbindkeys.service as an example.

[Unit]
Description=xbindkeys
PartOf=graphical-session.target

[Service]
ExecStart=/usr/bin/xbindkeys -n -f ${HOME}/projects/dotfiles/.xbindkeysrc
Restart=always

[Install]
WantedBy=xsession.target

Solution 2:

The usual hint is "don't". redshift is not a system-wide service – it would have a separate instance for each session, and it needs to know about how to connect to that specific session's Xorg.

(Xorg isn't a system service either – only the display manager is, and it also launches a separate Xorg for each session. // graphical.target will tell you when the display manager is ready, but it says nothing about when the DM actually starts the first – or all – displays.)

Just starting it on boot with DISPLAY=:0 is not enough, for there is no guarantee that there's exactly one display at any given time, nor that it is always :0 (for example, if Xorg crashes leaving a stale lockfile, the next one would run at :1 as it would think :0 is still occupied); you also need to set the path to your XAUTHORITY file as X11 requires authentication; and make sure redshift gets restarted if you ever log out & log in again.

So how to start it? Almost always, the desktop environment has several methods of starting its own session services. See an older post which already describes the two usual ones; the ~/.xprofile script and the ~/.config/autostart/*.desktop location.

If you use startx, you can use ~/.xinitrc to start such things. Standalone window managers often have their own startup/init scripts; e.g. ~/.config/openbox/autostart for Openbox.

What's common to all these methods is that the program is started from within the session – avoiding all the problems listed above.

Solution 3:

Here is what I just created as workaround to the not yet available graphical-session.target (On my Kubuntu 16.04 system):

  1. Create a pseudo systemd user unit which brings the graphical-session.target up and down.

Create ~/.config/systemd/user/xsession.target with following contents:

[Unit]
Description = Xsession up and running
BindsTo=graphical-session.target

Tell systemd about this new unit:

$> systemctl --user daemon-reload
  1. Create autostart and shutdown scripts which controls the xsession.target via the currently available mechanics of the Ubuntu 16.04 desktop.

Create ~/.config/autostart-scripts/xsession.target-login.sh with following contents:

#!/bin/bash

if ! systemctl --user is-active xsession.target &> /dev/null
then
  /bin/systemctl --user import-environment DISPLAY XAUTHORITY
  /bin/systemctl --user start xsession.target
fi

Create ~/.config/plasma-workspace/shutdown/xsession.target-logout.sh with following contents:

#!/bin/bash

if systemctl --user is-active xsession.target &> /dev/null
then
  /bin/systemctl --user stop xsession.target
fi

Make the scripts executable:

$> chmod +x ~/.config/autostart-scripts/xsession.target-login.sh
$> chmod +x ~/.config/plasma-workspace/shutdown/xsession.target-logout.sh

Note: these two files are placed where KDE will pick them up for autostart and shutdown. The files maybe placed somewhere else for other desktop environments (e.g. Gnome) - but I don't know about those environments.

Note: This workaround lacks support of multi desktop sessions. It only handles the graphical-session.target correctly as long as only one active X11 session is run on a machine (but that's the case for most of us linux users).

  1. Create your own systemd user units which depend on graphical-session.target and have them run cleanly while being logged in on your desktop.

As example @mkaito's unit should look like this:

[Unit]
Description=Redshift
PartOf=graphical-session.target

[Service]
ExecStart=/bin/redshift -l 28:-13 -t 5300:3300 -b 0.80:0.91 -m randr
Restart=always

(Don't forget to do a daemon-reload after editing your units!)

  1. Reboot your machine, login and verfiy your units are started as expected
$> systemctl --user status graphical-session.target
● graphical-session.target - Current graphical user session
   Loaded: loaded (/usr/lib/systemd/user/graphical-session.target; static; vendor preset: enabled)
   Active: active since Don 2017-01-05 15:08:42 CET; 47min ago
     Docs: man:systemd.special(7)
$> systemctl --user status your-unit...

At some future day (will it be Ubuntu 17.04?) my workaround become obsolete since the system will handle the graphical-session.target correctly itself. At that day just remove the autostart and shutdown script and also the xsession.target - your custom user units may stay untouched and just work.