Files
guix-tribes/scripts/build-kexec-image
self 7dec823794 chore: sync supertest dev channel to master
Source: guix-tribes master 2ea4cae872
Base: previous supertest-dev 4fee530b68
Mode: tree sync, preserving dev channel authorization
2026-06-08 08:02:39 +02:00

278 lines
7.1 KiB
Bash
Executable File

#!/bin/sh
set -eu
usage() {
cat <<'EOF'
Usage: scripts/build-kexec-image [options]
Build a Guix kexec installer tarball from this guix-tribes checkout.
Options:
--channels=PATH Guix channels file for time-machine
--output=PATH Output tarball path, or - for stdout
--gzip-level=N gzip level for final tarball (default: 4)
-h, --help Show this help
EOF
}
script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
root_dir=$(CDPATH= cd -- "$script_dir/.." && pwd)
channels=
output=
gzip_level="${NBDE_KEXEC_GZIP_LEVEL:-4}"
for arg in "$@"; do
case "$arg" in
--channels=*)
channels=${arg#*=}
;;
--output=*)
output=${arg#*=}
;;
--gzip-level=*)
gzip_level=${arg#*=}
;;
-h|--help)
usage
exit 0
;;
*)
echo "unknown argument: $arg" >&2
usage >&2
exit 2
;;
esac
done
[ -n "$channels" ] || {
echo "missing required --channels=PATH" >&2
usage >&2
exit 2
}
[ -n "$output" ] || {
echo "missing required --output=PATH" >&2
usage >&2
exit 2
}
load_guix_env() {
for profile in \
"$HOME/.config/guix/current/etc/profile" \
"$HOME/.guix-profile/etc/profile" \
/run/current-system/profile/etc/profile
do
[ -r "$profile" ] || continue
# shellcheck disable=SC1090
. "$profile"
done
}
require_tool() {
command -v "$1" >/dev/null 2>&1 || {
echo "$1 command not found" >&2
exit 1
}
}
require_file() {
path=$1
label=$2
[ -e "$path" ] || {
echo "$label not found: $path" >&2
exit 1
}
}
require_store_dir() {
path=$1
label=$2
case "$path" in
/gnu/store/*) ;;
*)
echo "$label did not produce a /gnu/store path: ${path:-<empty>}" >&2
exit 1
;;
esac
[ -d "$path" ] || {
echo "$label store path does not exist: $path" >&2
exit 1
}
}
log() {
echo "$*" >&2
}
load_guix_env
require_tool guix
require_tool guile
require_tool mksquashfs
require_file "$channels" "channels file"
require_file "$script_dir/kexec-run" "kexec runner"
require_file "$root_dir/examples/build-host-kexec-installer.scm" "system file"
tmp=$(mktemp -d /tmp/guix-kexec-image.XXXXXX)
system_build_stdout="$tmp/system-build.stdout"
static_kexec_stdout="$tmp/static-kexec.stdout"
closure_paths="$tmp/closure-paths"
cleanup() {
chmod -R u+w "$tmp" >/dev/null 2>&1 || true
rm -rf "$tmp"
}
trap cleanup EXIT INT TERM
log "building kexec installer system"
if ! guix time-machine -C "$channels" -- system build -L "$root_dir" \
"$root_dir/examples/build-host-kexec-installer.scm" >"$system_build_stdout"; then
[ ! -s "$system_build_stdout" ] || cat "$system_build_stdout" >&2
echo "failed to build kexec installer system" >&2
exit 1
fi
system=$(tail -n 1 "$system_build_stdout")
require_store_dir "$system" "kexec installer system"
require_file "$system/parameters" "kexec installer system parameters"
require_file "$system/boot" "kexec installer system boot directory"
log "building static kexec-tools"
if ! guix time-machine -C "$channels" -- build -e '(begin
(use-modules (gnu packages linux)
(guix build-system gnu))
(static-package kexec-tools))' >"$static_kexec_stdout"; then
[ ! -s "$static_kexec_stdout" ] || cat "$static_kexec_stdout" >&2
echo "failed to build static kexec-tools" >&2
exit 1
fi
static_kexec=$(tail -n 1 "$static_kexec_stdout")
require_store_dir "$static_kexec" "static kexec-tools"
require_file "$static_kexec/sbin/kexec" "static kexec binary"
guile -s /dev/stdin -- "$system" >"$tmp/metadata" <<'GUILE'
(use-modules (ice-9 match)
(srfi srfi-1)
(srfi srfi-13))
(define (field name fields)
(match (assoc name fields)
((_ value) value)
(_ (error "missing boot parameter field" name))))
(let* ((system (last (command-line)))
(sexp (call-with-input-file
(string-append system "/parameters")
read)))
(match sexp
(('boot-parameters fields ...)
(let* ((kernel (field 'kernel fields))
(initrd (field 'initrd fields))
(root (field 'root-device fields))
(args (field 'kernel-arguments fields))
(boot-link (string-append system "/boot"))
(boot-path (canonicalize-path boot-link))
(boot-args
(append
(cond
((equal? root "tmpfs")
'("rootfstype=tmpfs"))
((and (string? root) (not (string=? root "none")))
(list (string-append "root=" root)))
(else
'()))
(list (string-append "gnu.system=" system)
(string-append "gnu.load=" boot-path))
args)))
(display kernel)
(newline)
(display initrd)
(newline)
(display boot-path)
(newline)
(display (string-join boot-args " "))
(newline)))
(_
(error "unrecognized boot parameters file" sexp))))
GUILE
kernel=$(sed -n '1p' "$tmp/metadata")
initrd=$(sed -n '2p' "$tmp/metadata")
boot_path=$(sed -n '3p' "$tmp/metadata")
cmdline=$(sed -n '4p' "$tmp/metadata")
boot_refs=$(guix gc --references "$boot_path")
store_item_root() {
case "$1" in
/gnu/store/*)
printf '%s\n' "$1" | sed 's#^\(/gnu/store/[^/]*\).*#\1#'
;;
*)
echo "not a store item: $1" >&2
exit 1
;;
esac
}
initrd_store=$(store_item_root "$initrd")
parameters_store=$(store_item_root "$(readlink -f "$system/parameters")")
system_refs="$tmp/system-refs"
guix gc --references "$system" \
| while IFS= read -r ref; do
case "$ref" in
"$initrd_store"|"$parameters_store")
log "excluding already-copied boot artifact from embedded store: $ref"
;;
*)
printf '%s\n' "$ref"
;;
esac
done >"$system_refs"
mkdir -p "$tmp/kexec"
cp "$kernel" "$tmp/kexec/bzImage"
cp "$initrd" "$tmp/kexec/initrd"
chmod 644 "$tmp/kexec/initrd"
cp "$static_kexec/sbin/kexec" "$tmp/kexec/kexec-static"
chmod 755 "$tmp/kexec/kexec-static"
cp "$script_dir/kexec-run" "$tmp/kexec/run"
chmod 755 "$tmp/kexec/run"
printf '%s\n' "$cmdline" >"$tmp/kexec/cmdline"
{
printf '%s\n' "$system" "$boot_path"
printf '%s\n' $boot_refs
cat "$system_refs"
guix gc --requisites "$boot_path" $boot_refs $(cat "$system_refs")
} | sort -u >"$closure_paths"
squashfs_root="$tmp/squashfs-root"
mkdir -p "$squashfs_root"
while IFS= read -r path; do
[ -n "$path" ] || continue
cp -a "$path" "$squashfs_root/$(basename "$path")"
done <"$closure_paths"
log "packing Guix store closure into initrd"
mksquashfs "$squashfs_root" "$tmp/kexec/gnu-store.squashfs" \
-comp gzip -Xcompression-level 9 -no-xattrs -noappend >&2
squashfs_cpio="$tmp/squashfs-cpio"
mkdir -p "$squashfs_cpio"
cp "$tmp/kexec/gnu-store.squashfs" "$squashfs_cpio/gnu-store.squashfs"
( cd "$squashfs_cpio" && printf 'gnu-store.squashfs' | cpio -o -H newc | gzip -9 ) \
>> "$tmp/kexec/initrd"
rm "$tmp/kexec/gnu-store.squashfs"
log "writing kexec installer tarball"
case "$output" in
-)
GZIP="-$gzip_level" tar -C "$tmp" -czf - kexec
;;
*)
mkdir -p "$(dirname "$output")"
GZIP="-$gzip_level" tar -C "$tmp" -czf "$output" kexec
;;
esac