Abandon UniFi IaC, add manual network segmentation plan (#189)

## Summary

- Abandon the UniFi Pulumi IaC approach after provider bugs caused a network outage (no-op update reset undeclared properties on the default LAN network)
- Remove untracked IaC artifacts (`pulumi/unifi/`, `mise-tasks/unifi-preview`, `mise-tasks/unifi-up`) locally
- Mark `add-unifi-pulumi-stack` plan as Abandoned with explanation
- Create new `segment-home-network` plan for manual three-network segmentation (Main/IoT/Guest) via UX7 web UI
- Rewrite UniFi reference card to remove all Pulumi/IaC references
- Update plan and how-to indexes

## Test plan

- [x] `docs-check-links` passes
- [x] `docs-check-index` passes
- [x] Pre-commit hooks pass
- [ ] Review segmentation plan for completeness before executing manually

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/189
This commit is contained in:
Erich Blume 2026-02-14 09:47:04 -08:00
commit 657bb28fd1
7 changed files with 176 additions and 283 deletions

View file

@ -0,0 +1 @@
Abandon UniFi Pulumi IaC (provider bugs caused network outage); add manual three-network segmentation plan for UX7 web UI.

View file

@ -1 +0,0 @@
Updated UniFi Pulumi plan: switched provider to ubiquiti-community/unifi, added config backup step, fixed safety guard and 1Password paths.

View file

@ -1,6 +1,6 @@
---
title: How-To
modified: 2026-02-11
modified: 2026-02-14
tags:
- how-to
---
@ -62,7 +62,8 @@ Migration and transition plans for upcoming infrastructure changes.
| [[plans]] | Index of all plans |
| [[completed]] | Completed plans archive |
| [[migrate-forgejo-from-brew]] | Transition Forgejo from Homebrew to source-built binary |
| [[add-unifi-pulumi-stack]] | Add Pulumi IaC for UniFi Express 7 |
| [[add-unifi-pulumi-stack]] | Add Pulumi IaC for UniFi Express 7 (abandoned) |
| [[segment-home-network]] | Manual three-network segmentation for UniFi Express 7 |
| [[adopt-dagger-ci]] | Adopt Dagger as CI/CD build engine |
| [[upstream-fork-strategy]] | Stacked-branch forking strategy for upstream projects |
| [[adopt-oidc-provider]] | Deploy OIDC identity provider for SSO across services |

View file

@ -1,6 +1,6 @@
---
title: "Plan: Add UniFi Pulumi Stack"
modified: 2026-02-13
modified: 2026-02-14
tags:
- how-to
- plans
@ -10,258 +10,23 @@ tags:
# Plan: Add UniFi Pulumi Stack
> **Status:** Planned (not yet executed)
> **Blocked by:** 1Password credential setup (API key)
> **Status:** Abandoned
> **Superseded by:** [[segment-home-network]]
## Background
## Why Abandoned
The UniFi Express 7 (UX7) is the home WiFi router, currently unmanaged. This plan adds a Pulumi stack (`pulumi/unifi/`) to bring it under IaC control, following the same conventions as `pulumi/tailscale/` and `pulumi/gandi/`.
Attempted Feb 2026 with the `ubiquiti-community/unifi` Terraform provider via `pulumi package add`. Two issues made the approach unviable:
### Why IaC for the Router?
1. **API key auth skips UniFi OS auto-detection** ([provider bug #74](https://github.com/ubiquiti-community/terraform-provider-unifi/issues/74)) — requires username/password instead, which is unsuitable for IaC
2. **"No-op" update on the default LAN network reset undeclared properties** — bricked the network, requiring a factory reset and backup restore
- **Reproducibility** — WiFi networks, firewall rules, and DHCP settings are declared in code
- **Audit trail** — changes go through PR review like all other infrastructure
- **Consistency** — joins the existing Pulumi stacks for Tailscale ACLs and DNS
- **Network segmentation** — declare main/guest/IoT WiFi networks with proper firewall zones
The provider ecosystem (ubiquiti-community, filipowm, pulumiverse) is too immature for critical single-device infrastructure like the home router. A provider bug that causes a network outage on a no-op update is an unacceptable risk.
### Ethernet Requirement (Resolved)
## What Survives
The UX7 has one LAN port. Modifying WiFi settings over WiFi would sever the management connection mid-apply. This was resolved by installing:
1. **Two switches** (UniFi Switch Flex Mini recommended) daisy-chained:
- Switch A by the router: connects UX7, sifaka
- Switch B on the desk (~12ft cable): connects indri and gilbert
2. **Cat6 Ethernet cables**: one ~12ft run between switches, plus short cables for each device
```
UniFi Express 7 [LAN port]
└── Switch A (by router/sifaka)
├── sifaka (short cable)
└── ~12ft Cat6 ──→ Switch B (on desk)
├── indri (Cat6)
└── gilbert (USB-C adapter)
```
Daisy-chaining is standard Layer 2 networking — no speed loss per device, no subnet impact. The only shared bottleneck is the 1 Gbps uplink between the two switches, which is more than adequate for homelab use. UniFi Flex Minis will appear in the UX7's controller for monitoring and eventual Pulumi management.
## Prerequisites
Before starting the execution session:
- [ ] Purchase 2x UniFi Switch Flex Mini (USW-Flex-Mini)
- [ ] Purchase Cat6 cables: 1x ~12ft, 3-4x short (~3ft)
- [ ] Cable everything up and verify all devices have network connectivity
- [ ] Verify the machine running Pulumi (gilbert) has an active wired Ethernet connection as its default route: `route -n get default` should show a non-Wi-Fi interface
- [ ] **Back up the UX7 configuration** via `https://192.168.1.1` → Settings → System → Backup, and download a `.unf` backup file. Store it safely before making any IaC changes. This provides a rollback path if a provider bug corrupts network or firewall state.
- [ ] Create an API key on the UX7 via `https://192.168.1.1` → Settings → Control Plane → API (preferred over username/password for IaC)
- [ ] Store UniFi API key in 1Password: vault `blumeops`, item `unifi`, category `API_CREDENTIAL`
- [ ] Verify Pulumi CLI version is >= v3.147.0 (`pulumi version`)
## Provider: ubiquiti-community/unifi via `pulumi package add`
We use `pulumi package add terraform-provider ubiquiti-community/unifi` to consume the [ubiquiti-community fork](https://github.com/ubiquiti-community/terraform-provider-unifi) of the UniFi Terraform provider directly from Pulumi. This approach:
- **Generates a local Python SDK** in `./sdks/unifi/` and adds a package reference to `Pulumi.yaml`
- **Supports API key authentication** — cleaner than username/password for IaC
- **Actively maintained** — v0.41.12 (Jan 2026), responsive maintainer, 12 releases since Oct 2025
- **Uses Pulumi Cloud state** — same as existing stacks, free tier
- **No fork/bridge maintenance** — just re-run `pulumi package add` on new provider versions
- **Broader ecosystem** — part of the [ubiquiti-community](https://github.com/ubiquiti-community) org alongside go-unifi, unifi-api, and other tools
### Why Not Other Providers?
| Provider | Why Not |
|----------|---------|
| `pulumiverse_unifi` | Bridges `paultyng/terraform-provider-unifi`, which is abandoned. No API key auth, no newer resource types. |
| `filipowm/unifi` | Maintainer unresponsive since April 2025. Critical bug ([#94](https://github.com/filipowm/terraform-provider-unifi/issues/94)): applying `unifi_network` resources wipes all zone-based firewall rules. Unmerged community fix PRs. |
| `paultyng/unifi` | Abandoned since March 2023. No API key auth, no zone-based firewall. |
### Zone-Based Firewall: Deferred
The ubiquiti-community provider does not yet support zone-based firewall resources ([#77](https://github.com/ubiquiti-community/terraform-provider-unifi/issues/77)). Zone-based firewall rules will be managed manually in the UX7 web UI until provider support lands. This is acceptable because:
- The initial goal is bringing networks, WLANs, and DHCP under IaC
- Network segmentation (which needs firewall zones) is a future phase
- The filipowm provider — the only one with zone firewall support — has a showstopper bug that makes it unusable for this purpose anyway
## Network Segmentation Goals
Once the stack is operational, we plan to configure these network zones:
| Network | VLAN | Subnet | Purpose | Devices |
|---------|------|--------|---------|---------|
| BlumeOps Services | TBD | `192.168.10.0/24` | Infrastructure and services | indri, sifaka, k8s pods |
| User Devices | 1 | `192.168.1.0/24` | Trusted personal devices | gilbert, ringtail |
| Guest | TBD | `192.168.2.0/24` | Guest WiFi, internet-only | Visitors |
| IoT / Appliances | TBD | `192.168.3.0/24` | Smart devices, isolated | Frame TV, dishwasher, etc. |
### Motivation: NFS Share Exposure
The immediate security driver for segmentation is NFS. Currently, sifaka's NFS exports (`/volume1/torrents`, `/volume1/music`, `/volume1/photos`) whitelist `192.168.1.0/24` and `100.64.0.0/10` (Docker NAT). This means **any device on the WiFi** — including IoT appliances, guest devices, or a compromised smart TV — can mount and write to these shares.
After segmentation, NFS exports will be restricted to the BlumeOps Services subnet (`192.168.10.0/24`) and the Docker NAT range (`100.64.0.0/10`). Only indri, sifaka, and k8s pods will have NFS access.
### Zone-Based Firewall Rules
| Source | Destination | Policy |
|--------|-------------|--------|
| BlumeOps Services | Internet | Allow |
| BlumeOps Services | User Devices | Allow (for management, e.g., SSH from ringtail) |
| User Devices | BlumeOps Services | Allow (trusted users need access to services) |
| User Devices | Internet | Allow |
| Guest | Internet | Allow |
| Guest | All other zones | **Block** |
| IoT / Appliances | Internet | Allow |
| IoT / Appliances | User Devices | **Block** (except mDNS for AirPlay/casting) |
| IoT / Appliances | BlumeOps Services | **Allow specific ports** (Jellyfin, Navidrome for streaming) |
### NFS Export Changes
After the network migration, update sifaka's NFS export rules:
| Share | Before | After |
|-------|--------|-------|
| `/volume1/torrents` | `192.168.1.0/24`, `100.64.0.0/10` | `192.168.10.0/24`, `100.64.0.0/10` |
| `/volume1/music` | `192.168.1.0/24`, `100.64.0.0/10` | `192.168.10.0/24`, `100.64.0.0/10` |
| `/volume1/photos` | `192.168.1.0/24`, `100.64.0.0/10` | `192.168.10.0/24`, `100.64.0.0/10` |
This is a manual change in the Synology DSM NFS settings (not managed by Pulumi — sifaka's NFS config is outside the UniFi provider's scope). The k8s PersistentVolume definitions (`argocd/manifests/*/pv-nfs.yaml`) resolve sifaka by hostname and don't need subnet changes.
These will be declared after the initial import is stable.
## Pulumi Stack Structure
Following the conventions of `pulumi/tailscale/` and `pulumi/gandi/`:
```
pulumi/unifi/
├── Pulumi.yaml # name: blumeops-unifi, python runtime, uv toolchain
│ # includes parameterized package reference for ubiquiti-community/unifi
├── Pulumi.home-network.yaml # Stack config: router_url, site
├── pyproject.toml # Python >=3.11, pulumi>=3.0.0
├── sdks/unifi/ # Generated Python SDK from pulumi package add
│ └── ... # (auto-generated, committed to repo)
├── __main__.py # Main program with safety guard
├── .gitignore # .venv/, __pycache__/, *.py[cod]
└── uv.lock # Generated by uv sync, committed
```
### Provider Configuration
**Authentication** (via environment variables in mise tasks):
| Variable | Value | Notes |
|----------|-------|-------|
| `UNIFI_API_KEY` | `op read "op://blumeops/unifi/credential"` | API key created in UX7 control plane |
| `UNIFI_API` | `https://192.168.1.1` | No `/api` suffix — SDK auto-discovers `/proxy/network` for UniFi OS |
| `UNIFI_INSECURE` | `true` | UX7 uses a self-signed TLS certificate |
### Safety Guard
The `__main__.py` must fail fast before creating any Pulumi resources if the default network route goes through Wi-Fi. This prevents accidentally modifying WiFi settings while connected over WiFi (which would sever the management connection mid-apply).
The check works as follows:
1. Run `route -n get default` and extract the `interface:` field (e.g., `en5`)
2. Run `networksetup -listallhardwareports` and find which hardware port owns that interface
3. If the hardware port is `Wi-Fi`, abort with an error
This is host-agnostic — it works on both gilbert (where the Ethernet adapter is `AX88179A` on `en5`) and indri (where it's `Ethernet` on `en0`).
## Execution Steps
### Step 1: Create Stack Directory and Install Provider
```fish
mkdir -p pulumi/unifi
cd pulumi/unifi
# Create Pulumi.yaml, pyproject.toml, .gitignore, __main__.py
pulumi package add terraform-provider ubiquiti-community/unifi
# This generates sdks/unifi/ and updates Pulumi.yaml with package reference
pulumi install
uv sync
```
### Step 2: Create Stack Files
Create `Pulumi.yaml`, `Pulumi.home-network.yaml`, `pyproject.toml`, `.gitignore`, and `__main__.py`. The main program should declare:
- **Ethernet safety guard** (verify default route is not Wi-Fi)
- **Default LAN network** resource (corporate, `192.168.1.0/24`, DHCP)
- **WiFi WLAN** resources (commented out initially — need SSID names and IDs from the controller)
- **Exports** for router IP, network ID, subnet
### Step 3: Create Mise Tasks
Create `mise-tasks/unifi-preview` and `mise-tasks/unifi-up` following the pattern from `tailnet-up`/`dns-up`:
```bash
UNIFI_API_KEY=$(op read "op://blumeops/unifi/credential")
export UNIFI_API="https://192.168.1.1"
export UNIFI_INSECURE="true"
```
### Step 4: Initialize Stack
```fish
cd pulumi/unifi
uv sync
pulumi stack init home-network
```
### Step 5: Import Existing Resources
Discover resource IDs from the UniFi controller API or web UI, then import:
```fish
# Import default network
pulumi import unifi:index/network:Network default-lan <network-id>
# Later, import WLANs
pulumi import unifi:index/wlan:Wlan home-wifi <wlan-id>
```
Adjust `__main__.py` resource properties to match the actual controller state until `pulumi preview` shows no diff.
### Step 6: Documentation Updates
- Update `docs/reference/infrastructure/unifi.md` — remove `(planned)` markers, update provider to ubiquiti-community
- Add changelog fragment
### Step 7: Verify
- `mise run unifi-preview` shows no unexpected diffs
- Pre-commit hooks pass
- `docs-check-links` and `docs-check-index` pass
## Known Limitations
- **macOS-specific guard** — the `networksetup` and `route` checks only work on macOS, which is fine since the stack is run from gilbert or indri, both permanently macOS
- **User group ID discovery** — the provider may not expose a `get_user_group` data source. Must be discovered manually from the controller API (`/proxy/network/api/s/default/rest/usergroup`) and hardcoded
## Future Considerations
- **Zone-based firewall rules** — manage via Pulumi once ubiquiti-community adds support ([#77](https://github.com/ubiquiti-community/terraform-provider-unifi/issues/77)). Until then, configure manually in the UX7 web UI.
- **Network segmentation** — depends on zone-based firewall support; see goals above
- **UnPoller** — add Prometheus metrics exporter for UniFi gear, integrates with existing Grafana stack
- **Switch management** — manage the USW-Flex-Minis via the same Pulumi stack once adopted into the UX7 controller
- **Provider updates** — re-run `pulumi package add terraform-provider ubiquiti-community/unifi` to update
## Reference Pattern Files
| File | Purpose |
|------|---------|
| `pulumi/tailscale/__main__.py` | Pulumi program pattern (resources, exports, data sources) |
| `pulumi/gandi/__main__.py` | Config resolution pattern (`pulumi.Config().require()`) |
| `pulumi/tailscale/Pulumi.yaml` | Project definition pattern |
| `pulumi/gandi/Pulumi.eblu-me.yaml` | Stack config pattern |
| `mise-tasks/tailnet-up` | Mise task credential pattern (`op read`) |
| `docs/reference/infrastructure/gandi.md` | Infrastructure reference card pattern |
The network segmentation goals from this plan remain valid and are carried forward in [[segment-home-network]], which describes how to configure three-network segmentation manually through the UX7 web UI.
## Related
- [[hosts]] - Device inventory (UniFi Express 7)
- [[unifi]] - Reference card
- [[power]] - UPS power chain
- [[indri]] - Server connected via Cat6 Ethernet
- [[tailscale]] - Tailnet networking
- [[segment-home-network]] — Manual segmentation plan (replacement)
- [[unifi]] — Reference card

View file

@ -1,6 +1,6 @@
---
title: Plans
modified: 2026-02-11
modified: 2026-02-14
tags:
- how-to
- plans
@ -15,7 +15,8 @@ Plans differ from regular how-to guides in that they describe work that has been
| Plan | Status | Description |
|------|--------|-------------|
| [[migrate-forgejo-from-brew]] | Planned | Transition Forgejo from Homebrew to source-built binary with LaunchAgent |
| [[add-unifi-pulumi-stack]] | Planned | Add Pulumi IaC for UniFi Express 7 home network |
| [[add-unifi-pulumi-stack]] | Abandoned | Add Pulumi IaC for UniFi Express 7 (provider bugs — see doc) |
| [[segment-home-network]] | Planned | Manual three-network segmentation for UniFi Express 7 |
| [[upstream-fork-strategy]] | Planned | Stacked-branch forking strategy for tracking upstream projects |
| [[adopt-oidc-provider]] | Planning | Deploy OIDC identity provider for SSO across services |
| [[harden-zot-registry]] | Planned | Add authentication and tag immutability to zot registry |

View file

@ -0,0 +1,124 @@
---
title: "Plan: Segment Home Network"
modified: 2026-02-14
tags:
- how-to
- plans
- networking
---
# Plan: Segment Home Network
> **Status:** Planned (not yet executed)
> **Replaces:** [[add-unifi-pulumi-stack]] (abandoned — provider bugs)
## Background
All devices currently share a single flat `192.168.1.0/24` network. This means IoT appliances (Frame TV, dishwasher) and guest devices can reach NFS shares, management interfaces, and all other services on the LAN.
This plan segments the home network into three zones using the UX7 web UI. The IaC approach was abandoned after the `ubiquiti-community/unifi` Terraform provider bricked the network on a no-op update — see [[add-unifi-pulumi-stack]] for details.
### Security Driver: NFS Exposure
Sifaka's NFS exports (`/volume1/torrents`, `/volume1/music`, `/volume1/photos`) whitelist `192.168.1.0/24`. Today, **any device on the WiFi** — including IoT appliances or guest devices — can mount and write to these shares. After segmentation, only Main network devices (192.168.1.0/24) have NFS access. IoT (192.168.3.0/24) and Guest (192.168.2.0/24) are on different subnets and cannot reach NFS even without firewall rules.
## Prerequisites
- [ ] **Back up the UX7 configuration** via `https://192.168.1.1` → Settings → System → Backup. Download the `.unf` backup file before making any changes.
- [ ] Verify all wired devices (indri, sifaka, gilbert) have connectivity
- [ ] Know which devices should go on each network
## Three Networks
| Network | SSID | VLAN | Subnet | Bands | Purpose |
|---------|------|------|--------|-------|---------|
| Main | Radio New Vegas | 1 (default) | 192.168.1.0/24 | All | Trusted devices (indri, sifaka, gilbert, mouse) |
| IoT | (TBD by user) | 3 | 192.168.3.0/24 | 2.4GHz only | Smart devices (Frame TV, appliances) |
| Guest | (TBD by user) | 2 | 192.168.2.0/24 | All | Visitors, internet-only |
## UX7 Configuration Steps
All configuration is done through the UX7 web UI at `https://192.168.1.1`.
### 1. Create IoT Network
Settings → Networks → Create New:
- **Name:** IoT
- **VLAN ID:** 3
- **Gateway/Subnet:** 192.168.3.1/24
- **DHCP:** Enabled, range 192.168.3.6192.168.3.254
### 2. Create Guest Network
Settings → Networks → Create New:
- **Name:** Guest
- **VLAN ID:** 2
- **Gateway/Subnet:** 192.168.2.1/24
- **DHCP:** Enabled, range 192.168.2.6192.168.2.254
### 3. Create IoT WLAN
Settings → WiFi → Create New:
- **SSID:** (user's choice)
- **Network:** IoT
- **Band:** 2.4GHz only
- **Security:** WPA2/WPA3
### 4. Create Guest WLAN
Settings → WiFi → Create New:
- **SSID:** (user's choice)
- **Network:** Guest
- **Security:** WPA2/WPA3
- **Guest policies:** Enabled (client isolation)
### 5. Enable mDNS Reflector
Settings → Networks → Global Network Settings:
- Enable **Multicast DNS** — this allows AirPlay/casting discovery across VLANs (Main ↔ IoT)
## Firewall Rules (Zone-Based)
Configured at Settings → Firewall & Security → Firewall Rules.
UX7 zones correspond to networks. Default inter-VLAN policy is **allow**, so we add **block** rules. **Rule ordering matters** — allow rules must come before matching block rules.
| # | Name | Action | Source | Destination | Protocol/Port | Notes |
|---|------|--------|--------|-------------|---------------|-------|
| 1 | Guest → LAN block | Block | Guest | Main | All | Internet-only isolation |
| 2 | Guest → IoT block | Block | Guest | IoT | All | No cross-zone access |
| 3 | IoT → Main streaming allow | Allow | IoT | Main (indri IP) | TCP 443 | Jellyfin/Navidrome via Caddy — must be BEFORE the block rule |
| 4 | IoT → Main block | Block | IoT | Main | All | Protect NFS and trusted devices |
### Notes on Firewall Rules
**IoT streaming:** Jellyfin (port 8096) and Navidrome bind behind [[caddy]] on indri:443. IoT devices (Frame TV) access media via `https://jellyfin.ops.eblu.me` which resolves to indri's LAN IP. Rule 3 allows IoT → indri:443 only. All other Main network access from IoT is blocked by rule 4.
**NFS exports:** No changes needed to sifaka's NFS configuration. The exports whitelist `192.168.1.0/24` — after segmentation, only Main network devices are on that subnet. IoT (192.168.3.0/24) and Guest (192.168.2.0/24) can't reach NFS because they're on different subnets. The firewall rules provide defense-in-depth.
## Verification
After applying the configuration:
- [ ] From Main device: internet works, can reach all services, can mount NFS
- [ ] From IoT device: internet works, can stream Jellyfin, CANNOT mount NFS
- [ ] From Guest device: internet works, CANNOT reach any internal service
- [ ] AirPlay/casting from Main to IoT TV works (mDNS reflector)
- [ ] All wired devices (indri, sifaka, gilbert) unaffected on default VLAN
## Future Considerations
- **UnPoller** — add Prometheus metrics exporter for UniFi gear, integrates with existing Grafana stack
- **IaC revisit** — if the ubiquiti-community provider matures and fixes the destructive-update bug, IaC could be reconsidered
## Related
- [[add-unifi-pulumi-stack]] — Previous IaC approach (abandoned)
- [[unifi]] — Reference card
- [[hosts]] — Device inventory
- [[power]] — UPS power chain

View file

@ -1,6 +1,6 @@
---
title: UniFi
modified: 2026-02-10
modified: 2026-02-14
tags:
- infrastructure
- networking
@ -8,7 +8,7 @@ tags:
# UniFi
Home WiFi router and network controller, managed via Pulumi IaC.
Home WiFi router and network controller, managed via the UX7 web UI.
## Quick Reference
@ -17,8 +17,7 @@ Home WiFi router and network controller, managed via Pulumi IaC.
| **Model** | UniFi Express 7 (UX7) |
| **LAN IP** | `192.168.1.1` |
| **Management URL** | `https://192.168.1.1` |
| **IaC** | `pulumi/unifi/` (planned) |
| **Stack** | `home-network` (planned) |
| **Management** | Web UI only (no IaC — see [[add-unifi-pulumi-stack]]) |
| **Power** | Battery-backed via UPS (see [[power]]) |
## What It Does
@ -26,9 +25,19 @@ Home WiFi router and network controller, managed via Pulumi IaC.
The UX7 is the home WiFi access point and network gateway. It provides:
- WiFi (main, guest, IoT networks)
- DHCP for `192.168.1.0/24`
- Built-in UniFi controller for managing adopted devices (switches, APs)
- Firewall and traffic management
- DHCP for all network subnets
- Built-in UniFi controller for managing adopted devices (switches)
- Zone-based firewall and traffic management
## Networks
| Network | VLAN | Subnet | Purpose |
|---------|------|--------|---------|
| Main | 1 (default) | 192.168.1.0/24 | Trusted devices (indri, sifaka, gilbert, mouse) |
| Guest | 2 | 192.168.2.0/24 | Visitors, internet-only |
| IoT | 3 | 192.168.3.0/24 | Smart devices (Frame TV, appliances) |
See [[segment-home-network]] for the full segmentation plan and firewall rules.
## Network Topology
@ -42,36 +51,29 @@ ISP Modem
└── gilbert (USB-C adapter)
```
All wired devices share the `192.168.1.0/24` subnet. The two daisy-chained UniFi Switch Flex Minis provide enough ports for all devices while using the UX7's single LAN port.
## Pulumi Configuration (Planned)
The Pulumi program will live in `pulumi/unifi/`:
- `__main__.py` — declares networks, WLANs, and firewall zones
- `Pulumi.home-network.yaml` — stack config (router URL, site)
- `sdks/unifi/` — generated Python SDK from `pulumi package add terraform-provider filipowm/unifi`
Provider: [filipowm/terraform-provider-unifi](https://github.com/filipowm/terraform-provider-unifi) v1.0.0, consumed via `pulumi package add terraform-provider`.
See [[add-unifi-pulumi-stack]] for the full implementation plan.
All wired devices share the default VLAN (192.168.1.0/24). The two daisy-chained UniFi Switch Flex Minis provide enough ports for all devices while using the UX7's single LAN port.
## Operations
| Task | Command |
|------|---------|
| Preview changes | `mise run unifi-preview` (planned) |
| Apply changes | `mise run unifi-up` (planned) |
| Web management | `https://192.168.1.1` |
| Task | Method |
|------|--------|
| Manage networks/WiFi/firewall | `https://192.168.1.1` web UI |
| Backup configuration | Settings → System → Backup |
| Restore from backup | Settings → System → Backup → Restore |
## Authentication
The provider uses an API key created in the UX7 control plane (Settings → Control Plane → API). The key is stored in 1Password (`op://blumeops/unifi/credential`) and injected via mise task environment variables.
Local admin account on the UX7. Credentials stored in 1Password (vault `blumeops`). WiFi passphrase stored in 1Password item "Radio New Vegas" (Wireless Router type) in vault `blumeops`.
## Why Not IaC?
Attempted Feb 2026 with the `ubiquiti-community/unifi` Terraform provider via Pulumi. A "no-op" update on the default LAN network reset undeclared properties, bricking the network and requiring a factory reset. The provider ecosystem is too immature for single-device infrastructure. See [[add-unifi-pulumi-stack]] for details.
## Related
- [[add-unifi-pulumi-stack]] - Implementation plan
- [[hosts]] - Device inventory
- [[power]] - UPS power chain
- [[indri]] - Primary server (wired connection required for management)
- [[tailscale]] - Tailnet networking
- [[segment-home-network]] — Network segmentation plan
- [[add-unifi-pulumi-stack]] — Previous IaC approach (abandoned)
- [[hosts]] — Device inventory
- [[power]] — UPS power chain
- [[indri]] — Primary server (wired connection)
- [[tailscale]] — Tailnet networking