{"id":48024786,"url":"https://github.com/aioue/ansible-unifi-inventory","last_synced_at":"2026-07-22T20:00:43.253Z","repository":{"id":346194211,"uuid":"1073231505","full_name":"aioue/ansible-unifi-inventory","owner":"aioue","description":"Dynamic Ansible inventory plugin for UniFi OS controllers","archived":false,"fork":false,"pushed_at":"2026-07-22T18:44:03.000Z","size":109,"stargazers_count":11,"open_issues_count":2,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-22T19:25:13.681Z","etag":null,"topics":["ansible","ansible-galaxy-collections","ansible-inventory","unifi"],"latest_commit_sha":null,"homepage":"https://aioue.net/2026/03/22/ansible-unifi-inventory-plugin/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aioue.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-09T19:58:57.000Z","updated_at":"2026-07-22T18:44:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/aioue/ansible-unifi-inventory","commit_stats":null,"previous_names":["aioue/ansible-unifi-inventory"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/aioue/ansible-unifi-inventory","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aioue%2Fansible-unifi-inventory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aioue%2Fansible-unifi-inventory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aioue%2Fansible-unifi-inventory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aioue%2Fansible-unifi-inventory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aioue","download_url":"https://codeload.github.com/aioue/ansible-unifi-inventory/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aioue%2Fansible-unifi-inventory/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35775334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-22T02:00:06.236Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansible","ansible-galaxy-collections","ansible-inventory","unifi"],"created_at":"2026-04-04T13:49:44.711Z","updated_at":"2026-07-22T20:00:43.160Z","avatar_url":"https://github.com/aioue.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# aioue.network\n\n[![CI](https://github.com/aioue/ansible-unifi-inventory/actions/workflows/ci.yml/badge.svg)](https://github.com/aioue/ansible-unifi-inventory/actions/workflows/ci.yml)\n[![Galaxy](https://img.shields.io/ansible/collection/v/aioue/network)](https://galaxy.ansible.com/ui/repo/published/aioue/network/)\n[![Release](https://img.shields.io/github/v/release/aioue/ansible-unifi-inventory)](https://github.com/aioue/ansible-unifi-inventory/releases)\n[![License](https://img.shields.io/github/license/aioue/ansible-unifi-inventory)](LICENSE)\n\nDynamic inventory plugin for Ansible that discovers hosts from a UniFi OS controller (UDM, UCG, etc.). Built on top of [aiounifi](https://github.com/Kane610/aiounifi) (v91+ required; tested with [v92](https://github.com/Kane610/aiounifi/releases/tag/v92)).\n\n```shell\n$ ansible-inventory -i inventory/unifi.yaml all --graph\n@all:\n  |--@ungrouped:\n  |--@unifi_clients:\n  |  |--Kitchen_Echo\n  |  |--nas-server\n  |  |--Study_Proxmox\n  |  |--phone\n  |  |--homeassistant\n  |--@unifi_wireless_clients:\n  |  |--Kitchen_Echo\n  |  |--phone\n  |--@unifi_wired_clients:\n  |  |--nas-server\n  |  |--Study_Proxmox\n  |  |--homeassistant\n  |--@network_default:\n  |  |--nas-server\n  |  |--Study_Proxmox\n  |  |--homeassistant\n  |  |--phone\n  |--@network_iot:\n  |  |--Kitchen_Echo\n  |--@vlan_30:\n  |  |--Kitchen_Echo\n  |--@vlan_iot:\n  |  |--Kitchen_Echo\n  |--@ssid_home_iot:\n  |  |--Kitchen_Echo\n  |--@ssid_home_wifi:\n  |  |--phone\n  |--@unifi_devices:\n  |  |--U6_Pro\n  |  |--USW_Flex\n  |  |--USW_Ultra\n  |  |--Dream_Machine\n  |--@unifi_uap:\n  |  |--U6_Pro\n  |--@unifi_usw:\n  |  |--USW_Flex\n  |  |--USW_Ultra\n  |--@device_state_connected:\n  |  |--U6_Pro\n  |  |--USW_Flex\n  |  |--USW_Ultra\n  |  |--Dream_Machine\n  |--@unifi_poe_powered:\n  |  |--USW_Flex\n  |--@unifi_udm:\n  |  |--Dream_Machine\n```\n\nExample host variables (`include_devices: true`; all fields from a live run with aiounifi v92, identifying values sanitized):\n\n```shell\n$ ansible-inventory -i inventory/unifi.yaml --host Kitchen_Echo\n{\n  \"ansible_host\": \"192.168.30.13\",\n  \"association_time\": 1783656843,\n  \"first_seen\": 1735624765,\n  \"fixed_ip\": \"192.168.30.13\",\n  \"ip\": \"192.168.30.13\",\n  \"ipv4\": \"192.168.30.13\",\n  \"ipv6\": \"2001:db8:1::772\",\n  \"ipv6_addresses\": [\n    \"2001:db8:1::772\",\n    \"fe80::4a78:5eff:fefa:7ce1\"\n  ],\n  \"is_wired\": false,\n  \"last_seen_iso\": \"2026-07-22T18:33:32Z\",\n  \"last_seen_unix\": 1784745212,\n  \"latest_association_time\": 1784294898,\n  \"mac\": \"48:78:5e:fa:7c:e1\",\n  \"network\": \"IoT\",\n  \"network_id\": \"670ef99bba911339bf2b894c\",\n  \"oui\": \"Amazon Technologies Inc.\",\n  \"powersave_enabled\": false,\n  \"site\": \"default\",\n  \"ssid\": \"home.iot\",\n  \"unifi_name\": \"Kitchen Echo\",\n  \"vlan\": 30,\n  \"vlan_name\": \"IoT\"\n}\n\n$ ansible-inventory -i inventory/unifi.yaml --host nas-server\n{\n  \"ansible_host\": \"192.168.1.148\",\n  \"association_time\": 1781365738,\n  \"first_seen\": 1773981600,\n  \"ip\": \"192.168.1.148\",\n  \"ipv4\": \"192.168.1.148\",\n  \"ipv6\": \"fe80::be24:11ff:feaf:77dd\",\n  \"is_wired\": true,\n  \"last_seen_iso\": \"2026-07-22T18:33:42Z\",\n  \"last_seen_unix\": 1784745222,\n  \"latest_association_time\": 1781925104,\n  \"mac\": \"bc:24:11:af:77:dd\",\n  \"network\": \"Default\",\n  \"network_id\": \"670ed85d2ce59e0ea329eff1\",\n  \"oui\": \"Example Vendor Inc.\",\n  \"site\": \"default\",\n  \"switch_depth\": 1,\n  \"unifi_hostname\": \"nas-server\",\n  \"unifi_name\": \"nas-server\",\n  \"wired_rate_mbps\": 1000\n}\n\n$ ansible-inventory -i inventory/unifi.yaml --host U6_Pro\n{\n  \"ansible_host\": \"192.168.1.252\",\n  \"client_count\": 7,\n  \"cpu_percent\": \"7.9\",\n  \"device_id\": \"671113bfba911339bf2be6c8\",\n  \"disabled\": false,\n  \"firmware_version\": \"6.8.2.15592\",\n  \"has_fan\": false,\n  \"has_temperature\": false,\n  \"ip\": \"192.168.1.252\",\n  \"last_seen\": 1784745212,\n  \"led_override\": \"off\",\n  \"led_override_color\": \"#0000ff\",\n  \"mac\": \"ac:8b:a9:43:b5:cd\",\n  \"mem_percent\": \"65.7\",\n  \"model\": \"UAP6MP\",\n  \"overheating\": false,\n  \"site\": \"default\",\n  \"state\": \"CONNECTED\",\n  \"supports_led_ring\": false,\n  \"system_uptime\": \"4768028\",\n  \"type\": \"uap\",\n  \"unifi_name\": \"U6 Pro\",\n  \"upgradable\": false,\n  \"uplink\": {\n    \"full_duplex\": true,\n    \"max_speed\": 1000,\n    \"name\": \"eth0\",\n    \"port_idx\": 1,\n    \"speed\": 1000,\n    \"type\": \"wire\",\n    \"up\": true,\n    \"uplink_device_name\": \"USW Ultra\",\n    \"uplink_mac\": \"28:70:4e:6d:f9:32\",\n    \"uplink_remote_port\": 1,\n    \"uplink_source\": \"lldp_uplink\"\n  },\n  \"uptime\": 4768028\n}\n\n$ ansible-inventory -i inventory/unifi.yaml --host USW_Flex\n{\n  \"ansible_host\": \"192.168.1.231\",\n  \"client_count\": 5,\n  \"cpu_percent\": \"11.0\",\n  \"device_id\": \"67d1d99d2b37f907a11a58ed\",\n  \"disabled\": false,\n  \"firmware_version\": \"2.1.8.971\",\n  \"has_fan\": false,\n  \"has_temperature\": false,\n  \"ip\": \"192.168.1.231\",\n  \"last_seen\": 1784745222,\n  \"led_override\": \"on\",\n  \"led_override_color\": \"#0000ff\",\n  \"mac\": \"94:2a:6f:fe:0e:e5\",\n  \"mem_percent\": \"82.8\",\n  \"model\": \"USWED37\",\n  \"overheating\": false,\n  \"poe_ports\": [\n    {\n      \"is_uplink\": false,\n      \"name\": \"Port 1\",\n      \"poe_enable\": false,\n      \"poe_good\": false,\n      \"poe_mode\": \"auto\",\n      \"poe_power\": \"0.00\",\n      \"poe_voltage\": \"0.00\",\n      \"port_idx\": 1,\n      \"up\": false\n    },\n    {\n      \"is_uplink\": false,\n      \"name\": \"Port 2\",\n      \"poe_enable\": false,\n      \"poe_good\": false,\n      \"poe_mode\": \"auto\",\n      \"poe_power\": \"0.00\",\n      \"poe_voltage\": \"0.00\",\n      \"port_idx\": 2,\n      \"up\": false\n    },\n    {\n      \"is_uplink\": false,\n      \"name\": \"Port 3\",\n      \"poe_enable\": false,\n      \"poe_good\": false,\n      \"poe_mode\": \"auto\",\n      \"poe_power\": \"0.00\",\n      \"poe_voltage\": \"0.00\",\n      \"port_idx\": 3,\n      \"up\": false\n    },\n    {\n      \"is_uplink\": false,\n      \"name\": \"Port 4\",\n      \"poe_enable\": false,\n      \"poe_good\": false,\n      \"poe_mode\": \"auto\",\n      \"poe_power\": \"0.00\",\n      \"poe_voltage\": \"0.00\",\n      \"port_idx\": 4,\n      \"up\": true\n    },\n    {\n      \"is_uplink\": false,\n      \"name\": \"Port 5\",\n      \"poe_enable\": false,\n      \"poe_good\": false,\n      \"poe_mode\": \"auto\",\n      \"poe_power\": \"0.00\",\n      \"poe_voltage\": \"0.00\",\n      \"port_idx\": 5,\n      \"up\": true\n    },\n    {\n      \"is_uplink\": false,\n      \"name\": \"Port 6\",\n      \"poe_enable\": true,\n      \"poe_good\": true,\n      \"poe_mode\": \"auto\",\n      \"poe_power\": \"5.91\",\n      \"poe_voltage\": \"47.24\",\n      \"port_idx\": 6,\n      \"up\": true\n    },\n    {\n      \"is_uplink\": false,\n      \"name\": \"Port 7\",\n      \"poe_enable\": true,\n      \"poe_good\": true,\n      \"poe_mode\": \"auto\",\n      \"poe_power\": \"13.60\",\n      \"poe_voltage\": \"47.05\",\n      \"port_idx\": 7,\n      \"up\": true\n    },\n    {\n      \"is_uplink\": false,\n      \"name\": \"Port 8\",\n      \"poe_enable\": false,\n      \"poe_good\": false,\n      \"poe_mode\": \"auto\",\n      \"poe_power\": \"0.00\",\n      \"poe_voltage\": \"0.00\",\n      \"port_idx\": 8,\n      \"up\": false\n    }\n  ],\n  \"site\": \"default\",\n  \"state\": \"CONNECTED\",\n  \"supports_led_ring\": false,\n  \"type\": \"usw\",\n  \"unifi_name\": \"USW Flex 2.5G 8 PoE\",\n  \"upgradable\": false,\n  \"uplink\": {\n    \"full_duplex\": true,\n    \"max_speed\": 10000,\n    \"media\": \"10GE\",\n    \"name\": \"eth0\",\n    \"port_idx\": 9,\n    \"speed\": 2500,\n    \"type\": \"wire\",\n    \"up\": true,\n    \"uplink_device_name\": \"Dream Machine\",\n    \"uplink_mac\": \"28:70:4e:6e:44:a7\",\n    \"uplink_remote_port\": 4,\n    \"uplink_source\": \"lldp_uplink\"\n  },\n  \"uptime\": 4768031\n}\n```\n\n## What This Is\n\n- **Dynamic inventory plugin** for Ansible that fetches UniFi network clients as inventory hosts.\n- **Supports UniFi OS controllers** (modern UniFi Dream Machine, Cloud Gateway, etc.).\n- **Discovers clients** connected to your network (wired and wireless).\n- **Optionally includes UniFi devices** (access points, switches, gateways).\n\n## What This Is Not\n\n- Not a UniFi controller configuration tool.\n- Not compatible with legacy UniFi controllers (pre-UniFi OS) without modification.\n\n## Prerequisites\n\n- **Python 3.12+** (newer `aiounifi` releases may require 3.13+; check `pip install` output)\n- **Ansible 2.15+**\n- **UniFi OS controller** accessible via network (UDM, UCG, etc.).\n- **API credentials**: API token (preferred), local admin without 2FA, or username/password with `totp_secret` for 2FA accounts (aiounifi v91+)\n- **Python dependencies**: Install in the same Python environment as Ansible:\n  ```bash\n  pip install -r requirements.txt\n  ```\n\n## Installation\n\nInstall the `aioue.network` collection from this GitHub repository:\n\n```shell\nansible-galaxy collection install git+https://github.com/aioue/ansible-unifi-inventory.git\n```\n\nYou can also include it in a `requirements.yml` file:\n\n```yaml\n---\ncollections:\n  - name: aioue.network\n    source: https://github.com/aioue/ansible-unifi-inventory.git\n    type: git\n    # If you need a specific version, you can specify a branch or tag:\n    # version: v1.1.0\n```\n\nThen install with `ansible-galaxy collection install -r requirements.yml`.\n\n## Configuration\n\nThis is an Ansible inventory plugin. Configuration is done via a YAML inventory file that uses the plugin.\n\n### Inventory File Naming\n\nName inventory files `*.unifi.yml` or `*.unifi.yaml` so Ansible auto-detects the plugin without listing it in `enable_plugins`. Examples: `prod.unifi.yml`, `inventory/unifi.yaml`.\n\nIf you use a different filename, set `plugin: aioue.network.unifi` explicitly in the file.\n\n### Create an Inventory File\n\nCreate a new inventory file (e.g., `prod.unifi.yml`) with your settings.\n\n**Important:** Use the Fully Qualified Collection Name (FQCN) `aioue.network.unifi` for the `plugin` key.\n\n```yaml\n# Example: prod.unifi.yml\n\nplugin: aioue.network.unifi\n\n# UniFi controller URL (required)\nurl: \"https://192.168.1.1\"\n\n# --- Authentication (pick one method; see \"Authentication\" below) ---\n# If token is non-empty, username/password/totp_secret are ignored.\n\n# Method A: API token (preferred for automation)\ntoken: \"your-api-token-here\"\n\n# Method B: Local admin password (no 2FA)\n# username: \"ansible-admin\"\n# password: \"your-password\"\n\n# Method C: Password + TOTP (2FA or ui.com SSO; aiounifi v91+, pyotp)\n# username: \"your-account\"\n# password: \"your-password\"\n# totp_secret: \"BASE32-TOTP-SEED\"  # setup seed, not the 6-digit code\n\n# Templated credentials (e.g. Ansible Vault lookups) are also supported:\n# token: \"{{ lookup('ansible.builtin.unvault', 'secrets.yml') | from_yaml | json_query('unifi_token') }}\"\n# username: \"{{ lookup('ansible.builtin.unvault', 'secrets.yml') | from_yaml | json_query('unifi_username') }}\"\n# password: \"{{ lookup('ansible.builtin.unvault', 'secrets.yml') | from_yaml | json_query('unifi_password') }}\"\n# totp_secret: \"{{ lookup('ansible.builtin.unvault', 'secrets.yml') | from_yaml | json_query('unifi_totp_secret') }}\"\n\nsite: \"default\"\nverify_ssl: false\ninclude_devices: false\nlast_seen_minutes: 30\n\n# Optional: use MAC-based hostnames when device names are missing or unstable\n# hostname: mac\n```\n\n### Hostname Option\n\nThe `hostname` option controls which UniFi field becomes the Ansible inventory hostname:\n\n| Value | Source |\n|-------|--------|\n| `name` (default) | UniFi friendly name with sanitization; original stored in `unifi_name` |\n| `mac` | MAC address with colons replaced by hyphens (e.g. `aa-bb-cc-dd-ee-ff`) |\n\nWhen using `name`, hosts without a friendly name fall back to OUI plus MAC suffix, or the raw MAC.\nWhen using `mac`, the friendly name (if any) is still available in the `unifi_name` host variable.\n\n### Constructable Inventory (keyed_groups, compose, filters)\n\nThe plugin supports standard Constructable inventory options for dynamic grouping and host variable composition.\n\n**keyed_groups** - create groups from host variables:\n\n```yaml\nplugin: aioue.network.unifi\nurl: \"https://192.168.1.1\"\ntoken: \"your-token\"\n\nkeyed_groups:\n  - key: ssid\n    prefix: ssid\n    separator: \"_\"\n  - key: vlan_name\n    prefix: vlan\n    separator: \"_\"\n  - key: network\n    prefix: network\n    separator: \"_\"\n```\n\n**compose** - set or override host variables:\n\n```yaml\nplugin: aioue.network.unifi\nurl: \"https://192.168.1.1\"\ntoken: \"your-token\"\n\ncompose:\n  ansible_host: ip | default(ipv6)\n  device_label: name | default(mac)\n```\n\n**filters** - include or exclude hosts (requires `community.library_inventory_filtering_v1`):\n\n```yaml\nplugin: aioue.network.unifi\nurl: \"https://192.168.1.1\"\ntoken: \"your-token\"\n\nfilters:\n  - include: is_wired\n  - exclude: ssid == \"Guest\"\n```\n\n### Inventory Caching\n\nAs of 1.1.0, use Ansible's built-in inventory caching instead of plugin-specific `cache_ttl` / `cache_path` options (removed in 1.1.0).\n\nConfigure caching in `ansible.cfg`:\n\n```ini\n[inventory]\ncache = true\ncache_plugin = ansible.builtin.jsonfile\ncache_timeout = 30\ncache_connection = /tmp/ansible_inventory_cache\n```\n\nOr per inventory source in your inventory file:\n\n```yaml\nplugin: aioue.network.unifi\nurl: \"https://192.168.1.1\"\ntoken: \"your-token\"\ncache: true\ncache_plugin: ansible.builtin.jsonfile\ncache_timeout: 30\n```\n\nSee [Ansible inventory cache documentation](https://docs.ansible.com/ansible/latest/inventory_guide/index.html#inventory-plugins-and-caching) for available cache plugins and options.\n\n### Environment Variables\n\nYou can also provide configuration via environment variables, which override settings in the YAML file. Handy for CI/CD when you do not want credentials in the inventory file.\n\n```bash\nexport UNIFI_URL=https://192.168.1.1\nexport UNIFI_SITE=default\nexport UNIFI_VERIFY_SSL=false\n\n# Method A: token (preferred)\nexport UNIFI_TOKEN=your-api-token-here\n\n# Method B or C: password login (add UNIFI_TOTP_SECRET for 2FA / SSO)\n# export UNIFI_USERNAME=ansible-admin\n# export UNIFI_PASSWORD=your-password\n# export UNIFI_TOTP_SECRET=BASE32-TOTP-SEED\n```\n\n## Usage\n\nOnce the collection is installed and your inventory file is created, use it like any other Ansible inventory source.\n\n### Use With ansible-inventory\n\n```bash\n# View full inventory as JSON\nansible-inventory -i prod.unifi.yml --list\n\n# Show hosts in a specific group\nansible-inventory -i prod.unifi.yml --graph unifi_wired_clients\n\n# See graph of all groups\nansible-inventory -i prod.unifi.yml --graph\n```\n\n### Use With Ansible Ad-Hoc Commands\n\n```bash\n# Ping all discovered hosts\nansible -i prod.unifi.yml all -m ping\n\n# Target only wireless clients\nansible -i prod.unifi.yml unifi_wireless_clients -m shell -a \"uptime\"\n\n# Target a specific SSID group\nansible -i prod.unifi.yml ssid_guest_wifi -m shell -a \"uptime\"\n```\n\n### Use With Ansible Playbooks\n\n```bash\nansible-playbook -i prod.unifi.yml site.yml\nansible-playbook -i prod.unifi.yml site.yml --limit unifi_clients\n```\n\n### Using Multiple Inventory Sources\n\n```bash\nansible-playbook -i static_hosts.yml -i prod.unifi.yml site.yml\n```\n\n## Authentication\n\nPick **one** method below. If `token` is non-empty, `username`, `password`, and `totp_secret` are ignored.\n\n| Method | When to use | Inventory keys | Notes |\n|--------|-------------|----------------|-------|\n| API token | Automation (recommended) | `token` | No login call; avoids controller rate limits |\n| Local password | Simple homelab setup | `username`, `password` | Local admin account with 2FA disabled |\n| Password + TOTP | ui.com SSO or 2FA-enabled account | `username`, `password`, `totp_secret` | aiounifi v91+ and `pyotp` required |\n\nConnection options (`url`, `username`, `password`, `token`, `totp_secret`) support Jinja2 templating, so you can reference Ansible Vault lookups or variables directly in the inventory file.\n\n### API Token (Preferred)\n\nThe `token` value is the Network API token from your controller. The plugin passes it as the `unifises` session cookie (no username/password login).\n\n1. Log in to your UniFi controller.\n2. Go to `Settings \u003e Network \u003e Control Plane \u003e Integrations \u003e Network API` (or similar path).\n3. Create a new token.\n4. Use this token for the `token` config option or the `UNIFI_TOKEN` environment variable.\n\nWorks with local and ui.com admin accounts. Tokens can be revoked without changing account passwords.\n\n### Local Admin Password\n\nFor password login without 2FA, create a **local admin account** (not a ui.com SSO account):\n\n1. Go to `UniFi OS Settings \u003e Admins \u0026 Users`.\n2. Create a new user with the \"Admin\" role.\n3. Select **Restrict to Local Access Only**.\n4. Do **NOT** enable 2FA for this account.\n5. Use these credentials for `username`/`password` or `UNIFI_USERNAME`/`UNIFI_PASSWORD`.\n\nPassword login calls the controller login endpoint on every uncached inventory refresh. Enable inventory caching (`cache: true`, `cache_timeout`) or switch to a token if you hit rate limits.\n\n### Password with TOTP (2FA / SSO)\n\nFor accounts with 2FA enabled (local or ui.com SSO), set `totp_secret` to the **TOTP shared secret** from authenticator setup - the base32 seed string, not the rotating 6-digit code. Requires aiounifi v91+ (`Configuration.totp_secret`) and `pyotp`.\n\n```yaml\nusername: \"{{ vault_unifi_username }}\"\npassword: \"{{ vault_unifi_password }}\"\ntotp_secret: \"{{ vault_unifi_totp_secret }}\"\n```\n\nui.com SSO accounts cannot use password-only login; use an API token or password with `totp_secret`.\n\n## Inventory Schema\n\n### Groups\n\nThe plugin creates these dynamic groups:\n\n**For clients:**\n- `unifi_clients` - all discovered clients\n- `unifi_wireless_clients` - wireless clients only\n- `unifi_wired_clients` - wired clients only\n- `ssid_\u003cname\u003e` - clients on specific SSID (e.g., `ssid_guest_wifi`)\n- `vlan_\u003cid\u003e` - clients on specific VLAN ID (e.g., `vlan_10`)\n- `vlan_\u003cname\u003e` - clients on specific VLAN name (e.g., `vlan_guest_network`)\n- `network_\u003cname\u003e` - clients on specific network (e.g., `network_iot`)\n\n**For devices (when `include_devices: true`):**\n- `unifi_devices` - all UniFi devices\n- `unifi_uap` - UniFi access points\n- `unifi_usw` - UniFi switches\n- `unifi_ugw` / `unifi_uxg` / `unifi_ucg` / `unifi_udm` - UniFi gateways\n- `device_state_\u003cstate\u003e` - devices by state (e.g. `device_state_connected`)\n- `unifi_upgradable` - devices with firmware updates available\n- `unifi_overheating` - devices reporting overheating\n- `unifi_poe_powered` - switches with at least one PoE port delivering power\n\nAdditional groups can be created with `keyed_groups` (see above).\n\n### Host Variables (Clients)\n\nEach client host includes:\n- `ansible_host` - IP address (IPv4 preferred, IPv6 fallback)\n- `mac` - MAC address\n- `ip` / `ipv4` - IPv4 address (if available)\n- `ipv6` - IPv6 address (if available)\n- `ipv6_addresses` - All IPv6 addresses (if multiple)\n- `is_wired` - boolean, true if wired connection\n- `site` - UniFi site name\n- `last_seen_unix` - Unix timestamp of last seen\n- `last_seen_iso` - ISO 8601 timestamp of last seen\n- `ssid` - SSID name (wireless only)\n- `ap_mac` - AP MAC address (wireless only)\n- `sw_mac` - Switch MAC address (wired only)\n- `port` - Switch port number (wired only)\n- `vlan` - VLAN ID (if available)\n- `vlan_name` - VLAN name (if available)\n- `network` - Network name (if available)\n- `network_id` - Network ID (if available)\n- `oui` - Device manufacturer OUI (if available)\n- `is_guest` - boolean, true for guest network clients\n- `blocked` - boolean, true when blocked in UniFi\n- `firmware_version` - Client firmware version (when reported by UniFi)\n- `fixed_ip` - DHCP reservation / static IP (when configured)\n- `unifi_hostname` - Client hostname from UniFi (distinct from inventory hostname)\n- `device_name` - UniFi device name field (when set)\n- `first_seen` / `association_time` / `latest_association_time` - Client lifecycle timestamps\n- `switch_depth` - Switch hops for wired clients\n- `wired_rate_mbps` - Negotiated link speed (wired clients)\n- `powersave_enabled` - Wireless power-save state\n\n### Host Variables (Devices)\n\nEach device host includes:\n- `ansible_host` - Management IP address\n- `mac` - MAC address\n- `ip` - IP address\n- `model` - Device model\n- `type` - Device type (uap, usw, ugw, udm, etc.)\n- `firmware_version` - Current firmware version\n- `site` - UniFi site name\n- `device_id` - UniFi device ID\n- `state` - Device state (e.g. `CONNECTED`)\n- `adopted` - boolean, adoption status\n- `upgradable` - boolean, firmware update available\n- `upgrade_to_firmware` - Target firmware when upgradable\n- `overheating` - boolean, thermal warning state\n- `disabled` - boolean, administratively disabled\n- `uptime` - Device uptime in seconds\n- `uplink_depth` - Hops to gateway\n- `client_count` - Connected client count (`user_num_sta`)\n- `uplink` - Compact uplink summary (type, speed, remote device; no rx/tx counters)\n- `cpu_percent` / `mem_percent` / `system_uptime` - From `system-stats`\n- `poe_ports` - List of PoE-capable switch ports with power state (switches only)\n- `outlets` - PDU/outlet relay state (gateways and outlet-capable devices)\n- `general_temperature` / `fan_level` / `has_fan` / `has_temperature` - Thermal state\n- `last_seen` - Device last-seen timestamp\n- `supports_led_ring` / `led_override` / `led_override_color` - LED state (read-only)\n\n## Configuration Options Reference\n\n| Option | Env Var | Config Key | Default |\n|--------|---------|------------|---------|\n| Controller URL | `UNIFI_URL` | `url` | (required) |\n| Username | `UNIFI_USERNAME` | `username` | \"\" |\n| Password | `UNIFI_PASSWORD` | `password` | \"\" |\n| API Token | `UNIFI_TOKEN` | `token` | \"\" |\n| TOTP Secret | `UNIFI_TOTP_SECRET` | `totp_secret` | \"\" |\n| Site Name | `UNIFI_SITE` | `site` | `default` |\n| Verify SSL | `UNIFI_VERIFY_SSL` | `verify_ssl` | `true` |\n| Include Devices | `UNIFI_INCLUDE_DEVICES` | `include_devices` | `false` |\n| Last Seen Minutes | `UNIFI_LAST_SEEN_MINUTES` | `last_seen_minutes` | `30` |\n| Hostname Source | `UNIFI_HOSTNAME` | `hostname` | `name` |\n\nInventory caching is configured via standard Ansible options (`cache`, `cache_plugin`, `cache_timeout`), not plugin-specific keys.\n\n## Security Best Practices\n\n### Don't Commit Secrets\n\n- **Never commit** inventory files with real credentials.\n- Use a local file and add it to `.gitignore`.\n- Use Ansible Vault to encrypt the inventory file.\n\n### Use Ansible Vault\n\n```bash\nansible-vault encrypt prod.unifi.yml\nansible-playbook -i prod.unifi.yml site.yml --ask-vault-pass\n```\n\n### Use Environment Variables\n\nFor CI/CD pipelines, use environment variables to inject secrets (see [Environment Variables](#environment-variables) above).\n\n```bash\nexport UNIFI_URL=https://192.168.1.1\nexport UNIFI_TOKEN=$VAULT_UNIFI_TOKEN\nansible-playbook -i prod.unifi.yml site.yml\n```\n\nPrefer **API tokens** over username/password for automation. Tokens skip the login endpoint and can be revoked without changing account credentials.\n\n## Troubleshooting\n\n### SSL Certificate Errors\n\n**Symptom:** `SSL: CERTIFICATE_VERIFY_FAILED` errors\n\n**Solution:** Self-signed certificates are common on UniFi controllers.\n- Set `verify_ssl: false` in your inventory config file (easiest, but less secure).\n- Add your controller's certificate to your system trust store.\n\n### Authentication Failures\n\n**Symptom:** \"Authentication failed\" or 403/401 errors\n\n**Causes:**\n- Incorrect username/password or token.\n- Token expired or revoked.\n- **Two-Factor Authentication (2FA)** on a password account without `totp_secret` configured.\n- Installed aiounifi is older than v91 (upgrade for `totp_secret` and `AuthenticationRateLimitError`).\n- Using a ui.com SSO account without token or `totp_secret`.\n\n**Solution:**\n- Verify credentials.\n- Prefer **token authentication** for automation (avoids login rate limits).\n- For 2FA or ui.com SSO accounts, set `totp_secret` (base32 seed, not the 6-digit code) or use an API token.\n- For password-only automation, use a local admin without 2FA.\n- Upgrade aiounifi to v91+ if `totp_secret` or rate-limit errors are missing.\n- Regenerate your API token if it was revoked.\n\n### No Hosts Returned\n\n**Symptom:** Empty inventory\n\n**Causes:**\n- `last_seen_minutes` threshold is too low.\n- No clients have been active recently.\n- Wrong `site` name specified.\n- `filters` excluding all hosts.\n\n**Solution:**\n- Increase `last_seen_minutes` to `1440` (24 hours).\n- Verify your `site` name in the UniFi controller (often `default`).\n- Enable devices: `include_devices: true`.\n- Review `filters` rules.\n\n### Stale Inventory Data\n\n**Symptom:** Inventory doesn't reflect recent changes (new clients, IP changes).\n\n**Solution:**\n- Clear the Ansible inventory cache directory (path set in `cache_connection`).\n- Reduce `cache_timeout` for more frequent updates.\n- Disable caching temporarily: `cache: false`.\n\n### Network Timeouts\n\n**Symptom:** Network request errors, \"Connection refused\".\n\n**Causes:**\n- Controller URL is incorrect or unreachable from where Ansible is running.\n- Firewall blocking HTTPS (port 443) access.\n\n**Solution:**\n- Verify controller URL.\n- Test connectivity: `curl -k https://192.168.1.1`\n- Check firewall rules.\n\n## Advanced Usage\n\n### Filter by Last Seen Time\n\n```yaml\nplugin: aioue.network.unifi\nurl: \"https://192.168.1.1\"\ntoken: \"your-token\"\nlast_seen_minutes: 5\n```\n\n### Include Infrastructure Devices\n\n```yaml\nplugin: aioue.network.unifi\nurl: \"https://192.168.1.1\"\ntoken: \"your-token\"\ninclude_devices: true\n```\n\n### Multiple Sites\n\nCreate separate inventory files per site:\n\n**`site_default.unifi.yml`:**\n```yaml\nplugin: aioue.network.unifi\nurl: \"https://192.168.1.1\"\ntoken: \"your-token\"\nsite: \"default\"\n```\n\n**`site_branch.unifi.yml`:**\n```yaml\nplugin: aioue.network.unifi\nurl: \"https://192.168.1.1\"\ntoken: \"your-token\"\nsite: \"branch-office\"\n```\n\n## Performance Notes\n\n- Enable Ansible inventory caching to reduce UniFi API calls on repeated runs.\n- The first uncached run is slower (typically 2-10 seconds) while data is fetched from the API.\n- Cached runs within the `cache_timeout` window are much faster.\n\n## Upgrading\n\n### From 1.0.0\n\n1. Upgrade: `ansible-galaxy collection install aioue.network --upgrade`\n2. Remove `cache_ttl` and `cache_path` from inventory files; configure Ansible inventory cache (see above)\n3. Optionally set `hostname: mac` for stable MAC-based host keys\n4. Optionally rename inventory files to `*.unifi.yml` for auto-detection\n\n### From pre-collection versions\n\nIf you copied `unifi.py` into a local plugins directory:\n\n1. Install the collection: `ansible-galaxy collection install aioue.network`\n2. Update inventory files: `plugin: unifi` → `plugin: aioue.network.unifi`\n3. Remove custom `inventory_plugins` / `enable_plugins` entries for the old plugin\n4. Remove the old plugin file from `~/.ansible/plugins/inventory/` or your custom path\n\n## Releasing a New Version\n\n1. Bump `version:` in `galaxy.yml`\n2. Update `CHANGELOG.md`\n3. Commit, tag, and push:\n\n```bash\ngit tag v1.x.x\ngit push origin v1.x.x\n```\n\nThe GitHub Actions workflow builds the collection, publishes to Ansible Galaxy, and creates a GitHub Release.\n\n## Contributing\n\nFor issues or enhancements, please ensure:\n- Python 3.12+ compatibility\n- Type hints for all functions\n- PEP 8 code style\n\n## License\n\nGNU General Public License v3.0 or later (GPL-3.0+)\n\nSee [LICENSE](LICENSE) file for full text.\n\nCopyright (c) 2025 Tom Paine (https://github.com/aioue)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faioue%2Fansible-unifi-inventory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faioue%2Fansible-unifi-inventory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faioue%2Fansible-unifi-inventory/lists"}