v0.4.65: Local directory ISO source (bind-mount workaround for Unraid)
Field report: even with CAP_SYS_ADMIN and full --privileged, NFS mounts
inside the OpenPXE container fail on Unraid with the same
"failed to apply fstab options" error v0.4.64 added diagnostics for.
The root cause is the host kernel: Unraid's base kernel ships without
the nfs/nfsv4 client modules loaded. Capabilities are necessary but
not sufficient; the modules have to be present on the host kernel for
in-container mount(2) to do anything. No container-side change can
fix that.
This is exactly the case every other PXE/imaging tool sidesteps
(Bootimus uses SMB; iVentoy, FOG, MAAS, Cobbler all rely on the host
to mount network storage and bind-mount the path into the imaging
service). v0.4.65 brings OpenPXE in line with that pattern.
What's new:
* `IsoSource::LocalDir { dir_id, relative_path }` — third source kind
alongside `Local` (uploaded) and `Nfs` (in-container mount).
* `LocalDirManager` (crates/iso-store/src/local_dir.rs) — registers
bind-mounted directories, validates them (absolute path, exists, is
a directory, readable), scans for *.iso files, registers them with
IsoStore. Persisted to <work_dir>/local_dirs.json so the relationship
survives restarts.
* `NfsHostCaps::detect()` — pure read of /proc/filesystems on startup.
Surfaced via GET /api/nfs/capabilities and used by the Storage tab to
show a prominent red banner above the NFS form when in-container
mounts cannot possibly work, pointing the operator at the Local
Directories card as the recommended path.
* Four new API routes:
GET /api/nfs/capabilities
GET /api/local-dirs
POST /api/local-dirs { path, label? }
DELETE /api/local-dirs/:id
POST /api/local-dirs/:id/scan
UI changes (crates/webui/src/app.js):
* Storage tab: new "Local directories" card under the NFS card with
the bind-mount form, an explainer paragraph (with the Docker
`-v /mnt/user/isos:/mnt/external-isos` command), and the list of
registered directories with rescan + remove actions.
* When NFS host caps are unavailable, the NFS card sprouts a red
banner explaining what's wrong and pointing at the local-dir
workaround. The card sub-header also flips to "N registered ·
recommended on this host".
* ISO table: new "dir:<id>" source badge; on-disk ISOs show "on disk"
in the actions column instead of a delete button (same pattern as
NFS — OpenPXE doesn't own those bytes).
* API reference table picks up the four new endpoints + a hint about
the new `port` field on NFS add.
Tests (+12, total 162):
* iso-store: 7 local_dir unit tests covering relative-path rejection,
missing path, non-directory file, empty-directory success, default
label, idempotent re-add, remove + iso-path-resolution clear.
* iso-store: 1 nfs unit test confirming NfsHostCaps::detect() never
panics and the boolean accessors are consistent.
* http-api: 4 integration tests covering /api/nfs/capabilities,
/api/local-dirs list/add/remove + relative-path 400.
`cargo clippy --workspace --all-targets -- -D warnings` clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
0afbe860e8
commit
761489761c
@@ -9,7 +9,7 @@ use openpxe_core::{
|
||||
};
|
||||
use openpxe_dhcp_proxy::DhcpProxyServer;
|
||||
use openpxe_http_api::{build_router, AppState};
|
||||
use openpxe_iso_store::{IsoStore, NfsManager, SmbManager};
|
||||
use openpxe_iso_store::{IsoStore, LocalDirManager, NfsHostCaps, NfsManager, SmbManager};
|
||||
use openpxe_tftp::TftpServer;
|
||||
use std::net::{Ipv4Addr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
@@ -128,6 +128,39 @@ async fn main() -> anyhow::Result<()> {
|
||||
tracing::warn!(target: "openpxe::nfs", "could not reload NFS mounts: {e}");
|
||||
}
|
||||
|
||||
// v0.4.65: snapshot host kernel capabilities so the Storage tab can
|
||||
// warn the operator early when in-container NFS mounts cannot
|
||||
// possibly succeed (Unraid is the dominant case — its base kernel
|
||||
// ships without nfs/nfsv4 client modules loaded). Pure read of
|
||||
// /proc/filesystems; no side effects.
|
||||
let nfs_host_caps = NfsHostCaps::detect();
|
||||
if nfs_host_caps.available {
|
||||
tracing::info!(
|
||||
target: "openpxe::nfs",
|
||||
v3 = nfs_host_caps.has_nfs3, v4 = nfs_host_caps.has_nfs4,
|
||||
"host kernel NFS client support"
|
||||
);
|
||||
} else {
|
||||
tracing::warn!(
|
||||
target: "openpxe::nfs",
|
||||
detail = %nfs_host_caps.detail,
|
||||
"host kernel lacks NFS client modules — in-container `mount -t nfs` will fail \
|
||||
regardless of CAP_SYS_ADMIN. Use Local Directories instead."
|
||||
);
|
||||
}
|
||||
|
||||
// v0.4.65: bind-mounted host directory manager. Provides the
|
||||
// container-friendly ISO source that doesn't depend on kernel NFS
|
||||
// support — the operator mounts the share on the host, bind-mounts
|
||||
// the path into the container, and registers it here.
|
||||
let local_dirs = LocalDirManager::new(&config.paths.work_dir, iso_store.clone());
|
||||
if let Err(e) = local_dirs.load_and_rescan().await {
|
||||
tracing::warn!(
|
||||
target: "openpxe::local_dir",
|
||||
"could not reload local directories: {e}"
|
||||
);
|
||||
}
|
||||
|
||||
// Sniff network details for the Network tab. None of these are
|
||||
// required for PXE to work — they're informational, surfaced in the
|
||||
// UI so an operator doesn't have to drop to a shell to find their
|
||||
@@ -153,6 +186,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
metrics: metrics.clone(),
|
||||
smb: Some(smb.clone()),
|
||||
nfs: nfs.clone(),
|
||||
local_dirs: local_dirs.clone(),
|
||||
nfs_host_caps: nfs_host_caps.clone(),
|
||||
uploads: openpxe_http_api::uploads::UploadSessions::default(),
|
||||
log_bus: log_bus.clone(),
|
||||
started_at: time::OffsetDateTime::now_utc(),
|
||||
|
||||
Reference in New Issue
Block a user