You've already forked guix-tribes
2ec1a43460
Pinned Docker E2E / pinned-docker-e2e (push) Failing after 32m40s
Do not eagerly load Hyper-V, VMware, or Xen frontend modules in the kexec installer initrd. The broadened compatibility list caused Hetzner CPX31 kexec boots to fall back to the Ubuntu disk before installer SSH became reachable; the KVM/QEMU and common emulated NIC subset boots successfully.\n\nAlso require cpio in the local kexec image build script so missing cpio fails before writing an incomplete image.
279 lines
7.1 KiB
Bash
Executable File
279 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_tool cpio
|
|
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
|