#!/bin/bash # {{ healthcheck_name }} — {{ healthcheck_description }} # Managed by Ansible (roles/healthcheck). Do not edit on the host. # # The exit code is the real answer; systemd stores it: # systemctl is-failed {{ healthcheck_name }}-healthcheck.service # The push below is an optional extra, and having no URL is normal. set -uo pipefail LOG_FILE="{{ healthcheck_log_dir }}/{{ healthcheck_name }}.log" PUSH_URL="${HEALTHCHECK_PUSH_URL:-}" PUSH_TOKEN="${HEALTHCHECK_PUSH_TOKEN:-}" # Set only by checks that report several results; see healthcheck_push_base. PUSH_BASE="${HEALTHCHECK_PUSH_BASE:-}" log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $*" >> "$LOG_FILE"; } # Report to Gatus as an external endpoint. Note this is a POST with a bearer # token, not a GET with a query string - it is not the shape Uptime Kuma used. report() { local success="$1" message="$2" # No push URL is normal, not an error: the exit code below is a complete # answer for anything reading unit state. [ -n "$PUSH_URL" ] || return 0 local encoded encoded=$(printf '%s' "$message" | sed 's/%/%25/g; s/ /%20/g; s/&/%26/g; s/+/%2B/g; s/#/%23/g') local code code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \ --max-time 15 --retry 2 --retry-delay 3 \ -H "Authorization: Bearer ${PUSH_TOKEN}" \ "${PUSH_URL}?success=${success}&error=${encoded}" 2>/dev/null) if [ "$code" = "200" ]; then log "reported success=${success}" else log "ERROR: report failed (HTTP ${code})" return 1 fi } # Report to an arbitrary endpoint key under PUSH_BASE. Used by checks that # produce one result per item rather than a single verdict. report_key() { local key="$1" success="$2" message="$3" [ -n "$PUSH_BASE" ] || return 0 local encoded encoded=$(printf '%s' "$message" | sed 's/%/%25/g; s/ /%20/g; s/&/%26/g; s/+/%2B/g; s/#/%23/g') curl -s -o /dev/null --max-time 15 --retry 2 --retry-delay 3 -X POST \ -H "Authorization: Bearer ${PUSH_TOKEN}" \ "${PUSH_BASE}/${key}/external?success=${success}&error=${encoded}" 2>/dev/null || true } # ── the check ──────────────────────────────────────────────────────────────── # # The blank line before the closing brace below is load-bearing. Jinja strips an # included template's trailing newline, and trim_blocks (on by default in # Ansible) then eats the newline after {% raw %}{% endif %}{% endraw %} - so without it the brace lands # on the same line as the check body's last statement, producing `return 0}` and # a script that dies with "syntax error: unexpected end of file". check() { {% if healthcheck_check %} {% include 'checks/' ~ healthcheck_check ~ '.sh.j2' %} {% else %} {{ healthcheck_command }} {% endif %} } MESSAGE="" if check; then log "OK${MESSAGE:+ - $MESSAGE}" report "true" "${MESSAGE:-ok}" exit 0 else log "FAILED${MESSAGE:+ - $MESSAGE}" report "false" "${MESSAGE:-check failed}" exit 1 fi