From f8c308d9b729491a0c8d0d946ea2c8007ea2ac41 Mon Sep 17 00:00:00 2001 From: Adolfo Delorenzo Date: Thu, 14 May 2026 20:18:41 -0600 Subject: [PATCH] ci: fix release.yaml so v0.3.1+ auto-publishes a complete release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes that should have happened pre-v0.3.0: 1. Add a build-disk-arm64 job that runs on the arm64-linux runner (Odroid), building kernel + rootfs + disk-image then xz-compressing the .arm64.img. The previous release.yaml shipped x86_64 only. 2. Replace softprops/action-gh-release@v2 with a direct curl against Gitea's /api/v1/repos///releases endpoint. The softprops action hard-codes api.github.com instead of honouring ${{ github.api_url }}, so on Gitea's act_runner it succeeds silently without creating a release. The curl path uses the auto-populated ${{ secrets.GITHUB_TOKEN }} for auth; doc note in ci-runners.md covers the GITEA_TOKEN fallback. 3. Downgrade actions/upload-artifact and actions/download-artifact from @v4 to @v3 to match Gitea act_runner v1.0.x's compatibility — same fix we applied to ci.yaml in 0c6e200. Also compress the x86 disk image with xz before uploading (parity with the arm64 path, saves ~95% on bandwidth), and emit SHA256SUMS over all attached artifacts. docs/ci-runners.md gains a "Workflows in this repo" table, a per-job breakdown of the release pipeline, the rationale for direct-curl over the marketplace action, and a "manually re-running a release" section warning against force-updating published tags. This commit fixes the workflow but does not retroactively rebuild v0.3.0. v0.3.0's release page already has the manually-uploaded arm64 image and SHA256SUMS; x86 users who want the v0.3.0 artifact build from source (documented in the release body). v0.3.1 will be the first tag that exercises the fixed workflow end-to-end. Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/release.yaml | 245 ++++++++++++++++++++++++---------- docs/ci-runners.md | 57 ++++++++ 2 files changed, 231 insertions(+), 71 deletions(-) diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index dab137b..7cd45db 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -1,5 +1,19 @@ name: Release +# Triggered by `git push origin vX.Y.Z`. Builds Go binaries (amd64+arm64), +# x86_64 ISO + disk image, ARM64 disk image, computes SHA256SUMS over all +# artifacts, and posts a Gitea release with everything attached via the +# Gitea API. +# +# Notes for future-you: +# - upload-artifact / download-artifact are pinned to @v3 because Gitea's +# act_runner v1.0.x doesn't fully implement v4 yet. +# - The release step uses curl against Gitea's own /api/v1/repos/.../releases +# instead of a third-party action (softprops/action-gh-release et al); +# act_runner doesn't reliably proxy GitHub.com-targeted actions. +# - The arm64 disk-image build runs on the Odroid self-hosted runner via +# the `arm64-linux` label. Docs in docs/ci-runners.md. + on: push: tags: @@ -11,19 +25,16 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 with: go-version: '1.22' - - name: Test cloud-init run: cd cloud-init && go test ./... -count=1 - - name: Test update agent run: cd update && go test ./... -count=1 build-binaries: - name: Build Binaries + name: Build Binaries (${{ matrix.suffix }}) runs-on: ubuntu-latest needs: test strategy: @@ -37,129 +48,221 @@ jobs: suffix: linux-arm64 steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 with: go-version: '1.22' - - name: Get version id: version run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT - - name: Build cloud-init run: | CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \ go build -ldflags="-s -w -X main.version=${{ steps.version.outputs.version }}" \ -o kubesolo-cloudinit-${{ matrix.suffix }} ./cmd/ working-directory: cloud-init - - name: Build update agent run: | CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \ go build -ldflags="-s -w -X main.version=${{ steps.version.outputs.version }}" \ -o kubesolo-update-${{ matrix.suffix }} . working-directory: update - - name: Upload binaries - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: binaries-${{ matrix.suffix }} path: | cloud-init/kubesolo-cloudinit-${{ matrix.suffix }} update/kubesolo-update-${{ matrix.suffix }} - build-iso: - name: Build ISO (amd64) + build-iso-amd64: + name: Build x86_64 ISO + disk image runs-on: ubuntu-latest needs: build-binaries steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 with: go-version: '1.22' - - name: Install build deps run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ cpio gzip genisoimage isolinux syslinux syslinux-common \ syslinux-utils xorriso xz-utils wget squashfs-tools \ - dosfstools e2fsprogs fdisk parted bsdtar - - - name: Build ISO - run: make iso - - - name: Build disk image - run: make disk-image - - - name: Get version - id: version - run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT - - - name: Upload ISO - uses: actions/upload-artifact@v4 + dosfstools e2fsprogs fdisk parted libarchive-tools \ + grub-common grub-efi-amd64-bin grub-pc-bin kpartx busybox-static + - name: Build kernel + ISO + disk-image + run: | + make kernel + make build-cloudinit build-update-agent + make rootfs initramfs + make iso + make disk-image + - name: Compress disk image + # The raw .img is 4 GB sparse; xz takes it to ~50-300 MB depending + # on dictionary level. Use -6 (default) for memory safety on the + # GitHub-Actions-style runner. + run: | + xz -k -T0 --memlimit-compress=1500MiB -6 output/*.img + ls -lh output/ + - name: Upload x86_64 artifacts + uses: actions/upload-artifact@v3 with: - name: iso-amd64 - path: output/*.iso + name: image-amd64 + path: | + output/*.iso + output/*.img.xz - - name: Upload disk image - uses: actions/upload-artifact@v4 + build-disk-arm64: + name: Build ARM64 disk image + runs-on: arm64-linux + needs: test + steps: + - uses: actions/checkout@v4 + - name: Show host info + run: | + uname -a + nproc + free -h + df -h / + - name: Build kernel + rootfs + disk-image + # Runner runs as root via systemd; explicit sudo is harmless but + # documented as such in docs/ci-runners.md. + run: | + make kernel-arm64 + make build-cross + make rootfs-arm64 + make disk-image-arm64 + - name: Compress disk image + run: | + xz -k -T0 --memlimit-compress=1500MiB -6 output/*.arm64.img + ls -lh output/ + - name: Upload ARM64 artifacts + uses: actions/upload-artifact@v3 with: - name: disk-image-amd64 - path: output/*.img + name: image-arm64 + path: output/*.arm64.img.xz release: - name: Create Release + name: Publish Gitea Release runs-on: ubuntu-latest - needs: [build-binaries, build-iso] + needs: [build-binaries, build-iso-amd64, build-disk-arm64] steps: - uses: actions/checkout@v4 - name: Get version id: version - run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT + # `cat VERSION` would be stale on tag pushes (VERSION already bumped + # for the tag, but using ref_name is unambiguous). + run: echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v3 with: path: artifacts - - name: Compute checksums + - name: Flatten artifacts + compute checksums run: | - cd artifacts - find . -type f \( -name "*.iso" -o -name "*.img" -o -name "kubesolo-*" \) \ - -exec sha256sum {} \; | sort > ../SHA256SUMS - cd .. + mkdir -p release + # Each upload-artifact wrote into artifacts//... + find artifacts -type f \( \ + -name "*.iso" -o \ + -name "*.img.xz" -o \ + -name "kubesolo-*" \ + \) -exec cp {} release/ \; + (cd release && sha256sum * | sort > SHA256SUMS) + ls -lh release/ + cat release/SHA256SUMS - - name: Create release - uses: softprops/action-gh-release@v2 - with: - name: KubeSolo OS v${{ steps.version.outputs.version }} - body: | - ## KubeSolo OS v${{ steps.version.outputs.version }} + - name: Install release tooling + run: sudo apt-get update && sudo apt-get install -y jq curl - ### Downloads - - **ISO** — Boot from CD/USB, ideal for testing - - **Disk Image** — Raw disk with A/B partitions + GRUB - - **Binaries** — Standalone cloud-init and update agent + - name: Render release body + id: body + run: | + VERSION="${{ steps.version.outputs.version }}" + # Strip the leading 'v' for cosmetic display in the body. + DISPLAY="${VERSION#v}" + cat > release-body.md < Creating release for $TAG against $REPO_API" + CREATE_RESP=$(curl -fsSL -X POST \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + "$REPO_API/releases") + + RELEASE_ID=$(echo "$CREATE_RESP" | jq -r '.id') + if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then + echo "ERROR: Could not extract release id from response:" + echo "$CREATE_RESP" | jq . || echo "$CREATE_RESP" + exit 1 + fi + echo "==> Release id: $RELEASE_ID" + + # 2. Upload each asset. asset?name= names the attachment; we use + # the basename so users see the same filename the build produced. + for f in release/*; do + [ -f "$f" ] || continue + name=$(basename "$f") + echo "==> Uploading $name ($(du -h "$f" | cut -f1))" + curl -fsSL -X POST \ + -H "Authorization: token $TOKEN" \ + -F "attachment=@$f" \ + "$REPO_API/releases/$RELEASE_ID/assets?name=$name" >/dev/null + done + + echo "==> Release published: $REPO_API/../releases/tag/$TAG" diff --git a/docs/ci-runners.md b/docs/ci-runners.md index de25c8a..2a51dc1 100644 --- a/docs/ci-runners.md +++ b/docs/ci-runners.md @@ -26,6 +26,63 @@ Generic ubuntu jobs that don't care about arch fall through to whichever runner them up first; on the Odroid they run in Docker via the `ubuntu-latest` / `ubuntu-22.04` / `ubuntu-24.04` labels. +## Workflows in this repo + +| Workflow file | Trigger | Where it runs | What it produces | +|---|---|---|---| +| `.gitea/workflows/ci.yaml` | push / PR to main | ubuntu-latest | Go tests, cross-arch binary build, shellcheck | +| `.gitea/workflows/build-arm64.yaml` | push to main, tags `v*`, manual | `arm64-linux` (Odroid) | ARM64 kernel + rootfs + disk image; uploads as workflow artifact only | +| `.gitea/workflows/release.yaml` | tags `v*` | mix: ubuntu-latest + `arm64-linux` | Full release: x86 ISO + disk, ARM64 disk, Go binaries, SHA256SUMS — posted to Gitea Releases via API | + +### Release workflow specifics + +`release.yaml` is what fires when you `git push origin vX.Y.Z`. The pipeline: + +1. **test** — `go test` cloud-init + update modules (ubuntu-latest). +2. **build-binaries** — cross-compiles `kubesolo-cloudinit` and + `kubesolo-update` for linux-amd64 + linux-arm64 with the version baked + in via `-X main.version=…`. +3. **build-iso-amd64** — runs `make iso disk-image` on ubuntu-latest; + produces the x86_64 ISO and a `.img.xz` compressed disk image. +4. **build-disk-arm64** — runs the same flow on the Odroid (`arm64-linux` + label); produces `.arm64.img.xz`. +5. **release** — downloads everything, computes `SHA256SUMS`, calls + Gitea's `POST /api/v1/repos///releases` to create the + release, then `POST .../releases//assets?name=…` once per asset. + +Authentication uses Gitea's built-in `${{ secrets.GITHUB_TOKEN }}` — the +runner auto-populates that secret with repo-write scope. If your runner +is configured without that automatic token (e.g. an older `act_runner`), +generate a personal access token with `repo:write` scope, add it as an +org secret named `GITEA_TOKEN`, and swap the `TOKEN: ${{ secrets.GITHUB_TOKEN }}` +line in `release.yaml` for `TOKEN: ${{ secrets.GITEA_TOKEN }}`. + +### Why not the GitHub Marketplace release actions? + +`release.yaml` used to call `softprops/action-gh-release@v2`. That action +hard-codes calls to `api.github.com` instead of using `${{ github.api_url }}` +(which Gitea sets to its own API). On Gitea's act_runner the action fails +silently — the job reports green but no release is created. We replaced +it with a direct `curl` so the behaviour is explicit and debuggable. + +Similarly, `actions/upload-artifact@v4` and `@download-artifact@v4` are not +fully implemented by act_runner v1.0.x. Pin to `@v3` until upstream +support catches up. + +### Manually re-running a release + +Releases are immutable once published, but you can: + +- **Delete and recreate the release** through the Gitea UI on the + `releases/tag/vX.Y.Z` page, then push the tag again (Gitea reuses the + existing tag), and re-trigger the workflow via the Actions UI. +- **Trigger the build-arm64 workflow manually** for a one-off arm64 + artifact: Gitea UI → Actions → ARM64 Build → Run workflow. + +Don't force-update a published tag — anyone who already fetched it (or +downloaded an asset) sees a checksum mismatch. Prefer cutting a new patch +release (vX.Y.Z+1) over rewriting a published one. + ## Registering a new runner ### Prerequisites