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: - 'v*' jobs: test: name: Test 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 (${{ matrix.suffix }}) runs-on: ubuntu-latest needs: test strategy: matrix: include: - goos: linux goarch: amd64 suffix: linux-amd64 - goos: linux goarch: arm64 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@v3 with: name: binaries-${{ matrix.suffix }} path: | cloud-init/kubesolo-cloudinit-${{ matrix.suffix }} update/kubesolo-update-${{ matrix.suffix }} build-iso-amd64: name: Build x86_64 ISO + disk image # Routes to a runner with the `amd64-linux` label. As of v0.3.x no such # runner exists in this Gitea instance — the only runner is the Odroid # which is arm64 and would fail apt-installing grub-efi-amd64-bin / # syslinux because those packages aren't in the arm64 ports repo. The # job stays in the workflow (so it auto-runs once an amd64 runner is # registered) but is gated and the release job continues without it. if: false # remove this line once an amd64-linux runner is registered runs-on: amd64-linux 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 libarchive-tools \ grub-common grub-efi-amd64-bin grub-pc-bin kpartx \ busybox-static iptables nftables - 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: image-amd64 path: | output/*.iso output/*.img.xz 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: image-arm64 path: output/*.arm64.img.xz release: name: Publish Gitea Release runs-on: ubuntu-latest # build-iso-amd64 is gated `if: false` in v0.3.x (no amd64 runner yet); # don't block the release on it. build-disk-arm64 is required — that's # the headline artifact for v0.3.x. build-binaries is required since # the Go binaries are core to every release. needs: [build-binaries, build-disk-arm64] # `if: always()` so the release publishes even if the gated x86 job # somehow ran-and-failed instead of being skipped. The downstream # `find` in the Flatten step ignores missing files gracefully. if: always() && needs.build-binaries.result == 'success' && needs.build-disk-arm64.result == 'success' steps: - uses: actions/checkout@v4 - name: Get version id: version # `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@v3 with: path: artifacts - name: Flatten artifacts + compute checksums run: | 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: Install release tooling run: sudo apt-get update && sudo apt-get install -y jq curl - 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 < **x86_64 ISO + disk image**: not built automatically yet. The > release workflow's amd64 build job needs an amd64-linux runner, > which this Gitea instance doesn't have yet. To produce them > yourself, clone the repo at this tag and run \`make iso disk-image\` > on any Linux amd64 host. ### Verify \`\`\` sha256sum -c SHA256SUMS \`\`\` ### Quick start (ARM64) \`\`\` # On Graviton/Ampere/any UEFI ARM64 host: xz -d kubesolo-os-${DISPLAY}.arm64.img.xz sudo dd if=kubesolo-os-${DISPLAY}.arm64.img of=/dev/sdX bs=4M status=progress # Under qemu-system-aarch64 (Apple Silicon w/ HVF): UEFI_FW=\$(brew --prefix qemu)/share/qemu/edk2-aarch64-code.fd qemu-system-aarch64 -M virt -accel hvf -cpu host -m 2048 -smp 2 \\ -nographic -bios "\$UEFI_FW" \\ -drive file=kubesolo-os-${DISPLAY}.arm64.img,format=raw,if=virtio,media=disk \\ -device virtio-rng-pci \\ -net nic,model=virtio \\ -net user,hostfwd=tcp::6443-:6443,hostfwd=tcp::8080-:8080 \`\`\` Then from the host: \`curl http://localhost:8080 > ~/.kube/kubesolo-config\` and \`kubectl --kubeconfig ~/.kube/kubesolo-config get nodes\`. EOF cat release-body.md - name: Create release via Gitea API env: # Gitea's act_runner auto-populates this with repo-write scope. # If not, set a personal access token as a secret named GITEA_TOKEN # on the org and swap the var name below. TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail TAG="${{ steps.version.outputs.version }}" REPO_API="${{ github.api_url }}/repos/${{ github.repository }}" # 1. Create the release. The API is GitHub-compatible at the # request shape; the response includes the numeric release id we # need for asset uploads. PAYLOAD=$(jq -n \ --arg tag "$TAG" \ --arg name "KubeSolo OS $TAG" \ --rawfile body release-body.md \ '{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}') echo "==> 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"