MKOSI笔记
MKOSI-INITRD
mkosi-initrd是使用MKOSI进行Initrd构建的工具。
1 | mkosi-initrd [选项] |
可用选项有:
-k|--kernel-version=:使用的内核版本,默认使用$(uname -r)。-t|--format=:输出格式,可选值有cpio或uki,默认使用cpio。-g|--generic:生成通用Initrd,即可以在不同的硬件平台上启动当前系统的Initrd。-o|--output=:输出文件名前缀,默认为initrd。-O|--output-dir=:输出的目录,默认为工作目录。--profile:启用的额外Initrd配置,默认不启用。lvm:LVM支持。
--debug:Debug输出。--debug-sandbox:使用strace运行mkosi-sandbox。
它本质上是一个使用了特殊的默认配置的MKOSI Wrapper脚本(见mkosi.conf),在执行时,它会获取当前主机的内核版本(或用户指定的内核版本),并将当前内核的/usr/lib/modules/版本号目录映射到镜像中进行构建。
该命令行工具由MKOSI的Pypi包提供,对于发行版来说,它们也可以使用Kernel-install脚本进行集成(参考setup-mkosi)。
MKOSI-ADDON
使用MKOSI构建UKI插件的工具。它会包含当前系统环境中的/etc/crypttab中标记为x-initrd.attach的条目,以及/etc/kernel/cmdline的内容。
1 | mkosi-addon [选项] |
可用选项有:
--kernel-version=:使用的内核版本,默认使用$(uname -r)。-o|--output=:输出文件名前缀,默认为initrd。-O|--output-dir=:输出的目录,默认为工作目录。--debug-sandbox:使用strace运行mkosi-sandbox。
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命名空间。--pack-fds:将继承的文件描述符打包暴露到$LISTEN_FDS环境变量中。
实例:
1 | mkosi-sandbox --bind / / --same-dir --become-root bash --login |
1 | mkosi-sandbox \ |
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 --installroot、debootstrap、pacstrap等工具之上的,快速构建客制化的系统镜像并进行测试的工具。
“镜像”在这里是一个通用概念,这意味着MKOSI可以生成以下类型的“镜像”:
- 使用GPT分区表的磁盘镜像,可以克隆到磁盘中。
- CPIO归档,用于作为Initrd使用。
- USI(Unified System Image,即将完整的操作系统打包进UKI的镜像)
- Sysext、Confext、Portable Service镜像
- 用于容器的目录树镜像
安装
mkosi在各大发行版的软件源中有提供:
1 | # Debian |
也可以使用pip安装:
1 | pipx install git+https://mirror.ghproxy.com/https://github.com/systemd/mkosi.git |
提示:pipx安装的程序在venv中运行,因此不可以使用系统Python解释执行。
语法
1 | mkosi [选项] 子命令 |
构建配置文件
mkosi的任何命令都基于特定的规则读取当前目录下的,或是mkosi/目录下的配置文件,优先级顺序分别是:
- 最优先:命令行选项。
- 当前目录下的
mkosi.local.conf配置文件和mkosi.local目录下的配置文件。该文件仅用于本地配置,不应该共享。 -C|--directory=指定目录(默认为当前目录)下的mkosi.conf配置文件。--directory=指定目录(默认为当前目录)下的mkosi.conf.d/目录内的配置文件。- 用户指定的Profile对应的
mkosi.profiles/里面的子目录配置。 mkosi.images/里面的子镜像的配置。
子命令
mkosi由多种子命令组成,这些命令其实都是一些外部工具的简易接口,它们大部分支持通过--分隔传递一些含有特殊意义的参数:
summary:打印配置总结。- 默认情况下:打印最新构建的配置总结,也就是
.mkosi-private/history/latest.json的内容。 -f:强制重新解析配置文件并打印当前配置文件的配置总结。
- 默认情况下:打印最新构建的配置总结,也就是
build:从配置文件中读取配置并开始构建镜像。--之后的命令行参数会被传递给mkosi.build脚本。
sysupdate:(需要宿主机的systemd版本大于v257)执行systemd-sysupdate,其中,--transfer-source=设为输出目录(默认为mkosi.output/),--definition=设为SysupdateDirectory=的值(默认为mkosi.sysupdate/)。- 要求:MKOSI使用的Sysupdate配置中,必须设置
PathRelativeTo=explicit和Path=/才能正常使用sysupdate子命令进行更新,这会使得配置中的Path=使用mkosi.output/(或OutputDirectory=指定的目录)作为根。 - (v27以后不再需要)这同时也意味着,
SplitArtifacts=partitions必须启用以对分区进行更新,而且更新内容(分区镜像文件、EFI程序、Initrd)文件名必须与Sysupdate配置中的MatchPattern=匹配。- v27以后,MKOSI会在执行
mkosi sysupdate时隐式自动地调用systemd-repart对镜像进行分割。
- v27以后,MKOSI会在执行
--之后的命令行参数会被传递为systemd-sysupdate的额外参数。
- 要求:MKOSI使用的Sysupdate配置中,必须设置
shell:(如果还没有构建)先构建镜像,然后调用systemd-nspawnChroot到镜像内。- 仅适用于
QCow2=no且未压缩的镜像。 --之后的命令行参数会被在镜像中执行。
- 仅适用于
boot:(如果还没有构建)先构建镜像,然后调用systemd-nspawn以容器形式启动镜像。- 仅适用于
QCow2=no且未压缩的镜像。 --之后的命令行参数会被传递为执行的命令行。
- 仅适用于
vm:(如果还没有构建)先构建镜像,然后调用qemu或systemd-vmspawn以虚拟机形式启动镜像。- 对于目录树,MKOSI使用
virtiofsd引导。 - 任何
--之后的命令行参数都会作为命令行参数被传递给qemu或systemd-vmspawn。
- 对于目录树,MKOSI使用
serve:(如果还没有构建)先构建镜像,然后调用内置的HTTP服务器在8081端口上暴露工作目录。- (仅用于
disk镜像)burn 块设备:(如果还没有构建)先构建镜像,然后将镜像扩展到目标块设备的大小,再烧录到指定的块设备上。 genkey:创建密钥。ssh:对于Ssh=yes的镜像,通过ssh连接到镜像中。--之后的命令行参数会被传递为ssh的额外参数。
journalctl:获取镜像内的日志。--之后的命令行参数会被传递为journalctl的额外参数。
coredumpctl:获取镜像内的内核转储文件。--之后的命令行参数会被传递为coredumpctl的额外参数。
box:在构建沙箱中执行命令。--之后的命令行参数会被传递为执行的命令行。
clean:清理之前的构建产品。clean的作用范围由执行时生效的Dependencies=决定,它会清理整个依赖链条上的所有镜像,而非仅仅清理指定的单个目标。因此用--dependency X clean时,X的所有依赖镜像也会被一并清理。所以请不要依赖clean清理单个构建产物,你应该直接执行rm。- 加一次
-f|--force,会导致同时清理镜像缓存和Tools Tree。 - 加两次
-f|--force,会导致同时清理软件包缓存。
bump:如果启用了版本管理(提供--image-version=命令行选项或是ImageVersion=配置项),那么刷新mkosi.version文件中的镜像版本,如果mkosi.bump文件存在,那么会执行该文件并使用它的输出,否则使用MKOSI的内置逻辑(版本数字+1)。- 执行该命令时必须确保
mkosi.version文件存在,第一次执行时可以使用bash mkosi.bump > mkosi.version。 - 在启用了
History=yes的情况下,更新版本前需要执行mkosi clean。
- 执行该命令时必须确保
genkey:生成安全启动密钥。init:安装MKOSI的tmpfiles配置文件。如果XDG_CONFIG_HOME=/或XDG_CONFIG_HOME=/root,那么会安装到/etc/tmpfiles.d/,否则会安装到$XDG_CONFIG_HOME/user-tmpfiles.d/。
命令行选项
这里提到的命令行选项是和构建配置完全无关的:
-f|--force:进行指定操作前,不加分辨地重新构建镜像。如果该选项出现两次,那么还会不加分辨地清除缓存,这在进行系统更新时很有用。-R|--run-build-scripts:仅仅只执行mkosi.build脚本(类似于Format=none的行为)。-B|--auto-bump:执行操作后自动刷新版本号。-C|--directory=:指定工作目录,默认为当前工作目录。--debug:启用Debug级输出。--debug-shell:执行操作失败时,在/work/src目录下启动一个Debug Shell用于排错。--debug-workspace:执行操作失败时,当前工作空间的内容不会删除。--debug-sandbox:使用strace运行mkosi-sandbox。
构建配置
构建配置文件符合.ini格式,每一行都是Key=Value的形式,每一个配置项都有等价的命令行选项,转换方式是SomeSetting到some-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=:构建的架构。可用的值有alpha、arc、arm、arm64、ia64、loongarch64、mips64-le、mips-le、parisc、ppc、ppc64、ppc64-le、riscv32、riscv64、s390、s390x、tilegx、x86、x86-64、uefi。HostArchitecture=:主机的架构。Image=:构建的镜像名。子镜像名是它在mkosi.images/下的目录名,主镜像名总是main。ImageId=:构建的镜像ID。ImageVersion=:构建的镜像版本。Bootable=:构建的镜像是否可引导。Format=:构建的镜像格式。ToolsTreeDistribution=:使用的Tools Tree发行版。PathExists=:主机上存在指定目录。SystemdVersion=:主机上的systemd版本。
[Match]段之间是纯粹的AND关系,也就是说,不同的[Match]段必须全部满足。而[TriggerMatch]段之间是OR关系,也就是说,不同的[TriggerMatch]段中,只要有一个[TriggerMatch]段满足即可。
[Assert]段与[TriggerAssert]段
强限制构建时必须满足特定条件。一旦不满足,则立刻发生错误并报告问题。适用于对用户进行友好的限制。
可用配置项和[Match]完全一致。[TriggerAssert]段和[Assert]段的关系也与[Match]段和[TriggerMatch]段的关系一致。
(v20以上版本)[Include]段
Include=:导入指定的额外配置文件。如果值为mkosi-initrd,那么会使用mkosi-initrd的配置生成Initrd。- 特殊值
mkosi-initrd表示mkosi-initrd的配置。- 这是推荐的行为,因为MKOSI开发组期望构建出的镜像使用与本地环境无关的Initrd。构建过程中会使用镜像内的安装的内核和固件(在安装时均会被MKOSI移动到
/usr/lib/modules/目录下)进行打包。
- 这是推荐的行为,因为MKOSI开发组期望构建出的镜像使用与本地环境无关的Initrd。构建过程中会使用镜像内的安装的内核和固件(在安装时均会被MKOSI移动到
- 特殊值
mkosi-tools表示ToolsTree的配置。 - 特殊值
mkosi-vm表示运行虚拟机时的默认配置。
- 特殊值
(v20以上版本)[Config]段
额外配置。
Profiles=:使用指定的Profile,Profile是mkosi.profiles/目录下保存的一个配置文件或子工作目录,它支持的内容和MKOSI的主工作目录完全一致(除了不能再包含mkosi.profiles/目录),会覆盖主工作目录的对应部分。- Profile通常是让用户通过
--profile=命令行选项进行自选的,因此很适合表达“同一镜像的不同口味”或“可选功能集”。另外,Profiles=还可以参与[Match]匹配,因此它不仅能“额外引入一段配置”,还会影响条件配置是否生效。 - 子镜像中也可以创建Profile,但是子镜像中不能设置
Profiles=,而且子镜像的Profile必须是工作目录,不可以是配置文件。 - Profile是继承的:在主镜像上通过
Profiles=或--profile=启用的Profile,会传递给所有即将被构建的子镜像,无论这些镜像是不是定义了这个Profile。因此,切换Profile的影响范围并不局限于主镜像,而是会参与所有相关子镜像的最终配置计算。 - Profile会影响缓存:只要某个子镜像的最终生效配置因此发生变化(例如包列表、仓库、脚本等变化),MKOSI就会判定该子镜像的渐进构建缓存失效,并触发这个子镜像清理并重新构建。
- Profile通常是让用户通过
ConfigureScript=:在读取配置文件后(ToolsTree构建之前),在宿主环境中立刻执行的脚本,它会通过标准输入接收JSON格式的配置文件内容,应当通过标准输出输出JSON格式的配置文件内容,用于动态创建或修改构建配置。默认为mkosi.configure。MinimumVersion=:要求的最小MKOSI版本号。可以是一个版本号,或是一个以commit:开头的Git Commit Hash。PassEnvironment=:传递的环境变量,空格分隔。Dependencies=:子镜像的依赖关系,子镜像是mkosi.images/目录下保存的一个子工作目录,它支持的配置项和MKOSI的主工作目录相比会少一些。- 该配置项对应的命令行选项是
--dependency,不是--dependencies。 - 在主(Main)镜像中设置:选择要构建的子镜像子集(及其依赖)。未设置时,默认构建所有子镜像,即相当于
Dependencies=*,如果有Dependencies=,那么只构建指定的子镜像。 - 在子镜像中设置:声明构建前提,保证依赖镜像先于本镜像构建。这个声明是双向的——不仅构建时决定顺序,
clean时同样决定清理范围。因此,如果多个子镜像共享同一基础镜像,不要在子镜像内声明对共享镜像的Dependencies=,否则--dependency <subimage> clean会导致连带清理共享镜像,破坏缓存。如果你希望明确表明依赖关系,使用PathExists=%O/XXX。 - 子镜像通过其目录确定镜像名,不会受到
ImageId=的影响。 - 要使用子镜像的产物,在主镜像中使用
%O占位符。这个占位符不会被延迟展开。 - 这些配置项只影响主镜像和默认工具树,而且不允许在子镜像中进行配置:
RepositoryKeyCheck=RepositoryKeyFetch=SourceDateEpoch=CacheOnly=WorkspaceDirectory=PackageCacheDirectory=BuildSources=BuildSourcesEphemeral=ProxyClientCertificate=ProxyClientKey=ProxyExclude=ProxyPeerCertificate=ProxyUrl=
- 这些配置项同时影响主镜像、默认工具树和所有子镜像,而且不允许在子镜像中进行配置:
Architecture=BuildDirectory=CacheDirectory=Distribution=ExtraSearchPaths=Incremental=LocalMirror=Mirror=OutputDirectory=OutputMode=PackageDirectories=Release=RepartOffline=Repositories=SandboxTrees=ToolsTree=ToolsTreeCertificates=UseSubvolumes=SecureBootCertificate=SecureBootCertificateSource=SecureBootKey=SecureBootKeySource=VerityCertificate=VerityCertificateSource=VerityKey=VerityKeySource=SignExpectedPcrCertificate=SignExpectedPcrCertificateSource=SignExpectedPcrKey=SignExpectedPcrKeySource=VolatilePackageDirectories=WithNetwork=WithTests
- 这些配置项会被传递到子镜像,而且可以在子镜像中进行覆盖配置:
Profiles=ImageId=ImageVersion=SectorSize=CacheKey=BuildKey=CompressLevel=
- 这些配置项不会被传递到子镜像:
MinimumVersion=PassEnvironment=ToolsTreeDistribution=ToolsTreeRelease=ToolsTreeProfiles=ToolsTreeMirror=ToolsTreeRepositories=ToolsTreeSandboxTrees=ToolsTreePackages=ToolsTreePackageDirectories=History=- 整个
[Runtime]段
- 这些配置项实际上只会在MKOSI运行过程中进行检查,因此可以用于动态依赖(例如,使用
%O指定输出目录中的产物):BaseTrees=ExtraTrees=Initrds=
- 该配置项对应的命令行选项是
[Distribution]段
发行版配置。
Distribution=:使用的发行版,目前支持fedora,debian,ubuntu,arch,opensuse,mageia,centos,centos_epel,openmandriva,rocky,rocky_epel,alma,alma_epel,gentoo,custom。默认使用主机的发行版。- 当
Distribution=custom时,无法自动构建Rootfs,需要自行提供。
- 当
Release=:使用的版本。默认使用主机的版本。Snapshot=:使用的快照日期,格式为YYYYmmdd。Architecture=:使用的架构。默认使用主机的架构。Mirror=:使用的软件源。- 在使用EPEL时,如果要为EPEL使用其他软件源,使用
$EPEL_MIRROR环境变量。 - Debian的Debug和Security源无法直接切换软件源,考虑使用
mkosi.sandbox,或是构建无Debug和Security仓库的sid|unstable版本。 - MKOSI内置了一部分软件源,要禁用它们,考虑创建
mkosi.sandbox,并在其中包含空的仓库配置文件。 - 该选项的值在大部分情况下会被写入镜像,作为镜像的默认软件源。但是OpenSUSE构建后不会写入软件源,用户需要自行执行
zypper addrepo。 - Fedora Rawhide国内可用的镜像站只有SJTU。
- 在使用EPEL时,如果要为EPEL使用其他软件源,使用
LocalMirror=:使用本地软件源,这意味着:- 在构建前置镜像(如ToolsTree)时不会使用这里的软件源。
- 在构建主镜像时只会使用这些软件源,会屏蔽掉内置的默认软件源并覆盖
Mirror=的值,但是不会写入镜像。 - 对于Apt,默认情况下只会启用
main分区。
RepositoryKeyCheck=:检查镜像源密钥。- Archlinux Arm的Key暂时没有在其他发行版上打包,所以构建时除非把ToolsTree设为Arch,否则该配置项必须禁用。
RepositoryKeyFetch=:在安装时自动尝试获取软件源密钥,默认启用,如果禁用,将从宿主机传递软件源密钥,这需要安装对应的*-keyring软件包。Repositories=:启用的Yum源名(例如epel),或是Apt源的分区(例如main)。使用逗号分隔。
如果希望屏蔽掉内置的默认软件源并覆盖Mirror=的值,使用自定义镜像源,建议使用mkosi.sandbox/(SandboxTrees=)目录,并在目录中创建空的包管理器对应的软件源文件:
etc/apt/sources.list.d/mkosi.sources(注意必须是DEB822格式)etc/yum.repos.d/mkosi.repoetc/pacman.confetc/apk/repositoriesetc/zypp/repos.d/mkosi.repo
[Output]段
输出方式。
Format=:导出格式,mkosi支持非常多种镜像,包括但不仅限于:disk:GPT+UEFI磁盘镜像。使用systemd-repart生成。directory:纯目录形式的容器根文件系统。tar:Tar包。cpio:CPIO包。addon:UKI扩展。uki:UKI镜像。- 该格式会导致
[Content]段的配置作用于UKI的Initrd中的内容(而不是不存在的根文件系统),通常情况下会希望启用Include=mkosi-initrd以针对Initrd进行裁剪。 - 需要特别注意,除非
MakeInitrd=yes,否则UKI内的systemd无法按照预期的行为进行换根操作。
- 该格式会导致
esp:构建一个包含单个ESP的GPT分区表的磁盘镜像,ESP中包含Bootloader(必须包含,必须安装systemd-boot)和UKI(可选包含,如果Bootable=yes且安装了内核包),从而可以直接通过VM启动。- 该格式会导致
[Content]段的配置作用于UKI的Initrd中的内容(而不是不存在的根文件系统),通常情况下会希望启用Include=mkosi-initrd以针对Initrd进行裁剪。 - 需要特别注意,除非
MakeInitrd=yes,否则UKI内的systemd无法按照预期的行为进行换根操作。
- 该格式会导致
oci:OCI容器镜像。镜像的Entrypoint固定为/sbin/init加上KernelCommandLine=的值。sysext|confext|portable:系统扩展镜像。它们的分区格式配置位于Repart Definitions。none:当前配置用于分组,或是执行纯构建操作。如果没有mkosi.build脚本的话,会跳过任何构建过程(包括mkosi.prepare脚本,此时不仅不会生成产物,也不会执行实际镜像构建,mkosi.prepare脚本所做的任何操作都是没有意义的)。- 需要特别注意:
Format=none且没有mkosi.build脚本的镜像,虽然不会真正执行构建,但是仍然会参与MKOSI的依赖解析、clean范围计算和缓存状态判断。但是,这类镜像又永远不会生成渐进缓存,这就导致在进行缓存一致性判断时,它总是会导致Cache Mismatch,一旦它出现在依赖链条上,就会导致所有其他本来可以复用缓存的子镜像全部被判定为需要清理缓存。在把主镜像设为Format=none、并结合--dependency把子镜像当作Build Target使用时,这一点尤其容易导致反直觉的缓存清理行为。
- 需要特别注意:
ManifestFormat=:生成镜像大纲(保存安装的所有软件包列表)的格式,可选json或changelog,默认不生成。Output=:输出文件/目录的前缀,默认为image。ImageId + ImageVersion(%i_%v)会被用作默认的输出文件名前缀(Output=)。
OutputExtension=:输出文件的后缀,会覆盖默认后缀。OutputDirectory=:构建产品的保存目录,默认为工作目录下的image,也可以手动创建mkosi.output/以默认使用该目录。CompressOutput=:输出镜像的压缩方式,请注意,这会导致shell、boot、vm命令在该镜像上不可用。仅可用于tar、cpio、uki、esp和oci镜像。可选值有布尔值和xz、zstd等压缩算法,以及none。默认使用zstd。CompressLevel=:压缩等级。
SplitArtifacts=:分开导出的产品列表,使用逗号分隔,可选值有uki、kernel、initrd、partitions、tar、pcrs、roothash、kernel-modules-initrd、os_release。- 默认值
no等价于uki,kernel,initrd。 yes等价于uki,kernel,initrd,partitions。uki会导出UKI。kernel和initrd会导出组成UKI的内核和Initrd。partitions在使用磁盘镜像时,会告诉systemd-repart针对每个分区分别进行导出。默认情况下,导出的分区文件会以输出文件名前缀.分区名命名。tar会额外导出一份构建时产生的根文件系统的Tar包。
- 默认值
ImageVersion=:镜像的版本号。- 你也可以使用
mkosi.version文件指定版本号,此文件还可以是一个可执行的Shell脚本,使用其输出作为版本号。但是请注意,这种情况下脚本的输出必须是稳定的,否则会导致MKOSI的历史记录完全失效。 mkosi.version文件可以通过mkosi bump命令进行更新,如果存在mkosi.bump脚本,那么还会使用这个脚本的输出更新版本。ImageId + ImageVersion(%i_%v)会被用作默认的输出文件名前缀(Output=)。
- 你也可以使用
ImageId=:镜像ID,会被写入镜像的os-release文件中。ImageId + ImageVersion(%i_%v)会被用作默认的输出文件名前缀(Output=)。
RepartDirectories=:systemd-repart的分区定义配置文件目录,默认为工作目录下的mkosi.repart/。- 注意,MKOSI的分区配置文件中的
CopyFiles=的源路径会使用镜像中的根文件系统。
- 注意,MKOSI的分区配置文件中的
SectorSize=:systemd-repart的块大小。Seed=:systemd-repart使用的UUID,也可以使用mkosi.seed文件保存,默认值为random。Overlay=:该配置项必须和BaseTrees=一起使用,是否仅仅保留相对于Base Tree发生改变的内容,用于构建系统扩展镜像。- 在构建系统扩展镜像时,可以使用该选项结合
BaseTree=指定的目录以获取增量部分,即扩展镜像的部分。
- 在构建系统扩展镜像时,可以使用该选项结合
CleanScripts=:用于构建的最后阶段进行清理的脚本,默认为mkosi.clean。OciLabels=:仅用于OCI镜像,配置OCI标签,OCI标签没有明确要求,完全是用户自定义的元数据,大家一般遵循org.opencontainers.image.*作为前缀。OciAnnotations=:仅用于OCI镜像,配置OCI Annotation。默认情况下,MKOSI会自动设置io.systemd.mkosi.version,如果配置了ImageVersion=,那么还会自动设置org.opencontainers.image.version。- 一些常见的Annotation包括:
org.opencontainers.image.created:镜像创建时间。org.opencontainers.image.authors:作者。org.opencontainers.image.url:主页URL。org.opencontainers.image.documentation:文档。org.opencontainers.image.source:源代码。org.opencontainers.image.version:版本号。
[Build]段
ToolsTree=:在指定目录下搜索构建工具组,也可以使用mkosi.tools/目录保存,这有助于使用其他版本的构建工具组。- 默认情况下,在构建不同发行版时会自动使用Tools Tree。
- 如果值为空,那么任何情况下都不会使用Tools Tree。
- 如果值为
yes或default,那么会从指定的发行版的软件源中自动获取Tools Tree。在目标发行版的systemd版本低于构建所需的最低要求时,会自动启用。 - 在当前发行版低于构建所需的最低systemd版本时,该选项必须启用。
- 在宿主机没有安装构建时必需的工具(如
reprepo)时,启用该选项可以避免在宿主机安装软件包。 ToolsTreeDistribution=:用于自动获取ToolsTree=的目标发行版。ToolsTreeRelease=:用于自动获取ToolsTree=的目标发行版版本。ToolsTreeMirror=:用于自动获取ToolsTree=的目标发行版软件源。ToolsTreeRepositories=:和Repositories=一样,但是用于构建ToolsTree=。ToolsTreeSandboxTrees=:和SandboxTrees=一样,但是用于构建ToolsTree=。ToolsTreeProfiles=:在ToolsTree=中启用的Profile,默认启用除了devel和gui之外的所有Profile,逗号分隔,可选值有:misc:用于脚本的各种杂项工具。package-manager:包管理器及其附属的各种工具。runtime:用于boot镜像时所需的各种工具。gui:用于VM的GUI控制台的工具。devel:用于Debug的各种工具。
ToolsTreePackages=:在ToolsTree=中包含的额外软件包。ToolsTreeCertificates=:是否使用Tools Tree中的证书,这包括/etc/pki、/etc/ssl、/etc/ca-certificates和/var/lib/ca-certificates,如果禁用,使用主机上的证书。- 你也可以使用
mkosi.tools.conf文件或包含若干配置文件的mkosi.tools.conf/目录来对ToolsTree进行细粒度的配置(但不允许配置Distribution=、Release=和Mirror=)。配置文件中接受的配置项和主镜像基本上一致。
Incremental=:渐进构建,可选值为布尔值或strict。在执行mkosi.build脚本之前提供镜像缓存,这可以显著提高构建速度。- 如果为
strict,那么如果缓存镜像不存在则会报错退出。 - 每个缓存都会保存一个Manifest,在重新缓存时,会对其进行对比并确定缓存是否可用,Manifest保存的元数据包括:
Distribution=Release=Mirror=Repositories=Architecture=Snapshot=- 包管理器
Packages=BuildPackages=PackageDirectories=Overlay=PrepareScripts=,不包括任何其他脚本
- 如果为
CacheOnly=:纯缓存模式,可选值有auto、metadata、always或never,如果为always,那么包管理器不会联网,总是只使用本地缓存;如果为metadata,那么包管理器不会更新元数据,但是可以下载软件包;如果为auto,那么仅在不存在本地缓存时更新元数据。SandboxTrees=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是MKOSI构建沙箱内的路径,表示在执行构建之前拷贝到构建沙箱的文件。也可以使用mkosi.sandbox/目录进行保存。WorkspaceDirectory=:临时文件的保存目录,默认为$XDG_CACHE_HOME或$HOME/.cache或/var/tmp。CacheDirectory=:Incremental=yes时镜像缓存的保存目录,可以直接使用工作目录下的mkosi.cache/(如果存在)。PackageCacheDirectory=:为该镜像特别设置软件包管理器的缓存目录,可以直接使用工作目录下的mkosi.pkgcache/(如果存在),默认情况下使用系统缓存目录/var/cache/或~/.cache/。BuildDirectory=:用于保存树外构建中间产物的目录,这使得构建的中间产物可以在多次构建时共享,实现增量构建,在构建脚本中可以通过$BUILDDIR访问。默认为mkosi.builddir/。UseSubvolumes=:是否在构建过程中使用Btrfs子卷和快照,这可以显著提高Base Tree和Cached Tree的拷贝速度,同时,如果Format=directory,那么最终产物会保持为一个Btrfs子卷,可选值有布尔值或auto。RepartOffline=:是否在不使用回环设备的情况下构建磁盘镜像。默认情况下为yes。- 如果禁用,那么
systemd-repart必须具有特权,因此构建只能通过root用户执行。 - 只有两种情况下必须禁用:
- 使用了Btrfs,而且在
mkosi.repart/配置中设置了Subvolumes=。因为Btrfs子卷必须通过回环设备创建。 - 另一种是以XFS为根文件系统,而且启用了SELinux。因为必须通过回环设备才能确保SELinux扩展属性被写入XFS文件系统。
- 使用了Btrfs,而且在
- 如果禁用,那么
History=:是否保存最新一次构建的历史快找,历史会被保存在.mkosi-private/history/latest.json。ExtraSearchPaths=:用于搜索工具的额外目录,会在构建时被添加到$PATH中。- 如果你使用了
ToolsTree=,那么这个配置项的路径代表着ToolsTree内的路径。你必须在ToolsTree中安装你的额外工具(通过mkosi box命令,例如mkosi box -- meson setup build)。
- 如果你使用了
ProxyUrl=:使用的代理地址。ProxyExclude=:不使用代理的主机名。ProxyPeerCertificate=:代理Peer的证书。ProxyClientCertificate=:代理客户端的证书。ProxyCientKey=:代理客户端的Key。
WithTests=:设为假,则在执行mkosi.build脚本时,$WITH_TESTS变量为0。WithNetwork=:在执行mkosi.build、mkosi.postinst和mkosi.finalize脚本时提供网络。MakeScriptsExecutable=:是否自动将脚本设为可执行,默认值为no。
[Content]段
镜像配置:
Packages=:包含的包,使用逗号或是换行符分隔。会使用该发行版对应的包管理器进行安装。VolatilePackages=:包含的包,但是这些包不受Incremental=影响,不会被缓存。BuildPackages=:在构建时,使用Overlay的方式安装软件包。这样的包不会在镜像中保留,应当用于mkosi.build.chroot脚本。PackageDirectories=:读取指定目录下的额外软件包,并允许在构建时通过Packages=进行安装。也可以使用mkosi.packages/目录保存。VolatilePackageDirectories=:读取指定目录下的额外软件包,并允许在构建时通过Packages=进行安装,这些包不受Incremental=影响,不会被缓存。WithRecommends=:是否安装弱依赖,仅适用于apt、dnf和zypper。默认禁用。WithDocs=:是否在镜像内保留文档。默认启用。MakeInitrd=:是否在镜像内创建/init(指向/usr/lib/systemd/systemd)和/etc/initrd-release文件。该配置项通常在MKOSI-INITRD内部使用。BaseTrees=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是镜像内的路径,表示在获取发行版基本根文件系统之前,将主机文件/目录拷贝到镜像内。这也是Overlay=yes参考的基础文件系统。如果没有指定镜像内的路径,那么默认为/。- 设置该配置项会导致MKOSI跳过自动构建基本的Base Tree。
- 注意!如果自行提供Base Tree,那么必须在Base Tree,或者Skeleton Tree,或者
mkosi.prepare脚本中提供/usr/lib/os-release。
SkeletonTrees=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是镜像内的路径,表示在已经构建基本根文件系统,开始安装软件包之前,将主机文件/目录拷贝到镜像内。mkosi.skeleton/目录下的文件总是会被拷贝到相同的目标位置下。
ExtraTrees=:接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是镜像内的路径,表示在安装软件包之后,将主机文件/目录拷贝到镜像内。mkosi.extra/目录下的文件总是会被拷贝到相同的目标位置下。
RemovePackages=:在构建构建后删除指定的包。RemoveFiles=:在构建后删除指定通配符匹配的文件,路径使用镜像内的根文件系统。CleanPackageMetadata=:是否在构建后清理镜像内包管理器的缓存。默认为auto,表示仅在镜像类型为directory或tar时启用。- Apt:
/var/lib/apt、/var/cache/apt和/var/lib/dpkg。 - DNF:
/var/lib/dnf、/var/cache/dnf、/var/lib/rpm和/usr/lib/sysimage/rpm。 - DNF5:
/var/lib/libdnf5、/var/cache/libdnf5、/var/lib/rpm和/usr/lib/sysimage/rpm。 - Zypper:
/var/lib/zypp、/var/cache/zypp、/var/lib/rpm、/usr/lib/sysimage/libdnf5和/usr/lib/sysimage/rpm。 - Pacman:
/var/lib/pacman、/var/cache/pacman。 - Apk:
/var/lib/apk、/var/cache/apk。
- Apt:
Bootable=:镜像是否可引导(是否安装引导程序),可选值除了布尔值还有auto。默认为auto,即仅在安装了Bootloader软件包时才安装。如果设为yes,那么在Bootloader=设置的内核和Bootloader软件包不存在时,构建会失败。- 如果设为
yes,镜像内必须安装对应架构的内核和引导程序。 - 设为
no时,如果设置了Format=esp,那么仍然会在ESP中安装引导程序,但是不会在ESP中安装UKI或内核+Initrd文件。 Bootloader=:使用的引导程序,可选none、uki、systemd-boot、grub、systemd-boot-signed、grub-signed、uki-signed。默认为systemd-boot。- 如果
Bootable=no,那么该配置项无意义。 - 如果使用
none,那么和Bootable=no的效果大体上一致,但存在细微差异。Bootable=yes+Bootloader=none时总是会检查构建过程中是否安装了内核,没有的话会导致构建失败。Bootable=yes+Bootloader=none时总是会尝试生成Initrd。Bootable=yes+Bootloader=none时总是会自动创建ESP分区。Bootable=yes+Bootloader=none时总是会设置KERNEL_INSTALL_BYPASS=1环境变量。Bootable=yes+Bootloader=none时不会设置BOOT_ROOT=/boot环境变量。Bootable=yes+Bootloader=none时不会为apt设置INITRD=No。Bootable=yes+Bootloader=none时如果启用了SecureBoot仍然会进行检查密钥。
- 如果使用
grub,那么镜像中必须安装对应架构的grub软件包,确保存在/usr/lib/grub或/usr/share/grub2目录,宿主机(或Tools Tree)中必须存在grub-mkimage程序(通常由grub-common包提供)。MKOSI会将GRUB的EFI程序安装到构建过程中的/efi目录,为GRUB生成配置文件/efi/grub/grub.cfg(除非用户明确提供了mkosi.extra/efi/)。此外,MKOSI还会生成一个/efi/EFI/发行版名称/grub.cfg配置文件,它的内容实际上是加载/efi/grub/grub.cfg。 - 如果使用
systemd-boot,那么镜像中必须安装对应架构的systemd-boot软件包,确保存在bootctl程序和systemd-boot的EFI程序。MKOSI会将systemd-boot的EFI程序安装到构建过程中的/efi目录,在不使用UKI的情况下,MKOSI会为systemd-boot生成标准的Type1引导项文件/efi/loader/entries/Token-内核版本.conf。 - 如果使用
uki,那么不会在ESP中安装任何其他引导程序,而是直接将UKI安装到构建过程中的/efi目录(准确地说,是/efi/EFI/BOOT/BOOT*.EFI如果启用了Shim,那么是/efi/EFI/BOOT/grub*.EFI),作为EFI Boot Manager的引导项使用。 *-signed表示使用对应的Bootloader的已签名版本。MKOSI会认为对应的Bootloader已经签名,在SecureBoot=yes的情况下,不会再对Bootloader进行签名。
- 如果
BiosBootloader=:是否安装BIOS引导,可选none或grub,如果启用,那么需要创建大小为1MB,类型为21686148-6449-6e6f-744e-656564454649(Hah!IdontNeedEFI)的biosboot分区。默认为none。- 如果
Bootable=no或Format=不是disk,那么该配置项无意义。 - 如果使用
grub,那么宿主环境中必须安装i386架构的grub软件包(grub-pc-bin和grub-common),确保提供了grub-bios-setup和grub-mkimage命令行工具,特别注意,AArch64架构上没有这个软件包。
- 如果
ShimBootLoader=:是否安装Shim,可选none、unsigned或signed。在SecureBoot=yes的情况下,如果为unsigned,那么MKOSI会安装未签名的Shim,并自行对其进行签名,如果为signed,那么MKOSI会安装已签名的Shim。默认为none。- 如果设为
unsigned,那么镜像内必须安装shim-unsigned软件包。 - 如果设为
signed,那么镜像内必须安装shim-signed软件包。 - 如果设为
signed,那么除非Bootloader=明确指定*-signed,否则默认情况下不会使用已签名的Bootloader。 - 在没有启用
SecureBoot=true的情况下,该选项没有意义。
- 如果设为
- 如果设为
KernelModules=:在镜像中包含的内核模块,格式为相对于/usr/lib/modules/内核版本/kernel/的子目录的通配模式(即*、?和[-])。这对于Initrd特别有用。- 默认包含所有内核模块。
- 匹配所有以
.ko、.ko.gz、.ko.xz、.ko.zst结尾的模块文件。 -开头的行会被视为反选。- 没有以
/开头的行会被递归地在所有子目录中匹配(相当于隐式地在行首添加了一个*/)。 /结尾的行会被递归匹配其所有的子目录(相当于隐式地在行尾添加了一个*)。- 关键词
default表示包含MKOSI-INITRD的mkosi.conf中的所有默认配置,参见mkosi.conf。这也是未设置该配置项的默认值。MKOSI-INITRD中的配置是systemd开发组在开发ParticleOS时在不同硬件平台上测试得到的,目前仍在更新,因此可能会有缺失。 - 关键词
host表示包含宿主机当前加载的所有模块。 KernelModulesIncludeHost=:是否在镜像中包含宿主机当前加载的所有模块。这等价于KernelModulesInitrdInclude=host。- 该配置项仅对没有启用
Overlay=yes的镜像(也就是说,不是Sysext、Confext和Portable Service的镜像)有意义。 - 值得参考的通用Initrd模块内容:Ubuntu Core的选择。
KernelModulesInclude=:(已过时)在镜像中包含的内核模块,格式为相对于/usr/lib/modules/内核版本/kernel目录的,re.search()风格的路径正则表达式。- 该配置项的优先级高于
KernelModulesExclude=,且在没有设置KernelModulesExclude=的情况下没有意义,因为MKOSI默认情况下总是包含所有的内核模块。
- 该配置项的优先级高于
KernelModulesExclude=:(已过时)在镜像中排除的内核模块,格式为相对于/usr/lib/modules/内核版本/kernel目录的,re.search()风格的路径正则表达式。- 该配置项默认为空。
FirmwareFiles=:在镜像中包含的固件,格式为相对于/usr/lib/firmware/目录的通配模式。即使没有任何内核模块依赖这些固件,MKOSI也会强制将其包含到镜像内。FirmwareInclude=:(已过时)在镜像中包含的固件,格式为相对于/usr/lib/firmware/目录的,re.search()风格的路径正则表达式。即使没有任何内核模块依赖这些固件,MKOSI也会强制将其包含到镜像内。- 该配置项的优先级高于
FirmwareExclude=。 FirmwareExclude=:(已过时)在镜像中排除的固件,格式为相对于/usr/lib/firmware/目录的,re.search()风格的路径正则表达式。即使有内核模块依赖这些固件,MKOSI也会强制将其排除掉。- 不存在
FirmwareFilesInitrd=,这是故意的,用户应当通过mkosi.images/子镜像功能实现更细粒度的Initrd自定义。
Initrd配置:
Initrds=:禁用自动构建的默认Initrd,使用用户提供的指定Initrd,在构建时,MKOSI会取消构建Default Initrd,转而使用这里提供的Initrd。- MKOSI会在推测镜像可引导(安装了内核包、
Format=disk)时自动构建默认Initrd,将该配置项置为空表示禁用内置的检测逻辑,强制禁用默认Initrd构建。 - 特殊值
default表示构建内置的默认Initrd(Default Initrd)。默认情况下,如果你没有指定Initrds=为其他特定的镜像,会自动触发构建。 - 你也可以在构建时使用
$ARTIFACTDIR/io.mkosi.initrd目录保存流程中构建的Initrd,这里的Initrd会被追加到Default Initrd或用户提供的Initrd中。 - 如果要在构建时使用其他方式构建Initrd,应该在
mkosi.images/目录下创建一个子镜像,并将该配置项设为Initrds=%O/子镜像名称。
- MKOSI会在推测镜像可引导(安装了内核包、
InitrdProfile=:启用的额外Default Initrd配置,默认不启用。lvm:LVM支持。raid:RAID支持。pksc11:PKSC11智能卡支持。plymouth:Plymouth Logo支持。nfs:NFS上的根文件系统支持。- 你也可以使用
mkosi.initrd.conf文件或包含若干配置文件的mkosi.initrd.conf/目录来对ToolsTree进行细粒度的配置(但不允许配置Distribution=、Release=和Mirror=)。配置文件中接受的配置项和主镜像基本上一致。
KernelModulesInitrd=:在构建可引导(Bootable=true且安装了内核包)的镜像时,MKOSI会先读取MKOSI-INITRD的配置,构建一个目标发行版的,不包含任何模块的默认Initrd(Default Initrd)(注意了!MKOSI-INITRD的构建配置中没有包含内核模块包,所以不会包含任何内核模块),然后,该配置项用于确定是否需要构建一个额外的,包含特定的当前正在构建的镜像的内核模块的Initrd(Kernel Modules Initrd)(即,根据KernelModulesInitrdInclude=和KernelModulesInitrdExclude=配置项构建的Initrd),并将其合并到默认Initrd中。默认启用。- 如果你的镜像中没有安装内核包,那么你必须单独构建一个
default-initrd子镜像才会触发自动构建Default Initrd,子镜像的构建配置中只需要包含Include=mkosi-initrd,然后在主镜像中使用Initrds=%O/initrd.cpio.zst。 - 在启用了UKI(
UnifiedKernelImages!=none)时,禁用该配置项有助于用户根据自己的需要构建出更通用的UKI镜像,并使用Format=addon构建扩展Initrd进行补充。 - 在用户使用自己提供的Initrd(
Initrds=),且自己提供的Initrd中包含内核模块时,也可以考虑禁用该配置项。 - 在未启用UKI时,该配置项没有意义,因为此时总是会构建默认Initrd和内核模块Initrd,并进行合并。
- 如果你的镜像中没有安装内核包,那么你必须单独构建一个
KernelInitrdModules=:在Initrd中包含的模块,格式为相对于/usr/lib/modules/内核版本/kernel/目录的通配模式。- 默认包含所有内核模块。
- 关键词
default表示包含MKOSI-INITRD的mkosi.conf中的所有KernelModules=配置,参见mkosi.conf。这也是未设置该配置项的默认值。 - 关键词
host表示包含宿主机当前加载的所有模块。 KernelModulesInitrdIncludeHost=:是否在Initrd中包含宿主机当前加载的所有模块。这等价于KernelModulesInitrdInclude=host。KernelModulesInitrdInclude=:(已过时)在Initrd中包含的模块,格式为相对于/usr/lib/modules/内核版本/kernel目录的,re.search()风格的路径正则表达式。- 该配置项的优先级高于
KernelModulesInitrdExclude=,且在没有设置KernelModulesInitrdExclude=的情况下没有意义,因为MKOSI默认情况下总是包含所有的内核模块。
- 该配置项的优先级高于
KernelModulesInitrdExclude=:(已过时)在Initrd中排除的模块,格式为相对于/usr/lib/modules/内核版本/kernel目录的,re.search()风格的路径正则表达式。- 因为MKOSI默认会加载
mkosi-initrd的配置(KernelModulesInitrdInclude=default),因此如果希望重设模块,需要先设置KernelModulesInitrdExclude=.*。 - 该配置项默认为空。
- 因为MKOSI默认会加载
UnifiedKernelImages=:是否使用UKI而不是独立Initrd,接受的值有none、unsigned、signed、auto。默认为auto,即尽可能使用。- 如果
Bootloader=*-signed,那么除非UnifiedKernelImages=被明确设为unsigned或none,构建时会需要在/usr/lib/modules/内核版本/目录下提供一个预构建的、已签名的UKI(例如发行版提供的,包含已签名的UKI的软件包),否则构建会失败。UnifiedKernelImages=unsigned会触发在本地执行ukify并构建UKI,这在设置了Firmware=custom的情况下很有用。 - 如果
UnifiedKernelImages=不为none或auto,那么镜像中必须安装systemd-boot-efi(或其他提供/usr/lib/systemd/boot/efi/*.efi.stub桩程序的软件包)。 - 默认内核命令行参数逻辑:
- 如果启用了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 PARTTYPE为root*的分区,那么会将其PARTUUID写入BLS引导条目的root=PARTUUID=内核命令行参数中。 - 如果未启用UKI,且使用GRUB:如果
systemd-repart发现了存在哈希值(即,启用了Verity)的根文件系统分区,那么会将其哈希值写入grub.cfg的roothash=内核命令行参数中。否则,如果systemd-repart发现了存在哈希值(即,启用了Verity)的/usr/文件系统分区,那么会将其哈希值写入grub.cfg的usrhash=内核命令行参数中。否则,如果找到了GPT PARTTYPE为root*的分区,那么会将其PARTUUID写入grub.cfg的root=PARTUUID=内核命令行参数中。否则,如果找到了GPT PARTLABEL为usr*的分区,那么会将其PARTUUID写入grub.cfg的mount.usr=PARTUUID=内核命令行参数中。
- 如果启用了UKI:如果
UnifiedKernelImageFormat=:UKI文件命名,默认为&e-&k,支持的占位符有:&&:&本身。&e:Token。&k:内核版本。&h:roothash=或usrhash=的值。&c:引导尝试次数。
UnifiedKernelImageProfiles=:为构建的UKI添加配置(即.profile段),接受逗号分隔的配置文件。也可以保存在mkosi.uki-profiles/目录下。配置以.conf结尾,只有一个UKIProfile段。请注意,每个UKI配置在使用时都会被追加UKI的基础内核命令行参数。可用内容有:Profile=:配置信息,包括ID(必须)和TITLE。Cmdline=:内核命令行参数。SignExpectedPcr=:是否为该配置进行PCR签名。默认启用。- 部分UEFI固件实现会限制PE段(Section)数量,因此可能无法接受过多的UKI配置文件。
PeAddons=:(已被移除)构建额外的UKI插件,接收逗号分隔的ukify配置文件,额外的PE插件会以配置文件名.addon.efi命名,也可以将ukify配置文件保存在mkosi.pe-addons/目录下。- 该配置项现在已经废弃,为MKOSI-ADDON让位。
- 如果
Splash=:是否在Initrd中添加海报。MicrocodeHost=:是否在Initrd中仅仅包含主机的CPU微码。默认禁用。InitrdPackages=:在MKOSI自动构建的默认Initrd中额外安装的软件包。InitrdVolatilePackages=:在MKOSI自动构建的默认Initrd中额外安装的软件包。但是这些包不受Incremental=影响,不会被缓存。KernemModulesInitrd=:是否额外导出独立的模块Initrd,该Initrd只包含UKI中的内核模块部分,默认启用。KernelCommandLine=:封装在UKI或Initrd内的内核命令行参数。- 如果检测到使用了Verity的
root分区或usr分区,那么会自动填充roothash=分区Hash值或usrhash=分区Hash值。 - 内核命令行参数
root=PARTUUID和mount.usr=PARTUUID会被自动填充类型为root或/usr的分区的UUID。
- 如果检测到使用了Verity的
镜像预设配置,这些配置会通过systemd-firstboot的对应选项传递,还会写入到镜像内的/usr/lib/credstore目录下:
MachineID=:设置镜像内的机器ID,默认为random,也可以使用mkosi.machine-id文件保存。Locale=:镜像内的语言。- 对于Arch、Debian这类使用
locale-gen工具在本地编译语言的发行版来说,如果/etc/locale.gen内配置了语言(通过sed -i '/^# 语言.UTF-8.*/c\语言.UTF-8 UTF-8' /etc/locale.gen),那么在构建时会自动执行locale-gen。
- 对于Arch、Debian这类使用
Keymap=:镜像内的Keymap。Hostname=:设置镜像内的默认主机名。RootShell=:Root用户的默认Shell。RootPassword=:设置镜像内Root用户的默认密码。- 你也可以使用
mkosi.rootpw文件指定密码,该文件还可以是一个可以执行的Shell脚本,使用其输出作为密码。
- 你也可以使用
Autologin=:是否设置Root用户在/dev/pts/0、/dev/tty1和/dev/hvc0的自动登录。Ssh=:是否在镜像内创建SSH套接字单元以允许通过mkosi ssh连接,使用mkosi genkey创建密钥后才可以使用mkosi ssh。可选值有:auto:如果镜像中存在sshd且不存在systemd-ssh-generator,则创建单元。always:总是创建单元。never:总是不创建单元。runtime:不配置单元,但是在运行时仍然进行SSH密钥的检测。
SELinuxRelabel=:是否在镜像内进行Relabel。
为了实现更多功能,还可以在构建镜像的不同过程中执行各种钩子脚本脚本:
SyncScripts=:在开始构建镜像之前,在宿主环境中执行的脚本,该脚本有网络连接,用于更新需要在镜像中进行构建的各种源代码,注意该脚本的更新操作不会破坏BuildSourcesEphemeral=,默认为mkosi.sync。PrepareScripts=:该脚本会首先在镜像中安装软件包后,以final位置参数被执行一次(执行的操作会被保留在镜像中),然后在创建构建Overlayfs之后以build位置参数再被执行一次(执行的操作不会被保留在镜像中)。该脚本被执行时有网络连接,因此可以用于安装软件包。默认为mkosi.prepare。- 注意!如果自行提供Base Tree,那么必须在Base Tree,或者Skeleton Tree,或者
mkosi.prepare脚本中提供/usr/lib/os-release。
- 注意!如果自行提供Base Tree,那么必须在Base Tree,或者Skeleton Tree,或者
BuildScripts=:在准备好构建环境后,在宿主环境或镜像中执行的脚本,该脚本可以具有网络连接,取决于WithNetwork=的值。默认为mkosi.build。BuildSources=:进行构建时拷贝的代码树目录,接受逗号分隔的,冒号绝对路径对。前者是主机上的路径(默认为.),后者是沙盒内的路径(默认为同名路径),镜像内的路径总是会自动添加/work/src前缀。默认情况下值为.,即将当前工作目录完全拷贝到沙盒的/work/src。BuildSourcesEphemeral=:是否不保留对源码树的更改,如果启用,那么除了mkosi.sync以外的所有脚本对BuildSources=的修改都不会保留。如果设为buildcache,那么mkosi.build脚本的修改产生的Overlay会被保留在BuildDirectory=中,其他脚本的修改仍然会被抛弃,默认禁用。
PostInstallationScripts=:在配置完Extra Tree后,在宿主环境或镜像中执行的脚本,该脚本可以具有网络连接,取决于WithNetwork=的值,默认为mkosi.postinst。FinalizeScripts=:在Overlay的下层文件系统(即Base Tree)已经移除后,在宿主环境或镜像中执行的脚本,该脚本可以具有网络连接,取决于WithNetwork=的值,默认为mkosi.finalize。PostOutputScripts=:在输出产品后,在宿主环境或镜像中执行的脚本,该脚本没有网络连接,默认为mkosi.postoutput。Environment=:给脚本传递的环境变量。MKOSI_LESS:使用指定的分页器,而非less。$MKOSI_DNF:指定使用的dnf。EPEL_MIRROR:指定EPEL的镜像站。MKOSI_DNF_DISABLE_PLUGINS:指定dnf禁用的插件。MKOSI_DNF_ENABLE_PLUGINS:指定dnf启用的插件。
EnvironmentFile=:从文件给脚本传递环境变量。
[Validation]段
SecureBoot=:是否启用安全启动,这会影响是否对systemd-boot(或是UKI)进行签名。SecureBootKey=:使用的安全启动PEM密钥。默认使用mkosi.key。SecureBootCertificate=:使用的安全启动X509证书。默认使用mkosi.crt。SecureBootSignTool=:为PE文件进行安全启动签名的程序,可选systemd-sbsign、sbsign或auto。
SecureBootAutoEnroll=:是否自动导入安全启动的Key。- 这仅对虚拟机生效,如果希望对物理机生效,在Extra Tree中包含
/efi/loader/loader.conf文件,并写入secure-boot-enroll force或secure-boot-enroll manual。
- 这仅对虚拟机生效,如果希望对物理机生效,在Extra Tree中包含
Verity=:是否为扩展镜像启用Verity,可选值有signed、hash、defer、auto或布尔值。- 如果设为
signed或yes,那么必须提供VerityKey=和VerityCertificate=,从而让镜像在构建时创建独立的签名分区,否则构建会失败。 - 如果设为
hash,那么MKOSI会启用Verity,但是不会在镜像中创建签名分区。 - 如果设为
defer,那么MKOSI会启用Verity,在镜像中预留出签名分区的空间,但是不实际创建。 - 如果设为
no,那么MKOSI会禁用Verity。 - 如果设为
auto,那么MKOSI会在提供了提供VerityKey=和VerityCertificate=时启用Verity并转为signed模式,否则禁用Verity。这是默认值。 VerityKey=:用于为Verity设备签名的PEM密钥。默认使用mkosi.key。VerityCertificate=:用于为Verity设备签名的X509证书。默认使用mkosi.crt。
- 如果设为
SignExpectedPcr=:使用systemd-measure在UKI中包含PCR(平台配置寄存器)的签名,可选布尔值或auto,如果设为auto(默认值),那么会根据systemd-measure的存在性判断是否包含签名。SignExpectedPcrKey=:签名使用的PEM密钥。默认使用mkosi.key。SignExpectedPcrCertificate=:签名使用的X509证书。默认使用mkosi.crt。
Passphrase=:LUKS加密的密钥文件,必须是600权限位,它应当保存完整的LUKS密钥,以一个换行结尾(即/etc/crypttab文件的格式)。- 也可以选择在工作目录下创建
mkosi.passphrase文件。
- 也可以选择在工作目录下创建
Checksum=:指定镜像的校验码。Sign=:是否用gpg给校验码签名。默认为否。OpenPGPTool=:签名使用的OpenPGP实现,默认为gpg,可选值有sqop、rsop。Key=:给校验码签名的密钥。
[Runtime]段
通用:
Register=:是否向systemd-machined注册,以允许通过machinectl进行管理,默认启用。Machine=:机器名,用于systemd-machined注册的名称。Credentials=:启动VM/容器时传递的systemd的系统凭据。Ephemeral=:是否将镜像设为暂态,所有操作在关闭之后都会撤销。RuntimeTrees=接受逗号分隔的,冒号绝对路径对。前者是主机上的路径,后者是虚拟机内的路径,如果没有后面的部分,则自动挂载在/root/src目录下。RuntimeSize=:将镜像在启动时扩大到指定大小。RuntimeNetwork=:使用的网络模式,可选值有user、interface或none。interface依赖systemd-networkd配置网络接口。默认值为user。RuntimeBuildSources=:挂载指定的源代码目录到虚拟机内的/work目录下。RuntimeHome=:是否挂载主机的家目录到虚拟机的/root。SshKey=:使用的SSH私钥。SshCertificate=:使用的SSH证书。ForwardJournal=:是否将容器或虚拟机的日志发送到宿主机。SysupdateDirectory=:mkosi sysupdate使用的配置目录,默认使用mkosi.sysupdate/。- MKOSI使用的Sysupdate配置必须设置
PathRelativeTo=explicit和Path=/才能正常使用sysupdate子命令进行更新,这会使得配置中的Path=使用mkosi.output/(或OutputDirectory=指定的目录)作为根。 - 这同时也意味着,
SplitArtifacts=必须启用以对分区进行更新,而且更新内容(分区镜像文件、EFI程序、Initrd)文件名必须与Sysupdate配置中的MatchPattern=匹配。
- MKOSI使用的Sysupdate配置必须设置
StorageTargetMode=:是否在serve时使用systemd-storagetm提供镜像服务。可选值有auto或布尔值。如果设为auto,那么会自动检测systemd-storagetm的存在性,以及当前构建产品是否为disk格式,如果都满足时,才会启用。
容器配置:
NSpawnSettings=:使用systemd-nspawn启动时,读取的.nspawn单元文件。- 默认使用
mkosi.nspawn文件。
- 默认使用
UnitProperties=:传递给NSPawn的Scope属性。
所有虚拟机配置项都仅适用于使用vm启动的虚拟机:
VirtualMachineMonitor=:使用的虚拟机超管,可选值有qemu或vmspawn。默认值为qemu。Console=:虚拟机终端的接入模式。可选值有:interactive交互文本终端,read-only只读的交互文本终端,nativeQemu原生的交互文本终端,gui使用图形界面。gui需要在ToolsTree中额外安装qemu-system-gui和pipewire。添加QemuArgs=-full-screen可以让Qemu进入全屏模式。
RAM=:设置Qemu的内存。默认为2G。KVM=:是否启用KVM加速,默认为auto。VSock=:是否启用VSock,默认为auto。VSockCID=:VSock的连接ID,可选值有hash、auto或手动输入。TPM=:是否启用TPM,默认为auto。Removable=:是否以可插拔形式接入根文件系统镜像,默认禁用。Firmware=:使用的Qemu固件,可选uefi-secure-boot、uefi、linux、linux-noinitrd、auto。默认值为auto,表示按顺序尝试使用uefi-secure-boot和linux。Linux=:指定使用的Direct Boot Kernel。FirmwareVariables=:设置UEFI固件中的变量。可选值有:microsoft导入包含Microsoft安全启动证书的固件变量,microsoft-mok导入包含额外安全启动证书的固件变量,custom导入SecureBootCertificate=指定的安全启动证书,或是一个路径。变量文件可以通过virt-fw-vars工具生成。
Drives=:添加的驱动器,格式为ID:大小[:目录:选项:文件ID:标记位]。标记位支持的值有:persist:为磁盘启用持久化。
QemuArgs:配置空格或换行符分隔的Qemu原生参数。KernelCommandLineExtra=:通过Qemu传递的额外内核命令行参数。- 在Qemu中硬编码的额外内核命令行参数为(mkosi/mkosi/qemu.py):
rwsystemd.wants=network.targetmodule_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=noneloglevel=4SYSTEMD_SULOGIN_FORCE=1
- 在Qemu中硬编码的额外内核命令行参数为(mkosi/mkosi/qemu.py):
分区
MKOSI会从mkosi.repart/或RepartDirectories=指定的目录中读取systemd-repart的配置文件,并据此对磁盘镜像进行分区。文件命名应当为优先级-分区名.conf,格式为:
1 | [Partition] |
默认情况下,MKOSI使用以下内置配置:
00-esp.conf:
1 | [Partition] |
- 需要注意的是,
CopyFiles=、CopyBlocks=的源路径的根是构建中的镜像的根(也就是$DESTDIR)。 - 创建
vfat文件系统需要宿主机(或Tools Tree)中存在mcopy程序(通常由mtools包提供)。
05-bios.conf:
1 | [Partition] |
10-root.conf:
1 | [Partition] |
资源目录
有以下相关的资源目录:
mkosi.conf.d/:额外的MKOSI配置,可以是文件,也可以是子工作目录,子工作目录支持的内容和MKOSI的主工作目录完全一致(必须包含mkosi.conf文件)。- 如果使用文件的话,那么文件中的
[Match]段仅对该文件生效。 - 如果使用子工作目录的话,那么子工作目录中的
mkosi.conf中的[Match]段对整个子工作目录生效。
- 如果使用文件的话,那么文件中的
mkosi.tools.conf或mkosi.tools.conf/:额外的ToolsTree配置,可以是文件,也可以是包含配置文件的目录。mkosi.local/:额外的本地MKOSI配置,可以是文件,也可以是子工作目录。mkosi.skeleton/或mkosi.skeleton.tar:在已经构建基本根文件系统,开始安装软件包之前,将文件/目录拷贝到镜像内。与SkeletonTrees=配置项有关。mkosi.extra/或mkosi.extra.tar:在安装软件包之后,将额外的文件/目录拷贝到镜像内。与ExtraTrees=配置项有关。mkosi.sandbox/或mkosi.sandbox.tar:在构建时配置包管理器,但是不将文件写入镜像(这个目录之前被称为mkosi.pkgmngr/)。这可以作为在构建时手动配置软件包管理器的手段,如果需要包含到镜像中,应该使用mkosi.skeleton/或mkosi.skeleton.tar。mkosi.nspawn:systemd-nspawn读取的容器单元文件。与NspawnSettings=配置项有关。mkosi.cache/:Incremental=yes时镜像缓存镜像缓存的保存目录。与CacheDirectory=配置项有关。mkosi.builddir/:用于执行树外构建的构建目录。与BuildDirectory=配置项有关。mkosi.rootpw:镜像中Root用户的密码。文件必须具有0600或更低的权限位。与RootPasswordFile=配置项有关。mkosi.passphrase:LUKS加密使用的密码。文件必须具有0600或更低的权限位。与PassphraseFile=配置项有关。mkosi.crt和mkosi.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.sync.d、mkosi.prepare.d、mkosi.build.d、mkosi.postinst.d、mkosi.finalize.d、mkosi.postoutput.d、mkosi.clean.d:各种钩子脚本的保存目录,目录中的脚本应当以.sh为后缀,可以添加额外的.chroot后缀以在镜像内执行。mkosi.pe-addons/(已经废弃):额外的PE插件的ukify配置文件。与PeAddons=配置项有关。
钩子脚本
钩子脚本如果在末尾添加了.chroot后缀(例如,mkosi.postinst.chroot),那么会Chroot到镜像中执行,否则会在宿主环境中执行。
注:
- “在宿主环境中”意味着使用宿主机提供的命令行工具。
- “在镜像中”意味着使用镜像中提供的命令行工具。
脚本中有以下可用变量(具体可参考__init__.py的run_*_scripts函数):
| 变量 | 含义 | configure |
sync |
prepare |
build |
postinst |
finalize |
postoutput |
clean |
|---|---|---|---|---|---|---|---|---|---|
$ARCHITECTURE |
镜像的架构 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$QEMU_ARCHITECTURE |
Qemu格式的架构,用于通过qemu-system-$QEMU_ARCHITECTURE获取Qemu程序 |
✓ | |||||||
$DISTRIBUTION |
镜像的发行版 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$DISTRIBUTION_ARCHITECTURE |
发行版格式的架构 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$RELEASE |
镜像的版本 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$PROFILES |
Profiles=中设置的配置文件,使用逗号分隔 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
$BUILDROOT |
当前正在构建的镜像的根目录,实际上是/buildroot |
✓ | ✓ | ✓ | ✓ | ||||
$CACHED |
如果有可用的缓存,为1 |
✓ | |||||||
$CHROOT_SCRIPT |
当前正在执行的脚本相对于镜像根目录的路径,主要用于mkosi-chroot,实际上是/work/build-script |
✓ | ✓ | ✓ | ✓ | ||||
$SRCDIR |
启动mkosi的路径,实际上是/work/src |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$CHROOT_SRCDIR |
执行mkosi-chroot以后,$SRCDIR所在的路径,实际上也是/work/src |
✓ | ✓ | ✓ | ✓ | ||||
$BUILDDIR |
树外构建中间文件目录(mkosi.builddir)相对于镜像根目录的路径,实际上是/work/build/$发行版名称,仅在BuildDirectory=或mkosi.builddir/存在时,才会被定义 |
✓ | ✓ | ✓ | ✓ | ||||
$CHROOT_BUILDDIR |
执行mkosi-chroot以后,$BUILDDIR所在的路径,实际上也是/work/build/$发行版名称,仅在BuildDirectory=或mkosi.builddir/存在时,才会被定义 |
✓ | |||||||
$DESTDIR |
用于保存mkosi.build构建时生成的产品的目录,实际上是/work/dest,该目录仅在执行mkosi.build时存在,在mkosi.build脚本执行完成后,该目录下的层次结构会被拷贝到镜像的根文件系统并进行覆盖 |
✓ | |||||||
$CHROOT_DESTDIR |
执行mkosi-chroot以后,$DESTDIR所在的路径,实际上也是/work/dest |
✓ | |||||||
$OUTPUTDIR |
镜像构建产物的保存目录,实际上是/work/out,注意,在mkosi.build脚本中不可见 |
✓ | ✓ | ✓ | ✓ | ||||
$CHROOT_OUTPUTDIR |
执行mkosi-chroot以后,$OUTPUTDIR所在的路径,实际上也是/work/out |
✓ | ✓ | ||||||
$PACKAGEDIR |
PackageDirectories=设定的本地软件包目录,实际上是/work/packages,mkosi.build脚本可以将产品软件包放到该目录 |
✓ | ✓ | ✓ | ✓ | ||||
$ARTIFACTDIR |
实际上是/work/artifacts,类似于$PACKAGEDIR,但是并非用于软件包管理器,而是用于mkosi内部使用的产品,例如手动构建的Initrd,目前接受io.mkosi.microcode和io.mkosi.initrd两个子目录 |
✓ | ✓ | ✓ | ✓ | ||||
$WITH_DOCS |
是否设置了WithDocs= |
✓ | ✓ | ||||||
$WITH_TESTS |
是否设置了WithTests= |
✓ | ✓ | ||||||
$WITH_NETWORK |
是否设置了WithNetwork= |
✓ | ✓ | ✓ | ✓ | ||||
$SOURCE_DATE_EPOCH |
如果设置了SourceDateEpoch=TIMESTAMP,那么会保存时间戳 |
✓ | ✓ | ✓ | ✓ | ✓ | |||
$MKOSI_UID |
调用mkosi的用户UID |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$MKOSI_GID |
调用mkosi的用户GID |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$MKOSI_CONFIG |
当前镜像配置的JSON | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
$IMAGE_ID |
ImageId=的值 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$IMAGE_VERSION |
ImageVersion=的值 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
$MKOSI_DEBUG |
Debug=的值 |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
此外,以下命令可用:
mkosi-chroot:一个封装的chroot,会自动Chroot到镜像内执行命令。- 镜像对应的发行版的包管理器可用,你也可以使用
mkosi-install|remove|upgrade|reinstall封装工具。 git已经自动配置好safe.directory=*。useradd和groupadd已经自动配置好--root=$BUILDROOT。
执行流程
创建MKOSI构建沙箱:
- 解析命令行选项。
- 解析配置文件。
- 在宿主环境中执行
mkosi.configure脚本。 - 检查当前用户是不是Root,如果当前用户不是Root,则尝试根据当前用户的SubUID和SubGID隔离用户命名空间。
- 隔离挂载命名空间。
- 只读化重新挂载各种目录,包括:
/usr/etc/opt/srv/boot/efi/media/mnt
开始构建镜像:
- 拷贝Sandbox Tree(
mkosi.sandbox/)中的文件到构建沙箱中。 - 更新软件包元数据。
- 在宿主环境中执行Sync脚本(
mkosi.sync)。 - 拷贝Base Tree中的文件到镜像中。如果配置了
Overlay=yes,那么执行Overlay挂载。 - 检查并尝试复用缓存镜像。
- 拷贝一份包管理器元数据到镜像内。
- 拷贝Skeleton Tree(
mkosi.skeleton/)中的文件到镜像中。 - 安装发行版基本文件(如果设置了
BaseTrees=,则会跳过这一步)。 - 安装发行版软件包。
- 使用
final命令行参数在宿主环境或镜像中执行mkosi.prepare脚本(如果Format=none且没有mkosi.build脚本的话,则会跳过)。 - 如果存在
mkosi.build脚本的话,在镜像的Overlayfs中安装构建所需的软件包(BuildPackages=)。 - 如果存在
mkosi.build脚本的话,使用build命令行参数,在已经安装了BuildPackages=的Overlayfs中执行mkosi.prepare脚本。 - 如果存在对应的配置(
Incremental=)的话,缓存该镜像(如果Format=none且没有mkosi.build脚本的话,则会跳过)。 - 如果存在
mkosi.build脚本的话,在已经安装了BuildPackages=的Overlayfs中执行mkosi.build脚本。 - 如果
Format=none的话,结束构建。 - 拷贝构建脚本的产物(保存在
$DESTDIR目录下)到镜像内。 - 拷贝Extra Tree(
mkosi.extra/)中的文件到镜像内。 - 在宿主环境或镜像中执行
mkosi.postinst脚本。 - 写入配置项中配置的其他杂项文件。例如
Ssh=、Autologin=和MakeInitrd=。 - 如果配置了
Bootable=yes,安装systemd-boot,并按需配置(SecureBoot=)安全启动。 - 如果
/etc/locale.gen配置了需要编译的语言,执行locale-gen。 - 如果没有配置
Overlay=yes,执行systemd-sysusers。 - 如果没有配置
Overlay=yes,执行systemd-tmpfiles。 - 如果没有配置
Overlay=yes,执行systemctl preset-all。 - 如果配置了
KernelModules=,删除镜像的模块目录/usr/lib/modules/内不需要的模块。如果配置了FirmwareFiles=,删除镜像的模块目录/usr/lib/firmware/内不需要的固件。 - 如果发行版没有提供
modules.dep、modules.dep.bin、modules.symbols、modules.symbols.bin全部四个文件,执行depmod生成它们。 - 如果没有配置
Overlay=yes,执行systemd-firstboot。 - 如果没有配置
Overlay=yes,执行systemd-hwdb。 - 如果安装了SELinux Policy的话,执行SELinux Relabel。
- 移除指定的软件包和文件(
RemovePackages=、RemoveFiles=)。 - 如果配置了
Overlay=yes,对比Overlay的下层文件系统清理同名文件。 - 在宿主环境或镜像中执行
mkosi.finalize脚本。 - 配置内核,如果存在对应的配置(
UnifiedKernelImages=)的话,生成UKI镜像。创建Bootloader配置文件。 - 如果
Format=disk,导出磁盘文件。 - 导出产物到
mkosi.output/。 - 在宿主环境中执行
mkosi.postoutput脚本。
最佳实践
- 要构建磁盘镜像,你需要这些软件包:
btrfs-progs apt dosfstools mtools e2fsprogs squashfs-tools gnupg python3 tar xfsprogs zypper sbsigntool[s] [edk2-]ovmf xz[-utils]。 - 创建工作目录,并使用
mkosi.conf和mkosi.conf.d/*.conf(或mkosi.conf.d/*/mkosi.conf)配置文件以固化构建信息。 - 使用
mkosi.postinst.chroot脚本在镜像中进行配置。 - 使用
mkosi.extra向镜像中传递额外文件。 - 使用
mkosi.build.chroot脚本进行构建,这个脚本对镜像根文件系统的修改不会保留。在脚本中使用$DESTDIR保存构建产物。 - 缓存有两种维度:使用
mkosi.pkgcache目录进行软件包缓存,使用mkosi.cache目录保存Incremental=的增量构建镜像。需要重建增量缓存时,使用-ff选项。 - 务必谨慎使用
mkosi clean:它会清理整个依赖链条上的所有镜像,而非仅仅清理--dependency指定的单个目标。反过来说,通过--dependency指定构建目标在构建阶段是可行的,但是在清理时不能依赖clean,你应该直接执行rm。 Format=none且没有mkosi.build脚本的镜像,虽然不会真正执行构建,但是仍然会参与MKOSI的依赖解析、clean范围计算和缓存状态判断。但是,这类镜像又永远不会生成渐进缓存,这就导致在进行缓存一致性判断时,它总是会导致Cache Mismatch。一旦它出现在依赖链条上,就会导致所有其他本来可以复用缓存的子镜像全部被判定为需要清理缓存。在把主镜像设为Format=none、并结合--dependency把子镜像当作Build Target使用时,这一点尤其容易导致反直觉的缓存清理行为。- 要测试不同的内核命令行参数的效果,使用
KernelCommandLineExtra=而不是重建镜像,以提高效率。 - 可以在无特权的情况下构建磁盘镜像,但是容器文件系统仍然需要Root权限。
- 在启用
SecureBoot=yes时,可以执行mkosi genkey直接生成证书和密钥,也可以自己创建mkosi.key和mkosi.crt。你还可以使用ShimBootloader=yes以安装Shim。 - 使用
BiosBootloader=grub以为BIOS引导安装GRUB。 - 除非你需要调用包管理器或
useradd等命令,否则你应该使用没有.chroot后缀的钩子脚本。 - 使用
useradd -m -g $USER --password "$(openssl passwd -stdin -6 <<< '密码明文')"添加一个普通用户。 - 在Debian中
mkosi qemu不可用,解决方案为执行cp /usr/lib/tmpfiles.d/static-nodes-permissions.conf /etc/tmpfiles.d/static-nodes-permissions.conf,修改/dev/kvm的权限位为0666。 - 在非特权模式构建时,
chown()调用是被禁用的(钩子脚本中没有禁用,要禁用,设置环境变量MKOSI_CHROOT_SUPPRESS_CHOWN=1),要解决该问题,使用SubUID:unshare --map-auto --map-current-user --setuid 0 --setgid 0。 - 在你正式构建时,使用
WithRecommends=no以安装弱依赖。 - 在使用
dd克隆产品镜像前,执行truncate -s|--size=文件大小 镜像文件.img以调整磁盘镜像大小。 - MKOSI可以负责构建Initrd,因此一个没有Initrd构建工具的镜像是可能的。
- 不过需要注意的是,MKOSI构建的Initrd中会使用systemd作为
/init,这与使用MKOSI-INITRD构建的Initrd是一致的。
- 不过需要注意的是,MKOSI构建的Initrd中会使用systemd作为
- 使用
mkosi.profiles/保存不同的镜像口味,使用mkosi.conf.d/保存不同情况下的镜像配置,对于多种子场景的情况,将情况相对少且固定的子场景放在父目录(例如,输出格式为容器或VM),将情况相对较多且不固定的子场景放在子目录(例如,版本)。 - 使用
Format=uki和MakeInitrd=no来创建一个USI(Unified System Image,单个UEFI PE形式的完整系统)。 - 在构建自定义发行版时,使用
mkosi.skeleton/提供最小化的根文件系统,然后使用mkosi.prepare.chroot脚本安装软件包。 - 如果希望通过
mkosi.configure脚本动态修改配置,使用jq从标准输入获取JSON,然后进行修改:jq -Mc '. += {"配置项": "值"}',打印到标准输出即可。 - 要构建源代码,在
BuildPackages=中添加构建需要的软件包,然后用BuildSources=提供源代码,在mkosi.prepare脚本中安装动态的构建时依赖(在命令行参数为final时在镜像中安装,在命令行参数为build时在构建Overlayfs中安装),并使用mkosi.build脚本执行构建,将产物保存到$OUTPUTDIR,通过mkosi.postinst脚本安装产物。 - 国内具有
debian-debug的镜像站为ISCAS,建议使用。 - 如果
mkosi vm启动时阻塞,考虑几个问题:磁盘大小是否经过调整(RuntimeSize=),GRUB/SDBoot的Timeout是否为无限或比较大的数字,Initrd中是否包含了必要的内核模块。 - 如果要使用自定义的内核,只需要在构建时不安装发行版的内核包或移除掉
/usr/lib/modules/目录下的所有文件,然后把自己的内核放在镜像的/usr/lib/modules/<版本号>目录下(版本号格式见KERNEL_VERSION_PATTERN)即可。但要注意,/usr/lib/modules/目录下只能有一个子目录,否则MKOSI会无法判断使用哪个内核而导致构建失败。 - 在特殊情况下,使用
kill -s 'SIGTMIN+4' $PID发送信号给Nspawn容器中的systemd以进行强制关机。 - 单元依赖的Vendor模式(直接在
/usr/lib/systemd/system/multi-user.target.wants/目录下创建软链接)仅适用于没有[Install]段的内部单元声明内部依赖,尽量不要使用,而是使用/usr/lib/systemd/{system,user}-preset/*.preset。 - Preset机制不支持进行
mask,你需要使用mkosi.extra或systemd-tmpfiles在/etc/systemd/system/目录下创建指向/dev/null的符号链接并进行覆盖(L+ /etc/systemd/system/服务名.service - - - - /dev/null)。 - 在构建Confext镜像(
Overlay=yes)时,不要在Preset文件中设置disable *.service,这不会起作用(disable意味着删除/etc/systemd/system/*.target.wants/下的符号链接,删除动作没有办法在Confext镜像中保存),使用Mask,通过mkosi.extra(systemd-tmpfiles也不会被执行)在/etc/systemd/system/目录下创建指向/dev/null的同名符号链接。- 增量优于覆盖。尽量在
/etc下创建符号链接,而不是覆盖/usr下的Vendor单元文件。 - 如果单元文件本来就在
/etc(虽然通常情况下不会出现这种情况),通过mkosi.extra创建符号链接也不会起作用(如果和Base Tree中的文件名相同,会被MKOSI删掉)。最后的手段是:你可以使用systemd的Drop-in机制,创建/etc/systemd/system/单元.service.d/99-disable.conf,内容加一行ConditionPathExists=!/。
- 增量优于覆盖。尽量在
对于使用systemd的kernel-install的发行版来说,MKOSI在构建时会自动跳过内核安装前后置Hook的执行,但是,Debian使用自己实现的Hook(位于/etc/kernel/postinst.d/和/etc/kernel/postrm.d/目录下),因此要跳过,创建脚本mkosi.skeleton/etc/kernel/postinst.d/00disable:
1 | #!/bin/sh |
以及脚本mkosi.skeleton/etc/kernel/postrm.d/00disable:
1 | #!/bin/sh |
发行版最小化构建
一些发行版的最小化构建软件包如下:
Arch
1 | [Content] |
Fedora
1 | [Content] |
CentOS/Rocky
1 | [Content] |
Debian
1 | [Content] |
Ubuntu
1 | [Distribution] |
OpenSUSE
1 | [Content] |
实例
构建一个Fedora容器镜像
使用命令行:
1 | mkosi -d fedora -p kernel,systemd,systemd-udev,systemd-boot --format directory build |
使用配置文件:
1 | # mkosi.conf |
然后执行:
1 | mkosi build |
构建一个Debian可引导磁盘镜像
使用配置文件:
1 | # mkosi.conf |
然后执行:
1 | mkosi build |
构建一个用于编译程序的临时环境
mkosi.conf:
1 | [Distribution] |
mkosi.build.chroot:
1 | #!/bin/bash |
mkosi.finalize:
1 | #!/bin/bash |
构建Initrd
mkosi.conf:
1 | [Output] |
或是利用现有的mkosi-initrd配置:
1 | [Include] |
构建一个USI(UEFI PE程序形式的完整系统镜像)
具体的精简方式见Build USI。
mkosi.conf:
1 | [Distribution] |
- 建议禁用
systemd-homed.service、systemd-userdbd.service和systemd-sysupdate.service。
构建一个Stub NetESP(纯网络启动镜像)
这种情况下,UKI必须通过HTTP提供。
mkosi.conf:
1 | [Distribution] |
mkosi.extra/efi/loader/entries/netesp.conf:
1 | title NetBoot |
mkosi.extra/efi/loader/loader.conf:
1 | timeout 3 |
思路:systemd-boot从远程服务器下载带有root=dissect的UKI,UKI中保存systemd-import-generator相关的内核命令行参数,同样从远程拉取Rootfs并启动,例如:
1 | ip=dhcp rd.systemd.pull=raw,machine,verify=no,blockdev:rootdisk:http://地址/rootfs.raw root=dissect rd.systemd.mask=systemd-repart.service systemd.mask=systemd-repart.service |
构建单独包含NetBoot的用户空间逻辑的UKI(用于UEFI HTTP Boot服务器或直接使用)
mkosi.conf:
1 | [Include] |
构建一个Pull-Tar NetESP(根文件系统从网络拉取)
mkosi.conf:
1 | [Include] |
构建一个Pull-Hermetic-USR-Raw NetESP(根文件系统从网络拉取)
mkosi.conf:
1 | [Include] |
构建一个系统扩展镜像
mkosi.conf(如果没有转换为Format=disk的需求,你也可以将mkosi.images/base/mkosi.conf的配置合并到这里,少一次拷贝):
1 | [Output] |
mkosi.images/base/mkosi.conf(扩展镜像相对的基础镜像):
1 | [Output] |
- 除了文件存在包含关系(因为
BaseTrees=%O/base)外,子镜像的配置和父镜像大部分没有继承关系(参考上文)。请务必注意,尤其是有关Initrd的配置。
mkosi.images/btrfs-sysext/mkosi.conf:
1 | [Assert] |
mkosi.images/btrfs-confext/mkosi.conf:
1 | [Assert] |
注意:MKOSI在构建时,如果发现镜像列表中有至少一个镜像具有BaseTrees=配置,那么就会导致缓存失效,判定元数据需要同步,进而强制清除列表中所有镜像的缓存(mkosi.cache/)。因此在构建扩展镜像时,务必只指定最终的目标镜像(如 mkosi --dependency sysext-xxx build)。否则会导致基座镜像的缓存被误删,导致每次都重新构建。
保留Confext中的额外信息
构建Overlay=yes的镜像时,MKOSI默认不会在最后执行一次systemd-tmpfiles和preset。如果你希望在Confext中保留单元依赖和/etc下可能创建的目录,创建mkosi.postinst.chroot,在其中包含:
1 | SYSTEMD_TMPFILES_FORCE_SUBVOL=0 \ |
构建一个多模态扩展镜像
一个同时包含/etc和/usr的扩展镜像,既可以当作Sysext使用,也可以当作Confext使用。
mkosi.conf以及mkosi.images/base/mkosi.conf的内容与上一段完全相同,如果没有转换为Format=disk的需求,你同样也可以将mkosi.images/base/mkosi.conf的配置合并到mkosi.conf,少一次拷贝。
mkosi.images/btrfs/mkosi.conf:
1 | [Config] |
你还需要自定义磁盘分区格式mkosi.images/btrfs/mkosi.repart/10-root.conf:
1 | [Partition] |
接着还需要一个自定义脚本mkosi.images/btrfs/mkosi.postinst.chroot:
1 | #!/bin/sh |
构建一个Portable Service
Portable Service不可以是扩展镜像,也不支持Overlay=yes。
mkosi.conf:
1 | [Build] |
mkosi.build:
1 | #!/bin/bash |
mkosi.extra/usr/lib/systemd/system/XXX.service:
1 | # 关键点是必须包含StateDirectory等目录,否则程序将无法写入数据目录 |
mkosi.finalize:
1 | #!/bin/bash |
镜像信息
默认情况下,MKOSI构建的镜像无法被systemd-dissect判断为Portable Service镜像,这是因为镜像中的os-release文件中没有包含PORTABLE_PREFIXES=字段,你可以手动配置:
mkosi.conf:
1 | [Content] |
或者:
mkosi.finalize:
1 | #!/bin/bash |
可变镜像
默认情况下镜像中会使用erofs,因此镜像是不可变的,你必须使用StateDirectory=等单元配置项将宿主系统中的目录以读写模式绑定挂载到Portable Service的根文件系统中。
如果你希望调整这一行为,创建mkosi.repart/,包含以下文件:
10-root.conf:
1 | [Partition] |
- 如果你没有设置
StateDirectory=等单元配置项,那么写入的数据会被保存在镜像内部。
构建一个不可变操作系统
首先创建一个如下分区的操作系统:
00-esp.conf:
1 | [Partition] |
10-root-verity-sig.conf:
1 | [Partition] |
11-root-verity.conf:
1 | [Partition] |
12-root.conf:
1 | [Partition] |
除此之外我们还需要构建一个Initrd,Initrd中的systemd-repart.service的启动时间需要进行些许修改:
mkosi.images/initrd/mkosi.conf
1 | [Include] |
mkosi.images/initrd/mkosi.extra/usr/lib/systemd/system/systemd-repart.service.d/sysroot.conf:
1 | [Unit] |
为了进行AB更新,我们还需要在镜像中包含一些预设的分区配置:
mkosi.extra/usr/lib/repart.d/00-esp.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/10-root-verity-sig.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/11-root-verity.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/12-root.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/20-root-verity-sig.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/21-root-verity.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/22-root.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/30-swap.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/40-var.conf:
1 | [Partition] |
因为/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 | [Transfer] |
mkosi.extra/usr/lib/sysupdate.d/11-root-verity.transfer:
1 | [Transfer] |
mkosi.extra/usr/lib/sysupdate.d/12-root.transfer:
1 | [Transfer] |
mkosi.extra/usr/lib/sysupdate.d/20-uki.transfer:
1 | [Transfer] |
构建一个不安全的/usr不可变操作系统
由于上文提到的原因(UKI必须使用Verity Hash对/usr/分区进行定位),我们无法让MKOSI在构建镜像时,自动向UKI中填充/usr/分区所在的位置,因此,我们必须手动在mkosi.conf中设置:
1 | [Content] |
systemd-repart配置如下:
00-esp.conf:
1 | [Partition] |
10-usr.conf:
1 | [Partition] |
此外,还需要在镜像内提供一些分区配置:
mkosi.extra/usr/lib/repart.d/00-esp.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/10-usr-verA.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/20-usr-verB.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/30-swap.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/40-root.conf:
1 | [Partition] |
mkosi.extra/usr/lib/repart.d/50-home.conf:
1 | [Partition] |
我们还需要确保/etc/目录在启动时动态生成,因此需要:
mkosi.extra/usr/lib/tmpfiles.d/etc.conf:
1 | # 仅作参考 |
为了确保ldconfig.service在/etc/目录生成后再启动,我们还需要:
mkosi.extra/usr/lib/systemd/system/ldconfig.service.d/tmpfiles.conf:
1 | [Unit] |
最后,我们需要MKOSI的更新配置:
mkosi.sysupdate/10-usr.transfer:
1 | [Transfer] |
mkosi.sysupdate/20-uki.transfer:
1 | [Transfer] |
杂记
DEB822
1 | Types: deb |
UserNS
在使用Apparmor的情况下,MKOSI需要开启UserNS支持,执行:
1 | sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=1 |
ToolsTree的问题
/usr/share/factory
如果使用了ToolsTree,而且镜像中存在/usr/share/factory,那么ToolsTree中必须也存在/usr/share/factory,否则会发生文件系统只读错误,这是因为ToolsTree的/usr会被只读地绑定挂载到Sandbox中,这导致无法在ToolsTree中自动创建/usr/share/factory挂载点。
不过,该问题在Commit 09497d0中已被修复。
os-release
在Sandbox中,/etc/os-release来自Tools Tree,/usr/lib/os-release来自宿主环境,这意味着MKOSI期望/etc/os-release是一个指向/usr/lib/os-release的符号链接,如果Tools Tree中不是这样,那么会导致systemd-sysupdate的版本判断完全错乱,Debian Sid就有这个问题,因此不要使用Sid作为Tools Tree。
内核模块的问题
Issue 3603报告了一个在默认情况下MKOSI构建的镜像的Initrd中不包含任何模块的问题(即KernelInitrdModules=没有设置的默认情况下不是包含所有模块而是不包含任何模块)。
这个问题的主要原因是这个逻辑错误导致在没有设置KernelModulesInitrdExclude=(exclude集合)的情况下,如果也没有设置KernelInitrdModules=(include集合),那么最终包含的内核模块集合(modules集合)被错误地重设为空(被设为了空的keep集合,而不是保留之前的值)。
gen_required_kernel_modules函数会调用有问题的filter_kernel_modules函数,这个函数只在两个地方出现:
- 在
run_depmod函数调用的process_kernel_modules函数中。 - 在
build_kernel_modules_initrd函数中。
直觉上这似乎也会影响影响镜像内包含的内核模块(run_depmod函数),但是实际上却没有,这是为什么呢?因为process_kernel_modules函数明确包含了在没有设置KernelModules=、KernelModulesExclude=、FirmwareFiles=和FirmwareExclude=的情况下跳过的逻辑,所以并没有造成任何影响。
loader.conf的位置
根据Issue 3614,如果为ESP分区配置了:
1 | CopyFiles=/boot |
那么如果希望在镜像内安装systemd-boot的配置文件loader.conf,则必须放在mkosi.extra/efi/loader/loader.conf。因为多个CopyFiles=字段会按顺序拷贝文件,而systemd-boot在通过bootctl安装到镜像中时,如果/efi/loader/loader.conf不存在,则会自动生成一个默认配置文件,如果用户自定义的配置文件放在mkosi.extra/boot/loader/loader.conf,就会被它覆盖。
“自动安装”元数据丢失
MKOSI支持保留镜像内的软件包元数据(CleanPackageMetadata=no),但是对于apt和dnf来说,“自动安装”(Auto-Installed)元数据会被丢失。
于PR 3682修复。
mkosi.version重新定义
PR 3696重写了MKOSI的历史记录逻辑,现在MKOSI的历史记录中不会保存完整的配置记录,因此一旦配置发生改变,就不会认为镜像已被构建。
这带来的最大影响是现在不可以再使用完全动态生成版本的mkosi.version脚本,否则MKOSI总是会认为镜像未被构建。解决方案是转而使用mkosi.bump。
LUKS加密
Passphrase=和mkosi.passphrase创建的密钥会用于mkosi.repart中设置了Encrypt=key-file的分区。
APK
部分发行版尚未打包APK,可以下载静态编译版本并放在PATH路径下:apk-tools-static。
Archlinux ARM构建失败
Archlinux ARM的内核路径不符合标准,因此需要额外的处理才能正常构建,创建mkosi.postinst.chroot:
1 | #!/bin/bash |
UKI Profile
UKI Profile在设计上可以支持额外文件,但是这不会再实现了,Lennart最终决定在systemd-boot实现在加载UKI时Overlay式的扩展。
UNAME到OCI架构格式的转换
1 | UNAME_TO_OCI = { |