]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Convert script settings to lists 1903/head
authorGeorges Discry <georges@discry.be>
Fri, 15 Sep 2023 20:19:41 +0000 (22:19 +0200)
committerGeorges Discry <georges@discry.be>
Wed, 27 Sep 2023 14:49:51 +0000 (16:49 +0200)
With `mkosi.conf.d`, `mkosi.presets` and/or `[Match]`, it's become
really easy to split the configuration of an image in logical parts. It
might be done to isolate some settings that go together or to share the
same configuration across several images.

However, for each `mkosi.prepare`, `mkosi.build`, `mkosi.postinst` and
`mkosi.finalize` step, only one script could be executed as the one with
highest priority overrides the others.

Each script is now managed as a list of executables so that it's also
possible to split them if desired.

Backward compatibility is kind of maintained by keeping the same options
on the command line and by introducing aliases for the renamed INI
settings (with a deprecation warning). These aliases also parse the
settings as a list, so compatibility is still broken when a script was
expected to override a previously defined one, as they are now both
executed.

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

index 8ab118191abfe382d973292854cf0f1c8906dce5..5b3d6f04bfcca8d3b1c80255309103cf0847cec8 100644 (file)
@@ -260,10 +260,10 @@ def finalize_mounts(config: MkosiConfig) -> list[PathString]:
     return flatten(["--bind", src, target] for src, target in sorted(set(sources), key=lambda s: s[1]))
 
 
-def run_prepare_script(state: MkosiState, build: bool) -> None:
-    if state.config.prepare_script is None:
+def run_prepare_scripts(state: MkosiState, build: bool) -> None:
+    if not state.config.prepare_scripts:
         return
-    if build and state.config.build_script is None:
+    if build and not state.config.build_scripts:
         return
 
     env = dict(
@@ -276,43 +276,41 @@ def run_prepare_script(state: MkosiState, build: bool) -> None:
         MKOSI_GID=str(state.gid),
     )
 
-    chroot: list[PathString] = chroot_cmd(
-        state.root,
-        options=[
-            "--bind", state.config.prepare_script, "/work/prepare",
-            "--bind", Path.cwd(), "/work/src",
-            "--chdir", "/work/src",
-            "--setenv", "SRCDIR", "/work/src",
-            "--setenv", "BUILDROOT", "/",
-        ],
-    )
-
-    if build:
-        with complete_step("Running prepare script in build overlay…"), mount_build_overlay(state):
-            bwrap(
-                [state.config.prepare_script, "build"],
-                network=True,
-                readonly=True,
-                options=finalize_mounts(state.config),
-                scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
-                env=env | state.config.environment,
-                stdin=sys.stdin,
-            )
-    else:
-        with complete_step("Running prepare script…"):
-            bwrap(
-                [state.config.prepare_script, "final"],
-                network=True,
-                readonly=True,
-                options=finalize_mounts(state.config),
-                scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
-                env=env | state.config.environment,
-                stdin=sys.stdin,
+    with contextlib.ExitStack() as stack:
+        if build:
+            stack.enter_context(mount_build_overlay(state))
+            step_msg = "Running prepare script {} in build overlay…"
+            arg = "build"
+        else:
+            step_msg = "Running prepare script {}…"
+            arg = "final"
+
+        for script in state.config.prepare_scripts:
+            chroot: list[PathString] = chroot_cmd(
+                state.root,
+                options=[
+                    "--bind", script, "/work/prepare",
+                    "--bind", Path.cwd(), "/work/src",
+                    "--chdir", "/work/src",
+                    "--setenv", "SRCDIR", "/work/src",
+                    "--setenv", "BUILDROOT", "/",
+                ],
             )
 
+            with complete_step(step_msg.format(script)):
+                bwrap(
+                    [script, arg],
+                    network=True,
+                    readonly=True,
+                    options=finalize_mounts(state.config),
+                    scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
+                    env=env | state.config.environment,
+                    stdin=sys.stdin,
+                )
+
 
-def run_build_script(state: MkosiState) -> None:
-    if state.config.build_script is None:
+def run_build_scripts(state: MkosiState) -> None:
+    if not state.config.build_scripts:
         return
 
     env = dict(
@@ -338,40 +336,40 @@ def run_build_script(state: MkosiState) -> None:
             CHROOT_BUILDDIR="/work/build",
         )
 
-    chroot = chroot_cmd(
-        state.root,
-        options=[
-            "--bind", state.config.build_script, "/work/build-script",
-            "--bind", state.install_dir, "/work/dest",
-            "--bind", state.staging, "/work/out",
-            "--bind", Path.cwd(), "/work/src",
-            *(["--bind", str(state.config.build_dir), "/work/build"] if state.config.build_dir else []),
-            "--chdir", "/work/src",
-            "--setenv", "SRCDIR", "/work/src",
-            "--setenv", "DESTDIR", "/work/dest",
-            "--setenv", "OUTPUTDIR", "/work/out",
-            "--setenv", "BUILDROOT", "/",
-            *(["--setenv", "BUILDDIR", "/work/build"] if state.config.build_dir else []),
-            "--remount-ro", "/",
-        ],
-    )
+    with mount_build_overlay(state), mount_passwd(state.name, state.uid, state.gid, state.root):
+        for script in state.config.build_scripts:
+            chroot = chroot_cmd(
+                state.root,
+                options=[
+                    "--bind", script, "/work/build-script",
+                    "--bind", state.install_dir, "/work/dest",
+                    "--bind", state.staging, "/work/out",
+                    "--bind", Path.cwd(), "/work/src",
+                    *(["--bind", str(state.config.build_dir), "/work/build"] if state.config.build_dir else []),
+                    "--chdir", "/work/src",
+                    "--setenv", "SRCDIR", "/work/src",
+                    "--setenv", "DESTDIR", "/work/dest",
+                    "--setenv", "OUTPUTDIR", "/work/out",
+                    "--setenv", "BUILDROOT", "/",
+                    *(["--setenv", "BUILDDIR", "/work/build"] if state.config.build_dir else []),
+                    "--remount-ro", "/",
+                ],
+            )
 
-    with complete_step("Running build script…"),\
-         mount_build_overlay(state),\
-         mount_passwd(state.name, state.uid, state.gid, state.root):
-        bwrap(
-            [state.config.build_script, *(state.args.cmdline if state.args.verb == Verb.build else [])],
-            network=state.config.with_network,
-            readonly=True,
-            options=finalize_mounts(state.config),
-            scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
-            env=env | state.config.environment,
-            stdin=sys.stdin,
-        )
+            with complete_step(f"Running build script {script}…"):
+                bwrap(
+                    [script, *(state.args.cmdline if state.args.verb == Verb.build else [])],
+                    network=state.config.with_network,
+                    readonly=True,
+                    options=finalize_mounts(state.config),
+                    scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
+                    env=env | state.config.environment,
+                    stdin=sys.stdin,
+                )
 
 
-def run_postinst_script(state: MkosiState) -> None:
-    if state.config.postinst_script is None:
+def run_postinst_scripts(state: MkosiState) -> None:
+    if not state.config.postinst_scripts:
         return
 
     env = dict(
@@ -386,33 +384,34 @@ def run_postinst_script(state: MkosiState) -> None:
         MKOSI_GID=str(state.gid),
     )
 
-    chroot = chroot_cmd(
-        state.root,
-        options=[
-            "--bind", state.config.postinst_script, "/work/postinst",
-            "--bind", state.staging, "/work/out",
-            "--bind", Path.cwd(), "/work/src",
-            "--chdir", "/work/src",
-            "--setenv", "SRCDIR", "/work/src",
-            "--setenv", "OUTPUTDIR", "/work/out",
-            "--setenv", "BUILDROOT", "/",
-        ],
-    )
-
-    with complete_step("Running postinstall script…"):
-        bwrap(
-            [state.config.postinst_script, "final"],
-            network=state.config.with_network,
-            readonly=True,
-            options=finalize_mounts(state.config),
-            scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
-            env=env | state.config.environment,
-            stdin=sys.stdin,
+    for script in state.config.postinst_scripts:
+        chroot = chroot_cmd(
+            state.root,
+            options=[
+                "--bind", script, "/work/postinst",
+                "--bind", state.staging, "/work/out",
+                "--bind", Path.cwd(), "/work/src",
+                "--chdir", "/work/src",
+                "--setenv", "SRCDIR", "/work/src",
+                "--setenv", "OUTPUTDIR", "/work/out",
+                "--setenv", "BUILDROOT", "/",
+            ],
         )
 
+        with complete_step(f"Running postinstall script {script}…"):
+            bwrap(
+                [script, "final"],
+                network=state.config.with_network,
+                readonly=True,
+                options=finalize_mounts(state.config),
+                scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
+                env=env | state.config.environment,
+                stdin=sys.stdin,
+            )
+
 
-def run_finalize_script(state: MkosiState) -> None:
-    if state.config.finalize_script is None:
+def run_finalize_scripts(state: MkosiState) -> None:
+    if not state.config.finalize_scripts:
         return
 
     env = dict(
@@ -427,30 +426,31 @@ def run_finalize_script(state: MkosiState) -> None:
         MKOSI_GID=str(state.gid),
     )
 
-    chroot = chroot_cmd(
-        state.root,
-        options=[
-            "--bind", state.config.finalize_script, "/work/finalize",
-            "--bind", state.staging, "/work/out",
-            "--bind", Path.cwd(), "/work/src",
-            "--chdir", "/work/src",
-            "--setenv", "SRCDIR", "/work/src",
-            "--setenv", "OUTPUTDIR", "/work/out",
-            "--setenv", "BUILDROOT", "/",
-        ],
-    )
-
-    with complete_step("Running finalize script…"):
-        bwrap(
-            [state.config.finalize_script],
-            network=state.config.with_network,
-            readonly=True,
-            options=finalize_mounts(state.config),
-            scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
-            env=env | state.config.environment,
-            stdin=sys.stdin,
+    for script in state.config.finalize_scripts:
+        chroot = chroot_cmd(
+            state.root,
+            options=[
+                "--bind", script, "/work/finalize",
+                "--bind", state.staging, "/work/out",
+                "--bind", Path.cwd(), "/work/src",
+                "--chdir", "/work/src",
+                "--setenv", "SRCDIR", "/work/src",
+                "--setenv", "OUTPUTDIR", "/work/out",
+                "--setenv", "BUILDROOT", "/",
+            ],
         )
 
+        with complete_step(f"Running finalize script {script}…"):
+            bwrap(
+                [script],
+                network=state.config.with_network,
+                readonly=True,
+                options=finalize_mounts(state.config),
+                scripts={"mkosi-chroot": chroot} | package_manager_scripts(state),
+                env=env | state.config.environment,
+                stdin=sys.stdin,
+            )
+
 
 def run_openssl(args: Sequence[PathString], stdout: _FILE = None) -> CompletedProcess:
     with tempfile.NamedTemporaryFile(prefix="mkosi-openssl.cnf") as config:
@@ -1661,7 +1661,7 @@ def run_selinux_relabel(state: MkosiState) -> None:
 
 
 def need_build_packages(config: MkosiConfig) -> bool:
-    return config.build_script is not None and len(config.build_packages) > 0
+    return bool(config.build_scripts and config.build_packages)
 
 
 def save_cache(state: MkosiState) -> None:
@@ -1879,14 +1879,14 @@ def build_image(args: MkosiArgs, config: MkosiConfig, name: str, uid: int, gid:
             if not cached:
                 with mount_cache_overlay(state):
                     install_distribution(state)
-                    run_prepare_script(state, build=False)
+                    run_prepare_scripts(state, build=False)
                     install_build_packages(state)
-                    run_prepare_script(state, build=True)
+                    run_prepare_scripts(state, build=True)
 
                 save_cache(state)
                 reuse_cache(state)
 
-            run_build_script(state)
+            run_build_scripts(state)
 
             if state.config.output_format == OutputFormat.none:
                 # Touch an empty file to indicate the image was built.
@@ -1896,7 +1896,7 @@ def build_image(args: MkosiArgs, config: MkosiConfig, name: str, uid: int, gid:
 
             install_build_dest(state)
             install_extra_trees(state)
-            run_postinst_script(state)
+            run_postinst_scripts(state)
 
             configure_autologin(state)
             configure_initrd(state)
@@ -1918,7 +1918,7 @@ def build_image(args: MkosiArgs, config: MkosiConfig, name: str, uid: int, gid:
             clean_package_manager_metadata(state)
             remove_files(state)
             run_selinux_relabel(state)
-            run_finalize_script(state)
+            run_finalize_scripts(state)
 
         normalize_mtime(state.root, state.config.source_date_epoch)
         partitions = make_image(state, skip=("esp", "xbootldr"))
index de2d283bb9ac2b3945a68c84a575eb4a58ce5e3c..46e2452aa0cdb8ef8641a1957c2ecccd94e73e20 100644 (file)
@@ -151,6 +151,7 @@ def parse_path(value: str,
                *,
                required: bool = True,
                resolve: bool = True,
+               executable: bool = False,
                expanduser: bool = True,
                expandvars: bool = True,
                secret: bool = False) -> Path:
@@ -170,6 +171,9 @@ def parse_path(value: str,
     if resolve:
         path = path.resolve()
 
+    if executable and not os.access(path, os.X_OK):
+        die(f"{value} is not executable")
+
     if secret and path.exists():
         mode = path.stat().st_mode & 0o777
         if mode & 0o007:
@@ -210,17 +214,6 @@ def config_make_string_matcher(allow_globs: bool = False) -> ConfigMatchCallback
     return config_match_string
 
 
-def config_parse_script(value: Optional[str], old: Optional[Path]) -> Optional[Path]:
-    if not value:
-        return None
-
-    path = parse_path(value)
-    if not os.access(path, os.X_OK):
-        die(f"{value} is not executable")
-
-    return path
-
-
 def config_parse_boolean(value: Optional[str], old: Optional[bool]) -> Optional[bool]:
     if value is None:
         return False
@@ -425,6 +418,7 @@ def config_match_version(match: str, value: str) -> bool:
 def make_path_parser(*,
                      required: bool = True,
                      resolve: bool = True,
+                     executable: bool = False,
                      expanduser: bool = True,
                      expandvars: bool = True,
                      secret: bool = False) -> Callable[[str], Path]:
@@ -432,6 +426,7 @@ def make_path_parser(*,
         parse_path,
         required=required,
         resolve=resolve,
+        executable=executable,
         expanduser=expanduser,
         expandvars=expandvars,
         secret=secret,
@@ -441,6 +436,7 @@ def make_path_parser(*,
 def config_make_path_parser(*,
                             required: bool = True,
                             resolve: bool = True,
+                            executable: bool = False,
                             expanduser: bool = True,
                             expandvars: bool = True,
                             secret: bool = False) -> ConfigParseCallback:
@@ -452,6 +448,7 @@ def config_make_path_parser(*,
             value,
             required=required,
             resolve=resolve,
+            executable=executable,
             expanduser=expanduser,
             expandvars=expandvars,
             secret=secret,
@@ -716,10 +713,10 @@ class MkosiConfig:
     clean_package_metadata: ConfigFeature
     source_date_epoch: Optional[int]
 
-    prepare_script: Optional[Path]
-    build_script: Optional[Path]
-    postinst_script: Optional[Path]
-    finalize_script: Optional[Path]
+    prepare_scripts: list[Path]
+    build_scripts: list[Path]
+    postinst_scripts: list[Path]
+    finalize_scripts: list[Path]
     build_sources: list[tuple[Path, Optional[Path]]]
     environment: dict[str, str]
     with_tests: bool
@@ -859,17 +856,16 @@ class MkosiConfig:
         return f"{self.output_with_version}.changelog"
 
     def cache_manifest(self) -> dict[str, Any]:
-        manifest: dict[str, Any] = {
+        return {
             "packages": self.packages,
             "build_packages": self.build_packages,
             "repositories": self.repositories,
+            "prepare_scripts": [
+                base64.b64encode(script.read_bytes()).decode()
+                for script in self.prepare_scripts
+            ]
         }
 
-        if self.prepare_script:
-            manifest["prepare_script"] = base64.b64encode(self.prepare_script.read_bytes()).decode()
-
-        return manifest
-
 
 def parse_ini(path: Path, only_sections: Sequence[str] = ()) -> Iterator[tuple[str, str, str]]:
     """
@@ -1169,7 +1165,7 @@ SETTINGS = (
         metavar="PACKAGE",
         section="Content",
         parse=config_make_list_parser(delimiter=","),
-        help="Additional packages needed for build script",
+        help="Additional packages needed for build scripts",
     ),
     MkosiConfigSetting(
         dest="with_docs",
@@ -1249,37 +1245,49 @@ SETTINGS = (
         help="Set the $SOURCE_DATE_EPOCH timestamp",
     ),
     MkosiConfigSetting(
-        dest="prepare_script",
+        dest="prepare_scripts",
+        long="--prepare-script",
         metavar="PATH",
         section="Content",
-        parse=config_parse_script,
+        parse=config_make_list_parser(delimiter=",", parse=make_path_parser(executable=True)),
         paths=("mkosi.prepare",),
+        path_default=False,
         help="Prepare script to run inside the image before it is cached",
+        compat_names=("PrepareScript",),
     ),
     MkosiConfigSetting(
-        dest="build_script",
+        dest="build_scripts",
+        long="--build-script",
         metavar="PATH",
         section="Content",
-        parse=config_parse_script,
+        parse=config_make_list_parser(delimiter=",", parse=make_path_parser(executable=True)),
         paths=("mkosi.build",),
+        path_default=False,
         help="Build script to run inside image",
+        compat_names=("BuildScript",),
     ),
     MkosiConfigSetting(
-        dest="postinst_script",
+        dest="postinst_scripts",
+        long="--postinst-script",
         metavar="PATH",
-        name="PostInstallationScript",
+        name="PostInstallationScripts",
         section="Content",
-        parse=config_parse_script,
+        parse=config_make_list_parser(delimiter=",", parse=make_path_parser(executable=True)),
         paths=("mkosi.postinst",),
+        path_default=False,
         help="Postinstall script to run inside image",
+        compat_names=("PostInstallationScript",),
     ),
     MkosiConfigSetting(
-        dest="finalize_script",
+        dest="finalize_scripts",
+        long="--finalize-script",
         metavar="PATH",
         section="Content",
-        parse=config_parse_script,
+        parse=config_make_list_parser(delimiter=",", parse=make_path_parser(executable=True)),
         paths=("mkosi.finalize",),
+        path_default=False,
         help="Postinstall script to run outside image",
+        compat_names=("FinalizeScript",),
     ),
     MkosiConfigSetting(
         dest="build_sources",
@@ -1305,7 +1313,7 @@ SETTINGS = (
         section="Content",
         parse=config_parse_boolean,
         default=True,
-        help="Do not run tests as part of build script, if supported",
+        help="Do not run tests as part of build scripts, if supported",
     ),
     MkosiConfigSetting(
         dest="with_network",
@@ -2310,7 +2318,7 @@ def load_config(args: argparse.Namespace) -> MkosiConfig:
     # For unprivileged builds we need the userxattr OverlayFS mount option, which is only available
     # in Linux v5.11 and later.
     if (
-        (args.build_script is not None or args.base_trees) and
+        (args.build_scripts or args.base_trees) and
         GenericVersion(platform.release()) < GenericVersion("5.11") and
         os.geteuid() != 0
     ):
@@ -2424,13 +2432,13 @@ def summary(args: MkosiArgs, config: MkosiConfig) -> str:
 Clean Package Manager Metadata: {yes_no_auto(config.clean_package_metadata)}
              Source Date Epoch: {none_to_none(config.source_date_epoch)}
 
-                Prepare Script: {none_to_none(config.prepare_script)}
-                  Build Script: {none_to_none(config.build_script)}
-            Postinstall Script: {none_to_none(config.postinst_script)}
-               Finalize Script: {none_to_none(config.finalize_script)}
+               Prepare Scripts: {line_join_list(config.prepare_scripts)}
+                 Build Scripts: {line_join_list(config.build_scripts)}
+           Postinstall Scripts: {line_join_list(config.postinst_scripts)}
+              Finalize Scripts: {line_join_list(config.finalize_scripts)}
                  Build Sources: {line_join_source_target_list(config.build_sources)}
             Script Environment: {line_join_list(env)}
-     Run Tests in Build Script: {yes_no(config.with_tests)}
+    Run Tests in Build Scripts: {yes_no(config.with_tests)}
           Scripts With Network: {yes_no(config.with_network)}
 
                       Bootable: {yes_no_auto(config.bootable)}
index 1c8f6089b50fbb663d29ca7c76b33007464e917c..a41d2e6dcc9918f421bd353768394c79b1b54f52 100644 (file)
@@ -226,7 +226,7 @@ The following output formats are supported:
 
 The output format may also be set to *none* to have mkosi produce no
 image at all. This can be useful if you only want to use the image to
-produce another output in the build script (e.g. build an rpm).
+produce another output in the build scripts (e.g. build an rpm).
 
 When a *GPT* disk image is created, repart partition definition files
 may be placed in `mkosi.repart/` to configure the generated disk image.
@@ -509,10 +509,10 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
   systems that support out-of-tree builds (such as Meson). The directory
   used this way is shared between repeated builds, and allows the build
   system to reuse artifacts (such as object files, executable, …)
-  generated on previous invocations. The build script can find the path
+  generated on previous invocations. The build scripts can find the path
   to this directory in the `$BUILDDIR` environment variable. This
   directory is mounted into the image's root directory when
-  `mkosi-chroot` is invoked during execution of the build script. If
+  `mkosi-chroot` is invoked during execution of the build scripts. If
   this option is not specified, but a directory `mkosi.builddir/` exists
   in the local directory it is automatically used for this purpose (also
   see the **Files** section below).
@@ -616,8 +616,8 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
   option may be used multiple times in which case the specified package
   lists are combined. Use `BuildPackages=` to specify packages that
   shall only be installed in an overlay that is mounted when the prepare
-  script is executed with the `build` argument and when the build script
-  is executed.
+  scripts are executed with the `build` argument and when the build scripts
+  are executed.
 
 : The types and syntax of *package specifications* that are allowed
   depend on the package installer (e.g. `dnf` for `rpm`-based distros or
@@ -648,10 +648,10 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 
 : Similar to `Packages=`, but configures packages to install only in an
   overlay that is made available on top of the image to the prepare
-  script when executed with the `build` argument and the build script.
+  scripts when executed with the `build` argument and the build scripts.
   This option should be used to list packages containing header files,
   compilers, build systems, linkers and other build tools the
-  `mkosi.build` script requires to operate. Note that packages listed
+  `mkosi.build` scripts require to operate. Note that packages listed
   here will be absent in the final image.
 
 `WithDocs=`, `--with-docs`
@@ -659,7 +659,7 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 : Include documentation in the image built. By default if the
   underlying distribution package manager supports it documentation is
   not included in the image built. The `$WITH_DOCS` environment
-  variable passed to the `mkosi.build` script indicates whether this
+  variable passed to the `mkosi.build` scripts indicates whether this
   option was used or not.
 
 `BaseTrees=`, `--base-tree=`
@@ -742,25 +742,29 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
   package manager executable is *not* present at the end of the
   installation.
 
-`PrepareScript=`, `--prepare-script=`
+`PrepareScripts=`, `--prepare-script=`
 
-: Takes a path to an executable that is used as the prepare script for
-  this image. See the **Scripts** section for more information.
+: Takes a comma-separated list of paths to executables that are used as
+  the prepare scripts for this image. See the **Scripts** section for
+  more information.
 
-`BuildScript=`, `--build-script=`
+`BuildScripts=`, `--build-script=`
 
-: Takes a path to an executable that is used as build script for this
-  image. See the **Scripts** section for more information.
+: Takes a comma-separated list of paths to executables that are used as
+  the build scripts for this image. See the **Scripts** section for more
+  information.
 
-`PostInstallationScript=`, `--postinst-script=`
+`PostInstallationScripts=`, `--postinst-script=`
 
-: Takes a path to an executable that is used as the post-installation
-  script for this image. See the **Scripts** section for more information.
+: Takes a comma-separated list of paths to executables that are used as
+  the post-installation scripts for this image. See the **Scripts** section
+  for more information.
 
-`FinalizeScript=`, `--finalize-script=`
+`FinalizeScripts=`, `--finalize-script=`
 
-: Takes a path to an executable that is used as the finalize script for
-  this image. See the **Scripts** section for more information.
+: Takes a comma-separated list of paths to executables that are used as
+  the finalize scripts for this image. See the **Scripts** section for more
+  information.
 
 `BuildSources=`, `--build-sources=`
 
@@ -788,17 +792,17 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 
 : If set to false (or when the command-line option is used), the
   `$WITH_TESTS` environment variable is set to `0` when the
-  `mkosi.build` script is invoked. This is supposed to be used by the
-  build script to bypass any unit or integration tests that are
+  `mkosi.build` scripts are invoked. This is supposed to be used by the
+  build scripts to bypass any unit or integration tests that are
   normally run during the source build process. Note that this option
-  has no effect unless the `mkosi.build` build script honors it.
+  has no effect unless the `mkosi.build` build scripts honor it.
 
 `WithNetwork=`, `--with-network=`
 
-: When true, enables network connectivity while the build script
-  `mkosi.build` is invoked. By default, the build script runs with
+: When true, enables network connectivity while the build scripts
+  `mkosi.build` are invoked. By default, the build scripts run with
   networking turned off. The `$WITH_NETWORK` environment variable is
-  passed to the `mkosi.build` build script indicating whether the
+  passed to the `mkosi.build` build scripts indicating whether the
   build is done with or without network.
 
 `Bootable=`, `--bootable=`
@@ -1009,7 +1013,7 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 
 : Enable incremental build mode. In this mode, a copy of the OS image is
   created immediately after all OS packages are installed and the
-  prepare script has executed but before the `mkosi.build` script is
+  prepare scripts have executed but before the `mkosi.build` scripts are
   invoked (or anything that happens after it). On subsequent invocations
   of `mkosi` with the `-i` switch this cached image may be used to skip
   the OS package installation, thus drastically speeding up repetitive
@@ -1244,16 +1248,16 @@ Then, for each preset, we execute the following steps:
 8. Copy skeleton trees (`mkosi.skeleton`) into image
 9. Install distribution and packages into image or use cache tree if
    available
-10. Run prepare script on image with the `final` argument (`mkosi.prepare`)
-11. Install build packages in overlay if a build script is configured
-12. Run prepare script on overlay with the `build` argument if a build
-    script is configured (`mkosi.prepare`)
+10. Run prepare scripts on image with the `final` argument (`mkosi.prepare`)
+11. Install build packages in overlay if any build scripts are configured
+12. Run prepare scripts on overlay with the `build` argument if any build
+    scripts are configured (`mkosi.prepare`)
 13. Cache the image if configured (`--incremental`)
-14. Run build script on image + overlay if a build script is configured (`mkosi.build`)
+14. Run build scripts on image + overlay if any build scripts are configured (`mkosi.build`)
 15. Finalize the build if the output format `none` is configured
-16. Copy the build script outputs into the image
+16. Copy the build scripts outputs into the image
 17. Copy the extra trees into the image (`mkosi.extra`)
-18. Run post-install script (`mkosi.postinst`)
+18. Run post-install scripts (`mkosi.postinst`)
 19. Write config files required for `Ssh=`, `Autologin=` and `MakeInitrd=`
 20. Install systemd-boot and configure secure boot if configured (`--secure-boot`)
 21. Run `systemd-sysusers`
@@ -1263,7 +1267,7 @@ Then, for each preset, we execute the following steps:
 25. Run `systemd-hwdb`
 26. Remove packages and files (`RemovePackages=`, `RemoveFiles=`)
 27. Run SELinux relabel is a SELinux policy is installed
-28. Run finalize script (`mkosi.finalize`)
+28. Run finalize scripts (`mkosi.finalize`)
 29. Generate unified kernel image if configured to do so
 30. Generate final output format
 
@@ -1280,7 +1284,7 @@ are mounted into the current working directory before running the script
 and `$SRCDIR` is set to point to the current working directory. The
 following scripts are supported:
 
-* If **`mkosi.prepare`** (`PrepareScript=`) exists, it is first called
+* 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
   parameter, right after the build packages are installed and the build
@@ -1295,7 +1299,7 @@ following scripts are supported:
   easily be thrown away and rebuilt so there's no risk of conflicting
   dependencies and no risk of polluting the host system.
 
-* If **`mkosi.build`** (`BuildScript=`) exists, it is executed with the
+* If **`mkosi.build`** (`BuildScripts=`) exists, it is executed with the
   build overlay mounted on top of the image's root directory. When
   running the build script, `$DESTDIR` points to a directory where the
   script should place any files generated it would like to end up in the
@@ -1304,13 +1308,13 @@ following scripts are supported:
   *source* trees from the build script. After running the build script,
   the contents of `$DESTDIR` are copied into the image.
 
-* If **`mkosi.postinst`** (`PostInstallationScript=`) exists, it is
+* If **`mkosi.postinst`** (`PostInstallationScripts=`) exists, it is
   executed after the (optional) build tree and extra trees have been
   installed. This script may be used to alter the images without any
   restrictions, after all software packages and built sources have been
   installed.
 
-* If **`mkosi.finalize`** (`FinalizeScript=`) exists, it is executed as
+* If **`mkosi.finalize`** (`FinalizeScripts=`) exists, it is executed as
   the last step of preparing an image.
 
 Scripts executed by mkosi receive the following environment variables:
@@ -1332,8 +1336,8 @@ Scripts executed by mkosi receive the following environment variables:
   will have after invoking `mkosi-chroot`.
 
 * `$DESTDIR` is a directory into which any installed software generated
-  by the build script may be placed. This variable is only set when
-  executing the build script. `$CHROOT_DESTDIR` contains the value that
+  by a build script may be placed. This variable is only set when
+  executing a build script. `$CHROOT_DESTDIR` contains the value that
   `$DESTDIR` will have after invoking `mkosi-chroot`.
 
 * `$OUTPUTDIR` points to the staging directory used to store build
@@ -1346,18 +1350,18 @@ Scripts executed by mkosi receive the following environment variables:
 
 * `$WITH_DOCS` is either `0` or `1` depending on whether a build
   without or with installed documentation was requested
-  (`WithDocs=yes`). The build script should suppress installation of
+  (`WithDocs=yes`). A build script should suppress installation of
   any package documentation to `$DESTDIR` in case `$WITH_DOCS` is set
   to `0`.
 
 * `$WITH_TESTS` is either `0`or `1` depending on whether a build
   without or with running the test suite was requested
-  (`WithTests=no`). The build script should avoid running any unit or
+  (`WithTests=no`). A build script should avoid running any unit or
   integration tests in case `$WITH_TESTS` is `0`.
 
 * `$WITH_NETWORK` is either `0`or `1` depending on whether a build
   without or with networking is being executed (`WithNetwork=no`).
-  The build script should avoid any network communication in case
+  A build script should avoid any network communication in case
   `$WITH_NETWORK` is `0`.
 
 * `$SOURCE_DATE_EPOCH` is defined if requested (`SourceDateEpoch=TIMESTAMP`,
@@ -1446,14 +1450,14 @@ local directory:
   to speed repeated runs of the tool.
 
 * The **`mkosi.builddir/`** directory, if it exists, is automatically used as out-of-tree build directory, if
-  the build commands in the `mkosi.build` script support it. Specifically, this directory will be mounted
-  into the build container, and the `$BUILDDIR` environment variable will be set to it when the build script
-  is invoked. The build script may then use this directory as build directory, for automake-style or
+  the build commands in the `mkosi.build` scripts support it. Specifically, this directory will be mounted
+  into the build container, and the `$BUILDDIR` environment variable will be set to it when the build scripts
+  are invoked. A build script may then use this directory as build directory, for automake-style or
   ninja-style out-of-tree builds. This speeds up builds considerably, in particular when `mkosi` is used in
   incremental mode (`-i`): not only the image and build overlay, but also the build tree is reused between
   subsequent invocations. Note that if this directory does not exist the `$BUILDDIR` environment variable is
-  not set, and it is up to build script to decide whether to do in in-tree or an out-of-tree build, and which
-  build directory to use.
+  not set, and it is up to the build scripts to decide whether to do in in-tree or an out-of-tree build, and
+  which build directory to use.
 
 * The **`mkosi.rootpw`** file can be used to provide the password for the root user of the image. If the
   password is prefixed with `hashed:` it is treated as an already hashed root password. The password may
@@ -1557,7 +1561,7 @@ re-building of images. Specifically:
    be shared, using the `mkosi.builddir/` directory. This directory
    allows build systems such as Meson to reuse already compiled
    sources from a previous built, thus speeding up the build process
-   of the `mkosi.build` build script.
+   of a `mkosi.build` build script.
 
 The package cache and incremental mode are unconditionally useful. The
 final cache only apply to uses of `mkosi` with a source tree and build