uptrakit

Proxmox VE Integration

Proxmox VE Integration

Uptrakit can discover virtual machines and containers from a Proxmox VE cluster and link them to managed hosts.

See also: Plugin Configurations, Proxmox Plugin Development.

Overview

The Proxmox VE plugin connects to your Proxmox cluster's REST API to discover QEMU VMs and LXC containers. Once discovered, you can manually link them to existing Uptrakit-managed hosts to see Proxmox metadata alongside update information.

Beyond discovery and manual matching, Uptrakit can automatically create a PVE snapshot or vzdump backup for a matched guest before applying an update (configurable per software item/host). These protection artifacts are labeled in the PVE UI with the software name, the version transition (e.g. 1.0.0 -> 2.0.0), and the triggering update's ID, so you can identify and manage them at a glance. Automated rollback from these artifacts is not yet implemented — a future capability.

Setup

Prerequisites

  • A Proxmox VE cluster (version 7.x or 8.x)
  • An API token with at least read access to nodes and VMs/CTs

Creating a Proxmox API Token

  1. In the Proxmox web UI, go to Datacenter > Permissions > API Tokens
  2. Click Add and create a token for an existing user
  3. Note the token ID and secret — you will need the full string in the format USER@REALM!TOKENID=SECRET

Uptrakit's automatic provisioning (see Automatic Credential Provisioning below) grants only the minimum privileges each feature needs, via custom roles rather than the built-in PVEAuditor role. If you are creating a token manually instead of letting the SSH agent provision one, grant it Sys.Audit, VM.Audit, VM.GuestAgent.Audit, and VM.GuestAgent.FileRead on / for discovery-only access -- the same privilege set as the UptrakitAudit role Uptrakit creates automatically. Update-protection (snapshots/backups) and resource-scaling features need additional privileges; see Automatic Credential Provisioning for the full list.

Adding the Plugin Configuration

Create a plugin configuration via the API or CLI:

uptrakit plugin-configs create \
  --name "My PVE Cluster" \
  --plugin-type infrastructure.proxmox \
  --config '{
    "api_url": "https://pve.local:8006",
    "api_token": "root@pam!uptrakit=your-secret-here",
    "verify_tls": false
  }'

Configuration Fields

FieldRequiredDefaultDescription
api_urlYesProxmox VE API URL (must be HTTPS)
api_tokenYesAPI token in USER@REALM!TOKENID=SECRET format
verify_tlsNotrueSet to false for self-signed certificates
node_filterNo[]Restrict discovery to specific node names

Usage

All Proxmox operations are accessed through shared surfaces.

Testing the Connection

uptrakit surfaces invoke proxmox.hosts test-connection \
  --params '{"plugin_config_id": "YOUR_PLUGIN_CONFIG_ID"}'

Discovering VMs and Containers

uptrakit surfaces invoke proxmox.hosts discover \
  --params '{"plugin_config_id": "YOUR_PLUGIN_CONFIG_ID"}'

This queries all online nodes (or filtered nodes) and lists their QEMU VMs and LXC containers. For running QEMU VMs, it also attempts to query the guest agent for IP address information.

Listing Discovered Guests

uptrakit surfaces invoke proxmox.hosts list \
  --params '{"plugin_config_id": "YOUR_PLUGIN_CONFIG_ID"}'

Matching to Uptrakit Hosts

Matching is manual — you explicitly link a discovered Proxmox guest to an Uptrakit host:

uptrakit surfaces invoke proxmox.hosts match \
  --params '{"mapping_id": "MAPPING_ID", "host_id": "HOST_ID"}'

To remove a match:

uptrakit surfaces invoke proxmox.hosts unmatch \
  --params '{"mapping_id": "MAPPING_ID"}'

Viewing Proxmox Info for a Host

uptrakit surfaces invoke proxmox.host-info info \
  --params '{"host_id": "HOST_ID"}'

Node Filtering

To restrict discovery to specific Proxmox nodes, set the node_filter field in your plugin configuration:

{
  "api_url": "https://pve.local:8006",
  "api_token": "root@pam!uptrakit=secret",
  "node_filter": ["pve1", "pve3"]
}

Only nodes listed in the filter will be queried. An empty array (the default) means all online nodes are included.

Bootstrapping Guests via SSH Agent

When the SSH agent bootstraps a Proxmox VE node (via regular SSH bootstrap), it automatically detects PVE and provisions API credentials for the tenant. This enables a second bootstrap mode: bootstrapping guests (LXC containers and QEMU VMs) directly through the PVE node without needing SSH access to the guest.

Automatic Credential Provisioning

Uptrakit provisions PVE API access using a single cluster-wide user shared by every tenant, plus one privilege-separated token per tenant -- not a dedicated user per tenant:

  • One user, uptrakit@pve, shared across every tenant on the cluster. It never gets a password -- it exists only to hold API tokens.

  • One API token per tenant, id tenant-{tenant_uuid} (full form uptrakit@pve!tenant-{tenant_uuid}), created with --privsep=1. Because the token is privilege-separated, its effective privileges are the intersection of the grants on uptrakit@pve and the grants on the token itself -- the user-level grants are the ceiling, the token-level grants are the selection within it. A tenant's token can never exceed what uptrakit@pve itself is granted, and revoking one tenant's token never affects another tenant.

  • Three custom roles, created and kept up to date automatically:

    • UptrakitAudit -- read-only access (Sys.Audit, VM.Audit, VM.GuestAgent.Audit, VM.GuestAgent.FileRead), granted on /.
    • UptrakitProtection -- pre-update snapshot/backup access (VM.Snapshot, VM.Backup, Datastore.AllocateSpace), granted on /vms and /storage.
    • UptrakitScaling -- live resource-scaling access (VM.Audit, VM.Config.CPU, VM.Config.Memory), granted on /vms.

    Each role is granted at both the user level (uptrakit@pve) and the token level (uptrakit@pve!tenant-{tenant_uuid}) -- privilege separation requires a grant at both levels before it takes effect for the token.

Multiple tenants can share one uptrakit@pve user on the same cluster, each with its own token; a sync only ever creates, reads, or removes its own tenant's token, never another tenant's. See ADR-0044 for the full rationale.

Reuse is gated by whether the agent already holds an acknowledgment for a reported plugin configuration, not by whether a token happens to exist on the cluster: if the cluster already has a token for the same tenant (from bootstrapping another node in the same cluster) but the agent has no such acknowledgment yet, the token is regenerated rather than reused.

Recovering from a deleted plugin config

That ack-gating is a permanent hazard, not just a startup edge case: deleting a pve-* plugin configuration in the controller does not clear the corresponding acknowledgment on the agent. The agent keeps taking the reuse branch against a plugin configuration the controller no longer has, and never re-reports one on its own -- re-syncing or re-bootstrapping the same host does not recover automatically.

The reliable recovery is to delete this tenant's token on the PVE node itself, as root:

pveum user token remove 'uptrakit@pve' 'tenant-{tenant_uuid}'

This is the same command as Removing one tenant below. With the token gone, the next sync observes it missing and stops reusing the acknowledged plugin configuration id -- it takes the create branch instead, issuing a fresh token and reporting a new plugin configuration.

Removing the host from Uptrakit and re-bootstrapping it recovers only on a standalone PVE node: the re-added host gets a fresh host ID with no local tracking state, so the next bootstrap provisions PVE credentials and reports a plugin configuration from a clean slate. It does not work on a multi-node cluster -- removing a host only deletes its ssh_hosts entry, not the underlying Proxmox host-tracking row, so that row keeps the same node name and still matches the cluster on the next sync, feeding the same stale plugin configuration id right back in.

Each token created or regenerated this way carries a comment visible in pveum user token list 'uptrakit@pve': Uptrakit managed token (<instance host>, tenant <tenant uuid>) when the controller's oauth.canonical_host setting is configured and sanitizes cleanly, or Uptrakit managed token (tenant <tenant uuid>) when it is not set (the default) or does not sanitize. The comment is stamped only at token creation or regeneration time -- changing oauth.canonical_host later does not update the comment on tokens that already exist. A canonical-host change also does not reach an agent until its next reconnect to the controller; for the embedded SSH agent specifically, that means the next controller restart, since embedded services receive settings once at controller boot and never reconnect. See oauth.canonical_host for where this setting is configured.

Privileged session requirement

Credential provisioning issues pveum commands (pveum user add, pveum user token add, and related role/ACL commands) directly against the PVE node, with no sudo wrapping. pveum is never included in the agent's generated sudoers file -- no plugin declares it as a required sudo command -- so provisioning only succeeds when the SSH session used for bootstrap or sync is already a privileged (root) session. Syncing or bootstrapping as an unprivileged user is not blocked ahead of time; the sync still runs, it just fails once it reaches a pveum command.

When that happens, the bootstrap or sync summary reports:

PVE credential setup failed (see agent logs)

Check the agent logs for the underlying pveum error, and re-run bootstrap or sync using a privileged (root) SSH session.

How It Works

  1. Bootstrap the PVE node — Use the regular SSH bootstrap to set up the PVE host. The agent detects PVE automatically and provisions (or reuses) the API user/token described above.
  2. Bootstrap guests — Use the "Bootstrap via Proxmox" action in the UI or CLI. The agent SSHs to the PVE node and runs commands inside the guest via pct exec (LXC) or qm guest exec (QEMU).

Bootstrap via Proxmox Action

Available in the SSH Hosts surface page when at least one PVE node has been bootstrapped.

FieldRequiredDefaultDescription
PVE HostYesPVE node to use as gateway (select from bootstrapped nodes)
Guest VMIDYesVMID of the target container or VM
Guest TypeYeslxcLXC Container or QEMU VM
Host NameYesFriendly name for identification
Target UsernameNouptrakitUser to create/use in the guest
Allow AllNofalseUse NOPASSWD: ALL in sudoers

What Happens During Guest Bootstrap

  1. Connects to the PVE node via SSH
  2. Creates the target user inside the guest
  3. Deploys an SSH key to the guest's authorized_keys
  4. Configures sudoers for the target user
  5. Retrieves the guest's IP address
  6. Verifies direct SSH connectivity to the guest
  7. Saves the host entry to the local database

After bootstrap, the guest is managed like any other SSH host — the agent connects directly to it for version checks and updates.

Bootstrap via Discovered Guest

The "Bootstrap via Discovered Guest" action matches guests discovered by the Proxmox plugin to PVE hosts using the stored PVE node name and plugin config ID. This requires that PVE hosts have been synced (via the Sync Host action or during initial bootstrap) so the short node name (e.g. optiplex2) is stored in the local database. Without a stored node name, matching will fail.

If matching fails, use the Sync Host row action in the web UI (or run uptrakit surfaces ssh-agent.hosts --target-provider-id <PROVIDER_ID> sync <host-id>) to populate the node name, then retry.

Deprovisioning

Use these steps when you stop using Uptrakit's Proxmox VE integration entirely, or when you drop a single tenant from a shared cluster.

Removing one tenant

To remove a single tenant's access while leaving other tenants on the same cluster untouched:

pveum user token remove 'uptrakit@pve' 'tenant-{tenant_uuid}'

This is safe to run with other tenants still active on the cluster -- it removes only that tenant's token. The shared uptrakit@pve user, its roles, and every other tenant's token are unaffected.

Removing the last tenant

First confirm no other tenant still has a token on the shared user:

pveum user token list 'uptrakit@pve'

Only once that list is empty is it safe to remove the shared user and its custom roles:

pveum user delete 'uptrakit@pve'
pveum role delete 'UptrakitAudit'
pveum role delete 'UptrakitProtection'
pveum role delete 'UptrakitScaling'

Deleting uptrakit@pve or any of the three roles while another tenant still has a token on that user breaks that tenant's PVE integration -- always confirm the token list is empty first.

Host-side cleanup

Removing a host from Uptrakit (uptrakit-agent-ssh host remove, or deleting the host in the controller UI) does not remove the managed user, its SSH key, or its sudoers drop-in from the PVE node itself -- see SSH Agent Host Management -- Removing a host. To clean those up on the PVE node, remove the target user's entry from ~<username>/.ssh/authorized_keys (or delete the user outright), and delete its sudoers drop-in at /etc/sudoers.d/uptrakit-<username> (default target username uptrakit).

Controller-side cleanup

Deleting the pve-* plugin configuration in the web UI removes only the controller's record of it -- it does not remove anything on the PVE node. Use the pveum commands above to remove the actual PVE-side user, token, and roles.

Security Considerations

  • The API token secret is stored encrypted at rest and masked in API responses
  • HTTPS is required for the API URL — HTTP connections are rejected
  • Private and loopback addresses are allowed since Proxmox is typically deployed on-premise
  • TLS verification can be disabled for self-signed certificates common in PVE installations