Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package mkosi for openSUSE:Factory checked 
in at 2025-02-03 21:43:52
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/mkosi (Old)
 and      /work/SRC/openSUSE:Factory/.mkosi.new.2316 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "mkosi"

Mon Feb  3 21:43:52 2025 rev:23 rq:1242148 version:25.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/mkosi/mkosi.changes      2025-01-24 
13:41:21.069003466 +0100
+++ /work/SRC/openSUSE:Factory/.mkosi.new.2316/mkosi.changes    2025-02-03 
21:44:44.829537131 +0100
@@ -1,0 +2,40 @@
+Sat Feb  1 18:25:18 UTC 2025 - Antonio Feijoo <[email protected]>
+
+- Update to 25.3:
+  * Use become_root_cmd() when running systemd-repart in run_shell() as well
+  * Use shutil.copy() to copy ovmf variables
+  * The dpkg architecture name for loongarch64 is loong64
+  * mkosi-initrd: Add two more modules
+  * Check if list matches are empty if empty string is matched against
+  * opensuse: fix package name: btrfs-progs -> btrfsprogs
+  * Log command line for abnormal signals
+  * zypper: set $releasever variable
+  * Tools tree improvements
+  * mkosi-initrd: handle PermissionError when reading /etc/crypttab
+  * Move want_uki() check out of build_uki_profiles()
+  * mkosi-sandbox: Improve formatting of error messages
+  * Fix verity signature check in case keys are configured
+  * Treat terminal as dumb if either stdout or stderr is not a tty
+  * Various cache fixes
+  * config: add mkosi-addon
+  * Calculate PE section size correctly
+  * Use directory in user's home as output directory if possible
+  * Fix condition when removing duplicate files from the overlay
+  * Make secure boot keys/crts/source config universal
+
+- Update to 25.2:
+  * Only parse profiles from subimages and includes if those are dirs
+  * Use all threads when relabelling files with setfiles
+
+- Update to 25.1:
+  * Remove depmod check in check_tools()
+  * news: fix typo detected by Lintian
+  * Create zipapp for mkosi sandbox like we do in generate-zipapp.sh
+  * man: document kernel baseline for mkosi
+  * sandbox: Show better error on ENOSYS
+  * Add fallback to sudo if run0 is not available
+  * Do not check uid in have_cache() for default tools tree
+  * Use resource_path() to access files in our own module
+  * Fix accessing "name" field in busctl json output
+
+-------------------------------------------------------------------

Old:
----
  mkosi-25.tar.gz

New:
----
  mkosi-25.3.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ mkosi.spec ++++++
--- /var/tmp/diff_new_pack.rpGxYh/_old  2025-02-03 21:44:45.501564870 +0100
+++ /var/tmp/diff_new_pack.rpGxYh/_new  2025-02-03 21:44:45.501564870 +0100
@@ -19,7 +19,7 @@
 %define pythons python3
 
 Name:           mkosi
-Version:        25
+Version:        25.3
 Release:        0
 Summary:        Build bespoke OS Images
 License:        LGPL-2.1-or-later

++++++ mkosi-25.tar.gz -> mkosi-25.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/.git new/mkosi-25.3/.git
--- old/mkosi-25/.git   2025-01-23 16:12:01.158991380 +0100
+++ new/mkosi-25.3/.git 2025-02-01 19:23:42.472847497 +0100
@@ -1 +1 @@
-gitdir: /home/afeijoo/src/mkosi/upstream-fork/main/.git/worktrees/mkosi-v25
+gitdir: /home/afeijoo/src/mkosi/upstream-fork/main/.git/worktrees/mkosi-25.3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/__init__.py 
new/mkosi-25.3/mkosi/__init__.py
--- old/mkosi-25/mkosi/__init__.py      2025-01-23 16:12:01.170991559 +0100
+++ new/mkosi-25.3/mkosi/__init__.py    2025-02-01 19:23:42.480847637 +0100
@@ -127,7 +127,7 @@
 )
 from mkosi.sysupdate import run_sysupdate
 from mkosi.tree import copy_tree, make_tree, move_tree, rmtree
-from mkosi.user import INVOKING_USER
+from mkosi.user import INVOKING_USER, become_root_cmd
 from mkosi.util import (
     PathString,
     current_home_dir,
@@ -139,6 +139,7 @@
     make_executable,
     one_zero,
     read_env_file,
+    resource_path,
     scopedenv,
 )
 from mkosi.versioncomp import GenericVersion
@@ -184,11 +185,24 @@
             rel = p.relative_to(context.root)
             q = context.workspace / "lower" / rel
 
-            if not q.is_symlink() and q.is_dir():
-                if p.is_symlink() or not p.is_dir():
-                    die(f"/{rel} is a directory in the base tree but not in 
the overlay")
+            if (
+                context.config.output_format == OutputFormat.sysext
+                and not rel.is_relative_to("usr")
+                and not rel.is_relative_to("opt")
+            ):
+                continue
+
+            if context.config.output_format == OutputFormat.confext and not 
rel.is_relative_to("etc"):
+                continue
+
+            if not q.is_symlink() and not q.exists():
+                continue
+
+            if not p.is_symlink() and p.is_dir():
+                if q.is_symlink() or not q.is_dir():
+                    die(f"/{rel} is a directory in the overlay but not in the 
base tree")
                 shutil.copystat(q, p)
-            elif q.is_symlink() or q.exists():
+            else:
                 logging.info(f"Removing duplicate path /{rel} from overlay")
                 p.unlink()
 
@@ -2058,7 +2072,7 @@
 
 
 def build_uki_profiles(context: Context, cmdline: Sequence[str]) -> list[Path]:
-    if not want_uki(context) or not 
context.config.unified_kernel_image_profiles:
+    if not context.config.unified_kernel_image_profiles:
         return []
 
     stub = systemd_addon_stub_binary(context)
@@ -2130,7 +2144,7 @@
 
     token = find_entry_token(context)
     cmdline = finalize_cmdline(context, partitions, 
finalize_roothash(partitions))
-    profiles = build_uki_profiles(context, cmdline)
+    profiles = build_uki_profiles(context, cmdline) if want_uki(context) else 
[]
 
     for kver, kimg in gen_kernel_images(context):
         if want_uki(context):
@@ -2655,9 +2669,6 @@
         if config.output_format == OutputFormat.none:
             return
 
-        if config.bootable != ConfigFeature.disabled:
-            check_tool(config, "depmod", reason="generate kernel module 
dependencies")
-
         if want_efi(config):
             if config.unified_kernel_image_profiles:
                 check_ukify(
@@ -3028,7 +3039,7 @@
 
     with complete_step(f"Relabeling files using {policy} policy"):
         run(
-            [setfiles, "-mFr", "/buildroot", "-c", binpolicy, fc, 
"/buildroot"],
+            [setfiles, "-mFr", "/buildroot", "-T0", "-c", binpolicy, fc, 
"/buildroot"],
             sandbox=context.sandbox(options=["--bind", context.root, 
"/buildroot"]),
             check=context.config.selinux_relabel == ConfigFeature.enabled,
         )
@@ -3045,6 +3056,7 @@
     final, build, manifest = cache_tree_paths(context.config)
 
     with complete_step("Installing cache copies"):
+        rmtree(final)
         move_tree(
             context.root,
             final,
@@ -3053,6 +3065,7 @@
         )
 
         if need_build_overlay(context.config) and (context.workspace / 
"build-overlay").exists():
+            rmtree(build)
             move_tree(
                 context.workspace / "build-overlay",
                 build,
@@ -3079,7 +3092,7 @@
         logging.debug(f"{final} does not exist, not reusing cached images")
         return False
 
-    if (uid := final.stat().st_uid) != os.getuid():
+    if config.image != "tools" and (uid := final.stat().st_uid) != os.getuid():
         logging.debug(
             f"{final} uid ({uid}) does not match user uid ({os.getuid()}), not 
reusing cached images"
         )
@@ -3114,10 +3127,12 @@
 
 
 def reuse_cache(context: Context) -> bool:
-    if not have_cache(context.config):
+    if not context.config.incremental or context.config.base_trees or 
context.config.overlay:
         return False
 
     final, build, _ = cache_tree_paths(context.config)
+    if not final.exists() or (need_build_overlay(context.config) and not 
build.exists()):
+        return False
 
     with complete_step("Copying cached trees"):
         copy_tree(
@@ -3240,9 +3255,11 @@
     logging.debug(json.dumps(output, indent=4))
 
     partitions = [Partition.from_dict(d) for d in output]
+    arch = context.config.architecture
 
     if context.config.verity == ConfigFeature.enabled and not any(
-        p.type.startswith("usr-verity-sig") or 
p.type.startswith("root-verity-sig") for p in partitions
+        p.type.startswith(f"usr-{arch}-verity-sig") or 
p.type.startswith(f"root-{arch}-verity-sig")
+        for p in partitions
     ):
         die(
             "Verity is explicitly enabled but didn't find any verity signature 
partition",
@@ -3899,11 +3916,24 @@
     with contextlib.ExitStack() as stack:
         if config.tools() != Path("/"):
             d = 
stack.enter_context(tempfile.TemporaryDirectory(prefix="mkosi-path-"))
-            zipapp.create_archive(
-                source=Path(__file__).parent,
-                target=Path(d) / "mkosi",
-                interpreter="/usr/bin/env python3",
-            )
+
+            # We have to point zipapp to a directory containing the mkosi 
module and set the entrypoint
+            # manually instead of directly at the mkosi package, otherwise we 
get ModuleNotFoundError when
+            # trying to run a zipapp created from a packaged version of mkosi. 
While zipapp.create_archive()
+            # supports a filter= argument, trying to use this within a 
site-packages directory is rather slow
+            # so we copy the mkosi package to a temporary directory instead 
which is much faster.
+            with (
+                tempfile.TemporaryDirectory(prefix="mkosi-zipapp-") as tmp,
+                resource_path(sys.modules[__package__ or __name__]) as module,
+            ):
+                copy_tree(module, Path(tmp) / module.name, 
sandbox=config.sandbox)
+                zipapp.create_archive(
+                    source=tmp,
+                    target=Path(d) / "mkosi",
+                    main="mkosi.__main__:main",
+                    interpreter="/usr/bin/env python3",
+                )
+
             make_executable(Path(d) / "mkosi")
             mounts += ["--ro-bind", d, "/mkosi"]
             stack.enter_context(scopedenv({"PATH": 
f"/mkosi:{os.environ['PATH']}"}))
@@ -3999,6 +4029,7 @@
                     network=True,
                     devices=True,
                     options=["--bind", fname, workdir(fname)],
+                    setup=become_root_cmd(),
                 ),
             )  # fmt: skip
 
@@ -4110,7 +4141,7 @@
                 network=True,
                 relaxed=True,
                 options=["--same-dir"],
-                setup=["run0"] if os.getuid() != 0 else [],
+                setup=become_root_cmd(),
             ),
         )
 
@@ -4150,7 +4181,7 @@
             network=True,
             devices=config.output_format == OutputFormat.disk,
             relaxed=True,
-            setup=["run0"] if need_root else [],
+            setup=become_root_cmd() if need_root else [],
         ),
     )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/bootloader.py 
new/mkosi-25.3/mkosi/bootloader.py
--- old/mkosi-25/mkosi/bootloader.py    2025-01-23 16:12:01.170991559 +0100
+++ new/mkosi-25.3/mkosi/bootloader.py  2025-02-01 19:23:42.484847706 +0100
@@ -277,6 +277,10 @@
 
     # TODO: Use ignore_padding=True instead of length once we can depend on a 
newer pefile.
     # TODO: Drop KeyError logic once we drop support for Ubuntu Jammy and 
sdmagic will always be available.
+    # Misc_VirtualSize is the section size in memory, which can be bigger or 
smaller than SizeOfRawData,
+    # which is the aligned section size on disk. The closest approximation of 
the actual section size will be
+    # the minimum of these two. If Misc_VirtualSize < SizeOfRawData, we'll get 
the actual size. Otherwise
+    # padding might be inclduded.
     pefile = textwrap.dedent(
         f"""\
         import pefile
@@ -286,7 +290,9 @@
         section = {{s.Name.decode().strip("\\0"): s for s in 
pe.sections}}.get("{section}")
         if not section:
             sys.exit(67)
-        
sys.stdout.buffer.write(section.get_data(length=section.Misc_VirtualSize))
+        sys.stdout.buffer.write(
+            section.get_data(length=min(section.Misc_VirtualSize, 
section.SizeOfRawData))
+        )
         """
     )
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/burn.py new/mkosi-25.3/mkosi/burn.py
--- old/mkosi-25/mkosi/burn.py  2025-01-23 16:12:01.170991559 +0100
+++ new/mkosi-25.3/mkosi/burn.py        2025-02-01 19:23:42.484847706 +0100
@@ -6,6 +6,7 @@
 from mkosi.config import Args, Config, OutputFormat
 from mkosi.log import complete_step, die
 from mkosi.run import run
+from mkosi.user import become_root_cmd
 
 
 def run_burn(args: Args, config: Config) -> None:
@@ -44,6 +45,6 @@
                 network=True,
                 relaxed=True,
                 options=["--same-dir"],
-                setup=["run0"] if os.getuid() != 0 else [],
+                setup=become_root_cmd(),
             ),
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/config.py 
new/mkosi-25.3/mkosi/config.py
--- old/mkosi-25/mkosi/config.py        2025-01-23 16:12:01.170991559 +0100
+++ new/mkosi-25.3/mkosi/config.py      2025-02-01 19:23:42.484847706 +0100
@@ -31,10 +31,10 @@
 from typing import Any, Callable, Generic, Optional, TypeVar, Union, cast
 
 from mkosi.distributions import Distribution, detect_distribution
-from mkosi.log import ARG_DEBUG, ARG_DEBUG_SANDBOX, ARG_DEBUG_SHELL, Style, die
+from mkosi.log import ARG_DEBUG, ARG_DEBUG_SANDBOX, ARG_DEBUG_SHELL, die
 from mkosi.pager import page
 from mkosi.run import SandboxProtocol, find_binary, nosandbox, run, 
sandbox_cmd, workdir
-from mkosi.sandbox import __version__
+from mkosi.sandbox import Style, __version__
 from mkosi.user import INVOKING_USER
 from mkosi.util import (
     PathString,
@@ -55,7 +55,7 @@
 ConfigMatchCallback = Callable[[str, T], bool]
 ConfigDefaultCallback = Callable[[argparse.Namespace], T]
 
-BUILTIN_CONFIGS = ("mkosi-tools", "mkosi-initrd", "mkosi-vm")
+BUILTIN_CONFIGS = ("mkosi-tools", "mkosi-initrd", "mkosi-vm", "mkosi-addon")
 
 
 class Verb(StrEnum):
@@ -738,6 +738,9 @@
 
 def config_make_list_matcher(parse: Callable[[str], T]) -> 
ConfigMatchCallback[list[T]]:
     def config_match_list(match: str, value: list[T]) -> bool:
+        if not match:
+            return len(value) == 0
+
         return parse(match) in value
 
     return config_match_list
@@ -2990,6 +2993,7 @@
         parse=config_parse_key,
         paths=("mkosi.key",),
         help="UEFI SecureBoot private key",
+        scope=SettingScope.universal,
     ),
     ConfigSetting(
         dest="secure_boot_key_source",
@@ -2998,6 +3002,7 @@
         parse=config_parse_key_source,
         default=KeySource(type=KeySourceType.file),
         help="The source to use to retrieve the secure boot signing key",
+        scope=SettingScope.universal,
     ),
     ConfigSetting(
         dest="secure_boot_certificate",
@@ -3006,6 +3011,7 @@
         parse=config_parse_certificate,
         paths=("mkosi.crt",),
         help="UEFI SecureBoot certificate in X509 format",
+        scope=SettingScope.universal,
     ),
     ConfigSetting(
         dest="secure_boot_certificate_source",
@@ -4151,7 +4157,7 @@
                 )
 
             with chdir(path if path.is_dir() else Path.cwd()):
-                self.parse_config_one(path if path.is_file() else Path("."), 
parse_profiles=True)
+                self.parse_config_one(path if path.is_file() else Path.cwd(), 
parse_profiles=p.is_dir())
 
     def finalize_value(self, setting: ConfigSetting[T]) -> Optional[T]:
         # If a value was specified on the CLI, it always takes priority. If 
the setting is a collection of
@@ -4255,9 +4261,6 @@
 
             v = self.expand_specifiers(v, path)
 
-            if not v:
-                die("Match value cannot be empty")
-
             if s := SETTINGS_LOOKUP_BY_NAME.get(k):
                 if not s.match:
                     die(f"{k} cannot be used in [{section}]")
@@ -4297,6 +4300,8 @@
         s: Optional[ConfigSetting[object]]  # Hint to mypy that we might 
assign None
         extras = path.is_dir()
 
+        assert path.is_absolute()
+
         if path.is_dir():
             path /= "mkosi.conf"
 
@@ -4310,7 +4315,7 @@
                     or (localpath := path.parent / "mkosi.local.conf").exists()
                 ):  # fmt: skip
                     with chdir(localpath if localpath.is_dir() else 
Path.cwd()):
-                        self.parse_config_one(localpath if localpath.is_file() 
else Path("."))
+                        self.parse_config_one(localpath if localpath.is_file() 
else Path.cwd())
 
                     # Local configuration should override other file based
                     # configuration but not the CLI itself so move the 
finalized
@@ -4367,10 +4372,9 @@
                             )
 
         if path.exists():
-            abs_path = Path.cwd() / path
-            logging.debug(f"Loading configuration file {abs_path}")
+            logging.debug(f"Loading configuration file {path}")
             files = getattr(self.config, "files")
-            files += [abs_path]
+            files += [path]
 
             for section, k, v in parse_ini(
                 path,
@@ -4412,17 +4416,19 @@
 
         if extras and (path.parent / "mkosi.conf.d").exists():
             for p in sorted((path.parent / "mkosi.conf.d").iterdir()):
+                p = p.absolute()
+
                 if p.is_dir() or p.suffix == ".conf":
                     with chdir(p if p.is_dir() else Path.cwd()):
-                        self.parse_config_one(p if p.is_file() else Path("."))
+                        self.parse_config_one(p if p.is_file() else Path.cwd())
 
         if parse_profiles:
             for profile in 
self.finalize_value(SETTINGS_LOOKUP_BY_DEST["profiles"]) or []:
                 for p in (Path(profile), Path(f"{profile}.conf")):
-                    p = Path("mkosi.profiles") / p
+                    p = Path.cwd() / "mkosi.profiles" / p
                     if p.exists():
                         with chdir(p if p.is_dir() else Path.cwd()):
-                            self.parse_config_one(p if p.is_file() else 
Path("."))
+                            self.parse_config_one(p if p.is_file() else 
Path.cwd())
 
         return True
 
@@ -4524,7 +4530,7 @@
 
     # Parse the global configuration unless the user explicitly asked us not 
to.
     if args.directory is not None:
-        context.parse_config_one(Path("."), parse_profiles=True, 
parse_local=True)
+        context.parse_config_one(Path.cwd(), parse_profiles=True, 
parse_local=True)
 
     config = copy.deepcopy(context.config)
 
@@ -4572,6 +4578,8 @@
         )
 
         for p in sorted(Path("mkosi.images").iterdir()):
+            p = p.absolute()
+
             if not p.is_dir() and not p.suffix == ".conf":
                 continue
 
@@ -4597,8 +4605,8 @@
 
             with chdir(p if p.is_dir() else Path.cwd()):
                 if not context.parse_config_one(
-                    p if p.is_file() else Path("."),
-                    parse_profiles=True,
+                    p if p.is_file() else Path.cwd(),
+                    parse_profiles=p.is_dir(),
                     parse_local=True,
                 ):
                     continue
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/distributions/debian.py 
new/mkosi-25.3/mkosi/distributions/debian.py
--- old/mkosi-25/mkosi/distributions/debian.py  2025-01-23 16:12:01.170991559 
+0100
+++ new/mkosi-25.3/mkosi/distributions/debian.py        2025-02-01 
19:23:42.484847706 +0100
@@ -114,7 +114,7 @@
             "mips"        : ["lib32", "lib64"],
             "mipsel"      : ["lib32", "lib64"],
             "mips64el"    : ["lib32", "lib64", "libo32"],
-            "loongarch64" : ["lib32", "lib64"],
+            "loong64"     : ["lib32", "lib64"],
             "powerpc"     : ["lib64"],
             "ppc64"       : ["lib32", "lib64"],
             "ppc64el"     : ["lib64"],
@@ -227,7 +227,7 @@
             Architecture.x86_64:      "amd64",
             Architecture.x86:         "i386",
             Architecture.ia64:        "ia64",
-            Architecture.loongarch64: "loongarch64",
+            Architecture.loongarch64: "loong64",
             Architecture.mips64_le:   "mips64el",
             Architecture.mips_le:     "mipsel",
             Architecture.parisc:      "hppa",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/initrd.py 
new/mkosi-25.3/mkosi/initrd.py
--- old/mkosi-25/mkosi/initrd.py        2025-01-23 16:12:01.170991559 +0100
+++ new/mkosi-25.3/mkosi/initrd.py      2025-02-01 19:23:42.484847706 +0100
@@ -3,6 +3,7 @@
 import argparse
 import contextlib
 import dataclasses
+import logging
 import os
 import platform
 import shutil
@@ -102,20 +103,23 @@
 
     # Generate crypttab with all the x-initrd.attach entries
     if Path("/etc/crypttab").exists():
-        crypttab = [
-            line
-            for line in Path("/etc/crypttab").read_text().splitlines()
-            if (
-                len(entry := line.split()) >= 4
-                and not entry[0].startswith("#")
-                and "x-initrd.attach" in entry[3]
-            )
-        ]
-        if crypttab:
-            with (Path(staging_dir) / "crypttab").open("w") as f:
-                f.write("# Automatically generated by mkosi-initrd\n")
-                f.write("\n".join(crypttab))
-            cmdline += ["--extra-tree", 
f"{staging_dir}/crypttab:/etc/crypttab"]
+        try:
+            crypttab = [
+                line
+                for line in Path("/etc/crypttab").read_text().splitlines()
+                if (
+                    len(entry := line.split()) >= 4
+                    and not entry[0].startswith("#")
+                    and "x-initrd.attach" in entry[3]
+                )
+            ]
+            if crypttab:
+                with (Path(staging_dir) / "crypttab").open("w") as f:
+                    f.write("# Automatically generated by mkosi-initrd\n")
+                    f.write("\n".join(crypttab))
+                cmdline += ["--extra-tree", 
f"{staging_dir}/crypttab:/etc/crypttab"]
+        except PermissionError:
+            logging.warning("Permission denied to access /etc/crypttab, the 
initrd may be unbootable")
 
     return cmdline
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/installer/zypper.py 
new/mkosi-25.3/mkosi/installer/zypper.py
--- old/mkosi-25/mkosi/installer/zypper.py      2025-01-23 16:12:01.170991559 
+0100
+++ new/mkosi-25.3/mkosi/installer/zypper.py    2025-02-01 19:23:42.484847706 
+0100
@@ -109,6 +109,7 @@
             "--cache-dir=/var/cache/zypp",
             "--non-interactive",
             "--no-refresh",
+            f"--releasever={context.config.release}",
             *(["--gpg-auto-import-keys"] if 
context.config.repository_key_fetch else []),
             *(["--no-gpg-checks"] if not context.config.repository_key_check 
else []),
             *([f"--plus-content={repo}" for repo in 
context.config.repositories]),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/log.py new/mkosi-25.3/mkosi/log.py
--- old/mkosi-25/mkosi/log.py   2025-01-23 16:12:01.170991559 +0100
+++ new/mkosi-25.3/mkosi/log.py 2025-02-01 19:23:42.484847706 +0100
@@ -6,7 +6,9 @@
 import os
 import sys
 from collections.abc import Iterator
-from typing import Any, Final, NoReturn, Optional
+from typing import Any, NoReturn, Optional
+
+from mkosi.sandbox import Style
 
 # This global should be initialized after parsing arguments
 ARG_DEBUG = contextvars.ContextVar("debug", default=False)
@@ -15,24 +17,6 @@
 LEVEL = 0
 
 
-def terminal_is_dumb() -> bool:
-    if not sys.stdout.isatty() and not sys.stderr.isatty():
-        return True
-
-    return os.getenv("TERM", "") == "dumb"
-
-
-class Style:
-    # fmt: off
-    bold: Final[str]   = "\033[0;1;39m"     if not terminal_is_dumb() else ""
-    blue: Final[str]   = "\033[0;1;34m"     if not terminal_is_dumb() else ""
-    gray: Final[str]   = "\033[0;38;5;245m" if not terminal_is_dumb() else ""
-    red: Final[str]    = "\033[31;1m"       if not terminal_is_dumb() else ""
-    yellow: Final[str] = "\033[33;1m"       if not terminal_is_dumb() else ""
-    reset: Final[str]  = "\033[0m"          if not terminal_is_dumb() else ""
-    # fmt: on
-
-
 def die(message: str, *, hint: Optional[str] = None) -> NoReturn:
     logging.error(f"{message}")
     if hint:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/qemu.py new/mkosi-25.3/mkosi/qemu.py
--- old/mkosi-25/mkosi/qemu.py  2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/qemu.py        2025-02-01 19:23:42.484847706 +0100
@@ -775,7 +775,7 @@
             if config.firmware_variables == Path("microsoft") or not 
config.firmware_variables
             else config.firmware_variables
         )
-        shutil.copy2(vars, ovmf_vars)
+        shutil.copy(vars, ovmf_vars)
 
     return ovmf_vars, ovmf_vars_format
 
@@ -999,7 +999,7 @@
         ).stdout.strip()
     )
 
-    return any(service.name == "org.freedesktop.machine1" for service in 
services)
+    return any(service["name"] == "org.freedesktop.machine1" for service in 
services)
 
 
 def finalize_register(config: Config) -> bool:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/resources/man/mkosi.1.md 
new/mkosi-25.3/mkosi/resources/man/mkosi.1.md
--- old/mkosi-25/mkosi/resources/man/mkosi.1.md 2025-01-23 16:12:01.174991619 
+0100
+++ new/mkosi-25.3/mkosi/resources/man/mkosi.1.md       2025-02-01 
19:23:42.484847706 +0100
@@ -1256,9 +1256,6 @@
     | `diffutils`             | ✓      | ✓      | ✓      | ✓    | ✓  
    | ✓    | ✓        |
     | `distribution-gpg-keys` | ✓      | ✓      | ✓      | ✓    |      
  | ✓    | ✓        |
     | `dnf`                   | ✓      | ✓      | ✓      | ✓    | ✓  
    | ✓    | ✓        |
-    | `dnf-plugins-core`      | ✓      | ✓      |        |      |        | 
     | ✓        |
-    | `dnf5`                  | ✓      |        |        |      |        |   
   | ✓        |
-    | `dnf5-plugins`          | ✓      |        |        |      |        |   
   | ✓        |
     | `dosfstools`            | ✓      | ✓      | ✓      | ✓    | ✓  
    | ✓    | ✓        |
     | `e2fsprogs`             | ✓      | ✓      | ✓      | ✓    | ✓  
    | ✓    | ✓        |
     | `edk2-ovmf`             | ✓      | ✓      | ✓      | ✓    | ✓  
    | ✓    | ✓        |
@@ -2705,12 +2702,18 @@
 - `ToolsTree=`
 - `ToolsTreeCertificates=`
 - `UseSubvolumes=`
+- `SecureBootCertificate=`
+- `SecureBootCertificateSource=`
+- `SecureBootKey=`
+- `SecureBootKeySource=`
 - `VerityCertificate=`
+- `VerityCertificateSource=`
 - `VerityKey=`
 - `VerityKeySource=`
 - `SignExpectedPcrCertificate=`
+- `SignExpectedPcrCertificateSource=`
 - `SignExpectedPcrKey=`
-- `SignExpectedPcrSource=`
+- `SignExpectedPcrKeySource=`
 - `VolatilePackageDirectories=`
 - `WithNetwork=`
 - `WithTests`
@@ -2885,6 +2888,8 @@
 very out of date. We currently recommend running **mkosi** from git until a
 new release happens.
 
+mkosi requires a Linux kernel that provides `mount_setattr()` which was 
introduces in 5.12.
+
 mkosi currently requires systemd 254 to build bootable disk images.
 
 When not using distribution packages make sure to install the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/resources/man/mkosi.news.7.md 
new/mkosi-25.3/mkosi/resources/man/mkosi.news.7.md
--- old/mkosi-25/mkosi/resources/man/mkosi.news.7.md    2025-01-23 
16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/resources/man/mkosi.news.7.md  2025-02-01 
19:23:42.484847706 +0100
@@ -72,7 +72,7 @@
 - `run0` is now automatically used to escalate privileges for commands that 
need it, like the `burn` verb.
 - `/usr/share/keyrings` and `/usr/share/distribution-gpg-keys` are no longer 
automatically picked up from the
   tools tree when `ToolsTreeCertificates=` is set, since they aren't 
certificates, use a sandbox tree
-  instead. This allows to override `SignedBy=` keys for APT repositories.
+  instead. This allows one to override `SignedBy=` keys for APT repositories.
 - The `agetty.autologin` and `login.noauth` credentials are no longer set 
unconditionally.
 - Access to the output directory in build scripts was removed. To put artifacts
   from the build directory into the output directory, copy them from the build 
directory
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/resources/mkosi-initrd/mkosi.conf 
new/mkosi-25.3/mkosi/resources/mkosi-initrd/mkosi.conf
--- old/mkosi-25/mkosi/resources/mkosi-initrd/mkosi.conf        2025-01-23 
16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/resources/mkosi-initrd/mkosi.conf      2025-02-01 
19:23:42.484847706 +0100
@@ -71,8 +71,10 @@
                     /loop.ko
                     /mdio_devres.ko
                     /mei.ko
+                    /mxm-wmi.ko
                     /nvme.ko
                     /overlay.ko
+                    /parport.ko
                     /pmt_telemetry.ko
                     /qemu_fw_cfg.ko
                     /raid[0-9]*.ko
@@ -84,6 +86,7 @@
                     /snd-intel-dspcfg.ko
                     /snd-soc-hda-codec.ko
                     /squashfs.ko
+                    /usb-storage.ko
                     /vfat.ko
                     /virtio_balloon.ko
                     /virtio_blk.ko
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-initrd/mkosi.conf.d/10-opensuse.conf 
new/mkosi-25.3/mkosi/resources/mkosi-initrd/mkosi.conf.d/10-opensuse.conf
--- old/mkosi-25/mkosi/resources/mkosi-initrd/mkosi.conf.d/10-opensuse.conf     
2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/resources/mkosi-initrd/mkosi.conf.d/10-opensuse.conf   
2025-02-01 19:23:42.484847706 +0100
@@ -22,7 +22,7 @@
         libtss2-tcti-device0
 
         # File system checkers for supported root file systems
-        btrfs-progs
+        btrfsprogs
         e2fsprogs
         xfsprogs
         erofs-utils
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf
--- old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf 2025-01-23 
16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf       2025-02-01 
19:23:42.484847706 +0100
@@ -20,7 +20,6 @@
         cpio
         curl
         diffutils
-        dnf
         dosfstools
         e2fsprogs
         findutils
@@ -41,5 +40,4 @@
         tar
         util-linux
         xfsprogs
-        zsh
         zstd
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-arch.conf 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-arch.conf
--- old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-arch.conf  
2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-arch.conf        
2025-02-01 19:23:42.484847706 +0100
@@ -12,10 +12,10 @@
         createrepo_c
         debian-archive-keyring
         distribution-gpg-keys
+        dnf
         dpkg
         edk2-ovmf
         erofs-utils
-        fish
         git
         grub
         libseccomp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-azure-centos-fedora/mkosi.conf
 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-azure-centos-fedora/mkosi.conf
--- 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-azure-centos-fedora/mkosi.conf
     2025-01-23 16:12:01.174991619 +0100
+++ 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-azure-centos-fedora/mkosi.conf
   2025-02-01 19:23:42.484847706 +0100
@@ -11,7 +11,6 @@
 [Content]
 Packages=
         createrepo_c
-        dnf-plugins-core
         git-core
         grub2-tools
         libseccomp
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf
--- old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf  
2025-01-23 16:12:01.174991619 +0100
+++ 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf    
    2025-02-01 19:23:42.484847706 +0100
@@ -5,4 +5,6 @@
 
 [Content]
 Packages=
+        dnf
+        dnf-plugins-core
         perf
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf.d/20-epel-packages-10.conf
 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf.d/20-epel-packages-10.conf
--- 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf.d/20-epel-packages-10.conf
       2025-01-23 16:12:01.174991619 +0100
+++ 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf.d/20-epel-packages-10.conf
     2025-02-01 19:23:42.484847706 +0100
@@ -8,4 +8,3 @@
 Packages=
         btrfs-progs
         distribution-gpg-keys
-        fish
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf.d/20-epel-packages-9.conf
 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf.d/20-epel-packages-9.conf
--- 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf.d/20-epel-packages-9.conf
        2025-01-23 16:12:01.174991619 +0100
+++ 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-centos/mkosi.conf.d/20-epel-packages-9.conf
      2025-02-01 19:23:42.488847776 +0100
@@ -11,7 +11,6 @@
         btrfs-progs
         debian-keyring
         distribution-gpg-keys
-        fish
         pacman
         sbsigntools
         ubu-keyring
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-debian-kali-ubuntu/mkosi.conf
 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-debian-kali-ubuntu/mkosi.conf
--- 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-debian-kali-ubuntu/mkosi.conf
      2025-01-23 16:12:01.174991619 +0100
+++ 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-debian-kali-ubuntu/mkosi.conf
    2025-02-01 19:23:42.488847776 +0100
@@ -16,8 +16,8 @@
         btrfs-progs
         createrepo-c
         debian-archive-keyring
+        dnf
         erofs-utils
-        fish
         git-core
         grub-common
         libarchive-tools
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-fedora/mkosi.conf 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-fedora/mkosi.conf
--- old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-fedora/mkosi.conf  
2025-01-23 16:12:01.174991619 +0100
+++ 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-fedora/mkosi.conf    
    2025-02-01 19:23:42.488847776 +0100
@@ -13,7 +13,6 @@
         dnf5
         dnf5-plugins
         erofs-utils
-        fish
         pacman
         perf
         pkcs11-provider
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf 
new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf
--- old/mkosi-25/mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf      
2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf    
2025-02-01 19:23:42.488847776 +0100
@@ -5,15 +5,13 @@
 
 [Content]
 Packages=
-        btrfs-progs
+        btrfsprogs
         ca-certificates-mozilla
         createrepo_c
         distribution-gpg-keys
-        dnf-plugins-core
         dnf5
         dnf5-plugins
         erofs-utils
-        fish
         git-core
         glibc-gconv-modules-extra
         grep
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/run.py new/mkosi-25.3/mkosi/run.py
--- old/mkosi-25/mkosi/run.py   2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/run.py 2025-02-01 19:23:42.488847776 +0100
@@ -20,10 +20,9 @@
 from types import TracebackType
 from typing import TYPE_CHECKING, Any, Callable, NoReturn, Optional, Protocol
 
-import mkosi.sandbox
 from mkosi.log import ARG_DEBUG, ARG_DEBUG_SANDBOX, ARG_DEBUG_SHELL, die
 from mkosi.sandbox import acquire_privileges, joinpath, umask
-from mkosi.util import _FILE, PathString, current_home_dir, flatten, one_zero, 
unique
+from mkosi.util import _FILE, PathString, current_home_dir, flatten, one_zero, 
resource_path, unique
 
 SD_LISTEN_FDS_START = 3
 
@@ -127,8 +126,13 @@
 
 
 def log_process_failure(sandbox: Sequence[str], cmdline: Sequence[str], 
returncode: int) -> None:
-    if returncode < 0:
+    if -returncode in (signal.SIGINT, signal.SIGTERM):
         logging.error(f"Interrupted by {signal.Signals(-returncode).name} 
signal")
+    elif returncode < 0:
+        logging.error(
+            f'"{shlex.join([*sandbox, *cmdline] if ARG_DEBUG.get() else 
cmdline)}"'
+            f" was killed by {signal.Signals(-returncode).name} signal."
+        )
     elif returncode == 127:
         logging.error(f"{cmdline[0]} not found.")
     else:
@@ -559,102 +563,106 @@
 ) -> Iterator[list[PathString]]:
     assert not (overlay and relaxed)
 
-    cmdline: list[PathString] = [
-        *setup,
-        *(["strace", "--detach-on=execve"] if ARG_DEBUG_SANDBOX.get() else []),
-        sys.executable, "-SI", mkosi.sandbox.__file__,
-        "--proc", "/proc",
-        # We mounted a subdirectory of TMPDIR to /var/tmp so we unset TMPDIR 
so that /tmp or /var/tmp are
-        # used instead.
-        "--unsetenv", "TMPDIR",
-        *network_options(network=network),
-    ]  # fmt: skip
+    with contextlib.ExitStack() as stack:
+        module = stack.enter_context(resource_path(sys.modules[__package__ or 
__name__]))
 
-    if overlay and (overlay / "usr").exists():
-        cmdline += [
-            "--overlay-lowerdir", tools / "usr",
-            "--overlay-lowerdir", overlay / "usr",
-            "--overlay", "/usr",
+        cmdline: list[PathString] = [
+            *setup,
+            *(["strace", "--detach-on=execve"] if ARG_DEBUG_SANDBOX.get() else 
[]),
+            sys.executable, "-SI", module / "sandbox.py",
+            "--proc", "/proc",
+            # We mounted a subdirectory of TMPDIR to /var/tmp so we unset 
TMPDIR so that /tmp or /var/tmp are
+            # used instead.
+            "--unsetenv", "TMPDIR",
+            *network_options(network=network),
         ]  # fmt: skip
-    else:
-        cmdline += ["--ro-bind", tools / "usr", "/usr"]
-
-    for d in ("bin", "sbin", "lib", "lib32", "lib64"):
-        if (p := tools / d).is_symlink():
-            cmdline += ["--symlink", p.readlink(), Path("/") / 
p.relative_to(tools)]
-        elif p.is_dir():
-            cmdline += ["--ro-bind", p, Path("/") / p.relative_to(tools)]
-
-    # If we're using /usr from a tools tree, we have to use /etc/alternatives 
and /etc/ld.so.cache from the
-    # tools tree as well if they exists since those are directly related to 
/usr. In relaxed mode, we only do
-    # this if the mountpoint already exists on the host as otherwise we'd 
modify the host's /etc by creating
-    # the mountpoint ourselves (or fail when trying to create it).
-    for p in (Path("etc/alternatives"), Path("etc/ld.so.cache")):
-        if (tools / p).exists() and (not relaxed or (Path("/") / p).exists()):
-            cmdline += ["--ro-bind", tools / p, Path("/") / p]
 
-    if (tools / "nix/store").exists():
-        cmdline += ["--bind", tools / "nix/store", "/nix/store"]
-
-    if relaxed:
-        for p in Path("/").iterdir():
-            if p not in (
-                Path("/home"),
-                Path("/proc"),
-                Path("/usr"),
-                Path("/nix"),
-                Path("/bin"),
-                Path("/sbin"),
-                Path("/lib"),
-                Path("/lib32"),
-                Path("/lib64"),
-            ):
-                if p.is_symlink():
-                    cmdline += ["--symlink", p.readlink(), p]
-                else:
-                    cmdline += ["--bind", p, p]
-
-            # /etc might be full of symlinks to /usr/share/factory, so make 
sure we use /usr/share/factory
-            # from the host and not from the tools tree.
-            if (
-                tools != Path("/")
-                and (tools / "usr/share/factory").exists()
-                and (factory := Path("/usr/share/factory")).exists()
-            ):
-                cmdline += ["--bind", factory, factory]
+        if overlay and (overlay / "usr").exists():
+            cmdline += [
+                "--overlay-lowerdir", tools / "usr",
+                "--overlay-lowerdir", overlay / "usr",
+                "--overlay", "/usr",
+            ]  # fmt: skip
+        else:
+            cmdline += ["--ro-bind", tools / "usr", "/usr"]
 
-        if home := current_home_dir():
-            cmdline += ["--bind", home, home]
-    else:
-        cmdline += [
-            "--dir", "/var/tmp",
-            "--dir", "/var/log",
-            "--unshare-ipc",
-            # apivfs_script_cmd() and chroot_script_cmd() are executed from 
within the sandbox, but they
-            # still use sandbox.py, so we make sure it is available inside the 
sandbox so it can be executed
-            # there as well.
-            "--ro-bind", Path(mkosi.sandbox.__file__), "/sandbox.py",
-        ]  # fmt: skip
+        for d in ("bin", "sbin", "lib", "lib32", "lib64"):
+            if (p := tools / d).is_symlink():
+                cmdline += ["--symlink", p.readlink(), Path("/") / 
p.relative_to(tools)]
+            elif p.is_dir():
+                cmdline += ["--ro-bind", p, Path("/") / p.relative_to(tools)]
+
+        # If we're using /usr from a tools tree, we have to use 
/etc/alternatives and /etc/ld.so.cache from
+        # the tools tree as well if they exists since those are directly 
related to /usr. In relaxed mode, we
+        # only do this if the mountpoint already exists on the host as 
otherwise we'd modify the host's /etc
+        # by creating the mountpoint ourselves (or fail when trying to create 
it).
+        for p in (Path("etc/alternatives"), Path("etc/ld.so.cache")):
+            if (tools / p).exists() and (not relaxed or (Path("/") / 
p).exists()):
+                cmdline += ["--ro-bind", tools / p, Path("/") / p]
+
+        if (tools / "nix/store").exists():
+            cmdline += ["--bind", tools / "nix/store", "/nix/store"]
+
+        if relaxed:
+            for p in Path("/").iterdir():
+                if p not in (
+                    Path("/home"),
+                    Path("/proc"),
+                    Path("/usr"),
+                    Path("/nix"),
+                    Path("/bin"),
+                    Path("/sbin"),
+                    Path("/lib"),
+                    Path("/lib32"),
+                    Path("/lib64"),
+                ):
+                    if p.is_symlink():
+                        cmdline += ["--symlink", p.readlink(), p]
+                    else:
+                        cmdline += ["--bind", p, p]
+
+                # /etc might be full of symlinks to /usr/share/factory, so 
make sure we use
+                # /usr/share/factory from the host and not from the tools tree.
+                if (
+                    tools != Path("/")
+                    and (tools / "usr/share/factory").exists()
+                    and (factory := Path("/usr/share/factory")).exists()
+                ):
+                    cmdline += ["--bind", factory, factory]
 
-        if devices:
-            cmdline += ["--bind", "/sys", "/sys", "--bind", "/dev", "/dev"]
+            if home := current_home_dir():
+                cmdline += ["--bind", home, home]
         else:
-            cmdline += ["--dev", "/dev"]
+            cmdline += [
+                "--dir", "/var/tmp",
+                "--dir", "/var/log",
+                "--unshare-ipc",
+                # apivfs_script_cmd() and chroot_script_cmd() are executed 
from within the sandbox, but they
+                # still use sandbox.py, so we make sure it is available inside 
the sandbox so it can be
+                # executed there as well.
+                "--ro-bind", module / "sandbox.py", "/sandbox.py",
+            ]  # fmt: skip
 
-        if network:
-            for p in (Path("/etc/resolv.conf"), Path("/run/systemd/resolve")):
-                if p.exists():
-                    cmdline += ["--ro-bind", p, p]
-
-        home = None
+            if devices:
+                cmdline += ["--bind", "/sys", "/sys", "--bind", "/dev", "/dev"]
+            else:
+                cmdline += ["--dev", "/dev"]
 
-    path = finalize_path(root=tools, extra=[Path("/scripts"), *extra] if 
scripts else extra, relaxed=relaxed)
-    cmdline += ["--setenv", "PATH", path]
+            if network:
+                for p in (Path("/etc/resolv.conf"), 
Path("/run/systemd/resolve")):
+                    if p.exists():
+                        cmdline += ["--ro-bind", p, p]
+
+        path = finalize_path(
+            root=tools,
+            extra=[Path("/scripts"), *extra] if scripts else extra,
+            relaxed=relaxed,
+        )
+        cmdline += ["--setenv", "PATH", path]
 
-    if scripts:
-        cmdline += ["--ro-bind", scripts, "/scripts"]
+        if scripts:
+            cmdline += ["--ro-bind", scripts, "/scripts"]
 
-    with contextlib.ExitStack() as stack:
         tmp: Optional[Path]
 
         if not overlay and not relaxed:
@@ -739,23 +747,23 @@
     network: bool = False,
     options: Sequence[PathString] = (),
 ) -> Iterator[list[PathString]]:
-    cmdline: list[PathString] = [
-        sys.executable, "-SI", mkosi.sandbox.__file__,
-        "--bind", root, "/",
-        # We mounted a subdirectory of TMPDIR to /var/tmp so we unset TMPDIR 
so that /tmp or /var/tmp are
-        # used instead.
-        "--unsetenv", "TMPDIR",
-        *network_options(network=network),
-        *apivfs_options(root=Path("/")),
-        *chroot_options(),
-    ]  # fmt: skip
+    with vartmpdir() as dir, resource_path(sys.modules[__package__ or 
__name__]) as module:
+        cmdline: list[PathString] = [
+            sys.executable, "-SI", module / "sandbox.py",
+            "--bind", root, "/",
+            # We mounted a subdirectory of TMPDIR to /var/tmp so we unset 
TMPDIR so that /tmp or /var/tmp are
+            # used instead.
+            "--unsetenv", "TMPDIR",
+            *network_options(network=network),
+            *apivfs_options(root=Path("/")),
+            *chroot_options(),
+        ]  # fmt: skip
 
-    if network:
-        for p in (Path("/etc/resolv.conf"), Path("/run/systemd/resolve")):
-            if p.exists():
-                cmdline += ["--ro-bind", p, p]
+        if network:
+            for p in (Path("/etc/resolv.conf"), Path("/run/systemd/resolve")):
+                if p.exists():
+                    cmdline += ["--ro-bind", p, p]
 
-    with vartmpdir() as dir:
         yield [*cmdline, "--bind", dir, "/var/tmp", *options]
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/sandbox.py 
new/mkosi-25.3/mkosi/sandbox.py
--- old/mkosi-25/mkosi/sandbox.py       2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/sandbox.py     2025-02-01 19:23:42.488847776 +0100
@@ -13,7 +13,7 @@
 import sys
 import warnings  # noqa: F401 (loaded lazily by os.execvp() which happens too 
late)
 
-__version__ = "25"
+__version__ = "25.3"
 
 # The following constants are taken from the Linux kernel headers.
 AT_EMPTY_PATH = 0x1000
@@ -30,6 +30,7 @@
 CLONE_NEWUSER = 0x10000000
 EPERM = 1
 ENOENT = 2
+ENOSYS = 38
 F_GETFD = 1
 F_SETFD = 2
 FD_CLOEXEC = 1
@@ -99,13 +100,38 @@
 libc.fcntl.argtypes = (ctypes.c_int, ctypes.c_int, ctypes.c_int)
 
 
-def oserror(filename: str = "") -> None:
+def terminal_is_dumb() -> bool:
+    return not sys.stdout.isatty() or not sys.stderr.isatty() or 
os.getenv("TERM", "") == "dumb"
+
+
+class Style:
+    # fmt: off
+    bold: str   = "\033[0;1;39m"     if not terminal_is_dumb() else ""
+    blue: str   = "\033[0;1;34m"     if not terminal_is_dumb() else ""
+    gray: str   = "\033[0;38;5;245m" if not terminal_is_dumb() else ""
+    red: str    = "\033[31;1m"       if not terminal_is_dumb() else ""
+    yellow: str = "\033[33;1m"       if not terminal_is_dumb() else ""
+    reset: str  = "\033[0m"          if not terminal_is_dumb() else ""
+    # fmt: on
+
+
+ENOSYS_MSG = f"""\
+{Style.red}mkosi was unable to invoke the {{syscall}}() system 
call.{Style.reset}
+This probably means either the system call is not implemented by the running 
kernel version ({{kver}}) or the
+system call is prohibited via seccomp if mkosi is being executed inside a 
containerized environment.\
+"""
+
+
+def oserror(syscall: str, filename: str = "") -> None:
+    if ctypes.get_errno() == ENOSYS:
+        print(ENOSYS_MSG.format(syscall=syscall, kver=os.uname().version), 
file=sys.stderr)
+
     raise OSError(ctypes.get_errno(), os.strerror(ctypes.get_errno()), 
filename or None)
 
 
 def unshare(flags: int) -> None:
     if libc.unshare(flags) < 0:
-        oserror()
+        oserror("unshare")
 
 
 def statfs(path: str) -> int:
@@ -115,7 +141,7 @@
     buffer = (ctypes.c_long * 15)()
 
     if libc.statfs(path.encode(), ctypes.byref(buffer)) < 0:
-        oserror(path)
+        oserror("statfs", path)
 
     return int(buffer[0])
 
@@ -125,12 +151,12 @@
     typeb = type.encode() if type else None
     optionsb = options.encode() if options else None
     if libc.mount(srcb, dst.encode(), typeb, flags, optionsb) < 0:
-        oserror(dst)
+        oserror("mount", dst)
 
 
 def umount2(path: str, flags: int = 0) -> None:
     if libc.umount2(path.encode(), flags) < 0:
-        oserror(path)
+        oserror("umount2", path)
 
 
 def cap_permitted_to_ambient() -> None:
@@ -146,13 +172,13 @@
     payload = (cap_user_data_t * LINUX_CAPABILITY_U32S_3)()
 
     if libc.capget(ctypes.addressof(header), ctypes.byref(payload)) < 0:
-        oserror()
+        oserror("capget")
 
     payload[0].inheritable = payload[0].permitted
     payload[1].inheritable = payload[1].permitted
 
     if libc.capset(ctypes.addressof(header), ctypes.byref(payload)) < 0:
-        oserror()
+        oserror("capset")
 
     effective = payload[1].effective << 32 | payload[0].effective
 
@@ -166,7 +192,7 @@
             break
 
         if effective & (1 << cap) and libc.prctl(PR_CAP_AMBIENT, 
PR_CAP_AMBIENT_RAISE, cap, 0, 0) < 0:
-            oserror()
+            oserror("prctl")
 
 
 def have_effective_cap(capability: int) -> bool:
@@ -225,7 +251,7 @@
 
     keyring = libkeyutils.keyctl_join_session_keyring(None)
     if keyring == -1:
-        oserror()
+        oserror("keyctl")
 
 
 def mount_rbind(src: str, dst: str, attrs: int = 0) -> None:
@@ -245,7 +271,7 @@
         fd = libc.syscall(NR_open_tree, AT_FDCWD, src.encode(), flags)
 
     if fd < 0:
-        oserror(src)
+        oserror("open_tree", src)
 
     try:
         attr = mount_attr()
@@ -274,7 +300,7 @@
             r = libc.syscall(NR_mount_setattr, fd, b"", flags, 
ctypes.addressof(attr), MOUNT_ATTR_SIZE_VER0)
 
         if r < 0:
-            oserror(src)
+            oserror("mount_setattr", src)
 
         try:
             libc.move_mount.argtypes = (
@@ -297,7 +323,7 @@
             r = libc.syscall(NR_move_mount, fd, b"", AT_FDCWD, dst.encode(), 
MOVE_MOUNT_F_EMPTY_PATH)
 
         if r < 0:
-            oserror(dst)
+            oserror("move_mount", dst)
     finally:
         os.close(fd)
 
@@ -325,7 +351,7 @@
 
     event = libc.eventfd(0, 0)
     if event < 0:
-        oserror()
+        oserror("eventfd")
 
     pid = os.fork()
     if pid == 0:
@@ -713,13 +739,11 @@
 """
 
 
-UNSHARE_EPERM_MSG = """
-mkosi was forbidden to unshare namespaces.
-
+UNSHARE_EPERM_MSG = f"""\
+{Style.red}mkosi was forbidden to unshare namespaces{Style.reset}.
 This probably means your distribution has restricted unprivileged user 
namespaces.
-
 Please consult the REQUIREMENTS section of the mkosi man page, e.g. via "mkosi
-documentation", for workarounds.
+documentation", for workarounds.\
 """
 
 
@@ -896,7 +920,7 @@
     # We're guaranteed to have / be a mount when we get here, so pivot_root() 
won't fail anymore,
     # even if we're in the initramfs.
     if libc.pivot_root(b".", b".") < 0:
-        oserror()
+        oserror("pivot_root")
 
     # As documented in the pivot_root() man page, this will unmount the old 
rootfs.
     umount2(".", MNT_DETACH)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/sysupdate.py 
new/mkosi-25.3/mkosi/sysupdate.py
--- old/mkosi-25/mkosi/sysupdate.py     2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/sysupdate.py   2025-02-01 19:23:42.488847776 +0100
@@ -9,6 +9,7 @@
 from mkosi.config import Args, ArtifactOutput, Config
 from mkosi.log import die
 from mkosi.run import run
+from mkosi.user import become_root_cmd
 from mkosi.util import PathString
 
 
@@ -54,7 +55,7 @@
                 devices=True,
                 network=True,
                 relaxed=True,
-                setup=["run0"] if os.getuid() != 0 else [],
+                setup=become_root_cmd(),
                 options=[
                     *(["--bind", "/boot", "/boot"] if Path("/boot").exists() 
else []),
                     *(["--bind", "/efi", "/efi"] if Path("/efi").exists() else 
[]),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/mkosi/user.py new/mkosi-25.3/mkosi/user.py
--- old/mkosi-25/mkosi/user.py  2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/mkosi/user.py        2025-02-01 19:23:42.488847776 +0100
@@ -6,7 +6,7 @@
 from pathlib import Path
 
 from mkosi.log import die
-from mkosi.run import spawn
+from mkosi.run import find_binary, spawn
 from mkosi.sandbox import CLONE_NEWUSER, unshare
 from mkosi.util import flock, parents_below
 
@@ -183,3 +183,10 @@
     ]  # fmt: skip
 
     return [str(x) for x in cmd]
+
+
+def become_root_cmd() -> list[str]:
+    if os.getuid() == 0:
+        return []
+
+    return ["run0"] if find_binary("run0") else ["sudo"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/pyproject.toml new/mkosi-25.3/pyproject.toml
--- old/mkosi-25/pyproject.toml 2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/pyproject.toml       2025-02-01 19:23:42.488847776 +0100
@@ -7,7 +7,7 @@
 authors = [
     {name = "mkosi contributors", email = 
"[email protected]"},
 ]
-version = "25"
+version = "25.3"
 description = "Build Bespoke OS Images"
 readme = "README.md"
 requires-python = ">=3.9"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/tests/__init__.py 
new/mkosi-25.3/tests/__init__.py
--- old/mkosi-25/tests/__init__.py      2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/tests/__init__.py    2025-02-01 19:23:42.488847776 +0100
@@ -17,6 +17,7 @@
 from mkosi.run import CompletedProcess, fork_and_wait, run
 from mkosi.sandbox import acquire_privileges
 from mkosi.tree import rmtree
+from mkosi.user import INVOKING_USER
 from mkosi.util import _FILE, PathString
 
 
@@ -33,7 +34,12 @@
         self.config = config
 
     def __enter__(self) -> "Image":
-        self.output_dir = Path(os.getenv("TMPDIR", "/var/tmp")) / 
uuid.uuid4().hex[:16]
+        if (cache := INVOKING_USER.cache_dir()) and os.access(cache, os.W_OK):
+            tmpdir = cache
+        else:
+            tmpdir = Path("/var/tmp")
+
+        self.output_dir = Path(os.getenv("TMPDIR", tmpdir)) / 
uuid.uuid4().hex[:16]
 
         return self
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/tests/test_config.py 
new/mkosi-25.3/tests/test_config.py
--- old/mkosi-25/tests/test_config.py   2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/tests/test_config.py 2025-02-01 19:23:42.488847776 +0100
@@ -696,7 +696,31 @@
         assert config.image_id != "abcde"
 
 
[email protected]("dist1,dist2", 
itertools.combinations_with_replacement(Distribution, 2))
+def test_match_empty(tmp_path: Path) -> None:
+    with chdir(tmp_path):
+        Path("mkosi.conf").write_text(
+            """\
+            [Match]
+            Profiles=
+
+            [Build]
+            Environment=ABC=QED
+            """
+        )
+
+        _, [config] = parse_config([])
+
+        assert config.environment.get("ABC") == "QED"
+
+        _, [config] = parse_config(["--profile", "profile"])
+
+        assert config.environment.get("ABC") is None
+
+
[email protected](
+    "dist1,dist2",
+    itertools.combinations_with_replacement([Distribution.debian, 
Distribution.opensuse], 2),
+)
 def test_match_distribution(tmp_path: Path, dist1: Distribution, dist2: 
Distribution) -> None:
     with chdir(tmp_path):
         parent = Path("mkosi.conf")
@@ -750,7 +774,7 @@
         assert "testpkg3" in conf.packages
 
 
[email protected]("release1,release2", 
itertools.combinations_with_replacement([36, 37, 38], 2))
[email protected]("release1,release2", 
itertools.combinations_with_replacement([36, 37], 2))
 def test_match_release(tmp_path: Path, release1: int, release2: int) -> None:
     with chdir(tmp_path):
         parent = Path("mkosi.conf")
@@ -844,9 +868,7 @@
     assert config.output == "qed"
 
 
[email protected](
-    "image1,image2", itertools.combinations_with_replacement(["image_a", 
"image_b", "image_c"], 2)
-)
[email protected]("image1,image2", 
itertools.combinations_with_replacement(["image_a", "image_b"], 2))
 def test_match_imageid(tmp_path: Path, image1: str, image2: str) -> None:
     with chdir(tmp_path):
         parent = Path("mkosi.conf")
@@ -918,7 +940,7 @@
     "op,version",
     itertools.product(
         ["", "==", "<", ">", "<=", ">="],
-        [122, 123, 124],
+        [122, 123],
     ),
 )
 def test_match_imageversion(tmp_path: Path, op: str, version: str) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/mkosi-25/tests/test_initrd.py 
new/mkosi-25.3/tests/test_initrd.py
--- old/mkosi-25/tests/test_initrd.py   2025-01-23 16:12:01.174991619 +0100
+++ new/mkosi-25.3/tests/test_initrd.py 2025-02-01 19:23:42.488847776 +0100
@@ -223,11 +223,11 @@
 
         # The fallback value is for CentOS and related distributions.
         maxsize = 1024**2 * {
-            Distribution.fedora: 63,
+            Distribution.fedora: 67,
             Distribution.debian: 62,
             Distribution.ubuntu: 57,
             Distribution.arch: 86,
-            Distribution.opensuse: 64,
+            Distribution.opensuse: 67,
         }.get(config.distribution, 58)
 
         assert (Path(image.output_dir) / "image.initrd").stat().st_size <= 
maxsize

Reply via email to