Surface Pi-hole health and configuration changes in Tempo’s timeline with five default actions (open admin, open query log, open settings, copy server URL, copy domain).

Pi-hole has no native push webhook out of the box, so this integration is poll-driven: a small bash script runs on cron, checks Pi-hole’s state via its HTTP API, and POSTs an event to Tempo when something interesting changes.

Tested with Pi-hole v6 (FTL HTTP API). v5 with the legacy PHP API also works with minor URL adjustments — see the v5 note at the bottom.


Install

  1. Download pi-hole.tempo-score from the button above.
  2. Double-click it. Tempo opens a review sheet — click Install. The score lands in ~/Library/Application Support/Tempo/Scores/.
  3. In Tempo Settings → Ingestion, add a token named pi-hole bound to net.pi-hole.pi-hole. Copy the token.
  4. Note your Tempo endpoint: http://<your-mac-hostname>:7776/ingest (or 127.0.0.1 if Tempo is loopback-only).
  5. Install the polling script (below).

Polling script

Save as pihole-tempo.sh, edit the four config values, run on cron every 5–10 minutes. The script tracks state across runs so it only POSTs to Tempo when something changes (no spam).

#!/usr/bin/env bash
# pihole-tempo.sh — emit Pi-hole state changes to Tempo
set -euo pipefail

# ── Config ────────────────────────────────────────────────────────────────
PIHOLE_URL="http://pi.hole"        # base URL of your Pi-hole
PIHOLE_PASS="your-admin-password"  # admin password (v6) or API token
TEMPO_URL="http://your-mac.local:7776/ingest"
TEMPO_TOKEN="paste-tempo-token-here"
STATE_DIR="${HOME}/.local/state/pihole-tempo"
# ──────────────────────────────────────────────────────────────────────────

mkdir -p "$STATE_DIR"
LAST_FILE="${STATE_DIR}/last_status"

# Auth (Pi-hole v6 — single-shot session)
SID=$(curl -s -X POST -H "Content-Type: application/json" \
    -d "{\"password\":\"${PIHOLE_PASS}\"}" \
    "${PIHOLE_URL}/api/auth" | jq -r '.session.sid // empty')

if [ -z "$SID" ]; then
    STATUS="unreachable"
else
    BLOCKING=$(curl -s -H "X-FTL-SID: $SID" "${PIHOLE_URL}/api/dns/blocking" \
               | jq -r '.blocking // "unknown"')
    case "$BLOCKING" in
        enabled)   STATUS="up" ;;
        disabled)  STATUS="disabled" ;;
        *)         STATUS="unreachable" ;;
    esac
fi

LAST=$( [ -f "$LAST_FILE" ] && cat "$LAST_FILE" || echo "")
echo "$STATUS" > "$LAST_FILE"

# Only emit on transitions
if [ "$STATUS" = "$LAST" ]; then exit 0; fi

case "$STATUS" in
    up)          ACTION="blocking_enabled";  TITLE="Pi-hole — blocking enabled" ;;
    disabled)    ACTION="blocking_disabled"; TITLE="Pi-hole — blocking disabled" ;;
    unreachable) ACTION="";                  TITLE="Pi-hole unreachable" ;;
esac

curl -sS -X POST \
    -H "X-Tempo-Token: ${TEMPO_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "{
      \"providerIdentifier\": \"net.pi-hole.pi-hole\",
      \"title\": \"${TITLE}\",
      \"startDate\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",
      \"eventType\": \"alert\",
      \"metadata\": {
        \"Status\": \"${STATUS}\",
        \"Action\": \"${ACTION}\",
        \"ServerUrl\": \"${PIHOLE_URL}\"
      }
    }" \
    "${TEMPO_URL}" >/dev/null

Schedule

crontab -e
# Run every 5 minutes
*/5 * * * * /path/to/pihole-tempo.sh >> /tmp/pihole-tempo.log 2>&1

Verify

Disable Pi-hole blocking from the admin UI for 30s, then run the script manually. You should see a Pi-hole — blocking disabled event in Tempo within a couple of seconds, marked warning.

Severity rules

MatchSeverityBadge
Status: downcriticalDown
Status: unreachablecriticalUnreachable
Status: high_loadwarningHigh load
Status: disabledwarningDisabled
Action: blocking_disabledwarningBlocking off
Action: update_availableinfoUpdate
Action: gravity_updateinfoGravity
Action: blocking_enabledinfoBlocking on
(default)infoInfo

Required metadata fields

Pi-hole v5 note

For v5, replace the auth/blocking calls with the legacy PHP API:

# v5 auth: API token (web admin → Settings → API → "Show API token")
PIHOLE_TOKEN="your-api-token-here"

BLOCKING=$(curl -s "${PIHOLE_URL}/admin/api.php?status&auth=${PIHOLE_TOKEN}" \
           | jq -r '.status // "unknown"')
case "$BLOCKING" in
    enabled)  STATUS="up" ;;
    disabled) STATUS="disabled" ;;
    *)        STATUS="unreachable" ;;
esac

The rest of the script is identical.

Sample event payload

{
  "providerIdentifier": "net.pi-hole.pi-hole",
  "title": "Pi-hole — blocking disabled",
  "startDate": "2026-04-29T10:00:00Z",
  "eventType": "alert",
  "metadata": {
    "Status": "disabled",
    "Action": "blocking_disabled",
    "ServerUrl": "http://pi.hole"
  }
}

Notes