]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add MakeScriptsExecutable= setting to optionally try to make scripts executable befor...
authorLuca Boccassi <luca.boccassi@gmail.com>
Wed, 28 Jan 2026 22:43:21 +0000 (22:43 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 3 Feb 2026 16:16:01 +0000 (17:16 +0100)
If it fails, it was going to die() anyway.

OBS sources defined inline (ie, not in a tarball) cannot have the mode preserved,
so it's not possible to have mkosi.build or so as a bare script
in an OBS project, one needs to tar it up and extract it again later,
which means it cannot be edited by the inline editor, which is very
convenient for small and trivial builds like an addon.

mkosi/__init__.py
mkosi/config.py
mkosi/resources/man/mkosi.1.md
mkosi/resources/mkosi-obs/mkosi.conf
tests/test_json.py

index 03ec72e8233e9c2d01baaa6fb3afb4d88e971060..7cb78c9adf7c868c3fa787631ca5e04bfa1312ce 100644 (file)
@@ -649,13 +649,21 @@ def finalize_config_json(config: Config) -> Iterator[Path]:
         yield Path(f.name)
 
 
+def check_script(config: Config, script: Path) -> None:
+    if not os.access(script, os.X_OK):
+        if config.make_scripts_executable:
+            logging.warning(f"{script} is not executable, attempting to chmod it")
+            os.chmod(script, os.stat(script).st_mode | stat.S_IXUSR)
+        else:
+            die(f"{script} is not executable")
+
+
 def run_configure_scripts(config: Config) -> Config:
     if not config.configure_scripts:
         return config
 
     for script in config.configure_scripts:
-        if not os.access(script, os.X_OK):
-            die(f"{script} is not executable")
+        check_script(config, script)
 
     env = dict(
         DISTRIBUTION=str(config.distribution),
@@ -703,8 +711,7 @@ def run_sync_scripts(config: Config) -> None:
         return
 
     for script in config.sync_scripts:
-        if not os.access(script, os.X_OK):
-            die(f"{script} is not executable")
+        check_script(config, script)
 
     env = dict(
         DISTRIBUTION=str(config.distribution),
@@ -2723,8 +2730,7 @@ def check_inputs(config: Config) -> None:
         config.finalize_scripts,
         config.postoutput_scripts,
     ):
-        if not os.access(script, os.X_OK):
-            die(f"{script} is not executable")
+        check_script(config, script)
 
     if config.secure_boot and not config.secure_boot_key:
         die(
@@ -4592,8 +4598,7 @@ def run_clean_scripts(config: Config) -> None:
         return
 
     for script in config.clean_scripts:
-        if not os.access(script, os.X_OK):
-            die(f"{script} is not executable")
+        check_script(config, script)
 
     env = dict(
         DISTRIBUTION=str(config.distribution),
index ab00e552ca959028f8e89b833508b041da054f0f..a502e1e23321f2f20f0e74d314156cd67cbdac5a 100644 (file)
@@ -2151,6 +2151,7 @@ class Config:
     proxy_peer_certificate: Optional[Path]
     proxy_client_certificate: Optional[Path]
     proxy_client_key: Optional[Path]
+    make_scripts_executable: bool
 
     nspawn_settings: Optional[Path]
     ephemeral: bool
@@ -4015,6 +4016,14 @@ SETTINGS: list[ConfigSetting[Any]] = [
         help="Set the proxy client key",
         scope=SettingScope.multiversal,
     ),
+    ConfigSetting(
+        dest="make_scripts_executable",
+        metavar="BOOL",
+        section="Build",
+        parse=config_parse_boolean,
+        default=False,
+        help="Whether mkosi will try to make build/postinst/finalize scripts executable if they are not",
+    ),
     # Runtime section
     ConfigSetting(
         dest="nspawn_settings",
@@ -5788,6 +5797,8 @@ def summary(config: Config) -> str:
            Proxy Client Certificate: {none_to_none(config.proxy_client_certificate)}
                    Proxy Client Key: {none_to_none(config.proxy_client_key)}
 
+    Automatically set +x on scripts: {yes_no(config.make_scripts_executable)}
+
     {bold("HOST CONFIGURATION")}:
                     NSpawn Settings: {none_to_none(config.nspawn_settings)}
                           Ephemeral: {config.ephemeral}
index 90c0da5d1f95d2472f6d1fb5ac94e0a834d736c8..c3da06a7fb4c20724977bb72901aaccafbb50c7a 100644 (file)
@@ -1753,6 +1753,10 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
     Currently, setting a proxy client key is only supported when **dnf** or
     **dnf5** is used to build the image.
 
+`MakeScriptsExecutable=`, `--make-scripts-executable=`
+:   If one of the hook scripts (see `SCRIPTS` section) is not marked as executable, attempt to chmod it
+    instead of failing outright. Defaults to `no`.
+
 ### [Runtime] Section (previously known as the [Host] section)
 
 `NSpawnSettings=`, `--settings=`
index 811ccb59891eacdfb954fea3fecb0bfd7bd49567..a939b18ee1f56ac49fbec832b45bdd01013edd13 100644 (file)
@@ -8,3 +8,6 @@ SecureBoot=no
 SignExpectedPcr=no
 Verity=defer
 Checksum=yes
+
+[Build]
+MakeScriptsExecutable=yes
index a17fdcfae83e1d6a3c841fabe5d75e03381f48e5..308b27279eaf5ebbd5f8662a2d0aae95af0bf25b 100644 (file)
@@ -254,6 +254,7 @@ def test_config() -> None:
             "Machine": "machine",
             "MachineId": "b58253b0-cc92-4a34-8782-bcd99b20d07f",
             "MakeInitrd": false,
+            "MakeScriptsExecutable": false,
             "ManifestFormat": [
                 "json",
                 "changelog"
@@ -508,6 +509,7 @@ def test_config() -> None:
         locale="en_C.UTF-8",
         machine_id=uuid.UUID("b58253b0cc924a348782bcd99b20d07f"),
         machine="machine",
+        make_scripts_executable=False,
         make_initrd=False,
         manifest_format=[ManifestFormat.json, ManifestFormat.changelog],
         maxmem=123,