Failure to change my desktop background via crontab

I have a shell script called test.sh:

#!/bin/sh                                                                                            
touch /home/me/testing.txt
/usr/bin/gsettings set org.gnome.desktop.background picture-uri "file:///home/me/Downloads/apod.jpg"
  1. When I run it from the terminal using bash test.sh it works perfectly.

  2. When I run it via crontab, it executes and the testing.txt file is created, but the line that changes the desktop background doesn’t work. (crontab entry is * * * * * bash /home/me/test.sh).

  3. When I run the change background line on its own at the terminal: /usr/bin/gsettings set org.gnome.desktop.background picture-uri "file:///home/me/Downloads/apod.jpg" it works perfectly.

I’m having trouble pinning down where the actual problem is and why the second part of the shell script doesn’t work when running through crontab.

It has been suggested to me that:

A cron job in a multi-user system, each user sitting on his own desktop, cannot know which one to change.

And so perhaps this is the problem. Can anyone suggest how I can fix this? I don’t have to use crontab, I just want some script to run once a day.

I’m using Ubuntu 18.04.


EDIT: The top answer to this question looks promising. I added the lines:

# export DBUS_SESSION_BUS_ADDRESS environment variable
PID=$(pgrep gnome-session)
export DBUS_SESSION_BUS_ADDRESS=$(grep -z DBUS_SESSION_BUS_ADDRESS /proc/$PID/environ|cut -d= -f2-)

to my shell script, but this still didn’t work, but I don’t understand what I’m doing well enough to know if I have to tailor these lines to my own system somehow.

Answer

Yes, the gsettings command needs access to the D-Bus session bus of your desktop session.

I’m not entirely sure of Ubuntu 18.04’s architecture, but I think it was already fully systemd-based, so this simpler version may work:

export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus"

If that doesn’t help, then make the desktop environment stash the correct address somewhere that the cron job could retrieve:

  • ~/.xprofile:

    echo "$DBUS_SESSION_BUS_ADDRESS" > ~/.dbus.env
    
  • Your cronjob:

    export DBUS_SESSION_BUS_ADDRESS="$(cat ~/.dbus.env)"
    

Whichever method you choose, you should actually check whether it gives the same results as simply doing echo $DBUS_SESSION_BUS_ADDRESS from a graphical terminal. (You could additionally use ‘busctl –user’ to test the connection from both a terminal and a cronjob.)

A cron job in a multi-user system, each user sitting on his own desktop, cannot know which one to change.

This is kind of related, but originally this was about a slightly different situation. It talks about the case where people try to directly use an X11 app to draw a new wallpaper on the background (X root window), e.g. ‘hsetroot’.

It’s not that cron jobs don’t know which user they are running for; they do. The problem is kind of the opposite, it’s that X11 displays have no relationship to users – if all you have is an UID, there is no way to find the X11 display for that UID. (There could be more than one X11 display per user, in fact!)

Programs running directly inside your desktop session get this information from the DISPLAY environment variable, but cron jobs aren’t part of any desktop session and don’t have DISPLAY – so yes, a cron job doesn’t know which X11 display to connect to.

In your case, however, that’s not very related because gsettings does not use X11 – it only has to talk via D-Bus to a settings storage service. (Your desktop environment (GNOME) automatically pulls the new wallpaper URL instead of your cronjob having to push it there, so the original DISPLAY problem goes away.)

So it basically replaces the problem of finding the X server, with the problem of finding the D-Bus server.

In older distros, it wouldn’t have made any difference, as the D-Bus addresses were randomly generated and didn’t really have any relationship to your UID either, so the program still had to be given the correct DBUS_SESSION_BUS_ADDRESS upfront (or derive it indirectly from DISPLAY, which is why some people think D-Bus requires DISPLAY).

The StackOverflow post you’ve found is mostly correct about this, though now slightly outdated. (But its suggested workaround of pgrep gnome-session completely forgets the possibility that there could be multiple gnome-session instances owned by different users, so you really should ask pgrep to filter by UID as well.)

However, things have slightly changed with systemd pushing towards the D-Bus session bus being just one per user (and limiting each user to one graphical session). As part of this, the DBUS_SESSION_BUS_ADDRESS is no longer unpredictable – it is always at the same location that only varies by UID, and can be known by cron jobs. This is the “/run/user/…/bus” path that I suggested above.

Note that in some Debian and Ubuntu versions, this new architecture is opt-in by installing the dbus-user-session package – without it, the desktop environment will still use the old method of randomly generating the D-Bus address every time.

(In addition to this, systems with ‘systemd-logind’ do have an easier way to determine “the” X11 display of a graphical session if there is one, at least with display managers which create it as a graphical session upfront. Meanwhile, Wayland took the same approach of putting the display socket under /run/user – there still could be multiple, but at least the default “wayland-0” is easy to find from just the UID.)

Leave a Reply

Your email address will not be published. Required fields are marked *