A Simple Host-Specific Container Build
======================================

``zbm-builder.sh`` mounts a build directory (by default, the current working directory) into the container to provide a
path to inject custom configuration into the container. If the system will manage ZFSBootMenu images exclusively via a
build container, an obvious location for the build directory is ``/etc/zfsbootmenu``. Start by creating this directory
and populating a simple ``config.yaml`` for container builds::

  mkdir -p /etc/zfsbootmenu

  cat > /etc/zfsbootmenu/config.yaml <<EOF
  Global:
    InitCPIO: true
  Components:
    Enabled: false
  EFI:
    Enabled: true
    Versions: false
  Kernel:
    Prefix: zfsbootmenu
    CommandLine: zfsbootmenu ro quiet loglevel=0 nomodeset
  EOF

  curl -L -o /etc/zfsbootmenu/zbm-builder.sh https://raw.githubusercontent.com/zbm-dev/zfsbootmenu/master/zbm-builder.sh
  chmod 755 /etc/zfsbootmenu/zbm-builder.sh

In this configuration, ``mkinitcpio`` will be used instead of ``dracut``. Component generation is disabled, so
``generate-zbm`` will produce only a UEFI bundle. That bundle has numeric versioning disabled, so ``generate-zbm`` will
produce an unversioned ``zfsbootmenu.EFI``; if the generator detects an existing ``zfsbootmenu.EFI`` in the output
directory, it will make a single backup of that file as ``zfsbootmenu-backup.EFI`` before overwriting it. A simple
kernel command-line is specified and may be overridden as necessary.

The default ``mkinitcpio.conf`` in the container, which should generally not be overridden, will source all files in
``/etc/zfsbootmenu/mkinitcpio.conf.d``.

Custom Font
-----------

On high-resolution screens, the Linux kernel does not always do a good job choosing a console font. A nice font can be
explicitly specified in the ZFSBootMenu configuration for ``mkinitcpio``. The container entrypoint must be told to
install the desired font and the ``mkinitcpio`` configuration should include the necessary module and executable to set
the font::

  echo "BUILD_ARGS+=( -p terminus-font )" >> /etc/zfsbootmenu/zbm-builder.conf

  cat > /etc/zfsbootmenu/mkinitcpio.conf.d/consolefont.conf <<EOF
  BINARIES+=(setfont)
  HOOKS+=(consolefont)
  EOF

This approach uses the configuration file capability of ``zbm-builder.sh`` to specify build options without requiring
that they be included on the command line.

As configured, ``mkinitcpio`` will not see a configured console font and will omit the font from generated images. To
make ``mkinitcpio`` aware of the desired font, it must be specified in ``/etc/rc.conf`` within the container. The
"terraform" capabilities of the container entrypoint can be used to accomplish this::

  mkdir -p /etc/zfsbootmenu/rc.d

  cat > /etc/zfsbootmenu/rc.d/consolefont <<EOF
  #!/bin/sh
  sed -e '/FONT=/a FONT="ter-132n"' -i /etc/rc.conf
  EOF

  chmod 755 /etc/zfsbootmenu/rc.d/consolefont

When the container entrypoint finds an ``rc.d`` subdirectory in the build root, it will run each executable file therein
before generating a ZFSBootMenu image.  If any of these executable should fail, image generation is aborted.

Host-Specific Files
-------------------

By default, ``zbm-builder.sh`` will copy the file ``/etc/hostid`` from the host to the build directory so that the
hostid of the generated ZFSBootMenu image will match that of your host. This is often desirable for customized builds,
but it would be undesirable for copies of these files in ``/etc/zfsbootmenu`` to fall out of synchronization with the
host versions. To avoid this issue, tell ``zbm-builder.sh`` to remove any copies in ``/etc/zfsbootmenu`` before
determining whether the host versions should be copied in for image creation::

  echo "REMOVE_HOST_FILES=yes" >> /etc/zfsbootmenu/zbm-builder.conf

If you would rather not see those files at all, it is possible to instruct ``generate-zbm`` to remove them after they
are used. Edit the configuration at ``/etc/zfsbootmenu/config.yaml`` and add the following key:

.. code-block:: yaml

  Global:
    PostHooksDir: /build/cleanup.d

Alternatively, tell the build container to add this option dynamically::

  echo "BUILD_ARGS+=( -e '.Global.PostHooksDir=\"/build/cleanup.d\"' )" \
      >> /etc/zfsbootmenu/zbm-builder.conf

Next, create a post-generation hook to remove the files::

  mkdir -p /etc/zfsbootmenu/cleanup.d

  cat > /etc/zfsbootmenu/cleanup.d/hostfiles <<EOF
  #!/bin/sh
  rm -f /build/zpool.cache /build/hostid
  EOF

  chmod 755 /etc/zfsbootmenu/cleanup.d/hostfiles

The Output Directory
--------------------

At this point, it should be possible to generate images by running

.. code-block::

  cd /etc/zfsbootmenu && ./zbm-builder.sh

However, these images will reside in ``/etc/zfsbootmenu/build`` and will require manual management. A better alternative
is to let ``generate-zbm`` manage the ZFSBootMenu output directory directly. Assuming that ZFSBootMenu images should be
installed in ``/boot/efi/EFI/zfsbootmenu``, tell ``zbm-builder.sh`` to mount the directory inside the container, and
tell the container that it should write its images to the mounted directory::

  cat >> /etc/zfsbootmenu/zbm-builder.conf <<EOF
  RUNTIME_ARGS+=( -v /boot/efi/EFI/zfsbootmenu:/output )
  BUILD_ARGS+=( -o /output )
  EOF

Now, running

.. code-block::

  cd /etc/zfsbootmenu && ./zbm-builder.sh

should create images directly in ``/boot/efi/EFI/zfsbootmenu`` and create a backup of any existing ``zfsbootmenu.EFI``.

Networking in Rootfull Containers
---------------------------------

Manipulating files in ``/etc/zfsbootmenu`` and ``/boot/efi/EFI/zfsbootmenu`` may require root privileges, which means
that ``zbm-builder.sh`` and the build container will need to run as root. In some configurations, ``podman`` may not
provide working networking for rootfull containers by default. A simple fix is to allow the containers to use the host
network stack, which can be accomplished by running

.. code-block::

  echo "RUTNIME_ARGS+=( --net=host )" >> /etc/zfsbootmenu/zbm-builder.conf

Adding Remote Access Capabilities
---------------------------------

The process for including ``dropbear`` for remote access to container-built
ZFSBootMenu images is largely the same as the
:doc:`process for host-built images </general/remote-access>`, but care must be taken to ensure that all
necessary components are available within the build directory.

- The :doc:`core configuration changes </general/mkinitcpio>` should be **ignored**. They are unnecessary with the
  container configuration described above.

- The :ref:`basic network access <remote-mkinitcpio-net>` and :ref:`dropbear <remote-mkinitcpio-dropbear>` instructions
  are generally applicable, except **no changes should be made to** ``/etc/zfsbootmenu/mkinitcpio.conf`` and **all
  references to paths in** ``/etc/dropbear`` **should be replaced with corresponding references to paths in**
  ``/etc/zfsbootmenu/dropbear``.

Specific alterations are noted below.

Configuring Basic Network Access
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Commands to fetch and unpack the ``mkinitcpio-rclocal`` module and create an ``/etc/zfsbootmenu/initcpio/rc.local``
script still apply as described to containerized builds. Subsequent ``sed`` and ``echo`` commands that write to
``/etc/zfsbootmenu/mkinitcpio.conf`` should be ignored because this file should not exist. Instead, create a
configuration snippet that will add network configuration to the ZFSBootMenu image::

  cat > /etc/zfsbootmenu/mkinitcpio.conf.d/network.conf <<EOF
  BINARIES+=(ip dhclient dhclient-script)
  HOOKS+=(rclocal)
  rclocal_hook="/build/initcpio/rc.local"
  EOF

.. note::

  If a static IP address will be configured, it is acceptable to leave ``dhclient`` and ``dhclient-script`` out of the
  ``BINARIES`` array.

Next, edit ``/etc/zfsbootmenu/config.yaml`` to add a hook directory configuration telling `mkinitcpio` where to find
custom modules:

.. code-block:: yaml

  Global:
    InitCPIOHookDirs:
      - /build/initcpio
      - /usr/lib/initcpio

Configuring Dropbear
~~~~~~~~~~~~~~~~~~~~

The commands to fetch and unpack the ``mkinitcpio-dropbear`` module still apply to containerized builds. Instead of
adding ``dropbear`` to the non-existent configuration ``/etc/zfsbootmenu/mkinitcpio.conf``, create a snippet::

  cat > /etc/zfsbootmenu/mkinitcpio.conf.d/dropbear.conf <<EOF
  HOOKS+=(dropbear)
  EOF

Rather than creating keys (and optional configuration) in ``/etc/dropbear``, create the keys and configuration in
``/etc/zfsbootmenu/dropbear``::

  mkdir -p /etc/zfsbootmenu/dropbear

  ## Not strictly required; see note below
  for keytype in rsa ecdsa ed25519; do
      dropbearkey -t "${keytype}" -f "/etc/zfsbootmenu/dropbear/dropbear_${keytype}_host_key"
  done

  ## If desired
  echo 'dropbear_listen=2222' > /etc/zfsbootmenu/dropbear/dropbear.conf

.. note::

  Generating keys is not strictly necessary and can be skipped if ``dropbearkey`` is not available on the host. The
  build container will generally lack SSH host keys, so the ``mkinitcpio-dropbear`` module will default to creating new,
  random keys in the build directory. These keys will persist for subsequent use.

The file ``/etc/zfsbootmenu/dropbear/root_key`` is required to provide a list of authorized keys in the ZFSBootMenu
image. Unlike with host builds, this may not be a symlink to a user's ``authorized_keys`` file because that path will be
unavailble in the container. Instead, simply copy a desired ``authorized_keys`` file to
``/etc/zfsbootmenu/dropbear/root_key``. Alternatively, dynamism can be preserved by relying on bind-mounting a specific
``authorized_keys`` file into the build container::

  echo "RUNTIME_ARGS+=( -v /home/${dropbear_user}/.ssh/authorized_keys:/authorized_keys:ro )" >> /etc/zfsbootmenu/zbm-builder.conf
  ln -s /authorized_keys /etc/zfsbootmenu/dropbear/root_key

Replace ``${dropbear_user}`` with the desired user whose ``authorized_keys`` file should govern access to ZFSBootMenu.

Make sure that the build container installs the packages necessary to provide ``dropbear``::

  echo "BUILD_ARGS+=( -p dropbear -p psmisc )" >> /etc/zfsbootmenu/zbm-builder.conf

Finally, add a "terraform" script to copy contents to the expected ``/etc/dropbear`` directory from the build directory::

  cat > /etc/zfsbootmenu/rc.d/dropbear <<EOF
  #!/bin/sh

  [ -d /build/dropbear ] || exit 0

  mkdir -p /etc/dropbear
  cp -R /build/dropbear/* /etc/dropbear/
  EOF

  chmod 755 /etc/zfsbootmenu/rc.d/dropbear
