• v0.8.1 5f98e6e03f

    mward4 released this 2026-07-16 15:56:49 -04:00 | 0 commits to main since this release

    Compatibility & 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:latest
    

    Add ISO by URL

    The Storage upload card gains an Or add by URL field. Paste any http(s) .iso link 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.

    Zero-touch admin bootstrap

    Set OPENPXE_ADMIN_USERNAME + OPENPXE_ADMIN_PASSWORD (or OPENPXE_ADMIN_PASSWORD_FILE for 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

    Dnsmasq-PXE is a manual dnsmasq + TFTP + NFS setup guide — the exact stack OpenPXE replaces, so nothing to port. Bootimus is the true peer; OpenPXE already matched it on proxyDHCP, web-only UI, distro auto-detect, Windows wimboot + SMB, per-MAC pins, WoL, boot log, REST API, and Secure Boot — and still leads on NFS/SFTP libraries and Queued Deployment. This release closes the two ease-of-use gaps that remained: getting images onto the box, and first-run friction.

    Under the "fewer options, things just work" principle we deliberately skipped a hardcoded tool catalog (URL rot + menu clutter — the URL field covers it) and generic kernel/initrd guessing for unknown ISOs (keeps the "sanboot beats a broken guess" rule from v0.7.4).

    Downloads
  • v0.8.0 1c262a6d61

    mward4 released this 2026-06-21 23:55:35 -04:00 | 1 commits to main since this release

    Dependency 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:latest
    

    Operator 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 the x-api-key header, 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 .iso files 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 a pagehide keepalive-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 .partial that blocked the next attempt with "already uploading." begin_upload now 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_REV 3 → 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-xml and x509-parser leave the build entirely. No behavior change — just less to compile and audit.

    Downloads
  • v0.7.5 1ded291c7b

    mward4 released this 2026-06-12 17:05:26 -04:00 | 3 commits to main since this release

    Closes 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:latest
    

    About 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-CD001 sector 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 N with 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
  • v0.7.4 6524aa4118

    mward4 released this 2026-06-12 15:26:10 -04:00 | 4 commits to main since this release

    Probe-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:latest
    

    Introspection, 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: smbclient can'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
  • v0.7.3 934cfbab46

    mward4 released this 2026-06-09 22:44:31 -04:00 | 5 commits to main since this release

    Design-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:latest
    

    Image digest sha256:72552bc3f3bf9f3a12f6cfe9951c289d2b699fbe8f1f23af4c4e2ba1459476a5 (both tags), commit 934cfba.

    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
  • v0.7.2 a71057fce6

    mward4 released this 2026-06-09 21:47:48 -04:00 | 6 commits to main since this release

    Operator-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 — digest sha256:558849673250115eacd2af6f98789dd214dd49372328dd9a388b5985c17daf84 (linux/amd64).

    Downloads
  • v0.7.1 29040e8a5a

    mward4 released this 2026-06-09 21:16:22 -04:00 | 7 commits to main since this release

    Operational 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.json in 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 — digest sha256:4bc3da30528177c6895e2ec9e8ccdced7e06145a4c4765d95c61821f2888a163 (linux/amd64).

    Downloads
  • v0.7.0 3a32d65fb7

    mward4 released this 2026-06-09 20:17:27 -04:00 | 8 commits to main since this release

    Three 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=Shim in 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=…; a 200 {"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 — digest sha256: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
  • v0.6.3 7f25bb681c

    mward4 released this 2026-06-09 19:47:13 -04:00 | 9 commits to main since this release

    Security-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 response content-length fix (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 — digest sha256:f35e6b76aa6ae31bcc24505a91eef55f4dc4df86567a820dd3d40d2e49902eee (linux/amd64).

    Downloads
  • v0.6.2 5da05a519d

    mward4 released this 2026-06-09 16:45:11 -04:00 | 10 commits to main since this release

    Full-codebase review and polish pass — every loose end finished or removed, the serving hot paths optimized, and the dependency tree refreshed for reliability. No behavior changes for working clients; legacy clients get clearer protocol errors.

    Performance (per-boot hot paths)

    • Zero-copy iPXE serving — TFTP and HTTP now serve the embedded boot binaries straight from the binary's rodata instead of heap-copying ~1 MiB per request.
    • PXE menu background cached — the composited console --picture PNG was re-encoded (~50–200 ms) for every booting client; it's now composed once per logo change.
    • bcrypt off the async runtime — boot-password checks and login/setup/rotation run on the blocking pool, so CPU-heavy auth can't stall ISO streaming to imaging machines.
    • Per-range-request overhead halved on /iso/… (single registry lock + clone instead of two), O(1) DHCP driver-escalation bookkeeping, one-allocation MAC formatting, and a fixed introspection buffer that no longer forces a guaranteed 32 MiB realloc.

    Correctness & legacy-client polish

    • TFTP speaks up instead of going silent: write requests get a proper Illegal TFTP operation error and non-octet (netascii) transfers a clear only octet mode is supported — legacy clients fail fast with a reason instead of retrying into a timeout.
    • HTTP Range handling is now RFC 7233-correct: malformed Range: headers are ignored (200 + full body) instead of answered with a bogus 206.

    Cleanup

    • Removed the never-enforced mac_allowlist/subnet_allowlist config fields (line-of-sight serving is the design; nothing should advertise access control that doesn't exist).
    • Removed dead client-registry API and never-emitted event variants; rustfmt sweep over six drifted files.

    Dependencies

    • dhcproto 0.12 → 0.15 — drops the deprecated/unmaintained trust-dns-proto from the tree (replaced by hickory-proto) and picks up three releases of DHCP option coverage.
    • socket2 0.6 (dedupes the tree), bcrypt 0.19, tower-http 0.6.11 (sheds iri-string), tokio 1.52.3 / hyper 1.10 lockfile refresh; dead nom workspace entry deleted.

    Validation

    cargo fmt clean · clippy (pedantic profile) clean · all 272 workspace tests green on the new dependency stack · WebUI JS syntax-checked.

    Image

    docker pull gitea.milesward.dev/mward4/openpxe:0.6.2
    

    :0.6.2 / :latest — digest sha256:53f33a65129d1b49106f5b376c0517bcbee117fe743e22e8497a7c2f55563b99 (linux/amd64).

    Downloads