Author SHA1 Message Date
kyle 067b714678 Upload files to "/" 2026-10-07 20:10:11 +00:00
kyle 3355a7d4bc Update collectors/veeam/Send-VeeamBackupStatus.ps1 2026-10-07 20:09:47 +00:00
kyle e916afaae1 Update collectors/veeam/MockVeeam.ps1 2026-10-07 20:09:36 +00:00
kyle 1f96d11a27 Update collectors/veeam/Install-VeeamCollector.ps1 2026-10-07 20:09:26 +00:00
kyle 41c4fa2700 Delete collectors/veeam/Send-VeeamBackupStatus.ps1 2026-10-07 20:09:13 +00:00
kyle a84a35463b Delete collectors/veeam/MockVeeam.ps1 2026-10-07 20:09:09 +00:00
kyle 96b729bec8 Delete collectors/veeam/Install-VeeamCollector.ps1 2026-10-07 20:09:04 +00:00
kyle ae574fa2b6 Delete README.md 2026-10-07 20:08:58 +00:00
kyle e50711b93b Delete Get-VeeamCollectorDiagnostics.ps1 2026-10-07 20:07:30 +00:00
kyle 8fd17e3817 Upload files to "/" 2026-10-07 20:05:28 +00:00
kyle 986cf3be88 Upload files to "collectors/veeam" 2026-10-07 19:42:01 +00:00
kyle 17ee303670 Update scheduled task time to one hour from 15 minutes 2026-10-07 15:57:49 +00:00
kyle effafc5fa0 Delete directory 'test' 2026-10-07 15:56:47 +00:00
kyle 2318579baa Update test/testfolder 2026-10-07 15:56:26 +00:00
kyle 1f02c1c84b Add testfolder 2026-10-07 15:55:31 +00:00
kyle 3b1aee7016 Update scheduled task to use Powershell 7
Todo: add logic to check for and install Powershell 7
2026-10-07 15:48:27 +00:00
kyle d3ab05416d Update manage.py 2026-10-07 01:36:10 +00:00
kyle b8452e1fbb Upload files to "/" 2026-10-06 00:55:03 +00:00
kyle d12e00f69e Update collectors/veeam/README.md 2026-10-06 00:54:51 +00:00
kyle 33c1613ef9 Update collectors/vveam/README.md 2026-10-06 00:54:38 +00:00
kyle 8e29b9cbc4 Update collectors/veeam/Install-VeeamCollector.ps1 2026-10-06 00:54:26 +00:00
kyle 49bf9ef094 Update collectors/vveam/Install-VeeamCollector.ps1 2026-10-06 00:54:13 +00:00
kyle 4d183da7bd Update collectors/veeam/Send-VeeamBackupStatus.ps1 2026-10-06 00:53:47 +00:00
kyle 25f7717ace Update collectors/veeam/MockVeeam.ps1 2026-10-06 00:53:34 +00:00
kyle be23e03c5f Upload files to "/" 2026-10-06 00:53:21 +00:00
kyle 65c587da0d Delete format.js 2026-10-05 18:05:01 +00:00
kyle 29f961f80e Delete config.js 2026-10-05 18:04:49 +00:00
kyle 02789b1910 Update deploy/nginx.conf 2026-10-05 18:03:49 +00:00
kyle 54a6c0b3d6 Update deploy/backup-dashboard-api.service 2026-10-05 18:02:38 +00:00
kyle f11345bb0f Upload files to "backend" 2026-10-05 04:03:26 +00:00
kyle 7217b3e9a8 Upload files to "backend/app" 2026-10-05 04:03:05 +00:00
kyle 47ec7dc9a7 Upload files to "backend/app" 2026-10-05 04:02:57 +00:00
kyle 15ea9e0fec Update backend/app/__init__.py 2026-10-05 04:02:44 +00:00
kyle fe4bd6827c Upload files to "backend" 2026-10-05 04:02:31 +00:00
kyle 4e57eb1a46 Update backend/tests/test_api.py 2026-10-05 04:02:13 +00:00
kyle 670994b937 Update backend/tests/__init__.py 2026-10-05 04:02:03 +00:00
kyle a7c3169606 Upload files to "backend" 2026-10-05 04:01:49 +00:00
kyle 938a0a2af0 Upload files to "backend" 2026-10-05 04:01:38 +00:00
kyle f7e029db8a Upload files to "public/js" 2026-10-05 04:01:12 +00:00
kyle 2df1a75fd5 Update public/css/styles.css 2026-10-05 04:00:56 +00:00
kyle 4d8ee209a5 Update public/index.html 2026-10-05 04:00:38 +00:00
kyle 6c486fbaf3 Update public/js/api.js 2026-10-05 04:00:29 +00:00
kyle fdc7deff16 Upload files to "/" 2026-10-05 04:00:12 +00:00
kyle 0391807027 Upload files to "/" 2026-10-05 03:59:41 +00:00
kyle b0c560377b Update backend/requirements.txt 2026-10-05 03:58:42 +00:00
kyle 17346cc058 Update backend/requirements-dev.txt 2026-10-05 03:58:28 +00:00
kyle 65eb3c88f3 Update backend/manage.py 2026-10-05 03:58:08 +00:00
kyle 996fe33a40 initial upload 2026-10-05 03:57:48 +00:00
9 changed files with 16 additions and 98 deletions
+8
View File
@@ -0,0 +1,8 @@
__pycache__/
*.pyc
.venv/
venv/
.pytest_cache/
*.db
*.db-wal
*.db-shm
Binary file not shown.
+3 -24
View File
@@ -5,9 +5,6 @@ from pathlib import Path
# The modal shows this many days of run history; retention can never be shorter.
HISTORY_DAYS = 30
# Shared by the systemd service (EnvironmentFile=) and manage.py, so both always use the same DB.
DEFAULT_ENV_FILE = "/etc/backup-dashboard/backup-dashboard.env"
@dataclass(frozen=True)
class Settings:
@@ -17,27 +14,9 @@ class Settings:
serve_static: bool = False
def read_env_file(path: Path) -> dict[str, str]:
"""Parse simple KEY=VALUE lines (the subset systemd's EnvironmentFile accepts)."""
values = {}
try:
lines = path.read_text().splitlines()
except FileNotFoundError:
return values
for line in lines:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
key, _, value = line.partition("=")
values[key.strip()] = value.strip().strip("\"'")
return values
def load_settings() -> Settings:
# Real environment variables win over the env file; the env file wins over dev defaults.
env = read_env_file(Path(os.environ.get("BACKUP_DASH_ENV_FILE", DEFAULT_ENV_FILE))) | os.environ
return Settings(
db_path=Path(env.get("BACKUP_DASH_DB", "backup-dashboard.db")),
retention_days=max(HISTORY_DAYS, int(env.get("BACKUP_DASH_RETENTION_DAYS", "90"))),
serve_static=env.get("BACKUP_DASH_SERVE_STATIC") == "1",
db_path=Path(os.environ.get("BACKUP_DASH_DB", "backup-dashboard.db")),
retention_days=max(HISTORY_DAYS, int(os.environ.get("BACKUP_DASH_RETENTION_DAYS", "90"))),
serve_static=os.environ.get("BACKUP_DASH_SERVE_STATIC") == "1",
)
Binary file not shown.
-52
View File
@@ -1,52 +0,0 @@
import subprocess
import sys
from pathlib import Path
from app.settings import load_settings
BACKEND = Path(__file__).resolve().parents[1]
def run_manage(*args, env, cwd):
return subprocess.run([sys.executable, str(BACKEND / "manage.py"), *args],
capture_output=True, text=True, env=env, cwd=cwd)
def base_env(tmp_path, monkeypatch):
env_file = tmp_path / "backup-dashboard.env"
monkeypatch.setenv("BACKUP_DASH_ENV_FILE", str(env_file))
monkeypatch.delenv("BACKUP_DASH_DB", raising=False)
return env_file, {"BACKUP_DASH_ENV_FILE": str(env_file), "PYTHONPATH": str(BACKEND), "PATH": "/usr/bin:/bin"}
def test_env_file_supplies_db_path_and_real_env_wins(tmp_path, monkeypatch):
env_file, _ = base_env(tmp_path, monkeypatch)
env_file.write_text('# comment\nBACKUP_DASH_DB="/srv/from-file.db"\nBACKUP_DASH_RETENTION_DAYS=120\n')
assert load_settings().db_path == Path("/srv/from-file.db")
assert load_settings().retention_days == 120
monkeypatch.setenv("BACKUP_DASH_DB", "/srv/from-env.db")
assert load_settings().db_path == Path("/srv/from-env.db")
def test_manage_refuses_to_create_db_implicitly(tmp_path, monkeypatch):
_, env = base_env(tmp_path, monkeypatch)
workdir = tmp_path / "somewhere-else"
workdir.mkdir()
r = run_manage("create-key", "k", env=env, cwd=workdir)
assert r.returncode != 0
assert "Database not found" in r.stderr
assert not (workdir / "backup-dashboard.db").exists()
def test_manage_uses_db_from_env_file(tmp_path, monkeypatch):
env_file, env = base_env(tmp_path, monkeypatch)
service_db = tmp_path / "service.db"
env_file.write_text(f"BACKUP_DASH_DB={service_db}\n")
assert run_manage("--create", "list-keys", env=env, cwd=tmp_path).returncode == 0 # service's first start
workdir = tmp_path / "somewhere-else"
workdir.mkdir()
r = run_manage("create-key", "veeam-acme", env=env, cwd=workdir)
assert r.returncode == 0, r.stderr
assert str(service_db) in r.stderr
assert "veeam-acme" in run_manage("list-keys", env=env, cwd=tmp_path).stdout
assert not (workdir / "backup-dashboard.db").exists()
+3 -16
View File
@@ -5,25 +5,19 @@ It covers jobs returned by `Get-VBRJob`: VM backup, replication and backup copy
## Install (on the VBR server, as an administrator)
Requires PowerShell 7 installed machine-wide (`winget install Microsoft.PowerShell` or the MSI),
which VBR 13's Veeam module expects. The Microsoft Store version won't work, because the SYSTEM
account can't run it. The installer checks for this before changing anything.
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, from PowerShell 7 (`pwsh`):
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,
run by `C:\Program Files\PowerShell\7\pwsh.exe`; use `-PwshPath` if it's installed elsewhere)
3. Preview the payloads, then backfill 30 days so the history view is populated straight away
(from `pwsh`):
- 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
@@ -44,10 +38,6 @@ delete the scheduled task and both `BackupDashboard` folders.
| Message | Session reason, each VM or object that didn't succeed, and warning/error lines from the session log |
| Size | `BackupStats.BackupSize` |
**Backup copy jobs (VBR 12+):** Veeam records their sessions under hidden per-source "worker" jobs.
The collector files each worker session under its copy job (via the worker's `PolicyJobId`), so a
copy job gets one run per source job it copies, with the source in `copied_from`.
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.
@@ -58,9 +48,6 @@ down) fill in automatically. Sessions already delivered are recorded in
- 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.
- A job shows **Unknown** on the dashboard: the collector sent the job but matched no sessions to it.
Run `.\Get-VeeamCollectorDiagnostics.ps1` (read-only). It lists sessions whose job ID matches no
job, plus the type and link properties of the jobs left without sessions.
- `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.
+2 -2
View File
@@ -6,8 +6,8 @@ After=network.target
User=backupdash
Group=backupdash
WorkingDirectory=/opt/backup-dashboard/backend
# Settings live in this file so manage.py reads exactly the same ones (see deploy/backup-dashboard.env)
EnvironmentFile=/etc/backup-dashboard/backup-dashboard.env
Environment=BACKUP_DASH_DB=/var/lib/backup-dashboard/backup-dashboard.db
Environment=BACKUP_DASH_RETENTION_DAYS=90
# Bound to localhost: only nginx can reach it, so nginx's auth can't be bypassed from outside.
# One worker is plenty at this scale and keeps SQLite writes simple.
ExecStart=/opt/backup-dashboard/venv/bin/uvicorn --factory app.main:create_app --host 127.0.0.1 --port 8000 --proxy-headers
-4
View File
@@ -1,4 +0,0 @@
# Install as /etc/backup-dashboard/backup-dashboard.env
# Read by both the API service and manage.py, so they always use the same database.
BACKUP_DASH_DB=/var/lib/backup-dashboard/backup-dashboard.db
BACKUP_DASH_RETENTION_DAYS=90