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:
co-authored by
Claude Opus 4.8
parent
06695c3d77
commit
0b6d2e377b
Generated
+8
-8
@@ -2669,7 +2669,7 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openpxe"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -2691,7 +2691,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-core"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -2718,7 +2718,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-dhcp-proxy"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -2732,7 +2732,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-http-api"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -2768,7 +2768,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-ipxe-assets"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"openpxe-core",
|
||||
"rust-embed",
|
||||
@@ -2778,7 +2778,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-iso-store"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bcrypt",
|
||||
@@ -2807,7 +2807,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-tftp"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -2821,7 +2821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-webui"
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
|
||||
[[package]]
|
||||
name = "p256"
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.5.9"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.95"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -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 },
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
// 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: format!("{title} (SAN boot — may fail for >1GiB ISOs)"),
|
||||
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(),
|
||||
|
||||
@@ -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)))
|
||||
|
||||
Reference in New Issue
Block a user