Add English documentation

This commit is contained in:
2026-05-13 17:31:30 +02:00
parent f440a8e497
commit e77d73d6dc
14 changed files with 1351 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
# Proxmox Backup Server API
[Version francaise](../api-pbs.md)
This document describes the PBS collection used by the report.
## Objective
PBS collection complements the PVE report with configured retention policies and backup versions visible on configured Proxmox Backup Server instances.
The application can query one or more PBS servers. Each server is collected only if its API URL, token ID and token secret are provided.
This step does not prove synchronizations between PBS servers. It collects prune jobs and snapshots visible on each configured PBS.
## Authentication
The PBS client uses a PBS API token.
The HTTP header is:
```text
Authorization: PBSAPIToken=TOKENID:TOKENSECRET
```
Example PBS server configuration:
```env
PBS01_NAME=display-name
PBS01_API_URL=https://backup.example.invalid:8007
PBS01_API_TOKEN_ID=
PBS01_API_TOKEN_SECRET=
```
To collect multiple PBS servers, duplicate this block with `PBS02_*`, `PBS03_*`, `PBS04_*`, etc. All complete `PBS<number>_*` blocks are detected automatically.
The token secret must never be logged or appear in the report.
## Used Endpoints
| Data | PBS endpoint |
| --- | --- |
| Prune jobs | `/config/prune` |
| PBS datastores | `/config/datastore` |
| Datastore status | `/admin/datastore/{datastore}/status` |
| Datastore garbage collector status | `/admin/datastore/{datastore}/gc` |
| Datastore namespaces | `/admin/datastore/{datastore}/namespace` |
| Datastore/namespace snapshots | `/admin/datastore/{datastore}/snapshots?ns={namespace}` |
| PBS users | `/access/users` |
| Effective permissions | `/access/permissions?auth-id={auth-id}&path={path}` |
The client automatically appends `/api2/json` to the PBS URL if this suffix is not already present.
## Normalized Data
Each PBS prune job is transformed into a retention policy with the following fields:
- job identifier;
- PBS server;
- datastore;
- namespace;
- schedule;
- active/inactive state;
- `keep-last`;
- `keep-hourly`;
- `keep-daily`;
- `keep-weekly`;
- `keep-monthly`;
- `keep-yearly`;
- `max-depth`;
- comment.
If the `ns` field is missing, the namespace is considered to be the root `/`.
The report computes the expected version count for a VM/CT from the active retention policy matching the same PBS server, datastore and namespace as the snapshots. This count is the sum of configured `keep-last`, `keep-hourly`, `keep-daily`, `keep-weekly`, `keep-monthly` and `keep-yearly` values. The delta displays `visible snapshot count - expected count`.
Each PBS snapshot is aggregated by datastore, namespace, backup type and VMID to produce:
- number of backup versions;
- oldest backup date;
- newest backup date;
- size of the newest backup when PBS exposes this information.
Snapshots still visible on PBS while the VM/CT no longer exists in the PVE inventory are kept in the retention table and marked `Not active on PVE`.
Snapshots whose namespace is not declared in PVE-side PBS storages are excluded from these retention tables.
The status of each configured PBS datastore is collected to display total, used and free space in the report.
## PBS Users Declared on PVE
The `--dump-pbs-users` diagnostic command collects PBS storages declared in PVE through `/storage`, then matches their `username` field with users visible on each configured PBS through `/access/users`.
For each PVE PBS storage with a `username`, the diagnostic queries effective permissions for this auth-id on the target PBS path:
```text
/datastore/{datastore}
/datastore/{datastore}/{namespace}
```
The second format is used when a non-root namespace is declared on the PVE side.
Collected information feeds the report table `PBS users - Access audit` and the diagnostic command. It is used to verify which PBS account is used by PVE, whether the account is active when PBS exposes this information, and which effective privileges are available on the target datastore/namespace.
If the PVE `username` is a PBS token of type `user@realm!token`, matching with `/access/users` uses the parent user `user@realm`; permissions are requested with the full auth-id.
PBS types `vm` and `ct` are matched with PVE types `qemu` and `lxc`.
Snapshot collection queries datastores exposed by each configured PBS.
For each datastore, it also adds all namespaces declared in PVE PBS storages, even if the relevant PBS does not directly receive VM/CT backups from PVE. This makes it possible to find synchronized backups on a secondary PBS, for example in namespaces `serveurs-internes` or `Serveurs-PVELAB`.
Namespaces returned by the target PBS API are still collected as well. Snapshots are then matched to VM/CT by type and VMID.
Because these additional servers can contain direct and synchronized backups, some `/snapshots` calls can take longer. Increase the timeout for the relevant PBS block if the default timeout is insufficient.
## Error Handling
If no PBS is configured, PBS collection is skipped.
If a configured PBS is unavailable, the report continues and a `pbs_retention` anomaly is added for the relevant server.
If a configured PBS is unavailable for snapshot collection, the report continues and a `pbs_snapshots` anomaly is added for the relevant server.
When a namespace declared in PVE is tested on a PBS datastore where it does not exist, PBS may return `HTTP 400 - Bad Request` on the `/snapshots` endpoint. For a non-root namespace, this response is interpreted as namespace absence on that datastore and is not reported as an anomaly. Other errors, including a `400` on root `/`, remain visible.
If the `/config/prune` response is not a list, the report continues and adds an anomaly.
If `/config/prune` returns an empty list while a PBS is configured, a `warning` anomaly is added to verify either the real absence of prune jobs or the effective permissions of the PBS token.
## Limits
Collection does not yet check:
- synchronizations between PBS servers;
- prune job execution state;
- the effective result of a prune on snapshots.
These points can be added in a dedicated step.
+227
View File
@@ -0,0 +1,227 @@
# Proxmox VE API
[Version francaise](../api-pve.md)
## Principles
The application must use the Proxmox VE REST API in read-only mode.
Current state: `pve_client.py` implements the reusable API connection and endpoint checks for `/nodes`, `/storage`, `/cluster` and the configured backup jobs endpoint.
Collection of PBS storages, backup jobs, VM/CT and pools is implemented. Coverage computation supports `vmid` values explicitly listed in active jobs, `all=1` jobs and `pool=<name>` jobs.
The base URL usually follows this format:
```text
https://<pve-host>:8006/api2/json
```
The script can query any available node in the cluster for global endpoints.
## Token Authentication
Recommended authentication uses the HTTP `Authorization` header.
Format:
```text
Authorization: PVEAPIToken=<token-id>=<token-secret>
```
The token secret must never be logged.
In `.env`, set:
```env
PVE_API_TOKEN_ID=backup-report@pve!report
PVE_API_TOKEN_SECRET=secret-provided-by-pve
```
## Useful Endpoints
Exact endpoints must be checked against the target PVE version during implementation.
Commonly useful endpoints:
| Need | Indicative endpoint |
| --- | --- |
| PVE version | `/version` |
| Cluster resources | `/cluster/resources` |
| Storage configuration | `/storage` |
| Cluster index | `/cluster` |
| Backup jobs | `/cluster/backup` |
| Pools | `/pools` and `/pools/{poolid}` |
| Recent backup tasks | `/cluster/tasks` and `/nodes/{node}/tasks` |
| QEMU VM configuration | `/nodes/{node}/qemu/{vmid}/config` |
| LXC container configuration | `/nodes/{node}/lxc/{vmid}/config` |
| Node status | `/nodes` |
The nominal value for backup jobs is:
```text
/cluster/backup
```
`PVE_BACKUP_JOBS_ENDPOINT` exists only as a technical override. The expected value remains `/cluster/backup`.
Observed privilege for backup jobs:
```text
Sys.Audit on /
```
If the token does not have this privilege, PVE typically returns `HTTP 403 - Permission check failed (/, Sys.Audit)`.
With separated-privilege tokens, seeing `PVEAuditor` in the UI is not always enough: check effective token permissions, not only parent user permissions.
Manual check:
```sh
PYTHONPATH=src python3 -m pve_backup_report --check-api
```
The command displays only status and returned item counts. It does not log the token secret.
Endpoints are tested independently. A failure on the backup jobs endpoint does not prevent displaying the result of `/nodes`, `/storage` and `/cluster`.
## PBS Storages
A PBS storage is usually identified by a type equivalent to `pbs`.
Expected fields to normalize:
- `storage` or equivalent ID;
- `type`;
- `server`;
- `datastore`;
- `namespace`;
- `username`.
Not all fields are necessarily present depending on version or configuration.
The report must display an explicit value such as `not specified` when a non-critical field is missing. For active state, PVE usually exposes `disable=1` when a storage is disabled and omits the field when it is active. Missing `disable` must therefore be interpreted as active.
Current diagnostic command:
```sh
PYTHONPATH=src python3 -m pve_backup_report --dump-inventory
```
It displays normalized PBS storages without showing secrets.
## VM and CT Resources
Inventory can be built from `/cluster/resources`.
Filter useful types:
- `qemu` for VMs;
- `lxc` for containers.
Useful fields:
- `vmid`;
- `name`;
- `type`;
- `node`;
- `status`.
VM/CT notes are collected from each guest configuration on its current node:
- QEMU VM: `/nodes/{node}/qemu/{vmid}/config`;
- LXC container: `/nodes/{node}/lxc/{vmid}/config`.
The expected field is `description`. If a VM/CT configuration is unavailable, collection continues, a `guests` anomaly is added, and the note is displayed as `not specified` in the report.
Diagnostic command:
```sh
PYTHONPATH=src python3 -m pve_backup_report --dump-coverage
```
Final model preview command:
```sh
PYTHONPATH=src python3 -m pve_backup_report --dump-report-data
```
## Latest Completed Backup
Latest backup state is inferred from recent PVE tasks retrieved through `/cluster/tasks` and `/nodes/{node}/tasks` without parameters, then locally filtered on type `vzdump`.
The number of tasks inspected locally is controlled by `PVE_TASK_HISTORY_LIMIT`.
The number of lines retrieved for each task log is controlled by `PVE_TASK_LOG_LIMIT`.
If task history is unavailable, report generation continues and an anomaly is added.
For jobs backing up multiple VM/CT, the per-VM/CT result is extracted from the task log through `/nodes/{node}/tasks/{upid}/log`.
Backup duration is primarily extracted from `Finished Backup of ... (HH:MM:SS)` lines, because this value corresponds to the specific VM/CT. As a fallback, for a simple task or a VM/CT explicitly started in the log, it can be computed from `starttime` and `endtime`.
The VMID is extracted from log lines `Starting Backup of ...`, `Finished Backup of ...` and `Backup of ... failed`. If the log is unavailable, the script falls back to fields `vmid`, `id`, `guest` or `upid`.
If a VM/CT has just been added to a job and has not yet had a completed backup in the available history, no latest backup result can be set.
## Backup Jobs
PVE backup jobs contain the information required to determine:
- whether the job is enabled or disabled;
- target storage;
- schedule;
- VM/CT selection;
- possible exclusions;
- backup mode.
The implementation must distinguish:
- active jobs;
- disabled jobs;
- jobs targeting PBS;
- jobs targeting another storage type.
Current state: jobs are normalized with ID, storage, schedule, enabled state, mode, raw selection and raw exclusions.
Coverage computation interprets explicit `vmid=...` lists, `all=1` jobs, `pool=<name>` jobs and simple exclusions.
If a job references a missing or uncollectable pool, the relevant coverage remains `indetermine`.
## Scheduling
PVE can express schedules using calendar syntax.
The report must display the raw value if complete parsing is not implemented.
If a readable interpretation is added, keep the source value too in case of ambiguity.
## Collection Anomalies
Add an anomaly when:
- an endpoint returns an error;
- a response is incomplete;
- a job references an unknown storage;
- a VM selection type is not supported;
- a field required for analysis is missing.
An anomaly must contain:
- severity;
- affected component;
- short message;
- non-sensitive technical details.
## Client-Handled Errors
The client distinguishes:
- connection error or timeout;
- HTTP error with endpoint and return code;
- invalid JSON response;
- JSON response without a `data` field.
Error messages must never include `PVE_API_TOKEN_SECRET`.
## TLS
The API connection can be run with `PVE_VERIFY_TLS=false` when the existing CA is incompatible with strict Python/OpenSSL validation and cannot be replaced.
+159
View File
@@ -0,0 +1,159 @@
# Target Architecture
[Version francaise](../architecture.md)
## Overview
The application is a batch tool designed to run once per day. It collects data from the Proxmox VE API, transforms it into internal objects, computes backup coverage, then generates a timestamped PDF report.
The Docker container runs a single command and exits with an explicit return code.
Current repository state: the CLI, configuration, logging, PVE/PBS collection, coverage analysis and WeasyPrint PDF generation are implemented. The Docker container includes the system dependencies required for PDF rendering.
## Components
### CLI
The CLI is the script entry point.
Responsibilities:
- load configuration;
- initialize logging;
- run collection;
- run analysis;
- generate the PDF;
- return an appropriate exit code.
### Configuration
The configuration module reads environment variables and validates required values.
It must not contain API call logic.
### PVE Client
The PVE client encapsulates HTTP calls to the Proxmox VE API.
The client uses `requests` and a reusable session.
It implements:
- API token authentication;
- configurable TLS verification;
- optional CA bundle;
- network timeout;
- dedicated errors for connection, HTTP and invalid response failures;
- verification methods for `/nodes`, `/storage` and `/cluster/backup`.
It handles:
- base URL;
- token authentication;
- TLS verification;
- HTTP errors;
- timeouts;
- JSON deserialization.
### Collectors
Collectors retrieve raw information:
- storages;
- backup jobs;
- cluster resources;
- useful VM and CT information;
- VM/CT notes from their node-local configuration;
- PBS prune jobs, when PBS collection is configured;
- PBS snapshots by datastore and namespace, when the corresponding PBS collection is configured.
Each collector must return structured data, not raw JSON scattered across the application.
Current state: collection of PBS storages via `/storage`, backup jobs via `/cluster/backup`, VM/CT via `/cluster/resources`, pools via `/pools`, latest `vzdump` tasks via `/cluster/tasks` and `/nodes/{node}/tasks`, plus prune jobs and snapshots via the PBS API, is implemented.
### Models
Models represent internal entities:
- `PbsStorage`;
- `PbsRetentionPolicy`;
- `PbsBackupSnapshotSummary`;
- `Guest`;
- `BackupJob`;
- `BackupCoverage`;
- `ReportData`;
- `ReportSummary`;
- `CollectionIssue`.
Models can be implemented with `dataclasses` or Pydantic depending on project choices.
### Coverage Analysis
The analysis module compares the VM/CT inventory with active backup jobs.
It produces one status per VM/CT:
- `sauvegarde_planifiee`;
- `non_sauvegardee`;
- `indetermine`.
Current state: coverage based on explicitly listed `vmid` values, `all=1` and `pool=<name>` is computed. Simple exclusions are handled.
### PDF Generation
The PDF generator receives a complete `ReportData` object and produces a file.
It must not call the PVE API.
It must not reread global configuration except for strictly necessary rendering options.
The main renderer uses `WeasyPrint` from an HTML/CSS template. This keeps business logic separate from presentation and makes visual changes easier.
The PDF currently contains the summary, PBS storages declared in PVE, PBS storage usage, PBS retention policies, backup jobs, VM/CT without backup, VM/CT coverage, VM/CT backup retention by PBS with expected version count and delta, and anomalies.
## Data Flow
1. Load configuration.
2. Connect to the PVE API.
3. Optionally connect to configured PBS APIs.
4. Collect storages.
5. Filter PBS storages.
6. Collect prune jobs and snapshots, if configured.
7. Collect backup jobs.
8. Collect VMs and CTs.
9. Compute coverage.
10. Build the report model.
11. Generate the PDF.
12. Log result and exit code.
A preparation step assembles a final `ReportData` object with a computed `ReportSummary`. The `--dump-report-data` command previews this model as JSON.
The `--generate-pdf` command renders this model as a PDF with WeasyPrint. With Docker Compose, it must be launched explicitly:
```sh
docker compose run --rm pve-backup-report --generate-pdf
```
## Error Handling
Blocking errors stop execution:
- invalid configuration;
- authentication failure;
- PVE API fully unavailable;
- inability to write the PDF.
Partial errors can be added to the report:
- a node does not respond;
- an expected field is missing;
- a job cannot be interpreted;
- a referenced storage is missing from collection.
## Idempotence
Daily execution must not modify PVE state.
The tool is read-only with regard to the cluster.
The only expected side effect is the creation of a new report in the output directory.
+251
View File
@@ -0,0 +1,251 @@
# Configuration
[Version francaise](../configuration.md)
## Environment Variables
Recommended minimal configuration:
```env
PVE_API_URL=https://pve.example.invalid:8006
PVE_API_TOKEN_ID=
PVE_API_TOKEN_SECRET=
REPORT_OUTPUT_DIR=/reports
REPORT_TIMEZONE=Europe/Paris
REPORT_LANGUAGE=fr
```
A `.env.example` file is provided as a template. The real `.env` file must not be committed.
Optional configuration:
```env
PVE_VERIFY_TLS=false
PVE_CA_BUNDLE=
PVE_TIMEOUT_SECONDS=30
PVE_BACKUP_JOBS_ENDPOINT=/cluster/backup
PVE_TASK_HISTORY_LIMIT=500
PBS_HOSTNAMES=backup.example.invalid=display-name
LOG_LEVEL=INFO
REPORT_FILENAME_PREFIX=rapport-sauvegardes-pve
```
## Variable Description
| Variable | Required | Description |
| --- | --- | --- |
| `PVE_API_URL` | Yes | Proxmox VE API URL, with scheme and port. |
| `PVE_API_TOKEN_ID` | Yes | Full PVE API token identifier. |
| `PVE_API_TOKEN_SECRET` | Yes | PVE API token secret. |
| `REPORT_OUTPUT_DIR` | No | PDF report output directory. Application default: `reports/`. With Docker Compose, use `/reports`. |
| `REPORT_TIMEZONE` | No | Time zone used for report dates. Default: `Europe/Paris`. |
| `REPORT_LANGUAGE` | No | PDF report language. Supported values: `fr` or `en`. Default: `fr`. |
| `PVE_VERIFY_TLS` | No | Enables TLS verification. Default: `true`. |
| `PVE_CA_BUNDLE` | No | Path to an internal CA mounted in the container. |
| `PVE_TIMEOUT_SECONDS` | No | HTTP timeout. Default: `30`. |
| `PVE_BACKUP_JOBS_ENDPOINT` | No | Endpoint listing scheduled backup jobs. Default: `/cluster/backup`. Keep this value except for specific cases. |
| `PVE_TASK_HISTORY_LIMIT` | No | Number of recent PVE tasks inspected to find the latest backup. Default: `500`. |
| `PVE_TASK_LOG_LIMIT` | No | Number of lines retrieved per `vzdump` task log. Default: `5000`. |
| `PBS_HOSTNAMES` | No | Manual IP/DNS to display-name mapping for PBS servers. |
| `PBS<number>_NAME` | No | Display name of the configured PBS server, for example `PBS01_NAME`. Default: the `PBS<number>` prefix. |
| `PBS<number>_API_URL` | No | PBS API URL, for example `https://backup.example.invalid:8007`. Enables collection when the token is also set. |
| `PBS<number>_API_TOKEN_ID` | No | Full PBS API token identifier. |
| `PBS<number>_API_TOKEN_SECRET` | No | PBS API token secret. |
| `PBS<number>_VERIFY_TLS` | No | Enables TLS verification for this PBS server. Default: `PVE_VERIFY_TLS` value. |
| `PBS<number>_CA_BUNDLE` | No | Path to an internal CA for this PBS server. |
| `PBS<number>_TIMEOUT_SECONDS` | No | HTTP timeout for this PBS server. Default: `PVE_TIMEOUT_SECONDS` or `30`. |
| `LOG_LEVEL` | No | Log level. Default: `INFO`. |
| `REPORT_FILENAME_PREFIX` | No | PDF filename prefix. |
## Docker Compose Example
```yaml
services:
pve-backup-report:
build: .
env_file:
- .env
volumes:
- ./reports:/reports
restart: "no"
```
With this mount, Docker configuration must contain:
```env
REPORT_OUTPUT_DIR=/reports
```
Reports are then written to `./reports/` on the host.
## PBS Name Mapping
`PBS_HOSTNAMES` displays a host name in parentheses in the `PBS server` column.
Format:
```env
PBS_HOSTNAMES=backup.example.invalid=display-name
```
Rendered output:
```text
backup.example.invalid (display-name)
```
## PBS Collection
PBS collection is optional. For each PBS server, it is active only when API URL, token ID and token secret are all provided.
A PBS block follows the `PBS<number>_*` pattern. Collection and display order follow numeric prefix order: `PBS01`, `PBS02`, `PBS10`, etc.
```env
PBS01_NAME=display-name
PBS01_API_URL=https://backup.example.invalid:8007
PBS01_API_TOKEN_ID=
PBS01_API_TOKEN_SECRET=
```
To add more PBS servers, duplicate the block and increment the prefix number:
```env
PBS02_NAME=secondary-display-name
PBS02_API_URL=https://backup-secondary.example.invalid:8007
PBS02_API_TOKEN_ID=
PBS02_API_TOKEN_SECRET=
PBS10_NAME=tenth-display-name
PBS10_API_URL=https://backup-extra.example.invalid:8007
PBS10_API_TOKEN_ID=
PBS10_API_TOKEN_SECRET=
```
The PBS token is sent with the `PBSAPIToken` authentication scheme as `TOKENID:TOKENSECRET`.
PBS collection is active only when URL, token ID and token secret are all set. A URL alone does not block startup, but no collection is run for that PBS.
The Docker service launches `pve-backup-report`. To produce a report, pass `--generate-pdf` explicitly.
To test API connectivity:
```sh
docker compose run --rm pve-backup-report --check-api
```
To generate the PDF:
```sh
docker compose run --rm pve-backup-report --generate-pdf
```
## PVE Token Permissions
The token must be limited to read-only access.
Expected permissions according to the implementation:
- read cluster configuration;
- read storages;
- read VM/CT resources;
- read backup jobs.
For testing the backup jobs endpoint, PVE may require the `Sys.Audit` privilege on `/`.
If `--check-api` returns:
```text
<endpoint>: HTTP 403 - Permission check failed (/, Sys.Audit)
```
add this privilege to the token role, or use an existing read-only role that contains it.
In the observed project state, `/cluster/backup` exists and the expected error for insufficient permissions is `403`, not `404`.
### Effective Token Permission Diagnostics
A PVE token can use separated privileges. In that mode, effective token permissions are the intersection of the parent user's permissions and the token's own ACLs.
Check the token first:
```sh
pveum user token permissions <user@realm> <tokenid> --path /
```
Example:
```sh
pveum user token permissions backup-report@pve report --path /
```
Then check ACLs:
```sh
pveum acl list
```
To explicitly add `PVEAuditor` to the token on `/`:
```sh
pveum acl modify / --tokens 'backup-report@pve!report' --roles PVEAuditor --propagate 1
```
If the token uses separated privileges, the parent user must also have compatible access:
```sh
pveum acl modify / --users backup-report@pve --roles PVEAuditor --propagate 1
```
Another, less restrictive option is to create or modify the token with full privileges (`privsep=0`). In that case, the token inherits permissions from its parent user. This option is less granular and must be chosen deliberately.
Avoid global administrator privileges.
## TLS
TLS verification can be explicitly disabled for local connections:
```env
PVE_VERIFY_TLS=false
PVE_CA_BUNDLE=
```
When `PVE_VERIFY_TLS=false`, `PVE_CA_BUNDLE` is ignored even if set.
When this option is explicitly used, the application logs a single warning and hides repeated `urllib3` warnings to keep logs readable.
If the internal CA is not trusted by the container, prefer a mounted volume and set `PVE_CA_BUNDLE`.
Example:
```yaml
volumes:
- ./reports:/reports
- /etc/ssl/local/pve-ca.pem:/etc/ssl/local/pve-ca.pem:ro
```
Then:
```env
PVE_CA_BUNDLE=/etc/ssl/local/pve-ca.pem
```
## Output Directory
The output directory must be persistent.
With Docker, mount a host volume:
```yaml
volumes:
- /srv/pve-backup-report/reports:/reports
```
And configure:
```env
REPORT_OUTPUT_DIR=/reports
```
The container must be allowed to write to this directory.
Reports must not be produced in an ephemeral path if they are required for audit retention.
+301
View File
@@ -0,0 +1,301 @@
# Operations
[Version francaise](../exploitation.md)
## Docker Execution
Docker is the recommended operating mode. The container runs a one-shot command, writes logs to stdout/stderr, generates the PDF report and exits.
### Preparation
```sh
cp .env.example .env
mkdir -p reports
```
In `.env`, adjust PVE/PBS access and keep the following for Docker execution:
```env
REPORT_OUTPUT_DIR=/reports
```
The Docker Compose service mounts `./reports` to `/reports`, making PDFs available on the host in the `reports/` directory.
### Build the Image
```sh
docker compose build
```
The image installs Python dependencies and the system libraries required by WeasyPrint.
### Configuration Check
```sh
docker compose run --rm pve-backup-report --check-config
```
### API Connectivity Test
```sh
docker compose run --rm pve-backup-report --check-api
```
This command tests `/nodes`, `/storage`, `/cluster` and the endpoint configured in `PVE_BACKUP_JOBS_ENDPOINT`.
If `/cluster/backup` returns `HTTP 403 - Permission check failed (/, Sys.Audit)`, the token works but lacks the `Sys.Audit` privilege on `/`.
### PDF Report Generation
```sh
docker compose run --rm pve-backup-report --generate-pdf
```
The report is created in `REPORT_OUTPUT_DIR`. The filename contains a timestamp and never overwrites previous reports.
Example file generated on the host:
```text
reports/rapport-sauvegardes-pve-2026-05-09-020000.pdf
```
### Docker Diagnostic Commands
Inventory:
```sh
docker compose run --rm pve-backup-report --dump-inventory
```
Coverage:
```sh
docker compose run --rm pve-backup-report --dump-coverage
```
Report data as JSON:
```sh
docker compose run --rm pve-backup-report --dump-report-data
```
This output is a technical preview of the report model. Raw sensitive fields are filtered, but the JSON contains operational information such as VM/CT names, VMIDs, PVE notes, servers, datastores, namespaces, PBS users and backup metadata. Treat it as internal data and do not share it outside the authorized operations/audit scope.
PBS datastore usage:
```sh
docker compose run --rm pve-backup-report --dump-pbs-storage-usages
```
PBS users used by PVE storages:
```sh
docker compose run --rm pve-backup-report --dump-pbs-users
```
Diagnostic for a missing latest backup:
```sh
docker compose run --rm pve-backup-report --debug-last-backup-vmid <VMID>
```
Running `docker compose run --rm pve-backup-report` without arguments does not generate a PDF. To produce the report, use `--generate-pdf` explicitly.
## Direct CLI Execution
This mode is useful for development or diagnostics outside the container.
Local installation:
```sh
python3 -m venv .venv
. .venv/bin/activate
python -m ensurepip --upgrade
pip install -r requirements.txt
pip install -e .
```
PDF generation uses WeasyPrint. Outside Docker, the host must provide the system libraries required by WeasyPrint.
With WeasyPrint 62.x, keep the `pydyf>=0.10,<0.11` constraint to avoid PDF generation errors related to the internal `pydyf` API.
Installed commands:
```sh
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
```
Without editable installation:
```sh
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
```
In direct local execution, `REPORT_OUTPUT_DIR` can remain `reports/` or point to another directory accessible by the current user.
The same `.env` file can be used in Docker and local execution. The Docker value `REPORT_OUTPUT_DIR=/reports` remains valid in the container with the `./reports:/reports` mount. Outside Docker, if `/reports` is not accessible, the application automatically uses the local `reports/` directory and logs the fallback.
It is still possible to explicitly force a local path:
```env
REPORT_OUTPUT_DIR=reports
```
## PBS Retention Policy
To populate the `Retention policy` table and the `VM/CT backup retention` tables, set the API variables for the relevant PBS:
```env
PBS01_NAME=display-name
PBS01_API_URL=https://backup.example.invalid:8007
PBS01_API_TOKEN_ID=
PBS01_API_TOKEN_SECRET=
PBS01_VERIFY_TLS=true
```
For multiple PBS servers, duplicate this block with `PBS02_*`, `PBS03_*`, `PBS04_*`, etc. The report creates retention sections for each configured PBS.
Collection reads prune jobs from configured PBS servers through `/config/prune`. If a PBS is unavailable, if its token lacks sufficient rights, or if no policy is returned, the report continues and adds a `pbs_retention` anomaly.
Collection also reads snapshots from each configured PBS datastore. Namespaces declared in PVE PBS storages are queried on each PBS, including a secondary PBS that does not directly receive backups from PVE but contains synchronized backups. These data feed the `VM/CT backup retention <PBS>` tables, including VM/CT still visible on PBS but absent from the PVE inventory. They are marked `Not active on PVE` in the report.
If a PVE namespace does not exist on a tested PBS datastore, the possible `HTTP 400 - Bad Request` returned by PBS is ignored for that non-root namespace to avoid an expected anomaly.
Retention tables are split by namespace and also display the datastore, because the same namespace can exist on multiple datastores on the same PBS.
They also display the expected version count and a delta compared with visible snapshot count. This calculation depends on an active retention policy matching the same PBS server, datastore and namespace.
The report also collects datastore status from each configured PBS to display total, used and free space.
The garbage collector status of each PBS datastore is also collected. If the garbage collector is running, a warning is displayed only under retention tables for the relevant PBS/datastore.
## Daily Scheduling
Recommended scheduling on Debian 13 can be done with a systemd timer or cron. The container and CLI are designed for one-shot execution: the command generates the report and exits.
### Cron with Docker Compose
Example host crontab to run the report every day at 02:00:
```cron
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
```
Important points:
- adapt `/srv/pve-backup-report` to the real repository path;
- keep `REPORT_OUTPUT_DIR=/reports` in `.env` for Docker;
- verify that the host `reports/` directory exists and is accessible;
- use `flock` to avoid two simultaneous generations if an execution takes longer than expected;
- redirect stdout/stderr to a log file or to the host supervision system.
To test the exact scheduled command:
```sh
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
```
### Cron Calling the Script Directly
Example with the project's virtual environment:
```cron
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
```
In this mode:
- `cd` to the repository allows loading the default `.env` file;
- `REPORT_OUTPUT_DIR` can remain `/reports`, `reports/`, or point to an absolute path accessible by the cron user;
- the cron user must have read access to `.env` and write access to the report directory;
- WeasyPrint system libraries must be installed on the host.
To test the exact scheduled command:
```sh
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
```
Adjust the schedule to avoid high-load periods or maintenance windows.
## Daily Verification
After execution, check:
- presence of a new PDF;
- command return code;
- container logs;
- anomalies section;
- list of VM/CT without backup.
## Evidence Retention
For audit use, keep reports in a backed-up location.
Recommendations:
- persistent storage outside the container;
- limited write permissions;
- read access for authorized people only;
- retention aligned with internal policy;
- system clock synchronized by NTP.
## Recommended Exit Codes
| Code | Meaning |
| --- | --- |
| `0` | Report generated without blocking error. |
| `1` | Configuration error. |
| `2` | PVE authentication impossible. |
| `3` | PVE API unavailable. |
| `4` | PDF write error. |
| `5` | Unexpected error. |
An execution with non-blocking anomalies can return `0` if the report is generated and anomalies are visible in the PDF.
## Monitoring
Monitor at least:
- absence of today's report;
- command failure;
- increase in the number of VM/CT without backup;
- recurring anomalies;
- PVE token expiration or revocation.
## Log Rotation
Logs must remain on stdout/stderr to integrate with Docker.
Rotation is handled by Docker or by the operating platform.
## Restore and Audit
This report proves the observed configuration at execution time.
It does not by itself prove that a restore is possible.
For stronger evidence, complement this project with:
- checks of latest job executions;
- verification of snapshots present in PBS;
- periodic restore tests;
- signature or immutable storage of reports.
+257
View File
@@ -0,0 +1,257 @@
# PDF Report
[Version francaise](../rapport-pdf.md)
## Objective
The PDF must be readable by an operations team and usable as follow-up evidence during an audit.
It must prioritize clarity, generation date, backup coverage and exceptions.
Current state: PDF generation is implemented with `WeasyPrint` from an HTML/CSS template.
The previous `reportlab` renderer remains present in the repository as a technical reference, but `--generate-pdf` uses the WeasyPrint renderer.
The WeasyPrint report language is configurable with `REPORT_LANGUAGE=fr` or `REPORT_LANGUAGE=en`.
The default value remains `fr`.
Current WeasyPrint rendering state:
- Complete cover page (`break-after page`): logo bar on a white background at the top, main title in blue `#1f4e79` 32pt centered, metadata bar (Generation, Version) anchored at the bottom through a flex spacer.
- Footer present on every page including the cover page: document label on the left, generation date in the center, version on the right, separated from content by a horizontal line.
- Page headers except on the first page: report title on the left, page number over total on the right.
- Table of contents with automatic page numbers (CSS `target-counter`) and visual hierarchy: group titles in bold blue, indented subsections.
- SVG icons before each section title (bar chart, server, cylinder, clock, gear, warning triangle, shield, archive, cross, grey circle with question mark for unspecified namespace) to identify each part quickly.
- Section headers with blue left border and very light grey background to distinguish titles from content.
- Summary section displayed as KPI cards (7 columns over 2 lines) instead of a table; "Not backed up" and "Anomalies" indicators appear in orange when greater than zero.
- Tables with dark headers, alternating rows, and CSS classes applied to cells by value: green for "Active" and "Success", red/bold for "Inactive" and "Failure", amber for "Undetermined", grey italic for "not specified".
- Warning messages (PBS GC running) and empty messages visually distinct from tables.
- The report remains readable in black-and-white printing: styling relies on bold, italic and left border in addition to color.
The final report data model can be previewed with:
```sh
PYTHONPATH=src python3 -m pve_backup_report --dump-report-data
```
This command produces JSON containing the computed summary, PBS storages, backup jobs, VM/CT coverage and anomalies.
Raw sensitive fields are filtered from this output, but `--dump-report-data` remains a technical audit output: it contains VM/CT names, VMIDs, PVE notes, servers, datastores, namespaces, PBS users and infrastructure metadata. Treat it as internal data.
PDF generation:
```sh
PYTHONPATH=src python3 -m pve_backup_report --generate-pdf
```
The file is written to `REPORT_OUTPUT_DIR` with a timestamped name. An existing report is never overwritten.
## Filename
Recommended format:
```text
rapport-sauvegardes-pve-YYYY-MM-DD-HHMMSS.pdf
```
Example:
```text
rapport-sauvegardes-pve-2026-05-07-020000.pdf
```
The script must refuse to overwrite an existing file or generate a unique alternative name.
## Report Structure
### Cover Page
Implemented structure (full page, `break-after page`):
1. **Logo bar**: Proxmox VE logo (base64 PNG) on a white background, separated by a grey line at the bottom.
2. **Title area**: `{{ title }}` in blue `#1f4e79` 32pt centered, on a white background.
3. **Spacer**: `div` with `flex: 1` absorbing empty space and pushing the metadata panel to the bottom.
4. **Metadata bar**: light grey background with blue top border, two centered items - Generation (`{{ generated_at }}`) and Version (`{{ version }}`) - separated by a vertical line.
### Table of Contents
The table of contents is placed after the cover page.
It is automatically fed from report section titles to make navigation in the PDF easier.
### Executive Summary
Minimum indicators:
- total VM count;
- total CT count;
- number of VM/CT with a planned backup;
- number of VM/CT without planned backup;
- number of undetermined VM/CT;
- number of PBS storages;
- number of anomalies.
The summary computed in `ReportSummary` also contains:
- number of active jobs;
- number of inactive jobs;
- number of backups to non-PBS storage;
- number of backups to disabled PBS storage;
### PBS Storages Declared in PVE
Expected table:
| ID | Username | PBS server | Datastore | Namespace | Enabled |
| --- | --- | --- | --- | --- | --- |
Secrets must never appear.
The `Enabled` column displays `yes` when the PBS storage is active in PVE. If the PVE `disable` field is absent, the storage is considered active; `disable=1` is displayed as `no`.
### PBS Users - Access Audit
This table is placed immediately after `PBS storages declared in PVE`.
Expected table:
| PBS server | Auth-id | PVE storage | Datastore | Namespace | Enabled | Expiration | Email | Permissions | Comment |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
Data comes from PVE PBS storage `username` values, matched with users and effective permissions exposed by configured PBS servers.
If the PVE auth-id is a token of type `user@realm!token`, the `Auth-id` column keeps the full value and user metadata is matched with `user@realm`.
Secrets must never appear. Displayed permissions are only effective active privileges on the target datastore/namespace.
### PBS Storage Usage
Expected table:
| PBS server | Datastore | Total space | Used space | Free space |
| --- | --- | --- | --- | --- |
Values come from the status of datastores on each configured PBS.
Sizes are displayed in readable units (`Kio`, `Mio`, `Gio`, `Tio`).
### VM/CT Backups by Namespace
Backup tables are grouped under a level 1 title `VM/CT backups`.
VM/CT are displayed in multiple tables, one table per namespace.
The title of each table indicates the relevant namespace.
Expected columns:
| VMID | Name | Notes | Type | Node | VM state | Backup | PBS server | Storage | Mode | Backup schedule | Last backup |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
The `Notes` column contains notes set on the VM or CT in PVE. If no note is set or if it cannot be collected, the displayed value is `not specified`.
The `Backup` column displays `Active` when an active PBS backup is planned, otherwise `Inactive`.
The `Backup schedule` column contains the schedules of associated backup jobs, i.e. the value displayed in the `Schedule` column of the `Backup jobs` table.
The `PBS server` column is inferred from the associated PBS storage when known. If `PBS_HOSTNAMES` contains a mapping, rendering is `IP (host-name)`.
Namespace is not displayed as a column in these tables because it is already carried by the table title.
Tables are sorted alphabetically by namespace, then rows are sorted alphabetically by `Backup schedule`. `VMID` is no longer the primary sort criterion.
The `Mode` column translates the technical PVE job mode:
- `snapshot` becomes `No interruption expected`;
- `suspend` becomes `Temporary suspension`;
- `stop` becomes `Stopped during backup`.
The `Last backup` column displays state `Success`, `Failure` or `Undetermined`, followed by date, end time and duration of the latest known `vzdump` task for the VM/CT when this duration is available.
For multi-VM/CT jobs, this information is inferred from the `vzdump` task log, not only from the global job task.
The value `not specified` is expected if no completed `vzdump` task is available in accessible PVE history, for example for a VM/CT recently added to a job before its first effective backup.
Rows without backup must be visually identifiable.
Undetermined rows must be distinct from non-backed-up rows.
### VM/CT Backup Retention by PBS and Namespace
Retention tables are grouped under a level 1 title `VM/CT backup retention`.
Retention data is displayed in multiple tables, split by PBS server then namespace.
Each table title indicates the PBS and relevant namespace.
Expected columns:
| VMID | VM/CT name | Datastore | PVE state | Version count | Expected version count | Delta | Oldest | Newest | Size |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
The `Datastore` column distinguishes two backups in the same namespace on the same PBS but stored in different datastores.
The `Version count`, `Oldest`, `Newest` and `Size` columns are populated from the relevant PBS through the snapshot list for the datastore and namespace.
The `Expected version count` column is computed from the active retention policy matching the same PBS server, datastore and namespace, by summing configured `keep-last`, `keep-hourly`, `keep-daily`, `keep-weekly`, `keep-monthly` and `keep-yearly` values.
The `Delta` column displays `Version count - Expected version count` with a `+` or `-` sign except when the delta is zero. If no matching active policy is found, `Expected version count` and `Delta` display `not specified`.
The `Size` column is the size of the newest backup visible on the relevant PBS.
The `PVE state` column indicates whether the VM/CT still exists in the PVE inventory (`Active on PVE`) or no longer exists (`Not active on PVE`).
Each PBS server can have multiple tables:
- `VM/CT backup retention <PBS server> - <namespace>`.
Namespace is not displayed as a column in these tables because it is already carried by the table title.
If the garbage collector of the relevant PBS/datastore is running when the report is generated, a warning is displayed just below the relevant table title. This warning explains that the VM/CT backup version count may appear higher than the configured version count.
Each table lists only VM/CT for which at least one matching snapshot is visible on the relevant PBS.
VM/CT still visible on PBS but absent from the PVE inventory remain displayed in the table and are marked `Not active on PVE`.
Snapshots whose namespace does not exist in namespaces declared on PVE are excluded from these tables.
Retention tables are sorted by namespace, then by VM/CT name and VMID when information is available.
### Retention Policy
The `Retention policy` table is populated from configured PBS servers when their `*_API_URL`, `*_API_TOKEN_ID` and `*_API_TOKEN_SECRET` variables are set.
Collection reads PBS prune jobs through `/config/prune` and displays:
- PBS server;
- datastore;
- namespace;
- schedule;
- active/inactive state;
- columns `Last`, `Hourly`, `Daily`, `Weekly`, `Monthly`, `Yearly`;
- `max-depth`.
A namespace missing in PBS is displayed as `/`, meaning the root namespace.
### VM/CT Without Backup
This section must include only uncovered workloads.
It must be short and directly actionable.
### Anomalies
This section lists collection or interpretation issues.
It can be empty, but must then indicate that no anomaly was detected.
## Readability
The report must remain readable in black-and-white printing.
Do not rely only on color to indicate risk.
Use explicit labels:
- `Planned backup`;
- `Not backed up`;
- `Undetermined`.
## Timestamp
All displayed dates must use the configured time zone.
The time zone must appear in the report.
## Retention
The daily report must be kept in a persistent directory.
Retention policy for generated reports is separate from PBS retention policies and must be documented in operations if added.