Repository navigation
Expand file tree
/
Copy pathContainerfile.opensuse
More file actions
397 lines (378 loc) · 21.7 KB
/
Copy pathContainerfile.opensuse
File metadata and controls
397 lines (378 loc) · 21.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
# Containerfile.opensuse — openSUSE Tumbleweed on bootc.
# Based on bootcrew/mono but adapted for TunaOS manifest-driven desktop installs.
#
# ARG BASE_IMAGE = registry.opensuse.org/opensuse/tumbleweed:latest
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
# 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
# Only Tuna's curated files: install-desktop.sh applies experiences/<desktop>
# whenever it is present, and the other desktops' trees are not part of this
# image today.
COPY experiences/tuna /experiences/tuna
FROM ${COMMON_IMAGE_REF} AS common
FROM ${BASE_IMAGE} AS base
FROM base 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: they are visible to `FROM`
# interpolation and nothing else. A build stage sees an ARG only if it declares
# it, and an ARG is never automatically an ENV. So when 40-services /
# 90-image-info / 99-cleanup were wired into this stage, they ran with
# IMAGE_VENDOR unset and died under `set -u`:
#
# /run/context/build_scripts/90-image-info.sh: line 8: IMAGE_VENDOR: unbound variable
#
# That took out every sailfin cell, not just the cosmic one that surfaced it.
# Containerfile.ubuntu:53-66 and Containerfile.arch already do this; opensuse
# and gentoo declared the args globally and stopped there.
#
# IMAGE_NAME_VARIANT specifically: 90-image-info.sh keys its codename table on
# the canonical variant id (`sailfin`), and openSUSE 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 `opensuse`. 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}
# Install bootc from the openSUSE Virtualization:containers OBS project rather
# than building it from git. The source build linked a libssl soname the
# runtime image doesn't ship (libssl.so.60 — see #643); the packaged bootc has
# correct runtime deps and bundles the 51bootc dracut module. Also faster (no
# cargo compile). Verified: bootc 1.15.2 install/lint work on Tumbleweed.
#
# `sudo` is listed explicitly: unlike the Fedora/EL bases, the openSUSE
# Tumbleweed base (and the enhanced_base pattern) ships none, so the live-ISO
# NOPASSWD sudoers drop-in that customize-live.sh writes had nothing to
# authorise — every `sudo` iso-e2e.sh runs in the sailfin guest failed with
# "bash: line 1: sudo: command not found", killing the LUKS E2E at
# `sudo podman load` (defect 3 of tunaOS#953).
#
# `cryptsetup` and `device-mapper` are listed for the INITRAMFS, not for the
# running system: dracut's `dm` module is `require_binaries dmsetup` and its
# `crypt` module is `require_binaries cryptsetup` + a dependency on `dm`. The
# base ships neither, so the dracut run below silently dropped all LUKS
# support —
# dracut[E]: Module 'crypt' depends on module 'dm', which can't be installed
# dracut[E]: Module 'systemd-cryptsetup' depends on module 'crypt', ...
# — and the installed encrypted disk had no way to unlock: the initrd ignored
# fisherman's rd.luks.name karg, never prompted for a passphrase, and sat in
# dracut-initqueue waiting for a root UUID that only appears after the LUKS
# volume opens, until it dropped to the emergency shell (defect 8 of
# tunaOS#953, run 30733919804). The GNOME pattern pulls cryptsetup in later,
# but the initramfs is built here, and no other flavor's package set does.
#
# ffmpeg-8, NOT the unversioned `ffmpeg` name (tunaOS#1832, nightly
# 32091072257): Tumbleweed flipped `ffmpeg` to the 9.x stream, whose openSUSE
# build compiles the h264/hevc/vc1 decoders out — and Packman publishes no
# 9.x yet (measured against the live Essentials index 2026-08-18: newest CLI
# is ffmpeg-8 8.1.2, newest soname libavcodec62). Installing the unversioned
# name therefore hands /usr/bin/ffmpeg to a binary that cannot decode h264 no
# matter what the Packman dup below does to the libraries. ffmpeg-8 resolves
# from packman-essentials directly (priority 90 beats the distro's 99 at
# equal versions), keeps the CLI on the stream Packman actually ships full
# codecs for, and install-desktop.sh's vendor check asserts the ownership.
# Move back to the unversioned name when Packman publishes the 9.x stream.
RUN set -eu; \
retry_zypper() { \
attempt=1; \
until zypper "$@"; do \
if [ "$attempt" -ge 4 ]; then \
echo "zypper failed after ${attempt} attempts: zypper $*" >&2; \
return 1; \
fi; \
echo "zypper attempt ${attempt} failed; retrying in $((attempt * 5))s..." >&2; \
sleep "$((attempt * 5))"; \
attempt=$((attempt + 1)); \
done; \
}; \
retry_zypper --non-interactive addrepo -fG \
"https://download.opensuse.org/repositories/Virtualization:/containers/openSUSE_Tumbleweed/" \
virtualization-containers && \
retry_zypper --non-interactive --gpg-auto-import-keys refresh && \
retry_zypper install -y \
bootc \
btrfs-progs ca-certificates ca-certificates-mozilla cpio cryptsetup curl device-mapper dosfstools dracut e2fsprogs flatpak glib2 \
kernel-default kernel-firmware-all openssh ostree skopeo sudo \
systemd systemd-boot udev xfsprogs zstd chrony && \
update-ca-certificates && \
retry_zypper --non-interactive removerepo virtualization-containers && \
retry_zypper install -y -t pattern enhanced_base && \
retry_zypper --non-interactive addrepo --check --refresh --priority 90 --name packman-essentials https://ftp.gwdg.de/pub/linux/misc/packman/suse/openSUSE_Tumbleweed/Essentials packman-essentials && \
retry_zypper --non-interactive --gpg-auto-import-keys refresh && \
retry_zypper --non-interactive dup --from packman-essentials --allow-vendor-change && \
retry_zypper --non-interactive install -y \
ffmpeg-8 \
gstreamer-plugins-good \
gstreamer-plugins-bad \
gstreamer-plugins-ugly \
gstreamer-plugins-libav \
libavcodec-full \
vlc-codecs \
glow \
gum \
just \
wl-clipboard \
tailscale && \
systemctl enable sshd && \
systemctl mask systemd-firstboot.service
# remora. This stage runs NONE of the numbered build_scripts (10-base-packages,
# 26-packages-post, 40-services...) — 26-packages-post.sh, which installs
# remora on the dnf variants, finishes by rebuilding the initramfs using
# Fedora kernel naming (`rpm -qa | grep -P 'kernel-...'` finds no
# kernel-default on openSUSE) and would clobber the composefs/bootc initramfs
# this file builds and verifies below. So sailfin shipped without remora, and
# every sailfin Gate failed on reason=remora_not_found — flagging a desktop
# that had actually booted fine as broken. Install just remora, from the
# script both paths now share.
RUN --mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/install-remora.sh
RUN echo "uninitialized" > /etc/machine-id
RUN mkdir -p /usr/lib/systemd/system/sshd.service.d && \
printf '[Unit]\nBefore=ssh-access.target\nWants=ssh-access.target\n' \
> /usr/lib/systemd/system/sshd.service.d/10-ssh-access-target.conf
RUN printf '[Match]\nType=ether\n\n[Network]\nDHCP=yes\n' \
> /usr/lib/systemd/network/20-wired.network
RUN printf 'L! /etc/resolv.conf - - - - /run/systemd/resolve/stub-resolv.conf\n' \
> /usr/lib/tmpfiles.d/resolv-conf.conf
# Opt the 51bootc dracut module in BEFORE building the initramfs. Its check()
# returns 255 ("never installed by default; see 10-bootc-base.conf for how base
# images can opt in"), so without this the initramfs ships no
# bootc-root-setup.service — and nothing in it handles the composefs= karg that
# a composefs-native deployment boots with.
#
# ostree-prepare-root.service cannot cover for it: that unit is
# ConditionKernelCommandLine=ostree, and composefs deployments have composefs=
# with no ostree= karg, so it is silently skipped. The result was /sysroot
# mounted but never prepared → initrd-switch-root fails → dracut emergency
# shell, which is what made every sailfin desktop Gate time out waiting for a
# graphical session.
#
# omit_dracutmodules is declared here too, not just on the dracut command line
# below: any in-image rebuild (dev-ISO creation, kernel update) re-runs dracut
# without our flags, and tpm2-tss/pcsc fail the openSUSE base's module checks.
#
# crypt/dm are requested explicitly for the same reason, plus one of its own:
# openSUSE's dracut defaults to hostonly, and a rebuild on a machine that is
# not itself LUKS-rooted would quietly drop the modules that unlock the disk.
# Naming them makes a missing cryptsetup/dmsetup a hard dracut error instead
# of a boot-time emergency shell.
RUN mkdir -p /usr/lib/dracut/dracut.conf.d && \
printf 'add_dracutmodules+=" bootc crypt dm "\nomit_dracutmodules+=" tpm2-tss pcsc "\ninstall_items+=" /usr/lib/systemd/system/initrd-root-fs.target.wants/bootc-root-setup.service "\n' \
> /usr/lib/dracut/dracut.conf.d/10-bootc-base.conf
# ...and place the wants-symlink ourselves, because 51bootc's install() puts it
# at ${systemdsystemconfdir}/initrd-root-fs.target.wants/ — and openSUSE's
# dracut leaves systemdsystemconfdir EMPTY, so the link lands at the initramfs
# ROOT (/initrd-root-fs.target.wants/), a directory systemd never scans. The
# unit was therefore present in the initramfs but never pulled in, so nothing
# consumed composefs= and switch-root had no prepared root.
# projectbluefin/dakota (the working composefs reference) carries it at
# /usr/lib/systemd/system/initrd-root-fs.target.wants/bootc-root-setup.service
# -> ../bootc-root-setup.service; match that exactly. dracut copies unit
# wants-links from /usr/lib (that is how ostree-prepare-root's link gets in).
RUN mkdir -p /usr/lib/systemd/system/initrd-root-fs.target.wants && \
ln -sf ../bootc-root-setup.service \
/usr/lib/systemd/system/initrd-root-fs.target.wants/bootc-root-setup.service
# The LUKS assertions below check the systemd unlock path, not a `cryptsetup`
# binary: dracut's crypt module needs cryptsetup at BUILD time (its check() is
# `require_binaries cryptsetup`, which is why the package is installed above),
# but in a systemd initrd dracut-ng 110 deliberately ships only
# systemd-cryptsetup + systemd-cryptsetup-generator + crypt-run-generator and
# leaves the standalone CLI out. Asserting on `sbin/cryptsetup` therefore failed
# a perfectly good initramfs (run 30735665323); verified against the real
# lsinitrd output for Tumbleweed's dracut 110+suse.45.
#
# plymouth-set-theme.sh first: the variant's boot animation has to be chosen
# before dracut copies the theme in. It never fails the build.
RUN --mount=type=bind,from=context,source=/,target=/run/context set -eu; \
/run/context/build_scripts/plymouth-set-theme.sh || true; \
kver="$(ls /usr/lib/modules | head -1)"; \
img="/usr/lib/modules/${kver}/initramfs.img"; \
dracut --force --no-hostonly --reproducible --zstd --omit "tpm2-tss pcsc" \
--kver "${kver}" "${img}"; \
lsinitrd "${img}" > /tmp/initramfs.list; \
if ! grep -q 'usr/lib/systemd/system/initrd-root-fs.target.wants/bootc-root-setup.service' /tmp/initramfs.list; then \
echo "ERROR: initramfs lacks the bootc-root-setup wants-link under /usr/lib/systemd/system — composefs boot would emergency-shell" >&2; \
exit 1; \
fi; \
for mod in crypt dm; do \
if ! grep -qx "${mod}" /tmp/initramfs.list; then \
echo "ERROR: initramfs has no '${mod}' dracut module — a LUKS root would never unlock and the disk would boot to the dracut emergency shell" >&2; \
exit 1; \
fi; \
done; \
for path in usr/bin/systemd-cryptsetup usr/sbin/dmsetup dm-crypt.ko; do \
if ! grep -q "${path}" /tmp/initramfs.list; then \
echo "ERROR: initramfs has no ${path} — the dm/crypt dracut modules shipped incomplete, so a LUKS root would never unlock" >&2; \
exit 1; \
fi; \
done; \
rm -f /tmp/initramfs.list
RUN mkdir -p /sysroot/ostree /boot /usr/lib/ostree /var && \
printf '[composefs]\nenabled = yes\n[sysroot]\nreadonly = true\n' > /usr/lib/ostree/prepare-root.conf && \
echo "HOME=/var/home" | tee -a "/etc/default/useradd" && \
rm -rf /boot /home /root /usr/local /srv /opt /mnt /var && \
mkdir -p /sysroot/ostree /boot /usr/lib/ostree /var && \
ln -sT sysroot/ostree /ostree && \
ln -sT var/roothome /root && ln -sT var/srv /srv && \
ln -sT var/opt /opt && ln -sT var/mnt /mnt && \
ln -sT var/home /home && ln -sT ../var/usrlocal /usr/local && \
for d in /var/* /var/.[!.]*; do [ -d "$d" ] && ! mountpoint -q "$d" 2>/dev/null && rm -rf "$d" 2>/dev/null || true; done && \
# The /var targets of the aliasing symlinks above must EXIST, both in the
# image and at runtime. Sailfin shipped them dangling, so anything that
# did `mkdir /mnt` got EEXIST (mkdir on a dangling symlink), which is how
# the live-ISO E2E died: fisherman's "creating mount point
# /mnt/fisherman-target: mkdir /mnt: file exists" (defect 3 of tunaOS#953).
# Create them here for the image (the live ISO's /var comes straight from
# it) and declare them in tmpfiles.d so they survive bootc's /var reset,
# identical to Containerfile.debian / Containerfile.arch.
mkdir -p /var/tmp /var/mnt /var/home /var/opt /var/srv /var/roothome \
/var/usrlocal && \
printf 'd /var/opt 0755 root root -\nd /var/home 0755 root root -\nd /var/srv 0755 root root -\nd /var/mnt 0755 root root -\nd /var/usrlocal 0755 root root -\nd /var/roothome 0700 root root -\nd /var/tmp 1777 root root -\nd /run/media 0755 root root -\n' \
> /usr/lib/tmpfiles.d/bootc-base-dirs.conf
RUN cp -rn /usr/etc/. /etc/ 2>/dev/null; rm -rf /usr/etc
RUN printf '# NTP pool\npool pool.ntp.org iburst\n\ndriftfile /var/lib/chrony/drift\nmakestep 1.0 3\nrtcsync\n' > /etc/chrony.conf
RUN printf 'd /var/lib/chrony 0750 chrony chrony\n' >> /usr/lib/tmpfiles.d/chrony.conf
# The /var wipe above destroys openSUSE's CA bundle (/var/lib/ca-certificates,
# which /etc/ssl/ca-bundle.pem symlinks into), so build-time TLS (curl in
# install-desktop.sh's yq fetch) fails cert verification. Regenerate it from the
# persistent /usr trust anchors so the desktop stages (FROM system) can curl.
# NOTE: runtime CA persistence across bootc's /var reset is a separate follow-up.
RUN update-ca-certificates || true
# bootc install (the qcow2 Gate) needs a container signature policy; openSUSE's
# base image doesn't ship /etc/containers/policy.json, so bootc fails with
# "no policy.json file found".
#
# This used to be an inline printf here. gurnard then hit the other half of the
# same problem — a policy that EXISTS but this image's skopeo cannot parse,
# which fails every skopeo operation just as thoroughly — so both live in
# build_scripts/bootc/containers-policy.sh now. Writing the default when the
# file is missing is still the first thing it does.
RUN --mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/bootc/containers-policy.sh
# The shared Bluefin layer is configuration and hardware integration, not a
# package dependency. Keep it as a transparent, pinned build-time file copy so
# Sailfin has the same curated base experience as the EL10 variants.
COPY --from=context /files /
COPY --from=common /system_files/shared /
# Enable the e2e readiness marker explicitly. On the dnf/apt/pacman variants
# this is done by build_scripts/40-services.sh — run it here too for
# standardization (service enablement, dconf update, readiness marker).
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
# Cache-bust: the build_scripts RUNs in the desktop stages below are invoked via
# a bind mount, whose content is NOT part of buildah's layer cache key — so
# editing install-desktop.sh etc. would silently reuse a stale cached layer.
# Hashing build_scripts/ into an ENV here (inherited by the child stages) forces
# correct invalidation. (Same mechanism as Containerfile.el10 / .ubuntu.)
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 system AS desktop
ARG DESKTOP_FLAVOR
ARG TUNAOS_IMAGE_FLAVOR
ENV TUNAOS_IMAGE_FLAVOR=${TUNAOS_IMAGE_FLAVOR}
ENV DESKTOP_FLAVOR=${DESKTOP_FLAVOR}
RUN --mount=type=bind,from=context,source=/,target=/run/context \
/run/context/build_scripts/desktop/install-desktop.sh ${DESKTOP_FLAVOR}
# The desktop zypper install repopulates /var and /usr/etc and shadows the
# bootc ostree symlink layout the system stage set up. Re-assert the exact same
# layout after the install so bootc lint passes (identical to the system stage).
RUN zypper clean -a 2>/dev/null || true
RUN set -x && rm -rf /boot /home /root /usr/local /srv /opt /mnt /var /ostree && \
mkdir -p /sysroot/ostree /boot /usr/lib/ostree /var /var/tmp /var/mnt \
/var/home /var/opt /var/srv /var/roothome /var/usrlocal && \
ln -sT sysroot/ostree /ostree && \
ln -sT var/roothome /root && ln -sT var/srv /srv && \
ln -sT var/opt /opt && ln -sT var/mnt /mnt && \
ln -sT var/home /home && ln -sT ../var/usrlocal /usr/local
RUN cp -rn /usr/etc/. /etc/ 2>/dev/null; rm -rf /usr/etc && rm -rf /usr/share/doc/bootc/baseimage
# The re-assert above wipes /var a second time, so it takes openSUSE's generated
# trust store (/var/lib/ca-certificates, which both /etc/ssl/ca-bundle.pem and
# /etc/ssl/certs symlink into) with it — undoing the system stage's regeneration
# and shipping an image whose TLS trust is two dangling symlinks. Every in-image
# TLS client then fails with "unable to get local issuer certificate", which is
# what broke the live-ISO customize step on sailfin: `flatpak install` against
# tunaos.org and the installer-bundle curl against github.com both died there,
# taking out all four sailfin cells of the LUKS matrix. Regenerate the store and
# assert it exists, so a trust-less image fails the build instead of shipping.
# The tmpfiles.d entries keep the directories after bootc's /var reset; actually
# repopulating them at runtime is the follow-up noted in the system stage.
RUN update-ca-certificates && \
test -s /var/lib/ca-certificates/ca-bundle.pem && \
printf 'd /var/lib/ca-certificates 0755 root root - -\nd /var/lib/ca-certificates/pem 0755 root root - -\nd /var/lib/ca-certificates/openssl 0755 root root - -\n' \
> /usr/lib/tmpfiles.d/tunaos-ca-certificates.conf
# DEBUG: dump the exact root state bootc lint will see (remove once green).
RUN echo "=== PRE-LINT ROOT STATE ===" && ls -la / && \
{ test -L /ostree && echo "OSTREE-SYMLINK-TARGET: [$(readlink /ostree)]"; } || echo "OSTREE: NOT A SYMLINK / MISSING" && \
ls -la /sysroot && echo "=== END STATE ==="
LABEL containers.bootc=1 ostree.bootable=1
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).
# Bluefin's GNOME branding/defaults are deliberately scoped to the GNOME image;
# do not leak them into KDE, Niri, or XFCE targets.
FROM desktop AS gnome
COPY --from=common /system_files/bluefin /
RUN if command -v glib-compile-schemas &>/dev/null; then glib-compile-schemas /usr/share/glib-2.0/schemas; fi && \
if command -v dconf &>/dev/null; then dconf update; fi
FROM desktop AS kde
FROM desktop AS niri
FROM desktop AS xfce
# cosmic is declared build_image: true for sailfin in build-config.yml, and
# manifests/desktops/cosmic.yaml carries a zypper section added specifically
# for it. Only this alias was missing, so every sailfin:cosmic build died with
# `The target "cosmic" was not found in the provided Dockerfile` (#1012)
# before it could reach the packages that were already staged for it.
FROM desktop AS cosmic
# Tuna Desktop (tuna-os/tuna-desktop ADR 0008, epic #577; tunaOS#3201).
# tuna-desktop comes from the factory's signed opensuse-tumbleweed index,
# declared in manifests/desktops/tuna.yaml; everything else is Tumbleweed's.
FROM desktop AS tuna