Two real issues v0.4.6 left on the table: Asset caching: - index.html now interpolates the running OpenPXE version into every asset URL as `?v=<version>` (app.css, app.js, logo.svg). Combined with `Cache-Control: no-cache, must-revalidate` on the asset handlers, browsers and intermediary proxies are forced to fetch fresh on every upgrade. Without this, last release's bundled JS kept serving the old UI even after the operator pulled the new image — invisible to anyone who only checks the version chip in the footer (which is dynamic). - The Cache-Control header is also applied to logo.svg and loader.svg so a logo upload reflects immediately rather than after a hard refresh. Real-image PXE menu logo (matches iVentoy now): - New Dockerfile stage `ipxe-build` clones the iPXE source and compiles all four binaries (undionly.kpxe, snponly.efi for x86_64/i386, snponly.efi for arm64 via gcc-aarch64-linux-gnu) with IMAGE_PNG + CONSOLE_FRAMEBUFFER + CONSOLE_VESAFB enabled. Replaces the boot.ipxe.org fetch — those binaries are built without PNG support, which is why v0.4.6's `console --picture` line silently no-op'd. - `iso-store::pxe_logo::compose_pxe_logo` decodes any operator upload (PNG / JPEG / WebP / GIF), downscales-to-fit if larger than 600×200, and pastes it onto a transparent 1024×768 canvas centered horizontally with a 64-pixel top margin. iPXE paints the result at 1:1 on the typical VESA framebuffer, giving the iVentoy-style centered-logo look regardless of the operator's source dimensions. - GET /branding/pxe-logo now returns the composed PNG. wimboot still fetches from ipxe/wimboot's GitHub release (separately signed). - Dropped the ASCII OpenPXE wordmark from render_menu — once the real image paints, the banner would duplicate it visually. iPXE builds without PNG (none of ours after this release, but a third- party undionly might) simply show the menu without a logo, which is the right graceful-degradation outcome. Quality: - 142 tests passing (was 138 in v0.4.6): +4 pxe_logo unit tests covering canvas dimensions, centered-top placement, oversize downscale, and unsupported-bytes error handling; existing integration tests updated to verify the 1024×768 IHDR header from the composed PNG instead of round-tripping the raw upload. - cargo clippy --workspace --all-targets clean. - Image dependency: `image = "0.25"` with only `png/jpeg/webp/gif` features enabled. No new transitive C deps. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
195 lines
9.4 KiB
Docker
195 lines
9.4 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 wimboot (and a sanity-check fetch of upstream iPXE) ##########
|
|
# v0.4.61: we no longer ship the boot.ipxe.org iPXE binaries directly;
|
|
# instead we build iPXE from source with IMAGE_PNG enabled (see the
|
|
# ipxe-build stage below). The fetch stage still pulls wimboot (a
|
|
# pre-signed binary from ipxe/wimboot's GitHub release) since that's
|
|
# unrelated to the PNG concern.
|
|
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
|
|
RUN mkdir -p assets/ipxe && \
|
|
curl --fail --silent --show-error --location \
|
|
-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 ##########
|
|
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/
|
|
# v0.4.61: iPXE binaries come from our own source-built stage with
|
|
# 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
|
|
# 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"]
|