Writing a service that depends on Xorg

33

13

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?

mkaito

Posted 2014-05-28T14:22:53.607

Reputation: 1 651

Answers

23

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 and Debian Stretch).

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

John Eikenberry

Posted 2014-05-28T14:22:53.607

Reputation: 346

2If you can provide an example unit file, and explain how to have the unit be able to use DISPLAY and XAUTHORITY, I'll be happy to switch the accepted answer. – mkaito – 2016-09-27T23:10:15.497

@mkaito I'll look into that once Debian releases Stretch. I'm running Debian stable and was waiting until then to play with it more. – John Eikenberry – 2017-02-25T20:28:35.717

@mkaito At https://github.com/systemd/systemd/blob/v219/NEWS#L194 it says "An X11 session scriptlet is now shipped that uploads $DISPLAY and $XAUTHORITY into the environment of the systemd --user daemon if a session begins. This should improve compatibility with X11 enabled applications run as systemd user services."

– josch – 2018-04-03T22:48:48.747

I would still like to see an example unit file, just to make it clear if there's anything special needed. – mkaito – 2018-04-11T08:16:30.613

11

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.

user1686

Posted 2014-05-28T14:22:53.607

Reputation: 283 655

While redshift is not a system-wide service in many cases it makes sense to be a user service which is exactly what the OP is trying to do. – simotek – 2018-06-27T01:08:07.433

5

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.

gue

Posted 2014-05-28T14:22:53.607

Reputation: 151

I know this is an old comment but you can also add startup/login scripts via the System settings app under Workspace > Startup and Shutdown > Autostart, if you want to put those scripts in a place you will remember them. – AmbientCyan – 2019-07-24T15:51:08.927

2

This solution does exactly what the author of the question asks:

it needs to wait until Xorg is up and running

While there could be better ways of doing it, as already answered by other users, this is another approach to this problem.

It is similar to systemd's systemd-networkd-wait-online.service which blocks until certain criteria are met. Other services which depend on it will be launched as soon as this service starts successfully or times out.

Per the manual (section "Files"), X server will create a UNIX socket /tmp/.X11-unix/Xn (where n is a display number).

By monitoring presence of this socket we can determine that the server for particular display has started.

confirm_x_started.sh:

#!/bin/bash
COUNTER=0

while [ 1 ]
do
  # Check whether or not socket exists
  if [ -S /tmp/.X11-unix/X0 ]
  then
    exit 0
  fi

  ((++COUNTER))

  if [ $COUNTER -gt 20 ]
  then
    exit 1
  fi

  sleep 0.5
done

x_server_started.service:

[Unit]
Description=Monitor X server start

[Service]
Type=oneshot
ExecStart=/path/to/confirm_x_started.sh

[Install]
WantedBy=example.target

Now, enable x_server_started.service to start at the same time with the X server.

Make other services (which need X server to be started) to depend on x_server_started.service

dependent unit:

[Unit]
Description=Service that needs to have the X server started
Requires=x_server_started.service
After=x_server_started.service

[Service]
ExecStart=/path/to/binary

[Install]
WantedBy=example.target

If X server starts without a problem, the x_server_started.service will start almost immediately and systemd will proceed to start all units that are dependent on x_server_started.service.

VL-80

Posted 2014-05-28T14:22:53.607

Reputation: 3 867

This works nicely. The extra service is a nice touch. You can also just use ExecStartPre in your target service. I had to add an extra 'sleep 1' before 'exit 0' though, it seems it was a bit too fast to just try and catch X right away. – TTimo – 2018-10-08T14:46:43.620