[FIX] qemu debian s390x : adresse fixe, l'initrd ne sait pas faire de DHCP

Le syslog de d-i a tranche : « Menu item 'netcfg-static' selected », puis
« Taking down interface enc1 ». netcfg-dhcp n'apparait JAMAIS — il n'est
pas dans l'initrd s390x, et aucun variant netboot n'existe pour cette
architecture (404 sur trixie comme sur bookworm).

Aucun DHCP n'etait donc tente. Sept hypotheses ont cherche pourquoi il
echouait ; il n'avait jamais lieu. C'est la convention IBM Z, ou le
reseau se donne au parmfile.

deploy_qemu choisit desormais une adresse libre en HAUT de la plage —
dnsmasq attribue depuis le bas — et la preseede. Mesure : l'ecran
d'adressage statique a disparu, l'installateur poursuit.

--- EN ---

d-i's syslog settled it: "Menu item 'netcfg-static' selected", then
"Taking down interface enc1". netcfg-dhcp NEVER appears — it is not in
the s390x initrd, and no netboot variant exists for this architecture
(404 on trixie and bookworm alike).

So no DHCP was ever attempted. Seven hypotheses looked for why it
failed; it never happened. This is the IBM Z convention, where the
network is given in the parmfile.

deploy_qemu now picks a free address at the TOP of the range — dnsmasq
allocates from the bottom — and preseeds it. Measured: the static
addressing screen is gone, the installer moves on.

Assisted-by: Claude Opus 5
(cherry picked from commit 8c630969a605ec191a65dc93896014ac9e3a1227)
This commit is contained in:
Mathieu Benoit 2026-08-13 21:41:25 -04:00
parent 56803b3807
commit 08ebf99c77

View file

@ -1711,8 +1711,63 @@ def prepare_disk(
runner.run(["qemu-img", "resize", str(disk), size], privileged=True)
def static_net_plan(net: str | None, use_sudo: bool) -> dict[str, str] | None:
"""Adresse fixe libre pour une VM installée par debian-installer.
L'initrd s390x ne contient QUE « netcfg-static » : le journal de d-i
montre « Menu item 'netcfg-static' selected », jamais netcfg-dhcp, puis
« Taking down interface enc1 ». Aucun DHCP n'est tenté — c'est la
convention IBM Z, où la configuration réseau se donne au parmfile. Il
faut donc fournir une adresse, et elle doit être libre.
On la prend en HAUT de la plage : dnsmasq attribue depuis le bas, donc
les collisions avec un bail futur sont les plus improbables là.
"""
if not net:
return None
cmd = ["virsh", "-c", LIBVIRT_URI, "net-dumpxml", net]
if use_sudo:
cmd.insert(0, "sudo")
try:
xml = subprocess.run(
cmd, capture_output=True, text=True, timeout=20
).stdout
except (OSError, subprocess.SubprocessError):
return None
m = re.search(r"<ip address='([\d.]+)' netmask='([\d.]+)'", xml)
if not m:
return None
gateway, netmask = m.group(1), m.group(2)
base = gateway.rsplit(".", 1)[0]
taken = {gateway}
lease_cmd = ["virsh", "-c", LIBVIRT_URI, "net-dhcp-leases", net]
if use_sudo:
lease_cmd.insert(0, "sudo")
try:
out = subprocess.run(
lease_cmd, capture_output=True, text=True, timeout=20
).stdout
taken |= set(re.findall(r"(\d+\.\d+\.\d+\.\d+)/\d+", out))
except (OSError, subprocess.SubprocessError):
pass
for last in range(250, 200, -1):
ip = f"{base}.{last}"
if ip in taken or _ip_reachable(ip, port=22, timeout=0.4):
continue
return {
"ip": ip,
"netmask": netmask,
"gateway": gateway,
"dns": gateway,
}
return None
def build_preseed(
args: argparse.Namespace, pw_hash: str | None, ssh_keys: list[str]
args: argparse.Namespace,
pw_hash: str | None,
ssh_keys: list[str],
static: dict[str, str] | None = None,
) -> str:
"""Preseed debian-installer équivalent au cloud-config des autres distros.
@ -1743,6 +1798,21 @@ def build_preseed(
# « auto » évite la question du choix d'interface : sous virtio-ccw
# elle s'appelle enc1 et non eth0, et le nom n'est pas devinable.
f"d-i netcfg/get_hostname string {args.hostname}",
# Adresse fixe : sans elle, netcfg-static pose la question a l'ecran
# et l'installation s'arrete la, indefiniment.
*(
[
"d-i netcfg/disable_autoconfig boolean true",
"d-i netcfg/disable_dhcp boolean true",
f"d-i netcfg/get_ipaddress string {static['ip']}",
f"d-i netcfg/get_netmask string {static['netmask']}",
f"d-i netcfg/get_gateway string {static['gateway']}",
f"d-i netcfg/get_nameservers string {static['dns']}",
"d-i netcfg/confirm_static boolean true",
]
if static
else []
),
"d-i netcfg/get_domain string localdomain",
"d-i netcfg/hostname string " + args.hostname,
"d-i mirror/country string manual",
@ -2661,8 +2731,18 @@ def main() -> None:
create_blank_disk(disk, args.disk_size, runner, args.force)
print(f"\n== 4/5 Preseed embarqué dans l'initrd ==")
static = static_net_plan(network_name(args.network), not args.dry_run)
if static:
print(f" Adresse fixe retenue : {static['ip']}"
f" (passerelle {static['gateway']})")
else:
print(" ⚠ Aucune adresse fixe déterminée : netcfg-static posera"
" la question à l'écran et l'installation s'arrêtera.")
build_installer_initrd(
build_preseed(args, pw_hash, ssh_keys), initrd_src, initrd, runner
build_preseed(args, pw_hash, ssh_keys, static),
initrd_src,
initrd,
runner,
)
installer = (kernel, initrd)
else: