Repository navigation
Expand file tree
/
Copy pathContainerfile.ubuntu
More file actions
582 lines (545 loc) · 30.3 KB
/
Copy pathContainerfile.ubuntu
File metadata and controls
582 lines (545 loc) · 30.3 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
# Containerfile.ubuntu — TunaOS grouper (Ubuntu 26.04 "Resolute Raccoon")
#
# RFC 010. Builds directly from stock docker.io/library/ubuntu:resolute and
# applies all TunaOS customizations while apt is fully intact, then bootcifies
# as the final per-variant step.
#
# Key design (mirrors bootc-shindig/ubuntu-bootc-remix):
# * No pre-stripped bootc base image, no apt bootstrap hacks — apt just works.
# * bootc is installed from the bootc-shindig/bootc-deb apt repo (it is not in
# the Ubuntu archive); it pulls dracut + ostree + libostree-dev.
# * The composefs-backed bootc layout is applied LAST via finalize.sh →
# mount-system.sh, which wipes /var. apt is unusable after that point, so
# every package install must happen before it.
# * Sysroot backend is composefs (read-only) — no ostree repo. See
# build_scripts/bootc/sandbox/usr/lib/ostree/prepare-root.conf.
#
# Skips (RPM/Bluefin-specific): RHSM, COPR, dnf versionlock, RPM Fusion, EPEL.
ARG BASE_IMAGE="docker.io/library/ubuntu:resolute@sha256:6a06111c3b0a8600feadb270aec771dc239bf407b25625f93f44ab00e11f4d3c"
ARG ENABLE_HWE="${ENABLE_HWE:-0}"
ARG ENABLE_NVIDIA="${ENABLE_NVIDIA:-0}"
ARG DESKTOP_FLAVOR="${DESKTOP_FLAVOR:-gnome}"
ARG TUNAOS_IMAGE_FLAVOR
ARG COMMON_IMAGE_REF="ghcr.io/projectbluefin/common:unpinned-must-override"
FROM ${COMMON_IMAGE_REF} AS common
FROM ${BASE_IMAGE} AS bootc-builder
ARG ORAS_VERSION=1.2.0
ARG ORAS_SHA256_AMD64=5b3f1cbb86d869eee68120b9b45b9be983f3738442f87ee5f06b00edd0bab336
ARG ORAS_SHA256_ARM64=27df680a39fc2fcedc549cb737891623bc696c9a92a03fd341e9356a35836bae
ARG TARGETARCH
ARG BOOTC_VERSION
RUN apt-get update -y && \
apt-get install -y --no-install-recommends curl ca-certificates tar gzip && \
case "${TARGETARCH}" in \
amd64) ORAS_SHA256="${ORAS_SHA256_AMD64}" ;; \
arm64) ORAS_SHA256="${ORAS_SHA256_ARM64}" ;; \
*) echo "Unsupported ORAS architecture: ${TARGETARCH}" >&2; exit 1 ;; \
esac && \
curl -fsSL "https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_${TARGETARCH}.tar.gz" -o /tmp/oras.tar.gz && \
echo "${ORAS_SHA256} /tmp/oras.tar.gz" | sha256sum -c - && \
tar -xzf /tmp/oras.tar.gz -C /usr/local/bin oras && \
rm -f /tmp/oras.tar.gz && \
mkdir -p /fetch /output && cd /fetch && \
oras pull "ghcr.io/tuna-os/bootc-toolchain-sid:${BOOTC_VERSION}-${TARGETARCH}" && \
tar -xzf toolchain.tar.gz -C /output && \
rm -rf /fetch
# The sid toolchain ships bootc but no libostree, because sid packages one new
# enough (build-toolchain.yml gives it build_ostree: "0"). That assumption holds
# on resolute and fails on noble, whose 2024.5 lacks the LIBOSTREE_2025.* symbol
# versions bootc is linked against — gurnard:pantheon got all the way to
# `bootc install to-filesystem` inside the VM before finding out.
#
# So probe it: run the bootc we are about to ship against this base's packaged
# libostree and, ONLY when it cannot load, build an ABI-matched one from the
# pinned source into the toolchain tree. A no-op on resolute, and on any future
# base whose archive catches up. Deciding this behaviourally rather than from a
# release-name case is what keeps the two releases from needing a table here.
#
# COPY, not the usual context bind mount: a bind mount's content is not part of
# the layer's cache key (see the BUILD_SCRIPTS_HASH comment below), so editing
# the script or moving the ostree pin would silently reuse a stale toolchain
# tree. This stage is discarded, so the extra layer costs nothing.
COPY build_scripts/bootc/build-libostree.sh image-versions.yaml /tmp/bootc/
RUN VERSIONS_FILE=/tmp/bootc/image-versions.yaml /tmp/bootc/build-libostree.sh
# Context layer. There is no way to share a stage between Containerfiles, so
# the four core COPYs below are duplicated in all six — and duplication that
# cannot be factored out is duplication that has to be ASSERTED instead. Two
# entries had already gone missing from this one:
#
# manifests the desktop stages above pantheon each call their own
# build_scripts/desktop script and never read a manifest, so this
# stage never carried them. pantheon is manifest-only, so
# install-desktop.sh looked for
# /run/context/manifests/desktops/pantheon.yaml in a directory
# that did not exist and gurnard died with "No manifest found"
# (#1014).
# overrides still missing until now, and latent rather than live: the only
# thing under system_files_overrides/niri is an SELinux config,
# which Ubuntu has no use for, and the nvidia/hwe overrides are
# applied from Containerfile.overlay's own context stage. The
# next override added for a desktop this file builds would have
# been silently skipped — copy_systemfiles_for() returns 0 with
# "No system file overrides" when the directory is absent.
#
# tests/bats/test_containerfile_context.bats asserts all six agree.
FROM scratch AS context
COPY system_files /files
COPY system_files_overrides /overrides
COPY build_scripts /build_scripts
COPY manifests /manifests
COPY tunaos.toml /tunaos.toml
# Only Tuna's curated files: install-desktop.sh applies experiences/<desktop>
# whenever it is present, and the other desktops' trees are not part of the
# apt images today.
COPY experiences/tuna /experiences/tuna
# Per-file, and correctly so: only the Containerfiles whose build scripts read
# it carry image-versions.yaml (el10, debian, ubuntu run 10-base-packages.sh;
# kcm-ublue.sh guards on dnf, so the pacman/zypper/emerge files never reach
# its read). Adding it everywhere for symmetry would be cargo cult.
COPY image-versions.yaml /image-versions.yaml
COPY --from=common /system_files/shared /files
# ==============================================================================
# Base stage (no DE) — shared layer, built on stock Ubuntu with apt intact
# ==============================================================================
FROM ${BASE_IMAGE} AS base-no-de
ARG IMAGE_NAME_VARIANT
ENV IMAGE_NAME_VARIANT=${IMAGE_NAME_VARIANT}
ARG BASE_IMAGE
ARG ENABLE_HWE
ARG ENABLE_NVIDIA
ARG DESKTOP_FLAVOR
ARG TUNAOS_IMAGE_FLAVOR
ENV TUNAOS_IMAGE_FLAVOR=${TUNAOS_IMAGE_FLAVOR}
ARG IMAGE_NAME
ARG IMAGE_VENDOR
ARG IMAGE_REGISTRY="ghcr.io"
ARG TUNAOS_PUBLISHED_IMAGE_REF
ARG ENABLE_SSHD="${ENABLE_SSHD:-0}"
ENV BASE_IMAGE=${BASE_IMAGE} \
IMAGE_NAME=${IMAGE_NAME} \
IMAGE_VENDOR=${IMAGE_VENDOR} \
IMAGE_REGISTRY=${IMAGE_REGISTRY} \
ENABLE_HWE=${ENABLE_HWE} \
TUNAOS_PUBLISHED_IMAGE_REF=${TUNAOS_PUBLISHED_IMAGE_REF} \
ENABLE_NVIDIA=${ENABLE_NVIDIA} \
DESKTOP_FLAVOR=${DESKTOP_FLAVOR} \
ENABLE_SSHD=${ENABLE_SSHD} \
DEBIAN_FRONTEND=noninteractive
# Every build_scripts/*.sh RUN below uses
# `--mount=type=bind,from=context,source=/,target=/run/context` to invoke
# scripts without a COPY layer — but that bind mount's content is NOT part
# of buildah's cache-key hashing, so editing a script alone never
# invalidates the cached layer that ran it (confirmed: a runner reused a
# pre-fix cached layer verbatim after the script changed, silently
# shipping the old package list — grouper missed a fdisk fix this way).
# An ARG/ENV set here changes the cache key for every RUN after it, so
# hashing build_scripts/ content into it forces correct invalidation.
ARG BUILD_SCRIPTS_HASH
ENV BUILD_SCRIPTS_HASH=${BUILD_SCRIPTS_HASH}
# Enable universe + multiverse (resolute uses deb822 /etc/apt/sources.list.d/
# ubuntu.sources; fall back to the classic sources.list if present).
RUN set -eux; \
if [ -f /etc/apt/sources.list.d/ubuntu.sources ]; then \
sed -i 's/^Components: .*/Components: main restricted universe multiverse/' \
/etc/apt/sources.list.d/ubuntu.sources; \
fi; \
if [ -f /etc/apt/sources.list ]; then \
sed -i 's/ main$/ main restricted universe multiverse/' /etc/apt/sources.list; \
fi; \
apt-get update -y
# Install the bootc toolchain (bootc + bootupd, plus a source libostree on bases
# whose packaged one cannot load bootc) from the builder stage
COPY --from=bootc-builder /output /
# libostree. The bootc COPYed above is linked against LIBOSTREE_2025.2 /
# LIBOSTREE_2025.3, and whether this base can satisfy that is a per-release
# accident — read off Launchpad, not assumed:
#
# noble (24.04, gurnard) ostree 2024.5-1build2 -> too old
# resolute (26.04, grouper) ostree 2025.7-3build1 -> fine
#
# So resolute installs the archive library and noble must not: apt would put
# 2024.5 on the same path the source-built one occupies, and bootc would be
# right back to "version LIBOSTREE_2025.2 not found".
#
# Containerfile.debian keeps the archive out on trixie with an equivs dummy, and
# the trap worth writing down is that the dummy is only half of that pattern:
# debian's toolchain is built with BUILD_OSTREE=1 and SHIPS a libostree for
# exactly this case. Copying the dummy alone into a file whose toolchain carries
# none replaces "wrong version" with nothing at all —
#
# bootc: error while loading shared libraries: libostree-1.so.1:
# cannot open shared object file: No such file or directory
#
# — which is what gurnard did when that was tried. The dummy is therefore
# conditioned on the source library being present, not on the release name: the
# bootc-builder stage above builds one when and only when the packaged lib
# cannot load bootc, so `ls /usr/lib/*/libostree-1.so.1` is the honest test for
# which of the two situations we are in.
#
# Publishing a bootc-toolchain-noble with build_ostree: "1" and selecting the
# toolchain by base (as Containerfile.debian does for trixie) would be the
# tidier long-term arrangement — it would move this build off the image's
# critical path — but it is not required for correctness, and building against
# the base we actually ship on is what makes the ABI match by construction.
#
# One thing to know before attempting that: toolchain/Containerfile.debian does
# not build on noble as written. Two of its build deps are absent there, and
# both are redundant rather than renamed-in-place, so dropping them keeps one
# list for every base instead of a case whose arms drift:
#
# libarchive13 the runtime lib; libarchive-dev already depends on it, and
# noble's time_t64 transition renamed it libarchive13t64.
# e2fslibs-dev transitional, depends on libext2fs-dev, which is listed.
#
# Every other name in that list is published for noble, trixie and sid —
# checked against the archives, not assumed.
RUN set -eux; \
apt-get update -y; \
export DEBIAN_FRONTEND=noninteractive; \
apt-get install -y --no-install-recommends curl ca-certificates; \
if ls /usr/lib/*/libostree-1.so.1 >/dev/null 2>&1; then \
apt-get install -y --no-install-recommends \
equivs libglib2.0-0t64 libgpgme11t64 libarchive13t64 libfuse3-3; \
printf 'Package: libostree-1-1\nVersion: 99:0\nArchitecture: %s\nMaintainer: TunaOS <build@tunaos.org>\nDescription: satisfied by the source libostree from bootc-toolchain\n' \
"$(dpkg --print-architecture)" > /tmp/libostree.ctl; \
( cd /tmp && equivs-build libostree.ctl && dpkg -i libostree-1-1_*.deb ); \
apt-mark hold libostree-1-1; \
ldconfig; \
else \
apt-get install -y --no-install-recommends ostree libostree-1-1; \
fi; \
# Both paths must end with a loadable bootc and ostree. LD_BIND_NOW binds
# every symbol now, so an ABI mismatch fails the build here — a three-minute
# error naming the cause — instead of surviving to `bootc install` in a QEMU
# guest forty minutes later, where fisherman reports it as the uninformative
# "bootc install to-filesystem (via container): exit status 1".
ostree --version; \
LD_BIND_NOW=1 bootc --version
# Install bootc dependencies and the bootc runtime tooling needed for
# `bootc container lint`, plus the static composefs/bootc overlay. apt intact.
#
# The tpm2/tss2 packages are here because dracut's tpm2-tss module is part of
# Ubuntu's default module set, and dracut treats a requested-but-unsatisfiable
# module as fatal rather than skipping it:
#
# dracut[E]: Module 'tpm2-tss' cannot be installed.
#
# That killed every grouper build that reached finalize.sh. Installing the TPM2
# userspace is the right resolution rather than omitting the module — LUKS
# auto-unlock is enrolled post-install (`ujust enable-luks-tpm2`), so the
# initramfs has to be able to talk to the TPM when that happens.
#
# The t64 suffixes are Ubuntu's 64-bit time_t transition; the unsuffixed names
# do not exist in resolute. All five were verified against the base image.
#
# network-manager: the Ubuntu base ships NO network manager at all, and
# nothing enabled one on the apt path — so grouper and gurnard booted their
# live ISO with a NIC and no address. That presents as an SSH failure, not a
# network one: sshd starts, host keys generate, and QEMU's user networking
# accepts the host-side connect while nothing answers inside, so the run
# reports "Connection timed out during banner exchange" (grouper:gnome, LUKS
# run 31061486055). Containerfile.debian and .arch each install one and pass;
# el10's base ships one; Ubuntu's does not, and the difference was never
# deliberate.
#
# Installing it is not enough on Ubuntu: network-manager ships
# 10-globally-managed-devices.conf (unmanaged-devices=*,except:type:wifi,...),
# so NM leaves the wired NIC alone unless netplan hands it every device. Ubuntu
# Desktop does that with ubuntu-settings' /usr/lib/netplan/00-network-manager-
# all.yaml (renderer: NetworkManager), which grouper:gnome gets through
# ubuntu-desktop-minimal. grouper:tuna does not install it, booted with no
# address, and `corral ssh` answered "no route to host". The same file, at the
# same path and mode 0600 (netplan warns about readable configs), is written
# below for every flavor; ubuntu-settings replaces it with identical bytes
# where it is installed.
#
# cryptsetup: the LUKS install path needs it on the installing host.
#
# sudo: the Ubuntu bootc base ships none, unlike the Fedora/EL ones.
# customize-live.sh writes a NOPASSWD sudoers drop-in for liveuser on dev
# ISOs, which authorises nothing without the binary — every `sudo` in the E2E
# guest then dies with "command not found", killing the run at
# `sudo podman load`. That is tunaOS#953 defect 3, fixed on sailfin by naming
# sudo in Containerfile.opensuse and never carried to the apt bases, so it
# recurred on grouper. customize-live.sh now asserts it at ISO-build time.
#
# software-properties-common is deliberately NOT here. It was added for
# add-apt-repository, which no manifest-declared PPA uses any more: it resolves
# the suite through python-apt's os-release lookup, and TunaOS branding puts a
# fish codename in VERSION_CODENAME, so it cannot resolve a suite on any image
# this Containerfile produces (tunaOS#1014). install-desktop.sh writes the
# deb822 source itself from UBUNTU_CODENAME; nothing else in the tree calls
# add-apt-repository.
#
# systemd-cryptenroll, which fisherman requires to enrol the TPM2 keyslot, is
# handled separately below because WHICH package ships it depends on the
# release, and this Containerfile serves two:
#
# noble (24.04, gurnard) -> systemd (no systemd-cryptsetup)
# resolute (26.04, grouper) -> systemd-cryptsetup (systemd no longer has it)
#
# Naming either one unconditionally breaks the other release.
RUN --mount=type=bind,from=context,source=/,target=/run/context \
apt-get update -y && \
apt-get install -y --no-install-recommends \
ca-certificates curl gpg \
dracut \
btrfs-progs dosfstools e2fsprogs xfsprogs skopeo jq \
sudo \
systemd-boot systemd-boot-efi dmsetup \
cryptsetup \
network-manager \
tpm2-tools libtss2-tcti-device0t64 libtss2-tctildr0t64 \
libtss2-rc0t64 libtss2-esys-3.0.2-0t64 && \
# Install the split-out package only on releases that have it, then assert
# the binary is present either way. Without the assertion a rename lands as
# a fisherman abort 40 minutes later inside a VM ("missing required host
# tool: systemd-cryptenroll", flounder:kde LUKS run 31060069809); with it,
# the build fails here naming exactly what is missing.
if apt-cache show systemd-cryptsetup 2>/dev/null | grep -q '^Package:'; then \
apt-get install -y --no-install-recommends systemd-cryptsetup; \
fi && \
if ! command -v systemd-cryptenroll >/dev/null 2>&1; then \
echo "ERROR: systemd-cryptenroll missing after install." >&2; \
echo " fisherman needs it to enrol the LUKS TPM2 keyslot and aborts without it." >&2; \
echo " It ships in 'systemd' on noble and 'systemd-cryptsetup' on resolute;" >&2; \
echo " check which package provides it on this release and add it above." >&2; \
exit 1; \
fi && \
install -D -m 0600 /dev/null /usr/lib/netplan/00-network-manager-all.yaml && \
printf 'network:\n version: 2\n renderer: NetworkManager\n' \
> /usr/lib/netplan/00-network-manager-all.yaml && \
# pcsc (smart card daemon) dracut module is part of Ubuntu's default
# module set but the pcscd package isn't needed for the initramfs.
echo 'omit_dracutmodules+=" pcsc "' > /etc/dracut.conf.d/omit-pcsc.conf && \
cp -a /run/context/build_scripts/bootc/sandbox/. / && \
apt-get clean -y && rm -rf /var/lib/apt/lists/*
# Stub kernel/grub post-install hooks so package installs that pull a kernel do
# not try to build an initramfs or grub config — finalize.sh builds the
# initramfs explicitly with dracut.
RUN printf '#!/bin/sh\nexit 0\n' | tee \
/usr/sbin/update-initramfs /usr/sbin/mkinitramfs \
/usr/sbin/update-grub /usr/sbin/grub-mkconfig > /dev/null && \
chmod +x /usr/sbin/update-initramfs /usr/sbin/mkinitramfs \
/usr/sbin/update-grub /usr/sbin/grub-mkconfig && \
mkdir -p /etc/kernel/postinst.d && \
printf '#!/bin/sh\nexit 0\n' > /etc/kernel/postinst.d/kdump-tools && \
chmod +x /etc/kernel/postinst.d/kdump-tools
# Install the kernel + firmware and stage vmlinuz where bootc expects it.
# ENABLE_ASAHI=1 (arm64 only) swaps in the UbuntuAsahi PPA 16K-page Apple
# Silicon kernel + platform userspace instead of linux-generic — see
# build_scripts/ubuntu-kernel.sh.
#
# The generic path is not "install linux-generic": these images are
# composefs-native and bootc mounts the composefs image from an open file
# (CONFIG_EROFS_FS_BACKED_BY_FILE, Linux 6.12+), which noble's GA 6.8 kernel
# cannot do and its HWE kernel can. ubuntu-kernel.sh reads the config of the
# kernel it is about to install and picks accordingly — the same "ask, don't
# assume the release" shape as the libostree build above.
ARG ENABLE_ASAHI="${ENABLE_ASAHI:-0}"
ENV ENABLE_ASAHI=${ENABLE_ASAHI}
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/ubuntu-kernel.sh
# Ensure /opt is a real directory for the build scripts (finalize.sh turns it
# into a bind-mount target later).
RUN rm -rf /opt && mkdir /opt
# Copy system files and apply TunaOS customizations.
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/00-copy-files.sh
# Workarounds (IS_* flags guard all RPM-specific blocks; false on Ubuntu).
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/01-workarounds.sh
# Base packages WITHOUT DE (no RHSM secret mount — Ubuntu has no subscriptions).
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
bash -c "source /run/context/build_scripts/lib.sh && source /run/context/build_scripts/10-base-packages.sh"
# Desktop-agnostic additional packages (apt branch in the script), then the
# image-wide additions from tunaos.toml.
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/20-packages.sh && \
/run/context/build_scripts/30-image-packages.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
# Enable systemd units (safe_enable/safe_disable).
RUN --mount=type=tmpfs,dst=/opt --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
# Add the Flathub remote (flatpak comes from 10-base-packages.sh).
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
flatpak remote-add --if-not-exists --system flathub https://dl.flathub.org/repo/flathub.flatpakrepo
# SHA_HEAD_SHORT changes on every commit — declared here (not in the early
# ENV block) so per-commit values only rebuild from this layer down and the
# expensive apt layers above stay cache-hittable. See Containerfile.
ARG SHA_HEAD_SHORT
ENV SHA_HEAD_SHORT=${SHA_HEAD_SHORT}
RUN --mount=type=tmpfs,dst=/opt --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=/opt --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
# The signature policy that arrived with projectbluefin/common's shared tree
# pins sigstore requirements using `keyPaths`, a field skopeo 1.14 introduced.
# Ubuntu noble ships 1.13.3, which rejects the whole file — and a policy that
# does not parse makes EVERY skopeo operation fail, including the one bootc
# install uses. gurnard:pantheon died there. Validate against this image's own
# skopeo rather than assuming the base's is new enough.
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/containers-policy.sh
# https://bootc-dev.github.io/bootc/bootc-images.html#standard-metadata-for-bootc-compatible-images
LABEL containers.bootc=1
# ==============================================================================
# Desktop variant stages — install DE (apt intact), then bootcify + lint LAST
# ==============================================================================
FROM base-no-de AS base
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh
# GNOME goes through the manifest installer. gnome.sh's apt branch and
# manifests/desktops/gnome.yaml's packages.apt held the SAME sixteen names, and
# the script said so itself — "Ubuntu builds take this script path, not the
# manifest, so both must list it" — which is the duplication docs/VISION.md exists to
# retire. gnome-session, ubuntu-session and libpam-gnome-keyring were the three
# the manifest was missing; they are in it now, so the two lists are identical
# and there is only one.
FROM base-no-de AS gnome
COPY --from=common /system_files/bluefin /
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/install-desktop.sh gnome && \
/run/context/build_scripts/desktop/configure-desktop-runtime.sh gnome
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh
# KDE goes through the manifest installer. kde.sh's apt branch was a verbatim
# copy of manifests/desktops/kde.yaml's `packages.apt` list plus
# `systemctl enable sddm`, which is what `display_manager: sddm` already means
# — a second copy of the same nine package names, kept in sync by hand. The
# manifest path additionally applies disable_desktop_files (Discover, replaced
# by Bazaar/Flatpak) and the post_install hooks, both of which the script's
# apt branch exited before ever reaching. kcm-ublue.sh self-skips without dnf.
FROM base-no-de AS kde
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/install-desktop.sh kde && \
/run/context/build_scripts/desktop/configure-desktop-runtime.sh kde
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh
# COSMIC goes through the manifest-driven installer, not desktop/cosmic.sh.
# cosmic.sh has no apt branch — gnome.sh, kde.sh, xfce.sh and niri.sh all open
# with `if [[ "$PKG_MGR" == "apt" ]]`, cosmic.sh goes straight to
# `dnf -y copr enable`, so this stage died at `dnf: command not found` on every
# grouper:cosmic build (LUKS run 31134394662, and the flavour has never
# published). Every other Containerfile already calls install-desktop.sh for
# cosmic; only this one still reached for the per-DE script.
#
# The manifest path is also the only one that can install COSMIC on Ubuntu at
# all: it comes solely from ppa:hepp3n/cosmic-epoch, and `packages.apt.ppa`
# with `condition: ubuntu` is handled by install-desktop.sh (_td_add_ppa) and
# nowhere else. That is the same reason the pantheon stage below uses it.
FROM base-no-de AS cosmic
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/install-desktop.sh cosmic && \
/run/context/build_scripts/desktop/configure-desktop-runtime.sh cosmic
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh
FROM base-no-de AS niri
# Zirconium's curated DMS/Niri integration is session-specific. Keep its
# config, policy, and greeter files out of every other desktop image.
# Sourced from the zirconium-dev/zirconium source tarball (mkosi.extra)
# via install-zirconium.sh — no OCI stage, pinned to the commit in
# image-versions.yaml (downloads.zirconium_src) rather than a branch tip.
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/install-zirconium.sh
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/niri.sh base && \
/run/context/build_scripts/desktop/configure-desktop-runtime.sh niri
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh
FROM base-no-de AS xfce
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/xfce.sh base && \
/run/context/build_scripts/desktop/configure-desktop-runtime.sh xfce
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh
# Pantheon (gurnard, Ubuntu noble). Unlike the stages above there is no
# desktop/pantheon.sh — Pantheon is manifest-only, so this goes through the
# generic manifest-driven installer, which is also the only path that handles
# the `packages.apt.ppa` block the manifest needs (Pantheon is not in the
# Ubuntu archive at all; it comes exclusively from ppa:elementary-os/stable).
# gurnard resolved to Containerfile.el10 until #1014, so this stage had never
# been reachable to notice it was missing.
FROM base-no-de AS pantheon
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/install-desktop.sh pantheon && \
/run/context/build_scripts/desktop/configure-desktop-runtime.sh pantheon
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh
# GNOME + ZFS-root support. zfs.sh installs the prebuilt zfs.ko, the userspace
# tools and the dracut 90zfs module BEFORE finalize.sh builds the initramfs and
# bootcifies (apt is unusable afterwards). Enables `bootc install to-filesystem`
# onto a ZFS root — bootc has no `--filesystem zfs`. See tuna-os/tunaOS#625.
FROM base-no-de AS gnome-zfs
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/install-desktop.sh gnome
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/zfs.sh
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh
# Tuna Desktop (tuna-os/tuna-desktop ADR 0008, epic #577; tunaOS#3205).
# tuna-desktop comes from the signed Tideforge apt index for Ubuntu, declared
# in manifests/desktops/tuna.yaml; everything else is Ubuntu's.
FROM base-no-de AS tuna
RUN --mount=type=tmpfs,dst=/opt --mount=type=tmpfs,dst=/tmp \
--mount=type=tmpfs,dst=/boot \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/install-desktop.sh tuna
RUN --mount=type=tmpfs,dst=/tmp \
--mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/finalize.sh