Files
harmony/fleet/scripts/release.sh
2026-09-15 22:08:08 -04:00

193 lines
7.0 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
Publish Fleet operator, NATS callout, and fleet-agent images.
Does not provision a tenant and does not patch Devices.
Usage:
fleet/scripts/release.sh [options]
At least one release version is required:
--control-version VERSION Operator and NATS callout version
--agent-version VERSION Standalone fleet-agent version
--agent-arch ARCH x86_64 (default) or aarch64
Registry:
--registry HOST Default: hub.nationtech.io
--repository PATH Default: harmony
--publish-only Accepted; this script only publishes
-h, --help
HARMONY_ZITADEL_KEY_JSON: Fleet bot → OpenBao harmony/harmony-fleet
RegistryCredentials. Fallback: REGISTRY_USER / REGISTRY_TOKEN.
Tenant provision and device upgrades: docs/guides/fleet-products.md
EOF
}
fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
require_command() {
command -v "$1" >/dev/null 2>&1 || fail "required command not found: $1"
}
validate_version() {
[[ "$1" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] ||
fail "version must be SemVer without a leading v or build metadata: $1"
}
# The on-device updater runs `<candidate> --self-test --expected-version <tag>`.
# The agent exits 1 if VERSION (from HARMONY_FLEET_AGENT_VERSION via build.rs)
# does not match. Publishing a binary with the workspace crate version baked in
# is how 0.6.7 sat on the Pi as a failed upgrade. Prefer `--version` when we
# can run the binary; if this host cannot execute aarch64, search the image
# bytes (do not `strings | grep -q` under `pipefail` — grep -q SIGPIPEs).
assert_agent_version() {
local binary="$1"
local version="$2"
local reported
if [[ "$(uname -m)" == "x86_64" && "$agent_arch" == "x86_64" ]]; then
reported="$("$binary" --version)"
[[ "${reported##* }" == "$version" ]] ||
fail "built agent reports the wrong version: $reported"
elif command -v qemu-aarch64 >/dev/null 2>&1 && [[ "$agent_arch" == "aarch64" ]]; then
reported="$(qemu-aarch64 "$binary" --version)"
[[ "${reported##* }" == "$version" ]] ||
fail "built agent reports the wrong version: $reported"
else
require_command python3
python3 - "$binary" "$version" <<'PY' || fail "cross-built agent does not contain version $version"
import re, sys
data = open(sys.argv[1], "rb").read()
version = sys.argv[2].encode()
if not re.search(rb"(?<![0-9])" + re.escape(version) + rb"(?![0-9])", data):
sys.exit(1)
PY
fi
}
digest_line() {
local key="$1"
local file="$2"
local matches
matches="$(grep -E "^${key}=(oci://)?hub\\.nationtech\\.io/harmony/[^[:space:]]+@sha256:[0-9a-f]{64}$" "$file" || true)"
[[ "$(printf '%s\n' "$matches" | grep -c .)" -eq 1 ]] ||
fail "expected one ${key}= digest line in release output"
printf '%s\n' "${matches#*=}"
}
control_version="${CONTROL_VERSION:-}"
agent_version="${AGENT_VERSION:-}"
agent_arch="${AGENT_ARCH:-x86_64}"
registry="${FLEET_REGISTRY:-hub.nationtech.io}"
repository="${FLEET_REGISTRY_REPOSITORY:-harmony}"
while (($#)); do
case "$1" in
--control-version) control_version="${2:?missing control version}"; shift 2 ;;
--agent-version) agent_version="${2:?missing agent version}"; shift 2 ;;
--agent-arch) agent_arch="${2:?missing agent architecture}"; shift 2 ;;
--registry) registry="${2:?missing registry}"; shift 2 ;;
--repository) repository="${2:?missing repository}"; shift 2 ;;
--publish-only) shift ;;
--deploy-manifest|--deploy-bin|--context|--namespace|--device|--kube-context)
fail "release.sh does not provision or operate a tenant; see docs/guides/fleet-products.md"
;;
-h|--help) usage; exit 0 ;;
*) fail "unknown argument: $1" ;;
esac
done
[[ -n "$control_version" || -n "$agent_version" ]] || fail "set a control or agent version"
[[ -z "$control_version" ]] || validate_version "$control_version"
[[ -z "$agent_version" ]] || validate_version "$agent_version"
[[ "$agent_arch" == "x86_64" || "$agent_arch" == "aarch64" ]] ||
fail "agent architecture must be x86_64 or aarch64"
if [[ "$registry" != "hub.nationtech.io" || "$repository" != "harmony" ]]; then
fail "Fleet releases publish to hub.nationtech.io/harmony"
fi
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/../.." && pwd)"
cd "$repo_root"
require_command cargo
require_command sha256sum
require_command stat
[[ -z "$control_version" ]] || require_command docker
if [[ -z "${HARMONY_ZITADEL_KEY_JSON:-}" ]]; then
: "${REGISTRY_USER:?REGISTRY_USER is required for publication}"
: "${REGISTRY_TOKEN:?REGISTRY_TOKEN is required for publication}"
fi
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
agent_binary=""
agent_size=""
agent_sha256=""
agent_tag_ref=""
agent_oci_ref=""
if [[ -n "$agent_version" ]]; then
printf '==> Building fleet agent %s for %s\n' "$agent_version" "$agent_arch"
export HARMONY_FLEET_AGENT_VERSION="$agent_version"
if [[ "$agent_arch" == "x86_64" ]]; then
[[ "$(uname -m)" == "x86_64" ]] || fail "x86_64 agent build requires an x86_64 host"
cargo build --release --locked -p harmony-fleet-agent
agent_binary="$repo_root/target/release/harmony-fleet-agent"
else
require_command rustup
rustup target add aarch64-unknown-linux-gnu
cargo build --release --locked --target aarch64-unknown-linux-gnu \
-p harmony-fleet-agent
agent_binary="$repo_root/target/aarch64-unknown-linux-gnu/release/harmony-fleet-agent"
fi
[[ -x "$agent_binary" ]] || fail "agent binary not produced: $agent_binary"
assert_agent_version "$agent_binary" "$agent_version"
agent_size="$(stat -c %s "$agent_binary")"
agent_sha256="$(sha256sum "$agent_binary" | cut -d' ' -f1)"
agent_tag_ref="$registry/$repository/harmony-fleet-agent:$agent_version-$agent_arch"
fi
operator_ref=""
callout_ref=""
if [[ -n "$agent_version" ]]; then
printf '==> Publishing %s\n' "$agent_tag_ref"
agent_refs="$tmp/agent-refs"
RUST_LOG="${RUST_LOG:-info}" \
cargo run --release -p harmony-fleet-deploy --bin harmony-fleet-release -- \
agent --binary "$agent_binary" --reference "$agent_tag_ref" \
--version "$agent_version" >"$agent_refs"
cat "$agent_refs"
agent_oci_ref="$(digest_line agent "$agent_refs")"
fi
if [[ -n "$control_version" ]]; then
printf '==> Resolving or publishing Fleet control plane %s\n' "$control_version"
control_refs="$tmp/control-refs"
RUST_LOG="${RUST_LOG:-info}" \
cargo run --release -p harmony-fleet-deploy --bin harmony-fleet-release -- \
control --version "$control_version" --push >"$control_refs"
cat "$control_refs"
operator_ref="$(digest_line operator "$control_refs")"
callout_ref="$(digest_line callout "$control_refs")"
fi
printf '\nRelease outputs:\n'
[[ -z "$operator_ref" ]] || printf ' operator=%s\n callout=%s\n' "$operator_ref" "$callout_ref"
if [[ -n "$agent_oci_ref" ]]; then
printf ' agent.version=%s\n' "$agent_version"
printf ' agent.architecture=%s\n' "$agent_arch"
printf ' agent.artifactUrl=%s\n' "$agent_oci_ref"
printf ' agent.maxBytes=%s\n' "$agent_size"
printf ' agent.sha256=%s\n' "$agent_sha256"
fi