Martin Pitt [Thu, 18 Jun 2026 04:38:44 +0000 (06:38 +0200)]
curl: Retry on failures
`curl()` is being used for things like fetching Fedora rawhide's GPG
key, opensuse's `/history/latest` or CentOS' `.composeinfo`. These are
all volatile and thus hard to pre-download/cache. But we've seen several
transient download failures like
> curl: (35) Recv failure: Connection reset by peer
Make these more robust with curl's retry (with exponential back-off)
mechanism.
Clayton Craft [Mon, 15 Jun 2026 23:31:47 +0000 (16:31 -0700)]
action: install apk to /usr/bin
/usr/local is cleaned up by ci.yml immediately after actions.yaml adds
apk into that dir, which leads to job that try to use apk to fail with
"apk not found"
Clayton Craft [Mon, 11 May 2026 21:27:14 +0000 (14:27 -0700)]
mkosi-vm: add systemd-efistub to postmarketOS config
On Alpine/pmOS, the EFI stub is split into a separate systemd-efistub
package rather than being bundled with systemd-boot, so it needs to
be added to the tools tree so mkosi can find it when building UKI/ESP
images.
Martin Pitt [Mon, 15 Jun 2026 08:52:44 +0000 (10:52 +0200)]
dnf: Work around librepo rejecting valid repomd signatures cross-distro
librepo 1.20 rejects valid repository metadata GPG signatures with gpgme
2.1 ("Bad GPG signature") [1]. This happens a lot when building a distro
which signs its repos (CentOS) on newer distros like Arch, Debian
testing, or openSUSE Tumbleweed, with their newer gpgme.
Thus until this is fixed, only validate repo GPG signatures when the
distributions match. We can assume that distros test their own dnf repos :-)
Revert once librepo gets fixed and into the distros.
mkosi only ever set `gpgcheck=1` (package signature verification) in the
generated dnf repository files and left `repo_gpgcheck` (repository
*metadata* signature verification) at the dnf default.
That default is not repository-aware. It is a global default of the dnf5
build doing the work, i.e. of the tools tree, not of the distribution
being built: Fedora's dnf5 defaults it to off, openSUSE's defaults it to
on. So whether repomd.xml.asc was verified depended on the tools tree
rather than on whether the repository actually publishes a signature,
and it ended up enabled for repositories that can never satisfy it.
Fedora and EPEL don't publish a repomd.xml.asc at all, and the CentOS
extras-common SIG repository signs it with the SIG key rather than the
main key we configure.
This stayed invisible because skip_if_unavailable was on by default too:
a repository that failed metadata verification was silently disabled and
makecache still succeeded. Now that we set `skip_if_unavailable=0`, those
failures are fatal, so the inherited default has to go.
Set `repo_gpgcheck` explicitly per repository instead. Default it to
"on", as repositories that do sign their metadata (e.g. CentOS BaseOS,
AppStream, CRB) benefit from it, and it should be an explicit opt-out
for new distros.
Explicitly turn it off for the ones that don't publish a verifiable
signature (Fedora, EPEL, CentOS extras-common). This makes the behaviour
deterministic regardless of the tools tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Martin Pitt [Mon, 15 Jun 2026 05:53:00 +0000 (07:53 +0200)]
dnf: Don't skip unavailable repositories during makecache
`sync()` currently often fails to retrieve the GPG signature and writes
a warning, but still returns successfully:
> Status code: 404 for https://.../fedora/../repodata/repomd.xml.asc
> GPG verification is enabled, but GPG signature is not available
The subsequent package installation then fails with something like
> Failed to resolve the transaction:
> No match for argument: basesystem
This is confusing. Let's rather fail early at the root cause. Some
distributions seem to enable `skip_if_unavailable=1` by default, so
disable that option.
This was even worse for repositories that publish metadata GPG
signatures (e.g. CentOS Stream): a transient failure to retrieve or
verify repomd.xml.asc would make dnf silently skip the entire repository
and return success with an unusable cache, which made the whole build
unreproducible and unreliable.
Jörg Behrmann [Sat, 13 Jun 2026 12:51:29 +0000 (14:51 +0200)]
tree: check for root when copying SELinux attributes as well
A user complained that the previous check
statfs(os.fspath(dst.parent)) != OVERLAYFS_SUPER_MAGIC or not tree_has_selinux_xattr(src)
worked for them when changing the or to and and
statfs(os.fspath(dst.parent)) != OVERLAYFS_SUPER_MAGIC and not tree_has_selinux_xattr(src)
thus always excluding extended attributes when the tree contained SELinux
attributes, implying that not being root was the issue. Therefore this commit
pulls our the check for not being root out of the IMA attribute check.
It's only conjecture, but it seems unlikely that copying IMA attributes to
overlayfs would work, when SELinux doesn't. This allows to make the whole
handling more symmetrical.
Luca Boccassi [Mon, 1 Jun 2026 22:16:40 +0000 (23:16 +0100)]
obs: enable PR CI workflow
Build packages on OBS against the PR and reports status back
to Github. This just builds the mkosi package for now, next
step is to also build particleos images.
Martin Pitt [Tue, 2 Jun 2026 09:21:17 +0000 (11:21 +0200)]
tests: Mark install tests and run them separately
The unit tests also run during package builds, but test_install.py
requires network access (venv creation) and is generally not necessary
for most code changes. This also speeds up running the unit tests
considerably.
Luca Boccassi [Mon, 1 Jun 2026 21:37:29 +0000 (22:37 +0100)]
Fix linter unit tests at package build time
Unit tests are broken as they expect a CI environment, but they are also
ran at package build time. Skip gracefully when the git or the
make-man-page.sh scripts are not available.
Martin Pitt [Mon, 1 Jun 2026 12:34:07 +0000 (14:34 +0200)]
Don't add El Torito boot catalog for BIOS/grub images by default
systemd-repart v261 adds an El Torito boot catalog, which writes an
ISO9660 header to the image. grub-bios-setup interprets that header as a
filesystem on the bare disk and fails:
> warning: Attempting to install GRUB to a disk with multiple partition labels. This is not supported yet
> Embedding is not possible. GRUB can only be installed in this setup by
. using blocklists. However, blocklists are UNRELIABLE and their use is
. discouraged
> error: will not proceed with blocklists
Adjust auto mode to only add the El Torito catalog when grub is not
installed as the BIOS bootloader (`Explicit ElTorito=yes` still forces
it on). This goes back to the status quo ante until systemd 260 for
grub/BIOS.
Martin Pitt [Thu, 28 May 2026 16:19:23 +0000 (18:19 +0200)]
tests: Move unit tests from GitHub workflow into pytest
Extract all CI linter/formatter/install checks from the GitHub workflow
into pytest tests. Workflows are hard to reproduce locally, and it's
much more convenient to reproduce what CI does with a simple `pytest`
command. Update the documentation and also stop recommending locally
installed `mypy` and friends -- let's always use the version from
`mkosi box` to get something halfway reproducible.
Keep the `sudo mkosi -h` test in the workflow, though -- it cannot run
inside `mkosi box` as that doesn't have `sudo`.
Some people like to run mypy and other linters from their own dev
environment. Skip tests whose tools aren't installed, but only if they
don't run in `mkosi box` -- in the latter (and in CI) we want to fail
tests, to avoid silently skipping tests due to accidentally dropping a
tool.
When building the box with Fedora instead of Arch, pyright fails:
> mkosi/sandbox.py:889:50 - error: "parent" is possibly unbound (reportPossiblyUnboundVariable)
> mkosi/sandbox.py:889:59 - error: "base" is possibly unbound (reportPossiblyUnboundVariable)
> mkosi/sandbox.py:899:52 - error: "parent" is possibly unbound (reportPossiblyUnboundVariable)
> mkosi/sandbox.py:899:61 - error: "base" is possibly unbound (reportPossiblyUnboundVariable)
This isn't a bug: The variables are only used when nofollow=True, but
pyright cannot infer this from the control flow. Just quiesce this by
initializing the variables.
Martin Pitt [Thu, 28 May 2026 16:04:36 +0000 (18:04 +0200)]
pytest: Restrict discovery to tests/
Configure pytest to only look in tests/ directory. That will prevent
picking up test files inside of built artifacts (like mkosi.output/ or mkosi.tools/).
Fixes this failure of `bin/mkosi box -- python3 -m pytest`:
> ImportError while importing test module 'mkosi/mkosi.tools/usr/lib/python3.14/site-packages/mypyc/test/test_run.py'.
> E ModuleNotFoundError: No module named 'distutils'
Paul Meyer [Fri, 22 May 2026 07:21:51 +0000 (09:21 +0200)]
finalize_scripts: tighten the PATH-strip condition to actual self-exec
The PATH-strip prelude exists to prevent `/scripts/<name>` from
recursing into itself when the script execs a binary named `<name>` via
PATH lookup. The current heuristic, `config.find_binary(name)`, checks a
broader condition: whether *some* binary called `<name>` exists in the
search path. That produces false positives for scripts that exec a
different binary entirely.
In particular, `mkosi-install` execs `dnf install $@`, expecting `dnf`
to resolve to the wrapped `/scripts/dnf` (which adds `--installroot=…`,
`--use-host-config`, `--setopt=…`). When the prelude gets emitted for
`mkosi-install` too, it strips `/scripts` from PATH before the exec, so
`dnf` resolves to the raw system binary and is invoked with none of the
wrapper's overrides.
Tighten the check: emit the prelude only when one of the exec'd argv
tokens is the unqualified string `name`, which is the only case where
PATH lookup could resolve back to `/scripts/<name>`.
Daan De Meyer [Tue, 26 May 2026 10:58:04 +0000 (10:58 +0000)]
Mount /etc/resolv.conf symlink into sandbox
Currently, if /etc/resolv.conf is a symlink, we
bind mount the actual file it points to into the
sandbox. Problem with this approach is that if the
file it points to is replaced on the host, creating
nested sandboxes will fail because bind mounting a
file whose source does not exist anymore fails with
ENOENT when you call mount.
To fix this (partially), make sure we bind mount the
/etc/resolv.conf symlink if it one. That way, on
resolved systems, we'll bind mount the /etc/resolv.conf
symlink and the /run/systemd/resolve directory into the
sandbox, with the latter containing the symlink target of
/etc/resolv.conf. Because we don't mount the target file
directly but its parent directory, if the file is replaced,
the sandbox will see the new file.
To make this work we add new --bind-nofollow and
--ro-bind-nofollow options to mkosi-sandbox.
Luca Boccassi [Tue, 26 May 2026 16:36:29 +0000 (17:36 +0100)]
nspawn: do not fail if --forward-journal is not available
--forward-journal is only available in 261, which means we cannot use the
current mkosi main in the systemd stable branches. Check the version and
fallback to the previous implementation if it's older.
Luca Boccassi [Tue, 26 May 2026 10:58:36 +0000 (11:58 +0100)]
tools: move grub-pc-bin to arch-specific drop-in
2026-05-26T09:06:43.9842316Z Package grub-pc-bin is not available, but is referred to by another package.
2026-05-26T09:06:43.9842760Z This may mean that the package is missing, has been obsoleted, or
2026-05-26T09:06:43.9843062Z is only available from another source
2026-05-26T09:06:43.9843221Z
2026-05-26T09:06:43.9867449Z E: Package 'grub-pc-bin' has no installation candidate
Michael Vogt [Wed, 13 May 2026 16:03:13 +0000 (18:03 +0200)]
fedora: allow Snapshot= for any kojipkgs-style mirror
The Snapshot= setting was gated to Release=rawhide and
Mirror=https://kojipkgs.fedoraproject.org with two hard die() checks,
but the URL builder underneath works for any release and any mirror
that mimics the koji compose layout.
So this commit drops the restrictions and just assuems a koji style
layout when snapshot is used. With that any koji style snapshot
can be used.
Daan De Meyer [Tue, 19 May 2026 20:15:49 +0000 (20:15 +0000)]
postmarketos: Persist fetched keyring across Contexts
Extract the fetched alpine-keys/postmarketos-keys apks straight into
context.keyring_dir so the trusted keys survive between Contexts (e.g.
when building an image after sync_repository_metadata). The per-Context
sandbox_tree is discarded between phases, so writing only there would
cause the keys to disappear before setup() needs them. setup() now does
all the arch-specific key selection, reading from
context.keyring_dir/usr/share/apk/keys/<arch>/ as well as the tools tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Daan De Meyer [Tue, 19 May 2026 14:33:21 +0000 (14:33 +0000)]
sandbox: split CLI and library entry points to fix console-script execution
When installed via pip/pipx/Nix, the mkosi-sandbox console-script calls
main() with __name__ == "mkosi.sandbox" rather than "__main__", so the
old is_main() check returned False and refused to execvp a trailing
command. Fix this by separating the two responsibilities: enter() is the
library function that sets up the sandbox in the current process and
returns any trailing argv, while main() is the CLI wrapper that calls
enter(), prints friendly error messages carried on a new SandboxOSError,
and execvp's the command. The console-script entry point points at
main() and works regardless of how it is invoked.
Fixes: https://github.com/systemd/mkosi/issues/4303 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Daan De Meyer [Tue, 19 May 2026 12:43:46 +0000 (12:43 +0000)]
Make full $PATH available when building tools tree
There's no risk of issues where stuff from $PATH expects
/usr from the host when building the tools tree or not using
it at all, so make the full $PATH available in that case.
This allows stuff like downloading apk to ~/.local/bin and
building postmarketos images.
To make this work we also have to mount /home into the sandbox.
This shouldn't be an issue generally. We don't expect tools to
accidentally pick stuff up from /home unless actually intended.
Daan De Meyer [Tue, 19 May 2026 13:01:15 +0000 (13:01 +0000)]
apk: Implement repository_key_fetch for the postmarketOS distribution
When repository_key_fetch is enabled, fetch the alpine-keys and postmarketos-keys
packages with --allow-untrusted, extract them, and copy the trusted public keys
into /etc/apk/keys/ in the sandbox tree so subsequent installs verify normally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Daan De Meyer [Sun, 17 May 2026 14:03:59 +0000 (14:03 +0000)]
mkosi-initrd: Trim orphaned GPU/audio modules, add ACPI platform attrs
The DRM and sound helpers (drm_buddy, drm_display_helper, ttm, intel-gtt,
mxm-wmi, snd-intel-dspcfg, snd-soc-hda-codec) had no actual KMS/audio
driver in the include list to pull them in, and there is no audio/GPU
userspace running in the initrd anyway. uvc and videobuf2-* are USB
webcam plumbing, also unused. Add firmware_attributes_class and
platform_profile so udev does not log autoload failures for those on
modern laptops.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Daan De Meyer [Thu, 14 May 2026 07:46:44 +0000 (09:46 +0200)]
mkosi-tools: Add fish to misc profile
Now that fish supports SHELL_PROMPT_PREFIX, it becomes more
viable again to run mkosi box -- /bin/fish since it will now
properly show that a box environment was entered. Hence let's
put it in the misc profile so that it is easily available in the
box environment.
Daan De Meyer [Thu, 14 May 2026 08:17:01 +0000 (08:17 +0000)]
box: Drop background tinting
Now that we have $SHELL_PROMPT_PREFIX integration,
let's drop the background tinting. Especially since it
doesn't quite nest all that well yet and we often invoke
nspawn and such from within mkosi box which does its own
background tinting.
Daan De Meyer [Mon, 11 May 2026 19:43:56 +0000 (21:43 +0200)]
vmspawn: Forward journal-remote settings to vmspawn
Pass the same MaxUse/KeepFree/MaxFileSize/MaxFiles settings to vmspawn
via its new --forward-journal-* options that we configure in
systemd-journal-remote.conf for qemu.
Daan De Meyer [Mon, 11 May 2026 19:47:19 +0000 (21:47 +0200)]
nspawn: Use --forward-journal instead of running journal-remote ourselves
systemd-nspawn now supports --forward-journal with matching
--forward-journal-max-use, --forward-journal-keep-free,
--forward-journal-max-file-size and --forward-journal-max-files options,
so let it run systemd-journal-remote itself instead of doing it ourselves
via a bind-mounted unix socket.
Clayton Craft [Fri, 8 May 2026 02:47:55 +0000 (19:47 -0700)]
apk: skip removal of packages that aren't installed
This fixes a failure when RemovePackages= listed package that wasn't
installed. The installed package database is queried first, and filter
to only packages that are actually present before calling apk del.
The query matches against both name and provides fields so packages
specified by a provider name are also found.
This omits armhf, which is armv6, since mkosi only knows a single
architecture for all 32-bit ARM variants. It is eventually going to be
removed upstream anyways, so let's not bother with it.
apk: Fix EBUSY in sync() when context.root is a mount point
sync() temporarily moved context.root out of the way to get an empty
directory to run apk against without touching the image root. This fails
when context.root is a mount point, e.g. when Overlay= is enabled:
Previously --cache-max-age was unconditionally applied in install()
and scripts(), suppressing metadata refresh even with CacheOnly=never.
This updates it to respect the CacheOnly config option instead.
This does not work as a per distribution CacheKey= means that
we can switch between distributions without invalidating caches,
meaning the output files won't be removed yet will still be those
of the previous distribution rather than the new one.
Revert for now until I figure out a better approach.
Avoid invalidating all caches on mid-build-failure recovery
Previously, repository_metadata_needs_sync() returned True whenever any
Cacheonly=auto image lacked a cache, which caused run_clean() to wipe
every image cache to avoid partial-upgrade scenarios. As a result, if
mkosi was building several images for the first time and one of them
failed mid-way, the next run would re-sync metadata, wipe the caches of
the images that had already succeeded, and rebuild everything from
scratch.
Use the manifest file as a "previously cached" marker — it's written
when an image builds successfully and survives even after the cache
contents go stale — and only re-sync metadata (and invalidate all
caches) when every incremental image was previously cached and at least
one of them is now out of date. In the mid-build-failure case, the image
that failed never wrote a manifest, so we don't trigger the re-sync and
the surviving caches are preserved.
For all-non-incremental setups we keep the previous behavior of always
syncing — there are no caches to preserve and every build downloads
packages. In a mixed setup, the non-incremental images are ignored by
the heuristic and may eventually fail because of stale metadata, but
that's the cost of mixing them with incremental images we want to
preserve.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nspawn: Drop logic to run systemd-repart on the image before booting
This bit still requires root and it's unlikely we'll ever be able to
make it work unprivileged. Instead we should probably do something with
mstack in the future instead. But for now let's drop this logic so that
booting (verity signed) disk images works unprivileged on systems with
nsresourced and mountfsd.
Fall back from nsresourced on UserNamespaceInterfaceNotSupported
This fixes an issue with running mkosi on postmarketOS, where mkosi
was failing if nsresourced was unable to initialize BPF support. The
error is treated the same as InvalidParameter so mkosi falls back to an
unprivileged user namespace when the kernel lacks BPF LSM support.
Work to enable BPF LSM support there is ongoing[1].
Build UKI when making directory images with Bootable=auto
qemu supports direct kernel booting UKI images with virtiofs as the
rootfs so let's make that work by building UKIs if we're building
directory images with Bootable=auto. Importantly, we build a UKI
without an initrd in that case so that the time consuming step of
building the initrd is still skipped.
Add DriveType= setting for qemu root disk device type
Add a new DriveType= setting that allows configuring the device type
used for the root disk when booting a virtual machine with qemu. The
supported types are virtio-blk (default, preserving existing behavior),
virtio-scsi, and nvme.
Previously, the only way to use nvme was by manually adding qemu device
arguments via QemuArgs=. This makes nvme a first-class option.
When Removable= is enabled, the drive type is forced to virtio-scsi
regardless of the DriveType= setting, preserving existing behavior.
Signed-off-by: Christian Brauner <brauner@kernel.org>