📄 PVE Backup Report
Python tool to generate a daily backup report for Proxmox VE against Proxmox Backup Server.
✨ Features
- 📄 Timestamped daily PDF report, without overwriting previous reports.
- 🧭 Inventory of PBS storages declared in Proxmox VE.
- ✅ Coverage analysis for QEMU VMs and LXC containers.
- 📝 PVE VM/CT notes included in the report.
- 🕒 Retrieval of the latest known backups via PVE tasks and
vzdumplogs. - 🗄️ Optional collection of PBS datastores, namespaces, prune jobs, snapshots and storage usage.
- 🔐 Audit of PBS backup accounts and effective permissions.
- 📊 PBS retention with expected version counts, delta and PVE state for VM/CT entries.
- ⚠️ Anomalies section for errors or partial collection data.
- 🐳 Recommended execution with Docker, or direct CLI execution.
🔐 Prerequisites
Create dedicated API users or tokens for this reporting application. They must be limited to audit/read-only permissions:
- Proxmox VE: assign only the
PVEAuditorrole to the user or API token used byPVE_API_TOKEN_ID. - Proxmox Backup Server: assign only the
Auditrole to the user or API token used by eachPBS<number>_API_TOKEN_ID.
Do not use administrator or write-enabled accounts. The application only needs to read inventory, backup jobs, tasks, datastores, namespaces, snapshots and retention data.
🐳 Usage with Docker
Docker is the recommended execution mode. The image includes the Python dependencies and system libraries required by WeasyPrint.
📦 Setup
cp .env.example .env
mkdir -p reports
Then edit .env with your PVE cluster and PBS connection details.
In Docker, keep:
REPORT_OUTPUT_DIR=/reports
The compose.yaml file mounts the local ./reports directory into the container at /reports. Generated PDFs are therefore visible on the host in ./reports/.
⚙️ Minimal configuration
PVE_API_URL=https://pve.example.invalid:8006
PVE_API_TOKEN_ID=backup-report@pve!report
PVE_API_TOKEN_SECRET=change-me
REPORT_OUTPUT_DIR=/reports
REPORT_TIMEZONE=Europe/Paris
REPORT_LANGUAGE=fr
PVE_VERIFY_TLS=true
To enable PBS collection, provide the URL, token ID and secret for the relevant PBS. An incomplete configuration does not enable PBS collection.
Example with one PBS:
PBS01_NAME=nom-affiche
PBS01_API_URL=https://backup.example.invalid:8007
PBS01_API_TOKEN_ID=
PBS01_API_TOKEN_SECRET=
One or more PBS servers can be configured. To add a server, duplicate the block and increment the prefix number: PBS02_*, PBS03_*, PBS04_*, etc. The application automatically detects all PBS<number>_* blocks present in the environment.
🏗️ Build
docker compose build
✅ Verification
docker compose run --rm pve-backup-report --check-config
docker compose run --rm pve-backup-report --check-api
--check-api tests the main PVE endpoints. If /cluster/backup returns HTTP 403 - Permission check failed (/, Sys.Audit), the token works but is missing the Sys.Audit privilege on /.
📄 Generating the report
docker compose run --rm pve-backup-report --generate-pdf
The PDF report is written to ./reports/ with a timestamped filename and never overwrites a previous report.
🧪 Docker diagnostic commands
docker compose run --rm pve-backup-report --dump-inventory
docker compose run --rm pve-backup-report --dump-coverage
docker compose run --rm pve-backup-report --dump-report-data
docker compose run --rm pve-backup-report --dump-pbs-storage-usages
docker compose run --rm pve-backup-report --dump-pbs-users
docker compose run --rm pve-backup-report --debug-last-backup-vmid <VMID>
Running the container without arguments only executes the default pve-backup-report command. To generate a PDF, use --generate-pdf explicitly.
🕒 Scheduling with cron and Docker
Example crontab on the host to run the report every day at 02:00:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * cd /srv/pve-backup-report && /usr/bin/flock -n /tmp/pve-backup-report.lock /usr/bin/docker compose run --rm pve-backup-report --generate-pdf >> /var/log/pve-backup-report.log 2>&1
Replace /srv/pve-backup-report with the actual path to the repository. The cd is important: Docker Compose finds compose.yaml there and the application loads .env from it.
💻 Direct command-line usage
This mode is useful for development or diagnostics outside a container. The host must have Python, the project's Python dependencies and the system libraries required by WeasyPrint.
📦 Local installation
python3 -m venv .venv
. .venv/bin/activate
python -m ensurepip --upgrade
pip install -r requirements.txt
pip install -e .
To run the tests:
pip install -e ".[dev]"
pytest
🧰 Installed commands
pve-backup-report --check-config
pve-backup-report --check-api
pve-backup-report --dump-inventory
pve-backup-report --dump-coverage
pve-backup-report --dump-report-data
pve-backup-report --dump-pbs-storage-usages
pve-backup-report --dump-pbs-users
pve-backup-report --generate-pdf
pve-backup-report --debug-last-backup-vmid <VMID>
Without an editable install, from the repository:
PYTHONPATH=src python3 -m pve_backup_report --check-config
PYTHONPATH=src python3 -m pve_backup_report --check-api
PYTHONPATH=src python3 -m pve_backup_report --dump-inventory
PYTHONPATH=src python3 -m pve_backup_report --dump-coverage
PYTHONPATH=src python3 -m pve_backup_report --dump-report-data
PYTHONPATH=src python3 -m pve_backup_report --dump-pbs-storage-usages
PYTHONPATH=src python3 -m pve_backup_report --dump-pbs-users
PYTHONPATH=src python3 -m pve_backup_report --generate-pdf
PYTHONPATH=src python3 -m pve_backup_report --debug-last-backup-vmid <VMID>
In direct local execution, REPORT_OUTPUT_DIR can remain reports/ or point to any other directory writable by the current user.
The same .env file can be used in Docker and in local execution. If REPORT_OUTPUT_DIR=/reports is kept outside Docker and /reports is not accessible, the application automatically falls back to the local reports/ directory and logs the fallback.
🕒 Scheduling with cron without Docker
Example using the project's virtual environment:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * cd /srv/pve-backup-report && /usr/bin/flock -n /tmp/pve-backup-report.lock /srv/pve-backup-report/.venv/bin/pve-backup-report --generate-pdf >> /var/log/pve-backup-report.log 2>&1
The user running the crontab must have read access to .env and write access to REPORT_OUTPUT_DIR.
🔧 Key variables
| Variable | Description |
|---|---|
PVE_API_URL |
URL of a reachable PVE node, e.g. https://pve.example.invalid:8006. |
PVE_API_TOKEN_ID |
Full token identifier, e.g. backup-report@pve!report. |
PVE_API_TOKEN_SECRET |
Token secret. |
REPORT_LANGUAGE |
PDF report language, fr or en. Default: fr. |
PVE_VERIFY_TLS |
Keep true in production; use PVE_CA_BUNDLE for an internal CA. |
PVE_TASK_HISTORY_LIMIT |
Number of recent PVE tasks inspected to find the latest backup. |
PVE_TASK_LOG_LIMIT |
Number of lines retrieved per vzdump log to extract per-VM/CT detail. |
PBS_HOSTNAMES |
Optional manual mapping of PBS servers as address=display-name,address2=display-name2. |
PBS<number>_* |
Optional PBS API configurations, e.g. PBS01_*, PBS02_*, PBS10_*. |
REPORT_FILENAME_PREFIX |
Prefix for the generated PDF filename. |
📄 PDF output
--generate-pdf generates a timestamped PDF in REPORT_OUTPUT_DIR. The Last backup column shows the status, date, time and duration when PVE provides that information. The PBS VM/CT backup retention <PBS> tables are split by namespace and show the datastore, the number of PBS versions, and the oldest and most recent backup visible on each configured PBS.
🤖 AI Usage
The application was entirely coded using Codex and Claude Code. I would never have had enough time to build the application on my own in such a short period of time.
⚖️ License
This project is distributed under the Apache License 2.0. See the LICENSE file.