Skip to content

Commit

Permalink
zbm-builder.sh: support custom build/source paths and hooks
Browse files Browse the repository at this point in the history
* Build from a local source tree with `-l`
* Override build directory with `-b`
* Include hooks in $BUILD_DIRECTORY/hooks.{early_setup,setup,teardown}.d
* Update command-line documentation and docs/BUILD.md

Co-authored-by: James R. Todd <toddy@toddy.cloud>
Co-authored-by: Andrew J. Hesford <ajh@sideband.org>

Closes: #282.
  • Loading branch information
jrtoddy authored and ahesford committed Mar 24, 2022
1 parent fc83894 commit 7982971
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.swp
*/*.swp
releases/*
build
24 changes: 18 additions & 6 deletions docs/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,25 @@ The default behavior of `zbm-builder.sh` will:
2. If `./config.yaml` exists, inform the builder to use that custom configuration instead of the default
3. Run the internal build script to produce output in the `./build` subdirectory

### Customizing Images
### Custom ZFSBootMenu Hooks

A custom `config.yaml` may be provided in the working directory to override the
default ZFSBootMenu configuration. The build container runs its build script
from the working directory on the host. Therefore, relative paths in a custom
`config.yaml` will be interpreted relative to the working directory when
`zbm-builder.sh` is invoked.
ZFSBootMenu supports [custom hooks](pod/zfsbootmenu.7.pod#options-for-dracut) in three stages:

1. `early_setup` hooks run after the `zfs` kernel driver has been loaded, but before ZFSBootMenu attempts to import any pools.
2. `setup` hooks run after pools are imported, right before ZFSBootMenu will either boot a default environment or present a menu.
3. `teardown` hooks run immediately before ZFSBootMenu will `kexec` the kernel for the selected environment.

When `zbm-builder.sh` runs, it will identify custom hooks as executable files in the respective subdirectories of its build directory:

1. `hooks.early_setup.d`
2. `hooks.setup.d`
3. `hooks.teardown.d`

For each hook directory that contains at least one executable file, `zbm-builder.sh` will write custom configuration snippets for `dracut` and `mkinitcpio` that will include these files in the output images.

### Fully Customizing Images

A custom `config.yaml` may be provided in the working directory to override the default ZFSBootMenu configuration. The build container runs its build script from the working directory on the host. Therefore, relative paths in a custom `config.yaml` will be interpreted relative to the working directory when `zbm-builder.sh` is invoked.

> The internal build script **always** overrides the output paths for ZFSBootMenu components and UEFI executables to ensure that the images will reside in the `./build` subdirectory upon completion. Relative paths are primarily useful for specifying local `dracut` or `mkinitcpio` configuration paths.
Expand Down
180 changes: 133 additions & 47 deletions zbm-builder.sh
Original file line number Diff line number Diff line change
@@ -1,71 +1,103 @@
#!/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
}

usage() {
cat <<EOF
cat << EOF
Build ZFSBootMenu images in an OCI container using podman or docker.
Usage: $0 [options]
OPTIONS:
-h Display help text
-L Use local './' source tree instead of remote.
(Default: upstream master.)
OPTIONS:
-H Do not copy /etc/hostid into image
(Has no effect if ./hostid exists)
-h Display this help text
-C Do not copy /etc/zfs/zpool.cache into image
(Has no effect if ./zpool.cache exists)
-b <path>
Use an alternate build directory
(Default: current directory)
-d Force use of docker instead of podman
-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)
-t <tag>
Build the given ZFSBootMenu tag
(Default: upstream master)
Build specific ZFSBootMenu commit or tag (e.g. v1.12.0, d5594589)
(Default: current upstream master)
-B <tag>
Use the given zbm-builder tag to build images
(Default: ghcr.io/zbm-dev/zbm-builder:latest)
-C Do not include host /etc/zfs/zpool.cache in image
(If ./zpool.cache exists, this switch will be ignored)
-H Do not include host /etc/hostid in image
(If ./hostid exists, this switch will be ignored)
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
}

if command -v podman >/dev/null 2>&1; then
PODMAN=podman
elif command -v docker >/dev/null 2>&1; then
PODMAN=docker
fi

SKIP_HOSTID=
SKIP_CACHE=

BUILD_TAG="ghcr.io/zbm-dev/zbm-builder:latest"
# 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}"

BUILD_ARGS=()
VOLUME_ARGS=("-v" "${PWD}:/build")
# By default, there is no local repo or repo tag
BUILD_REPO=
BUILD_TAG=

while getopts "hHLCdt:B:" opt; do
if command -v podman >/dev/null 2>&1; then
PODMAN="podman"
else
PODMAN="docker"
fi

while getopts "b:dhi:l:t:CH" opt; do
case "${opt}" in
L)
VOLUME_ARGS+=("-v" "${PWD}:/zbm")
;;
H)
SKIP_HOSTID="yes"
;;
C)
SKIP_CACHE="yes"
b)
BUILD_DIRECTORY="${OPTARG}"
;;
d)
PODMAN=docker
;;
t)
BUILD_ARGS+=( "-t" "${OPTARG}" )
;;
B)
BUILD_TAG="${OPTARG}"
;;
h)
usage
exit 0
;;
i)
BUILD_IMG="${OPTARG}"
;;
l)
BUILD_REPO="${OPTARG}"
;;
t)
BUILD_TAG="${OPTARG}"
;;
C)
SKIP_CACHE="yes"
;;
H)
SKIP_HOSTID="yes"
;;
*)
usage
exit 1
Expand All @@ -74,14 +106,39 @@ while getopts "hHLCdt:B:" opt; do
done

if ! command -v "${PODMAN}" >/dev/null 2>&1; then
echo "ERROR: container front-end ${PODMAN} not found"
echo "ERROR: this script requires podman or docker"
exit 1
fi

# Always mount a build directory at /build
if ! BUILD_DIRECTORY="$( sanitise_path "${BUILD_DIRECTORY}" )"; then
echo "ERROR: build directory does not exist"
exit 1
fi

VOLUME_ARGS=( "-v" "${BUILD_DIRECTORY}:/build" )

# 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

VOLUME_ARGS+=( "-v" "${BUILD_REPO}:/zbm:ro" )
fi

# If a tag was specified for building, pass to the container
if [ -n "${BUILD_TAG}" ]; then
BUILD_ARGS=( "-t" "${BUILD_TAG}" )
else
BUILD_ARGS=()
fi

# If no local hostid is available, copy the system hostid if desired
if ! [ -r ./hostid ]; then
if ! [ -r "${BUILD_DIRECTORY}"/hostid ]; then
if [ "${SKIP_HOSTID}" != "yes" ] && [ -r /etc/hostid ]; then
if ! cp /etc/hostid ./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
Expand All @@ -90,21 +147,50 @@ if ! [ -r ./hostid ]; then
fi

# If no local zpool.cache is available, copy the system cache if desired
if ! [ -r ./zpool.cache ]; then
if ! [ -r "${BUILD_DIRECTORY}"/zpool.cache ]; then
if [ "${SKIP_CACHE}" != "yes" ] && [ -r /etc/zfs/zpool.cache ]; then
if ! cp /etc/zfs/zpool.cache ./zpool.cache; then
if ! cp /etc/zfs/zpool.cache "${BUILD_DIRECTORY}"/zpool.cache; then
echo "ERROR: unable to copy /etc/zfs/zpool.cache"
echo "Copy a zpool cache to ./zpool.cache or use -C to disable"
exit 1
fi
fi
fi

# If no config is specified, use in-tree default
if ! [ -r ./config.yaml ]; then
# If no config is specified, use in-tree default but force EFI and components
if ! [ -r "${BUILD_DIRECTORY}"/config.yaml ]; then
BUILD_ARGS+=( "-c" "/zbm/etc/zfsbootmenu/config.yaml" )
BUILD_ARGS+=( "-e" ".EFI.Enabled=true" )
BUILD_ARGS+=( "-e" ".Components.Enabled=true" )
fi

# Make `/build` the working directory so relative paths in a config file make sense
"${PODMAN}" run --rm "${VOLUME_ARGS[@]}" -w "/build" "${BUILD_TAG}" "${BUILD_ARGS[@]}"
# Try to include ZBM hooks in the images by default
for stage in early_setup setup teardown; do
[ -d "${BUILD_DIRECTORY}/hooks.${stage}.d" ] || continue

# Only executable hooks are added to the image
hooks=()
for f in "${BUILD_DIRECTORY}/hooks.${stage}.d"/*; do
[ -x "${f}" ] || continue
hooks+=( "/build/hooks.${stage}.d/${f##*/}" )
done

[ "${#hooks[@]}" -gt 0 ] || continue

hconf="zbm-builder.${stage}.conf"

# Write a dracut configuration snippet
mkdir -p "${BUILD_DIRECTORY}/dracut.conf.d"
echo "zfsbootmenu_${stage}+=\" ${hooks[*]} \"" > "${BUILD_DIRECTORY}/dracut.conf.d/${hconf}"

# Write a mkinitcpio configuration snippet
mkdir -p "${BUILD_DIRECTORY}/mkinitcpio.conf.d"
echo "zfsbootmenu_${stage}=(" > "${BUILD_DIRECTORY}/mkinitcpio.conf.d/${hconf}"
for hook in "${hooks[@]}"; do
echo " \"${hook}\"" >> "${BUILD_DIRECTORY}/mkinitcpio.conf.d/${hconf}"
done
echo ")" >> "${BUILD_DIRECTORY}/mkinitcpio.conf.d/${hconf}"
done

# Make `/build` the working directory so relative paths in configs make sense
exec "${PODMAN}" run --rm "${VOLUME_ARGS[@]}" -w "/build" "${BUILD_IMG}" "${BUILD_ARGS[@]}"

0 comments on commit 7982971

Please sign in to comment.