Ubuntu: Running a bash script periodically with a system-level Systemd timer

If you have a Bash script that needs to run periodically, you can run it using a crontab entry or file.  But you can also have it invoked from Systemd using systemd.timer.

Running via Systemd provides more powerful constructs for invocation, configuration, monitoring, and logging.  In this article, I will show how to periodically run a simple Bash script using a system-level Systemd timer.

If you want to run a user-level Systemd timer, see my other article.

Prerequisites

Install the package prerequisites and download the github project code.

# packages
sudo apt install curl git -y

# pull project
git clone https://github.com/fabianlee/systemd-periodic-bash.git
cd systemd-periodic-bash

Running Systemd service

Now use the supplied script to create the Systemd service and timer, and run the Bash script every 60 seconds.  We will go into the details of how this works in the upcoming sections.

cd systemd-systemlevel

# create user, systemd files, start
./create-simple-service.sh

# configure environment variable
# set 'env_arg' to any value, I set mine to 'my foo'
sudo vi /etc/default/simple

If you tail the logs, you should see output similar to below every 60 seconds.

$ tail -f /var/log/simple/simple.log

Jan 3 18:15:21 mybox1 simpleservice[19070]: ========================
Jan 3 18:15:21 mybox1 simpleservice[19071]: Mon 03 Jan 2022 06:15:20 PM EST
Jan 3 18:15:21 mybox1 simpleservice[19070]: ========================
Jan 3 18:15:21 mybox1 simpleservice[19070]: env_arg environment variable: my foo
Jan 3 18:15:21 mybox1 simpleservice[19070]: loop counter 1
Jan 3 18:15:21 mybox1 simpleservice[19070]: loop counter 2
Jan 3 18:15:21 mybox1 simpleservice[19070]: loop counter 3

Overview

Because we are running this Bash script via Systemd, we get a lot of production-level features over using a simple cron. Basically, we get all the power of Systemd.

  • Invocation – run once at boot, periodically, or after the network is initialized
  • Isolation – as a specific user or group
  • Configuration – pull in properties from ‘/etc/default’ or other locations
  • Monitoring – process monitored for success/error, watchdog process
  • Logging – to syslog, file

Systemd service unit file

Our simple.service file is shown below.  The “$scriptdir” value is a  placeholders, replaced by create-simple-service.sh.

[Unit]
Description=simple systemd bash script
ConditionPathExists=$scriptdir/simple-bash.sh
After=network.target

[Service]
#Type=simple
Type=forking
User=simpleservice
Group=simpleservice

# define 'env_var' env var, secured by root ownership and mode 600
EnvironmentFile=-/etc/default/simple

WorkingDirectory=$scriptdir
ExecStart=$scriptdir/simple-bash.sh 3

# make sure log directory exists and owned by syslog
PermissionsStartOnly=true
ExecStartPre=/bin/mkdir -p /var/log/simple
ExecStartPre=/bin/chown syslog:adm /var/log/simple
ExecStartPre=/bin/chmod 755 /var/log/simple
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=simpleservice

This has the effect of:

  • Starting the service after the network is initialized (After=network.target)
  • Running the service using the ‘simpleservice’ user (User=simpleservice)
  • Loading environment keys from an optional default file (EnvironmentFile)
  • Invoking the Bash script with a command line argument (ExecStart)
  • Hooks that run before the invocation (ExecStartPre)
  • Sending output of script to syslog (StandardOutput,SyslogIdentifier)

service and timer files

The .service and .timer files used by Systemd are kept in “/lib/systemd/system” for system-level services.

These are enabled and started like this.

service=simple

# enable and start service and timer
sudo systemctl enable $service.service $service.timer
sudo systemctl start $service.service
sudo systemctl start $service.timer

 

REFERENCES

debian, systemd.service docs

NOTES

check that user level systemd process is running

# you should see 'lib/systemd/systemd --user'
ps aux | grep systemd | grep "^$USER"

tried reinstalling dbus

sudo apt install --reinstall dbus
sudo apt install --reinstall dbus-user-session

checked env vars

id -u
id -un
echo $XDG_RUNTIME_DIR
echo $DBUS_SESSION_BUS_ADDRESS

tried enabling linger

loginctl enable-linger $USER

this showed errors, so restarted

# tried restart of login service
sudo systemctl restart systemd-logind.service

# then errors were shown in user system, so restarted
sudo systemctl status user@1000.service --no-pager -l
sudo systemctl restart user@1000.service

# then this worked
systemctl --user status