Skip to content

Commit

Permalink
Improve containerized builds
Browse files Browse the repository at this point in the history
- "Tiered" configuration simplifies management and allows more targeted
  overrides, symlinking configs in `/etc/zfsbootmenu` in the container:

  1. First tier comes from `etc/zfsbootmenu` (global defaults)

  2. Second tier comes from `etc/zbm-builder` (container defaults)

  3. Third tier comes from the build root (build specific)

  Configurations in later tiers override those with conflicting names in
  earlier tiers.

- Tiered configuration now includes mkinitcpio configuration, allowing
  containers to build mkinitcpio images

- Container configuration for mkinitcpio supports dracut-style snippets
  in `mkinitcpio.conf.d`

- The builder now looks for an `rc.d` subdirectory in the build root and
  will invoke every executable file therein before generating images to
  provide a means to "terraform" the build container

- The `zbm-builder.sh` wrapper now supports a configuration file to
  allow defaults to be specified; this requires a two-pass getopts to
  find and load the configuration file before parsing remaining options

- A new option to `zbm-builder.sh`, `-R`, will remove any existing host
  files (`hostid` and `zpool.cache`) from the build root to make sure
  they are always up to date with the host versions

- The container entrypoint now configures `generate-zbm` to write its
  output directly to the desired output directory rather than staging in
  a temporary output directory, allowing `generate-zbm` to manage
  version rollovers as it does in host installations

- Remove superfluous arguments from container entrypoint to manage
  `hostid`, `zpool.cache` and `config.yaml`; the files either exist in
  the build root or the container will use defaults

- Drop `docker-compose.yml` and now-obsolete `config.yaml.default`

- Update documentation to better reflect current build procedure
  • Loading branch information
ahesford committed Nov 26, 2022
1 parent 087892c commit 5427883
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 214 deletions.
17 changes: 17 additions & 0 deletions etc/zbm-builder/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Global:
# Set InitCPIO to true to use mkinitcpio for ZBM images
#InitCPIO: true
# The build container *forces* Global.ManageImages
Components:
# Enable only current kernel/initramfs components and a backup
Enabled: true
Versions: false
# The ZBM build container *forces* Components.ImageDir
EFI:
# Enable only current EFI executable and a backup file
Enabled: true
Versions: false
# The ZBM build container *forces* EFI.ImageDir
Kernel:
# Set ZBM command-line options for the EFI bundle here
CommandLine: zfsbootmenu ro quiet loglevel=4 nomodeset
42 changes: 42 additions & 0 deletions etc/zbm-builder/mkinitcpio.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# This is a specialization of the ZFSBootMenu mkinitcpio.conf file for use in
# the ghcr.io/zbm-dev/zbm-builder container. Most documentation has been
# omitted. For a description of this file, see the manual pages
#
# - zfsbootmenu(7)
# - mkinitcpio(5)
#
# As well as a more thoroughly commented version at the location
# etc/zfsbootmenu/mkinitcpio.conf in the zfsbootmenu git repository.

# No specific customizations for container builds
MODULES=()
BINARIES=()
FILES=()

# Because generate-zbm adds the required 'zfsbootmenu' hook, omit it here.
HOOKS=(base udev autodetect modconf block filesystems keyboard)

# In containers, it is helpful to allow some dynamic configuration. In
# particular, the zbm-builder.sh helper script that configures and runs the
# build container will automatically create configuration entries in the
# subdirectories
#
# dracut.conf.d
# mkinitcpio.conf.d
#
# of the build directory for any hook found in the subdirectories
#
# hooks.early_setup.d
# hooks.setup.d
# hooks.teardown.d
#
# of the same build directory. Support for mkinitcpio.conf.d mimics similar
# support for dracut.conf.d built directly into dracut.
#
# Note that, inside the container, the build directory will be mounted at
# /build, so reference those paths here.
for _zbm_hook in /build/mkinitcpio.conf.d/*; do
[ -r "${_zbm_hook}" ] || continue
. "${_zbm_hook}"
done
unset _zbm_hook
216 changes: 134 additions & 82 deletions releng/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,20 @@ specific commit based on the contents of `/etc/zbm-commit-hash`.

When running a container from the ZFSBootMenu builder image, it is generally
expected that some compatible volume (generally, a local directory) will be
bind-mounted as a volume at the path `/zbm` inside the container. This volume
may either be empty or contain a pre-existing ZFSBootMenu source tree.
Specifically, if the volume is not empty, it must contain the following
components of the ZFSBootMenu repository:

- `dracut`, the Dracut module that populates an image with ZFSBootMenu;

- `zfsbootmenu`, the core ZFSBootMenu components;

- `bin/generate-zbm`, the executable script that creates ZFSBootMenu images;

If the build script finds the volume mounted at `/zbm` empty, it will fetch an
archive of the official ZFSBootMenu repository on github.com. This makes the
image capable of producing default images without needing a local clone of the
repository. The specific commit, tag or branch to fetch can be specified at
container run time.
bind-mounted as a volume at the path `/build` inside the container. The volume
may either be empty or contain a custom ZFSBootMenu configuration and will
contain build products (a UEFI bundle, separate kernel and initramfs
components, or both) in a `build` subdirectory.

The container entrypoint expects to run `generate-zbm` directly from a
ZFSBootMenu source repository that is available at `/zbm` within the container.
If the entrypoint finds `/zbm` nonexistent or devoid of files, it will fetch a
copy of the upstream source repository and unpack it where expected. (The tag,
commit or branch to fetch can be specified at runtime or a default will be
chosen as encoded in the container image.) Any pre-existing and non-empty
`/zbm` within the container must contain a copy of the source repository. This
is useful, *e.g.*, to bind-mount a local clone of the repository into the
container.

## Command-Line Arguments and Environment Variables

Expand All @@ -88,43 +86,20 @@ command-line argument to see a summary of build options and their default
options. The options are:

- `$BUILDROOT` specifies a default root for image builds. The build root is
expected to hold a default default configuration file and output directory,
as well as optional hostid and pool cache files. If an output directory,
specific configuration and (when appropriate) hostid or pool cache are
specified, then `$BUILDROOT` is not relevant.
expected to hold configuration files and, optionally, an output directory,
hostid and pool cache files. The value of `$BUILDROOT` is `/build` by
default.

The environment variable or default can be overridden with the `-b` option.

- `$ZBMCONF` specifies the in-container path to a specific configuration file.
The build script will override any `ImageDir` paths and remove any
`Global.BootMountPoint` option but otherwise uses the configuration as-is.A

The environment variable or default can be overridded with the `-c` option.

- `$ZBMOUTPUT` specifies the in-container path to an output directory. As noted
above, the build script overrides any `ImageDir` path in a configuration,
pointing it instead to a temporary output directory. After the script
successfully runs `generate-zbm`, it will copy any artifacts from the
temporary build directory to `$ZBMOUTPUT`.
- `$ZBMOUTPUT` specifies an alternative output directory for ZFSBootMenu build
products. The container *always* overrides configurations to store build
products (UEFI bundles and kernel components, as configured) in a temporary
directory; these products will be copied to `$ZBMOUTPUT` after successful
image creation. The value of `$ZBMOUTPUT` is `${BUILDROOT}/build` by default.

The environment variable or default can be overridded with the `-o` option.

- `$HOSTID` specifies the in-container path to a hostid file. If this file is
specified, it will be copied to `/etc/hostid` inside the container for
inclusion in ZFSBootMenu images. If not, any `/etc/hostid` in the container
will be removed. (Note: unless the `zfsbootmenu` dracut module is configured
with `release_mode=1`, the module may still create an `/etc/hostid` with
potentially arbitrary contents in output images.

The environment variable or default can be overridded with the `-H` option.

- `$POOLCACHE` specifies the in-container path to a ZFS pool cache file. If
this file is specified, it will be copied to `/etc/zfs/zpool.cache` inside
the container for inclusion in ZFSBootMenu images. If not, any
`/etc/zfs/zpool.cache` in the container will be removed.

The environment variable or default can be overridded with the `-C` option.

- `$ZBMTAG` specifies any "commit-ish" label recognized by `git` as a pointer
to a specific git commit. This can be a branch name (to grab the head of that
branch), tag or commit hash. If `/zbm` in the container is not pre-populated,
Expand All @@ -136,56 +111,133 @@ options. The options are:

The environment variable or default can be overridded with the `-t` option.

An additional command-line argument, `-e`, allows the ZFSBootMenu configuration
to be modified with `yq-go eval` statements at container run time. Do not use
this unless you review the build script and understand, without documentation,
what will happen!
A couple of additional arguments may only be set from the command line:

- `-e <statement>` provides a statement that will be evaluated via `yq-go eval`
to modify the `generate-zbm` configuration file immediately before an image
is built. This option may be specified more than once.

> Do not use this unless you review the build script and understand, without
> documentation, what will happen!
- `-p <package>` specifies a Void Linux package to install in the container
before images are generated. This option may be specified more than once.

## ZFSBootMenu Configuration and Execution

After the ZFSBootMenu container entrypoint fetches (or identifies) a copy of
the ZFSBootMenu source repository, it "installs" the copy into the container by
symlinking key components of the source repository into the container
filesystem:

- If the source repository is sufficiently new, a symbolic link

/usr/share/zfsbootmenu -> /zbm/zfsbootmenu

will point to the core ZFSBootMenu library.

- For newer versions of ZFSBootMenu, the symbolic link

/usr/lib/dracut/modules.d/90zfsbootmenu -> /zbm/dracut

will point to the dracut module; for older versions, the link

/usr/lib/dracut/modules.d/90zfsbootmenu -> /zbm/90zfsbootmenu

will serve the same purpose.

- If the ZFSBootMenu repository contains a mkinitcpio module, a family of links

/usr/lib/initcpio/hooks/* -> /zbm/initcpio/hooks/*
/usr/lib/initcpio/install/* -> /zbm/initcpio/install/*

for each file in `/zbm/initcpio/{hooks,install}` will be made to make
`mkinitcpio` aware of the ZFSBootMenu module.

Configuration files are handled in a multi-pass approach that synthesizes a
composite configuration from increasingly specific sources. In the first pass,
generic upstream configurations are linked *if the source exists*:

/etc/zfsbootmenu/config.yaml -> /zbm/etc/zfsbootmenu/config.yaml
/etc/zfsbootmenu/mkinitcpio.conf -> /zbm/etc/zfsbootmenu/mkinitcpio.conf
/etc/zfsbootmenu/dracut.conf.d/* -> /zbm/etc/zfsbootmenu/dracut.conf.d/*
/etc/zfsbootmenu/mkinitcpio.conf.d/* -> /zbm/etc/zfsbootmenu/mkinitcpio.conf.d/*

Next, container-specific defaults are linked *if the source exists*:

/etc/zfsbootmenu/config.yaml -> /zbm/etc/zbm-builder/config.yaml
/etc/zfsbootmenu/mkinitcpio.conf -> /zbm/etc/zbm-builder/mkinitcpio.conf
/etc/zfsbootmenu/dracut.conf.d/* -> /zbm/etc/zbm-builder/dracut.conf.d/*
/etc/zfsbootmenu/mkinitcpio.conf.d/* -> /zbm/etc/zbm-builder/mkinitcpio.conf.d/*

Finally, build-specific configurations are linked *if the source exists*:

/etc/zfsbootmenu/config.yaml -> ${BUILDROOT}/config.yaml
/etc/zfsbootmenu/mkinitcpio.conf -> ${BUILDROOT}/mkinitcpio.conf
/etc/zfsbootmenu/dracut.conf.d/* -> ${BUILDROOT}/dracut.conf.d/*
/etc/zfsbootmenu/mkinitcpio.conf.d/* -> ${BUILDROOT}/mkinitcpio.conf.d/*

Conflicting links will *replace* any links made by earlier passes. This allows
each level of configurations to mask or augment earlier defaults.

> NOTE: `mkinitcpio` does not natively support configuration snippets in
> `/etc/zfsbootmenu/mkinitcpio.conf.d`. ZFSBootMenu includes a default
> `mkinitcpio.conf` that manually sources these snippets to emulate the
> standard configuration behavior of dracut.
In addition, host-specific files are linked if each exists:

/etc/hostid -> ${BUILDROOT}/hostid
/etc/zfs/zpool.cache -> ${BUILDROOT}/zfs/zpool.cache

When launched, the container entrypoint will run any executable files it finds
in `${BUILDROOT}/rc.d`. This provides a means to "terraform" the build
container before running `generate-zbm` and can be a useful way to, *e.g.*:

- Modify the `FONT` variable defined in `/etc/rc.conf`, which will be parsed by
`mkinitcpio` to set a default console font in ZFSBootMenu images.

- Create additional links to directories in `$BUILDROOT`, such as

/etc/initcpio -> ${BUILDROOT}/initcpio

to provide additional `mkinitcpio` modules or

/etc/dropbear -> ${BUILDROOT}/dropbear

to provide host keys and configuration for the `dropbear` `mkinitcpio`
module.

## Build Examples

To use the previously created `zbm` image to produce ZFSBootMenu files from the
default configuration using a local ZFSBootMenu repository `/sw/zfsbootmenu`,
simply run
default configuration, simply run

```sh
podman run -v /sw/zfsbootmenu:/zbm zbm
podman run -v .:/build zbm
```

After some console output, the container should terminate and the directory
`/sw/zfsbootmenu/releng/docker/build` should contain the UEFI bundle
`vmlinuz.EFI` as well as the components `vmlinuz-bootmenu` (a stock Void Linux
kernel) and corresponding ZFSBootMenu initramfs `initramfs-bootmenu.img`.

In the default configuration, the ZFSBootMenu images probably contain an
arbitrary `/etc/hostid` that likely does not agree with the corresponding file
on the host. To make sure that the hostid within the images remains consistent
with the build host, first copy the file from the host to the `releng/docker`
directory:

```sh
cp /etc/hostid /sw/zfsbootmenu/releng/docker/hostid
podman run -v /sw/zfsbootmenu:/zbm zbm
```
`./build` should contain the UEFI bundle `vmlinuz.EFI` as well as the
components `vmlinuz-bootmenu` (a stock Void Linux kernel) and corresponding
ZFSBootMenu initramfs `initramfs-bootmenu.img`.

To create an image from the current `master` branch without having a local
repository, store the output images in `/boot/efi/EFI/zfsbootmenu` and include
the hostid of the current system, assuming a `zbm` builder container is tagged
locally:
To provide the hostid and pool cache files to the build container and run from
the `/etc/zfsbootmenu/build` directory, copy the desired files and run the
container with the appropriate volume mount:

```sh
mkdir -p /boot/efi/EFI/zfsbootmenu
podman run -v /boot/efi/EFI/zfsbootmenu:/output \
-v /etc/hostid:/hostid:ro zbm -o /output -H /hostid
cp /etc/hostid /etc/zfs/zpool.cache /etc/zfsbootmenu/build
podman run -v /etc/zfsbootmenu/build:/build zbm
```

# Using Docker Compose

The file `docker-compose.yml` defines a Docker Compose service that will create
a ZFSBootMenu builder image and mount the parent repository (at path `../..`)
at `/zbm` in the build container. To use this service, simply run
To create an image from a local repository available at `/sw/zfsbootmenu` and
again use a build root of `/etc/zfsbootmenu/build`, run

```sh
docker-compose up
podman run -v /etc/zfsbootmenu/build:/build -v /sw/zfsbootmenu:/zbm:ro zbm
```

from this directory.
Because the build container does not modify the repository found in `/zbm`, it
is possible to mount that volume read-only (as indicated by the `:ro` suffix)
without consequence.

0 comments on commit 5427883

Please sign in to comment.