I've wanted something with a bit more flexibility than standard Cron for a while, and I finally came up with a simple solution that I like and felt might be of interest to this community.
The core of my scheduler is a Bash script, and the script searches for
files in a "tasks" subfolder that has one folder for each hostname the
scheduler is run on. Here's what the layout of what I currently have
looks like:
cron$ tree
.
|-- cron
|-- logs
| `-- [...]
`-- tasks
`-- sinister
|-- backup.sh
|-- email-digest.sh
`-- woot-watch.sh
Each of the scripts has two functions defined: "condition," which exits
successfully if the script should run and "run," which contains the
execution logic. The core script sources each of the tasks for the
current host then checks to see if the "condition" function exits
successfully before invoking the "run" function and logging the output.
I currently run the core scheduler on top of top Vixie / ISC cron so I
don't have to worry privileged system access:
~$ /usr/bin/crontab -l
MAILTO=""
* * * * * bash ~/cron/cron
I've attached the prototype I'm currently using to this email as well as
a sample task script. I know Bash isn't considered suckless, but there's
nothing preventing any of this from being done in POSIX shell. The
script also recognizes "-l" and "-e" flags for listing and editing
scripts, respectively, and I currently have the core script symlinked to
~/bin/crontab.
Eric
#!/usr/bin/env bash
set -o pipefail
export YEAR=$(printf '%(%Y)T' -1)
export MONTH=$(printf '%(%m)T' -1)
export DAY=$(printf '%(%d)T' -1)
export HOUR=$(printf '%(%H)T' -1)
export MINUTE=$(printf '%(%M)T' -1)
export DAYOFWEEK=$(printf '%(%w)T' -1)
export MAILTO=$USER
export SENDMAIL=/usr/sbin/sendmail
run_tasks() {
local basename
local default_subject
local logfile
local send_email
local task
send_email=0
for task in "tasks/$HOSTNAME/"*; do
if ! [[ -e "$task" ]]; then
break
else
basename=$(basename "$task")
basename=${basename%.sh}
fi
if source "$task" && condition; then
echo "[!] $basename"
(
logfile="logs/$basename.$(printf '%(%Y-%m-%dT%H:%M%z)T' -1)"
if ! run &> "$logfile"; then
send_email=${send_email:-1}
default_subject="$basename (failed)"
else
default_subject="$basename"
fi
[[ "$send_email" -ne 0 ]] && {
echo "Date: $(date -R)"
echo "From: Cron Script <$USER@$HOSTNAME>"
echo "To: <$MAILTO>"
echo "Subject: ${SUBJECT:-$default_subject}"
echo
cat "$logfile"
} | "$SENDMAIL" -oi -- "$MAILTO"
if [[ -e "$logfile" ]] && ! [[ -s "$logfile" ]]; then
rm "$logfile"
fi
) &
else
echo "[ ] $basename"
fi
done
wait
}
main() {
local line
local log_format
local _
case "$1" in
"")
log_format='%(%Y-%m-%dT%H:%M:%S%z)T [%d]: %s\n'
while read line; do
printf "$log_format" -1 "$$" "$line" | tee -a master-log
done < <(echo "Started:"; run_tasks 2>&1; echo "Finished.")
;;
-e)
shopt -s nullglob
for _ in tasks/"$HOSTNAME"/*"$2"*; do
$EDITOR -- tasks/"$HOSTNAME"/*"$2"*
exit
done
$EDITOR -- "tasks/$HOSTNAME/$2"
;;
-l)
if which tree &> /dev/null; then
tree tasks
else
ls -l -R tasks
fi
esac
}
cd "$(dirname "$(readlink -f "$0")")"
mkdir -p logs "tasks/$HOSTNAME"
main "$@"
backup.sh
Description: Bourne shell script
