# scripts/lib-hosts.sh
#
# Single source of truth for the four OpenStack KVM host VMs (VR0 / Baldurkeep):
# their enrollment identity and virsh power parameters. SOURCED, not executed.
# ASCII + LF. Contains constants + read-only helpers ONLY; no mutations.
#
# WHY hostname-keyed (NOT system_id-keyed): MAAS system_ids are minted fresh on
# every (re-)enrollment. The old 4na83t/qdbqd6/h8frng/tmsafc died when the hosts
# were decomposed on 2026-06-26 and re-enrollment assigned new random ids. The
# stable identities are the hostname and the libvirt domain name, so every map
# here keys on hostname and the live system_id is resolved AT RUNTIME via
# host_sysid(). This SUPERSEDES the SYSID-keyed maps in lib-net.sh (DOCFIX-040).
#
# NO SECRET lives here. The libvirt SSH password is read interactively at run time
# (reenroll-hosts.sh) and is never written to a file, a command line, or the repo.
# shellcheck shell=bash
# shellcheck disable=SC2034 # constants are consumed by sourcing scripts
# Guard: sourced only.
if [ "${BASH_SOURCE[0]:-}" = "${0}" ]; then
echo "lib-hosts.sh is a sourced library; do not run it directly." >&2
exit 2
fi
# The four OpenStack KVM hosts. libvirt domain name == MAAS hostname == power_id.
HOSTS=( openstack0 openstack1 openstack2 openstack3 )
# host -> last IPv4 octet on every plane (.40-.43). Stable by design (D-052 index).
declare -A HOST_OCTET=( [openstack0]=40 [openstack1]=41 [openstack2]=42 [openstack3]=43 )
# host -> PXE/boot NIC MAC == the 2_metal interface (libvirt <boot order='1'/>).
# Fixed in the libvirt domain XML; captured 2026-06-26. The boot NIC MUST be the
# metal/PXE plane or commissioning cannot DHCP/PXE.
declare -A HOST_BOOT_MAC=(
[openstack0]=52:54:00:4f:1c:0b
[openstack1]=52:54:00:83:25:1f
[openstack2]=52:54:00:23:bd:72
[openstack3]=52:54:00:b2:7b:30
)
# virsh power (non-secret). MAAS reaches libvirt over SSH on the OOB host address.
# Mirrors the surviving juju/lxd/tailscale virsh machines' live config (read
# 2026-06-26). power_id is set per host to the hostname by the caller.
VIRSH_POWER_ADDRESS="qemu+ssh://logxen@10.12.64.1/system"
HOST_ARCH="amd64"
# MAAS tag the deploy bundle places units against (constraint tags=openstack).
# Must be (re-)applied to every host post-commission or the bundle cannot bind.
HOST_TAG="openstack"
# host_sysid <hostname>: resolve the LIVE MAAS system_id by hostname (never
# hardcode it). Echoes the system_id, or empty if the host is not enrolled.
# VR0 ONLY: VR0 hostnames are stable (hostname == domain name == power_id).
# For VR1 DC nodes the MAAS hostname is a RANDOM name RE-MINTED on every
# (re-)enlistment (measured: the 2026-07-21 pod-delete recovery renamed all 9)
# -- use host_sysid_by_bootmac() there instead.
host_sysid() {
local hn="$1"
maas "${MAAS_PROFILE:-admin}" machines read 2>/dev/null \
| jq -r --arg h "$hn" '.[] | select(.hostname==$h) | .system_id' | head -1
}
# host_sysid_by_bootmac <mac>: resolve the LIVE MAAS system_id by boot-NIC MAC.
# THE VR1 resolver: VR1 node identity is the tofu node name -> its config-PINNED
# boot MAC (opentofu/vr1-dcN-substrate; pinned 2026-07-21/22); the MAC survives
# re-enlistment, the random hostname does not.
host_sysid_by_bootmac() {
local mac="$1"
maas "${MAAS_PROFILE:-admin}" machines read 2>/dev/null \
| jq -r --arg m "$mac" '.[] | select(.boot_interface.mac_address==$m) | .system_id' | head -1
}
# --- $DC selector convention (2026-07-09, tooling gap register #1) ---
#
# Mirrors lib-net.sh's lib_net_select_dc -- SAME region-qualified selectors
# (D-119). The VR1 arms were POPULATED 2026-07-23 (Stage 4 carve session) from
# MEASURED enrollment: both fleets commissioned Ready, identities cross-checked
# tofu-name -> pinned boot MAC -> live system_id 9/9 per DC
# (docs/audit/stage4-carve-verify-20260723.txt). Until then both VR1 arms
# failed loud by design; that era's asymmetry note is history (git).
#
# VR1 IDENTITY MODEL (differs from VR0 -- read before consuming these maps):
# - HOSTS entries are the TOFU NODE NAMES (config-of-record, stable), NOT
# MAAS hostnames -- MAAS names are random and re-minted on re-enlistment.
# - Resolve a live system_id via host_sysid_by_bootmac "${HOST_BOOT_MAC[$h]}"
# (never via host_sysid; never cache system_ids).
# - HOST_OCTET carries the D-134-AMENDED per-role bands (control .100-.102,
# compute .120-.121, storage .150-.153), same octet on all six planes
# (D-133 flat carve -- applied + verified live, capture above).
# - VIRSH_POWER_ADDRESS is the per-DC rack transit URI (ruled step-D shape,
# per-machine virsh power -- D-103/D-123 amendments; SEC-012/SEC-016 keys).
# - HOST_TAG is the region-qualified per-DC placement tag (ruled 2026-07-23).
# ONE SELECTION PER SHELL: cross-DC re-selection is REFUSED (stale-value
# guard, lib-net precedent); same-DC re-selection is a no-op.
_LIB_HOSTS_SELECTED=""
lib_hosts_select_dc() {
local dc="${1:?usage: lib_hosts_select_dc <vr0-dc0|vr1-dc0|vr1-dc1>}"
if [ -n "$_LIB_HOSTS_SELECTED" ] && [ "$_LIB_HOSTS_SELECTED" != "$dc" ]; then
echo "FAIL: lib-hosts already selected '$_LIB_HOSTS_SELECTED' in this shell -- start a fresh shell to select '$dc' (stale cross-DC host values are the D-117 off-by-one class)" >&2
return 1
fi
case "$dc" in
vr0-dc0)
: # already the sourced defaults above -- explicit no-op, not a guess.
;;
vr1-dc0)
# MEASURED 2026-07-23 (Stage 4): 9/9 Ready; MACs are the config pins
# (opentofu/vr1-dc0-substrate, pinned from virsh domiflist 2026-07-21);
# octets applied live per D-134 amendment; tag applied 9/9 same day.
HOSTS=( vr1-dc0-control-01 vr1-dc0-control-02 vr1-dc0-control-03
vr1-dc0-compute-01 vr1-dc0-compute-02
vr1-dc0-storage-01 vr1-dc0-storage-02 vr1-dc0-storage-03 vr1-dc0-storage-04 )
HOST_OCTET=( [vr1-dc0-control-01]=100 [vr1-dc0-control-02]=101 [vr1-dc0-control-03]=102
[vr1-dc0-compute-01]=120 [vr1-dc0-compute-02]=121
[vr1-dc0-storage-01]=150 [vr1-dc0-storage-02]=151
[vr1-dc0-storage-03]=152 [vr1-dc0-storage-04]=153 )
HOST_BOOT_MAC=( [vr1-dc0-control-01]=52:54:00:be:69:c5
[vr1-dc0-control-02]=52:54:00:02:ff:57
[vr1-dc0-control-03]=52:54:00:4f:de:a9
[vr1-dc0-compute-01]=52:54:00:1b:19:e6
[vr1-dc0-compute-02]=52:54:00:18:ab:b4
[vr1-dc0-storage-01]=52:54:00:5f:8d:42
[vr1-dc0-storage-02]=52:54:00:48:86:2c
[vr1-dc0-storage-03]=52:54:00:b1:94:d1
[vr1-dc0-storage-04]=52:54:00:2b:ed:ab )
VIRSH_POWER_ADDRESS="qemu+ssh://jessea123@172.31.0.2/system"
HOST_TAG="openstack-vr1-dc0"
;;
vr1-dc1)
# MEASURED 2026-07-23 (Stage 4): 9/9 Ready; MACs are the config pins
# (opentofu/vr1-dc1-substrate, 52:54:01:d1:NN:01 encodes the node index);
# octets applied live per D-134 amendment; tag applied 9/9 same day.
HOSTS=( vr1-dc1-control-01 vr1-dc1-control-02 vr1-dc1-control-03
vr1-dc1-compute-01 vr1-dc1-compute-02
vr1-dc1-storage-01 vr1-dc1-storage-02 vr1-dc1-storage-03 vr1-dc1-storage-04 )
HOST_OCTET=( [vr1-dc1-control-01]=100 [vr1-dc1-control-02]=101 [vr1-dc1-control-03]=102
[vr1-dc1-compute-01]=120 [vr1-dc1-compute-02]=121
[vr1-dc1-storage-01]=150 [vr1-dc1-storage-02]=151
[vr1-dc1-storage-03]=152 [vr1-dc1-storage-04]=153 )
HOST_BOOT_MAC=( [vr1-dc1-control-01]=52:54:01:d1:01:01
[vr1-dc1-control-02]=52:54:01:d1:02:01
[vr1-dc1-control-03]=52:54:01:d1:03:01
[vr1-dc1-compute-01]=52:54:01:d1:04:01
[vr1-dc1-compute-02]=52:54:01:d1:05:01
[vr1-dc1-storage-01]=52:54:01:d1:06:01
[vr1-dc1-storage-02]=52:54:01:d1:07:01
[vr1-dc1-storage-03]=52:54:01:d1:08:01
[vr1-dc1-storage-04]=52:54:01:d1:09:01 )
VIRSH_POWER_ADDRESS="qemu+ssh://jessea123@172.31.0.6/system"
HOST_TAG="openstack-vr1-dc1"
;;
dc0|dc1|dc2)
echo "FAIL: bare '$dc' is RETIRED (D-119). It was AMBIGUOUS ACROSS REGIONS: 'dc0' meant VR0's live DC0 here, but VR1's FIRST DC in the NetBox importer. Use the region-qualified selector: vr0-dc0 | vr1-dc0 | vr1-dc1" >&2
return 1
;;
*)
echo "FAIL: unknown DC '$dc' (expected vr0-dc0, vr1-dc0, or vr1-dc1)" >&2
return 1
;;
esac
_LIB_HOSTS_SELECTED="$dc"
}