From 2af5a36b4d5facfb5ae08d2da4f3ef846d2a059d Mon Sep 17 00:00:00 2001 From: "Youri T. K. K. Mattar" Date: Tue, 11 Aug 2026 16:56:15 -0300 Subject: [PATCH] fix(readme): the first command told people to pull an image that did not exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported by the first person to try it, which is the whole point of writing a section for people who have not seen the project before — and an unambiguous failure of the milestone that added it. The README said `docker run ghcr.io/…/oxinit:demo`. No tag had been pushed, so the release workflow that published that image had never run. `test-demo` passed the entire time: it builds the image locally and checks that one, so the test and the README were talking about two different images and nothing compared them. The demo image is documentation, not a release artifact. It is published from main by CI now, rather than from a tag by the release workflow — tying it to a release meant the README pointed at something that would not exist until a version number had been declared, which is a separate decision and was not about to be made. The release workflow adds a versioned tag and nothing else. The README leads with the two commands that need no registry, and offers the pull second. And CI checks that the image reference in the README is the one the publish step pushes. That is the half a test on a locally built image structurally cannot cover, and it is the half that failed. Not automatable, and stated in ROADMAP rather than left to be discovered again: a package pushed by Actions is private by default, and an anonymous pull against a private package fails with the same `denied`. Making it public is a one-time setting on the repository's package page. --- .github/workflows/ci.yml | 35 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 18 +++++++++++++----- README.md | 23 ++++++++++++----------- ROADMAP.md | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e0cc3e..2a02e1b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,25 @@ jobs: # The library crates have no OS dependency and run on the host. The # parser, the graph, the restart policy, the log format and the calendar # arithmetic are where the logic errors are. + # `test-demo` builds the image locally and checks it, which is what let + # the README ship a `docker run ghcr.io/...` line pointing at something + # that had never been published: the test and the README were talking + # about different images. This checks they agree on the name, which is + # the half a test on a locally-built image structurally cannot. + - name: The README's image reference is the one CI publishes + run: | + OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + WANT="ghcr.io/$OWNER/oxinit:demo" + if ! grep -qF "$WANT" README.md; then + echo "README.md does not mention $WANT, which is what CI pushes" + exit 1 + fi + if ! grep -qF 'oxinit:demo' .github/workflows/ci.yml; then + echo "the publish step no longer pushes the tag the README names" + exit 1 + fi + echo "README and CI agree on $WANT" + - name: Host tests run: cargo test -p oxinit-unit -p oxinit-graph -p oxinit-service -p oxinit-cgroup -p oxinit-user -p oxinit-ipc -p oxinit-log -p oxinit-timer @@ -164,6 +183,9 @@ jobs: name: the README's demo runs-on: ubuntu-latest needs: check + permissions: + contents: read + packages: write steps: - uses: actions/checkout@v4 - run: rustup target add x86_64-unknown-linux-musl @@ -185,6 +207,19 @@ jobs: # works is worse than a README with nothing in it. - run: cargo xtask test-demo + # The demo image is documentation, not a release artifact, so it is + # published from `main` rather than from a tag. Tying it to a release + # meant the README told people to pull something that would not exist + # until a version number had been declared — which is a separate + # decision, and was not going to be made that week. + - name: Publish the demo image + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + run: | + OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + docker tag oxinit:demo "ghcr.io/$OWNER/oxinit:demo" + docker push "ghcr.io/$OWNER/oxinit:demo" + distro: name: a real userspace runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee1e882..f3a8106 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,18 +81,20 @@ jobs: # demo/, not units/ — the latter is the test suite and contains services # that fail on purpose. - name: Build the demo image - run: cargo xtask demo --build-only --tag ghcr.io/${{ github.repository_owner }}/oxinit:demo + run: cargo xtask demo --build-only - name: Log in to ghcr.io run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + # Only the versioned tag. `:demo` tracks `main` and is published by CI, + # because it is documentation rather than a release artifact — the README + # points at it, and a README should not be waiting on a version number + # somebody has not decided to declare yet. - name: Push run: | OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]') - docker tag ghcr.io/${{ github.repository_owner }}/oxinit:demo ghcr.io/$OWNER/oxinit:demo - docker tag ghcr.io/$OWNER/oxinit:demo ghcr.io/$OWNER/oxinit:${{ github.ref_name }} - docker push ghcr.io/$OWNER/oxinit:demo - docker push ghcr.io/$OWNER/oxinit:${{ github.ref_name }} + docker tag oxinit:demo "ghcr.io/$OWNER/oxinit:${{ github.ref_name }}" + docker push "ghcr.io/$OWNER/oxinit:${{ github.ref_name }}" release: name: publish @@ -125,6 +127,12 @@ jobs: docker run --rm --name oxinit-demo -p 8080:8080 ghcr.io/OWNER/oxinit:TAG ``` + `ghcr.io/OWNER/oxinit:demo` tracks `main` if you would rather have the + latest. + + ```bash + ``` + ### Install Download the tarball for your architecture below, check it, and put diff --git a/README.md b/README.md index 49733f9..586740f 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,17 @@ flowchart TD That is deliberate: a bug in the log writer kills the log writer, and a bug in PID 1 kills the machine. -## Try it — one command +## Try it -You need [Docker](https://docs.docker.com/get-started/get-docker/) and nothing -else. No Rust, no virtual machine, no kernel. +You need [Docker](https://docs.docker.com/get-started/get-docker/) and a Rust +toolchain. Two commands: + +```bash +cargo xtask fetch --arch x86_64 # a kernel and a busybox, into target/ +cargo xtask demo # build the image and run it +``` + +Or pull the image, which tracks `main` and needs no toolchain at all: ```bash docker run --rm --name oxinit-demo -p 8080:8080 ghcr.io/youhide/oxinit:demo @@ -81,14 +88,8 @@ for each to actually be gone, and exits `0`. Most container images have to be killed after a ten-second grace period; that shows up as exit code 137. The units the demo runs are in [demo/](demo/), one small file each, and they -are meant to be read. - -To build and run it yourself instead of pulling the published image: - -```bash -cargo xtask fetch --arch x86_64 # a kernel and a busybox, into target/ -cargo xtask demo # build the image and run it -``` +are meant to be read. The image is 4.6 MB — statically linked against musl, +`FROM scratch`, no base image and no shared libraries. ## Why diff --git a/ROADMAP.md b/ROADMAP.md index 8f229ba..fa439fe 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -963,6 +963,39 @@ twelve seconds later — and the test image asks for a shutdown sixteen seconds in, inside that window with margin on both sides. Reproducing it by luck once is not a regression test. +### The first command in the README did not work + +Reported by the first person to try it, which is the whole point of writing a +section for people who have not seen the project before — and an unambiguous +failure of the milestone that added it. + +The README told a newcomer to `docker run ghcr.io/…/oxinit:demo`. No tag had +been pushed, so the release workflow that published that image had never run, +and the image did not exist. `test-demo` passed the entire time: it builds the +image locally and checks *that*, so the test and the README were talking about +two different images and nothing compared them. + +Three fixes, and only one of them is the obvious one. + +The demo image is **documentation, not a release artifact**, so it is published +from `main` by CI rather than from a tag by the release workflow. Tying it to a +release meant the README pointed at something that would not exist until a +version number had been declared — a separate decision, and not one anybody was +about to make. The release workflow now adds a versioned tag and nothing else. + +The README leads with the two commands that work with no registry at all, and +offers the pull second. + +And CI checks that the image reference in the README is the one the publish +step pushes. That is the half a test on a locally built image structurally +cannot cover, and it is the half that failed. + +One thing here is not automatable: a package pushed by Actions is **private by +default**, and an anonymous `docker run` against a private package fails with +exactly the `denied` that was reported. Making it public is a one-time setting +on the repository's package page, and it belongs to whoever owns the account +rather than to CI. + ## Not doing, and why These were on the list. They are coming off it with a reason rather than