]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Make source directories ephemeral when running scripts 2041/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 6 Nov 2023 13:04:26 +0000 (14:04 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 6 Nov 2023 16:09:34 +0000 (17:09 +0100)
Various tools like to write to the source directory, which we want
to avoid. Let's make source directories ephemeral when running scripts
so tools can write to it as much as they want but we can throw away all
those changes when we're done running scripts.

Specifically, this makes running rpmbuild as documented in
docs/building-rpm-from-source.md a lot nicer as the source directory won't be
polluted with all manner of temporary files anymore.

NEWS.md
mkosi/__init__.py
mkosi/resources/mkosi.md

diff --git a/NEWS.md b/NEWS.md
index 83f2051211928a790ff33f30f8f4982ab1480091..bcffe804ec7518e57d856bb3902dc353b7f1ba6c 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
@@ -38,6 +38,9 @@
   `-kernel` or `QemuKernel=`
 - We don't create subdirectories beneath the configured cache directory
   anymore.
+- Source directories are now made ephemeral when running scripts. This
+  means any changes made to source directories while running scripts
+  will be undone after the scripts have finished executing.
 
 ## v18
 
index abb2149615e9243c596752272c8a62e66a1b3b24..dc976b2d64609770afe3977adfd91ee815e7df2e 100644 (file)
@@ -321,18 +321,24 @@ def mount_build_overlay(state: MkosiState, volatile: bool = False) -> Iterator[P
         yield state.root
 
 
-def finalize_mounts(config: MkosiConfig) -> list[PathString]:
-    sources = [
-        (source, target)
-        for source, target
-        in [(Path.cwd(), Path.cwd())] + [t.with_prefix(Path.cwd()) for t in config.build_sources]
-    ]
+@contextlib.contextmanager
+def finalize_mounts(config: MkosiConfig) -> Iterator[list[PathString]]:
+    with contextlib.ExitStack() as stack:
+        sources = [
+            (stack.enter_context(mount_overlay([source])), target)
+            for source, target
+            in [(Path.cwd(), Path.cwd())] + [t.with_prefix(Path.cwd()) for t in config.build_sources]
+        ]
 
-    # bwrap() mounts /home and /var read-only during execution. So let's add the bind mount options for the
-    # directories that could be in /home or /var that we do need to be writable.
-    sources += [(d, d) for d in (config.workspace_dir, config.cache_dir, config.output_dir, config.build_dir) if d]
+        # bwrap() mounts /home and /var read-only during execution. So let's add the bind mount options for the
+        # directories that could be in /home or /var that we do need to be writable.
+        sources += [
+            (d, d)
+            for d in (config.workspace_dir_or_default(), config.cache_dir, config.output_dir, config.build_dir)
+            if d
+        ]
 
-    return flatten(["--bind", src, target] for src, target in sorted(set(sources), key=lambda s: s[1]))
+        yield flatten(["--bind", src, target] for src, target in sorted(set(sources), key=lambda s: s[1]))
 
 
 def script_maybe_chroot(script: Path, mountpoint: str) -> list[str]:
@@ -407,6 +413,7 @@ def run_prepare_scripts(state: MkosiState, build: bool) -> None:
             step_msg = "Running prepare script {}…"
             arg = "final"
 
+        mounts = stack.enter_context(finalize_mounts(state.config))
         cd = stack.enter_context(finalize_chroot_scripts(state))
 
         for script in state.config.prepare_scripts:
@@ -432,7 +439,7 @@ def run_prepare_scripts(state: MkosiState, build: bool) -> None:
                     script_maybe_chroot(script, "/work/prepare") + [arg],
                     network=True,
                     readonly=True,
-                    options=finalize_mounts(state.config),
+                    options=mounts,
                     scripts=hd,
                     env=env | state.config.environment,
                     stdin=sys.stdin,
@@ -469,6 +476,7 @@ def run_build_scripts(state: MkosiState) -> None:
     with (
         mount_build_overlay(state, volatile=True),
         finalize_chroot_scripts(state) as cd,
+        finalize_mounts(state.config) as mounts,
     ):
         for script in state.config.build_scripts:
             helpers = {
@@ -502,7 +510,7 @@ def run_build_scripts(state: MkosiState) -> None:
                     script_maybe_chroot(script, "/work/build-script") + cmdline,
                     network=state.config.with_network,
                     readonly=True,
-                    options=finalize_mounts(state.config),
+                    options=mounts,
                     scripts=hd,
                     env=env | state.config.environment,
                     stdin=sys.stdin,
@@ -525,7 +533,10 @@ def run_postinst_scripts(state: MkosiState) -> None:
         SRCDIR=str(Path.cwd()),
     )
 
-    with finalize_chroot_scripts(state) as cd:
+    with (
+        finalize_chroot_scripts(state) as cd,
+        finalize_mounts(state.config) as mounts,
+    ):
         for script in state.config.postinst_scripts:
             helpers = {
                 "mkosi-chroot": chroot_cmd(
@@ -552,7 +563,7 @@ def run_postinst_scripts(state: MkosiState) -> None:
                     script_maybe_chroot(script, "/work/postinst") + ["final"],
                     network=state.config.with_network,
                     readonly=True,
-                    options=finalize_mounts(state.config),
+                    options=mounts,
                     scripts=hd,
                     env=env | state.config.environment,
                     stdin=sys.stdin,
@@ -575,7 +586,10 @@ def run_finalize_scripts(state: MkosiState) -> None:
         SRCDIR=str(Path.cwd()),
     )
 
-    with finalize_chroot_scripts(state) as cd:
+    with (
+        finalize_chroot_scripts(state) as cd,
+        finalize_mounts(state.config) as mounts,
+    ):
         for script in state.config.finalize_scripts:
             helpers = {
                 "mkosi-chroot": chroot_cmd(
@@ -602,7 +616,7 @@ def run_finalize_scripts(state: MkosiState) -> None:
                     script_maybe_chroot(script, "/work/finalize"),
                     network=state.config.with_network,
                     readonly=True,
-                    options=finalize_mounts(state.config),
+                    options=mounts,
                     scripts=hd,
                     env=env | state.config.environment,
                     stdin=sys.stdin,
@@ -2606,9 +2620,9 @@ def check_workspace_directory(config: MkosiConfig) -> None:
         die(f"The workspace directory ({wd}) cannot be located in the current working directory ({Path.cwd()})",
             hint="Use WorkspaceDirectory= to configure a different workspace directory")
 
-    for src, _ in config.build_sources:
-        if wd.is_relative_to(src):
-            die(f"The workspace directory ({wd}) cannot be a subdirectory of any source directory ({src})",
+    for tree in config.build_sources:
+        if wd.is_relative_to(tree.source):
+            die(f"The workspace directory ({wd}) cannot be a subdirectory of any source directory ({tree.source})",
                 hint="Use WorkspaceDirectory= to configure a different workspace directory")
 
 
index 9c3dc5eccd1e2a68828e1ae675d20f0a0f52ee3d..5ce0043cacdac125505b2faa5d65d76d5603dfec 100644 (file)
@@ -1671,11 +1671,17 @@ available via `$PATH` to simplify common usecases.
   ```
 
 When scripts are executed, any directories that are still writable are
-also made read-only (`/home`, `/var`, `/root`, ...) and only the minimal set
-of directories that need to be writable remain writable. This is to
+also made read-only (`/home`, `/var`, `/root`, ...) and only the minimal
+set of directories that need to be writable remain writable. This is to
 ensure that scripts can't mess with the host system when mkosi is
 running as root.
 
+Note that when executing scripts, all source directories are made
+ephemeral which means all changes made to source directories while
+running scripts are thrown away after the scripts finish executing. Use
+the output, build or cache directories if you need to persist data
+between builds.
+
 # Files
 
 To make it easy to build images for development versions of your