• v0.5.1 cbcd63bb14

    OpenPXE v0.5.1 Pre-Release

    mward4 released this 2026-05-31 02:10:37 -04:00 | 26 commits to main since this release

    OpenPXE v0.5.1

    SAML SSO goes live, and the Settings/Storage tabs get a cleanup.

    Single sign-on (SAML 2.0) — now wired end-to-end

    SSO configuration has been storage-only since v0.4.5 (you could paste your
    IdP metadata, but it didn't log anyone in). v0.5.1 wires the real flow.

    • "Sign in with " on the login page now starts an SP-initiated SAML
      login: OpenPXE builds an AuthnRequest, redirects to your IdP, and the new
      /api/sso/acs endpoint consumes the signed SAMLResponse, mints an
      operator session, and drops you on the dashboard.
    • IdP-initiated login is supported too, gated behind a new
      "Allow SSO login initiated by identity provider" toggle (off by default) —
      mirroring FleetDM.
    • Pure-Rust, no OpenSSL. Signature verification + exclusive XML
      canonicalization run on the pure-Rust bergshamra
      crate (RustCrypto, #![forbid(unsafe_code)]). The container stays a fully
      static, OpenSSL-free musl binary — no xmlsec/libxml2 C dependencies.
    • Security checks are the ones that matter for an SP: the signature is
      verified against your IdP's pinned certificate only (an attacker can't swap
      in their own key via KeyInfo), with XML-Signature-Wrapping defenses, plus
      Status / Destination / Audience / time-window validation, InResponseTo
      correlation, IdP-initiated gating, and an assertion-replay guard.
    • Access model: any user your IdP authenticates and OpenPXE
      cryptographically verifies gets an operator session. OpenPXE stays
      single-tier (no user/role table); the local admin account remains a
      guaranteed fallback owner
      regardless of SSO state.
    • New SSO settings: Entity ID (defaults to your public base URL) and the
      IdP-initiated toggle. A new GET /api/sso/metadata endpoint serves OpenPXE's
      SP metadata for easy import into your IdP.

    Scope for this release: assertions must be unencrypted, AuthnRequests are
    sent unsigned (your IdP must not require client signatures), and the NameID
    is the user's email. Single Logout and encrypted assertions are not yet
    supported.

    🧹 UI cleanup

    • Advanced tab folded into Settings. The standalone Advanced sidebar
      entry is gone; its webhook/email notification settings and the API reference
      now live in a collapsible "Advanced" section at the bottom of Settings.
    • Storage → "Remote shares". The separate SMB shares and NFS shares
      cards are merged into one Remote shares card with a protocol dropdown
      (SMB / NFS) and a single, protocol-badged table. Same backends, far less
      clutter — and room for Config files down the road.

    Quality

    • 17 new SAML SP unit tests (accept the valid case; reject tampered, unsigned,
      wrong-key, wrong-audience, expired, future-dated, wrong-issuer, and
      non-success responses) + 6 ACS integration tests (happy path, IdP-initiated
      gating, SP request correlation, assertion replay, garbage input).
    • Full workspace: 206 tests green, clippy clean.

    🐳 Pull it

    docker pull gitea.milesward.dev/mward4/openpxe:0.5.1
    # or
    docker pull gitea.milesward.dev/mward4/openpxe:latest
    
    Downloads
  • v0.5.0 f9df3f8bd8

    mward4 released this 2026-05-29 15:12:27 -04:00 | 28 commits to main since this release

    OpenPXE v0.5.0

    The v0.4.x chapter is closed — NFS works end to end — and v0.5.0
    turns OpenPXE from "a PXE server" into something you can hand an L1
    desktop tech: wake machines, get notified, brand the whole thing,
    and check for updates, all from one container.

    Wake-on-LAN

    Bind a MAC under Hosts, and each bound host now has a Wake
    button. It sends a Wake-on-LAN magic packet to the limited broadcast
    and the server's own subnet broadcast (computed from OpenPXE's
    advertised IP + detected mask), so it reaches the right VLAN with zero
    network config on the common "same segment" setup. No special container
    capability required.

    Safety: WoL only fires for MACs that are actually bound — it's not an
    open "spray packets at anything" endpoint.

    Webhook notifications (Slack / Teams / Discord / SMTP)

    A new Advanced tab carries notification settings. Pick one provider,
    paste the incoming-webhook URL (or fill in SMTP), hit Send test, and
    you'll get a ping when:

    • a machine PXE-boots an image (the canonical "imaging started" moment),
    • a host is woken via WoL.

    All four transports are native — Slack, Discord, and Teams over their
    incoming-webhook JSON shapes, email over SMTP (rustls, STARTTLS or
    implicit TLS — never plaintext). Sends are best-effort and time-bounded,
    so a flaky webhook can never wedge a PXE boot. The SMTP password is
    stored but never returned by the API — the config screen shows
    "(unchanged)" and only overwrites it if you type a new one.

    Advanced settings tab

    New nav item under Settings. Holds the webhook config and the API
    reference
    block (relocated from the bottom of Settings — Settings is
    now just Account / SSO / Branding).

    Login page logo (FleetDM style)

    The sign-in and first-run-setup screens now use the same brand treatment
    as the dashboard: when you've uploaded a custom logo under
    Settings → Branding, it spans the card header at full width and the
    "OpenPXE" wordmark is dropped — your logo is the brand. No custom logo?
    The bundled mark + wordmark, as before. Rendered from the public
    /api/me bootstrap so there's no flash of the default.

    About: check for updates + licenses

    • Check for updates button queries the project's Gitea releases API
      and tells you whether you're current or a newer tag is out (with a
      link to the release). Strictly on-demand — OpenPXE never polls in the
      background, preserving the air-gapped promise.
    • A License card documents the dual MIT OR Apache-2.0 license
      with links to both full texts, plus a note on bundled components
      (iPXE GPLv2/UBDL, samba's smbclient, wimtools, the Rust crates).

    Under the hood

    • New core::wol and core::notify (config + persistence); delivery
      lives in http-api::notify (reqwest for chat, lettre for SMTP).
    • New deps: lettre (SMTP) and the json feature on reqwest. Both
      use rustls + ring — the static musl binary stays OpenSSL-free.
    • 179 tests passing (WoL magic-packet loopback, notify config
      round-trip + password redaction, webhook validation, WoL-on-unbound
      → 404, version comparison). cargo clippy --workspace --all-targets -D warnings clean.

    Container image

    • gitea.milesward.dev/mward4/openpxe:0.5.0
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe; PNG-enabled x86_64 iPXE baked in
    (from v0.4.69).

    Compatibility

    • Additive. New state file notify.json; existing files load unchanged.
    • /api/me gains has_custom_logo + logo_rev (additive).
    • The API reference moved tabs in the UI but every endpoint is
      unchanged; new endpoints: /api/hosts/:mac/wol, /api/notify,
      /api/notify/test, /api/updates/check.
    Downloads
  • mward4 released this 2026-05-29 03:11:35 -04:00 | 30 commits to main since this release

    OpenPXE v0.4.69

    The graphical-boot-menu release. Plus the NFS fix that finally lands
    NFS shares, and a FleetDM-style full-width custom logo.

    1. Graphical PXE boot background — built from source, no more ASCII

    OpenPXE now paints a real graphical background on the PXE boot menu,
    the way iVentoy does. Your uploaded logo sits across the top of the
    boot screen on a clean dark field; the ISO menu renders below it. With
    no logo uploaded you still get a designed background (a default OpenPXE
    mark) — the old ASCII wordmark is gone entirely.

    How (and why it took until now): the boot-menu background needs
    iPXE compiled with CONSOLE_FRAMEBUFFER + IMAGE_PNG + CONSOLE_CMD
    so its console --picture command can paint a PNG. The public iPXE
    binaries omit those flags, and building iPXE ourselves had been blocked
    for eight releases by cc1 segfaulting under QEMU emulation on the
    arm64 build host.

    v0.4.69 fixes the build the right way: a dedicated build stage that
    runs on the native builder architecture and cross-compiles x86_64
    iPXE with a real cross-compiler (x86_64-linux-gnu-gcc). No emulation,
    no segfault — the iPXE build now takes ~2 minutes and is fully baked
    into the image. Validated end-to-end: the PNG decoder, framebuffer
    console, and pixel-buffer code are all confirmed linked into the
    binary.

    What this means in practice:

    • x86_64 UEFI clients (the overwhelming majority of modern
      machines) get the full graphical background.
    • BIOS / i386 / arm64 clients keep the upstream binaries and fall
      back to a clean text menu (no ASCII, just the menu).
    • Upload any PNG, JPEG, WebP, or GIF logo — OpenPXE composites it
      onto a 1024×768 background and transcodes to PNG (the only format
      iPXE consumes). WebP works on the input side even though iPXE itself
      can't read WebP.

    The same uploaded logo now drives three places: the WebUI top-left
    brand mark, and the PXE boot background on machines.

    2. NFS shares actually mount now (NFS3ERR_ACCES fixed)

    v0.4.68's privileged-port fix got past the mount denial
    (MNT3ERR_ACCES); operators then hit NFS3ERR_ACCES on the directory
    listing. Root cause: the NFS client defaulted to AUTH_NONE, but
    essentially every NFS server (UniFi UNAS included) exports sec=sys
    and rejects anonymous callers on the actual file operations.

    v0.4.69 presents a proper AUTH_SYS credential (uid 0 / gid 0). On a
    no_root_squash server that's full root read; on the far more common
    root_squash server it maps to the anonymous user, which can read any
    world-readable ISO share — the normal case. No new UI field: ISO
    libraries are read-only shared data, and a uid knob is exactly the kind
    of thing that makes a tool confusing for an L1 tech.

    For your UNAS Pro: server 192.168.1.51, export
    /var/nfs/shared/isos, host IP allow-listed — this should now mount
    and list. If you still see NFS3ERR_ACCES, the new hint spells out
    that it's now a server-side directory-permission/squash issue, not an
    IP or auth-flavor one.

    3. FleetDM-style full-width logo (top-left)

    When you upload a custom logo it now spans the sidebar header
    (left-aligned, up to ~200×50, scaled to fit) and the "OpenPXE"
    wordmark is hidden — the FleetDM pattern, where the uploaded logo is
    the sole brand element. The bundled-default (no upload) keeps the mark

    • wordmark. Rendered server-side so there's no flash of the default on
      load.

    Quality

    • 164 tests passing. cargo clippy --workspace --all-targets -D warnings clean.
    • iPXE build stage validated in isolation (png/fbcon/pixbuf linkage
      confirmed) before the full image build.

    Container image

    • gitea.milesward.dev/mward4/openpxe:0.4.69
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe binary; PNG-enabled x86_64 iPXE
    baked in.

    What still needs your real-world test

    The PNG background is validated at the binary level (the right code is
    compiled and linked) but hasn't been booted on real hardware from this
    build yet. Please PXE-boot an x86_64 UEFI machine and confirm the
    background paints. Once that's confirmed in the field, this chapter
    closes and we move to v0.4.7.

    Compatibility

    • Additive; no data-file format changes.
    • /branding/pxe-logo now always returns a PNG (was 404 with no logo).
      iPXE clients are unaffected (they just get a background now).
    Downloads
  • mward4 released this 2026-05-28 21:34:41 -04:00 | 31 commits to main since this release

    OpenPXE v0.4.68

    Bug-fix release addressing four issues from v0.4.67 field validation.

    1. NFS mount failed with MNT3ERR_ACCES even with the host IP allow-listed

    This is the big one. Operators added their UNAS Pro NFS export with
    the host IP correctly in the allow-list and still got
    connect failed: MNT3ERR_ACCES.

    Root cause: Linux kernel nfsd — which UniFi UNAS, Synology, TrueNAS,
    and essentially every appliance NAS run underneath — exports with the
    secure option by default. secure means the server only accepts
    mount/NFS requests whose TCP source port is privileged (< 1024). A
    client connecting from an ephemeral high port is denied at mount time,
    regardless of the allow-list.

    v0.4.67 deliberately connected from a non-privileged port on the
    mistaken assumption that uid 10001 couldn't bind low ports. But the
    OpenPXE binary carries CAP_NET_BIND_SERVICE (granted via setcap so
    it can bind the DHCP/TFTP/HTTP low ports as non-root), and that
    capability also lets it bind a privileged source port for an
    outbound connection.

    Fix: OpenPXE now connects from a privileged source port first (what
    every appliance NAS with default secure exports needs), and falls
    back to a non-privileged port for insecure exports or environments
    without the capability. Each attempt has its own connect timeout; a
    no-answer timeout on the first attempt skips the fallback so an
    unreachable server doesn't double your wait.

    For your UniFi UNAS Pro: with v0.4.68, server 192.168.1.51 +
    export /var/nfs/shared/isos (the full path the UNAS mount command
    shows — not just /isos) should now mount. No server-side change
    needed since your export uses the default secure option and OpenPXE
    now speaks it correctly.

    If a mount still fails, the error banner now distinguishes
    MNT3ERR_ACCES and spells out both checks: host IP in the allow-list,
    and adding insecure to the export if the server refuses privileged
    ports for some reason.

    2. Custom logo didn't update the top-left brand mark

    The WebUI brand mark and favicon were cache-busted only by the OpenPXE
    version (?v=0.4.67), which doesn't change when you upload a logo — so
    the browser kept serving the cached default mark.

    Fix: the branding store now carries a revision counter that bumps on
    every logo upload/clear and is woven into the brand-mark URL as a second
    cache-bust token. Upload a logo, the page reloads, the new mark appears
    immediately. Remove it, the default comes back. No hard refresh needed.

    About PXE-booting machines: this fixes the WebUI top-left mark. The
    PXE boot menu still shows the ASCII "OpenPXE" wordmark, not your
    uploaded image — painting a PNG in the iPXE menu requires an
    IMAGE_PNG-enabled iPXE build, which the stock boot.ipxe.org binaries
    don't include. The server-side compositor that prepares your logo for
    that paint (/branding/pxe-logo) is wired and ready; it goes live the
    moment the iPXE rebuild lands (queued for native x86_64 hardware). Until
    then, the boot menu wordmark is text-only by design.

    3. Disk-space card on the Dashboard

    The disk-space card from the Storage tab now also appears on the
    Dashboard, directly under the stat strip, so capacity is visible from
    the landing page. Same coloured bar, same 80%/95% warnings.

    4. NFS "Add share" button spacing

    The NFS shares form has a single row (vs the SMB form's two), so the Add
    share button sat flush against the input fields. Added top spacing to
    match the SMB card.

    Quality

    • 162 tests passing (+2: logo-revision bump, MNT3ERR_ACCES hint).
    • cargo clippy --workspace --all-targets -- -D warnings clean.

    Container image

    • gitea.milesward.dev/mward4/openpxe:0.4.68
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe binary.

    Compatibility

    • Additive. No data-file format changes; branding.json gains a rev
      field that defaults to 0 on existing installs.
    • API and route surfaces unchanged.
    Downloads
  • mward4 released this 2026-05-28 12:56:52 -04:00 | 32 commits to main since this release

    OpenPXE v0.4.67

    NFS is back. Done right this time.

    TL;DR

    OpenPXE now supports both SMB and NFSv3 for remote ISO libraries,
    side-by-side. Pick whichever your NAS exposes. Both work in any
    container — no kernel modules, no CAP_SYS_ADMIN, no --privileged.
    Both are baked into the single OpenPXE image with zero additional
    dependencies for the operator to install.

    Why NFS, again

    The user asked for it after validating SMB works. Most NAS appliances
    (UniFi UNAS Pro, Synology, TrueNAS, OpenMediaVault) expose both
    protocols. Some shops prefer NFS for performance, some prefer SMB for
    the simpler auth model. v0.4.67 lets operators not have to choose.

    What's different from the v0.4.64 NFS path

    v0.4.64 shelled out to mount(8) and asked the kernel to attach the
    remote share. That required NFS client modules loaded on the host
    kernel. Containers on hosts without those modules — Unraid being the
    dominant case — failed regardless of container capabilities.

    v0.4.67 doesn't touch the kernel. The new NfsShareManager uses the
    nfs3_client Rust crate to
    speak NFSv3 over a plain TCP socket entirely in userspace. The crate
    runs in-process inside the openpxe binary — no subprocess, no
    mount.nfs, no PATH lookups. The kernel sees plain TCP traffic.

    Side-by-side with SMB

    Storage tab now has two cards stacked: SMB shares (v0.4.65,
    unchanged) and NFS shares (v0.4.67). Both forms use the same
    {error, stderr, hint} JSON error shape so the UI's "connect
    failed" banner renders identically for both protocols. Operators
    can run any mix of SMB and NFS shares simultaneously; the Available
    images table shows their ISOs intermixed with badges (smb:<id> /
    nfs:<id> / local).

    NFS has one architectural advantage over the SMB userspace path

    Range requests work. NFSv3 READ3 takes an explicit offset, so
    the HTTP ISO download handler can seek into the middle of a 5 GB
    ISO without reading what comes before. SMB-sourced ISOs still
    return 416 for ranges because smbclient -c 'get file -' is a
    sequential stream — that's a smbclient CLI limitation, not a
    protocol one. For PXE workloads (iPXE chain, casper sanboot,
    wimboot) it doesn't usually matter; for casper installers with
    iso-url=-style random-access reads, it does.

    What's the same

    • IsoStore::iso_path_for returns None for both SMB and NFS
      sources; the HTTP handler dispatches to the right share manager.
    • Hint translation is parallel: NT_STATUS_* for SMB, NFS3ERR_*
      for NFS, mapped to actionable operator-facing guidance.
    • Persistence: <work_dir>/smb_shares.json and
      <work_dir>/nfs_shares.json side by side.

    NFS auth (or lack thereof)

    NFSv3 uses AUTH_SYS — the client tells the server "I'm uid X" and
    the server decides whether to trust that. There's nothing for the UI
    to ask for, because access control is on the server side by client
    IP. The form is just two fields: server and export path.

    If your server demands privileged-port clients (sub-1024 source
    port), add insecure to the export in /etc/exports. OpenPXE runs
    as uid 10001 and can't bind sub-1024 source ports — the hint
    translation calls this out specifically when a MOUNT3 fails.

    Terminal

    The nfs command is back, this time as in-process:

    nfs list
    nfs add 10.0.0.5:/srv/isos
    nfs add 10.0.0.5:/srv/isos 2049
    nfs scan <id>
    nfs remove <id>
    

    The v0.4.64 nfs command (kernel mount) and the v0.4.67 nfs
    command (in-process via nfs3_client) share the name but not the
    mechanism. v0.4.64 was deleted in v0.4.65; this is the new
    implementation.

    Hints for the common NFSv3 errors

    Wire error UI hint
    NFS3ERR_ACCES The server rejected this client. Most likely your export is restricted by client IP — add this OpenPXE host (or its subnet) to the export's allowed-clients list on the server.
    NFS3ERR_NOENT / NFS3ERR_NOTDIR The export path doesn't exist on the server, or it isn't a directory. UniFi UNAS Pro exposes shares under /var/nfs/shared/<name>.
    NFS3ERR_STALE / NFS3ERR_BADHANDLE The server's view of the share changed under us. Re-scan; if that doesn't help, remove and re-add the share.
    connect timed out Verify IP, port (default 2049), and firewall.
    connection refused Make sure nfsd is running and (for v3) portmap on port 111 is reachable.
    mount denied /etc/exports may need insecure — OpenPXE runs as uid 10001 and uses non-privileged source ports.

    Quality

    • 160 tests passing (was 150 in v0.4.66, +10).
    • cargo clippy --workspace --all-targets -- -D warnings clean.
    • nfs3_client is at 0.9.0 (MIT/Unlicense, rust-version 1.88 — we're
      on 1.95).

    Image

    • gitea.milesward.dev/mward4/openpxe:0.4.67
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe binary. Image grows by the size
    of the linked nfs3_client + nfs3_types crates (small, <500 KiB on
    disk).

    What about v0.5

    v0.5 ships once a few more operators have validated SMB and NFS in
    production. Likely scope: an Audit/Logs tab, the iVentoy-style
    themed PXE menu (the rebuilt iPXE with IMAGE_PNG from native
    x86_64 hardware that's been queued since v0.4.61), and whatever
    operator-reported issues land between now and then. This v0.4.67 is
    the last feature drop before that consolidation.

    Compatibility

    • API is additive — every v0.4.66 client keeps working.
    • No data file format changes; auth.json, branding.json,
      sso.json, hosts.json, meta.json, smb_shares.json all load
      identically.
    • The Prometheus nfs_active gauge now sums SMB + NFS reachable
      shares (it had been re-purposed to track SMB only in v0.4.65;
      v0.4.67 brings it back to "external storage shares reachable"
      which matches the original v0.4.4 intent).
    Downloads
  • mward4 released this 2026-05-28 11:58:56 -04:00 | 33 commits to main since this release

    OpenPXE v0.4.66

    Hotfix for v0.4.65. The new SMB shares manager shelled out to
    smbclient — which wasn't actually in the v0.4.65 image. Every
    "Add share" surfaced:

    Mount failed: could not exec smbclient: No such file or directory (os error 2)

    What happened

    In Debian 12, the samba package ships the server (smbd,
    nmbd) only. The smbclient CLI lives in a separate smbclient
    package — they're siblings, not parent/child. v0.4.65's Dockerfile
    only installed samba, so the new userspace SMB consumer had nothing
    to exec.

    Fix

    • Runtime image's apt-get install line now includes smbclient.
      The image grows by less than 1 MB (still ~98 MB total).
    • Removed nfs-common from the runtime image — v0.4.65 stopped
      using mount.nfs so the helpers were dead weight.

    Defensive improvements

    So that this exact failure mode never bites again (e.g. if anyone
    builds a stripped-down custom image), both list_isos and
    stream_iso now detect ENOENT on spawn and surface:

    Mount failed: could not exec smbclient: No such file or directory (os error 2)
    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.

    The hint translation table picks up the missing-binary pattern
    verbatim, so the UI banner shows the remediation alongside the raw
    error.

    How to upgrade

    Pull :0.4.66 (or :latest). Existing SMB share configurations in
    <work_dir>/smb_shares.json are loaded as-is and re-scanned on
    startup — once the binary is present, the next scan succeeds and
    your ISOs surface in Available images.

    Quality

    • 150 tests passing (+1 from v0.4.65 — the new missing-binary
      hint translation has its own test).
    • cargo clippy --workspace --all-targets -- -D warnings clean.

    Container image

    • gitea.milesward.dev/mward4/openpxe:0.4.66
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe binary unchanged from v0.4.65.

    Compatibility

    • No source-side changes that affect persisted state.
    • API and UI surfaces unchanged.
    • The fix is image-only — code-wise this release just hardens the
      error path for an environment that v0.4.66's image makes
      unreachable.
    Downloads
  • mward4 released this 2026-05-28 11:19:53 -04:00 | 34 commits to main since this release

    OpenPXE v0.4.65

    NFS is gone. SMB shares are in.

    Why

    v0.4.64 added pre-flight probes, retry-with-minimal-options, and a
    catalog of actionable hints for kernel-mount NFS. None of that helps
    when the host kernel doesn't have the nfs client modules loaded —
    mount(2) inside the container returns EOPNOTSUPP / EINVAL and there's
    nothing container-side configuration can do about it. Unraid's base
    kernel ships without those modules. CIFS (kernel-mount SMB) has the
    identical problem.

    The right fix is to not depend on kernel mounts at all. Samba's
    smbclient CLI speaks the SMB protocol entirely in userspace over a
    plain TCP socket — no kernel modules, no CAP_SYS_ADMIN, no
    --privileged. Bootimus uses this approach. iVentoy / FOG / MAAS /
    Cobbler all rely on the host to mount network storage; OpenPXE
    v0.4.65 ships its own SMB consumer so operators don't have to.

    What got removed

    • The whole crates/iso-store/src/nfs.rs module — NfsManager,
      NfsMount, NfsAddRequest, NfsVersion, mount-options retry, TCP
      probe, stderr-to-hint translation.
    • IsoSource::Nfs variant on the ISO source enum.
    • IsoStore::set_nfs_root and the nfs_root field.
    • Every /api/nfs/* route.
    • The nfs terminal command.
    • The Storage tab's "NFS shares" card.

    If you had NFS mounts persisted in <work_dir>/nfs.json they're
    ignored at startup; OpenPXE no longer reads that file.

    What got added

    • crates/iso-store/src/smb_share.rsSmbShareManager that drives
      smbclient as a subprocess. Indexes a share via
      smbclient //server/share -A creds -c "ls *.iso". Streams files
      via smbclient //server/share -A creds -c "get file -" piped
      straight into HTTP response bodies — no local cache, no double
      disk usage.
    • IsoSource::Smb { share_id, relative_path } variant.
    • /api/smb-shares API (list / add / delete / scan).
    • share terminal command:
      share list
      share add //10.0.0.5/isos guest
      share add //nas.lan/isos myuser:mypassword
      share scan <id>
      share remove <id>
      
    • Storage tab: a new SMB shares card with a two-column form for
      server + share name, three-column form for the Guest checkbox /
      username / password. Username & password auto-disable when Guest
      is checked.
    • TCP pre-flight probe (4s timeout) to server:445 before shelling
      out to smbclient. Catches wrong-IP / firewall cases with a clean
      error instead of waiting for smbclient to time out.
    • Operator-friendly hints for the common NT_STATUS codes:
      LOGON_FAILURE → check credentials; BAD_NETWORK_NAME → check
      share name (e.g. //nas/isos, not //nas/shared/isos);
      ACCESS_DENIED → check share-side permissions; CONNECTION REFUSED
      / TIMEOUT → verify smbd is running + firewall.

    Credentials

    • Username and password live in a 0600-permissioned credentials file
      under <work_dir>/smb_creds/<share-id>.cred. Passing them on the
      command line would leak them through ps and the container audit
      log.
    • Guest mode (-N to smbclient) is the default and most NAS
      appliances exposing ISO libraries support it.

    How to use it on Unraid (the original failure case)

    1. Pull :0.4.65 (or :latest).
    2. Restart the container with whatever capabilities you had before —
      none of the NFS workarounds matter anymore. Plain docker run
      with no special caps works.
    3. In your UniFi UNAS Pro / Synology / TrueNAS / whatever, make sure
      SMB is enabled on the share holding your ISOs. (UniFi UNAS Pro:
      Drive → Settings → Services → SMB. Most setups have it on by
      default.)
    4. In OpenPXE's Storage tab, the SMB shares card now has fields:
      • SMB server: 192.168.1.51
      • Share name: isos (just the share name, not a path)
      • Guest: checked if the share allows anonymous read; uncheck
        to enter a username and password.
    5. Click Add share. OpenPXE lists the share's ISOs and surfaces
      them in the Available images table within seconds.

    Known limitation

    Range requests on SMB-sourced ISOs return 416. smbclient -c 'get file -' is a sequential whole-file stream — there's no native seek
    in the CLI. PXE workloads in practice (iPXE chain loading, casper
    sanboot, wimboot) all do sequential whole-file reads, so this works
    for ISO delivery. If a workload comes up that genuinely needs random
    access into SMB-sourced ISOs, a follow-up release can swap to
    libsmbclient-based seek without changing the API or the UI.

    ISO introspection (auto-detecting the distro family, finding kernel

    • initrd paths inside the ISO9660 PVD) is also currently no-op for
      SMB-sourced ISOs since it would require seeking into the file over
      the network. Operators get generic sanboot boot entries that work
      for any bootable ISO. A bounded smbclient get of the first 64 KiB
      would be enough to drive real detection; punted to a follow-up.

    About v0.4.65 (the bind-mount one)

    Earlier today I published a v0.4.65 release that proposed a
    "register a bind-mounted host directory" workaround instead of an
    in-container SMB client. The operator correctly pointed out that's
    not the right architecture — it makes OpenPXE depend on operator-
    managed host plumbing instead of being self-contained. That release
    has been deleted from Gitea (release, tag, and container image all
    removed), and the commit that introduced it has been reverted on
    main. This v0.4.65 is the SMB swap; that's what the version
    number now means.

    Tests

    149 passing, cargo clippy --workspace --all-targets -- -D warnings
    clean. New tests cover:

    • Parser: ISO listing with skipped dot/dotdot, filenames containing
      spaces, non-ISO files correctly skipped.
    • Server normalization (smb://, cifs://, \\, // all stripped).
    • Hint translation for LOGON_FAILURE, BAD_NETWORK_NAME, unknown.
    • HTTP integration: missing server, missing username when not Guest,
      paths in share name all rejected with the right hint.

    Container image

    • gitea.milesward.dev/mward4/openpxe:0.4.65
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe binary. Image size ~98 MB (the
    samba package was already shipped for the outbound smbd serving
    Windows install media; smbclient comes with it for free).

    Compatibility

    • nfs.json is no longer read. If you had NFS mounts configured,
      re-create them as SMB shares in the new Storage tab.
    • The Prometheus gauge previously named "nfs_active" still exists
      but now tracks reachable SMB shares (same semantic — number of
      external storage sources currently working).
    • The /api/status JSON shape gains smb_share_count and
      smb_share_reachable; the old nfs_count / nfs_active fields
      are gone. Any external monitoring keyed off the old names needs
      updating.
    Downloads
  • mward4 released this 2026-05-27 13:53:20 -04:00 | 37 commits to main since this release

    OpenPXE v0.4.64

    Patch release that turns the NFS mount failure path from a dead-end
    into something operators can actually act on.

    The bug

    Configuring an NFS share from the Storage tab against an NFSv3 (or v4.1)
    endpoint surfaced this in the UI banner:

    Mount failed: invalid input: mount exit 32: mount.nfs: failed to
    apply fstab options

    That message is from nfs_options2string() in nfs-utils 2.6.x, and is
    deeply misleading — it has nothing to do with /etc/fstab. The
    dominant root causes are:

    1. The container is missing CAP_SYS_ADMIN (mount(2) returns EPERM, and
      nfs-utils transforms that into the "fstab options" message).
    2. /etc/mtab is unwritable in the container, so the option
      transformation that updates the mount table fails.
    3. An auxiliary mount option (timeo=, retrans=) tripped a known
      transform edge case in nfs-utils 2.6.x.

    None of which the operator could tell from the original error.

    What changed

    Pre-flight probe. Before shelling out to mount(8) we now try a TCP
    connect to server:2049 (or the configured port) with a 4-second
    timeout. A wrong IP or a firewall now produces:

    Mount failed: cannot reach NFS port: 192.168.1.51:2049: timed out
    after 4s
    no TCP answer from 192.168.1.51:2049 within 4s — check the IP and
    any firewall in between

    …instead of getting stuck waiting on mount.nfs and then spitting out
    the unhelpful fstab message.

    proto=tcp explicit on NFSv3. UDP has been deprecated for years
    and many appliances (most UniFi/Synology setups) don't bind it. v0.4.64
    passes proto=tcp so the kernel doesn't try UDP first.

    Auto-retry with minimal options. When mount.nfs returns "failed to
    apply fstab options" or "internal option parsing error", we retry
    once with just vers=N,ro/rw — stripping proto=, nolock, soft,
    timeo=, retrans=. That bypasses the nfs-utils option-transform
    quirk; if it still fails, we get a real kernel error we can translate.

    Stderr → hint translation. Each persisted mount now carries an
    optional last_hint field alongside last_error. Well-known patterns
    are translated into actionable guidance:

    Raw stderr Hint
    failed to apply fstab options / internal option parsing error Container is likely missing CAP_SYS_ADMIN — run with --cap-add=SYS_ADMIN, or use a privileged SCC on OpenShift
    Operation not permitted / Permission denied Same as above
    access denied by server Check the export's allowed-hosts list includes this OpenPXE host's IP
    no route to host / Network is unreachable Server not reachable on this network
    Connection refused NFS isn't listening here — verify the export path (UniFi UNAS Pro exports under /var/nfs/shared/<name>, not the share name on its own)
    Connection timed out Firewall dropping the connection, or wrong port
    no such file or directory / does not exist Export path doesn't exist on the server (same UNAS Pro hint)
    rpc: program not registered Server doesn't speak the requested NFS version — try the other entry
    protocol not supported / invalid argument Version mismatch — try the other entry

    The structured {error, stderr, hint} is returned over the API and
    rendered in the UI as two lines: bold raw error, dimmer actionable
    hint underneath.

    Server normalization. http://, https://, nfs:// schemes and
    trailing slashes pasted into the server field are now stripped. A
    port suffix on the server (nas.lan:2049) is still preserved.

    Optional port field. NfsAddRequest now accepts an optional port
    that flows through into port=<n> on the mount options. Defaults to
    2049 when omitted.

    Terminal parity

    The nfs mount terminal command now prints the actionable hint on a
    follow-up hint: … line so SSH/terminal users see the same diagnostic
    information the UI does.

    Quality

    • 150 tests passing (up from 142). 8 new tests cover the option
      string (incl. proto=tcp on v3, port=N when non-default), the
      minimal-options retry path, server normalization, and every
      well-known stderr → hint translation.
    • cargo clippy --workspace --all-targets -- -D warnings clean.

    Compatibility

    • Existing nfs.json files load unchanged — port defaults to 2049
      and last_hint defaults to absent.
    • API: POST /api/nfs now returns a JSON body on failure instead of
      plain text. The body shape is {error, stderr, hint}. Clients that
      previously read the body as text still get readable JSON.
    • Terminal: nfs mount output may include a follow-up hint: line on
      failure. Existing parsers that only read the first line are
      unaffected.

    Container image

    • gitea.milesward.dev/mward4/openpxe:0.4.64
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe binary unchanged. Image size ~98 MB.

    One thing v0.4.64 does not fix

    If your container genuinely doesn't have CAP_SYS_ADMIN, NFS mounts
    will still fail — that's a Linux mount(2) requirement, not something
    OpenPXE can work around. What v0.4.64 does fix is making it obvious
    that's the problem. The Storage tab now tells you so in plain English.

    Downloads
  • mward4 released this 2026-05-26 02:32:38 -04:00 | 38 commits to main since this release

    OpenPXE v0.4.63

    Three UI fixes operators flagged on v0.4.62, plus the queued PXE-theme
    research note for the next release. No backend behaviour changes.

    Settings card alignment

    The SSO header now sits in the same 4-column grid as the Administrator
    account card above it — display name / IdP logo URL / Metadata source /
    IdP metadata URL, column-for-column aligned with Current password /
    New username / New password / Confirm. Previously the SSO row used a
    3-column grid that didn't line up with the 4-column row above and the
    metadata URL hung underneath in its own row.

    When the operator picks "Metadata XML" the URL column collapses out and
    the multi-line <EntityDescriptor> textarea drops onto its own
    full-width row below — there's no way to fit a 6-row textarea into a
    single grid cell without making everything around it look stretched.

    Enable-single-sign-on checkbox now actually paints

    <meta name="color-scheme" content="dark light"> in index.html was
    telling the browser "default to dark form chrome on a tie" — which
    forced the SSO enable-checkbox to render as an opaque black square in
    light mode regardless of the accent-color CSS hint. (Specs are
    specs: accent-color only repaints the check mark, not the
    container.)

    Two things land in v0.4.63:

    1. CSS color-scheme tracks data-theme directly — dark when the
      OpenPXE theme is dark, light when it's light. Overrides the meta
      so the rest of the native chrome (date pickers, scroll bars) also
      follows the active palette.
    2. The checkbox itself is now custom-styled — 16x16 rounded square,
      accent fill + tick on :checked, hover ring, focus glow. Chrome,
      Firefox and Safari now render the control identically across both
      palettes; we no longer depend on whichever WebKit happens to honour
      accent-color.

    Dropdown affordance restored

    With -webkit-appearance: none on inputs and selects (used to bring
    password/text inputs to a consistent baseline), the native dropdown
    arrow disappeared on <select>. That's why "Metadata source" looked
    squished — there was no visible cue the field was a picker. v0.4.63
    paints a chevron back in via a data-URI SVG (themed light/dark) and
    reserves 30 px of right-padding for it.

    Spacing under the action buttons

    Update credentials and Save SSO settings now sit with explicit
    margin above them so they no longer butt against the input row above.

    Queued: iVentoy-style PXE menu theming

    docs/queued/ipxe-pxe-menu-theme-research.md captures the design for
    the next release:

    • iVentoy uses iPXE's console --picture with baked-in per-resolution
      PNGs (1024×768 default, plus 1280×1024 and 1920×1080 selectable in
      its Configuration tab). No EDID auto-detect — daemon-side resolution
      pick.
    • The same primitive is already wired in crates/http-api/src/ipxe_script.rs.
    • Plan: extend the existing pxe_logo.rs compositor to emit
      per-resolution framed PNGs with title bar + footer painted into the
      background, then console --left/--right/--top/--bottom insets the
      iPXE menu into the framed window. This lands once the
      IMAGE_PNG-enabled iPXE rebuild ships on native x86_64 hardware.

    Container image

    • gitea.milesward.dev/mward4/openpxe:0.4.63
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe binary unchanged.

    Quality

    • 142 tests passing (unchanged from v0.4.62).
    • cargo clippy --workspace --all-targets -- -D warnings clean.

    Compatibility

    • Existing deployments upgrade in place. No data-file format changes;
      auth.json, branding.json, sso.json, hosts.json, meta.json
      all read identically.
    • Browser cache-bust from v0.4.62 carries the new asset URLs into
      every session without a hard refresh.
    Downloads
  • mward4 released this 2026-05-26 00:53:30 -04:00 | 39 commits to main since this release

    OpenPXE v0.4.62

    The buildable companion to v0.4.61. Same cache fix and PXE-logo
    compositor; the iPXE-rebuild-with-PNG-support stage from v0.4.61 has
    been walked back and queued for a follow-up release once it can be
    validated on native x86_64 hardware.

    Why v0.4.62 exists

    v0.4.61 ships in source with an ipxe-build Dockerfile stage that
    compiles iPXE from source with IMAGE_PNG enabled. That stage hits
    intermittent cc1: internal compiler error: Segmentation fault when
    cross-emulating x86_64 gcc under QEMU on arm64 build hosts. No
    v0.4.61 image was ever published as a result.

    v0.4.62 keeps everything else from v0.4.61 (the actual fix the
    operator reported, plus the compositor code) and rolls the iPXE
    build stage back to the boot.ipxe.org fetch path. The compositor at
    /branding/pxe-logo is wired and ready — when the iPXE-rebuild stage
    lands in a follow-up release (on native x86_64 hardware), no other
    code change is needed. The operator's logo will start painting in the
    PXE menu the moment the underlying iPXE binaries grow PNG support.

    The actual fix: v0.4.6 UI changes land in your browser

    Operators who pulled v0.4.6 didn't see the v0.4.6 UI changes — the
    top-right user menu, the IdP logo URL field, the realigned account
    form. The version chip in the footer correctly read "v0.4.6" because
    it's dynamic, but app.js and app.css live at fixed URLs that
    browsers happily reused from before the upgrade.

    Fixed in v0.4.62:

    • index.html now appends ?v=<openpxe-version> to every asset URL
      (app.css, app.js, logo.svg). Every release ships brand-new asset
      URLs.
    • Cache-Control: no-cache, must-revalidate on every asset handler.
      index.html itself is no-cache so the browser learns about the new
      ?v=… value on the next page load.

    Net effect: v0.4.62 → next release upgrades, and any prior upgrade
    once you're on v0.4.62, will land in the browser on the next page
    load. No hard refresh required.

    PXE-logo compositor (ready, waiting for PNG-enabled iPXE)

    Even though the operator's logo can't paint in the PXE menu yet
    (boot.ipxe.org's binaries omit IMAGE_PNG), the server-side
    compositor that prepares the logo for that paint is in place:

    • New openpxe-iso-store::pxe_logo::compose_pxe_logo decodes any
      raster the operator uploads (PNG / JPEG / WebP / GIF), downscales
      to fit a 600×200 bounding box, and pastes it horizontally centered
      with a 64-pixel top margin onto a transparent 1024×768 canvas.
      Returns PNG bytes.
    • GET /branding/pxe-logo returns that composed PNG. SVG uploads
      still 404 here (iPXE can't rasterize SVG); the WebUI's top-left
      mark continues to read directly from the SVG upload.

    The follow-up release will swap the boot.ipxe.org fetch for a
    make-from-source stage with IMAGE_PNG enabled, at which point
    this endpoint goes live without any code change.

    What stayed from v0.4.6

    The ASCII OpenPXE wordmark in render_menu is back for v0.4.62. It
    paints on every iPXE build regardless of PNG support — it's the
    correct fallback for the binaries we currently ship. When the real
    image paints, it'll be retired in the same release.

    Container image

    • gitea.milesward.dev/mward4/openpxe:0.4.62
    • gitea.milesward.dev/mward4/openpxe:latest

    linux/amd64; static-musl /openpxe binary unchanged. Image size
    ~98 MB.

    Quality

    • 142 tests passing (unchanged from v0.4.61): the compositor
      tests still run; the menu test asserts the ASCII banner is present.
    • cargo clippy --workspace --all-targets clean.

    Compatibility

    • Existing deployments upgrade in place. No data-file format
      changes; all of auth.json, branding.json, sso.json,
      hosts.json, meta.json continue to read identically.
    • The v0.4.61 git tag stays where it is — broken Dockerfile,
      reference-only. Pull v0.4.62 (or :latest) for a working image.
    Downloads