]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add support for sync scripts 2418/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 22 Feb 2024 09:16:22 +0000 (10:16 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 22 Feb 2024 12:31:21 +0000 (13:31 +0100)
Sync scripts allow updating various sources automatically before
doing a build.

mkosi/__init__.py
mkosi/config.py
mkosi/resources/mkosi.md
tests/test_json.py

index 2522b64d1baadacca650226dcd00278c7dc53e4f..343d549edf85ad0d3422cc0001dbff453b4f09d2 100644 (file)
@@ -411,6 +411,52 @@ def finalize_chroot_scripts(context: Context) -> contextlib.AbstractContextManag
     return finalize_scripts(scripts, root=context.root)
 
 
+def run_sync_scripts(context: Context) -> None:
+    if not context.config.sync_scripts:
+        return
+
+    env = dict(
+        ARCHITECTURE=str(context.config.architecture),
+        SRCDIR="/work/src",
+        MKOSI_UID=str(INVOKING_USER.uid),
+        MKOSI_GID=str(INVOKING_USER.gid),
+        CACHED=one_zero(have_cache(context.config)),
+    )
+
+    # We make sure to mount everything in to make ssh work since syncing might involve git which could invoke ssh.
+    if agent := os.getenv("SSH_AUTH_SOCK"):
+        env["SSH_AUTH_SOCK"] = agent
+
+    with (
+        finalize_source_mounts(context.config, ephemeral=False) as sources,
+        finalize_host_scripts(context) as hd,
+    ):
+        for script in context.config.sync_scripts:
+            options = [
+                *sources,
+                "--ro-bind", script, "/work/sync",
+                "--chdir", "/work/src",
+            ]
+
+            if (p := INVOKING_USER.home()).exists():
+                options += ["--ro-bind", p, p]
+            if (p := Path(f"/run/user/{INVOKING_USER.uid}")).exists():
+                options += ["--ro-bind", p, p]
+
+            with complete_step(f"Running sync script {script}…"):
+                run(
+                    ["/work/sync", "final"],
+                    env=env | context.config.environment,
+                    stdin=sys.stdin,
+                    sandbox=context.sandbox(network=True, options=options, scripts=hd),
+                    # Make sure we run as the invoking user when we're running as root so that files are owned by the
+                    # right user. bubblewrap will automatically map the running user to root in the user namespace it
+                    # creates.
+                    user=INVOKING_USER.uid,
+                    group=INVOKING_USER.gid,
+                )
+
+
 def run_prepare_scripts(context: Context, build: bool) -> None:
     if not context.config.prepare_scripts:
         return
@@ -2252,7 +2298,13 @@ def check_inputs(config: Config) -> None:
             if not p.is_file():
                 die(f"Initrd {p} is not a file")
 
-    for script in config.prepare_scripts + config.build_scripts + config.postinst_scripts + config.finalize_scripts:
+    for script in itertools.chain(
+        config.sync_scripts,
+        config.prepare_scripts,
+        config.build_scripts,
+        config.postinst_scripts,
+        config.finalize_scripts,
+    ):
         if not os.access(script, os.X_OK):
             die(f"{script} is not executable")
 
@@ -3680,6 +3732,8 @@ def run_sync(args: Args, config: Config, *, resources: Path) -> None:
         for p in config.distribution.package_manager(config).cache_subdirs(src):
             INVOKING_USER.mkdir(p)
 
+        run_sync_scripts(context)
+
 
 def run_build(args: Args, config: Config, *, resources: Path) -> None:
     check_inputs(config)
index 3d747191cda48d0ea5374dfcfc3eed25ae460870..75b8deeef2e95dc125c2f86669335e5c6ad049bc 100644 (file)
@@ -1226,6 +1226,7 @@ class Config:
     clean_package_metadata: ConfigFeature
     source_date_epoch: Optional[int]
 
+    sync_scripts: list[Path]
     prepare_scripts: list[Path]
     build_scripts: list[Path]
     postinst_scripts: list[Path]
@@ -1955,6 +1956,15 @@ SETTINGS = (
         default_factory_depends=("environment",),
         help="Set the $SOURCE_DATE_EPOCH timestamp",
     ),
+    ConfigSetting(
+        dest="sync_scripts",
+        long="--sync-script",
+        metavar="PATH",
+        section="Content",
+        parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+        paths=("mkosi.sync",),
+        help="Sync script to run before starting the build",
+    ),
     ConfigSetting(
         dest="prepare_scripts",
         long="--prepare-script",
@@ -3509,6 +3519,7 @@ def summary(config: Config) -> str:
      Clean Package Manager Metadata: {config.clean_package_metadata}
                   Source Date Epoch: {none_to_none(config.source_date_epoch)}
 
+                       Sync Scripts: {line_join_list(config.sync_scripts)}
                     Prepare Scripts: {line_join_list(config.prepare_scripts)}
                       Build Scripts: {line_join_list(config.build_scripts)}
                 Postinstall Scripts: {line_join_list(config.postinst_scripts)}
index 6e4bcab473ebdb47d28d3f9dc0252bba3c73836b..f046c3038dff737f9db0858a635cbc868537bd5c 100644 (file)
@@ -1022,6 +1022,12 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
   package manager executable is *not* present at the end of the
   installation.
 
+`SyncScripts=`, `--sync-script=`
+
+: Takes a comma-separated list of paths to executables that are used as
+  the sync scripts for this image. See the **Scripts** section for
+  more information.
+
 `PrepareScripts=`, `--prepare-script=`
 
 : Takes a comma-separated list of paths to executables that are used as
@@ -1062,7 +1068,8 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 : Takes a boolean. Disabled by default. Configures whether changes to
   source directories (The working directory and configured using
   `BuildSources=`) are persisted. If enabled, all source directories
-  will be reset to their original state after scripts finish executing.
+  will be reset to their original state after scripts (except sync
+  scripts) finish executing.
 
 `Environment=`, `--environment=`
 
@@ -1832,6 +1839,14 @@ are mounted into the current working directory before running the script
 in the current working directory. `$SRCDIR` is set to point to the
 current working directory. The following scripts are supported:
 
+* If **`mkosi.sync`** (`SyncScripts=`) exists, it is executed before the
+  image is built. This script may be used to update various sources that
+  are used to build the image. One use case is to run `git pull` on
+  various source repositories before building the image. Specifically,
+  the `BuildSourcesEphemeral=` setting does not apply to sync scripts,
+  which means sync scripts can be used to update build sources even if
+  `BuildSourcesEphemeral=` is enabled.
+
 * If **`mkosi.prepare`** (`PrepareScripts=`) exists, it is first called
   with the `final` argument, right after the software packages are
   installed. It is called a second time with the `build` command line
index d6da93f6175487c658e0fc7ff1928f8b17258a62..2d0f2b880268a1cef03ec8e041a2116bd2acb144 100644 (file)
@@ -278,6 +278,9 @@ def test_config() -> None:
             "Ssh": false,
             "SshCertificate": "/path/to/cert",
             "SshKey": null,
+            "SyncScripts": [
+                "/sync"
+            ],
             "Timezone": null,
             "ToolsTree": null,
             "ToolsTreeDistribution": null,
@@ -408,6 +411,7 @@ def test_config() -> None:
         ssh = False,
         ssh_certificate = Path("/path/to/cert"),
         ssh_key = None,
+        sync_scripts = [Path("/sync")],
         timezone = None,
         tools_tree = None,
         tools_tree_distribution = None,