From e589e48511030459a0770b902f55e91c874799d7 Mon Sep 17 00:00:00 2001 From: Jino Jose Date: Wed, 24 Jun 2026 18:54:49 +0530 Subject: [PATCH] Pause autoinstall on network screen --- README.md | 2 ++ autoinstall/user-data | 8 +++++-- autoinstall/websetup/server.py | 39 +++++++++++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 64d72fd..d80760a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ sudo bash install.sh Server reboots automatically after NVIDIA drivers install. Phase 2 runs on its own after reboot. +On the custom ISO, Ubuntu autoinstall now pauses on the installer network screen so the operator can choose the final IP address from the VM console before installation continues. + ## What Gets Installed (Entry Tier) | Service | Port | Notes | diff --git a/autoinstall/user-data b/autoinstall/user-data index b847ece..aef7e99 100644 --- a/autoinstall/user-data +++ b/autoinstall/user-data @@ -1,14 +1,18 @@ #cloud-config autoinstall: version: 1 + interactive-sections: + - network # ── Locale & keyboard ────────────────────────── locale: en_IN.UTF-8 keyboard: layout: us - # ── Network: DHCP on first ethernet ─────────── - # Final network config is set by the web setup UI (browser on port 80) + # ── Network: stop on installer network screen ─ + # Subiquity will show the network UI and use the values below as defaults. + # This lets the operator set the final static IP from the VM console before + # installation completes. network: network: version: 2 diff --git a/autoinstall/websetup/server.py b/autoinstall/websetup/server.py index b35558a..ee99b0e 100644 --- a/autoinstall/websetup/server.py +++ b/autoinstall/websetup/server.py @@ -58,6 +58,19 @@ def validate_static_network(ip, prefix, gateway, dns): raise ValueError("CIDR prefix must be between 1 and 32") return str(prefix_int) +def is_ip_in_use(ip): + """Best-effort conflict check before taking a static IP.""" + try: + result = subprocess.run( + ["ping", "-c", "1", "-W", "1", ip], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + return result.returncode == 0 + except Exception: + return False + def apply_static_ip(iface, ip, prefix, gateway, dns): prefix = validate_static_network(ip, prefix, gateway, dns) config = f"""network: @@ -270,6 +283,9 @@ HTML = r"""
+
+ We will ping the IP before applying it. If it replies, we will block the change to avoid taking an address that is already in use. +
@@ -437,7 +453,15 @@ function applyStaticIP() { dns: document.getElementById('ip-dns').value, }; fetch('/api/network', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) }) - .then(r=>r.json()).then(d=>{ if(!d.ok) alert('Network config failed: ' + d.error); }); + .then(r=>r.json()).then(d=>{ + if(!d.ok) { + alert('Network config failed: ' + d.error); + return; + } + if (d.warning) { + alert(d.warning); + } + }); } // ── Tier ─────────────────────────────────────────────────── @@ -663,7 +687,20 @@ class Handler(BaseHTTPRequestHandler): if body.get("mode") == "static": ifaces = get_interfaces() iface = ifaces[0] if ifaces else "eth0" + validate_static_network(body["ip"], body["prefix"], body["gateway"], body["dns"]) + if is_ip_in_use(body["ip"]): + self.send_json({ + "ok": False, + "error": f"IP address {body['ip']} is already replying to ping. Choose a different address." + }, 409) + return apply_static_ip(iface, body["ip"], body["prefix"], body["gateway"], body["dns"]) + self.send_json({ + "ok": True, + "ip": get_ip(), + "warning": "Ping check passed before applying the static IP. Note: a non-reply does not guarantee the address is unused if another host blocks ICMP." + }) + return self.send_json({"ok": True, "ip": get_ip()}) except Exception as e: self.send_json({"ok": False, "error": str(e)}, 500)