briven-backup-alert.sh 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. #!/usr/bin/env bash
  2. # Posts a Discord alert when briven-backup.service exits non-zero. Invoked
  3. # by systemd's OnFailure= hook on briven-backup.service; see
  4. # road-to-ga.md §0.1 + §0.2 for the contract.
  5. #
  6. # Reads $BRIVEN_DISCORD_WEBHOOK_ALERTS from /etc/briven/backup.env (via
  7. # EnvironmentFile= in the .service unit). Reads /run/briven-backup-status
  8. # for the failure detail written by briven-backup.sh.
  9. #
  10. # Behaviour:
  11. # - webhook unset → log "no webhook configured" and exit 0 (silent
  12. # skip; OnFailure already counted the parent unit as failed)
  13. # - webhook set → POST a JSON payload with the failing DBs, exit 0
  14. # even on curl failure so systemd doesn't cascade alerts on the alert
  15. set -uo pipefail
  16. HOSTNAME_SHORT="$(hostname -s 2>/dev/null || echo unknown)"
  17. STATUS_FILE="/run/briven-backup-status"
  18. log() {
  19. printf '[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"
  20. }
  21. if [ -z "${BRIVEN_DISCORD_WEBHOOK_ALERTS:-}" ]; then
  22. log "no BRIVEN_DISCORD_WEBHOOK_ALERTS configured — skipping Discord alert"
  23. exit 0
  24. fi
  25. upload_failures="unknown"
  26. upload_failure_dbs="unknown"
  27. if [ -r "$STATUS_FILE" ]; then
  28. # shellcheck disable=SC1090
  29. source "$STATUS_FILE"
  30. fi
  31. # Discord embed payload. Title makes it visually red, description names
  32. # the failing DBs. We deliberately keep this short — no recipient PII
  33. # (none of these are user-bound) but also no oversharing of bucket /
  34. # credentials. The journalctl tail is the source of truth for ops; this
  35. # message just routes attention.
  36. PAYLOAD=$(cat <<JSON
  37. {
  38. "username": "briven-backup",
  39. "embeds": [{
  40. "title": "briven backup: off-site upload failed",
  41. "color": 15158332,
  42. "description": "Host: \`${HOSTNAME_SHORT}\`\nFailed uploads: \`${upload_failures}\`\nDatabases: \`${upload_failure_dbs}\`\n\nLocal dumps are safe. Investigate with:\n\`\`\`\njournalctl -u briven-backup.service -n 200\n\`\`\`"
  43. }]
  44. }
  45. JSON
  46. )
  47. # curl: --silent --show-error to keep journal clean on success but
  48. # capture errors. --fail to treat 4xx/5xx as failure. Timeout caps tail
  49. # latency at 10s so OnFailure's cascade can't stall a reboot.
  50. if ! curl --silent --show-error --fail \
  51. --max-time 10 \
  52. -H 'content-type: application/json' \
  53. -d "$PAYLOAD" \
  54. "$BRIVEN_DISCORD_WEBHOOK_ALERTS" >/dev/null; then
  55. log "Discord webhook POST failed — alert was not delivered"
  56. fi
  57. # Always exit 0 — the parent unit's failure status is what matters.
  58. # A failure here just means the operator finds out via journal instead.
  59. exit 0