ci: fix release.yaml so v0.3.1+ auto-publishes a complete release
Some checks failed
ARM64 Build / Build generic ARM64 disk image (push) Failing after 3s
CI / Go Tests (push) Successful in 1m40s
CI / Shellcheck (push) Successful in 55s
CI / Build Go Binaries (amd64, linux, linux-amd64) (push) Successful in 1m16s
CI / Build Go Binaries (arm64, linux, linux-arm64) (push) Successful in 1m21s
Some checks failed
ARM64 Build / Build generic ARM64 disk image (push) Failing after 3s
CI / Go Tests (push) Successful in 1m40s
CI / Shellcheck (push) Successful in 55s
CI / Build Go Binaries (amd64, linux, linux-amd64) (push) Successful in 1m16s
CI / Build Go Binaries (arm64, linux, linux-arm64) (push) Successful in 1m21s
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/<owner>/<repo>/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) <noreply@anthropic.com>
This commit is contained in:
@@ -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/<name>/...
|
||||
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
|
||||
|
||||
- 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 <<EOF
|
||||
See [docs/release-notes-${DISPLAY}.md](./docs/release-notes-${DISPLAY}.md)
|
||||
and [CHANGELOG.md](./CHANGELOG.md) for the full release notes.
|
||||
|
||||
### 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
|
||||
|
||||
- \`kubesolo-os-${DISPLAY}.iso\` — bootable x86_64 ISO
|
||||
- \`kubesolo-os-${DISPLAY}.img.xz\` — x86_64 raw disk image (A/B GPT, GRUB)
|
||||
- \`kubesolo-os-${DISPLAY}.arm64.img.xz\` — ARM64 raw disk image (A/B GPT, UEFI)
|
||||
- \`kubesolo-cloudinit-linux-{amd64,arm64}\` — standalone cloud-init parser
|
||||
- \`kubesolo-update-linux-{amd64,arm64}\` — standalone update agent
|
||||
- \`SHA256SUMS\` — checksums for every artifact above
|
||||
|
||||
### Verify
|
||||
```
|
||||
sha256sum -c SHA256SUMS
|
||||
```
|
||||
|
||||
### Quick Start
|
||||
```bash
|
||||
# Boot in QEMU
|
||||
qemu-system-x86_64 -m 1024 -smp 2 -enable-kvm \
|
||||
-cdrom kubesolo-os-${{ steps.version.outputs.version }}.iso \
|
||||
\`\`\`
|
||||
sha256sum -c SHA256SUMS
|
||||
\`\`\`
|
||||
|
||||
### Quick start
|
||||
|
||||
\`\`\`
|
||||
# x86_64 in QEMU/KVM
|
||||
xz -d kubesolo-os-${DISPLAY}.img.xz
|
||||
qemu-system-x86_64 -m 2048 -smp 2 -enable-kvm \\
|
||||
-drive file=kubesolo-os-${DISPLAY}.img,format=raw,if=virtio \\
|
||||
-nographic
|
||||
```
|
||||
files: |
|
||||
artifacts/**/*.iso
|
||||
artifacts/**/*.img
|
||||
artifacts/**/kubesolo-*
|
||||
SHA256SUMS
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
# ARM64 on Graviton/Ampere or under qemu-system-aarch64
|
||||
xz -d kubesolo-os-${DISPLAY}.arm64.img.xz
|
||||
dd if=kubesolo-os-${DISPLAY}.arm64.img of=/dev/sdX bs=4M status=progress
|
||||
\`\`\`
|
||||
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"
|
||||
|
||||
@@ -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/<owner>/<repo>/releases` to create the
|
||||
release, then `POST .../releases/<id>/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
|
||||
|
||||
Reference in New Issue
Block a user