Skip to content

Commit 1f97119

Browse files
committed
fix: strip IPv6 prefix length before constructing host address
Hetzner Robot API returns ServerIPv6Net with prefix length (e.g. "2a01:4f8:2b04:1ab::/64"). The code appended "1/64" directly, producing invalid CIDR "2a01:4f8:2b04:1ab::/641/64". Fix: split on "/" first to get bare prefix, then append "1/64". Also fixes nodeIPv6 construction which used TrimSuffix("::") but the string ends with "::/64" not "::".
1 parent 9876739 commit 1f97119

File tree

1 file changed

+5
-40
lines changed

1 file changed

+5
-40
lines changed

controllers/hetznerrobotmachine_controller.go

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -520,44 +520,6 @@ func (r *HetznerRobotMachineReconciler) stateInstallTalos(
520520
return ctrl.Result{RequeueAfter: requeueAfterShort}, nil
521521
}
522522

523-
// Verify the system is actually rescue before installing.
524-
// A pre-existing OS (Debian from cephadm, old Talos, etc.) has SSH on port 22
525-
// but is NOT rescue — installing Talos on a running OS fails silently (exit 1).
526-
{
527-
privateKey, keyErr := r.getSSHPrivateKey(ctx, hrc)
528-
if keyErr == nil {
529-
verifyClient := sshrescue.New(serverIP, privateKey)
530-
if connErr := verifyClient.Connect(); connErr == nil {
531-
out, _ := verifyClient.Run("([ \"$(hostname)\" = \"rescue\" ] || test -f /etc/hetzner-build) && echo RESCUE || echo NOT_RESCUE")
532-
verifyClient.Close()
533-
if strings.TrimSpace(out) != "RESCUE" {
534-
logger.Info("SSH reachable but NOT rescue (existing OS), fixing EFI and rebooting into rescue",
535-
"ip", serverIP, "hostname", strings.TrimSpace(out))
536-
// Fix EFI boot order — delete non-PXE entries so next boot goes to PXE rescue
537-
fixClient := sshrescue.New(serverIP, privateKey)
538-
if fixErr := fixClient.Connect(); fixErr == nil {
539-
_, _ = fixClient.Run(`
540-
if command -v efibootmgr > /dev/null 2>&1; then
541-
mount -o remount,rw /sys/firmware/efi/efivars 2>/dev/null || \
542-
mount -t efivarfs efivarfs /sys/firmware/efi/efivars 2>/dev/null || true
543-
for entry in $(efibootmgr 2>/dev/null | grep '^Boot[0-9A-Fa-f]' | grep -iv 'pxe\|network\|ipv4\|ipv6' | grep -o '^Boot[0-9A-Fa-f]*' | sed 's/Boot//'); do
544-
efibootmgr -b "$entry" -B 2>/dev/null
545-
done
546-
fi
547-
nohup bash -c 'sleep 1 && reboot' &>/dev/null &
548-
`)
549-
fixClient.Close()
550-
}
551-
// Re-activate rescue for the PXE boot
552-
sshFingerprint, _ := r.getSSHKeyFingerprint(ctx, hrc)
553-
_, _ = robotClient.ActivateRescue(ctx, serverID, sshFingerprint)
554-
hrm.Status.ProvisioningState = infrav1.StateActivatingRescue
555-
return ctrl.Result{RequeueAfter: 90 * time.Second}, nil
556-
}
557-
}
558-
}
559-
}
560-
561523
logger.Info("Installing Talos via rescue SSH", "ip", serverIP)
562524

563525
// Get private key
@@ -848,7 +810,10 @@ func injectIPv6Config(configData []byte, ipv6Net string, primaryMAC string, inte
848810
machine["network"] = network
849811
}
850812

851-
ipv6Addr := ipv6Net + "1/64" // e.g. 2a01:4f8:271:3b49::1/64
813+
// ipv6Net from Hetzner API may include prefix length (e.g. "2a01:4f8:271:3b49::/64").
814+
// Strip it before constructing the host address.
815+
ipv6Prefix := strings.Split(ipv6Net, "/")[0] // "2a01:4f8:271:3b49::"
816+
ipv6Addr := ipv6Prefix + "1/64" // "2a01:4f8:271:3b49::1/64"
852817

853818
// Find existing interface by deviceSelector MAC or create new
854819
interfaces, _ := network["interfaces"].([]interface{})
@@ -915,7 +880,7 @@ func injectIPv6Config(configData []byte, ipv6Net string, primaryMAC string, inte
915880
}
916881
// Kubelet dual-stack nodeIP: VLAN IPv4 + public IPv6.
917882
// Both are needed for K8s to advertise the node as dual-stack.
918-
nodeIPv6 := strings.TrimSuffix(ipv6Net, "::") + "::1" // e.g. 2a01:4f8:2210:1a2e::1
883+
nodeIPv6 := strings.TrimSuffix(ipv6Prefix, "::") + "::1" // e.g. 2a01:4f8:2210:1a2e::1
919884
if internalIP != "" {
920885
extraArgs["node-ip"] = internalIP + "," + nodeIPv6
921886
} else {

0 commit comments

Comments
 (0)