MAKEIMG(1)

2024-06-19

NAME

makeimg - Build system images from declarative input.

SYNOPSIS

makeimg [OPTION] [DIR]...

DESCRIPTION

makeimg is a script to create a Linux system image from a set of input files, most notably an IMGBUILD(5) file. Currently supported targets are Arch Linux, Alpine Linux, and Debian Linux.

The IMGBUILD has to be in the current directory. Additional input files are taken from the provided directories. If not provided, DIR defaults to ./files.

In rough terms, makeimg will:

Each of these steps is described in more detail below. Every step except the bootstrapping is optional.

Much like a PKGBUILD(5), an IMGBUILD will usually not just be a file, but an entire directory, most likely a git repo. The default structure is:


  <PROJECT>/
  |- files/
  |- secrets/
  |- IMGBUILD
  |- pacman.conf

Everything except the IMGBUILD file itself is optional. For Alpine images, instead of pacman.conf you might for example have a repositories file and a keys directory.

Bootstrapping

First, the base system is bootstrapped with pacstrap(8), apk(8), or debootstrap(8). The packages to be installed and some options can be defined in the IMGBUILD file.

For Arch Linux images, if a file pacman.conf exists, it is used as config for pacstrap(8)(but not copied to the new image).

For Alpine Linux images, if a file repositories and/or a directory keys exist, it is used as config for apk(8) (the repositories are not automatically copied to the new image, but apk will always copy over the keys).

Files

makeimg scans all input directories (by default, a folder files/ in the directory where it is being run). All symlinks and regular files whose name does not end with '.makeimg.patch' or '.makeimg.template' are considererd an input file. All input files are copied over to the image, preserving their path underneath files/. For example, the file files/etc/hostname would be copied to /etc/hostname in the image.

Files copied to the image are owned by root, their mode is preserved (see section File Modes). If a change of ownership is required it has to be performed in the provisioning step (see section Provisioning).

Patches

While the files mechanism can also be used to overwrite files that already exist in the image, it can be preferrable to apply a patch instead. Benefits could be the automatic integration of upstream changes or the breaking of the build if the upstream file changed in a way that the patch no longer applies (whereas overwriting with a potentially invalid file will always succeed).

makeimg will scan all input directories for regular files whose name ends in '.makeimg.patch'. For each such file, makeimg will attempt to apply it as a patch to an existing file in the image. The patch target is determined by the file name underneath the input directory, not the patch header. For example, the patch files/etc/locale.gen.makeimg.patch would always be applied to /etc/locale.gen in the image.

Templates

makeimg will scan all input directories for regular files whose name ends in '.makeimg.template'. For each such file, makeimg will attempt to render it as a template into the image. Like for patches, the target file is determined by the template's file name underneath the input directory, without the '.makeimg.template' extension.

Templates are handled exactly like files, except that shell expansion is performed on each file. The input file is essentially treated like a heredoc, so substitution can even occur in what would seem to be single quotes. While providing ample opportunity for fun and mischief, the main intention is to provide re-usable variables as well as a primitive mechanism for hiding secrets.

Consider the following example, which could be used to render a Wireguard config:


[Interface]
Address = ${SERVER_IP}/24
ListenPort = 51820
PrivateKey = $(makeimg -S +vpn_server_key)

[Peer]
PublicKey = 1wVQaL04+Z9aLG6vhernjFVpAN0w5yGZAGSip5GgDgQ=
AllowedIPs = 10.0.0.2/32

The variable SERVER_IP could for example be centrally defined in the IMGBUILD and then used in multiple templates, making it easy to change.

The command makeimg -S produces the value of a defined secret, see section Secrets below.

Since rendering templates involves plenty of subshells, checking return codes is next to impossible. Instead, makeimg checks for any output to stderr during template rendering. If there is any, it assumes an error has happened. This behavior can be overridden with the -W flag if needed, but this is considered dangerous.

Note that also due to this any process executed as part of templated cannot ask for passwords by printing the prompt to stderr. For the example above, any PGP keys required to show the secret must either already be unlocked in gpg-agent or gpg must be configured to use a graphical prompt.

The template rendering subshell sets -o nounset, so referencing undefined variables will cause an error. For command substitution, make sure a failed command causes output on stderr to detect the error.

Like copied files, the rendered file will be owned by root and the mode preserved from the input file (see section File Modes).

Secrets

Each file in the secrets folder defines a secret of the same name. A secret file should contain a shell command that outputs the sensitive value. Returning to the VPN example above, there might be a file secrets/+vpn_server_key with the following contents:


pass show vpn/server_key

As seen in the template example, makeimg -S +vpn_server_key could be used to show the value of the secret.

As makeimg is usually invoked through sudo(8), it may sometimes be desirable to execute the secret-showing command as the original user ($SUDO_USER). This is achieved by prefixing the secret name with +, as is the case here. Thus, this example should work for a pass(1) setup of a regular user. The same mechanism is implemented for doas(1).

If the secret name does not start with +, the secret command is executed as the current user (usually root).

Pre-revealed secrets

The makeimg secrets mechanism accounts for the fact that sometimes the secrets might not be accessible at build time. For this, pre-revealed secrets can be used.

All secrets can pre-revealed to a given directory by running makeimg -R DIR. These revealed secrets can then be used during build time by passing the folder to the -r/--revealed flag. An example use case might be that the secrets require a PGP key on a developer's laptop, but the build is supposed to run on a different machine.


# reveal secrets, creates folder xsecrets with sensitive contents!
# Will require access to whatever your secrets need.
makeimg -R xsecrets
# Copy folder if needed, e.g. to a different machine
# This is for illustration only, be careful with the secrets!
scp -r . user@buildhost:
# Build image using the pre-revealed secrets, no additional access required
ssh user@buildhost makeimg -r xsecrets

Provisioning

As a final step, the provision function of the IMGBUILD is called. See IMGBUILD(5) for details.

Login passwords

To avoid even hashed passwords in the IMGBUILD, login passwords can be defined in secrets (see Secrets above). To do so, set the passwords variable in the IMGBUILD file. See IMGBUILD(5) for details.

Output

The main output of makeimg is the image as described by the IMGBUILD file. The format (and hence naming) of that image depends on settings in the IMGBUILD. See IMGBUILD(5) for details.

For each generated image makeimg also generates a list of packages that are installed in the image, along with version number (the output of pacman -Q, apk list -I, or apt list --installed). This list can be used to create an inventory of packages and versions used, for example for checking for security advisories.

File Modes

File modes are important for files and templates, as the target file will be given the mode of the source file. However, file modes may not be preserved by some version control systems (e.g. git). As a mitigation, makeimg supports creating a record of all relevant file modes and using this record to set all file modes correctly in the local checkout.

First, make sure all files and templates have the desired mode. Then, run makeimg -M to create the file .makeimg.modes. Make sure to add that file to version control. Repeat this process for new files. After a fresh checkout of a repository, run makeimg -m to apply the modes from .makeimg.modes to the files on-disk.

OPTIONS

-l, --list

List all input files (files, patches, templates).

-f, --files

List all regular input files.

-p, --patches

List all input patches.

-t, --templates

List all input templates.

-s, --secrets DIR

Path to secret definitions (default: ./secrets).

-r, --revealed DIR

Path to already revealed secrets. Causes makeimg -S to read secrets from here rather then the regular secrets directory.

-S, --secret NAME

Show contents of defined secret NAME.

-R, --reveal-all DIR

Reveal contents of all secrets to DIR. The output is suitable to be passed to -r in another invocation of makeimg.

-m, --apply-modes

Apply recorded modes to files and templates.

-M, --record-modes

Record current modes of files and templates.

-W, --warn-only

Do not fail on output to stderr during template rendering. Use at your own risk, see the section on template rendering above.

-k, --keep

Keep build environment around in case of build errors. This makes it easier to poke around, but any unmounting and/or detaching of loopback devices to properly clean up the build has to be performed manually.

-F, --format FORMAT

Override the format specified in the IMGBUILD to FORMAT. This can potentially break the build! But if the IMGBUILD was written with this in mind, it can be useful for testing. See IMGBUILD(5).

-h, --help

Output command line options.

SEE ALSO

IMGBUILD(5), pacstrap(8), apk(8)

AUTHORS

This project is is maintained by Conrad Hoffmann <ch@bitfehler.net>. The source code can be found at https://git.sr.ht/~bitfehler/makeimg. Bugs or patches can be submitted by email to ~bitfehler/anemos@lists.sr.ht.