-
released this
2026-10-18 06:54:27 -04:00 | 1 commits to main since this releaseMenu-selection regression fix for v0.8.1, reported in #2: on x86_64 UEFI every menu item except the first bounced back to the menu with Queued Deployment highlighted, and
x(Exit) did nothing. Root cause was in the iPXE scripts OpenPXE generates — no client, firmware or network change is involved.docker pull gitea.milesward.dev/mward4/openpxe:0.8.2 docker pull gitea.milesward.dev/mward4/openpxe:latestMenu item selection works again
The generated dispatch lines were
iseq ${target} linux && chain .../_linux_menu.ipxe || goto menu. iPXE evaluates&&/||left to right (POSIX-shell style), so forA && B || CtheCbranch runs wheneverAfails — i.e. for every item except the first, thechainwas skipped andgoto menuredrew the menu with the--default queueitem highlighted. Dispatch is now the canonical iPXE pattern:goto ${target} || goto menufollowed by one:labelblock per item, for the top menu, both installer submenus, Tools and Utilities. A non-matching item can no longer fall into a straygoto menu.Exit, Back and failed boots
Menu-to-menu transitions use
chain --replace, so a submenu replaces the current menu image instead of nesting inside it.x(Exit iPXE) now reaches the firmware from any menu depth, Back/forward navigation no longer piles up script images, and boot-entry scripts recover to the main menu themselves if a kernel/initrd fetch fails (|| goto failed+chain --replace /boot.ipxe), now that the calling menu has been replaced.Same-class script fixes
- Arch footer: the arch-label probe chain ended in
|| true, which is not an iPXE command — on pairs outside the three allowlisted ones (e.g. i386 + EFI) the script aborted before drawing the menu. It is now three|| echo-terminated probes. - Utilities → Back returns to Tools again; it used to redraw Utilities and look like a dead key.
- NIC Info:
prompt --timeoutexpiring no longer aborts the script — the client returns to the menu. - Password prompt: emptiness is tested with
isset ${password}instead of comparing against a literal""token (iPXE's tokenizer has no quoting), so an accidental empty Enter returns to the main menu as the banner promises instead of taking the failed-login path. - Queued Deployment: if the queue endpoint is unreachable, the client falls back to the main menu instead of aborting into firmware.
No firmware change needed
snponly.efi,ipxe.efiand the other boot binaries are untouched — only the server-generated menu and entry scripts changed. Upgrading the container (or building frommain) is enough; no ISO re-upload, re-probe or settings migration.Tests
The menu scripts now have real control-flow tests: a small interpreter mirrors iPXE's
system()/process_script()semantics (whitespace tokenisation, left-associative&&/||,gotolabels, abort on a failing line) and asserts that every top-menu and submenu item reaches its expected chain URL, plus Exit/Reboot/firmware, unknown-architecture, password-prompt, shell and queue flows. The old dispatch lines fail these tests.cargo test --workspaceis green andcargo fmt --checkis clean.Thanks to @mmBesar for the detailed report and the Ctrl-B experiments that localised this so quickly.
Downloads
- Arch footer: the arch-label probe chain ended in
-
released this
2026-07-16 15:56:49 -04:00 | 4 commits to main since this releaseCompatibility & ease-of-use pass, inspired by Bootimus. Add images from the internet in one field, and stand up a fresh container with no setup wizard.
docker pull gitea.milesward.dev/mward4/openpxe:0.8.1 docker pull gitea.milesward.dev/mward4/openpxe:latestAdd ISO by URL
The Storage upload card gains an Or add by URL field. Paste any
http(s).isolink and the server streams it straight into storage and auto-detects it — same result as a drag-drop, no download-then-reupload. Progress (and Cancel) show as rows, just like uploads..iso-only, 16 GiB cap, credentials stripped from any displayed URL. Operator-gated and never runs at boot, so offline boot stays intact.The thought process behind this being if you were to stream a .iso from an internal url, as you update the url no configuration would be needed. IE. https://myurl.com/windows11.iso and as the .iso file is updated/iterated the PXE solution follows.
Zero-touch admin bootstrap
Set
OPENPXE_ADMIN_USERNAME+OPENPXE_ADMIN_PASSWORD(orOPENPXE_ADMIN_PASSWORD_FILEfor a Docker/K8s secret) and a fresh container comes up with the admin already created — no first-run wizard. Seeds the first run only; once an admin exists it's a no-op, so a lingering env var can't reset a rotated password.On the reference projects
Reviewed other paid/enterprise offerings and these were the main takeaways. Will continue iterating, and working towards a 1.0 releases this Q4.
Downloads
-
released this
2026-06-21 23:55:35 -04:00 | 5 commits to main since this releaseDependency prune, a memtest mislabel fix, concurrent multi-file uploads, and an operator API key for driving the HTTP API outside the browser.
docker pull gitea.milesward.dev/mward4/openpxe:0.8.0 docker pull gitea.milesward.dev/mward4/openpxe:latestOperator API key (
x-api-key)/api/*is no longer cookie-only. A persisted key — generated on first run, shown in Settings → Advanced → API with Copy + Regenerate — authenticates API calls when sent as thex-api-keyheader, with the same access as a logged-in operator. Drive uploads, deletes, and config from Postman / scripts / CI without a browser session. Constant-time verified; regenerating invalidates the old key immediately.Concurrent, stackable uploads
Drop several
.isofiles at once — each gets its own progress row and uploads independently (the browser's per-origin connection cap paces them) instead of the second drop clobbering the first. A page-leave warning fires while uploads are in flight, and apagehidekeepalive-abort tells the server to clean up if you navigate away.Abandoned uploads self-heal
Refreshing or closing the tab mid-upload used to strand a
.partialthat blocked the next attempt with "already uploading."begin_uploadnow reclaims a stale.partial(refresh / crash / dropped connection all safe) — the chunked protocol can't resume a dead session anyway.memtest86 (and other signed bootables) no longer flagged Windows
The bulk Windows scan dropped its over-broad
"microsoft"UTF-16 marker, which matched the Microsoft-signed Secure-Boot EFI loader that memtest86 — and signed BSDs / firmware tools — ship (the string lives in the loader's FAT long-filename entries). The remaining markers are all Windows-exclusive filenames.INTROSPECT_REV3 → 4, so existing local ISOs re-probe on first startup and shed the bogus label automatically.Leaner dependency tree
14 unused dependency declarations removed across 7 crates (ponytail audit);
quick-xmlandx509-parserleave the build entirely. No behavior change — just less to compile and audit.Downloads
-
v0.7.5 — Joliet fallback + tolerant El Torito: appliance ISOs classify; Unattended pagination Pre-Release
released this
2026-06-12 17:05:26 -04:00 | 7 commits to main since this releaseCloses the two false-negative holes found while triaging the v0.7.4 dashboard, and pages the Unattended files list.
docker pull gitea.milesward.dev/mward4/openpxe:0.7.5 docker pull gitea.milesward.dev/mward4/openpxe:latestAbout those "Non-bootable images"
The dashboard card is working as designed for genuine data ISOs — VMware VCSA and similar appliance bundles ship no El Torito boot catalog at all (you mount them on a workstation; they were never PXE-bootable, and before v0.7.4 they silently got sanboot entries that failed on real hardware). But the triage surfaced two ways a genuinely bootable image could be mislabeled, both fixed here:
- Joliet namespace fallback. The ISO9660 walker now falls back to the Joliet supplementary namespace when the primary tree misses. Windows-oriented mastering tools — common for vendor/appliance ISOs — write a minimal or mangled primary tree and keep the real filenames only in Joliet; those images probed as "no installer files" and their in-ISO kernel fetches 404'd. Applies to introspection (local + NFS/SFTP) and
/iso/{id}/{*path}serving. - Gap-tolerant El Torito walk. The boot-catalog scan no longer aborts at the first non-
CD001sector or stops at a Set Terminator — sloppy mastering leaves zeroed filler sectors inside the descriptor area that used to hide a real boot record. All 16 descriptor sectors are examined (the 25-byte exact signature can't false-positive). The PVD/volume-label scan got the same tolerance. - Everything re-checks itself:
INTROSPECT_REV→ 3, so local ISOs re-probe on first startup and the remote caches self-invalidate — anything the stricter v0.7.4 logic flagged gets a second look with the new walker automatically. If an image still shows in Non-bootable after this release, it really has no boot catalog and no installer tree.
Storage
Unattended files get the exact pager Available images got in v0.7.4: 5 per page,
Showing X–Y of Nwith Prev/Next, composed with the filter, page resets on every keystroke.
Validation: clippy (pedantic) clean, fmt clean, 319 workspace tests green (+3: Joliet fallback lookup, Joliet-only classification, filler-sector boot record), web UI syntax-checked.
Downloads
- Joliet namespace fallback. The ISO9660 walker now falls back to the Joliet supplementary namespace when the primary tree misses. Windows-oriented mastering tools — common for vendor/appliance ISOs — write a minimal or mangled primary tree and keep the real filenames only in Joliet; those images probed as "no installer files" and their in-ISO kernel fetches 404'd. Applies to introspection (local + NFS/SFTP) and
-
released this
2026-06-12 15:26:10 -04:00 | 8 commits to main since this releaseProbe-based introspection — remote-share ISOs finally classify, the gparted false-Windows bug is fixed, and Storage pages its library.
docker pull gitea.milesward.dev/mward4/openpxe:0.7.4 docker pull gitea.milesward.dev/mward4/openpxe:latestIntrospection, rebuilt
Detection no longer greps raw sectors for filename strings — it walks the ISO9660 directory tree and checks that the well-known boot files actually exist. The same probes run over NFS (READ3 at offset) and SFTP (seekable handles), so share-hosted ISOs classify like local ones instead of registering as
Unknown:- Remote ISOs: each scan registers entries instantly with a filename-based family, then a background pass probes every ISO over the share and upgrades it in place — volume label, real family, verified kernel/initrd, El Torito. Results persist in a per-share cache keyed on
path@size, so container restarts re-probe only new or replaced files. Your 40-ISO NFS wall of "Unknown" becomes AlmaLinux / CentOS / RHEL / RHCOS / OpenShift rows on the first scan after upgrade. - The gparted bug: any Linux ISO shipping GRUB/syslinux chainload modules contains the literal string
bootmgr, which the old byte-scan read as Windows. Linux probes now run first; gparted-live (and Clonezilla, Kali live, …) classify as Debian-family. Local ISOs re-probe automatically on first startup — no re-upload. - No more guessed boot paths: kernel entries are emitted only when kernel+initrd verifiably exist in the image. Shapes whose boot protocol needs arguments we don't render yet (Debian-live, d-i netinst, CoreOS/RHCOS/agent ISOs — the latter need their embedded ignition) classify for the UI but keep their working sanboot entries.
- Remote in-ISO serving:
/iso/{id}/{*path}now locates and streams files from inside NFS/SFTP-hosted ISOs, so a verified kernel entry on a remote RHEL DVD boots end-to-end. - SMB:
smbclientcan't seek, so SMB ISOs get the filename-token family and an honest "awaiting introspection" label, keeping their optimistic sanboot entry.
Storage
The Available images table is paged 5 per page with a quiet footer pager (
Showing 1–5 of 50· Prev/Next). The filter from v0.7.2 composes with it — search narrows, then pages, and the page resets on every keystroke.Hosts / Queue
The deployment-profile picker now reads “Unattended file (in Storage → Advanced)” so it's obvious where those files are managed.
Validation: clippy (pedantic) clean, fmt clean, 316 workspace tests green (+17 new: ISO9660 walker, probe shapes including the gparted regression and CoreOS, filename-token table, cache round-trips), web UI syntax-checked.
Downloads
- Remote ISOs: each scan registers entries instantly with a filename-based family, then a background pass probes every ISO over the share and upgrades it in place — volume label, real family, verified kernel/initrd, El Torito. Results persist in a per-share cache keyed on
-
released this
2026-06-09 22:44:31 -04:00 | 9 commits to main since this releaseDesign-language cleanup of v0.7.2 — no behaviour, protocol, or boot-path changes.
docker pull gitea.milesward.dev/mward4/openpxe:0.7.3 docker pull gitea.milesward.dev/mward4/openpxe:latestImage digest
sha256:72552bc3f3bf9f3a12f6cfe9951c289d2b699fbe8f1f23af4c4e2ba1459476a5(both tags), commit934cfba.Hosts
The Pin MAC to boot target form collapses to a single aligned 4-up row — MAC · Label · Architecture · Boot binary — with Target dropping full-width onto its own line beneath them. The per-field hints that broke the row's alignment moved into the explanatory note, so every control shares one baseline.
Storage
The image and unattended filter inputs are now wrapped in a
label.field, so they inherit the standard text-field chrome — border, radius, height, focus ring — instead of the raw browser<input type=search>look. They span the full card width for continuity with the rest of the page.Network
The Link row (operstate · speed · duplex · port MAC) moved below Public base URL. Its joined value runs long, so placing it last lets it wrap at the bottom of the list without shoving the other rows around.
Validation: clippy clean, fmt clean, 299 workspace tests green, web UI syntax-checked.
Downloads
-
released this
2026-06-09 21:47:48 -04:00 | 10 commits to main since this releaseOperator-experience release.
Storage
- Filter inputs for the Available images table (matches filename, family, category, source) and the Unattended files list. Forty-image libraries are now a keystroke away instead of a scroll.
Hosts — one form, one mental model
- The separate Boot rules card is gone. The Pin form gained Architecture (optional) next to Label (and kept the v0.7.1 boot-binary pin): a full MAC = per-host pin exactly as before; a MAC prefix and/or an architecture = first-match-wins group rule. Saved rules show in a compact Group rules card with remove buttons.
- The boot-decision webhook keeps working via the API (
/api/boot-rules) but no longer has a UI knob.
Network
- New Link row under NIC name:
link up · 1000 Mb/s · full duplex · port aa:bb:cc:…— read from sysfs at startup, so you can confirm which physical port answers PXE in multi-NIC or trunked environments. Shows—where unavailable (virtual NICs).
About
- Hero copy rewritten — positioning lead, three-pillar feature grid, privacy + no-test-cert principles stated crisply.
Fit & finish
- The doubled 30px gap above Queue Launch for all waiting and Network Save collapsed to the standard 16px every other card action uses.
Validation
clippy clean · fmt clean · 299 workspace tests green · WebUI syntax-checked.
Image
docker pull gitea.milesward.dev/mward4/openpxe:0.7.2:0.7.2/:latest— digestsha256:558849673250115eacd2af6f98789dd214dd49372328dd9a388b5985c17daf84(linux/amd64).Downloads
-
v0.7.1 — some devices just hate iPXE Pre-Release
released this
2026-06-09 21:16:22 -04:00 | 11 commits to main since this releaseOperational refinement of the v0.7.0 escalation ladder, answering "can a machine try all three boot binaries in one go?" The protocol can't carry three boot files in one cycle — but we can make the learning walk a once-per-machine-ever event, and let operators skip it entirely.
What changed
- Learned driver modes persist (
driver_modes.jsonin the work dir). A MAC that reaches the Shim rung, or proves Builtin works via the iPXE handoff, is remembered across restarts and idle periods — no more re-walking the ladder every 30 minutes. The file only records exceptions; a healthy fleet never writes it. Corrupt/missing file starts clean. - Rules can pin the boot binary. The Boot rules table (Hosts tab) gains a Boot binary column: auto (learn) / Firmware NIC / iPXE drivers / Secure Boot (shim). A pinned rule is consulted before the ladder — a known Secure-Boot rack boots the signed chain on its very first cycle, zero failures. Mode pins and target rules coexist on the same client.
- Same-boot self-healing in the shim stage. The GRUB config now tries to chainload full iPXE before showing the signed menu: with Secure Boot off the chainload succeeds and the client gets the complete iPXE feature set back in that same boot (and the handoff pins the working mode); with Secure Boot on, shim refuses it inline — no reboot — and the signed menu appears.
Why not literally one cycle for everyone
DHCP/PXE hands the firmware exactly one boot file per cycle, the Secure-Boot refusal happens after our part is done (no error report comes back), and the broken-NIC case specifically requires the firmware itself to load builtin-driver iPXE — GRUB's network rides the same broken firmware stack, so routing everything through shim-first would strand exactly the machines the Builtin rung exists for. Worst case remains 2 failed cycles (~15–30 s) — now paid once per machine per lifetime, or never with a rule pin.
Validation
clippy clean · fmt clean · 299 workspace tests green (+9 new: persistence round-trip across restart, Shim/Builtin pins surviving the TTL, corrupt-file recovery, default-mode-never-persisted, rule-pin matching with unknown-mode tolerance and pin/target coexistence, GRUB chainload-before-menu ordering, API round-trip of the pin field).
Image
docker pull gitea.milesward.dev/mward4/openpxe:0.7.1:0.7.1/:latest— digestsha256:4bc3da30528177c6895e2ec9e8ccdced7e06145a4c4765d95c61821f2888a163(linux/amd64).Downloads
- Learned driver modes persist (
-
released this
2026-06-09 20:17:27 -04:00 | 12 commits to main since this releaseThree features. All zero-toggle, all inside the core principles (single static binary, container-first, no test certs, no client trust-store changes).
Secure Boot — automatic signed shim+GRUB chain
The v0.6.1 escalation ladder gains a third rung: Firmware → Builtin → Shim. Secure-Boot firmware downloads our unsigned iPXE but refuses to execute it — after two unconfirmed attempts that MAC is automatically offered Fedora's Microsoft-signed
shim, which loads the signed GRUB, which fetches a server-rendered menu. Fully signed chain, Secure Boot stays ON, no firmware settings to change on the client.- Signed binaries (
shimx64.efi/grubx64.efi+ arm64 equivalents) are extracted from official Fedora 43 packages at image build and shipped byte-for-byte unmodified. - The GRUB menu is rendered live from your boot entries (Linux installers — a signed GRUB only boots signed kernels; sanboot/wimboot entries are iPXE-only and omitted there).
- Watch for
driver=Shimin the DHCP log when an SB client walks the ladder.
Boot rules + decision webhook (Matrix Boot, but open)
Ordered, first-match-wins rules — MAC prefix (OUI) and/or client architecture → boot target — generalizing per-MAC pins to whole racks or device families. Plus an optional boot-decision webhook: unmatched boots
GET <url>?mac=…&arch=…; a200 {"target":"<id>"}chains to that target, anything else falls through to the menu (2-second budget, fail-open — a dead endpoint can never block PXE). Editor on the Hosts tab. Decision order: exact pin → rules → webhook → menu.Hardened answer-file delivery
Answer files embed credentials; serving them openly is what got WDS hands-free deployment killed (CVE-2026-0386). Every generated unattended URL now carries a short-lived boot-scoped token, and
/unattended/*requires it (or a logged-in operator session) once an admin exists. Automatic — no setting, nothing to migrate; setup-mode installs stay open.Validation
clippy clean · fmt clean · 290 workspace tests green (+18 new) · image build verified the live Fedora fetch (shim-x64 15.8-3 + grub2 2.12-40.fc43, x86_64 and arm64).
Image
docker pull gitea.milesward.dev/mward4/openpxe:0.7.0:0.7.0/:latest— digestsha256:db8cfde9b27a0602981066bcfea52cd5399e90fa1945bf2449b3b8cfde51857f(linux/amd64).Real-world validation notes: the Shim rung needs a Secure-Boot-enabled machine to confirm end-to-end (it adds two quick failed PXE attempts before the signed chain kicks in — expected); rules/webhook and tokens are fully covered by integration tests.
Downloads
- Signed binaries (
-
v0.6.3 — russh security bump + axum 0.8 Pre-Release
released this
2026-06-09 19:47:13 -04:00 | 13 commits to main since this releaseSecurity-driven dependency release. Recommended upgrade for anyone using SFTP remote libraries.
Security
- russh 0.55 → 0.61.2 — closes a batch of advisories reachable from our SFTP client path: unbounded / allocation-first packet parsing (CVE-2026-48110, CVE-2026-46702, CVE-2026-46673 — HIGH) plus CVE-2026-48107 in client auth. Previously a malicious or compromised SFTP server an operator pointed OpenPXE at could OOM the server. Bonus: ~21% SSH throughput improvement upstream (mlock removed on non-secret buffers) — directly in the remote-share ISO streaming path.
- bergshamra 0.4 → 0.5.1 (SAML) — the long-standing russh version pin existed only because bergshamra-crypto used release-candidate RustCrypto crates; 0.5 moved to the stable generation, so the pin is gone. 0.5 is also secure-by-default for XML-DSig (settings we already enforced explicitly) and fixes an XML-Enc DerivedKey fallthrough.
Framework
- axum 0.7 → 0.8.9 — route captures migrated to
{param}syntax (router +/api/docs). Gains the HEAD responsecontent-lengthfix (iPXE/sanboot clients probe with HEAD before issuing Range requests) and returns us to the maintained release line.
Validation
clippy clean · fmt clean · all 272 workspace tests green · static musl image build (the authoritative gate for the russh bump) passed.
Image
docker pull gitea.milesward.dev/mward4/openpxe:0.6.3:0.6.3/:latest— digestsha256:f35e6b76aa6ae31bcc24505a91eef55f4dc4df86567a820dd3d40d2e49902eee(linux/amd64).Downloads