return max(old, new)
+def file_run_or_read(file: Path) -> str:
+ "Run the specified file and capture its output if it's executable, else read file contents"
+
+ if os.access(file, os.X_OK):
+ return run([file.absolute()], stdout=subprocess.PIPE, env=os.environ).stdout
+
+ content = file.read_text()
+
+ if content.startswith("#!/"):
+ die(f"{file} starts with a shebang ({content.splitlines()[0]})",
+ hint="This file should be executable")
+
+ return content
+
+
@dataclasses.dataclass(frozen=True)
class KeySource:
class Type(StrEnum):
self.config,
s.dest,
s.parse(
- extra.read_text().rstrip("\n") if s.path_read_text else f,
+ file_run_or_read(extra).rstrip("\n") if s.path_read_text else f,
getattr(self.config, s.dest, None)
),
)
`ImageVersion=`, `--image-version=`
: Configure the image version. This accepts any string, but it is
recommended to specify a series of dot separated components. The
- version may also be configured in a file `mkosi.version` in which
- case it may be conveniently managed via the `bump` verb or the
- `--auto-bump` option. When specified the image version is included
- in the default output file name, i.e. instead of `image.raw` the
- default will be `image_0.1.raw` for version `0.1` of the image, and
- similar. The version is also passed via the `$IMAGE_VERSION` to any
- build scripts invoked (which may be useful to patch it into
+ version may also be configured by reading a `mkosi.version` file (in
+ which case it may be conveniently managed via the `bump` verb or the
+ `--auto-bump` option) or by reading stdout if it is executable (see
+ the **Scripts** section below). When specified the image version is
+ included in the default output file name, i.e. instead of `image.raw`
+ the default will be `image_0.1.raw` for version `0.1` of the image,
+ and similar. The version is also passed via the `$IMAGE_VERSION` to
+ any build scripts invoked (which may be useful to patch it into
`/usr/lib/os-release` or similar, in particular the `IMAGE_VERSION=`
field of it).
`RootPassword=`, `--root-password=`,
: Set the system root password. If this option is not used, but a `mkosi.rootpw` file is found in the local
- directory, the password is automatically read from it. If the password starts with `hashed:`, it is treated
- as an already hashed root password. The root password is also stored in `/usr/lib/credstore` under the
- appropriate systemd credential so that it applies even if only `/usr` is shipped in the image. To create
+ directory, the password is automatically read from it or if the file is executable it is run as a script
+ and stdout is read instead (see the **Scripts** section below). If the password starts with `hashed:`, it is
+ treated as an already hashed root password. The root password is also stored in `/usr/lib/credstore` under
+ the appropriate systemd credential so that it applies even if only `/usr` is shipped in the image. To create
an unlocked account without any password use `hashed:` without a hash.
`Autologin=`, `--autologin`
artifacts from `SplitArtifacts=yes` or RPMs built in a build script).
Note that this script does not use the tools tree even if one is configured.
+* If **`mkosi.version`** exists and is executable, it is run during
+ configuration parsing and populates `ImageVersion=` with the output on stdout.
+ This can be used for external version tracking, e.g. with `git describe` or
+ `date '+%Y-%m-%d'`. Note that this script is executed on the host system
+ without any sandboxing.
+
+* If **`mkosi.rootpw`** exists and is executable, it is run during
+ configuration parsing and populates `RootPassword=` with the output
+ on stdout. This can be used to randomly generate a password and can
+ be remembered by outputting it to stderr or by reading `$MKOSI_CONFIG`
+ in another script (e.g. `mkosi.postoutput`). Note that this script is
+ executed on the host system without any sandboxing.
+
If a script uses the `.chroot` extension, mkosi will chroot into the
image using `mkosi-chroot` (see below) before executing the script. For
example, if `mkosi.postinst.chroot` exists, mkosi will chroot into the
assert sub.environment["PassThisEnv"] == "abc"
assert "TestValue2" not in sub.environment
+
+
+def test_mkosi_version_executable(tmp_path: Path) -> None:
+ d = tmp_path
+
+ version = d / "mkosi.version"
+ version.write_text("#!/bin/sh\necho '1.2.3'\n")
+
+ with chdir(d):
+ with pytest.raises(SystemExit) as error:
+ _, [config] = parse_config()
+
+ assert error.type is SystemExit
+ assert error.value.code != 0
+
+ version.chmod(0o755)
+
+ with chdir(d):
+ _, [config] = parse_config()
+ assert config.image_version == "1.2.3"