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
140 changes: 0 additions & 140 deletions .github/workflows/apt-indices-refresh.yml

This file was deleted.

3 changes: 0 additions & 3 deletions .github/workflows/official-golden.yml
Original file line number Diff line number Diff line change
Expand Up @@ -394,13 +394,10 @@ jobs:
env:
GOLDEN_NAME: preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}
run: |
MAX_AGE_DAYS="$(grep -E '^[[:space:]]*apt_indices_max_age_days[[:space:]]*=' versions.toml | cut -d'"' -f2 || true)"
test -n "$MAX_AGE_DAYS"
python3 scripts/write-golden-provenance.py \
--golden "dist/$GOLDEN_NAME" \
--base-evidence "dist/$GOLDEN_NAME.base-provenance.json" \
--base-sbom "dist/$GOLDEN_NAME.base-sbom.spdx.json" \
--apt-indices-max-age-days "$MAX_AGE_DAYS" \
--output "dist/$GOLDEN_NAME.provenance.json"

- name: Install cosign
Expand Down
16 changes: 6 additions & 10 deletions .github/workflows/release-golden.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
name: release-golden

# Manual/emergency golden bake. Releases do NOT bake: a golden's content is
# keyed by the official-image pin (official-image.toml), the toolchain pins
# (versions.toml), and the apt-index bake date — none of which move with an
# engine release — and the runtime runner bundle is mounted from the engine at
# job time, so a per-release bake buys nothing while costing a hosted runner
# >110 GiB of staging. Production bakes are host-driven: x86_64 on a Linux
# host with KVM and disk, aarch64 on an Apple Silicon host. `release.yml` seeds
# each release with the newest complete golden pair (payload + sidecars).
# keyed by the official-image pin in official-image.toml, which does not move
# with an engine release, and the runtime runner bundle is mounted from the
# engine at job time, so a per-release bake buys nothing while costing a hosted
# runner >110 GiB of staging. Production bakes are host-driven: x86_64 on an
# x86_64 host with KVM and disk, aarch64 on macstudio. `release.yml` seeds each
# release with the newest complete golden pair (payload + sidecars).
on:
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -295,13 +294,10 @@ jobs:
env:
GOLDEN_NAME: preloop-ubuntu-24.04-${{ env.GOLDEN_ARCH }}
run: |
MAX_AGE_DAYS="$(grep -E '^[[:space:]]*apt_indices_max_age_days[[:space:]]*=' versions.toml | cut -d'"' -f2 || true)"
test -n "$MAX_AGE_DAYS"
python3 scripts/write-golden-provenance.py \
--golden "dist/$GOLDEN_NAME" \
--base-evidence "dist/$GOLDEN_NAME.base-provenance.json" \
--base-sbom "dist/$GOLDEN_NAME.base-sbom.spdx.json" \
--apt-indices-max-age-days "$MAX_AGE_DAYS" \
--output "dist/$GOLDEN_NAME.provenance.json"

- name: Install cosign
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,11 @@ jobs:
subject-path: artifacts/*
# Engines resolve the golden at their own release tag, so a release must
# carry a complete, verifiable golden pair — but goldens are baked by
# their own hosts (x86_64 on a Linux KVM host, aarch64 on an Apple
# Silicon host), never by this pipeline: a golden's content is keyed by
# the official image pin, the toolchain pins and the apt-index bake
# date, and the runner bundle is mounted from the engine at job time, so
# a per-release bake would ship an identical artifact for >110 GiB of
# staging.
# their own hosts (x86_64 on an x86_64 KVM host, aarch64 on macstudio),
# never by this pipeline: a golden's content is keyed by the official
# image pin and the runner contract, and the runner bundle is mounted
# from the engine at job time, so a per-release bake would ship an
# identical artifact for >110 GiB of staging.
# Seed the newest pair that exists, sidecars included, so a release is
# never a guaranteed 404 (golden_url_candidates prefers the own tag) and
# never claims a checksum it cannot prove.
Expand Down
12 changes: 10 additions & 2 deletions .github/workflows/smolvm-release-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,16 @@ jobs:
rm -f "$SOCKET"
}
trap cleanup EXIT
IMAGE="$(grep '^ubuntu_24_04_base' versions.toml | cut -d'"' -f2)"
[ -n "$IMAGE" ] || { echo "ubuntu_24_04_base missing from versions.toml" >&2; exit 1; }
# The base image once came from versions.toml's `ubuntu_24_04_base`;
# that pin is gone with the curated bake. Verify the runtime against
# the image the official goldens are actually built from, per arch.
case "$(uname -m)" in
x86_64) OFFICIAL_KEY=official_runner_image_base_amd64 ;;
arm64|aarch64) OFFICIAL_KEY=official_runner_image_base_arm64 ;;
*) echo "unsupported architecture: $(uname -m)" >&2; exit 1 ;;
esac
IMAGE="$(awk -F'"' -v k="^${OFFICIAL_KEY} = " '$0 ~ k { print $2; exit }' official-image.toml)"
[ -n "$IMAGE" ] || { echo "$OFFICIAL_KEY missing from official-image.toml" >&2; exit 1; }
python3 -c "import socket; s = socket.socket(socket.AF_UNIX); s.bind('$SOCKET'); s.close()"
echo "creating machine from $IMAGE"
smolvm machine create --name "$NAME" \
Expand Down
98 changes: 98 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,104 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Releases before v0.27.0 predate the changelog.
## [Unreleased]

### Changed

- **One golden source: the official packed golden, or the image you
configured.** `runs-on: ubuntu-latest`/`ubuntu-24.04` — and any pooled
environment with no image configured — now resolve to the published packed
golden, downloaded per architecture from a digest-pinned OCI reference
(`PRELOOP_GOLDEN_OCI_REF` overrides it; `PRELOOP_GOLDEN_URL` still selects a
release-asset mirror) and installed only after its checksum (release asset)
or layer digest (OCI) matches. The transfer resumes from a `.partial` file
across retries and engine restarts, and a download that cannot complete
fails the job: there is no local bake and no stock-Ubuntu fallback behind it.
`ubuntu-22.04` now maps nowhere and keeps the configured image instead of
silently selecting a 24.04 base.

The curated stock bake is deleted with it: no toolchain layers
(Rust/Go/Python/Node), no `base_install_script`, no apt package pins or
index-freshness marker (and no weekly `apt-indices-refresh` workflow), no
goldens baked per `runs-on` environment, no local bake from stock Ubuntu and
no direct-create fallback when the packed artifact is unavailable.
`PRELOOP_USE_PACKED_GOLDEN` is gone — a file-pack backend always uses a
packed golden — and the pool holds one golden per pool environment and forks
it per job.

A configured image (`PRELOOP_RUNNER_BASE_IMAGE`, or the `[golden]
base_image` that `preloop init` records) is now used exactly as it is.
Preloop adds only the GitHub-runner machinery at golden build: a `runner`
account (uid 1001) with its home, `_work` and passwordless sudo unless the
image already has them; an owner-only ownership walk over the runner home
(`find … ! -user 1001 -exec chown -h 1001:1001 {} +`, which leaves a
runner-owned file's group alone); a writable `/opt/hostedtoolcache` with
`RUNNER_TOOL_CACHE`/`AGENT_TOOLSDIRECTORY` in `/etc/environment`; and the
`/etc/preloop-bake.json` build record. The only checked requirement is a
glibc dynamic loader — a missing `bash`, `git` or `docker` fails the step
that needs it, exactly as on GitHub-hosted runners. `preloop build-golden`
takes the image it bakes (`--base-image <ref>`, or the configured image when
the flag is absent); the official golden is published packed and cannot be
built locally.

Guest `PATH`, `RUSTUP_HOME` and `CARGO_HOME` overrides are gone: the guest
runner's PATH is the system PATH, and tool versions come from the workflow's
`setup-*` actions, as they do on GitHub.

- **A golden is keyed by everything that changes it.** The official golden's
fingerprint now covers the source this host would fetch it from
(`PRELOOP_GOLDEN_OCI_REF` or the `PRELOOP_GOLDEN_URL` mirror), so changing
the mirror re-fetches instead of serving the previous pack forever; a
configured image's fingerprint covers its effective runner account, so
changing `PRELOOP_RUNNER_USER`/`_UID` rebuilds instead of adopting a golden
baked for the account before it. A backend that cannot restore packs
(AgentENV) boots the pinned official runner image for the official golden —
the sentinel itself is not an image — and an unconfigured pool no longer
fails to start there.

- **Leftovers of earlier releases are cleaned up at startup.** Packed-golden
payloads keyed on the retired stock Ubuntu bases
(`preloop-[mirror.gcr.io-library-]ubuntu-{24.04,22.04}[-sha256-…]-<arch>-<fingerprint>`)
live under a stem this release no longer produces, so no fingerprint
rotation would ever reach them; the startup sweep now removes them. Goldens
prepared per `runs-on` environment (`<prefix>-golden-<fingerprint12>`) keep
their fingerprint record, which exempted them from the stale-machine
cleanup; one that neither the official golden nor the configured image
resolves to is now deleted with its record.

- **Apt indices are baked into the golden.** The runner-image dump ships with
`/var/lib/apt/lists` wiped, and a workflow's `sudo apt-get install <pkg>`
with no `apt-get update` first (uv's musl cell) fails with `E: Unable to
locate package`. The golden contract now refreshes the indices at build, and
an unpacked pack that has none is refreshed once before it is frozen, so
forks inherit them. Also fixes `golden_contract_script("root", …)`, which
rendered `; ;` and did not parse as `sh`.

- **The guest runner inherits the image's environment.** It is launched
through a shell that sources `/etc/environment`, so the PATH a step sees is
the image's own — the tools a hosted image preinstalls keep resolving —
while the pool's variables are re-asserted afterwards. An image that
already carries a `runner` account at another uid now has that account moved
to the configured uid instead of running jobs as a uid that owns nothing.
The Rosetta amd64 install is no longer fatal for a configured image without
apt: the bake warns that x86_64 binaries will not run instead of refusing an
otherwise usable image.

- **Stopping the engine no longer waits for a golden transfer.**
`prepare_fork_base` checks the shutdown token before starting a download or
bake, so a restart during the warm returns promptly; the transfer resumes on
the next start.

### Security

- **The golden's runner account name is validated** before it reaches the
root-run bake scripts and the `sudoers.d` filename: anything that is not a
Linux account name (or `root`) is refused, closing a shell-injection hole
for a hostile or mistyped `PRELOOP_RUNNER_USER`.

- **A `PRELOOP_GOLDEN_URL` mirror must be verifiable.** Its bytes become the
image every job runs, so an unverified payload is now refused: the mirror
must publish `<asset>.sha256` or the host must pin the digest with
`PRELOOP_GOLDEN_SHA256`. The engine's own release assets keep the previous
tolerated-with-a-warning behavior.
### Added
- In-place reruns now persist `jobs.request_id` and copy it into
per-attempt `job_history`, preserving log/step/artifact resolution for
Expand Down
19 changes: 13 additions & 6 deletions benchmarks/real-world/conformance-10repos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ export PRELOOP_CLIENT_TIMEOUT_SECONDS="${PRELOOP_CLIENT_TIMEOUT_SECONDS:-3600}"
SERVER_BIN="${PRELOOP_BIN:-$ROOT/target/debug/preloop}"
CLIENT_BIN="${PRELOOP_CLIENT_BIN:-$ROOT/target/debug/preloop-runner-client}"
GUEST_RUNNER_BUNDLE="${PRELOOP_RUNNER_BUNDLE:-$ROOT/target/aarch64-unknown-linux-gnu/debug}"
OFFICIAL_GOLDEN_BASE="ghcr.io/preloopdev/runner-images:ubuntu24-arm64-runner-large-latest@sha256:a58990d6b6f8ca5861f33d77e1d3f0732d7d14261caacd6fba8c8f707c05b40e"
OFFICIAL_GOLDEN_NAME="preloop-ghcr.io-preloopdev-runner-images-ubuntu24-arm64-runner-large-latest-sha256-a58990d6b6f8ca5861f33d77e1d3f0732d7d14261caacd6fba8c8f707c05b40e-aarch64"
OFFICIAL_GOLDEN_ARTIFACT="${PRELOOP_GOLDEN_ARTIFACT:-$HOME/.config/preloop/vms/${OFFICIAL_GOLDEN_NAME}.smolmachine}"
# The official sentinel: what a pool with no image configured resolves to,
# and what `runs-on: ubuntu-latest`/`ubuntu-24.04` resolve to. The engine
# fetches the packed golden from the per-architecture OCI reference
# (PRELOOP_GOLDEN_OCI_REF overrides it) and keys the payload by this
# sentinel plus the environment fingerprint — ask `preloop golden-path`
# for that path, never spell it here.
OFFICIAL_GOLDEN_SENTINEL="preloop-official-golden"
# Operator cache of the same ~9GB payload, symlinked into the campaign home
# so a harness restart does not re-download it. Point PRELOOP_GOLDEN_ARTIFACT
# at a copy that lives elsewhere.
OFFICIAL_GOLDEN_ARTIFACT="${PRELOOP_GOLDEN_ARTIFACT:-$HOME/.config/preloop/vms/${OFFICIAL_GOLDEN_SENTINEL}-aarch64.smolmachine}"
SERVER_PID=""
FAILED_TARGETS=""
HOST_HOME="${HOME:-}"
Expand Down Expand Up @@ -119,7 +127,7 @@ prepare_golden_home() {
# environment fingerprint). A hand-rolled hash drifts whenever the bake,
# toolchains, or node pins change, and a miss means a full 9GB rebuild.
expected="$(PRELOOP_HOME="$CAMPAIGN_HOME" \
PRELOOP_RUNNER_BASE_IMAGE="$OFFICIAL_GOLDEN_BASE" \
PRELOOP_RUNNER_BASE_IMAGE="$OFFICIAL_GOLDEN_SENTINEL" \
"$SERVER_BIN" golden-path)" || fail "could not compute golden payload path"
mkdir -p "$(dirname "$expected")"
ln -sfn "$OFFICIAL_GOLDEN_ARTIFACT" "$expected"
Expand Down Expand Up @@ -176,10 +184,9 @@ start_server() {
SMOLVM_DATA_DIR="${SMOLVM_DATA_DIR:-$CAMPAIGN_HOME/smolvm}" \
SMOLVM_AGENT_ROOTFS="${SMOLVM_AGENT_ROOTFS:-$SMOLVM_HOME_DIR/agent-rootfs}" \
SMOLVM_LIB_DIR="${SMOLVM_LIB_DIR:-$SMOLVM_HOME_DIR/lib}" \
PRELOOP_RUNNER_BASE_IMAGE="$OFFICIAL_GOLDEN_BASE" \
PRELOOP_RUNNER_BASE_IMAGE="$OFFICIAL_GOLDEN_SENTINEL" \
PRELOOP_RUNNER_BUNDLE="$GUEST_RUNNER_BUNDLE" \
PRELOOP_RUNNER_NAME_PREFIX="conformance-10repos" \
PRELOOP_USE_PACKED_GOLDEN=1 \
PRELOOP_RUNNER_POOL_ENABLED=1 \
PRELOOP_RUNNER_POOL_SIZE="$POOL_SIZE" \
PRELOOP_USE_FORK=1 \
Expand Down
Loading