-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTaskfile.yml
More file actions
447 lines (405 loc) · 22.7 KB
/
Copy pathTaskfile.yml
File metadata and controls
447 lines (405 loc) · 22.7 KB
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# spin-machine — a virtual machine: QEMU, the guest kernel, the base image, and the
# definition of the machine they make.
#
# The four are one thing. A VM restored from a template loads device and CPU state into a
# machine that has to be the same shape as the one the template was frozen from, and
# nothing checks that at run time. So `machine.Spec.Fingerprint` hashes the QEMU binary,
# the kernel and the initrd by content together with the machine's shape, and a release in
# which any of them moved has a different fingerprint by construction. That is why they
# ship as one tarball with one version: shipped separately, two could drift and leave the
# third to notice.
#
# task build everything, into _output/
# task shell boot the machine and look around inside it
# task test the machine definition
# task release one tarball, one version
#
# What is in this file is the vars every part reads and the targets that cross all of them.
# Each part's own targets live beside the thing they build — qemu/Taskfile.yml,
# kernel/Taskfile.yml, image/Taskfile.yml — namespaced, so `task qemu:build` is next to
# qemu/Dockerfile and nobody reads three hundred lines to change one flag.
#
# What is deliberately NOT here: the software that runs inside a guest. This repository
# builds a machine and knows nothing about what boots on it, and there is no exception to
# that. A debug initramfs is the one that keeps being proposed: it is a second init, doing
# what the init a consumer already brings does, and the machine boots straight into
# /sbin/init without one.
version: '3'
silent: true
output: prefixed
includes:
qemu:
taskfile: qemu/Taskfile.yml
# The build context is the repository root: the Dockerfiles COPY qemu/devices.mak and
# image/mkosi.extra by their path from here, so every part sees the same tree and no
# `..` appears in a COPY.
dir: '{{.ROOT_DIR}}'
kernel:
taskfile: kernel/Taskfile.yml
dir: '{{.ROOT_DIR}}'
e2fsprogs:
taskfile: e2fsprogs/Taskfile.yml
dir: '{{.ROOT_DIR}}'
image:
taskfile: image/Taskfile.yml
dir: '{{.ROOT_DIR}}'
vars:
ROOT_DIR:
sh: pwd
OUTPUT_DIR: '{{.OUTPUT_DIR | default "_output"}}'
# The same directory, absolute, resolved once.
#
# Everything handed to `docker -v`, to a VM, or to a checksum has to be an absolute path,
# and `{{.OUTPUT_ABS}}` is only one when OUTPUT_DIR is relative. With
# OUTPUT_DIR=/tmp/build it produced <repository>/tmp/build, so QEMU and the kernel were
# extracted to /tmp/build and the base image was written somewhere else entirely — a
# build that succeeds and leaves half its output where nothing looks for it.
#
# -m so an output directory that does not exist yet still resolves.
OUTPUT_ABS:
sh: realpath -m "{{.OUTPUT_DIR}}"
# One version for all three artefacts. See the header.
#
# --dirty, and it is the whole point of the line. Without it, `task release` on a tagged
# commit with edits in the tree produces spin-machine-v20260914.03-linux-x86_64.tar.gz
# with version=v20260914.03 in its manifest: the same name and the same declared identity
# as the release that was actually published, around different bytes. machine.env is how
# a host explains why its templates stopped matching, so it is the one file that must not
# be able to claim an identity the bytes do not have.
#
# The fallback is --tags --always --dirty rather than a bare hash, matching what
# hack/release defaults to on its own: v20260914.03-1-g86d2386 says which release this is
# one commit past, where 86d2386 says nothing until somebody looks it up.
VERSION:
sh: |
echo "${VERSION:-$(git describe --tags --exact-match --dirty 2>/dev/null \
|| git describe --tags --always --dirty 2>/dev/null \
|| echo dev)}"
# The version pins are NOT here. Each lives once, in the Dockerfile that builds the
# thing, and the Taskfile beside it reads that — see qemu/Taskfile.yml.
#
# They were here as well as there, which is one duplication and one worse thing: this
# file had to be in every workflow's path filter, since a version bump in it must rebuild
# what it pins. So `task lint` gaining a check, or a comment being fixed, fired QEMU, the
# kernel and the base image — three builds, tens of minutes each, for an edit that could
# not have changed any of them.
#
# What is left here is what does not change an artefact: how many jobs to compile with,
# where to put the output, which cache backend to use.
QEMU_JOBS: '{{.QEMU_JOBS | default 8}}'
KERNEL_ARCH: '{{.KERNEL_ARCH | default "x86_64"}}'
KERNEL_NPROC: '{{.KERNEL_NPROC | default 8}}'
# Where BuildKit reads and writes its cache: a directory under the repository for a
# developer, GitHub's cache service in CI. One name to switch — `CACHE_BACKEND=gha` — and
# every scope below is decided here, next to the builds they belong to, rather than as
# eight cache strings pasted into workflow files.
#
# The scopes are why this is a var and not a string in a workflow. `type=gha` defaults to
# the scope `buildkit`, and Docker's own documentation says what that means for a
# repository with more than one build: "each build will overwrite the cache of the
# previous, leaving only the final cache." Four builds sharing that default is four builds
# each destroying the last one's cache and then missing on the next run. The names have to
# be distinct, and — this is the part that is easy to get wrong — they have to be the
# *same* names in the per-artefact workflows and in the release, or a release warms
# nothing that the lane on main already paid for.
#
# `gha` needs one thing this file cannot give it — see the note in
# .github/workflows/qemu.yml. The variables that authorise a write to the cache service
# are not in a plain `run:` step's environment, and without them `--cache-to type=gha` is
# accepted, stores nothing, and says nothing.
CACHE_BACKEND: '{{.CACHE_BACKEND | default "local"}}'
BUILDKIT_CACHE_DIR: '{{.BUILDKIT_CACHE_DIR | default (printf "%s/.cache/buildkit" .ROOT_DIR)}}'
QEMU_CACHE_FROM: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=qemu{{else}}type=local,src={{.BUILDKIT_CACHE_DIR}}/qemu{{end}}'
QEMU_CACHE_TO: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=qemu,mode=max{{else}}type=local,dest={{.BUILDKIT_CACHE_DIR}}/qemu,mode=max,compression=zstd{{end}}'
KERNEL_CACHE_FROM: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=kernel{{else}}type=local,src={{.BUILDKIT_CACHE_DIR}}/kernel{{end}}'
KERNEL_CACHE_TO: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=kernel,mode=max{{else}}type=local,dest={{.BUILDKIT_CACHE_DIR}}/kernel,mode=max,compression=zstd{{end}}'
# The firmware experiment. Its own scope like everything else, and it is never built by
# `task build`: nothing in a release comes from it.
# The floor-kernel experiment, like QBOOT_ above: never built by `task build`.
KMIN_CACHE_FROM: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=kmin{{else}}type=local,src={{.BUILDKIT_CACHE_DIR}}/kmin{{end}}'
KMIN_CACHE_TO: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=kmin,mode=max{{else}}type=local,dest={{.BUILDKIT_CACHE_DIR}}/kmin,mode=max,compression=zstd{{end}}'
QBOOT_CACHE_FROM: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=qboot{{else}}type=local,src={{.BUILDKIT_CACHE_DIR}}/qboot{{end}}'
QBOOT_CACHE_TO: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=qboot,mode=max{{else}}type=local,dest={{.BUILDKIT_CACHE_DIR}}/qboot,mode=max,compression=zstd{{end}}'
E2FSPROGS_CACHE_FROM: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=e2fsprogs{{else}}type=local,src={{.BUILDKIT_CACHE_DIR}}/e2fsprogs{{end}}'
E2FSPROGS_CACHE_TO: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=e2fsprogs,mode=max{{else}}type=local,dest={{.BUILDKIT_CACHE_DIR}}/e2fsprogs,mode=max,compression=zstd{{end}}'
# The container that builds the base image. Small — mkosi, qemu-utils — and cached like
# the rest: uncached, every CI run reinstalls it from the archive before any image work
# starts.
IMAGE_CACHE_FROM: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=image{{else}}type=local,src={{.BUILDKIT_CACHE_DIR}}/image{{end}}'
IMAGE_CACHE_TO: '{{if eq .CACHE_BACKEND "gha"}}type=gha,scope=image,mode=max{{else}}type=local,dest={{.BUILDKIT_CACHE_DIR}}/image,mode=max,compression=zstd{{end}}'
# mkosi's package cache, which is not a BuildKit cache because mkosi does not run under
# BuildKit (see image/Dockerfile). A rebuild of the base image is ~1.5 GB of apt.
MKOSI_CACHE_DIR: '{{.MKOSI_CACHE_DIR | default (printf "%s/.cache/mkosi" .ROOT_DIR)}}'
IMAGE_BUILDER_TAG: '{{.IMAGE_BUILDER_TAG | default "spin-machine/image-builder:dev"}}'
# Where the QEMU runtime image is published. The default is a local tag so
# `task qemu:image` needs no registry.
QEMU_IMAGE: '{{.QEMU_IMAGE | default "spin-stack/spin-machine-qemu"}}'
# An external runtime to check a release against, and the guest init it boots. Both
# empty by default: nothing here needs them, and `task verify:consumer` says so and
# skips rather than pointing at a path that may not exist.
#
# CONSUMER_RUNTIME is a command that takes `--image <qcow2>` and a `--` command to run
# in the guest, and CONSUMER_INITRD the initrd it expects to boot.
CONSUMER_RUNTIME: '{{.CONSUMER_RUNTIME | default ""}}'
CONSUMER_INITRD: '{{.CONSUMER_INITRD | default ""}}'
SUDO: '{{.SUDO | default "sudo"}}'
tasks:
default:
cmds:
- task --list
build:
desc: Build QEMU, the guest kernel, e2fsprogs, the base image and the tools into _output/
cmds:
- task: qemu:build
- task: kernel:build
# Before the image, which is made with them.
- task: e2fsprogs:build
- task: image:build
- task: tools
- cmd: echo "✓ build complete — {{.OUTPUT_ABS}}/"
tools:
desc: >-
Build spin-machine: boots a VM of this machine, prints its fingerprint, and gives a
running one a disk or saves it (`spin-machine -h`).
sources:
- machine/**/*.go
- cmd/**/*.go
- go.mod
generates:
- '{{.OUTPUT_ABS}}/bin/spin-machine'
cmds:
- mkdir -p {{.OUTPUT_ABS}}/bin
- CGO_ENABLED=0 go build -ldflags '-s -w' -o {{.OUTPUT_ABS}}/bin/spin-machine ./cmd/spin-machine
boot:bench:
desc: >-
What a boot costs, measured from the host and repeated enough to believe. Needs
/dev/kvm, a built release, and sudo — variants that mask a unit write into a throwaway
overlay through qemu-nbd. REPS= to change the count, DEBUG=1 for kernel and systemd
logging (which inflates what it measures, equally in every variant).
cmds:
- SPIN_BOOT_BENCH=1 go test ./boot/ -run TestBootCost -v -timeout 60m
boot:logind:
desc: >-
Check first SSH login, reconnect, user services and logout in a disposable guest.
Needs a built release; uses KVM when available, otherwise TCG.
The image does not start logind at boot, so this checks the thing that makes that
safe: that the first login activates it and gets a real session. SPIN_LOGIND_NO_SEATS=1
removes the drop-in that allows it and is expected to fail, not to measure anything.
cmds:
- SPIN_LOGIND_TEST=1 go test ./boot/ -run '^TestLogindSessions$' -count=1 -v -timeout 5m
boot:initcalls:
desc: >-
Where the kernel's own boot goes, initcall by initcall, as a p50 over REPS boots.
Needs KVM, a built release and sudo. It boots with the console silent and reads the
ring buffer afterwards, because initcall_debug on a serial console puts a VM exit
inside every interval it reports. TOP= how many rows, REPS= how many boots.
cmds:
- SPIN_INITCALL_PROBE=1 go test ./boot/ -run TestKernelInitcalls -count=1 -v -timeout 30m
boot:unitload:
desc: >-
What systemd spends loading units and building the initial transaction, from its own
instrumentation: `systemd --test` reports it at LOG_INFO where a real boot logs it at
LOG_DEBUG. Many runs inside one boot, so there is no boot-to-boot noise, and it
measures the generators by masking two that cannot do anything on this machine. Needs
KVM, a built release and sudo.
cmds:
- SPIN_UNITLOAD_PROBE=1 go test ./boot/ -run TestUnitLoadCost -count=1 -v -timeout 15m
boot:userspace:
desc: >-
systemd's own account of the boot: its kernel/userspace split, `blame` and the
critical chain, read after the boot rather than during it. Needs KVM, a built release
and sudo. Read a critical chain as what finished last, never as what was blocking —
see the "console no dev" row in boot/bench_test.go for what that mistake cost.
cmds:
- SPIN_USERSPACE_PROBE=1 go test ./boot/ -run TestUserspaceCost -count=1 -v -timeout 15m
boot:systemd:
desc: >-
Inside early userspace, in systemd's own words: debug logging to the journal, read
afterwards, ranked by the gaps in it. Needs KVM, a built release and sudo. It inflates
the phase it measures, so the absolute numbers are not `task boot:bench`'s — the shape
is what it is for. GAP= the floor in ms, TOP= how many gaps.
cmds:
- SPIN_SYSTEMD_DEBUG=1 go test ./boot/ -run TestSystemdDebug -count=1 -v -timeout 15m
boot:floor:
desc: >-
What a kernel costs before anything is configured into it: the release kernel against
the one `task kernel:minimal` builds, interleaved, on a marker both can reach. Needs
KVM, a built release, `task kernel:minimal`, and a diagnostic initrd in
SPIN_PROBE_INITRD=. The answer bounds every kernel-config change that could follow.
cmds:
- SPIN_FLOOR_PROBE=1 go test ./boot/ -run TestKernelFloor -count=1 -v -timeout 20m
boot:firmware:
desc: >-
Compare firmware for this machine: SeaBIOS against the qboot build in qemu/qboot/.
Needs KVM, a built release, `task qemu:qboot`, and a diagnostic initrd in
SPIN_PROBE_INITRD=. Without the firmware or the initrd it says which is missing and
skips: neither is an artefact a release carries. It asserts a firmware can run this
machine at all before it times one, because a firmware with no WRITE_POINTER loses
vmgenid and a lost vmgenid has no symptom.
cmds:
- SPIN_FIRMWARE_PROBE=1 go test ./boot/ -run TestFirmwareCost -count=1 -v -timeout 20m
boot:trace:
desc: >-
One boot's console printed against the host's clock, for finding a gap that belongs to
neither systemd nor the kernel. SPIN_GETTY_EXEC= replaces agetty to tell its own delays
apart from the tty's.
cmds:
- SPIN_BOOT_TRACE=1 go test ./boot/ -run TestBootTrace -v -timeout 10m
test:
desc: Test the machine definition.
cmds:
- go test ./...
lint:
desc: >-
Everything CI checks that is not a build: formatting, vet, the Taskfiles parse, and
every shell script is syntactically valid.
cmds:
- |
set -euo pipefail
unformatted=$(gofmt -l machine cmd boot)
if [ -n "$unformatted" ]; then
echo "not gofmt'd:"; echo "$unformatted"; exit 1
fi
echo "OK: gofmt"
- go vet ./...
- cmd: 'echo "OK: go vet"'
# `task --list` parses every included Taskfile, so a syntax error or a name defined
# twice fails here rather than in whichever lane first called it.
- task --list > /dev/null
- cmd: 'echo "OK: the Taskfiles parse"'
- |
set -euo pipefail
for f in hack/release hack/fingerprint-diff image/build.sh image/mkosi.postinst.chroot \
image/mkosi.extra/usr/local/lib/spin-base/*.sh boot/testdata/*.sh; do
bash -n "$f" || { echo "$f does not parse" >&2; exit 1; }
done
echo "OK: the shell scripts parse"
verify:args:
desc: >-
Ask the QEMU in _output/ whether it accepts the command line the machine package
builds. Every interesting Spec is started under the TCG binary, stopped before its
first instruction, and required to answer on QMP.
cmds:
# It crosses two parts — the machine definition and the binary — so it is here rather
# than in qemu/Taskfile.yml, and it refuses rather than skipping: the test itself
# skips when there is no binary, which is right for `go test ./...` in a source
# checkout and would be a gate that passes for the wrong reason here.
- |
set -euo pipefail
test -x {{.OUTPUT_ABS}}/bin/qemu-system-x86_64-tcg || {
echo "no {{.OUTPUT_ABS}}/bin/qemu-system-x86_64-tcg to ask." >&2
echo " task qemu:fetch the published build of the pinned version, seconds" >&2
echo " task qemu:build from source, tens of minutes" >&2
exit 1; }
# -count=1 because the answer depends on a file the test cache does not know about:
# a rebuilt QEMU with a device removed would otherwise be met with a cached pass.
# -v because what was rewritten for the TCG binary, and what a host without
# /dev/vhost-vsock left unchecked, is the part a reader has to see.
# And the chain over descriptors: what QEMU does with it, not only whether it parses.
- SPIN_MACHINE_OUTPUT={{.OUTPUT_ABS}} go test ./machine -run 'TestQEMUAcceptsEveryArgument|TestAChainOverDescriptors' -count=1 -v
fingerprint:
desc: >-
Print this machine's identity: the hash of the QEMU binary, the kernel and the initrd
by content, together with the four arguments that decide the machine's shape. Two
machines with the same fingerprint can exchange templates.
deps: [tools]
cmds:
- '{{.OUTPUT_ABS}}/bin/spin-machine fingerprint --release {{.OUTPUT_ABS}} {{.CLI_ARGS}}'
shell:
desc: >-
Boot this machine and get a shell inside the base image, to see how it feels and what
it is missing. `task shell INIT=/bin/bash` boots a bare shell instead of systemd.
aliases: [vm]
interactive: true
cmds:
- task: image:shell
vars:
INIT: '{{.INIT}}'
# ==========================================================================
# Release
# ==========================================================================
release:
desc: Build everything and pack one versioned tarball.
cmds:
- task: build
# No QEMU_VERSION or KERNEL_VERSION: hack/release reads both from the Dockerfiles,
# the same way it already reads the tarball checksums. Passing them from here would
# put the pin back in this file, where a workflow would have to watch it.
- VERSION="{{.VERSION}}" OUTPUT_DIR="{{.OUTPUT_ABS}}" KERNEL_ARCH="{{.KERNEL_ARCH}}" hack/release
verify:consumer:
desc: >-
Check a release against an external runtime, if one is configured. Pass
CONSUMER_RUNTIME=<command> and CONSUMER_INITRD=<path>; with neither, this says so and
skips. Nothing in this repository needs it — it is the assertion that a release is
usable by something that boots guests.
cmds:
- task: release
- |
set -euo pipefail
if [ -z "{{.CONSUMER_RUNTIME}}" ]; then
echo "no CONSUMER_RUNTIME set — skipping. This repository builds a machine and"
echo "does not ship the software that boots on it; point this at a runtime to"
echo "check a release end to end:"
echo " task verify:consumer CONSUMER_RUNTIME=<cmd> CONSUMER_INITRD=<path>"
exit 0
fi
rel={{.OUTPUT_ABS}}/release/spin-machine-{{.VERSION}}-linux-{{.KERNEL_ARCH}}
test -f "$rel.tar.gz" || { echo "no release tarball at $rel.tar.gz" >&2; exit 1; }
# Removed with sudo: the VM below runs as root and leaves its state and log
# directories owned by root inside $stage, so a plain rm -rf leaves them and the
# trap prints permission errors after a run that succeeded.
stage=$(mktemp -d); trap '{{.SUDO}} rm -rf "$stage"' EXIT
tar -xzf "$rel.tar.gz" -C "$stage"
share="$stage/$(basename "$rel")/usr/share/spin-stack"
# A release is not bootable on its own, by design: the guest's init belongs to
# whoever runs guests. This is where the two halves are put together.
if [ -n "{{.CONSUMER_INITRD}}" ]; then
test -f "{{.CONSUMER_INITRD}}" || {
echo "no initrd at {{.CONSUMER_INITRD}}" >&2; exit 1; }
cp "{{.CONSUMER_INITRD}}" "$share/kernel/"
fi
# The image inside the tarball, and not the one in _output. They are the same
# file today and the point of this task is the day they are not: a release is
# verified by booting what it ships, or it is not verified.
img="$share/image/rootfs.qcow2"
test -f "$img" || { echo "the tarball carries no image at $img" >&2; exit 1; }
# Three questions, because "it printed something" answers none of them.
#
# One: does a write inside the guest reach the disk and come back? A boot that
# cannot write has a read-only overlay or no overlay at all, and prints exactly
# what a working one prints.
marker="spin-machine-$(date -u +%s)-$$"
before=$(sha256sum "$img" | cut -d' ' -f1)
out=$({{.SUDO}} SPIN_MACHINE_SHARE="$share" {{.CONSUMER_RUNTIME}} \
--image "$img" -- /bin/sh -c \
"echo $marker > /root/marker; sync; cat /root/marker" 2>/dev/null)
[ "$out" = "$marker" ] || {
echo "the guest could not write and read back a file; it printed: $out" >&2
exit 1; }
# Two: is that write private to the VM that made it? Every VM maps this one file
# through a backing chain, so a write that leaked out of an overlay is a container
# reading another container's disk.
leaked=$({{.SUDO}} SPIN_MACHINE_SHARE="$share" {{.CONSUMER_RUNTIME}} \
--image "$img" -- /bin/sh -c \
"cat /root/marker 2>/dev/null || echo absent" 2>/dev/null)
[ "$leaked" = "absent" ] || {
echo "a second VM saw the first one's write: $leaked" >&2
echo "the overlays are not isolated, or the base image was written to." >&2
exit 1; }
# Three: is the base itself untouched? It is the check that costs nothing and
# catches the worst failure — a base that moved under every overlay built on it.
after=$(sha256sum "$img" | cut -d' ' -f1)
[ "$before" = "$after" ] || {
echo "the base image changed during a boot: $before -> $after" >&2
echo "every overlay in existence is now built on a file that moved." >&2
exit 1; }
echo "OK: the packaged image boots under {{.CONSUMER_RUNTIME}}; writes land in the"
echo " overlay, do not leak to the next VM, and leave the base byte-identical"
clean:
desc: Remove build output. Leaves the caches; use clean:cache for those.
cmds:
- rm -rf {{.OUTPUT_ABS}}
clean:cache:
desc: Remove the BuildKit and mkosi caches. A full rebuild after this is hours.
cmds:
- rm -rf {{.BUILDKIT_CACHE_DIR}} {{.MKOSI_CACHE_DIR}}