Shell integration

Wire Drumbeats into a shell script or system cron. Quick example, centralized helper, error handling with traps, and production patterns.

This guide shows how to wire Drumbeats into shell scripts and system cron entries. Bash is the assumed shell; the patterns work in zsh, dash, and busybox with minimal changes — the only hard requirement is curl.

Quick example — one-liner in crontab

The most compact pattern wraps the job command directly in the crontab line:

bash
0 2 * * * /usr/bin/backup.sh \
  && curl -sf https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  || curl -sf https://api.drumbeats.io/v1/ping/<monitor-id>/failure
0 2 * * * /usr/bin/backup.sh \
  && curl -sf https://api.drumbeats.io/v1/ping/<monitor-id>/success \
  || curl -sf https://api.drumbeats.io/v1/ping/<monitor-id>/failure

&& runs the success ping when the job exits zero; || runs the failure ping when the job exits non-zero. The -sf flags make curl silent and exit non-zero on HTTP errors so a failed ping does not corrupt the cron output.

Centralized helper script

For more than a couple of monitored jobs, store the monitor IDs in a helper that crontab entries can source:

/usr/local/lib/drumbeats.sh
#!/usr/bin/env bash
# Source this file before calling drumbeats_run.
# Usage:  drumbeats_run <monitor-key> <command...>

DRUMBEATS_BASE="${DRUMBEATS_BASE_URL:-https://api.drumbeats.io/v1}"

declare -A DRUMBEATS_MONITORS=(
  [daily_backup]="11111111-2222-3333-4444-555555555555"
  [hourly_sync]="66666666-7777-8888-9999-aaaaaaaaaaaa"
  [newsletter_send]="bbbbbbbb-cccc-dddd-eeee-ffffffffffff"
)

_drumbeats_ping() {
  local monitor=$1 event=$2 run_id=$3 payload=$4
  local url="${DRUMBEATS_BASE}/ping/${monitor}/${event}?run_id=${run_id}"
  if [ -z "$payload" ]; then
    curl -sf --max-time 3 "$url" >/dev/null 2>&1 || true
  else
    curl -sf --max-time 3 -X POST "$url" \
      -H 'Content-Type: application/json' \
      -d "$(jq -n --arg p "$payload" '{payload: $p}')" >/dev/null 2>&1 || true
  fi
}

drumbeats_run() {
  local key=$1
  shift
  local monitor=${DRUMBEATS_MONITORS[$key]}
  if [ -z "$monitor" ]; then
    echo "drumbeats: unknown monitor key '$key'" >&2
    return 2
  fi
  local run_id="${key}-$(date +%s)-$RANDOM"

  _drumbeats_ping "$monitor" "start" "$run_id" ""

  # Capture stderr so we can surface it as the failure payload.
  local err
  err=$("$@" 2>&1)
  local status=$?

  if [ $status -eq 0 ]; then
    _drumbeats_ping "$monitor" "success" "$run_id" ""
  else
    _drumbeats_ping "$monitor" "failure" "$run_id" "${err:0:20000}"
  fi
  return $status
}
#!/usr/bin/env bash
# Source this file before calling drumbeats_run.
# Usage:  drumbeats_run <monitor-key> <command...>

DRUMBEATS_BASE="${DRUMBEATS_BASE_URL:-https://api.drumbeats.io/v1}"

declare -A DRUMBEATS_MONITORS=(
  [daily_backup]="11111111-2222-3333-4444-555555555555"
  [hourly_sync]="66666666-7777-8888-9999-aaaaaaaaaaaa"
  [newsletter_send]="bbbbbbbb-cccc-dddd-eeee-ffffffffffff"
)

_drumbeats_ping() {
  local monitor=$1 event=$2 run_id=$3 payload=$4
  local url="${DRUMBEATS_BASE}/ping/${monitor}/${event}?run_id=${run_id}"
  if [ -z "$payload" ]; then
    curl -sf --max-time 3 "$url" >/dev/null 2>&1 || true
  else
    curl -sf --max-time 3 -X POST "$url" \
      -H 'Content-Type: application/json' \
      -d "$(jq -n --arg p "$payload" '{payload: $p}')" >/dev/null 2>&1 || true
  fi
}

drumbeats_run() {
  local key=$1
  shift
  local monitor=${DRUMBEATS_MONITORS[$key]}
  if [ -z "$monitor" ]; then
    echo "drumbeats: unknown monitor key '$key'" >&2
    return 2
  fi
  local run_id="${key}-$(date +%s)-$RANDOM"

  _drumbeats_ping "$monitor" "start" "$run_id" ""

  # Capture stderr so we can surface it as the failure payload.
  local err
  err=$("$@" 2>&1)
  local status=$?

  if [ $status -eq 0 ]; then
    _drumbeats_ping "$monitor" "success" "$run_id" ""
  else
    _drumbeats_ping "$monitor" "failure" "$run_id" "${err:0:20000}"
  fi
  return $status
}

Use it from any cron entry:

bash
0 2 * * * source /usr/local/lib/drumbeats.sh && drumbeats_run daily_backup /usr/bin/backup.sh
0 2 * * * source /usr/local/lib/drumbeats.sh && drumbeats_run daily_backup /usr/bin/backup.sh

The wrapper captures stdout+stderr and POSTs the last 20KB as the failure payload — exactly what shows up on the incident timeline. No more digging through /var/log/cron.

Error handling

  • set -e. The helper above uses local capture to avoid set -e aborting before the failure ping fires. If your script uses set -e, wrap the command in if ! cmd; then …; fi rather than relying on $?.
  • Signals. A SIGTERM from the system (e.g. systemd timeout) kills the script before any cleanup. Pair max_duration_seconds on the monitor with the start ping so Drumbeats catches the hang server-side regardless.
  • Long-running output. A 100MB log file passed as the payload will get rejected. The ${err:0:20000} slice keeps the payload under the 25KB beat-cost cap (see Beats and usage).

Production patterns

  • Time-bound pings. curl --max-time 3 keeps a slow Drumbeats request from blocking the cron run.
  • Quiet output. Cron emails the output of any non-empty stdout/stderr. Redirect ping output to /dev/null so the only mail you get is your own command's mail.
  • Trap on EXIT. For scripts with multiple exit paths (functions, subshells), use trap to send a failure ping on any unclean exit:
bash
  trap '_drumbeats_ping "$MONITOR" failure "$RUN_ID" ""' EXIT
  trap '_drumbeats_ping "$MONITOR" failure "$RUN_ID" ""' EXIT

Clear the trap before the success ping.

  • Multi-environment via slug. /v1/s-ping/<project>/<slug>/<event> lets the same cron entry route to different monitors based on the project ID env var — see Ping API: scheduled pings.
  • Monitor types — pick Cron, Heartbeat, or Event-driven first.
  • Ping API — endpoint and parameter reference.
  • Production hardening — retries, timeouts, observability patterns for any language.
  • Alternatives — how Drumbeats compares to Cronitor and Healthchecks.io for shell-driven cron.