return make_image(context, msg=msg, skip=skip, split=split, tabs=tabs, root=context.root, definitions=definitions)
+def make_oci(context: Context, root_layer: Path, dst: Path) -> None:
+ ca_store = dst / "blobs" / "sha256"
+ with umask(~0o755):
+ ca_store.mkdir(parents=True)
+
+ layer_diff_digest = hash_file(root_layer)
+ maybe_compress(
+ context,
+ context.config.compress_output,
+ context.staging / "rootfs.layer",
+ # Pass explicit destination to suppress adding an extension
+ context.staging / "rootfs.layer",
+ )
+ layer_digest = hash_file(root_layer)
+ root_layer.rename(ca_store / layer_digest)
+
+ creation_time = (
+ datetime.datetime.fromtimestamp(context.config.source_date_epoch, tz=datetime.timezone.utc)
+ if context.config.source_date_epoch is not None
+ else datetime.datetime.now(tz=datetime.timezone.utc)
+ ).isoformat()
+
+ oci_config = {
+ "created": creation_time,
+ "architecture": context.config.architecture.to_oci(),
+ # Name of the operating system which the image is built to run on as defined by
+ # https://github.com/opencontainers/image-spec/blob/v1.0.2/config.md#properties.
+ "os": "linux",
+ "rootfs": {
+ "type": "layers",
+ "diff_ids": [f"sha256:{layer_diff_digest}"],
+ },
+ "config": {
+ "Cmd": [
+ "/sbin/init",
+ *context.config.kernel_command_line,
+ ],
+ },
+ "history": [
+ {
+ "created": creation_time,
+ "comment": "Created by mkosi",
+ },
+ ],
+ }
+ oci_config_blob = json.dumps(oci_config)
+ oci_config_digest = hashlib.sha256(oci_config_blob.encode()).hexdigest()
+ with umask(~0o644):
+ (ca_store / oci_config_digest).write_text(oci_config_blob)
+
+ layer_suffix = context.config.compress_output.oci_media_type_suffix()
+ oci_manifest = {
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "config": {
+ "mediaType": "application/vnd.oci.image.config.v1+json",
+ "digest": f"sha256:{oci_config_digest}",
+ "size": (ca_store / oci_config_digest).stat().st_size,
+ },
+ "layers": [
+ {
+ "mediaType": f"application/vnd.oci.image.layer.v1.tar{layer_suffix}",
+ "digest": f"sha256:{layer_digest}",
+ "size": (ca_store / layer_digest).stat().st_size,
+ }
+ ],
+ "annotations": {
+ "io.systemd.mkosi.version": __version__,
+ **({
+ "org.opencontainers.image.version": context.config.image_version,
+ } if context.config.image_version else {}),
+ }
+ }
+ oci_manifest_blob = json.dumps(oci_manifest)
+ oci_manifest_digest = hashlib.sha256(oci_manifest_blob.encode()).hexdigest()
+ with umask(~0o644):
+ (ca_store / oci_manifest_digest).write_text(oci_manifest_blob)
+
+ (dst / "index.json").write_text(
+ json.dumps(
+ {
+ "schemaVersion": 2,
+ "mediaType": "application/vnd.oci.image.index.v1+json",
+ "manifests": [
+ {
+ "mediaType": "application/vnd.oci.image.manifest.v1+json",
+ "digest": f"sha256:{oci_manifest_digest}",
+ "size": (ca_store / oci_manifest_digest).stat().st_size,
+ }
+ ],
+ }
+ )
+ )
+
+ (dst / "oci-layout").write_text(json.dumps({"imageLayoutVersion": "1.0.0"}))
+
+
def make_esp(context: Context, uki: Path) -> list[Partition]:
if not (arch := context.config.architecture.to_efi()):
die(f"Architecture {context.config.architecture} does not support UEFI")
tools=context.config.tools(),
sandbox=context.sandbox,
)
+ elif context.config.output_format == OutputFormat.oci:
+ make_tar(
+ context.root, context.staging / "rootfs.layer",
+ tools=context.config.tools(),
+ sandbox=context.sandbox,
+ )
+ make_oci(
+ context,
+ context.staging / "rootfs.layer",
+ context.staging / context.config.output_with_format,
+ )
elif context.config.output_format == OutputFormat.cpio:
make_cpio(
context.root, context.staging / context.config.output_with_format,
sysext = enum.auto()
tar = enum.auto()
uki = enum.auto()
+ oci = enum.auto()
def extension(self) -> str:
return {
xz = enum.auto()
bz2 = enum.auto()
gz = enum.auto()
+ gzip = "gz"
lz4 = enum.auto()
lzma = enum.auto()
Compression.zstd: ".zst"
}.get(self, f".{self}")
+ def oci_media_type_suffix(self) -> str:
+ suffix = {
+ Compression.none: "",
+ Compression.gz: "+gzip",
+ Compression.zstd: "+zstd",
+ }.get(self)
+
+ if not suffix:
+ die(f"Compression {self} not supported for OCI layers")
+
+ return suffix
+
class DocFormat(StrEnum):
auto = enum.auto()
return a
+ def to_oci(self) -> str:
+ a = {
+ Architecture.arm : "arm",
+ Architecture.arm64 : "arm64",
+ Architecture.loongarch64 : "loong64",
+ Architecture.mips64_le : "mips64le",
+ Architecture.mips_le : "mipsle",
+ Architecture.ppc : "ppc",
+ Architecture.ppc64 : "ppc64",
+ Architecture.ppc64_le : "ppc64le",
+ Architecture.riscv32 : "riscv",
+ Architecture.riscv64 : "riscv64",
+ Architecture.s390x : "s390x",
+ Architecture.x86 : "386",
+ Architecture.x86_64 : "amd64",
+ }.get(self)
+
+ if not a:
+ die(f"Architecture {self} not supported by OCI")
+
+ return a
+
def default_serial_tty(self) -> str:
return {
Architecture.arm : "ttyAMA0",
return Compression.xz
else:
return Compression.zstd
+ elif namespace.output_format == OutputFormat.oci:
+ return Compression.gz
else:
return Compression.none
archive is generated), `disk` (a block device OS image with a GPT
partition table), `uki` (a unified kernel image with the OS image in
the `.initrd` PE section), `esp` (`uki` but wrapped in a disk image
- with only an ESP partition), `sysext`, `confext`, `portable` or `none`
+ with only an ESP partition), `oci` (a directory compatible with the
+ OCI image specification), `sysext`, `confext`, `portable` or `none`
(the OS image is solely intended as a build image to produce another
artifact).
: Configure compression for the resulting image or archive. The argument can be
either a boolean or a compression algorithm (`xz`, `zstd`). `zstd`
compression is used by default, except CentOS and derivatives up to version
- 8, which default to `xz`. Note that when applied to block device image types,
+ 8, which default to `xz`, and OCI images, which default to `gzip`.
+ Note that when applied to block device image types,
compression means the image cannot be started directly but needs to be
decompressed first. This also means that the `shell`, `boot`, `qemu` verbs
are not available when this option is used. Implied for `tar`, `cpio`, `uki`,
- and `esp`.
+ `esp`, and `oci`.
`CompressLevel=`, `--compress-level=`