- new helper overlay/usr/share/veilor-os/scripts/persist-install-logs.sh detects boot USB (BOOT=/findfs, /run/install/repo, /sys/block removable), copies /tmp/anaconda.log + program/storage/packaging/dnf/syslog/X + journalctl -b + dmesg + lsblk/blkid/mount + /proc/cmdline into /veilor-install-logs/<UTC-ts>/ on the stick; mirrors backup into /mnt/sysroot/var/log/veilor-install-logs/ so logs survive even on RO USB or detect failure - toggle: kernel cmdline veilor.install_logs=on|off (default ON until v1.0 final); never fails install on log persistence error - kickstart/install-ostreecontainer-installer.ks: add %post --nochroot block calling helper with toggle-aware inline fallback if helper missing - .github/workflows/build-installer-iso.yml: switch bib config from [customizations.user] to [customizations.installer.kickstart] so our new %post --nochroot actually lands in the produced ISO; admin user now created by ks user directive (locked + chage 0); ostreecontainer line stripped (bib auto-appends it); kernel-cmdline-default limitation documented (osbuild/bootc-image-builder#899) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
208 lines
9 KiB
YAML
208 lines
9 KiB
YAML
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 podman jq
|
|
|
|
- name: Login to Forgejo registry (pull veilor-os OCI)
|
|
env:
|
|
FORGEJO_REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
|
FORGEJO_REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [ -n "${FORGEJO_REGISTRY_TOKEN:-}" ]; then
|
|
echo "$FORGEJO_REGISTRY_TOKEN" | podman login \
|
|
--username "${FORGEJO_REGISTRY_USER:-veilor-org}" \
|
|
--password-stdin git.s8n.ru
|
|
fi
|
|
|
|
- name: Build installer ISO with bootc-image-builder
|
|
run: |
|
|
set -euxo pipefail
|
|
# livemedia-creator does NOT support ostreecontainer (only
|
|
# ostreesetup / url / nfs install methods). bootc-image-builder
|
|
# is the canonical tool for ostreecontainer-based installer
|
|
# ISOs; consumes our OCI image directly.
|
|
OUT="/tmp/bib-out-$$"
|
|
rm -rf "$OUT"
|
|
mkdir -p "$OUT"
|
|
# Pull the veilor-os OCI we built; bootc-image-builder needs
|
|
# it locally to compose the installer ISO.
|
|
podman pull ghcr.io/veilor-org/veilor-os:43 || \
|
|
podman pull git.s8n.ru/veilor-org/veilor-os:43
|
|
# Generate config.toml for bootc-image-builder.
|
|
#
|
|
# We use [customizations.installer.kickstart] (NOT
|
|
# [customizations.user]) because we need our own %post --nochroot
|
|
# block to persist install logs back to the boot USB. Per upstream
|
|
# docs, [customizations.user] and [customizations.installer.kickstart]
|
|
# are mutually exclusive (see osbuild/bootc-image-builder#528) — so
|
|
# the admin user is now created by a kickstart `user` directive
|
|
# below, locked + chage 0 so first SDDM login forces a real pw.
|
|
#
|
|
# bootc-image-builder auto-appends `ostreecontainer ...` to the
|
|
# contents we provide; we MUST NOT include that line ourselves
|
|
# (we strip it from the source kickstart with sed).
|
|
#
|
|
# NOTE on kernel cmdline default: ideally we'd set
|
|
# `veilor.install_logs=on` as an installer-kernel default, but
|
|
# `[customizations.kernel].append` targets the INSTALLED system's
|
|
# kargs.d, not the live ISO's grub.cfg (osbuild/bootc-image-builder
|
|
# #899 still open). The persist-install-logs.sh helper defaults to
|
|
# ON when the toggle is absent, so the desired default is achieved
|
|
# without needing installer-cmdline injection. Operators flip to
|
|
# off at boot via GRUB edit: append `veilor.install_logs=off`.
|
|
KS_SRC="kickstart/install-ostreecontainer-installer.ks"
|
|
KS_FILTERED="$(grep -v '^ostreecontainer' "$KS_SRC")"
|
|
# Insert a locked admin user directive under the rootpw block —
|
|
# Anaconda's interactive Users spoke is unavailable in unattended
|
|
# bib mode, so we pre-create admin and let chage -d 0 force a pw
|
|
# change at first login.
|
|
USER_LINE='user --name=admin --groups=wheel --plaintext --password="" --lock'
|
|
KS_FILTERED="$(printf '%s\n' "$KS_FILTERED" | awk -v ul="$USER_LINE" '/^rootpw --lock$/ { print; print ul; next } { print }')"
|
|
{
|
|
echo '[customizations.installer.kickstart]'
|
|
echo 'contents = """'
|
|
printf '%s\n' "$KS_FILTERED"
|
|
echo '"""'
|
|
} > /tmp/bib-config.toml
|
|
podman run --rm \
|
|
--privileged \
|
|
--pull=newer \
|
|
--security-opt label=type:unconfined_t \
|
|
-v "$OUT:/output" \
|
|
-v /tmp/bib-config.toml:/config.toml:ro \
|
|
-v /var/lib/containers/storage:/var/lib/containers/storage \
|
|
quay.io/centos-bootc/bootc-image-builder:latest \
|
|
--type anaconda-iso \
|
|
--config /config.toml \
|
|
--rootfs btrfs \
|
|
ghcr.io/veilor-org/veilor-os:43
|
|
mkdir -p build/out
|
|
find "$OUT" -name '*.iso' -exec cp {} build/out/ \;
|
|
ls -lh build/out/
|
|
|
|
- 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
|