Skip to content

Roadmap: bring the home UniFi network under declarative management #2

Description

@devantler

🤖 Generated by the Daily AI Engineer

Roadmap: bring the home UniFi network under declarative management

Reconciled 2026-07-18. This epic had drifted badly: it described the repo as an empty
OpenTofu/Terraform tenant awaiting import-first onboarding, but the repo carries no .tf files at
all
— its desired state is now Crossplane managed resources, and several themes below were either
delivered in a different form or retired outright. Rewritten against the repo's actual contents so
the roadmap of record stops pointing at an architecture that no longer exists. Everything asserted
here was checked against main today; see Evidence at the end.

Where the product is. devantler-tech/unifi holds the home network's desired state as
Crossplane managed resources, reconciled against the UniFi controller API by
provider-upjet-unifi:

  • wireguard-vpn-client.yaml — a vpn.../Client (the gateway dials the cluster's WireGuard server,
    so the dynamic residential IP never needs to be reachable) plus its TrafficRoute.
  • admin-dns.yamldns.../Records pointing the admin hostnames at the in-tunnel VIP.
  • kustomization.yaml — what the platform reconciles.

CI (Validate (Crossplane manifests)) builds with kustomize and validates with kubeconform.
Onboarding an object that already exists in the controller is adopt-first — set
crossplane.io/external-name to its UniFi id — which is the Crossplane successor to the old
import-first rule, and is documented in docs/runbook.md.

Where it should be. The whole home network — L3 networks/VLANs, WLANs, firewall rules,
port-forwards, DNS — under the same declarative management, reproducible and drift-corrected, with
the VPN path actually carrying traffic.

Themes

T1 — Onboard the live network (the core mission). Partially delivered. The VPN client, its
traffic route, and the admin DNS records are authored as managed resources. The rest of the network —
core L3 networks/VLANs, WLANs/SSIDs, firewall rules + port-forwards — is still unmanaged, and each
category is an independent adopt-first PR: write the resource, set crossplane.io/external-name to
the existing object's id, and confirm reconciliation is a no-op before changing anything.

T2 — Safe reconcile & CI quality. Delivered in its current form: manifests are schema-validated in
CI on every PR. The old Terraform-era guards (the static import-first check and tflint) were retired
along with the .tf files they scanned — they are not a gap to reinstate. The remaining piece is a
pre-apply drift/no-op signal so a reviewer can see what a change would do to the live controller
before it merges.

T3 — Crossplane steady state. ✅ Delivered. This was gated on
monorepo #1860 ("build the real-CRD
Crossplane provider"), which closed as completed on 2026-07-03; the provider exists, is actively
developed, and is what reconciles this repo today. Ongoing work is provider currency rather than
migration — a provider release can change resource schemas, so a bump gets its changelog read and the
manifests re-validated.

T4 — Documentation & operability. ✅ Delivered. README.md, AGENTS.md and docs/runbook.md
describe the adopt-first Crossplane flow, so adding a resource is a checklist.

The one real blocker

Three values in wireguard-vpn-client.yaml are still placeholders, and all three resolve only once the
platform-side WireGuard server is provisioned — it is a devantler-tech/platform dependency, not
work this repo can do:

  • REPLACE_WITH_CONTROL_PLANE_PUBLIC_IP — the peer endpoint.
  • REPLACE_WITH_VPN_CLIENT_NETWORK_ID — the client's UniFi network id.
  • the admin-DNS VIP, which needs the in-tunnel gateway to exist.

Until then the VPN path cannot carry traffic, and the admin DNS records would point at an address that
answers nothing. The categories in T1 that do not touch the tunnel (VLANs, WLANs, firewall,
port-forwards) are NOT blocked by this
and are the available work.

Checklist

  • T1: VPN client + traffic route authored as managed resources
  • T1: admin DNS records authored as managed resources
  • T1: onboard core L3 networks/VLANs, adopt-first — unblocked, available now
  • T1: onboard WLANs/SSIDs, adopt-first — unblocked, available now
  • T1: onboard firewall rules + port-forwards, adopt-first — unblocked, available now
  • T2: manifests schema-validated in CI (kustomize + kubeconform)
  • T2: pre-apply drift / no-op preview signal
  • T3: Crossplane steady state reached — provider built and reconciling (monorepo #1860 completed)
  • T3: keep the provider current; re-validate manifests on a schema-affecting bump
  • T4: adopt-first runbook + operability docs

Evidence for this reconciliation

Checked against main on 2026-07-18: the repo tree contains 0 files ending in .tf; the tracked
manifests are wireguard-vpn-client.yaml, admin-dns.yaml and kustomization.yaml; .github/workflows/ci.yaml
runs kustomize build + kubeconform -strict; the three REPLACE_WITH_…/VIP placeholders are present
in the manifests as quoted above; and monorepo #1860 reports state: CLOSED, stateReason: COMPLETED,
closed 2026-07-03.

Problem → direction → rough size per child. The unblocked T1 categories are the highest-value
available work; the tunnel-dependent items wait on the platform.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Fields

    No fields configured for Epic.

    Projects

    Status
    📥 Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions