Files
unpoller_unpoller/pkg/inputunifi
Cooper Ry LeesandClaude Opus 5 31cc5a0f31 feat: poll Protect-only consoles via disable_network (closes #1066)
A UNVR or UNVR Pro runs UniFi Protect with no Network application installed.
UnPoller could not poll one at all: NewUnifi ends with GetServerData(), a GET of
/proxy/network/status, which such a console answers with its UniFi OS SPA HTML.
The controller entry died during initialisation and re-failed every interval,
never even printing a config summary -- while the Protect Integration API on the
same host answered every endpoint with the same key.

Set disable_network = true on that controller. It defaults to false, so nothing
about an existing config changes.

The Protect collectors were already complete and already not site-scoped; three
things stood between them and a Protect-only console:

  - getUnifi now calls unifi.NewProtectClient, which skips the Network probe and
    validates the Protect Integration API instead (unpoller/unifi#240).

  - pollController aborted on getFilteredSites long before reaching
    collectProtect, and collectControllerEvents did the same before
    collectProtectLogs. The Network pass is extracted into pollNetwork and
    skipped wholesale; the event collector list reduces to collectProtectLogs,
    the only site-independent one.

  - Metrics counted a poll successful only if it produced devices or clients. A
    Protect-only console produces neither, so a filtered scrape of one -- the
    Prometheus per-target path -- fell through to the dynamic-controller branch
    and reported ErrDynamicLookupsDisabled despite a successful collection.
    ProtectDevices now counts too.

Two smaller things worth calling out for reviewers:

  - extractDevices dereferenced metrics.Devices unguarded. That was already a
    latent panic; skipping the Network pass makes it reachable, so it is fixed
    here rather than left for the first person to hit it.

  - RawMetrics answers the raw-path kind for these consoles and rejects the
    site-scoped kinds with ErrNetworkDisabled. Returning an empty result would
    read as "this console has no devices" rather than "wrong question".

warnProtectOnly logs an error, without failing the controller, for the two
configurations that can never collect anything: disable_network with neither
Protect save flag, and save_protect_devices with no key to authenticate with.
Silently collecting nothing is the failure mode hardest to spot in a log.

pkg/inputunifi had no tests before this. input_test.go follows inputunas'
input_test.go: an httptest fake UNVR serving the console's SPA HTML for
everything but the Protect paths and the login, covering initialisation,
metrics, events, the filtered scrape, RawMetrics, both warnings, config binding
across toml/json/yaml/env, and that the shipped examples leave Network enabled.
TestProtectOnlyControllerFailsWithoutFlag pins the original bug against that
same console, so the flag is demonstrably what makes the difference.

Requires github.com/unpoller/unifi/v6 with NewProtectClient (unpoller/unifi#240).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GVreutpEATmBjm6PBw9RjQ
2026-08-31 13:08:27 +00:00
..
2022-12-02 20:46:16 -05:00

inputunifi

UnPoller Input Plugin

Polls UniFi controllers and hands their metrics and events to every configured output. All configuration lives under [unifi] — see the commented [[unifi.controller]] block in examples/up.conf.example for every available option.

Protect-only consoles (UNVR)

A UNVR or UNVR Pro runs UniFi Protect with no Network application installed. UnPoller's normal startup probes the Network API to read the controller version, which on these appliances returns the UniFi OS SPA HTML rather than JSON:

[ERROR] Controller 3 of 3 Auth or Connection Error, retrying: unifi controller:
        unable to get server version: invalid character '<' looking for beginning of value

Set disable_network = true on that controller. UnPoller then skips the Network API entirely and collects only UniFi Protect:

[[unifi.controller]]
  url                  = "https://unvr.example.com"
  # Optional: a local read-only account. Only needed for save_protect_logs, which uses the
  # legacy Protect endpoints and authenticates with a session cookie.
  user                 = "unpoller"
  pass                 = "unpoller"
  # Required. Mint this in Protect under Settings -> Control Plane -> Integrations.
  protect_api_key      = "unifiprotectapikey"

  disable_network      = true
  save_protect_devices = true
  save_protect_logs    = false
  verify_ssl           = false

As an environment variable this is UP_UNIFI_CONTROLLER_0_DISABLE_NETWORK=true.

What is and isn't collected

With disable_network = true
Protect devices — cameras, sensors, lights, bridges, link stations, NVR ✅ save_protect_devices
Protect event logs ✅ save_protect_logs
Sites, clients, devices, DPI, traffic, rogue APs, speed tests ❌ never polled
Events, syslog, alarms, anomalies, IDs ❌ never polled

The Network-only save_* options are ignored rather than honoured, so leaving them at their defaults is fine. UnPoller logs an error at startup if disable_network is set with neither save_protect_devices nor save_protect_logs — that combination collects nothing at all.

A console that runs both applications (a UDM, UCG, or a UniFi OS Server with Protect installed) should leave disable_network at its default of false and simply set save_protect_devices = true. This flag is only for consoles with no Network application.

Mixing is fine: a Protect-only console is configured as one more [[unifi.controller]] alongside your normal ones.

See unpoller/unpoller#1066.