banshee is under active development: collectors will keep changing, and a version you publish will likely need adjustments. For stable use, wait for version 1.0.0.

Virtualization

banshee/virtualization-collection/virtualization/proxmox/https/

backup-tasks

The result of the last backup job of each node of a Proxmox VE cluster, and the time since, by its API.

  • virtualization
  • proxmox
  • pve
  • backup
  • vzdump
  • task
  • https
  • api

by banshee1 installation1 version

v1Latest

Published on October 27, 2026. First version.

Keen 1.0.0 · 81 lines
// The last backup job of each node of a Proxmox VE cluster, or a single Proxmox VE server, by its API behind
// an API token (GET /api2/json/nodes, then GET /api2/json/nodes/<node>/tasks?typefilter=vzdump&limit=1 for
// each node online): its result, and the time since it ended.
// The address is any node of the cluster, the host's: https://<host:address>:<port>/api2/json/.
need('protocol:http');
$address = need('host:address');
$token = need('protocol:http:token');
$port = variable('proxmox:port', 'integer', array(
    'label'   => 'API port',
    'help'    => 'The port of the Proxmox VE API: 8006, unless a proxy serves it on another.',
    'default' => 8006,
));

per('node', 'Nodes');
output('result', 'text', array('label' => 'Result of the last backup job', 'per' => 'node'));
output('age', 'number', array('label' => 'Time since the last backup job', 'unit' => 'h', 'per' => 'node'));

watch.state('failed', 'Backup job failed', 'result', array('warning' => array('warnings'), 'critical' => array('failed')));
watch.above('old', 'No backup job lately', 'age', array('warning' => 30, 'enabled' => false));
watch.missing('missing', 'Node without backup job', 'result', array('enabled' => false));

// One GET, the token in the header Authorization: PVEAPIToken=<user>@<realm>!<token id>=<secret>. Proxmox VE
// answers {"data": ...}. The answer is not secret, its body having carried none.
function api_get($base, $path, $token) {
    $answer = http.get($base . $path, headers=array('Authorization' => 'PVEAPIToken=' . $token));
    if ($answer['status'] == 401) {
        fail('misconfigured', 'Proxmox VE refuses the API token, code 401');
    }
    if ($answer['status'] == 403) {
        fail('misconfigured', 'The API token may not read ' . $path . ', code 403');
    }
    if ($answer['status'] != 200) {
        fail('unreachable', 'Proxmox VE answers ' . $path . ' with code ' . $answer['status']);
    }
    $page = json.read($answer['body']);
    return $page['data'];
}

$base = 'https://' . $address . ':' . $port . '/api2/json';

$nodes = api_get($base, '/nodes', $token);
if (list.count($nodes) == 0) {
    fail('misconfigured', 'Proxmox VE lists no node');
}
$now = time.now();
foreach ($nodes as $node) {
    if ($node['status'] != 'online') {
        continue;
    }
    $name = $node['node'];
    // The node reads its task archive from the newest: limit=1 gives the last backup job that ended, running
    // ones left out. [{"type": "vzdump", "status": "OK", "starttime": 1793062800, "endtime": 1793066412,
    // "user": "root@pam", ...}]. The status is "OK", "WARNINGS: <n>", or the error that ended the job. Reading
    // the tasks of every account asks Sys.Audit on the node.
    $answer = http.get($base . '/nodes/' . url.encode($name) . '/tasks?typefilter=vzdump&limit=1',
        headers=array('Authorization' => 'PVEAPIToken=' . $token));
    if ($answer['status'] == 401) {
        fail('misconfigured', 'Proxmox VE refuses the API token, code 401');
    }
    if ($answer['status'] == 403) {
        fail('misconfigured', 'The API token may not read the tasks of ' . $name . ', code 403');
    }
    // A node gone between the two readings answers another code: "Node not online" of proxmox/https/nodes
    // says it.
    if ($answer['status'] != 200) {
        continue;
    }
    $page = json.read($answer['body']);
    if (list.count($page['data']) == 0) {
        continue;
    }
    $task = $page['data'][0];
    $result = 'failed';
    if ($task['status'] == 'OK') {
        $result = 'ok';
    } elseif (list.count(text.match($task['status'], '^WARNINGS: [0-9]+$')) > 0) {
        $result = 'warnings';
    }
    value('result', $result, $name);
    value('age', number.round(($now - $task['endtime']) / 3600, 1), $name);
}