Compute (Hypervisor)
The Compute module (id hypervisor, v1.0.0) connects FreeSDN to one or more Proxmox VE clusters. It gives you a unified view of cluster health, node resources, virtual machine and container lifecycle, snapshots, scheduled backups, storage pools, SDN zones, and per-guest firewall rules - all under the same 7-tier RBAC and staged-write safety contract that governs every other FreeSDN adapter.
The Proxmox adapter spans approximately across adapter.py, client.py, constants.py, and models.py, and ships with full dual-gate enforcement, circuit breaker, central secret redaction, tenant scoping, SSRF protection, and role gates. See Supported Vendors for the complete contract matrix.
Connecting a Proxmox cluster
Section titled “Connecting a Proxmox cluster”1. Create an API token in Proxmox VE
Section titled “1. Create an API token in Proxmox VE”In the Proxmox web UI navigate to Datacenter → Permissions → API Tokens. Create a token for a user with PVEAdmin or an equivalent privilege set. Note the token ID (format user@realm!tokenname) and the token secret - the secret is shown only once.
API token authentication is preferred over username/password. If you use username/password, the adapter caches the PVE ticket for 90 minutes (tickets are valid 2 hours; the cache is deliberately shorter to avoid serving an expiring ticket). On a 401 from an idempotent request it drops the cached ticket and retries once.
2. Register the controller in FreeSDN
Section titled “2. Register the controller in FreeSDN”POST /api/v1/controllersContent-Type: application/json
{ "name": "pve-cluster-1", "controller_type": "proxmox", "host": "pve.lan", "port": 8006, "use_ssl": true, "verify_ssl": false, "config": { "token_id": "root@pam!freesdn", "token_secret": "<token-secret>" }, "site_id": "<site-uuid>"}Default connection parameters: port 8006, SSL on, certificate verification off (Proxmox nodes ship with self-signed certs). Set verify_ssl: true only if your cluster uses a trusted CA.
3. Trigger an initial sync
Section titled “3. Trigger an initial sync”POST /api/v1/discovery/controllers/<controller-uuid>The sync loop runs automatically every 120 seconds (configurable). Each Proxmox node becomes a ProxmoxNode record in the unified device inventory. Individual VMs and containers are tracked in hypervisor.virtual_machines.
Module settings
Section titled “Module settings”| Setting | Default | Range | Description |
|---|---|---|---|
sync_interval | 120 | 30-3600 s | How often FreeSDN polls the cluster for node and VM state |
show_templates | false | bool | Show VM templates in the Virtual Machines list |
Configure via Hypervisor → Settings in the UI or:
PUT /api/v1/modules/org/{organization_id}/hypervisor/settingsContent-Type: application/json
{ "sync_interval": 60, "show_templates": false }Feature domains
Section titled “Feature domains”The adapter covers 12 feature domains:
| Domain | What you get |
|---|---|
| Cluster | Status, quorum, resources, log, replication jobs, cluster-wide options |
| Nodes | Inventory, CPU/memory/disk metrics, RRD history, sensors, services, physical disks, syslog, APT updates, certificates, subscription |
| VMs (QEMU) | List, config, create, power ops, clone, migrate, resize, template conversion, pending config diff, CloudInit, console proxy, bulk ops |
| Containers (LXC) | Same lifecycle as VMs; remote-migrate included |
| Snapshots | Create (with optional RAM state), rollback, delete per VM/CT |
| Backups | Scheduled job CRUD, manual run, prune, restore from PBS or local storage, backup age report |
| Storage pools | Browse pools and content by type, ISO/template upload (4 GB cap), volume delete, prune preview |
| Tasks | List recent tasks, status detail, log tail, stop a running task |
| HA | Resource and group CRUD |
| SDN | Zone and VNet CRUD, dependency-safe zone delete, apply pending SDN config |
| Ceph | Cluster status and detail (404 if Ceph is not deployed on the node) |
| Firewall | Cluster, node, and per-guest firewall rule CRUD |
Key API endpoints
Section titled “Key API endpoints”All endpoints mount under /api/v1/hypervisor/. Reads require a valid session (viewer+). Writes require site_admin minimum role. Browse the full surface at /api/v1/docs (enable ENABLE_DOCS=true in non-production environments).
Cluster and fleet
Section titled “Cluster and fleet”| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/dashboard | Cluster dashboard summary |
| GET | /controllers/{id}/cluster/status | Quorum state, node count, PVE version |
| GET | /controllers/{id}/cluster/resources | All resources; ?type=node|qemu|lxc|storage|sdn |
| GET | /controllers/{id}/cluster/log | Cluster log; ?max_entries=1..5000 (default 50) |
| GET | /fleet/dashboard | Cross-cluster summary across all Proxmox controllers; ?site_id |
| GET | /fleet/task-statistics | Cross-cluster task statistics; ?site_id |
| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/nodes | List nodes |
| GET | /controllers/{id}/nodes/{node} | Node detail (CPU, memory, disk, PVE version) |
| GET | /controllers/{id}/nodes/{node}/services | Node service list |
| GET | /controllers/{id}/nodes/{node}/disks | Physical disks |
| GET | /controllers/{id}/nodes/{node}/disks/smart | SMART data; ?disk=/dev/sda |
| GET | /controllers/{id}/nodes/{node}/syslog | Syslog tail; ?limit=1..500 |
| GET | /controllers/{id}/nodes/{node}/sensors | Sensor/temperature readings |
| GET | /controllers/{id}/nodes/{node}/rrd | RRD history (LTTB-downsampled); ?timeframe=hour|day|week|month|year&max_points=10..5000 |
| POST | /controllers/{id}/nodes/{node}/reboot | Reboot node (site_admin) |
| POST | /controllers/{id}/nodes/{node}/shutdown | Shut down node (site_admin) |
The node path parameter is validated against ^[a-zA-Z0-9._-]+$ (max 63 chars).
VMs and containers
Section titled “VMs and containers”| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/vms | All VMs across nodes; ?type=qemu|lxc |
| GET | /controllers/{id}/nodes/{node}/vms | VMs on a specific node |
| GET | /controllers/{id}/nodes/{node}/containers | LXC containers on a node |
| GET | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/config | VM/CT config (secrets redacted) |
| POST | /controllers/{id}/vms | Create QEMU VM (site_admin) |
| POST | /controllers/{id}/containers | Create LXC container (site_admin) |
| POST | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/action | Power action: start|stop|shutdown|reboot|suspend|resume (site_admin) |
| PUT | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/config | Update VM/CT config (site_admin) |
| POST | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/clone | Clone to new VM (site_admin) |
| POST | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/migrate | Migrate to another node (site_admin) |
| PUT | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/resize | Resize disk (site_admin) |
| DELETE | /controllers/{id}/nodes/{node}/{vm_type}/{vmid} | Delete VM/CT - irreversible (site_admin) |
| POST | /controllers/{id}/bulk-action | Run an action on multiple VMs/CTs (site_admin) |
| POST | /controllers/{id}/bulk-migrate | Migrate multiple VMs to a target node (site_admin) |
vmid is validated as an integer in the range 100-999,999,999. vm_type accepts only qemu or lxc.
Snapshots
Section titled “Snapshots”| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/snapshots | List snapshots |
| POST | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/snapshots | Create snapshot; body: snapname (alphanum/_-, ≤40), description (≤255), vmstate bool |
| POST | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/snapshots/{snapname}/rollback | Roll back to snapshot (site_admin) |
| DELETE | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/snapshots/{snapname} | Delete snapshot (site_admin) |
Storage pools and content
Section titled “Storage pools and content”| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/nodes/{node}/storage | List storage pools with usage stats |
| GET | /controllers/{id}/nodes/{node}/storage/{storage}/content | Browse content; ?content=images|iso|backup|rootdir|vztmpl|snippets&vmid= |
| POST | /controllers/{id}/nodes/{node}/storage/{storage}/upload | Upload ISO or template (multipart, 4 GB cap) |
| GET | /controllers/{id}/nodes/{node}/storage/{storage}/prune-preview | Preview what a prune would remove |
| POST | /controllers/{id}/nodes/{node}/storage/{storage}/prune | Execute prune with retention policy (site_admin) |
| DELETE | /controllers/{id}/nodes/{node}/storage/{storage}/content/{volume} | Delete a storage volume (site_admin) |
Upload streams through a 1 MB-chunk temp file then posts to PVE. The temp file is removed in a finally block. Uploads beyond 4 GB receive HTTP 413.
Backups
Section titled “Backups”| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/backup/jobs | List scheduled backup jobs |
| POST | /controllers/{id}/backup/jobs | Create backup job (site_admin) |
| PUT | /controllers/{id}/backup/jobs/{job_id} | Update backup job (site_admin) |
| DELETE | /controllers/{id}/backup/jobs/{job_id} | Delete backup job (site_admin) |
| POST | /controllers/{id}/nodes/{node}/{vm_type}/{vmid}/backup | Run manual backup; body: storage, mode, compress (site_admin) |
| POST | /controllers/{id}/backup/restore | Restore from archive; body: node, vm_type, archive, vmid, storage, start_after_restore, unique_mac (site_admin) |
| GET | /controllers/{id}/backup/age-report | Age report; ?threshold_hours=1..8760 (default 24) |
SDN zones and VNets
Section titled “SDN zones and VNets”| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/sdn/zones | List SDN zones |
| GET | /controllers/{id}/sdn/vnets | List VNets |
| POST | /controllers/{id}/sdn/zones | Create zone (site_admin) |
| POST | /controllers/{id}/sdn/vnets | Create VNet (site_admin) |
| DELETE | /controllers/{id}/sdn/zones/{zone} | Delete zone - returns 409 with blocking VNet names if dependents exist (site_admin) |
| DELETE | /controllers/{id}/sdn/vnets/{vnet} | Delete VNet (site_admin) |
| POST | /controllers/{id}/sdn/apply | Apply pending SDN configuration (site_admin) |
Zone deletes are dependency-safe: the API refuses with HTTP 409 (listing the blocking VNets) rather than leaving orphaned VNets.
HA resources and groups
Section titled “HA resources and groups”| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/ha/resources | List HA resources |
| POST | /controllers/{id}/ha/resources | Add VM/CT to HA; sid format (vm|ct):\d+ (site_admin) |
| DELETE | /controllers/{id}/ha/resources/{sid} | Remove from HA (site_admin) |
| GET | /controllers/{id}/ha/groups | List HA groups |
| POST | /controllers/{id}/ha/groups | Create HA group (site_admin) |
| DELETE | /controllers/{id}/ha/groups/{group} | Delete HA group (site_admin) |
Guest agent (QEMU)
Section titled “Guest agent (QEMU)”| Method | Path | Purpose |
|---|---|---|
| GET | /controllers/{id}/nodes/{node}/qemu/{vmid}/agent/info | Guest network interfaces (502 if agent unavailable) |
| POST | /controllers/{id}/nodes/{node}/qemu/{vmid}/agent/exec | Execute command in guest; body: command, input_data - output redacted (site_admin) |
| GET | /controllers/{id}/nodes/{node}/qemu/{vmid}/agent/exec-status/{pid} | Poll exec stdout/status - redacted (site_admin) |
| POST | /controllers/{id}/nodes/{node}/qemu/{vmid}/agent/file-read | Read file from guest filesystem - redacted (site_admin) |
| POST | /controllers/{id}/nodes/{node}/qemu/{vmid}/agent/file-write | Write file into guest filesystem (site_admin) |
These endpoints accept site_admin minimum role. The QEMU guest agent package must be installed and running inside the VM.
Staged writes
Section titled “Staged writes”FreeSDN’s Proxmox integration uses a dual-gate safety contract for all mutations. The gate has two independent conditions; both must be cleared before a write reaches the cluster:
- Environment gate:
ADAPTER_READ_ONLY=falsemust be set on theapicontainer. The default istrue.OMADA_READ_ONLYmust also be set tofalse(defaulttrue). The Proxmox client OR’s both flags; leaving either attruekeeps all writes refused - regardless of whether an Omada controller is registered.OMADA_READ_ONLYis a legacy alias for the global adapter write gate, not a feature toggle for Omada users. - Call-site gate: the staging applier must pass
force=trueon the internal adapter call. The module service layer does not passforce=true. Only the gateway-proxmox staging endpoints pass it, and only after an operator applies a pending change.
What this means in practice
Section titled “What this means in practice”With default settings (ADAPTER_READ_ONLY=true), every write endpoint in /api/v1/hypervisor/… is refused by the adapter read-only gate and returns an AdapterError - the request does not record a pending change and does not touch the cluster. The HypervisorService layer never passes force=True; the read-only gate therefore blocks all writes unconditionally when the default is in effect.
To stage mutations, use the gateway-proxmox staging endpoints described below, which create PendingChange records and queue them for operator review. An operator with site_admin+ role reviews and applies from the Hypervisor UI (Pending Changes tab) or via:
POST /api/v1/gateway-vpn/changes/{change-id}/applyThe staging endpoints live under /api/v1/gateway-proxmox-{vm,container,snapshot,storage,backup,cluster}/ (gated at include time by enforce_catastrophic_stage_role). You do not call them directly from user-facing code - the UI and Fabric executor drive them.
Staging flow for a VM operation
Section titled “Staging flow for a VM operation”- Operator authors a change in the UI (e.g., stop a VM for maintenance).
- FreeSDN creates a
PendingChangerecord with featureproxmox.vm.stop, stores the payload, and returns aPendingChangeResponse. - A
site_adminreviews the pending change in the UI. - On apply: the staging applier calls the adapter with
force=True; the dual-gate clears; Proxmox executes the stop; the change is marked applied.
Fabric integration
Section titled “Fabric integration”The hypervisor module exposes five Fabric operation targets. All are staged writes - an operator must sign off before they execute.
| Operation id | Required inputs | Permission |
|---|---|---|
hypervisor.vm.snapshot | controller_id, node, vmid, snapname | hypervisor.manage_snapshots |
hypervisor.vm.start | controller_id, node, vmid (+ vm_type qemu/lxc) | hypervisor.manage_vms |
hypervisor.vm.stop | same | hypervisor.manage_vms |
hypervisor.vm.shutdown | same | hypervisor.manage_vms |
hypervisor.vm.reboot | same | hypervisor.manage_vms |
Example wiring: OPNsense firewall rule applied → snapshot affected VMs. Author this as a Fabric Connection targeting hypervisor.vm.snapshot and wire it to the controller.change.applied event from your firewall controller. See Fabric for wiring syntax and Connection authoring.
Permissions
Section titled “Permissions”| Permission code | Minimum role | Covers |
|---|---|---|
hypervisor.view | viewer | Read-only access to all cluster, node, VM, and storage data |
hypervisor.manage_vms | site_admin | VM/CT power operations, console, guest agent, bulk ops |
hypervisor.manage_snapshots | site_admin | Create, rollback, and delete snapshots |
hypervisor.manage_backups | site_admin | Create, update, and trigger backup jobs |
hypervisor.manage_nodes | site_admin | Node-level operations (reboot, shutdown, services) |
Frontend: the Hypervisor page
Section titled “Frontend: the Hypervisor page”Navigate to Hypervisor in the left sidebar (route /hypervisor). Choose a controller from the dropdown when you have multiple Proxmox clusters registered.
With no controller selected - the page shows a fleet dashboard: clusters online/total, total nodes, VMs, containers, and aggregate CPU/memory/storage utilization drawn from /fleet/dashboard.
With a controller selected - tabs include:
| Tab | Contents |
|---|---|
| Dashboard | Cluster health, quorum state, HA active count, per-node resource bars |
| Nodes | Node list with CPU/memory/disk sparklines; click a node for a detail drawer with sub-tabs: Overview · VMs · Containers · Services · Disks · Network · Sensors |
| Virtual Machines | VM list with status, vCPU, memory; power actions; bulk action bar |
| Containers | LXC container list; same operations as VMs |
| Storage | Pool browser with content-type filter (all/ISO/templates/backup/disk images/snippets); upload and restore dialogs |
| Tasks | Recent task list with status and log tail |
| Backup | Scheduled job list; manual backup trigger; backup age report |
| Firewall | Cluster and per-guest firewall rule tables |
| HA | HA resource and group management |
| Pools | Resource pool list |
Additional component tabs available in the drawer and via navigation: Ceph, Replication, PBS (Proxmox Backup Server), Certificates, SDN, Monitoring (RRD charts), Updates (APT), Subscriptions, Templates (when show_templates=true), Cluster Log, Kiosk Mode.
Adapter internals
Section titled “Adapter internals”Authentication and connection
Section titled “Authentication and connection”The Proxmox client (ProxmoxClientConfig) connects to {host}:{port} (default 8006) over HTTPS. It supports two auth modes:
- API token (preferred):
token_id(user@realm!tokenname) +token_secret. Tokens are Fernet-decrypted at runtime; they never appear in logs or error messages. - Ticket auth: username/password/realm. The client caches the ticket for 90 minutes (PVE tickets are valid 2 hours); on a 401 from an idempotent request it drops the cached ticket and retries once.
Safety mechanisms
Section titled “Safety mechanisms”- Read-only gate: every
POST,PUT,PATCH, andDELETErequest checks_is_adapter_read_only()before proceeding. If the gate is closed, the request is recorded asread_only_blockedin metrics and anAdapterErroris raised. - Circuit breaker: 5 consecutive failures open the breaker for 60 seconds. Idempotent timeouts retry with jittered backoff.
- Path-traversal guard:
_validate_path(path)runs at every_requestchokepoint. - Rate limiter: 120 requests/minute, 10 concurrent connections. A dedicated 2-slot semaphore handles large uploads so a 4 GB ISO transfer does not starve API calls.
- Response size cap:
check_response_size(resp)bounds device response bodies. - Secret redaction:
redact_secrets(central, ~90 sensitive key patterns, camelCase-aware) is applied to every adapter read. Full VM/CT config responses (GET …/config) go through this broader central filter._SENSITIVE_CONFIG_KEYS = {cipassword, sshkeys, args, hookscript}are stripped from pending-config (GET …/pending) responses only. CloudInitcipassword/sshkeys/ipconfigNare redacted. PVE ticket fragments and URLs are stripped from error messages.
RRD downsampling
Section titled “RRD downsampling”All RRD endpoints use LTTB (Largest-Triangle-Three-Buckets) downsampling. The max_points parameter (10-5000, default 500) controls output resolution. This keeps chart queries fast even for year-range timeframes.
Gotchas and limitations
Section titled “Gotchas and limitations”- Proxmox VE only. Proxmox Mail Gateway (PMG) and Proxmox Backup Server (PBS, as a standalone appliance) are not managed here. The adapter only talks to PVE clusters.
- Cluster membership changes require shell access. The Proxmox REST API does not expose adding or removing cluster nodes. Use the Proxmox UI or SSH for those operations.
- Node status may lag by up to
sync_intervalseconds. If a node goes offline between sync cycles, FreeSDN’sstatusfield reflects the last successful poll, not real-time state. - Templates hidden by default. VM templates do not appear in the Virtual Machines list unless you set
show_templates: truein module settings. - Ceph tab returns 404 when Ceph is not deployed. This is expected - the adapter passes the 404 through cleanly rather than raising an error.
- SDN zone delete is dependency-safe. Attempting to delete a zone that has dependent VNets returns HTTP 409 with the blocking VNet names. Delete the VNets first.
- Upload cap is 4 GB. Uploading ISOs or templates larger than 4 GB returns HTTP 413. Split or pre-download large images directly on the PVE node.
- Remote-migrate requires a separate Proxmox cluster as the target. Both source and target clusters must be reachable from the FreeSDN API container.
Next steps
Section titled “Next steps”- Supported Vendors - Proxmox adapter contract, maturity tier, and known limitations.
- Staged Changes - how the pending-change queue works across all adapters.
- Fabric - wire hypervisor operations to events from other modules.
- Roles and Permissions - full 7-tier role hierarchy and how site grants interact with module permissions.
- Storage (TrueNAS) - companion module for ZFS pool health and staged blob writes.