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
