Files
Backup-Dashboard/README.md
T
kyleandClaude Opus 5.5 10181e014b Add Azure Backup (MARS agent) collector
PowerShell script for servers running the MARS agent, run as a SYSTEM
scheduled task (hourly) under Windows PowerShell 5.1, which the
agent's MSOnlineBackup module targets. Each server is one dashboard job.

- Backup jobs from Get-OBJob (in progress and -Previous) within the
  lookback window; restores are ignored. Completed with failed bytes
  or an error reports as warning
- Next run computed from the backup and System State policy schedules;
  left empty for every-N-weeks schedules to avoid false Overdue alarms
- Property reads tolerate unknown names, since the agent's object
  model isn't documented; Get-MarsCollectorDiagnostics.ps1 dumps the
  real structure for checking
- Install-MarsCollector.ps1 mirrors the Veeam installer; tests/MockMars.ps1
  provides stand-in cmdlets

Not yet run against a real MARS agent.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-07 15:23:46 -06:00

5.2 KiB
Raw Blame History

Backup-Dashboard

Proof of concept backup dashboard, intended to display backup jobs from multiple sources.

collectors ──POST /api/v1/ingest (API key)──▶ ┌──────────────┐
                                              │ FastAPI +    │
browser ──nginx (basic auth, TLS)──▶ /api/* ─▶│ SQLite       │
        └─ static files: public/              └──────────────┘
  • public/: static frontend (no build step). Add ?mock=1 to the URL to use built-in sample data.
  • backend/: FastAPI API backed by a single SQLite file.
  • deploy/: nginx site and systemd unit.
  • collectors/: scripts that report results from backup products (Veeam B&R, Azure Backup / MARS).

Data model

  • Tenant (company) → Job → Run.
  • A job is keyed by (tenant, source_system, job.external_id), so renaming a job at the source updates it instead of creating a duplicate.
  • A run has status (running | success | warning | failed), started_at and finished_at. The dashboard's Last Run is the most recent finished_at. While a job is running it shows Running, and Last Run still shows when the previous run finished.
  • Runs older than BACKUP_DASH_RETENTION_DAYS (default 90, minimum 30) are purged every 6 hours.

Reporting results (collectors)

Each collector (a script on a Veeam server, a webhook relay, etc.) gets its own API key:

cd backend && python manage.py create-key veeam-acme

Then it POSTs one report per run. Send running when a run starts (optional) and the final status when it ends. Reports with the same started_at (or run.external_id) update the same run, and retries are safe.

curl -X POST https://backups.example.com/api/v1/ingest \
  -H "Authorization: Bearer bdk_..." -H "Content-Type: application/json" \
  -d '{
    "tenant": "Acme Corp",
    "source_system": "Veeam B&R",
    "job": { "external_id": "4f1c…", "name": "File Server – Nightly", "next_run_at": "2026-10-05T22:00:00-04:00" },
    "run": {
      "status": "success",
      "started_at": "2026-10-04T22:00:05-04:00",
      "finished_at": "2026-10-04T22:47:12-04:00",
      "message": "Job completed successfully.",
      "size_bytes": 128849018880,
      "details": { "repository": "REPO-01" }
    }
  }'
  • All timestamps must include a timezone offset (Get-Date -Format o in PowerShell does this).
  • job.next_run_at controls the Overdue flag. If you omit it, the stored value is kept. If you send null, it's cleared.
  • run can be omitted to only register or update a job.
  • Unknown fields are rejected (HTTP 422), so typos in collector scripts fail loudly.

API (read)

Endpoint Returns
GET /api/v1/jobs All jobs with current status, running_since, last_run_at, next_run_at
GET /api/v1/jobs/{id} One job, plus the last finished run's duration, size, message and details
GET /api/v1/jobs/{id}/runs?days=30 Run history (no messages), newest first
GET /api/v1/runs/{id} One run including message and details
GET /api/v1/health {"status": "ok"}

The OpenAPI schema is at /api/v1/openapi.json.

Authentication

  • Dashboard users: nginx basic auth over HTTPS (deploy/nginx.conf). Every user can see every tenant. Planned: Microsoft 365 sign-in through oauth2-proxy with the Entra ID provider and nginx auth_request. That's an nginx-only change. The frontend already tells the user to reload when a session expires (HTTP 401).
  • Collectors: per-collector bearer keys, checked by the app. Only a SHA-256 hash is stored. To revoke one, run python manage.py revoke-key NAME.

Development

cd backend
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
pytest
python manage.py --create seed-demo            # 30 days of fake history in ./backup-dashboard.db
BACKUP_DASH_SERVE_STATIC=1 uvicorn --factory app.main:create_app --reload
# → http://localhost:8000

Deployment (Linux, nginx)

  1. Copy the repo to /opt/backup-dashboard, create a venv at /opt/backup-dashboard/venv, and run pip install -r backend/requirements.txt.
  2. Copy deploy/backup-dashboard.env to /etc/backup-dashboard/backup-dashboard.env. Both the service and manage.py read their settings from it.
  3. Create a backupdash system user, then install deploy/backup-dashboard-api.service into /etc/systemd/system/ and enable it. The database is created on first start.
  4. Copy public/ to /var/www/backup-dashboard/public, install deploy/nginx.conf, get a certificate with certbot, and create users with htpasswd -B.
  5. Run manage.py as the service user. It finds the database through the env file:
    cd /opt/backup-dashboard/backend
    sudo -u backupdash /opt/backup-dashboard/venv/bin/python manage.py create-key veeam-acme
    
    It prints the database path it's using and won't create a new database unless you pass --create.

Back up the SQLite file with sqlite3 backup-dashboard.db ".backup copy.db". Don't use a plain cp while the service is running.