UniFi site¶
terraform/unifi/network declares the UniFi site: networks, WAN, Wi-Fi,
adopted devices, DHCP reservations, firewall policies, port forwards, the
WireGuard server, dynamic DNS and most site settings. The console is for
reading. Changes go through Terraform. The user-defined local.bigd.no records
are a separate stack, see dns.md.
Networks and Wi-Fi¶
| Network | VLAN | Subnet | Firewall zone | SSID |
|---|---|---|---|---|
| Trusted | untagged | 10.3.10.0/24 |
Internal | Eden-Trusted (2.4 and 5 GHz) |
| IoT | 20 | 10.3.20.0/24 |
Unsecure Zone | Eden-IoT (2.4 GHz only) |
| Guest | 1000 | 192.168.100.0/24 |
Hotspot | Eden-Guest (guest, L2 isolation) |
Every network gives its clients Home Assistant (10.3.10.15, AdGuard add-on) as
the DNS server. The primary WAN uses it too. DHCP pools run from .6 to .254. The
three SSIDs are WPA2 with WPA3 transition and optional PMF.
Internet 1 is the active WAN (DHCP, weighted, priority 1). Internet 2 is
declared but disabled, failover-only.
Devices¶
| Device | Address | Notes |
|---|---|---|
| Cloud Gateway Fiber | 10.3.10.1 |
router, firewall, resolver for local.bigd.no |
| USW-Lite-8-PoE | 10.3.10.3 |
port 6 is "LG TV" with IoT as native network |
| U6+ | 10.3.10.2 |
LED off; 2.4 GHz on channel 13 to stay clear of Zigbee channel 20, 5 GHz on 108 |
The cluster, NAS and PBS connect through the switch.
Reservations¶
clients.tf holds the fixed IPs: NAS .10, PBS .11, hyper1-3 .12-.14,
Home Assistant .15, Hue Bridge Pro .16, Talos control plane .31-.33 and
workers .34-.36, all on Trusted, plus the Bluetooth proxy on IoT
(10.3.20.39). Each Trusted reservation also sets local_dns_record. The
generated <name>.local.bigd.no records in the console come from that field.
Firewall¶
The predefined Block All policies of the controller isolate IoT from Trusted. They
are not managed here. Every policy in firewall.tf is a hole through that block:
| Policy | From | To |
|---|---|---|
| Allow Internal to Unsecure Zone | Trusted | IoT, any |
| IoT -> Home Assistant | IoT | 10.3.10.15 tcp/udp 53, 8123 |
| LG TV -> Plex | LG TV MAC | 10.3.10.103 tcp 32400 |
| LG TV -> jarvis | LG TV MAC | 10.3.10.102 (private Traefik) tcp 443 |
| Voice PE -> innestemme | Voice PE MAC | 10.3.10.99 tcp 9090 |
Home Assistant connects out to ESPHome, Hue and Sonos. Trusted therefore reaches IoT without limits, and IoT only answers. IoT reaches Home Assistant for DNS and for TTS audio on 8123.
A new IoT device that needs something on Trusted gets its own policy, limited to
that device (CLIENT plus MAC) and destination port. Never add a network-to-network
allow. That opens the whole Trusted side again. A device moved from Wi-Fi to cable
gets a new MAC, so its policy must change too, and its switch port needs the IoT
native network in devices.tf.
Port forwards and remote access¶
| Forward | WAN port | Target |
|---|---|---|
| trefik-http | 80 tcp/udp | 10.3.10.101 (public Traefik gateway) |
| trefik-https | 443 tcp/udp (UDP for HTTP/3) | 10.3.10.101 |
| plex | 32400 tcp/udp | 10.3.10.103 (Plex VIP) |
remote-access.tf holds the emergency fallback WireGuard server (UDP 51820, clients
in 192.168.3.0/24) and the Cloudflare dynamic DNS clients of the gateway. The DDNS
clients keep ddns.bigd.no and ddns.nordbye.it on the current WAN address. Normal
remote access is Tailscale, see remote-access.md.
Secrets in tfvars¶
The Wi-Fi passphrases and DDNS tokens in terraform.tfvars must be the values
that the controller already has. The provider writes them on every update of their
resource. A wrong passphrase disconnects that SSID, and a wrong DDNS token
stops the WAN address updates. To rotate a secret, set a new value here.
The WireGuard private key is not declared. The console never shows it. A declared key would put the key that every peer trusts into tfvars and state.
Provider traps¶
- The provider fills unset arguments with its own defaults, not with the values of
the controller. Arguments that look redundant (
setting_preference,lte_lan,group_rekey,bss_transition,ap_group.ids,wlan_band) are therefore pinned to the console values. A removed argument shows as a change inplan. - Band steering is set on the access point (
unifi_device), not on the WLAN. Onunifi_wlan,bandsteering_modeis deprecated and never reaches the controller. - Devices have
prevent_destroy. A destroyedunifi_deviceis forgotten (unadopted).config_networkis pinned on the switch and AP, so an update can never send an empty management address. unifi_devicemanages only the ports declared inport_override. Other ports keep their console settings. A removed block does not reset its port.- Zones are data sources by name. The Default user group and the "All APs" group are hardcoded IDs (no data source exists). When "Unsecure Zone", "Internal" or "Hotspot" is renamed in the console, the plan breaks until the name is updated here.
indexon firewall policies is read-only. A new policy is added at the end of its zone pair. With allow-only policies the order does not matter.- Terraform sees only what it manages. Objects created in the console stay
invisible to
plan.
Site settings¶
settings.tf holds the site-level settings as one unifi_setting: country
(578, Norway, which decides the legal Wi-Fi channels), NTP, DPI, IPS, DNS over
HTTPS, syslog, IGMP snooping, the gateway touchscreen, network optimization,
the daily speedtest and the management settings.
- The provider writes only the declared sections, and the controller merges a
partial section into the stored one. Fields that the provider does not model
therefore keep their console values. This is why IPS ad blocking, DNS filtering
and the SSH credentials under
mgmtstay out of the repo and are not reset. - A declared section must list every field that the provider models. Unset fields
are sent as
falseor empty, not skipped.mgmt,igmp_snoopingandradiusare the exception. The provider reads the live section first and overlays only the declared fields. usg(conntrack timeouts, ALG modules, UPnP, mDNS) is not declared. The provider rebuilds it from scratch and always sends fields that it does not model,mdns_enabledandlldp_enable_allincluded. A declared section would switch them off.
Rebuilding state¶
Everything here was created in the console first and adopted with import blocks.
If the state is lost, write a temporary imports.tf from the _ids of the
controller (/proxy/network/api/s/default/rest/* and
/proxy/network/v2/api/site/default/firewall-policies). Clients import by MAC.
Apply only when the plan reports imports and no adds or destroys. Then delete the
file.
Not managed here¶
The gateway settings in the usg section (see above), the mDNS reflector,
Teleport, the predefined zones and their default policies, admin accounts and
backups.