Refresh RackPeek and UniFi network inventory
This commit is contained in:
190
automation/bin/unifi_network.py
Normal file
190
automation/bin/unifi_network.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Minimal Doris helper for UniFi OS / Network inventory reads.
|
||||||
|
|
||||||
|
Reads credentials from automation/.env by default and performs a cookie-based
|
||||||
|
login against UniFi OS, then calls Network app endpoints through
|
||||||
|
`/proxy/network/api/s/<site>/...`.
|
||||||
|
|
||||||
|
Default action prints a compact JSON summary suitable for RackPeek refresh work.
|
||||||
|
No secrets are printed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import ssl
|
||||||
|
import sys
|
||||||
|
from http.cookiejar import CookieJar
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
from urllib import error, request
|
||||||
|
|
||||||
|
DEFAULT_TIMEOUT = 20
|
||||||
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
REPO_ROOT = SCRIPT_DIR.parent.parent
|
||||||
|
DEFAULT_ENV_PATH = REPO_ROOT / 'automation' / '.env'
|
||||||
|
|
||||||
|
|
||||||
|
def load_env_file(path: Path) -> None:
|
||||||
|
if not path.exists():
|
||||||
|
return
|
||||||
|
for raw_line in path.read_text(encoding='utf-8').splitlines():
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not line or line.startswith('#') or '=' not in line:
|
||||||
|
continue
|
||||||
|
key, value = line.split('=', 1)
|
||||||
|
os.environ.setdefault(key.strip(), value.strip().strip('"').strip("'"))
|
||||||
|
|
||||||
|
|
||||||
|
def env_bool(name: str, default: bool) -> bool:
|
||||||
|
value = os.environ.get(name)
|
||||||
|
if value is None:
|
||||||
|
return default
|
||||||
|
return value.strip().lower() in {'1', 'true', 'yes', 'on'}
|
||||||
|
|
||||||
|
|
||||||
|
class UniFiClient:
|
||||||
|
def __init__(self, base_url: str, username: str, password: str, *, verify_tls: bool = True, timeout: int = DEFAULT_TIMEOUT) -> None:
|
||||||
|
self.base_url = base_url.rstrip('/')
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.timeout = timeout
|
||||||
|
self.cookie_jar = CookieJar()
|
||||||
|
handlers: list[Any] = [request.HTTPCookieProcessor(self.cookie_jar)]
|
||||||
|
if not verify_tls:
|
||||||
|
handlers.append(request.HTTPSHandler(context=ssl._create_unverified_context()))
|
||||||
|
self.opener = request.build_opener(*handlers)
|
||||||
|
|
||||||
|
def _json_request(self, method: str, path: str, body: dict[str, Any] | None = None) -> tuple[int, Any]:
|
||||||
|
url = self.base_url + path
|
||||||
|
data = None
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'doris-unifi-helper/1.0',
|
||||||
|
}
|
||||||
|
if body is not None:
|
||||||
|
data = json.dumps(body).encode('utf-8')
|
||||||
|
req = request.Request(url, data=data, method=method.upper(), headers=headers)
|
||||||
|
try:
|
||||||
|
with self.opener.open(req, timeout=self.timeout) as resp:
|
||||||
|
raw = resp.read().decode('utf-8', 'replace')
|
||||||
|
ctype = resp.headers.get('Content-Type', '')
|
||||||
|
payload = json.loads(raw) if 'json' in ctype else raw
|
||||||
|
return resp.status, payload
|
||||||
|
except error.HTTPError as exc:
|
||||||
|
raw = exc.read().decode('utf-8', 'replace')
|
||||||
|
try:
|
||||||
|
payload = json.loads(raw)
|
||||||
|
except Exception:
|
||||||
|
payload = raw
|
||||||
|
return exc.code, payload
|
||||||
|
|
||||||
|
def login(self) -> None:
|
||||||
|
status, payload = self._json_request('POST', '/api/auth/login', {
|
||||||
|
'username': self.username,
|
||||||
|
'password': self.password,
|
||||||
|
'rememberMe': True,
|
||||||
|
})
|
||||||
|
if status >= 300:
|
||||||
|
raise SystemExit(f'UniFi login failed: HTTP {status} {payload!r}')
|
||||||
|
|
||||||
|
def get(self, path: str) -> Any:
|
||||||
|
status, payload = self._json_request('GET', path)
|
||||||
|
if status >= 300:
|
||||||
|
raise SystemExit(f'UniFi GET {path} failed: HTTP {status} {payload!r}')
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def site_path(self, site: str, suffix: str) -> str:
|
||||||
|
suffix = suffix.lstrip('/')
|
||||||
|
return f'/proxy/network/api/s/{site}/{suffix}'
|
||||||
|
|
||||||
|
|
||||||
|
def response_data(payload: Any) -> Any:
|
||||||
|
if isinstance(payload, dict) and 'data' in payload:
|
||||||
|
return payload['data']
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def device_summary(devices: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
out = []
|
||||||
|
for dev in devices:
|
||||||
|
out.append({
|
||||||
|
'name': dev.get('name') or dev.get('hostname') or dev.get('mac'),
|
||||||
|
'model': dev.get('model'),
|
||||||
|
'type': dev.get('type') or dev.get('device_type'),
|
||||||
|
'state': dev.get('state'),
|
||||||
|
'ip': dev.get('ip'),
|
||||||
|
'mac': dev.get('mac'),
|
||||||
|
'version': dev.get('version'),
|
||||||
|
'uplink': ((dev.get('uplink') or {}).get('name') if isinstance(dev.get('uplink'), dict) else None),
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def client_summary(clients: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
out = []
|
||||||
|
for client in clients:
|
||||||
|
out.append({
|
||||||
|
'name': client.get('name') or client.get('hostname') or client.get('mac'),
|
||||||
|
'ip': client.get('ip'),
|
||||||
|
'mac': client.get('mac'),
|
||||||
|
'network': client.get('network'),
|
||||||
|
'ap_mac': client.get('ap_mac'),
|
||||||
|
'oui': client.get('oui'),
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def build_summary(client: UniFiClient, site: str) -> dict[str, Any]:
|
||||||
|
devices = response_data(client.get(client.site_path(site, 'stat/device')))
|
||||||
|
health = response_data(client.get(client.site_path(site, 'stat/health')))
|
||||||
|
clients = response_data(client.get(client.site_path(site, 'stat/sta')))
|
||||||
|
return {
|
||||||
|
'site': site,
|
||||||
|
'device_count': len(devices),
|
||||||
|
'client_count': len(clients),
|
||||||
|
'devices': device_summary(devices),
|
||||||
|
'health': health,
|
||||||
|
'clients': client_summary(clients),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description='Read UniFi Network inventory for Doris.')
|
||||||
|
parser.add_argument('--env-file', default=str(DEFAULT_ENV_PATH), help='Path to .env containing UNIFI_* vars')
|
||||||
|
parser.add_argument('--site', help='UniFi site name; defaults to UNIFI_SITE or default')
|
||||||
|
parser.add_argument('--raw', action='store_true', help='Print raw device/client/health payloads instead of compact summary')
|
||||||
|
parser.add_argument('--timeout', type=int, default=DEFAULT_TIMEOUT)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
load_env_file(Path(args.env_file))
|
||||||
|
base_url = os.environ.get('UNIFI_BASE_URL', 'https://10.5.0.1')
|
||||||
|
username = os.environ.get('UNIFI_USERNAME')
|
||||||
|
password = os.environ.get('UNIFI_PASSWORD')
|
||||||
|
site = args.site or os.environ.get('UNIFI_SITE', 'default')
|
||||||
|
verify_tls = env_bool('UNIFI_VERIFY_TLS', False)
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
raise SystemExit('Missing UNIFI_USERNAME or UNIFI_PASSWORD in env file/environment.')
|
||||||
|
|
||||||
|
client = UniFiClient(base_url, username, password, verify_tls=verify_tls, timeout=args.timeout)
|
||||||
|
client.login()
|
||||||
|
|
||||||
|
if args.raw:
|
||||||
|
payload = {
|
||||||
|
'devices': response_data(client.get(client.site_path(site, 'stat/device'))),
|
||||||
|
'health': response_data(client.get(client.site_path(site, 'stat/health'))),
|
||||||
|
'clients': response_data(client.get(client.site_path(site, 'stat/sta'))),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
payload = build_summary(client, site)
|
||||||
|
|
||||||
|
print(json.dumps(payload, indent=2, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -9,11 +9,15 @@ This directory is the git-tracked RackPeek inventory for the homelab.
|
|||||||
Update this inventory from the canonical repo docs first:
|
Update this inventory from the canonical repo docs first:
|
||||||
- `docs/architecture/ARCHITECTURE_OVERVIEW.md`
|
- `docs/architecture/ARCHITECTURE_OVERVIEW.md`
|
||||||
- `docs/architecture/SERVICES_DIRECTORY.md`
|
- `docs/architecture/SERVICES_DIRECTORY.md`
|
||||||
|
- `docs/architecture/NETWORKING_MODEL.md`
|
||||||
- `docs/servers/PLAUSIBLEDENABILITY.md`
|
- `docs/servers/PLAUSIBLEDENABILITY.md`
|
||||||
- `docs/servers/SERENITY.md`
|
- `docs/servers/SERENITY.md`
|
||||||
- `docs/servers/NOMAD.md`
|
- `docs/servers/NOMAD.md`
|
||||||
- `docs/servers/ROCINANTE.md`
|
- `docs/servers/ROCINANTE.md`
|
||||||
|
|
||||||
|
For UniFi-managed network gear, refresh from the validated read-only helper after reconciling the docs:
|
||||||
|
- `automation/bin/unifi_network.py`
|
||||||
|
|
||||||
## Runtime wiring
|
## Runtime wiring
|
||||||
The `dev/docker-compose.yaml` RackPeek service mounts this repo directory directly at `/app/config` on PD:
|
The `dev/docker-compose.yaml` RackPeek service mounts this repo directory directly at `/app/config` on PD:
|
||||||
- host path: `/mnt/docker-ssd/docker/compose/dev/rackpeek`
|
- host path: `/mnt/docker-ssd/docker/compose/dev/rackpeek`
|
||||||
|
|||||||
@@ -126,6 +126,118 @@ resources:
|
|||||||
Current operational note from 2026-05-21:
|
Current operational note from 2026-05-21:
|
||||||
- Unreachable from N.O.M.A.D. on ports 11434 and 8787 during Doris validation.
|
- Unreachable from N.O.M.A.D. on ports 11434 and 8787 during Doris validation.
|
||||||
|
|
||||||
|
- kind: Server
|
||||||
|
name: unifi-udm-pro
|
||||||
|
ports:
|
||||||
|
- type: rj45
|
||||||
|
speed: 10
|
||||||
|
count: 8
|
||||||
|
- type: sfp+
|
||||||
|
speed: 10
|
||||||
|
count: 2
|
||||||
|
tags:
|
||||||
|
- prod
|
||||||
|
- network
|
||||||
|
- unifi
|
||||||
|
- gateway
|
||||||
|
labels:
|
||||||
|
ip: 10.5.0.1
|
||||||
|
management-ip: 10.5.0.1
|
||||||
|
wan-ip: 69.166.182.157
|
||||||
|
os: UniFi OS 5.1.11.33023
|
||||||
|
mac: d0:21:f9:75:8a:ff
|
||||||
|
serial: D021F9758AFF
|
||||||
|
notes: |-
|
||||||
|
UniFi gateway and controller.
|
||||||
|
UniFi device name: UDM Pro - Old.
|
||||||
|
WAN uplink currently active on eth9.
|
||||||
|
|
||||||
|
- kind: Server
|
||||||
|
name: unifi-usw-24-poe
|
||||||
|
ports:
|
||||||
|
- type: rj45
|
||||||
|
speed: 1
|
||||||
|
count: 24
|
||||||
|
tags:
|
||||||
|
- prod
|
||||||
|
- network
|
||||||
|
- unifi
|
||||||
|
- switch
|
||||||
|
- poe
|
||||||
|
labels:
|
||||||
|
ip: 10.5.0.10
|
||||||
|
management-ip: 10.5.0.10
|
||||||
|
os: UniFi Switch 7.4.1.16850
|
||||||
|
mac: ac:8b:a9:ad:ab:86
|
||||||
|
serial: AC8BA9ADAB86
|
||||||
|
notes: |-
|
||||||
|
UniFi device name: USW-24-PoE.
|
||||||
|
Active in UniFi; management uplink reported on eth0.
|
||||||
|
|
||||||
|
- kind: Server
|
||||||
|
name: unifi-usw-pro-hd-24
|
||||||
|
ports:
|
||||||
|
- type: rj45
|
||||||
|
speed: 1
|
||||||
|
count: 24
|
||||||
|
tags:
|
||||||
|
- prod
|
||||||
|
- network
|
||||||
|
- unifi
|
||||||
|
- switch
|
||||||
|
labels:
|
||||||
|
ip: 10.5.0.135
|
||||||
|
management-ip: 10.5.0.135
|
||||||
|
os: UniFi Switch 7.4.1.16850
|
||||||
|
mac: 8c:30:66:d0:9f:48
|
||||||
|
serial: 8C3066D09F48
|
||||||
|
notes: |-
|
||||||
|
UniFi device name: USW Pro HD 24.
|
||||||
|
Active in UniFi; management uplink reported on eth0.
|
||||||
|
|
||||||
|
- kind: Server
|
||||||
|
name: unifi-u7-pro
|
||||||
|
ports:
|
||||||
|
- type: rj45
|
||||||
|
speed: 1
|
||||||
|
count: 1
|
||||||
|
tags:
|
||||||
|
- prod
|
||||||
|
- network
|
||||||
|
- unifi
|
||||||
|
- ap
|
||||||
|
labels:
|
||||||
|
ip: 10.5.0.21
|
||||||
|
management-ip: 10.5.0.21
|
||||||
|
os: UniFi AP 8.5.21.18681
|
||||||
|
mac: 8c:ed:e1:8a:58:54
|
||||||
|
serial: 8CEDE18A5854
|
||||||
|
notes: |-
|
||||||
|
UniFi device name: U7 Pro.
|
||||||
|
Active in UniFi; management uplink reported on eth0.
|
||||||
|
|
||||||
|
- kind: Server
|
||||||
|
name: unifi-u6-lr
|
||||||
|
ports:
|
||||||
|
- type: rj45
|
||||||
|
speed: 1
|
||||||
|
count: 1
|
||||||
|
tags:
|
||||||
|
- prod
|
||||||
|
- network
|
||||||
|
- unifi
|
||||||
|
- ap
|
||||||
|
- disconnected
|
||||||
|
labels:
|
||||||
|
ip: 10.5.0.20
|
||||||
|
management-ip: 10.5.0.20
|
||||||
|
os: UniFi AP 6.7.41.15623
|
||||||
|
mac: d0:21:f9:69:a5:f1
|
||||||
|
serial: D021F969A5F1
|
||||||
|
notes: |-
|
||||||
|
UniFi device name: U6 LR.
|
||||||
|
Currently disconnected in UniFi as of 2026-05-21.
|
||||||
|
|
||||||
- kind: System
|
- kind: System
|
||||||
type: Baremetal
|
type: Baremetal
|
||||||
name: pd-truenas
|
name: pd-truenas
|
||||||
@@ -164,6 +276,46 @@ resources:
|
|||||||
runsOn:
|
runsOn:
|
||||||
- rocinante
|
- rocinante
|
||||||
|
|
||||||
|
- kind: System
|
||||||
|
type: Embedded
|
||||||
|
name: unifi-gateway
|
||||||
|
os: unifi-os-5.1.11.33023
|
||||||
|
ip: 10.5.0.1
|
||||||
|
runsOn:
|
||||||
|
- unifi-udm-pro
|
||||||
|
|
||||||
|
- kind: System
|
||||||
|
type: Embedded
|
||||||
|
name: unifi-usw-24-poe-firmware
|
||||||
|
os: unifi-switch-7.4.1.16850
|
||||||
|
ip: 10.5.0.10
|
||||||
|
runsOn:
|
||||||
|
- unifi-usw-24-poe
|
||||||
|
|
||||||
|
- kind: System
|
||||||
|
type: Embedded
|
||||||
|
name: unifi-usw-pro-hd-24-firmware
|
||||||
|
os: unifi-switch-7.4.1.16850
|
||||||
|
ip: 10.5.0.135
|
||||||
|
runsOn:
|
||||||
|
- unifi-usw-pro-hd-24
|
||||||
|
|
||||||
|
- kind: System
|
||||||
|
type: Embedded
|
||||||
|
name: unifi-u7-pro-firmware
|
||||||
|
os: unifi-ap-8.5.21.18681
|
||||||
|
ip: 10.5.0.21
|
||||||
|
runsOn:
|
||||||
|
- unifi-u7-pro
|
||||||
|
|
||||||
|
- kind: System
|
||||||
|
type: Embedded
|
||||||
|
name: unifi-u6-lr-firmware
|
||||||
|
os: unifi-ap-6.7.41.15623
|
||||||
|
ip: 10.5.0.20
|
||||||
|
runsOn:
|
||||||
|
- unifi-u6-lr
|
||||||
|
|
||||||
- kind: Service
|
- kind: Service
|
||||||
name: homepage
|
name: homepage
|
||||||
network:
|
network:
|
||||||
@@ -256,6 +408,20 @@ resources:
|
|||||||
runsOn:
|
runsOn:
|
||||||
- pd-truenas
|
- pd-truenas
|
||||||
|
|
||||||
|
- kind: Service
|
||||||
|
name: unifi-network-controller
|
||||||
|
network:
|
||||||
|
ip: 10.5.0.1
|
||||||
|
port: 443
|
||||||
|
protocol: TCP
|
||||||
|
url: https://10.5.0.1
|
||||||
|
tags:
|
||||||
|
- internal
|
||||||
|
- network
|
||||||
|
- unifi
|
||||||
|
runsOn:
|
||||||
|
- unifi-gateway
|
||||||
|
|
||||||
- kind: Service
|
- kind: Service
|
||||||
name: authelia
|
name: authelia
|
||||||
network:
|
network:
|
||||||
|
|||||||
@@ -2,8 +2,16 @@
|
|||||||
|
|
||||||
## Physical Network
|
## Physical Network
|
||||||
- **Gear:** Unifi throughout
|
- **Gear:** Unifi throughout
|
||||||
|
- **Gateway / controller:** UDM Pro (`10.5.0.1`, WAN `69.166.182.157`)
|
||||||
|
- **Core / access switching:**
|
||||||
|
- USW-24-PoE (`10.5.0.10`)
|
||||||
|
- USW Pro HD 24 (`10.5.0.135`)
|
||||||
|
- **Wi-Fi access points:**
|
||||||
|
- U7 Pro (`10.5.0.21`) — active
|
||||||
|
- U6 LR (`10.5.0.20`) — currently disconnected in UniFi as of 2026-05-21
|
||||||
- **Main LAN:** 10.5.1.x (Trusted VLAN)
|
- **Main LAN:** 10.5.1.x (Trusted VLAN)
|
||||||
- **Other VLANs:** Cameras, Guest, IoT
|
- **Management VLAN:** 10.5.0.x
|
||||||
|
- **Other VLANs:** Cameras, Guest, IoT / Old IoT
|
||||||
- **Serenity IP:** 10.5.1.5
|
- **Serenity IP:** 10.5.1.5
|
||||||
- **N.O.M.A.D. IP:** 10.5.1.16
|
- **N.O.M.A.D. IP:** 10.5.1.16
|
||||||
- **PlausibleDeniability:** 10.5.1.6 (static on LAN)
|
- **PlausibleDeniability:** 10.5.1.6 (static on LAN)
|
||||||
|
|||||||
106
docs/operations/UNIFI_NETWORK_ACCESS.md
Normal file
106
docs/operations/UNIFI_NETWORK_ACCESS.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# UniFi network operator access for Doris
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Give Doris safe, durable read access to the UniFi Network controller at `https://10.5.0.1` so she can:
|
||||||
|
|
||||||
|
- verify gateway / switch / AP inventory
|
||||||
|
- refresh RackPeek network device data
|
||||||
|
- check client/device state during troubleshooting
|
||||||
|
- avoid using your full owner account for routine reads
|
||||||
|
|
||||||
|
## Preferred access model
|
||||||
|
|
||||||
|
Use a **dedicated local Doris account** on UniFi OS with the **least privilege that still allows Network inventory reads**.
|
||||||
|
|
||||||
|
Target shape:
|
||||||
|
|
||||||
|
- Account name: `doris` or `doris-unifi`
|
||||||
|
- Scope: **Network application only**
|
||||||
|
- Permission: **view-only / read-only** if UniFi exposes that role cleanly
|
||||||
|
- Local account: yes
|
||||||
|
- Cloud account sharing: no
|
||||||
|
|
||||||
|
If UniFi's UI forces a broader role for API readability, use the narrowest app-scoped permission available and document the final role here.
|
||||||
|
|
||||||
|
## Secrets placement
|
||||||
|
|
||||||
|
Do **not** commit credentials to git.
|
||||||
|
|
||||||
|
When the Doris UniFi account exists, store the live credential in:
|
||||||
|
|
||||||
|
- PD live env: `/mnt/docker-ssd/docker/compose/automation/.env`
|
||||||
|
- synced encrypted backup: `/mnt/docker-ssd/docker/secrets/env/automation.env`
|
||||||
|
|
||||||
|
Expected vars:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
UNIFI_BASE_URL=https://10.5.0.1
|
||||||
|
UNIFI_USERNAME=doris
|
||||||
|
UNIFI_PASSWORD=REPLACE_ME
|
||||||
|
UNIFI_SITE=default
|
||||||
|
UNIFI_VERIFY_TLS=false
|
||||||
|
```
|
||||||
|
|
||||||
|
After editing the live `.env` on PD, run the normal secrets sync:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PATH="/mnt/docker-ssd/bin:$PATH"
|
||||||
|
bash /mnt/docker-ssd/docker/compose/scripts/sync-envs-to-secrets.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Doris helper
|
||||||
|
|
||||||
|
Repo helper:
|
||||||
|
|
||||||
|
- `automation/bin/unifi_network.py`
|
||||||
|
|
||||||
|
Default behavior:
|
||||||
|
|
||||||
|
- loads `automation/.env`
|
||||||
|
- logs into UniFi OS with cookie auth
|
||||||
|
- queries Network app endpoints through `/proxy/network/api/s/<site>/...`
|
||||||
|
- prints a JSON inventory summary without exposing the password
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/docker-ssd/docker/compose
|
||||||
|
python3 automation/bin/unifi_network.py
|
||||||
|
python3 automation/bin/unifi_network.py --raw
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation checklist
|
||||||
|
|
||||||
|
1. Confirm `https://10.5.0.1` is reachable from Doris's host.
|
||||||
|
2. Create the dedicated UniFi operator account.
|
||||||
|
3. Add the `UNIFI_*` vars to live `automation/.env` on PD.
|
||||||
|
4. Sync secrets repo.
|
||||||
|
5. Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/docker-ssd/docker/compose
|
||||||
|
python3 automation/bin/unifi_network.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Success means Doris can read:
|
||||||
|
|
||||||
|
- device inventory
|
||||||
|
- controller health summary
|
||||||
|
- connected client list
|
||||||
|
|
||||||
|
## Future integration
|
||||||
|
|
||||||
|
Once access is verified, use the helper as an input for:
|
||||||
|
|
||||||
|
- RackPeek network device refreshes
|
||||||
|
- dashboard network summaries
|
||||||
|
- targeted checks like "what AP is this client on" or "is switch X offline"
|
||||||
|
|
||||||
|
## Current status
|
||||||
|
|
||||||
|
- UniFi OS front door verified reachable at `https://10.5.0.1`
|
||||||
|
- unauthenticated API requests correctly return `401 Unauthorized`
|
||||||
|
- Doris helper scaffold exists in the repo
|
||||||
|
- dedicated Doris account validated end-to-end against the `default` site from N.O.M.A.D.
|
||||||
|
- current verified read scope is sufficient to read device inventory, controller health, and connected clients
|
||||||
Reference in New Issue
Block a user