Files
2026-08-02 21:41:19 -04:00

262 lines
17 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!--
Rackpad population settings.
This document is loaded by the script that shares its base name, so Invoke-RackpadPopulation.ps1 reads
Settings\Invoke-RackpadPopulation.xml unless the ConfigurationPath parameter says otherwise.
Ownership model.
Omada owns switches, access points, switch ports, cables, VLANs, and wireless.
UniFi owns the same shapes as Omada and can run alongside it.
Proxmox owns hypervisor hosts, bridges, guests, and guest network interfaces.
Hyper-V owns the same shapes as Proxmox and can run alongside it.
OPNsense owns VLANs, subnets, and the routed edge.
The live sources are authoritative. This document cannot create devices, VLANs, subnets, or SSIDs; it only supplies what no API
knows, which is the physical layout consisting of rooms, racks, and rack unit placement, together with the address ranges that
serve them.
Rooms and racks are optional. Leave them empty and discovered equipment stays loose within the lab. Declare them and they are
created, and every Placement entry that names them files the matching equipment into them.
DHCP is served elsewhere and is deliberately out of scope. No DHCP scope is created, updated, or deleted, and IP assignments are
always written using the static allocation mode. Leave DHCP as a manual concern within Rackpad.
Value expansion happens before this document is parsed, in three passes over the raw text.
1. Environment variables in the %NAME% form are expanded.
2. PowerShell expressions are expanded against the current execution context, so $env:NAME, $($env:NAME), and any other
PowerShell variable or subexpression resolves. User and machine scoped environment variables are promoted into the process
first, so a variable created after the shell started still resolves.
3. The expanded text is loaded into an XmlDocument and navigated using XPath.
Because pass two evaluates PowerShell, treat this document with the same care as a script, and escape any literal dollar sign
that is not meant as an expression using a backtick.
Secrets belong in CDATA sections so that reserved XML characters survive unchanged. Prefer keeping the value itself in an
environment variable and referencing it here.
-->
<RackpadPopulation Version="2026.08.02.0000">
<Instances>
<Instance Name="Primary" Enabled="false">
<!-- Rackpad connection. -->
<Rackpad BaseURL="$env:RACKPAD_BASE_URL" SkipCertificateCheck="false">
<Username><![CDATA[$env:RACKPAD_USERNAME]]></Username>
<Password><![CDATA[$env:RACKPAD_PASSWORD]]></Password>
</Rackpad>
<!-- The lab is resolved by name. When CreateIfMissing is true a lab with this name is created on the first run. -->
<Lab Name="Primary Lab"
Description="Layer two switching and wireless from the controller, layer three from the virtualized edge firewall."
Location=""
CreateIfMissing="true" />
<!--
PruneManagedObjects Deletes objects that a previous run of this script created and that are no longer wanted.
Objects created by hand within the Rackpad user interface are never touched.
ManageIPAssignments Writes an IP assignment for each management address and each recognized client address.
ManagePortLinks Writes cable links derived from controller client sessions when both endpoints are known.
ManageWireless Writes the controller, SSIDs, access point metadata, radios, and client associations.
ManageClientEndpoints Creates an endpoint device per wireless client. Rackpad refuses a client association whose client
is not an existing device, so wireless associations are only written when this is enabled.
Leave false unless you want every phone and television in the inventory.
StateDirectory Where the managed object state document is written. Leave empty to use the Data directory
beside the script, which keeps the solution self contained and portable.
-->
<Options PruneManagedObjects="true"
ManageIPAssignments="true"
ManagePortLinks="true"
ManageWireless="true"
ManageClientEndpoints="false"
StateDirectory="" />
<Layout>
<!-- Remove or empty these elements to leave every discovered device loose within the lab. -->
<Rooms>
<Room Name="Server Room" Description="Primary equipment room." Location="" Enabled="true">
<Notes><![CDATA[Core switching, hypervisors, and the routed edge live here.]]></Notes>
</Room>
</Rooms>
<Racks>
<Rack Name="RACK-01" Room="Server Room" TotalU="22" Description="Primary rack." Location="1st Floor" Enabled="true" />
</Racks>
<!--
Placement pins discovered equipment to a physical position without having to name every device.
InclusionExpression and ExclusionExpression are regular expressions, matched case insensitively against whichever
value MatchBy selects: Hostname, MAC, or Serial. A rule matches when the inclusion expression matches and the
exclusion expression, if present, does not.
Rules are evaluated in document order and the FIRST match wins, so specific rules go above general ones, exactly as
in an access list. That means a broad catch-all can sit at the bottom and carve-outs above it.
Supplying a Rack implies rack placement; supplying only a Room leaves the device as loose room technology.
Equipment matching no rule stays loose.
StartU is optional. Omit it in a broad rule and matching devices are assigned to the rack without a fixed rack unit,
which you can then arrange in the user interface. Only name a rack unit on a rule that matches a single device.
The literal Match attribute from earlier versions is still accepted and is treated as an anchored exact expression.
-->
<Placements>
<!-- Wireless first, so the catch-all below never claims an access point. -->
<Placement InclusionExpression=".*-AP-.*" MatchBy="Hostname" Room="Server Room" Enabled="false" />
<!-- The core switch is the one device worth pinning to a specific rack unit. -->
<Placement InclusionExpression=".*-SW-0*1$" MatchBy="Hostname" Rack="RACK-01" StartU="42" HeightU="1" Face="front" RackSlot="full" Enabled="false" />
<!-- Everything else racks, except storage and anything explicitly excluded. -->
<Placement InclusionExpression=".*" ExclusionExpression=".*NAS.*|.*STORAGE.*" MatchBy="Hostname" Rack="RACK-01" HeightU="1" Face="front" RackSlot="full" Enabled="false" />
</Placements>
</Layout>
<Networking>
<!--
Address ranges become IP zones inside the subnet that the routed edge reported. The parent subnet must already be
discovered by a source; this document cannot create it. Kind accepts static, reserved, or infrastructure. A DHCP
kind is rejected and downgraded to static because DHCP is out of scope.
Room and Rack are recorded against the zone description so that the range stays attributable in the user interface.
-->
<IPRanges>
<IPRange Subnet="10.0.0.0/24" Kind="infrastructure" StartIP="10.0.0.1" EndIP="10.0.0.31" Description="Network equipment" Room="Server Room" Rack="RACK-01" Enabled="false" />
<IPRange Subnet="10.0.0.0/24" Kind="static" StartIP="10.0.0.32" EndIP="10.0.0.127" Description="Static server assignments" Room="Server Room" Enabled="false" />
<IPRange Subnet="10.0.0.0/24" Kind="reserved" StartIP="10.0.0.240" EndIP="10.0.0.254" Description="Reserved" Enabled="false" />
</IPRanges>
<!--
The public address lookup is an outbound request to a third party geolocation service. Only the request leaves the
environment; no inventory data is transmitted. The result is written as a slash thirty two subnet carrying the
internet service provider detail, and is attributed to EdgeHostname when supplied, otherwise to the first
discovered firewall or router.
ProviderURL understands the ipinfo.io and ip-api.com response shapes.
-->
<PublicIP Enabled="false" ProviderURL="https://ipinfo.io/json" EdgeHostname="" />
</Networking>
<Sources>
<!--
Omada. Two transports exist and the richer one is preferred.
Username and Password are a controller login and unlock the controller interface, which carries switch uplinks and
downlinks with both port numbers, per port LLDP neighbours, VLAN definitions with gateway subnets, and the real SSID
configuration. A Viewer role account is sufficient because nothing is ever written.
ClientID and ClientSecret are Open API credentials, created under Global View, Settings, Platform Integration, Open
API. This transport is a published, versioned contract but is narrower: no uplinks at all, and no SSID configuration,
which forces SSIDs to be inferred from whichever clients happen to be associated.
Supply both and the controller interface is used with the Open API held in reserve, so an expired session or a route a
given controller release does not implement costs only that request rather than the whole collection. Set
PreferControllerInterface to false to pin collection to the Open API. Supply only one and that one is used.
Controllers usually present a self signed certificate, so SkipCertificateCheck defaults to true.
Remove the Sites element entirely to process every site the credentials can reach.
-->
<Omada Enabled="false" BaseURL="$env:OMADA_API_BASE_URL" SkipCertificateCheck="true" PreferControllerInterface="true">
<ClientID><![CDATA[$env:OMADA_API_CLIENT_ID]]></ClientID>
<ClientSecret><![CDATA[$env:OMADA_API_CLIENT_SECRET]]></ClientSecret>
<Username><![CDATA[$env:OMADA_API_USERNAME]]></Username>
<Password><![CDATA[$env:OMADA_API_PASSWORD]]></Password>
<Sites>
<Site Name="Site Name" Enabled="false" />
</Sites>
</Omada>
<!--
UniFi Network. The same two transport arrangement as Omada, for the same reason.
Username and Password are a controller login and unlock the network interface, which carries per port media, LLDP
neighbours, uplinks with the remote port, network definitions with their subnets, and the real wireless configuration.
A read only administrator is sufficient because nothing is ever written.
APIKey is an integration API key, created under Settings, Control Plane, Integrations. That transport is published and
versioned but narrower, covering sites, devices, and clients without port or topology detail.
Supply both and the network interface is used with the integration API held in reserve. Set PreferControllerInterface
to false to pin collection to the integration API.
Both deployment styles are detected rather than configured. A UniFi OS host such as a Dream Machine, Cloud Key Gen2, or
UniFi OS Server signs in at /api/auth/login and proxies the network application beneath /proxy/network. An older self
hosted controller signs in at /api/login and serves it from the root, usually on port 8443.
Remove the Sites element entirely to process every site. Site names are matched against the description shown in the
user interface, or against the short site key.
-->
<Unifi Enabled="false" BaseURL="$env:UNIFI_API_BASE_URL" SkipCertificateCheck="true" PreferControllerInterface="true">
<Username><![CDATA[$env:UNIFI_API_USERNAME]]></Username>
<Password><![CDATA[$env:UNIFI_API_PASSWORD]]></Password>
<APIKey><![CDATA[$env:UNIFI_API_KEY]]></APIKey>
<Sites>
<Site Name="Default" Enabled="false" />
</Sites>
</Unifi>
<!--
Proxmox Virtual Environment. Create an API token under Datacenter, Permissions, API Tokens and grant it PVEAuditor
on the root path. TokenID uses the USER@REALM!TOKENNAME form.
List one endpoint per cluster and one per standalone host. Querying any single member of a cluster returns every node
in that cluster, so a five node cluster needs only one entry. Separate clusters cannot see one another, so each needs
its own entry with its own token. Name is a label; when supplied it overrides whatever the endpoint reports, which is
how a standalone host with no cluster name still reads sensibly. If two endpoints overlap and report the same node,
the duplicate is collapsed rather than written twice.
-->
<Proxmox Enabled="false">
<Endpoints>
<Endpoint Name="Cluster A" BaseURL="$env:PROXMOX_API_BASE_URL" SkipCertificateCheck="true" IncludeTemplates="false" Enabled="false">
<TokenID><![CDATA[$env:PROXMOX_API_TOKEN_ID]]></TokenID>
<TokenSecret><![CDATA[$env:PROXMOX_API_TOKEN_SECRET]]></TokenSecret>
</Endpoint>
<Endpoint Name="Standalone Host" BaseURL="" SkipCertificateCheck="true" IncludeTemplates="false" Enabled="false">
<TokenID><![CDATA[]]></TokenID>
<TokenSecret><![CDATA[]]></TokenSecret>
</Endpoint>
</Endpoints>
</Proxmox>
<!--
Hyper-V. Requires the Hyper-V module on the machine running this script. Hosts are reached through a CIM session,
so Username and Password are host credentials. Set UseDCOM when PowerShell remoting is unavailable.
The guest operating system is read from the integration services key value pair exchange, which works remotely and
needs no guest credential. UseGuestIntrospection additionally attempts PowerShell Direct for a more precise answer,
which only works when this script runs on the Hyper-V host itself and a guest credential is supplied.
-->
<HyperV Enabled="false" ClusterName="" UseDCOM="false" UseGuestIntrospection="false">
<Hosts>
<Host Name="HYPERVISOR-01" Enabled="false" />
</Hosts>
<Username><![CDATA[$env:HYPERV_USERNAME]]></Username>
<Password><![CDATA[$env:HYPERV_PASSWORD]]></Password>
<GuestUsername><![CDATA[$env:HYPERV_GUEST_USERNAME]]></GuestUsername>
<GuestPassword><![CDATA[$env:HYPERV_GUEST_PASSWORD]]></GuestPassword>
</HyperV>
<!--
OPNsense. Create the key and secret under System, Access, Users, then the API keys section of the user.
Hostname is the name that the firewall guest carries within its hypervisor, which is how the two sources are joined
so that the routed edge appears as a virtual device on its host rather than as a second unrelated record.
ParentHostname is only used when no hypervisor source is enabled and the firewall still needs a host to hang from.
-->
<OPNsense Enabled="false" BaseURL="$env:OPNSENSE_API_BASE_URL" SkipCertificateCheck="true" Hostname="" ParentHostname="">
<APIKey><![CDATA[$env:OPNSENSE_API_KEY]]></APIKey>
<APISecret><![CDATA[$env:OPNSENSE_API_SECRET]]></APISecret>
</OPNsense>
</Sources>
</Instance>
</Instances>
</RackpadPopulation>