Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- 'Cargo.toml'
- 'Cargo.lock'
- 'deny.toml'
- 'docker/**'
- '.github/workflows/ci.yml'
pull_request:
branches: [ main ]
Expand All @@ -18,6 +19,7 @@ on:
- 'Cargo.toml'
- 'Cargo.lock'
- 'deny.toml'
- 'docker/**'
- '.github/workflows/ci.yml'

# Cancel outdated workflow runs
Expand Down Expand Up @@ -301,6 +303,67 @@ jobs:
working-directory: fuzz
run: cargo audit

# Job 5b: Container images must build, and the binary inside must run.
#
# Nothing validated docker/Dockerfile before: packages.yml only fires on a
# published release or a manual dispatch. Two defects accumulated unnoticed --
# the builder pinned a Rust below the workspace MSRV, and the alpine stage
# copied a Debian glibc binary into a musl image, producing a container in
# which prtip could not exec at all.
#
# `--version` is the check that matters. A glibc/musl mismatch builds a
# perfectly good-looking image and only fails when something tries to run it.
docker:
name: Docker Images
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Buildx
uses: docker/setup-buildx-action@v3

- name: Build Debian runtime image
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
target: runtime
tags: prtip:ci-runtime
load: true
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Build Alpine image
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
target: alpine
tags: prtip:ci-alpine
load: true
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Binary runs in both images
run: |
for image in prtip:ci-runtime prtip:ci-alpine; do
echo "::group::$image"
docker run --rm "$image" --version
echo "::endgroup::"
done

- name: Alpine binary is musl-linked, not glibc
run: |
# Guards against a regression to copying the Debian binary: that
# produced an image which built fine and failed on first exec.
interp=$(docker run --rm --entrypoint sh prtip:ci-alpine \
-c 'ldd /usr/local/bin/prtip 2>&1' | head -1)
echo "interpreter: $interp"
case "$interp" in
*ld-musl*) echo "OK: musl-linked" ;;
*) echo "FAIL: expected a musl interpreter, got: $interp"; exit 1 ;;
esac

# Job 6: MSRV (Minimum Supported Rust Version) check
msrv:
name: MSRV Check (1.88)
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,28 @@ along with every unmaintained/unsound warning. `cargo audit` and
third-party-material statement moved there, because `LICENSE` has to be the
licence text and nothing else for both of the reasons above. Nothing was
dropped in the move.
- **The Docker build was broken by the MSRV raise.** `docker/Dockerfile` pinned
`ARG RUST_VERSION=1.85`, below the workspace's new 1.88 floor, so the builder
stage could no longer compile the project at all. Nothing caught it because
`packages.yml` — the only workflow that touches the Dockerfile — runs solely on
a published release or a manual dispatch.
- **The Alpine image never worked.** Its stage copied the Debian-built **glibc**
binary into a **musl** image. Alpine ships no `/lib64/ld-linux-x86-64.so.2`, so
the kernel failed the exec with a bare "no such file or directory" — the image
built cleanly and `prtip` could not run in it, which the stage's own
`HEALTHCHECK` would have failed on every start. The comment in place
("For now, use the glibc binary (requires compatibility layer or rebuild)")
Comment on lines +271 to +274
acknowledged it. There is now a real `musl-builder` stage building natively on
`rust:1.88-alpine` with `libpcap-dev`, `openssl-dev` and a C toolchain from
apk, which is far simpler than assembling a musl sysroot on Debian. Verified:
the binary reports `prtip 1.0.0` inside the image and links
`/lib/ld-musl-x86_64.so.1`; the image is 26.9 MB against the Debian variant's
97 MB.
- **CI now builds both container images and runs the binary in each.** A
glibc/musl mismatch produces a perfectly good-looking image that only fails
when something tries to execute it, so the gate asserts `--version` succeeds
and that the Alpine binary's interpreter is musl. `ci.yml` path filters now
include `docker/**`.
- **Seven broken internal documentation links** in
`to-dos/PHASE-5/SPRINT-5.5.5-TODO.md` and `SPRINT-5.5.6-TODO.md`. They pointed
at `../docs/...` and `../benchmarks/...` from `to-dos/PHASE-5/`, which resolves
Expand Down
46 changes: 39 additions & 7 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
# docker run --rm --net=host --cap-add=NET_RAW prtip -sS -p 80,443 192.168.1.1
# docker run -it --rm --net=host --cap-add=NET_RAW prtip --tui

ARG RUST_VERSION=1.85
# Must be >= the workspace `rust-version` in Cargo.toml (1.88). A pin below
# the MSRV fails the build with "requires rustc 1.88 or newer"; nothing in CI
# caught the drift because packages.yml only runs on releases.
ARG RUST_VERSION=1.88
Comment on lines +12 to +15
ARG VERSION=1.0.0

# ==============================================================================
Expand Down Expand Up @@ -104,6 +107,34 @@ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
ENTRYPOINT ["prtip"]
CMD ["--help"]

# ==============================================================================
# Stage 2b: musl builder (for the Alpine variant)
# ==============================================================================
# Built inside Alpine rather than cross-compiled from Debian. `pcap` links
# against libpcap, and `mlua` builds a vendored Lua, so both need a C toolchain
# and headers that match the target libc. Building natively on Alpine gets musl
# versions of both from apk, which is far simpler than assembling a musl
# sysroot on Debian.
FROM rust:${RUST_VERSION}-alpine AS musl-builder

WORKDIR /build

RUN apk add --no-cache \
libpcap-dev \
musl-dev \
pkgconfig \
build-base \
linux-headers \
openssl-dev \
openssl-libs-static

COPY Cargo.toml Cargo.lock ./
COPY crates/ crates/

RUN cargo build --release --locked

RUN strip /build/target/release/prtip

# ==============================================================================
# Stage 3: Alpine variant (smaller image, musl libc)
# ==============================================================================
Expand All @@ -123,12 +154,13 @@ RUN apk add --no-cache \
# Create non-root user
RUN adduser -D -s /bin/sh prtip

# Note: For Alpine, you need to build with musl target
# This stage expects a pre-built musl binary
# COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/prtip /usr/local/bin/prtip

# For now, use the glibc binary (requires compatibility layer or rebuild)
COPY --from=builder /build/target/release/prtip /usr/local/bin/prtip
# Binary comes from the musl builder stage above, not from `builder`.
# Copying the Debian-built glibc binary here produced an image in which
# `prtip` could not execute at all -- musl provides no
# /lib64/ld-linux-x86-64.so.2, so the kernel fails the exec with a bare
# "no such file or directory". The HEALTHCHECK below would have caught it
# on every run of this image.
Comment on lines +158 to +162
COPY --from=musl-builder /build/target/release/prtip /usr/local/bin/prtip

COPY README.md /usr/share/doc/prtip/
COPY LICENSE /usr/share/doc/prtip/
Expand Down
Loading