Storage
banshee/storage-collection/storage/truenas/https/api-v2.0/
disks
The state and the error counts of each disk of the ZFS pools of a TrueNAS by its REST API v2.0.
par banshee1 installation1 version
v1Dernière
Publiée le 27 octobre 2026. First version.
// The state and the error counts of each disk of the ZFS pools of a TrueNAS, by its REST API v2.0
// (GET /api/v2.0/pool, the topology of each pool), behind an API key: what zpool status shows for each
// device. The REST API v2.0 answers on TrueNAS CORE 12 and 13 and on TrueNAS SCALE up to 25.10.
// A disk outside any pool is not read: ZFS knows nothing of it.
need('protocol:http');
$address = need('host:address');
$token = need('protocol:http:token');
per('disk', 'Disks');
output('state', 'text', array('label' => 'State', 'per' => 'disk'));
output('errors', 'number', array('label' => 'Read, write and checksum errors', 'per' => 'disk'));
watch.expect('not-online', 'Disk not online', 'state', array('warning' => array('online', 'avail')));
watch.state('failed', 'Disk failed', 'state', array('critical' => array('faulted', 'unavail', 'removed')));
watch.above('errors', 'Disk errors', 'errors', array('warning' => 0));
watch.missing('missing', 'Disk missing', 'state', array('enabled' => false));
// The key goes in a header: the answer is not secret, its body having carried none.
$answer = http.get('https://' . $address . '/api/v2.0/pool', headers=array('Authorization' => 'Bearer ' . $token));
if ($answer['status'] == 401 || $answer['status'] == 403) {
fail('misconfigured', 'The TrueNAS API refuses the key, code ' . $answer['status']);
}
if ($answer['status'] != 200) {
fail('unreachable', 'The TrueNAS API answers code ' . $answer['status'],
'A TrueNAS 26 or later has no REST API: it answers 404.');
}
// Each pool has a topology: {"data": [...], "log": [...], "cache": [...], "spare": [...], "special": [...],
// "dedup": [...]}, each a list of devices {"name", "type", "status", "stats": {"read_errors",
// "write_errors", "checksum_errors"}, "children": [...]}. A mirror or a RAIDZ holds its disks in children,
// and a disk being replaced holds the old and the new one: the devices are walked through a list that
// grows, until none is left. Only a pool online or degraded is read: a failed pool may have no topology.
$pools = json.read($answer['body']);
$read = 0;
foreach ($pools as $pool) {
$health = text.lower($pool['status']);
if ($health != 'online' && $health != 'degraded') {
continue;
}
foreach (array('data', 'log', 'cache', 'spare', 'special', 'dedup') as $group) {
$pending = list.get($pool['topology'], $group, array());
$i = 0;
while ($i < list.count($pending)) {
$device = $pending[$i];
$i = $i + 1;
$children = list.get($device, 'children', array());
if (list.count($children) > 0) {
foreach ($children as $child) {
$pending = list.add($pending, $child);
}
continue;
}
if ($device['type'] != 'DISK') {
continue;
}
// The device's name is the one ZFS keeps, which a missing disk keeps too.
$name = $pool['name'] . ': ' . $device['name'];
$stats = $device['stats'];
$read = $read + 1;
value('state', text.lower($device['status']), $name);
value('errors', $stats['read_errors'] + $stats['write_errors'] + $stats['checksum_errors'], $name);
}
}
}
if ($read == 0) {
fail('misconfigured', 'The TrueNAS API shows no disk in an online or degraded pool');
}