Presets can be defined in mkosi.presets/. A preset is just like a
regular config file/directory, except that mkosi can build multiple
presets sequentially.
If mkosi.presets/ exists, for each preset mkosi will read the global
configuration, followed by the individual preset configuration. It
will then build each of the presets in alpha-numerical order. Later
presets can use outputs of earlier presets, specifically using the
BaseTrees= and Initrds= options.
While this has many use cases, one promising use case is to allow
building an initrd and a final image that uses that initrd within a
single invocation of mkosi.
Let's stop running kernel-install in favor of always doing prebuilt
initrds by default. We reimplement the depmod hook of kernel-install
ourselves (required for centos stream 8).
If no initrd is provided by the user, we build a minimal one ourselves
that's sufficient to boot in a qemu VM. If the default initrd is not
sufficient, the upcoming preset support can be used to build a custom
initrd instead.
Switch back to using ttyS0 as the default serial console
virtio_console is not always a builtin kernel module on all the
distributions we support. This means that the kernel can only
start logging to it after the initramfs has been unpacked and the
virtio_console module has been loaded from it. Any message that's
logged to kmsg before that dissapears into the void which makes
debugging boot failures rather difficult.
Instead, until systemd-stub is recent enough in all supported distros
to receive extra kernel cmdline arguments via smbios, let's use a
default kernel cmdline of "console=ttyS0" so that the serial console
works by default but can still be overridden by users.
centos: Make sure glibc-all-langpacks doesn't get installed
On CentOS 8, glibc-all-langpacks is pulled in as a dependency of
filesystem. Let's make sure dnf prefers glibc-minimal-langpack by
installing it explicitly.
Currently, when base trees are used in incremental mode, we cache
the base trees as well. When running from a cache copy, any changes
to the base trees are not taken into account. Let's change this and
only cache the files we add/change/delete on top of the base layers.
This makes sure that we can still use our cache even if the base layer
changes, since we won't ignore all changes made to the base layer.
hash signing: Use keyring of running user for non-root builds
`gpg` will attempt to use the root user keyring When running as a non-root
user instead of using the keyring of the user that is executing `mkosi`.
This change will attempt to use the keyring given by `GNUPGHOME` and
fallback to using `~/.gnupg`.
While SUDO_UID and SUDO_GID are useful, since sudo allows to run things as
different UID and GID, SUDO_USER is less useful, since it can't change the
mapping from UID to name in the namespace it's in. Let's cut out the middleman
of getpass.getuser, that looks through several more environment
variables (LOGNAME, USER, LNAME, USERNAME) before falling back to os.getuid and
looking up the corresponding name and do that right away.
Let's move the cache images to mkosi.cache/ and go back to non
distro specific output directories. This gives us a stable path
for the build outputs that doesn't depend on the specific distro or
version used.
Georges Discry [Sun, 23 Apr 2023 10:27:01 +0000 (12:27 +0200)]
Enable --repo-dir for Debian and Ubuntu
Additional repositories can now be configured for Debian and Ubuntu.
Because APT can only be configured with a single path for
`Dir::Etc::sourceparts`, the content of the directories listed in
`repo_dirs` are copied in a `apt/sources.list.d` directory managed by
mkosi outside the image root. Therefore, only one file will be kept if
several files have the same name.
Like the main `sources.list` generated by mkosi, the additional
repositories are copied in the image if APT is installed.
- Remove optional exception argument of die() in favor of just using
logging.error() and re-raising the exception
- Instead of raising RuntimeError in die(), just call sys.exit()
instead which raises SystemExit.
- Show stacktrace of every exception that isn't SystemExit,
KeyboardInterrupt or subprocess.CalledProcessError.
Revert back to shell based chmod solution for bwrap
Mounting the host directories doesn't work because those directories
are owned by root on the host which translates to nobody in the user
namespace, breaking systemd tmpfiles tests.
Use conditional apivfs with Debian/Ubuntu and Arch as well
Arch's filesystem package ships apivfs directories as well and
in Debian/Ubuntu, we don't want to have apivfs mounted when running
the apt update and when only downloading packages.
Let's drop the support for multiple string arguments since we only
have "run" anyway and make --boolean a simple yes/no option. To
get an interactive shell on failure, a new option --debug-shell is
added.
- To split up our dependencies more, we need to make run.py
independent of MkosiState, so let's do that.
- To get rid of the shell hacks in both functions to chmod /tmp,
/var/tmp and /dev/shm, let's just mount the relevant files from
the host which have the right permissions.
- Fixing the above exposed a bug in the logic to set up rpm based
systems, which all ship a filesystem package that includes
directories such as /tmp, /proc, /sys, ... which we overmount
with apivfs or tmpfs filesystems when running rpm, causing errors
when the filesystem package tries to set up these directories. To
ensure these directories are created with the permissions from the
filesystem package, the run_with_apivfs() function is renamed to
bwrap() and gains an apivfs argument, which takes a path to set
up apivfs directories in. If not provided, no apivfs is set up.
This is then used to install the filesystem package without apivfs
so that the directories can be created with the right permissions.
- The various rpm distributions now install the filesystem package
instead of the setup package by default, so we can disable apivfs
properly while filesystem is being installed. system-user-root
was removed for opensuse because the filesystem package depends on
it.
On CentOS, we were creating the wrong symlink. Let's use a proper
relative symlink that always works regardless of whether we're chrooted
or not.
On Debian/Ubuntu, we weren't creating a symlink at all. So let's make
sure we also create a symlink there and use a relative symlink there as
well.
We use os.path.relpath() because Path().relative_to() can only do relative
paths to subpaths which isn't the case here. In the future we can set
strict=False and use relative_to().
Georges Discry [Thu, 20 Apr 2023 20:10:06 +0000 (22:10 +0200)]
Use an entry point instead of a script
The use of a custom script to launch mkosi has several shortcomings:
1. It translates `$PKEXEC_UID` into various `$SUDO_`* variables. That
translation is lost if mkosi is run as a zipapp or as a module with
`pkexec python3 -m mkosi`.
2. It has to muck around with `$PYTHONPATH` to launch mkosi directly
from its source code.
3. It is tied to setuptools and has no equivalent in the standard
project metadata (as in pyproject.toml).
4. And it also has to find the correct interpreter and maybe muck around
with `$PYTHONPATH` to launch mkosi from its installation, which may
be on the system, in the user's sitedir or in a virtualenv, as a
standard or an editable installation.
Point 1 is not needed anymore now that `mkosi.backend.current_user()` is
used everywhere.
Point 2 can be done by directly running mkosi as a module from its
sources with:
- `[sudo|pkexec] python3 -m mkosi` inside the project directory
- `[sudo] PYTHONPATH=<PROJECT_PATH> python3 -m mkosi` from anywhere
- `[sudo|pkexec] <PROJECT_PATH>/bin/mkosi` from anywhere. The script is
now a simple shim that is only used for this use case and not
installed anymore.
Point 3 and 4 are better supported by using a `"console_scripts"` entry
point when installed. However, it is not possible anymore to run a
user-installed mkosi with sudo/pkexec.
Georges Discry [Sun, 23 Apr 2023 12:51:08 +0000 (14:51 +0200)]
Disallow `<SOURCE>:<TARGET>` with an empty target
The configurations of the form `<SOURCE>[:<TARGET>]` require an absolute
target if given. However, `<SOURCE>:` would be ambiguously interpreted
as `<SOURCE>` so it is now disallowed.
For reference, `systemd-nspawn --bind` uses a similar form and refuses
to take an empty target.
Georges Discry [Sun, 23 Apr 2023 12:47:44 +0000 (14:47 +0200)]
Allow setting empty environment variables
Trying to set an empty environment variable with `FOO=` would instead
pass the `FOO` variable from the host, which is the expected behavior
when `=` is missing.
debian: Add TODO to drop /var/lib/dpkg/status logic when possible
It's created automatically by apt since apt 2.5.4 so add a note that
we can drop it once that version is widely available. Also disable
dpkg locking since it causes a apt failure due to not being able to
access the lock file in /var/lib/dpkg. Since we're building in a
chroot, we don't care about locking anyway.
Georges Discry [Fri, 21 Apr 2023 23:24:24 +0000 (01:24 +0200)]
Fix installation of essential packages on Debian stretch
There is a bug in Debian stretch where `libuuid1` (which is essential)
unnecessarily depends on `passwd`. When configuring the essential
packages and their dependencies, `passwd` is configured before
`base-passwd` and the installation fails.
debootstrap did not have this issue because it explicitly orders the
installation of some essential packages, including `base-passwd`.
By explicitly installing `base-passwd` first, the rest of the essential
packages can then be configured without failure.
Georges Discry [Fri, 21 Apr 2023 19:56:10 +0000 (21:56 +0200)]
Use shlex escaping in Environment
The Environment configuration takes lists of space-separated values and
combine them in a single list, splitting the values on spaces and
newlines. However, environment variables might sometimes need a space in
their value, which is currently impossible to configure.
Instead of blindly splitting the values on spaces and newlines, they are
now split with shlex. Spaces, newlines and backslashes can be escaped
with backslashes, single quotes and double quotes.
This behavior also more closely matches the Environment directive of systemd.
Instead of automatically making cpio's initrds, let's put this behind
a MakeInitrd= option. This allows booting directly into cpios with systemd
when they're not configured as an initramfs.
Georges Discry [Thu, 20 Apr 2023 20:09:58 +0000 (22:09 +0200)]
Apply path expansion when parsing
When the `extra_search_paths` configuration was introduced, it would
perform path expansions with the environment variables (using $) and the
home directories (starting with ~). Home directories would not be
expanded when mkosi was run with sudo or pkexec. Instead, `$SUDO_HOME`
could be used but only when run with sudo or pkexec.
However, those expansions were not applied to other paths in the
configuration and were broken with the rewrite of the configuration
loading, as the parser would check if the paths exist before expanding.
Those expansions are now handled directly in the parser and are
performed before validation. Environment variables expansion is enabled
for all paths and home directory expansion is enabled for all paths on
the host.
Furthermore, home directory expansion is always available, with `~`
expanding to the user's home directory when sudo or pkexec are used (and
not `/root`), replacing the need for `$SUDO_HOME`.
Georges Discry [Thu, 20 Apr 2023 20:09:54 +0000 (22:09 +0200)]
Unify loading the current user info
There are a few places where mkosi wants to know who is the current user
invoking the script with sudo or pkexec, instead of root. Some
combinations of `$SUDO_UID`, `$PKEXEC_UID` and `$SUDO_USER` are used,
but without consistency. Furthermore, when pkexec is used, several
places depend on `bin/mkosi` setting the correct `$SUDO_`* environment
variables.
Those places now use the `mkosi.backend.current_user()` function.
Furthermore, that function returns an `InvokingUser` wrapping the
complete passwd entry from the `pwd` module instead of just the uid/gid.
Split up --base-image into --base-tree and --overlay
--base-tree indicates a base image. It differs from --skeleton-tree
in that use of --base-tree indicates that we've already installed a
distribution and should only install extra packages, not install the
distribution from scratch.
If --overlay is not specified, we just copy all the specified base trees
to the root directory before doing anything else (even before copying the
skeleton trees) and after that we operate as usual.
If --overlay is specified, instead of copying all the base trees to
the root directory, we set up an overlayfs mount with all the base
trees as lowerdirs. After that we operate as usual. The effect is that
all our usual steps will operate on a full view of the image, but the
output will only contain the additions we made on top of the specified
base trees.
Empty install directory before running build script
The install directory is shared between distros, and depending on
the distro, different files might be installed. Let's make sure we
empty the install directory before running the build so that files
from different distros don't end up mixed in the install directory.
Fix stdin being redirect to /dev/null when running ssh, shell or qemu
30b8996a22022289bbf9820b6a04c5bfaf771d5e changed the default of
stdin to /dev/null, which is great, but for the ssh, shell, boot and
qemu commands, we actually need it connected to stdin of the calling
terminal, so let's fix that.
Georges Discry [Thu, 20 Apr 2023 20:09:34 +0000 (22:09 +0200)]
Check if already root in become_root
Instead of requiring the check for `os.getuid() != 0` before calling
`mkosi.run.become_root()`, that check is now performed at the beginning
with an early return if the user is already root.