feat: emit release drill record drafts #159
14 changed files with 372 additions and 54 deletions
|
|
@ -15,6 +15,7 @@ which records not just what changed but what each drill run proved.
|
|||
|
||||
### Added
|
||||
|
||||
- The hardware drill can pin inputs and emit an uncoloured release-record draft with a shared run ID (#152)
|
||||
- `kimi-box` template — the Moonshot Kimi CLI agent seed (#158; rig#109's tenant)
|
||||
|
||||
## 0.9.0 — 2026-07-21
|
||||
|
|
|
|||
|
|
@ -49,13 +49,15 @@ By default that installs the **latest release** — the installer resolves the
|
|||
release tag off GitHub's `releases/latest` redirect (no API, no token) and
|
||||
downloads exactly that tree, so two operators running it get the same box.
|
||||
If the resolution fails it says so and stops — it never silently hands out
|
||||
`main`. `BOX_REF` picks another channel (a set ref is tried as a tag first,
|
||||
then as a branch — [#83](https://github.com/heavy-duty/box/issues/83)):
|
||||
`main`. `BOX_REF` picks another channel: a named ref is tried as a tag first,
|
||||
then as a branch ([#83](https://github.com/heavy-duty/box/issues/83)), while a
|
||||
full 40-hex commit SHA selects GitHub's immutable commit archive:
|
||||
|
||||
```sh
|
||||
curl -fsSL .../install.sh | bash # the latest release (default)
|
||||
curl -fsSL .../install.sh | BOX_REF=0.6.0 bash # pin a release
|
||||
curl -fsSL .../install.sh | BOX_REF=main bash # the development tip
|
||||
curl -fsSL .../install.sh | BOX_REF=<full-commit-sha> bash # exact tree
|
||||
```
|
||||
|
||||
(A dev tree's `VERSION` carries a `-dev` suffix, so it lands beside your
|
||||
|
|
|
|||
32
bin/box
32
bin/box
|
|
@ -1096,36 +1096,48 @@ load_template() {
|
|||
T_DISK="${disk:-${BOX_DISK:-${T_DISK:-60GiB}}}"
|
||||
}
|
||||
|
||||
# The ONE substitution a template gets — user-data.yaml is otherwise passed to
|
||||
# Incus verbatim. The tenant seeds preinstall rig, which inverts the rig→box
|
||||
# install edge (rig#28: rig installs box on hosts; now box guests install rig),
|
||||
# and that edge needs a pin point (#81): the seed carries @RIG_REPO@ /
|
||||
# @RIG_REF@ tokens, resolved here from the mint environment — RIG_REPO
|
||||
# (default heavy-duty/rig) and RIG_REF (default main). Both directions track
|
||||
# main unpinned today, said honestly (the same treatment rig#29 gave box's own
|
||||
# unpinned install) until a release flow exists (rig#32 / #83). The values are
|
||||
# The ONE substitution a template gets — @RIG_INSTALL@ — while user-data.yaml
|
||||
# is otherwise passed to Incus verbatim. The tenant seeds preinstall rig, which
|
||||
# inverts the rig→box install edge (rig#28: rig installs box on hosts; now box
|
||||
# guests install rig), and that edge needs a pin point (#81). The renderer
|
||||
# resolves the mint environment's RIG_REPO (default heavy-duty/rig) and RIG_REF
|
||||
# (default main) into the whole installer command. Named refs keep the normal
|
||||
# tag/branch channel; a drill-pinned commit carries its immutable archive as
|
||||
# RIG_INSTALL_SOURCE (#152). Both values are
|
||||
# allowlist-validated BEFORE touching the YAML: they land inside a runcmd
|
||||
# shell line, so a quote, a space or a newline smuggled through the
|
||||
# environment must die on the host, never execute in the guest. bash's =~
|
||||
# anchors to the whole string — a multi-line value cannot sneak one clean
|
||||
# line past it the way a line-oriented grep would.
|
||||
# The rig pin, resolved from the mint environment, in ONE place: render_userdata
|
||||
# substitutes it into the seed, and the mint stamp (#103) records it onto the
|
||||
# builds it into the seed, and the mint stamp (#103) records it onto the
|
||||
# instance. Two spellings of the same default would eventually disagree, and a
|
||||
# stamp that disagrees with the seed is worse than no stamp at all.
|
||||
rig_repo() { printf '%s\n' "${RIG_REPO:-heavy-duty/rig}"; }
|
||||
rig_ref() { printf '%s\n' "${RIG_REF:-main}"; }
|
||||
|
||||
render_userdata() {
|
||||
local f="$1" repo data ref
|
||||
local f="$1" repo data ref install
|
||||
repo="$(rig_repo)"; ref="$(rig_ref)"
|
||||
[[ "$repo" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] \
|
||||
|| die "RIG_REPO must look like owner/repo: $repo"
|
||||
[[ "$ref" =~ ^[A-Za-z0-9._/-]+$ ]] \
|
||||
|| die "RIG_REF must be a plain ref name (letters, digits, . _ / -): $ref"
|
||||
if [[ "$ref" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
# rig's installer currently resolves named tags/branches. A release drill
|
||||
# pins the moving ref before mint, so hand that installer the immutable
|
||||
# commit archive as a local source instead of asking it to resolve again
|
||||
# inside the guest (#152).
|
||||
printf -v install '%s' \
|
||||
"rig_source=/tmp/rig-$ref.tar.gz; rig_installer=/tmp/rig-install-$ref.sh; curl -fsSL https://github.com/$repo/archive/$ref.tar.gz -o \"\$rig_source\" && curl -fsSL https://raw.githubusercontent.com/$repo/$ref/install.sh -o \"\$rig_installer\" && HOME=/root RIG_REPO=\"$repo\" RIG_REF=\"$ref\" RIG_INSTALL_SOURCE=\"\$rig_source\" bash \"\$rig_installer\"; rig_rc=\$?; rm -f \"\$rig_source\" \"\$rig_installer\"; exit \"\$rig_rc\""
|
||||
else
|
||||
printf -v install 'curl -fsSL https://raw.githubusercontent.com/%s/%s/install.sh | HOME=/root RIG_REPO="%s" RIG_REF="%s" bash' \
|
||||
"$repo" "$ref" "$repo" "$ref"
|
||||
fi
|
||||
data="$(cat "$f")"
|
||||
data="${data//@RIG_REPO@/$repo}"
|
||||
data="${data//@RIG_REF@/$ref}"
|
||||
data="${data//@RIG_INSTALL@/$install}"
|
||||
printf '%s\n' "$data"
|
||||
}
|
||||
|
||||
|
|
|
|||
103
drill/drill.sh
103
drill/drill.sh
|
|
@ -10,6 +10,8 @@
|
|||
# bash drill/drill.sh --yes # no prompt (CI, or you've read it)
|
||||
# bash drill/drill.sh --ref main # drill a different branch of the repo
|
||||
# bash drill/drill.sh --keep-boxes # leave the boxes up to poke at
|
||||
# --emit-record <path> write an editable Markdown record draft
|
||||
# --run-id <id> share one ID across the family drills
|
||||
#
|
||||
# Four phases:
|
||||
# A. Incus semantics — the assumptions box is built on, probed directly.
|
||||
|
|
@ -36,6 +38,17 @@ REPO="${BOX_REPO:-heavy-duty/box}"
|
|||
REF="${BOX_REF:-main}"
|
||||
YES=0; KEEP=0
|
||||
SELF="$(readlink -f "$0")"
|
||||
EMIT_RECORD="${DRILL_EMIT_RECORD:-}"
|
||||
RUN_ID="${DRILL_RUN_ID:-}"
|
||||
DRILL_STARTED_EPOCH="${DRILL_STARTED_EPOCH:-$(date +%s)}"
|
||||
DRILL_DATE="${DRILL_DATE:-$(date -u +%F)}"
|
||||
if [ -z "${DRILL_INVOCATION:-}" ]; then
|
||||
printf -v DRILL_INVOCATION 'bash %q' "$0"
|
||||
printf -v _drill_args ' %q' "$@"
|
||||
DRILL_INVOCATION+="$_drill_args"
|
||||
fi
|
||||
# shellcheck source=drill/record.sh
|
||||
. "$(dirname "$SELF")/record.sh"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
|
|
@ -43,11 +56,19 @@ while [ $# -gt 0 ]; do
|
|||
--keep-boxes) KEEP=1; shift ;;
|
||||
--repo) REPO="$2"; shift 2 ;;
|
||||
--ref) REF="$2"; shift 2 ;;
|
||||
--emit-record)
|
||||
[ "$#" -ge 2 ] || { echo "drill: --emit-record needs a value" >&2; exit 2; }
|
||||
EMIT_RECORD="$2"; shift 2 ;;
|
||||
--run-id)
|
||||
[ "$#" -ge 2 ] || { echo "drill: --run-id needs a value" >&2; exit 2; }
|
||||
RUN_ID="$2"; shift 2 ;;
|
||||
--in-group) shift; break ;; # internal: see below
|
||||
-h|--help) sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
-h|--help) sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) echo "drill: unknown option: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
export DRILL_EMIT_RECORD="$EMIT_RECORD" DRILL_RUN_ID="$RUN_ID"
|
||||
export DRILL_STARTED_EPOCH DRILL_DATE DRILL_INVOCATION
|
||||
|
||||
pass=0; fail=0; findings=(); audit=()
|
||||
ok() { printf ' \033[32mPASS\033[0m %s\n' "$*"; pass=$((pass + 1)); }
|
||||
|
|
@ -57,6 +78,50 @@ inf() { printf ' %s\n' "$*"; }
|
|||
phase(){ printf '\n\033[1m══ %s\033[0m\n' "$*"; }
|
||||
aud() { audit+=("$*"); } # an answer for the #15 audit
|
||||
|
||||
record_version="$(cat "$(dirname "$SELF")/../VERSION" 2>/dev/null || echo unknown)"
|
||||
record_box_sha="${DRILL_BOX_SHA:-<unresolved>}"
|
||||
record_rig_repo="${DRILL_RIG_REPO:-<not recorded>}"
|
||||
record_rig_ref="${DRILL_RIG_REF:-<not recorded>}"
|
||||
record_rig_sha="${DRILL_RIG_SHA:-<unresolved>}"
|
||||
INSTALL_REF="${DRILL_INSTALL_REF:-$REF}"
|
||||
|
||||
finish_drill() {
|
||||
local rc="$1" record_os record_host record_elapsed
|
||||
trap - EXIT
|
||||
if [ "$rc" -ne 0 ] && [ "$fail" -eq 0 ]; then
|
||||
fail=1
|
||||
findings+=("FAIL: drill exited early with status $rc; inspect the terminal output")
|
||||
fi
|
||||
[ -n "$RUN_ID" ] || RUN_ID="$(drill_default_run_id "$record_version" "$DRILL_DATE")"
|
||||
# Standard host metadata, optional by design when a preflight fails before
|
||||
# Incus exists.
|
||||
# shellcheck disable=SC1091
|
||||
record_os="$(. /etc/os-release 2>/dev/null; printf '%s' "${PRETTY_NAME:-unknown OS}")"
|
||||
record_host="$(hostname) / $record_os, $(uname -srmo), Incus $(incus --version 2>/dev/null || echo unknown)"
|
||||
record_elapsed=$(($(date +%s) - DRILL_STARTED_EPOCH))
|
||||
if drill_write_record "$EMIT_RECORD" "$record_version" "$RUN_ID" "$record_host" \
|
||||
"$DRILL_DATE" "$REPO" "$REF" "$record_box_sha" \
|
||||
"$record_rig_repo" "$record_rig_ref" "$record_rig_sha" \
|
||||
"$DRILL_INVOCATION" "$pass" "$fail" "$record_elapsed" findings; then
|
||||
inf "wrote editable release-record draft: $EMIT_RECORD"
|
||||
else
|
||||
rc=1
|
||||
fi
|
||||
exit "$rc"
|
||||
}
|
||||
|
||||
if [ -n "$EMIT_RECORD" ]; then
|
||||
trap 'finish_drill "$?"' EXIT
|
||||
if [ "$record_box_sha" = '<unresolved>' ]; then
|
||||
if ! record_box_sha="$(drill_resolve_ref_sha "$REPO" "$REF")"; then
|
||||
echo "drill: cannot resolve exact box SHA for $REPO@$REF — refusing to drill an unpinned source" >&2
|
||||
exit 1
|
||||
fi
|
||||
INSTALL_REF="$record_box_sha"
|
||||
fi
|
||||
export DRILL_BOX_SHA="$record_box_sha" DRILL_INSTALL_REF="$INSTALL_REF"
|
||||
fi
|
||||
|
||||
wait_box() { # poll until exec answers (the VM agent can take a while), ~4 min
|
||||
# 2 min was too short: run 17's legacy box came up AFTER the window closed —
|
||||
# the drill called it dead and then every migration check on it passed.
|
||||
|
|
@ -217,8 +282,8 @@ EOF
|
|||
# OWNS still suppresses the setup prompt via BOX_SKIP_SETUP_HOST above.
|
||||
export BOX_YES=1
|
||||
|
||||
BOX_REPO="$REPO" BOX_REF="$REF" \
|
||||
bash -c "$(curl -fsSL "https://raw.githubusercontent.com/$REPO/$REF/install.sh")" \
|
||||
BOX_REPO="$REPO" BOX_REF="$INSTALL_REF" \
|
||||
bash -c "$(curl -fsSL "https://raw.githubusercontent.com/$REPO/$INSTALL_REF/install.sh")" \
|
||||
|| { echo "install failed"; exit 1; }
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
|
|
@ -229,15 +294,15 @@ EOF
|
|||
# the wrong tree while reporting success. A drill that silently drills the
|
||||
# wrong code is worse than one that fails.
|
||||
got="$(cat "$HOME/.local/share/box/current/INSTALLED_FROM" 2>/dev/null || echo '<unknown>')"
|
||||
if [ "$got" != "$REPO@$REF" ]; then
|
||||
echo "drill: FATAL — asked to install $REPO@$REF, but the tree says '$got'." >&2
|
||||
if [ "$got" != "$REPO@$INSTALL_REF" ]; then
|
||||
echo "drill: FATAL — asked to install $REPO@$INSTALL_REF, but the tree says '$got'." >&2
|
||||
echo " Your local drill.sh is probably STALE (pre-0.5.0 it passed CLAUDEBOX_*," >&2
|
||||
echo " which today's install.sh ignores, so it fell back to main). Fix:" >&2
|
||||
echo " git fetch origin && git checkout <the branch you mean> && git pull" >&2
|
||||
echo " then re-run this drill." >&2
|
||||
exit 1
|
||||
fi
|
||||
inf "installed tree confirms: $got"
|
||||
inf "installed tree confirms: $got (requested as $REPO@$REF)"
|
||||
|
||||
phase "Host setup (Incus, boxnet, ACL, profile, firewall)"
|
||||
if [ "$fw_absent_pre" = 1 ]; then
|
||||
|
|
@ -280,11 +345,28 @@ EOF
|
|||
# credentials are untouched, so we still have to enter the group ourselves —
|
||||
# once, for the remainder of the drill.
|
||||
inf "re-entering inside the incus-admin group…"
|
||||
exec sg incus-admin -c "IN_GROUP=1 DRILL_OWNS_SETUP='$OWNS' BOX_REPO='$REPO' BOX_REF='$REF' KEEP=$KEEP bash '$SELF' --in-group"
|
||||
printf -v reexec '%q ' env IN_GROUP=1 "DRILL_OWNS_SETUP=$OWNS" \
|
||||
"BOX_REPO=$REPO" "BOX_REF=$REF" "KEEP=$KEEP" \
|
||||
"DRILL_EMIT_RECORD=$EMIT_RECORD" "DRILL_RUN_ID=$RUN_ID" \
|
||||
"DRILL_STARTED_EPOCH=$DRILL_STARTED_EPOCH" "DRILL_DATE=$DRILL_DATE" \
|
||||
"DRILL_INVOCATION=$DRILL_INVOCATION" "DRILL_BOX_SHA=$record_box_sha" \
|
||||
"DRILL_INSTALL_REF=$INSTALL_REF" bash "$SELF" --in-group
|
||||
exec sg incus-admin -c "$reexec"
|
||||
fi
|
||||
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
KEEP="${KEEP:-0}"
|
||||
record_version="$(cat "$HOME/.local/share/box/current/VERSION" 2>/dev/null || printf '%s' "$record_version")"
|
||||
if [ -n "$EMIT_RECORD" ] && [ "$record_rig_sha" = '<unresolved>' ]; then
|
||||
record_rig_repo="${RIG_REPO:-heavy-duty/rig}"
|
||||
record_rig_ref="${RIG_REF:-main}"
|
||||
if ! record_rig_sha="$(drill_resolve_ref_sha "$record_rig_repo" "$record_rig_ref")"; then
|
||||
echo "drill: cannot resolve exact rig SHA for $record_rig_repo@$record_rig_ref — refusing to mint from an unpinned source" >&2
|
||||
exit 1
|
||||
fi
|
||||
export RIG_REPO="$record_rig_repo" RIG_REF="$record_rig_sha"
|
||||
export DRILL_RIG_REPO="$record_rig_repo" DRILL_RIG_REF="$record_rig_ref" DRILL_RIG_SHA="$record_rig_sha"
|
||||
fi
|
||||
|
||||
# PROVE THE INSTALLER'S CONTRACT (#64) — first, before the clean or anything
|
||||
# else on this host mutates the stack, and before the drill runs setup-host
|
||||
|
|
@ -590,6 +672,13 @@ printf '\n minting a claude-box box (cold, ~10 min)…\n'
|
|||
t0=$SECONDS
|
||||
if mint_box /tmp/mint-drill.log --name drill --template claude-box; then
|
||||
ok "box new --name drill --template claude-box ($((SECONDS - t0))s)"
|
||||
if [ -n "$EMIT_RECORD" ]; then
|
||||
minted_rig_repo="$(incus config get drill user.box.rig.repo 2>/dev/null)"
|
||||
minted_rig_ref="$(incus config get drill user.box.rig.ref 2>/dev/null)"
|
||||
if [ "$minted_rig_repo" != "$record_rig_repo" ] || [ "$minted_rig_ref" != "$record_rig_sha" ]; then
|
||||
no "minted box records $minted_rig_repo@$minted_rig_ref, expected pinned $record_rig_repo@$record_rig_sha"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
no "box new FAILED — tail: $(tail -3 /tmp/mint-drill.log | tr '\n' ' ')"
|
||||
timeout -k 5 60 incus delete -f drill >/dev/null 2>&1
|
||||
|
|
|
|||
77
drill/record.sh
Normal file
77
drill/record.sh
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env bash
|
||||
# Pure helpers for drill/drill.sh's generated release-record draft (#152).
|
||||
|
||||
drill_default_run_id() {
|
||||
local version="$1" run_date="$2"
|
||||
printf 'drill-%s-%s-01\n' "$version" "${run_date//-/}"
|
||||
}
|
||||
|
||||
drill_resolve_ref_sha() {
|
||||
local repo="$1" ref="$2" remote refs sha
|
||||
case "$ref" in
|
||||
[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])
|
||||
printf '%s\n' "$ref"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
case "$repo" in
|
||||
/*|./*|../*|http://*|https://*|git@*) remote="$repo" ;;
|
||||
*) remote="https://github.com/$repo.git" ;;
|
||||
esac
|
||||
refs="$(git ls-remote "$remote" \
|
||||
"refs/heads/$ref" "refs/tags/$ref" "refs/tags/$ref^{}" 2>/dev/null)" || return 1
|
||||
sha="$(printf '%s\n' "$refs" | awk -v ref="$ref" '
|
||||
$2 == "refs/tags/" ref "^{}" { peeled=$1 }
|
||||
$2 == "refs/heads/" ref { head=$1 }
|
||||
$2 == "refs/tags/" ref { tag=$1 }
|
||||
END { if (peeled) print peeled; else if (tag) print tag; else if (head) print head }
|
||||
')"
|
||||
[ -n "$sha" ] || return 1
|
||||
printf '%s\n' "$sha"
|
||||
}
|
||||
|
||||
drill_write_record() {
|
||||
local path="$1" version="$2" run_id="$3" host="$4" run_date="$5"
|
||||
local box_repo="$6" box_ref="$7" box_sha="$8" rig_repo="$9"
|
||||
shift 9
|
||||
local rig_ref="$1" rig_sha="$2" invocation="$3" passed="$4" failed="$5"
|
||||
local elapsed="$6" findings_name="$7" tmp finding plain total minutes
|
||||
local -n record_findings="$findings_name"
|
||||
|
||||
if [ -e "$path" ]; then
|
||||
echo "drill: record path already exists; refusing to overwrite evidence: $path" >&2
|
||||
return 1
|
||||
fi
|
||||
[ -d "$(dirname "$path")" ] || {
|
||||
echo "drill: record directory does not exist: $(dirname "$path")" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
total=$((passed + failed))
|
||||
minutes=$((elapsed / 60))
|
||||
tmp="$(mktemp "$path.tmp.XXXXXX")" || return 1
|
||||
{
|
||||
printf '# Release drill — %s\n\n' "$version"
|
||||
printf '> Generated draft: verify the facts and add operator judgement before commit.\n\n'
|
||||
printf -- "- **Run ID:** \`%s\`\n" "$run_id"
|
||||
printf -- '- **Host:** %s\n' "$host"
|
||||
printf -- '- **Date:** %s\n' "$run_date"
|
||||
printf -- '- **Candidate refs:**\n'
|
||||
printf " - box \`%s\` @ \`%s\` (\`%s\`)\n" "$box_ref" "$box_sha" "$box_repo"
|
||||
printf " - rig \`%s\` @ \`%s\` (\`%s\`)\n" "$rig_ref" "$rig_sha" "$rig_repo"
|
||||
printf '\n## What ran\n\n'
|
||||
printf "\`%s\` — phases A, B, C, E, D, M.\n" "$invocation"
|
||||
printf '\n## Result\n\n'
|
||||
printf '**%s/%s passed, %s failed.** %s minutes wall clock.\n\n' \
|
||||
"$passed" "$total" "$failed" "$minutes"
|
||||
if [ "${#record_findings[@]}" -eq 0 ]; then
|
||||
printf -- '- No failures or notes recorded by the harness.\n'
|
||||
else
|
||||
for finding in "${record_findings[@]}"; do
|
||||
plain="$(printf '%s' "$finding" | sed $'s/\033\\[[0-9;]*m//g')"
|
||||
printf -- '- %s\n' "$plain"
|
||||
done
|
||||
fi
|
||||
} > "$tmp" || { rm -f "$tmp"; return 1; }
|
||||
mv "$tmp" "$path"
|
||||
}
|
||||
|
|
@ -47,6 +47,33 @@ is a good record. So is a maintainer's written waiver explaining why this
|
|||
release shipped without a full drill. What the gate refuses is silence — #95,
|
||||
#114 and #148 all shipped unproven because a skip left no trace.
|
||||
|
||||
## Generate the factual draft
|
||||
|
||||
The harness can write the facts it already knows directly into a new record:
|
||||
|
||||
```sh
|
||||
bash drill/drill.sh --ref release/0.9.1 \
|
||||
--run-id drill-0.9.1-20260818-01 \
|
||||
--emit-record drills/0.9.1.md
|
||||
```
|
||||
|
||||
`--run-id` wins over `DRILL_RUN_ID`; if neither is set, the harness generates
|
||||
`drill-<version>-<UTC date>-01`. Supply the same ID explicitly to each family
|
||||
drill so their records reconcile. The emitter refuses to overwrite an existing
|
||||
file, and its Markdown contains no terminal colour escapes.
|
||||
|
||||
When record emission is requested, the harness resolves the requested box and
|
||||
rig refs before their installers run and feeds those installers the resulting
|
||||
full commit SHAs. The draft keeps both the human-facing refs and the immutable
|
||||
SHAs. If either ref cannot be pinned, the drill refuses before mutating the host
|
||||
and still writes the failed preflight record.
|
||||
|
||||
The generated file is a **draft, not finished release evidence**. It records
|
||||
the host, invocation, elapsed time, findings, and the exact box and rig refs and
|
||||
SHAs the run selected. Before committing it, verify those facts and add the
|
||||
operator judgement that a script cannot make: hardware detail, the meaning of
|
||||
any failures or skips, and whether they block release.
|
||||
|
||||
## Worked example
|
||||
|
||||
The version below is a **placeholder that can never be a real release**.
|
||||
|
|
|
|||
35
install.sh
35
install.sh
|
|
@ -24,10 +24,11 @@ set -euo pipefail
|
|||
# downloading — for CI and the drill, so what lands is the code under review.
|
||||
|
||||
REPO="${BOX_REPO:-heavy-duty/box}"
|
||||
# Three install channels, one knob (#83): BOX_REF unset installs the LATEST
|
||||
# Four install channels, one knob (#83, #152): BOX_REF unset installs the LATEST
|
||||
# RELEASE (the tag resolved from GitHub's releases/latest redirect, below);
|
||||
# BOX_REF=<tag> pins a release; BOX_REF=<branch> (say, main) is the dev
|
||||
# channel. A set ref is tried as a tag first, then as a branch.
|
||||
# channel; and a full 40-hex commit SHA is fetched immutably. A named ref is
|
||||
# tried as a tag first, then as a branch.
|
||||
REF="${BOX_REF:-}"
|
||||
# Root installs GLOBALLY, non-root installs per-user. box's install tree is
|
||||
# EXECUTED by other users (the multi-user host path: rig installs box once, every
|
||||
|
|
@ -232,17 +233,27 @@ else
|
|||
fi
|
||||
INSTALLED_FROM="$REPO@$REF"
|
||||
log "installing box from $REPO@$REF"
|
||||
# A ref is a TAG first (the pinned-release channel), a branch second (the
|
||||
# dev channel, BOX_REF=main) — and the fallback only exists for a ref the
|
||||
# OPERATOR named: a resolved latest tag has no branch to fall through to.
|
||||
URL="https://github.com/$REPO/archive/refs/tags/$REF.tar.gz"
|
||||
log "downloading $URL"
|
||||
if ! curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz"; then
|
||||
[ -n "${BOX_REF:-}" ] || die "failed to download $URL"
|
||||
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
|
||||
log "no tag '$REF' — trying it as a branch: $URL"
|
||||
if [[ "$REF" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
# The drill resolves moving candidate refs before it mutates a host, then
|
||||
# installs the exact object it measured (#152). GitHub's commit archive is
|
||||
# outside refs/{tags,heads}; guessing either namespace makes every SHA 404.
|
||||
URL="https://github.com/$REPO/archive/$REF.tar.gz"
|
||||
log "downloading immutable commit $URL"
|
||||
curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz" \
|
||||
|| die "failed to download it as either — '$REF' is neither a tag nor a branch of $REPO"
|
||||
|| die "failed to download commit $REF from $REPO"
|
||||
else
|
||||
# A named ref is a TAG first (the pinned-release channel), a branch second
|
||||
# (the dev channel, BOX_REF=main). The fallback only exists for a ref the
|
||||
# OPERATOR named: a resolved latest tag has no branch to fall through to.
|
||||
URL="https://github.com/$REPO/archive/refs/tags/$REF.tar.gz"
|
||||
log "downloading $URL"
|
||||
if ! curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz"; then
|
||||
[ -n "${BOX_REF:-}" ] || die "failed to download $URL"
|
||||
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
|
||||
log "no tag '$REF' — trying it as a branch: $URL"
|
||||
curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz" \
|
||||
|| die "failed to download it as either — '$REF' is neither a tag nor a branch of $REPO"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "extracting archive"
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
|
|
@ -31,4 +31,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
124
test/cli.sh
124
test/cli.sh
|
|
@ -266,15 +266,28 @@ check "render_userdata: the pin's defaults came with it (guards the grep)" 0 "he
|
|||
check "render_userdata: the extracted function is valid bash" 0 "" bash -n "$RUFN"
|
||||
|
||||
SEED="$(mktemp)"
|
||||
printf '#cloud-config\nruncmd:\n - curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash\n' > "$SEED"
|
||||
printf "#cloud-config\nruncmd:\n - '@RIG_INSTALL@'\n" > "$SEED"
|
||||
# shellcheck disable=SC2016 # $0/$1 expand in the child shell, by design
|
||||
rud() { # rud [VAR=val ...] — render the fixture seed through the real function
|
||||
env "$@" bash -c 'die() { echo "box: $*" >&2; exit 1; }; . "$0"; render_userdata "$1"' "$RUFN" "$SEED"
|
||||
}
|
||||
check "render_userdata: defaults pin heavy-duty/rig" 0 "githubusercontent.com/heavy-duty/rig/main/install.sh" rud
|
||||
check "render_userdata: defaults feed the installer's own env too" 0 'RIG_REPO="heavy-duty/rig" RIG_REF="main"' rud
|
||||
check "render_userdata: the rig install pins HOME=/root (runcmd has no \$HOME)" 0 "HOME=/root " rud
|
||||
check "render_userdata: RIG_REPO/RIG_REF override at mint" 0 "dan-claude-bot/rig/feat/bootstrap-roles/install.sh" \
|
||||
rud RIG_REPO=dan-claude-bot/rig RIG_REF=feat/bootstrap-roles
|
||||
RIG_COMMIT=89abcdef0123456789abcdef0123456789abcdef
|
||||
check "render_userdata: a rig commit downloads its immutable archive" 0 "archive/$RIG_COMMIT.tar.gz" \
|
||||
rud RIG_REPO=heavy-duty/rig RIG_REF="$RIG_COMMIT"
|
||||
# $rig_source belongs to the rendered guest shell.
|
||||
# shellcheck disable=SC2016
|
||||
check "render_userdata: a rig commit feeds the archive to the installer" 0 'RIG_INSTALL_SOURCE="$rig_source"' \
|
||||
rud RIG_REPO=heavy-duty/rig RIG_REF="$RIG_COMMIT"
|
||||
# Expansion belongs to the bash -c fixture.
|
||||
# shellcheck disable=SC2016
|
||||
check "render_userdata: a named rig ref keeps the normal channel" 1 "" \
|
||||
bash -c 'die() { exit 1; }; RIG_REPO=heavy-duty/rig RIG_REF=main; . "$1"; render_userdata "$2" | grep -q RIG_INSTALL_SOURCE' \
|
||||
_ "$RUFN" "$SEED"
|
||||
# shellcheck disable=SC2016 # $0/$1 expand in the child shells, by design
|
||||
check "render_userdata: no token survives the render" 1 "" \
|
||||
bash -c 'env bash -c "die() { echo box: \$*; exit 1; }; . \"\$0\"; render_userdata \"\$1\"" "$1" "$2" | grep -q @RIG_' _ "$RUFN" "$SEED"
|
||||
|
|
@ -326,26 +339,13 @@ for d in "$ROOT"/templates/*/; do
|
|||
# The thin-template contract (#81), both halves per template:
|
||||
#
|
||||
# THE SEED — a template that names a tenant role (BOX_BOOTSTRAP_ROLE) must
|
||||
# preinstall rig carrying BOTH pin tokens, on the installer URL and on the
|
||||
# installer's own env, or the pin is a half-truth: a mint would fetch one
|
||||
# ref's installer and install another ref's tree.
|
||||
# delegate its rig install to render_userdata. That one renderer owns both
|
||||
# the named-ref channel and the immutable-commit archive channel.
|
||||
# ------------------------------------------------------------------------
|
||||
trole="$(tpl "$ROOT" "$t" | sed -n 's/.*ROLE=\([^ ]*\).*/\1/p')"
|
||||
if [ -n "$trole" ]; then
|
||||
check "template '$t': the seed installs rig (role '$trole')" 0 "" \
|
||||
grep -q 'install.sh' "$d/user-data.yaml"
|
||||
# shellcheck disable=SC2016 # $1 expands in the child shell, by design
|
||||
check "template '$t': the rig install carries the @RIG_REPO@ pin token" 0 "" \
|
||||
bash -c 'grep "install.sh" "$1" | grep -q "@RIG_REPO@/@RIG_REF@"' _ "$d/user-data.yaml"
|
||||
# shellcheck disable=SC2016
|
||||
check "template '$t': the pin reaches the installer's env too" 0 "" \
|
||||
bash -c 'grep "install.sh" "$1" | grep -q "RIG_REPO=\"@RIG_REPO@\" RIG_REF=\"@RIG_REF@\""' _ "$d/user-data.yaml"
|
||||
# HOME=/root: a scar found live — cloud-init's runcmd has no $HOME and
|
||||
# rig's installer (set -u) dies on it (rig#39). The pin must survive
|
||||
# every seed rewrite.
|
||||
# shellcheck disable=SC2016
|
||||
check "template '$t': the rig install pins HOME=/root (runcmd has no \$HOME)" 0 "" \
|
||||
bash -c 'grep "install.sh" "$1" | grep -q "HOME=/root "' _ "$d/user-data.yaml"
|
||||
check "template '$t': the seed installs rig through the renderer (role '$trole')" 0 "" \
|
||||
grep -q "^[[:space:]]*- '@RIG_INSTALL@'\$" "$d/user-data.yaml"
|
||||
fi
|
||||
# ------------------------------------------------------------------------
|
||||
# THE ABSENCE — no tenant content in ANY template, ever again. Everything a
|
||||
|
|
@ -3093,6 +3093,94 @@ check "revoke-user: the cert leftover check matches the capture, not a pipe" 0 "
|
|||
check "drill: reads the installed tree through current/" 0 "" \
|
||||
grep -qF '.local/share/box/current/VERSION' "$ROOT/drill/drill.sh"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Drill record emission (#152). Feed the pure renderer facts captured by the
|
||||
# destructive harness and inspect the Markdown it writes; no fake Incus run is
|
||||
# mistaken for evidence from real hardware.
|
||||
# ---------------------------------------------------------------------------
|
||||
DRILL_RECORD_DIR="$(mktemp -d)"
|
||||
DRILL_RECORD="$DRILL_RECORD_DIR/0.9.1.md"
|
||||
|
||||
drill_record_fixture() {
|
||||
bash -c '
|
||||
set -u
|
||||
. "$1/drill/record.sh"
|
||||
findings=("FAIL: sibling isolation regressed" $'"'"'NOTE: \033[33mcontainer fallback\033[0m'"'"')
|
||||
drill_write_record "$2" \
|
||||
"0.9.1" "drill-0.9.1-20260818-01" "builder / Debian 13, Incus 6.0.2" \
|
||||
"2026-08-18" "heavy-duty/box" "release/0.9.1" \
|
||||
"0123456789abcdef0123456789abcdef01234567" \
|
||||
"heavy-duty/rig" "release/0.4.0" \
|
||||
"89abcdef0123456789abcdef0123456789abcdef" \
|
||||
"bash drill/drill.sh --ref release/0.9.1 --run-id drill-0.9.1-20260818-01" \
|
||||
84 1 2460 findings
|
||||
' _ "$ROOT" "$DRILL_RECORD"
|
||||
}
|
||||
|
||||
check "drill record: renderer writes a Markdown draft" 0 "" drill_record_fixture
|
||||
check "drill record: names the release version" 0 "# Release drill — 0.9.1" cat "$DRILL_RECORD"
|
||||
# Backticks are literal Markdown delimiters.
|
||||
# shellcheck disable=SC2016
|
||||
check "drill record: carries the shared run ID" 0 '**Run ID:** `drill-0.9.1-20260818-01`' cat "$DRILL_RECORD"
|
||||
# shellcheck disable=SC2016
|
||||
check "drill record: pins the box ref and full SHA" 0 'box `release/0.9.1` @ `0123456789abcdef0123456789abcdef01234567`' cat "$DRILL_RECORD"
|
||||
# shellcheck disable=SC2016
|
||||
check "drill record: pins the rig ref and full SHA" 0 'rig `release/0.4.0` @ `89abcdef0123456789abcdef0123456789abcdef`' cat "$DRILL_RECORD"
|
||||
# shellcheck disable=SC2016
|
||||
check "drill record: records the invocation" 0 '`bash drill/drill.sh --ref release/0.9.1 --run-id drill-0.9.1-20260818-01`' cat "$DRILL_RECORD"
|
||||
check "drill record: renders pass/fail counts and wall clock" 0 '**84/85 passed, 1 failed.** 41 minutes wall clock.' cat "$DRILL_RECORD"
|
||||
check "drill record: carries plain findings" 0 '- FAIL: sibling isolation regressed' cat "$DRILL_RECORD"
|
||||
check "drill record: strips terminal colour" 1 "" grep -q $'\033' "$DRILL_RECORD"
|
||||
|
||||
printf 'operator evidence\n' > "$DRILL_RECORD_DIR/existing.md"
|
||||
# Expansion belongs to the bash -c fixture.
|
||||
# shellcheck disable=SC2016
|
||||
check "drill record: refuses to overwrite evidence" 1 "already exists" \
|
||||
bash -c '. "$1/drill/record.sh"; findings=(); drill_write_record "$2" v id host date repo ref sha rig rref rsha invocation 0 0 0 findings' \
|
||||
_ "$ROOT" "$DRILL_RECORD_DIR/existing.md"
|
||||
check "drill record: an overwrite refusal preserves the file" 0 "operator evidence" cat "$DRILL_RECORD_DIR/existing.md"
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
check "drill record: default run ID joins version and UTC date" 0 "drill-0.9.1-20260818-01" \
|
||||
bash -c '. "$1/drill/record.sh"; drill_default_run_id 0.9.1 2026-08-18' _ "$ROOT"
|
||||
# shellcheck disable=SC2016
|
||||
check "drill record: an already exact ref stays exact" 0 "0123456789abcdef0123456789abcdef01234567" \
|
||||
bash -c '. "$1/drill/record.sh"; drill_resolve_ref_sha repo 0123456789abcdef0123456789abcdef01234567' _ "$ROOT"
|
||||
|
||||
REF_FIXTURE="$DRILL_RECORD_DIR/ref-fixture"
|
||||
git init -q "$REF_FIXTURE"
|
||||
git -C "$REF_FIXTURE" config user.name fixture
|
||||
git -C "$REF_FIXTURE" config user.email fixture@example.invalid
|
||||
printf 'branch\n' > "$REF_FIXTURE/value"
|
||||
git -C "$REF_FIXTURE" add value
|
||||
git -C "$REF_FIXTURE" commit -qm branch
|
||||
git -C "$REF_FIXTURE" branch candidate
|
||||
printf 'tag\n' > "$REF_FIXTURE/value"
|
||||
git -C "$REF_FIXTURE" commit -qam tag
|
||||
git -C "$REF_FIXTURE" tag candidate
|
||||
TAG_SHA="$(git -C "$REF_FIXTURE" rev-parse 'refs/tags/candidate^{commit}')"
|
||||
# The installer tries a tag before a branch when both share a name, so the
|
||||
# record resolver must pin the same object rather than merely finding a ref.
|
||||
# shellcheck disable=SC2016
|
||||
check "drill record: tag resolution matches the installer's tag-first channel" 0 "$TAG_SHA" \
|
||||
bash -c '. "$1/drill/record.sh"; drill_resolve_ref_sha "$2" candidate' _ "$ROOT" "$REF_FIXTURE"
|
||||
|
||||
EMPTY_REMOTE="$DRILL_RECORD_DIR/empty.git"
|
||||
git init -q --bare "$EMPTY_REMOTE"
|
||||
EARLY_RECORD="$DRILL_RECORD_DIR/early-failure.md"
|
||||
check "drill record: an unresolved source fails before consent or mutation" 1 "cannot resolve exact box SHA" \
|
||||
bash "$ROOT/drill/drill.sh" --repo "$EMPTY_REMOTE" --ref absent --emit-record "$EARLY_RECORD"
|
||||
check "drill record: an early preflight failure still emits evidence" 0 "drill exited early" \
|
||||
cat "$EARLY_RECORD"
|
||||
check "drill: help names --emit-record" 0 "--emit-record <path>" bash "$ROOT/drill/drill.sh" --help
|
||||
check "drill: help names --run-id" 0 "--run-id <id>" bash "$ROOT/drill/drill.sh" --help
|
||||
check "drill: --emit-record needs a value before any mutation" 2 "--emit-record needs a value" \
|
||||
bash "$ROOT/drill/drill.sh" --emit-record
|
||||
check "drill: --run-id needs a value before any mutation" 2 "--run-id needs a value" \
|
||||
bash "$ROOT/drill/drill.sh" --run-id
|
||||
|
||||
rm -rf "$DRILL_RECORD_DIR"
|
||||
|
||||
echo "---"
|
||||
echo "$PASS passed, $FAIL failed"
|
||||
rm -rf "$SHIMDIR" "$WORK"
|
||||
|
|
|
|||
|
|
@ -109,6 +109,16 @@ check "dev channel: tries the tag first" 0 "refs/tags/main.tar.gz" head -1 "$L3"
|
|||
check "dev channel: then downloads the branch" 0 "" \
|
||||
grep -qF "archive/refs/heads/main.tar.gz" "$L3"
|
||||
|
||||
COMMIT_REF=0123456789abcdef0123456789abcdef01234567
|
||||
H5="$WORK/h5"; B5="$WORK/b5"; L5="$WORK/c5.log"
|
||||
check "commit channel: installs an immutable full SHA" 0 "installing box from heavy-duty/box@$COMMIT_REF" \
|
||||
ninst "$H5" "$B5" BOX_REF="$COMMIT_REF" FAKE_CURL_LOG="$L5" \
|
||||
FAKE_SERVE_URL="https://github.com/heavy-duty/box/archive/$COMMIT_REF.tar.gz"
|
||||
check "commit channel: downloads the commit archive directly" 0 "archive/$COMMIT_REF.tar.gz" cat "$L5"
|
||||
check "commit channel: never guesses tag or branch namespaces" 1 "" grep -Eq 'refs/(tags|heads)' "$L5"
|
||||
check "commit channel: records the immutable source" 0 "heavy-duty/box@$COMMIT_REF" \
|
||||
cat "$H5/versions/9.9.9/INSTALLED_FROM"
|
||||
|
||||
H4="$WORK/h4"; B4="$WORK/b4"; L4="$WORK/c4.log"
|
||||
check "resolution failure: names the latest-release probe" 1 "could not resolve the latest release" \
|
||||
ninst "$H4" "$B4" FAKE_CURL_RC=6 FAKE_CURL_LOG="$L4"
|
||||
|
|
@ -122,6 +132,7 @@ check "unknown ref names both attempted channels" 1 "neither a tag nor a branch"
|
|||
check "README documents the latest-release channel" 0 "" grep -qF 'latest release' "$ROOT/README.md"
|
||||
check "README documents the pinned channel" 0 "" grep -qF 'BOX_REF=0.6.0' "$ROOT/README.md"
|
||||
check "README documents the dev channel" 0 "" grep -qF 'BOX_REF=main' "$ROOT/README.md"
|
||||
check "README documents the immutable commit channel" 0 "" grep -qF 'BOX_REF=<full-commit-sha>' "$ROOT/README.md"
|
||||
|
||||
echo "---"
|
||||
echo "$PASS passed, $FAIL failed"
|
||||
|
|
|
|||
Loading…
Reference in a new issue