homelab/ansible/archive/playbooks/network/omada_find_hue_hub.yml

209 lines
8.4 KiB
YAML

---
# Phase 1: Query Omada client table and find the Philips Hue hub by MAC OUI.
# Phase 2: Probe the discovered IP via the Hue Bridge local API.
#
# Philips Hue / Signify known MAC OUI prefixes:
# 00:17:88 — classic Hue Bridge and bulbs
# EC:B5:FA — newer Hue Bridge v2 and Hue products
#
# Usage:
# ansible-playbook playbooks/network/omada_find_hue_hub.yml --ask-vault-pass
- name: Find and probe Philips Hue hub via Omada client table
hosts: localhost
connection: local
gather_facts: false
vars_files:
- "../../group_vars/all.yml"
- "../../group_vars/vault/all.yml"
vars:
omada_validate_certs: false
omada_page_size: 200
# Signify / Philips Hue MAC OUI prefixes (lowercase, colon-separated)
hue_mac_ouis:
- "00:17:88"
- "ec:b5:fa"
hue_probe_validate_certs: false
tasks:
# -----------------------------------------------------------------------
# PHASE 1 — Omada token
# -----------------------------------------------------------------------
- name: Request Omada access token (client credentials)
ansible.builtin.uri:
url: "{{ omada_base_url }}/openapi/authorize/token?grant_type=client_credentials"
method: POST
validate_certs: "{{ omada_validate_certs }}"
headers:
Content-Type: application/json
body_format: json
body:
omadacId: "{{ omada_id }}"
client_id: "{{ omada_client_id }}"
client_secret: "{{ omada_client_secret }}"
return_content: true
status_code: 200
register: omada_token_response
no_log: true
failed_when:
- omada_token_response.json is not defined
- omada_token_response.json.errorCode | default(-1) != 0
- name: Save access token
ansible.builtin.set_fact:
omada_access_token: "{{ omada_token_response.json.result.accessToken }}"
no_log: true
# -----------------------------------------------------------------------
# PHASE 1 — Get site list
# -----------------------------------------------------------------------
- name: Query Omada sites
ansible.builtin.uri:
url: "{{ omada_base_url }}/openapi/v1/{{ omada_id }}/sites?page=1&pageSize={{ omada_page_size }}"
method: GET
validate_certs: "{{ omada_validate_certs }}"
headers:
Content-Type: application/json
Authorization: "AccessToken={{ omada_access_token }}"
return_content: true
status_code: 200
register: omada_sites_response
no_log: true
failed_when:
- omada_sites_response.json is not defined
- omada_sites_response.json.errorCode | default(-1) != 0
- name: Save site list
ansible.builtin.set_fact:
omada_sites: "{{ omada_sites_response.json.result.data | default([]) }}"
# -----------------------------------------------------------------------
# PHASE 1 — Query clients per site
# -----------------------------------------------------------------------
- name: Query all clients per site
ansible.builtin.uri:
url: "{{ omada_base_url }}/openapi/v1/{{ omada_id }}/sites/{{ item.siteId }}/clients?page=1&pageSize={{ omada_page_size }}"
method: GET
validate_certs: "{{ omada_validate_certs }}"
headers:
Content-Type: application/json
Authorization: "AccessToken={{ omada_access_token }}"
return_content: true
status_code: 200
loop: "{{ omada_sites }}"
loop_control:
label: "{{ item.name | default(item.siteId) }}"
register: omada_clients_by_site
no_log: true
failed_when: false
# -----------------------------------------------------------------------
# PHASE 1 — Filter for Hue hub by OUI
# -----------------------------------------------------------------------
- name: Collect all clients from all sites into flat list
ansible.builtin.set_fact:
all_clients: "{{ omada_clients_by_site.results
| selectattr('json', 'defined')
| selectattr('json.errorCode', 'equalto', 0)
| map(attribute='json.result.data')
| flatten }}"
- name: Filter clients by Hue MAC OUI prefixes
ansible.builtin.set_fact:
hue_candidates: "{{ all_clients | selectattr('mac', 'defined')
| selectattr('ip', 'defined')
| selectattr('mac', 'search', hue_mac_ouis | join('|'), ignorecase=True)
| list }}"
- name: Report Omada client search results
ansible.builtin.debug:
msg:
- "Total clients scanned: {{ all_clients | length }}"
- "Hue hub candidates found: {{ hue_candidates | length }}"
- "Candidates: {{ hue_candidates | map(attribute='mac') | list }}"
- name: Abort with clear message if no Hue hub found
ansible.builtin.fail:
msg: >
No Philips Hue hub found in Omada client table.
Verify the hub is powered on and connected to a monitored VLAN.
Expected MAC OUI prefixes: {{ hue_mac_ouis | join(', ') }}.
when: hue_candidates | length == 0
- name: Save first Hue hub candidate IP and MAC
ansible.builtin.set_fact:
hue_ip: "{{ hue_candidates[0].ip }}"
hue_mac: "{{ hue_candidates[0].mac }}"
hue_hostname: "{{ hue_candidates[0].hostname | default('unknown') }}"
- name: Report discovered Hue hub
ansible.builtin.debug:
msg:
- "Hue hub MAC : {{ hue_mac }}"
- "Hue hub IP : {{ hue_ip }}"
- "Hue hub hostname: {{ hue_hostname }}"
# -----------------------------------------------------------------------
# PHASE 2 — Probe Hue Bridge local API
# -----------------------------------------------------------------------
- name: Probe Hue Bridge local discovery endpoint
ansible.builtin.uri:
url: "http://{{ hue_ip }}/api/config"
method: GET
validate_certs: "{{ hue_probe_validate_certs }}"
return_content: true
timeout: 5
status_code: 200
register: hue_config_response
failed_when: false
- name: Probe Hue Bridge HTTPS clip v2 endpoint (newer firmware)
ansible.builtin.uri:
url: "https://{{ hue_ip }}/clip/v2/resource/bridge"
method: GET
validate_certs: "{{ hue_probe_validate_certs }}"
return_content: true
timeout: 5
status_code: [200, 401, 403]
register: hue_clipv2_response
failed_when: false
- name: Build Hue adoption readiness summary
ansible.builtin.set_fact:
hue_adoption_summary:
mac: "{{ hue_mac }}"
ip: "{{ hue_ip }}"
hostname: "{{ hue_hostname }}"
local_api_v1:
reachable: "{{ hue_config_response.status | default('n/a') == 200 }}"
http_status: "{{ hue_config_response.status | default('n/a') }}"
bridge_id: "{{ hue_config_response.json.bridgeid | default('n/a') }}"
model_id: "{{ hue_config_response.json.modelid | default('n/a') }}"
sw_version: "{{ hue_config_response.json.swversion | default('n/a') }}"
api_version: "{{ hue_config_response.json.apiversion | default('n/a') }}"
name: "{{ hue_config_response.json.name | default('n/a') }}"
local_api_v2_clip:
reachable: "{{ hue_clipv2_response.status | default('n/a') in [200, 401, 403] }}"
http_status: "{{ hue_clipv2_response.status | default('n/a') }}"
note: >-
{{
'CLIP v2 available (needs app_key header for full access)' if hue_clipv2_response.status | default(0) in [401, 403]
else 'CLIP v2 accessible' if hue_clipv2_response.status | default(0) == 200
else 'CLIP v2 not reachable'
}}
ansible_adoption:
method: "ansible.builtin.uri (REST API — no SSH required)"
auth_required: "Hue app_key token (generate by pressing bridge button + POST /api)"
next_step: >-
{{
'Bridge is reachable. Press the physical button on the bridge, then run omada_adopt_hue_hub.yml to generate an app_key.'
if (hue_config_response.status | default(0) == 200 or hue_clipv2_response.status | default(0) in [200, 401, 403])
else 'Bridge API not responding. Check firewall rules for VLAN ' ~ hue_hostname ~ ' → Ansible control node.'
}}
- name: Print Hue adoption readiness summary
ansible.builtin.debug:
var: hue_adoption_summary