go-netops/docs/architecture.md
Claude b27034b710
docs: add production documentation and README
Co-Authored-By: Charon <charon@lethean.io>
2026-02-20 15:22:57 +00:00

216 lines
7.7 KiB
Markdown

# Architecture
Module: `forge.lthn.ai/core/go-netops`
## Overview
go-netops is a thin Go wrapper around the [unpoller/unifi](https://github.com/unpoller/unifi) SDK
that provides a simplified interface for querying UniFi network controllers.
It handles authentication, TLS configuration, multi-site filtering, and
config resolution from file, environment, and CLI flags.
## Package Structure
```
go-netops/
├── unifi/
│ ├── client.go # Client struct and constructor (New)
│ ├── clients.go # Connected client queries with filtering
│ ├── config.go # Config resolution + persistence (NewFromConfig, ResolveConfig, SaveConfig)
│ ├── devices.go # Infrastructure device queries (UAP, USW, USG, UDM, UXG)
│ ├── networks.go # Network configuration queries (VLANs, DHCP, firewall zones)
│ ├── routes.go # Gateway routing table queries
│ ├── sites.go # Site listing
│ ├── client_test.go # Client creation tests
│ └── config_test.go # Config resolution and persistence tests
├── go.mod
└── go.sum
```
All exported types live in the `unifi` package under the `unifi/` subdirectory.
## Key Types
### Client
The central type. Wraps the unpoller SDK client with config-based authentication.
```go
type Client struct {
api *uf.Unifi
url string
}
func New(url, user, pass, apikey string, insecure bool) (*Client, error)
func NewFromConfig(flagURL, flagUser, flagPass, flagAPIKey string, flagInsecure *bool) (*Client, error)
func (c *Client) API() *uf.Unifi
func (c *Client) URL() string
```
Two constructors are provided:
- **`New`** — direct credentials, useful for tests and scripts.
- **`NewFromConfig`** — three-tier config resolution (file, env, flags), used by CLI commands.
### ClientFilter
Controls which connected clients are returned from a query.
```go
type ClientFilter struct {
Site string // Filter by site name (empty = all sites)
Wired bool // Show only wired clients
Wireless bool // Show only wireless clients
}
func (c *Client) GetClients(filter ClientFilter) ([]*uf.Client, error)
```
### DeviceInfo
Flat representation of any UniFi infrastructure device, abstracting over the SDK's
separate UAP/USW/USG/UDM/UXG containers.
```go
type DeviceInfo struct {
Name string
IP string
Mac string
Model string
Version string
Type string // uap, usw, usg, udm, uxg
Status int // 1 = online
}
func (c *Client) GetDevices(siteName string) (*uf.Devices, error)
func (c *Client) GetDeviceList(siteName, deviceType string) ([]DeviceInfo, error)
```
- **`GetDevices`** returns the raw SDK container (for callers that need full detail).
- **`GetDeviceList`** returns a flat, uniform slice with optional type filtering.
### NetworkConf
Network configuration entry covering VLANs, DHCP, DNS, firewall zones, and WAN settings.
```go
type NetworkConf struct {
ID string `json:"_id"`
Name string `json:"name"`
Purpose string `json:"purpose"` // wan, corporate, remote-user-vpn
IPSubnet string `json:"ip_subnet"` // CIDR
VLAN int `json:"vlan"`
VLANEnabled bool `json:"vlan_enabled"`
Enabled bool `json:"enabled"`
NetworkGroup string `json:"networkgroup"` // LAN, WAN, WAN2
NetworkIsolationEnabled bool `json:"network_isolation_enabled"`
InternetAccessEnabled bool `json:"internet_access_enabled"`
IsNAT bool `json:"is_nat"`
DHCPEnabled bool `json:"dhcpd_enabled"`
DHCPStart string `json:"dhcpd_start"`
DHCPStop string `json:"dhcpd_stop"`
DHCPDNS1 string `json:"dhcpd_dns_1"`
DHCPDNS2 string `json:"dhcpd_dns_2"`
DHCPDNSEnabled bool `json:"dhcpd_dns_enabled"`
MDNSEnabled bool `json:"mdns_enabled"`
FirewallZoneID string `json:"firewall_zone_id"`
GatewayType string `json:"gateway_type"`
VPNType string `json:"vpn_type"`
WANType string `json:"wan_type"` // pppoe, dhcp, static
WANNetworkGroup string `json:"wan_networkgroup"`
}
func (c *Client) GetNetworks(siteName string) ([]NetworkConf, error)
```
Uses the raw controller REST API (`/api/s/{site}/rest/networkconf`) since the
unpoller SDK does not wrap this endpoint.
### Route
Gateway routing table entry with prefix, next-hop, interface, type, and metrics.
```go
type Route struct {
Network string `json:"pfx"` // CIDR prefix
NextHop string `json:"nh"` // Next-hop address or interface
Interface string `json:"intf"` // Interface name (e.g. "br0", "eth4")
Type string `json:"type"` // S=static, C=connected, K=kernel, B=bgp, O=ospf
Distance int `json:"distance"` // Administrative distance
Metric int `json:"metric"`
Uptime int `json:"uptime"` // Seconds
Selected bool `json:"fib"` // In forwarding table
}
func (c *Client) GetRoutes(siteName string) ([]Route, error)
func RouteTypeName(code string) string
```
Also uses the raw controller API (`/api/s/{site}/stat/routing`).
## Design Decisions
### Thin Wrapper
The package deliberately stays thin. It delegates transport, session management,
and cookie handling to the unpoller SDK, adding only:
- **Config resolution** — three-tier priority (config file, env vars, CLI flags).
- **TLS flexibility** — `insecure` flag for self-signed certificates on home lab controllers.
- **Flat types** — `DeviceInfo` and `NetworkConf` flatten the SDK's nested/typed structures
into uniform slices for easier consumption by CLI commands and templates.
- **Raw API access** — `GetNetworks` and `GetRoutes` call controller endpoints the SDK does
not wrap, parsing the JSON directly.
### Config Resolution Order
All credential resolution follows the same three-tier pattern used across the core framework:
1. **Config file**`~/.core/config.yaml` (keys: `unifi.url`, `unifi.user`, `unifi.pass`, `unifi.apikey`, `unifi.insecure`)
2. **Environment variables**`UNIFI_URL`, `UNIFI_USER`, `UNIFI_PASS`, `UNIFI_APIKEY`, `UNIFI_INSECURE`
3. **CLI flags** — highest priority, override everything
### Site Filtering
Most query methods accept a `siteName` parameter. An empty string queries all sites.
The `getSitesForFilter` helper resolves site names and returns an error for unknown sites,
preventing silent data omission.
### TLS Configuration
The client enforces TLS 1.2 as the minimum version regardless of the `insecure` flag.
The `insecure` flag only controls certificate verification, not protocol version.
## API Patterns
### Constructor Pattern
```go
// Direct credentials
client, err := unifi.New("https://10.69.1.1", "admin", "pass", "", true)
// Config-resolved (CLI usage)
client, err := unifi.NewFromConfig(flagURL, flagUser, flagPass, flagAPIKey, &flagInsecure)
```
### Query Pattern
All query methods follow the same shape: accept optional filters, return typed slices, wrap errors with `log.E`.
```go
clients, err := client.GetClients(unifi.ClientFilter{Site: "default", Wireless: true})
devices, err := client.GetDeviceList("default", "uap")
networks, err := client.GetNetworks("default")
routes, err := client.GetRoutes("default")
sites, err := client.GetSites()
```
### Config Persistence
```go
// Save credentials
err := unifi.SaveConfig("https://10.69.1.1", "admin", "pass", "", nil)
// Resolve credentials (without creating a client)
url, user, pass, apikey, insecure, err := unifi.ResolveConfig("", "", "", "", nil)
```