From 2d63e7c438a076d806fea16e06d61dd04649bd3a Mon Sep 17 00:00:00 2001 From: lczyk Date: Mon, 1 Jun 2026 21:19:27 +0100 Subject: [PATCH 01/18] feat: chiselled rocks build for bread flavours parallel rocks/ tree migrating both flavours (bread, bread-chisel-releases) x {24.04,25.10,26.04} to rockcraft. base bare + build-base ubuntu@, built from chisel slices; sshd runs as a pebble service. the bread rock is the spread test-host itself, so allocate docker-runs the loaded rock directly. tests live only in top-level rocks/tests/ (one flavour-branching contract task over 12 systems). rocks-ci.yaml builds per-arch + runs the suite; no publishing. existing docker-image world untouched (ci.yaml ignores rocks/**). known holes: tree + skopeo have no slice yet (omitted from the cr flavour); pam is permit-only. --- .github/workflows/ci.yaml | 5 + .github/workflows/rocks-ci.yaml | 109 ++++++++++++++ .gitignore | 4 + rocks/bread-24.04/hack/banner.txt | 13 ++ rocks/bread-24.04/hack/bread-warning.sh | 39 +++++ rocks/bread-24.04/hack/hash_inputs.sh | 16 ++ rocks/bread-24.04/hack/lazy-apt.sh | 21 +++ rocks/bread-24.04/hack/sshd-entry.sh | 13 ++ rocks/bread-24.04/makefile | 45 ++++++ rocks/bread-24.04/rockcraft.yaml | 127 ++++++++++++++++ rocks/bread-25.10/hack/banner.txt | 13 ++ rocks/bread-25.10/hack/bread-warning.sh | 39 +++++ rocks/bread-25.10/hack/hash_inputs.sh | 16 ++ rocks/bread-25.10/hack/lazy-apt.sh | 21 +++ rocks/bread-25.10/hack/sshd-entry.sh | 13 ++ rocks/bread-25.10/makefile | 45 ++++++ rocks/bread-25.10/rockcraft.yaml | 127 ++++++++++++++++ rocks/bread-26.04/hack/banner.txt | 13 ++ rocks/bread-26.04/hack/bread-warning.sh | 39 +++++ rocks/bread-26.04/hack/hash_inputs.sh | 16 ++ rocks/bread-26.04/hack/lazy-apt.sh | 21 +++ rocks/bread-26.04/hack/sshd-entry.sh | 13 ++ rocks/bread-26.04/makefile | 45 ++++++ rocks/bread-26.04/rockcraft.yaml | 127 ++++++++++++++++ .../hack/banner.txt | 13 ++ .../hack/bread-warning.sh | 39 +++++ .../hack/chisel_override_build.sh | 43 ++++++ .../hack/docker_override_build.sh | 28 ++++ .../hack/hash_inputs.sh | 16 ++ .../hack/lazy-apt.sh | 21 +++ .../hack/sshd-entry.sh | 13 ++ rocks/bread-chisel-releases-24.04/makefile | 43 ++++++ ...p-maintenance-check-via-CHISEL_HACKS.patch | 49 ++++++ ...ve-maintenance-check-via-CHISEL_HACK.patch | 58 +++++++ ...-release-validation-via-CHISEL_HACKS.patch | 27 ++++ .../rockcraft.yaml | 141 ++++++++++++++++++ .../hack/banner.txt | 13 ++ .../hack/bread-warning.sh | 39 +++++ .../hack/chisel_override_build.sh | 43 ++++++ .../hack/docker_override_build.sh | 28 ++++ .../hack/hash_inputs.sh | 16 ++ .../hack/lazy-apt.sh | 21 +++ .../hack/sshd-entry.sh | 13 ++ rocks/bread-chisel-releases-25.10/makefile | 43 ++++++ ...p-maintenance-check-via-CHISEL_HACKS.patch | 49 ++++++ ...ve-maintenance-check-via-CHISEL_HACK.patch | 58 +++++++ ...-release-validation-via-CHISEL_HACKS.patch | 27 ++++ .../rockcraft.yaml | 141 ++++++++++++++++++ .../hack/banner.txt | 13 ++ .../hack/bread-warning.sh | 39 +++++ .../hack/chisel_override_build.sh | 43 ++++++ .../hack/docker_override_build.sh | 28 ++++ .../hack/hash_inputs.sh | 16 ++ .../hack/lazy-apt.sh | 21 +++ .../hack/sshd-entry.sh | 13 ++ rocks/bread-chisel-releases-26.04/makefile | 43 ++++++ ...p-maintenance-check-via-CHISEL_HACKS.patch | 49 ++++++ ...ve-maintenance-check-via-CHISEL_HACK.patch | 58 +++++++ ...-release-validation-via-CHISEL_HACKS.patch | 27 ++++ .../rockcraft.yaml | 141 ++++++++++++++++++ rocks/makefile | 53 +++++++ rocks/tests/allocate.sh | 43 ++++++ rocks/tests/contract/task.yaml | 69 +++++++++ rocks/tests/discard.sh | 24 +++ rocks/tests/spread.yaml | 35 +++++ 65 files changed, 2637 insertions(+) create mode 100644 .github/workflows/rocks-ci.yaml create mode 100644 rocks/bread-24.04/hack/banner.txt create mode 100755 rocks/bread-24.04/hack/bread-warning.sh create mode 100755 rocks/bread-24.04/hack/hash_inputs.sh create mode 100755 rocks/bread-24.04/hack/lazy-apt.sh create mode 100755 rocks/bread-24.04/hack/sshd-entry.sh create mode 100644 rocks/bread-24.04/makefile create mode 100644 rocks/bread-24.04/rockcraft.yaml create mode 100644 rocks/bread-25.10/hack/banner.txt create mode 100755 rocks/bread-25.10/hack/bread-warning.sh create mode 100755 rocks/bread-25.10/hack/hash_inputs.sh create mode 100755 rocks/bread-25.10/hack/lazy-apt.sh create mode 100755 rocks/bread-25.10/hack/sshd-entry.sh create mode 100644 rocks/bread-25.10/makefile create mode 100644 rocks/bread-25.10/rockcraft.yaml create mode 100644 rocks/bread-26.04/hack/banner.txt create mode 100755 rocks/bread-26.04/hack/bread-warning.sh create mode 100755 rocks/bread-26.04/hack/hash_inputs.sh create mode 100755 rocks/bread-26.04/hack/lazy-apt.sh create mode 100755 rocks/bread-26.04/hack/sshd-entry.sh create mode 100644 rocks/bread-26.04/makefile create mode 100644 rocks/bread-26.04/rockcraft.yaml create mode 100644 rocks/bread-chisel-releases-24.04/hack/banner.txt create mode 100755 rocks/bread-chisel-releases-24.04/hack/bread-warning.sh create mode 100755 rocks/bread-chisel-releases-24.04/hack/chisel_override_build.sh create mode 100755 rocks/bread-chisel-releases-24.04/hack/docker_override_build.sh create mode 100755 rocks/bread-chisel-releases-24.04/hack/hash_inputs.sh create mode 100755 rocks/bread-chisel-releases-24.04/hack/lazy-apt.sh create mode 100755 rocks/bread-chisel-releases-24.04/hack/sshd-entry.sh create mode 100644 rocks/bread-chisel-releases-24.04/makefile create mode 100644 rocks/bread-chisel-releases-24.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch create mode 100644 rocks/bread-chisel-releases-24.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch create mode 100644 rocks/bread-chisel-releases-24.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch create mode 100644 rocks/bread-chisel-releases-24.04/rockcraft.yaml create mode 100644 rocks/bread-chisel-releases-25.10/hack/banner.txt create mode 100755 rocks/bread-chisel-releases-25.10/hack/bread-warning.sh create mode 100755 rocks/bread-chisel-releases-25.10/hack/chisel_override_build.sh create mode 100755 rocks/bread-chisel-releases-25.10/hack/docker_override_build.sh create mode 100755 rocks/bread-chisel-releases-25.10/hack/hash_inputs.sh create mode 100755 rocks/bread-chisel-releases-25.10/hack/lazy-apt.sh create mode 100755 rocks/bread-chisel-releases-25.10/hack/sshd-entry.sh create mode 100644 rocks/bread-chisel-releases-25.10/makefile create mode 100644 rocks/bread-chisel-releases-25.10/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch create mode 100644 rocks/bread-chisel-releases-25.10/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch create mode 100644 rocks/bread-chisel-releases-25.10/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch create mode 100644 rocks/bread-chisel-releases-25.10/rockcraft.yaml create mode 100644 rocks/bread-chisel-releases-26.04/hack/banner.txt create mode 100755 rocks/bread-chisel-releases-26.04/hack/bread-warning.sh create mode 100755 rocks/bread-chisel-releases-26.04/hack/chisel_override_build.sh create mode 100755 rocks/bread-chisel-releases-26.04/hack/docker_override_build.sh create mode 100755 rocks/bread-chisel-releases-26.04/hack/hash_inputs.sh create mode 100755 rocks/bread-chisel-releases-26.04/hack/lazy-apt.sh create mode 100755 rocks/bread-chisel-releases-26.04/hack/sshd-entry.sh create mode 100644 rocks/bread-chisel-releases-26.04/makefile create mode 100644 rocks/bread-chisel-releases-26.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch create mode 100644 rocks/bread-chisel-releases-26.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch create mode 100644 rocks/bread-chisel-releases-26.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch create mode 100644 rocks/bread-chisel-releases-26.04/rockcraft.yaml create mode 100644 rocks/makefile create mode 100755 rocks/tests/allocate.sh create mode 100644 rocks/tests/contract/task.yaml create mode 100755 rocks/tests/discard.sh create mode 100644 rocks/tests/spread.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ef38bee..eff545b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,12 +2,17 @@ name: CI run-name: CI for ${{ github.ref }} # Build + test on PR and on push to main. No publishing. +# Rock-only changes are handled by rocks-ci.yaml -- ignore them here. on: pull_request: + paths-ignore: + - 'rocks/**' push: branches: - main + paths-ignore: + - 'rocks/**' workflow_dispatch: # Cancel in-flight runs on the same ref when a newer one starts. diff --git a/.github/workflows/rocks-ci.yaml b/.github/workflows/rocks-ci.yaml new file mode 100644 index 0000000..d447a2e --- /dev/null +++ b/.github/workflows/rocks-ci.yaml @@ -0,0 +1,109 @@ +name: rocks CI +run-name: rocks CI for ${{ github.ref }} + +# Build + test the chiselled rocks on PR + push to main. No publishing. +# Build packs each rock per-arch (native runners); test loads that arch's rocks +# into the local daemon and runs the single top-level spread suite over the +# native-arch systems. +on: + pull_request: + paths: + - 'rocks/**' + - '.github/workflows/rocks-ci.yaml' + push: + branches: + - main + paths: + - 'rocks/**' + - '.github/workflows/rocks-ci.yaml' + workflow_dispatch: + +concurrency: + group: rocks-ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build: + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + dir: + - bread-24.04 + - bread-25.10 + - bread-26.04 + - bread-chisel-releases-24.04 + - bread-chisel-releases-25.10 + - bread-chisel-releases-26.04 + arch: + - amd64 + - arm64 + include: + - arch: amd64 + runner: ubuntu-24.04 + - arch: arm64 + runner: ubuntu-24.04-arm + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Pack rock + uses: canonical/craft-actions/rockcraft-pack@main + with: + path: rocks/${{ matrix.dir }} + verbosity: brief + + - name: Rename to predictable artefact name + run: mv rocks/${{ matrix.dir }}/*.rock rocks/${{ matrix.dir }}/${{ matrix.dir }}.rock + + - name: Upload rock artefact + uses: actions/upload-artifact@v6 + with: + name: rock-${{ matrix.dir }}-${{ matrix.arch }} + path: rocks/${{ matrix.dir }}/${{ matrix.dir }}.rock + + test: + needs: build + runs-on: ${{ matrix.runner }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-24.04 + - arch: arm64 + runner: ubuntu-24.04-arm + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Download this arch's rocks + uses: actions/download-artifact@v6 + with: + pattern: rock-*-${{ matrix.arch }} + path: dl + + - name: Place rocks under their dirs + run: | + set -e + for d in dl/rock-*-${{ matrix.arch }}; do + base=$(basename "$d") # rock-- + dir=${base#rock-} + dir=${dir%-${{ matrix.arch }}} + cp "$d/$dir.rock" "rocks/$dir/$dir.rock" + done + + - name: Install spread + skopeo + run: | + sudo apt-get update + sudo apt-get install -y skopeo + go install github.com/canonical/spread/cmd/spread@latest + echo "$HOME/go/bin" >> "$GITHUB_PATH" + + - name: Run the top-level spread suite (${{ matrix.arch }}) + run: make -C rocks spread-only diff --git a/.gitignore b/.gitignore index ba02087..0fc9e81 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,9 @@ .spread-reuse*.yaml .stamp/ cache/ + +# rockcraft (rocks/) build artefacts +*.rock +.rock.stamp __pycache__/ *.pyc diff --git a/rocks/bread-24.04/hack/banner.txt b/rocks/bread-24.04/hack/banner.txt new file mode 100644 index 0000000..3c06b8b --- /dev/null +++ b/rocks/bread-24.04/hack/banner.txt @@ -0,0 +1,13 @@ + ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ + ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ +███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ +███▒▒░░░░░░░ ░░▒▒▓▓▓▓ +███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ + ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ + ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ + ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ + ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ + ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ + ███▒▒░░░░░░░ ░░░░░░░▓▓ + █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-24.04/hack/bread-warning.sh b/rocks/bread-24.04/hack/bread-warning.sh new file mode 100755 index 0000000..2ee0140 --- /dev/null +++ b/rocks/bread-24.04/hack/bread-warning.sh @@ -0,0 +1,39 @@ +# bread-warning: insecure-image notice. +# +# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive +# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same +# session. Silent on non-tty (spread, scripts, ci logs) unless +# BREAD_BANNER_FORCE is set. +# +# Banner content is verbatim from /etc/bread-banner.txt (shipped from +# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped +# in red ANSI; everything else is printed as-is. +# +# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no +# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER +# to stderr on every connection, which would inject ~14 lines of noise into +# every non-interactive `ssh root@host cmd` that spread runs. This shell +# hook covers every interactive path a human actually hits (ssh login shell +# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing +# worth the noise. + +[ -n "$BREAD_BANNER_SHOWN" ] && return 0 +if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then + export BREAD_BANNER_SHOWN=1 + return 0 +fi +export BREAD_BANNER_SHOWN=1 + +__bw_banner=/etc/bread-banner.txt +[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } + +if [ -t 1 ] && [ -z "$NO_COLOR" ]; then + __bw_r=$(printf '\033[1;31m') + __bw_n=$(printf '\033[0m') + sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" + unset __bw_r __bw_n +else + cat "$__bw_banner" +fi + +unset __bw_banner diff --git a/rocks/bread-24.04/hack/hash_inputs.sh b/rocks/bread-24.04/hack/hash_inputs.sh new file mode 100755 index 0000000..a6c2a51 --- /dev/null +++ b/rocks/bread-24.04/hack/hash_inputs.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Single sha256 over everything that affects this rock's build: the +# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the +# .rock.stamp short-circuit in the makefile so `make build` only repacks when a +# real input changed. +# +# Stdout: hex digest only. +set -euo pipefail + +cd "$(dirname "$0")/.." + +# shellcheck disable=SC2046 # word-splitting of find output is intended here. +sha256sum \ + rockcraft.yaml \ + $(find hack patches -type f 2>/dev/null | sort) \ + | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-24.04/hack/lazy-apt.sh b/rocks/bread-24.04/hack/lazy-apt.sh new file mode 100755 index 0000000..1038a4f --- /dev/null +++ b/rocks/bread-24.04/hack/lazy-apt.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# lazy-apt: on first non-update call, runs apt-get update then replaces both +# shims with symlinks to real apt. Installed at /usr/local/bin/apt and +# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call +# apt/apt-get without an explicit update still work after the image was built +# with apt lists wiped. +# +# Replaces with symlinks rather than deleting: bash hashes the shim path on +# first use, so deletion would leave a stale hash entry causing subsequent +# calls to fail with "No such file or directory". + +real="/usr/bin/$(basename "$0")" + +if [ "$1" != "update" ]; then + /usr/bin/apt-get update +fi + +ln -sf /usr/bin/apt /usr/local/bin/apt +ln -sf /usr/bin/apt-get /usr/local/bin/apt-get + +exec "$real" "$@" diff --git a/rocks/bread-24.04/hack/sshd-entry.sh b/rocks/bread-24.04/hack/sshd-entry.sh new file mode 100755 index 0000000..080d94c --- /dev/null +++ b/rocks/bread-24.04/hack/sshd-entry.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pebble service command for sshd in the bread rock. +# +# host keys are generated here on first boot rather than baked into the rock, +# so throwaway test containers don't all share the same keys. also ensures the +# privsep runtime dir exists. then execs sshd in the foreground (pebble manages +# the process). +set -e + +mkdir -p /run/sshd +ssh-keygen -A + +exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-24.04/makefile b/rocks/bread-24.04/makefile new file mode 100644 index 0000000..737225a --- /dev/null +++ b/rocks/bread-24.04/makefile @@ -0,0 +1,45 @@ +.DEFAULT_GOAL := help + +DOCKER ?= docker + +# Predictable artefact name (matches the dir + the spread-rock- load tag). +ROCK := bread-24.04.rock + +.PHONY: help +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: build +build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) + +.PHONY: FORCE +FORCE: + +# Stamp content = hash of all build inputs; only rewritten when it changes, so +# the rock only repacks on a real input change. +.rock.stamp: FORCE + @new=$$(hack/hash_inputs.sh) ; \ + cur=$$(cat $@ 2>/dev/null || true) ; \ + [ "$$new" = "$$cur" ] || echo "$$new" > $@ + +$(ROCK): .rock.stamp + rockcraft pack --verbosity=brief + mv bread_24.04_*.rock $(ROCK) + +.PHONY: size +size: ## Show the packed rock size + @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi + +.PHONY: ls-rock +ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) + @tag=bread-ls-$$$$ ; \ + skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ + cid=$$($(DOCKER) create $$tag:latest) ; \ + $(DOCKER) export $$cid | tar tv ; \ + $(DOCKER) rm $$cid > /dev/null ; \ + $(DOCKER) rmi $$tag:latest > /dev/null + +.PHONY: clean +clean: ## Remove the packed rock + stamp + rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-24.04/rockcraft.yaml b/rocks/bread-24.04/rockcraft.yaml new file mode 100644 index 0000000..65d6ecf --- /dev/null +++ b/rocks/bread-24.04/rockcraft.yaml @@ -0,0 +1,127 @@ +# spellchecker: ignore rockcraft chiselled coreutils passwd nologin sshd usrmerge pebble noninteractive + +name: bread +version: "24.04" +summary: chiselled ubuntu 24.04 + sshd spread test-host rock +description: | + Throwaway spread test-host: a chiselled (base: bare) Ubuntu 24.04 rock with + just enough to act as a spread system -- bash, coreutils, openssh-server, + apt, and a pam_permit-only auth stack (accepts ANY password for root). + + INSECURE BY DESIGN. Built for ephemeral spread test containers only; never + expose to a network or use in production. + +base: bare +build-base: ubuntu@24.04 + +platforms: + amd64: + arm64: + +# sshd runs as a pebble service (pebble is pid 1 in the rock). The +# openssh-server_services slice ships systemd units, not a pebble layer, so we +# drive sshd ourselves via hack/sshd-entry.sh (host-key gen + exec sshd -D). +services: + sshd: + override: replace + command: /usr/local/bin/sshd-entry.sh + startup: enabled + +parts: + bread: + plugin: nil + stage-packages: + # fs skeleton + os-release (version contract check) + chisel manifest + - base-files_base + - base-files_etc + - base-files_var + - base-files_tmp + - base-files_lib + - base-files_home + - base-files_release-info + - base-files_chisel + # shell + core utils. tar + gzip are needed by spread to sync the + # project onto the system over ssh (not part of coreutils). + - bash_bins + - coreutils_bins + - tar_bins + - gzip_bins + # users / login (base-passwd_data writes /etc/passwd + /etc/group w/ root) + - base-passwd_data + - login_bins + - passwd_bins + # ssh (openssh-server_bins pulls openssh-client_bins -> ssh-keygen, and + # libpam-modules_libs -> pam_permit.so, as essentials) + - openssh-server_bins + - openssh-server_config + - ca-certificates_data + # pam: only pam_permit.so (always-allow). NOT libpam-modules_config / + # _pam-config -- we don't want the real pam_unix default stacks. + - libpam-modules_libs + # apt. apt_apt-get pulls dpkg_maintscript-support but NOT /usr/bin/dpkg, + # so apt-get install cannot unpack without dpkg_bins -- add it so the + # lazy-apt shim (and test suites that apt-get install) actually work. + - apt_apt-get + - apt_config + - apt_var + - apt_default-source-list + - dpkg_bins + override-build: | + set -eu + craftctl default + cd "$CRAFT_PART_INSTALL" + + # --- users: base-passwd_data gives us /etc/passwd + /etc/group (incl + # root), but not the sshd privsep user nor any /etc/shadow. add both. + grep -q '^sshd:' etc/passwd || \ + echo 'sshd:x:101:65534::/run/sshd:/usr/sbin/nologin' >> etc/passwd + # root password "bread" (matches the docker-image flavour). pam_permit + # makes the value irrelevant for auth, but root must not be locked. + hash=$(openssl passwd -6 bread) + { + printf 'root:%s:20000:0:99999:7:::\n' "$hash" + printf 'sshd:!:20000:0:99999:7:::\n' + } > etc/shadow + chmod 0640 etc/shadow + + # --- sshd config. openssh-server_config ships sshd_config.d/ but not + # /etc/ssh/sshd_config itself, so write a minimal one. + mkdir -p etc/ssh + cat > etc/ssh/sshd_config <<'EOF' + PermitRootLogin yes + PasswordAuthentication yes + UsePAM yes + Subsystem sftp /usr/lib/openssh/sftp-server + PidFile /run/sshd.pid + EOF + + # --- pam: permit-only stack (always-allow), verbatim shape from the + # docker-image flavour. + mkdir -p etc/pam.d + printf '%s\n' \ + 'auth required pam_permit.so' \ + 'account required pam_permit.so' \ + 'session required pam_permit.so' \ + 'password required pam_permit.so' \ + > etc/pam.d/sshd + + # --- /etc/profile: base-files ships profile.d/ but not /etc/profile, so + # login shells wouldn't source the bread-warning banner. add a minimal one. + cat > etc/profile <<'EOF' + if [ -d /etc/profile.d ]; then + for __pf in /etc/profile.d/*.sh; do + [ -r "$__pf" ] && . "$__pf" + done + unset __pf + fi + EOF + + # --- bread bits: sshd entrypoint, lazy-apt shim, insecure-image banner. + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/sshd-entry.sh" usr/local/bin/sshd-entry.sh + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt-get + install -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/banner.txt" etc/bread-banner.txt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/bread-warning.sh" etc/profile.d/00-bread-warning.sh + mkdir -p root + printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ + >> root/.bashrc diff --git a/rocks/bread-25.10/hack/banner.txt b/rocks/bread-25.10/hack/banner.txt new file mode 100644 index 0000000..3c06b8b --- /dev/null +++ b/rocks/bread-25.10/hack/banner.txt @@ -0,0 +1,13 @@ + ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ + ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ +███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ +███▒▒░░░░░░░ ░░▒▒▓▓▓▓ +███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ + ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ + ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ + ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ + ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ + ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ + ███▒▒░░░░░░░ ░░░░░░░▓▓ + █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-25.10/hack/bread-warning.sh b/rocks/bread-25.10/hack/bread-warning.sh new file mode 100755 index 0000000..2ee0140 --- /dev/null +++ b/rocks/bread-25.10/hack/bread-warning.sh @@ -0,0 +1,39 @@ +# bread-warning: insecure-image notice. +# +# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive +# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same +# session. Silent on non-tty (spread, scripts, ci logs) unless +# BREAD_BANNER_FORCE is set. +# +# Banner content is verbatim from /etc/bread-banner.txt (shipped from +# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped +# in red ANSI; everything else is printed as-is. +# +# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no +# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER +# to stderr on every connection, which would inject ~14 lines of noise into +# every non-interactive `ssh root@host cmd` that spread runs. This shell +# hook covers every interactive path a human actually hits (ssh login shell +# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing +# worth the noise. + +[ -n "$BREAD_BANNER_SHOWN" ] && return 0 +if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then + export BREAD_BANNER_SHOWN=1 + return 0 +fi +export BREAD_BANNER_SHOWN=1 + +__bw_banner=/etc/bread-banner.txt +[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } + +if [ -t 1 ] && [ -z "$NO_COLOR" ]; then + __bw_r=$(printf '\033[1;31m') + __bw_n=$(printf '\033[0m') + sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" + unset __bw_r __bw_n +else + cat "$__bw_banner" +fi + +unset __bw_banner diff --git a/rocks/bread-25.10/hack/hash_inputs.sh b/rocks/bread-25.10/hack/hash_inputs.sh new file mode 100755 index 0000000..a6c2a51 --- /dev/null +++ b/rocks/bread-25.10/hack/hash_inputs.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Single sha256 over everything that affects this rock's build: the +# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the +# .rock.stamp short-circuit in the makefile so `make build` only repacks when a +# real input changed. +# +# Stdout: hex digest only. +set -euo pipefail + +cd "$(dirname "$0")/.." + +# shellcheck disable=SC2046 # word-splitting of find output is intended here. +sha256sum \ + rockcraft.yaml \ + $(find hack patches -type f 2>/dev/null | sort) \ + | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-25.10/hack/lazy-apt.sh b/rocks/bread-25.10/hack/lazy-apt.sh new file mode 100755 index 0000000..1038a4f --- /dev/null +++ b/rocks/bread-25.10/hack/lazy-apt.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# lazy-apt: on first non-update call, runs apt-get update then replaces both +# shims with symlinks to real apt. Installed at /usr/local/bin/apt and +# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call +# apt/apt-get without an explicit update still work after the image was built +# with apt lists wiped. +# +# Replaces with symlinks rather than deleting: bash hashes the shim path on +# first use, so deletion would leave a stale hash entry causing subsequent +# calls to fail with "No such file or directory". + +real="/usr/bin/$(basename "$0")" + +if [ "$1" != "update" ]; then + /usr/bin/apt-get update +fi + +ln -sf /usr/bin/apt /usr/local/bin/apt +ln -sf /usr/bin/apt-get /usr/local/bin/apt-get + +exec "$real" "$@" diff --git a/rocks/bread-25.10/hack/sshd-entry.sh b/rocks/bread-25.10/hack/sshd-entry.sh new file mode 100755 index 0000000..080d94c --- /dev/null +++ b/rocks/bread-25.10/hack/sshd-entry.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pebble service command for sshd in the bread rock. +# +# host keys are generated here on first boot rather than baked into the rock, +# so throwaway test containers don't all share the same keys. also ensures the +# privsep runtime dir exists. then execs sshd in the foreground (pebble manages +# the process). +set -e + +mkdir -p /run/sshd +ssh-keygen -A + +exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-25.10/makefile b/rocks/bread-25.10/makefile new file mode 100644 index 0000000..8b9b97d --- /dev/null +++ b/rocks/bread-25.10/makefile @@ -0,0 +1,45 @@ +.DEFAULT_GOAL := help + +DOCKER ?= docker + +# Predictable artefact name (matches the dir + the spread-rock- load tag). +ROCK := bread-25.10.rock + +.PHONY: help +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: build +build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) + +.PHONY: FORCE +FORCE: + +# Stamp content = hash of all build inputs; only rewritten when it changes, so +# the rock only repacks on a real input change. +.rock.stamp: FORCE + @new=$$(hack/hash_inputs.sh) ; \ + cur=$$(cat $@ 2>/dev/null || true) ; \ + [ "$$new" = "$$cur" ] || echo "$$new" > $@ + +$(ROCK): .rock.stamp + rockcraft pack --verbosity=brief + mv bread_25.10_*.rock $(ROCK) + +.PHONY: size +size: ## Show the packed rock size + @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi + +.PHONY: ls-rock +ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) + @tag=bread-ls-$$$$ ; \ + skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ + cid=$$($(DOCKER) create $$tag:latest) ; \ + $(DOCKER) export $$cid | tar tv ; \ + $(DOCKER) rm $$cid > /dev/null ; \ + $(DOCKER) rmi $$tag:latest > /dev/null + +.PHONY: clean +clean: ## Remove the packed rock + stamp + rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-25.10/rockcraft.yaml b/rocks/bread-25.10/rockcraft.yaml new file mode 100644 index 0000000..750f21e --- /dev/null +++ b/rocks/bread-25.10/rockcraft.yaml @@ -0,0 +1,127 @@ +# spellchecker: ignore rockcraft chiselled coreutils passwd nologin sshd usrmerge pebble noninteractive + +name: bread +version: "25.10" +summary: chiselled ubuntu 25.10 + sshd spread test-host rock +description: | + Throwaway spread test-host: a chiselled (base: bare) Ubuntu 25.10 rock with + just enough to act as a spread system -- bash, coreutils, openssh-server, + apt, and a pam_permit-only auth stack (accepts ANY password for root). + + INSECURE BY DESIGN. Built for ephemeral spread test containers only; never + expose to a network or use in production. + +base: bare +build-base: ubuntu@25.10 + +platforms: + amd64: + arm64: + +# sshd runs as a pebble service (pebble is pid 1 in the rock). The +# openssh-server_services slice ships systemd units, not a pebble layer, so we +# drive sshd ourselves via hack/sshd-entry.sh (host-key gen + exec sshd -D). +services: + sshd: + override: replace + command: /usr/local/bin/sshd-entry.sh + startup: enabled + +parts: + bread: + plugin: nil + stage-packages: + # fs skeleton + os-release (version contract check) + chisel manifest + - base-files_base + - base-files_etc + - base-files_var + - base-files_tmp + - base-files_lib + - base-files_home + - base-files_release-info + - base-files_chisel + # shell + core utils. tar + gzip are needed by spread to sync the + # project onto the system over ssh (not part of coreutils). + - bash_bins + - coreutils_bins + - tar_bins + - gzip_bins + # users / login (base-passwd_data writes /etc/passwd + /etc/group w/ root) + - base-passwd_data + - login_bins + - passwd_bins + # ssh (openssh-server_bins pulls openssh-client_bins -> ssh-keygen, and + # libpam-modules_libs -> pam_permit.so, as essentials) + - openssh-server_bins + - openssh-server_config + - ca-certificates_data + # pam: only pam_permit.so (always-allow). NOT libpam-modules_config / + # _pam-config -- we don't want the real pam_unix default stacks. + - libpam-modules_libs + # apt. apt_apt-get pulls dpkg_maintscript-support but NOT /usr/bin/dpkg, + # so apt-get install cannot unpack without dpkg_bins -- add it so the + # lazy-apt shim (and test suites that apt-get install) actually work. + - apt_apt-get + - apt_config + - apt_var + - apt_default-source-list + - dpkg_bins + override-build: | + set -eu + craftctl default + cd "$CRAFT_PART_INSTALL" + + # --- users: base-passwd_data gives us /etc/passwd + /etc/group (incl + # root), but not the sshd privsep user nor any /etc/shadow. add both. + grep -q '^sshd:' etc/passwd || \ + echo 'sshd:x:101:65534::/run/sshd:/usr/sbin/nologin' >> etc/passwd + # root password "bread" (matches the docker-image flavour). pam_permit + # makes the value irrelevant for auth, but root must not be locked. + hash=$(openssl passwd -6 bread) + { + printf 'root:%s:20000:0:99999:7:::\n' "$hash" + printf 'sshd:!:20000:0:99999:7:::\n' + } > etc/shadow + chmod 0640 etc/shadow + + # --- sshd config. openssh-server_config ships sshd_config.d/ but not + # /etc/ssh/sshd_config itself, so write a minimal one. + mkdir -p etc/ssh + cat > etc/ssh/sshd_config <<'EOF' + PermitRootLogin yes + PasswordAuthentication yes + UsePAM yes + Subsystem sftp /usr/lib/openssh/sftp-server + PidFile /run/sshd.pid + EOF + + # --- pam: permit-only stack (always-allow), verbatim shape from the + # docker-image flavour. + mkdir -p etc/pam.d + printf '%s\n' \ + 'auth required pam_permit.so' \ + 'account required pam_permit.so' \ + 'session required pam_permit.so' \ + 'password required pam_permit.so' \ + > etc/pam.d/sshd + + # --- /etc/profile: base-files ships profile.d/ but not /etc/profile, so + # login shells wouldn't source the bread-warning banner. add a minimal one. + cat > etc/profile <<'EOF' + if [ -d /etc/profile.d ]; then + for __pf in /etc/profile.d/*.sh; do + [ -r "$__pf" ] && . "$__pf" + done + unset __pf + fi + EOF + + # --- bread bits: sshd entrypoint, lazy-apt shim, insecure-image banner. + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/sshd-entry.sh" usr/local/bin/sshd-entry.sh + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt-get + install -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/banner.txt" etc/bread-banner.txt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/bread-warning.sh" etc/profile.d/00-bread-warning.sh + mkdir -p root + printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ + >> root/.bashrc diff --git a/rocks/bread-26.04/hack/banner.txt b/rocks/bread-26.04/hack/banner.txt new file mode 100644 index 0000000..3c06b8b --- /dev/null +++ b/rocks/bread-26.04/hack/banner.txt @@ -0,0 +1,13 @@ + ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ + ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ +███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ +███▒▒░░░░░░░ ░░▒▒▓▓▓▓ +███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ + ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ + ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ + ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ + ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ + ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ + ███▒▒░░░░░░░ ░░░░░░░▓▓ + █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-26.04/hack/bread-warning.sh b/rocks/bread-26.04/hack/bread-warning.sh new file mode 100755 index 0000000..2ee0140 --- /dev/null +++ b/rocks/bread-26.04/hack/bread-warning.sh @@ -0,0 +1,39 @@ +# bread-warning: insecure-image notice. +# +# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive +# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same +# session. Silent on non-tty (spread, scripts, ci logs) unless +# BREAD_BANNER_FORCE is set. +# +# Banner content is verbatim from /etc/bread-banner.txt (shipped from +# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped +# in red ANSI; everything else is printed as-is. +# +# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no +# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER +# to stderr on every connection, which would inject ~14 lines of noise into +# every non-interactive `ssh root@host cmd` that spread runs. This shell +# hook covers every interactive path a human actually hits (ssh login shell +# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing +# worth the noise. + +[ -n "$BREAD_BANNER_SHOWN" ] && return 0 +if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then + export BREAD_BANNER_SHOWN=1 + return 0 +fi +export BREAD_BANNER_SHOWN=1 + +__bw_banner=/etc/bread-banner.txt +[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } + +if [ -t 1 ] && [ -z "$NO_COLOR" ]; then + __bw_r=$(printf '\033[1;31m') + __bw_n=$(printf '\033[0m') + sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" + unset __bw_r __bw_n +else + cat "$__bw_banner" +fi + +unset __bw_banner diff --git a/rocks/bread-26.04/hack/hash_inputs.sh b/rocks/bread-26.04/hack/hash_inputs.sh new file mode 100755 index 0000000..a6c2a51 --- /dev/null +++ b/rocks/bread-26.04/hack/hash_inputs.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Single sha256 over everything that affects this rock's build: the +# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the +# .rock.stamp short-circuit in the makefile so `make build` only repacks when a +# real input changed. +# +# Stdout: hex digest only. +set -euo pipefail + +cd "$(dirname "$0")/.." + +# shellcheck disable=SC2046 # word-splitting of find output is intended here. +sha256sum \ + rockcraft.yaml \ + $(find hack patches -type f 2>/dev/null | sort) \ + | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-26.04/hack/lazy-apt.sh b/rocks/bread-26.04/hack/lazy-apt.sh new file mode 100755 index 0000000..1038a4f --- /dev/null +++ b/rocks/bread-26.04/hack/lazy-apt.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# lazy-apt: on first non-update call, runs apt-get update then replaces both +# shims with symlinks to real apt. Installed at /usr/local/bin/apt and +# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call +# apt/apt-get without an explicit update still work after the image was built +# with apt lists wiped. +# +# Replaces with symlinks rather than deleting: bash hashes the shim path on +# first use, so deletion would leave a stale hash entry causing subsequent +# calls to fail with "No such file or directory". + +real="/usr/bin/$(basename "$0")" + +if [ "$1" != "update" ]; then + /usr/bin/apt-get update +fi + +ln -sf /usr/bin/apt /usr/local/bin/apt +ln -sf /usr/bin/apt-get /usr/local/bin/apt-get + +exec "$real" "$@" diff --git a/rocks/bread-26.04/hack/sshd-entry.sh b/rocks/bread-26.04/hack/sshd-entry.sh new file mode 100755 index 0000000..080d94c --- /dev/null +++ b/rocks/bread-26.04/hack/sshd-entry.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pebble service command for sshd in the bread rock. +# +# host keys are generated here on first boot rather than baked into the rock, +# so throwaway test containers don't all share the same keys. also ensures the +# privsep runtime dir exists. then execs sshd in the foreground (pebble manages +# the process). +set -e + +mkdir -p /run/sshd +ssh-keygen -A + +exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-26.04/makefile b/rocks/bread-26.04/makefile new file mode 100644 index 0000000..4e0be0e --- /dev/null +++ b/rocks/bread-26.04/makefile @@ -0,0 +1,45 @@ +.DEFAULT_GOAL := help + +DOCKER ?= docker + +# Predictable artefact name (matches the dir + the spread-rock- load tag). +ROCK := bread-26.04.rock + +.PHONY: help +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: build +build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) + +.PHONY: FORCE +FORCE: + +# Stamp content = hash of all build inputs; only rewritten when it changes, so +# the rock only repacks on a real input change. +.rock.stamp: FORCE + @new=$$(hack/hash_inputs.sh) ; \ + cur=$$(cat $@ 2>/dev/null || true) ; \ + [ "$$new" = "$$cur" ] || echo "$$new" > $@ + +$(ROCK): .rock.stamp + rockcraft pack --verbosity=brief + mv bread_26.04_*.rock $(ROCK) + +.PHONY: size +size: ## Show the packed rock size + @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi + +.PHONY: ls-rock +ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) + @tag=bread-ls-$$$$ ; \ + skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ + cid=$$($(DOCKER) create $$tag:latest) ; \ + $(DOCKER) export $$cid | tar tv ; \ + $(DOCKER) rm $$cid > /dev/null ; \ + $(DOCKER) rmi $$tag:latest > /dev/null + +.PHONY: clean +clean: ## Remove the packed rock + stamp + rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-26.04/rockcraft.yaml b/rocks/bread-26.04/rockcraft.yaml new file mode 100644 index 0000000..3395964 --- /dev/null +++ b/rocks/bread-26.04/rockcraft.yaml @@ -0,0 +1,127 @@ +# spellchecker: ignore rockcraft chiselled coreutils passwd nologin sshd usrmerge pebble noninteractive + +name: bread +version: "26.04" +summary: chiselled ubuntu 26.04 + sshd spread test-host rock +description: | + Throwaway spread test-host: a chiselled (base: bare) Ubuntu 26.04 rock with + just enough to act as a spread system -- bash, coreutils, openssh-server, + apt, and a pam_permit-only auth stack (accepts ANY password for root). + + INSECURE BY DESIGN. Built for ephemeral spread test containers only; never + expose to a network or use in production. + +base: bare +build-base: ubuntu@26.04 + +platforms: + amd64: + arm64: + +# sshd runs as a pebble service (pebble is pid 1 in the rock). The +# openssh-server_services slice ships systemd units, not a pebble layer, so we +# drive sshd ourselves via hack/sshd-entry.sh (host-key gen + exec sshd -D). +services: + sshd: + override: replace + command: /usr/local/bin/sshd-entry.sh + startup: enabled + +parts: + bread: + plugin: nil + stage-packages: + # fs skeleton + os-release (version contract check) + chisel manifest + - base-files_base + - base-files_etc + - base-files_var + - base-files_tmp + - base-files_lib + - base-files_home + - base-files_release-info + - base-files_chisel + # shell + core utils. tar + gzip are needed by spread to sync the + # project onto the system over ssh (not part of coreutils). + - bash_bins + - coreutils_bins + - tar_bins + - gzip_bins + # users / login (base-passwd_data writes /etc/passwd + /etc/group w/ root) + - base-passwd_data + - login_bins + - passwd_bins + # ssh (openssh-server_bins pulls openssh-client_bins -> ssh-keygen, and + # libpam-modules_libs -> pam_permit.so, as essentials) + - openssh-server_bins + - openssh-server_config + - ca-certificates_data + # pam: only pam_permit.so (always-allow). NOT libpam-modules_config / + # _pam-config -- we don't want the real pam_unix default stacks. + - libpam-modules_libs + # apt. apt_apt-get pulls dpkg_maintscript-support but NOT /usr/bin/dpkg, + # so apt-get install cannot unpack without dpkg_bins -- add it so the + # lazy-apt shim (and test suites that apt-get install) actually work. + - apt_apt-get + - apt_config + - apt_var + - apt_default-source-list + - dpkg_bins + override-build: | + set -eu + craftctl default + cd "$CRAFT_PART_INSTALL" + + # --- users: base-passwd_data gives us /etc/passwd + /etc/group (incl + # root), but not the sshd privsep user nor any /etc/shadow. add both. + grep -q '^sshd:' etc/passwd || \ + echo 'sshd:x:101:65534::/run/sshd:/usr/sbin/nologin' >> etc/passwd + # root password "bread" (matches the docker-image flavour). pam_permit + # makes the value irrelevant for auth, but root must not be locked. + hash=$(openssl passwd -6 bread) + { + printf 'root:%s:20000:0:99999:7:::\n' "$hash" + printf 'sshd:!:20000:0:99999:7:::\n' + } > etc/shadow + chmod 0640 etc/shadow + + # --- sshd config. openssh-server_config ships sshd_config.d/ but not + # /etc/ssh/sshd_config itself, so write a minimal one. + mkdir -p etc/ssh + cat > etc/ssh/sshd_config <<'EOF' + PermitRootLogin yes + PasswordAuthentication yes + UsePAM yes + Subsystem sftp /usr/lib/openssh/sftp-server + PidFile /run/sshd.pid + EOF + + # --- pam: permit-only stack (always-allow), verbatim shape from the + # docker-image flavour. + mkdir -p etc/pam.d + printf '%s\n' \ + 'auth required pam_permit.so' \ + 'account required pam_permit.so' \ + 'session required pam_permit.so' \ + 'password required pam_permit.so' \ + > etc/pam.d/sshd + + # --- /etc/profile: base-files ships profile.d/ but not /etc/profile, so + # login shells wouldn't source the bread-warning banner. add a minimal one. + cat > etc/profile <<'EOF' + if [ -d /etc/profile.d ]; then + for __pf in /etc/profile.d/*.sh; do + [ -r "$__pf" ] && . "$__pf" + done + unset __pf + fi + EOF + + # --- bread bits: sshd entrypoint, lazy-apt shim, insecure-image banner. + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/sshd-entry.sh" usr/local/bin/sshd-entry.sh + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt-get + install -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/banner.txt" etc/bread-banner.txt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/bread-warning.sh" etc/profile.d/00-bread-warning.sh + mkdir -p root + printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ + >> root/.bashrc diff --git a/rocks/bread-chisel-releases-24.04/hack/banner.txt b/rocks/bread-chisel-releases-24.04/hack/banner.txt new file mode 100644 index 0000000..3c06b8b --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/hack/banner.txt @@ -0,0 +1,13 @@ + ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ + ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ +███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ +███▒▒░░░░░░░ ░░▒▒▓▓▓▓ +███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ + ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ + ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ + ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ + ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ + ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ + ███▒▒░░░░░░░ ░░░░░░░▓▓ + █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-chisel-releases-24.04/hack/bread-warning.sh b/rocks/bread-chisel-releases-24.04/hack/bread-warning.sh new file mode 100755 index 0000000..2ee0140 --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/hack/bread-warning.sh @@ -0,0 +1,39 @@ +# bread-warning: insecure-image notice. +# +# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive +# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same +# session. Silent on non-tty (spread, scripts, ci logs) unless +# BREAD_BANNER_FORCE is set. +# +# Banner content is verbatim from /etc/bread-banner.txt (shipped from +# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped +# in red ANSI; everything else is printed as-is. +# +# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no +# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER +# to stderr on every connection, which would inject ~14 lines of noise into +# every non-interactive `ssh root@host cmd` that spread runs. This shell +# hook covers every interactive path a human actually hits (ssh login shell +# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing +# worth the noise. + +[ -n "$BREAD_BANNER_SHOWN" ] && return 0 +if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then + export BREAD_BANNER_SHOWN=1 + return 0 +fi +export BREAD_BANNER_SHOWN=1 + +__bw_banner=/etc/bread-banner.txt +[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } + +if [ -t 1 ] && [ -z "$NO_COLOR" ]; then + __bw_r=$(printf '\033[1;31m') + __bw_n=$(printf '\033[0m') + sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" + unset __bw_r __bw_n +else + cat "$__bw_banner" +fi + +unset __bw_banner diff --git a/rocks/bread-chisel-releases-24.04/hack/chisel_override_build.sh b/rocks/bread-chisel-releases-24.04/hack/chisel_override_build.sh new file mode 100755 index 0000000..d8432fb --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/hack/chisel_override_build.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Build chisel + chisel-hacked from source (canonical/chisel @ $CHISEL_REF), +# native arch, into $CRAFT_PART_INSTALL/usr/local/bin. Mirrors the root repo's +# hack/build_binaries.sh, minus the cross-compile -- rockcraft builds natively +# per platform so GOARCH is just the host arch. +# +# Env (from the part's build-environment): +# CHISEL_REF git ref for canonical/chisel +set -euo pipefail + +: "${CHISEL_REF:?required}" + +# go.mod may demand a newer toolchain than the snap ships; let go fetch it. +export GOTOOLCHAIN=auto + +out="$CRAFT_PART_INSTALL/usr/local/bin" +mkdir -p "$out" + +src="$CRAFT_PART_BUILD/chisel-src" +git clone https://github.com/canonical/chisel "$src" +cd "$src" +git checkout "$CHISEL_REF" + +# Inject the version string the way upstream's cmd/mkversion.sh would, so +# `chisel --version` reports the pinned tag instead of "unknown". +ver=$(git describe --tags --dirty --always) +echo "==> chisel version: $ver" + +CGO_ENABLED=0 go build -trimpath \ + -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=$ver" \ + -o "$out/chisel" ./cmd/chisel + +# chisel-hacked: same source + the CHISEL_HACKS patches, -hacked version suffix. +for p in "$CRAFT_PROJECT_DIR"/patches/chisel/0001-*.patch \ + "$CRAFT_PROJECT_DIR"/patches/chisel/0002-*.patch \ + "$CRAFT_PROJECT_DIR"/patches/chisel/0003-*.patch; do + echo "==> applying $(basename "$p")" + git apply "$p" +done + +CGO_ENABLED=0 go build -trimpath \ + -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=${ver}-hacked" \ + -o "$out/chisel-hacked" ./cmd/chisel diff --git a/rocks/bread-chisel-releases-24.04/hack/docker_override_build.sh b/rocks/bread-chisel-releases-24.04/hack/docker_override_build.sh new file mode 100755 index 0000000..5e40e1f --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/hack/docker_override_build.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Fetch the upstream docker static CLI for the build-for arch and drop it at +# /usr/local/bin/docker. Upstream's static build uses a recent go toolchain and +# so dodges the qemu emulation bug that hits ubuntu apt's docker.io (go 1.24). +# +# Env (from the part's build-environment): +# DOCKER_VERSION docker static release version +set -euo pipefail + +: "${DOCKER_VERSION:?required}" + +# docker.com static uses uname-style arch names. +case "$CRAFT_ARCH_BUILD_FOR" in + amd64) docker_arch=x86_64 ;; + arm64) docker_arch=aarch64 ;; + *) echo "unsupported arch: $CRAFT_ARCH_BUILD_FOR" >&2; exit 1 ;; +esac + +out="$CRAFT_PART_INSTALL/usr/local/bin" +mkdir -p "$out" + +url="https://download.docker.com/linux/static/stable/${docker_arch}/docker-${DOCKER_VERSION}.tgz" +echo "==> downloading docker $DOCKER_VERSION for $CRAFT_ARCH_BUILD_FOR from $url" + +tmp=$(mktemp -d) +curl -fsSL "$url" | tar -xz -C "$tmp" docker/docker +install -m0755 "$tmp/docker/docker" "$out/docker" +rm -rf "$tmp" diff --git a/rocks/bread-chisel-releases-24.04/hack/hash_inputs.sh b/rocks/bread-chisel-releases-24.04/hack/hash_inputs.sh new file mode 100755 index 0000000..a6c2a51 --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/hack/hash_inputs.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Single sha256 over everything that affects this rock's build: the +# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the +# .rock.stamp short-circuit in the makefile so `make build` only repacks when a +# real input changed. +# +# Stdout: hex digest only. +set -euo pipefail + +cd "$(dirname "$0")/.." + +# shellcheck disable=SC2046 # word-splitting of find output is intended here. +sha256sum \ + rockcraft.yaml \ + $(find hack patches -type f 2>/dev/null | sort) \ + | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-chisel-releases-24.04/hack/lazy-apt.sh b/rocks/bread-chisel-releases-24.04/hack/lazy-apt.sh new file mode 100755 index 0000000..1038a4f --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/hack/lazy-apt.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# lazy-apt: on first non-update call, runs apt-get update then replaces both +# shims with symlinks to real apt. Installed at /usr/local/bin/apt and +# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call +# apt/apt-get without an explicit update still work after the image was built +# with apt lists wiped. +# +# Replaces with symlinks rather than deleting: bash hashes the shim path on +# first use, so deletion would leave a stale hash entry causing subsequent +# calls to fail with "No such file or directory". + +real="/usr/bin/$(basename "$0")" + +if [ "$1" != "update" ]; then + /usr/bin/apt-get update +fi + +ln -sf /usr/bin/apt /usr/local/bin/apt +ln -sf /usr/bin/apt-get /usr/local/bin/apt-get + +exec "$real" "$@" diff --git a/rocks/bread-chisel-releases-24.04/hack/sshd-entry.sh b/rocks/bread-chisel-releases-24.04/hack/sshd-entry.sh new file mode 100755 index 0000000..080d94c --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/hack/sshd-entry.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pebble service command for sshd in the bread rock. +# +# host keys are generated here on first boot rather than baked into the rock, +# so throwaway test containers don't all share the same keys. also ensures the +# privsep runtime dir exists. then execs sshd in the foreground (pebble manages +# the process). +set -e + +mkdir -p /run/sshd +ssh-keygen -A + +exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-chisel-releases-24.04/makefile b/rocks/bread-chisel-releases-24.04/makefile new file mode 100644 index 0000000..1dbd78c --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/makefile @@ -0,0 +1,43 @@ +.DEFAULT_GOAL := help + +DOCKER ?= docker + +# Predictable artefact name (matches the dir + the spread-rock- load tag). +ROCK := bread-chisel-releases-24.04.rock + +.PHONY: help +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: build +build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) + +.PHONY: FORCE +FORCE: + +.rock.stamp: FORCE + @new=$$(hack/hash_inputs.sh) ; \ + cur=$$(cat $@ 2>/dev/null || true) ; \ + [ "$$new" = "$$cur" ] || echo "$$new" > $@ + +$(ROCK): .rock.stamp + rockcraft pack --verbosity=brief + mv bread-chisel-releases_24.04_*.rock $(ROCK) + +.PHONY: size +size: ## Show the packed rock size + @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi + +.PHONY: ls-rock +ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) + @tag=bread-cr-ls-$$$$ ; \ + skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ + cid=$$($(DOCKER) create $$tag:latest) ; \ + $(DOCKER) export $$cid | tar tv ; \ + $(DOCKER) rm $$cid > /dev/null ; \ + $(DOCKER) rmi $$tag:latest > /dev/null + +.PHONY: clean +clean: ## Remove the packed rock + stamp + rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-chisel-releases-24.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-24.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch new file mode 100644 index 0000000..d3c0637 --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch @@ -0,0 +1,49 @@ +From 38a1af51d71f7904693a88db64df808afed80827 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:10:52 +0100 +Subject: [PATCH 1/3] chore: skip maintenance check via CHISEL_HACKS + +--- + cmd/chisel/cmd_cut.go | 19 ++++++++++++------- + 1 file changed, 12 insertions(+), 7 deletions(-) + +diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go +index 35c81a7..98c1a36 100644 +--- a/cmd/chisel/cmd_cut.go ++++ b/cmd/chisel/cmd_cut.go +@@ -2,6 +2,7 @@ package main + + import ( + "fmt" ++ "os" + "slices" + "time" + +@@ -63,13 +64,17 @@ func (cmd *cmdCut) Execute(args []string) error { + return err + } + +- if time.Now().Before(release.Maintenance.Standard) { +- if slices.Contains(cmd.Ignore, "unstable") { +- logf(`Warning: This release is in the "unstable" maintenance status. ` + +- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) +- } else { +- return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + +- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping maintenance check due to CHISEL_HACKS=1") ++ } else { ++ if time.Now().Before(release.Maintenance.Standard) { ++ if slices.Contains(cmd.Ignore, "unstable") { ++ logf(`Warning: This release is in the "unstable" maintenance status. ` + ++ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) ++ } else { ++ return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + ++ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ } + } + } + +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-24.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch b/rocks/bread-chisel-releases-24.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch new file mode 100644 index 0000000..851b6a5 --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch @@ -0,0 +1,58 @@ +From 76d4ec4dfb51fc7d4d625c5ee6021d9452e6b263 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:11:06 +0100 +Subject: [PATCH 2/3] chore: skip archive maintenance check via CHISEL_HACKS + +--- + cmd/chisel/cmd_cut.go | 34 +++++++++++++++++++--------------- + 1 file changed, 19 insertions(+), 15 deletions(-) + +diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go +index 98c1a36..b4381d4 100644 +--- a/cmd/chisel/cmd_cut.go ++++ b/cmd/chisel/cmd_cut.go +@@ -107,22 +107,26 @@ func (cmd *cmdCut) Execute(args []string) error { + archives[archiveName] = openArchive + } + +- hasMaintainedArchive := false +- for _, archive := range archives { +- if archive.Options().Maintained { +- hasMaintainedArchive = true +- break ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping archive maintenance check due to CHISEL_HACKS=1") ++ } else { ++ hasMaintainedArchive := false ++ for _, archive := range archives { ++ if archive.Options().Maintained { ++ hasMaintainedArchive = true ++ break ++ } + } +- } +- if !hasMaintainedArchive { +- if slices.Contains(cmd.Ignore, "unmaintained") { +- logf(`Warning: No archive has "maintained" maintenance status. ` + +- `Consider the different Ubuntu Pro subscriptions to be safe. ` + +- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) +- } else { +- return fmt.Errorf(`no archive has "maintained" maintenance status, ` + +- `consider the different Ubuntu Pro subscriptions to be safe, ` + +- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ if !hasMaintainedArchive { ++ if slices.Contains(cmd.Ignore, "unmaintained") { ++ logf(`Warning: No archive has "maintained" maintenance status. ` + ++ `Consider the different Ubuntu Pro subscriptions to be safe. ` + ++ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) ++ } else { ++ return fmt.Errorf(`no archive has "maintained" maintenance status, ` + ++ `consider the different Ubuntu Pro subscriptions to be safe, ` + ++ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ } + } + } + +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-24.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-24.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch new file mode 100644 index 0000000..a6774d0 --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch @@ -0,0 +1,27 @@ +From 7af23d09332e1fa104ad5c802e176e90f86d67d3 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:11:28 +0100 +Subject: [PATCH 3/3] chore: skip release validation via CHISEL_HACKS + +--- + internal/setup/setup.go | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/internal/setup/setup.go b/internal/setup/setup.go +index 6ed24ab..01ea8eb 100644 +--- a/internal/setup/setup.go ++++ b/internal/setup/setup.go +@@ -194,6 +194,10 @@ func ReadRelease(dir string) (*Release, error) { + return nil, err + } + ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping release validation due to CHISEL_HACKS=1") ++ return release, nil ++ } + err = release.validate() + if err != nil { + return nil, err +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-24.04/rockcraft.yaml b/rocks/bread-chisel-releases-24.04/rockcraft.yaml new file mode 100644 index 0000000..74f8913 --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/rockcraft.yaml @@ -0,0 +1,141 @@ +# spellchecker: ignore rockcraft chiselled coreutils passwd nologin sshd pebble chisel CHISEL GOTOOLCHAIN noninteractive + +name: bread-chisel-releases +version: "24.04" +summary: chiselled ubuntu 24.04 + sshd + chisel + tooling spread test-host rock +description: | + Throwaway spread test-host for chisel-releases spread tests: a chiselled + (base: bare) Ubuntu 24.04 rock with bash, coreutils, openssh-server, apt, a + pam_permit-only auth stack, plus chisel (+ chisel-hacked), docker, and the + tooling chisel-releases tests typically need (curl, wget, git, jq, file, + sudo, ip). + + INSECURE BY DESIGN. Built for ephemeral spread test containers only; never + expose to a network or use in production. + +base: bare +build-base: ubuntu@24.04 + +platforms: + amd64: + arm64: + +services: + sshd: + override: replace + command: /usr/local/bin/sshd-entry.sh + startup: enabled + +parts: + # --- de-novo copy of the bread base (rocks can't build from rocks) ------- + bread: + plugin: nil + stage-packages: + - base-files_base + - base-files_etc + - base-files_var + - base-files_tmp + - base-files_lib + - base-files_home + - base-files_release-info + - base-files_chisel + - bash_bins + - coreutils_bins + - tar_bins + - gzip_bins + - base-passwd_data + - login_bins + - passwd_bins + - openssh-server_bins + - openssh-server_config + - ca-certificates_data + - libpam-modules_libs + - apt_apt-get + - apt_config + - apt_var + - apt_default-source-list + - dpkg_bins + override-build: | + set -eu + craftctl default + cd "$CRAFT_PART_INSTALL" + + grep -q '^sshd:' etc/passwd || \ + echo 'sshd:x:101:65534::/run/sshd:/usr/sbin/nologin' >> etc/passwd + hash=$(openssl passwd -6 bread) + { + printf 'root:%s:20000:0:99999:7:::\n' "$hash" + printf 'sshd:!:20000:0:99999:7:::\n' + } > etc/shadow + chmod 0640 etc/shadow + + mkdir -p etc/ssh + cat > etc/ssh/sshd_config <<'EOF' + PermitRootLogin yes + PasswordAuthentication yes + UsePAM yes + Subsystem sftp /usr/lib/openssh/sftp-server + PidFile /run/sshd.pid + EOF + + mkdir -p etc/pam.d + printf '%s\n' \ + 'auth required pam_permit.so' \ + 'account required pam_permit.so' \ + 'session required pam_permit.so' \ + 'password required pam_permit.so' \ + > etc/pam.d/sshd + + cat > etc/profile <<'EOF' + if [ -d /etc/profile.d ]; then + for __pf in /etc/profile.d/*.sh; do + [ -r "$__pf" ] && . "$__pf" + done + unset __pf + fi + EOF + + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/sshd-entry.sh" usr/local/bin/sshd-entry.sh + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt-get + install -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/banner.txt" etc/bread-banner.txt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/bread-warning.sh" etc/profile.d/00-bread-warning.sh + mkdir -p root + printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ + >> root/.bashrc + + # --- chisel-releases tooling (slices where they exist) ------------------- + # holes: tree + skopeo have no slice yet -> omitted for now. + tooling: + plugin: nil + stage-packages: + - curl_bins + - wget_bins + - git_bins + - git_http-support + - jq_bins + - file_bins + - sudo_bins + - sudo_config + - iproute2_ip-bin + + # --- chisel + chisel-hacked, built from source (go is build-only) -------- + chisel: + plugin: nil + build-snaps: + - go + build-packages: + - git + build-environment: + - CHISEL_REF: v1.4.1 + override-build: bash "$CRAFT_PROJECT_DIR/hack/chisel_override_build.sh" + + # --- docker static CLI --------------------------------------------------- + docker: + plugin: nil + build-packages: + - curl + - ca-certificates + build-environment: + - DOCKER_VERSION: 29.5.2 + override-build: bash "$CRAFT_PROJECT_DIR/hack/docker_override_build.sh" diff --git a/rocks/bread-chisel-releases-25.10/hack/banner.txt b/rocks/bread-chisel-releases-25.10/hack/banner.txt new file mode 100644 index 0000000..3c06b8b --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/hack/banner.txt @@ -0,0 +1,13 @@ + ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ + ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ +███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ +███▒▒░░░░░░░ ░░▒▒▓▓▓▓ +███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ + ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ + ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ + ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ + ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ + ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ + ███▒▒░░░░░░░ ░░░░░░░▓▓ + █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-chisel-releases-25.10/hack/bread-warning.sh b/rocks/bread-chisel-releases-25.10/hack/bread-warning.sh new file mode 100755 index 0000000..2ee0140 --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/hack/bread-warning.sh @@ -0,0 +1,39 @@ +# bread-warning: insecure-image notice. +# +# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive +# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same +# session. Silent on non-tty (spread, scripts, ci logs) unless +# BREAD_BANNER_FORCE is set. +# +# Banner content is verbatim from /etc/bread-banner.txt (shipped from +# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped +# in red ANSI; everything else is printed as-is. +# +# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no +# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER +# to stderr on every connection, which would inject ~14 lines of noise into +# every non-interactive `ssh root@host cmd` that spread runs. This shell +# hook covers every interactive path a human actually hits (ssh login shell +# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing +# worth the noise. + +[ -n "$BREAD_BANNER_SHOWN" ] && return 0 +if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then + export BREAD_BANNER_SHOWN=1 + return 0 +fi +export BREAD_BANNER_SHOWN=1 + +__bw_banner=/etc/bread-banner.txt +[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } + +if [ -t 1 ] && [ -z "$NO_COLOR" ]; then + __bw_r=$(printf '\033[1;31m') + __bw_n=$(printf '\033[0m') + sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" + unset __bw_r __bw_n +else + cat "$__bw_banner" +fi + +unset __bw_banner diff --git a/rocks/bread-chisel-releases-25.10/hack/chisel_override_build.sh b/rocks/bread-chisel-releases-25.10/hack/chisel_override_build.sh new file mode 100755 index 0000000..d8432fb --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/hack/chisel_override_build.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Build chisel + chisel-hacked from source (canonical/chisel @ $CHISEL_REF), +# native arch, into $CRAFT_PART_INSTALL/usr/local/bin. Mirrors the root repo's +# hack/build_binaries.sh, minus the cross-compile -- rockcraft builds natively +# per platform so GOARCH is just the host arch. +# +# Env (from the part's build-environment): +# CHISEL_REF git ref for canonical/chisel +set -euo pipefail + +: "${CHISEL_REF:?required}" + +# go.mod may demand a newer toolchain than the snap ships; let go fetch it. +export GOTOOLCHAIN=auto + +out="$CRAFT_PART_INSTALL/usr/local/bin" +mkdir -p "$out" + +src="$CRAFT_PART_BUILD/chisel-src" +git clone https://github.com/canonical/chisel "$src" +cd "$src" +git checkout "$CHISEL_REF" + +# Inject the version string the way upstream's cmd/mkversion.sh would, so +# `chisel --version` reports the pinned tag instead of "unknown". +ver=$(git describe --tags --dirty --always) +echo "==> chisel version: $ver" + +CGO_ENABLED=0 go build -trimpath \ + -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=$ver" \ + -o "$out/chisel" ./cmd/chisel + +# chisel-hacked: same source + the CHISEL_HACKS patches, -hacked version suffix. +for p in "$CRAFT_PROJECT_DIR"/patches/chisel/0001-*.patch \ + "$CRAFT_PROJECT_DIR"/patches/chisel/0002-*.patch \ + "$CRAFT_PROJECT_DIR"/patches/chisel/0003-*.patch; do + echo "==> applying $(basename "$p")" + git apply "$p" +done + +CGO_ENABLED=0 go build -trimpath \ + -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=${ver}-hacked" \ + -o "$out/chisel-hacked" ./cmd/chisel diff --git a/rocks/bread-chisel-releases-25.10/hack/docker_override_build.sh b/rocks/bread-chisel-releases-25.10/hack/docker_override_build.sh new file mode 100755 index 0000000..5e40e1f --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/hack/docker_override_build.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Fetch the upstream docker static CLI for the build-for arch and drop it at +# /usr/local/bin/docker. Upstream's static build uses a recent go toolchain and +# so dodges the qemu emulation bug that hits ubuntu apt's docker.io (go 1.24). +# +# Env (from the part's build-environment): +# DOCKER_VERSION docker static release version +set -euo pipefail + +: "${DOCKER_VERSION:?required}" + +# docker.com static uses uname-style arch names. +case "$CRAFT_ARCH_BUILD_FOR" in + amd64) docker_arch=x86_64 ;; + arm64) docker_arch=aarch64 ;; + *) echo "unsupported arch: $CRAFT_ARCH_BUILD_FOR" >&2; exit 1 ;; +esac + +out="$CRAFT_PART_INSTALL/usr/local/bin" +mkdir -p "$out" + +url="https://download.docker.com/linux/static/stable/${docker_arch}/docker-${DOCKER_VERSION}.tgz" +echo "==> downloading docker $DOCKER_VERSION for $CRAFT_ARCH_BUILD_FOR from $url" + +tmp=$(mktemp -d) +curl -fsSL "$url" | tar -xz -C "$tmp" docker/docker +install -m0755 "$tmp/docker/docker" "$out/docker" +rm -rf "$tmp" diff --git a/rocks/bread-chisel-releases-25.10/hack/hash_inputs.sh b/rocks/bread-chisel-releases-25.10/hack/hash_inputs.sh new file mode 100755 index 0000000..a6c2a51 --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/hack/hash_inputs.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Single sha256 over everything that affects this rock's build: the +# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the +# .rock.stamp short-circuit in the makefile so `make build` only repacks when a +# real input changed. +# +# Stdout: hex digest only. +set -euo pipefail + +cd "$(dirname "$0")/.." + +# shellcheck disable=SC2046 # word-splitting of find output is intended here. +sha256sum \ + rockcraft.yaml \ + $(find hack patches -type f 2>/dev/null | sort) \ + | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-chisel-releases-25.10/hack/lazy-apt.sh b/rocks/bread-chisel-releases-25.10/hack/lazy-apt.sh new file mode 100755 index 0000000..1038a4f --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/hack/lazy-apt.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# lazy-apt: on first non-update call, runs apt-get update then replaces both +# shims with symlinks to real apt. Installed at /usr/local/bin/apt and +# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call +# apt/apt-get without an explicit update still work after the image was built +# with apt lists wiped. +# +# Replaces with symlinks rather than deleting: bash hashes the shim path on +# first use, so deletion would leave a stale hash entry causing subsequent +# calls to fail with "No such file or directory". + +real="/usr/bin/$(basename "$0")" + +if [ "$1" != "update" ]; then + /usr/bin/apt-get update +fi + +ln -sf /usr/bin/apt /usr/local/bin/apt +ln -sf /usr/bin/apt-get /usr/local/bin/apt-get + +exec "$real" "$@" diff --git a/rocks/bread-chisel-releases-25.10/hack/sshd-entry.sh b/rocks/bread-chisel-releases-25.10/hack/sshd-entry.sh new file mode 100755 index 0000000..080d94c --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/hack/sshd-entry.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pebble service command for sshd in the bread rock. +# +# host keys are generated here on first boot rather than baked into the rock, +# so throwaway test containers don't all share the same keys. also ensures the +# privsep runtime dir exists. then execs sshd in the foreground (pebble manages +# the process). +set -e + +mkdir -p /run/sshd +ssh-keygen -A + +exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-chisel-releases-25.10/makefile b/rocks/bread-chisel-releases-25.10/makefile new file mode 100644 index 0000000..aee2477 --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/makefile @@ -0,0 +1,43 @@ +.DEFAULT_GOAL := help + +DOCKER ?= docker + +# Predictable artefact name (matches the dir + the spread-rock- load tag). +ROCK := bread-chisel-releases-25.10.rock + +.PHONY: help +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: build +build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) + +.PHONY: FORCE +FORCE: + +.rock.stamp: FORCE + @new=$$(hack/hash_inputs.sh) ; \ + cur=$$(cat $@ 2>/dev/null || true) ; \ + [ "$$new" = "$$cur" ] || echo "$$new" > $@ + +$(ROCK): .rock.stamp + rockcraft pack --verbosity=brief + mv bread-chisel-releases_25.10_*.rock $(ROCK) + +.PHONY: size +size: ## Show the packed rock size + @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi + +.PHONY: ls-rock +ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) + @tag=bread-cr-ls-$$$$ ; \ + skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ + cid=$$($(DOCKER) create $$tag:latest) ; \ + $(DOCKER) export $$cid | tar tv ; \ + $(DOCKER) rm $$cid > /dev/null ; \ + $(DOCKER) rmi $$tag:latest > /dev/null + +.PHONY: clean +clean: ## Remove the packed rock + stamp + rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-chisel-releases-25.10/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-25.10/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch new file mode 100644 index 0000000..d3c0637 --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch @@ -0,0 +1,49 @@ +From 38a1af51d71f7904693a88db64df808afed80827 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:10:52 +0100 +Subject: [PATCH 1/3] chore: skip maintenance check via CHISEL_HACKS + +--- + cmd/chisel/cmd_cut.go | 19 ++++++++++++------- + 1 file changed, 12 insertions(+), 7 deletions(-) + +diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go +index 35c81a7..98c1a36 100644 +--- a/cmd/chisel/cmd_cut.go ++++ b/cmd/chisel/cmd_cut.go +@@ -2,6 +2,7 @@ package main + + import ( + "fmt" ++ "os" + "slices" + "time" + +@@ -63,13 +64,17 @@ func (cmd *cmdCut) Execute(args []string) error { + return err + } + +- if time.Now().Before(release.Maintenance.Standard) { +- if slices.Contains(cmd.Ignore, "unstable") { +- logf(`Warning: This release is in the "unstable" maintenance status. ` + +- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) +- } else { +- return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + +- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping maintenance check due to CHISEL_HACKS=1") ++ } else { ++ if time.Now().Before(release.Maintenance.Standard) { ++ if slices.Contains(cmd.Ignore, "unstable") { ++ logf(`Warning: This release is in the "unstable" maintenance status. ` + ++ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) ++ } else { ++ return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + ++ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ } + } + } + +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-25.10/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch b/rocks/bread-chisel-releases-25.10/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch new file mode 100644 index 0000000..851b6a5 --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch @@ -0,0 +1,58 @@ +From 76d4ec4dfb51fc7d4d625c5ee6021d9452e6b263 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:11:06 +0100 +Subject: [PATCH 2/3] chore: skip archive maintenance check via CHISEL_HACKS + +--- + cmd/chisel/cmd_cut.go | 34 +++++++++++++++++++--------------- + 1 file changed, 19 insertions(+), 15 deletions(-) + +diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go +index 98c1a36..b4381d4 100644 +--- a/cmd/chisel/cmd_cut.go ++++ b/cmd/chisel/cmd_cut.go +@@ -107,22 +107,26 @@ func (cmd *cmdCut) Execute(args []string) error { + archives[archiveName] = openArchive + } + +- hasMaintainedArchive := false +- for _, archive := range archives { +- if archive.Options().Maintained { +- hasMaintainedArchive = true +- break ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping archive maintenance check due to CHISEL_HACKS=1") ++ } else { ++ hasMaintainedArchive := false ++ for _, archive := range archives { ++ if archive.Options().Maintained { ++ hasMaintainedArchive = true ++ break ++ } + } +- } +- if !hasMaintainedArchive { +- if slices.Contains(cmd.Ignore, "unmaintained") { +- logf(`Warning: No archive has "maintained" maintenance status. ` + +- `Consider the different Ubuntu Pro subscriptions to be safe. ` + +- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) +- } else { +- return fmt.Errorf(`no archive has "maintained" maintenance status, ` + +- `consider the different Ubuntu Pro subscriptions to be safe, ` + +- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ if !hasMaintainedArchive { ++ if slices.Contains(cmd.Ignore, "unmaintained") { ++ logf(`Warning: No archive has "maintained" maintenance status. ` + ++ `Consider the different Ubuntu Pro subscriptions to be safe. ` + ++ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) ++ } else { ++ return fmt.Errorf(`no archive has "maintained" maintenance status, ` + ++ `consider the different Ubuntu Pro subscriptions to be safe, ` + ++ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ } + } + } + +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-25.10/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-25.10/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch new file mode 100644 index 0000000..a6774d0 --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch @@ -0,0 +1,27 @@ +From 7af23d09332e1fa104ad5c802e176e90f86d67d3 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:11:28 +0100 +Subject: [PATCH 3/3] chore: skip release validation via CHISEL_HACKS + +--- + internal/setup/setup.go | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/internal/setup/setup.go b/internal/setup/setup.go +index 6ed24ab..01ea8eb 100644 +--- a/internal/setup/setup.go ++++ b/internal/setup/setup.go +@@ -194,6 +194,10 @@ func ReadRelease(dir string) (*Release, error) { + return nil, err + } + ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping release validation due to CHISEL_HACKS=1") ++ return release, nil ++ } + err = release.validate() + if err != nil { + return nil, err +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-25.10/rockcraft.yaml b/rocks/bread-chisel-releases-25.10/rockcraft.yaml new file mode 100644 index 0000000..a023bba --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/rockcraft.yaml @@ -0,0 +1,141 @@ +# spellchecker: ignore rockcraft chiselled coreutils passwd nologin sshd pebble chisel CHISEL GOTOOLCHAIN noninteractive + +name: bread-chisel-releases +version: "25.10" +summary: chiselled ubuntu 25.10 + sshd + chisel + tooling spread test-host rock +description: | + Throwaway spread test-host for chisel-releases spread tests: a chiselled + (base: bare) Ubuntu 25.10 rock with bash, coreutils, openssh-server, apt, a + pam_permit-only auth stack, plus chisel (+ chisel-hacked), docker, and the + tooling chisel-releases tests typically need (curl, wget, git, jq, file, + sudo, ip). + + INSECURE BY DESIGN. Built for ephemeral spread test containers only; never + expose to a network or use in production. + +base: bare +build-base: ubuntu@25.10 + +platforms: + amd64: + arm64: + +services: + sshd: + override: replace + command: /usr/local/bin/sshd-entry.sh + startup: enabled + +parts: + # --- de-novo copy of the bread base (rocks can't build from rocks) ------- + bread: + plugin: nil + stage-packages: + - base-files_base + - base-files_etc + - base-files_var + - base-files_tmp + - base-files_lib + - base-files_home + - base-files_release-info + - base-files_chisel + - bash_bins + - coreutils_bins + - tar_bins + - gzip_bins + - base-passwd_data + - login_bins + - passwd_bins + - openssh-server_bins + - openssh-server_config + - ca-certificates_data + - libpam-modules_libs + - apt_apt-get + - apt_config + - apt_var + - apt_default-source-list + - dpkg_bins + override-build: | + set -eu + craftctl default + cd "$CRAFT_PART_INSTALL" + + grep -q '^sshd:' etc/passwd || \ + echo 'sshd:x:101:65534::/run/sshd:/usr/sbin/nologin' >> etc/passwd + hash=$(openssl passwd -6 bread) + { + printf 'root:%s:20000:0:99999:7:::\n' "$hash" + printf 'sshd:!:20000:0:99999:7:::\n' + } > etc/shadow + chmod 0640 etc/shadow + + mkdir -p etc/ssh + cat > etc/ssh/sshd_config <<'EOF' + PermitRootLogin yes + PasswordAuthentication yes + UsePAM yes + Subsystem sftp /usr/lib/openssh/sftp-server + PidFile /run/sshd.pid + EOF + + mkdir -p etc/pam.d + printf '%s\n' \ + 'auth required pam_permit.so' \ + 'account required pam_permit.so' \ + 'session required pam_permit.so' \ + 'password required pam_permit.so' \ + > etc/pam.d/sshd + + cat > etc/profile <<'EOF' + if [ -d /etc/profile.d ]; then + for __pf in /etc/profile.d/*.sh; do + [ -r "$__pf" ] && . "$__pf" + done + unset __pf + fi + EOF + + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/sshd-entry.sh" usr/local/bin/sshd-entry.sh + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt-get + install -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/banner.txt" etc/bread-banner.txt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/bread-warning.sh" etc/profile.d/00-bread-warning.sh + mkdir -p root + printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ + >> root/.bashrc + + # --- chisel-releases tooling (slices where they exist) ------------------- + # holes: tree + skopeo have no slice yet -> omitted for now. + tooling: + plugin: nil + stage-packages: + - curl_bins + - wget_bins + - git_bins + - git_http-support + - jq_bins + - file_bins + - sudo_bins + - sudo_config + - iproute2_ip-bin + + # --- chisel + chisel-hacked, built from source (go is build-only) -------- + chisel: + plugin: nil + build-snaps: + - go + build-packages: + - git + build-environment: + - CHISEL_REF: v1.4.1 + override-build: bash "$CRAFT_PROJECT_DIR/hack/chisel_override_build.sh" + + # --- docker static CLI --------------------------------------------------- + docker: + plugin: nil + build-packages: + - curl + - ca-certificates + build-environment: + - DOCKER_VERSION: 29.5.2 + override-build: bash "$CRAFT_PROJECT_DIR/hack/docker_override_build.sh" diff --git a/rocks/bread-chisel-releases-26.04/hack/banner.txt b/rocks/bread-chisel-releases-26.04/hack/banner.txt new file mode 100644 index 0000000..3c06b8b --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/hack/banner.txt @@ -0,0 +1,13 @@ + ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ + ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ + ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ +███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ +███▒▒░░░░░░░ ░░▒▒▓▓▓▓ +███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ + ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ + ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ + ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ + ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ + ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ + ███▒▒░░░░░░░ ░░░░░░░▓▓ + █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-chisel-releases-26.04/hack/bread-warning.sh b/rocks/bread-chisel-releases-26.04/hack/bread-warning.sh new file mode 100755 index 0000000..2ee0140 --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/hack/bread-warning.sh @@ -0,0 +1,39 @@ +# bread-warning: insecure-image notice. +# +# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive +# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same +# session. Silent on non-tty (spread, scripts, ci logs) unless +# BREAD_BANNER_FORCE is set. +# +# Banner content is verbatim from /etc/bread-banner.txt (shipped from +# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped +# in red ANSI; everything else is printed as-is. +# +# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no +# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER +# to stderr on every connection, which would inject ~14 lines of noise into +# every non-interactive `ssh root@host cmd` that spread runs. This shell +# hook covers every interactive path a human actually hits (ssh login shell +# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing +# worth the noise. + +[ -n "$BREAD_BANNER_SHOWN" ] && return 0 +if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then + export BREAD_BANNER_SHOWN=1 + return 0 +fi +export BREAD_BANNER_SHOWN=1 + +__bw_banner=/etc/bread-banner.txt +[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } + +if [ -t 1 ] && [ -z "$NO_COLOR" ]; then + __bw_r=$(printf '\033[1;31m') + __bw_n=$(printf '\033[0m') + sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" + unset __bw_r __bw_n +else + cat "$__bw_banner" +fi + +unset __bw_banner diff --git a/rocks/bread-chisel-releases-26.04/hack/chisel_override_build.sh b/rocks/bread-chisel-releases-26.04/hack/chisel_override_build.sh new file mode 100755 index 0000000..d8432fb --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/hack/chisel_override_build.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Build chisel + chisel-hacked from source (canonical/chisel @ $CHISEL_REF), +# native arch, into $CRAFT_PART_INSTALL/usr/local/bin. Mirrors the root repo's +# hack/build_binaries.sh, minus the cross-compile -- rockcraft builds natively +# per platform so GOARCH is just the host arch. +# +# Env (from the part's build-environment): +# CHISEL_REF git ref for canonical/chisel +set -euo pipefail + +: "${CHISEL_REF:?required}" + +# go.mod may demand a newer toolchain than the snap ships; let go fetch it. +export GOTOOLCHAIN=auto + +out="$CRAFT_PART_INSTALL/usr/local/bin" +mkdir -p "$out" + +src="$CRAFT_PART_BUILD/chisel-src" +git clone https://github.com/canonical/chisel "$src" +cd "$src" +git checkout "$CHISEL_REF" + +# Inject the version string the way upstream's cmd/mkversion.sh would, so +# `chisel --version` reports the pinned tag instead of "unknown". +ver=$(git describe --tags --dirty --always) +echo "==> chisel version: $ver" + +CGO_ENABLED=0 go build -trimpath \ + -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=$ver" \ + -o "$out/chisel" ./cmd/chisel + +# chisel-hacked: same source + the CHISEL_HACKS patches, -hacked version suffix. +for p in "$CRAFT_PROJECT_DIR"/patches/chisel/0001-*.patch \ + "$CRAFT_PROJECT_DIR"/patches/chisel/0002-*.patch \ + "$CRAFT_PROJECT_DIR"/patches/chisel/0003-*.patch; do + echo "==> applying $(basename "$p")" + git apply "$p" +done + +CGO_ENABLED=0 go build -trimpath \ + -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=${ver}-hacked" \ + -o "$out/chisel-hacked" ./cmd/chisel diff --git a/rocks/bread-chisel-releases-26.04/hack/docker_override_build.sh b/rocks/bread-chisel-releases-26.04/hack/docker_override_build.sh new file mode 100755 index 0000000..5e40e1f --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/hack/docker_override_build.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Fetch the upstream docker static CLI for the build-for arch and drop it at +# /usr/local/bin/docker. Upstream's static build uses a recent go toolchain and +# so dodges the qemu emulation bug that hits ubuntu apt's docker.io (go 1.24). +# +# Env (from the part's build-environment): +# DOCKER_VERSION docker static release version +set -euo pipefail + +: "${DOCKER_VERSION:?required}" + +# docker.com static uses uname-style arch names. +case "$CRAFT_ARCH_BUILD_FOR" in + amd64) docker_arch=x86_64 ;; + arm64) docker_arch=aarch64 ;; + *) echo "unsupported arch: $CRAFT_ARCH_BUILD_FOR" >&2; exit 1 ;; +esac + +out="$CRAFT_PART_INSTALL/usr/local/bin" +mkdir -p "$out" + +url="https://download.docker.com/linux/static/stable/${docker_arch}/docker-${DOCKER_VERSION}.tgz" +echo "==> downloading docker $DOCKER_VERSION for $CRAFT_ARCH_BUILD_FOR from $url" + +tmp=$(mktemp -d) +curl -fsSL "$url" | tar -xz -C "$tmp" docker/docker +install -m0755 "$tmp/docker/docker" "$out/docker" +rm -rf "$tmp" diff --git a/rocks/bread-chisel-releases-26.04/hack/hash_inputs.sh b/rocks/bread-chisel-releases-26.04/hack/hash_inputs.sh new file mode 100755 index 0000000..a6c2a51 --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/hack/hash_inputs.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Single sha256 over everything that affects this rock's build: the +# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the +# .rock.stamp short-circuit in the makefile so `make build` only repacks when a +# real input changed. +# +# Stdout: hex digest only. +set -euo pipefail + +cd "$(dirname "$0")/.." + +# shellcheck disable=SC2046 # word-splitting of find output is intended here. +sha256sum \ + rockcraft.yaml \ + $(find hack patches -type f 2>/dev/null | sort) \ + | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-chisel-releases-26.04/hack/lazy-apt.sh b/rocks/bread-chisel-releases-26.04/hack/lazy-apt.sh new file mode 100755 index 0000000..1038a4f --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/hack/lazy-apt.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# lazy-apt: on first non-update call, runs apt-get update then replaces both +# shims with symlinks to real apt. Installed at /usr/local/bin/apt and +# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call +# apt/apt-get without an explicit update still work after the image was built +# with apt lists wiped. +# +# Replaces with symlinks rather than deleting: bash hashes the shim path on +# first use, so deletion would leave a stale hash entry causing subsequent +# calls to fail with "No such file or directory". + +real="/usr/bin/$(basename "$0")" + +if [ "$1" != "update" ]; then + /usr/bin/apt-get update +fi + +ln -sf /usr/bin/apt /usr/local/bin/apt +ln -sf /usr/bin/apt-get /usr/local/bin/apt-get + +exec "$real" "$@" diff --git a/rocks/bread-chisel-releases-26.04/hack/sshd-entry.sh b/rocks/bread-chisel-releases-26.04/hack/sshd-entry.sh new file mode 100755 index 0000000..080d94c --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/hack/sshd-entry.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# pebble service command for sshd in the bread rock. +# +# host keys are generated here on first boot rather than baked into the rock, +# so throwaway test containers don't all share the same keys. also ensures the +# privsep runtime dir exists. then execs sshd in the foreground (pebble manages +# the process). +set -e + +mkdir -p /run/sshd +ssh-keygen -A + +exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-chisel-releases-26.04/makefile b/rocks/bread-chisel-releases-26.04/makefile new file mode 100644 index 0000000..8d9126f --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/makefile @@ -0,0 +1,43 @@ +.DEFAULT_GOAL := help + +DOCKER ?= docker + +# Predictable artefact name (matches the dir + the spread-rock- load tag). +ROCK := bread-chisel-releases-26.04.rock + +.PHONY: help +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +.PHONY: build +build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) + +.PHONY: FORCE +FORCE: + +.rock.stamp: FORCE + @new=$$(hack/hash_inputs.sh) ; \ + cur=$$(cat $@ 2>/dev/null || true) ; \ + [ "$$new" = "$$cur" ] || echo "$$new" > $@ + +$(ROCK): .rock.stamp + rockcraft pack --verbosity=brief + mv bread-chisel-releases_26.04_*.rock $(ROCK) + +.PHONY: size +size: ## Show the packed rock size + @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi + +.PHONY: ls-rock +ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) + @tag=bread-cr-ls-$$$$ ; \ + skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ + cid=$$($(DOCKER) create $$tag:latest) ; \ + $(DOCKER) export $$cid | tar tv ; \ + $(DOCKER) rm $$cid > /dev/null ; \ + $(DOCKER) rmi $$tag:latest > /dev/null + +.PHONY: clean +clean: ## Remove the packed rock + stamp + rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-chisel-releases-26.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-26.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch new file mode 100644 index 0000000..d3c0637 --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch @@ -0,0 +1,49 @@ +From 38a1af51d71f7904693a88db64df808afed80827 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:10:52 +0100 +Subject: [PATCH 1/3] chore: skip maintenance check via CHISEL_HACKS + +--- + cmd/chisel/cmd_cut.go | 19 ++++++++++++------- + 1 file changed, 12 insertions(+), 7 deletions(-) + +diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go +index 35c81a7..98c1a36 100644 +--- a/cmd/chisel/cmd_cut.go ++++ b/cmd/chisel/cmd_cut.go +@@ -2,6 +2,7 @@ package main + + import ( + "fmt" ++ "os" + "slices" + "time" + +@@ -63,13 +64,17 @@ func (cmd *cmdCut) Execute(args []string) error { + return err + } + +- if time.Now().Before(release.Maintenance.Standard) { +- if slices.Contains(cmd.Ignore, "unstable") { +- logf(`Warning: This release is in the "unstable" maintenance status. ` + +- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) +- } else { +- return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + +- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping maintenance check due to CHISEL_HACKS=1") ++ } else { ++ if time.Now().Before(release.Maintenance.Standard) { ++ if slices.Contains(cmd.Ignore, "unstable") { ++ logf(`Warning: This release is in the "unstable" maintenance status. ` + ++ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) ++ } else { ++ return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + ++ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ } + } + } + +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-26.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch b/rocks/bread-chisel-releases-26.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch new file mode 100644 index 0000000..851b6a5 --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch @@ -0,0 +1,58 @@ +From 76d4ec4dfb51fc7d4d625c5ee6021d9452e6b263 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:11:06 +0100 +Subject: [PATCH 2/3] chore: skip archive maintenance check via CHISEL_HACKS + +--- + cmd/chisel/cmd_cut.go | 34 +++++++++++++++++++--------------- + 1 file changed, 19 insertions(+), 15 deletions(-) + +diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go +index 98c1a36..b4381d4 100644 +--- a/cmd/chisel/cmd_cut.go ++++ b/cmd/chisel/cmd_cut.go +@@ -107,22 +107,26 @@ func (cmd *cmdCut) Execute(args []string) error { + archives[archiveName] = openArchive + } + +- hasMaintainedArchive := false +- for _, archive := range archives { +- if archive.Options().Maintained { +- hasMaintainedArchive = true +- break ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping archive maintenance check due to CHISEL_HACKS=1") ++ } else { ++ hasMaintainedArchive := false ++ for _, archive := range archives { ++ if archive.Options().Maintained { ++ hasMaintainedArchive = true ++ break ++ } + } +- } +- if !hasMaintainedArchive { +- if slices.Contains(cmd.Ignore, "unmaintained") { +- logf(`Warning: No archive has "maintained" maintenance status. ` + +- `Consider the different Ubuntu Pro subscriptions to be safe. ` + +- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) +- } else { +- return fmt.Errorf(`no archive has "maintained" maintenance status, ` + +- `consider the different Ubuntu Pro subscriptions to be safe, ` + +- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ if !hasMaintainedArchive { ++ if slices.Contains(cmd.Ignore, "unmaintained") { ++ logf(`Warning: No archive has "maintained" maintenance status. ` + ++ `Consider the different Ubuntu Pro subscriptions to be safe. ` + ++ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) ++ } else { ++ return fmt.Errorf(`no archive has "maintained" maintenance status, ` + ++ `consider the different Ubuntu Pro subscriptions to be safe, ` + ++ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) ++ } + } + } + +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-26.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-26.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch new file mode 100644 index 0000000..a6774d0 --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch @@ -0,0 +1,27 @@ +From 7af23d09332e1fa104ad5c802e176e90f86d67d3 Mon Sep 17 00:00:00 2001 +From: spread-bread +Date: Mon, 1 Jun 2026 15:11:28 +0100 +Subject: [PATCH 3/3] chore: skip release validation via CHISEL_HACKS + +--- + internal/setup/setup.go | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/internal/setup/setup.go b/internal/setup/setup.go +index 6ed24ab..01ea8eb 100644 +--- a/internal/setup/setup.go ++++ b/internal/setup/setup.go +@@ -194,6 +194,10 @@ func ReadRelease(dir string) (*Release, error) { + return nil, err + } + ++ if os.Getenv("CHISEL_HACKS") == "1" { ++ logf("Skipping release validation due to CHISEL_HACKS=1") ++ return release, nil ++ } + err = release.validate() + if err != nil { + return nil, err +-- +2.53.0 + diff --git a/rocks/bread-chisel-releases-26.04/rockcraft.yaml b/rocks/bread-chisel-releases-26.04/rockcraft.yaml new file mode 100644 index 0000000..2e24af9 --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/rockcraft.yaml @@ -0,0 +1,141 @@ +# spellchecker: ignore rockcraft chiselled coreutils passwd nologin sshd pebble chisel CHISEL GOTOOLCHAIN noninteractive + +name: bread-chisel-releases +version: "26.04" +summary: chiselled ubuntu 26.04 + sshd + chisel + tooling spread test-host rock +description: | + Throwaway spread test-host for chisel-releases spread tests: a chiselled + (base: bare) Ubuntu 26.04 rock with bash, coreutils, openssh-server, apt, a + pam_permit-only auth stack, plus chisel (+ chisel-hacked), docker, and the + tooling chisel-releases tests typically need (curl, wget, git, jq, file, + sudo, ip). + + INSECURE BY DESIGN. Built for ephemeral spread test containers only; never + expose to a network or use in production. + +base: bare +build-base: ubuntu@26.04 + +platforms: + amd64: + arm64: + +services: + sshd: + override: replace + command: /usr/local/bin/sshd-entry.sh + startup: enabled + +parts: + # --- de-novo copy of the bread base (rocks can't build from rocks) ------- + bread: + plugin: nil + stage-packages: + - base-files_base + - base-files_etc + - base-files_var + - base-files_tmp + - base-files_lib + - base-files_home + - base-files_release-info + - base-files_chisel + - bash_bins + - coreutils_bins + - tar_bins + - gzip_bins + - base-passwd_data + - login_bins + - passwd_bins + - openssh-server_bins + - openssh-server_config + - ca-certificates_data + - libpam-modules_libs + - apt_apt-get + - apt_config + - apt_var + - apt_default-source-list + - dpkg_bins + override-build: | + set -eu + craftctl default + cd "$CRAFT_PART_INSTALL" + + grep -q '^sshd:' etc/passwd || \ + echo 'sshd:x:101:65534::/run/sshd:/usr/sbin/nologin' >> etc/passwd + hash=$(openssl passwd -6 bread) + { + printf 'root:%s:20000:0:99999:7:::\n' "$hash" + printf 'sshd:!:20000:0:99999:7:::\n' + } > etc/shadow + chmod 0640 etc/shadow + + mkdir -p etc/ssh + cat > etc/ssh/sshd_config <<'EOF' + PermitRootLogin yes + PasswordAuthentication yes + UsePAM yes + Subsystem sftp /usr/lib/openssh/sftp-server + PidFile /run/sshd.pid + EOF + + mkdir -p etc/pam.d + printf '%s\n' \ + 'auth required pam_permit.so' \ + 'account required pam_permit.so' \ + 'session required pam_permit.so' \ + 'password required pam_permit.so' \ + > etc/pam.d/sshd + + cat > etc/profile <<'EOF' + if [ -d /etc/profile.d ]; then + for __pf in /etc/profile.d/*.sh; do + [ -r "$__pf" ] && . "$__pf" + done + unset __pf + fi + EOF + + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/sshd-entry.sh" usr/local/bin/sshd-entry.sh + install -D -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt-get + install -m0755 "$CRAFT_PROJECT_DIR/hack/lazy-apt.sh" usr/local/bin/apt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/banner.txt" etc/bread-banner.txt + install -D -m0644 "$CRAFT_PROJECT_DIR/hack/bread-warning.sh" etc/profile.d/00-bread-warning.sh + mkdir -p root + printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ + >> root/.bashrc + + # --- chisel-releases tooling (slices where they exist) ------------------- + # holes: tree + skopeo have no slice yet -> omitted for now. + tooling: + plugin: nil + stage-packages: + - curl_bins + - wget_bins + - git_bins + - git_http-support + - jq_bins + - file_bins + - sudo_bins + - sudo_config + - iproute2_ip-bin + + # --- chisel + chisel-hacked, built from source (go is build-only) -------- + chisel: + plugin: nil + build-snaps: + - go + build-packages: + - git + build-environment: + - CHISEL_REF: v1.4.1 + override-build: bash "$CRAFT_PROJECT_DIR/hack/chisel_override_build.sh" + + # --- docker static CLI --------------------------------------------------- + docker: + plugin: nil + build-packages: + - curl + - ca-certificates + build-environment: + - DOCKER_VERSION: 29.5.2 + override-build: bash "$CRAFT_PROJECT_DIR/hack/docker_override_build.sh" diff --git a/rocks/makefile b/rocks/makefile new file mode 100644 index 0000000..edb8155 --- /dev/null +++ b/rocks/makefile @@ -0,0 +1,53 @@ +.DEFAULT_GOAL := help + +DOCKER ?= docker + +# Rock dirs = every subdir with a rockcraft.yaml (tests/ has none). +DIRS := $(patsubst %/rockcraft.yaml,%,$(wildcard */rockcraft.yaml)) + +# Native arch for the default `test` filter (uname -m -> docker/spread arch). +# Accept both linux (x86_64/aarch64) and macos/docker (amd64/arm64) spellings. +UNAME_M := $(shell uname -m) +ifeq ($(filter $(UNAME_M),x86_64 amd64),$(UNAME_M)) + NATIVE_ARCH := amd64 +else ifeq ($(filter $(UNAME_M),aarch64 arm64),$(UNAME_M)) + NATIVE_ARCH := arm64 +else + $(error unsupported host arch: $(UNAME_M)) +endif + +# Default spread filter: every rock dir on the native arch. +SPREAD_FILTER ?= $(addprefix rock:,$(addsuffix -$(NATIVE_ARCH),$(DIRS))) + +.PHONY: help +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-16s\033[0m %s\n", $$1, $$2}' + @echo "" + @echo "rock dirs: $(DIRS)" + +.PHONY: build-all +build-all: ## Pack every rock (narrow via DIRS="bread-24.04 ...") + @for d in $(DIRS); do echo "==> build $$d" && $(MAKE) -C $$d build || exit 1; done + +.PHONY: load +load: ## Load every built rock into the local daemon as spread-rock-:latest + @for d in $(DIRS); do \ + f="$$d/$$d.rock" ; \ + [ -f "$$f" ] || { echo "skip $$d (no $$f)"; continue; } ; \ + echo "==> load $$f -> spread-rock-$$d:latest" ; \ + skopeo --insecure-policy copy "oci-archive:$$f" "docker-daemon:spread-rock-$$d:latest" ; \ + done + +.PHONY: test +test: build-all load ## Build + load all rocks, then run the top-level spread suite (native arch) + cd tests && spread $(SPREAD_FILTER) + +.PHONY: spread-only +spread-only: load ## Load prebuilt rocks + run spread, no packing (for ci where rocks are downloaded) + cd tests && spread $(SPREAD_FILTER) + +.PHONY: clean +clean: ## Per-dir clean + remove the loaded spread-rock-* tags + @for d in $(DIRS); do $(MAKE) -C $$d clean ; done + -@for d in $(DIRS); do $(DOCKER) rmi -f "spread-rock-$$d:latest" 2>/dev/null ; done diff --git a/rocks/tests/allocate.sh b/rocks/tests/allocate.sh new file mode 100755 index 0000000..a5ca513 --- /dev/null +++ b/rocks/tests/allocate.sh @@ -0,0 +1,43 @@ +#spellchecker: ignore noninteractive + +set -e + +# System name encodes flavour, version + arch, e.g. +# bread-24.04-amd64 -> dir=bread-24.04 +# bread-chisel-releases-26.04-arm64 -> dir=bread-chisel-releases-26.04 +# Parse from the right so the hyphens in bread-chisel-releases don't trip us up. +name="$SPREAD_SYSTEM" +arch="${name##*-}" +rest="${name%-*}" +ver="${rest##*-}" +dir="${rest%-*}-$ver" + +# The rock is loaded into the local daemon (by `make load` / the ci test job) +# under this tag before spread runs -- so allocate just `docker run`s it. +tag="spread-rock-$dir:latest" +echo "system: $name -> image: $tag" + +# Unique container name per worker (flock counter pattern, cf. charmcraft). +sleep 0.$RANDOM +export counter_file=".spread-worker-num" +instance_num=$( + flock -x $counter_file bash -c ' + [ -s $counter_file ] || echo 0 > $counter_file + num=$(< $counter_file) && echo $num + echo $(( $num + 1 )) > $counter_file' +) +container_name="${name}-${instance_num}" +echo "container: $container_name" + +docker rm -f "$container_name" 2>/dev/null || true + +# The rock runs pebble as pid 1, which starts the enabled sshd service. +docker run \ + --rm \ + --platform "linux/$arch" \ + --name "$container_name" \ + -d "$tag" + +until docker exec "$container_name" pgrep sshd >/dev/null 2>&1; do sleep 1; done + +ADDRESS "$(docker inspect "$container_name" --format '{{.NetworkSettings.Networks.bridge.IPAddress}}')" diff --git a/rocks/tests/contract/task.yaml b/rocks/tests/contract/task.yaml new file mode 100644 index 0000000..7d8a9f3 --- /dev/null +++ b/rocks/tests/contract/task.yaml @@ -0,0 +1,69 @@ +summary: rock contract -- version/arch + bread bits, plus chisel/tooling for the cr flavour + +execute: | + set -e + + name="$SPREAD_SYSTEM" + arch="${name##*-}" + rest="${name%-*}" + expected_ver="${rest##*-}" + flavour="${rest%-*}" + + # --- ubuntu version (from base-files_release-info) matches the system name. + actual_ver=$(. /etc/os-release && echo "$VERSION_ID") + if [ "$expected_ver" != "$actual_ver" ]; then + echo "ver mismatch: expected=$expected_ver got=$actual_ver" + exit 1 + fi + + # --- arch matches (uname -m -> docker arch name). + case "$(uname -m)" in + x86_64) actual_arch=amd64 ;; + aarch64) actual_arch=arm64 ;; + *) actual_arch=$(uname -m) ;; + esac + if [ "$arch" != "$actual_arch" ]; then + echo "arch mismatch: expected=$arch got=$actual_arch" + exit 1 + fi + + # --- shell + core utils present. + command -v bash >/dev/null || { echo "missing bash"; exit 1; } + command -v ls >/dev/null || { echo "missing coreutils (ls)"; exit 1; } + + # --- bread-warning banner: file present + prints exactly once on a forced + # login shell, silent without a tty. + test -r /etc/profile.d/00-bread-warning.sh || { + echo "missing /etc/profile.d/00-bread-warning.sh"; exit 1; } + out=$(BREAD_BANNER_FORCE=1 bash -l -c true 2>&1) + count=$(echo "$out" | grep -c "INSECURE TEST IMAGE" || true) + if [ "$count" != "1" ]; then + echo "banner print count: expected=1 got=$count" + echo "--- $out ---" + exit 1 + fi + silent=$(bash -l -c true 2>&1) + if echo "$silent" | grep -q "INSECURE TEST IMAGE"; then + echo "banner leaked on non-tty"; exit 1 + fi + + # --- apt: update resolves, and the lazy-apt shim fires (a non-update call + # runs apt-get update first, then replaces itself with a symlink to real apt). + apt-get update + [ -L /usr/local/bin/apt-get ] || { echo "lazy-apt shim did not self-replace"; exit 1; } + + # --- chisel-releases flavour extras. + case "$flavour" in + *chisel*) + chisel --version | grep -q "v1\.4\.1" || { + echo "chisel missing or wrong version"; chisel --version || true; exit 1; } + chisel-hacked --version | grep -q "v1\.4\.1-hacked" || { + echo "chisel-hacked missing or wrong version"; chisel-hacked --version || true; exit 1; } + # tree + skopeo are holes (no slice yet) -> not checked. + for cmd in curl wget git jq file sudo ip docker; do + command -v "$cmd" >/dev/null || { echo "missing command: $cmd"; exit 1; } + done + ;; + esac + + echo "ok: $SPREAD_SYSTEM" diff --git a/rocks/tests/discard.sh b/rocks/tests/discard.sh new file mode 100755 index 0000000..d9342b9 --- /dev/null +++ b/rocks/tests/discard.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +echo "discarding container for system: $SPREAD_SYSTEM_ADDRESS" + +# Can't filter by IP directly; find the container by inspecting all of them. +container_name="" +for cid in $(docker ps -a --filter "network=bridge" --format '{{.ID}}'); do + cname=$(docker inspect "$cid" --format '{{.Name}}' | sed 's/^\/\(.*\)/\1/') + cip=$(docker inspect "$cid" --format '{{.NetworkSettings.Networks.bridge.IPAddress}}' || echo "") + if [ "$cip" == "$SPREAD_SYSTEM_ADDRESS" ]; then + container_name="$cname" + break + fi +done + +if [ -n "$container_name" ]; then + echo "removing container: $container_name" + docker rm -f "$container_name" 2>/dev/null || true +else + echo "no container found with IP address: $SPREAD_SYSTEM_ADDRESS" + exit 1 +fi diff --git a/rocks/tests/spread.yaml b/rocks/tests/spread.yaml new file mode 100644 index 0000000..88da93d --- /dev/null +++ b/rocks/tests/spread.yaml @@ -0,0 +1,35 @@ +project: rocks + +path: /spread-bread-rocks + +exclude: + - .git + - .github + +backends: + # The bread rock IS the test host (unlike a payload rock), so allocate just + # `docker run`s the rock that was loaded into the local daemon as + # spread-rock-:latest, and ssh's in. One system per (flavour, ver, arch). + rock: + type: adhoc + allocate: source allocate.sh + discard: source discard.sh + systems: + - bread-24.04-amd64: {username: root, password: bread} + - bread-24.04-arm64: {username: root, password: bread} + - bread-25.10-amd64: {username: root, password: bread} + - bread-25.10-arm64: {username: root, password: bread} + - bread-26.04-amd64: {username: root, password: bread} + - bread-26.04-arm64: {username: root, password: bread} + - bread-chisel-releases-24.04-amd64: {username: root, password: bread} + - bread-chisel-releases-24.04-arm64: {username: root, password: bread} + - bread-chisel-releases-25.10-amd64: {username: root, password: bread} + - bread-chisel-releases-25.10-arm64: {username: root, password: bread} + - bread-chisel-releases-26.04-amd64: {username: root, password: bread} + - bread-chisel-releases-26.04-arm64: {username: root, password: bread} + +suites: + contract/: + summary: rock contract -- ubuntu version + arch + bread bits (+ chisel/tooling for the cr flavour) + +kill-timeout: 15m From a1a5319c162d687d482366174bdf8c63cd1b7eca Mon Sep 17 00:00:00 2001 From: lczyk Date: Mon, 1 Jun 2026 21:36:42 +0100 Subject: [PATCH 02/18] fix: retry chisel-releases archive flakes when cutting rock slices rockcraft's stage-packages slice cut has no retry, so a single intermittent "expected digest ..." from the chisel-releases archive failed the whole pack (seen in rocks CI). drop stage-packages; chisel-cut the slices ourselves in override-build (clone chisel-releases @ ubuntu-, cut via chisel_cut.sh which retries 10x on the known archive flakes). pattern from not-quite-rust-rock. cr flavour folds its tooling slices into the same single cut. --- rocks/bread-24.04/hack/chisel_cut.sh | 57 ++++++++++++++++ rocks/bread-24.04/rockcraft.yaml | 64 ++++++++---------- rocks/bread-25.10/hack/chisel_cut.sh | 57 ++++++++++++++++ rocks/bread-25.10/rockcraft.yaml | 64 ++++++++---------- rocks/bread-26.04/hack/chisel_cut.sh | 57 ++++++++++++++++ rocks/bread-26.04/rockcraft.yaml | 64 ++++++++---------- .../hack/chisel_cut.sh | 57 ++++++++++++++++ .../rockcraft.yaml | 66 +++++++------------ .../hack/chisel_cut.sh | 57 ++++++++++++++++ .../rockcraft.yaml | 66 +++++++------------ .../hack/chisel_cut.sh | 57 ++++++++++++++++ .../rockcraft.yaml | 66 +++++++------------ 12 files changed, 495 insertions(+), 237 deletions(-) create mode 100755 rocks/bread-24.04/hack/chisel_cut.sh create mode 100755 rocks/bread-25.10/hack/chisel_cut.sh create mode 100755 rocks/bread-26.04/hack/chisel_cut.sh create mode 100755 rocks/bread-chisel-releases-24.04/hack/chisel_cut.sh create mode 100755 rocks/bread-chisel-releases-25.10/hack/chisel_cut.sh create mode 100755 rocks/bread-chisel-releases-26.04/hack/chisel_cut.sh diff --git a/rocks/bread-24.04/hack/chisel_cut.sh b/rocks/bread-24.04/hack/chisel_cut.sh new file mode 100755 index 0000000..db2af91 --- /dev/null +++ b/rocks/bread-24.04/hack/chisel_cut.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Wrapper around `chisel cut` with retry on known-flaky archive +# errors. Patterns lifted from canonical/chisel-releases' +# .github/scripts/install-slices/install_slices.py. +# +# Usage: chisel_cut.sh [ ...] +# Requires: +# - to be a chisel-releases checkout +# - $CRAFT_PART_INSTALL to be set (rockcraft env) +set -e + +N_RETRIES=10 +RETRY_DELAY=60 +PATTERNS=( + # https://github.com/canonical/chisel-releases/issues/765 + "cannot fetch from archive" + # https://github.com/canonical/chisel-releases/issues/766 + "cannot talk to archive" + # https://github.com/canonical/chisel-releases/issues/768 + "cannot find archive data" + # transient digest mismatch (chisel bug) + "expected digest" +) + +[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } +[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } + +release_dir=$1 +shift + +for attempt in $(seq 1 "$N_RETRIES"); do + err=$(chisel cut \ + --release "$release_dir" \ + --root "$CRAFT_PART_INSTALL" \ + "$@" 2>&1) && rc=0 || rc=$? + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$err" + exit 0 + fi + + matched="" + matched_line="" + for p in "${PATTERNS[@]}"; do + line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true + if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi + done + + if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then + echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 + echo " > $matched_line" >&2 + sleep "$RETRY_DELAY" + continue + fi + + printf '%s\n' "$err" >&2 + exit "$rc" +done diff --git a/rocks/bread-24.04/rockcraft.yaml b/rocks/bread-24.04/rockcraft.yaml index 65d6ecf..b6ec2b1 100644 --- a/rocks/bread-24.04/rockcraft.yaml +++ b/rocks/bread-24.04/rockcraft.yaml @@ -29,46 +29,36 @@ services: parts: bread: + # We chisel-cut the slices ourselves rather than using stage-packages: + # rockcraft's stage-packages cut has no retry, and the chisel-releases + # archive intermittently flakes ("expected digest ..."). hack/chisel_cut.sh + # retries around that. (Pattern from not-quite-rust-rock.) plugin: nil - stage-packages: - # fs skeleton + os-release (version contract check) + chisel manifest - - base-files_base - - base-files_etc - - base-files_var - - base-files_tmp - - base-files_lib - - base-files_home - - base-files_release-info - - base-files_chisel - # shell + core utils. tar + gzip are needed by spread to sync the - # project onto the system over ssh (not part of coreutils). - - bash_bins - - coreutils_bins - - tar_bins - - gzip_bins - # users / login (base-passwd_data writes /etc/passwd + /etc/group w/ root) - - base-passwd_data - - login_bins - - passwd_bins - # ssh (openssh-server_bins pulls openssh-client_bins -> ssh-keygen, and - # libpam-modules_libs -> pam_permit.so, as essentials) - - openssh-server_bins - - openssh-server_config - - ca-certificates_data - # pam: only pam_permit.so (always-allow). NOT libpam-modules_config / - # _pam-config -- we don't want the real pam_unix default stacks. - - libpam-modules_libs - # apt. apt_apt-get pulls dpkg_maintscript-support but NOT /usr/bin/dpkg, - # so apt-get install cannot unpack without dpkg_bins -- add it so the - # lazy-apt shim (and test suites that apt-get install) actually work. - - apt_apt-get - - apt_config - - apt_var - - apt_default-source-list - - dpkg_bins + build-snaps: + - chisel + build-packages: + - git override-build: | set -eu - craftctl default + + releases=$(mktemp -d) + git clone --depth 1 --branch ubuntu-24.04 \ + https://github.com/canonical/chisel-releases.git "$releases" + + # tar + gzip: needed by spread to sync the project onto the system over + # ssh (not part of coreutils). dpkg_bins: apt_apt-get only pulls + # dpkg_maintscript-support, so apt-get install can't unpack without it. + # pam: only libpam-modules_libs (carries pam_permit.so) -- NOT the + # _config / _pam-config default stacks. + bash "$CRAFT_PROJECT_DIR/hack/chisel_cut.sh" "$releases" \ + base-files_base base-files_etc base-files_var base-files_tmp \ + base-files_lib base-files_home base-files_release-info base-files_chisel \ + bash_bins coreutils_bins tar_bins gzip_bins \ + base-passwd_data login_bins passwd_bins \ + openssh-server_bins openssh-server_config ca-certificates_data \ + libpam-modules_libs \ + apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins + cd "$CRAFT_PART_INSTALL" # --- users: base-passwd_data gives us /etc/passwd + /etc/group (incl diff --git a/rocks/bread-25.10/hack/chisel_cut.sh b/rocks/bread-25.10/hack/chisel_cut.sh new file mode 100755 index 0000000..db2af91 --- /dev/null +++ b/rocks/bread-25.10/hack/chisel_cut.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Wrapper around `chisel cut` with retry on known-flaky archive +# errors. Patterns lifted from canonical/chisel-releases' +# .github/scripts/install-slices/install_slices.py. +# +# Usage: chisel_cut.sh [ ...] +# Requires: +# - to be a chisel-releases checkout +# - $CRAFT_PART_INSTALL to be set (rockcraft env) +set -e + +N_RETRIES=10 +RETRY_DELAY=60 +PATTERNS=( + # https://github.com/canonical/chisel-releases/issues/765 + "cannot fetch from archive" + # https://github.com/canonical/chisel-releases/issues/766 + "cannot talk to archive" + # https://github.com/canonical/chisel-releases/issues/768 + "cannot find archive data" + # transient digest mismatch (chisel bug) + "expected digest" +) + +[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } +[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } + +release_dir=$1 +shift + +for attempt in $(seq 1 "$N_RETRIES"); do + err=$(chisel cut \ + --release "$release_dir" \ + --root "$CRAFT_PART_INSTALL" \ + "$@" 2>&1) && rc=0 || rc=$? + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$err" + exit 0 + fi + + matched="" + matched_line="" + for p in "${PATTERNS[@]}"; do + line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true + if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi + done + + if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then + echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 + echo " > $matched_line" >&2 + sleep "$RETRY_DELAY" + continue + fi + + printf '%s\n' "$err" >&2 + exit "$rc" +done diff --git a/rocks/bread-25.10/rockcraft.yaml b/rocks/bread-25.10/rockcraft.yaml index 750f21e..5534a67 100644 --- a/rocks/bread-25.10/rockcraft.yaml +++ b/rocks/bread-25.10/rockcraft.yaml @@ -29,46 +29,36 @@ services: parts: bread: + # We chisel-cut the slices ourselves rather than using stage-packages: + # rockcraft's stage-packages cut has no retry, and the chisel-releases + # archive intermittently flakes ("expected digest ..."). hack/chisel_cut.sh + # retries around that. (Pattern from not-quite-rust-rock.) plugin: nil - stage-packages: - # fs skeleton + os-release (version contract check) + chisel manifest - - base-files_base - - base-files_etc - - base-files_var - - base-files_tmp - - base-files_lib - - base-files_home - - base-files_release-info - - base-files_chisel - # shell + core utils. tar + gzip are needed by spread to sync the - # project onto the system over ssh (not part of coreutils). - - bash_bins - - coreutils_bins - - tar_bins - - gzip_bins - # users / login (base-passwd_data writes /etc/passwd + /etc/group w/ root) - - base-passwd_data - - login_bins - - passwd_bins - # ssh (openssh-server_bins pulls openssh-client_bins -> ssh-keygen, and - # libpam-modules_libs -> pam_permit.so, as essentials) - - openssh-server_bins - - openssh-server_config - - ca-certificates_data - # pam: only pam_permit.so (always-allow). NOT libpam-modules_config / - # _pam-config -- we don't want the real pam_unix default stacks. - - libpam-modules_libs - # apt. apt_apt-get pulls dpkg_maintscript-support but NOT /usr/bin/dpkg, - # so apt-get install cannot unpack without dpkg_bins -- add it so the - # lazy-apt shim (and test suites that apt-get install) actually work. - - apt_apt-get - - apt_config - - apt_var - - apt_default-source-list - - dpkg_bins + build-snaps: + - chisel + build-packages: + - git override-build: | set -eu - craftctl default + + releases=$(mktemp -d) + git clone --depth 1 --branch ubuntu-25.10 \ + https://github.com/canonical/chisel-releases.git "$releases" + + # tar + gzip: needed by spread to sync the project onto the system over + # ssh (not part of coreutils). dpkg_bins: apt_apt-get only pulls + # dpkg_maintscript-support, so apt-get install can't unpack without it. + # pam: only libpam-modules_libs (carries pam_permit.so) -- NOT the + # _config / _pam-config default stacks. + bash "$CRAFT_PROJECT_DIR/hack/chisel_cut.sh" "$releases" \ + base-files_base base-files_etc base-files_var base-files_tmp \ + base-files_lib base-files_home base-files_release-info base-files_chisel \ + bash_bins coreutils_bins tar_bins gzip_bins \ + base-passwd_data login_bins passwd_bins \ + openssh-server_bins openssh-server_config ca-certificates_data \ + libpam-modules_libs \ + apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins + cd "$CRAFT_PART_INSTALL" # --- users: base-passwd_data gives us /etc/passwd + /etc/group (incl diff --git a/rocks/bread-26.04/hack/chisel_cut.sh b/rocks/bread-26.04/hack/chisel_cut.sh new file mode 100755 index 0000000..db2af91 --- /dev/null +++ b/rocks/bread-26.04/hack/chisel_cut.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Wrapper around `chisel cut` with retry on known-flaky archive +# errors. Patterns lifted from canonical/chisel-releases' +# .github/scripts/install-slices/install_slices.py. +# +# Usage: chisel_cut.sh [ ...] +# Requires: +# - to be a chisel-releases checkout +# - $CRAFT_PART_INSTALL to be set (rockcraft env) +set -e + +N_RETRIES=10 +RETRY_DELAY=60 +PATTERNS=( + # https://github.com/canonical/chisel-releases/issues/765 + "cannot fetch from archive" + # https://github.com/canonical/chisel-releases/issues/766 + "cannot talk to archive" + # https://github.com/canonical/chisel-releases/issues/768 + "cannot find archive data" + # transient digest mismatch (chisel bug) + "expected digest" +) + +[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } +[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } + +release_dir=$1 +shift + +for attempt in $(seq 1 "$N_RETRIES"); do + err=$(chisel cut \ + --release "$release_dir" \ + --root "$CRAFT_PART_INSTALL" \ + "$@" 2>&1) && rc=0 || rc=$? + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$err" + exit 0 + fi + + matched="" + matched_line="" + for p in "${PATTERNS[@]}"; do + line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true + if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi + done + + if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then + echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 + echo " > $matched_line" >&2 + sleep "$RETRY_DELAY" + continue + fi + + printf '%s\n' "$err" >&2 + exit "$rc" +done diff --git a/rocks/bread-26.04/rockcraft.yaml b/rocks/bread-26.04/rockcraft.yaml index 3395964..1f5315b 100644 --- a/rocks/bread-26.04/rockcraft.yaml +++ b/rocks/bread-26.04/rockcraft.yaml @@ -29,46 +29,36 @@ services: parts: bread: + # We chisel-cut the slices ourselves rather than using stage-packages: + # rockcraft's stage-packages cut has no retry, and the chisel-releases + # archive intermittently flakes ("expected digest ..."). hack/chisel_cut.sh + # retries around that. (Pattern from not-quite-rust-rock.) plugin: nil - stage-packages: - # fs skeleton + os-release (version contract check) + chisel manifest - - base-files_base - - base-files_etc - - base-files_var - - base-files_tmp - - base-files_lib - - base-files_home - - base-files_release-info - - base-files_chisel - # shell + core utils. tar + gzip are needed by spread to sync the - # project onto the system over ssh (not part of coreutils). - - bash_bins - - coreutils_bins - - tar_bins - - gzip_bins - # users / login (base-passwd_data writes /etc/passwd + /etc/group w/ root) - - base-passwd_data - - login_bins - - passwd_bins - # ssh (openssh-server_bins pulls openssh-client_bins -> ssh-keygen, and - # libpam-modules_libs -> pam_permit.so, as essentials) - - openssh-server_bins - - openssh-server_config - - ca-certificates_data - # pam: only pam_permit.so (always-allow). NOT libpam-modules_config / - # _pam-config -- we don't want the real pam_unix default stacks. - - libpam-modules_libs - # apt. apt_apt-get pulls dpkg_maintscript-support but NOT /usr/bin/dpkg, - # so apt-get install cannot unpack without dpkg_bins -- add it so the - # lazy-apt shim (and test suites that apt-get install) actually work. - - apt_apt-get - - apt_config - - apt_var - - apt_default-source-list - - dpkg_bins + build-snaps: + - chisel + build-packages: + - git override-build: | set -eu - craftctl default + + releases=$(mktemp -d) + git clone --depth 1 --branch ubuntu-26.04 \ + https://github.com/canonical/chisel-releases.git "$releases" + + # tar + gzip: needed by spread to sync the project onto the system over + # ssh (not part of coreutils). dpkg_bins: apt_apt-get only pulls + # dpkg_maintscript-support, so apt-get install can't unpack without it. + # pam: only libpam-modules_libs (carries pam_permit.so) -- NOT the + # _config / _pam-config default stacks. + bash "$CRAFT_PROJECT_DIR/hack/chisel_cut.sh" "$releases" \ + base-files_base base-files_etc base-files_var base-files_tmp \ + base-files_lib base-files_home base-files_release-info base-files_chisel \ + bash_bins coreutils_bins tar_bins gzip_bins \ + base-passwd_data login_bins passwd_bins \ + openssh-server_bins openssh-server_config ca-certificates_data \ + libpam-modules_libs \ + apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins + cd "$CRAFT_PART_INSTALL" # --- users: base-passwd_data gives us /etc/passwd + /etc/group (incl diff --git a/rocks/bread-chisel-releases-24.04/hack/chisel_cut.sh b/rocks/bread-chisel-releases-24.04/hack/chisel_cut.sh new file mode 100755 index 0000000..db2af91 --- /dev/null +++ b/rocks/bread-chisel-releases-24.04/hack/chisel_cut.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Wrapper around `chisel cut` with retry on known-flaky archive +# errors. Patterns lifted from canonical/chisel-releases' +# .github/scripts/install-slices/install_slices.py. +# +# Usage: chisel_cut.sh [ ...] +# Requires: +# - to be a chisel-releases checkout +# - $CRAFT_PART_INSTALL to be set (rockcraft env) +set -e + +N_RETRIES=10 +RETRY_DELAY=60 +PATTERNS=( + # https://github.com/canonical/chisel-releases/issues/765 + "cannot fetch from archive" + # https://github.com/canonical/chisel-releases/issues/766 + "cannot talk to archive" + # https://github.com/canonical/chisel-releases/issues/768 + "cannot find archive data" + # transient digest mismatch (chisel bug) + "expected digest" +) + +[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } +[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } + +release_dir=$1 +shift + +for attempt in $(seq 1 "$N_RETRIES"); do + err=$(chisel cut \ + --release "$release_dir" \ + --root "$CRAFT_PART_INSTALL" \ + "$@" 2>&1) && rc=0 || rc=$? + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$err" + exit 0 + fi + + matched="" + matched_line="" + for p in "${PATTERNS[@]}"; do + line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true + if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi + done + + if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then + echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 + echo " > $matched_line" >&2 + sleep "$RETRY_DELAY" + continue + fi + + printf '%s\n' "$err" >&2 + exit "$rc" +done diff --git a/rocks/bread-chisel-releases-24.04/rockcraft.yaml b/rocks/bread-chisel-releases-24.04/rockcraft.yaml index 74f8913..3c79ad6 100644 --- a/rocks/bread-chisel-releases-24.04/rockcraft.yaml +++ b/rocks/bread-chisel-releases-24.04/rockcraft.yaml @@ -27,37 +27,34 @@ services: startup: enabled parts: - # --- de-novo copy of the bread base (rocks can't build from rocks) ------- + # --- de-novo copy of the bread base (rocks can't build from rocks), with + # the chisel-releases tooling slices folded into the same single cut. ------ bread: plugin: nil - stage-packages: - - base-files_base - - base-files_etc - - base-files_var - - base-files_tmp - - base-files_lib - - base-files_home - - base-files_release-info - - base-files_chisel - - bash_bins - - coreutils_bins - - tar_bins - - gzip_bins - - base-passwd_data - - login_bins - - passwd_bins - - openssh-server_bins - - openssh-server_config - - ca-certificates_data - - libpam-modules_libs - - apt_apt-get - - apt_config - - apt_var - - apt_default-source-list - - dpkg_bins + build-snaps: + - chisel + build-packages: + - git override-build: | set -eu - craftctl default + + releases=$(mktemp -d) + git clone --depth 1 --branch ubuntu-24.04 \ + https://github.com/canonical/chisel-releases.git "$releases" + + # bread base slices + chisel-releases tooling slices, one cut. + # holes: tree + skopeo have no slice yet -> omitted. + bash "$CRAFT_PROJECT_DIR/hack/chisel_cut.sh" "$releases" \ + base-files_base base-files_etc base-files_var base-files_tmp \ + base-files_lib base-files_home base-files_release-info base-files_chisel \ + bash_bins coreutils_bins tar_bins gzip_bins \ + base-passwd_data login_bins passwd_bins \ + openssh-server_bins openssh-server_config ca-certificates_data \ + libpam-modules_libs \ + apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins \ + curl_bins wget_bins git_bins git_http-support jq_bins file_bins \ + sudo_bins sudo_config iproute2_ip-bin + cd "$CRAFT_PART_INSTALL" grep -q '^sshd:' etc/passwd || \ @@ -104,21 +101,6 @@ parts: printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ >> root/.bashrc - # --- chisel-releases tooling (slices where they exist) ------------------- - # holes: tree + skopeo have no slice yet -> omitted for now. - tooling: - plugin: nil - stage-packages: - - curl_bins - - wget_bins - - git_bins - - git_http-support - - jq_bins - - file_bins - - sudo_bins - - sudo_config - - iproute2_ip-bin - # --- chisel + chisel-hacked, built from source (go is build-only) -------- chisel: plugin: nil diff --git a/rocks/bread-chisel-releases-25.10/hack/chisel_cut.sh b/rocks/bread-chisel-releases-25.10/hack/chisel_cut.sh new file mode 100755 index 0000000..db2af91 --- /dev/null +++ b/rocks/bread-chisel-releases-25.10/hack/chisel_cut.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Wrapper around `chisel cut` with retry on known-flaky archive +# errors. Patterns lifted from canonical/chisel-releases' +# .github/scripts/install-slices/install_slices.py. +# +# Usage: chisel_cut.sh [ ...] +# Requires: +# - to be a chisel-releases checkout +# - $CRAFT_PART_INSTALL to be set (rockcraft env) +set -e + +N_RETRIES=10 +RETRY_DELAY=60 +PATTERNS=( + # https://github.com/canonical/chisel-releases/issues/765 + "cannot fetch from archive" + # https://github.com/canonical/chisel-releases/issues/766 + "cannot talk to archive" + # https://github.com/canonical/chisel-releases/issues/768 + "cannot find archive data" + # transient digest mismatch (chisel bug) + "expected digest" +) + +[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } +[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } + +release_dir=$1 +shift + +for attempt in $(seq 1 "$N_RETRIES"); do + err=$(chisel cut \ + --release "$release_dir" \ + --root "$CRAFT_PART_INSTALL" \ + "$@" 2>&1) && rc=0 || rc=$? + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$err" + exit 0 + fi + + matched="" + matched_line="" + for p in "${PATTERNS[@]}"; do + line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true + if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi + done + + if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then + echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 + echo " > $matched_line" >&2 + sleep "$RETRY_DELAY" + continue + fi + + printf '%s\n' "$err" >&2 + exit "$rc" +done diff --git a/rocks/bread-chisel-releases-25.10/rockcraft.yaml b/rocks/bread-chisel-releases-25.10/rockcraft.yaml index a023bba..e074cec 100644 --- a/rocks/bread-chisel-releases-25.10/rockcraft.yaml +++ b/rocks/bread-chisel-releases-25.10/rockcraft.yaml @@ -27,37 +27,34 @@ services: startup: enabled parts: - # --- de-novo copy of the bread base (rocks can't build from rocks) ------- + # --- de-novo copy of the bread base (rocks can't build from rocks), with + # the chisel-releases tooling slices folded into the same single cut. ------ bread: plugin: nil - stage-packages: - - base-files_base - - base-files_etc - - base-files_var - - base-files_tmp - - base-files_lib - - base-files_home - - base-files_release-info - - base-files_chisel - - bash_bins - - coreutils_bins - - tar_bins - - gzip_bins - - base-passwd_data - - login_bins - - passwd_bins - - openssh-server_bins - - openssh-server_config - - ca-certificates_data - - libpam-modules_libs - - apt_apt-get - - apt_config - - apt_var - - apt_default-source-list - - dpkg_bins + build-snaps: + - chisel + build-packages: + - git override-build: | set -eu - craftctl default + + releases=$(mktemp -d) + git clone --depth 1 --branch ubuntu-25.10 \ + https://github.com/canonical/chisel-releases.git "$releases" + + # bread base slices + chisel-releases tooling slices, one cut. + # holes: tree + skopeo have no slice yet -> omitted. + bash "$CRAFT_PROJECT_DIR/hack/chisel_cut.sh" "$releases" \ + base-files_base base-files_etc base-files_var base-files_tmp \ + base-files_lib base-files_home base-files_release-info base-files_chisel \ + bash_bins coreutils_bins tar_bins gzip_bins \ + base-passwd_data login_bins passwd_bins \ + openssh-server_bins openssh-server_config ca-certificates_data \ + libpam-modules_libs \ + apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins \ + curl_bins wget_bins git_bins git_http-support jq_bins file_bins \ + sudo_bins sudo_config iproute2_ip-bin + cd "$CRAFT_PART_INSTALL" grep -q '^sshd:' etc/passwd || \ @@ -104,21 +101,6 @@ parts: printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ >> root/.bashrc - # --- chisel-releases tooling (slices where they exist) ------------------- - # holes: tree + skopeo have no slice yet -> omitted for now. - tooling: - plugin: nil - stage-packages: - - curl_bins - - wget_bins - - git_bins - - git_http-support - - jq_bins - - file_bins - - sudo_bins - - sudo_config - - iproute2_ip-bin - # --- chisel + chisel-hacked, built from source (go is build-only) -------- chisel: plugin: nil diff --git a/rocks/bread-chisel-releases-26.04/hack/chisel_cut.sh b/rocks/bread-chisel-releases-26.04/hack/chisel_cut.sh new file mode 100755 index 0000000..db2af91 --- /dev/null +++ b/rocks/bread-chisel-releases-26.04/hack/chisel_cut.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Wrapper around `chisel cut` with retry on known-flaky archive +# errors. Patterns lifted from canonical/chisel-releases' +# .github/scripts/install-slices/install_slices.py. +# +# Usage: chisel_cut.sh [ ...] +# Requires: +# - to be a chisel-releases checkout +# - $CRAFT_PART_INSTALL to be set (rockcraft env) +set -e + +N_RETRIES=10 +RETRY_DELAY=60 +PATTERNS=( + # https://github.com/canonical/chisel-releases/issues/765 + "cannot fetch from archive" + # https://github.com/canonical/chisel-releases/issues/766 + "cannot talk to archive" + # https://github.com/canonical/chisel-releases/issues/768 + "cannot find archive data" + # transient digest mismatch (chisel bug) + "expected digest" +) + +[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } +[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } + +release_dir=$1 +shift + +for attempt in $(seq 1 "$N_RETRIES"); do + err=$(chisel cut \ + --release "$release_dir" \ + --root "$CRAFT_PART_INSTALL" \ + "$@" 2>&1) && rc=0 || rc=$? + if [ "$rc" -eq 0 ]; then + printf '%s\n' "$err" + exit 0 + fi + + matched="" + matched_line="" + for p in "${PATTERNS[@]}"; do + line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true + if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi + done + + if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then + echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 + echo " > $matched_line" >&2 + sleep "$RETRY_DELAY" + continue + fi + + printf '%s\n' "$err" >&2 + exit "$rc" +done diff --git a/rocks/bread-chisel-releases-26.04/rockcraft.yaml b/rocks/bread-chisel-releases-26.04/rockcraft.yaml index 2e24af9..8048d3b 100644 --- a/rocks/bread-chisel-releases-26.04/rockcraft.yaml +++ b/rocks/bread-chisel-releases-26.04/rockcraft.yaml @@ -27,37 +27,34 @@ services: startup: enabled parts: - # --- de-novo copy of the bread base (rocks can't build from rocks) ------- + # --- de-novo copy of the bread base (rocks can't build from rocks), with + # the chisel-releases tooling slices folded into the same single cut. ------ bread: plugin: nil - stage-packages: - - base-files_base - - base-files_etc - - base-files_var - - base-files_tmp - - base-files_lib - - base-files_home - - base-files_release-info - - base-files_chisel - - bash_bins - - coreutils_bins - - tar_bins - - gzip_bins - - base-passwd_data - - login_bins - - passwd_bins - - openssh-server_bins - - openssh-server_config - - ca-certificates_data - - libpam-modules_libs - - apt_apt-get - - apt_config - - apt_var - - apt_default-source-list - - dpkg_bins + build-snaps: + - chisel + build-packages: + - git override-build: | set -eu - craftctl default + + releases=$(mktemp -d) + git clone --depth 1 --branch ubuntu-26.04 \ + https://github.com/canonical/chisel-releases.git "$releases" + + # bread base slices + chisel-releases tooling slices, one cut. + # holes: tree + skopeo have no slice yet -> omitted. + bash "$CRAFT_PROJECT_DIR/hack/chisel_cut.sh" "$releases" \ + base-files_base base-files_etc base-files_var base-files_tmp \ + base-files_lib base-files_home base-files_release-info base-files_chisel \ + bash_bins coreutils_bins tar_bins gzip_bins \ + base-passwd_data login_bins passwd_bins \ + openssh-server_bins openssh-server_config ca-certificates_data \ + libpam-modules_libs \ + apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins \ + curl_bins wget_bins git_bins git_http-support jq_bins file_bins \ + sudo_bins sudo_config iproute2_ip-bin + cd "$CRAFT_PART_INSTALL" grep -q '^sshd:' etc/passwd || \ @@ -104,21 +101,6 @@ parts: printf '\n# bread-warning\n[ -r /etc/profile.d/00-bread-warning.sh ] && . /etc/profile.d/00-bread-warning.sh\n' \ >> root/.bashrc - # --- chisel-releases tooling (slices where they exist) ------------------- - # holes: tree + skopeo have no slice yet -> omitted for now. - tooling: - plugin: nil - stage-packages: - - curl_bins - - wget_bins - - git_bins - - git_http-support - - jq_bins - - file_bins - - sudo_bins - - sudo_config - - iproute2_ip-bin - # --- chisel + chisel-hacked, built from source (go is build-only) -------- chisel: plugin: nil From 25d9eabc976dc8c6d58fc3840c8b10719b824639 Mon Sep 17 00:00:00 2001 From: lczyk Date: Mon, 1 Jun 2026 21:45:29 +0100 Subject: [PATCH 03/18] fix: use iproute2_bins slice (ip-bin is 26.04-only) iproute2_ip-bin doesn't exist on the ubuntu-24.04/25.10 chisel-releases branches (only the unified iproute2_bins, which ships ip); on 26.04 iproute2_bins pulls ip-bin transitively. so iproute2_bins resolves ip on all three. unblocks the bread-chisel-releases rock pack. --- rocks/bread-chisel-releases-24.04/rockcraft.yaml | 2 +- rocks/bread-chisel-releases-25.10/rockcraft.yaml | 2 +- rocks/bread-chisel-releases-26.04/rockcraft.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rocks/bread-chisel-releases-24.04/rockcraft.yaml b/rocks/bread-chisel-releases-24.04/rockcraft.yaml index 3c79ad6..5cca426 100644 --- a/rocks/bread-chisel-releases-24.04/rockcraft.yaml +++ b/rocks/bread-chisel-releases-24.04/rockcraft.yaml @@ -53,7 +53,7 @@ parts: libpam-modules_libs \ apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins \ curl_bins wget_bins git_bins git_http-support jq_bins file_bins \ - sudo_bins sudo_config iproute2_ip-bin + sudo_bins sudo_config iproute2_bins cd "$CRAFT_PART_INSTALL" diff --git a/rocks/bread-chisel-releases-25.10/rockcraft.yaml b/rocks/bread-chisel-releases-25.10/rockcraft.yaml index e074cec..41ea737 100644 --- a/rocks/bread-chisel-releases-25.10/rockcraft.yaml +++ b/rocks/bread-chisel-releases-25.10/rockcraft.yaml @@ -53,7 +53,7 @@ parts: libpam-modules_libs \ apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins \ curl_bins wget_bins git_bins git_http-support jq_bins file_bins \ - sudo_bins sudo_config iproute2_ip-bin + sudo_bins sudo_config iproute2_bins cd "$CRAFT_PART_INSTALL" diff --git a/rocks/bread-chisel-releases-26.04/rockcraft.yaml b/rocks/bread-chisel-releases-26.04/rockcraft.yaml index 8048d3b..77b515c 100644 --- a/rocks/bread-chisel-releases-26.04/rockcraft.yaml +++ b/rocks/bread-chisel-releases-26.04/rockcraft.yaml @@ -53,7 +53,7 @@ parts: libpam-modules_libs \ apt_apt-get apt_config apt_var apt_default-source-list dpkg_bins \ curl_bins wget_bins git_bins git_http-support jq_bins file_bins \ - sudo_bins sudo_config iproute2_ip-bin + sudo_bins sudo_config iproute2_bins cd "$CRAFT_PART_INSTALL" From 31cbacd3415304fddeddd29295e9ddd9f9241cc9 Mon Sep 17 00:00:00 2001 From: lczyk Date: Mon, 1 Jun 2026 23:31:11 +0100 Subject: [PATCH 04/18] fix: nest contract task under run/ so spread discovers it spread finds tasks as //task.yaml; the contract task sat directly at contract/task.yaml, so no jobs existed and the filter matched nothing. move it to contract/run/task.yaml. --- rocks/tests/contract/{ => run}/task.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rocks/tests/contract/{ => run}/task.yaml (100%) diff --git a/rocks/tests/contract/task.yaml b/rocks/tests/contract/run/task.yaml similarity index 100% rename from rocks/tests/contract/task.yaml rename to rocks/tests/contract/run/task.yaml From 933475712d604240f472a960145b071be708103d Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 00:00:13 +0100 Subject: [PATCH 05/18] fix: grep-free contract task + bounded allocate with diagnostics the rock has no grep (coreutils_bins doesn't ship it), so the contract task aborted at the banner check; rewrite it to use shell case instead of grep/sed. allocate: bound the sshd wait to 60s and, on timeout, dump docker logs + sshd -t + the sshd bits, then fail fast -- so a rock whose sshd never starts (25.10/26.04) surfaces the cause instead of spinning to the kill-timeout. --- rocks/tests/allocate.sh | 24 +++++++++++++++++++++- rocks/tests/contract/run/task.yaml | 32 ++++++++++++++---------------- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/rocks/tests/allocate.sh b/rocks/tests/allocate.sh index a5ca513..e717dd6 100755 --- a/rocks/tests/allocate.sh +++ b/rocks/tests/allocate.sh @@ -38,6 +38,28 @@ docker run \ --name "$container_name" \ -d "$tag" -until docker exec "$container_name" pgrep sshd >/dev/null 2>&1; do sleep 1; done +# Bounded wait for sshd. On timeout, dump pebble/sshd output (docker logs) + +# a config check so ci shows WHY sshd didn't come up, then fail fast instead of +# letting spread spin to the kill-timeout. +ready= +for _ in $(seq 1 60); do + if docker exec "$container_name" pgrep sshd >/dev/null 2>&1; then + ready=1 + break + fi + sleep 1 +done + +if [ -z "$ready" ]; then + echo "ERROR: sshd did not come up in $container_name (60s)" >&2 + echo "--- docker logs $container_name ---" >&2 + docker logs "$container_name" >&2 2>&1 || true + echo "--- sshd -t (config test) ---" >&2 + docker exec "$container_name" /usr/sbin/sshd -t >&2 2>&1 || true + echo "--- ls /usr/sbin/sshd /usr/lib/openssh /run/sshd ---" >&2 + docker exec "$container_name" ls -l /usr/sbin/sshd /usr/lib/openssh /run/sshd >&2 2>&1 || true + docker rm -f "$container_name" >/dev/null 2>&1 || true + exit 1 +fi ADDRESS "$(docker inspect "$container_name" --format '{{.NetworkSettings.Networks.bridge.IPAddress}}')" diff --git a/rocks/tests/contract/run/task.yaml b/rocks/tests/contract/run/task.yaml index 7d8a9f3..9a4b32a 100644 --- a/rocks/tests/contract/run/task.yaml +++ b/rocks/tests/contract/run/task.yaml @@ -3,6 +3,8 @@ summary: rock contract -- version/arch + bread bits, plus chisel/tooling for the execute: | set -e + # grep is NOT in the rock (coreutils_bins doesn't ship it), so this task + # uses only shell builtins + coreutils -- no grep/sed. name="$SPREAD_SYSTEM" arch="${name##*-}" rest="${name%-*}" @@ -31,34 +33,30 @@ execute: | command -v bash >/dev/null || { echo "missing bash"; exit 1; } command -v ls >/dev/null || { echo "missing coreutils (ls)"; exit 1; } - # --- bread-warning banner: file present + prints exactly once on a forced - # login shell, silent without a tty. + # --- bread-warning banner: present on a forced login shell, silent on non-tty. test -r /etc/profile.d/00-bread-warning.sh || { echo "missing /etc/profile.d/00-bread-warning.sh"; exit 1; } out=$(BREAD_BANNER_FORCE=1 bash -l -c true 2>&1) - count=$(echo "$out" | grep -c "INSECURE TEST IMAGE" || true) - if [ "$count" != "1" ]; then - echo "banner print count: expected=1 got=$count" - echo "--- $out ---" - exit 1 - fi + case "$out" in + *"INSECURE TEST IMAGE"*) : ;; + *) echo "banner missing on forced login shell"; echo "--- $out ---"; exit 1 ;; + esac silent=$(bash -l -c true 2>&1) - if echo "$silent" | grep -q "INSECURE TEST IMAGE"; then - echo "banner leaked on non-tty"; exit 1 - fi + case "$silent" in + *"INSECURE TEST IMAGE"*) echo "banner leaked on non-tty"; exit 1 ;; + esac - # --- apt: update resolves, and the lazy-apt shim fires (a non-update call - # runs apt-get update first, then replaces itself with a symlink to real apt). + # --- apt: update resolves, and the lazy-apt shim self-replaces with a symlink. apt-get update [ -L /usr/local/bin/apt-get ] || { echo "lazy-apt shim did not self-replace"; exit 1; } # --- chisel-releases flavour extras. case "$flavour" in *chisel*) - chisel --version | grep -q "v1\.4\.1" || { - echo "chisel missing or wrong version"; chisel --version || true; exit 1; } - chisel-hacked --version | grep -q "v1\.4\.1-hacked" || { - echo "chisel-hacked missing or wrong version"; chisel-hacked --version || true; exit 1; } + cv=$(chisel --version 2>&1) + case "$cv" in *v1.4.1*) : ;; *) echo "chisel ver wrong: $cv"; exit 1 ;; esac + chv=$(chisel-hacked --version 2>&1) + case "$chv" in *v1.4.1-hacked*) : ;; *) echo "chisel-hacked ver wrong: $chv"; exit 1 ;; esac # tree + skopeo are holes (no slice yet) -> not checked. for cmd in curl wget git jq file sudo ip docker; do command -v "$cmd" >/dev/null || { echo "missing command: $cmd"; exit 1; } From 8d1125f62d714fc8f242d6e25c46e7750c926185 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 00:53:52 +0100 Subject: [PATCH 06/18] fix: robust banner check + TCP-22 allocate probe with diagnostics banner: source the profile.d script directly instead of via a login shell -- a chiselled rock's login shell doesn't source /etc/profile -> profile.d (separate cosmetic hole), so the login-shell path gave empty output. assert the files are installed and the script prints the banner when sourced. allocate: gate readiness on an actual TCP connect to port 22 rather than pgrep sshd -- a process can exist without listening, which false-readied 25.10/26.04 and spun spread to the kill-timeout. on timeout, dump docker logs plus sshd -t plus the sshd bits so a non-listening sshd surfaces its cause. --- rocks/tests/allocate.sh | 21 +++++++++++---------- rocks/tests/contract/run/task.yaml | 14 +++++++------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/rocks/tests/allocate.sh b/rocks/tests/allocate.sh index e717dd6..20909dc 100755 --- a/rocks/tests/allocate.sh +++ b/rocks/tests/allocate.sh @@ -38,12 +38,15 @@ docker run \ --name "$container_name" \ -d "$tag" -# Bounded wait for sshd. On timeout, dump pebble/sshd output (docker logs) + -# a config check so ci shows WHY sshd didn't come up, then fail fast instead of -# letting spread spin to the kill-timeout. +ip=$(docker inspect "$container_name" --format '{{.NetworkSettings.Networks.bridge.IPAddress}}') + +# Bounded wait for sshd to actually accept TCP on :22 (pgrep is not enough -- a +# process can exist without listening). On timeout, dump pebble/sshd output so +# ci shows WHY sshd isn't listening, then fail fast instead of letting spread +# spin to the kill-timeout. ready= for _ in $(seq 1 60); do - if docker exec "$container_name" pgrep sshd >/dev/null 2>&1; then + if timeout 1 bash -c "echo > /dev/tcp/$ip/22" 2>/dev/null; then ready=1 break fi @@ -51,15 +54,13 @@ for _ in $(seq 1 60); do done if [ -z "$ready" ]; then - echo "ERROR: sshd did not come up in $container_name (60s)" >&2 + echo "ERROR: sshd not listening on $container_name ($ip:22) after 60s" >&2 echo "--- docker logs $container_name ---" >&2 docker logs "$container_name" >&2 2>&1 || true - echo "--- sshd -t (config test) ---" >&2 - docker exec "$container_name" /usr/sbin/sshd -t >&2 2>&1 || true - echo "--- ls /usr/sbin/sshd /usr/lib/openssh /run/sshd ---" >&2 - docker exec "$container_name" ls -l /usr/sbin/sshd /usr/lib/openssh /run/sshd >&2 2>&1 || true + echo "--- pebble services + sshd -t + sshd bits ---" >&2 + docker exec "$container_name" sh -c 'pgrep -a sshd; /usr/sbin/sshd -t; ls -l /usr/sbin/sshd /usr/lib/openssh /run/sshd /etc/ssh' >&2 2>&1 || true docker rm -f "$container_name" >/dev/null 2>&1 || true exit 1 fi -ADDRESS "$(docker inspect "$container_name" --format '{{.NetworkSettings.Networks.bridge.IPAddress}}')" +ADDRESS "$ip" diff --git a/rocks/tests/contract/run/task.yaml b/rocks/tests/contract/run/task.yaml index 9a4b32a..fec3156 100644 --- a/rocks/tests/contract/run/task.yaml +++ b/rocks/tests/contract/run/task.yaml @@ -33,17 +33,17 @@ execute: | command -v bash >/dev/null || { echo "missing bash"; exit 1; } command -v ls >/dev/null || { echo "missing coreutils (ls)"; exit 1; } - # --- bread-warning banner: present on a forced login shell, silent on non-tty. + # --- bread-warning banner: files installed + the script prints the banner when + # sourced with FORCE. Sourced directly rather than via `bash -l` -- whether a + # login shell sources /etc/profile -> profile.d in the chiselled rock is a + # separate (cosmetic) hole; this checks the banner machinery itself. test -r /etc/profile.d/00-bread-warning.sh || { echo "missing /etc/profile.d/00-bread-warning.sh"; exit 1; } - out=$(BREAD_BANNER_FORCE=1 bash -l -c true 2>&1) + test -r /etc/bread-banner.txt || { echo "missing /etc/bread-banner.txt"; exit 1; } + out=$(BREAD_BANNER_FORCE=1 bash -c '. /etc/profile.d/00-bread-warning.sh' 2>&1) case "$out" in *"INSECURE TEST IMAGE"*) : ;; - *) echo "banner missing on forced login shell"; echo "--- $out ---"; exit 1 ;; - esac - silent=$(bash -l -c true 2>&1) - case "$silent" in - *"INSECURE TEST IMAGE"*) echo "banner leaked on non-tty"; exit 1 ;; + *) echo "banner did not print when sourced"; echo "--- $out ---"; exit 1 ;; esac # --- apt: update resolves, and the lazy-apt shim self-replaces with a symlink. From 5661ebc4ec7c2d05fd921536d637aed7528c24f8 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 01:02:25 +0100 Subject: [PATCH 07/18] fix: add sshd-session + sshd-auth to openssh slice on 25.10/26.04 OpenSSH >=9.8 (25.10, 26.04) re-execs sshd-session (and sshd-auth) per connection; the chisel-releases openssh-server slice lists only /usr/sbin/sshd, so sshd exits 255 at startup and never listens. patch the cloned slice (sed) to add both binaries before cutting -- only on the newer series; 24.04 ships OpenSSH 9.6 which predates the split and has no such binaries. --- rocks/bread-25.10/rockcraft.yaml | 9 +++++++++ rocks/bread-26.04/rockcraft.yaml | 9 +++++++++ rocks/bread-chisel-releases-25.10/rockcraft.yaml | 9 +++++++++ rocks/bread-chisel-releases-26.04/rockcraft.yaml | 9 +++++++++ 4 files changed, 36 insertions(+) diff --git a/rocks/bread-25.10/rockcraft.yaml b/rocks/bread-25.10/rockcraft.yaml index 5534a67..f680333 100644 --- a/rocks/bread-25.10/rockcraft.yaml +++ b/rocks/bread-25.10/rockcraft.yaml @@ -45,6 +45,15 @@ parts: git clone --depth 1 --branch ubuntu-25.10 \ https://github.com/canonical/chisel-releases.git "$releases" + # OpenSSH >=9.8 (this release) re-execs sshd-session + sshd-auth per + # connection; the openssh-server slice ships only /usr/sbin/sshd, so sshd + # exits 255 at startup. Add them to the cloned slice before cutting. + # (24.04's OpenSSH 9.6 predates the split and has no such binaries.) + sed -i \ + -e '\#^ /usr/sbin/sshd:#a\ /usr/lib/openssh/sshd-session:' \ + -e '\#^ /usr/sbin/sshd:#a\ /usr/lib/openssh/sshd-auth:' \ + "$releases/slices/openssh-server.yaml" + # tar + gzip: needed by spread to sync the project onto the system over # ssh (not part of coreutils). dpkg_bins: apt_apt-get only pulls # dpkg_maintscript-support, so apt-get install can't unpack without it. diff --git a/rocks/bread-26.04/rockcraft.yaml b/rocks/bread-26.04/rockcraft.yaml index 1f5315b..98d2a61 100644 --- a/rocks/bread-26.04/rockcraft.yaml +++ b/rocks/bread-26.04/rockcraft.yaml @@ -45,6 +45,15 @@ parts: git clone --depth 1 --branch ubuntu-26.04 \ https://github.com/canonical/chisel-releases.git "$releases" + # OpenSSH >=9.8 (this release) re-execs sshd-session + sshd-auth per + # connection; the openssh-server slice ships only /usr/sbin/sshd, so sshd + # exits 255 at startup. Add them to the cloned slice before cutting. + # (24.04's OpenSSH 9.6 predates the split and has no such binaries.) + sed -i \ + -e '\#^ /usr/sbin/sshd:#a\ /usr/lib/openssh/sshd-session:' \ + -e '\#^ /usr/sbin/sshd:#a\ /usr/lib/openssh/sshd-auth:' \ + "$releases/slices/openssh-server.yaml" + # tar + gzip: needed by spread to sync the project onto the system over # ssh (not part of coreutils). dpkg_bins: apt_apt-get only pulls # dpkg_maintscript-support, so apt-get install can't unpack without it. diff --git a/rocks/bread-chisel-releases-25.10/rockcraft.yaml b/rocks/bread-chisel-releases-25.10/rockcraft.yaml index 41ea737..292b67a 100644 --- a/rocks/bread-chisel-releases-25.10/rockcraft.yaml +++ b/rocks/bread-chisel-releases-25.10/rockcraft.yaml @@ -42,6 +42,15 @@ parts: git clone --depth 1 --branch ubuntu-25.10 \ https://github.com/canonical/chisel-releases.git "$releases" + # OpenSSH >=9.8 (this release) re-execs sshd-session + sshd-auth per + # connection; the openssh-server slice ships only /usr/sbin/sshd, so sshd + # exits 255 at startup. Add them to the cloned slice before cutting. + # (24.04's OpenSSH 9.6 predates the split and has no such binaries.) + sed -i \ + -e '\#^ /usr/sbin/sshd:#a\ /usr/lib/openssh/sshd-session:' \ + -e '\#^ /usr/sbin/sshd:#a\ /usr/lib/openssh/sshd-auth:' \ + "$releases/slices/openssh-server.yaml" + # bread base slices + chisel-releases tooling slices, one cut. # holes: tree + skopeo have no slice yet -> omitted. bash "$CRAFT_PROJECT_DIR/hack/chisel_cut.sh" "$releases" \ diff --git a/rocks/bread-chisel-releases-26.04/rockcraft.yaml b/rocks/bread-chisel-releases-26.04/rockcraft.yaml index 77b515c..741baae 100644 --- a/rocks/bread-chisel-releases-26.04/rockcraft.yaml +++ b/rocks/bread-chisel-releases-26.04/rockcraft.yaml @@ -42,6 +42,15 @@ parts: git clone --depth 1 --branch ubuntu-26.04 \ https://github.com/canonical/chisel-releases.git "$releases" + # OpenSSH >=9.8 (this release) re-execs sshd-session + sshd-auth per + # connection; the openssh-server slice ships only /usr/sbin/sshd, so sshd + # exits 255 at startup. Add them to the cloned slice before cutting. + # (24.04's OpenSSH 9.6 predates the split and has no such binaries.) + sed -i \ + -e '\#^ /usr/sbin/sshd:#a\ /usr/lib/openssh/sshd-session:' \ + -e '\#^ /usr/sbin/sshd:#a\ /usr/lib/openssh/sshd-auth:' \ + "$releases/slices/openssh-server.yaml" + # bread base slices + chisel-releases tooling slices, one cut. # holes: tree + skopeo have no slice yet -> omitted. bash "$CRAFT_PROJECT_DIR/hack/chisel_cut.sh" "$releases" \ From d8abfc024d47399f8f284a99aeb80e02185d20bf Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 01:14:40 +0100 Subject: [PATCH 08/18] fix: clear BREAD_BANNER_SHOWN in banner contract check the session shell already sources /etc/profile -> profile.d, whose silent non-tty path exports BREAD_BANNER_SHOWN=1. so the contract's re-source hit the dedupe guard and printed nothing. clear the var in the check so the banner actually re-prints. --- rocks/tests/contract/run/task.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rocks/tests/contract/run/task.yaml b/rocks/tests/contract/run/task.yaml index fec3156..168f7e8 100644 --- a/rocks/tests/contract/run/task.yaml +++ b/rocks/tests/contract/run/task.yaml @@ -33,14 +33,14 @@ execute: | command -v bash >/dev/null || { echo "missing bash"; exit 1; } command -v ls >/dev/null || { echo "missing coreutils (ls)"; exit 1; } - # --- bread-warning banner: files installed + the script prints the banner when - # sourced with FORCE. Sourced directly rather than via `bash -l` -- whether a - # login shell sources /etc/profile -> profile.d in the chiselled rock is a - # separate (cosmetic) hole; this checks the banner machinery itself. + # --- bread-warning banner: files installed + the script prints when sourced. + # spread's (non-tty) session shell already sourced /etc/profile -> profile.d, + # whose silent path exports BREAD_BANNER_SHOWN=1 -- so clear it here, else the + # dedupe guard makes the re-source a no-op. test -r /etc/profile.d/00-bread-warning.sh || { echo "missing /etc/profile.d/00-bread-warning.sh"; exit 1; } test -r /etc/bread-banner.txt || { echo "missing /etc/bread-banner.txt"; exit 1; } - out=$(BREAD_BANNER_FORCE=1 bash -c '. /etc/profile.d/00-bread-warning.sh' 2>&1) + out=$(BREAD_BANNER_SHOWN= BREAD_BANNER_FORCE=1 bash -c '. /etc/profile.d/00-bread-warning.sh' 2>&1) case "$out" in *"INSECURE TEST IMAGE"*) : ;; *) echo "banner did not print when sourced"; echo "--- $out ---"; exit 1 ;; From a81e6eee2c43a58bf21b6454d84049d53ce1ca7a Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 08:22:47 +0100 Subject: [PATCH 09/18] refactor: dedupe rock hack/patches into shared dir + ruby sync the hack scripts + chisel patches were copied into every rock dir. move them to a single source of truth (rocks/hack, rocks/patches) and regenerate each rock's own copy from there via rocks/sync_shared.rb -- rockcraft only copies the project dir into the build, so each rock still needs its physical copy at pack time, but it's no longer hand-maintained in 6 places. per-dir hack/ + patches/ are now gitignored + synced. the per-dir makefiles run the sync before packing (.rock.stamp depends on it); the ci build job runs it before rockcraft-pack. byte-identical output, so rocks build/test unchanged. --- .github/workflows/rocks-ci.yaml | 3 + .gitignore | 5 ++ rocks/bread-24.04/makefile | 6 +- rocks/bread-25.10/hack/banner.txt | 13 ----- rocks/bread-25.10/hack/bread-warning.sh | 39 ------------- rocks/bread-25.10/hack/chisel_cut.sh | 57 ------------------ rocks/bread-25.10/hack/hash_inputs.sh | 16 ----- rocks/bread-25.10/hack/lazy-apt.sh | 21 ------- rocks/bread-25.10/hack/sshd-entry.sh | 13 ----- rocks/bread-25.10/makefile | 6 +- rocks/bread-26.04/hack/banner.txt | 13 ----- rocks/bread-26.04/hack/bread-warning.sh | 39 ------------- rocks/bread-26.04/hack/chisel_cut.sh | 57 ------------------ rocks/bread-26.04/hack/hash_inputs.sh | 16 ----- rocks/bread-26.04/hack/lazy-apt.sh | 21 ------- rocks/bread-26.04/hack/sshd-entry.sh | 13 ----- rocks/bread-26.04/makefile | 6 +- .../hack/banner.txt | 13 ----- .../hack/bread-warning.sh | 39 ------------- .../hack/chisel_cut.sh | 57 ------------------ .../hack/hash_inputs.sh | 16 ----- .../hack/lazy-apt.sh | 21 ------- .../hack/sshd-entry.sh | 13 ----- rocks/bread-chisel-releases-24.04/makefile | 6 +- .../hack/banner.txt | 13 ----- .../hack/bread-warning.sh | 39 ------------- .../hack/chisel_cut.sh | 57 ------------------ .../hack/chisel_override_build.sh | 43 -------------- .../hack/docker_override_build.sh | 28 --------- .../hack/hash_inputs.sh | 16 ----- .../hack/lazy-apt.sh | 21 ------- .../hack/sshd-entry.sh | 13 ----- rocks/bread-chisel-releases-25.10/makefile | 6 +- ...p-maintenance-check-via-CHISEL_HACKS.patch | 49 ---------------- ...ve-maintenance-check-via-CHISEL_HACK.patch | 58 ------------------- ...-release-validation-via-CHISEL_HACKS.patch | 27 --------- .../hack/banner.txt | 13 ----- .../hack/bread-warning.sh | 39 ------------- .../hack/chisel_cut.sh | 57 ------------------ .../hack/chisel_override_build.sh | 43 -------------- .../hack/docker_override_build.sh | 28 --------- .../hack/hash_inputs.sh | 16 ----- .../hack/lazy-apt.sh | 21 ------- .../hack/sshd-entry.sh | 13 ----- rocks/bread-chisel-releases-26.04/makefile | 6 +- ...p-maintenance-check-via-CHISEL_HACKS.patch | 49 ---------------- ...ve-maintenance-check-via-CHISEL_HACK.patch | 58 ------------------- ...-release-validation-via-CHISEL_HACKS.patch | 27 --------- rocks/{bread-24.04 => }/hack/banner.txt | 0 rocks/{bread-24.04 => }/hack/bread-warning.sh | 0 rocks/{bread-24.04 => }/hack/chisel_cut.sh | 0 .../hack/chisel_override_build.sh | 0 .../hack/docker_override_build.sh | 0 rocks/{bread-24.04 => }/hack/hash_inputs.sh | 0 rocks/{bread-24.04 => }/hack/lazy-apt.sh | 0 rocks/{bread-24.04 => }/hack/sshd-entry.sh | 0 ...p-maintenance-check-via-CHISEL_HACKS.patch | 0 ...ve-maintenance-check-via-CHISEL_HACK.patch | 0 ...-release-validation-via-CHISEL_HACKS.patch | 0 rocks/sync_shared.rb | 40 +++++++++++++ 60 files changed, 78 insertions(+), 1211 deletions(-) delete mode 100644 rocks/bread-25.10/hack/banner.txt delete mode 100755 rocks/bread-25.10/hack/bread-warning.sh delete mode 100755 rocks/bread-25.10/hack/chisel_cut.sh delete mode 100755 rocks/bread-25.10/hack/hash_inputs.sh delete mode 100755 rocks/bread-25.10/hack/lazy-apt.sh delete mode 100755 rocks/bread-25.10/hack/sshd-entry.sh delete mode 100644 rocks/bread-26.04/hack/banner.txt delete mode 100755 rocks/bread-26.04/hack/bread-warning.sh delete mode 100755 rocks/bread-26.04/hack/chisel_cut.sh delete mode 100755 rocks/bread-26.04/hack/hash_inputs.sh delete mode 100755 rocks/bread-26.04/hack/lazy-apt.sh delete mode 100755 rocks/bread-26.04/hack/sshd-entry.sh delete mode 100644 rocks/bread-chisel-releases-24.04/hack/banner.txt delete mode 100755 rocks/bread-chisel-releases-24.04/hack/bread-warning.sh delete mode 100755 rocks/bread-chisel-releases-24.04/hack/chisel_cut.sh delete mode 100755 rocks/bread-chisel-releases-24.04/hack/hash_inputs.sh delete mode 100755 rocks/bread-chisel-releases-24.04/hack/lazy-apt.sh delete mode 100755 rocks/bread-chisel-releases-24.04/hack/sshd-entry.sh delete mode 100644 rocks/bread-chisel-releases-25.10/hack/banner.txt delete mode 100755 rocks/bread-chisel-releases-25.10/hack/bread-warning.sh delete mode 100755 rocks/bread-chisel-releases-25.10/hack/chisel_cut.sh delete mode 100755 rocks/bread-chisel-releases-25.10/hack/chisel_override_build.sh delete mode 100755 rocks/bread-chisel-releases-25.10/hack/docker_override_build.sh delete mode 100755 rocks/bread-chisel-releases-25.10/hack/hash_inputs.sh delete mode 100755 rocks/bread-chisel-releases-25.10/hack/lazy-apt.sh delete mode 100755 rocks/bread-chisel-releases-25.10/hack/sshd-entry.sh delete mode 100644 rocks/bread-chisel-releases-25.10/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch delete mode 100644 rocks/bread-chisel-releases-25.10/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch delete mode 100644 rocks/bread-chisel-releases-25.10/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch delete mode 100644 rocks/bread-chisel-releases-26.04/hack/banner.txt delete mode 100755 rocks/bread-chisel-releases-26.04/hack/bread-warning.sh delete mode 100755 rocks/bread-chisel-releases-26.04/hack/chisel_cut.sh delete mode 100755 rocks/bread-chisel-releases-26.04/hack/chisel_override_build.sh delete mode 100755 rocks/bread-chisel-releases-26.04/hack/docker_override_build.sh delete mode 100755 rocks/bread-chisel-releases-26.04/hack/hash_inputs.sh delete mode 100755 rocks/bread-chisel-releases-26.04/hack/lazy-apt.sh delete mode 100755 rocks/bread-chisel-releases-26.04/hack/sshd-entry.sh delete mode 100644 rocks/bread-chisel-releases-26.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch delete mode 100644 rocks/bread-chisel-releases-26.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch delete mode 100644 rocks/bread-chisel-releases-26.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch rename rocks/{bread-24.04 => }/hack/banner.txt (100%) rename rocks/{bread-24.04 => }/hack/bread-warning.sh (100%) rename rocks/{bread-24.04 => }/hack/chisel_cut.sh (100%) rename rocks/{bread-chisel-releases-24.04 => }/hack/chisel_override_build.sh (100%) rename rocks/{bread-chisel-releases-24.04 => }/hack/docker_override_build.sh (100%) rename rocks/{bread-24.04 => }/hack/hash_inputs.sh (100%) rename rocks/{bread-24.04 => }/hack/lazy-apt.sh (100%) rename rocks/{bread-24.04 => }/hack/sshd-entry.sh (100%) rename rocks/{bread-chisel-releases-24.04 => }/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch (100%) rename rocks/{bread-chisel-releases-24.04 => }/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch (100%) rename rocks/{bread-chisel-releases-24.04 => }/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch (100%) create mode 100644 rocks/sync_shared.rb diff --git a/.github/workflows/rocks-ci.yaml b/.github/workflows/rocks-ci.yaml index d447a2e..fdf0c81 100644 --- a/.github/workflows/rocks-ci.yaml +++ b/.github/workflows/rocks-ci.yaml @@ -51,6 +51,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Sync shared hack/ + patches/ into the rock dir + run: ruby rocks/sync_shared.rb ${{ matrix.dir }} + - name: Pack rock uses: canonical/craft-actions/rockcraft-pack@main with: diff --git a/.gitignore b/.gitignore index 0fc9e81..907c629 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,10 @@ cache/ # rockcraft (rocks/) build artefacts *.rock .rock.stamp + +# per-rock hack/ + patches/ are synced from rocks/hack + rocks/patches +# (see rocks/sync_shared.rb) -- only the shared source of truth is tracked. +rocks/bread-*/hack/ +rocks/bread-*/patches/ __pycache__/ *.pyc diff --git a/rocks/bread-24.04/makefile b/rocks/bread-24.04/makefile index 737225a..1bcf932 100644 --- a/rocks/bread-24.04/makefile +++ b/rocks/bread-24.04/makefile @@ -18,7 +18,11 @@ FORCE: # Stamp content = hash of all build inputs; only rewritten when it changes, so # the rock only repacks on a real input change. -.rock.stamp: FORCE +.PHONY: sync +sync: ## Populate hack/ (+ patches/) from shared rocks/hack + @ruby ../sync_shared.rb $(notdir $(CURDIR)) + +.rock.stamp: sync FORCE @new=$$(hack/hash_inputs.sh) ; \ cur=$$(cat $@ 2>/dev/null || true) ; \ [ "$$new" = "$$cur" ] || echo "$$new" > $@ diff --git a/rocks/bread-25.10/hack/banner.txt b/rocks/bread-25.10/hack/banner.txt deleted file mode 100644 index 3c06b8b..0000000 --- a/rocks/bread-25.10/hack/banner.txt +++ /dev/null @@ -1,13 +0,0 @@ - ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ - ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ - ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ -███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ -███▒▒░░░░░░░ ░░▒▒▓▓▓▓ -███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ - ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ - ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ - ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ - ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ - ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ - ███▒▒░░░░░░░ ░░░░░░░▓▓ - █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-25.10/hack/bread-warning.sh b/rocks/bread-25.10/hack/bread-warning.sh deleted file mode 100755 index 2ee0140..0000000 --- a/rocks/bread-25.10/hack/bread-warning.sh +++ /dev/null @@ -1,39 +0,0 @@ -# bread-warning: insecure-image notice. -# -# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive -# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same -# session. Silent on non-tty (spread, scripts, ci logs) unless -# BREAD_BANNER_FORCE is set. -# -# Banner content is verbatim from /etc/bread-banner.txt (shipped from -# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped -# in red ANSI; everything else is printed as-is. -# -# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no -# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER -# to stderr on every connection, which would inject ~14 lines of noise into -# every non-interactive `ssh root@host cmd` that spread runs. This shell -# hook covers every interactive path a human actually hits (ssh login shell -# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing -# worth the noise. - -[ -n "$BREAD_BANNER_SHOWN" ] && return 0 -if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then - export BREAD_BANNER_SHOWN=1 - return 0 -fi -export BREAD_BANNER_SHOWN=1 - -__bw_banner=/etc/bread-banner.txt -[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } - -if [ -t 1 ] && [ -z "$NO_COLOR" ]; then - __bw_r=$(printf '\033[1;31m') - __bw_n=$(printf '\033[0m') - sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" - unset __bw_r __bw_n -else - cat "$__bw_banner" -fi - -unset __bw_banner diff --git a/rocks/bread-25.10/hack/chisel_cut.sh b/rocks/bread-25.10/hack/chisel_cut.sh deleted file mode 100755 index db2af91..0000000 --- a/rocks/bread-25.10/hack/chisel_cut.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# Wrapper around `chisel cut` with retry on known-flaky archive -# errors. Patterns lifted from canonical/chisel-releases' -# .github/scripts/install-slices/install_slices.py. -# -# Usage: chisel_cut.sh [ ...] -# Requires: -# - to be a chisel-releases checkout -# - $CRAFT_PART_INSTALL to be set (rockcraft env) -set -e - -N_RETRIES=10 -RETRY_DELAY=60 -PATTERNS=( - # https://github.com/canonical/chisel-releases/issues/765 - "cannot fetch from archive" - # https://github.com/canonical/chisel-releases/issues/766 - "cannot talk to archive" - # https://github.com/canonical/chisel-releases/issues/768 - "cannot find archive data" - # transient digest mismatch (chisel bug) - "expected digest" -) - -[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } -[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } - -release_dir=$1 -shift - -for attempt in $(seq 1 "$N_RETRIES"); do - err=$(chisel cut \ - --release "$release_dir" \ - --root "$CRAFT_PART_INSTALL" \ - "$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq 0 ]; then - printf '%s\n' "$err" - exit 0 - fi - - matched="" - matched_line="" - for p in "${PATTERNS[@]}"; do - line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true - if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi - done - - if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then - echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 - echo " > $matched_line" >&2 - sleep "$RETRY_DELAY" - continue - fi - - printf '%s\n' "$err" >&2 - exit "$rc" -done diff --git a/rocks/bread-25.10/hack/hash_inputs.sh b/rocks/bread-25.10/hack/hash_inputs.sh deleted file mode 100755 index a6c2a51..0000000 --- a/rocks/bread-25.10/hack/hash_inputs.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -# Single sha256 over everything that affects this rock's build: the -# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the -# .rock.stamp short-circuit in the makefile so `make build` only repacks when a -# real input changed. -# -# Stdout: hex digest only. -set -euo pipefail - -cd "$(dirname "$0")/.." - -# shellcheck disable=SC2046 # word-splitting of find output is intended here. -sha256sum \ - rockcraft.yaml \ - $(find hack patches -type f 2>/dev/null | sort) \ - | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-25.10/hack/lazy-apt.sh b/rocks/bread-25.10/hack/lazy-apt.sh deleted file mode 100755 index 1038a4f..0000000 --- a/rocks/bread-25.10/hack/lazy-apt.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# lazy-apt: on first non-update call, runs apt-get update then replaces both -# shims with symlinks to real apt. Installed at /usr/local/bin/apt and -# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call -# apt/apt-get without an explicit update still work after the image was built -# with apt lists wiped. -# -# Replaces with symlinks rather than deleting: bash hashes the shim path on -# first use, so deletion would leave a stale hash entry causing subsequent -# calls to fail with "No such file or directory". - -real="/usr/bin/$(basename "$0")" - -if [ "$1" != "update" ]; then - /usr/bin/apt-get update -fi - -ln -sf /usr/bin/apt /usr/local/bin/apt -ln -sf /usr/bin/apt-get /usr/local/bin/apt-get - -exec "$real" "$@" diff --git a/rocks/bread-25.10/hack/sshd-entry.sh b/rocks/bread-25.10/hack/sshd-entry.sh deleted file mode 100755 index 080d94c..0000000 --- a/rocks/bread-25.10/hack/sshd-entry.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# pebble service command for sshd in the bread rock. -# -# host keys are generated here on first boot rather than baked into the rock, -# so throwaway test containers don't all share the same keys. also ensures the -# privsep runtime dir exists. then execs sshd in the foreground (pebble manages -# the process). -set -e - -mkdir -p /run/sshd -ssh-keygen -A - -exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-25.10/makefile b/rocks/bread-25.10/makefile index 8b9b97d..a95a252 100644 --- a/rocks/bread-25.10/makefile +++ b/rocks/bread-25.10/makefile @@ -18,7 +18,11 @@ FORCE: # Stamp content = hash of all build inputs; only rewritten when it changes, so # the rock only repacks on a real input change. -.rock.stamp: FORCE +.PHONY: sync +sync: ## Populate hack/ (+ patches/) from shared rocks/hack + @ruby ../sync_shared.rb $(notdir $(CURDIR)) + +.rock.stamp: sync FORCE @new=$$(hack/hash_inputs.sh) ; \ cur=$$(cat $@ 2>/dev/null || true) ; \ [ "$$new" = "$$cur" ] || echo "$$new" > $@ diff --git a/rocks/bread-26.04/hack/banner.txt b/rocks/bread-26.04/hack/banner.txt deleted file mode 100644 index 3c06b8b..0000000 --- a/rocks/bread-26.04/hack/banner.txt +++ /dev/null @@ -1,13 +0,0 @@ - ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ - ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ - ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ -███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ -███▒▒░░░░░░░ ░░▒▒▓▓▓▓ -███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ - ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ - ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ - ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ - ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ - ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ - ███▒▒░░░░░░░ ░░░░░░░▓▓ - █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-26.04/hack/bread-warning.sh b/rocks/bread-26.04/hack/bread-warning.sh deleted file mode 100755 index 2ee0140..0000000 --- a/rocks/bread-26.04/hack/bread-warning.sh +++ /dev/null @@ -1,39 +0,0 @@ -# bread-warning: insecure-image notice. -# -# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive -# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same -# session. Silent on non-tty (spread, scripts, ci logs) unless -# BREAD_BANNER_FORCE is set. -# -# Banner content is verbatim from /etc/bread-banner.txt (shipped from -# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped -# in red ANSI; everything else is printed as-is. -# -# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no -# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER -# to stderr on every connection, which would inject ~14 lines of noise into -# every non-interactive `ssh root@host cmd` that spread runs. This shell -# hook covers every interactive path a human actually hits (ssh login shell -# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing -# worth the noise. - -[ -n "$BREAD_BANNER_SHOWN" ] && return 0 -if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then - export BREAD_BANNER_SHOWN=1 - return 0 -fi -export BREAD_BANNER_SHOWN=1 - -__bw_banner=/etc/bread-banner.txt -[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } - -if [ -t 1 ] && [ -z "$NO_COLOR" ]; then - __bw_r=$(printf '\033[1;31m') - __bw_n=$(printf '\033[0m') - sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" - unset __bw_r __bw_n -else - cat "$__bw_banner" -fi - -unset __bw_banner diff --git a/rocks/bread-26.04/hack/chisel_cut.sh b/rocks/bread-26.04/hack/chisel_cut.sh deleted file mode 100755 index db2af91..0000000 --- a/rocks/bread-26.04/hack/chisel_cut.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# Wrapper around `chisel cut` with retry on known-flaky archive -# errors. Patterns lifted from canonical/chisel-releases' -# .github/scripts/install-slices/install_slices.py. -# -# Usage: chisel_cut.sh [ ...] -# Requires: -# - to be a chisel-releases checkout -# - $CRAFT_PART_INSTALL to be set (rockcraft env) -set -e - -N_RETRIES=10 -RETRY_DELAY=60 -PATTERNS=( - # https://github.com/canonical/chisel-releases/issues/765 - "cannot fetch from archive" - # https://github.com/canonical/chisel-releases/issues/766 - "cannot talk to archive" - # https://github.com/canonical/chisel-releases/issues/768 - "cannot find archive data" - # transient digest mismatch (chisel bug) - "expected digest" -) - -[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } -[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } - -release_dir=$1 -shift - -for attempt in $(seq 1 "$N_RETRIES"); do - err=$(chisel cut \ - --release "$release_dir" \ - --root "$CRAFT_PART_INSTALL" \ - "$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq 0 ]; then - printf '%s\n' "$err" - exit 0 - fi - - matched="" - matched_line="" - for p in "${PATTERNS[@]}"; do - line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true - if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi - done - - if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then - echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 - echo " > $matched_line" >&2 - sleep "$RETRY_DELAY" - continue - fi - - printf '%s\n' "$err" >&2 - exit "$rc" -done diff --git a/rocks/bread-26.04/hack/hash_inputs.sh b/rocks/bread-26.04/hack/hash_inputs.sh deleted file mode 100755 index a6c2a51..0000000 --- a/rocks/bread-26.04/hack/hash_inputs.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -# Single sha256 over everything that affects this rock's build: the -# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the -# .rock.stamp short-circuit in the makefile so `make build` only repacks when a -# real input changed. -# -# Stdout: hex digest only. -set -euo pipefail - -cd "$(dirname "$0")/.." - -# shellcheck disable=SC2046 # word-splitting of find output is intended here. -sha256sum \ - rockcraft.yaml \ - $(find hack patches -type f 2>/dev/null | sort) \ - | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-26.04/hack/lazy-apt.sh b/rocks/bread-26.04/hack/lazy-apt.sh deleted file mode 100755 index 1038a4f..0000000 --- a/rocks/bread-26.04/hack/lazy-apt.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# lazy-apt: on first non-update call, runs apt-get update then replaces both -# shims with symlinks to real apt. Installed at /usr/local/bin/apt and -# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call -# apt/apt-get without an explicit update still work after the image was built -# with apt lists wiped. -# -# Replaces with symlinks rather than deleting: bash hashes the shim path on -# first use, so deletion would leave a stale hash entry causing subsequent -# calls to fail with "No such file or directory". - -real="/usr/bin/$(basename "$0")" - -if [ "$1" != "update" ]; then - /usr/bin/apt-get update -fi - -ln -sf /usr/bin/apt /usr/local/bin/apt -ln -sf /usr/bin/apt-get /usr/local/bin/apt-get - -exec "$real" "$@" diff --git a/rocks/bread-26.04/hack/sshd-entry.sh b/rocks/bread-26.04/hack/sshd-entry.sh deleted file mode 100755 index 080d94c..0000000 --- a/rocks/bread-26.04/hack/sshd-entry.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# pebble service command for sshd in the bread rock. -# -# host keys are generated here on first boot rather than baked into the rock, -# so throwaway test containers don't all share the same keys. also ensures the -# privsep runtime dir exists. then execs sshd in the foreground (pebble manages -# the process). -set -e - -mkdir -p /run/sshd -ssh-keygen -A - -exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-26.04/makefile b/rocks/bread-26.04/makefile index 4e0be0e..c417070 100644 --- a/rocks/bread-26.04/makefile +++ b/rocks/bread-26.04/makefile @@ -18,7 +18,11 @@ FORCE: # Stamp content = hash of all build inputs; only rewritten when it changes, so # the rock only repacks on a real input change. -.rock.stamp: FORCE +.PHONY: sync +sync: ## Populate hack/ (+ patches/) from shared rocks/hack + @ruby ../sync_shared.rb $(notdir $(CURDIR)) + +.rock.stamp: sync FORCE @new=$$(hack/hash_inputs.sh) ; \ cur=$$(cat $@ 2>/dev/null || true) ; \ [ "$$new" = "$$cur" ] || echo "$$new" > $@ diff --git a/rocks/bread-chisel-releases-24.04/hack/banner.txt b/rocks/bread-chisel-releases-24.04/hack/banner.txt deleted file mode 100644 index 3c06b8b..0000000 --- a/rocks/bread-chisel-releases-24.04/hack/banner.txt +++ /dev/null @@ -1,13 +0,0 @@ - ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ - ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ - ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ -███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ -███▒▒░░░░░░░ ░░▒▒▓▓▓▓ -███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ - ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ - ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ - ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ - ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ - ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ - ███▒▒░░░░░░░ ░░░░░░░▓▓ - █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-chisel-releases-24.04/hack/bread-warning.sh b/rocks/bread-chisel-releases-24.04/hack/bread-warning.sh deleted file mode 100755 index 2ee0140..0000000 --- a/rocks/bread-chisel-releases-24.04/hack/bread-warning.sh +++ /dev/null @@ -1,39 +0,0 @@ -# bread-warning: insecure-image notice. -# -# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive -# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same -# session. Silent on non-tty (spread, scripts, ci logs) unless -# BREAD_BANNER_FORCE is set. -# -# Banner content is verbatim from /etc/bread-banner.txt (shipped from -# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped -# in red ANSI; everything else is printed as-is. -# -# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no -# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER -# to stderr on every connection, which would inject ~14 lines of noise into -# every non-interactive `ssh root@host cmd` that spread runs. This shell -# hook covers every interactive path a human actually hits (ssh login shell -# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing -# worth the noise. - -[ -n "$BREAD_BANNER_SHOWN" ] && return 0 -if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then - export BREAD_BANNER_SHOWN=1 - return 0 -fi -export BREAD_BANNER_SHOWN=1 - -__bw_banner=/etc/bread-banner.txt -[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } - -if [ -t 1 ] && [ -z "$NO_COLOR" ]; then - __bw_r=$(printf '\033[1;31m') - __bw_n=$(printf '\033[0m') - sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" - unset __bw_r __bw_n -else - cat "$__bw_banner" -fi - -unset __bw_banner diff --git a/rocks/bread-chisel-releases-24.04/hack/chisel_cut.sh b/rocks/bread-chisel-releases-24.04/hack/chisel_cut.sh deleted file mode 100755 index db2af91..0000000 --- a/rocks/bread-chisel-releases-24.04/hack/chisel_cut.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# Wrapper around `chisel cut` with retry on known-flaky archive -# errors. Patterns lifted from canonical/chisel-releases' -# .github/scripts/install-slices/install_slices.py. -# -# Usage: chisel_cut.sh [ ...] -# Requires: -# - to be a chisel-releases checkout -# - $CRAFT_PART_INSTALL to be set (rockcraft env) -set -e - -N_RETRIES=10 -RETRY_DELAY=60 -PATTERNS=( - # https://github.com/canonical/chisel-releases/issues/765 - "cannot fetch from archive" - # https://github.com/canonical/chisel-releases/issues/766 - "cannot talk to archive" - # https://github.com/canonical/chisel-releases/issues/768 - "cannot find archive data" - # transient digest mismatch (chisel bug) - "expected digest" -) - -[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } -[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } - -release_dir=$1 -shift - -for attempt in $(seq 1 "$N_RETRIES"); do - err=$(chisel cut \ - --release "$release_dir" \ - --root "$CRAFT_PART_INSTALL" \ - "$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq 0 ]; then - printf '%s\n' "$err" - exit 0 - fi - - matched="" - matched_line="" - for p in "${PATTERNS[@]}"; do - line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true - if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi - done - - if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then - echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 - echo " > $matched_line" >&2 - sleep "$RETRY_DELAY" - continue - fi - - printf '%s\n' "$err" >&2 - exit "$rc" -done diff --git a/rocks/bread-chisel-releases-24.04/hack/hash_inputs.sh b/rocks/bread-chisel-releases-24.04/hack/hash_inputs.sh deleted file mode 100755 index a6c2a51..0000000 --- a/rocks/bread-chisel-releases-24.04/hack/hash_inputs.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -# Single sha256 over everything that affects this rock's build: the -# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the -# .rock.stamp short-circuit in the makefile so `make build` only repacks when a -# real input changed. -# -# Stdout: hex digest only. -set -euo pipefail - -cd "$(dirname "$0")/.." - -# shellcheck disable=SC2046 # word-splitting of find output is intended here. -sha256sum \ - rockcraft.yaml \ - $(find hack patches -type f 2>/dev/null | sort) \ - | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-chisel-releases-24.04/hack/lazy-apt.sh b/rocks/bread-chisel-releases-24.04/hack/lazy-apt.sh deleted file mode 100755 index 1038a4f..0000000 --- a/rocks/bread-chisel-releases-24.04/hack/lazy-apt.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# lazy-apt: on first non-update call, runs apt-get update then replaces both -# shims with symlinks to real apt. Installed at /usr/local/bin/apt and -# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call -# apt/apt-get without an explicit update still work after the image was built -# with apt lists wiped. -# -# Replaces with symlinks rather than deleting: bash hashes the shim path on -# first use, so deletion would leave a stale hash entry causing subsequent -# calls to fail with "No such file or directory". - -real="/usr/bin/$(basename "$0")" - -if [ "$1" != "update" ]; then - /usr/bin/apt-get update -fi - -ln -sf /usr/bin/apt /usr/local/bin/apt -ln -sf /usr/bin/apt-get /usr/local/bin/apt-get - -exec "$real" "$@" diff --git a/rocks/bread-chisel-releases-24.04/hack/sshd-entry.sh b/rocks/bread-chisel-releases-24.04/hack/sshd-entry.sh deleted file mode 100755 index 080d94c..0000000 --- a/rocks/bread-chisel-releases-24.04/hack/sshd-entry.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# pebble service command for sshd in the bread rock. -# -# host keys are generated here on first boot rather than baked into the rock, -# so throwaway test containers don't all share the same keys. also ensures the -# privsep runtime dir exists. then execs sshd in the foreground (pebble manages -# the process). -set -e - -mkdir -p /run/sshd -ssh-keygen -A - -exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-chisel-releases-24.04/makefile b/rocks/bread-chisel-releases-24.04/makefile index 1dbd78c..586d168 100644 --- a/rocks/bread-chisel-releases-24.04/makefile +++ b/rocks/bread-chisel-releases-24.04/makefile @@ -16,7 +16,11 @@ build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) .PHONY: FORCE FORCE: -.rock.stamp: FORCE +.PHONY: sync +sync: ## Populate hack/ (+ patches/) from shared rocks/hack + @ruby ../sync_shared.rb $(notdir $(CURDIR)) + +.rock.stamp: sync FORCE @new=$$(hack/hash_inputs.sh) ; \ cur=$$(cat $@ 2>/dev/null || true) ; \ [ "$$new" = "$$cur" ] || echo "$$new" > $@ diff --git a/rocks/bread-chisel-releases-25.10/hack/banner.txt b/rocks/bread-chisel-releases-25.10/hack/banner.txt deleted file mode 100644 index 3c06b8b..0000000 --- a/rocks/bread-chisel-releases-25.10/hack/banner.txt +++ /dev/null @@ -1,13 +0,0 @@ - ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ - ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ - ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ -███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ -███▒▒░░░░░░░ ░░▒▒▓▓▓▓ -███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ - ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ - ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ - ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ - ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ - ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ - ███▒▒░░░░░░░ ░░░░░░░▓▓ - █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-chisel-releases-25.10/hack/bread-warning.sh b/rocks/bread-chisel-releases-25.10/hack/bread-warning.sh deleted file mode 100755 index 2ee0140..0000000 --- a/rocks/bread-chisel-releases-25.10/hack/bread-warning.sh +++ /dev/null @@ -1,39 +0,0 @@ -# bread-warning: insecure-image notice. -# -# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive -# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same -# session. Silent on non-tty (spread, scripts, ci logs) unless -# BREAD_BANNER_FORCE is set. -# -# Banner content is verbatim from /etc/bread-banner.txt (shipped from -# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped -# in red ANSI; everything else is printed as-is. -# -# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no -# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER -# to stderr on every connection, which would inject ~14 lines of noise into -# every non-interactive `ssh root@host cmd` that spread runs. This shell -# hook covers every interactive path a human actually hits (ssh login shell -# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing -# worth the noise. - -[ -n "$BREAD_BANNER_SHOWN" ] && return 0 -if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then - export BREAD_BANNER_SHOWN=1 - return 0 -fi -export BREAD_BANNER_SHOWN=1 - -__bw_banner=/etc/bread-banner.txt -[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } - -if [ -t 1 ] && [ -z "$NO_COLOR" ]; then - __bw_r=$(printf '\033[1;31m') - __bw_n=$(printf '\033[0m') - sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" - unset __bw_r __bw_n -else - cat "$__bw_banner" -fi - -unset __bw_banner diff --git a/rocks/bread-chisel-releases-25.10/hack/chisel_cut.sh b/rocks/bread-chisel-releases-25.10/hack/chisel_cut.sh deleted file mode 100755 index db2af91..0000000 --- a/rocks/bread-chisel-releases-25.10/hack/chisel_cut.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# Wrapper around `chisel cut` with retry on known-flaky archive -# errors. Patterns lifted from canonical/chisel-releases' -# .github/scripts/install-slices/install_slices.py. -# -# Usage: chisel_cut.sh [ ...] -# Requires: -# - to be a chisel-releases checkout -# - $CRAFT_PART_INSTALL to be set (rockcraft env) -set -e - -N_RETRIES=10 -RETRY_DELAY=60 -PATTERNS=( - # https://github.com/canonical/chisel-releases/issues/765 - "cannot fetch from archive" - # https://github.com/canonical/chisel-releases/issues/766 - "cannot talk to archive" - # https://github.com/canonical/chisel-releases/issues/768 - "cannot find archive data" - # transient digest mismatch (chisel bug) - "expected digest" -) - -[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } -[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } - -release_dir=$1 -shift - -for attempt in $(seq 1 "$N_RETRIES"); do - err=$(chisel cut \ - --release "$release_dir" \ - --root "$CRAFT_PART_INSTALL" \ - "$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq 0 ]; then - printf '%s\n' "$err" - exit 0 - fi - - matched="" - matched_line="" - for p in "${PATTERNS[@]}"; do - line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true - if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi - done - - if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then - echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 - echo " > $matched_line" >&2 - sleep "$RETRY_DELAY" - continue - fi - - printf '%s\n' "$err" >&2 - exit "$rc" -done diff --git a/rocks/bread-chisel-releases-25.10/hack/chisel_override_build.sh b/rocks/bread-chisel-releases-25.10/hack/chisel_override_build.sh deleted file mode 100755 index d8432fb..0000000 --- a/rocks/bread-chisel-releases-25.10/hack/chisel_override_build.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# Build chisel + chisel-hacked from source (canonical/chisel @ $CHISEL_REF), -# native arch, into $CRAFT_PART_INSTALL/usr/local/bin. Mirrors the root repo's -# hack/build_binaries.sh, minus the cross-compile -- rockcraft builds natively -# per platform so GOARCH is just the host arch. -# -# Env (from the part's build-environment): -# CHISEL_REF git ref for canonical/chisel -set -euo pipefail - -: "${CHISEL_REF:?required}" - -# go.mod may demand a newer toolchain than the snap ships; let go fetch it. -export GOTOOLCHAIN=auto - -out="$CRAFT_PART_INSTALL/usr/local/bin" -mkdir -p "$out" - -src="$CRAFT_PART_BUILD/chisel-src" -git clone https://github.com/canonical/chisel "$src" -cd "$src" -git checkout "$CHISEL_REF" - -# Inject the version string the way upstream's cmd/mkversion.sh would, so -# `chisel --version` reports the pinned tag instead of "unknown". -ver=$(git describe --tags --dirty --always) -echo "==> chisel version: $ver" - -CGO_ENABLED=0 go build -trimpath \ - -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=$ver" \ - -o "$out/chisel" ./cmd/chisel - -# chisel-hacked: same source + the CHISEL_HACKS patches, -hacked version suffix. -for p in "$CRAFT_PROJECT_DIR"/patches/chisel/0001-*.patch \ - "$CRAFT_PROJECT_DIR"/patches/chisel/0002-*.patch \ - "$CRAFT_PROJECT_DIR"/patches/chisel/0003-*.patch; do - echo "==> applying $(basename "$p")" - git apply "$p" -done - -CGO_ENABLED=0 go build -trimpath \ - -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=${ver}-hacked" \ - -o "$out/chisel-hacked" ./cmd/chisel diff --git a/rocks/bread-chisel-releases-25.10/hack/docker_override_build.sh b/rocks/bread-chisel-releases-25.10/hack/docker_override_build.sh deleted file mode 100755 index 5e40e1f..0000000 --- a/rocks/bread-chisel-releases-25.10/hack/docker_override_build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -# Fetch the upstream docker static CLI for the build-for arch and drop it at -# /usr/local/bin/docker. Upstream's static build uses a recent go toolchain and -# so dodges the qemu emulation bug that hits ubuntu apt's docker.io (go 1.24). -# -# Env (from the part's build-environment): -# DOCKER_VERSION docker static release version -set -euo pipefail - -: "${DOCKER_VERSION:?required}" - -# docker.com static uses uname-style arch names. -case "$CRAFT_ARCH_BUILD_FOR" in - amd64) docker_arch=x86_64 ;; - arm64) docker_arch=aarch64 ;; - *) echo "unsupported arch: $CRAFT_ARCH_BUILD_FOR" >&2; exit 1 ;; -esac - -out="$CRAFT_PART_INSTALL/usr/local/bin" -mkdir -p "$out" - -url="https://download.docker.com/linux/static/stable/${docker_arch}/docker-${DOCKER_VERSION}.tgz" -echo "==> downloading docker $DOCKER_VERSION for $CRAFT_ARCH_BUILD_FOR from $url" - -tmp=$(mktemp -d) -curl -fsSL "$url" | tar -xz -C "$tmp" docker/docker -install -m0755 "$tmp/docker/docker" "$out/docker" -rm -rf "$tmp" diff --git a/rocks/bread-chisel-releases-25.10/hack/hash_inputs.sh b/rocks/bread-chisel-releases-25.10/hack/hash_inputs.sh deleted file mode 100755 index a6c2a51..0000000 --- a/rocks/bread-chisel-releases-25.10/hack/hash_inputs.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -# Single sha256 over everything that affects this rock's build: the -# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the -# .rock.stamp short-circuit in the makefile so `make build` only repacks when a -# real input changed. -# -# Stdout: hex digest only. -set -euo pipefail - -cd "$(dirname "$0")/.." - -# shellcheck disable=SC2046 # word-splitting of find output is intended here. -sha256sum \ - rockcraft.yaml \ - $(find hack patches -type f 2>/dev/null | sort) \ - | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-chisel-releases-25.10/hack/lazy-apt.sh b/rocks/bread-chisel-releases-25.10/hack/lazy-apt.sh deleted file mode 100755 index 1038a4f..0000000 --- a/rocks/bread-chisel-releases-25.10/hack/lazy-apt.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# lazy-apt: on first non-update call, runs apt-get update then replaces both -# shims with symlinks to real apt. Installed at /usr/local/bin/apt and -# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call -# apt/apt-get without an explicit update still work after the image was built -# with apt lists wiped. -# -# Replaces with symlinks rather than deleting: bash hashes the shim path on -# first use, so deletion would leave a stale hash entry causing subsequent -# calls to fail with "No such file or directory". - -real="/usr/bin/$(basename "$0")" - -if [ "$1" != "update" ]; then - /usr/bin/apt-get update -fi - -ln -sf /usr/bin/apt /usr/local/bin/apt -ln -sf /usr/bin/apt-get /usr/local/bin/apt-get - -exec "$real" "$@" diff --git a/rocks/bread-chisel-releases-25.10/hack/sshd-entry.sh b/rocks/bread-chisel-releases-25.10/hack/sshd-entry.sh deleted file mode 100755 index 080d94c..0000000 --- a/rocks/bread-chisel-releases-25.10/hack/sshd-entry.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# pebble service command for sshd in the bread rock. -# -# host keys are generated here on first boot rather than baked into the rock, -# so throwaway test containers don't all share the same keys. also ensures the -# privsep runtime dir exists. then execs sshd in the foreground (pebble manages -# the process). -set -e - -mkdir -p /run/sshd -ssh-keygen -A - -exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-chisel-releases-25.10/makefile b/rocks/bread-chisel-releases-25.10/makefile index aee2477..649462b 100644 --- a/rocks/bread-chisel-releases-25.10/makefile +++ b/rocks/bread-chisel-releases-25.10/makefile @@ -16,7 +16,11 @@ build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) .PHONY: FORCE FORCE: -.rock.stamp: FORCE +.PHONY: sync +sync: ## Populate hack/ (+ patches/) from shared rocks/hack + @ruby ../sync_shared.rb $(notdir $(CURDIR)) + +.rock.stamp: sync FORCE @new=$$(hack/hash_inputs.sh) ; \ cur=$$(cat $@ 2>/dev/null || true) ; \ [ "$$new" = "$$cur" ] || echo "$$new" > $@ diff --git a/rocks/bread-chisel-releases-25.10/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-25.10/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch deleted file mode 100644 index d3c0637..0000000 --- a/rocks/bread-chisel-releases-25.10/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 38a1af51d71f7904693a88db64df808afed80827 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:10:52 +0100 -Subject: [PATCH 1/3] chore: skip maintenance check via CHISEL_HACKS - ---- - cmd/chisel/cmd_cut.go | 19 ++++++++++++------- - 1 file changed, 12 insertions(+), 7 deletions(-) - -diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go -index 35c81a7..98c1a36 100644 ---- a/cmd/chisel/cmd_cut.go -+++ b/cmd/chisel/cmd_cut.go -@@ -2,6 +2,7 @@ package main - - import ( - "fmt" -+ "os" - "slices" - "time" - -@@ -63,13 +64,17 @@ func (cmd *cmdCut) Execute(args []string) error { - return err - } - -- if time.Now().Before(release.Maintenance.Standard) { -- if slices.Contains(cmd.Ignore, "unstable") { -- logf(`Warning: This release is in the "unstable" maintenance status. ` + -- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) -- } else { -- return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + -- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping maintenance check due to CHISEL_HACKS=1") -+ } else { -+ if time.Now().Before(release.Maintenance.Standard) { -+ if slices.Contains(cmd.Ignore, "unstable") { -+ logf(`Warning: This release is in the "unstable" maintenance status. ` + -+ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) -+ } else { -+ return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + -+ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ } - } - } - --- -2.53.0 - diff --git a/rocks/bread-chisel-releases-25.10/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch b/rocks/bread-chisel-releases-25.10/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch deleted file mode 100644 index 851b6a5..0000000 --- a/rocks/bread-chisel-releases-25.10/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 76d4ec4dfb51fc7d4d625c5ee6021d9452e6b263 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:11:06 +0100 -Subject: [PATCH 2/3] chore: skip archive maintenance check via CHISEL_HACKS - ---- - cmd/chisel/cmd_cut.go | 34 +++++++++++++++++++--------------- - 1 file changed, 19 insertions(+), 15 deletions(-) - -diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go -index 98c1a36..b4381d4 100644 ---- a/cmd/chisel/cmd_cut.go -+++ b/cmd/chisel/cmd_cut.go -@@ -107,22 +107,26 @@ func (cmd *cmdCut) Execute(args []string) error { - archives[archiveName] = openArchive - } - -- hasMaintainedArchive := false -- for _, archive := range archives { -- if archive.Options().Maintained { -- hasMaintainedArchive = true -- break -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping archive maintenance check due to CHISEL_HACKS=1") -+ } else { -+ hasMaintainedArchive := false -+ for _, archive := range archives { -+ if archive.Options().Maintained { -+ hasMaintainedArchive = true -+ break -+ } - } -- } -- if !hasMaintainedArchive { -- if slices.Contains(cmd.Ignore, "unmaintained") { -- logf(`Warning: No archive has "maintained" maintenance status. ` + -- `Consider the different Ubuntu Pro subscriptions to be safe. ` + -- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) -- } else { -- return fmt.Errorf(`no archive has "maintained" maintenance status, ` + -- `consider the different Ubuntu Pro subscriptions to be safe, ` + -- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ if !hasMaintainedArchive { -+ if slices.Contains(cmd.Ignore, "unmaintained") { -+ logf(`Warning: No archive has "maintained" maintenance status. ` + -+ `Consider the different Ubuntu Pro subscriptions to be safe. ` + -+ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) -+ } else { -+ return fmt.Errorf(`no archive has "maintained" maintenance status, ` + -+ `consider the different Ubuntu Pro subscriptions to be safe, ` + -+ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ } - } - } - --- -2.53.0 - diff --git a/rocks/bread-chisel-releases-25.10/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-25.10/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch deleted file mode 100644 index a6774d0..0000000 --- a/rocks/bread-chisel-releases-25.10/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 7af23d09332e1fa104ad5c802e176e90f86d67d3 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:11:28 +0100 -Subject: [PATCH 3/3] chore: skip release validation via CHISEL_HACKS - ---- - internal/setup/setup.go | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/internal/setup/setup.go b/internal/setup/setup.go -index 6ed24ab..01ea8eb 100644 ---- a/internal/setup/setup.go -+++ b/internal/setup/setup.go -@@ -194,6 +194,10 @@ func ReadRelease(dir string) (*Release, error) { - return nil, err - } - -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping release validation due to CHISEL_HACKS=1") -+ return release, nil -+ } - err = release.validate() - if err != nil { - return nil, err --- -2.53.0 - diff --git a/rocks/bread-chisel-releases-26.04/hack/banner.txt b/rocks/bread-chisel-releases-26.04/hack/banner.txt deleted file mode 100644 index 3c06b8b..0000000 --- a/rocks/bread-chisel-releases-26.04/hack/banner.txt +++ /dev/null @@ -1,13 +0,0 @@ - ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ - ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ - ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ -███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ -███▒▒░░░░░░░ ░░▒▒▓▓▓▓ -███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ - ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ - ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ - ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ - ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ - ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ - ███▒▒░░░░░░░ ░░░░░░░▓▓ - █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/bread-chisel-releases-26.04/hack/bread-warning.sh b/rocks/bread-chisel-releases-26.04/hack/bread-warning.sh deleted file mode 100755 index 2ee0140..0000000 --- a/rocks/bread-chisel-releases-26.04/hack/bread-warning.sh +++ /dev/null @@ -1,39 +0,0 @@ -# bread-warning: insecure-image notice. -# -# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive -# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same -# session. Silent on non-tty (spread, scripts, ci logs) unless -# BREAD_BANNER_FORCE is set. -# -# Banner content is verbatim from /etc/bread-banner.txt (shipped from -# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped -# in red ANSI; everything else is printed as-is. -# -# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no -# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER -# to stderr on every connection, which would inject ~14 lines of noise into -# every non-interactive `ssh root@host cmd` that spread runs. This shell -# hook covers every interactive path a human actually hits (ssh login shell -# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing -# worth the noise. - -[ -n "$BREAD_BANNER_SHOWN" ] && return 0 -if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then - export BREAD_BANNER_SHOWN=1 - return 0 -fi -export BREAD_BANNER_SHOWN=1 - -__bw_banner=/etc/bread-banner.txt -[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } - -if [ -t 1 ] && [ -z "$NO_COLOR" ]; then - __bw_r=$(printf '\033[1;31m') - __bw_n=$(printf '\033[0m') - sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" - unset __bw_r __bw_n -else - cat "$__bw_banner" -fi - -unset __bw_banner diff --git a/rocks/bread-chisel-releases-26.04/hack/chisel_cut.sh b/rocks/bread-chisel-releases-26.04/hack/chisel_cut.sh deleted file mode 100755 index db2af91..0000000 --- a/rocks/bread-chisel-releases-26.04/hack/chisel_cut.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash -# Wrapper around `chisel cut` with retry on known-flaky archive -# errors. Patterns lifted from canonical/chisel-releases' -# .github/scripts/install-slices/install_slices.py. -# -# Usage: chisel_cut.sh [ ...] -# Requires: -# - to be a chisel-releases checkout -# - $CRAFT_PART_INSTALL to be set (rockcraft env) -set -e - -N_RETRIES=10 -RETRY_DELAY=60 -PATTERNS=( - # https://github.com/canonical/chisel-releases/issues/765 - "cannot fetch from archive" - # https://github.com/canonical/chisel-releases/issues/766 - "cannot talk to archive" - # https://github.com/canonical/chisel-releases/issues/768 - "cannot find archive data" - # transient digest mismatch (chisel bug) - "expected digest" -) - -[ "$#" -ge 2 ] || { echo "usage: $0 [ ...]" >&2; exit 1; } -[ -n "${CRAFT_PART_INSTALL:-}" ] || { echo "CRAFT_PART_INSTALL unset" >&2; exit 1; } - -release_dir=$1 -shift - -for attempt in $(seq 1 "$N_RETRIES"); do - err=$(chisel cut \ - --release "$release_dir" \ - --root "$CRAFT_PART_INSTALL" \ - "$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq 0 ]; then - printf '%s\n' "$err" - exit 0 - fi - - matched="" - matched_line="" - for p in "${PATTERNS[@]}"; do - line=$(printf '%s\n' "$err" | grep -F "$p" | head -n1) || true - if [ -n "$line" ]; then matched=$p; matched_line=$line; break; fi - done - - if [ -n "$matched" ] && [ "$attempt" -lt "$N_RETRIES" ]; then - echo "chisel cut failed (attempt $attempt/$N_RETRIES): $matched. Retrying in ${RETRY_DELAY}s..." >&2 - echo " > $matched_line" >&2 - sleep "$RETRY_DELAY" - continue - fi - - printf '%s\n' "$err" >&2 - exit "$rc" -done diff --git a/rocks/bread-chisel-releases-26.04/hack/chisel_override_build.sh b/rocks/bread-chisel-releases-26.04/hack/chisel_override_build.sh deleted file mode 100755 index d8432fb..0000000 --- a/rocks/bread-chisel-releases-26.04/hack/chisel_override_build.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -# Build chisel + chisel-hacked from source (canonical/chisel @ $CHISEL_REF), -# native arch, into $CRAFT_PART_INSTALL/usr/local/bin. Mirrors the root repo's -# hack/build_binaries.sh, minus the cross-compile -- rockcraft builds natively -# per platform so GOARCH is just the host arch. -# -# Env (from the part's build-environment): -# CHISEL_REF git ref for canonical/chisel -set -euo pipefail - -: "${CHISEL_REF:?required}" - -# go.mod may demand a newer toolchain than the snap ships; let go fetch it. -export GOTOOLCHAIN=auto - -out="$CRAFT_PART_INSTALL/usr/local/bin" -mkdir -p "$out" - -src="$CRAFT_PART_BUILD/chisel-src" -git clone https://github.com/canonical/chisel "$src" -cd "$src" -git checkout "$CHISEL_REF" - -# Inject the version string the way upstream's cmd/mkversion.sh would, so -# `chisel --version` reports the pinned tag instead of "unknown". -ver=$(git describe --tags --dirty --always) -echo "==> chisel version: $ver" - -CGO_ENABLED=0 go build -trimpath \ - -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=$ver" \ - -o "$out/chisel" ./cmd/chisel - -# chisel-hacked: same source + the CHISEL_HACKS patches, -hacked version suffix. -for p in "$CRAFT_PROJECT_DIR"/patches/chisel/0001-*.patch \ - "$CRAFT_PROJECT_DIR"/patches/chisel/0002-*.patch \ - "$CRAFT_PROJECT_DIR"/patches/chisel/0003-*.patch; do - echo "==> applying $(basename "$p")" - git apply "$p" -done - -CGO_ENABLED=0 go build -trimpath \ - -ldflags "-s -w -X github.com/canonical/chisel/cmd.Version=${ver}-hacked" \ - -o "$out/chisel-hacked" ./cmd/chisel diff --git a/rocks/bread-chisel-releases-26.04/hack/docker_override_build.sh b/rocks/bread-chisel-releases-26.04/hack/docker_override_build.sh deleted file mode 100755 index 5e40e1f..0000000 --- a/rocks/bread-chisel-releases-26.04/hack/docker_override_build.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -# Fetch the upstream docker static CLI for the build-for arch and drop it at -# /usr/local/bin/docker. Upstream's static build uses a recent go toolchain and -# so dodges the qemu emulation bug that hits ubuntu apt's docker.io (go 1.24). -# -# Env (from the part's build-environment): -# DOCKER_VERSION docker static release version -set -euo pipefail - -: "${DOCKER_VERSION:?required}" - -# docker.com static uses uname-style arch names. -case "$CRAFT_ARCH_BUILD_FOR" in - amd64) docker_arch=x86_64 ;; - arm64) docker_arch=aarch64 ;; - *) echo "unsupported arch: $CRAFT_ARCH_BUILD_FOR" >&2; exit 1 ;; -esac - -out="$CRAFT_PART_INSTALL/usr/local/bin" -mkdir -p "$out" - -url="https://download.docker.com/linux/static/stable/${docker_arch}/docker-${DOCKER_VERSION}.tgz" -echo "==> downloading docker $DOCKER_VERSION for $CRAFT_ARCH_BUILD_FOR from $url" - -tmp=$(mktemp -d) -curl -fsSL "$url" | tar -xz -C "$tmp" docker/docker -install -m0755 "$tmp/docker/docker" "$out/docker" -rm -rf "$tmp" diff --git a/rocks/bread-chisel-releases-26.04/hack/hash_inputs.sh b/rocks/bread-chisel-releases-26.04/hack/hash_inputs.sh deleted file mode 100755 index a6c2a51..0000000 --- a/rocks/bread-chisel-releases-26.04/hack/hash_inputs.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -# Single sha256 over everything that affects this rock's build: the -# rockcraft.yaml plus all hack/ inputs (and patches/, if present). Drives the -# .rock.stamp short-circuit in the makefile so `make build` only repacks when a -# real input changed. -# -# Stdout: hex digest only. -set -euo pipefail - -cd "$(dirname "$0")/.." - -# shellcheck disable=SC2046 # word-splitting of find output is intended here. -sha256sum \ - rockcraft.yaml \ - $(find hack patches -type f 2>/dev/null | sort) \ - | sha256sum | cut -d' ' -f1 diff --git a/rocks/bread-chisel-releases-26.04/hack/lazy-apt.sh b/rocks/bread-chisel-releases-26.04/hack/lazy-apt.sh deleted file mode 100755 index 1038a4f..0000000 --- a/rocks/bread-chisel-releases-26.04/hack/lazy-apt.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# lazy-apt: on first non-update call, runs apt-get update then replaces both -# shims with symlinks to real apt. Installed at /usr/local/bin/apt and -# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call -# apt/apt-get without an explicit update still work after the image was built -# with apt lists wiped. -# -# Replaces with symlinks rather than deleting: bash hashes the shim path on -# first use, so deletion would leave a stale hash entry causing subsequent -# calls to fail with "No such file or directory". - -real="/usr/bin/$(basename "$0")" - -if [ "$1" != "update" ]; then - /usr/bin/apt-get update -fi - -ln -sf /usr/bin/apt /usr/local/bin/apt -ln -sf /usr/bin/apt-get /usr/local/bin/apt-get - -exec "$real" "$@" diff --git a/rocks/bread-chisel-releases-26.04/hack/sshd-entry.sh b/rocks/bread-chisel-releases-26.04/hack/sshd-entry.sh deleted file mode 100755 index 080d94c..0000000 --- a/rocks/bread-chisel-releases-26.04/hack/sshd-entry.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh -# pebble service command for sshd in the bread rock. -# -# host keys are generated here on first boot rather than baked into the rock, -# so throwaway test containers don't all share the same keys. also ensures the -# privsep runtime dir exists. then execs sshd in the foreground (pebble manages -# the process). -set -e - -mkdir -p /run/sshd -ssh-keygen -A - -exec /usr/sbin/sshd -D -e diff --git a/rocks/bread-chisel-releases-26.04/makefile b/rocks/bread-chisel-releases-26.04/makefile index 8d9126f..e56137e 100644 --- a/rocks/bread-chisel-releases-26.04/makefile +++ b/rocks/bread-chisel-releases-26.04/makefile @@ -16,7 +16,11 @@ build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) .PHONY: FORCE FORCE: -.rock.stamp: FORCE +.PHONY: sync +sync: ## Populate hack/ (+ patches/) from shared rocks/hack + @ruby ../sync_shared.rb $(notdir $(CURDIR)) + +.rock.stamp: sync FORCE @new=$$(hack/hash_inputs.sh) ; \ cur=$$(cat $@ 2>/dev/null || true) ; \ [ "$$new" = "$$cur" ] || echo "$$new" > $@ diff --git a/rocks/bread-chisel-releases-26.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-26.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch deleted file mode 100644 index d3c0637..0000000 --- a/rocks/bread-chisel-releases-26.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 38a1af51d71f7904693a88db64df808afed80827 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:10:52 +0100 -Subject: [PATCH 1/3] chore: skip maintenance check via CHISEL_HACKS - ---- - cmd/chisel/cmd_cut.go | 19 ++++++++++++------- - 1 file changed, 12 insertions(+), 7 deletions(-) - -diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go -index 35c81a7..98c1a36 100644 ---- a/cmd/chisel/cmd_cut.go -+++ b/cmd/chisel/cmd_cut.go -@@ -2,6 +2,7 @@ package main - - import ( - "fmt" -+ "os" - "slices" - "time" - -@@ -63,13 +64,17 @@ func (cmd *cmdCut) Execute(args []string) error { - return err - } - -- if time.Now().Before(release.Maintenance.Standard) { -- if slices.Contains(cmd.Ignore, "unstable") { -- logf(`Warning: This release is in the "unstable" maintenance status. ` + -- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) -- } else { -- return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + -- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping maintenance check due to CHISEL_HACKS=1") -+ } else { -+ if time.Now().Before(release.Maintenance.Standard) { -+ if slices.Contains(cmd.Ignore, "unstable") { -+ logf(`Warning: This release is in the "unstable" maintenance status. ` + -+ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) -+ } else { -+ return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + -+ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ } - } - } - --- -2.53.0 - diff --git a/rocks/bread-chisel-releases-26.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch b/rocks/bread-chisel-releases-26.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch deleted file mode 100644 index 851b6a5..0000000 --- a/rocks/bread-chisel-releases-26.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 76d4ec4dfb51fc7d4d625c5ee6021d9452e6b263 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:11:06 +0100 -Subject: [PATCH 2/3] chore: skip archive maintenance check via CHISEL_HACKS - ---- - cmd/chisel/cmd_cut.go | 34 +++++++++++++++++++--------------- - 1 file changed, 19 insertions(+), 15 deletions(-) - -diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go -index 98c1a36..b4381d4 100644 ---- a/cmd/chisel/cmd_cut.go -+++ b/cmd/chisel/cmd_cut.go -@@ -107,22 +107,26 @@ func (cmd *cmdCut) Execute(args []string) error { - archives[archiveName] = openArchive - } - -- hasMaintainedArchive := false -- for _, archive := range archives { -- if archive.Options().Maintained { -- hasMaintainedArchive = true -- break -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping archive maintenance check due to CHISEL_HACKS=1") -+ } else { -+ hasMaintainedArchive := false -+ for _, archive := range archives { -+ if archive.Options().Maintained { -+ hasMaintainedArchive = true -+ break -+ } - } -- } -- if !hasMaintainedArchive { -- if slices.Contains(cmd.Ignore, "unmaintained") { -- logf(`Warning: No archive has "maintained" maintenance status. ` + -- `Consider the different Ubuntu Pro subscriptions to be safe. ` + -- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) -- } else { -- return fmt.Errorf(`no archive has "maintained" maintenance status, ` + -- `consider the different Ubuntu Pro subscriptions to be safe, ` + -- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ if !hasMaintainedArchive { -+ if slices.Contains(cmd.Ignore, "unmaintained") { -+ logf(`Warning: No archive has "maintained" maintenance status. ` + -+ `Consider the different Ubuntu Pro subscriptions to be safe. ` + -+ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) -+ } else { -+ return fmt.Errorf(`no archive has "maintained" maintenance status, ` + -+ `consider the different Ubuntu Pro subscriptions to be safe, ` + -+ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ } - } - } - --- -2.53.0 - diff --git a/rocks/bread-chisel-releases-26.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch b/rocks/bread-chisel-releases-26.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch deleted file mode 100644 index a6774d0..0000000 --- a/rocks/bread-chisel-releases-26.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 7af23d09332e1fa104ad5c802e176e90f86d67d3 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:11:28 +0100 -Subject: [PATCH 3/3] chore: skip release validation via CHISEL_HACKS - ---- - internal/setup/setup.go | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/internal/setup/setup.go b/internal/setup/setup.go -index 6ed24ab..01ea8eb 100644 ---- a/internal/setup/setup.go -+++ b/internal/setup/setup.go -@@ -194,6 +194,10 @@ func ReadRelease(dir string) (*Release, error) { - return nil, err - } - -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping release validation due to CHISEL_HACKS=1") -+ return release, nil -+ } - err = release.validate() - if err != nil { - return nil, err --- -2.53.0 - diff --git a/rocks/bread-24.04/hack/banner.txt b/rocks/hack/banner.txt similarity index 100% rename from rocks/bread-24.04/hack/banner.txt rename to rocks/hack/banner.txt diff --git a/rocks/bread-24.04/hack/bread-warning.sh b/rocks/hack/bread-warning.sh similarity index 100% rename from rocks/bread-24.04/hack/bread-warning.sh rename to rocks/hack/bread-warning.sh diff --git a/rocks/bread-24.04/hack/chisel_cut.sh b/rocks/hack/chisel_cut.sh similarity index 100% rename from rocks/bread-24.04/hack/chisel_cut.sh rename to rocks/hack/chisel_cut.sh diff --git a/rocks/bread-chisel-releases-24.04/hack/chisel_override_build.sh b/rocks/hack/chisel_override_build.sh similarity index 100% rename from rocks/bread-chisel-releases-24.04/hack/chisel_override_build.sh rename to rocks/hack/chisel_override_build.sh diff --git a/rocks/bread-chisel-releases-24.04/hack/docker_override_build.sh b/rocks/hack/docker_override_build.sh similarity index 100% rename from rocks/bread-chisel-releases-24.04/hack/docker_override_build.sh rename to rocks/hack/docker_override_build.sh diff --git a/rocks/bread-24.04/hack/hash_inputs.sh b/rocks/hack/hash_inputs.sh similarity index 100% rename from rocks/bread-24.04/hack/hash_inputs.sh rename to rocks/hack/hash_inputs.sh diff --git a/rocks/bread-24.04/hack/lazy-apt.sh b/rocks/hack/lazy-apt.sh similarity index 100% rename from rocks/bread-24.04/hack/lazy-apt.sh rename to rocks/hack/lazy-apt.sh diff --git a/rocks/bread-24.04/hack/sshd-entry.sh b/rocks/hack/sshd-entry.sh similarity index 100% rename from rocks/bread-24.04/hack/sshd-entry.sh rename to rocks/hack/sshd-entry.sh diff --git a/rocks/bread-chisel-releases-24.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch b/rocks/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch similarity index 100% rename from rocks/bread-chisel-releases-24.04/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch rename to rocks/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch diff --git a/rocks/bread-chisel-releases-24.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch b/rocks/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch similarity index 100% rename from rocks/bread-chisel-releases-24.04/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch rename to rocks/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch diff --git a/rocks/bread-chisel-releases-24.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch b/rocks/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch similarity index 100% rename from rocks/bread-chisel-releases-24.04/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch rename to rocks/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch diff --git a/rocks/sync_shared.rb b/rocks/sync_shared.rb new file mode 100644 index 0000000..ee3ca81 --- /dev/null +++ b/rocks/sync_shared.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Populate a rock dir's hack/ (and patches/ for the chisel-releases flavour) +# from the shared rocks/hack + rocks/patches. +# +# rockcraft only copies the project dir (the one with rockcraft.yaml) into the +# build instance, so each rock needs its own physical copy of the shared files +# at pack time. The per-dir copies are gitignored and regenerated from here -- +# rocks/hack + rocks/patches are the single source of truth. +# +# Usage: ruby sync_shared.rb + +require "fileutils" + +ROOT = __dir__ + +# Scripts every rock needs. The chisel-releases flavour also builds chisel + +# docker from source, so it gets those two extra build scripts + the patches. +COMMON = %w[banner.txt bread-warning.sh lazy-apt.sh sshd-entry.sh hash_inputs.sh chisel_cut.sh].freeze +CR_ONLY = %w[chisel_override_build.sh docker_override_build.sh].freeze + +dir = ARGV[0] or abort "usage: ruby sync_shared.rb " +dest = File.join(ROOT, dir) +abort "no such rock dir: #{dir}" unless File.directory?(dest) + +cr = dir.include?("chisel-releases") + +FileUtils.mkdir_p(File.join(dest, "hack")) +(COMMON + (cr ? CR_ONLY : [])).each do |f| + FileUtils.cp(File.join(ROOT, "hack", f), File.join(dest, "hack", f), preserve: true) +end + +if cr + chisel = File.join(dest, "patches", "chisel") + FileUtils.mkdir_p(chisel) + Dir[File.join(ROOT, "patches", "chisel", "*.patch")].each do |p| + FileUtils.cp(p, File.join(chisel, File.basename(p)), preserve: true) + end +end From 6d1bb0298ad809ef08c3c080cdfeb8440b6fae6d Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 09:10:39 +0100 Subject: [PATCH 10/18] ci: trial node24 rockcraft-pack from craft-actions#59 swap the rocks build job's rockcraft-pack from canonical/craft-actions@main to the fork branch in canonical/craft-actions#59 (node24 migration), to clear the node20 deprecation warning. revert to canonical@main once that PR merges. --- .github/workflows/rocks-ci.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rocks-ci.yaml b/.github/workflows/rocks-ci.yaml index fdf0c81..9e95d2d 100644 --- a/.github/workflows/rocks-ci.yaml +++ b/.github/workflows/rocks-ci.yaml @@ -55,7 +55,9 @@ jobs: run: ruby rocks/sync_shared.rb ${{ matrix.dir }} - name: Pack rock - uses: canonical/craft-actions/rockcraft-pack@main + # TRIAL: node24 migration from canonical/craft-actions#59 (fork branch). + # revert to canonical/craft-actions/rockcraft-pack@main once that merges. + uses: dariofaccin/craft-actions/rockcraft-pack@feat/rockcraft-pack-node24 with: path: rocks/${{ matrix.dir }} verbosity: brief From 50014239dbe04de3b329b3db017209033e4e292e Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 09:15:31 +0100 Subject: [PATCH 11/18] refactor: source rock chisel patches from repo root, drop rocks/patches the chisel patches duplicated the repo-root patches/chisel (used by the docker-image build). sync_shared.rb now copies them from there into each cr rock dir, and rocks/patches is removed. --- ...p-maintenance-check-via-CHISEL_HACKS.patch | 49 ---------------- ...ve-maintenance-check-via-CHISEL_HACK.patch | 58 ------------------- ...-release-validation-via-CHISEL_HACKS.patch | 27 --------- rocks/sync_shared.rb | 5 +- 4 files changed, 4 insertions(+), 135 deletions(-) delete mode 100644 rocks/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch delete mode 100644 rocks/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch delete mode 100644 rocks/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch diff --git a/rocks/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch b/rocks/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch deleted file mode 100644 index d3c0637..0000000 --- a/rocks/patches/chisel/0001-chore-skip-maintenance-check-via-CHISEL_HACKS.patch +++ /dev/null @@ -1,49 +0,0 @@ -From 38a1af51d71f7904693a88db64df808afed80827 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:10:52 +0100 -Subject: [PATCH 1/3] chore: skip maintenance check via CHISEL_HACKS - ---- - cmd/chisel/cmd_cut.go | 19 ++++++++++++------- - 1 file changed, 12 insertions(+), 7 deletions(-) - -diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go -index 35c81a7..98c1a36 100644 ---- a/cmd/chisel/cmd_cut.go -+++ b/cmd/chisel/cmd_cut.go -@@ -2,6 +2,7 @@ package main - - import ( - "fmt" -+ "os" - "slices" - "time" - -@@ -63,13 +64,17 @@ func (cmd *cmdCut) Execute(args []string) error { - return err - } - -- if time.Now().Before(release.Maintenance.Standard) { -- if slices.Contains(cmd.Ignore, "unstable") { -- logf(`Warning: This release is in the "unstable" maintenance status. ` + -- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) -- } else { -- return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + -- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping maintenance check due to CHISEL_HACKS=1") -+ } else { -+ if time.Now().Before(release.Maintenance.Standard) { -+ if slices.Contains(cmd.Ignore, "unstable") { -+ logf(`Warning: This release is in the "unstable" maintenance status. ` + -+ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance to be safe`) -+ } else { -+ return fmt.Errorf(`this release is in the "unstable" maintenance status, ` + -+ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ } - } - } - --- -2.53.0 - diff --git a/rocks/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch b/rocks/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch deleted file mode 100644 index 851b6a5..0000000 --- a/rocks/patches/chisel/0002-chore-skip-archive-maintenance-check-via-CHISEL_HACK.patch +++ /dev/null @@ -1,58 +0,0 @@ -From 76d4ec4dfb51fc7d4d625c5ee6021d9452e6b263 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:11:06 +0100 -Subject: [PATCH 2/3] chore: skip archive maintenance check via CHISEL_HACKS - ---- - cmd/chisel/cmd_cut.go | 34 +++++++++++++++++++--------------- - 1 file changed, 19 insertions(+), 15 deletions(-) - -diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go -index 98c1a36..b4381d4 100644 ---- a/cmd/chisel/cmd_cut.go -+++ b/cmd/chisel/cmd_cut.go -@@ -107,22 +107,26 @@ func (cmd *cmdCut) Execute(args []string) error { - archives[archiveName] = openArchive - } - -- hasMaintainedArchive := false -- for _, archive := range archives { -- if archive.Options().Maintained { -- hasMaintainedArchive = true -- break -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping archive maintenance check due to CHISEL_HACKS=1") -+ } else { -+ hasMaintainedArchive := false -+ for _, archive := range archives { -+ if archive.Options().Maintained { -+ hasMaintainedArchive = true -+ break -+ } - } -- } -- if !hasMaintainedArchive { -- if slices.Contains(cmd.Ignore, "unmaintained") { -- logf(`Warning: No archive has "maintained" maintenance status. ` + -- `Consider the different Ubuntu Pro subscriptions to be safe. ` + -- `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) -- } else { -- return fmt.Errorf(`no archive has "maintained" maintenance status, ` + -- `consider the different Ubuntu Pro subscriptions to be safe, ` + -- `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ if !hasMaintainedArchive { -+ if slices.Contains(cmd.Ignore, "unmaintained") { -+ logf(`Warning: No archive has "maintained" maintenance status. ` + -+ `Consider the different Ubuntu Pro subscriptions to be safe. ` + -+ `See https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details.`) -+ } else { -+ return fmt.Errorf(`no archive has "maintained" maintenance status, ` + -+ `consider the different Ubuntu Pro subscriptions to be safe, ` + -+ `see https://documentation.ubuntu.com/chisel/en/latest/reference/chisel-releases/chisel.yaml/#maintenance for details`) -+ } - } - } - --- -2.53.0 - diff --git a/rocks/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch b/rocks/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch deleted file mode 100644 index a6774d0..0000000 --- a/rocks/patches/chisel/0003-chore-skip-release-validation-via-CHISEL_HACKS.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 7af23d09332e1fa104ad5c802e176e90f86d67d3 Mon Sep 17 00:00:00 2001 -From: spread-bread -Date: Mon, 1 Jun 2026 15:11:28 +0100 -Subject: [PATCH 3/3] chore: skip release validation via CHISEL_HACKS - ---- - internal/setup/setup.go | 4 ++++ - 1 file changed, 4 insertions(+) - -diff --git a/internal/setup/setup.go b/internal/setup/setup.go -index 6ed24ab..01ea8eb 100644 ---- a/internal/setup/setup.go -+++ b/internal/setup/setup.go -@@ -194,6 +194,10 @@ func ReadRelease(dir string) (*Release, error) { - return nil, err - } - -+ if os.Getenv("CHISEL_HACKS") == "1" { -+ logf("Skipping release validation due to CHISEL_HACKS=1") -+ return release, nil -+ } - err = release.validate() - if err != nil { - return nil, err --- -2.53.0 - diff --git a/rocks/sync_shared.rb b/rocks/sync_shared.rb index ee3ca81..4a1368d 100644 --- a/rocks/sync_shared.rb +++ b/rocks/sync_shared.rb @@ -14,6 +14,9 @@ require "fileutils" ROOT = __dir__ +# the chisel patches are shared with the repo-root docker-image build +# (hack/build_binaries.sh), so source them from there rather than duplicating. +REPO_ROOT = File.expand_path("..", ROOT) # Scripts every rock needs. The chisel-releases flavour also builds chisel + # docker from source, so it gets those two extra build scripts + the patches. @@ -34,7 +37,7 @@ if cr chisel = File.join(dest, "patches", "chisel") FileUtils.mkdir_p(chisel) - Dir[File.join(ROOT, "patches", "chisel", "*.patch")].each do |p| + Dir[File.join(REPO_ROOT, "patches", "chisel", "*.patch")].each do |p| FileUtils.cp(p, File.join(chisel, File.basename(p)), preserve: true) end end From 71991bc5e9ca9d195b98f83d358e19a2ef2517a2 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 09:49:45 +0100 Subject: [PATCH 12/18] refactor: single rocks/makefile with build-% pattern, drop per-dir makefiles fold the per-dir build recipe into one build-% pattern rule in rocks/makefile (sync shared files -> stamp short-circuit -> rockcraft pack -> rename to .rock) and remove the six per-dir makefiles. each rock dir is now just rockcraft.yaml + the synced hack/ (+ patches/ for the cr flavour). ci is unaffected -- the build job uses the rockcraft-pack action, the test job uses make spread-only. --- rocks/bread-24.04/makefile | 49 ---------------------- rocks/bread-25.10/makefile | 49 ---------------------- rocks/bread-26.04/makefile | 49 ---------------------- rocks/bread-chisel-releases-24.04/makefile | 47 --------------------- rocks/bread-chisel-releases-25.10/makefile | 47 --------------------- rocks/bread-chisel-releases-26.04/makefile | 47 --------------------- rocks/makefile | 41 +++++++++++++++--- 7 files changed, 35 insertions(+), 294 deletions(-) delete mode 100644 rocks/bread-24.04/makefile delete mode 100644 rocks/bread-25.10/makefile delete mode 100644 rocks/bread-26.04/makefile delete mode 100644 rocks/bread-chisel-releases-24.04/makefile delete mode 100644 rocks/bread-chisel-releases-25.10/makefile delete mode 100644 rocks/bread-chisel-releases-26.04/makefile diff --git a/rocks/bread-24.04/makefile b/rocks/bread-24.04/makefile deleted file mode 100644 index 1bcf932..0000000 --- a/rocks/bread-24.04/makefile +++ /dev/null @@ -1,49 +0,0 @@ -.DEFAULT_GOAL := help - -DOCKER ?= docker - -# Predictable artefact name (matches the dir + the spread-rock- load tag). -ROCK := bread-24.04.rock - -.PHONY: help -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -.PHONY: build -build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) - -.PHONY: FORCE -FORCE: - -# Stamp content = hash of all build inputs; only rewritten when it changes, so -# the rock only repacks on a real input change. -.PHONY: sync -sync: ## Populate hack/ (+ patches/) from shared rocks/hack - @ruby ../sync_shared.rb $(notdir $(CURDIR)) - -.rock.stamp: sync FORCE - @new=$$(hack/hash_inputs.sh) ; \ - cur=$$(cat $@ 2>/dev/null || true) ; \ - [ "$$new" = "$$cur" ] || echo "$$new" > $@ - -$(ROCK): .rock.stamp - rockcraft pack --verbosity=brief - mv bread_24.04_*.rock $(ROCK) - -.PHONY: size -size: ## Show the packed rock size - @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi - -.PHONY: ls-rock -ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) - @tag=bread-ls-$$$$ ; \ - skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ - cid=$$($(DOCKER) create $$tag:latest) ; \ - $(DOCKER) export $$cid | tar tv ; \ - $(DOCKER) rm $$cid > /dev/null ; \ - $(DOCKER) rmi $$tag:latest > /dev/null - -.PHONY: clean -clean: ## Remove the packed rock + stamp - rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-25.10/makefile b/rocks/bread-25.10/makefile deleted file mode 100644 index a95a252..0000000 --- a/rocks/bread-25.10/makefile +++ /dev/null @@ -1,49 +0,0 @@ -.DEFAULT_GOAL := help - -DOCKER ?= docker - -# Predictable artefact name (matches the dir + the spread-rock- load tag). -ROCK := bread-25.10.rock - -.PHONY: help -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -.PHONY: build -build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) - -.PHONY: FORCE -FORCE: - -# Stamp content = hash of all build inputs; only rewritten when it changes, so -# the rock only repacks on a real input change. -.PHONY: sync -sync: ## Populate hack/ (+ patches/) from shared rocks/hack - @ruby ../sync_shared.rb $(notdir $(CURDIR)) - -.rock.stamp: sync FORCE - @new=$$(hack/hash_inputs.sh) ; \ - cur=$$(cat $@ 2>/dev/null || true) ; \ - [ "$$new" = "$$cur" ] || echo "$$new" > $@ - -$(ROCK): .rock.stamp - rockcraft pack --verbosity=brief - mv bread_25.10_*.rock $(ROCK) - -.PHONY: size -size: ## Show the packed rock size - @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi - -.PHONY: ls-rock -ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) - @tag=bread-ls-$$$$ ; \ - skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ - cid=$$($(DOCKER) create $$tag:latest) ; \ - $(DOCKER) export $$cid | tar tv ; \ - $(DOCKER) rm $$cid > /dev/null ; \ - $(DOCKER) rmi $$tag:latest > /dev/null - -.PHONY: clean -clean: ## Remove the packed rock + stamp - rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-26.04/makefile b/rocks/bread-26.04/makefile deleted file mode 100644 index c417070..0000000 --- a/rocks/bread-26.04/makefile +++ /dev/null @@ -1,49 +0,0 @@ -.DEFAULT_GOAL := help - -DOCKER ?= docker - -# Predictable artefact name (matches the dir + the spread-rock- load tag). -ROCK := bread-26.04.rock - -.PHONY: help -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -.PHONY: build -build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) - -.PHONY: FORCE -FORCE: - -# Stamp content = hash of all build inputs; only rewritten when it changes, so -# the rock only repacks on a real input change. -.PHONY: sync -sync: ## Populate hack/ (+ patches/) from shared rocks/hack - @ruby ../sync_shared.rb $(notdir $(CURDIR)) - -.rock.stamp: sync FORCE - @new=$$(hack/hash_inputs.sh) ; \ - cur=$$(cat $@ 2>/dev/null || true) ; \ - [ "$$new" = "$$cur" ] || echo "$$new" > $@ - -$(ROCK): .rock.stamp - rockcraft pack --verbosity=brief - mv bread_26.04_*.rock $(ROCK) - -.PHONY: size -size: ## Show the packed rock size - @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi - -.PHONY: ls-rock -ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) - @tag=bread-ls-$$$$ ; \ - skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ - cid=$$($(DOCKER) create $$tag:latest) ; \ - $(DOCKER) export $$cid | tar tv ; \ - $(DOCKER) rm $$cid > /dev/null ; \ - $(DOCKER) rmi $$tag:latest > /dev/null - -.PHONY: clean -clean: ## Remove the packed rock + stamp - rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-chisel-releases-24.04/makefile b/rocks/bread-chisel-releases-24.04/makefile deleted file mode 100644 index 586d168..0000000 --- a/rocks/bread-chisel-releases-24.04/makefile +++ /dev/null @@ -1,47 +0,0 @@ -.DEFAULT_GOAL := help - -DOCKER ?= docker - -# Predictable artefact name (matches the dir + the spread-rock- load tag). -ROCK := bread-chisel-releases-24.04.rock - -.PHONY: help -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -.PHONY: build -build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) - -.PHONY: FORCE -FORCE: - -.PHONY: sync -sync: ## Populate hack/ (+ patches/) from shared rocks/hack - @ruby ../sync_shared.rb $(notdir $(CURDIR)) - -.rock.stamp: sync FORCE - @new=$$(hack/hash_inputs.sh) ; \ - cur=$$(cat $@ 2>/dev/null || true) ; \ - [ "$$new" = "$$cur" ] || echo "$$new" > $@ - -$(ROCK): .rock.stamp - rockcraft pack --verbosity=brief - mv bread-chisel-releases_24.04_*.rock $(ROCK) - -.PHONY: size -size: ## Show the packed rock size - @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi - -.PHONY: ls-rock -ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) - @tag=bread-cr-ls-$$$$ ; \ - skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ - cid=$$($(DOCKER) create $$tag:latest) ; \ - $(DOCKER) export $$cid | tar tv ; \ - $(DOCKER) rm $$cid > /dev/null ; \ - $(DOCKER) rmi $$tag:latest > /dev/null - -.PHONY: clean -clean: ## Remove the packed rock + stamp - rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-chisel-releases-25.10/makefile b/rocks/bread-chisel-releases-25.10/makefile deleted file mode 100644 index 649462b..0000000 --- a/rocks/bread-chisel-releases-25.10/makefile +++ /dev/null @@ -1,47 +0,0 @@ -.DEFAULT_GOAL := help - -DOCKER ?= docker - -# Predictable artefact name (matches the dir + the spread-rock- load tag). -ROCK := bread-chisel-releases-25.10.rock - -.PHONY: help -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -.PHONY: build -build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) - -.PHONY: FORCE -FORCE: - -.PHONY: sync -sync: ## Populate hack/ (+ patches/) from shared rocks/hack - @ruby ../sync_shared.rb $(notdir $(CURDIR)) - -.rock.stamp: sync FORCE - @new=$$(hack/hash_inputs.sh) ; \ - cur=$$(cat $@ 2>/dev/null || true) ; \ - [ "$$new" = "$$cur" ] || echo "$$new" > $@ - -$(ROCK): .rock.stamp - rockcraft pack --verbosity=brief - mv bread-chisel-releases_25.10_*.rock $(ROCK) - -.PHONY: size -size: ## Show the packed rock size - @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi - -.PHONY: ls-rock -ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) - @tag=bread-cr-ls-$$$$ ; \ - skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ - cid=$$($(DOCKER) create $$tag:latest) ; \ - $(DOCKER) export $$cid | tar tv ; \ - $(DOCKER) rm $$cid > /dev/null ; \ - $(DOCKER) rmi $$tag:latest > /dev/null - -.PHONY: clean -clean: ## Remove the packed rock + stamp - rm -f $(ROCK) .rock.stamp diff --git a/rocks/bread-chisel-releases-26.04/makefile b/rocks/bread-chisel-releases-26.04/makefile deleted file mode 100644 index e56137e..0000000 --- a/rocks/bread-chisel-releases-26.04/makefile +++ /dev/null @@ -1,47 +0,0 @@ -.DEFAULT_GOAL := help - -DOCKER ?= docker - -# Predictable artefact name (matches the dir + the spread-rock- load tag). -ROCK := bread-chisel-releases-26.04.rock - -.PHONY: help -help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -.PHONY: build -build: $(ROCK) ## Pack the rock (short-circuits if no inputs changed) - -.PHONY: FORCE -FORCE: - -.PHONY: sync -sync: ## Populate hack/ (+ patches/) from shared rocks/hack - @ruby ../sync_shared.rb $(notdir $(CURDIR)) - -.rock.stamp: sync FORCE - @new=$$(hack/hash_inputs.sh) ; \ - cur=$$(cat $@ 2>/dev/null || true) ; \ - [ "$$new" = "$$cur" ] || echo "$$new" > $@ - -$(ROCK): .rock.stamp - rockcraft pack --verbosity=brief - mv bread-chisel-releases_26.04_*.rock $(ROCK) - -.PHONY: size -size: ## Show the packed rock size - @if [ -f $(ROCK) ]; then du -h $(ROCK) | cut -f1; else echo "$(ROCK) not built"; fi - -.PHONY: ls-rock -ls-rock: $(ROCK) ## List the rock filesystem (skopeo -> docker export -> tar tv) - @tag=bread-cr-ls-$$$$ ; \ - skopeo --insecure-policy copy oci-archive:$(ROCK) docker-daemon:$$tag:latest > /dev/null ; \ - cid=$$($(DOCKER) create $$tag:latest) ; \ - $(DOCKER) export $$cid | tar tv ; \ - $(DOCKER) rm $$cid > /dev/null ; \ - $(DOCKER) rmi $$tag:latest > /dev/null - -.PHONY: clean -clean: ## Remove the packed rock + stamp - rm -f $(ROCK) .rock.stamp diff --git a/rocks/makefile b/rocks/makefile index edb8155..c4197bd 100644 --- a/rocks/makefile +++ b/rocks/makefile @@ -21,14 +21,30 @@ SPREAD_FILTER ?= $(addprefix rock:,$(addsuffix -$(NATIVE_ARCH),$(DIRS))) .PHONY: help help: ## Show this help - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ - awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-16s\033[0m %s\n", $$1, $$2}' + @grep -E '^[a-zA-Z_-]+%?:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-18s\033[0m %s\n", $$1, $$2}' @echo "" @echo "rock dirs: $(DIRS)" +# Pack one rock dir, e.g. `make build-bread-24.04`. Syncs the shared +# hack/patches first, then short-circuits on a stamp of all build inputs so it +# only repacks on a real change. rockcraft names the artefact +# __.rock (underscores); the predictable .rock has none, +# so `*_*.rock` only ever matches the freshly-packed file. +.PHONY: build-% +build-%: ## Pack one rock (make build-) + @ruby sync_shared.rb $* + @cd $* && \ + new=$$(hack/hash_inputs.sh) ; cur=$$(cat .rock.stamp 2>/dev/null || true) ; \ + if [ "$$new" = "$$cur" ] && [ -f $*.rock ]; then \ + echo "==> $* up-to-date" ; \ + else \ + echo "==> packing $*" ; \ + rockcraft pack --verbosity=brief && mv *_*.rock $*.rock && echo "$$new" > .rock.stamp ; \ + fi + .PHONY: build-all -build-all: ## Pack every rock (narrow via DIRS="bread-24.04 ...") - @for d in $(DIRS); do echo "==> build $$d" && $(MAKE) -C $$d build || exit 1; done +build-all: $(addprefix build-,$(DIRS)) ## Pack every rock (narrow via DIRS="bread-24.04 ...") .PHONY: load load: ## Load every built rock into the local daemon as spread-rock-:latest @@ -47,7 +63,20 @@ test: build-all load ## Build + load all rocks, then run the top-level spread s spread-only: load ## Load prebuilt rocks + run spread, no packing (for ci where rocks are downloaded) cd tests && spread $(SPREAD_FILTER) +.PHONY: size +size: ## Show packed rock sizes + @for d in $(DIRS); do [ -f $$d/$$d.rock ] && printf '%-32s %s\n' "$$d" "$$(du -h $$d/$$d.rock | cut -f1)" || echo "$$d (not built)" ; done + +.PHONY: ls-rock-% +ls-rock-%: ## List a rock's filesystem (make ls-rock-) + @tag=ls-$*-$$$$ ; \ + skopeo --insecure-policy copy oci-archive:$*/$*.rock docker-daemon:$$tag:latest > /dev/null ; \ + cid=$$($(DOCKER) create $$tag:latest) ; \ + $(DOCKER) export $$cid | tar tv ; \ + $(DOCKER) rm $$cid > /dev/null ; \ + $(DOCKER) rmi $$tag:latest > /dev/null + .PHONY: clean -clean: ## Per-dir clean + remove the loaded spread-rock-* tags - @for d in $(DIRS); do $(MAKE) -C $$d clean ; done +clean: ## Remove packed rocks, stamps, synced hack/patches + loaded tags + @for d in $(DIRS); do rm -f $$d/$$d.rock $$d/.rock.stamp ; rm -rf $$d/hack $$d/patches ; done -@for d in $(DIRS); do $(DOCKER) rmi -f "spread-rock-$$d:latest" 2>/dev/null ; done From 3b4844a38031e67cf4e0986cd50faba0bd8a104f Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 14:00:44 +0100 Subject: [PATCH 13/18] refactor: couple rock spread suite into top-level tests/ move the rock contract into the top-level tests/spread.yaml as its own backend (rock, docker-runs the loaded rock) + suite (contract-rocks), alongside the docker-image suites. spread has no systems wildcards, so scope by name: the bread-image suites whitelist the outer systems, contract-rocks excludes them. workflows stay separate and each runs only its backend: the docker ci runs spread outer:..., the rocks ci runs spread rock:... (via make spread-only, now cd ../tests). rocks/tests/ is removed. --- rocks/tests/spread.yaml | 35 ------------------- .../contract-rocks}/run/task.yaml | 0 .../allocate.sh => tests/rock-allocate.sh | 0 .../tests/discard.sh => tests/rock-discard.sh | 0 4 files changed, 35 deletions(-) delete mode 100644 rocks/tests/spread.yaml rename {rocks/tests/contract => tests/contract-rocks}/run/task.yaml (100%) rename rocks/tests/allocate.sh => tests/rock-allocate.sh (100%) rename rocks/tests/discard.sh => tests/rock-discard.sh (100%) diff --git a/rocks/tests/spread.yaml b/rocks/tests/spread.yaml deleted file mode 100644 index 88da93d..0000000 --- a/rocks/tests/spread.yaml +++ /dev/null @@ -1,35 +0,0 @@ -project: rocks - -path: /spread-bread-rocks - -exclude: - - .git - - .github - -backends: - # The bread rock IS the test host (unlike a payload rock), so allocate just - # `docker run`s the rock that was loaded into the local daemon as - # spread-rock-:latest, and ssh's in. One system per (flavour, ver, arch). - rock: - type: adhoc - allocate: source allocate.sh - discard: source discard.sh - systems: - - bread-24.04-amd64: {username: root, password: bread} - - bread-24.04-arm64: {username: root, password: bread} - - bread-25.10-amd64: {username: root, password: bread} - - bread-25.10-arm64: {username: root, password: bread} - - bread-26.04-amd64: {username: root, password: bread} - - bread-26.04-arm64: {username: root, password: bread} - - bread-chisel-releases-24.04-amd64: {username: root, password: bread} - - bread-chisel-releases-24.04-arm64: {username: root, password: bread} - - bread-chisel-releases-25.10-amd64: {username: root, password: bread} - - bread-chisel-releases-25.10-arm64: {username: root, password: bread} - - bread-chisel-releases-26.04-amd64: {username: root, password: bread} - - bread-chisel-releases-26.04-arm64: {username: root, password: bread} - -suites: - contract/: - summary: rock contract -- ubuntu version + arch + bread bits (+ chisel/tooling for the cr flavour) - -kill-timeout: 15m diff --git a/rocks/tests/contract/run/task.yaml b/tests/contract-rocks/run/task.yaml similarity index 100% rename from rocks/tests/contract/run/task.yaml rename to tests/contract-rocks/run/task.yaml diff --git a/rocks/tests/allocate.sh b/tests/rock-allocate.sh similarity index 100% rename from rocks/tests/allocate.sh rename to tests/rock-allocate.sh diff --git a/rocks/tests/discard.sh b/tests/rock-discard.sh similarity index 100% rename from rocks/tests/discard.sh rename to tests/rock-discard.sh From 0970978d52de9f69168c6bdb3b5ed78f690d733f Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 14:01:40 +0100 Subject: [PATCH 14/18] refactor: wire rock backend + suite into tests/spread.yaml complete the coupling: add the rock backend + contract-rocks suite (with name-based system scoping) to tests/spread.yaml, and repoint rocks/makefile's test targets at ../tests. (the file moves landed in the previous commit; the add of these two was dropped by a stale pathspec.) --- rocks/makefile | 8 ++++---- tests/spread.yaml | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/rocks/makefile b/rocks/makefile index c4197bd..80e9184 100644 --- a/rocks/makefile +++ b/rocks/makefile @@ -56,12 +56,12 @@ load: ## Load every built rock into the local daemon as spread-rock-:lates done .PHONY: test -test: build-all load ## Build + load all rocks, then run the top-level spread suite (native arch) - cd tests && spread $(SPREAD_FILTER) +test: build-all load ## Build + load all rocks, then run the rock suite (native arch) + cd ../tests && spread $(SPREAD_FILTER) .PHONY: spread-only -spread-only: load ## Load prebuilt rocks + run spread, no packing (for ci where rocks are downloaded) - cd tests && spread $(SPREAD_FILTER) +spread-only: load ## Load prebuilt rocks + run the rock suite, no packing (for ci where rocks are downloaded) + cd ../tests && spread $(SPREAD_FILTER) .PHONY: size size: ## Show packed rock sizes diff --git a/tests/spread.yaml b/tests/spread.yaml index 587b189..4ac1505 100644 --- a/tests/spread.yaml +++ b/tests/spread.yaml @@ -93,9 +93,37 @@ backends: password: bread workers: 2 + # The rock suite (contract-rocks) runs against this backend instead: it + # docker-runs the loaded rock directly (the rock is its own sshd host), no + # bread-test outer container. Coupled into this one spread.yaml but kept a + # separate backend + suite; the docker / rocks ci workflows each filter to + # their own backend (outer: vs rock:). + rock: + type: adhoc + allocate: source rock-allocate.sh + discard: source rock-discard.sh + systems: + - bread-24.04-amd64: {username: root, password: bread} + - bread-24.04-arm64: {username: root, password: bread} + - bread-25.10-amd64: {username: root, password: bread} + - bread-25.10-arm64: {username: root, password: bread} + - bread-26.04-amd64: {username: root, password: bread} + - bread-26.04-arm64: {username: root, password: bread} + - bread-chisel-releases-24.04-amd64: {username: root, password: bread} + - bread-chisel-releases-24.04-arm64: {username: root, password: bread} + - bread-chisel-releases-25.10-amd64: {username: root, password: bread} + - bread-chisel-releases-25.10-arm64: {username: root, password: bread} + - bread-chisel-releases-26.04-amd64: {username: root, password: bread} + - bread-chisel-releases-26.04-arm64: {username: root, password: bread} + suites: + # spread has no wildcards in `systems:`, so scope each suite by explicit + # name: the bread-image suites whitelist the outer systems; the rock suite + # excludes them (leaving the rock backend's systems). this keeps a bare + # `spread` from cross-running a suite on the wrong backend. contract-bread/: summary: bread image contract -- exercises inlined/bread-.yaml + systems: [ubuntu-26.04-amd64, ubuntu-26.04-arm64] # task.yaml here will run inner spread against each bread inlined yaml. # Variants below pick which version to exercise. environment: @@ -106,8 +134,13 @@ suites: contract-bread-chisel-releases/: summary: bread-chisel-releases image contract + systems: [ubuntu-26.04-amd64, ubuntu-26.04-arm64] environment: INLINED/v24_04: $REPO/inlined/bread-chisel-releases-24.04.yaml INLINED/v25_10: $REPO/inlined/bread-chisel-releases-25.10.yaml INLINED/v26_04: $REPO/inlined/bread-chisel-releases-26.04.yaml INLINED/v26_10: $REPO/inlined/bread-chisel-releases-26.10.yaml + + contract-rocks/: + summary: rock contract -- docker-runs each loaded rock + checks it + systems: [-ubuntu-26.04-amd64, -ubuntu-26.04-arm64] From 662e4d9ff74b964ebac240b6623734f953063bd7 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 14:04:53 +0100 Subject: [PATCH 15/18] refactor: source shared rock hack scripts from repo root banner.txt, bread-warning.sh and lazy-apt.sh were byte-identical copies of the repo-root hack/ ones. sync_shared.rb now pulls those three from repo-root hack/ (like the chisel patches), keeping only the rock-specific helpers (sshd-entry, chisel_cut, the chisel/docker build scripts, and a rock-flavoured hash_inputs) in rocks/hack. edit-once for the shared shim/banner logic. --- rocks/hack/banner.txt | 13 ------------- rocks/hack/bread-warning.sh | 39 ------------------------------------- rocks/hack/lazy-apt.sh | 21 -------------------- 3 files changed, 73 deletions(-) delete mode 100644 rocks/hack/banner.txt delete mode 100755 rocks/hack/bread-warning.sh delete mode 100755 rocks/hack/lazy-apt.sh diff --git a/rocks/hack/banner.txt b/rocks/hack/banner.txt deleted file mode 100644 index 3c06b8b..0000000 --- a/rocks/hack/banner.txt +++ /dev/null @@ -1,13 +0,0 @@ - ████▓▓▓▓▓ ▓▓▓▓▓▓▓▓ - ███▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▓▓ - ███▒▒▒▒░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░▒▒▓▓▓▓ -███▒▒▒▒░░░░░░░░░░░░░░ !! INSECURE TEST IMAGE !! ░░░░░░░░░░░░░░░░░░░░▒▒▓▓ -███▒▒░░░░░░░ ░░▒▒▓▓▓▓ -███▒▒▒▒░░░░ this image accepts ANY password for root (pam_permit). ░░▓▓▓▓ - ███▒▒▒▒░░ it is built for spread test containers only. do NOT ░░▓▓ - ███▒▒░░ expose to networks, do NOT use in production. do NOT ░░▓▓ - ███▒▒░░ use for anything that needs auth. provided "as is" ░░▓▓ - ███▒▒░░ without warranty of any kind. @lczyk ░░▓▓ - ███▒▒░░░ see: github.com/lczyk/spread-bread ░░░▓▓ - ███▒▒░░░░░░░ ░░░░░░░▓▓ - █▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ diff --git a/rocks/hack/bread-warning.sh b/rocks/hack/bread-warning.sh deleted file mode 100755 index 2ee0140..0000000 --- a/rocks/hack/bread-warning.sh +++ /dev/null @@ -1,39 +0,0 @@ -# bread-warning: insecure-image notice. -# -# Sourced from /etc/profile.d (login shells) and /root/.bashrc (interactive -# non-login shells). BREAD_BANNER_SHOWN dedupes when both fire in the same -# session. Silent on non-tty (spread, scripts, ci logs) unless -# BREAD_BANNER_FORCE is set. -# -# Banner content is verbatim from /etc/bread-banner.txt (shipped from -# hack/banner.txt). On tty the "!! INSECURE TEST IMAGE !!" header is wrapped -# in red ANSI; everything else is printed as-is. -# -# Note: sshd `Banner` (pre-auth) was considered and rejected. sshd has no -# tty-conditional banner; the openssh client prints SSH_MSG_USERAUTH_BANNER -# to stderr on every connection, which would inject ~14 lines of noise into -# every non-interactive `ssh root@host cmd` that spread runs. This shell -# hook covers every interactive path a human actually hits (ssh login shell -# + bashrc for `docker exec -it`), so the pre-auth banner buys nothing -# worth the noise. - -[ -n "$BREAD_BANNER_SHOWN" ] && return 0 -if [ ! -t 1 ] && [ -z "$BREAD_BANNER_FORCE" ]; then - export BREAD_BANNER_SHOWN=1 - return 0 -fi -export BREAD_BANNER_SHOWN=1 - -__bw_banner=/etc/bread-banner.txt -[ -r "$__bw_banner" ] || { unset __bw_banner; return 0; } - -if [ -t 1 ] && [ -z "$NO_COLOR" ]; then - __bw_r=$(printf '\033[1;31m') - __bw_n=$(printf '\033[0m') - sed "s/!! INSECURE TEST IMAGE !!/${__bw_r}!! INSECURE TEST IMAGE !!${__bw_n}/" "$__bw_banner" - unset __bw_r __bw_n -else - cat "$__bw_banner" -fi - -unset __bw_banner diff --git a/rocks/hack/lazy-apt.sh b/rocks/hack/lazy-apt.sh deleted file mode 100755 index 1038a4f..0000000 --- a/rocks/hack/lazy-apt.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# lazy-apt: on first non-update call, runs apt-get update then replaces both -# shims with symlinks to real apt. Installed at /usr/local/bin/apt and -# /usr/local/bin/apt-get (ahead of /usr/bin in PATH) so tests that call -# apt/apt-get without an explicit update still work after the image was built -# with apt lists wiped. -# -# Replaces with symlinks rather than deleting: bash hashes the shim path on -# first use, so deletion would leave a stale hash entry causing subsequent -# calls to fail with "No such file or directory". - -real="/usr/bin/$(basename "$0")" - -if [ "$1" != "update" ]; then - /usr/bin/apt-get update -fi - -ln -sf /usr/bin/apt /usr/local/bin/apt -ln -sf /usr/bin/apt-get /usr/local/bin/apt-get - -exec "$real" "$@" From 53e767ee4bbd3072e06fafcdb06c87702cc1e868 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 14:05:14 +0100 Subject: [PATCH 16/18] refactor: point sync_shared.rb at repo-root hack for shared scripts complete the previous commit: sync_shared.rb now copies banner.txt, bread-warning.sh and lazy-apt.sh from repo-root hack/ (the rock-hack copies were removed there). the add of this file was dropped by a stale pathspec. --- rocks/sync_shared.rb | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/rocks/sync_shared.rb b/rocks/sync_shared.rb index 4a1368d..e3cffef 100644 --- a/rocks/sync_shared.rb +++ b/rocks/sync_shared.rb @@ -2,26 +2,32 @@ # frozen_string_literal: true # Populate a rock dir's hack/ (and patches/ for the chisel-releases flavour) -# from the shared rocks/hack + rocks/patches. +# from the shared sources, so each rock dir is hand-maintained in one place. # # rockcraft only copies the project dir (the one with rockcraft.yaml) into the # build instance, so each rock needs its own physical copy of the shared files -# at pack time. The per-dir copies are gitignored and regenerated from here -- -# rocks/hack + rocks/patches are the single source of truth. +# at pack time. The per-dir copies are gitignored and regenerated from here. +# +# Sources: +# - repo-root hack/ + patches/chisel -- shared with the docker-image build, +# sourced from there rather than duplicated. +# - rocks/hack -- rock-specific helpers (incl. a rock-flavoured hash_inputs.sh +# that differs from the root one). # # Usage: ruby sync_shared.rb require "fileutils" ROOT = __dir__ -# the chisel patches are shared with the repo-root docker-image build -# (hack/build_binaries.sh), so source them from there rather than duplicating. REPO_ROOT = File.expand_path("..", ROOT) -# Scripts every rock needs. The chisel-releases flavour also builds chisel + -# docker from source, so it gets those two extra build scripts + the patches. -COMMON = %w[banner.txt bread-warning.sh lazy-apt.sh sshd-entry.sh hash_inputs.sh chisel_cut.sh].freeze -CR_ONLY = %w[chisel_override_build.sh docker_override_build.sh].freeze +# Identical to the repo-root copies -> single source there. +ROOT_HACK = %w[banner.txt bread-warning.sh lazy-apt.sh].freeze +# Rock-specific (hash_inputs.sh here differs from the root one; the rest are +# rock-only). The chisel-releases flavour also builds chisel + docker from +# source, so it gets those two extra build scripts + the patches. +ROCK_HACK = %w[sshd-entry.sh hash_inputs.sh chisel_cut.sh].freeze +CR_ONLY = %w[chisel_override_build.sh docker_override_build.sh].freeze dir = ARGV[0] or abort "usage: ruby sync_shared.rb " dest = File.join(ROOT, dir) @@ -30,7 +36,10 @@ cr = dir.include?("chisel-releases") FileUtils.mkdir_p(File.join(dest, "hack")) -(COMMON + (cr ? CR_ONLY : [])).each do |f| +ROOT_HACK.each do |f| + FileUtils.cp(File.join(REPO_ROOT, "hack", f), File.join(dest, "hack", f), preserve: true) +end +(ROCK_HACK + (cr ? CR_ONLY : [])).each do |f| FileUtils.cp(File.join(ROOT, "hack", f), File.join(dest, "hack", f), preserve: true) end From e81b3aef48d8b4a95a814ef897fa5c003ca907a8 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 23:02:20 +0100 Subject: [PATCH 17/18] chore: empty commit From f0218b254f1da09a584f1dc1eae7b15c523e2478 Mon Sep 17 00:00:00 2001 From: lczyk Date: Tue, 2 Jun 2026 23:04:12 +0100 Subject: [PATCH 18/18] ci: revert rockcraft-pack to canonical@main node24 migration (canonical/craft-actions#59) merged upstream, so drop the fork-branch trial and point back at canonical/craft-actions/rockcraft-pack@main. --- .github/workflows/rocks-ci.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/rocks-ci.yaml b/.github/workflows/rocks-ci.yaml index 9e95d2d..fdf0c81 100644 --- a/.github/workflows/rocks-ci.yaml +++ b/.github/workflows/rocks-ci.yaml @@ -55,9 +55,7 @@ jobs: run: ruby rocks/sync_shared.rb ${{ matrix.dir }} - name: Pack rock - # TRIAL: node24 migration from canonical/craft-actions#59 (fork branch). - # revert to canonical/craft-actions/rockcraft-pack@main once that merges. - uses: dariofaccin/craft-actions/rockcraft-pack@feat/rockcraft-pack-node24 + uses: canonical/craft-actions/rockcraft-pack@main with: path: rocks/${{ matrix.dir }} verbosity: brief