Runs `livemedia-creator --no-virt --make-disk` directly on the host to produce a disk image any VM manager can consume (e.g. via virtpull's `virtpull local ... --wonder-vm NAME`), instead of deploy.sh's live virt-install/libvirt session. An earlier version wrapped this in `mock`, mirroring .gitea/workflows/build-iso.yaml's ISO build. That doesn't work for --make-disk: mock's chroot has no live systemd-udevd, so udev never populates properties for the loop device livemedia-creator creates, and blivet's device scan crashes. --make-iso never hits this since it never touches block devices. Reproduced identically on Fedora 44 and 43 mock chroots, confirmed fixed by running directly on the host instead (root required, for /dev/loop-control access). Also fixes two real kickstart incompatibilities with the --no-virt disk-image path (applied to a scratch copy, not the shared ingredients): the `text` display-mode directive (needed for virt-install's netinstall console) conflicts with livemedia-creator's own display handling, and `part / --grow` with no explicit --size crashes livemedia-creator's upfront disk-size calculation (works fine under virt-install, which pre-creates the disk at a known size instead) — now configurable via --root-size. New --extra-ks FILE flag layers one-off local content (e.g. a bespoke user/rootpw override) onto the scratch copy without touching tracked ingredients, following the README's existing "bespoke dish not part of the matrix" pattern. Factored deploy/deploy-distro.sh's dish-picker into deploy/select-dish.sh, shared by both scripts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
185 lines
6.7 KiB
Bash
Executable File
185 lines
6.7 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 [--as qcow2] [--root-size MiB] [--extra-ks FILE]
|
|
#
|
|
# 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=""
|
|
ROOT_SIZE=8192
|
|
EXTRA_KS=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--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 [--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); then
|
|
echo "Failed to generate dishes in cook/ (see 'make all' in cook/)"
|
|
exit 1
|
|
fi
|
|
|
|
# Pick a dish (shared with deploy/deploy-distro.sh)
|
|
# shellcheck source=./deploy/select-dish.sh
|
|
source "$(dirname "$0")/deploy/select-dish.sh"
|
|
dish="$selected_dish"
|
|
|
|
# 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>"
|