Upload files to "/"

This commit is contained in:
2026-10-06 00:53:21 +00:00
parent 65c587da0d
commit be23e03c5f
4 changed files with 506 additions and 86 deletions
+53 -86
View File
@@ -1,101 +1,68 @@
# Backup-Dashboard
# Veeam Backup & Replication collector
Proof of concept backup dashboard, intended to display backup jobs from multiple sources.
A PowerShell script that runs on each client's VBR server and reports job results to the dashboard.
It covers jobs returned by `Get-VBRJob`: VM backup, replication and backup copy jobs.
```
collectors ──POST /api/v1/ingest (API key)──▶ ┌──────────────┐
│ FastAPI + │
browser ──nginx (basic auth, TLS)──▶ /api/* ─▶│ SQLite │
└─ static files: public/ └──────────────┘
```
## Install (on the VBR server, as an administrator)
- `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.
1. On the dashboard host, create a key for this server:
```bash
python manage.py create-key veeam-acme
```
2. Copy this folder to the VBR server and run:
```powershell
.\Install-VeeamCollector.ps1 -ApiUrl https://backups.example.com -Tenant "Acme Corp"
```
It prompts for the key, then:
- writes `C:\ProgramData\BackupDashboard\veeam-collector.json` (readable only by SYSTEM and Administrators)
- copies the script to `C:\Program Files\BackupDashboard\`
- registers the scheduled task **Backup Dashboard - Veeam collector** (SYSTEM, every 15 minutes)
3. Preview the payloads, then backfill 30 days so the history view is populated straight away:
```powershell
& 'C:\Program Files\BackupDashboard\Send-VeeamBackupStatus.ps1' -DryRun
& 'C:\Program Files\BackupDashboard\Send-VeeamBackupStatus.ps1' -LookbackHours 720
```
## Data model
Re-run the installer to change settings or update the script. To remove the collector,
delete the scheduled task and both `BackupDashboard` folders.
- **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.
## What it sends
## Reporting results (collectors)
Each collector (a script on a Veeam server, a webhook relay, etc.) gets its own API key:
```bash
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.
```bash
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 |
| Dashboard | Veeam |
|---|---|
| `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"}` |
| Company | `Tenant` from the config |
| Job | Job name. The job's GUID is the ID, so renaming a job in Veeam doesn't duplicate it |
| Status | Session result (Success / Warning / Failed). Any state other than Stopped is **Running**. A session that stopped with no result (e.g. cancelled) is reported as **Warning** |
| Started / finished | Session `CreationTime` / `EndTime` |
| Next scheduled | Job's next run. Sent as empty for disabled jobs and chained ("run after") jobs, so those are never flagged Overdue |
| Message | Session reason, each VM or object that didn't succeed, and warning/error lines from the session log |
| Size | `BackupStats.BackupSize` |
The OpenAPI schema is at `/api/v1/openapi.json`.
Each run covers the last 48 hours (`LookbackHours`), so a few missed runs (server rebooting, API
down) fill in automatically. Sessions already delivered are recorded in
`veeam-collector.state.json`. Running sessions are re-sent until they finish.
## Authentication
## Troubleshooting
- **Dashboard users:** nginx basic auth over HTTPS (`deploy/nginx.conf`). Every user can see every tenant.
*Planned:* Microsoft 365 sign-in through [oauth2-proxy](https://oauth2-proxy.github.io/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`.
- Log: `C:\ProgramData\BackupDashboard\veeam-collector.log` (rotates at 5 MB).
- Exit codes: `0` OK, `1` some jobs failed to send (they're retried next run), `2` setup failed
(config, Veeam module, or Veeam connection).
- `API key rejected`: the key was revoked or mistyped. Re-run the installer with a new key.
- `Get-VBRBackupSession` loads every session in the VBR database. On a server with years of history,
this can make each run take a minute or more. The task's 30-minute limit and "don't start if
already running" setting keep that safe.
## Development
## Testing without Veeam
```bash
cd backend
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
pytest
python manage.py seed-demo # 30 days of fake history
BACKUP_DASH_SERVE_STATIC=1 uvicorn --factory app.main:create_app --reload
# → http://localhost:8000
`tests/MockVeeam.ps1` defines stand-in `Get-VBRJob` / `Get-VBRBackupSession` cmdlets with a mix of
success, warning, failed, running and disabled jobs:
```powershell
. ./tests/MockVeeam.ps1
./Send-VeeamBackupStatus.ps1 -ConfigPath ./my-test-config.json -DryRun
```
## Deployment (Linux, nginx)
## Not covered yet
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. Create a `backupdash` system user, then install `deploy/backup-dashboard-api.service` into `/etc/systemd/system/` and enable it.
3. Copy `public/` to `/var/www/backup-dashboard/public`, install `deploy/nginx.conf`, get a certificate with certbot, and create users with `htpasswd -B`.
4. Run the `manage.py` commands as the service user with `BACKUP_DASH_DB=/var/lib/backup-dashboard/backup-dashboard.db`.
Back up the SQLite file with `sqlite3 backup-dashboard.db ".backup copy.db"`. Don't use a plain `cp`
while the service is running.
- Veeam Agent jobs managed by VBR (`Get-VBRComputerBackupJob`), tape jobs, and SureBackup.
- Veeam Backup for Microsoft 365, which has its own PowerShell module.