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():
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
try:
|
||||
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)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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 = []
|
||||
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]
|
||||
if dev_type != "disk":
|
||||
continue
|
||||
if boot_disk and name == boot_disk:
|
||||
continue
|
||||
device = f"/dev/{name}"
|
||||
size_gb = parse_size_gb(size)
|
||||
status = _smart_status(device)
|
||||
|
|
@ -120,7 +160,7 @@ def list_scored_devices():
|
|||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error listing devices: {e}")
|
||||
return []
|
||||
return parse_lsblk_output(result.stdout)
|
||||
return parse_lsblk_output(result.stdout, boot_disk=_boot_disk_name())
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue