From b8452e1fbba43c28e57aeffe61a969c207cbc2a1 Mon Sep 17 00:00:00 2001 From: kyle Date: Tue, 6 Oct 2026 00:55:03 +0000 Subject: [PATCH] Upload files to "/" --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2b324f3 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# 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](collectors/veeam/README.md)). + +## 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: + +```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 | +|---|---| +| `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](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`. + +## Development + +```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 +``` + +## 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. 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.