v0.7.4: probe-based introspection — remote shares classify, gparted bug fixed, Storage pagination
Introspection (the headline): detection is now probe-based. Instead of
grepping raw sectors for filename strings, we walk the ISO9660
directory tree and check whether the well-known boot files actually
exist — and the same probes run over NFS READ3 / SFTP seek-reads, so
share-hosted ISOs finally classify instead of registering as Unknown.
iso-store:
- New iso_fs module: the read-only ISO9660 walker (generalized from
http-api) over an IsoReadAt trait — local files, NFS, SFTP, and the
in-memory test images all share it. Iterative walk, 4 MiB directory
cap, strict-mastering trailing-dot normalization (VMLINUZ.;1 now
matches /vmlinuz), CachingReadAt collapses repeated directory reads
during the probe pass (~60 → ~6 round-trips per remote ISO).
- introspect.rs rewritten (INTROSPECT_REV 2): PVD label → El Torito →
/sources/boot.wim probe → verified Linux kernel+initrd probe table →
local-only 16 MiB UDF-Windows scan → filename-token fallback.
* Fixes the false-Windows bug: any Linux ISO shipping GRUB/syslinux
chainload modules contains the literal "bootmgr", so gparted-live
classified as WindowsPe. Linux probes now run first; the byte scan
only sees ISOs nothing else claimed. Local ISOs re-probe once on
startup via the rev bump — no re-upload.
* Kernel entries are emitted only when kernel+initrd verifiably
exist (no more guessed paths that 404 at boot). Debian-live /
d-i netinst / CoreOS shapes classify for the UI but keep their
working sanboot entries (their boot protocols need args we don't
render yet; CoreOS additionally needs its embedded ignition).
* Label + filename vocab extended: rhcos/coreos/openshift/okd,
gparted/clonezilla/kali/tails, almalinux/rocky, sles, manjaro.
- NFS + SFTP managers: per-ISO IsoReadAt readers (READ3-at-offset with
short-read looping / seek+read_exact), background introspection pass
after each scan — entries register instantly with a provisional
filename-based report (rev 0, optimistic sanboot preserved) and
upgrade in place as probes land (30s/ISO timeout, failures keep the
provisional). locate_in_iso() exposes the walker to the HTTP layer.
- remote_cache: introspection results persisted per protocol keyed
share/path@size and gated on INTROSPECT_REV — container restarts
re-probe only new/replaced ISOs; upgrades re-probe exactly once.
- SMB: smbclient can't seek, so SMB ISOs get the filename-token family
(rev stays 0 → sanboot entry + "awaiting introspection" label).
- IsoStore::update_external_introspection swaps in completed reports
and regenerates boot entries, preserving category/password.
http-api:
- /iso/{id}/{*path} now serves files from inside NFS/SFTP-hosted ISOs
(remote ISO9660 lookup + ranged share stream) — verified kernel
entries on remote Linux ISOs are actually bootable, end to end.
- iso_fs.rs deleted in favor of the shared iso-store module.
- full_flow fixtures build real directory trees via the shared
test-image builder (new iso-store feature) — a label-only blob no
longer earns a kernel entry, by design.
webui:
- Available images: paged 5 per page with a quiet footer pager
(Showing X–Y of N · Prev/Next), filter-then-paginate, page resets on
search input. Fifty images is five clean pages, not a scroll wall.
- Hosts/Queue profile: "Unattended file (in Storage → Advanced)" so
the picker says where the files live.
- Row badge keys on introspect_rev: probed remote ISOs read like local
ones; un-probed say "awaiting introspection".
Validation: clippy pedantic clean, fmt clean, 316 workspace tests
green (+17: walker, probe shapes incl. gparted regression + CoreOS,
filename table, cache round-trips), webui 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
934cfbab46
commit
6524aa4118
+94
-17
@@ -19,7 +19,6 @@ use crate::ipxe_script::{
|
||||
render_entry, render_family_menu, render_local_hdd, render_menu, render_nic_info,
|
||||
render_queue_entry, render_shell, render_tools_menu, render_util,
|
||||
};
|
||||
use crate::iso_fs;
|
||||
use crate::log_stream;
|
||||
use crate::state::AppState;
|
||||
use crate::terminal;
|
||||
@@ -36,6 +35,7 @@ use openpxe_core::{
|
||||
LogoSlot, NotifyConfig, Settings, SsoConfig, ALLOWED_LOGO_MIMES, MAX_LOGO_BYTES,
|
||||
};
|
||||
use openpxe_ipxe_assets::asset_slice;
|
||||
use openpxe_iso_store::iso_fs;
|
||||
use openpxe_iso_store::{
|
||||
render_template, IsoCategory, IsoMeta, IsoSource, NfsAddRequest, SftpAddRequest, SmbAddRequest,
|
||||
SmbState, UnattendedKind, UnattendedMeta,
|
||||
@@ -1087,28 +1087,105 @@ async fn iso_file(
|
||||
State(state): State<AppState>,
|
||||
AxumPath((id, path)): AxumPath<(String, String)>,
|
||||
) -> Response {
|
||||
// In-ISO file extraction is only supported for local ISOs — it
|
||||
// needs random-access reads into the ISO9660 directory tree, which
|
||||
// smbclient's whole-file streaming can't do efficiently. SMB-
|
||||
// sourced ISOs use the raw streaming endpoint above instead.
|
||||
let Some(iso_path) = state.iso_store.iso_path_for(&id) else {
|
||||
let Some(meta) = state.iso_store.get(&id) else {
|
||||
return (StatusCode::NOT_FOUND, "no such iso").into_response();
|
||||
};
|
||||
let p = iso_path.clone();
|
||||
let in_path = format!("/{path}");
|
||||
let loc = tokio::task::spawn_blocking(move || iso_fs::lookup(&p, &in_path))
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let Some(loc) = loc else {
|
||||
return (StatusCode::NOT_FOUND, "not found inside iso").into_response();
|
||||
};
|
||||
match stream_byte_range(&iso_path, loc.offset, loc.length).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")).into_response(),
|
||||
match &meta.source {
|
||||
IsoSource::Local => {
|
||||
let Some(iso_path) = state.iso_store.local_path(&meta) else {
|
||||
return (StatusCode::NOT_FOUND, "no such iso").into_response();
|
||||
};
|
||||
let p = iso_path.clone();
|
||||
let loc = tokio::task::spawn_blocking(move || iso_fs::lookup_local(&p, &in_path))
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let Some(loc) = loc else {
|
||||
return (StatusCode::NOT_FOUND, "not found inside iso").into_response();
|
||||
};
|
||||
match stream_byte_range(&iso_path, loc.offset, loc.length).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")).into_response(),
|
||||
}
|
||||
}
|
||||
// v0.7.4: remote ISOs serve in-ISO files too — the same ISO9660
|
||||
// walk runs over NFS READ3 / SFTP seek-reads, then the located
|
||||
// byte range streams through the share manager. This is what
|
||||
// makes the verified kernel/initrd boot entries on share-hosted
|
||||
// Linux ISOs actually bootable.
|
||||
IsoSource::Nfs {
|
||||
share_id,
|
||||
relative_path,
|
||||
} => {
|
||||
match state
|
||||
.nfs_shares
|
||||
.locate_in_iso(share_id, relative_path, &in_path)
|
||||
.await
|
||||
{
|
||||
Ok(Some(loc)) => {
|
||||
match state
|
||||
.nfs_shares
|
||||
.stream_iso(share_id, relative_path, loc.offset, Some(loc.length))
|
||||
.await
|
||||
{
|
||||
Ok(stream) => in_iso_stream_response(Body::from_stream(stream), loc.length),
|
||||
Err(e) => {
|
||||
(StatusCode::BAD_GATEWAY, format!("nfs stream: {e}")).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => (StatusCode::NOT_FOUND, "not found inside iso").into_response(),
|
||||
Err(e) => (StatusCode::BAD_GATEWAY, format!("nfs lookup: {e}")).into_response(),
|
||||
}
|
||||
}
|
||||
IsoSource::Sftp {
|
||||
share_id,
|
||||
relative_path,
|
||||
} => {
|
||||
match state
|
||||
.sftp_shares
|
||||
.locate_in_iso(share_id, relative_path, &in_path)
|
||||
.await
|
||||
{
|
||||
Ok(Some(loc)) => {
|
||||
match state
|
||||
.sftp_shares
|
||||
.stream_iso(share_id, relative_path, loc.offset, Some(loc.length))
|
||||
.await
|
||||
{
|
||||
Ok(stream) => in_iso_stream_response(Body::from_stream(stream), loc.length),
|
||||
Err(e) => {
|
||||
(StatusCode::BAD_GATEWAY, format!("sftp stream: {e}")).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => (StatusCode::NOT_FOUND, "not found inside iso").into_response(),
|
||||
Err(e) => (StatusCode::BAD_GATEWAY, format!("sftp lookup: {e}")).into_response(),
|
||||
}
|
||||
}
|
||||
// smbclient streams sequentially — no seeks, no ISO9660 walk.
|
||||
// SMB ISOs never emit kernel entries, so nothing requests this.
|
||||
IsoSource::Smb { .. } => (
|
||||
StatusCode::NOT_FOUND,
|
||||
"in-ISO files are not available for SMB-sourced ISOs",
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 200 response wrapping an in-ISO byte-range stream from a share
|
||||
/// manager. Content-Length is the located file's length — the stream is
|
||||
/// already bounded to exactly that range.
|
||||
fn in_iso_stream_response(body: Body, length: u64) -> Response {
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||
.header(header::CONTENT_LENGTH, length)
|
||||
.body(body)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn stream_file_range(
|
||||
path: &std::path::Path,
|
||||
range: Option<&HeaderValue>,
|
||||
|
||||
Reference in New Issue
Block a user