#!/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:-}" >&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