]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add sysupdate verb
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 8 Sep 2024 14:38:55 +0000 (16:38 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 11 Sep 2024 13:27:50 +0000 (15:27 +0200)
mkosi/__init__.py
mkosi/config.py
mkosi/resources/man/mkosi.md
mkosi/sysupdate.py [new file with mode: 0644]
tests/test_json.py

index 95634753037a56b6d5ca2ba4332ab9b5e27c9308..e4301705cbd950cf36b130cae289b3c81ce80fc7 100644 (file)
@@ -111,6 +111,7 @@ from mkosi.sandbox import (
     unshare,
     userns_has_single_user,
 )
+from mkosi.sysupdate import run_sysupdate
 from mkosi.tree import copy_tree, make_tree, move_tree, rmtree
 from mkosi.types import PathString
 from mkosi.user import INVOKING_USER
@@ -2233,6 +2234,15 @@ def check_tools(config: Config, verb: Verb) -> None:
     if verb == Verb.qemu and config.vmm == Vmm.vmspawn:
         check_systemd_tool(config, "systemd-vmspawn", version="256", reason="boot images with vmspawn")
 
+    if verb == Verb.sysupdate:
+        check_systemd_tool(
+            config,
+            "systemd-sysupdate",
+            "/usr/lib/systemd/systemd-sysupdate",
+            version="257~devel",
+            reason="Update the host system with systemd-sysupdate",
+        )
+
 
 def configure_ssh(context: Context) -> None:
     if not context.config.ssh:
@@ -4128,4 +4138,5 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
             Verb.qemu: run_vm,
             Verb.serve: run_serve,
             Verb.burn: run_burn,
+            Verb.sysupdate: run_sysupdate,
         }[args.verb](args, last)
index a8218bacf4d52c7e65aa3a69390ac3586441b129..a1f57b0aa4f36a50abd34a6705543417df868416 100644 (file)
@@ -75,6 +75,7 @@ class Verb(StrEnum):
     burn          = enum.auto()
     dependencies  = enum.auto()
     completion    = enum.auto()
+    sysupdate     = enum.auto()
 
     def supports_cmdline(self) -> bool:
         return self in (
@@ -88,6 +89,7 @@ class Verb(StrEnum):
             Verb.burn,
             Verb.completion,
             Verb.documentation,
+            Verb.sysupdate,
         )
 
     def needs_build(self) -> bool:
@@ -98,6 +100,7 @@ class Verb(StrEnum):
             Verb.qemu,
             Verb.serve,
             Verb.burn,
+            Verb.sysupdate,
         )
 
     def needs_root(self) -> bool:
@@ -1445,6 +1448,7 @@ class Config:
     image_version: Optional[str]
     split_artifacts: bool
     repart_dirs: list[Path]
+    sysupdate_dir: Optional[Path]
     sector_size: Optional[int]
     overlay: bool
     seed: uuid.UUID
@@ -3061,6 +3065,16 @@ SETTINGS = (
         parse=config_make_path_parser(required=False),
         help="Set the path used to store forwarded machine journals",
     ),
+    ConfigSetting(
+        dest="sysupdate_dir",
+        long="--sysupdate-dir",
+        metavar="PATH",
+        name="SysupdateDirectory",
+        section="Host",
+        parse=config_make_path_parser(),
+        paths=("mkosi.sysupdate",),
+        help="Directory containing systemd-sysupdate transfer definitions",
+    ),
     ConfigSetting(
         dest="qemu_gui",
         metavar="BOOL",
index 3320a807782c6f69d73208cffb36a1c3f8ea9244..4f6ffafc7f55e9096260b9c4e211bad9dedc82d1 100644 (file)
@@ -26,6 +26,8 @@ mkosi — Build Bespoke OS Images
 
 `mkosi [options…] coredumpctl [command line…]`
 
+`mkosi [options…] sysupdate [command line…]`
+
 `mkosi [options…] clean`
 
 `mkosi [options…] serve`
@@ -124,6 +126,13 @@ The following command line verbs are known:
     forwarded journal instead of the image. Note that this requires
     configuring systemd-coredump to store coredumps in the journal.
 
+`sysupdate`
+:   Invokes `systemd-sysupdate` with the `--transfer-source=` option set
+    to the output directory and the `--definitions=` option set to the
+    directory configured with `SysupdateDirectory=`. Any arguments
+    specified after the `sysupdate` verb are passed directly to
+    `systemd-sysupdate` invocation.
+
 `clean`
 :   Remove build artifacts generated on a previous build. If combined
     with `-f`, also removes incremental build cache images. If `-f` is
@@ -1699,6 +1708,21 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
     of file if your workload produces more than `4G` worth of journal
     data.
 
+`SysupdateDirectory=`, `--sysupdate-dir=`
+:   Path to a directory containing systemd-sysupdate transfer definition
+    files that are used by `mkosi sysupdate`. If `mkosi.sysupdate/`
+    exists in the local directory, it will be used for this purpose as
+    well.
+
+    Note that `mkosi sysupdate` invokes `systemd-sysupdate` with
+    `--transfer-source=` set to the mkosi output directory. To make use
+    of this in a transfer definition file, set `PathRelativeTo=explicit`
+    to have the `Path=` setting for the transfer source be interpreted
+    relative to the mkosi output directory. Generally, configuring
+    `PathRelativeTo=explicit` and `Path=/` for the transfer source is
+    sufficient for the match pattern to be interpreted relative to the
+    mkosi output directory.
+
 ### [Match] Section.
 
 `Profile=`
diff --git a/mkosi/sysupdate.py b/mkosi/sysupdate.py
new file mode 100644 (file)
index 0000000..efb3cf7
--- /dev/null
@@ -0,0 +1,47 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+import os
+import sys
+from pathlib import Path
+
+from mkosi.config import Args, Config
+from mkosi.log import die
+from mkosi.run import run
+from mkosi.types import PathString
+
+
+def run_sysupdate(args: Args, config: Config) -> None:
+    if not config.split_artifacts:
+        die("SplitArtifacts= must be enabled to be able to use mkosi sysupdate")
+
+    if not config.sysupdate_dir:
+        die("No sysupdate definitions directory specified",
+            hint="Specify a directory containing systemd-sysupdate transfer definitions with SysupdateDirectory=")
+
+    if not (sysupdate := config.find_binary("systemd-sysupdate", "/usr/lib/systemd/systemd-sysupdate")):
+        die("Could not find systemd-sysupdate")
+
+    cmd: list[PathString] = [
+        sysupdate,
+        "--definitions", config.sysupdate_dir,
+        "--transfer-source", config.output_dir_or_cwd(),
+        *args.cmdline,
+    ]
+
+    run(
+        cmd,
+        stdin=sys.stdin,
+        stdout=sys.stdout,
+        env=os.environ | config.environment,
+        log=False,
+        sandbox=config.sandbox(
+            binary=sysupdate,
+            devices=True,
+            network=True,
+            relaxed=True,
+            options=[
+                *(["--bind", "/boot", "/boot"] if Path("/boot").exists() else []),
+                *(["--bind", "/efi", "/efi"] if Path("/efi").exists() else []),
+            ]
+        ),
+    )
index c2a044c6a3d900d16bbc2678b83e4b8fcc1ecf8d..f887d3f800c459a4b78fd005ad8e92958fd7cd7c 100644 (file)
@@ -318,6 +318,7 @@ def test_config() -> None:
             "SyncScripts": [
                 "/sync"
             ],
+            "SysupdateDirectory": "/sysupdate",
             "Timezone": null,
             "ToolsTree": null,
             "ToolsTreeCertificates": true,
@@ -494,6 +495,7 @@ def test_config() -> None:
         ssh_certificate=Path("/path/to/cert"),
         ssh_key=None,
         sync_scripts=[Path("/sync")],
+        sysupdate_dir=Path("/sysupdate"),
         timezone=None,
         tools_tree=None,
         tools_tree_certificates=True,