feat(installer): filter the boot USB out of the install drive picker
On bare-metal installs, `lsblk` reports the USB stick the live ISO booted from as TYPE=disk, so it showed up in the drive picker alongside the real install target — a user could in theory pick the USB they had just booted from. `findmnt /run/archiso/bootmnt` resolves the boot partition and `lsblk -no PKNAME` walks it up to the parent disk; that disk is dropped before scoring. On a normal box neither file nor mountpoint exist and the picker is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aa7dea0528
commit
65d48c92f8
2 changed files with 63 additions and 3 deletions
|
|
@ -95,3 +95,23 @@ def test_drive_type_label_nvme_ssd_hdd():
|
||||||
|
|
||||||
def test_parse_lsblk_handles_empty_output():
|
def test_parse_lsblk_handles_empty_output():
|
||||||
assert parse_lsblk_output("") == []
|
assert parse_lsblk_output("") == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_lsblk_drops_boot_usb(monkeypatch):
|
||||||
|
import drives
|
||||||
|
|
||||||
|
monkeypatch.setattr(drives, "_smart_status", lambda _: "passed")
|
||||||
|
output = "sda 500G disk\nsdb 16G disk\nnvme0n1 1T disk\n"
|
||||||
|
devices = parse_lsblk_output(output, boot_disk="sdb")
|
||||||
|
names = [d["name"] for d in devices]
|
||||||
|
assert "/dev/sdb" not in names
|
||||||
|
assert names == ["/dev/nvme0n1", "/dev/sda"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_lsblk_no_boot_disk_keeps_all(monkeypatch):
|
||||||
|
import drives
|
||||||
|
|
||||||
|
monkeypatch.setattr(drives, "_smart_status", lambda _: "passed")
|
||||||
|
output = "sda 500G disk\nsdb 16G disk\n"
|
||||||
|
names = [d["name"] for d in parse_lsblk_output(output, boot_disk=None)]
|
||||||
|
assert set(names) == {"/dev/sda", "/dev/sdb"}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,41 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def _boot_disk_name():
|
||||||
|
"""Return the parent disk name of the live-ISO boot media (e.g. "sdb"), or None.
|
||||||
|
|
||||||
|
On a normal box `/run/archiso/bootmnt` does not exist and we return None,
|
||||||
|
leaving the device list untouched. On bare metal booted from USB this is
|
||||||
|
the stick we booted from — we want to filter it out so the user can't
|
||||||
|
accidentally pick it as the install target.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["findmnt", "-no", "SOURCE", "/run/archiso/bootmnt"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
if result.returncode != 0:
|
||||||
|
return None
|
||||||
|
partition = result.stdout.strip()
|
||||||
|
if not partition:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
parent = subprocess.run(
|
||||||
|
["lsblk", "-no", "PKNAME", partition],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None
|
||||||
|
if parent.returncode != 0:
|
||||||
|
return None
|
||||||
|
name = parent.stdout.strip().splitlines()[0] if parent.stdout.strip() else ""
|
||||||
|
return name or None
|
||||||
|
|
||||||
|
|
||||||
def _smart_status(device):
|
def _smart_status(device):
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
|
|
@ -75,11 +110,14 @@ def score_device(device, size_gb):
|
||||||
return get_drive_type_score(device) + get_drive_health(device) + get_size_score(size_gb)
|
return get_drive_type_score(device) + get_drive_health(device) + get_size_score(size_gb)
|
||||||
|
|
||||||
|
|
||||||
def parse_lsblk_output(output):
|
def parse_lsblk_output(output, boot_disk=None):
|
||||||
"""Parse `lsblk -dn -o NAME,SIZE,TYPE` output into scored device dicts.
|
"""Parse `lsblk -dn -o NAME,SIZE,TYPE` output into scored device dicts.
|
||||||
|
|
||||||
Keeps only TYPE=disk so the live ISO's own squashfs (loop) and the boot
|
Keeps only TYPE=disk so the live ISO's own squashfs (loop) and the boot
|
||||||
CD-ROM (rom) don't show up as install targets.
|
CD-ROM (rom) don't show up as install targets. If `boot_disk` is given,
|
||||||
|
that disk is also dropped — it's the USB stick the live ISO booted from
|
||||||
|
on bare metal, where it appears as TYPE=disk and would otherwise be a
|
||||||
|
valid-looking install target.
|
||||||
"""
|
"""
|
||||||
devices = []
|
devices = []
|
||||||
for line in output.strip().split("\n"):
|
for line in output.strip().split("\n"):
|
||||||
|
|
@ -91,6 +129,8 @@ def parse_lsblk_output(output):
|
||||||
name, size, dev_type = parts[0], parts[1], parts[2]
|
name, size, dev_type = parts[0], parts[1], parts[2]
|
||||||
if dev_type != "disk":
|
if dev_type != "disk":
|
||||||
continue
|
continue
|
||||||
|
if boot_disk and name == boot_disk:
|
||||||
|
continue
|
||||||
device = f"/dev/{name}"
|
device = f"/dev/{name}"
|
||||||
size_gb = parse_size_gb(size)
|
size_gb = parse_size_gb(size)
|
||||||
status = _smart_status(device)
|
status = _smart_status(device)
|
||||||
|
|
@ -120,7 +160,7 @@ def list_scored_devices():
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"Error listing devices: {e}")
|
print(f"Error listing devices: {e}")
|
||||||
return []
|
return []
|
||||||
return parse_lsblk_output(result.stdout)
|
return parse_lsblk_output(result.stdout, boot_disk=_boot_disk_name())
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue