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
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.