Skip to content

Commit 5b5d49e

Browse files
committed
os: add experimental reproducible mkosi backend
1 parent b304efe commit 5b5d49e

21 files changed

Lines changed: 655 additions & 4 deletions

os/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ os/
99
├── common/rootfs/ # backend-independent guest payload
1010
├── image/ # backend-independent release assembler
1111
├── spec/ # versioned backend artifact contract
12-
└── yocto/ # the currently implemented backend
12+
├── mkosi/ # experimental pinned Debian/mkosi backend
13+
└── yocto/ # production backend
1314
├── deps/ # external Yocto layers (git submodules)
1415
├── layers/ # dstack-owned Yocto layers
1516
└── tools/ # Yocto-specific host workarounds
1617
```
1718

18-
A future backend can be added at `os/<backend>/` (for example `os/mkosi/`)
19-
without moving shared payload or duplicating release packaging. No mkosi
20-
backend is implemented today.
19+
A backend can be added at `os/<backend>/` without moving shared payload or
20+
duplicating release packaging. The mkosi backend is experimental but implements
21+
the same artifact-manifest and common release-assembly contract as Yocto; see
22+
[`mkosi/README.md`](mkosi/README.md) for its scope and acceptance criteria.
2123

2224
## Build
2325

os/build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Build dstack guest OS release artifacts with a selected backend.
1313
1414
Available backends:
1515
yocto Reproducible Yocto build (default)
16+
mkosi Experimental pinned Debian/mkosi build
1617
1718
A backend lives at os/<name>/build.sh and implements the "image" action.
1819
The common image contract is documented in os/README.md.

os/mkosi/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
build*/
2+
repro*/
3+
*.raw
4+
*.raw.zst
5+

os/mkosi/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Experimental mkosi backend
2+
3+
This is a deliberately small Debian/mkosi implementation of the dstack guest
4+
OS. It is an experiment, not yet a replacement for the release Yocto backend.
5+
It tracks the Yocto image's confidential-computing, container, storage,
6+
network, TPM, SSH, chrony and dstack guest-service capabilities.
7+
8+
## Reproducibility model
9+
10+
`versions.env` pins the stable 7.x kernel tarball by SHA-256 and selects an
11+
immutable Debian snapshot. `Cargo.lock`, `--locked --offline`, a fixed
12+
`SOURCE_DATE_EPOCH`, normalized file mtimes, fixed kernel build identity and
13+
mkosi's deterministic image construction close the remaining inputs. The two
14+
dstack binaries therefore require an already populated Cargo cache; use a
15+
vendored source tree in hermetic CI. `DSTACK_SKIP_RUST=1` exists only for
16+
rootfs/kernel development and does **not** produce a functional guest.
17+
18+
The custom kernel starts from `x86_64_defconfig`, then applies the reviewed
19+
`kernel.config` fragment. This is the practical upstream equivalent of Yocto's
20+
`linux-yocto-tiny` plus explicit features: disabling arbitrary defconfig
21+
symbols without resolving Kconfig dependencies would be less auditable. Both
22+
the TDX DMA patch and the ACPI BadAML/SystemMemory sandbox patch are reused
23+
verbatim from `meta-dstack`; patch fuzz is forbidden. The final `.config` is
24+
checked before compilation.
25+
26+
## Build and acceptance
27+
28+
Host requirements include mkosi >= 26, systemd tools, a C/Rust kernel and EDK2
29+
build toolchain, `bc`, `bison`, `flex`, `nasm`, `iasl`, OpenSSL/ELF/UUID
30+
development headers, `patch`, `pax-utils` (`lddtree`), `squashfs-tools`,
31+
`cryptsetup`, `gdisk`, `dosfstools`, `mtools`, `curl`, `xz`, QEMU/KVM and root
32+
privileges (or a working user namespace). Full UKI release assembly also needs
33+
the pinned `nitro-tpm-pcr-compute` described by `os/image/assemble.sh`.
34+
35+
```sh
36+
./os/mkosi/build.sh lint
37+
./os/build.sh --backend mkosi --build-dir "$PWD/os/mkosi/build"
38+
./os/mkosi/build.sh repro-check "$PWD/os/mkosi/repro"
39+
# QEMU smoke-test the assembled UKI disk (host OVMF path is distro-specific)
40+
qemu-system-x86_64 -machine q35 -m 2G -nographic \
41+
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
42+
-drive if=virtio,format=raw,file=os/mkosi/build/out/prod/dstack-0.6.0/disk.raw
43+
```
44+
45+
Acceptance means: the static contract passes; a disk with systemd-boot/UKI
46+
boots on x86_64 QEMU; `/proc/config.gz` contains the checked TDX/SNP, TPM,
47+
ACPI, dm-verity/crypt, virtio, container and hardening options; dstack services
48+
are enabled; and two clean builds compare byte-for-byte. The backend exports
49+
artifact-manifest schema v1 and delegates final assembly to
50+
`os/image/assemble.sh`, exactly like Yocto. Its output contains the same
51+
`dstack-0.6.0/` directory, bare-metal and UKI tarballs, partitioned combined
52+
squashfs/dm-verity image, metadata, measurements, checksums, kernel, initramfs,
53+
OVMF and UKI. Package contents differ because this backend uses Debian, but
54+
consumers see the same release format and partition labels.
55+
56+
The firmware is not Debian's generic OVMF: `build-ovmf.sh` builds the same
57+
EDK2 stable-202502 revision and `pre202505` TDX measurement layout selected by
58+
the Yocto recipe. A generic OVMF cannot be substituted because `dstack-mr`
59+
would produce invalid or unparseable TDX measurement material.

os/mkosi/build.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/bin/bash
2+
# SPDX-License-Identifier: Apache-2.0
3+
set -euo pipefail
4+
SELF=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
5+
ROOT=$(cd "$SELF/../.." && pwd)
6+
source "$SELF/versions.env"
7+
action=${1:-image}
8+
BUILD_DIR=${2:-$SELF/build}
9+
case "$action" in
10+
image|repro-check|lint) ;;
11+
*) echo "Usage: $0 {image|repro-check|lint} [build-dir]" >&2; exit 2 ;;
12+
esac
13+
if [[ $action == lint ]]; then exec "$SELF/tests/acceptance.sh"; fi
14+
command -v mkosi >/dev/null || { echo 'mkosi >= 26 is required' >&2; exit 1; }
15+
actual=$(mkosi --version | awk '{print $2}' | cut -d. -f1)
16+
(( actual >= MKOSI_MIN_VERSION )) || { echo "mkosi >= $MKOSI_MIN_VERSION required" >&2; exit 1; }
17+
export SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-$(git -C "$ROOT" log -1 --format=%ct)}
18+
export TZ=UTC LC_ALL=C
19+
20+
build_one() {
21+
local out=$1 work=$2 flavor=$3
22+
local stage="$work/rootfs-stage" kstage="$work/kernel-stage" tree="$work/rootfs"
23+
rm -rf "$work" "$out"; mkdir -p "$stage" "$kstage" "$out"
24+
"$SELF/scripts/stage-rootfs.sh" "$stage"
25+
"$SELF/scripts/build-kernel.sh" "$work" "$kstage"
26+
"$SELF/scripts/build-ovmf.sh" "$work/ovmf-build" "$kstage/ovmf.fd"
27+
cat > "$work/mkosi.local.conf" <<EOF
28+
[Content]
29+
ExtraTrees=$stage
30+
Bootable=no
31+
[Output]
32+
Format=directory
33+
OutputDirectory=$work
34+
Output=rootfs
35+
CompressOutput=no
36+
EOF
37+
if [[ $flavor == dev ]]; then
38+
cat >> "$work/mkosi.local.conf" <<EOF
39+
[Content]
40+
Packages=strace,tcpdump,gdb,vim
41+
EOF
42+
fi
43+
devargs=()
44+
if [[ $flavor == dev ]]; then
45+
devargs+=(--package=strace --package=tcpdump --package=gdb --package=vim)
46+
fi
47+
mkosi --directory "$SELF" --force --extra-tree="$stage" "${devargs[@]}" \
48+
--format=directory --output-directory="$work" --output=rootfs \
49+
--compress-output=no --bootable=no build
50+
mkdir -p "$tree/usr/lib/modules"
51+
cp -a "$kstage/usr/lib/modules/." "$tree/usr/lib/modules/"
52+
artifact_dir="$work/artifacts/$flavor"
53+
"$SELF/scripts/make-release-artifacts.sh" "$tree" "$kstage" "$artifact_dir" "$flavor"
54+
DIST_DIR="$out" \
55+
TAR_OPTIONS="--sort=name --mtime=@$SOURCE_DATE_EPOCH --owner=0 --group=0 --numeric-owner" \
56+
"$ROOT/os/image/assemble.sh" --manifest "$artifact_dir/artifact-manifest.json"
57+
release_name=dstack
58+
[[ $flavor == dev ]] && release_name=dstack-dev
59+
"$SELF/tests/check-output.sh" "$out/$release_name-$DSTACK_VERSION" "$flavor"
60+
}
61+
62+
if [[ $action == image ]]; then
63+
for flavor in $FLAVORS; do build_one "$BUILD_DIR/out/$flavor" "$BUILD_DIR/work-$flavor" "$flavor"; done
64+
exit
65+
fi
66+
build_one "$BUILD_DIR/a" "$BUILD_DIR/work-a" prod
67+
build_one "$BUILD_DIR/b" "$BUILD_DIR/work-b" prod
68+
cmp "$BUILD_DIR/a/dstack-0.6.0.tar.gz" "$BUILD_DIR/b/dstack-0.6.0.tar.gz"
69+
echo 'reproducibility check passed'

os/mkosi/kernel.config

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# dstack 7.x kernel: x86_64 defconfig baseline plus explicitly audited guest features.
2+
CONFIG_LOCALVERSION="-dstack"
3+
CONFIG_LOCALVERSION_AUTO=n
4+
CONFIG_MODULES=y
5+
CONFIG_IKCONFIG=y
6+
CONFIG_IKCONFIG_PROC=y
7+
CONFIG_EFI=y
8+
CONFIG_EFI_STUB=y
9+
CONFIG_EFI_PARTITION=y
10+
CONFIG_ACPI=y
11+
CONFIG_ACPI_TABLE_UPGRADE=y
12+
CONFIG_HYPERVISOR_GUEST=y
13+
CONFIG_PARAVIRT=y
14+
CONFIG_KVM_GUEST=y
15+
CONFIG_INTEL_TDX_GUEST=y
16+
CONFIG_TDX_GUEST_DRIVER=y
17+
CONFIG_TSM_REPORTS=y
18+
CONFIG_AMD_MEM_ENCRYPT=y
19+
CONFIG_SEV_GUEST=y
20+
CONFIG_EFI_COCO_SECRET=y
21+
CONFIG_EFI_SECRET=y
22+
CONFIG_VMGENID=y
23+
CONFIG_VIRT_DRIVERS=y
24+
CONFIG_TCG_TPM=y
25+
CONFIG_TCG_TIS=y
26+
CONFIG_TCG_CRB=y
27+
CONFIG_VIRTIO=y
28+
CONFIG_VIRTIO_PCI=y
29+
CONFIG_VIRTIO_BLK=y
30+
CONFIG_VIRTIO_NET=y
31+
CONFIG_VIRTIO_CONSOLE=y
32+
CONFIG_VIRTIO_VSOCKETS=y
33+
CONFIG_VSOCKETS=y
34+
CONFIG_NET_9P=y
35+
CONFIG_NET_9P_VIRTIO=y
36+
CONFIG_9P_FS=y
37+
CONFIG_BLK_DEV_NVME=y
38+
CONFIG_NVME_CORE=y
39+
CONFIG_NET_VENDOR_GOOGLE=y
40+
CONFIG_GVE=y
41+
CONFIG_NET_VENDOR_AMAZON=y
42+
CONFIG_ENA_ETHERNET=y
43+
CONFIG_SERIAL_8250=y
44+
CONFIG_SERIAL_8250_CONSOLE=y
45+
CONFIG_MD=y
46+
CONFIG_BLK_DEV_DM=y
47+
CONFIG_DM_CRYPT=y
48+
CONFIG_DM_VERITY=y
49+
CONFIG_CRYPTO_SHA256=y
50+
CONFIG_CRYPTO_SHA512=y
51+
CONFIG_CRYPTO_AES_NI_INTEL=y
52+
CONFIG_EXT4_FS=y
53+
CONFIG_SQUASHFS=y
54+
CONFIG_SQUASHFS_ZSTD=y
55+
CONFIG_XFS_FS=m
56+
CONFIG_BTRFS_FS=m
57+
CONFIG_OVERLAY_FS=y
58+
CONFIG_OVERLAY_FS_METACOPY=y
59+
CONFIG_FUSE_FS=m
60+
CONFIG_BLK_DEV_LOOP=y
61+
CONFIG_TMPFS_XATTR=y
62+
CONFIG_TMPFS_POSIX_ACL=y
63+
CONFIG_QUOTA=y
64+
CONFIG_QFMT_V2=y
65+
CONFIG_CGROUPS=y
66+
CONFIG_BLK_CGROUP=y
67+
CONFIG_CGROUP_BPF=y
68+
CONFIG_CGROUP_CPUACCT=y
69+
CONFIG_CGROUP_DEVICE=y
70+
CONFIG_CGROUP_FREEZER=y
71+
CONFIG_CGROUP_SCHED=y
72+
CONFIG_CFS_BANDWIDTH=y
73+
CONFIG_NAMESPACES=y
74+
CONFIG_USER_NS=y
75+
CONFIG_SECCOMP=y
76+
CONFIG_SECCOMP_FILTER=y
77+
CONFIG_BPF=y
78+
CONFIG_BPF_SYSCALL=y
79+
CONFIG_BPF_JIT=y
80+
CONFIG_BRIDGE=m
81+
CONFIG_BRIDGE_NETFILTER=m
82+
CONFIG_VETH=m
83+
CONFIG_VLAN_8021Q=m
84+
CONFIG_TUN=m
85+
CONFIG_WIREGUARD=m
86+
CONFIG_NETFILTER=y
87+
CONFIG_NETFILTER_ADVANCED=y
88+
CONFIG_NF_TABLES=m
89+
CONFIG_NFT_COMPAT=m
90+
CONFIG_NF_NAT=m
91+
CONFIG_NETFILTER_XTABLES=m
92+
CONFIG_IP_SET=m
93+
CONFIG_IP_VS=m
94+
CONFIG_IPVLAN=m
95+
CONFIG_VXLAN=m
96+
CONFIG_CONFIGFS_FS=y
97+
CONFIG_SECURITY=y
98+
CONFIG_SECURITY_DMESG_RESTRICT=y
99+
CONFIG_HARDENED_USERCOPY=y
100+
CONFIG_FORTIFY_SOURCE=y
101+
CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y
102+
CONFIG_SLAB_FREELIST_RANDOM=y
103+
CONFIG_SLAB_FREELIST_HARDENED=y
104+
CONFIG_IO_STRICT_DEVMEM=y
105+
CONFIG_STACKPROTECTOR_STRONG=y
106+
CONFIG_STRICT_KERNEL_RWX=y
107+
CONFIG_STRICT_MODULE_RWX=y
108+
CONFIG_RANDOMIZE_BASE=y
109+
CONFIG_CPU_MITIGATIONS=y
110+
CONFIG_MITIGATION_PAGE_TABLE_ISOLATION=y
111+
CONFIG_MITIGATION_RETPOLINE=y
112+
CONFIG_LEGACY_TIOCSTI=n
113+
CONFIG_MAGIC_SYSRQ=n
114+
CONFIG_INET_ESP=n
115+
CONFIG_INET6_ESP=n
116+
CONFIG_AF_RXRPC=n
117+
CONFIG_SUSPEND=n
118+
CONFIG_BT=n
119+
CONFIG_WLAN=n
120+
CONFIG_MMC=n

os/mkosi/mkosi.conf

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
[Config]
3+
MinimumVersion=26
4+
5+
[Distribution]
6+
Distribution=debian
7+
Release=trixie
8+
Architecture=x86-64
9+
Snapshot=20260721T000000Z
10+
Repositories=main,contrib,non-free-firmware
11+
12+
[Output]
13+
Format=disk
14+
Output=dstack-mkosi.raw
15+
CompressOutput=no
16+
ImageId=dstack
17+
ImageVersion=0.1
18+
19+
[Content]
20+
Bootable=yes
21+
Bootloader=systemd-boot
22+
Hostname=dstack
23+
Packages=
24+
systemd
25+
systemd-boot
26+
systemd-resolved
27+
dbus
28+
ca-certificates
29+
bash
30+
coreutils
31+
iproute2
32+
iptables
33+
nftables
34+
docker.io
35+
docker-compose
36+
containerd
37+
chrony
38+
openssh-server
39+
wireguard-tools
40+
cryptsetup
41+
dmsetup
42+
tpm2-tools
43+
curl
44+
jq
45+
fuse3
46+
pigz
47+
xfsprogs
48+
e2fsprogs
49+
gdisk
50+
parted
51+
zfsutils-linux
52+
busybox-static
53+
cryptsetup-bin
54+
ovmf
55+
systemd-boot-efi
56+
SkeletonTrees=mkosi.skeleton
57+
PostInstallationScripts=mkosi.postinst
58+
KernelCommandLine=console=ttyS0,115200n8 console=tty0 panic=30 coherent_pool=64M
59+
60+
[Validation]
61+
SecureBoot=no

os/mkosi/mkosi.postinst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
# SPDX-License-Identifier: Apache-2.0
3+
set -euo pipefail
4+
5+
# mkosi executes post-installation scripts outside the image with BUILDROOT.
6+
B=${BUILDROOT:?}
7+
mkdir -p "$B/dev" "$B/proc" "$B/sys" "$B/run" "$B/tmp" "$B/dstack" \
8+
"$B/etc/wireguard" "$B/var/lib/docker" "$B/var/lib/containerd"
9+
rm -f "$B/etc/machine-id" "$B/var/lib/dbus/machine-id"
10+
: > "$B/etc/machine-id"
11+
rm -rf "$B/var/cache/apt/"* "$B/var/lib/apt/lists/"* "$B/var/log/"*
12+
ln -sfn /run/systemd/resolve/stub-resolv.conf "$B/etc/resolv.conf"
13+
ln -sfn dstack "$B/tapp"
14+
ln -sfn ../lib/systemd/systemd "$B/usr/sbin/init"
15+
16+
systemctl --root="$B" enable systemd-networkd systemd-resolved chrony docker \
17+
dstack-firstboot.service dstack-prepare.service \
18+
dstack-guest-agent.socket dstack-guest-agent.service \
19+
app-compose.service wg-checker.service
20+
systemctl --root="$B" mask getty@.service serial-getty@.service debug-shell.service \
21+
sleep.target suspend.target hibernate.target hybrid-sleep.target
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Acquire::Check-Valid-Until "false";
2+
APT::Install-Recommends "false";
3+
APT::Install-Suggests "false";
4+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"iptables": true,
3+
"ip6tables": true,
4+
"storage-driver": "overlay2"
5+
}
6+

0 commit comments

Comments
 (0)