Repository navigation
Expand file tree
/
Copy pathContainerfile.gentoo
More file actions
701 lines (667 loc) · 37.4 KB
/
Copy pathContainerfile.gentoo
File metadata and controls
701 lines (667 loc) · 37.4 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
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
# Containerfile.gentoo — Gentoo Linux on bootc.
# Based on bootcrew/gentoo-bootc but adapted for TunaOS manifest-driven desktop installs.
ARG BASE_IMAGE
ARG COMMON_IMAGE_REF="ghcr.io/projectbluefin/common:unpinned-must-override"
ARG IMAGE_NAME
ARG IMAGE_VENDOR
ARG SHA_HEAD_SHORT
ARG ENABLE_SSHD="${ENABLE_SSHD:-0}"
ARG DESKTOP_FLAVOR="${DESKTOP_FLAVOR:-gnome}"
ARG TUNAOS_IMAGE_FLAVOR
ARG IMAGE_REGISTRY="ghcr.io"
ARG IMAGE_NAME_VARIANT
ARG JUST_VERSION="1.35.0"
ARG JUST_SHA256="c4172306e241bd175c07316156a610593fa2b687ac49418520a375605d7cead4"
ARG GLOW_VERSION="1.5.1"
ARG GLOW_SHA256="d224be006fc4ee361c1d1a85dbd46c269372b76c5413e2a9bfd0f7fe52c66f78"
ARG GUM_VERSION="0.14.3"
ARG GUM_SHA256="76836c1ba0f5aaebed5426a02b986c890cc3560c6165aed5894758671115aeb1"
ARG BOOTC_COMMIT="3e76c16556c55e6d15d31bd47602b231e2131cb2"
# Context files sit at root; the build stages bind-mount this stage's / at
# /run/context, so e.g. /build_scripts → /run/context/build_scripts. (Copying
# to /run/context/... here would double-nest to /run/context/run/context/...)
FROM scratch AS context
COPY manifests /manifests
COPY tunaos.toml /tunaos.toml
COPY build_scripts /build_scripts
COPY system_files /files
COPY system_files_overrides /overrides
FROM ${COMMON_IMAGE_REF} AS common
# BASE_IMAGE, not a hardcoded ref. This line used to pin
# stage3@sha256:08914e15… while build-config.yml pinned
# stage3@sha256:9d03b48… for guppy — the ARG above was declared and then
# ignored, so every guppy build used this digest no matter what the config,
# renovate, or `just build` said. Same drift class as the base-image table in
# scripts/get-base-image.sh (#1014); build-config.yml is the source of truth
# and build-image-inner.sh already passes it through as a build-arg.
FROM ${BASE_IMAGE} AS base
ARG JUST_VERSION
ARG JUST_SHA256
ARG GLOW_VERSION
ARG GLOW_SHA256
ARG GUM_VERSION
ARG GUM_SHA256
# Speed: pull prebuilt, gpg-verified binary packages from Gentoo's official
# binhost instead of compiling all of @world from source (which blew past the
# 60-min CI job timeout — tuna-os/tunaOS#644). getuto imports the shipped
# gentoo-release key as the binpkg trust anchor. MAKEOPTS/--jobs parallelize
# the few things that still build from source (e.g. bootc from git in the
# builder stage, or any binpkg whose USE doesn't match).
# 1) Sync portage, select the systemd profile, and configure the official
# binhost + parallel-build opts. getuto imports the gentoo-release key as the
# binpkg trust anchor so signed binpkgs verify.
RUN NPROC="$(nproc)" && \
emerge --sync --quiet && \
eselect profile list | grep -E "desktop.*systemd" | grep -v 32 | head -1 | awk '{print $2}' | xargs eselect profile set && \
{ \
echo 'FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox getbinpkg"'; \
echo 'ACCEPT_LICENSE="*"'; \
echo 'USE="dracut nftables systemd X wayland dbus gtk gnome qt6 plasma opengl harfbuzz alsa pulseaudio x264 x265 aac mp3 opus vpx libaom encode ffmpeg gstreamer"'; \
echo "MAKEOPTS=\"-j${NPROC}\""; \
echo "EMERGE_DEFAULT_OPTS=\"--jobs=${NPROC} --load-average=${NPROC} --getbinpkg --binpkg-respect-use=n\""; \
} >> /etc/portage/make.conf && \
mkdir -p /etc/portage/binrepos.conf && \
printf '[gentoobinhost]\npriority = 9999\nsync-uri = https://distfiles.gentoo.org/releases/amd64/binpackages/23.0/x86-64/\n' \
>/etc/portage/binrepos.conf/gentoobinhost.conf && \
getuto && \
# boot only, here. The cryptsetup and tpm flags systemd also needs are
# granted AFTER @world, in the tooling layer below: with them visible to
# the first @world resolve, portage schedules the systemd source rebuild
# and the initial cryptsetup install in ONE transaction and dies on
# * Error: circular dependencies:
# (sys-fs/cryptsetup, binary) depends on (virtual/libudev) (runtime)
# (sys-apps/systemd, ebuild) depends on (sys-fs/cryptsetup) (buildtime)
# (all three guppy cells, run 31158591954, ~4 min in). Once cryptsetup is
# installed the buildtime edge is satisfied by the live system instead of
# the transaction, and the same flags resolve without a cycle.
echo "sys-apps/systemd boot" >> /etc/portage/package.use/systemd && \
echo "media-video/pipewire extra -ffmpeg" >> /etc/portage/package.use/pipewire && \
echo "media-libs/gst-plugins-bad -vulkan" >> /etc/portage/package.use/gst-plugins-bad && \
echo "sys-libs/minizip-ng compat" >> /etc/portage/package.use/minizip-ng && \
echo "gui-libs/gtk -vulkan" >> /etc/portage/package.use/gtk && \
echo "*/* -abi_x86_32" >> /etc/portage/package.use/multilib
# 2) Bring @world up to date from binpkgs — own layer so a later failure reuses
# this (expensive) cache on retry.
RUN emerge --verbose --deep --newuse @world
# 3) Install the bootc/base tooling, prebuilt kernel, and extra base packages.
# sys-apps/flatpak is not optional despite looking like a desktop nicety:
# live-iso/common/src/customize-live.sh pre-installs the TunaOS installer as
# a flatpak, and without it every guppy live-overlay build fails with
# ERROR: flatpak not installed; cannot pre-install org.tunaos.Installer<DE>
# (runs 30159027680, 30196443189). Gentoo was the last Containerfile without
# it — every other base already installs flatpak. Same class as #809, which
# added it to the openSUSE base.
#
# app-admin/sudo for the same reason, one class over. The stage3 tarball has no
# sudo — the rpm and apt bases either ship it or install it explicitly, and
# Gentoo was the last one without. customize-live.sh writes a NOPASSWD sudoers
# drop-in for liveuser whenever ENABLE_SSHD=1, which authorises nothing without
# the binary; its assertion catches that at ISO-build time rather than letting
# every `sudo` in the E2E guest die with "command not found":
#
# ERROR: ENABLE_SSHD=1 grants liveuser NOPASSWD sudo, but this image has no sudo.
#
# guppy:xfce, LUKS run 31082460289 — after a complete, correct image build.
#
# app-containers/podman, third of the same kind, and the one that skopeo looks
# like it covers but does not. guppy probes BACKEND=composefs-native, and the
# composefs install path cannot take fisherman's bootcDirect shortcut — bootc
# has to run in a container, which means podman in the live guest (see the
# SWAP_DISK comment in scripts/iso-e2e.sh, written from a sailfin OOM of that
# very process). skopeo is here for bootc's containers-image-proxy and answers a
# different question, so the gap read as "image absent" rather than "no podman":
# the offline store held ghcr.io/tuna-os/guppy:gnome and every probe of it was
# `sudo: podman: command not found`, so the cell fell through to the SSH image
# transfer that scripts/iso-e2e.sh documents as impossible and died on
#
# scp: write remote "/home/liveuser/luks-image-guppy-gnome.tar": Failure
#
# 2h30m into the job. guppy:gnome, LUKS run 31134373523. Every other base has
# podman — the rpm and apt ones from build_scripts/10-base-packages.sh,
# Containerfile.arch and .debian by name — and Gentoo runs none of those, which
# is why guppy was the only variant without it.
#
# sys-fs/cryptsetup and sys-fs/lvm2, fourth of the same kind, found the same
# way: with podman in place the guppy cells got past the store probe for the
# first time and died one line into the install instead —
#
# fisherman: fatal: missing required host tool: "systemd-cryptenroll" not
# found in PATH — install the "systemd" package on the host
#
# guppy:gnome, LUKS run 31144015274, 3h20m in. "Install systemd" is the right
# advice on every OTHER base, where the systemd package unconditionally ships
# the binary. Gentoo only builds systemd-cryptenroll when systemd has
# USE=cryptsetup, which neither the desktop/systemd profile nor the make.conf
# USE line enables — hence the `cryptsetup tpm` grant mid-layer below (tpm so
# the enroll tool can at least attempt the TPM2 path the recipe asks for; it
# falls back to passphrase everywhere today). The grant happens here and not
# in the package.use layer above because systemd[cryptsetup] build-depends on
# the cryptsetup this very list installs — flag first meant a circular-
# dependency abort at @world (run 31158591954, see the package.use comment).
# cryptsetup and lvm2 are named here explicitly because fisherman's LUKS path
# needs the cryptsetup and dmsetup binaries themselves right after the enroll
# tool — the bootc-install refactor comments further down have said "Gentoo
# emerges neither" for a while; this is the run that made it cost a cell.
# tpm2-tss is named so the tpm dep is already installed (as a binpkg) when the
# forced source rebuild runs, keeping --usepkg=n's blast radius to systemd
# itself.
RUN emerge --verbose --deep --newuse \
app-arch/cpio btrfs-progs dev-vcs/git dosfstools sys-fs/xfsprogs \
linux-firmware rust skopeo sys-kernel/gentoo-kernel-bin systemd \
sys-apps/flatpak app-admin/sudo app-containers/podman \
sys-fs/cryptsetup sys-fs/lvm2 app-crypt/tpm2-tss \
net-vpn/tailscale gui-apps/wl-clipboard media-video/ffmpeg media-plugins/gst-plugins-meta && \
# Now — and only now — grant systemd the flags that need those packages
# installed first (see the cycle comment above). Overwrite, not append:
# this line replaces the boot-only grant from the base layer.
echo "sys-apps/systemd boot cryptsetup tpm" > /etc/portage/package.use/systemd && \
# make.conf runs emerge with --binpkg-respect-use=n, so the binhost's
# systemd (built WITHOUT cryptsetup) satisfies the install above even with
# the package.use entry in place, and the flag silently does nothing. Force
# the one package whose USE actually matters to build from source.
#
# BOTH binpkg switches must be off. --usepkg=n alone does not do it:
# make.conf also sets --getbinpkg (and FEATURES=getbinpkg), and with
# getbinpkg still active emerge fetched the binhost package and "rebuilt"
# systemd in two seconds flat — the merge log's `<<< dir
# /usr/lib/systemd/boot` (removed, never replaced) is the binhost's
# flagless build erasing even the stage3's boot support, and all three
# guppy cells then died on the cryptenroll assert 2h45m in (run
# 31164551903). The assertion afterwards is the point: a binary this
# image exists to provide must fail the BUILD when absent, not a matrix
# cell three hours later.
emerge --oneshot --verbose --usepkg=n --getbinpkg=n sys-apps/systemd && \
command -v systemd-cryptenroll && \
command -v cryptsetup && \
command -v dmsetup && \
curl -fsSL -o /tmp/just.tar.gz "https://github.com/casey/just/releases/download/${JUST_VERSION}/just-${JUST_VERSION}-x86_64-unknown-linux-musl.tar.gz" && \
echo "${JUST_SHA256} /tmp/just.tar.gz" | sha256sum -c - && \
tar -xzf /tmp/just.tar.gz -C /usr/bin just && rm /tmp/just.tar.gz && \
curl -fsSL -o /tmp/glow.tar.gz "https://github.com/charmbracelet/glow/releases/download/v${GLOW_VERSION}/glow_Linux_x86_64.tar.gz" && \
echo "${GLOW_SHA256} /tmp/glow.tar.gz" | sha256sum -c - && \
tar -xzf /tmp/glow.tar.gz -C /usr/bin glow && rm /tmp/glow.tar.gz && \
curl -fsSL -o /tmp/gum.tar.gz "https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}/gum_${GUM_VERSION}_Linux_x86_64.tar.gz" && \
echo "${GUM_SHA256} /tmp/gum.tar.gz" | sha256sum -c - && \
tar -xzf /tmp/gum.tar.gz -C /tmp && mv "/tmp/gum_${GUM_VERSION}_Linux_x86_64/gum" /usr/bin/ && rm -rf /tmp/gum*
# No live-boot dracut modules needed: tacklebox injects its embedded
# tbox-live/tbox-root modules at ISO build time (tuna-os/tacklebox#90).
#
# It injects them by re-running THIS image's dracut with flags of its own, so an
# omit that only exists on our own dracut command lines below is not an omit at
# ISO time. tacklebox decides what to drop by probing for the module DIRECTORY
# (/usr/lib/dracut/modules.d/[0-9][0-9]<mod>), and sys-kernel/dracut ships the
# whole upstream modules.d tree whether or not the userspace behind a module was
# ever emerged — so the probe finds tpm2-tss "present", omits nothing, and
# dracut then rejects it because `require_binaries tpm2` cannot be met. guppy
# has no app-crypt/tpm2-tools and no pcscd. guppy:gnome, LUKS run 31111959946,
# after a completely successful image build:
#
# dracut[E]: Module 'tpm2-tss' cannot be installed.
# Error: prepare initramfs for guppy-gnome ... (does the image ship dracut?)
#
# Flavor-dependent, which is what let it hide: guppy:xfce came through the same
# rebuild ("rebuilt, added: tbox-live tbox-root", nothing omitted — run
# 31091141499), so a green cell was never evidence this base was covered. Same
# trap tests/bats/test_dracut_tpm2_userspace.bats documents for the apt bases,
# where flounder:kde was green while flounder:gnome died here.
#
# Declared in the image the way Containerfile.arch and .opensuse declare it, so
# every in-image rebuild inherits it — tacklebox's, and a kernel update's on the
# installed system. It goes in the `base` stage, not in the two stages that run
# dracut: `desktop-build` is FROM builder, not FROM system, so a drop-in written
# in `system` would reach base-no-de and no desktop image. TPM2 auto-unlock is
# enrolled post-install (`ujust enable-luks-tpm2`) and both dracut runs below
# have always passed this same omit, so nothing that worked stops working.
RUN mkdir -p /usr/lib/dracut/dracut.conf.d && \
printf 'omit_dracutmodules+=" tpm2-tss pcsc "\n' \
> /usr/lib/dracut/dracut.conf.d/30-omit-unsatisfiable.conf
FROM base AS builder
ARG BOOTC_COMMIT
# The cleanup below MUST stay in this RUN. Unlike Containerfile.arch — which
# builds bootc in a throwaway stage and COPY --from=bootc-builder /output / —
# this file is linear (base -> builder -> system), so every file created here
# ships in the published image. Deleting the tree in a later RUN would only
# write a whiteout; the layer would still carry the data.
#
# Without this, guppy:base shipped the whole bootc compile tree —
# /tmp/bootc/target/release/deps/*.rmeta and friends — inflating pulls and
# unpacked size, and it exhausted the browser builder's storage quota (#672).
#
# Gentoo cannot simply copy Arch's throwaway-builder pattern: this stage also
# emerges dev-util/ostree, which the final system needs at runtime.
RUN emerge --verbose dev-util/ostree net-misc/openssh && \
git init /tmp/bootc && \
git -C /tmp/bootc remote add origin https://github.com/bootc-dev/bootc.git && \
git -C /tmp/bootc fetch --depth 1 origin "${BOOTC_COMMIT}" && \
git -C /tmp/bootc checkout --detach FETCH_HEAD && \
make -C /tmp/bootc bin install-all && \
rm -rf /tmp/bootc /root/.cargo /root/.rustup /var/tmp/portage
FROM builder AS system
# Re-declare the identity args INSIDE this stage and promote them to ENV — the
# ARG lines at the top of this file are global, so they reach `FROM`
# interpolation and nothing else. A stage sees an ARG only if it declares it,
# and an ARG is never automatically an ENV. Without this, 90-image-info.sh
# (wired into this stage) runs with IMAGE_VENDOR unset and dies under `set -u`
# with "IMAGE_VENDOR: unbound variable", taking out every guppy cell. Same gap
# Containerfile.opensuse had; Containerfile.ubuntu and .arch already do it.
#
# IMAGE_NAME_VARIANT specifically: 90-image-info.sh keys its codename table on
# the canonical variant id (`guppy`), and gentoo is one of the bases whose
# Containerfile does not pin IMAGE_NAME, so lib.sh derives it from the detected
# base image and hands back the distro alias `gentoo`. Passing the variant id
# through is what keeps the alias from becoming the image's identity.
ARG BASE_IMAGE
ARG IMAGE_NAME
ARG IMAGE_VENDOR
ARG IMAGE_REGISTRY="ghcr.io"
ARG IMAGE_NAME_VARIANT
ARG SHA_HEAD_SHORT
ARG ENABLE_SSHD="${ENABLE_SSHD:-0}"
ARG DESKTOP_FLAVOR
ARG TUNAOS_IMAGE_FLAVOR
ENV TUNAOS_IMAGE_FLAVOR=${TUNAOS_IMAGE_FLAVOR}
ENV BASE_IMAGE=${BASE_IMAGE} \
IMAGE_NAME=${IMAGE_NAME} \
IMAGE_VENDOR=${IMAGE_VENDOR} \
IMAGE_REGISTRY=${IMAGE_REGISTRY} \
IMAGE_NAME_VARIANT=${IMAGE_NAME_VARIANT} \
SHA_HEAD_SHORT=${SHA_HEAD_SHORT} \
ENABLE_SSHD=${ENABLE_SSHD} \
DESKTOP_FLAVOR=${DESKTOP_FLAVOR}
# Stage the kernel image into /usr/lib/modules, and move portage's database out
# of /var. Both must happen BEFORE the ostree layout below, which deletes /boot
# outright and wipes /var.
RUN KVER=$(ls /usr/lib/modules | head -1) && \
rm -f "/usr/lib/modules/$KVER/vmlinuz" && \
(cp /boot/vmlinuz-* "/usr/lib/modules/$KVER/vmlinuz" 2>/dev/null || cp /usr/src/linux-*/arch/x86/boot/bzImage "/usr/lib/modules/$KVER/vmlinuz") && \
rm -rf /var/cache && \
# Keep portage's package database; drop only its caches.
#
# /var/db/pkg is the record of what is installed, and deleting it here
# (plus the /var wipe below) is why ghcr.io/tuna-os/guppy:base answers
# `qlist -ICv` with nothing — qlist is installed, the database simply
# isn't there, so the running system cannot enumerate its own packages
# (tuna-os/tunaos-packages#135).
#
# Relocated into /usr for the reason Fedora moved the rpmdb there
# (Changes/RelocateRPMToUsr): the database describes /usr, so keeping it
# under /usr makes it snapshot and roll back with the image instead of
# being erased with /var. The tmpfiles link restores the default path.
mkdir -p /usr/lib/sysimage && \
if [ -d /var/db/pkg ]; then rm -rf /usr/lib/sysimage/portage; cp -a /var/db /usr/lib/sysimage/portage; fi && \
rm -rf /var/db
# Shared bootc/ostree layout (the /var wipe, alias symlinks, tmpfiles.d and
# prepare-root.conf) — build_scripts/bootc/ostree-layout.sh. What stays here is
# Gentoo-specific: pruning /dev, and linking portage's relocated database back
# into place after the wipe.
#
# This block used to be duplicated verbatim in the `system` and `desktop`
# stages, and its symlink list was missing /srv, /mnt and /usr/local — all
# three removed on the way in and never put back. The shared script restores
# them; see its header.
RUN --mount=type=bind,from=context,source=/,target=/run/context \
find /dev -mindepth 1 -maxdepth 1 ! -name pts ! -name shm ! -name mqueue -exec rm -rf {} + 2>/dev/null || true && \
/run/context/build_scripts/bootc/ostree-layout.sh && \
printf 'd /var/db 0755 root root -\nL+ /var/db/pkg - - - - ../../usr/lib/sysimage/portage/pkg\n' \
> /usr/lib/tmpfiles.d/portage-sysimage.conf && \
mkdir -p /var/db && ln -sfnT ../../usr/lib/sysimage/portage/pkg /var/db/pkg
# Build the initramfs.
#
# On build_scripts/bootc/dracut-config.sh, like every other bootc variant, and
# AFTER ostree-layout.sh above — that script writes prepare-root.conf, which is
# where dracut-config.sh reads whether this image is composefs and therefore
# whether the initramfs needs the erofs driver. Reversed, every step still
# exits 0 and the initramfs silently cannot mount its own root. Same ordering
# note as Containerfile.debian and Containerfile.arch carry.
#
# It was NOT on the shared script, and the comment here said why: it names the
# crypt and dm dracut modules, "which require cryptsetup and dmsetup binaries;
# Gentoo emerges neither", so requesting them would be fatal, and packaging
# sys-fs/cryptsetup and sys-fs/lvm2 was the stated prerequisite. That is no
# longer true, measured inside the published ghcr.io/tuna-os/guppy:gnome:
#
# /usr/sbin/cryptsetup present
# /usr/sbin/dmsetup present
# dracut modules 51bootc, 70crypt, 70dm all installed
#
# so every module the shared config asks for is satisfiable on this base today.
#
# What the gap cost: with no drop-in, dracut never added 51bootc, and `lsinitrd`
# on that same image's initramfs matched ZERO ostree/bootc files. Nothing then
# prepared the deployment at boot, so /sysroot stayed the PHYSICAL root and
# switch-root refused it --
#
# Failed to switch root: Specified switch root path '/sysroot' does not seem
# to be an OS tree. os-release file is missing.
#
# -- dropping guppy:xfce to an emergency shell with the disk already unlocked
# and /sysroot already mounted (LUKS run 31232170155). Every other variant was
# immune because they all run this script.
#
# The --omit on the command line stays: it covers this build, while the drop-in
# written near the top of the file is what reaches tacklebox's ISO-time rebuild
# and post-install kernel updates.
RUN --mount=type=bind,from=context,source=/,target=/run/context set -eu; \
KVER="$(ls /usr/lib/modules | head -1)"; \
IMG="/usr/lib/modules/${KVER}/initramfs.img"; \
/run/context/build_scripts/bootc/dracut-config.sh "$KVER"; \
dracut --force --no-hostonly --reproducible --zstd --omit "tpm2-tss pcsc" \
--kver "$KVER" "$IMG"; \
# Assert the module actually landed, the way Containerfile.opensuse does.
# A missing 51bootc costs nothing at build time and everything at boot, and
# this exact absence shipped undetected through a green image build, a green
# ISO build and a successful disk unlock before failing in switch-root.
#
# Only the two things measured absent are asserted. The crypt/dm payload is
# deliberately NOT pinned here: opensuse asserted `sbin/cryptsetup` on the
# same reasoning and failed a perfectly good initramfs (run 30735665323),
# because a systemd initrd ships systemd-cryptsetup instead. Assert what was
# measured, not what seems like it ought to be there.
lsinitrd "$IMG" > /tmp/initramfs.list; \
if ! grep -qx bootc /tmp/initramfs.list; then \
echo "ERROR: initramfs has no 'bootc' dracut module — nothing prepares the composefs deployment, so switch-root gets the physical root and drops to an emergency shell" >&2; \
exit 1; \
fi; \
if ! grep -qEi 'ostree|bootc' /tmp/initramfs.list; then \
echo "ERROR: initramfs contains no ostree/bootc files at all" >&2; \
exit 1; \
fi; \
rm -f /tmp/initramfs.list
# Cache-bust so build_scripts edits invalidate the desktop stages' install
# layers (bind-mounted scripts aren't in the cache key). Inherited by children.
ARG BUILD_SCRIPTS_HASH
ENV BUILD_SCRIPTS_HASH=${BUILD_SCRIPTS_HASH}
# Base (no desktop) — the `base` flavor targets this. resolve-flavor.sh maps
# base → DESKTOP_FLAVOR=base-no-de, and the build does `--target=<flavor>`.
# (Matches the main Containerfile's base stage: label it bootable, no lint —
# the lint gate lives on the desktop stages.)
FROM system AS base-no-de
LABEL containers.bootc=1 ostree.bootable=1
# Desktop stage — manifest-driven; the DESKTOP_FLAVOR build-arg selects the DE.
FROM builder AS desktop-build
ARG DESKTOP_FLAVOR
ARG TUNAOS_IMAGE_FLAVOR
ENV TUNAOS_IMAGE_FLAVOR=${TUNAOS_IMAGE_FLAVOR}
ENV DESKTOP_FLAVOR=${DESKTOP_FLAVOR}
# Identity, and branding, BEFORE install-desktop.sh.
#
# install-desktop.sh ends by running the branding contract, and every other
# Containerfile satisfies it the same way: the base stage runs the numbered
# scripts — 90-image-info.sh among them — and the desktop stages build on top
# of an already-branded image. This branch does not. It goes
# builder -> desktop-build, while 90-image-info.sh lives in `system`, which is
# a sibling. So the contract ran against stock Gentoo and reported fourteen
# failures at once:
#
# FAIL: no fish codename defined for variant 'xfce'
# FAIL: PRETTY_NAME is 'Gentoo Linux' — still names the upstream distro
# FAIL: SUPPORT_URL is 'https://www.gentoo.org/support/'
# TUNAOS_BRANDING_FAIL variant=xfce failures=14
#
# "variant 'xfce'" is the same gap seen twice: install-desktop.sh passes
# `${IMAGE_NAME:-${_TD_DESKTOP}}`, so an unset IMAGE_NAME silently becomes the
# desktop name and the codename lookup is asked for a variant that does not
# exist. guppy:xfce only reached this once the libdbusmenu USE pin let the
# emerge finish.
#
# SHA_HEAD_SHORT is deliberately NOT here: it changes every commit, and an ENV
# above the emerge would invalidate the desktop package build on every push.
# 90-image-info.sh defaults it to "testing", and the `desktop` stage below
# re-runs the script with the real value — osr_set rewrites in place, so the
# second pass corrects BUILD_ID and IMAGE_VERSION rather than duplicating them.
ARG BASE_IMAGE
ARG IMAGE_NAME
ARG IMAGE_VENDOR
ARG IMAGE_REGISTRY="ghcr.io"
ARG IMAGE_NAME_VARIANT
ARG TUNAOS_IMAGE_FLAVOR
ENV TUNAOS_IMAGE_FLAVOR=${TUNAOS_IMAGE_FLAVOR}
ARG ENABLE_SSHD="${ENABLE_SSHD:-0}"
ENV BASE_IMAGE=${BASE_IMAGE} \
IMAGE_NAME=${IMAGE_NAME} \
IMAGE_VENDOR=${IMAGE_VENDOR} \
IMAGE_REGISTRY=${IMAGE_REGISTRY} \
IMAGE_NAME_VARIANT=${IMAGE_NAME_VARIANT} \
ENABLE_SSHD=${ENABLE_SSHD}
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/90-image-info.sh
# The branding contract is not satisfied by os-release alone: three of its
# fourteen assertions are about files, and those files come from system_files,
# which this stage did not have. Running 90-image-info.sh here takes the
# failures from fourteen to three, all of them assets:
#
# FAIL: LOGO=tunaos but no matching file under /usr/share/pixmaps or hicolor
# FAIL: no TunaOS wallpaper under /usr/share/backgrounds (only upstream artwork)
# FAIL: plymouth default is not a TunaOS theme
# TUNAOS_BRANDING_FAIL variant=guppy failures=3
#
# (measured in a gentoo/stage3 container against this tree: os-release branded,
# system_files absent). They are /usr/share/pixmaps/tunaos.svg,
# /usr/share/backgrounds/tunaos, and the default.plymouth symlink into
# themes/tunaos — all three carried by these two copies and nothing else. The
# `desktop` stage below copies them a second time, deliberately: its /var wipe
# and portage relocation come between, and that stage's copy is what ends up in
# the shipped image.
COPY --from=context /files /
COPY --from=common /system_files/shared /
# Desktop-only portage config, applied AFTER the base @world (any global
# flag change at base makes @world --newuse schedule from-source multilib
# rebuilds that die on pam<->systemd / ncurses<->gpm build cycles):
#
# - --binpkg-respect-use=y: the binhost publishes MULTIPLE builds per
# package (systemd- and elogind-flavored, 64-only and multilib); the
# base stage's respect-use=n grabs whichever is newest, which for the
# desktop stack meant OpenRC/elogind multilib builds — dragging in
# elogind and 32-bit USE-dep chains the binhost can't close. With
# respect-use=y the resolver picks the builds that match OUR profile.
# - package.mask elogind: belt-and-braces — this is a systemd image.
# - pins: USE combos no published build has (plasma-meta needs
# kwin[lock]; gnome's chain wants ngtcp2[gnutls], zlib[minizip];
# gnome-shell[networkmanager] needs a matching gnome-control-center;
# xfce4-panel wants libdbusmenu[gtk3], and the global USE list carries
# `gtk` but no `gtk3`, so guppy:xfce died in the resolver with
# emerge: there are no ebuilds built with USE flags to satisfy
# ">=dev-libs/libdbusmenu-16.04.0[gtk3]"
# reached via thunar[trash-panel-plugin] -> xfce4-panel. Pinned here
# rather than adding gtk3 globally, which would reschedule from-source
# rebuilds across the whole @world set for one panel dependency).
# These few build from source.
RUN NPROC="$(nproc)" && \
printf 'kde-plasma/kwin lock\nkde-plasma/kwin-x11 lock\nnet-libs/ngtcp2 gnutls\nsys-libs/zlib minizip\ngnome-base/gnome-control-center networkmanager\ngnome-base/gnome-shell networkmanager\ndev-libs/libdbusmenu gtk3\n' \
>> /etc/portage/package.use/tunaos-desktops && \
mkdir -p /etc/portage/package.mask && \
echo "sys-auth/elogind" >> /etc/portage/package.mask/tunaos-systemd && \
echo "EMERGE_DEFAULT_OPTS=\"--jobs=${NPROC} --load-average=${NPROC} --getbinpkg --binpkg-respect-use=y\"" \
>> /etc/portage/make.conf
RUN --mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/install-desktop.sh ${DESKTOP_FLAVOR}
FROM desktop-build AS desktop
# Stage the kernel image into /usr/lib/modules, and move portage's database out
# of /var. Both must happen BEFORE the ostree layout below, which deletes /boot
# outright and wipes /var.
RUN KVER=$(ls /usr/lib/modules | head -1) && \
rm -f "/usr/lib/modules/$KVER/vmlinuz" && \
(cp /boot/vmlinuz-* "/usr/lib/modules/$KVER/vmlinuz" 2>/dev/null || cp /usr/src/linux-*/arch/x86/boot/bzImage "/usr/lib/modules/$KVER/vmlinuz") && \
rm -rf /var/cache && \
# Keep portage's package database; drop only its caches.
#
# /var/db/pkg is the record of what is installed, and deleting it here
# (plus the /var wipe below) is why ghcr.io/tuna-os/guppy:base answers
# `qlist -ICv` with nothing — qlist is installed, the database simply
# isn't there, so the running system cannot enumerate its own packages
# (tuna-os/tunaos-packages#135).
#
# Relocated into /usr for the reason Fedora moved the rpmdb there
# (Changes/RelocateRPMToUsr): the database describes /usr, so keeping it
# under /usr makes it snapshot and roll back with the image instead of
# being erased with /var. The tmpfiles link restores the default path.
mkdir -p /usr/lib/sysimage && \
if [ -d /var/db/pkg ]; then rm -rf /usr/lib/sysimage/portage; cp -a /var/db /usr/lib/sysimage/portage; fi && \
rm -rf /var/db
# Shared bootc/ostree layout (the /var wipe, alias symlinks, tmpfiles.d and
# prepare-root.conf) — build_scripts/bootc/ostree-layout.sh. What stays here is
# Gentoo-specific: pruning /dev, and linking portage's relocated database back
# into place after the wipe.
#
# This block used to be duplicated verbatim in the `system` and `desktop`
# stages, and its symlink list was missing /srv, /mnt and /usr/local — all
# three removed on the way in and never put back. The shared script restores
# them; see its header.
RUN --mount=type=bind,from=context,source=/,target=/run/context \
find /dev -mindepth 1 -maxdepth 1 ! -name pts ! -name shm ! -name mqueue -exec rm -rf {} + 2>/dev/null || true && \
/run/context/build_scripts/bootc/ostree-layout.sh && \
printf 'd /var/db 0755 root root -\nL+ /var/db/pkg - - - - ../../usr/lib/sysimage/portage/pkg\n' \
> /usr/lib/tmpfiles.d/portage-sysimage.conf && \
mkdir -p /var/db && ln -sfnT ../../usr/lib/sysimage/portage/pkg /var/db/pkg
# Build the initramfs.
#
# On build_scripts/bootc/dracut-config.sh, like every other bootc variant, and
# AFTER ostree-layout.sh above — that script writes prepare-root.conf, which is
# where dracut-config.sh reads whether this image is composefs and therefore
# whether the initramfs needs the erofs driver. Reversed, every step still
# exits 0 and the initramfs silently cannot mount its own root. Same ordering
# note as Containerfile.debian and Containerfile.arch carry.
#
# It was NOT on the shared script, and the comment here said why: it names the
# crypt and dm dracut modules, "which require cryptsetup and dmsetup binaries;
# Gentoo emerges neither", so requesting them would be fatal, and packaging
# sys-fs/cryptsetup and sys-fs/lvm2 was the stated prerequisite. That is no
# longer true, measured inside the published ghcr.io/tuna-os/guppy:gnome:
#
# /usr/sbin/cryptsetup present
# /usr/sbin/dmsetup present
# dracut modules 51bootc, 70crypt, 70dm all installed
#
# so every module the shared config asks for is satisfiable on this base today.
#
# What the gap cost: with no drop-in, dracut never added 51bootc, and `lsinitrd`
# on that same image's initramfs matched ZERO ostree/bootc files. Nothing then
# prepared the deployment at boot, so /sysroot stayed the PHYSICAL root and
# switch-root refused it --
#
# Failed to switch root: Specified switch root path '/sysroot' does not seem
# to be an OS tree. os-release file is missing.
#
# -- dropping guppy:xfce to an emergency shell with the disk already unlocked
# and /sysroot already mounted (LUKS run 31232170155). Every other variant was
# immune because they all run this script.
#
# The --omit on the command line stays: it covers this build, while the drop-in
# written near the top of the file is what reaches tacklebox's ISO-time rebuild
# and post-install kernel updates.
RUN --mount=type=bind,from=context,source=/,target=/run/context set -eu; \
KVER="$(ls /usr/lib/modules | head -1)"; \
IMG="/usr/lib/modules/${KVER}/initramfs.img"; \
/run/context/build_scripts/bootc/dracut-config.sh "$KVER"; \
dracut --force --no-hostonly --reproducible --zstd --omit "tpm2-tss pcsc" \
--kver "$KVER" "$IMG"; \
# Assert the module actually landed, the way Containerfile.opensuse does.
# A missing 51bootc costs nothing at build time and everything at boot, and
# this exact absence shipped undetected through a green image build, a green
# ISO build and a successful disk unlock before failing in switch-root.
#
# Only the two things measured absent are asserted. The crypt/dm payload is
# deliberately NOT pinned here: opensuse asserted `sbin/cryptsetup` on the
# same reasoning and failed a perfectly good initramfs (run 30735665323),
# because a systemd initrd ships systemd-cryptsetup instead. Assert what was
# measured, not what seems like it ought to be there.
lsinitrd "$IMG" > /tmp/initramfs.list; \
if ! grep -qx bootc /tmp/initramfs.list; then \
echo "ERROR: initramfs has no 'bootc' dracut module — nothing prepares the composefs deployment, so switch-root gets the physical root and drops to an emergency shell" >&2; \
exit 1; \
fi; \
if ! grep -qEi 'ostree|bootc' /tmp/initramfs.list; then \
echo "ERROR: initramfs contains no ostree/bootc files at all" >&2; \
exit 1; \
fi; \
rm -f /tmp/initramfs.list
LABEL containers.bootc=1 ostree.bootable=1
COPY --from=context /files /
COPY --from=common /system_files/shared /
# The identity args again, for THIS stage.
#
# The `system` stage already re-declares and promotes them, with a comment
# explaining that the global ARGs at the top of the file reach `FROM`
# interpolation and nothing else. But `system` is a SIBLING of this stage, not
# an ancestor: the chain here is base -> builder -> desktop-build -> desktop,
# and none of those three set them. ENV is inherited by child stages; the
# `system` block is on a branch this one never touches.
#
# So 90-image-info.sh below runs with IMAGE_NAME, IMAGE_VENDOR and BASE_IMAGE
# unset. It is `set -euo pipefail` and those three are the ones with no `:-`
# default (IMAGE_REGISTRY, SHA_HEAD_SHORT and DESKTOP_FLAVOR all have one), so
# it dies on its eighth line:
#
# IMAGE_REF="ostree-image-signed:docker://${IMAGE_REGISTRY:-ghcr.io}/${IMAGE_VENDOR}/${IMAGE_NAME}"
# /run/context/build_scripts/90-image-info.sh: line 8: IMAGE_VENDOR: unbound variable
#
# guppy:base never hit it — base-no-de branches off `system`. Only the desktop
# flavors reach here, and until the libdbusmenu USE pin two commits ago they
# died earlier, in the emerge. Same fix as the `system` stage, one branch over.
#
# Declared HERE rather than in desktop-build so SHA_HEAD_SHORT, which changes
# every commit, does not invalidate the emerge layers above it — the reason
# Containerfile.ubuntu declares it late too.
ARG BASE_IMAGE
ARG IMAGE_NAME
ARG IMAGE_VENDOR
ARG IMAGE_REGISTRY="ghcr.io"
ARG IMAGE_NAME_VARIANT
ARG SHA_HEAD_SHORT
ARG ENABLE_SSHD="${ENABLE_SSHD:-0}"
ARG DESKTOP_FLAVOR
ARG TUNAOS_IMAGE_FLAVOR
ENV TUNAOS_IMAGE_FLAVOR=${TUNAOS_IMAGE_FLAVOR}
ENV BASE_IMAGE=${BASE_IMAGE} \
IMAGE_NAME=${IMAGE_NAME} \
IMAGE_VENDOR=${IMAGE_VENDOR} \
IMAGE_REGISTRY=${IMAGE_REGISTRY} \
IMAGE_NAME_VARIANT=${IMAGE_NAME_VARIANT} \
SHA_HEAD_SHORT=${SHA_HEAD_SHORT} \
ENABLE_SSHD=${ENABLE_SSHD} \
DESKTOP_FLAVOR=${DESKTOP_FLAVOR}
# Run the standard numbered base scripts (matching el10/arch/debian/opensuse
# convention): 40-services (service enablement, dconf), 90-image-info
# (branding/os-release), 99-cleanup (ostree prep).
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/40-services.sh
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/90-image-info.sh
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/99-cleanup.sh
# remora — the desktop experience contract requires it, and this base does
# not run 26-packages-post.sh, which is what installs remora on the dnf
# variants. Containerfile.opensuse already hit this and calls
# install-remora.sh directly for the same reason (see the comment there).
# Without it every Gate on this base fails with
#
# TUNAOS_DESKTOP_CONTRACT_FAIL reason=remora_contract_missing
#
# which reads as a broken desktop when the desktop booted fine — grouper gnome,
# kde, xfce and gnome-zfs all died this way in run 30692866756. The binary is
# static Go, so there is nothing distro-specific to guard.
RUN --mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/install-remora.sh
RUN bootc container lint
# Per-flavor target aliases so the build's `--target=<flavor>` resolves. Each
# resolves to the desktop stage above, specialized by the DESKTOP_FLAVOR
# build-arg (target and build-arg always agree — see resolve-flavor.sh).
FROM desktop AS gnome
COPY --from=common /system_files/bluefin /
RUN --mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/install-countme.sh
FROM desktop AS kde
# xfce is declared build_image: true for guppy in build-config.yml and
# manifests/desktops/xfce.yaml has an emerge section, but this alias was
# missing — `The target "xfce" was not found in the provided Dockerfile`
# (#1012). niri and cosmic are deliberately NOT added: they have no ebuilds in
# the Gentoo main tree (#923), so an alias would only move their failure from
# "no target" to "no package".
FROM desktop AS xfce