You've already forked guix-tribes
7dec823794
Source: guix-tribes master2ea4cae872Base: previous supertest-dev4fee530b68Mode: tree sync, preserving dev channel authorization
278 lines
7.1 KiB
Bash
Executable File
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
|