Background

  • Old code derived default gateway from config.DirectoryService.Get<IRouter>().SingleOrDefault().IP.

  • Wrong for greenfield: firewall (OPNsense, etc.) doesn’t exist as VM yet at install time.

  • Proxmox host installer (answer.toml), DC static-IP setup (Stage3Windows), and Linux/PE config emit (MainWinPE) all need a real reachable gateway during install - not a future VM IP.

  • Hence: split gateway-during-install (real upstream) from router-as-firewall (future VM, may not exist yet).

Phase 1 - Upstream gateway (current)

  • NETSetupConfig.GatewayIP carries the actual upstream port IP.

  • ISP/DC gateway, customer’s existing network gateway, or test bench’s real router.

  • All gateway-emitting code paths read GatewayIP first.

  • Fallback to Get<IRouter>().IP only when GatewayIP == IP.None (legacy customer JSONs).

  • Test fixture auto-populates GatewayIP from the Hyper-V external switch’s bound NIC default route:

  • VirtualHyperVTestBase.EnsureFreeIP

  • VirtualHyperVTestBase.PatchDhcpToHostGateway

  • Test bench reuses the real upstream - no fake gateway baked into fixtures.

LIBERATOR EXCEPTION (2026-05-27): ProxmoxAutoInstallExtensions.ToProxmoxAutoInstall now Liberator-gates the install-time network section. When the config has an OPNsense VM targeted at this host’s serial (IsLiberatorHost), the answer.toml emits 10.0.0.200/24 gw 10.0.0.1 dns 10.0.0.1 (Mikrotik factory LAN) regardless of GatewayIP / customer OU subnet. Reason: on a fresh Liberator install the customer OU subnet does not exist as a routable subnet - only the Mikrotik factory IP is reachable. All Phase 1/2 IPs come from src/NETSetup/Helpers/LiberatorNetwork.cs. See docs/Liberator.adoc "Proxmox Host IP - Two Phases" for the full table.

Phase 2 - Firewall takeover (shipped)

  • Trigger: OPNsense VM provisioned by EnsureCustomerVms AND reachable on its LAN IP via HTTPS:443.

  • Helper: src/NETSetup/Helpers/EnsureGatewayTakeover.cs. Runs every netsetup update cycle at Phase 3e (after ConfigureOpnsenseVlans + RewireVmNicsToVlans).

  • Idempotent skip when config.GatewayIP already == OPNsense LAN IP. Best-effort: failures log warning + continue.

  • After flip:

  • Proxmox host: awk-in-place rewrite of gateway line under iface vmbr0 in /etc/network/interfaces + ifreload -a.

  • In-memory: config.GatewayIP = opnsenseLan so NixOS VMs re-template networking.defaultGateway next UpdateNixosVm cycle.

  • Customer JSON on disk keeps Phase 1 upstream IP unchanged (by design - reinstall must start from real upstream).

  • Outcomes (unit-test enum): NoOpnsense, OpnsenseUnreachable, AlreadyTakenOver, FlippedToOpnsense.

LIBERATOR PHASE 2 NOTE (2026-05-27): EnsureGatewayTakeover flips the customer-OU gateway line on iface vmbr0. On a Liberator host, ConfigureProxmoxBridges separately writes the mgmt sub-interface vmbr0v250 at 10.0.250.2/24 gw 10.0.250.1 (Mikrotik mgmt, NOT OPNsense), which becomes the host’s default route. The two helpers are orthogonal: takeover deals with the vmbr0 (LAN VLAN routing for VM upstream), ConfigureProxmoxBridges deals with the vmbr0v250 (host OOB mgmt). On Liberator both run in Phase 3e in this order: ConfigureOpnsenseVlansConfigureMikrotikVlansConfigureProxmoxBridgesRewireVmNicsToVlansEnsureGatewayTakeover.

Code map (where gateway is read today)

  • src/NETSetup/Extensions/ProxmoxAutoInstallExtensions.cs - answer.toml network-manual section. Liberator gate (IsLiberatorHost) forces 10.0.0.200/24 gw 10.0.0.1 for Liberator hosts; non-Liberator stays on GatewayIP.

  • src/NETSetup/Stages/2-WinPE/MainWinPE.cs - Linux config bundle written to USB SYSTEM partition.

  • src/NETSetup/Stages/3-NETSetupWindows/Stage3Windows.cs + WindowsServerMethods.SetIPv4Configuration - DC static IPv4.

  • src/NETSetup/Helpers/EnsureGatewayTakeover.cs - Phase 2 host gateway flip on vmbr0 + in-memory config.GatewayIP rewrite (LAN gateway).

  • src/NETSetup/Helpers/ConfigureProxmoxBridges.cs - Liberator-only Phase 2 mgmt sub-iface vmbr0v250 rewrite at 10.0.250.2/24 gw 10.0.250.1 dns NixDns. Idempotent, only ifreload -a on diff.

  • src/NETSetup/Helpers/LiberatorNetwork.cs - public const holder for ALL Liberator Phase 1/2 IPs + mgmt VLAN id + bond/bridge names. Consumed by ProxmoxAutoInstallExtensions, ConfigureProxmoxBridges, MikrotikDesiredStateBuilder. No literals elsewhere.

  • src/NETSetup/Config/Nix/Templates/NixTemplateFactory.cs - reads config.GatewayIP each emit, so NixOS VMs follow Phase 2 flip automatically. Also injects ExtraARecords["proxmox"] = 10.0.250.2 for Liberator NixDns configs so proxmox.<domain> resolves once Phase 2 has run.

Open follow-ups

  • Windows DC NIC rewrite: extend WindowsServerMethods.SetIPv4Configuration (PSDirect) so existing Windows VMs follow the Phase 2 flip. Today: Windows VMs keep their install-time gateway until reinstall.

  • Multi-firewall HA / VRRP: gateway should track the VRRP virtual IP, not a single firewall’s LAN IP.