diff --git a/README.md b/README.md index 19e9a1a..ae7bb0c 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,27 @@ Override the checkout location with: - `FRUIX_CHANNEL_DIR=/path/to/fruix` +## Builder preparation + +The main bootstrap entry point is: + +- `./bootstrap/prepare-builder` + +It prepares a reusable per-user builder environment under: + +- `~/.local/opt/fruix-builder` + +including: + +- a local fixed Guile build +- local Guile extension modules +- a local Shepherd install +- a Guix source checkout used by the current Fruix code + +After that, `./bin/fruix` will automatically prefer that builder root if present. + +On the current foreign-FreeBSD host path, preparing the builder is mostly a user-space action, but materializing full Fruix system artifacts may still require `sudo` because some host inputs are root-readable only and some system-oriented build steps still assume elevated access. + ## Design docs - `docs/bootstrap.md` — boundary definition diff --git a/bin/fruix b/bin/fruix index 8274699..ee989f8 100755 --- a/bin/fruix +++ b/bin/fruix @@ -9,10 +9,37 @@ fi fruix_channel_dir=${FRUIX_CHANNEL_DIR:-$default_channel_dir} fruix_channel_url=${FRUIX_CHANNEL_URL:-https://git.teralink.net/self/fruix.git} -guix_source_dir=${GUIX_SOURCE_DIR:-"$HOME/repos/guix"} -guile_bin=${GUILE_BIN:-/tmp/guile-freebsd-validate-install/bin/guile} -guile_extra_prefix=${GUILE_EXTRA_PREFIX:-/tmp/guile-gnutls-freebsd-validate-install} -shepherd_prefix=${SHEPHERD_PREFIX:-/tmp/shepherd-freebsd-validate-install} +builder_root=${FRUIX_BUILDER_ROOT:-$HOME/.local/opt/fruix-builder} +legacy_guile_bin=/tmp/guile-freebsd-validate-install/bin/guile +legacy_guile_extra_prefix=/tmp/guile-gnutls-freebsd-validate-install +legacy_shepherd_prefix=/tmp/shepherd-freebsd-validate-install +legacy_guix_source_dir=$HOME/repos/guix + +if [ -x "$builder_root/guile/bin/guile" ]; then + default_guile_bin=$builder_root/guile/bin/guile +else + default_guile_bin=$legacy_guile_bin +fi +if [ -d "$builder_root/guile-extra" ]; then + default_guile_extra_prefix=$builder_root/guile-extra +else + default_guile_extra_prefix=$legacy_guile_extra_prefix +fi +if [ -d "$builder_root/shepherd" ]; then + default_shepherd_prefix=$builder_root/shepherd +else + default_shepherd_prefix=$legacy_shepherd_prefix +fi +if [ -d "$builder_root/src/guix/guix" ]; then + default_guix_source_dir=$builder_root/src/guix +else + default_guix_source_dir=$legacy_guix_source_dir +fi + +guix_source_dir=${GUIX_SOURCE_DIR:-$default_guix_source_dir} +guile_bin=${GUILE_BIN:-$default_guile_bin} +guile_extra_prefix=${GUILE_EXTRA_PREFIX:-$default_guile_extra_prefix} +shepherd_prefix=${SHEPHERD_PREFIX:-$default_shepherd_prefix} script=$fruix_channel_dir/scripts/fruix.scm modules_dir=$fruix_channel_dir/modules @@ -30,27 +57,29 @@ fi if [ ! -x "$guile_bin" ]; then echo "Guile binary is not executable: $guile_bin" >&2 + echo "Run $bootstrap_root/bootstrap/prepare-builder.sh first, or set GUILE_BIN explicitly." >&2 exit 1 fi +guile_prefix=$(CDPATH= cd -- "$(dirname "$guile_bin")/.." && pwd) + ensure_built() { if [ ! -d "$guile_extra_prefix/share/guile/site" ] || \ ! GUILE_LOAD_PATH="$guile_extra_prefix/share/guile/site/3.0${GUILE_LOAD_PATH:+:$GUILE_LOAD_PATH}" \ GUILE_LOAD_COMPILED_PATH="$guile_extra_prefix/lib/guile/3.0/site-ccache${GUILE_LOAD_COMPILED_PATH:+:$GUILE_LOAD_COMPILED_PATH}" \ GUILE_EXTENSIONS_PATH="$guile_extra_prefix/lib/guile/3.0/extensions${GUILE_EXTENSIONS_PATH:+:$GUILE_EXTENSIONS_PATH}" \ - LD_LIBRARY_PATH="$guile_extra_prefix/lib:/tmp/guile-freebsd-validate-install/lib:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \ + LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_prefix/lib:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \ "$guile_bin" -c '(catch #t (lambda () (use-modules (fibers)) (display "ok") (newline)) (lambda _ (display "missing") (newline)))' | grep -qx ok; then - METADATA_OUT= ENV_OUT= "$bootstrap_root/tests/shepherd/build-local-guile-fibers.sh" + METADATA_OUT= ENV_OUT= GUILE_BIN="$guile_bin" INSTALL_PREFIX="$guile_extra_prefix" "$bootstrap_root/tests/shepherd/build-local-guile-fibers.sh" fi if [ ! -x "$shepherd_prefix/bin/shepherd" ] || [ ! -x "$shepherd_prefix/bin/herd" ]; then - METADATA_OUT= ENV_OUT= GUILE_EXTRA_PREFIX="$guile_extra_prefix" "$bootstrap_root/tests/shepherd/build-local-shepherd.sh" + METADATA_OUT= ENV_OUT= GUILE_BIN="$guile_bin" GUILE_EXTRA_PREFIX="$guile_extra_prefix" INSTALL_PREFIX="$shepherd_prefix" GUIX_SOURCE_DIR="$guix_source_dir" "$bootstrap_root/tests/shepherd/build-local-shepherd.sh" fi } ensure_built -guile_prefix=$(CDPATH= cd -- "$(dirname "$guile_bin")/.." && pwd) guile_lib_dir=$guile_prefix/lib if [ -n "${GUILE_LOAD_PATH:-}" ]; then diff --git a/bootstrap/build-local-guile.sh b/bootstrap/build-local-guile.sh new file mode 100755 index 0000000..2efa668 --- /dev/null +++ b/bootstrap/build-local-guile.sh @@ -0,0 +1,148 @@ +#!/bin/sh +set -eu + +script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd) +bootstrap_root=$(CDPATH= cd -- "$script_dir/.." && pwd) + +guile_repo=${FRUIX_BOOTSTRAP_GUILE_REPO:-https://codeberg.org/guile/guile.git} +guile_commit=${FRUIX_BOOTSTRAP_GUILE_COMMIT:-bbf2baa10f6cc8dfdd9e4ea14b503d748287a03d} +install_prefix=${INSTALL_PREFIX:-$HOME/.local/opt/fruix-builder/guile} +make_bin=${MAKE_BIN:-gmake} +metadata_target=${METADATA_OUT:-} +env_target=${ENV_OUT:-} +jobs=${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || echo 4)} + +for tool in git gm4 autoconf automake libtoolize autoreconf pkg-config "$make_bin" cc; do + if ! command -v "$tool" >/dev/null 2>&1; then + echo "Required tool not found: $tool" >&2 + exit 1 + fi +done + +cleanup=0 +if [ -n "${WORKDIR:-}" ]; then + workdir=$WORKDIR + mkdir -p "$workdir" +else + workdir=$(mktemp -d /tmp/fruix-build-local-guile.XXXXXX) + cleanup=1 +fi +if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then + cleanup=0 +fi + +cleanup_workdir() { + if [ "$cleanup" -eq 1 ]; then + rm -rf "$workdir" + fi +} +trap cleanup_workdir EXIT INT TERM + +src_dir=$workdir/guile-src +build_dir=$workdir/guile-build +metadata_file=$workdir/local-guile-build-metadata.txt +env_file=$workdir/local-guile-env.sh +autogen_log=$workdir/autogen.log +configure_log=$workdir/configure.log +build_log=$workdir/build.log +install_log=$workdir/install.log +validation_log=$workdir/validation.log + +rm -rf "$src_dir" "$build_dir" +mkdir -p "$build_dir" "$install_prefix" + +printf 'Cloning Guile from: %s\n' "$guile_repo" +printf 'Checking out commit: %s\n' "$guile_commit" +printf 'Install prefix: %s\n' "$install_prefix" +printf 'Work directory: %s\n' "$workdir" + +git clone "$guile_repo" "$src_dir" >"$workdir/clone.log" 2>&1 +( + cd "$src_dir" + git checkout "$guile_commit" +) >"$workdir/checkout.log" 2>&1 +resolved_commit=$(git -C "$src_dir" rev-parse HEAD) + +( + cd "$src_dir" + M4=gm4 ./autogen.sh +) >"$autogen_log" 2>&1 + +( + cd "$build_dir" + env \ + M4=gm4 \ + MAKE="$make_bin" \ + PKG_CONFIG=pkg-config \ + PKG_CONFIG_PATH=/usr/local/libdata/pkgconfig:/usr/local/lib/pkgconfig \ + CPPFLAGS='-I/usr/local/include' \ + LDFLAGS="-L/usr/local/lib -Wl,-rpath,/usr/local/lib -Wl,-rpath,$install_prefix/lib" \ + "$src_dir/configure" \ + --prefix="$install_prefix" \ + --with-bdw-gc=bdw-gc-threaded \ + --with-libgmp-prefix=/usr/local \ + --with-libunistring-prefix=/usr/local \ + --with-libiconv-prefix=/usr/local \ + --with-libintl-prefix=/usr/local +) >"$configure_log" 2>&1 + +( + cd "$build_dir" + "$make_bin" -j"$jobs" +) >"$build_log" 2>&1 + +( + cd "$build_dir" + "$make_bin" install +) >"$install_log" 2>&1 + +guile_bin=$install_prefix/bin/guile +[ -x "$guile_bin" ] || { + echo "Built Guile binary missing: $guile_bin" >&2 + exit 1 +} + +guile_version=$(LD_LIBRARY_PATH="$install_prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$guile_bin" --version | head -n 1) +( + EXPECT_GUILE_SUBPROCESS_CRASH=0 \ + GUILE_BIN="$guile_bin" \ + "$bootstrap_root/tests/guile/run-subprocess-diagnostics.sh" +) >"$validation_log" 2>&1 + +cat >"$env_file" <"$metadata_file" </dev/null 2>&1 || { + echo "sudo is required to install host packages as a non-root user" >&2 + exit 1 + } + install_cmd="$sudo_cmd $pkg_cmd install -y" +fi + +printf 'Installing Fruix bootstrap host packages:\n' +for package in $packages; do + printf ' %s\n' "$package" +done + +# shellcheck disable=SC2086 +$install_cmd $packages + +printf 'PASS install-host-deps\n' diff --git a/bootstrap/prepare-builder b/bootstrap/prepare-builder new file mode 100755 index 0000000..08e99a3 --- /dev/null +++ b/bootstrap/prepare-builder @@ -0,0 +1,5 @@ +#!/bin/sh +set -eu + +script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd) +exec "$script_dir/prepare-builder.sh" "$@" diff --git a/bootstrap/prepare-builder.sh b/bootstrap/prepare-builder.sh new file mode 100755 index 0000000..f634d58 --- /dev/null +++ b/bootstrap/prepare-builder.sh @@ -0,0 +1,255 @@ +#!/bin/sh +set -eu + +script_dir=$(CDPATH= cd -- "$(dirname "$0")" && pwd) +bootstrap_root=$(CDPATH= cd -- "$script_dir/.." && pwd) + +builder_root_default=$HOME/.local/opt/fruix-builder +builder_root=${FRUIX_BUILDER_ROOT:-$builder_root_default} +guile_prefix=${FRUIX_BUILDER_GUILE_PREFIX:-$builder_root/guile} +guile_extra_prefix=${FRUIX_BUILDER_GUILE_EXTRA_PREFIX:-$builder_root/guile-extra} +shepherd_prefix=${FRUIX_BUILDER_SHEPHERD_PREFIX:-$builder_root/shepherd} +guix_source_dir=${GUIX_SOURCE_DIR:-$builder_root/src/guix} +guix_source_url=${GUIX_SOURCE_URL:-https://git.teralink.net/tribes/guix.git} +guile_repo=${FRUIX_BOOTSTRAP_GUILE_REPO:-https://codeberg.org/guile/guile.git} +guile_commit=${FRUIX_BOOTSTRAP_GUILE_COMMIT:-bbf2baa10f6cc8dfdd9e4ea14b503d748287a03d} +fruix_channel_url=${FRUIX_CHANNEL_URL:-https://git.teralink.net/self/fruix.git} +install_host_deps=1 +jobs=${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || echo 4)} + +usage() { + cat <&2 + usage >&2 + exit 1 + ;; + esac +done + +guile_prefix=$builder_root/guile +guile_extra_prefix=$builder_root/guile-extra +shepherd_prefix=$builder_root/shepherd + +state_dir=$builder_root/state +logs_dir=$builder_root/logs +mkdir -p "$builder_root" "$state_dir" "$logs_dir" +metadata_file=$builder_root/prepare-builder-metadata.txt +env_file=$builder_root/env.sh + +have_working_guile() { + [ -x "$guile_prefix/bin/guile" ] || return 1 + EXPECT_GUILE_SUBPROCESS_CRASH=0 \ + GUILE_BIN="$guile_prefix/bin/guile" \ + "$bootstrap_root/tests/guile/run-subprocess-diagnostics.sh" >/dev/null 2>&1 +} + +guile_module_probe() { + module_code=$1 + [ -x "$guile_prefix/bin/guile" ] || return 1 + guile_bin=$guile_prefix/bin/guile + guile_version=$(LD_LIBRARY_PATH="$guile_prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$guile_bin" -c '(display (effective-version))') + site_dir=$guile_extra_prefix/share/guile/site/$guile_version + site_ccache_dir=$guile_extra_prefix/lib/guile/$guile_version/site-ccache + extensions_dir=$guile_extra_prefix/lib/guile/$guile_version/extensions + gui_load_path=${site_dir} + gui_load_compiled_path=${site_ccache_dir} + gui_extensions_path=${extensions_dir} + env \ + LD_LIBRARY_PATH="$guile_extra_prefix/lib:$guile_prefix/lib:/usr/local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \ + GUILE_LOAD_PATH="$gui_load_path" \ + GUILE_LOAD_COMPILED_PATH="$gui_load_compiled_path" \ + GUILE_EXTENSIONS_PATH="$gui_extensions_path" \ + "$guile_bin" -c "$module_code" >/dev/null 2>&1 +} + +have_shepherd() { + [ -x "$shepherd_prefix/bin/shepherd" ] || return 1 + [ -x "$shepherd_prefix/bin/herd" ] || return 1 + guile_module_probe '(use-modules (fibers) (shepherd service)) (display "ok") (newline)' +} + +run_step() { + name=$1 + shift + printf '\n== %s ==\n' "$name" + "$@" +} + +if [ "$install_host_deps" -eq 1 ]; then + run_step install-host-deps "$script_dir/install-host-deps.sh" +fi + +if have_working_guile; then + printf '\nUsing existing working Guile at %s\n' "$guile_prefix/bin/guile" +else + run_step build-local-guile env \ + FRUIX_BOOTSTRAP_GUILE_REPO="$guile_repo" \ + FRUIX_BOOTSTRAP_GUILE_COMMIT="$guile_commit" \ + INSTALL_PREFIX="$guile_prefix" \ + JOBS="$jobs" \ + METADATA_OUT="$logs_dir/local-guile-build-metadata.txt" \ + ENV_OUT="$logs_dir/local-guile-env.sh" \ + "$script_dir/build-local-guile.sh" +fi + +run_step setup-guix-source env \ + GUIX_SOURCE_URL="$guix_source_url" \ + GUIX_SOURCE_DIR="$guix_source_dir" \ + METADATA_OUT="$logs_dir/guix-source-metadata.txt" \ + "$script_dir/setup-guix-source.sh" + +if guile_module_probe '(use-modules (gnutls)) (display "ok") (newline)'; then + printf '\nGuile (gnutls) already present in %s\n' "$guile_extra_prefix" +else + run_step build-local-guile-gnutls env \ + GUILE_BIN="$guile_prefix/bin/guile" \ + INSTALL_PREFIX="$guile_extra_prefix" \ + GUIX_SOURCE_DIR="$guix_source_dir" \ + JOBS="$jobs" \ + "$bootstrap_root/tests/guix/build-local-guile-gnutls.sh" +fi + +if guile_module_probe '(use-modules (bytestructures guile) (git)) (display "ok") (newline)'; then + printf '\nGuile-Git stack already present in %s\n' "$guile_extra_prefix" +else + run_step build-local-guile-git env \ + GUILE_BIN="$guile_prefix/bin/guile" \ + INSTALL_PREFIX="$guile_extra_prefix" \ + JOBS="$jobs" \ + "$bootstrap_root/tests/guix/build-local-guile-git.sh" +fi + +if guile_module_probe '(use-modules (json)) (display "ok") (newline)'; then + printf '\nGuile-JSON already present in %s\n' "$guile_extra_prefix" +else + run_step build-local-guile-json env \ + GUILE_BIN="$guile_prefix/bin/guile" \ + INSTALL_PREFIX="$guile_extra_prefix" \ + GUIX_SOURCE_DIR="$guix_source_dir" \ + JOBS="$jobs" \ + "$bootstrap_root/tests/guix/build-local-guile-json.sh" +fi + +if guile_module_probe '(use-modules (sqlite3) (gcrypt hash) (lzlib) (semver)) (display "ok") (newline)'; then + printf '\nGuile configure-time dependency modules already present in %s\n' "$guile_extra_prefix" +else + run_step build-local-guile-configure-deps env \ + GUILE_BIN="$guile_prefix/bin/guile" \ + INSTALL_PREFIX="$guile_extra_prefix" \ + GUIX_SOURCE_DIR="$guix_source_dir" \ + JOBS="$jobs" \ + "$bootstrap_root/tests/guix/build-local-guile-configure-deps.sh" +fi + +if guile_module_probe '(use-modules (fibers)) (display "ok") (newline)'; then + printf '\nGuile Fibers already present in %s\n' "$guile_extra_prefix" +else + run_step build-local-guile-fibers env \ + GUILE_BIN="$guile_prefix/bin/guile" \ + INSTALL_PREFIX="$guile_extra_prefix" \ + JOBS="$jobs" \ + "$bootstrap_root/tests/shepherd/build-local-guile-fibers.sh" +fi + +if have_shepherd; then + printf '\nShepherd already present in %s\n' "$shepherd_prefix" +else + run_step build-local-shepherd env \ + GUILE_BIN="$guile_prefix/bin/guile" \ + GUILE_EXTRA_PREFIX="$guile_extra_prefix" \ + INSTALL_PREFIX="$shepherd_prefix" \ + GUIX_SOURCE_DIR="$guix_source_dir" \ + JOBS="$jobs" \ + "$bootstrap_root/tests/shepherd/build-local-shepherd.sh" +fi + +guile_bin=$guile_prefix/bin/guile +guile_version=$(LD_LIBRARY_PATH="$guile_prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$guile_bin" -c '(display (effective-version))') +site_dir=$guile_extra_prefix/share/guile/site/$guile_version +site_ccache_dir=$guile_extra_prefix/lib/guile/$guile_version/site-ccache +extensions_dir=$guile_extra_prefix/lib/guile/$guile_version/extensions +guix_commit=$(git -C "$guix_source_dir" rev-parse HEAD) + +cat >"$env_file" <"$metadata_file" </dev/null 2>&1 || { + echo "git is required" >&2 + exit 1 +} + +mkdir -p "$(dirname "$guix_source_dir")" + +if [ -d "$guix_source_dir/guix" ]; then + printf 'Using existing Guix source tree: %s\n' "$guix_source_dir" +else + rm -rf "$guix_source_dir" + printf 'Cloning Guix source from: %s\n' "$guix_source_url" + git clone "$guix_source_url" "$guix_source_dir" >"$guix_source_dir.clone.log" 2>&1 +fi + +resolved_commit=$(git -C "$guix_source_dir" rev-parse HEAD) +metadata_file=$guix_source_dir/.fruix-bootstrap-guix-source.txt +cat >"$metadata_file" <&2 + exit 1 +} +command -v sudo >/dev/null 2>&1 || { + echo "sudo is required" >&2 + exit 1 +} + +cleanup=0 +if [ -n "${WORKDIR:-}" ]; then + workdir=$WORKDIR + sudo mkdir -p "$workdir" +else + workdir=$(mktemp -d /tmp/fruix-bootstrap-fresh-user.XXXXXX) + cleanup=1 +fi +if [ "${KEEP_WORKDIR:-0}" -eq 1 ]; then + cleanup=0 +fi + +sudoers_dir=/usr/local/etc/sudoers.d +sudoers_file=$sudoers_dir/fruix-bootstrap-$test_user +prepare_log=$workdir/prepare-builder.log +help_log=$workdir/fruix-help.txt +installer_log=$workdir/installer-iso.txt +metadata_file=$workdir/fresh-user-metadata.txt + +cleanup_all() { + if [ "$keep_user" -ne 1 ]; then + sudo rm -f "$sudoers_file" >/dev/null 2>&1 || true + if id "$test_user" >/dev/null 2>&1; then + sudo pkill -u "$test_user" >/dev/null 2>&1 || true + sudo pw userdel "$test_user" -r >/dev/null 2>&1 || true + fi + fi + if [ "$cleanup" -eq 1 ]; then + sudo rm -rf "$workdir" + fi +} +trap cleanup_all EXIT INT TERM + +run_as_test_user() { + sudo -H -u "$test_user" env HOME="$test_home" "$@" +} + +copy_tree() { + src=$1 + dst=$2 + sudo rm -rf "$dst" + sudo mkdir -p "$dst" + (cd "$src" && tar -cf - .) | sudo tar -C "$dst" -xf - + sudo chown -R "$test_user":"$test_user" "$dst" +} + +if id "$test_user" >/dev/null 2>&1; then + sudo pkill -u "$test_user" >/dev/null 2>&1 || true + sudo pw userdel "$test_user" -r >/dev/null 2>&1 || true +fi + +sudo pw useradd "$test_user" -m -s /bin/sh -G wheel +sudo mkdir -p "$sudoers_dir" +printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$test_user" | sudo tee "$sudoers_file" >/dev/null +sudo chmod 440 "$sudoers_file" +sudo chown -R "$test_user":"$test_user" "$workdir" + +copy_tree "$bootstrap_src" "$test_home/fruix-bootstrap" +copy_tree "$fruix_src" "$test_home/fruix" +run_as_test_user mkdir -p "$store_dir" + +run_as_test_user sh -lc "cd '$test_home/fruix-bootstrap' && ./bootstrap/prepare-builder --builder-root '$builder_root' >'$prepare_log' 2>&1" +run_as_test_user sh -lc "cd '$test_home/fruix-bootstrap' && ./bin/fruix --help >'$help_log' 2>&1" +run_as_test_user sh -lc "cd '$test_home/fruix-bootstrap' && sudo env HOME='$test_home' FRUIX_BUILDER_ROOT='$builder_root' FRUIX_FREEBSD_BUILD_JOBS='${FRUIX_FREEBSD_BUILD_JOBS:-8}' ./bin/fruix system installer-iso tests/system/phase7-minimal-operating-system.scm --system phase7-operating-system --store '$store_dir' --root-size '$root_size' --install-target-device '$install_target_device' >'$installer_log' 2>&1" + +field() { + sed -n "s/^$1=//p" "$installer_log" | tail -n 1 +} + +iso_store_path=$(field iso_store_path) +iso_image=$(field iso_image) +installer_closure_path=$(field installer_closure_path) +target_closure_path=$(field target_closure_path) +store_dir_out=$(field store_dir) + +[ -d "$iso_store_path" ] || { + echo "missing iso_store_path: $iso_store_path" >&2 + exit 1 +} +[ -f "$iso_image" ] || { + echo "missing iso_image: $iso_image" >&2 + exit 1 +} +[ -n "$installer_closure_path" ] || { + echo "missing installer_closure_path" >&2 + exit 1 +} +[ -n "$target_closure_path" ] || { + echo "missing target_closure_path" >&2 + exit 1 +} +[ "$store_dir_out" = "$store_dir" ] || { + echo "unexpected store_dir in output: $store_dir_out" >&2 + exit 1 +} + +sudo chown -R "$(id -un)":"$(id -gn)" "$workdir" + +cat >"$metadata_file" <