7.3 KiB
7.3 KiB
🛡️ The Sentinel – Backup Monitor
A self-hosted backup monitoring dashboard with a premium dark-theme UI, MongoDB backend, and deep integrations for Borgmatic, Uptime Kuma, Prometheus, and webhooks.
Features
- Sentinel UI – Premium dark-theme dashboard built on Material 3 design tokens, Tailwind CSS, glassmorphism effects, and Manrope + Inter typography
- Multi-Page SPA – Dashboard, Alert Center, Backup Hosts, Configuration
- Dashboard – Bento metric cards, SVG volume trend chart, host clusters by status, live backup stream
- Alert Center – Severity-based alerts (Critical/Stale) with pulse animations
- Host Management – Add, edit, disable, delete hosts via Web UI
- Detail Drawer – 30-day calendar heatmap, data volume chart, backup history per host
- Prometheus Metrics –
/metricsendpoint with per-host and global backup metrics - Uptime Kuma Integration – Automatic push forwarding per host after each backup
- Webhook Notifications – Configurable alerts on error/stale events (n8n, Telegram, etc.)
- API Key Auth – Optional authentication for write endpoints
- 90-Day History – MongoDB with automatic TTL cleanup
- Auto-Refresh – Dashboard updates every 30 seconds
- Zero Config – Hosts auto-register on first push
Quick Start
git clone https://github.com/feldjaeger/backup-monitor.git
cd backup-monitor
docker compose up -d
# Open http://localhost:9999
Docker Compose
services:
backup-monitor:
build: .
container_name: backup-monitor
restart: always
ports:
- "9999:9999"
environment:
- MONGO_URI=mongodb://mongo:27017
- STALE_HOURS=26
# - API_KEY=your-secret-key # optional: protect write endpoints
# - WEBHOOK_URLS=https://n8n.example.com/webhook/backup-alert
# - WEBHOOK_EVENTS=error,stale
depends_on:
- mongo
mongo:
image: mongo:8
container_name: backup-mongo
restart: always
volumes:
- mongo_data:/data/db
volumes:
mongo_data:
Push API
After each backup, send a POST request:
# Minimal push
curl -X POST -H "Content-Type: application/json" \
-d '{"host":"myserver","status":"ok"}' \
http://localhost:9999/api/push
# Full push with stats
curl -X POST -H "Content-Type: application/json" \
-d '{
"host": "myserver",
"status": "ok",
"duration_sec": 342,
"original_size": 5368709120,
"deduplicated_size": 104857600,
"compressed_size": 83886080,
"nfiles_new": 47,
"nfiles_changed": 12
}' \
http://localhost:9999/api/push
# With API key
curl -X POST -H "X-API-Key: your-key" -H "Content-Type: application/json" \
-d '{"host":"myserver","status":"ok"}' \
http://localhost:9999/api/push
Borgmatic Integration
Add to your borgmatic.yml:
after_backup:
- >-
bash -c '
STATS=$(borgmatic info --archive latest --json 2>/dev/null | python3 -c "
import sys,json
d=json.load(sys.stdin)[0][\"archives\"][-1]
s=d.get(\"stats\",{})
print(json.dumps({
\"host\":\"$(hostname)\",
\"status\":\"ok\",
\"duration_sec\":int(s.get(\"duration\",0)),
\"original_size\":s.get(\"original_size\",0),
\"deduplicated_size\":s.get(\"deduplicated_size\",0),
\"compressed_size\":s.get(\"compressed_size\",0),
\"nfiles_new\":s.get(\"nfiles\",0)
}))" 2>/dev/null || echo "{\"host\":\"$(hostname)\",\"status\":\"ok\"}");
curl -fsS -m 10 -X POST -H "Content-Type: application/json" -d "$STATS" "http://YOUR_SERVER:9999/api/push" || true
'
on_error:
- >-
curl -fsS -m 10 -X POST -H "Content-Type: application/json"
-d '{"host":"'$(hostname)'","status":"error","message":"Backup failed"}'
"http://YOUR_SERVER:9999/api/push" || true
API Reference
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/ |
❌ | Web UI |
GET |
/metrics |
❌ | Prometheus metrics |
GET |
/api/summary |
❌ | Dashboard summary |
GET |
/api/hosts |
❌ | List all hosts with status |
GET |
/api/history/<host>?days=30 |
❌ | Backup history |
GET |
/api/calendar/<host>?days=30 |
❌ | Calendar heatmap data |
POST |
/api/push |
🔑 | Push backup status |
POST |
/api/hosts |
🔑 | Add a host |
PUT |
/api/hosts/<name> |
🔑 | Update host settings |
DELETE |
/api/hosts/<name> |
🔑 | Delete host and history |
🔑 = requires API_KEY if set (via X-API-Key header or ?api_key= query param)
Prometheus Integration
backup_hosts_total 21
backup_host_status{host="myserver"} 1 # 1=ok, 0=error, -1=stale
backup_host_last_seconds{host="myserver"} 3600
backup_host_duration_seconds{host="myserver"} 342
backup_host_size_bytes{host="myserver"} 5368709120
backup_host_dedup_bytes{host="myserver"} 104857600
backup_today_total 22
backup_today_bytes 47280909120
Add to prometheus.yml:
scrape_configs:
- job_name: 'backup-monitor'
static_configs:
- targets: ['backup-monitor:9999']
scrape_interval: 60s
Webhook Notifications
environment:
- WEBHOOK_URLS=https://n8n.example.com/webhook/backup-alert
- WEBHOOK_EVENTS=error,stale
Payload:
{
"event": "error",
"host": "myserver",
"message": "Backup failed",
"timestamp": "2026-04-05T06:00:00Z"
}
Uptime Kuma Integration
- Create a Push monitor in Uptime Kuma
- In Backup Monitor: Host → Edit → paste Kuma Push URL
- After each backup, status is automatically forwarded to Kuma
Environment Variables
| Variable | Default | Description |
|---|---|---|
MONGO_URI |
mongodb://mongo:27017 |
MongoDB connection |
STALE_HOURS |
26 |
Hours without backup → stale |
API_KEY |
(empty) | Set to enable auth on write endpoints |
WEBHOOK_URLS |
(empty) | Comma-separated notification URLs |
WEBHOOK_EVENTS |
error,stale |
Events that trigger webhooks |
Design System
The UI follows The Sentinel design language:
- Colors: Material 3 tonal palette with deep slate surfaces (
#0b1326→#2d3449) - Typography: Manrope (headlines) + Inter (body/data)
- No-Line Rule: Card boundaries via background color shifts, no 1px borders
- Glass Effect: Backdrop-blur on navigation and overlays
- Pulse Animations: Status indicators with two-tone glow effects
- Tonal Depth: Layered surfaces creating architectural permanence
Tech Stack
- Backend: Python 3.12, Flask, Gunicorn
- Database: MongoDB 8
- Frontend: Tailwind CSS, Vanilla JS, Material Symbols, Google Fonts
- No build step – Tailwind loaded via CDN
Grafana Dashboard
Import grafana-dashboard.json into Grafana for a pre-built dashboard with:
- Overview stat panels (Hosts OK, Stale, Errors, Volume, Backups Today)
- Host status table with color-coded status, age, duration, size
- Backup volume per host (stacked time series)
- Backup duration per host
- Time since last backup with threshold coloring (green/yellow/red)
License
MIT