Cron monitoring examples: crontab, Docker
The principle is the same everywhere: make an HTTP request to the ping URL when the job succeeds (and, better, to /start at the beginning and /fail on failure). Replace <ping-key> with your check’s key. The -fsS -m 10 --retry 3 options are explained in the ping API.
The simplest: the ping is sent only if the script succeeds (&&).
0 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 https://app.silencewatch.com/p/<ping-key>To measure the duration and report failure explicitly, use a small wrapper script:
#!/bin/shURL=https://app.silencewatch.com/p/<ping-key>curl -fsS -m 10 --retry 3 "$URL/start"/usr/local/bin/backup.shcurl -fsS -m 10 --retry 3 "$URL/$?"In the service the timer starts:
[Service]Type=oneshotExecStart=/bin/sh -c '/usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 https://app.silencewatch.com/p/<ping-key>'[Timer]OnCalendar=*-*-* 02:00:00Persistent=true
[Install]WantedBy=timers.targetdocker run --rm my-image /app/job.sh \ && curl -fsS -m 10 --retry 3 https://app.silencewatch.com/p/<ping-key>In a docker-compose.yml you can chain in the service’s command: command: sh -c "/app/job.sh && curl -fsS -m 10 --retry 3 $PING_URL", provided the image contains curl.
apiVersion: batch/v1kind: CronJobmetadata: name: backupspec: schedule: "0 2 * * *" jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: backup image: my-image:latest command: ["/bin/sh", "-c"] args: - /app/backup.sh && curl -fsS -m 10 --retry 3 "$PING_URL" env: - name: PING_URL valueFrom: secretKeyRef: name: silencewatch key: ping-urlThe ping URL is a secret: put it in a Secret, not in the manifest.
on: schedule: - cron: "0 2 * * *"jobs: nightly: runs-on: ubuntu-latest steps: - run: ./scripts/nightly.sh - name: Ping SilenceWatch if: success() run: curl -fsS -m 10 --retry 3 "${{ secrets.SILENCEWATCH_PING_URL }}" - name: Report the failure if: failure() run: curl -fsS -m 10 --retry 3 "${{ secrets.SILENCEWATCH_PING_URL }}/fail"GitHub’s scheduled workflows can be delayed or skipped; that is exactly the kind of absence a dead man’s switch catches.
import requests
URL = "https://app.silencewatch.com/p/<ping-key>"
requests.get(f"{URL}/start", timeout=10)try: run_job()except Exception: requests.get(f"{URL}/fail", timeout=10) raiseelse: requests.get(URL, timeout=10)const PING_URL = 'https://app.silencewatch.com/p/<ping-key>';
await fetch(`${PING_URL}/start`, { signal: AbortSignal.timeout(10_000) });try { await runJob(); await fetch(PING_URL, { signal: AbortSignal.timeout(10_000) });} catch (error) { await fetch(`${PING_URL}/fail`, { signal: AbortSignal.timeout(10_000) }); throw error;}$url = "https://app.silencewatch.com/p/<ping-key>"& "C:\Jobs\backup.ps1"if ($?) { Invoke-RestMethod -Uri $url -TimeoutSec 10 }else { Invoke-RestMethod -Uri "$url/fail" -TimeoutSec 10 }For Spring Boot
Section titled “For Spring Boot”Write nothing: the Spring Boot starter discovers @Scheduled methods and Quartz jobs and sends the pings for you.