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>
223 lines
6 KiB
Bash
Executable file
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}"
|