v0.4.62: ship the v0.4.61 cache fix as a buildable image

v0.4.61 source landed in main with the cache fix and the
PXE-logo compositor, plus an aspirational Dockerfile stage that
rebuilds iPXE from source with IMAGE_PNG enabled. The Dockerfile
stage hits intermittent `cc1: internal compiler error: Segmentation
fault` when cross-emulating x86_64 gcc under QEMU on arm64 build
hosts, which is what the build host I was using does. No v0.4.61
image was ever published as a result.

v0.4.62 walks back the iPXE-from-source change and ships a working
image with the same cache fix and the same compositor code in place.
The iPXE rebuild is queued for a follow-up release, to be built and
validated on the actual x86_64 Unraid hardware where the QEMU
instability doesn't apply.

What's in v0.4.62 vs v0.4.6:

- Asset URL versioning: index.html now appends `?v=<openpxe-version>`
  to every asset URL (app.css, app.js, logo.svg). Combined with
  `Cache-Control: no-cache, must-revalidate` on the asset handlers,
  upgrades land in operators' browsers without a hard refresh. This
  is the fix for "I pulled v0.4.6 but the UI still looks like v0.4.5".
- New PXE-logo compositor in iso-store::pxe_logo: decodes any raster
  the operator uploads, scales-to-fit into a 600×200 bounding box,
  pastes it centered at the top of a 1024×768 PNG canvas, and serves
  the result at GET /branding/pxe-logo. Wired into render_menu's
  `console --picture` directive; takes effect when the shipped iPXE
  binaries grow PNG support.
- ASCII OpenPXE wordmark in render_menu retained for v0.4.62 — works
  on the boot.ipxe.org pre-builds we currently ship.

Quality:
- 142 tests passing.
- cargo clippy --workspace --all-targets clean.
- No image dependency change since v0.4.61 (the `image = "0.25"` dep
  added in v0.4.61 stays — it backs the compositor).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
Miles Ward
2026-05-26 00:53:30 -04:00
co-authored by Claude Opus 4.7
parent eb3b191a71
commit 89c02810c9
4 changed files with 46 additions and 102 deletions
Generated
+8 -8
View File
@@ -1140,7 +1140,7 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]] [[package]]
name = "openpxe" name = "openpxe"
version = "0.4.61" version = "0.4.62"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -1162,7 +1162,7 @@ dependencies = [
[[package]] [[package]]
name = "openpxe-core" name = "openpxe-core"
version = "0.4.61" version = "0.4.62"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bcrypt", "bcrypt",
@@ -1181,7 +1181,7 @@ dependencies = [
[[package]] [[package]]
name = "openpxe-dhcp-proxy" name = "openpxe-dhcp-proxy"
version = "0.4.61" version = "0.4.62"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -1195,7 +1195,7 @@ dependencies = [
[[package]] [[package]]
name = "openpxe-http-api" name = "openpxe-http-api"
version = "0.4.61" version = "0.4.62"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -1226,7 +1226,7 @@ dependencies = [
[[package]] [[package]]
name = "openpxe-ipxe-assets" name = "openpxe-ipxe-assets"
version = "0.4.61" version = "0.4.62"
dependencies = [ dependencies = [
"openpxe-core", "openpxe-core",
"rust-embed", "rust-embed",
@@ -1236,7 +1236,7 @@ dependencies = [
[[package]] [[package]]
name = "openpxe-iso-store" name = "openpxe-iso-store"
version = "0.4.61" version = "0.4.62"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bcrypt", "bcrypt",
@@ -1260,7 +1260,7 @@ dependencies = [
[[package]] [[package]]
name = "openpxe-tftp" name = "openpxe-tftp"
version = "0.4.61" version = "0.4.62"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@@ -1274,7 +1274,7 @@ dependencies = [
[[package]] [[package]]
name = "openpxe-webui" name = "openpxe-webui"
version = "0.4.61" version = "0.4.62"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
+1 -1
View File
@@ -12,7 +12,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.4.61" version = "0.4.62"
edition = "2021" edition = "2021"
rust-version = "1.95" rust-version = "1.95"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
+25 -19
View File
@@ -77,15 +77,20 @@ pub fn render_menu(isos: &[IsoMeta], settings: &Settings, base_url: &str) -> Str
); );
let _ = writeln!(s, ":menu"); let _ = writeln!(s, ":menu");
let _ = writeln!(s, "menu OpenPXE - network boot menu"); let _ = writeln!(s, "menu OpenPXE - network boot menu");
// v0.4.61: we used to draw an ASCII OpenPXE wordmark here. Now // ASCII OpenPXE wordmark. Works on every iPXE build, including
// that the bundled iPXE is built with `IMAGE_PNG`, the // the boot.ipxe.org pre-builds we ship (which omit `IMAGE_PNG`,
// `console --picture` line at the top of this script paints the // so `console --picture` paints nothing). When the queued iPXE
// operator's actual logo (composed server-side into a 1024×768 // source-build lands and the operator's uploaded raster actually
// canvas with the logo centered at the top) — the ASCII banner // paints via `console --picture`, this banner can be retired in
// became visual noise *on top* of the real image. Old iPXE // favour of the real image. The compositor at
// builds without PNG fall through the `|| console` clause and // /branding/pxe-logo is already wired and waiting.
// simply show the menu without a logo, which is the correct let _ = writeln!(s, "item --gap");
// graceful-degradation outcome. let _ = writeln!(s, "item --gap -- ___ ___ __ __ ___");
let _ = writeln!(s, "item --gap -- / _ \\ _ __ ___ _ _ | _ \\ \\/ / | __|");
let _ = writeln!(s, "item --gap -- | (_) | '_ \\/ -_) ' \\ | _/ \\ / | _|");
let _ = writeln!(s, "item --gap -- \\___/| .__/\\___|_||_| |_| /_/\\_\\ |___|");
let _ = writeln!(s, "item --gap -- |_|");
let _ = writeln!(s, "item --gap");
let _ = writeln!( let _ = writeln!(
s, s,
"item --gap -- ------------------------- Default -------------------------" "item --gap -- ------------------------- Default -------------------------"
@@ -626,12 +631,13 @@ mod password_tests {
#[test] #[test]
fn top_menu_has_polished_branding_and_arch_footer() { fn top_menu_has_polished_branding_and_arch_footer() {
// v0.4.6 polish + v0.4.61 image upgrade: the menu emits a // v0.4.6 polish + v0.4.62 stability fixes: the menu emits a
// `console --picture` line that the bundled iPXE (built with // `console --picture` line that PNG-capable iPXE builds will
// `IMAGE_PNG`) honours, plus an arch-resolved footer carrying // honour (queued for a follow-up release once we can rebuild
// the current OpenPXE version. The ASCII wordmark that used // iPXE from source on native x86_64 hardware), an ASCII
// to live here was dropped in v0.4.61 — it duplicated the now- // OpenPXE wordmark that works on every iPXE build (including
// working image. // the boot.ipxe.org pre-builds we currently ship), and a
// single-line footer carrying the OpenPXE version + arch.
let settings = Settings::default(); let settings = Settings::default();
let s = render_menu(&[], &settings, "http://10.0.0.5"); let s = render_menu(&[], &settings, "http://10.0.0.5");
assert!( assert!(
@@ -641,11 +647,11 @@ mod password_tests {
// Picture-or-text-console must be a single statement so older // Picture-or-text-console must be a single statement so older
// iPXE parsers don't choke on the chain. // iPXE parsers don't choke on the chain.
assert!(s.contains("|| console"), "missing graceful fallback:\n{s}"); assert!(s.contains("|| console"), "missing graceful fallback:\n{s}");
// No ASCII wordmark — once the real PNG paints, the ASCII // ASCII wordmark — paints on every iPXE build regardless of
// banner would duplicate the operator's logo visually. // PNG support.
assert!( assert!(
!s.contains("___ ___ __ __ ___"), s.contains("___ ___ __ __ ___"),
"ASCII banner shouldn't be emitted in v0.4.61+:\n{s}" "ASCII banner missing first row:\n{s}"
); );
// Footer with version + arch interpolation. The version comes // Footer with version + arch interpolation. The version comes
// from CARGO_PKG_VERSION at compile time. // from CARGO_PKG_VERSION at compile time.
+12 -74
View File
@@ -16,79 +16,21 @@
ARG RUST_VERSION=1.95 ARG RUST_VERSION=1.95
########## fetch wimboot (and a sanity-check fetch of upstream iPXE) ########## ########## fetch iPXE binaries + wimboot ##########
# v0.4.61: we no longer ship the boot.ipxe.org iPXE binaries directly; # v0.4.62: kept on the boot.ipxe.org pre-builds for the moment. We
# instead we build iPXE from source with IMAGE_PNG enabled (see the # want PNG support (so `console --picture` paints the operator's logo
# ipxe-build stage below). The fetch stage still pulls wimboot (a # on the PXE menu) but the obvious path — adding a new `ipxe-build`
# pre-signed binary from ipxe/wimboot's GitHub release) since that's # stage that compiles iPXE from source with `IMAGE_PNG` enabled —
# unrelated to the PNG concern. # runs into a QEMU/gcc instability when cross-emulating x86_64 on
# arm64 build hosts (intermittent `cc1` segfaults). The compositor
# at /branding/pxe-logo is already wired so when the iPXE rebuild
# lands (on native x86_64 hardware), no other code change is needed.
FROM debian:12-slim AS fetch FROM debian:12-slim AS fetch
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \ RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR /src WORKDIR /src
RUN mkdir -p assets/ipxe && \ COPY scripts/fetch-ipxe.sh scripts/fetch-ipxe.sh
curl --fail --silent --show-error --location \ RUN mkdir -p assets/ipxe && bash scripts/fetch-ipxe.sh
-o assets/ipxe/wimboot \
https://github.com/ipxe/wimboot/releases/latest/download/wimboot \
|| echo "wimboot fetch failed; Windows toggle will stay disabled"
########## build iPXE from source with IMAGE_PNG enabled ##########
# This stage replaces the old "grab pre-built binaries from
# boot.ipxe.org" path. The shipped binaries there are built with the
# default config which omits `IMAGE_PNG`, so the `console --picture`
# call in render_menu silently no-ops — operator logos never paint.
# Building from source lets us flip the one flag we need.
#
# Cross-compilation: x86_64 + i386 use the native toolchain that ships
# in the rust:bookworm base; arm64 uses gcc-aarch64-linux-gnu. The four
# output binaries match the names openpxe-ipxe-assets expects in
# assets/ipxe/.
FROM rust:${RUST_VERSION}-bookworm AS ipxe-build
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git build-essential liblzma-dev mtools genisoimage syslinux \
gcc-aarch64-linux-gnu \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Pin to a recent iPXE master tip via shallow clone. iPXE doesn't tag
# releases; pinning the SHA in source would be a periodic chore. The
# tradeoff is that "rebuild the container" silently picks up upstream
# patches — for a boot loader this is the right side of the
# pin-vs-fresh tradeoff (we want CVE fixes ASAP and the PXE chain is
# the trusted base).
RUN git clone --depth=1 https://github.com/ipxe/ipxe.git ipxe
WORKDIR /build/ipxe/src
# Feature flags landed via the `config/local/` override files iPXE's
# config system reads after `config/general.h`. We enable just the
# image format + framebuffer console plumbing — everything else stays
# at the upstream default. `keep-debug` is off; `parserrors` is off; we
# pin a small set of useful tweaks.
RUN mkdir -p config/local \
&& printf '%s\n' \
'#define IMAGE_PNG' \
'#define CONSOLE_FRAMEBUFFER' \
'#define CONSOLE_VESAFB' \
'#define DOWNLOAD_PROTO_HTTPS' \
'#define NSLOOKUP_CMD' \
'#define NTP_CMD' \
> config/local/general.h
# Each arch builds to its own `bin-*` directory. We copy the four
# output binaries into /out/ with the names openpxe-ipxe-assets
# expects. Stripping the binaries saves ~30% — they go into the rust
# binary via include_bytes! so the savings ripple through the final
# image.
RUN mkdir -p /out && \
make -j"$(nproc)" bin/undionly.kpxe && \
cp bin/undionly.kpxe /out/undionly.kpxe && \
make -j"$(nproc)" bin-x86_64-efi/snponly.efi && \
cp bin-x86_64-efi/snponly.efi /out/snponly.efi && \
make -j"$(nproc)" bin-x86_64-efi/ipxe.efi && \
cp bin-x86_64-efi/ipxe.efi /out/ipxe.efi && \
make -j"$(nproc)" bin-i386-efi/snponly.efi && \
cp bin-i386-efi/snponly.efi /out/snponly-i386.efi && \
make -j"$(nproc)" CROSS_COMPILE=aarch64-linux-gnu- bin-arm64-efi/snponly.efi && \
cp bin-arm64-efi/snponly.efi /out/snponly-arm64.efi && \
ls -lh /out/
########## build openpxe ########## ########## build openpxe ##########
FROM rust:${RUST_VERSION}-bookworm AS build FROM rust:${RUST_VERSION}-bookworm AS build
@@ -122,11 +64,7 @@ RUN apt-get update \
# `cargo build`, which is slow and can exhaust small Colima/CI disks. # `cargo build`, which is slow and can exhaust small Colima/CI disks.
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml Cargo.lock ./
COPY crates/ crates/ COPY crates/ crates/
# v0.4.61: iPXE binaries come from our own source-built stage with COPY --from=fetch /src/assets/ipxe /src/assets/ipxe
# IMAGE_PNG enabled. wimboot still comes from the fetch stage (it's
# from ipxe/wimboot's GitHub release, separately signed).
COPY --from=ipxe-build /out/ /src/assets/ipxe/
COPY --from=fetch /src/assets/ipxe/wimboot /src/assets/ipxe/wimboot
# Cache cargo registry + target across builds. The mtime touch is # Cache cargo registry + target across builds. The mtime touch is
# belt-and-suspenders: cargo occasionally misses mtime-only changes on # belt-and-suspenders: cargo occasionally misses mtime-only changes on