"--make-initrd", "yes",
"--bootable", "no",
"--manifest-format", "",
+ *(["--source-date-epoch", str(state.config.source_date_epoch)]
+ if state.config.source_date_epoch is not None else []),
*(["--locale", state.config.locale] if state.config.locale else []),
*(["--locale-messages", state.config.locale_messages] if state.config.locale_messages else []),
*(["--keymap", state.config.keymap] if state.config.keymap else []),
run_selinux_relabel(state)
run_finalize_script(state)
+ normalize_mtime(state.root, state.config.source_date_epoch)
partitions = make_image(state, skip=("esp", "xbootldr"))
install_unified_kernel(state, partitions)
prepare_grub_efi(state)
prepare_grub_bios(state, partitions)
+ normalize_mtime(state.root, state.config.source_date_epoch, directory=Path("boot"))
+ normalize_mtime(state.root, state.config.source_date_epoch, directory=Path("efi"))
partitions = make_image(state)
install_grub_bios(state, partitions)
make_image(state, split=True)
if args.verb == Verb.serve:
run_serve(last)
+
+
+def normalize_mtime(root: Path, mtime: Optional[int], directory: Optional[Path] = None) -> None:
+ directory = directory or Path("")
+ if mtime is None:
+ return
+
+ with complete_step(f"Normalizing modification times of /{directory}"):
+ os.utime(root / directory, (mtime, mtime), follow_symlinks=False)
+ for p in (root / directory).rglob("*"):
+ os.utime(p, (mtime, mtime), follow_symlinks=False)
except KeyError:
return Compression.zst if parse_boolean(value) else Compression.none
+
def config_parse_seed(value: Optional[str], old: Optional[str]) -> Optional[uuid.UUID]:
if not value or value == "random":
return None
die(f"{value} is not a valid UUID")
+def config_parse_source_date_epoch(value: Optional[str], old: Optional[int]) -> Optional[int]:
+ if value is None:
+ return None
+
+ try:
+ timestamp = int(value)
+ except ValueError:
+ raise ValueError(f"{value} is not a valid timestamp")
+ if timestamp < 0:
+ raise ValueError(f"{value} is negative")
+ return timestamp
+
def config_default_compression(namespace: argparse.Namespace) -> Compression:
if namespace.output_format == OutputFormat.cpio:
return None
+def config_default_source_date_epoch(namespace: argparse.Namespace) -> Optional[int]:
+ for env in namespace.environment:
+ if env.startswith("SOURCE_DATE_EPOCH="):
+ return config_parse_source_date_epoch(env.removeprefix("SOURCE_DATE_EPOCH="), None)
+ return config_parse_source_date_epoch(os.environ.get("SOURCE_DATE_EPOCH"), None)
+
+
def make_enum_parser(type: Type[enum.Enum]) -> Callable[[str], enum.Enum]:
def parse_enum(value: str) -> enum.Enum:
try:
remove_packages: list[str]
remove_files: list[str]
clean_package_metadata: ConfigFeature
+ source_date_epoch: Optional[int]
prepare_script: Optional[Path]
build_script: Optional[Path]
parse=config_parse_feature,
help="Use btrfs subvolumes for faster directory operations where possible",
),
- MkosiConfigSetting(
+ MkosiConfigSetting(
dest="seed",
metavar="UUID",
section="Output",
parse=config_parse_feature,
help="Remove package manager database and other files",
),
+ MkosiConfigSetting(
+ dest="source_date_epoch",
+ metavar="TIMESTAMP",
+ section="Content",
+ parse=config_parse_source_date_epoch,
+ default_factory=config_default_source_date_epoch,
+ default_factory_depends=("environment",),
+ help="Set the $SOURCE_DATE_EPOCH timestamp",
+ ),
MkosiConfigSetting(
dest="prepare_script",
metavar="PATH",
env["IMAGE_ID"] = args.image_id
if args.image_version is not None:
env["IMAGE_VERSION"] = args.image_version
+ if args.source_date_epoch is not None:
+ env["SOURCE_DATE_EPOCH"] = str(args.source_date_epoch)
if (proxy := os.environ.get("http_proxy")):
env["http_proxy"] = proxy
if (proxy := os.environ.get("https_proxy")):
Remove Packages: {line_join_list(config.remove_packages)}
Remove Files: {line_join_list(config.remove_files)}
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)}
builds, where deterministic UUIDs and other partition metadata should be
derived on each build.
+`SourceDateEpoch=`, `--source-date-epoch=`
+
+: Takes a timestamp as argument. Resets file modification times of all files to
+ this timestamp. The variable is also propagated to systemd-repart and
+ scripts executed by mkosi. If not set explicitly, `SOURCE_DATE_EPOCH` from
+ `--environment` and from the host environment are tried in that order.
+ This is useful to make builds reproducible. See
+ [SOURCE_DATE_EPOCH](https://reproducible-builds.org/specs/source-date-epoch/)
+ for more information.
+
### [Content] Section
`Packages=`, `--package=`, `-p`
The build script should avoid any network communication in case
`$WITH_NETWORK` is `0`.
+* `$SOURCE_DATE_EPOCH` is defined if requested (`SourceDateEpoch=TIMESTAMP`,
+ `Environment=SOURCE_DATE_EPOCH=TIMESTAMP` or the host environment variable
+ `$SOURCE_DATE_EPOCH`). This is useful to make builds reproducible. See
+ [SOURCE_DATE_EPOCH](https://reproducible-builds.org/specs/source-date-epoch/)
+ for more information.
+
Additionally, when a script is executed, a few scripts are made
available via `$PATH` to simplify common usecases.