bos/.forgejo/workflows/release-iso.yml
Breadway 9fe02eeea6
Some checks failed
Build and release ISO / release-iso (push) Failing after 46s
ci: publish signed [breadway] repo to dl.breadway.dev/arch
Host job on hestia (no container) collects breadlock plus the ISO AUR
republishes from the Forgejo registry, detach-signs them, repo-add -s,
and writes /srv/breadway-dl/arch/x86_64/. ISO SigLevel stays Never.
2026-08-16 01:00:47 +08:00

206 lines
8.8 KiB
YAML

name: Build and release ISO
# Builds the BOS ISO on the hestia self-hosted runner (native Arch container).
# Stages bakery desktop apps from the *minisign-verified* stable index at
# https://dl.breadway.dev/index.json (see iso/bread-lockfile.toml), then runs
# build-local.sh and uploads the ISO to a Forgejo release. A matching GitHub
# release is created best-effort and points at Forgejo for the download
# (GitHub releases cannot host files larger than 2 GB).
#
# Required secrets:
# RELEASE_TOKEN — Forgejo API token with write:repository scope
# MIRROR_TOKEN — GitHub personal access token with repo scope
# GPG_PRIVATE_KEY — armoured secret key for the dedicated "BOS Release Signing"
# identity (releases@breadway.dev); public half is committed
# at KEYS.asc. Signs ISO SHA256SUMS here; the same secret
# signs the [breadway] repo in signed-repo.yml. No passphrase
# (CI-only key, access controlled via the Forgejo secret
# store).
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: 'Git tag to build (e.g. v0.4.0)'
required: true
jobs:
release-iso:
runs-on: [self-hosted, hestia]
container:
# Floating tag: this environment cannot pin a reproducible digest of
# archlinux:latest. Do not invent one.
image: archlinux:latest
# --privileged: mkarchiso needs CAP_SYS_ADMIN for loop mounts + mknod
# --network=host: gives localhost:3002 access to Forgejo (avoids the
# public git.breadway.dev → Aegis → Tailscale round-trip for pacman)
options: --privileged --network=host
steps:
- name: Install build dependencies
run: |
# grub is required by profiledef.sh bootmodes=('uefi.grub'):
# mkarchiso validates grub-install on the *builder*, not the image.
# archiso pulls syslinux/squashfs-tools/libisoburn; it does not pull grub.
pacman -Syu --noconfirm archiso grub curl python git minisign
- name: Determine tag and version
id: vars
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG="${{ github.ref_name }}"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
- name: Clone repository at tag
run: |
git clone --branch "${{ steps.vars.outputs.tag }}" --depth 1 \
"https://git.breadway.dev/${GITHUB_REPOSITORY}.git" /bos
- name: Stage bakery ecosystem from signed stable index
run: |
set -euo pipefail
cd /bos
LAPTOP_HOME=/build-home python3 scripts/ci-stage-bakery.py
- name: Verify staged bakery bake inputs
run: |
set -euo pipefail
cd /bos
LAPTOP_HOME=/build-home bash scripts/ci-verify-bake.sh
- name: Build ISO
run: |
set -euo pipefail
mkdir -p /bos-work /bos-out
cd /bos
LAPTOP_HOME=/build-home \
WORK=/bos-work \
OUT=/bos-out \
CI_BUILD=1 \
bash build-local.sh
ls -lh /bos-out/*.iso
- name: Checksum and sign
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
run: |
set -euo pipefail
VERSION="${{ steps.vars.outputs.version }}"
ISO=$(ls /bos-out/*.iso | head -1)
ISO_NAME="bos-${VERSION}-x86_64.iso"
cd /bos-out
mv "$(basename "$ISO")" "$ISO_NAME"
sha256sum "$ISO_NAME" > SHA256SUMS
cat SHA256SUMS
pacman -S --noconfirm --needed gnupg
export GNUPGHOME=/tmp/gnupg-release
mkdir -m 700 -p "$GNUPGHOME"
echo "$GPG_PRIVATE_KEY" | gpg --batch --import
gpg --batch --yes --local-user releases@breadway.dev \
--detach-sign --armor -o SHA256SUMS.asc SHA256SUMS
echo "Signed SHA256SUMS -> SHA256SUMS.asc"
- name: Create Forgejo release and upload assets
env:
FORGEJO_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -euo pipefail
TAG="${{ steps.vars.outputs.tag }}"
VERSION="${{ steps.vars.outputs.version }}"
ISO_NAME="bos-${VERSION}-x86_64.iso"
# Use an existing release for this tag if one exists (e.g. created
# manually or by a prior re-run), otherwise create a fresh one.
EXISTING=$(curl -sf \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" \
2>/dev/null || true)
RELEASE_ID=$(echo "${EXISTING}" | python3 -c \
"import json,sys; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
if [ -z "${RELEASE_ID}" ]; then
RELEASE=$(curl -fsS -X POST \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-H "Content-Type: application/json" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases" \
-d "{
\"tag_name\": \"${TAG}\",
\"name\": \"BOS ${TAG}\",
\"prerelease\": false,
\"body\": \"ISO image attached below. Verify with SHA256SUMS + SHA256SUMS.asc (signed by the BOS Release Signing key — see KEYS.asc in the repo).\\n\\nSee the [README](https://github.com/Breadway/bos#testing-in-a-vm) for VM testing instructions.\"
}")
RELEASE_ID=$(echo "${RELEASE}" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
fi
echo "Using release ID: ${RELEASE_ID}"
upload_asset() {
local file="$1" name
name="$(basename "$file")"
local asset_id
asset_id=$(curl -sf \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets" \
| python3 -c "
import json,sys
assets=json.load(sys.stdin)
match=[a['id'] for a in assets if a['name']=='${name}']
print(match[0] if match else '')
" 2>/dev/null || true)
if [ -n "${asset_id}" ]; then
curl -fsS -X DELETE \
-H "Authorization: token ${FORGEJO_TOKEN}" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets/${asset_id}"
echo "Removed existing ${name} asset"
fi
curl -fsS -X POST \
-H "Authorization: token ${FORGEJO_TOKEN}" \
-F "attachment=@${file};filename=${name}" \
"http://localhost:3002/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets"
echo "Uploaded: ${name}"
}
upload_asset "/bos-out/${ISO_NAME}"
upload_asset "/bos-out/SHA256SUMS"
upload_asset "/bos-out/SHA256SUMS.asc"
- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.MIRROR_TOKEN }}
run: |
set -euo pipefail
TAG="${{ steps.vars.outputs.tag }}"
VERSION="${{ steps.vars.outputs.version }}"
FORGEJO_URL="https://git.breadway.dev/${GITHUB_REPOSITORY}/releases/tag/${TAG}"
printf '**Download ISO:** %s\n\nGitHub releases cannot host files >2 GB; the `bos-%s-x86_64.iso` (~2.5 GB), SHA256SUMS, and SHA256SUMS.asc (signed by the BOS Release Signing key — public half at [KEYS.asc](https://github.com/Breadway/bos/blob/main/KEYS.asc)) are all on Forgejo.\n\nSee the [README](https://github.com/Breadway/bos#testing-in-a-vm) for VM testing instructions.' \
"${FORGEJO_URL}" "${VERSION}" > /tmp/gh-release-notes.md
gh release create "${TAG}" \
--repo "Breadway/bos" \
--title "BOS ${TAG}" \
--notes-file /tmp/gh-release-notes.md \
|| echo "skip: GitHub release failed (MIRROR_TOKEN historically broken)"
# `stable` is a marker branch only — CI fast-forwards it to whatever
# commit the latest real (non-RC) release tag points at. Never merged
# into by hand, so unlike the old dev/beta/main model it can't rot:
# nobody has to remember to move it, a bot always does. Lets you
# `git diff stable..main` before a build to see what's new since the
# last release, without a human-maintained promotion step.
- name: Fast-forward stable branch to this tag
if: ${{ !contains(steps.vars.outputs.tag, '-rc.') }}
env:
FORGEJO_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
set -euo pipefail
cd /bos
git push "https://oauth2:${FORGEJO_TOKEN}@git.breadway.dev/${GITHUB_REPOSITORY}.git" \
"HEAD:refs/heads/stable" --force