Last active
July 6, 2023 15:40
-
-
Save PhrozenByte/c7c642ddd12a647f3ba8f8d54faf9d37 to your computer and use it in GitHub Desktop.
Runs a command if a Systemd calendar specification is due.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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