v0.6.0: bootable-ISO polish + close the 0.5.x chapter

Builds on v0.5.9's El Torito detection to make the boot menu honest and
clean, and confirms generic El Torito ISOs (ESXi/VMvisor installers, BSDs,
firmware tools) boot via iPXE sanboot with no special-casing:

- generate_boot_entries: an Unknown-family ISO now produces a sanboot entry
  only when it's actually bootable — it carries an El Torito catalog, OR it's
  a remote-share ISO we couldn't introspect (rev 0, assumed bootable). A
  locally-introspected ISO with no boot catalog (a data/appliance image like
  a VMware vCenter Server Appliance bundle) yields NO entry, so it stays out
  of the iPXE menu instead of offering a pick that always fails. ESXi
  installers (Unknown family + El Torito) surface under the installer menu
  and sanboot the raw ISO — backed by HTTP range reads, so size is moot.
- Dropped the stale "(SAN boot — may fail for >1GiB ISOs)" disclaimer and
  refreshed the SanBootIso doc: sanboot is the primary path for Windows and
  any El Torito image, and HTTP range reads remove the size limit.
- WebUI: renamed the dashboard panel "Images that won't boot with current
  settings" -> "Non-bootable images" (there's no setting that would make a
  data/appliance ISO boot).
- Tests: el_torito catalog detection + boot-entry generation across the
  ESXi / VCSA / remote-share cases.

Full v0.5.0->v0.5.9 compatibility sweep: clippy clean; entire workspace test
suite green (core 96, http-api 31+68, iso-store 61, dhcp 1, tftp 6, bin 2);
app.js syntax-checked.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
Miles Ward
2026-06-05 13:02:30 -04:00
co-authored by Claude Opus 4.8
parent 06695c3d77
commit 5df0fd5972
5 changed files with 85 additions and 22 deletions
+5 -3
View File
@@ -25,9 +25,11 @@ pub enum BootKind {
wimboot_url: String,
files: Vec<(String, String)>,
},
/// Last-resort: SAN-boot the ISO as an emulated CD. Only works for small
/// ISOs (<~1 GiB) and older distros. Kept for completeness, not the
/// default.
/// SAN-boot the raw ISO as an emulated CD (iPXE `sanboot`). The emulated
/// CD is backed by on-demand HTTP range reads, so ISO size is *not* a
/// constraint — this is the primary path for Windows (v0.5.8) and for any
/// El Torito-bootable image we don't special-case: ESXi/VMvisor
/// installers, BSDs, firmware/diagnostic tools, custom spins (v0.6.0).
SanBootIso { iso_url: String },
}
+70 -9
View File
@@ -639,15 +639,33 @@ fn generate_boot_entries(id: &str, filename: &str, r: &IntrospectionReport) -> V
}]
}
_ => {
// Last-resort SAN boot. Won't work for large modern ISOs, but
// lets the ISO at least appear in the menu.
vec![BootEntry {
id: format!("{id}-sanboot"),
title: format!("{title} (SAN boot — may fail for >1GiB ISOs)"),
kind: BootKind::SanBootIso {
iso_url: format!("iso/{id}.iso"),
},
}]
// No Windows-install media and no Linux kernel/initrd. Decide
// whether the ISO is bootable at all (v0.6.0):
// * `el_torito` — it carries a boot catalog, so iPXE sanboots
// the raw image as an emulated CD: BSDs, ESXi/VMvisor
// installers, firmware tools, custom spins. The emulated CD
// is backed by HTTP range reads, so ISO size is a non-issue
// (this is the same path Windows uses since v0.5.8) — hence
// no more "may fail for >1GiB ISOs" disclaimer.
// * `introspect_rev == 0` — a remote-share ISO we couldn't
// introspect (SMB/NFS/SFTP listings don't seek into the ISO).
// Offer sanboot optimistically rather than hide a
// likely-bootable installer.
// Otherwise it's a local image we *did* introspect and found to
// carry no boot catalog — a data/appliance ISO (e.g. a VMware
// vCenter Server Appliance bundle). It genuinely cannot boot, so
// we expose no menu entry; the dashboard flags it instead.
if r.el_torito || r.introspect_rev == 0 {
vec![BootEntry {
id: format!("{id}-sanboot"),
title,
kind: BootKind::SanBootIso {
iso_url: format!("iso/{id}.iso"),
},
}]
} else {
Vec::new()
}
}
}
}
@@ -721,6 +739,49 @@ mod tests {
assert!(!s.contains(" --- "), "stray ---: {s}");
}
#[test]
fn boot_entries_respect_el_torito_and_source() {
use crate::introspect::INTROSPECT_REV;
// ESXi / VMvisor installer shape: bootable (carries an El Torito
// catalog) but not classifiable as Windows or Linux. Must yield a
// single sanboot entry so it's selectable + boots via emulated CD.
let esxi = IntrospectionReport {
family: DistroFamily::Unknown,
volume_label: Some("ESXI-7.0U3".into()),
el_torito: true,
introspect_rev: INTROSPECT_REV,
..Default::default()
};
let e = generate_boot_entries("esxi", "VMware-VMvisor-Installer-7.0U3n.iso", &esxi);
assert_eq!(e.len(), 1, "ESXi should get exactly one boot entry");
assert!(matches!(e[0].kind, BootKind::SanBootIso { .. }));
// Clean title — no stale ">1GiB may fail" disclaimer.
assert!(!e[0].title.contains("may fail"), "title: {}", e[0].title);
// VCSA / data-appliance shape: locally introspected (rev set), no
// boot catalog, not Windows/Linux. Genuinely unbootable → no entry,
// so it stays out of the iPXE menu (the dashboard flags it instead).
let vcsa = IntrospectionReport {
family: DistroFamily::Unknown,
el_torito: false,
introspect_rev: INTROSPECT_REV,
..Default::default()
};
assert!(
generate_boot_entries("vcsa", "VMware-VCSA-all-8.0.iso", &vcsa).is_empty(),
"data/appliance ISO must produce no boot entry"
);
// Remote-share ISO: never introspected (rev 0, no random access over
// SMB/NFS/SFTP). Assume bootable and offer sanboot rather than hide a
// likely-bootable installer.
let remote = IntrospectionReport::default();
let r = generate_boot_entries("remote", "unknown-remote.iso", &remote);
assert_eq!(r.len(), 1, "remote (uninspected) ISO keeps a sanboot entry");
assert!(matches!(r[0].kind, BootKind::SanBootIso { .. }));
}
fn fake_meta(id: &str) -> IsoMeta {
IsoMeta {
id: id.into(),
+1 -1
View File
@@ -365,7 +365,7 @@
const settings = status.settings;
const problems = isos.map(i => ({i, b: bootability(i, settings)})).filter(x => !x.b.ok);
const problemsBlock = problems.length ? el('div', {class:'card'}, [
el('header', {}, [el('h2', {}, 'Images that won\'t boot with current settings')]),
el('header', {}, [el('h2', {}, 'Non-bootable images')]),
el('div', {class:'body'},
problems.map(({i, b}) => el('div', {class:'row-warn'},
'⚠ ' + i.filename + ' — ' + b.reason)))