feat(ip-pool): add guest-side static-IP boot scripts
template/guest-setup/linux/ci-report-ip.sh Updated version: checks guestinfo.ip-assignment first (static path), falls back to DHCP detection if absent. Applies static IP before network-online.target so SSH starts on the correct address immediately. template/guest-setup/linux/ci-report-ip.service Drops 'network.target' from After= — runs before network is up so the static IP is configured before SSH/other services start. template/guest-setup/windows/ci-static-ip.ps1 Reads guestinfo.ip-assignment via vmware-rpctool at system startup (SYSTEM account Task Scheduler task). Disables DHCP, applies static IP+gateway, reports back via guestinfo.ci-ip. No-op when ip-assignment is absent. template/Install-CIToolchain-Linux2404.sh Step 7b now copies scripts from guest-setup/linux/ instead of embedding the old DHCP-only inline script. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -170,51 +170,16 @@ sudo netplan generate
|
|||||||
assert_step "99-ci-dhcp-allnics.yaml exists" test -f /etc/netplan/99-ci-dhcp-allnics.yaml
|
assert_step "99-ci-dhcp-allnics.yaml exists" test -f /etc/netplan/99-ci-dhcp-allnics.yaml
|
||||||
|
|
||||||
# --- Step 7b: CI IP reporter via VMware guestinfo ---
|
# --- Step 7b: CI IP reporter via VMware guestinfo ---
|
||||||
# The host reads the guest IP via `vmrun readVariable guestVar ci-ip`.
|
# Supports two modes:
|
||||||
# IMPORTANT: vmware-rpctool sets 'guestinfo.ci-ip' but vmrun reads it as just
|
# Static (ip_pool): reads guestinfo.ip-assignment injected by the host
|
||||||
# 'ci-ip' under the guestVar namespace (the 'guestinfo.' prefix is implicit).
|
# orchestrator, applies static NIC config, reports back.
|
||||||
# This is the official VMware VMCI channel — works even when TCP/IP is not yet
|
# DHCP fallback: waits for DHCP address, reports via guestinfo.ci-ip.
|
||||||
# fully initialised on the host. Used as primary; getGuestIPAddress is fallback.
|
# The script and service are sourced from template/guest-setup/linux/.
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Step 7b: CI IP reporter ==="
|
echo "=== Step 7b: CI IP reporter (with static IP support) ==="
|
||||||
sudo tee /usr/local/bin/ci-report-ip.sh > /dev/null << 'EOSCRIPT'
|
sudo cp "$(dirname "$0")/guest-setup/linux/ci-report-ip.sh" /usr/local/bin/ci-report-ip.sh
|
||||||
#!/bin/bash
|
|
||||||
# Writes the VM's primary IPv4 to VMware guestinfo.ci-ip on every boot.
|
|
||||||
# Called by ci-report-ip.service.
|
|
||||||
# Retries for up to 60s to tolerate open-vm-tools re-init after UUID change
|
|
||||||
# (linked clones get a new UUID — vmware-rpctool may fail briefly on first boot).
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
IP=$(ip -4 route get 1.0.0.0 2>/dev/null \
|
|
||||||
| awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' \
|
|
||||||
| head -1)
|
|
||||||
if [ -n "$IP" ]; then
|
|
||||||
if /usr/bin/vmware-rpctool "info-set guestinfo.ci-ip $IP" 2>/dev/null; then
|
|
||||||
echo "ci-report-ip: set guestinfo.ci-ip=$IP (attempt $i)" | systemd-cat -t ci-report-ip
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
echo "ci-report-ip: failed to set guestinfo.ci-ip after 30 attempts" | systemd-cat -t ci-report-ip
|
|
||||||
exit 1
|
|
||||||
EOSCRIPT
|
|
||||||
sudo chmod +x /usr/local/bin/ci-report-ip.sh
|
sudo chmod +x /usr/local/bin/ci-report-ip.sh
|
||||||
|
sudo cp "$(dirname "$0")/guest-setup/linux/ci-report-ip.service" /etc/systemd/system/ci-report-ip.service
|
||||||
sudo tee /etc/systemd/system/ci-report-ip.service > /dev/null << 'EOSERVICE'
|
|
||||||
[Unit]
|
|
||||||
Description=Report CI VM IP to VMware host via guestinfo
|
|
||||||
After=open-vm-tools.service network.target
|
|
||||||
Wants=open-vm-tools.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/local/bin/ci-report-ip.sh
|
|
||||||
RemainAfterExit=yes
|
|
||||||
TimeoutStartSec=120
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOSERVICE
|
|
||||||
|
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
sudo systemctl enable ci-report-ip.service
|
sudo systemctl enable ci-report-ip.service
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Report CI VM IP to VMware host via guestinfo
|
||||||
|
# Run after open-vm-tools so vmware-rpctool is available.
|
||||||
|
# Do NOT add network-online.target — we want to run BEFORE the network stack
|
||||||
|
# settles so static IP is applied before SSH/other services start.
|
||||||
|
After=open-vm-tools.service
|
||||||
|
Wants=open-vm-tools.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/local/bin/ci-report-ip.sh
|
||||||
|
RemainAfterExit=yes
|
||||||
|
TimeoutStartSec=120
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# CI VM IP reporter — called by ci-report-ip.service at boot.
|
||||||
|
#
|
||||||
|
# Phase C extension: reads guestinfo.ip-assignment from the VMX (written by the
|
||||||
|
# host orchestrator before vmrun start). If present, applies a static IP to
|
||||||
|
# the primary NIC and reports it back immediately via guestinfo.ci-ip, skipping
|
||||||
|
# DHCP entirely. If absent, falls back to the original DHCP-detect behaviour.
|
||||||
|
#
|
||||||
|
# The VMCI channel (vmware-rpctool) does not require TCP/IP to be initialised —
|
||||||
|
# it communicates via the VMware virtual hardware interface. This means the
|
||||||
|
# static IP can be configured before network-online.target, so by the time SSH
|
||||||
|
# starts listening the NIC already has the correct address.
|
||||||
|
#
|
||||||
|
# Install:
|
||||||
|
# sudo cp ci-report-ip.sh /usr/local/bin/ci-report-ip.sh
|
||||||
|
# sudo chmod +x /usr/local/bin/ci-report-ip.sh
|
||||||
|
# sudo cp ci-report-ip.service /etc/systemd/system/ci-report-ip.service
|
||||||
|
# sudo systemctl daemon-reload
|
||||||
|
# sudo systemctl enable ci-report-ip.service
|
||||||
|
|
||||||
|
RPCTOOL=/usr/bin/vmware-rpctool
|
||||||
|
[ -x "$RPCTOOL" ] || exit 0
|
||||||
|
|
||||||
|
# ── Check for pre-assigned IP from orchestrator ───────────────────────────────
|
||||||
|
assigned_ip=$("$RPCTOOL" "info-get guestinfo.ip-assignment" 2>/dev/null | tr -d '[:space:]')
|
||||||
|
|
||||||
|
if [[ "$assigned_ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
netmask=$("$RPCTOOL" "info-get guestinfo.netmask" 2>/dev/null | tr -d '[:space:]')
|
||||||
|
gateway=$("$RPCTOOL" "info-get guestinfo.gateway" 2>/dev/null | tr -d '[:space:]')
|
||||||
|
|
||||||
|
# Resolve primary NIC (first non-loopback).
|
||||||
|
iface=$(ip -o link show | awk -F': ' '$2 != "lo" {print $2; exit}')
|
||||||
|
if [ -z "$iface" ]; then
|
||||||
|
echo "ci-report-ip: no non-loopback interface found" | systemd-cat -t ci-report-ip
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Compute prefix length from netmask (default /24).
|
||||||
|
prefix=24
|
||||||
|
if [ -n "$netmask" ]; then
|
||||||
|
prefix=$(python3 -c \
|
||||||
|
"import ipaddress; print(ipaddress.IPv4Network('0.0.0.0/$netmask',strict=False).prefixlen)" \
|
||||||
|
2>/dev/null) || prefix=24
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Flush any existing config (DHCP or stale static) and apply.
|
||||||
|
ip addr flush dev "$iface" 2>/dev/null || true
|
||||||
|
ip addr add "${assigned_ip}/${prefix}" dev "$iface"
|
||||||
|
ip link set "$iface" up
|
||||||
|
|
||||||
|
if [[ "$gateway" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
ip route add default via "$gateway" dev "$iface" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Report IP back to host via guestinfo.ci-ip.
|
||||||
|
"$RPCTOOL" "info-set guestinfo.ci-ip $assigned_ip" 2>/dev/null
|
||||||
|
echo "ci-report-ip: applied static IP $assigned_ip/$prefix via guestinfo" \
|
||||||
|
| systemd-cat -t ci-report-ip
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Fallback: DHCP — wait for IP and report ───────────────────────────────────
|
||||||
|
# Retries for up to 60 s to tolerate open-vm-tools re-init after UUID change
|
||||||
|
# (linked clones get a new UUID — vmware-rpctool may fail briefly on first boot).
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
IP=$(ip -4 route get 1.0.0.0 2>/dev/null \
|
||||||
|
| awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}' \
|
||||||
|
| head -1)
|
||||||
|
if [ -n "$IP" ]; then
|
||||||
|
if "$RPCTOOL" "info-set guestinfo.ci-ip $IP" 2>/dev/null; then
|
||||||
|
echo "ci-report-ip: set guestinfo.ci-ip=$IP via DHCP (attempt $i)" \
|
||||||
|
| systemd-cat -t ci-report-ip
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "ci-report-ip: failed to set guestinfo.ci-ip after 30 attempts" \
|
||||||
|
| systemd-cat -t ci-report-ip
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Applies a pre-assigned static IP from VMware guestinfo at boot.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Called by the CI-StaticIp Task Scheduler task at system startup (SYSTEM
|
||||||
|
account). Reads guestinfo.ip-assignment written by the host orchestrator
|
||||||
|
before vmrun start. If present, disables DHCP and applies the static IP
|
||||||
|
before WinRM has a chance to start on a DHCP address, eliminating the
|
||||||
|
20-180 s VMware-Tools IP-detect polling loop.
|
||||||
|
|
||||||
|
If guestinfo.ip-assignment is absent (template used standalone or
|
||||||
|
ip_pool not configured), exits 0 immediately — DHCP runs normally.
|
||||||
|
|
||||||
|
Install into the template VM:
|
||||||
|
1. Copy this script to C:\CI\ci-static-ip.ps1
|
||||||
|
2. Create a Task Scheduler task:
|
||||||
|
Name: CI-StaticIp
|
||||||
|
Trigger: At System Startup
|
||||||
|
Action: powershell.exe -NoProfile -NonInteractive
|
||||||
|
-ExecutionPolicy Bypass -File C:\CI\ci-static-ip.ps1
|
||||||
|
Run As: SYSTEM
|
||||||
|
Run with highest privileges: Yes
|
||||||
|
3. Take the BaseClean snapshot from powered-OFF state.
|
||||||
|
|
||||||
|
The VMCI channel (vmware-rpctool) does not require TCP/IP — it works
|
||||||
|
via the VMware virtual hardware interface, so guestinfo is readable
|
||||||
|
before the NIC is configured.
|
||||||
|
#>
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
$vmtoolsRpc = 'C:\Program Files\VMware\VMware Tools\vmware-rpctool.exe'
|
||||||
|
if (-not (Test-Path $vmtoolsRpc -PathType Leaf)) { exit 0 }
|
||||||
|
|
||||||
|
function Read-GuestInfo {
|
||||||
|
param([string]$Key)
|
||||||
|
$savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
|
||||||
|
$out = & $vmtoolsRpc "info-get guestinfo.$Key" 2>&1
|
||||||
|
$ErrorActionPreference = $savedEap
|
||||||
|
if ($LASTEXITCODE -eq 0 -and $out -notmatch 'No value found|^$') {
|
||||||
|
return ($out | Out-String).Trim()
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
$ip = Read-GuestInfo 'ip-assignment'
|
||||||
|
$netmask = Read-GuestInfo 'netmask'
|
||||||
|
$gateway = Read-GuestInfo 'gateway'
|
||||||
|
|
||||||
|
# No pre-assigned IP — let DHCP run normally.
|
||||||
|
if (-not ($ip -match '^\d{1,3}(\.\d{1,3}){3}$')) { exit 0 }
|
||||||
|
|
||||||
|
# Convert dotted-decimal netmask to prefix length (default /24).
|
||||||
|
function Get-PrefixLength {
|
||||||
|
param([string]$Mask)
|
||||||
|
if (-not ($Mask -match '^\d{1,3}(\.\d{1,3}){3}$')) { return 24 }
|
||||||
|
$bits = 0
|
||||||
|
foreach ($octet in $Mask.Split('.')) {
|
||||||
|
$b = [int]$octet
|
||||||
|
while ($b -gt 0) { $bits += ($b -band 1); $b = $b -shr 1 }
|
||||||
|
}
|
||||||
|
return $bits
|
||||||
|
}
|
||||||
|
$prefix = Get-PrefixLength $netmask
|
||||||
|
|
||||||
|
# Target the first Up adapter (skip loopback / Teredo / Bluetooth).
|
||||||
|
$adapter = Get-NetAdapter |
|
||||||
|
Where-Object { $_.Status -eq 'Up' -and $_.InterfaceDescription -notmatch 'Loopback|Bluetooth|Teredo' } |
|
||||||
|
Select-Object -First 1
|
||||||
|
if (-not $adapter) {
|
||||||
|
Write-EventLog -LogName Application -Source 'ci-static-ip' -EventId 1 `
|
||||||
|
-EntryType Error -Message 'No Up adapter found.' -ErrorAction SilentlyContinue
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disable DHCP and remove any existing IPv4 addresses.
|
||||||
|
$adapter | Set-NetIPInterface -Dhcp Disabled -ErrorAction SilentlyContinue
|
||||||
|
$adapter | Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||||
|
Remove-NetIPAddress -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Apply static IP.
|
||||||
|
$ipParams = @{
|
||||||
|
InterfaceAlias = $adapter.Name
|
||||||
|
IPAddress = $ip
|
||||||
|
PrefixLength = $prefix
|
||||||
|
ErrorAction = 'Stop'
|
||||||
|
}
|
||||||
|
if ($gateway -match '^\d{1,3}(\.\d{1,3}){3}$') {
|
||||||
|
$ipParams['DefaultGateway'] = $gateway
|
||||||
|
}
|
||||||
|
New-NetIPAddress @ipParams | Out-Null
|
||||||
|
|
||||||
|
# Use the gateway as DNS (fallback: Google DNS).
|
||||||
|
$dns = @()
|
||||||
|
if ($gateway -match '^\d{1,3}(\.\d{1,3}){3}$') { $dns += $gateway }
|
||||||
|
$dns += '8.8.8.8'
|
||||||
|
Set-DnsClientServerAddress -InterfaceAlias $adapter.Name `
|
||||||
|
-ServerAddresses $dns -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Report IP back via guestinfo.ci-ip so the host also has it.
|
||||||
|
$savedEap = $ErrorActionPreference; $ErrorActionPreference = 'Continue'
|
||||||
|
& $vmtoolsRpc "info-set guestinfo.ci-ip $ip" 2>&1 | Out-Null
|
||||||
|
$ErrorActionPreference = $savedEap
|
||||||
|
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user