v0.4.66: ship smbclient in the runtime image
v0.4.65 added the SmbShareManager but the Dockerfile only installed
the `samba` package — in Debian 12 that ships the SERVER (smbd) only,
not the `smbclient` CLI the new manager shells out to. Every "Add
share" attempt surfaced:
could not exec smbclient: No such file or directory (os error 2)
Fix is two lines: add `smbclient` to the runtime apt install, drop
the leftover `nfs-common` (no kernel-mount NFS anymore so the helpers
aren't needed).
While in the area, harden the manager so future stripped-down runtime
images get a useful error instead of a bare exec failure:
- `list_isos` and `stream_iso` both detect `ErrorKind::NotFound` on
spawn and emit "smbclient binary not found on $PATH".
- `hint_for` translates the missing-binary pattern into an actionable
hint: "pull OpenPXE v0.4.66+ or add the Debian `smbclient` package
to your runtime stage." So even on a custom build the UI still
surfaces a clear remediation.
Tests: 150 passing (+1 for the new hint). clippy clean.
The image is still ~98 MB — `smbclient` adds <1 MB on top of the
already-installed samba server.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
900b65b3ec
commit
9f66c269c4
Generated
+8
-8
@@ -1140,7 +1140,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "openpxe"
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -1162,7 +1162,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-core"
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bcrypt",
|
||||
@@ -1181,7 +1181,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-dhcp-proxy"
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -1195,7 +1195,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-http-api"
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -1226,7 +1226,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-ipxe-assets"
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
dependencies = [
|
||||
"openpxe-core",
|
||||
"rust-embed",
|
||||
@@ -1236,7 +1236,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-iso-store"
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bcrypt",
|
||||
@@ -1260,7 +1260,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-tftp"
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -1274,7 +1274,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openpxe-webui"
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.65"
|
||||
version = "0.4.66"
|
||||
edition = "2021"
|
||||
rust-version = "1.95"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -400,7 +400,20 @@ impl SmbShareManager {
|
||||
if share.guest {
|
||||
cmd.arg("-N");
|
||||
}
|
||||
let mut child = cmd.spawn().map_err(|e| Error::Other(e.into()))?;
|
||||
// v0.4.66: surface a useful error if smbclient isn't on the
|
||||
// PATH. Shouldn't happen on the stock image but custom
|
||||
// builds may strip it.
|
||||
let mut child = cmd.spawn().map_err(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
Error::Invalid(
|
||||
"smbclient binary not found on $PATH — install the \
|
||||
Debian `smbclient` package or pull OpenPXE v0.4.66+"
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
Error::Other(e.into())
|
||||
}
|
||||
})?;
|
||||
let stdout = child
|
||||
.stdout
|
||||
.take()
|
||||
@@ -517,9 +530,22 @@ impl SmbShareManager {
|
||||
let output = match cmd.output().await {
|
||||
Ok(o) => o,
|
||||
Err(e) => {
|
||||
// v0.4.66: distinguish ENOENT (missing binary) from
|
||||
// other exec failures and pre-fill the hint so the
|
||||
// UI shows a clear remediation instead of the bare
|
||||
// "No such file or directory (os error 2)". This
|
||||
// shouldn't fire on the stock image — the Dockerfile
|
||||
// installs the `smbclient` package — but is a useful
|
||||
// breadcrumb for anyone running OpenPXE in a stripped
|
||||
// base image.
|
||||
let stderr = if e.kind() == std::io::ErrorKind::NotFound {
|
||||
"smbclient binary not found on $PATH".to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
return Err((
|
||||
format!("could not exec smbclient: {e}"),
|
||||
String::new(),
|
||||
stderr,
|
||||
));
|
||||
}
|
||||
};
|
||||
@@ -762,7 +788,25 @@ async fn tcp_probe(
|
||||
/// hints. Returns `None` when we don't have a translation.
|
||||
fn hint_for(text: &str) -> Option<String> {
|
||||
let s = text.to_ascii_lowercase();
|
||||
if s.contains("nt_status_logon_failure") || s.contains("logon_failure") {
|
||||
if s.contains("smbclient binary not found")
|
||||
|| s.contains("smbclient: no such file")
|
||||
|| (s.contains("could not exec smbclient") && s.contains("os error 2"))
|
||||
{
|
||||
// v0.4.66: this only fires on a stripped / custom runtime
|
||||
// image — the stock OpenPXE container ships `smbclient` from
|
||||
// the Debian `smbclient` package. The error surfaced on
|
||||
// v0.4.65 specifically because that release's Dockerfile
|
||||
// installed `samba` (the server) but not `smbclient` (the
|
||||
// client CLI). Operators on the stock image should never see
|
||||
// this; if they do, the fix is to upgrade.
|
||||
Some(
|
||||
"smbclient isn't installed in this container. Pull the \
|
||||
official OpenPXE image v0.4.66 or newer — the stock image \
|
||||
ships smbclient. If you're running a custom build, add the \
|
||||
Debian `smbclient` package to your runtime stage."
|
||||
.into(),
|
||||
)
|
||||
} else if s.contains("nt_status_logon_failure") || s.contains("logon_failure") {
|
||||
Some(
|
||||
"the server rejected the credentials. Double-check the username \
|
||||
and password — many NAS appliances use a separate SMB account \
|
||||
@@ -880,6 +924,24 @@ mod tests {
|
||||
assert!(hint_for("some unrelated error text").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hint_for_missing_smbclient_calls_out_upgrade() {
|
||||
// The exec-side path sets `stderr` to "smbclient binary not
|
||||
// found on $PATH" when ENOENT lands.
|
||||
let h = hint_for("smbclient binary not found on $PATH").unwrap();
|
||||
assert!(
|
||||
h.contains("smbclient") && h.to_lowercase().contains("install"),
|
||||
"expected upgrade/install guidance, got: {h}"
|
||||
);
|
||||
// The raw error path on the API side passes the verbatim
|
||||
// exec error in `error` plus an empty `stderr`. The
|
||||
// SmbShareError constructor's hint_for fallback checks error
|
||||
// too, so this pattern needs to translate as well.
|
||||
let h2 = hint_for("could not exec smbclient: No such file or directory (os error 2)")
|
||||
.unwrap();
|
||||
assert!(h2.contains("smbclient"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_ls_iso_finds_one_iso_and_skips_directories() {
|
||||
let out = "\
|
||||
|
||||
@@ -81,23 +81,30 @@ FROM debian:12-slim AS runtime
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates libcap2-bin tini gosu iproute2 \
|
||||
wimtools samba nfs-common \
|
||||
wimtools samba smbclient \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& useradd --system --uid 10001 --home-dir /var/lib/openpxe --shell /usr/sbin/nologin openpxe \
|
||||
&& mkdir -p /var/lib/openpxe/isos /var/lib/openpxe/work /var/lib/openpxe/smb \
|
||||
&& chown -R openpxe:openpxe /var/lib/openpxe
|
||||
# v0.4.5: the openpxe binary itself is now built against musl and is
|
||||
# fully static — no glibc dependency. The runtime stage still ships
|
||||
# Debian slim because OpenPXE shells out to the four packages below for
|
||||
# Debian slim because OpenPXE shells out to the packages below for
|
||||
# functionality we deliberately don't reimplement in-process:
|
||||
#
|
||||
# wimtools - `wimlib-imagex`, used to inject startnet.cmd into boot.wim.
|
||||
# samba - `smbd` serves extracted Windows install media on :445 so
|
||||
# WinPE can `net use`. Guest read-only, scoped to
|
||||
# /var/lib/openpxe/smb.
|
||||
# nfs-common - `mount.nfs` / `mount.nfs4` for the Storage tab's NFS
|
||||
# share manager. Mount requires CAP_SYS_ADMIN; without it
|
||||
# mount(2) returns EPERM and the manager surfaces a clear
|
||||
# error in the UI.
|
||||
# /var/lib/openpxe/smb. This package provides the SERVER
|
||||
# side only; the client CLI is a separate package below.
|
||||
# smbclient - v0.4.66: Samba's `smbclient` userspace CLI, used by
|
||||
# the Storage tab's SMB shares manager to list and stream
|
||||
# ISOs from remote SMB servers without ever mounting them
|
||||
# in the kernel. In Debian 12 `smbclient` is NOT pulled
|
||||
# in by the `samba` package — they're siblings, not
|
||||
# parent/child. v0.4.65 shipped without this line and
|
||||
# every "Add share" attempt surfaced
|
||||
# `could not exec smbclient: No such file or directory`
|
||||
# until this landed.
|
||||
# iproute2 - `ip addr` / `ip route` for the auto-detected Network
|
||||
# tab fields (NIC name, subnet mask, default gateway).
|
||||
# Tiny, always available; we don't pull in netlink crates
|
||||
@@ -105,8 +112,13 @@ RUN apt-get update \
|
||||
# gosu - drops privileges cleanly from root after the entrypoint
|
||||
# fixes bind-mount ownership (common OpenShift/Docker UX
|
||||
# issue).
|
||||
#
|
||||
# v0.4.65 dropped `nfs-common` — kernel-mount NFS is gone. The SMB
|
||||
# shares replacement uses userspace `smbclient` and needs no kernel
|
||||
# helpers.
|
||||
#
|
||||
# A future "openpxe-static" variant could drop everything except the
|
||||
# binary onto distroless once we move the Windows + NFS legs to
|
||||
# binary onto distroless once we move the Windows + SMB legs to
|
||||
# in-process Rust crates.
|
||||
|
||||
COPY --from=build /openpxe /usr/local/bin/openpxe
|
||||
|
||||
Reference in New Issue
Block a user