0

I am trying to run a simple script that checks the battery percentage and shows it through a notification (notify-send).
The script runs correctly if ran from terminal manually: ~/path-to-script/my_script.sh or sh ~/path-to-script/my_script.sh or even bash ~/path-to-my-script/my_script.sh.
Where correctly means that the notification is shown.

However when trying to run it through cron no notification appears. This is what crontab -e looks like:

*/1 * * * * /usr/bin/sh ~/path-to-my-script/my_script.sh

I have, also, set some environment variables in crontab with sudo vim /etc/crontab:

SHELL=/usr/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

And this is the log from sudo service cron status:

gen 28 17:35:01 my_user CRON[7863]: pam_unix(cron:session): session opened for user my_user(uid=1001) by (uid=0)
gen 28 17:35:01 my_user CRON[7864]: (my_user) CMD (/usr/bin/sh ~/path-to-my-script/my_script.sh)
gen 28 17:35:01 my_user CRON[7863]: (CRON) info (No MTA installed, discarding output)
gen 28 17:35:01 my_user CRON[7863]: pam_unix(cron:session): session closed for user my_user

I am running on Ubuntu 22.04 using i3 as window manager. What could be the cause of this issue?

Please ask if more info is needed.

  • The log message seems to indicate that you don't have an MTA (mail transfer agent) installed. Perhaps you can try installing that? I'm curious why you get that message, though. You said it runs fine on the command-line. Why don't you create a dummy script that just does a date >~/hello.txt and see if the file gets updated. If so, your problem isn't cron, but something it is looking for...perhaps that MTA it's complaining about. – Ray Jan 28 '24 at 16:46
  • The No MTA installed message is because cron wants to report something and can't - far simpler than installing an MTA would be to redirect the output + error streams to a log file ex. */1 * * * * /usr/bin/sh ~/path-to-my-script/my_script.sh > ~/cron.log 2>&1 and examine that – steeldriver Jan 28 '24 at 17:17
  • 1
    ... in any case, the issue is likely that the process started from cron is not able to connect to your user's display - see for example How to use notify-send with crontab? – steeldriver Jan 28 '24 at 17:46
  • @steeldriver following your suggestion I've found the message that it prints to cron.log: Cannot autolaunch D-Bus without X11 $DISPLAY. So I've set DISPLAY=:0 in my_script.sh according to the value returned by echo $DISPLAY, I've also set DBUS_SESSION_BUS_ADDRESS to the value printed by echo $DBUS_SESSION_BUS_ADDRESS. Still the issue persists. – overmach Jan 28 '24 at 17:48
  • After some research based on the Cannot autolaunch D-Bus without X11 $DISPLAY I've found this discussion that suggests to prepend env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus to the command in crontab -e. It works! – overmach Jan 28 '24 at 18:09

2 Answers2

1

The modern and clean way to do this is to use the user's systemd timers. Missing the DBus environment variable is only one of the many reasons why cron is obsolete on modern Ubuntu Linux.

  1. Paste this in ~/.config/systemd/user/battery_notification.service:
[Unit]
Description=Sends notification about battery status

[Service]

%h means ~

ExecStart=%h/path-to-my-script/my_script.sh Type=oneshot

  1. Paste this in ~/.config/systemd/user/battery_notification.timer:
[Unit]
Description=Show battery notification every minute

If you want to limit it to a specific desktop environment,

you can change this line.

This mostly prevents unnecessary runs in SSH or after logging out,

and is another advantage over cron.

EDIT: Too bad i3 has a bug that breaks this: https://github.com/i3/i3/issues/5186

#Requisite=graphical-session.target #PartOf=graphical-session.target

[Install] #WantedBy=graphical-session.target WantedBy=dbus.service

[Timer] AccuracySec=1us

Initial delay

OnActiveSec=1s

Period

OnUnitActiveSec=1m

  1. Run the following:
systemctl --user daemon-reload
systemctl --user enable battery_notification.timer
systemctl --user start battery_notification.timer # Or reboot

You will see that the DBus environment variable, together will many other missing environment variables are set properly. The program is also now a child of one of your user processes, namely systemd --user not cron, so it is in the proper cgroup and other namespaces.

Another advantage is that it stops after you log out or if you don't log into your specified graphical environment. Useful commands might be systemctl --user list-timers to check the remaining time or systemctl --user disable battery_notification.timer && systemctl --user stop battery_notification.timer to stop it.

Daniel T
  • 4,594
  • Is the file in step 2 the same as the one in step 1? Namely: ~/.config/systemd/user/battery_notification.timer – overmach Jan 29 '24 at 20:42
  • @overmach Oops, step 2 is the .timer file and step 1 was supposed to be the .service file. Nice catch! – Daniel T Jan 29 '24 at 22:52
0

As reported in my comment the solution turned out to be: prepending env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus to the command in crontab -e as follows:

*/1 * * * * env DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u)/bus /usr/bin/sh ~/path-to-my-script/my_script.sh