clawdie-iso/scripts/write-artifact-manifest.sh
Sam & Claude b959b64d21 chore(iso): rename provenance "dirty" → "modified" (Sam & Claude)
Drop the "dirty" terminology in favor of "modified" (same boolean sense:
true = working tree has uncommitted or untracked changes). Pure rename — no
logic change. Safe now: nothing consumes these keys yet (checked colibri too).

- build-manifest.json keys: zot_dirty/colibri_dirty/iso_repo_dirty
  → zot_modified/colibri_modified/iso_repo_modified
- .clawdie-source.json: dirty_at_build → modified_at_build
- iso-publish manifest (write-artifact-manifest.sh): repo_dirty → repo_modified
- gate messages, comments, shell vars, and docs (BUILD/CHANGELOG/ISO-MANIFESTS/
  PLAN) reworded.

Checks: sh -n on all three scripts; release-gate smoke test PASS; prettier clean
on changed docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:06:17 +02:00

223 lines
6 KiB
Bash
Executable file

#!/bin/sh
# Write a JSON handoff manifest for a completed Clawdie ISO image artifact.
# Intended for the FreeBSD publish/deploy path; safe to run from repo root.
set -eu
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
export PATH
usage() {
cat <<'EOF'
Usage: scripts/write-artifact-manifest.sh <image.img|image.img.xz> [--base-url URL]
Writes <image>.manifest.json next to the image artifacts. The manifest is the
handoff contract for Hermes USB/IMG Deployer and other non-git artifact users.
Environment:
BUILD_COMMAND Optional build command string to record in the manifest.
BUILT_BY Optional builder identity; defaults to "Codex ISO Builder".
EOF
}
if [ "$#" -lt 1 ]; then
usage >&2
exit 2
fi
_artifact="$1"
shift
_base_url=""
while [ "$#" -gt 0 ]; do
case "$1" in
--base-url)
shift
if [ "$#" -eq 0 ]; then
echo "ERROR: --base-url requires a value" >&2
exit 2
fi
_base_url="${1%/}"
;;
-h|--help)
usage
exit 0
;;
*)
echo "ERROR: unknown argument: $1" >&2
usage >&2
exit 2
;;
esac
shift
done
case "${_artifact}" in
*.img.xz)
_xz="${_artifact}"
_raw="${_artifact%.xz}"
_stem="${_artifact%.img.xz}"
;;
*.img)
_raw="${_artifact}"
_xz="${_artifact}.xz"
_stem="${_artifact%.img}"
;;
*)
echo "ERROR: artifact must end in .img or .img.xz: ${_artifact}" >&2
exit 2
;;
esac
_sha="${_xz}.sha256"
_manifest="${_stem}.manifest.json"
if [ ! -f "${_xz}" ]; then
echo "ERROR: compressed image missing: ${_xz}" >&2
exit 1
fi
if [ ! -f "${_sha}" ]; then
echo "ERROR: checksum file missing: ${_sha}" >&2
exit 1
fi
json_escape() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}
json_string_or_null() {
if [ -n "$1" ]; then
printf '"%s"' "$(json_escape "$1")"
else
printf 'null'
fi
}
file_size() {
if stat -f '%z' "$1" >/dev/null 2>&1; then
stat -f '%z' "$1"
else
stat -c '%s' "$1"
fi
}
file_sha256() {
if command -v sha256 >/dev/null 2>&1; then
sha256 -q "$1"
else
sha256sum "$1" | awk '{print $1}'
fi
}
sha_from_file() {
if grep -q '=' "$1" 2>/dev/null; then
awk -F'= ' 'NF > 1 {print $2; exit}' "$1"
else
awk '{print $1; exit}' "$1"
fi
}
_xz_hash="$(file_sha256 "${_xz}")"
_sha_hash="$(sha_from_file "${_sha}")"
if [ "${_xz_hash}" != "${_sha_hash}" ]; then
echo "ERROR: checksum file does not match compressed image" >&2
echo " computed: ${_xz_hash}" >&2
echo " file: ${_sha_hash}" >&2
exit 1
fi
_raw_base="$(basename "${_raw}")"
_xz_base="$(basename "${_xz}")"
_sha_base="$(basename "${_sha}")"
_manifest_base="$(basename "${_manifest}")"
if [ -f "${_raw}" ]; then
_raw_size="$(file_size "${_raw}")"
else
_raw_size="null"
fi
_xz_size="$(file_size "${_xz}")"
_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)"
_commit="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
_repo_modified="null"
if git diff --quiet 2>/dev/null && git diff --cached --quiet 2>/dev/null; then
_repo_modified="false"
elif git rev-parse --git-dir >/dev/null 2>&1; then
_repo_modified="true"
fi
_host="$(hostname 2>/dev/null || echo unknown)"
_written_at="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
_run_stamp="$(date -u '+%Y%m%dT%H%M%SZ')"
_run_id="${_run_stamp}-${_stem##*/}"
_freebsd="$(freebsd-version -kru 2>/dev/null | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
_builder="${BUILT_BY:-Codex ISO Builder}"
_build_command="${BUILD_COMMAND:-unknown}"
if [ -n "${_base_url}" ]; then
_image_url="${_base_url}/${_xz_base}"
_sha_url="${_base_url}/${_sha_base}"
_manifest_url="${_base_url}/${_manifest_base}"
else
_image_url=""
_sha_url=""
_manifest_url=""
fi
_tmp="${_manifest}.tmp.$$"
cat > "${_tmp}" <<EOF
{
"schema": "clawdie.iso.publish.v1",
"legacy_schema": "clawdie.iso-artifact.v1",
"project": "clawdie-iso",
"workflow": "iso-publish",
"run_id": "$(json_escape "${_run_id}")",
"status": "pass",
"actor": "$(json_escape "${_builder}")",
"host": "$(json_escape "${_host}")",
"branch": "$(json_escape "${_branch}")",
"commit": "$(json_escape "${_commit}")",
"repo_modified": ${_repo_modified},
"started_at": null,
"completed_at": "$(json_escape "${_written_at}")",
"artifact_type": "operator-usb-image",
"build_host": "$(json_escape "${_host}")",
"built_by": "$(json_escape "${_builder}")",
"image": "$(json_escape "${_raw_base}")",
"compressed_image": "$(json_escape "${_xz_base}")",
"sha256_file": "$(json_escape "${_sha_base}")",
"manifest": "$(json_escape "${_manifest_base}")",
"sha256": "$(json_escape "${_xz_hash}")",
"raw_size_bytes": ${_raw_size},
"compressed_size_bytes": ${_xz_size},
"manifest_written_at": "$(json_escape "${_written_at}")",
"freebsd_version": "$(json_escape "${_freebsd}")",
"build_command": "$(json_escape "${_build_command}")",
"image_url": $(json_string_or_null "${_image_url}"),
"sha256_url": $(json_string_or_null "${_sha_url}"),
"manifest_url": $(json_string_or_null "${_manifest_url}"),
"inputs": {
"compressed_image_path": "$(json_escape "${_xz}")",
"sha256_path": "$(json_escape "${_sha}")"
},
"checks": [
{
"name": "checksum_file_matches_compressed_image",
"status": "pass",
"sha256": "$(json_escape "${_xz_hash}")"
}
],
"outputs": {
"manifest_path": "$(json_escape "${_manifest}")",
"image_url": $(json_string_or_null "${_image_url}"),
"sha256_url": $(json_string_or_null "${_sha_url}"),
"manifest_url": $(json_string_or_null "${_manifest_url}")
},
"logs": [],
"summary": "Published artifact manifest is ready for Hermes checksum/xz verification and flashing.",
"notes": "Built on FreeBSD; ready for Hermes USB/IMG Deployer after checksum and xz verification."
}
EOF
mv "${_tmp}" "${_manifest}"
printf '%s\n' "${_manifest}"