Files
unpoller_unpoller/pkg/inputunas
Cody LeeandClaude Opus 5 d5dfc771d5 feat: add opt-in UNAS Pro support (closes #785)
Adds a new `unas` input plugin that polls UNAS Pro storage consoles and
exports console health, storage pools, disks and shares to Prometheus,
InfluxDB and DataDog.

UNAS is a separate plugin rather than a device type inside inputunifi
because a storage-only console has no Network application: it cannot
answer /status, has no sites, and shares none of the UniFi device schema.

The plugin is opt-in and inert until an operator names a console. Opt-in
is expressed as "no devices configured" rather than a `disable` flag,
because a bool named `disable` zero-values to false and so cannot make a
plugin default-off. Initialize returns silently on an empty device list
and, unlike inputunifi, nothing synthesizes a default URL.

Two behaviours are worth calling out for reviewers:

  - Metrics returns (metrics, nil) whenever any console was collected.
    poller.collectMetrics uses `if err != nil {} else if metric != nil`,
    so returning both would discard every healthy console because one
    failed. Only a total failure returns an error.

  - Re-auth fires on total failure, not on a 401. A mid-session 401 from
    GetData surfaces as ErrInvalidStatusCode, not ErrAuthenticationFailed,
    so there is no sentinel to match on. Session expiry fails all four
    endpoints at once, which is exactly the total-failure case.

Prometheus metrics use the `unifi_unas_` prefix, which diverges from the
`unas_` prefix used by the reference implementation; dashboards built
against that will need query edits.

Requires unifi/v5 v5.31.0 for the UNAS client and structs.

Credit to alexgreenbank/unaspoller for mapping the endpoints.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 07:52:37 -05:00
..

UNAS Pro Input Plugin

Polls UniFi UNAS Pro storage consoles and hands their metrics to every configured output.

This plugin is opt-in and does nothing until you configure at least one device. With no [unas] section, or a section with no devices, it stays silent and inert — it logs nothing and polls nothing.

Why a separate plugin?

A UNAS Pro is a standalone UniFi OS console with no Network application: no sites, no /status endpoint, its own credentials, and usually its own host. inputunifi's sites-then-devices flow has nothing to offer it, so UNAS consoles are configured separately rather than as another UniFi controller.

Configuration

[unas]
  disable = false

# Applied to any device that does not set its own value.
[unas.defaults]
  user       = "unpoller"
  pass       = ""
  verify_ssl = false
  timeout    = "60s"

# Repeat for each console. Use its own local-account credentials.
# Do not add a path after the host.
[[unas.device]]
  url            = "https://192.168.1.10"
  user           = "unpoller"
  pass           = "unpoller"
  verify_ssl     = false
  timeout        = "60s"
  ssl_cert_paths = []

JSON and YAML use devices (plural) as the list key; TOML uses repeated [[unas.device]] tables. See examples/up.{conf,json,yaml}.example.

Environment variables

Variable Meaning
UP_UNAS_DISABLE Disable the plugin outright.
UP_UNAS_DEFAULT_USER Default username for every device.
UP_UNAS_DEFAULT_PASS Default password.
UP_UNAS_DEFAULT_VERIFY_SSL Default TLS verification.
UP_UNAS_DEFAULT_TIMEOUT Default HTTP timeout, e.g. 60s.
UP_UNAS_DEVICE_0_URL First console's URL.
UP_UNAS_DEVICE_0_USER First console's username.
UP_UNAS_DEVICE_0_PASS First console's password.
UP_UNAS_DEVICE_0_VERIFY_SSL First console's TLS verification.
UP_UNAS_DEVICE_0_TIMEOUT First console's HTTP timeout.

Increment the index for additional consoles. Note the defaults block is DEFAULT, singular.

Authentication

Username and password only. API keys are not supported: the UniFi library routes key auth through /status, which a storage-only console does not serve.

A UNAS session expires after roughly two hours. When it does, every endpoint starts failing at once; the plugin logs back in and retries once per poll, so this recovers without a restart.

Metrics

Data comes from the UniFi Drive API. Four endpoints are polled per cycle:

Endpoint Yields
/proxy/drive/api/v2/systems/device-info name, model, version, firmware, status, CPU load and temperature, memory
/proxy/drive/api/v2/storage pools (capacity, usage, status, RAID groups) and disks (health score, temperature, power-on hours, RPM, size, bad and uncorrectable sectors, read/write KB/s)
/proxy/users/drive/api/v2/drives per-share id, name, type, status, quota, usage, member count
/proxy/drive/api/v2/systems/network-io receive and transmit KB/s

An endpoint that fails is logged and its metrics omitted for that cycle; the rest are still reported. A console that fails every endpoint is reported as an error, and one dead console never suppresses the metrics of a healthy one.

Prometheus

Metric names are prefixed with the configured namespace, so they read as unifi_unas_disk_temperature_celsius, unifi_unas_pool_usage_bytes, unifi_unas_share_quota_bytes, and so on. Note this differs from unaspoller, which uses a bare unas_ prefix — the Grafana dashboard in #785 needs its queries adjusted accordingly. Consistency with every other unpoller metric wins here.

InfluxDB and DataDog

Four measurements / metric prefixes: unas_device, unas_pool, unas_disk, and unas_share (DataDog also emits unas_raid_group). They are kept separate because each has its own tag set; folding them together would give every point the union of those tags with most of them empty.

Loki and OpenTelemetry are not wired up: Loki carries events only, and a UNAS console exposes no event endpoints in this version.

Credit

Endpoint discovery and the JSON shapes come from alexgreenbank/unaspoller (MIT), which worked out the UniFi Drive API by observing the console's own web UI. See #785.

Known gaps

These need data from real hardware:

  • The networkInterfaces JSON key is an informed assumption — the reference implementation's struct tag for it was empty, so it was never actually verified. Interface data is not exported as metrics either way.
  • cacheSlots, expansions, usbs, riskReasons, and incompatibleReasons are kept as raw JSON in the library because no populated example exists.
  • /proxy/drive/api/v2/systems/disk-stats is not polled: it returns time series that do not map onto gauges, and its per-disk throughput is already in /storage.