zfsbootmenu/zbm-builder.sh

258 lines
6.1 KiB
Bash
Executable File

#!/bin/bash
# vim: softtabstop=2 shiftwidth=2 expandtab
sanitise_path() {
local rpath
if rpath="$(readlink -f "${1}")" && [ -d "${rpath}" ]; then
echo "${rpath}"
return 0
fi
return 1
}
boolean_enabled() {
local val="${1:-}"
case "${val,,}" in
yes|y|on|1) return 0 ;;
*) return 1 ;;
esac
}
usage() {
cat << EOF
Build ZFSBootMenu images in an OCI container using podman or docker.
Usage: $0 [options]
OPTIONS:
-h Display this help text
-b <path>
Use an alternate build directory
(Default: current directory)
-c <config>
Specify the path to a configuration file that will be sourced
(Default: \${BUILD_DIRECTORY}/zbm-builder.conf, if it exists)
-d Force use of docker instead of podman
-M <argument>
Provide a comma-separated list of options to use for volume
mounts of the build directory and (if specified) ZFSBootMenu
source tree within the build container. For example, specify
zbm-builder -M z
to label the volumes for use with SELinux.
NOTE: An 'ro' option is always added to the volume mounted from
the ZFSBootMenu source tree.
-O <argument>
Provide an option to 'podman run' or 'docker run'; if the
argument accepts one or more options, use a form with no spaces
or make sure each option gets its own '-O', e.g.:
zbm-builder -O -v -O /boot/efi/EFI/zfsbootmenu:/output
zbm-builder -O --volume -O /boot/efi/EFI/zfsbootmenu:/output
zbm-builder -O --volume=/boot/efi/EFI/zfsbootmenu:/output
May be specified multiple times.
-i <image>
Build within the named container image
(Default: ghcr.io/zbm-dev/zbm-builder:latest)
-l <path>
Build from ZFSBootMenu source tree at <path>
(Default: fetch upstream source tree inside container)
-R Remove any existing hostid in the build directory
-H Do not include host /etc/hostid in image
(If ./hostid exists, this switch will be ignored)
-- <arguments>
Additional arguments to the zbm-builder container
For more information, see documentation at
https://github.com/zbm-dev/zfsbootmenu/blob/master/README.md
https://github.com/zbm-dev/zfsbootmenu/blob/master/docs/BUILD.md
EOF
}
SKIP_HOSTID=
REMOVE_HOST_FILES=
MOUNT_FLAGS=
# By default, use the latest upstream build container image
BUILD_IMG="ghcr.io/zbm-dev/zbm-builder:latest"
# By default, build from the current directory
BUILD_DIRECTORY="${PWD}"
# By default, there is no local repo
BUILD_REPO=
# Arguments to the build script
BUILD_ARGS=()
# Arguments for the container runtime
RUNTIME_ARGS=()
# Optional configuration file
CONFIG=
if command -v podman >/dev/null 2>&1; then
PODMAN="podman"
else
PODMAN="docker"
fi
CMDOPTS="b:dhi:l:c:M:O:HR"
# First pass to get build directory and configuration file
while getopts "${CMDOPTS}" opt; do
case "${opt}" in
b)
BUILD_DIRECTORY="${OPTARG}"
;;
c)
CONFIG="${OPTARG}"
;;
h)
usage
exit 0
;;
esac
done
# Make sure the build directory is identifiable
if ! BUILD_DIRECTORY="$( sanitise_path "${BUILD_DIRECTORY}" )"; then
echo "ERROR: build directory does not exist"
exit 1
fi
# If a configuration wasn't specified, use a default it one exists
if [ -z "${CONFIG}" ] && [ -r "${BUILD_DIRECTORY}/zbm-builder.conf" ]; then
CONFIG="${BUILD_DIRECTORY}/zbm-builder.conf"
fi
# Read the optional configuration
if [ -n "${CONFIG}" ]; then
if [ -r "${CONFIG}" ]; then
# shellcheck disable=SC1090
source "${CONFIG}"
else
echo "ERROR: non-existent configuration specified"
exit 1
fi
fi
OPTIND=1
while getopts "${CMDOPTS}" opt; do
case "${opt}" in
# These have already been parsed in first pass
b|c|h)
;;
d)
PODMAN=docker
;;
i)
BUILD_IMG="${OPTARG}"
;;
l)
BUILD_REPO="${OPTARG}"
;;
M)
MOUNT_FLAGS="${OPTARG}"
;;
O)
RUNTIME_ARGS+=( "${OPTARG}" )
;;
H)
SKIP_HOSTID="yes"
;;
R)
REMOVE_HOST_FILES="yes"
;;
*)
usage
exit 1
;;
esac
done
# Drop all processed arguments
shift $((OPTIND-1))
if ! command -v "${PODMAN}" >/dev/null 2>&1; then
echo "ERROR: this script requires podman or docker"
exit 1
fi
# Always mount a build directory at /build
RUNTIME_ARGS+=( "-v" "${BUILD_DIRECTORY}:/build${MOUNT_FLAGS:+:${MOUNT_FLAGS}}" )
# Only mount a local repo at /zbm if specified
if [ -n "${BUILD_REPO}" ]; then
if ! BUILD_REPO="$( sanitise_path "${BUILD_REPO}" )"; then
echo "ERROR: local repository does not exist"
exit 1
fi
RUNTIME_ARGS+=( "-v" "${BUILD_REPO}:/zbm:ro${MOUNT_FLAGS:+,${MOUNT_FLAGS}}" )
fi
# Remove existing hostid
if boolean_enabled "${REMOVE_HOST_FILES}" && [ -e "${BUILD_DIRECTORY}/hostid" ]; then
if ! rm "${BUILD_DIRECTORY}/hostid"; then
echo "ERROR: failed to remove hostid from build directory"
exit 1
fi
echo "Removed hostid by user request"
fi
# If no local hostid is available, copy the system hostid if desired
if ! [ -r "${BUILD_DIRECTORY}"/hostid ]; then
if ! boolean_enabled "${SKIP_HOSTID}" && [ -r /etc/hostid ]; then
if ! cp /etc/hostid "${BUILD_DIRECTORY}"/hostid; then
echo "ERROR: unable to copy /etc/hostid"
echo "Copy a hostid file to ./hostid or use -H to disable"
exit 1
fi
fi
fi
# If no config is specified, use in-tree default but force EFI and components
if ! [ -r "${BUILD_DIRECTORY}"/config.yaml ]; then
BUILD_ARGS=(
"-e" ".EFI.Enabled=true"
"-e" ".Components.Enabled=true"
"${BUILD_ARGS[@]}"
)
fi
# Try to include ZBM hooks in the images by default
if [ -d "${BUILD_DIRECTORY}/hooks" ]; then
# Write a dracut configuration snippet
mkdir -p "${BUILD_DIRECTORY}/dracut.conf.d"
echo "zfsbootmenu_hook_root=/build/hooks" \
> "${BUILD_DIRECTORY}/dracut.conf.d/user_hooks.conf"
# Write a mkinitcpio configuration snippet
mkdir -p "${BUILD_DIRECTORY}/mkinitcpio.conf.d"
echo "zfsbootmenu_hook_root=/build/hooks" \
> "${BUILD_DIRECTORY}/mkinitcpio.conf.d/user_hooks.conf"
fi
# Make `/build` the working directory so relative paths in configs make sense
exec "${PODMAN}" run \
--rm -w "/build" "${RUNTIME_ARGS[@]}" \
"${BUILD_IMG}" "${BUILD_ARGS[@]}" "$@"