Skip to content

feat(unifi): converge-unifi — codify the WAN :443 port-forward (#221/#102) - #225

Merged
ChrisonSimtian merged 2 commits into
mainfrom
feat/unifi-portforward-shape-221
Jun 30, 2026
Merged

feat(unifi): converge-unifi — codify the WAN :443 port-forward (#221/#102)#225
ChrisonSimtian merged 2 commits into
mainfrom
feat/unifi-portforward-shape-221

Conversation

@ChrisonSimtian

Copy link
Copy Markdown
Collaborator

Completes the UniFi half of #221 — codifies the WAN :44310.10.0.13:443 port-forward to Pangolin that ADR-0007 stood up by hand. (The Cloudflare wildcard-DNS half is #223; #221 closes when both land.) Unblocks the write-path goal of #102.

What

A new converge-unifi engine command reconciles a declarative UniFi network desired-state via UnifiSharp's legacy write adapter (merged in UnifiSharp#12; the official integration API can't write port-forwards yet).

  • Infrastructure/unifi/network.yaml (kind: UnifiNetwork) — declares the pangolin-https port-forward. Pairs with the grey-cloud *.lab/*.arr A-records from feat(converge): wire wildcard grey-cloud A records into Pangolin (#221) #223: DNS points the hostnames at the home WAN IP, this forwards :443 in.
  • Unifi/UnifiNetworkSpec + UnifiConverge — YAML model, a pure add-only planner (creates declared rules missing by name; never deletes hand-managed ones — same guardrail as the Cloudflare reconcile, CLAUDE.md), ToLegacy mapping, and apply.
  • converge-unifi <file> [--apply]dry-run by default; --apply writes. Auth is the classic controller session (UNIFI_LEGACY_BASE_URL / UNIFI_USERNAME / UNIFI_PASSWORD).
  • Pin UnifiSharp 0.1.0-preview.27 (exact, like ProxmoxSharp — a GH-Packages index lag can't silently grab an older preview without the Legacy namespace) and bump the vendored submodule to the matching merged commit.

Verified end-to-end (against .containers/unifi)

converge-unifi … — dry-run →  ~ pangolin-https → wan :443 ⇒ 10.10.0.13:443/tcp (would create)
converge-unifi … --apply →    + pangolin-https … (created)
converge-unifi … — dry-run →  = pangolin-https (present) → nothing to do   # idempotent, add-only

Plus 5 unit tests (add-only plan, case-insensitive match, never-delete-unmanaged, field mapping, YAML load). 89 engine tests green.

Auth note for live use

The reconcile uses session auth (proven against the test container). To run it against the live Cloud Gateway, set UNIFI_LEGACY_BASE_URL + a local-admin UNIFI_USERNAME/UNIFI_PASSWORD. (The live gateway also accepts its X-API-KEY on the legacy API; adding optional X-API-KEY auth to the adapter is a clean future follow-up if you'd rather reuse the existing UNIFI_API_KEY.) Like #223, this PR codifies + proves the reconcile without applying to the live gateway.

🤖 Generated with Claude Code

ChrisonSimtian and others added 2 commits June 30, 2026 14:36
…102)

Add a `converge-unifi` engine command that reconciles a declarative UniFi
network desired-state (Infrastructure/unifi/network.yaml, kind: UnifiNetwork)
via UnifiSharp's legacy write adapter — codifying the WAN :443 → 10.10.0.13:443
port-forward to Pangolin that ADR-0007 stood up by hand. This is the UniFi half
of #221 (the Cloudflare wildcard DNS half is #223); unblocks #102.

- Infrastructure/unifi/network.yaml: declares the pangolin-https port-forward.
- Unifi/UnifiNetworkSpec + UnifiConverge: YAML model + a pure, add-only planner
  (create declared rules missing by name; never delete hand-managed ones — same
  guardrail as the Cloudflare reconcile) + ToLegacy mapping + apply.
- Program.cs: `converge-unifi <file> [--apply]` — dry-run by default. Auth is the
  classic controller session (UNIFI_LEGACY_BASE_URL / UNIFI_USERNAME / PASSWORD).
- Pin UnifiSharp 0.1.0-preview.27 (exact, like ProxmoxSharp) — the preview with
  the Legacy namespace; bump the vendored submodule to the matching merged commit.
- Tests: 5 unit (add-only plan, case-insensitive match, no-delete, mapping, YAML
  load). Verified end-to-end against .containers/unifi: dry-run → --apply
  (created) → re-plan (present, idempotent). 89 engine tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ValidateShapes CI gate runs `validate` over Infrastructure/ + stacks/, so the
new Infrastructure/unifi/network.yaml must satisfy shape.schema.json. Add the
UnifiNetwork kind + a strict unifiNetworkSpec (portForwards). Port fields accept
string|integer because the validator's YAML→JSON bridge coerces digit-strings to
integers; the engine model reads them as strings (allowing ranges like 8080-8090).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ChrisonSimtian
ChrisonSimtian merged commit c079bae into main Jun 30, 2026
6 checks passed
@ChrisonSimtian
ChrisonSimtian deleted the feat/unifi-portforward-shape-221 branch June 30, 2026 08:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant