diff --git a/CLAUDE.md b/CLAUDE.md index 581d797..5964ea6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,7 +95,8 @@ is the hardening knob). — full detail: `docs/src/security/threat-model.md`. checks (`bash -n`, the `host.sh` dry-run recipe, and — on a Nix+KVM box — `nix flake check` / `bash tests/boot.sh` / a `--shell` pass for TTY changes); if green, commit without stopping to ask per item. Surface anything only verifiable on a Nix+KVM box so it gets checked before being - claimed done. + claimed done. `./ci.sh` runs the exact `flake-check.yml` steps locally (it reads their `run:` + scripts straight from the workflow) — the fastest way to reproduce CI before a commit. - **Definition of done for a behaviour change:** `nix flake check` green **and** a stub-package boot test asserting the new behaviour under `tcg`/`q35` — plus a human `--shell` pass if it touches the TTY. Full loop + the fast stub-`claude` pattern: `docs/src/developing/build-test-debug.md`. diff --git a/ci.sh b/ci.sh new file mode 100755 index 0000000..ff6578d --- /dev/null +++ b/ci.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Local CI — runs the SAME checks as .github/workflows/flake-check.yml by pulling +# each step's `run:` script straight from that workflow, so the command list lives +# in exactly one place. Run before every commit. +# +# Safety: these `run:` scripts execute as your user with NO sandbox. If the workflow +# differs from origin/main (an unreviewed/unexpected change), the commands are shown +# and confirmation is required first — a silent edit to .github can't run behind your +# back. (A compromised origin/main is out of scope: it auto-deploys the docs site to +# GitHub Pages anyway.) +# +# `--inputs-from .` resolves every `nixpkgs#tool` against this flake's locked +# nixpkgs, so the tools are hash-pinned to flake.lock (no unpinned registry fetch). +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +workflow=.github/workflows/flake-check.yml + +steps=$(nix run --inputs-from . nixpkgs#yq -- -r \ + '.jobs.check.steps[] | select(.run) | "echo; echo \"» \(.name)\"\n\(.run)"' \ + "$workflow") + +# Gate: if the workflow deviates from the trusted remote, show what runs and confirm. +ref=origin/main +if git rev-parse --verify --quiet "$ref" >/dev/null && + ! git diff --quiet "$ref" -- "$workflow"; then + echo "⚠ $workflow differs from $ref — these commands will run as $(whoami):" + printf '%s\n' "----------------------------------------" "$steps" "----------------------------------------" + [ -t 0 ] || { + echo "refusing to auto-run a modified workflow non-interactively." + exit 1 + } + read -rp "Proceed? [y/N] " reply + [ "$reply" = y ] || [ "$reply" = Y ] || { + echo "aborted." + exit 1 + } +fi + +bash -euo pipefail <<<"$steps" + +echo +echo "✓ local CI passed"