#!/bin/bash # release.sh - Build a release artifact and create a Gitea release via `tea` # # Usage: # scripts/release.sh [VERSION] # # If VERSION is not given, it is read from the latest git tag (e.g. v0.1.5). # Falls back to "dev" if no tag is found. # # Steps: # 1. Validate toolchain (git, go, tar, tea) # 2. Determine version # 3. Build orca binary with version injection via -ldflags # 4. Package as tarball: orca-${VERSION}-${OS}-${ARCH}.tar.gz # 5. Generate release notes from `---ci---` blocks since last tag # 6. Invoke `tea releases create` to publish to Gitea # # Requires: # - GITEA_TOKEN environment variable # - `tea` CLI on PATH (https://gitea.com/gitea/tea) # # Idempotent: tea releases create will fail if the release already exists; # the script surfaces that error rather than silently swallowing it. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$REPO_ROOT" # Source .env for GITEA_TOKEN if present for env_file in "$REPO_ROOT/.env" "$PWD/.env" "./.env"; do if [ -f "$env_file" ]; then set -a # shellcheck disable=SC1090 . "$env_file" set +a break fi done # --- helpers -------------------------------------------------------------- err() { echo "release: error: $*" >&2; exit 1; } info() { echo "release: $*"; } require_tool() { command -v "$1" >/dev/null 2>&1 || err "required tool not found: $1" } # --- preflight ------------------------------------------------------------ require_tool git require_tool go require_tool tar if [ -z "${GITEA_TOKEN:-}" ]; then err "GITEA_TOKEN is not set. Export it or put it in .env" fi if ! command -v tea >/dev/null 2>&1; then err "tea CLI not found on PATH. Install from https://gitea.com/gitea/tea" fi # --- version detection ---------------------------------------------------- VERSION="${1:-}" if [ -z "$VERSION" ]; then VERSION="$(git describe --tags --abbrev=0 2>/dev/null || echo dev)" fi # Strip leading 'v' for the tarball name (we keep it in the release tag itself) VERSION_NUMBER="${VERSION#v}" info "version: $VERSION" info "building..." # --- build with version injection ---------------------------------------- # Cross-build linux-amd64 regardless of host arch (D-193). The install.sh # user base is amd64; the .coreci.yml release step hardcodes the amd64 # tarball name. Building for the host arch produced the wrong tarball when # the release was cut from an arm64 dev machine — the root cause of the # v0.4.5 install incident (REQ-097). GIT_COMMIT="$(git rev-parse --short HEAD)" BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" LDFLAGS="-s -w -X git.cloudinit.dev/coreci/orca/internal/cli.version=$VERSION -X git.cloudinit.dev/coreci/orca/internal/cli.gitCommit=$GIT_COMMIT -X git.cloudinit.dev/coreci/orca/internal/cli.buildTime=$BUILD_TIME" mkdir -p bin info "building orca-${VERSION}-linux-amd64 (cross-compile, CGO_ENABLED=0)..." CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="$LDFLAGS" -o bin/orca ./cmd/orca info "built: bin/orca (linux-amd64)" # --- tarball -------------------------------------------------------------- # Always produce the linux-amd64 tarball name that install.sh looks for. # (D-193: arm64 is a separate enhancement; this milestone ships amd64 only.) TARBALL="orca-${VERSION}-linux-amd64.tar.gz" tar -czf "$TARBALL" -C bin orca info "packaged: $TARBALL ($(du -h "$TARBALL" | cut -f1))" # --- release notes from ---ci--- blocks ---------------------------------- NOTES_FILE="$(mktemp)" trap 'rm -f "$NOTES_FILE"' EXIT { echo "# Release $VERSION" echo "" echo "_Built: $BUILD_TIME from $GIT_COMMIT" echo "" PREV_TAG="$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")" if [ -n "$PREV_TAG" ]; then RANGE="$PREV_TAG..HEAD" else RANGE="HEAD" fi echo "## Changes since $PREV_TAG" echo "" # Extract messages of ---ci--- tagged commits in the range git log --pretty=format:'- %s' "$RANGE" 2>/dev/null | head -100 || true echo "" } > "$NOTES_FILE" info "release notes: $NOTES_FILE" cat "$NOTES_FILE" # --- publish to gitea ----------------------------------------------------- info "creating gitea release..." tea releases create "$VERSION" \ --repo "$REPO" \ --title "Orca $VERSION" \ --note-file "$NOTES_FILE" \ --asset "$TARBALL" # --- post-create asset verification (REQ-097, gate C-21) ------------------ # tea releases create has been observed to exit 0 without attaching the # asset in some versions. Verify the asset actually appears in the release # via the Gitea API; retry once if missing; fail loudly if still missing. # This is the root-cause fix for the v0.8.x releases that shipped with zero # binary assets. verify_asset() { local tag="$1" want="$2" curl -fsSL "${GITEA_URL:-https://git.cloudinit.dev}/api/v1/repos/${GITEA_OWNER:-coreci}/${GITEA_REPO:-orca}/releases/tags/${tag}" \ | sed -n 's/.*"name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \ | grep -qx "$want" } info "verifying asset ${TARBALL} attached to release ${VERSION}..." if verify_asset "$VERSION" "$TARBALL"; then info "✓ asset verified: ${TARBALL}" else info "asset missing after tea releases create; retrying upload..." # Retry: re-add the asset via tea releases edit tea release edit "$VERSION" --repo "$REPO" --asset "$TARBALL" 2>/dev/null \ || tea releases edit "$VERSION" --repo "$REPO" --asset "$TARBALL" 2>/dev/null \ || true sleep 2 if verify_asset "$VERSION" "$TARBALL"; then info "✓ asset verified on retry: ${TARBALL}" else err "asset ${TARBALL} NOT attached to release ${VERSION} after retry — the release exists but has no binary. Run 'tea releases edit ${VERSION} --repo $REPO --asset $TARBALL' manually. (REQ-097, C-21)" fi fi info "✓ release $VERSION published with binary asset" # --- publish container image to gitea registry (REQ-046) ------------------ # Skipped gracefully if docker is not on PATH (e.g. local dev without docker). # The .coreci.yml release pipeline has a dedicated container-publish step # that runs in a docker:24-cli image with docker-in-docker. CONTAINER_REGISTRY="${CONTAINER_REGISTRY:-git.cloudinit.dev}" CONTAINER_OWNER="${CONTAINER_OWNER:-coreci}" CONTAINER_IMAGE="${CONTAINER_IMAGE:-orca}" IMAGE="${CONTAINER_REGISTRY}/${CONTAINER_OWNER}/${CONTAINER_IMAGE}" if ! command -v docker >/dev/null 2>&1; then info "docker not found on PATH — skipping container image publish (CI handles it)." else info "building container image ${IMAGE}:${VERSION}..." docker build \ --build-arg VERSION="$VERSION" \ --build-arg GIT_COMMIT="$GIT_COMMIT" \ --build-arg BUILD_TIME="$BUILD_TIME" \ -t "${IMAGE}:${VERSION}" \ -t "${IMAGE}:latest" \ "$REPO_ROOT" if [ -z "${GITEA_TOKEN:-}" ]; then info "GITEA_TOKEN not set — skipping docker push (image built locally only)." else info "logging in to ${CONTAINER_REGISTRY}..." echo "$GITEA_TOKEN" | docker login "$CONTAINER_REGISTRY" -u cloudinit-bot --password-stdin info "pushing ${IMAGE}:${VERSION}..." docker push "${IMAGE}:${VERSION}" info "pushing ${IMAGE}:latest..." docker push "${IMAGE}:latest" docker logout "$CONTAINER_REGISTRY" info "✓ container image ${IMAGE}:${VERSION} published" fi fi