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]>
133 lines
6.5 KiB
Docker
133 lines
6.5 KiB
Docker
# syntax=docker/dockerfile:1.7
|
|
#
|
|
# OpenPXE — multi-stage build.
|
|
#
|
|
# Design:
|
|
# - stage `fetch`: runs scripts/fetch-ipxe.sh to pull official iPXE binaries
|
|
# into assets/ipxe/ so the rust build can embed them via rust-embed.
|
|
# - stage `build`: compiles the workspace with cargo in release mode.
|
|
# - stage `runtime`: Debian slim image with setcap for NET_BIND_SERVICE,
|
|
# running as a non-root UID. No shell in PATH for the service user;
|
|
# attacker surface is just the openpxe binary + libc.
|
|
#
|
|
# Why not distroless? We want setcap support and easy debug (`oc rsh`).
|
|
# Debian slim at ~75 MB + binary ~25 MB is fine for a PXE server that
|
|
# spends most of its life idle.
|
|
|
|
ARG RUST_VERSION=1.95
|
|
|
|
########## fetch iPXE binaries + wimboot ##########
|
|
# v0.4.62: kept on the boot.ipxe.org pre-builds for the moment. We
|
|
# want PNG support (so `console --picture` paints the operator's logo
|
|
# on the PXE menu) but the obvious path — adding a new `ipxe-build`
|
|
# stage that compiles iPXE from source with `IMAGE_PNG` enabled —
|
|
# 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
|
|
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
WORKDIR /src
|
|
COPY scripts/fetch-ipxe.sh scripts/fetch-ipxe.sh
|
|
RUN mkdir -p assets/ipxe && bash scripts/fetch-ipxe.sh
|
|
|
|
########## build openpxe ##########
|
|
FROM rust:${RUST_VERSION}-bookworm AS build
|
|
WORKDIR /src
|
|
|
|
# v0.4.5: build a fully static musl binary (matches Bootimus v0.1.70's
|
|
# move). The resulting `/openpxe` has no glibc dependency at all, which:
|
|
# - Lets the runtime stage be any Linux distro (we still ship Debian
|
|
# slim for the `samba` / `wimtools` / `nfs-common` shellouts, but a
|
|
# scratch/distroless variant becomes a one-line swap).
|
|
# - Cuts a class of "GLIBC_2.39 not found" surprises when running on
|
|
# older RHEL/Rocky hosts that don't match Debian 12's libc version.
|
|
# - Sidesteps cross-compilation snags (the binary is its own world).
|
|
#
|
|
# x86_64-unknown-linux-musl is fully static by default (no extra
|
|
# RUSTFLAGS needed). musl-tools provides the linker.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends musl-tools \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& rustup target add x86_64-unknown-linux-musl
|
|
|
|
# Copy the whole workspace in one go. We used to do a two-pass "cache-prime
|
|
# with stubs, then real build" dance for dep-compile reuse; that turned out
|
|
# to silently serve stale stub binaries when cargo's fingerprint didn't
|
|
# notice the source swap. A single build is ~1.5 min longer on cold cache
|
|
# but guarantees the binary reflects the sources we copied.
|
|
# Do not copy rust-toolchain.toml into the image. The local workspace pins
|
|
# developer tooling, but inside Docker we intentionally use the Rust version
|
|
# selected by the base image. Copying rust-toolchain.toml with
|
|
# `channel = "stable"` makes rustup download a second full toolchain during
|
|
# `cargo build`, which is slow and can exhaust small Colima/CI disks.
|
|
COPY Cargo.toml Cargo.lock ./
|
|
COPY crates/ crates/
|
|
COPY --from=fetch /src/assets/ipxe /src/assets/ipxe
|
|
|
|
# Cache cargo registry + target across builds. The mtime touch is
|
|
# belt-and-suspenders: cargo occasionally misses mtime-only changes on
|
|
# networked FS; this forces a fingerprint check.
|
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|
--mount=type=cache,target=/src/target,sharing=locked \
|
|
find crates -name '*.rs' -exec touch {} + && \
|
|
cargo build --release --target x86_64-unknown-linux-musl --bin openpxe && \
|
|
cp target/x86_64-unknown-linux-musl/release/openpxe /openpxe && \
|
|
ls -l /openpxe
|
|
|
|
########## runtime ##########
|
|
FROM debian:12-slim AS runtime
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends \
|
|
ca-certificates libcap2-bin tini gosu iproute2 \
|
|
wimtools samba nfs-common \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& useradd --system --uid 10001 --home-dir /var/lib/openpxe --shell /usr/sbin/nologin openpxe \
|
|
&& mkdir -p /var/lib/openpxe/isos /var/lib/openpxe/work /var/lib/openpxe/smb \
|
|
&& chown -R openpxe:openpxe /var/lib/openpxe
|
|
# v0.4.5: the openpxe binary itself is now built against musl and is
|
|
# fully static — no glibc dependency. The runtime stage still ships
|
|
# Debian slim because OpenPXE shells out to the four packages below for
|
|
# functionality we deliberately don't reimplement in-process:
|
|
# wimtools - `wimlib-imagex`, used to inject startnet.cmd into boot.wim.
|
|
# samba - `smbd` serves extracted Windows install media on :445 so
|
|
# WinPE can `net use`. Guest read-only, scoped to
|
|
# /var/lib/openpxe/smb.
|
|
# nfs-common - `mount.nfs` / `mount.nfs4` for the Storage tab's NFS
|
|
# share manager. Mount requires CAP_SYS_ADMIN; without it
|
|
# mount(2) returns EPERM and the manager surfaces a clear
|
|
# error in the UI.
|
|
# iproute2 - `ip addr` / `ip route` for the auto-detected Network
|
|
# tab fields (NIC name, subnet mask, default gateway).
|
|
# Tiny, always available; we don't pull in netlink crates
|
|
# for this one-shot startup probe.
|
|
# gosu - drops privileges cleanly from root after the entrypoint
|
|
# fixes bind-mount ownership (common OpenShift/Docker UX
|
|
# issue).
|
|
# A future "openpxe-static" variant could drop everything except the
|
|
# binary onto distroless once we move the Windows + NFS legs to
|
|
# in-process Rust crates.
|
|
|
|
COPY --from=build /openpxe /usr/local/bin/openpxe
|
|
COPY deploy/docker/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
|
|
# Grant the binary the ability to bind <1024 ports as a non-root user.
|
|
# This is the only capability OpenPXE needs for proxy-mode DHCP + TFTP + HTTP.
|
|
RUN setcap cap_net_bind_service=+ep /usr/local/bin/openpxe
|
|
|
|
# IMPORTANT: we do NOT `USER openpxe` here. The entrypoint runs as root,
|
|
# chowns the mounted data dirs, then execs the binary via gosu as openpxe.
|
|
# OpenShift ignores USER directives anyway (it injects its own uid), and
|
|
# there entrypoint.sh's non-root branch just execs directly.
|
|
WORKDIR /var/lib/openpxe
|
|
|
|
ENV OPENPXE_ISO_DIR=/var/lib/openpxe/isos \
|
|
OPENPXE_WORK_DIR=/var/lib/openpxe/work \
|
|
OPENPXE_LOG=info,openpxe=info
|
|
|
|
EXPOSE 67/udp 69/udp 4011/udp 80/tcp 445/tcp
|
|
|
|
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
|