In 0.8.1 on x86_64 UEFI (snponly.efi) the boot menu drew and the keyboard
worked, but every item except the first one bounced straight back to the
menu with the default Queued Deployment entry highlighted; the `x` Exit
item did the same. Root cause was the generated dispatch lines:
iseq ${target} linux && chain .../boot/_linux_menu.ipxe || goto menu
iPXE evaluates `&&`/`||` strictly left to right (src/core/exec.c
`system()`), so for `A && B || C` the `goto menu` runs whenever A fails --
for every item except the first. Only `local` ever reached its chain line,
which is exactly the reported symptom. The same shape made `x` useless,
and `goto menu` redraws with `--default queue`. The bug report's Ctrl-B
test (chaining an entry script by hand) booted fine, confirming the
scripts and HTTP/TFTP paths were healthy.
Fix, all in crates/http-api/src/ipxe_script.rs:
- Dispatch with `goto ${target} || goto menu` to `:label` blocks (the
canonical iPXE menu pattern) in the top menu, both installer submenus,
the tools menu and the utilities menu, so a non-matching item can never
fall through into a stray `goto menu`.
- Menu-to-menu transitions use `chain --replace`, so the previous menu
image is replaced: `exit` from any submenu reaches the firmware and
repeated Back/forward navigation no longer nests script images.
- Boot-entry scripts now recover to the menu themselves (kernel/initrd
fetches `|| goto failed`, `:failed` chain --replace boot.ipxe), since
with --replace the calling menu can no longer catch their errors.
- Arch-label mapping: three `|| echo` probes instead of one chain ending
in `|| true` (`true` is not an iPXE command; non-allowlisted pairs such
as i386+efi aborted the script before the menu was drawn).
- Utilities menu Back really returns to Tools (it used to redraw
Utilities); `prompt --timeout` expiry no longer aborts NIC info;
password prompt tests emptiness with `isset` instead of a literal `""`
token (iPXE has no quoting, so an empty Enter skipped the main-menu
path); queue entry view falls back to the main menu if the queue
endpoint is unreachable.
Tests: new `menu_dispatch_tests` interpreter that mirrors iPXE's
`system()`/`process_script()` (whitespace tokenisation, left-associative
`&&`/`||`, goto labels, abort on nonzero line) and asserts each top-menu
and submenu item reaches its expected chain URL (the old lines would
fail these), plus exit/reboot/firmware, unknown-architecture, password
prompt, shell and queue flows. Text assertions, the insta snapshot and the
tools-menu wiring assertion in tests/full_flow.rs updated to match.
`cargo fmt --check` clean, no new clippy warnings, `cargo test
--workspace` green (all crates).
146 lines
5.6 KiB
TOML
146 lines
5.6 KiB
TOML
[workspace]
|
|
resolver = "2"
|
|
members = [
|
|
"crates/core",
|
|
"crates/dhcp-proxy",
|
|
"crates/tftp",
|
|
"crates/http-api",
|
|
"crates/iso-store",
|
|
"crates/ipxe-assets",
|
|
"crates/webui",
|
|
"crates/openpxe",
|
|
]
|
|
|
|
[workspace.package]
|
|
version = "0.8.2"
|
|
edition = "2021"
|
|
rust-version = "1.95"
|
|
license = "MIT OR Apache-2.0"
|
|
repository = "https://gitea.milesward.dev/mward4/OpenPXE"
|
|
authors = ["OpenPXE contributors"]
|
|
|
|
[workspace.dependencies]
|
|
tokio = { version = "1.52", features = ["full"] }
|
|
tokio-util = { version = "0.7", features = ["io"] }
|
|
tokio-stream = { version = "0.1", features = ["sync"] }
|
|
futures = "0.3"
|
|
async-trait = "0.1"
|
|
|
|
# v0.6.2: dhcproto 0.15 drops the deprecated trust-dns-proto dependency
|
|
# (replaced by hickory-proto) and carries three releases of DHCP option
|
|
# coverage accumulated upstream — both directly relevant to the proxy core.
|
|
dhcproto = "0.15"
|
|
socket2 = { version = "0.6", features = ["all"] }
|
|
bytes = "1.7"
|
|
|
|
axum = { version = "0.8", features = ["macros", "multipart", "http2"] }
|
|
tower = "0.5"
|
|
tower-http = { version = "0.6", features = ["fs", "trace", "cors", "limit"] }
|
|
hyper = "1.9"
|
|
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json"] }
|
|
|
|
serde = { version = "1.0", features = ["derive"] }
|
|
serde_json = "1.0"
|
|
toml = "0.8"
|
|
# v0.5.4: layered config (TOML file + env). Pure-Rust, no C deps; keeps the
|
|
# static-musl build OpenSSL-free. Replaces the hand-rolled apply_env mapping.
|
|
figment = { version = "0.10", features = ["toml", "env"] }
|
|
|
|
tracing = "0.1"
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
|
|
|
anyhow = "1.0"
|
|
thiserror = "2.0"
|
|
clap = { version = "4.5", features = ["derive", "env"] }
|
|
uuid = { version = "1.10", features = ["v4", "serde"] }
|
|
time = { version = "0.3", features = ["serde", "serde-human-readable", "formatting", "macros"] }
|
|
# sha2 stays 0.10 deliberately: bergshamra-crypto requires ^0.10, and
|
|
# bumping to 0.11 would split the RustCrypto digest stack in the tree.
|
|
sha2 = "0.10"
|
|
hex = "0.4"
|
|
bcrypt = "0.19"
|
|
parking_lot = "0.12"
|
|
rust-embed = { version = "8.5", features = ["include-exclude"] }
|
|
|
|
# v0.4.67: pure-Rust NFSv3 client. Replaces the (deleted-in-v0.4.65)
|
|
# kernel-mount NFS path with an in-process implementation that works
|
|
# in any container — no kernel modules, no CAP_SYS_ADMIN, no
|
|
# subprocess. Ships alongside the userspace SMB consumer; operators
|
|
# pick whichever protocol their NAS prefers.
|
|
nfs3_client = { version = "0.9", features = ["tokio"] }
|
|
nfs3_types = "0.5"
|
|
|
|
# v0.5.0: SMTP for webhook notifications (Slack/Teams/Discord go over
|
|
# plain HTTP via reqwest; email needs a real SMTP client). rustls TLS
|
|
# to match reqwest and stay musl-static-friendly — no OpenSSL.
|
|
lettre = { version = "0.11", default-features = false, features = ["smtp-transport", "tokio1-rustls-tls", "builder", "hostname"] }
|
|
|
|
# v0.5.1: pure-Rust SAML 2.0 Service Provider. bergshamra does XML-DSig
|
|
# verification + exclusive c14n with RustCrypto (no OpenSSL/xmlsec/libxml2
|
|
# C deps), so the static musl binary stays OpenSSL-free — samael was
|
|
# rejected precisely because it hard-requires OpenSSL. We build the thin
|
|
# SP layer (AuthnRequest, metadata parse, SAMLResponse semantics) on top.
|
|
bergshamra = "0.5"
|
|
roxmltree = "0.21"
|
|
# flate2 default backend is miniz_oxide (pure Rust) — do NOT enable the
|
|
# zlib/zlib-ng C backends, which would break the musl-static build.
|
|
flate2 = "1.1"
|
|
base64 = "0.22"
|
|
|
|
# v0.5.5: pure-Rust SSH/SFTP client for reading remote ISO libraries
|
|
# over SFTP without a kernel mount.
|
|
#
|
|
# CRITICAL #1 — crypto backend: `default-features = false` +
|
|
# `features = ["ring"]`. russh's *default* backend is `aws-lc-rs`, which
|
|
# pulls `aws-lc-sys` (C code, fiddly under musl); the `ring` feature
|
|
# instead reuses `ring 0.17` — the exact crate+version already in the
|
|
# binary via rustls + bergshamra — so SFTP adds ZERO new C/crypto deps
|
|
# and the static-musl build stays OpenSSL-free.
|
|
#
|
|
# CRITICAL #2 — history: this was pinned to =0.55.0 from v0.5.5 until
|
|
# v0.6.3 because bergshamra-crypto pinned release-candidate RustCrypto
|
|
# crates that conflicted with the stable generation russh 0.56+ pulls.
|
|
# bergshamra 0.5 (2026-06) moved to the stable generation (pkcs8 0.11),
|
|
# lifting the pin. v0.6.3 bumps to 0.61+, which also closes a batch of
|
|
# RUSTSEC advisories reachable from the SFTP *client* path (unbounded
|
|
# allocations in packet parsing — CVE-2026-48110/-46702/-46673 et al.)
|
|
# and drops mlock on non-secret buffers (~21% SSH throughput upstream).
|
|
#
|
|
# SCP was deliberately rejected: the protocol is sequential-only (no
|
|
# random access → no HTTP Range, unlike SFTP/NFS) and the mature SCP
|
|
# crates wrap libssh2 (C + OpenSSL), which would break this build.
|
|
russh = { version = "0.61", default-features = false, features = ["ring"] }
|
|
russh-sftp = "2.3"
|
|
|
|
openpxe-core = { path = "crates/core" }
|
|
openpxe-dhcp-proxy = { path = "crates/dhcp-proxy" }
|
|
openpxe-tftp = { path = "crates/tftp" }
|
|
openpxe-http-api = { path = "crates/http-api" }
|
|
openpxe-iso-store = { path = "crates/iso-store" }
|
|
openpxe-ipxe-assets = { path = "crates/ipxe-assets" }
|
|
openpxe-webui = { path = "crates/webui" }
|
|
|
|
[workspace.lints.rust]
|
|
unsafe_code = "deny"
|
|
rust_2018_idioms = { level = "warn", priority = -1 }
|
|
|
|
[workspace.lints.clippy]
|
|
pedantic = { level = "warn", priority = -1 }
|
|
module_name_repetitions = "allow"
|
|
missing_errors_doc = "allow"
|
|
missing_panics_doc = "allow"
|
|
must_use_candidate = "allow"
|
|
doc_markdown = "allow"
|
|
items_after_statements = "allow"
|
|
cast_possible_truncation = "allow"
|
|
cast_lossless = "allow"
|
|
cast_sign_loss = "allow"
|
|
similar_names = "allow"
|
|
too_many_lines = "allow"
|
|
|
|
[profile.release]
|
|
lto = "thin"
|
|
codegen-units = 1
|
|
strip = "symbols"
|
|
opt-level = 3
|