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 "$@"

Attachment: backup.sh
Description: Bourne shell script

Reply via email to