Daan De Meyer [Mon, 28 Nov 2022 14:45:31 +0000 (15:45 +0100)]
Migrate disk image building to systemd-repart
Let's switch to systemd-repart to build disk images. systemd-repart
allows building disk images declaratively. repart will assemble a
disk image according to a set of partition definition files, which
it will search for in a set of predefined locations or in the locations
provided via the --definitions option. Partition definition files are
defined in the usual systemd ini style and define a single partition
each. Many aspects of the partition can be configured, including its
size, type, and how it should be populated.
Of note should be the CopyFiles= setting that allows providing a set
of files/directories from which the partition should be populated. The
CopyFiles= setting takes a list of path pairs, where the first path in
a pair defines the source location, and the second path the destination
location in the partition. If combined with repart's --root or --image
options, the source path is resolved relative to the given root directory.
Along with switching to systemd-repart, we also rework how we build
images. Previously, we first provisioned the disk image with all its
partitions, then mounted the disk image and finally populated it with
contents. Now, we provision to a regular directory on the host filesystem
first, followed by creating the disk image with systemd-repart. The partition
definition files can be supplied via the --repart-directory option or
in the mkosi.repart/ directory. If not provided, a default configuration
consisting of an automatically sized root partition and an ESP partition of
256M is used.
repart's --root switch is used so that the partition definition files
don't need to encode the full path to the root directory of the image
we're building. For example, to copy the root directory to a root
partition, it's sufficient to specify "CopyFiles=/:/" in the definition
file.
If we're building non-bootable images, any ESP partition definitions
are excluded. To allow building UKIs with an embedded roothash cmdline
parameter, we run repart twice, first to populate all partitions except
the ESP/XBOOTLDR partitions, and again to populate the ESP/XBOOTLDR
partitions including any UKIs with embedded roothash cmdline parameter
(which we determined when running repart the first time).
Because we don't know up-front anymore where the ESP partition will be
mounted, all boot loader files are installed to /boot. So to populate
an ESP partition, you'd use "CopyFiles=/boot:/" in the partition
definition file of the ESP partition.
Also, since we don't know up-front anymore which filesystem we'll be
building for, we stop installing filesystem related packages by default.
Users will be required to add the necessary filesystem packages themselves.
Similarly, we also stop installing cryptsetup and device-mapper by default
since we don't know upfront anymore whether partitions will be verity
protected or encrypted.
By switching to systemd-repart, we also set ourselves up for building
images without needing root privileges or loop devices. systemd-repart
is fully capable (from v253 onwards) of building disk images without
needing root privileges or loop devices. After we switch to
systemd-repart, we'll only need root privileges to be able to run
systemd-nspawn (which should not be necessary in the future anymore).
This PR also removes all the option related to disk image building that
can now be specified in repart definition files instead. This includes:
- Format=gpt_xxx options are replaced with a single "disk" options.
Filesystem can now be specified with repart's Format= option
- Format=plain_squashfs (Can be reproduced by a single repart squashfs
root partition combined with SplitArtifacts=yes)
- Verity= (Replaced by repart's Verity= options)
- Encrypt= (Replaced by repart's Encrypt= option)
- RootSize=, HomeSize=, VarSize=, TmpSize=, ESPSize=, SwapSize=, SrvSize=
(Replaced by repart's size options)
- UsrOnly= (replaced with `CopyFiles=/:/usr` in a usr partition definition)
- OutputSplitRoot=, OutputSplitVerity=, (Replaced by repart's SplitName= option)
- OutputSplitKernel= (UKI is now always written to its own output file)
- GPTFirstLBA (Removed, no equivalent in repart)
- ReadOnly= (Replaced by repart's ReadOnly= option per partition)
- Minimize= (Replaced by repart's Minimize= option per partition)
- CompressFs= (No equivalent in repart, can be replicated by replacing mkfs.<fs>
in $PATH with a script that adds the necessary command line option)
- MkSquashfs= (Can be replaced with a script in $PATH that invokes
the correct binary)
We also remove the WithoutUnifiedKernelImages= switch as building unified
kernel images is trivial and fast these days.
Daan De Meyer [Mon, 28 Nov 2022 14:42:13 +0000 (15:42 +0100)]
ci: Rework integration tests
The current approach where we build the image as part of the integration
test is the wrong approach. Instead, we'll move to integration tests that
are integrated with a build system where building images and using them
in integration tests are separate steps. Building an image for use in an
integration test will be a regular target (custom_target()) in a build
system. Building an image that's not intended to be used in any other test
will be a regular test.
Because this model is going to be substantially different from what we have
now, let's get rid of the integration test machinery we added and temporarily
switch to the basic approach that's also used in the systemd repo for integration
tests.
Daan De Meyer [Fri, 18 Nov 2022 10:07:27 +0000 (11:07 +0100)]
Drop HostonlyInitrd
Adding this option was a mistake (mea culpa), we should limit host
impact on image builds as much as possible so let's drop this option
that makes the initrd generated by dracut host specific. This will
slow down initrd generation but once we switch to generating initrds
with mkosi we'll have a proper fix for that.
By default, mkosi would bind mount directories with nspawn's rootidmap
option to provide consistent files ownership. If the system doesn't
support ID mapping, mkosi would fail.
Add --idmap option, default to True, to allow users to disabled UID
mapping (--idmap=no).
Joerg Behrmann [Thu, 24 Nov 2022 11:54:34 +0000 (12:54 +0100)]
Move Fedora to DistributionInstaller
This doubles basically all rpm/dnf functions with copies in
mkosi.distributions.fedora, but this let's us keep the split bisectable without
having to do a big refactor of what these functions look like.
Joerg Behrmann [Tue, 4 Oct 2022 15:51:30 +0000 (17:51 +0200)]
Add remove_packages method to DistributionInstaller
This should probably live in PackageType, but there is a strong dependence
between package type and distribution and I haven't figured out a good way to do
this without cyclical imports.
Some are moved to backend for lack of a better place for now, most mount-related
functions are moved to a new mounts module, though some are left in __init__ for
later, when the dust settles and cyclic imports may become easier to avoid.
Command list and syntax is the most interesting part of the output.
But because of how argparse works, we were showing this part very
briefly and buried in other options. Instead of relying on the autogenerated
description, let's reuse the list from the man page.
Because a literal text is added, the lines for verbs and --help
and --version are suppressed.
I changed "Distribution" to "Distribution options" and so on for each
option group. The additional word connects the synopsis (which says
"[options…]" with the list below.
mkosi: drop ArgumentParserMkosi.fromfile_prefix_chars
mkosi is not usable as a library (all APIs are internal). So there is no need
to use a global variable for this. If we ever want to change it (and it's unlikely
that we ever would), we can just replace the character in the few places. The
code is much more readable without the indirection.
Add missing documentation about cmdlines for various verbs
The error message is changed to be more direct. If somebody doesn't know that
the parameters are passed to the command, "additional" would be unclear.
In the man page the verbs are listed by the order one would normally use them,
i.e. summary, then build, then qmeu/shell/boot/…, then clean, and the less
frequently used verbs last.
Descriptions are added to the commands which take parameters that they do that
and how those parameters are interpreted. In paricular, for 'boot' we accept
kernel commandline options, and for 'qemu' we only take qemu options. (Which is
understandable, given that we don't do a direct kernel boot, but not obvious.)
Georges Discry [Tue, 15 Nov 2022 10:19:42 +0000 (11:19 +0100)]
debian/ubuntu: Configure apt chroot with APT_CONFIG
When invoking apt switched from running inside the container to running
on the host, apt stopped reading the configuration files present in the
container. Because of this, it is not possible anymore to configure apt
by dropping a file in `mkosi.skeleton/etc/apt/apt.conf.d/`.
apt first loads its configuration files and then parse the command-line
options (so that they can override the options set in the files). While
setting `-o Dir=<container-root>` on the command-line has some impact on
the configuration of apt, it will not read the files in
`<container-root>/etc/apt/apt.conf.d/`, as apt has already read its
configuration files from the host with the configuration `Dir "/"`.
Rather than using the command-line, the APT_CONFIG environment variable
should be used to point to a file with the chroot configuration. When
set, that file is processed before anything else. By setting `Dir` this
way, apt will read the configuration files from the container and not
from the host.
As the host configuration is not read anymore, there is no more need to
mask `/etc/apt` on the host by bind-mounting an empty directory over it.
Georges Discry [Tue, 15 Nov 2022 11:22:31 +0000 (12:22 +0100)]
Use the right python when mkosi is a symlink
When the `mkosi` on the `PATH` is a symlink to the script installed
inside a virtualenv, the script fails to find the python executable
inside the virtualenv and falls back to the main python3. This is
notably the case when installing mkosi with pipx.
By first following the symlink inside the virtualenv, the script can
find the corresponding python executable.
Daan De Meyer [Mon, 14 Nov 2022 15:18:39 +0000 (16:18 +0100)]
Look in both the config file and the cwd for dropin files
A project might have multiple config files in subdirectories that
they use with --default but have shared configuration in the root
of the repository. When loading such a config file with --default,
we should still load shared dropins from the shared location.
Daan De Meyer [Mon, 14 Nov 2022 15:10:25 +0000 (16:10 +0100)]
Fix passing extra options to systemd-nspawn
systemd-nspawn ignores all options after the first argument so when
we're passing kernel command line arguments, we need to make sure
we pass the nspawn options first, otherwise they'll get ignored.
Joerg Behrmann [Tue, 8 Nov 2022 12:46:04 +0000 (13:46 +0100)]
Pass KERNEL_INSTALL_BYPASS through to the places where it is used
The idea of using KERNEL_INSTALL_BYPASS is to not run kernel-install twice
unnecessarily, since we run it later anyway, but a user reported a regression of
kernel-install not being run at all, although it apparently was in
earlier. Passing the environment variable through will at most run
kernel-install twice, this might be a workaround.
Joerg Behrmann [Fri, 28 Oct 2022 09:00:42 +0000 (11:00 +0200)]
Make load_args sideeffect free
It is unnecessary for the find_foo functions in load_args to generate and chown
directories, since for output and build directory this is already done by the
make_foo_dir functions in build_stuff. At a similar function for the cache
directory.
Having the find_foo functions make the directories has the downside of leading
to permission errors, when the tests are run with fakeroot as done by many
distro buildsystems.
mkosi: put various script-related status lines together
For some reason the build script was described quite far from the
other scripts. Since 19a989fdafe3b50b7c6629efa64e6e4b1fa0c31a the same
environment is used for all scripts, so the "Script Environment" line
is now under all scripts, since it applies to all of them.
mkosi: verify scripts after parsing config, show status in summary
We would refuse a script if not found or not executable immediately when
parsing the argument. But only the last specified script matters, so it's
better to delay the check: in initial parsing store the path, and later check
that it exists and matches the relevant criteria, at the time when we also
check the outputs.
Similarly for tree inputs: they are checked at this time too.
This fixes #1167.
Instead of trying to generate error messages, just reuse normal Python
exceptions: i.e. os.access() is replaced by open(). This way we don't need to
come up with error messages for various conditions and possibly get them wrong.
Rework status command to show status of inputs and scripts:
Bind-mount directories with nspawn's rootidmap option to prevent files
ownership discrepancies: files (and directories) created from within the
container in the mounted directory will be owned by the owner of the
directory on the backing filesystem.
This means, mkosi-generated directories and owner by any other user that
root won't be polluted by root-owned files and folders once the
container is stopped.
Change the owner of directories created by mkosi, unless --no-chown is
used. Directories owner will be set to SUDO_UID or PKEXEC_UID if
defined, or to current UID otherwise.
Daan De Meyer [Thu, 27 Oct 2022 08:09:45 +0000 (10:09 +0200)]
Add --noplugins when calling dnf
Let's isolate image builds using dnf from the host a bit more by
disabling all plugins. For example, plugins such as versionlock
can interfere with the version of packages installed in the image
which we want to avoid.
We build a precise error message, but then bury it under a wall of text
produced by print_usage(). The printing of help (or some subset of it) on
parsing error is just useless. Most likely the user made a typo in an option,
and printing a few dozen lines (and more in the future) of unhelpful
semi-related information is counterproductive.
I'm surprised that argparse doesn't make this configurable, but looking at the
code, it seems that the behaviour is hardcoded. Docs and stackoverflow also
yield no hints.
For interactive use, we have both short options and tab-completion.
Abbreviated options are problematic because of forward-compatibility:
if we add more options in the future, an older abbreviation might become
non-unique and stop working.
mkosi: do not build a temporary dictionary for kwargs
Let's use the usual method for passing mixed inherited and local keyword args.
We don't need to put the argument names in quotes so this looks nicer. Also,
if we were to make a mistake and pass the same kwargs in two places, previously
the local assignment would silently override the other value. But that seems
inverted because now the base class overwrites something specified by the daughter
class. Anyway, it's better to throw an error, which we now will do:
TypeError: argparse.ArgumentParser.__init__() got multiple values for keyword argument '…'
I used mkosi without any explicit distro/version parameters and was surprised
that it built for Fedora 36 after successfully detecting that it's running on
Fedora 37. Using the host version is more likely to be what users expect, and
also avoids the awkward issue that when we hardcode some default version, this
version is likely to be "wrong" (not the latest) until both the distro and we
make a release.
Looking at this more generally, if we have some constant config that doesn't
specify the distro version, we have three possible source of the version
default: host distro version, latest version that was known was mkosi was
released, and actual latest released distro version. It is nice to use the
first option, because that is what users generally expect, and also this makes
mkosi "stable", i.e. we may upgrade it at any time and users will not observe
unexpected changes in defaults. This makes mkosi closer to guidelines for
upgrades in stable distros, see
https://docs.fedoraproject.org/en-US/fesco/Updates_Policy/#stable-releases.
093d48a97b8211ff549f78f2b3b2ef77fcd45573 was aiming for "consistent results",
but this gives consistency with the wrong thing. For a normal user, the host
distro version would never change "unexpectedly", and in fact it is expected
that the defaults of programs change when the host distro is upgraded and not
at any other time. Mkosi itself should provide stable and backwards-compatible
behaviour over releases.
gsegatti [Wed, 19 Oct 2022 02:56:18 +0000 (23:56 -0300)]
Adding custom retry amount to Machine.run()
This PR aims to introduce a variable "retry_amount" passed via Machine's constructor.
This variable will define how many times a command will attempt to run when using a VM.
Default value remain as 30 if not explicitly set.
William Roberts [Wed, 26 Oct 2022 15:46:21 +0000 (10:46 -0500)]
ssh: fix copy_file for authorized_keys
When attempting to copy the public key to the remote images
authorized_keys file, the parent path does not exist and the following
exception is thrown[1]:
‣ Generating SSH key pair…
‣ (Unmounting image)
‣ (Detaching /dev/loop17)
Traceback (most recent call last):
File "/home/wcrobert/workspace/mkosi/mkosi/__init__.py", line 7357, in setup_ssh
copy_file(f"{f.name}.pub", authorized_keys)
File "/home/wcrobert/workspace/mkosi/mkosi/__init__.py", line 614, in copy_file
with open_close(newpath, os.O_WRONLY | os.O_CREAT | os.O_EXCL, st.st_mode) as newfd:
File "/usr/lib/python3.8/contextlib.py", line 113, in __enter__
return next(self.gen)
File "/home/wcrobert/workspace/mkosi/mkosi/__init__.py", line 571, in open_close
fd = os.open(path, flags | os.O_CLOEXEC, mode)
FileNotFoundError: [Errno 2] No such file or directory: '/var/tmp/mkosi-_0v_4emp/root/root/.ssh/authorized_keys'
The path only existed up to "/var/tmp/mkosi-_0v_4emp/root", thus a mkdir
with -p semantics is required to create the full path, add that in.
[1] Note line numbers are off due to having some print's scattered
through the code for debugging.
Fixes: #1238 Signed-off-by: William Roberts <william.c.roberts@intel.com>
Make inserted root or /usr partition at least as big as RootSize
When using SquashFS in particular, the size of the partition inserted will
always be the size of the SquashFS image. This change allows the partition
size to be set which allows for future growth.
Daan De Meyer [Fri, 14 Oct 2022 20:05:24 +0000 (22:05 +0200)]
Streamline basic config installation
Let's do our basic config installation a little earlier in the build
to allow users the opportunity to override it if needed. Also, rename
all the functions for more consistency.
Daan De Meyer [Fri, 14 Oct 2022 19:41:41 +0000 (21:41 +0200)]
Don't cache bootloader installation
bootctl might configure itself differently depending on what's on
the rest of the system. If we run it as part of the cached image
build, we're doing the installation with files missing that might
influence the end result, so let's not do the installation as part
of the cached image build.
Workaround for Debian based hosts shipping a patch to store the rpmdb under ~/.
The rpmdb is moved to where the guest expects it
(/usr/lib/sysimage/rpm or /var/lib/rpm)
So rpm on Debian has to be told to search in that location.
Joerg Behrmann [Mon, 3 Oct 2022 14:55:57 +0000 (16:55 +0200)]
Make SignExpectedPCR tri-valued
This makes SignEpectedPCR a tri-valued option using aspecial value "auto" in
addition to boolean values, where it will check whether cryptography is
importable and systemd-measure in the PATH and value True in that case and False
else.
For a True value it checks both conditions and fails hard if they are not met.
The checks are kept in the CLI definition so that what comes out stays a clean
boolean value and doesn't leak any decisions into layers further down. This
unfortunately necesitates a custom default value in the tests, so that they are
robust against what's installed on the system they run on and also needs to use
a function for the argparse type=, since actions are not called for every value.