name: Release # Publishes two artifact streams to the Gitea generic package registry: # # vetting/latest/vetting-bundle.tar.gz slim bundle (orchestrator + # agent + deploy scripts + a # live-image/VERSION pointer), # DELETE+PUT every release. # live-image//vmlinuz immutable kernel/initrd, # live-image//initrd.img one set per live-image # VERSION bump. # # The slow mkosi build (~9 min) only runs when live-image/VERSION changes; # the slim bundle always rebuilds since its inputs (orchestrator + agent + # deploy scripts) are cheap. On the instance, install.sh compares the # bundle's pointer to /var/lib/vetting/live/VERSION and fetches the # immutable artifacts only when they differ. on: push: branches: [main] paths-ignore: - '**/*.md' - '**/*_test.go' - 'docs/**' - 'test/**' - 'tools/**' - 'agent/tests/fakes/**' - '.gitea/workflows/ci.yml' - '.gitea/workflows/e2e.yml' - 'deploy/proxmox-install.sh' - 'deploy/vetting.example.yaml' - '.gitignore' - 'LICENSE' workflow_dispatch: permissions: contents: read jobs: detect: runs-on: ubuntu-latest outputs: li_version: ${{ steps.out.outputs.li_version }} li_changed: ${{ steps.out.outputs.li_changed }} steps: - uses: actions/checkout@v4 with: fetch-depth: 2 - id: out run: | set -euo pipefail v="$(tr -d '[:space:]' < live-image/VERSION)" if [[ ! "${v}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "::error::live-image/VERSION must match v.., got '${v}'" exit 1 fi echo "li_version=${v}" >> "$GITHUB_OUTPUT" # li_changed = VERSION file differs between HEAD^ and HEAD. On # the initial commit that introduces the file, HEAD^ is absent # and we fall through to "changed" so the first release # publishes the live image. if git rev-parse --verify HEAD^ >/dev/null 2>&1; then if git diff --name-only HEAD^..HEAD | grep -qx 'live-image/VERSION'; then echo "li_changed=true" >> "$GITHUB_OUTPUT" else echo "li_changed=false" >> "$GITHUB_OUTPUT" fi else echo "li_changed=true" >> "$GITHUB_OUTPUT" fi build-live-image: needs: detect if: ${{ needs.detect.outputs.li_changed == 'true' }} runs-on: ubuntu-latest timeout-minutes: 45 env: LI_VERSION: ${{ needs.detect.outputs.li_version }} REGISTRY_URL: ${{ vars.REGISTRY_URL }} REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} OWNER: ${{ gitea.repository_owner }} steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: "1.26.x" cache: false - name: Build agent (baked into initrd) run: make agent-linux - name: Install live-image build dependencies run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ debootstrap squashfs-tools dosfstools \ systemd-ukify systemd-boot kmod bubblewrap \ debian-archive-keyring python3-pip git zstd cpio # Ubuntu's apt-packaged mkosi is too old to wire # non-free-firmware shorthand through to debootstrap. # mkosi isn't published on PyPI under v24+ — install the # pinned tag from upstream git instead. bubblewrap provides # the sandbox mkosi uses for its `cp`/chroot plumbing. sudo pip install --break-system-packages \ "git+https://github.com/systemd/mkosi.git@v25.3" - name: Build live image run: make live-image - name: Publish live-image/${{ env.LI_VERSION }}/ run: | set -euo pipefail # Gitea's generic registry treats each version path as # immutable. A re-upload returns 409. Bumping VERSION is the # only way to publish new artifacts here. for f in vmlinuz initrd.img; do curl -fsSL -H "Authorization: token ${REGISTRY_TOKEN}" \ --upload-file "live-image/build/${f}" \ "${REGISTRY_URL}/api/packages/${OWNER}/generic/live-image/${LI_VERSION}/${f}" done bundle: # Always runs. If build-live-image was skipped, the prior upload at # live-image// is the one install.sh will fetch. The # bundle itself never carries vmlinuz/initrd — only the VERSION # pointer — so it's cheap to rebuild every push. needs: [detect, build-live-image] if: >- ${{ always() && needs.detect.result == 'success' && (needs.build-live-image.result == 'success' || needs.build-live-image.result == 'skipped') }} runs-on: ubuntu-latest timeout-minutes: 15 env: LI_VERSION: ${{ needs.detect.outputs.li_version }} REGISTRY_URL: ${{ vars.REGISTRY_URL }} REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} OWNER: ${{ gitea.repository_owner }} steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: "1.26.x" cache: false - name: Install templ run: go install github.com/a-h/templ/cmd/templ@v0.3.1001 - name: Build orchestrator + agent run: make orchestrator-linux agent-linux - name: Assemble slim bundle run: | set -euo pipefail stamp="vetting-bundle" rm -rf "build/${stamp}" "bin/${stamp}.tar.gz" mkdir -p "build/${stamp}/bin" "build/${stamp}/live-image" cp bin/vetting-linux-amd64 bin/vetting-agent.linux-amd64 \ "build/${stamp}/bin/" cp deploy/install.sh deploy/pxe-setup.sh deploy/vetting.service \ deploy/vetting.production.yaml deploy/ipxe-shas.txt \ "build/${stamp}/" # Embed only the live-image VERSION pointer. install.sh on # the target will compare this against /var/lib/vetting/live/VERSION # and curl the actual files from live-image// in the # registry when they differ. printf '%s\n' "${LI_VERSION}" > "build/${stamp}/live-image/VERSION" # Root-level VERSION keeps the orchestrator's git-short-sha # visible to operators (proxmox-install.sh cats it at the end # of the install). git rev-parse --short HEAD > "build/${stamp}/VERSION" tar -C build -czf "bin/${stamp}.tar.gz" "${stamp}" echo "wrote bin/${stamp}.tar.gz ($(du -h "bin/${stamp}.tar.gz" | cut -f1))" - name: Replace vetting/latest/ bundle run: | set -euo pipefail # Delete the whole "latest" version, not the file inside it. # Deleting just the file leaves a ghost version that makes # PUT 404. status=$(curl -sS -o /dev/null -w '%{http_code}' \ -H "Authorization: token ${REGISTRY_TOKEN}" \ -X DELETE \ "${REGISTRY_URL}/api/packages/${OWNER}/generic/vetting/latest") echo "DELETE vetting/latest -> ${status}" case "${status}" in 204|404) ;; *) echo "unexpected DELETE status ${status}"; exit 1 ;; esac # Give Gitea a moment to finalize the version delete before # the upload re-creates it under the same name. sleep 2 curl -fsSL -H "Authorization: token ${REGISTRY_TOKEN}" \ --upload-file bin/vetting-bundle.tar.gz \ "${REGISTRY_URL}/api/packages/${OWNER}/generic/vetting/latest/vetting-bundle.tar.gz"