Skip to content

Instantly share code, notes, and snippets.

@PhrozenByte
Last active July 6, 2023 15:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PhrozenByte/c7c642ddd12a647f3ba8f8d54faf9d37 to your computer and use it in GitHub Desktop.
Save PhrozenByte/c7c642ddd12a647f3ba8f8d54faf9d37 to your computer and use it in GitHub Desktop.
Runs a command if a Systemd calendar specification is due.
#!/bin/bash
# systemd-calendar-run.sh
# Runs a command if a Systemd calendar specification is due
#
# This script checks whether a given Systemd calendar specification (pass the
# '--on-calendar SPEC' option) relative to either the current, or a given time
# (pass the '--base-time TIMESTAMP' option) yields a past elapse time and runs
# the given command (pass as arguments: 'COMMAND [ARGUMENT]'). Otherwise the
# script exits with return code 254. When an invalid option is given, the
# script exits with return code 255.
#
# Special care must be taken when using relative calendar specifications (e.g.
# the default 'daily'): The specification is evaluated relative to a base time
# to yield its next elapse. This elapse time is then compared to the current
# time. If the base time is equal to the current time (which is the default),
# the calendar specification will always yield a next elapse in the future, so
# that the command is never run. Thus you must pass a different base time when
# using relative calendar specifications. Usually this equals to the time the
# command was last run.
#
# If the calendar specification is not due, the script exits with return code
# 254 and the command is not executed. To execute it at the expected time, pass
# the '--schedule' option: The script will then create a transient timer to run
# the command at the expected time.
#
# This script was created to ease running multiple interdependent Systemd
# oneshot services on a regular basis. Since the services are interdependent,
# they must run together and are therefore triggered by a single Systemd timer.
# However, not all Systemd services must always run. This script is used to
# skip execution of the less often required Systemd services.
#
# Copyright (C) 2023 Daniel Rudolf (<https://www.daniel-rudolf.de>)
# License: The MIT License <http://opensource.org/licenses/MIT>
#
# SPDX-License-Identifier: MIT
set -eu -o pipefail
export LC_ALL=C
print_usage() {
echo "Usage:"
echo " $(basename "$0") [--base-time TIMESTAMP] [--on-calendar SPEC] \\"
echo " [--schedule] COMMAND [ARGUMENT]..."
}
# read parameters
BASE_TIME="now"
ON_CALENDAR="daily"
SCHEDULE="no"
COMMAND=()
while [ $# -gt 0 ]; do
if [ "$1" == "--" ]; then
COMMAND=( "${@:2}" )
set --
break
fi
if [ "$1" == "--base-time" ]; then
if [ $# -lt 2 ]; then
echo "Missing required argument for option '--base-time': TIMESTAMP" >&2
exit 255
fi
BASE_TIME="$2"
shift 2
elif [ "$1" == "--on-calendar" ]; then
if [ $# -lt 2 ]; then
echo "Missing required argument for option '--on-calendar': SPEC" >&2
exit 255
fi
ON_CALENDAR="$2"
shift 2
elif [ "$1" == "--schedule" ]; then
SCHEDULE="yes"
shift
elif [ "$1" == "--help" ]; then
print_usage
exit 0
else
COMMAND=( "$@" )
set --
fi
done
# check parameters
if [ "${#COMMAND[@]}" -eq 0 ]; then
print_usage >&2
exit 255
elif ! which "${COMMAND[0]}" > /dev/null 2>&1; then
echo "Invalid command '${COMMAND[0]}': Command not found or not executable" >&2
exit 255
fi
if ! systemd-analyze calendar "$ON_CALENDAR" > /dev/null 2>&1; then
echo "Invalid calendar specification '$ON_CALENDAR': Invalid argument" >&2
exit 255
fi
if ! systemd-analyze timestamp "$BASE_TIME" > /dev/null 2>&1; then
echo "Invalid base timestamp '$BASE_TIME': Invalid argument" >&2
exit 255
fi
# check next elapse
NEXT_ELAPSE="$(systemd-analyze calendar --base-time="$BASE_TIME" "$ON_CALENDAR" \
| sed -ne 's/^ *Next elapse: \(.*\)$/\1/p')"
if [ "$(date +'%s')" -lt "$(date --date="$NEXT_ELAPSE" +'%s')" ]; then
# schedule execution
if [ "$SCHEDULE" == "yes" ]; then
SYSTEMD_RUN_OPTS=( --on-calendar="$NEXT_ELAPSE" )
[ "$(id -u)" == "0" ] || SYSTEMD_RUN_OPTS+=( --user )
if ! systemd-run "${SYSTEMD_RUN_OPTS[@]}" -- "$(which "${COMMAND[0]}")" "${COMMAND[@]:1}"; then
echo "Failed to schedule execution" >&2
exit 255
fi
fi
# command is not due
exit 254
fi
# run command
set +e +o pipefail
"$(which "${COMMAND[0]}")" "${COMMAND[@]:1}"
exit $?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment