Skip to content

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.

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.

POST /api/v1/controllers
Content-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.

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.

SettingDefaultRangeDescription
sync_interval12030-3600 sHow often FreeSDN polls the cluster for node and VM state
show_templatesfalseboolShow VM templates in the Virtual Machines list

Configure via Hypervisor → Settings in the UI or:

PUT /api/v1/modules/org/{organization_id}/hypervisor/settings
Content-Type: application/json
{ "sync_interval": 60, "show_templates": false }

The adapter covers 12 feature domains:

DomainWhat you get
ClusterStatus, quorum, resources, log, replication jobs, cluster-wide options
NodesInventory, 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
SnapshotsCreate (with optional RAM state), rollback, delete per VM/CT
BackupsScheduled job CRUD, manual run, prune, restore from PBS or local storage, backup age report
Storage poolsBrowse pools and content by type, ISO/template upload (4 GB cap), volume delete, prune preview
TasksList recent tasks, status detail, log tail, stop a running task
HAResource and group CRUD
SDNZone and VNet CRUD, dependency-safe zone delete, apply pending SDN config
CephCluster status and detail (404 if Ceph is not deployed on the node)
FirewallCluster, node, and per-guest firewall rule CRUD

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).

MethodPathPurpose
GET/controllers/{id}/dashboardCluster dashboard summary
GET/controllers/{id}/cluster/statusQuorum state, node count, PVE version
GET/controllers/{id}/cluster/resourcesAll resources; ?type=node|qemu|lxc|storage|sdn
GET/controllers/{id}/cluster/logCluster log; ?max_entries=1..5000 (default 50)
GET/fleet/dashboardCross-cluster summary across all Proxmox controllers; ?site_id
GET/fleet/task-statisticsCross-cluster task statistics; ?site_id
MethodPathPurpose
GET/controllers/{id}/nodesList nodes
GET/controllers/{id}/nodes/{node}Node detail (CPU, memory, disk, PVE version)
GET/controllers/{id}/nodes/{node}/servicesNode service list
GET/controllers/{id}/nodes/{node}/disksPhysical disks
GET/controllers/{id}/nodes/{node}/disks/smartSMART data; ?disk=/dev/sda
GET/controllers/{id}/nodes/{node}/syslogSyslog tail; ?limit=1..500
GET/controllers/{id}/nodes/{node}/sensorsSensor/temperature readings
GET/controllers/{id}/nodes/{node}/rrdRRD history (LTTB-downsampled); ?timeframe=hour|day|week|month|year&max_points=10..5000
POST/controllers/{id}/nodes/{node}/rebootReboot node (site_admin)
POST/controllers/{id}/nodes/{node}/shutdownShut down node (site_admin)

The node path parameter is validated against ^[a-zA-Z0-9._-]+$ (max 63 chars).

MethodPathPurpose
GET/controllers/{id}/vmsAll VMs across nodes; ?type=qemu|lxc
GET/controllers/{id}/nodes/{node}/vmsVMs on a specific node
GET/controllers/{id}/nodes/{node}/containersLXC containers on a node
GET/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/configVM/CT config (secrets redacted)
POST/controllers/{id}/vmsCreate QEMU VM (site_admin)
POST/controllers/{id}/containersCreate LXC container (site_admin)
POST/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/actionPower action: start|stop|shutdown|reboot|suspend|resume (site_admin)
PUT/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/configUpdate VM/CT config (site_admin)
POST/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/cloneClone to new VM (site_admin)
POST/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/migrateMigrate to another node (site_admin)
PUT/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/resizeResize disk (site_admin)
DELETE/controllers/{id}/nodes/{node}/{vm_type}/{vmid}Delete VM/CT - irreversible (site_admin)
POST/controllers/{id}/bulk-actionRun an action on multiple VMs/CTs (site_admin)
POST/controllers/{id}/bulk-migrateMigrate 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.

MethodPathPurpose
GET/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/snapshotsList snapshots
POST/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/snapshotsCreate snapshot; body: snapname (alphanum/_-, ≤40), description (≤255), vmstate bool
POST/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/snapshots/{snapname}/rollbackRoll back to snapshot (site_admin)
DELETE/controllers/{id}/nodes/{node}/{vm_type}/{vmid}/snapshots/{snapname}Delete snapshot (site_admin)
MethodPathPurpose
GET/controllers/{id}/nodes/{node}/storageList storage pools with usage stats
GET/controllers/{id}/nodes/{node}/storage/{storage}/contentBrowse content; ?content=images|iso|backup|rootdir|vztmpl|snippets&vmid=
POST/controllers/{id}/nodes/{node}/storage/{storage}/uploadUpload ISO or template (multipart, 4 GB cap)
GET/controllers/{id}/nodes/{node}/storage/{storage}/prune-previewPreview what a prune would remove
POST/controllers/{id}/nodes/{node}/storage/{storage}/pruneExecute 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.

MethodPathPurpose
GET/controllers/{id}/backup/jobsList scheduled backup jobs
POST/controllers/{id}/backup/jobsCreate 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}/backupRun manual backup; body: storage, mode, compress (site_admin)
POST/controllers/{id}/backup/restoreRestore from archive; body: node, vm_type, archive, vmid, storage, start_after_restore, unique_mac (site_admin)
GET/controllers/{id}/backup/age-reportAge report; ?threshold_hours=1..8760 (default 24)
MethodPathPurpose
GET/controllers/{id}/sdn/zonesList SDN zones
GET/controllers/{id}/sdn/vnetsList VNets
POST/controllers/{id}/sdn/zonesCreate zone (site_admin)
POST/controllers/{id}/sdn/vnetsCreate 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/applyApply 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.

MethodPathPurpose
GET/controllers/{id}/ha/resourcesList HA resources
POST/controllers/{id}/ha/resourcesAdd 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/groupsList HA groups
POST/controllers/{id}/ha/groupsCreate HA group (site_admin)
DELETE/controllers/{id}/ha/groups/{group}Delete HA group (site_admin)
MethodPathPurpose
GET/controllers/{id}/nodes/{node}/qemu/{vmid}/agent/infoGuest network interfaces (502 if agent unavailable)
POST/controllers/{id}/nodes/{node}/qemu/{vmid}/agent/execExecute 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-readRead file from guest filesystem - redacted (site_admin)
POST/controllers/{id}/nodes/{node}/qemu/{vmid}/agent/file-writeWrite 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.

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:

  1. Environment gate: ADAPTER_READ_ONLY=false must be set on the api container. The default is true. OMADA_READ_ONLY must also be set to false (default true). The Proxmox client OR’s both flags; leaving either at true keeps all writes refused - regardless of whether an Omada controller is registered. OMADA_READ_ONLY is a legacy alias for the global adapter write gate, not a feature toggle for Omada users.
  2. Call-site gate: the staging applier must pass force=true on the internal adapter call. The module service layer does not pass force=true. Only the gateway-proxmox staging endpoints pass it, and only after an operator applies a pending change.

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}/apply

The 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.

  1. Operator authors a change in the UI (e.g., stop a VM for maintenance).
  2. FreeSDN creates a PendingChange record with feature proxmox.vm.stop, stores the payload, and returns a PendingChangeResponse.
  3. A site_admin reviews the pending change in the UI.
  4. On apply: the staging applier calls the adapter with force=True; the dual-gate clears; Proxmox executes the stop; the change is marked applied.

The hypervisor module exposes five Fabric operation targets. All are staged writes - an operator must sign off before they execute.

Operation idRequired inputsPermission
hypervisor.vm.snapshotcontroller_id, node, vmid, snapnamehypervisor.manage_snapshots
hypervisor.vm.startcontroller_id, node, vmid (+ vm_type qemu/lxc)hypervisor.manage_vms
hypervisor.vm.stopsamehypervisor.manage_vms
hypervisor.vm.shutdownsamehypervisor.manage_vms
hypervisor.vm.rebootsamehypervisor.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.

Permission codeMinimum roleCovers
hypervisor.viewviewerRead-only access to all cluster, node, VM, and storage data
hypervisor.manage_vmssite_adminVM/CT power operations, console, guest agent, bulk ops
hypervisor.manage_snapshotssite_adminCreate, rollback, and delete snapshots
hypervisor.manage_backupssite_adminCreate, update, and trigger backup jobs
hypervisor.manage_nodessite_adminNode-level operations (reboot, shutdown, services)

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:

TabContents
DashboardCluster health, quorum state, HA active count, per-node resource bars
NodesNode list with CPU/memory/disk sparklines; click a node for a detail drawer with sub-tabs: Overview · VMs · Containers · Services · Disks · Network · Sensors
Virtual MachinesVM list with status, vCPU, memory; power actions; bulk action bar
ContainersLXC container list; same operations as VMs
StoragePool browser with content-type filter (all/ISO/templates/backup/disk images/snippets); upload and restore dialogs
TasksRecent task list with status and log tail
BackupScheduled job list; manual backup trigger; backup age report
FirewallCluster and per-guest firewall rule tables
HAHA resource and group management
PoolsResource 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.

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.
  • Read-only gate: every POST, PUT, PATCH, and DELETE request checks _is_adapter_read_only() before proceeding. If the gate is closed, the request is recorded as read_only_blocked in metrics and an AdapterError is 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 _request chokepoint.
  • 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. CloudInit cipassword/sshkeys/ipconfigN are redacted. PVE ticket fragments and URLs are stripped from error messages.

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.

  • 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_interval seconds. If a node goes offline between sync cycles, FreeSDN’s status field 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: true in 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.
  • 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.