diff --git a/.github/workflows/build-installer-iso.yml b/.github/workflows/build-installer-iso.yml new file mode 100644 index 0000000..90d2b20 --- /dev/null +++ b/.github/workflows/build-installer-iso.yml @@ -0,0 +1,163 @@ +name: Build veilor-os Installer ISO + +# v0.7+ — produces a small Anaconda installer ISO that consumes +# kickstart/install-ostreecontainer-installer.ks. The ISO boots +# Anaconda, asks for LUKS pw + admin pw interactively, then +# `ostreecontainer` populates / from the v0.7 OCI image at +# ghcr.io/veilor-org/veilor-os:43. + +on: + push: + branches: [v0.7-bluebuild-spike] + paths: + - 'kickstart/install-ostreecontainer.ks' + - 'kickstart/install-ostreecontainer-installer.ks' + - 'bluebuild/recipe.yml' + - '.github/workflows/build-installer-iso.yml' + workflow_dispatch: + inputs: + releasever: + description: 'Fedora release version' + required: false + default: '43' + +permissions: + contents: write # needed to create+update installer-latest release + +jobs: + build: + name: Build installer ISO + runs-on: nullstone + timeout-minutes: 120 + + env: + RELEASEVER: ${{ github.event.inputs.releasever || '43' }} + + steps: + - name: Checkout + uses: actions/checkout@v4.1.7 + + - name: Install build tooling (Fedora) + run: | + set -euxo pipefail + dnf -y upgrade --refresh + dnf -y install --skip-unavailable \ + lorax \ + pykickstart \ + anaconda-tui \ + syslinux \ + xorriso \ + grub2-efi-x64 \ + grub2-efi-x64-modules \ + grub2-pc \ + grub2-pc-modules \ + shim-x64 \ + efibootmgr + + - name: Validate installer kickstart + run: | + set -euxo pipefail + ksvalidator kickstart/install-ostreecontainer-installer.ks + + - name: Build installer ISO with livemedia-creator + run: | + set -euxo pipefail + mkdir -p build/out /var/lmc + ln -sfn "$GITHUB_WORKSPACE" /work + livemedia-creator \ + --make-iso \ + --no-virt \ + --ks kickstart/install-ostreecontainer-installer.ks \ + --resultdir build/out \ + --tmp /var/lmc \ + --title "veilor-os" \ + --project "veilor-os" \ + --releasever "$RELEASEVER" \ + --logfile build/out/build.log \ + 2>&1 | tee -a build/out/build.log + + - name: Rename ISO + sha256 + run: | + set -euxo pipefail + ISO_FILE=$(ls build/out/*.iso 2>/dev/null | head -1) + [ -n "$ISO_FILE" ] || { echo "[ERR] no ISO produced"; exit 1; } + ISO_NAME="veilor-os-installer-${RELEASEVER}-$(date +%Y%m%d-%H%M%S).iso" + mv "$ISO_FILE" "build/out/$ISO_NAME" + cd build/out + sha256sum "$ISO_NAME" > "$ISO_NAME.sha256" + ls -lh "$ISO_NAME" + + - name: Split ISO into 1900M chunks + if: success() && github.ref == 'refs/heads/v0.7-bluebuild-spike' + run: | + set -euo pipefail + cd build/out + ISO=$(ls *.iso | head -1) + [ -n "$ISO" ] || { echo "[ERR] no ISO"; exit 1; } + split -b 1900M -d --suffix-length=2 "$ISO" "${ISO}.part-" + rm -f "$ISO" + sha256sum *.part-* > "${ISO}.parts.sha256" + ls "${ISO}".part-* + + - name: Publish to installer-latest rolling prerelease (Forgejo) + if: success() && github.ref == 'refs/heads/v0.7-bluebuild-spike' && github.server_url != 'https://github.com' + env: + FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FORGEJO_API: ${{ github.server_url }}/api/v1 + REPO: ${{ github.repository }} + GIT_SHA: ${{ github.sha }} + run: | + set -euo pipefail + TAG="installer-latest" + REL_JSON=$(curl -fsSL -H "Authorization: token ${FORGEJO_TOKEN}" \ + "${FORGEJO_API}/repos/${REPO}/releases/tags/${TAG}" 2>/dev/null || echo "") + if [ -n "$REL_JSON" ]; then + REL_ID=$(echo "$REL_JSON" | grep -oE '"id":\s*[0-9]+' | head -1 | grep -oE '[0-9]+') + if [ -n "$REL_ID" ]; then + curl -fsSL -X DELETE -H "Authorization: token ${FORGEJO_TOKEN}" \ + "${FORGEJO_API}/repos/${REPO}/releases/${REL_ID}" || true + curl -fsSL -X DELETE -H "Authorization: token ${FORGEJO_TOKEN}" \ + "${FORGEJO_API}/repos/${REPO}/git/refs/tags/${TAG}" || true + fi + fi + BODY="Rolling auto-build from v0.7-bluebuild-spike. Latest commit: ${GIT_SHA}. + + Installer ISO — boots Anaconda, prompts for LUKS pw + admin pw, + then ostreecontainer-pulls / from ghcr.io/veilor-org/veilor-os:43. + + Reassemble: + cat veilor-os-installer-*.iso.part-* > veilor-os-installer.iso + sha256sum -c veilor-os-installer-*.iso.parts.sha256 + + Not a stable release — for testing only." + PAYLOAD=$(BODY="$BODY" TAG="$TAG" python3 -c " + import json,os + print(json.dumps({ + 'tag_name': os.environ['TAG'], + 'target_commitish': 'v0.7-bluebuild-spike', + 'name': 'installer-latest (auto)', + 'body': os.environ['BODY'], + 'prerelease': True, + 'draft': False, + }))") + REL_ID=$(curl -fsSL -X POST -H "Authorization: token ${FORGEJO_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + "${FORGEJO_API}/repos/${REPO}/releases" | \ + grep -oE '"id":\s*[0-9]+' | head -1 | grep -oE '[0-9]+') + [ -n "$REL_ID" ] || { echo "[ERR] failed to create release"; exit 1; } + cd build/out + for f in *.iso.part-* *.sha256; do + [ -f "$f" ] || continue + curl -fsSL -X POST -H "Authorization: token ${FORGEJO_TOKEN}" \ + -F "attachment=@${f}" \ + "${FORGEJO_API}/repos/${REPO}/releases/${REL_ID}/assets?name=${f}" + done + + - name: Print build log on failure + if: failure() + run: | + echo "─── build/out/build.log ───" + tail -200 build/out/build.log 2>/dev/null || echo "(no build.log)" + find build/out -name 'program.log' -exec tail -100 {} \; 2>/dev/null || true + find /var/lmc -name '*.log' -exec tail -50 {} \; 2>/dev/null || true diff --git a/kickstart/install-ostreecontainer-installer.ks b/kickstart/install-ostreecontainer-installer.ks new file mode 100644 index 0000000..0294de2 --- /dev/null +++ b/kickstart/install-ostreecontainer-installer.ks @@ -0,0 +1,47 @@ +# veilor-os installer kickstart — v0.7 CI build variant +# +# Derived from kickstart/install-ostreecontainer.ks by stripping all +# __PLACEHOLDER__ tokens that the runtime gum TUI substitutes at install +# time. Anaconda's interactive TUI handles disk selection, LUKS passphrase, +# and user account creation in their place. +# +# Consumed by livemedia-creator --make-iso to produce +# veilor-os-installer-43-*.iso. Do NOT add __PLACEHOLDER__ tokens here — +# they cannot be filled at build time. See install-ostreecontainer.ks +# for the runtime template the gum TUI fills in. + +# ── Locale / keyboard / time ── +keyboard --xlayouts='us' +lang en_US.UTF-8 +timezone Europe/London --utc + +# ── Install mode ── +text +firstboot --disable +eula --agreed +selinux --enforcing + +# ── Network ── +network --bootproto=dhcp --device=link --activate --hostname=veilor-install +firewall --enabled --service=ssh + +# ── Identity ── +# rootpw --lock only. No user directive — Anaconda's user spoke handles +# admin account creation interactively. Runtime ks substitutes +# --password=__ADMIN_PW__ for unattended installs. +rootpw --lock + +# ── Disk / partitioning ── +# Intentionally absent. Anaconda's disk spoke presents interactive +# disk + LUKS + btrfs selection. Runtime ks (gum TUI) provides the +# full partition spec at real-install time. + +# ── ostreecontainer: populate / from veilor-os OCI ── +ostreecontainer --url=ghcr.io/veilor-org/veilor-os:43 --transport=registry + +# ── %post (chroot) ── +%post +set -uo pipefail +echo veilor-install > /etc/hostname +chage -d 0 admin 2>/dev/null || true +%end