Manifest groups get a tier; only `default` is generated (make all TIER=..., --tier). Default: phyllomeos (GNOME + virt-manager) and phyllomeos-headless, Fedora 44, systemd-boot, CPU-agnostic hypervisor ingredient (hypervisor_type: any). guest tier: guest-server, guest-desktop. experimental: biosboot/grub, encrypted, rawhide. Generator clears stale recipes/dishes before writing. build-image.sh: --dish and --tier; deploy.sh: --tier. build-iso.yaml replaced by build-image.yaml: raw images for both editions on the fedora:host runner (TMPDIR=/var/tmp, max-parallel 1), attached with the flattened kickstarts to tagged releases. Live edition no longer built. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
208 lines
7.4 KiB
Bash
Executable File
208 lines
7.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# Build a raw (or qcow2) disk image from a dish, without libvirt/virt-install.
|
|
#
|
|
# Runs `livemedia-creator --no-virt --make-disk` directly on the host, so
|
|
# the result is a disk image you can hand to another VM manager directly
|
|
# (e.g. `virtpull local ./build/<dish>.img --wonder-vm NAME`) instead of a
|
|
# bootable ISO.
|
|
#
|
|
# Usage: ./build-image.sh [--dish NAME] [--tier TIER] [--as qcow2]
|
|
# [--root-size MiB] [--extra-ks FILE]
|
|
#
|
|
# --dish skips the interactive picker (used by CI); --tier selects which
|
|
# manifest tier of dishes to generate (default: default; see
|
|
# cook/recipes_manifest.yaml).
|
|
#
|
|
# Requires: lorax-lmc-novirt (livemedia-creator), qemu-img, and root (the
|
|
# --no-virt disk install needs real loop-device access via
|
|
# /dev/loop-control; individual commands below run under sudo — passwordless
|
|
# sudo, or run this whole script as root).
|
|
#
|
|
# Earlier iteration wrapped this in `mock` (mirroring
|
|
# .gitea/workflows/build-iso.yaml's ISO build) to pin the target release's
|
|
# toolchain. That does not work for --make-disk: mock's chroot has no live
|
|
# systemd-udevd, so udev never populates properties for the loop device
|
|
# livemedia-creator creates for the image, and blivet's device scan
|
|
# crashes (blivet/udev.py device_get_name: "TypeError: argument of type
|
|
# 'NoneType' is not a container or iterable") — reproduced identically on
|
|
# Fedora 44 and 43 mock chroots, and confirmed absent when run directly on
|
|
# the host instead. --make-iso never hits this: ISO building doesn't touch
|
|
# block devices at all, only --make-disk (and other disk/appliance modes)
|
|
# does. The consequence: this script now builds using whatever
|
|
# anaconda/lorax the host has installed — the kickstart's own repo/url
|
|
# lines still pin the actual package set to the target Fedora release
|
|
# regardless, same as any other direct (non-mock) livemedia-creator use.
|
|
|
|
set -euo pipefail
|
|
|
|
AS_FORMAT=""
|
|
DISH=""
|
|
TIER="${TIER:-default}"
|
|
ROOT_SIZE=8192
|
|
EXTRA_KS=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dish)
|
|
DISH="$2"
|
|
shift 2
|
|
;;
|
|
--tier)
|
|
TIER="$2"
|
|
shift 2
|
|
;;
|
|
--as)
|
|
AS_FORMAT="$2"
|
|
shift 2
|
|
;;
|
|
--root-size)
|
|
ROOT_SIZE="$2"
|
|
shift 2
|
|
;;
|
|
--extra-ks)
|
|
EXTRA_KS="$2"
|
|
shift 2
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1"
|
|
echo "Usage: $0 [--dish NAME] [--tier TIER] [--as qcow2] [--root-size MiB] [--extra-ks FILE]"
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
if [[ -n "$AS_FORMAT" && "$AS_FORMAT" != "qcow2" ]]; then
|
|
echo "--as only supports 'qcow2' (the build itself always produces raw)"
|
|
exit 1
|
|
fi
|
|
if ! [[ "$ROOT_SIZE" =~ ^[0-9]+$ ]]; then
|
|
echo "--root-size must be a whole number of MiB, got '$ROOT_SIZE'"
|
|
exit 1
|
|
fi
|
|
if [[ -n "$EXTRA_KS" && ! -f "$EXTRA_KS" ]]; then
|
|
echo "--extra-ks file not found: $EXTRA_KS"
|
|
exit 1
|
|
fi
|
|
|
|
for tool in livemedia-creator qemu-img; do
|
|
if ! command -v "$tool" &> /dev/null; then
|
|
echo "Missing required tool: $tool"
|
|
echo "Fedora: sudo dnf install lorax-lmc-novirt qemu-img"
|
|
exit 1
|
|
fi
|
|
done
|
|
if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then
|
|
echo "This needs root (loop-device access for the --no-virt disk install)."
|
|
echo "Re-run as root, or ensure passwordless sudo is available."
|
|
exit 1
|
|
fi
|
|
|
|
# Regenerate recipes and dishes before building (they are build products)
|
|
if ! (cd cook && make all TIER="$TIER"); then
|
|
echo "Failed to generate dishes in cook/ (see 'make all' in cook/)"
|
|
exit 1
|
|
fi
|
|
|
|
# Pick a dish: --dish, or interactively (shared with deploy/deploy-distro.sh)
|
|
if [[ -n "$DISH" ]]; then
|
|
dish="${DISH%.cfg}"
|
|
if [[ ! -f "cook/dishes/${dish}.cfg" ]]; then
|
|
echo "Dish not found: cook/dishes/${dish}.cfg (is --tier '$TIER' right?)"
|
|
exit 1
|
|
fi
|
|
else
|
|
# shellcheck source=./deploy/select-dish.sh
|
|
source "$(dirname "$0")/deploy/select-dish.sh"
|
|
dish="$selected_dish"
|
|
fi
|
|
|
|
# Derive the target release, same convention as
|
|
# deploy/deploy-distro.sh's find_fedora_iso() (used for --releasever, which
|
|
# substitutes @VERSION@ in bootloader/template text — the kickstart's own
|
|
# repo/url lines are what actually pin the package set).
|
|
if [[ "$dish" == *"rawhide"* ]]; then
|
|
releasever="rawhide"
|
|
elif [[ "$dish" =~ _([0-9]+) ]]; then
|
|
releasever="${BASH_REMATCH[1]}"
|
|
else
|
|
echo "Could not determine a Fedora release version from dish name '$dish'."
|
|
echo "Expected a '_<NN>' segment (e.g. desktop_44_standard...) or 'rawhide'."
|
|
exit 1
|
|
fi
|
|
|
|
out_dir="./build"
|
|
mkdir -p "$out_dir"
|
|
out_dir="$(cd "$out_dir" && pwd)"
|
|
|
|
# Adjustments needed only for this --no-virt disk-image path, applied to a
|
|
# scratch copy rather than the shared dish:
|
|
#
|
|
# 1. cook/ingredients/core/base.ks sets `text` for deploy/deploy-distro.sh's
|
|
# virt-install netinstall console; livemedia-creator manages the display
|
|
# mode itself and refuses a kickstart that also sets one ("The kickstart
|
|
# must not set a display mode").
|
|
#
|
|
# 2. cook/ingredients' storage layout uses `part / --grow` with no explicit
|
|
# --size: fine for virt-install, which pre-creates the disk at a known
|
|
# size for anaconda to grow into. livemedia-creator instead computes the
|
|
# total image size up front by summing partition sizes, and crashes
|
|
# (TypeError: unsupported operand type(s) for +: 'int' and 'NoneType')
|
|
# on a --grow partition with no size to add. Give it one via --root-size
|
|
# (default 8192 MiB; the desktop dishes likely need more).
|
|
#
|
|
# 3. --extra-ks appends arbitrary local content (e.g. a bespoke `user`/
|
|
# `rootpw`/`security` override) after the above. Kickstart's singular
|
|
# commands take the last occurrence, so this can override anything the
|
|
# dish already set without touching the shared ingredients — see the
|
|
# README's "bespoke dish not part of the matrix" pattern. Never commit
|
|
# a file used here if it carries real credentials.
|
|
disk_ks="$(mktemp "${TMPDIR:-/tmp}/${dish}.disk-build.XXXXXX.cfg")"
|
|
trap 'rm -f "$disk_ks"' EXIT
|
|
grep -v -E '^(text|cmdline|graphical)([[:space:]]|$)' "cook/dishes/${dish}.cfg" \
|
|
| awk -v size="$ROOT_SIZE" '
|
|
/^part / && /--grow/ && !/--size=/ { $0 = $0 " --size=" size }
|
|
{ print }
|
|
' > "$disk_ks"
|
|
if [[ -n "$EXTRA_KS" ]]; then
|
|
{
|
|
echo
|
|
cat "$EXTRA_KS"
|
|
} >> "$disk_ks"
|
|
fi
|
|
|
|
if command -v ksvalidator &> /dev/null; then
|
|
echo "Validating kickstart (informational; a build failure is the real test)..."
|
|
ksvalidator "$disk_ks" || true
|
|
fi
|
|
|
|
resultdir="$(mktemp -u "${TMPDIR:-/tmp}/lmc-result.XXXXXX")"
|
|
|
|
echo "Building disk image for '$dish' (target release $releasever)..."
|
|
sudo livemedia-creator --ks "$disk_ks" --no-virt --resultdir "$resultdir" \
|
|
--make-disk --image-name "${dish}.img" --releasever "$releasever"
|
|
|
|
# livemedia-creator's exact output filename depends on the installed lorax
|
|
# version; don't assume --image-name is honored verbatim, take whatever
|
|
# single disk image landed in the resultdir.
|
|
produced=$(sudo find "$resultdir" -maxdepth 1 -type f \( -iname '*.img' -o -iname '*disk*' \) | head -n 1)
|
|
if [[ -z "$produced" ]]; then
|
|
echo "livemedia-creator did not produce a recognizable disk image under $resultdir"
|
|
exit 1
|
|
fi
|
|
|
|
raw_out="${out_dir}/${dish}.img"
|
|
sudo cp --reflink=auto "$produced" "$raw_out"
|
|
sudo chown "$(id -u):$(id -g)" "$raw_out"
|
|
sudo rm -rf "$resultdir"
|
|
echo "Built: $raw_out"
|
|
|
|
if [[ "$AS_FORMAT" == "qcow2" ]]; then
|
|
qcow2_out="${out_dir}/${dish}.qcow2"
|
|
qemu-img convert -O qcow2 "$raw_out" "$qcow2_out"
|
|
echo "Converted: $qcow2_out"
|
|
final="$qcow2_out"
|
|
else
|
|
final="$raw_out"
|
|
fi
|
|
|
|
echo
|
|
echo "Next: virtpull local $final --wonder-vm <NAME>"
|