mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
ci: split package, tag, and release workflows (#855)
This commit is contained in:
@@ -8,6 +8,8 @@ over the air: `nanokvm_<version>.tar.gz` plus its `latest.json` manifest.
|
||||
| `build-in-container.sh` | Builds every riscv64 artifact (`kvm_system`, `libkvm.so`, `NanoKVM-Server`). Runs inside the `nanokvm-builder` image only. |
|
||||
| `package.sh` | Stages the package tree, creates the tarball, and writes `latest.json`. |
|
||||
| `compare-release.sh` | Diffs a freshly built package against the currently published one. Informational. |
|
||||
| `verify-release-assets.sh` | Checks the three GitHub Release assets before publishing or promotion. |
|
||||
| `verify-release-tag.sh` | Requires an annotated numeric tag whose commit is on `main`. |
|
||||
|
||||
## What the updater expects
|
||||
|
||||
@@ -46,9 +48,13 @@ make web # web/dist
|
||||
make package VERSION=2.4.4 # build/release/{nanokvm_2.4.4.tar.gz,latest.json}
|
||||
```
|
||||
|
||||
In CI this runs as the **NanoKVM Package** workflow. Pull requests get a
|
||||
short-lived Actions artifact for device testing; pushing a `MAJOR.MINOR.PATCH`
|
||||
tag creates or updates a GitHub release with three assets:
|
||||
In CI this runs as the **NanoKVM Package** workflow. Pull requests and manual
|
||||
runs only create Actions artifacts. For a public release, run **NanoKVM Create
|
||||
Tag**, then run **NanoKVM Release** and choose prerelease, stable, or promotion.
|
||||
Publishing attaches three assets:
|
||||
|
||||
Publishing is one-shot: an existing Release or draft is an error, and only a
|
||||
published prerelease can be promoted.
|
||||
|
||||
- `nanokvm_<version>.tar.gz`
|
||||
- `latest.json`
|
||||
|
||||
118
scripts/verify-release-assets.sh
Executable file
118
scripts/verify-release-assets.sh
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/bin/bash
|
||||
# Verify the three assets that make up a NanoKVM GitHub release.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ASSET_DIR="${1:-}"
|
||||
VERSION="${2:-}"
|
||||
|
||||
if [ -z "$ASSET_DIR" ] || [ -z "$VERSION" ]; then
|
||||
echo "Usage: $0 <asset-directory> <version>" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "[ERROR] invalid version '$VERSION', expected MAJOR.MINOR.PATCH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARBALL_NAME="nanokvm_${VERSION}.tar.gz"
|
||||
TARBALL="$ASSET_DIR/$TARBALL_NAME"
|
||||
MANIFEST="$ASSET_DIR/latest.json"
|
||||
CHECKSUM="$ASSET_DIR/sha256.txt"
|
||||
|
||||
for path in "$TARBALL" "$MANIFEST" "$CHECKSUM"; do
|
||||
if [ ! -f "$path" ]; then
|
||||
echo "[ERROR] missing release asset: $path" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
PACKAGE_ROOT="nanokvm_${VERSION}"
|
||||
ENTRY_LIST=$(mktemp)
|
||||
VERBOSE_LIST=$(mktemp)
|
||||
trap 'rm -f "$ENTRY_LIST" "$VERBOSE_LIST"' EXIT
|
||||
|
||||
if ! tar -tzf "$TARBALL" > "$ENTRY_LIST"; then
|
||||
echo "[ERROR] could not list release tarball" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! tar -tvzf "$TARBALL" > "$VERBOSE_LIST"; then
|
||||
echo "[ERROR] could not inspect release tarball entry types" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENTRY_COUNT=0
|
||||
while IFS= read -r entry; do
|
||||
ENTRY_COUNT=$((ENTRY_COUNT + 1))
|
||||
case "$entry" in
|
||||
"$PACKAGE_ROOT"|"$PACKAGE_ROOT"/*) ;;
|
||||
*)
|
||||
echo "[ERROR] archive entry is outside $PACKAGE_ROOT/: $entry" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case "/$entry/" in
|
||||
*/../*|*/./*)
|
||||
echo "[ERROR] archive entry contains an unsafe path component: $entry" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done < "$ENTRY_LIST"
|
||||
if [ "$ENTRY_COUNT" -eq 0 ]; then
|
||||
echo "[ERROR] release tarball is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while IFS= read -r verbose_entry; do
|
||||
entry_type=${verbose_entry:0:1}
|
||||
case "$entry_type" in
|
||||
-|d) ;;
|
||||
*)
|
||||
echo "[ERROR] archive contains a link or special entry: $verbose_entry" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done < "$VERBOSE_LIST"
|
||||
|
||||
ARCHIVE_VERSION=$(tar -xOzf "$TARBALL" "$PACKAGE_ROOT/version" 2>/dev/null) || {
|
||||
echo "[ERROR] release tarball does not contain $PACKAGE_ROOT/version" >&2
|
||||
exit 1
|
||||
}
|
||||
if [ "$ARCHIVE_VERSION" != "$VERSION" ]; then
|
||||
echo "[ERROR] archive version '$ARCHIVE_VERSION' does not match '$VERSION'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SHA256=$(sha256sum "$TARBALL" | cut -d' ' -f1)
|
||||
if ! printf '%s %s\n' "$SHA256" "$TARBALL_NAME" | cmp -s - "$CHECKSUM"; then
|
||||
echo "[ERROR] sha256.txt does not match $TARBALL_NAME" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MANIFEST_VERSION=$(jq -er '.version | select(type == "string" and length > 0)' "$MANIFEST")
|
||||
MANIFEST_NAME=$(jq -er '.name | select(type == "string" and length > 0)' "$MANIFEST")
|
||||
MANIFEST_SIZE=$(jq -er '.size | select(type == "number" and . >= 0 and floor == .)' "$MANIFEST")
|
||||
MANIFEST_SHA512=$(jq -er '.sha512 | select(type == "string" and length > 0)' "$MANIFEST")
|
||||
|
||||
if [ "$MANIFEST_VERSION" != "$VERSION" ]; then
|
||||
echo "[ERROR] latest.json version '$MANIFEST_VERSION' does not match '$VERSION'" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$MANIFEST_NAME" != "$TARBALL_NAME" ]; then
|
||||
echo "[ERROR] latest.json name '$MANIFEST_NAME' does not match '$TARBALL_NAME'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACTUAL_SIZE=$(wc -c < "$TARBALL" | tr -d ' ')
|
||||
if [ "$MANIFEST_SIZE" != "$ACTUAL_SIZE" ]; then
|
||||
echo "[ERROR] latest.json size '$MANIFEST_SIZE' does not match '$ACTUAL_SIZE'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ACTUAL_SHA512=$(openssl dgst -sha512 -binary "$TARBALL" | openssl base64 -A)
|
||||
if [ "$MANIFEST_SHA512" != "$ACTUAL_SHA512" ]; then
|
||||
echo "[ERROR] latest.json sha512 does not match $TARBALL_NAME" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[OK] verified NanoKVM release assets for $VERSION"
|
||||
43
scripts/verify-release-tag.sh
Executable file
43
scripts/verify-release-tag.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
# Resolve an annotated numeric tag from origin and verify its commit is on main.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TAG="${1:-}"
|
||||
EXPECTED_SHA="${2:-}"
|
||||
|
||||
if ! echo "$TAG" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "[ERROR] invalid tag '$TAG', expected MAJOR.MINOR.PATCH" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$EXPECTED_SHA" ] && ! echo "$EXPECTED_SHA" | grep -qE '^[0-9a-f]{40}$'; then
|
||||
echo "[ERROR] expected commit must be a full lowercase commit SHA" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TAG_REF="refs/tags/$TAG"
|
||||
PEELED_REF="${TAG_REF}^{}"
|
||||
REMOTE_REFS=$(git ls-remote --tags origin "$TAG_REF" "$PEELED_REF")
|
||||
TAG_OBJECT=$(printf '%s\n' "$REMOTE_REFS" | awk -v ref="$TAG_REF" '$2 == ref { print $1 }')
|
||||
SOURCE_SHA=$(printf '%s\n' "$REMOTE_REFS" | awk -v ref="$PEELED_REF" '$2 == ref { print $1 }')
|
||||
|
||||
if ! echo "$TAG_OBJECT" | grep -qE '^[0-9a-f]{40}$'; then
|
||||
echo "[ERROR] tag '$TAG' does not exist on origin" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! echo "$SOURCE_SHA" | grep -qE '^[0-9a-f]{40}$'; then
|
||||
echo "[ERROR] tag '$TAG' is not an annotated tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ -n "$EXPECTED_SHA" ] && [ "$SOURCE_SHA" != "$EXPECTED_SHA" ]; then
|
||||
echo "[ERROR] tag '$TAG' moved from '$EXPECTED_SHA' to '$SOURCE_SHA'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git fetch --no-tags origin main
|
||||
if ! git merge-base --is-ancestor "$SOURCE_SHA" FETCH_HEAD; then
|
||||
echo "[ERROR] tag '$TAG' points to a commit outside origin/main" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$SOURCE_SHA"
|
||||
Reference in New Issue
Block a user