MKOSI笔记

MKOSI-INITRD

mkosi-initrd是使用MKOSI进行initramfs构建的工具。

1
mkosi-initrd [选项]

可用选项有:

  • --kernel-version=:使用的内核版本,默认使用$(uname -r)
  • --format=|-t:输出格式,可选值有cpiouki,默认使用cpio
  • -o|--output=:输出文件名前缀,默认为initrd
  • -O|--output-dir=:输出的目录,默认为工作目录。
  • --debug:Debug输出。

它本质上是一个使用了特殊的默认配置的MKOSI Wrapper脚本(见mkosi.conf),在执行时,它会获取当前主机的内核版本(或用户指定的内核版本),并将当前内核的/usr/lib/modules/版本号目录映射到镜像中进行构建。

该命令行工具由MKOSI的Pypi包提供,对于发行版来说,它们也可以使用Kernel-install脚本进行集成(参考setup-mkosi)。

MKOSI-SANDBOX

mkosi-sandbox用于在沙箱中执行命令:

1
mkosi-sandbox [选项] [命令] [参数]

命令默认为bash

接受的选项有:

  • --tmpfs 目录:沙箱中挂载的Tmpfs。
  • --dev 目录:沙箱中创建的Devfs。
  • --proc 目录:沙箱中创建的Procfs。
  • --dir 目录:沙箱中递归创建的目录。
  • --bind|--bind-try|--ro-bind|--ro-bind-try 主机目录 沙箱目录:(只读)绑定挂载到沙箱的目录。
  • --symlink 路径 链接:在沙箱中创建软链接。
  • --write 数据 路径:在沙箱中写入指定文件。
  • --overlay-lowerdir 主机目录:设置下一个Overlayfs的下层目录。
  • --overlay-upperdir 主机目录|tmpfs:设置下一个Overlayfs的上层目录。
  • --overlay-workdir 主机目录:设置下一个Overlayfs挂载的工作目录。
  • --overlay 沙箱目录:在沙箱中挂载Overlayfs。
  • --unsetenv 变量名:在沙箱中去掉环境变量。
  • --setenv 变量名 值:在沙箱中设置环境变量。
  • --chdir 目录:启动沙箱时的工作目录。
  • --same-dir:启动沙箱时的工作目录为当前宿主机所处的目录。
  • --become-root:成为沙箱内的Root用户并获取CAP_SYS_ADMIN,默认为普通用户。
  • --suppress-chown:禁止使用chown()系统调用。
  • --unshare-net:隔离网络命名空间。
  • --unshare-ipc:隔离IPC命名空间。

实例:

1
mkosi-sandbox --bind / / --same-dir --become-root bash --login
1
2
3
4
5
6
7
8
9
10
11
12
mkosi-sandbox \
--ro-bind /usr /usr \
--symlink usr/bin /bin \
--symlink usr/bin /bin \
--symlink usr/lib /lib \
--symlink usr/lib64 /lib64 \
--symlink usr/sbin /sbin \
--dev /dev \
--proc /proc \
--tmpfs /tmp \
--become-root \
id

MKOSI

Linux发行版 = Base Files(Base Rootfs) + Essential Packages + Package Manager:

  • Debian/Ubuntu = debootstrap(base-files + base-passwd) + "Essential: yes" Packages + apt
  • Fedora/CentOS/RHEL = yum|dnf --installroot(setup + basesystem + filesystem) + @core Package Group + yum|dnf
  • Arch = pacstrap(base) + base Meta Package + pacman
  • Gentoo = stage 3 Image + @system Set + emerge

mkosi是建立在dnf --installrootdebootstrappacstrap等工具之上的,快速构建客制化的系统镜像并进行测试的工具。

“镜像”在这里是一个通用概念,这意味着MKOSI可以生成以下类型的“镜像”:

  • 使用GPT分区表的磁盘镜像,可以克隆到磁盘中。
  • CPIO归档,用于作为initramfs使用。
  • USI(Unified System Image,即将完整的操作系统打包进UKI的镜像)
  • Sysext、Confext、Portable Service Image镜像
  • 用于容器的目录树镜像

安装

mkosi在各大发行版的软件源中有提供:

1
2
3
4
5
# Debian
apt install mkosi

# RHEL
dnf install mkosi

也可以使用pip安装:

1
pipx install git+https://https://github.com/systemd/mkosi.git

语法

1
mkosi [选项] 子命令

构建配置文件

mkosi的任何命令都基于特定的规则读取配置文件,优先级顺序分别是:

  • 最优先:命令行选项。
  • 当前目录下的mkosi.local.conf配置文件或mkosi.local目录下的配置文件。该文件仅用于本地配置,不应该共享。
  • -C|--directory=指定目录(默认为当前目录)下的mkosi.conf配置文件。
  • --directory=指定目录(默认为当前目录)下的mkosi.conf.d/目录内的配置文件。
  • 用户指定的Profile对应的mkosi.profiles/里面的子目录配置。
  • mkosi.images/里面的子镜像的配置。

子命令

mkosi由多种子命令组成,这些命令其实都是一些外部工具的简易接口:

  • summary:打印配置文件中定义的配置总结,这也反映了构建时会进行的操作。
  • build:从配置文件中读取配置并开始构建镜像。
  • sysupdate:执行systemd-sysupdate,其中,--transfer-source=设为输出目录(默认为mkosi.output/),--definition=设为SysupdateDirectory=的值(默认为mkosi.sysupdate/),任何子命令之后的参数都会被传递给systemd-sysupdate
    • 要求:MKOSI使用的Sysupdate配置中,必须设置PathRelativeTo=explicitPath=/才能正常使用sysupdate子命令进行更新,这会使得配置中的Path=使用mkosi.output/(或OutputDirectory=指定的目录)作为根。
    • 如果使用了ToolsTree,且存在文件系统中存在/usr/share/factory,那么ToolsTree中必须也存在/usr/share/factory,否则会发生文件系统只读错误,这是因为ToolsTree的/usr在Sandbox中是被只读挂载的无法在沙盒中创建/usr/share/factory挂载点
    • 这同时也意味着,SplitArtifacts=partitions必须启用以对分区进行更新,而且更新内容(分区镜像文件、EFI程序、initramfs)文件名必须与Sysupdate配置中的MatchPattern=匹配。
  • shell:(如果还没有构建)先构建镜像,然后调用systemd-nspawnChroot到镜像内。
    • 仅适用于QCow2=no且未压缩的镜像。
  • boot:(如果还没有构建)先构建镜像,然后调用systemd-nspawn以容器形式启动镜像。
    • 仅适用于QCow2=no且未压缩的镜像。
  • qemu:(如果还没有构建)先构建镜像,然后调用qemu以虚拟机形式启动镜像。
    • 对于目录树,MKOSI使用virtiofsd引导。
    • ssh:连接到镜像对应的虚拟机。
    • genkey:创建SSH密钥。
  • serve:(如果还没有构建)先构建镜像,然后调用内置的HTTP服务器在8081端口上暴露工作目录。
  • (仅用于disk镜像)burn 块设备:(如果还没有构建)先构建镜像,然后将镜像扩展到目标块设备的大小,再烧录到指定的块设备上。
  • journalctl:获取镜像内的日志。
  • coredumpctl:获取镜像内的内核转储文件。
  • clean:清理之前的构建产物。
  • bump:刷新mkosi.version文件中的镜像版本。
  • genkey:生成安全启动密钥。

命令行选项

这里提到的命令行选项是和构建配置完全无关的:

  • -f|--force:进行指定操作前,不加分辨地重新构建镜像。
  • -B--auto-bump:执行操作后自动刷新版本号。
  • -C|--directory=:指定工作目录,默认为当前工作目录。
  • --debug:启用Debug级输出。
  • --debug-shell:当在镜像中执行命令失败时,启动一个额外的Debug Shell用于排错。
  • --debug-workspace=:执行操作后,指定的临时文件目录内容不会删除。

构建配置

构建配置文件符合.ini格式,每一行都是Key=Value的形式,每一个配置项都有等价的命令行选项,转换方式是SomeSettingsome-setting

配置中存在以下可用的占位符:

含义 占位符
Distribution= %d
Release= %r
Architecture= %a
Format= %t
Output= %o
OutputDirectory= %O
ImageId= %i
ImageVersion= %v
Profiles= %p
配置文件所在目录 %C
工作目录 %P
MKOSI的执行目录 %D
当前子镜像的镜像名 %I

具体的构建配置有:

[Match]段与[TriggerMatch]

匹配构建时包含当前配置文件的特定条件。每个配置项除了使用=连接键值对外,还可以使用=|连接键值对,多个使用=连接键值对的配置项之间是逻辑与关系,必须全部满足;多个使用=|连接键值对的配置项之间是逻辑或关系,满足一个即可。

可用配置项有:

  • Profiles=:使用的Profile。
  • Distribution=:构建的发行版。
  • Release=:构建的发行版版本。
  • Architecture=:构建的架构。可用的值有alphaarcarmarm64ia64loongarch64mips64-lemips-lepariscppcppc64ppc64-leriscv32riscv64s390s390xtilegxx86x86-64
  • HostArchitecture=:主机的架构。
  • ImageId=:构建的镜像名称。
  • ImageVersion=:构建的镜像版本。
  • Bootable=:构建的镜像是否可引导。
  • Format=:构建的镜像格式。
  • ToolsTreeDistribution=:使用的Tools Tree发行版。
  • PathExists=:主机上存在指定目录。
  • SystemdVersion=:主机上的systemd版本。

[Match]段之间是纯粹的AND关系,也就是说,不同的[Match]段必须全部满足。而[TriggerMatch]段之间是OR关系,也就是说,不同的[TriggerMatch]段中,只要有一个[TriggerMatch]段满足即可。

(v20以上版本)[Include]

  • Include=:导入指定的额外配置文件。如果值为mkosi-initrd,那么会使用mkosi-initrd的配置生成initramfs。
    • 特殊值mkosi-initrd表示mkosi-initrd的配置。
    • 特殊值mkosi-tools表示ToolsTree的配置。
    • 特殊值mkosi-vm表示运行虚拟机时的默认配置。

(v20以上版本)[Config]

额外配置。

  • Profiles=:使用指定的Profile,Profile是mkosi.profiles/目录下保存的一个子工作目录,它支持的内容和MKOSI的主工作目录完全一致(除了不能有mkosi.profiles/目录),会覆盖主工作目录的对应部分。
    • Profile通常是让用户通过--profile=命令行选项进行自选的。它可以用于匹配,因此适合保存相同镜像的不同口味。
    • Profile并不能修改mkosi.images/中的子镜像的配置。
  • ConfigureScript=:在读取配置文件后立刻执行的脚本。默认为mkosi.configure
  • MinumumVersion=:要求的最小MKOSI版本号。
  • PassEnvironment=:传递的环境变量,空格分隔。
  • Dependencies=:依赖的子镜像,子镜像是mkosi.images/目录下保存的一个子工作目录,它支持的配置项和MKOSI的主工作目录相比会少一些,如果没有Dependencies=,那么所有子镜像都会在主镜像构建之前自动构建,即Dependencies=*,如果有Dependencies=,那么只构建依赖的镜像。
    • 子镜像不接受的配置项有:
    • Architecture=
    • BuildDirectory=
    • BuildSources=
    • BuildSourcesEphemeral=
    • CacheDirectory=
    • CacheOnly=
    • Distribution=
    • ExtraSearchPaths=
    • Incremental=
    • LocalMirror=
    • Mirror=
    • OutputDirectory=
    • OutputMode=
    • PackageCacheDirectory=
    • PackageDirectories=
    • Profiles=
    • ProxyClientCertificate=
    • ProxyClientKey=
    • ProxyExclude=
    • ProxyPeerCertificate=
    • ProxyUrl=
    • Release=
    • RepartOffline=
    • Repositories=
    • RepositoryKeyCheck=
    • SandboxTrees=
    • SourceDateEpoch=
    • ToolsTree=
    • ToolsTreeCertificates=
    • UseSubvolumes=
    • VerityCertificate=
    • VerityKey=
    • VolatilePackageDirectories=
    • WithNetwork=
    • WithTests
    • WorkspaceDirectory=

[Distribution]

发行版配置。

  • Distribution=:使用的发行版,目前支持fedora,debian,ubuntu,arch,opensuse,mageia,centos,centos_epel,openmandriva,rocky,rocky_epel,alma,alma_epel,gentoo,custom。默认使用主机的发行版。
    • Distribution=custom时,无法自动构建Rootfs,需要自行提供。
  • Release=:使用的版本。默认使用主机的版本。
  • Architecture=:使用的架构。默认使用主机的架构。
  • Mirror=:使用的镜像源。
    • 在使用EPEL时,如果要为EPEL使用其他镜像,使用$EPEL_MIRROR环境变量。
    • Debian的Debug和Security源无法直接切换镜像,考虑使用mkosi.pkgmngr,或是构建无Debug和Security仓库的sid|unstable版本。
  • LocalMirror=:在构建时使用的镜像源,但并不写入最终镜像内。
  • RepositoryKeyCheck=:检查镜像源密钥。
  • (新版本)RepositoryKeyFetch=:在安装时自动尝试获取软件源密钥,默认启用,如果禁用,将从宿主机传递软件源密钥,这需要安装对应的*-keyring软件包。
  • Repositories=:启用的Yum源名(例如epel),或是Apt源的分区(例如main)。使用逗号分隔。

[Output]

输出方式。

  • Format=:导出格式,mkosi支持非常多种镜像,包括但不仅限于:
    • disk(旧版本为gpt_ext4|gpt_ext4|gpt_xfs):GPT+UEFI磁盘镜像。使用systemd-repart生成。
    • directory:纯目录形式的容器根文件系统。
    • tar:Tar包。
    • cpio:CPIO包。
    • uki:UKI镜像。
    • esp:类似于uki,但是转换为一个包含单个ESP的GPT分区表的磁盘镜像。
    • oci:OCI容器镜像。
    • sysext|confext|portable:系统扩展镜像。
    • none:当前配置用于分组,不实际生成镜像。
  • ManifestFormat=:生成镜像大纲(保存安装的所有软件包列表)的格式,可选jsonchangelog,默认不生成。
  • Output=:镜像文件/目录的前缀,默认为image
  • OutputDirectory=:构建产品的保存目录,默认为工作目录下的mkosi.output/
  • CompressOutput=:输出镜像的压缩方式,请注意,这会导致shellbootqemu命令在该镜像上不可用。仅可用于tarcpioukiespoci镜像。可选值有布尔值和xzzstd等压缩算法。默认使用zstd
    • CompressLevel=:(新版本)压缩等级。
  • SplitArtifacts=:分开导出的产品列表,使用逗号分隔,可选值有ukikernelinitrdpartitions
    • 默认值no等价于uki,kernel,initrd
    • uki会导出UKI。
    • kernelinitrd会导出组成UKI的内核和initramfs。
    • partitions在使用磁盘镜像时,会告诉systemd-repart针对每个分区分别进行导出。默认情况下,导出的分区文件会以镜像名.分区名命名。
    • OutputSplitRoot=:(旧版本)导出根分区镜像的名称。
    • OutputSplitVerify=:(旧版本)导出的校验分区的名称。
    • OutputSplitKernel=:(旧版本)导出的ESP分区的名称。
  • ImageVersion=:镜像的版本号。
    • 你也可以使用mkosi.version指定版本号,此文件还可以是一个可执行的Shell脚本,使其输出作为版本号。
  • ImageID=:镜像名称,会写入镜像的os-release文件中。
  • (新版本)RepartDirectories=systemd-repart的分区定义配置文件目录,默认为工作目录下的mkosi.repart/
    • 注意,MKOSI的分区配置文件中的CopyFiles=的源路径会使用镜像中的根文件系统。
  • (新版本)SectorSize=systemd-repart的块大小。
  • (新版本)Seed=systemd-repart使用的UUID,也可以使用mkosi.seed文件保存,默认值为random
  • (新版本)Overlay=:和BaseTrees=一起使用,是否仅仅保留相对于Base Tree发生改变的内容,用于构建系统扩展镜像。
    • 在构建系统扩展镜像时,可以使用该选项结合BaseTree=指定的目录以获取增量部分,即扩展镜像的部分。
  • (新版本)CleanScripts=:用于构建的最后阶段进行清理的脚本,默认为mkosi.clean

(v20以上版本)[Build]

  • ToolsTree=:在指定目录下搜索构建工具组,也可以使用mkosi.tools/目录保存,这有助于使用其他版本的构建工具组。
    • 如果值为default,那么会从指定的发行版的软件源中自动获取Tools Tree。在目标发行版的systemd版本低于构建所需的最低要求时,会自动启用。在当前发行版低于构建所需的最低systemd版本时,该选项必须启用。
    • ToolsTreeDistribution=:用于自动获取ToolsTree=的目标发行版。
    • ToolsTreeRelease=:用于自动获取ToolsTree=的目标发行版版本。
    • ToolsTreeMirror=:用于自动获取ToolsTree=的目标发行版镜像源。
    • ToolsTreeRepositories=:和Repositories=一样,但是用于构建ToolsTree=
    • ToolsTreeSandboxTrees=:和SandboxTrees=一样,但是用于构建ToolsTree=
    • ToolsTreePackages=:在ToolsTree=中包含的额外软件包。
    • ToolsTreeCertificates=:是否使用Tools Tree中的证书,这包括/etc/pki/etc/ssl/etc/ca-certificates/var/lib/ca-certificates,如果禁用,使用主机上的证书。
  • Incremental=:渐进构建,可选值为布尔值或strict。在执行mkosi.build脚本之前提供镜像缓存,这可以显著提高构建速度。如果为strict,那么如果缓存镜像不存在则会报错退出。
  • CacheOnly=:纯缓存模式,可选值有autometadataalwaysnever,如果为always,那么包管理器不会联网,只使用本地缓存;如果为metadata,那么包管理器不会联网更新元数据;如果为auto,那么仅在不存在缓存镜像时更新元数据。
  • SandboxTrees=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是MKOSI沙箱内的路径,表示在执行构建之前拷贝到沙箱的文件。也可以使用mkosi.sandbox/目录进行保存。
  • WorkspaceDirectory=:临时文件的保存目录,默认为$XDG_CACHE_HOME$HOME/.cache/var/tmp
  • CacheDirectory=:构建时软件包管理器缓存的保存目录,默认使用工作目录下的mkosi.cache/
  • PackageCacheDirectory=:软件包管理器的缓存目录。
  • BuildDirectory=:用于执行Out-of-Tree构建的构建目录,这使得构建产品可以在多次构建时共享。默认为mkosi.builddir/
  • UseSubvolumes=:是否使用Btrfs子卷,可选值有布尔值或auto
  • RepartOffline=:是否在不使用回环设备的情况下构建磁盘镜像,仅在以XFS为根文件系统且启用SELinux的情况下需要启用。
  • History=:是否保存历史,历史会被保存在.mkosi-private目录下。
  • ExtraSearchPaths=:用于搜索工具的额外目录,会在构建时被添加到$PATH中。
  • ProxyUrl=:使用的代理地址。
    • ProxyExclude=:不使用代理的主机名。
    • ProxyPeerCertificate=:代理Peer的证书。
    • ProxyClientCertificate=:代理客户端的证书。
    • ProxyCientKey=:代理客户端的Key。

[Content]

镜像配置:

  • Packages=:包含的包,使用逗号或是换行符分隔。会使用该发行版对应的包管理器进行安装。
  • BuildPackages=:在构建的Overlayfs中安装包。这样的包应当用于mkosi.build脚本。
  • PackageDirectories=:读取指定目录下的额外软件包并在构建时在镜像中进行安装。
  • (新版本)WithRecommends=:是否安装弱依赖,仅适用于aptdnfzypper。默认禁用。
  • WithDocs=:是否在镜像内保留文档。默认启用。
  • (新版本)MakeInitrd=:是否在镜像内创建/init/etc/initrd-release文件。该配置项通常在MKOSI-INITRD内部使用。
  • (新版本)BaseTrees=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是镜像内的路径,表示在获取发行版基本根文件系统之前,将主机文件/目录拷贝到镜像内。这也是Overlay=yes参考的基础文件系统。如果没有指定镜像内的路径,那么默认为/
  • SkeletonTrees=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是镜像内的路径,表示在已经构建基本根文件系统,开始安装软件包之前,将主机文件/目录拷贝到镜像内。
    • mkosi.skeleton/目录下的文件总是会被拷贝到相同的目标位置下。
  • ExtraTrees=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是镜像内的路径,表示在安装软件包之后,将主机文件/目录拷贝到镜像内。
    • mkosi.extra/目录下的文件总是会被拷贝到相同的目标位置下。
  • RemovePackages=:在构建后删除指定的包。
  • RemoveFiles=:在构建后删除指定的文件,路径使用镜像内的根文件系统。
  • CleanPackageMetadata=:是否在构建后清理镜像内包管理器的缓存。默认为是。
  • Bootable=:镜像是否可引导(是否安装引导程序),可选值除了布尔值还有auto。默认为auto,即尽可能设置。如果设为yes,那么在Bootloader=设置的Bootloader软件包不存在时,构建会失败。
    • 镜像内必须安装对应架构的内核。
    • (新版本)Bootloader=:使用的引导程序,可选nonesystemd-bootgrub。默认为systemd-boot
      • 如果Bootable=no,那么该配置项无意义。
      • 如果使用none,那么和Bootable=no效果一致。
      • 如果使用grub,那么镜像中必须安装对应架构的grub软件包,确保存在/usr/lib/grub/usr/share/grub2目录。
      • 如果使用systemd-boot,那么镜像中必须安装对应架构的grub软件包,确保存在bootctl程序。
    • (新版本)BiosBootloader=:是否安装BIOS引导,可选nonegrub,如果启用,那么需要创建大小为1MB,类型为21686148-6449-6e6f-744e-656564454649biosboot分区。默认为none
      • 如果Bootable=no,那么该配置项无意义。
      • 如果使用grub,那么镜像中必须安装对应架构的grub软件包。
    • (新版本)ShimBootLoader=:是否安装Shim,可选noneunsignedsigned。在SecureBoot=true的情况下,如果为unsigned,那么MKOSI会安装未签名的Shim,并自行对其进行签名,如果为signed,那么MKOSI会安装已签名的Shim。默认为none
      • 如果设为unsigned,那么镜像内必须安装shim-unsigned软件包。
      • 如果设为signed,那么镜像内必须安装shim-signed软件包。
      • 在没有启用SecureBoot=true的情况下,该选项没有意义。
  • (新版本)KernelModulesInclude=:在镜像中包含的内核模块,格式为相对于/usr/lib/modules/内核版本/kernel目录的路径正则表达式,这对于initramfs特别有用。
    • 关键词default表示包含MKOSI-INITRD的mkosi.conf中的所有默认配置,参见mkosi.conf。这也是未设置该配置项的默认值。
    • 关键词host表示包含宿主机当前加载的所有模块。
  • (新版本)KernelModulesExclude=:在镜像中排除的内核模块。
    • 因为MKOSI默认会加载mkosi-initrd的配置,因此如果希望重设模块,必须先设置KernelModulesExclude=.*

initramfs配置:

  • (新版本)KernelModulesInitrd=:是否在Bootable=true的镜像中使用MKOSI构建一个独立的,额外的initramfs,默认启用。
    • 这是推荐的行为,因为MKOSI开发组期望构建出的镜像使用与本地环境无关的initramfs。构建过程中会使用镜像内的安装的内核和固件(在安装时均会被MKOSI移动到/usr/lib/modules/目录下)进行打包。
  • (新版本)KernelModulesInitrdInclude=:在initramfs中包含的模块。
    • 关键词default表示包含MKOSI-INITRD的mkosi.conf中的所有默认配置,参见mkosi.conf。这也是未设置该配置项的默认值。
    • 关键词host表示包含宿主机当前加载的所有模块。
  • (新版本)KernelModulesInitrdExclude=:在initramfs中排除的模块。
  • UnifiedKernelImages=:是否使用UKI而不是独立initramfs。默认为auto,即尽可能使用。
    • 如果启用了UKI:如果systemd-repart发现了存在哈希值(即,启用了Verity)的根文件系统分区,那么会将其哈希值写入UKI的roothash=内核命令行参数中。否则,如果systemd-repart发现了存在哈希值(即,启用了Verity)的/usr/文件系统分区,那么会将其哈希值写入UKI的usrhash=内核命令行参数中。
    • 如果未启用UKI,且使用systemd-boot:如果systemd-repart发现了存在哈希值(即,启用了Verity)的根文件系统分区,那么会将其哈希值写入BLS引导条目的roothash=内核命令行参数中。否则,如果systemd-repart发现了存在哈希值(即,启用了Verity)的/usr/文件系统分区,那么会将其哈希值写入BLS引导条目的usrhash=内核命令行参数中。否则,如果找到了GPT PARTLABEL为root*的分区,那么会将其PARTUUID写入BLS引导条目的root=PARTUUID=内核命令行参数中。
    • 如果未启用UKI,且使用GRUB:如果systemd-repart发现了存在哈希值(即,启用了Verity)的根文件系统分区,那么会将其哈希值写入grub.cfgroothash=内核命令行参数中。否则,如果systemd-repart发现了存在哈希值(即,启用了Verity)的/usr/文件系统分区,那么会将其哈希值写入grub.cfgusrhash=内核命令行参数中。否则,如果找到了GPT PARTLABEL为root*的分区,那么会将其PARTUUID写入grub.cfgroot=PARTUUID=内核命令行参数中。否则,如果找到了GPT PARTLABEL为usr*的分区,那么会将其PARTUUID写入grub.cfgmount.usr=PARTUUID=内核命令行参数中。
    • UnifiedKernelImageFormat=:UKI文件命名,默认为&e-&k,支持的占位符有:
      • &&&本身。
      • &e:Token。
      • &k:内核版本。
      • &hroothash=usrhash=的值。
      • &c:引导尝试次数。
    • UnifiedKernelImageProfiles=:构建额外的UKI配置,接受逗号分隔的UKI配置文件。也可以保存在mkosi.uki-profiles/目录下。UKI配置以.conf结尾,只有一个UKIProfile段,可用内容有:
      • Profile=:配置信息,包括ID(必须)和TITLE
      • Cmdline=:内核命令行参数。
    • PeAddons=:(已被移除)构建额外的PE插件,接收逗号分隔的ukify配置文件,额外的PE插件会以配置文件名.addon.efi命名,也可以将ukify配置文件保存在mkosi.pe-addons/目录下。
  • (新版本)MicrocodeHost=:是否在initramfs中仅仅包含主机的CPU微码。默认禁用。
  • (新版本)InitrdPackages=:在initramfs中额外安装的软件包。
  • (新版本)Initrds=:使用用户提供的指定initramfs,而不是自行构建。
  • KernelCommandLine=:封装在UKI或initramfs内的内核命令行参数。
    • 如果检测到使用了Verity的root分区或usr分区,那么会自动填充roothash=分区Hash值usrhash=分区Hash值
    • 内核命令行参数root=PARTUUIDmount.usr=PARTUUID会被自动填充类型为root/usr的分区的UUID。

镜像预设配置:

  • MachineID=:设置镜像内的机器ID,默认为random,也可以使用mkosi.machine-id文件保存。
  • Locale=:镜像内的语言。
  • Keymap=:镜像内的Keymap。
  • Hostname=:设置镜像内的默认主机名。
  • RootShell=:Root用户的默认Shell。
  • RootPassword=(旧版本为Password=):设置镜像内Root用户的默认密码。
    • 也可以在工作目录下编写mkosi.rootpw文件。
  • Autologin=:是否设置Root用户在/dev/pts/0/dev/tty1/dev/hvc0的自动登录。
  • Ssh=:是否在镜像内创建SSH套接字以允许通过mkosi ssh连接,使用mkosi genkey创建密钥。
  • SELinuxRelabel=:是否在镜像内进行Relabel。

为了实现更多功能,还可以在构建镜像的不同过程中执行脚本:

  • (新版本)SyncScripts=:在同步软件包管理器元数据后执行的脚本,默认为mkosi.sync
  • PrepareScripts=:在安装软件包后立刻执行的脚本。默认为mkosi.prepare
  • BuildScripts=:在创建好根文件系统后立刻执行的脚本。默认为mkosi.build
    • BuildSources=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是镜像内的路径,进行构建时拷贝的代码树目录,镜像内的代码树目录总是会自动添加/work/src前缀。
    • BuildSourcesEphemeral=:是否不保留对源码树的更改,默认禁用。
  • PostInstallationScripts=:在配置完Extra Tree后执行的脚本,默认为mkosi.postinst
  • FinalizeScripts=:在构建完全完成后的最后阶段执行的脚本,默认为mkosi.finalize
  • PostOutputScripts=:在输出产品后执行的脚本,默认为mkosi.postoutput
  • Environment=:给脚本传递的环境变量。
  • EnvironmentFile=:从文件给脚本传递环境变量。
  • WithTests=:设为假,则在执行mkosi.build脚本时,$WITH_TESTS变量为0。
  • WithNetwork=:在执行脚本时提供网络。

[Validation]

  • SecureBoot=:是否启用安全启动,这会影响是否对UKI进行签名。
  • SecureBootAutoEnroll=:是否自动导入安全启动的Key。
    • 这仅对虚拟机生效,如果希望对物理机生效,在Extra Tree中包含/efi/loader/loader.conf文件,并写入secure-boot-enroll forcesecure-boot-enroll manual
  • SecureBootKey=:使用的安全启动PEM密钥。默认使用mkosi.key
  • SecureBootCertificate=:使用的安全启动X509证书。默认使用mkosi.crt
  • SecureBootSignTool=:为PE文件进行安全启动签名的程序,可选systemd-sbsignsbsignpesignauto
  • SignExpectedPcr=:使用systemd-measure在UKI中包含PCR(平台配置寄存器)的签名,可选布尔值或auto
  • Passphrase=:LUKS加密的密钥文件,必须是600权限位,它应当保存完整的LUKS密钥,以一个换行结尾(即/etc/crypttab文件的格式)。
    • 也可以选择在工作目录下创建mkosi.passphrase文件。
  • VerityKey=:用于为Verity设备签名的PEM密钥。默认使用mkosi.key
  • VerityCertificate=:用于为Verity设备签名的X509证书。默认使用mkosi.crt
  • Checksum=:指定镜像的校验码。
  • Sign=:是否用gpg给校验码签名。默认为否。
  • Key=:给校验码签名的密钥。
  • OpenPGPTool=:使用的OpenPGP实现,默认为gpg,可选值有sqoprsop

[Runtime]段(旧版本为[Host]段)

  • Credentials=:启动时传递给镜像中的systemd的系统凭据。

容器配置:

  • NSpawnSettings=:使用systemd-nspawn启动时,读取的.nspawn单元文件。
    • 默认使用mkosi.nspawn文件。

所有虚拟机配置项都仅适用于使用qemu启动的虚拟机,即只适用于磁盘类型的镜像:

  • VirtualMachineMonitor=:使用的虚拟机超管,可选值有qemuvmspawn
  • QemuGui=:若启用,会以图形界面形式启动Qemu。默认以Headless形式启动。
    • 旧版本下为QemuHeadless=:若启用,会以Headless形式启动Qemu。默认以图形界面形式启动。
  • QemuSmp=:设置Qemu的SMP。默认为2。
  • QemuMem=:设置Qemu的内存。默认为2G。
  • QemuKvm=:是否启用KVM加速,默认为auto
  • QemuVsock=:是否启用VSock,默认为auto
  • QemuVsockConnectionId=:VSock的连接ID,可选值有hashauto或手动输入。
  • QemuSwtpm=:是否启用TPM,默认为auto
  • QemuCdrom=:是否在Qemu中以CD形式读取镜像,默认禁用。
  • QemuBoot=:使用的Qemu固件,可选uefilinux
    • 新版本下改为QemuFirmware=:可选uefibioslinuxauto
    • 新版本下可以使用QemuFirmwareVariables=设置UEFI固件中的变量。
    • 新版本下可用QemuKernel=,指定使用的Direct Boot Kernel。
  • QemuDrives=:添加的驱动器,格式为ID:大小[:目录:选项]
  • RuntimeTrees=接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是虚拟机内的路径,如果没有后面的部分,则自动挂载在/root/src目录下。
  • RuntimeSize=:将镜像在启动时扩大到指定大小。
  • RuntimeNetwork=:使用的Qemu网络,可选值有userinterfacenone
  • RuntimeBuildSources=:挂载指定的源代码目录到虚拟机内的/work目录下。
  • RuntimeHome=:是否挂载主机的家目录到虚拟机的/root
  • RuntimeScratch=:是否在/var/tmp下挂载额外的空间。可选值为布尔值或auto
  • UnitProperties=:传递给NSPawn的Scope属性。
  • KernelCommandLineExtra=:通过Qemu传递的额外内核命令行参数。
    • 在Qemu中硬编码的额外内核命令行参数为(mkosi/mkosi/qemu.py):
      • rw
      • systemd.wants=network.target
      • module_blacklist=vmw_vmci systemd.tty.term.hvc0=宿主机终端类型 systemd.tty.columns.hvc0=宿主机终端列数 systemd.tty.rows.hvc0=宿主机终端行数
    • 除非明确地手动进行设置,否则存在以下内核命令行参数的默认值:
      • ip=enc0:any ip=enp0s1:any ip=enp0s2:any ip=host0:any ip=none
      • loglevel=4
      • SYSTEMD_SULOGIN_FORCE=1
  • QemuArgs:配置空格分隔的Qemu原生参数。
  • Ephemeral=:是否将镜像设为不可变,所有操作在关闭之后都会撤销。
  • SshKey=:使用的SSH私钥。
  • SshCertificate=:使用的SSH证书。
  • Machine=:机器名,用于SSH连接。
  • ForwardJournal=:是否将容器或虚拟机的日志发送到宿主机。
  • SysupdateDirectory=mkosi sysupdate使用的配置目录,默认使用mkosi.sysupdate/
    • MKOSI使用的Sysupdate配置必须设置PathRelativeTo=explicitPath=/才能正常使用sysupdate子命令进行更新,这会使得配置中的Path=使用mkosi.output/(或OutputDirectory=指定的目录)作为根。
    • 这同时也意味着,SplitArtifacts=必须启用以对分区进行更新,而且更新内容(分区镜像文件、EFI程序、initramfs)文件名必须与Sysupdate配置中的MatchPattern=匹配。

分区

(旧版本,现已删除)在[Partitions]段内支持以下配置项:

  • RootSize=:根分区大小,默认为3G。
  • ESPSize=:ESP大小,默认为256MB。仅在Bootable=yes时有效。
  • SwapSize=:Swap分区大小,默认为0。
  • HomeSize=/home分区大小,默认为0。
  • SrvSize=/srv分区大小,默认为0。
  • VarSize=/var分区大小。默认为0。
  • TmpSize=/var/tmp分区大小。默认为0。
  • BiosSize=biosboot分区大小,默认为0。
  • XbootldrSize=XBOOTLDR分区大小。默认为0。
  • UsrOnly=:仅仅保留/usr目录,很适合无状态的镜像。

MKOSI会从mkosi.repart/RepartDirectories=指定的目录中读取systemd-repart的配置文件,并据此对磁盘镜像进行分区。文件命名应当为优先级-分区名.conf,格式为:

1
2
3
4
5
6
7
[Partition]
Type=分区类型
Format=文件系统
# 是否精简化文件系统
Minimize=best|off|guess
SizeMinBytes=最小大小
SizeMaxBytes=最大大小

默认情况下,MKOSI使用以下内置配置:

00-esp.conf

1
2
3
4
5
6
7
[Partition]
Type=esp
Format=vfat
CopyFiles=/boot:/
CopyFiles=/efi:/
SizeMinBytes=512M
SizeMaxBytes=512M
  • 需要注意的是,CopyFiles=的源路径的根是构建中的镜像的根。

05-bios.conf

1
2
3
4
5
6
[Partition]
# UUID of the grub BIOS boot partition which grubs needs on GPT to
# embed itself into.
Type=21686148-6449-6e6f-744e-656564454649
SizeMinBytes=1M
SizeMaxBytes=1M

10-root.conf

1
2
3
4
5
[Partition]
Type=root
Format=<distribution-default-filesystem>
CopyFiles=/
Minimize=guess

资源目录

有以下相关的资源目录:

  • mkosi.conf.d/:额外的MKOSI配置,可以是文件,也可以是子工作目录,子工作目录支持的内容和MKOSI的主工作目录完全一致(必须包含mkosi.conf文件)。
    • 如果使用文件的话,那么文件中的[Match]段仅对该文件生效。
    • 如果使用子工作目录的话,那么子工作目录中的mkosi.conf中的[Match]段对整个子工作目录生效。
  • mkosi.local/:额外的本地MKOSI配置,可以是文件,也可以是子工作目录。
  • mkosi.skeleton/mkosi.skeleton.tar:在已经构建基本根文件系统,开始安装软件包之前,将文件/目录拷贝到镜像内。与SkeletonTrees=配置项有关。
  • mkosi.extra/mkosi.extra.tar:在安装软件包之后,将额外的文件/目录拷贝到镜像内。与ExtraTrees=配置项有关。
  • mkosi.pkgmngr/mkosi.pkgmngr.tar:在构建时配置包管理器,但是不将文件写入镜像。这可以作为在构建时手动配置软件包管理器的手段,如果需要包含到镜像中,应该使用mkosi.skeleton/mkosi.skeleton.tar
  • mkosi.nspawnsystemd-nspawn读取的容器单元文件。与NspawnSettings=配置项有关。
  • mkosi.cache/:构建时软件包管理器缓存的保存目录。与CacheDirectory=配置项有关。
  • mkosi.builddir/:用于执行Out-of-Tree构建的构建目录。与BuildDirectory=配置项有关。
  • mkosi.rootpw:镜像中Root用户的密码。文件必须具有0600或更低的权限位。与RootPasswordFile=配置项有关。
  • mkosi.passphrase:LUKS加密使用的密码。文件必须具有0600或更低的权限位。与PassphraseFile=配置项有关。
  • mkosi.crtmkosi.key:用于签名(UEFI SecureBoot、Verity等)的X.509证书和PEM私钥。与SecureBootCertificate=SecureBootKey=VerityCertificate=VerityKey=配置项有关。
  • mkosi.output/:用于保存所有的构建产品。与OutputDirectory=配置项有关。
  • mkosi.credentials/:额外的系统凭据。目录中的每个文件名将用作凭据名,文件内容成为凭据值。与Credentials=配置项有关。
  • mkosi.repart/:用于systemd-repart的分区定义文件,这些文件在构建磁盘镜像时传递给systemd-repart。如果存在mkosi.repart/或是使用了RepartDirectories=,那么就不会使用任何默认的分区定义。与RepartDirectories=配置项有关。
  • mkosi.uki-profiles/:额外的UKI配置文件。与UnifiedKernelImageProfiles=配置项有关。
  • mkosi.pe-addons/:额外的PE插件的ukify配置文件。与PeAddons=配置项有关。

钩子脚本

钩子脚本如果在末尾添加了.chroot后缀(例如,mkosi.postinst.chroot),那么会Chroot到镜像内部执行,否则会在工作环境中执行,脚本中有以下可用变量:

Variable 含义 configure sync prepare build postinst finalize postoutput clean
$ARCHITECTURE 镜像的架构
$QEMU_ARCHITECTURE Qemu格式的架构,用于通过qemu-system-$QEMU_ARCHITECTURE获取Qemu程序
$DISTRIBUTION 镜像的发行版
$DISTRIBUTION_ARCHITECTURE 发行版格式的架构
$RELEASE 镜像的版本
$PROFILES Profiles=中设置的配置文件,使用逗号分隔
$CACHED 如果有可用的缓存,为1
$CHROOT_SCRIPT Chroot的目标路径,用于结合mkosi-chroot使用
$SRCDIR 执行Chroot前的路径
$CHROOT_SRCDIR 执行Chroot以后,原来的目录所在的路径
$BUILDROOT 当前正在构建的镜像的根目录
$BUILDDIR 树外构建目录的路径
$CHROOT_BUILDDIR Chroot以后,树外构建目录的路径
$DESTDIR 构建脚本生成产品的保存目录
$CHROOT_DESTDIR Chroot以后,构建脚本生成产品的保存目录
$OUTPUTDIR 构建产品的暂存目录
$CHROOT_OUTPUTDIR Chroot以后,构建产品的暂存目录
$PACKAGEDIR 本地包存储的目录
$ARTIFACTDIR 构建产品的目录
$WITH_DOCS 是否有文档
$WITH_TESTS 是否有测试
$WITH_NETWORK 是否有网络
$SOURCE_DATE_EPOCH 如果设置了SourceDateEpoch=TIMESTAMP,那么会保存时间戳
$MKOSI_UID 调用mkosi的用户UID
$MKOSI_GID 调用mkosi的用户GID
$MKOSI_CONFIG 当前镜像配置的JSON
$IMAGE_ID ImageID=的值
$IMAGE_VERSION ImageVersion=的值

此外,以下命令可用:

  • mkosi-chroot:一个封装的chroot,会自动Chroot到镜像内执行命令。
  • 镜像的包管理器可用,你也可以使用mkosi-install|remove|upgrade|reinstall封装工具。
  • git已经自动配置好safe.directory=*
  • useraddgroupadd已经自动配置好--root=$BUILDROOT

执行流程

创建MKOSI沙箱:

  1. 解析命令行选项。
  2. 解析配置文件。
  3. 执行mkosi.configure脚本。
  4. 检查当前用户是不是Root,如果当前用户不是Root,则尝试根据当前用户的SubUID和SubGID隔离用户命名空间。
  5. 隔离挂载命名空间。
  6. 只读化重新挂载各种目录,包括:
    • /usr
    • /etc
    • /opt
    • /srv
    • /boot
    • /efi
    • /media
    • /mnt

开始构建镜像:

  1. (新版本)拷贝Sandbox Tree(mkosi.sandbox/)中的文件到镜像中。
  2. 更新软件包元数据。
  3. 执行Sync脚本(mkosi.sync),
  4. 拷贝Base Tree(mkosi.base/)中的文件到镜像中。
  5. 检查并尝试复用缓存镜像。
  6. 拷贝一份包管理器元数据到镜像内。
  7. 拷贝Skeleton Tree(mkosi.skeleton/)中的文件到镜像中。
  8. 安装发行版及其软件包。
  9. 使用final参数执行mkosi.prepare脚本。
  10. 如果存在mkosi.build脚本的话,在Overlayfs中安装构建所需的软件包。
  11. 使用build参数执行mkosi.prepare脚本。
  12. 如果存在对应的配置的话,缓存该镜像。
  13. 执行mkosi.build脚本。
  14. 结束构建。
  15. 拷贝构建脚本的产物到镜像内。
  16. 拷贝Extra Tree(mkosi.extra/)中的文件到镜像中。
  17. 执行mkosi.postinst脚本。
  18. 写入配置项中配置的其他杂项文件。例如Ssh=Autologin=MakeInitrd=
  19. 安装systemd-boot,并按需配置安全启动。
  20. 执行systemd-sysusers
  21. 执行systemd-tmpfiles
  22. 执行systemctl preset-all
  23. 执行depmod
  24. 执行systemd-firstboot
  25. 执行systemd-hwdb
  26. 移除指定的软件包和文件。
  27. 按需执行SELinux Relabel。
  28. 执行mkosi.finalize脚本。
  29. 如果存在对应的配置的话,生成UKI镜像。
  30. 导出产物。
  31. 执行mkosi.postoutput脚本。

最佳实践

  1. 创建工作目录,并使用mkosi.confmkosi.conf.d/*.conf(或mkosi.conf.d/*/mkosi.conf)配置文件以固化构建信息。
  2. 使用mkosi.postinst.chroot脚本在镜像中进行配置。
  3. 使用mkosi.extra向镜像中传递额外文件。
  4. 使用mkosi.build.chroot基本进行流水线构建。在脚本中使用$BUILDDIR$SRCDIR获取目录。
  5. 缓存有两种方式:使用mkosi.cache目录进行包缓存,或是使用Incremental=进行渐进构建。需要重建渐进缓存时,使用-ff选项。
  6. 要测试不同的内核命令行参数的效果,使用KernelCommandLineExtra=而不是重建镜像,以提高效率。
  7. 可以在无特权的情况下构建磁盘镜像,但是容器文件系统仍然需要Root权限。
  8. 在启用SecureBoot=yes时,可以执行mkosi genkey直接生成证书和密钥,也可以自己创建mkosi.keymkosi.crt。你还可以使用ShimBootloader=yes以安装Shim。
  9. 使用BiosBootloader=grub以为BIOS引导安装GRUB。
  10. 使用useradd -m -g $USER --password "$(openssl passwd -stdin -6 <<< '密码明文')"添加一个普通用户。
  11. 在Debian中mkosi qemu不可用,解决方案为执行cp /usr/lib/tmpfiles.d/static-nodes-permissions.conf /etc/tmpfiles.d/static-nodes-permissions.conf,修改/dev/kvm的权限位为0666
  12. 在非特权模式构建时,chown()调用是被禁用的(钩子脚本中没有禁用,要禁用,设置环境变量MKOSI_CHROOT_SUPPRESS_CHOWN=1),要解决该问题,使用SubUID:unshare --map-auto --map-current-user --setuid 0 --setgid 0
  13. 在你正式构建时,使用WithRecommends=no以安装弱依赖。
  14. 在使用dd克隆产品镜像前,执行truncate -s|--size=文件大小 镜像文件.img以调整磁盘镜像大小。
  15. MKOSI可以负责构建initramfs,因此一个没有initramfs构建工具的镜像是可能的。
    • 不过需要注意的是,MKOSI构建的initramfs中会使用systemd作为/init,这与使用MKOSI-INITRD构建的initramfs是一致的。
  16. 使用mkosi.profiles/保存不同的镜像口味,使用mkosi.conf.d/保存不同情况下的镜像配置,对于多种子场景的情况,将情况相对少且固定的子场景放在父目录(例如,输出格式为容器或VM),将情况相对较多且不固定的子场景放在子目录(例如,版本)。
  17. 使用Format=ukiMakeInitrd=no来创建一个USI(Unified System Image,单个UEFI PE形式的完整系统)。

发行版最小化构建

一些发行版的最小化构建软件包如下:

Arch

1
2
3
[Content]
Packages=linux
systemd

Fedora

1
2
3
4
5
6
[Content]
Packages=kernel
systemd
systemd-boot
udev
util-linux

CentOS/Rocky

1
2
3
4
5
[Content]
Packages=kernel
systemd
systemd-boot
udev

Debian

1
2
3
4
5
6
7
[Content]
Packages=linux-image-generic
systemd
systemd-boot
systemd-sysv
udev
dbus

Ubuntu

1
2
3
4
5
6
7
8
9
[Distribution]
Repositories=main,universe

[Content]
Packages=linux-image-generic
systemd
systemd-sysv
udev
dbus

OpenSUSE

1
2
3
4
[Content]
Packages=kernel-default
systemd
udev

实例

构建一个Fedora容器镜像

使用命令行:

1
mkosi -d fedora -p kernel,systemd,systemd-udev,systemd-boot --format directory build

使用配置文件:

1
2
3
4
5
6
7
8
9
# mkosi.conf
[Distribution]
Distribution=fedora

[Output]
Format=directory

[Content]
Packages=kernel,systemd,systemd-udev,systemd-boot

然后执行:

1
mkosi build

构建一个Debian可引导磁盘镜像

使用配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# mkosi.conf
[Distribution]
Distribution=debian
Mirror=https://mirrors.tuna.tsinghua.edu.cn/debian
RepositoryKeyCheck=no

[Output]
Format=gpt_ext4
Qcow2=yes
Output=debian-12-minimal

[Content]
Bootable=yes
Packages=linux-image-generic
systemd
systemd-boot
systemd-sysv
udev
dbus
WithDocs=yes
CleanPackageMetadata=yes
WithUnifiedKernelImages=no
KernelCommandLine=console=ttyS0
Password=toor

[Partitions]
RootSize=10G
ESPSize=200M
BiosSize=1M

[Validation]
SecureBoot=no

然后执行:

1
mkosi build

构建initramfs

mkosi.conf

1
2
3
4
5
6
7
[Output]
Format=cpio

[Content]
Packages=systemd
udev
kmod

构建一个USI(UEFI PE程序形式的完整系统镜像)

具体的精简方式见Build USI

mkosi.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[Distribution]
# 发行版
Distribution=arch

[Content]
MakeInitrd=no
# USI不需要安装任何Bootloader
Bootloader=none
KernelCommandLine=rw
# 我们不希望交互式初始化,跳过Firstboot
Locale=
Keymap=
Timezone=
RootPassword=
Packages=
base
linux
linux-firmware
systemd
less

[Output]
Format=uki
CompressOutput=zst
ImageId=example-usi

构建一个系统扩展镜像

mkosi.conf

1
2
3
4
5
6
7
8
[Output]
# 如果不希望保留基础镜像的话,使用
# Format=none
Format=disk

[Content]
# 会拷贝到构建时的
BaseTrees=%O/base

mkosi.images/base/mkosi.conf(扩展镜像相对的基础镜像):

1
2
3
4
5
6
7
8
9
10
11
[Output]
Format=directory
# 该镜像完全是个中间镜像
# 版本号在这里无意义
ImageVersion=

[Content]
Bootable=no
CleanPackageMetadata=no
Packages=systemd
udev
  • 在启用了Incremental=yes的情况下,子镜像不能是Bootable=yes,否则会导致Cache冲突。

mkosi.images/btrfs/mkosi.conf

1
2
3
4
5
6
7
8
9
10
11
[Config]
Dependencies=base

[Output]
Format=sysext
Overlay=yes

[Content]
Bootable=no
BaseTrees=%O/base
Packages=btrfs-progs

构建一个不可变操作系统

首先创建一个如下分区的操作系统:

00-esp.conf

1
2
3
4
5
6
7
[Partition]
Type=esp
Format=vfat
CopyFiles=/efi:/
CopyFiles=/boot:/
SizeMinBytes=1G
SizeMaxBytes=1G

10-root-verity-sig.conf

1
2
3
4
5
6
[Partition]
Type=root-verity-sig
Label=%M_%A_verity_sig
Verity=signature
VerityMatchKey=root
SplitName=%t.%U

11-root-verity.conf

1
2
3
4
5
6
7
8
[Partition]
Type=root-verity
Label=%M_%A_verity
Verity=hash
VerityMatchKey=root
SizeMinBytes=300M
SizeMaxBytes=300M
SplitName=%t.%U

12-root.conf

1
2
3
4
5
6
7
8
9
10
[Partition]
Type=root
Format=erofs
Label=%M_%A_root
Verity=data
VerityMatchKey=root
CopyFiles=/
ExcludeFilesTarget=/var/
Minimize=yes
SplitName=%t.%U

除此之外我们还需要构建一个initramfs,initramfs中的systemd-repart.service的启动时间需要进行些许修改:

mkosi.images/initrd/mkosi.conf

1
2
[Include]
Include=mkosi-initrd

mkosi.images/initrd/mkosi.extra/usr/lib/systemd/system/systemd-repart.service.d/sysroot.conf

1
2
3
[Unit]
After=sysroot.mount
ConditionDirectoryNotEmpty=|/sysroot/usr/lib/repart.d

为了进行AB更新,我们还需要在镜像中包含一些预设的分区配置:

mkosi.extra/usr/lib/repart.d/00-esp.conf

1
2
[Partition]
Type=esp

mkosi.extra/usr/lib/repart.d/10-root-verity-sig.conf

1
2
3
[Partition]
Type=root-verity-sig
Label=%M_%A_verity_sig

mkosi.extra/usr/lib/repart.d/11-root-verity.conf

1
2
3
[Partition]
Type=root-verity
Label=%M_%A_verity

mkosi.extra/usr/lib/repart.d/12-root.conf

1
2
3
4
5
[Partition]
Type=root
Label=%M_%A
SizeMinBytes=2G
SizeMaxBytes=2G

mkosi.extra/usr/lib/repart.d/20-root-verity-sig.conf

1
2
3
[Partition]
Type=root-verity-sig
Label=_empty

mkosi.extra/usr/lib/repart.d/21-root-verity.conf

1
2
3
4
5
[Partition]
Type=root-verity
Label=_empty
SizeMinBytes=300M
SizeMaxBytes=300M

mkosi.extra/usr/lib/repart.d/22-root.conf

1
2
3
4
5
[Partition]
Type=root
Label=_empty
SizeMinBytes=2G
SizeMaxBytes=2G

mkosi.extra/usr/lib/repart.d/30-swap.conf

1
2
3
4
5
6
[Partition]
Type=swap
Format=swap
Encrypt=tpm2
SizeMinBytes=4G
SizeMaxBytes=4G

mkosi.extra/usr/lib/repart.d/40-var.conf

1
2
3
4
5
[Partition]
Type=var
Format=ext4
Encrypt=tpm2
SizeMinBytes=2G

因为/etc不可变,所以我们还需要在构建时提供Machine ID:

1
systemd-id128 new > mkosi.machine-id

最后为了未来镜像中的系统更新,我们还需要提供systemd-sysupdate的配置(你需要自行补充[Source]段):

mkosi.extra/usr/lib/sysupdate.d/10-root-verity-sig.transfer

1
2
3
4
5
6
7
8
9
10
[Transfer]
ProtectVersion=%A

[Target]
Type=partition
Path=auto
MatchPattern=%M_@v_verity_sig
MatchPartitionType=root-verity-sig
PartitionFlags=0
ReadOnly=1

mkosi.extra/usr/lib/sysupdate.d/11-root-verity.transfer

1
2
3
4
5
6
7
8
9
10
[Transfer]
ProtectVersion=%A

[Target]
Type=partition
Path=auto
MatchPattern=%M_@v_verity
MatchPartitionType=root-verity
PartitionFlags=0
ReadOnly=1

mkosi.extra/usr/lib/sysupdate.d/12-root.transfer

1
2
3
4
5
6
7
8
9
10
[Transfer]
ProtectVersion=%A

[Target]
Type=partition
Path=auto
MatchPattern=%M_@v
MatchPartitionType=root
PartitionFlags=0
ReadOnly=1

mkosi.extra/usr/lib/sysupdate.d/20-uki.transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Transfer]
ProtectVersion=%A

[Target]
Type=regular-file
Path=/EFI/Linux
PathRelativeTo=boot
MatchPattern=%M_@v+@l-@d.efi \
%M_@v+@l.efi \
%M_@v.efi
Mode=0444
TriesLeft=3
TriesDone=0
InstancesMax=2

构建一个不安全的/usr不可变操作系统

由于上文提到的原因(UKI必须使用Verity Hash对/usr/分区进行定位),我们无法让MKOSI在构建镜像时,自动向UKI中填充/usr/分区所在的位置,因此,我们必须手动在mkosi.conf中设置:

1
2
3
4
[Content]
# 要么根文件系统在initramfs中是可写的,要么存在/usr目录,否则会导致挂载失败
# 也可以使用mount.usr=PARTUUID
KernelCommandLine=rw mount.usr=PARTLABEL=%i_%v

systemd-repart配置如下:

00-esp.conf

1
2
3
4
5
6
7
[Partition]
Type=esp
Format=vfat
CopyFiles=/efi:/
CopyFiles=/boot:/
SizeMinBytes=1G
SizeMaxBytes=1G

10-usr.conf

1
2
3
4
5
6
7
8
[Partition]
Type=usr
# 注意这里,和内核命令行参数一致
Label=%M_%A
Format=erofs
CopyFiles=/usr:/
Minimize=yes
SplitName=%t.%U

此外,还需要在镜像内提供一些分区配置:

mkosi.extra/usr/lib/repart.d/00-esp.conf

1
2
[Partition]
Type=esp

mkosi.extra/usr/lib/repart.d/10-usr-verA.conf

1
2
3
4
5
6
[Partition]
Type=usr
Label=%M_%A
SizeMinBytes=5G
SizeMaxBytes=20G
Weight=2000

mkosi.extra/usr/lib/repart.d/20-usr-verB.conf

1
2
3
4
5
6
[Partition]
Type=usr
Label=_empty
SizeMinBytes=5G
SizeMaxBytes=20G
Weight=2000

mkosi.extra/usr/lib/repart.d/30-swap.conf

1
2
3
4
5
[Partition]
Type=swap
Format=swap
SizeMinBytes=4G
SizeMaxBytes=4G

mkosi.extra/usr/lib/repart.d/40-root.conf

1
2
3
4
5
6
7
8
[Partition]
Type=root
Format=btrfs
SizeMinBytes=1G
Weight=20000
Subvolumes=/var
# 这是为了使systemd-journald使用持久模式
MakeDirectories=/var/log/journal

mkosi.extra/usr/lib/repart.d/50-home.conf

1
2
3
4
5
[Partition]
Type=home
Format=btrfs
SizeMinBytes=1G
Weight=40000

我们还需要确保/etc/目录在启动时动态生成,因此需要:

mkosi.extra/usr/lib/tmpfiles.d/etc.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 仅作参考
# This overrides the same file from systemd since we want to symlink everything
# into /etc instead of copying so updates to /usr propagate properly.
L /etc/os-release - - - - ../usr/lib/os-release
# mtab needs to be refreshed on every boot.
L+ /etc/mtab - - - - ../proc/self/mounts
# Contains the default systemd locale
L /etc/locale.conf
L /etc/nsswitch.conf
L /etc/issue
L /etc/profile
# Required by pam_env plugin
L /etc/security
L /etc/bash.bashrc
L /etc/bash.bash_logout
# Canonical location to look for certificates
L /etc/ca-certificates
L /etc/debuginfod
L /etc/ssh
# Canonical location to look for certificates
L /etc/ssl
# Required by pam environment plugin
L /etc/environment
# Contains the archlinux keyring required to build images
L /etc/pacman.d
# Required to generate desktop environment application menus
L /etc/xdg
# Contains default font configuration
L /etc/fonts
# Configuration for man
L /etc/man_db.conf
# Configuration for ldconfig
L /etc/ld.so.conf
L /etc/ld.so.conf.d
# We NEED authselect to correctly login
L /etc/authselect
L /etc/sysconfig

为了确保ldconfig.service/etc/目录生成后再启动,我们还需要:

mkosi.extra/usr/lib/systemd/system/ldconfig.service.d/tmpfiles.conf

1
2
[Unit]
After=systemd-tmpfiles-setup.service

最后,我们需要MKOSI的更新配置:

mkosi.sysupdate/10-usr.transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Transfer]
ProtectVersion=%A

[Source]
Type=regular-file
Path=/
PathRelativeTo=explicit
MatchPattern=%M_@v.usr-%a.@u.raw

[Target]
Type=partition
Path=auto
MatchPattern=%M_@v
MatchPartitionType=usr
PartitionFlags=0
ReadOnly=1

mkosi.sysupdate/20-uki.transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Transfer]
ProtectVersion=%A

[Source]
Type=regular-file
Path=/
PathRelativeTo=explicit
MatchPattern=%M_@v.efi

[Target]
Type=regular-file
Path=/EFI/Linux
PathRelativeTo=boot
MatchPattern=%M_@v+@l-@d.efi \
%M_@v+@l.efi \
%M_@v.efi
Mode=0444
TriesLeft=3
TriesDone=0
InstancesMax=2