Kapowarr is a Python/Flask-based server (port 5656) that automates downloading comic book volumes from configured sources, manages your local comic library, and keeps metadata synchronized with upstream databases like Comic Vine. It runs as a persistent daemon with a web UI for management, a background download queue, and scheduled library scans. When the Flask process crashes, download sources become unreachable, or the storage backend fills up, Kapowarr stops downloading silently — your "Wanted" list grows without any notification, and new issues for running series are never fetched. Without external monitoring, you may not notice for weeks that your comic collection stopped updating. Vigilmon lets you monitor Kapowarr's web UI availability, download queue health, metadata API connectivity, library scan completion, and storage backend so your comic library pipeline stays healthy.
What You'll Set Up
- Kapowarr web UI availability (port 5656)
- Download queue health and completion rate
- Comic Vine metadata API connectivity
- Library scan completion monitoring
- Storage backend health checks
Prerequisites
- Kapowarr running (Python/Flask, port 5656)
- Access to Kapowarr's API (available at
/api/on port 5656) - A free Vigilmon account
Step 1: Monitor Kapowarr Web UI Availability
The Kapowarr Flask application serves both the web UI and its API from the same process on port 5656. If the process crashes or the port becomes unreachable, downloads stop and you lose management access entirely. Set up an HTTP monitor as the primary health check:
Direct HTTP monitor in Vigilmon:
- Click Add Monitor → HTTP(S).
- Set URL to
http://your-kapowarr-host:5656/. - Set check interval to 2 minutes.
- Set expected response to HTTP 200.
For script-based monitoring (useful when Kapowarr is on an internal network):
#!/bin/bash
# /usr/local/bin/kapowarr-health-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/abc123"
KAPOWARR_HOST="localhost"
KAPOWARR_PORT="5656"
TIMEOUT=10
# Check web UI
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time "$TIMEOUT" \
"http://${KAPOWARR_HOST}:${KAPOWARR_PORT}/")
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "301" ]; then
echo "Kapowarr web UI OK (HTTP $HTTP_CODE)"
# Also verify API endpoint responds
API_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time "$TIMEOUT" \
"http://${KAPOWARR_HOST}:${KAPOWARR_PORT}/api/system/status")
if [ "$API_CODE" = "200" ] || [ "$API_CODE" = "401" ]; then
echo "Kapowarr API reachable (HTTP $API_CODE)"
curl -s "$HEARTBEAT_URL"
else
echo "Kapowarr API unreachable (HTTP $API_CODE)"
exit 1
fi
else
echo "Kapowarr web UI down (HTTP $HTTP_CODE)"
exit 1
fi
Install as a cron job:
chmod +x /usr/local/bin/kapowarr-health-check.sh
(crontab -l 2>/dev/null; echo "*/5 * * * * /usr/local/bin/kapowarr-health-check.sh >> /var/log/kapowarr-health.log 2>&1") | crontab -
Step 2: Monitor Download Queue Health
Kapowarr maintains a download queue for volumes and issues in your "Wanted" list. When all download sources fail, a volume has no valid release candidates, or network connectivity breaks mid-download, items stay in the queue indefinitely with no completion progress. Monitor queue health through the Kapowarr API:
#!/bin/bash
# /usr/local/bin/kapowarr-queue-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/def456"
KAPOWARR_API="http://localhost:5656/api"
API_KEY="your-kapowarr-api-key" # From Settings → API in the web UI
MAX_STUCK_MINUTES=120 # Alert if a download hasn't progressed in 2 hours
MAX_QUEUE_DEPTH=100 # Alert if more than 100 items are queued
TIMEOUT=15
# Get queue status
QUEUE_RESP=$(curl -s \
--max-time "$TIMEOUT" \
-H "api-key: ${API_KEY}" \
"${KAPOWARR_API}/download/queue")
if [ -z "$QUEUE_RESP" ]; then
echo "No response from Kapowarr download API"
exit 1
fi
QUEUE_RESULT=$(echo "$QUEUE_RESP" | python3 -c "
import sys, json
from datetime import datetime, timezone
try:
data = json.load(sys.stdin)
except Exception as e:
print(f'error:JSON parse failed: {e}')
sys.exit(1)
# Extract queue items
items = data if isinstance(data, list) else data.get('result', [])
total = len(items)
downloading = sum(1 for i in items if i.get('status') == 'downloading')
failed = sum(1 for i in items if i.get('status') in ('failed', 'error'))
waiting = sum(1 for i in items if i.get('status') in ('queued', 'waiting'))
print(f'total:{total}')
print(f'downloading:{downloading}')
print(f'failed:{failed}')
print(f'waiting:{waiting}')
" 2>/dev/null)
if [ -z "$QUEUE_RESULT" ]; then
echo "Failed to parse queue response"
exit 1
fi
TOTAL=$(echo "$QUEUE_RESULT" | grep "^total:" | cut -d: -f2)
FAILED=$(echo "$QUEUE_RESULT" | grep "^failed:" | cut -d: -f2)
WAITING=$(echo "$QUEUE_RESULT" | grep "^waiting:" | cut -d: -f2)
TOTAL="${TOTAL:-0}"
FAILED="${FAILED:-0}"
WAITING="${WAITING:-0}"
ALERT=0
if [ "$TOTAL" -gt "$MAX_QUEUE_DEPTH" ]; then
echo "Queue backed up: ${TOTAL} items (max: ${MAX_QUEUE_DEPTH})"
ALERT=1
fi
if [ "$FAILED" -gt 10 ]; then
echo "Failed downloads: ${FAILED} items in failed state"
ALERT=1
fi
if [ "$ALERT" -eq 0 ]; then
echo "Queue OK: ${TOTAL} total, ${WAITING} waiting, ${FAILED} failed"
curl -s "$HEARTBEAT_URL"
else
exit 1
fi
Run this every 10 minutes. A queue that never empties (total stays constant across multiple checks) indicates download sources are all failing or the download directory is unreachable.
Step 3: Monitor Comic Vine Metadata Connectivity
Kapowarr uses Comic Vine (and optionally other sources) to resolve series metadata, issue numbers, and cover art. When the Comic Vine API key is rate-limited, the API is down, or your key expires, Kapowarr cannot add new volumes to your library, match issues to series, or fetch cover art. Monitor Comic Vine connectivity directly:
#!/bin/bash
# /usr/local/bin/kapowarr-comicvine-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/ghi789"
COMIC_VINE_API_KEY="your-comic-vine-api-key"
TIMEOUT=20
# Check Comic Vine API connectivity and key validity
CV_RESP=$(curl -s \
--max-time "$TIMEOUT" \
"https://comicvine.gamespot.com/api/volumes/?api_key=${COMIC_VINE_API_KEY}&format=json&limit=1&field_list=id")
CV_STATUS=$(echo "$CV_RESP" | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
status = data.get('status_code', 0)
error = data.get('error', 'Unknown')
print(f'{status}:{error}')
except:
print('0:parse_error')
" 2>/dev/null)
CV_CODE=$(echo "$CV_STATUS" | cut -d: -f1)
CV_MSG=$(echo "$CV_STATUS" | cut -d: -f2-)
case "$CV_CODE" in
1)
echo "Comic Vine API OK (status: OK)"
curl -s "$HEARTBEAT_URL"
;;
100)
echo "Comic Vine API key invalid (status: $CV_CODE $CV_MSG)"
exit 1
;;
104)
echo "Comic Vine API rate limited (status: $CV_CODE $CV_MSG)"
exit 1
;;
0)
echo "Comic Vine API unreachable or parse error ($CV_MSG)"
exit 1
;;
*)
echo "Comic Vine API error (status: $CV_CODE $CV_MSG)"
exit 1
;;
esac
Run this every 30 minutes. Comic Vine rate limits (100 requests/hour on free keys) can be exhausted by library scans, leaving Kapowarr unable to resolve new series. Detecting rate-limit hits early lets you pause automatic scans before they exhaust the daily limit.
Step 4: Monitor Library Scan Completion
Kapowarr periodically scans your library directory to detect locally present files and match them against your "Wanted" list. When a library scan stalls (due to a filesystem error, an NFS mount going stale, or a very large directory taking too long), new files are never recognized and the "Downloaded" status for volumes doesn't update. Monitor scan completion via Kapowarr's task log:
#!/bin/bash
# /usr/local/bin/kapowarr-scan-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/jkl012"
KAPOWARR_API="http://localhost:5656/api"
API_KEY="your-kapowarr-api-key"
MAX_HOURS_SINCE_SCAN=25 # Alert if no library scan in 25 hours
TIMEOUT=15
# Get task history from Kapowarr API
TASKS_RESP=$(curl -s \
--max-time "$TIMEOUT" \
-H "api-key: ${API_KEY}" \
"${KAPOWARR_API}/tasks/history?limit=50")
if [ -z "$TASKS_RESP" ]; then
echo "No response from Kapowarr tasks API"
exit 1
fi
SCAN_RESULT=$(echo "$TASKS_RESP" | python3 -c "
import sys, json
from datetime import datetime, timezone
try:
data = json.load(sys.stdin)
except:
sys.exit(1)
tasks = data if isinstance(data, list) else data.get('result', [])
# Find last completed library scan
scan_tasks = [t for t in tasks if 'scan' in t.get('name', '').lower() or 'library' in t.get('name', '').lower()]
completed = [t for t in scan_tasks if t.get('status') in ('success', 'completed', 'done')]
if not completed:
print('no_scan_found')
sys.exit(0)
latest = completed[0]
completed_at = latest.get('finished_at') or latest.get('updated_at') or latest.get('created_at', '')
print(f'last_scan:{completed_at}')
" 2>/dev/null)
if [ "$SCAN_RESULT" = "no_scan_found" ] || [ -z "$SCAN_RESULT" ]; then
echo "No completed library scan found in recent task history"
exit 1
fi
LAST_SCAN_TIME=$(echo "$SCAN_RESULT" | grep "^last_scan:" | cut -d: -f2-)
if [ -z "$LAST_SCAN_TIME" ]; then
echo "Could not determine last scan time"
exit 1
fi
SCAN_EPOCH=$(date -d "$LAST_SCAN_TIME" +%s 2>/dev/null)
NOW_EPOCH=$(date +%s)
HOURS_AGO=$(( (NOW_EPOCH - SCAN_EPOCH) / 3600 ))
if [ "$HOURS_AGO" -le "$MAX_HOURS_SINCE_SCAN" ]; then
echo "Library scan OK: last completed ${HOURS_AGO}h ago"
curl -s "$HEARTBEAT_URL"
else
echo "Library scan stale: last completed ${HOURS_AGO}h ago (threshold: ${MAX_HOURS_SINCE_SCAN}h)"
exit 1
fi
Set the Vigilmon heartbeat to 26 hours. A missing library scan means locally downloaded files aren't being matched to their Kapowarr entries and the "downloaded" count never updates.
Step 5: Monitor Storage Backend
Kapowarr downloads comics to a configured library path (local disk, NFS, SMB, or a Docker volume). When the storage backend fills up or becomes unmounted, Kapowarr's download jobs fail at the write step — the download appears to start but files are never written. Monitor storage separately:
#!/bin/bash
# /usr/local/bin/kapowarr-storage-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/mno345"
LIBRARY_PATH="/mnt/comics" # Your Kapowarr library root — adjust as needed
DOWNLOAD_TEMP="/mnt/comics/temp_downloads" # Temp download dir if separate
DISK_THRESHOLD=85 # percent
TIMEOUT=10
FAILED=0
# Verify library path is mounted and accessible
if ! mountpoint -q "$LIBRARY_PATH" 2>/dev/null && [ ! -d "$LIBRARY_PATH" ]; then
echo "Library path not accessible: $LIBRARY_PATH"
exit 1
fi
# Test write access (Kapowarr needs to write to this path)
TEST_FILE="${LIBRARY_PATH}/.vigilmon_write_test_$$"
if ! touch "$TEST_FILE" 2>/dev/null; then
echo "Library path is read-only or write-protected: $LIBRARY_PATH"
FAILED=1
else
rm -f "$TEST_FILE"
echo "Write access OK: $LIBRARY_PATH"
fi
# Check disk space
DISK_PCT=$(df "$LIBRARY_PATH" | awk 'NR==2 {gsub(/%/,""); print $5}')
DISK_AVAIL=$(df -h "$LIBRARY_PATH" | awk 'NR==2 {print $4}')
if [ "${DISK_PCT:-0}" -ge "$DISK_THRESHOLD" ]; then
echo "Library disk usage high: ${DISK_PCT}% used (${DISK_AVAIL} free) — threshold: ${DISK_THRESHOLD}%"
FAILED=1
else
echo "Disk OK: ${DISK_PCT}% used (${DISK_AVAIL} free)"
fi
# Check temp download directory if separate
if [ -n "$DOWNLOAD_TEMP" ] && [ "$DOWNLOAD_TEMP" != "$LIBRARY_PATH" ] && [ -d "$DOWNLOAD_TEMP" ]; then
TEMP_PCT=$(df "$DOWNLOAD_TEMP" | awk 'NR==2 {gsub(/%/,""); print $5}')
if [ "${TEMP_PCT:-0}" -ge "$DISK_THRESHOLD" ]; then
echo "Temp download disk usage high: ${TEMP_PCT}% — stalled partial downloads may be filling disk"
FAILED=1
else
echo "Temp disk OK: ${TEMP_PCT}%"
fi
fi
# Count stale temp files (partial downloads older than 24 hours)
if [ -d "$DOWNLOAD_TEMP" ]; then
STALE_COUNT=$(find "$DOWNLOAD_TEMP" -name "*.part" -o -name "*.tmp" 2>/dev/null | \
xargs ls -lt 2>/dev/null | awk -v age=86400 '{
if (NR>1 && $0 ~ /^-/) {
# simplified: count all temp files as potential stale
count++
}
} END {print count+0}')
if [ "${STALE_COUNT:-0}" -gt 20 ]; then
echo "Stale partial downloads found: ${STALE_COUNT} temp files in $DOWNLOAD_TEMP"
FAILED=1
fi
fi
if [ "$FAILED" -eq 0 ]; then
curl -s "$HEARTBEAT_URL"
else
exit 1
fi
Run this every 15 minutes. Stale .part files accumulating in the temp directory usually means downloads are starting but being aborted — investigate Kapowarr logs for source errors or source URL changes.
Step 6: Set Up Alert Channels
Configure Vigilmon alert routing for Kapowarr:
- Go to Alert Channels in Vigilmon.
- Add email notification for all monitors — Kapowarr failures are not usually urgent enough for pager-style alerts.
- Add a Slack webhook to
#media-opsfor web UI downtime and storage alerts (these are actionable immediately). - Set Consecutive failures before alert to
2for the web UI monitor — Flask startup takes several seconds and brief restarts are normal. - Set Consecutive failures before alert to
3for the Comic Vine monitor — brief rate-limit hits during library scans are expected.
Add a maintenance window before Kapowarr updates:
curl -X POST https://vigilmon.online/api/maintenance \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"monitor_id": "KAPOWARR_WEB_MONITOR_ID",
"duration_minutes": 5,
"reason": "Kapowarr version update"
}'
# Update Kapowarr (Docker example)
docker compose pull kapowarr && docker compose up -d kapowarr
Summary
| Monitor | Target | What It Catches |
|---|---|---|
| HTTP monitor (web UI) | Port 5656 | Flask crash, process exit, port binding failure |
| Cron heartbeat (queue) | /api/download/queue | Stalled downloads, high failure rate, backed-up queue |
| Cron heartbeat (Comic Vine) | Comic Vine API status | Rate limiting, key expiry, API outage |
| Cron heartbeat (library scan) | Task history API | Stalled scan, filesystem error, NFS mount loss |
| Cron heartbeat (storage) | Disk space + write access | Full disk, read-only mount, stale partial downloads |
Kapowarr's failure mode is an invisible backlog — new comic issues are released, your "Wanted" list grows, but nothing downloads because the source failed, the API is rate-limited, or the disk is full. Without external monitoring, the only way to notice is to open the web UI and manually check the queue. Vigilmon's continuous checks make failures visible within minutes so your comic library stays current automatically.
Start monitoring for free at vigilmon.online.