-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathContainerfile
More file actions
220 lines (210 loc) · 13 KB
/
Copy pathContainerfile
File metadata and controls
220 lines (210 loc) · 13 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
# AI-hint: Defines the multi-stage Docker build process for the MiOS image, incorporating system configurations, automation scripts, and AI model bake parameters into the final bootable container.
# AI-related: /tmp/build/automation/lib/packages.sh, automation/45-coderun-sandbox-build.sh, /usr/share/mios/mios.toml, /usr/share/mios/flatpak-list, /usr/libexec/mios/copy-build-log.sh, mios-bootstrap, mios-dev, mios-sysext-pack, mios-coderun-sandbox, mios-additionalimagestores-perms
# syntax=docker/dockerfile:1.9
ARG BASE_IMAGE=ghcr.io/ublue-os/ucore-hci:stable-nvidia
FROM scratch AS ctx
COPY automation/ /ctx/automation/
COPY usr/ /ctx/usr/
COPY etc/ /ctx/etc/
# /home/ is bootstrap territory (mios-bootstrap.git stages user homes via
# profile/ in Phase-3); the build no longer pulls it.
# SSOT: mios.toml [packages.<section>].pkgs lives at
# usr/share/mios/mios.toml and is already shipped via the COPY usr/ above.
# build.sh exports $MIOS_TOML to /ctx/usr/share/mios/mios.toml so
# automation/lib/packages.sh resolves the canonical TOML manifest.
COPY VERSION /ctx/VERSION
COPY config/artifacts/ /ctx/bib-configs/
COPY tools/ /ctx/tools/
# Repo-root agent MD files. On a clean OCI/bootc image these do NOT otherwise
# exist at / (they're present on a dev box only via the Phase-1 git Total Root
# Merge) -> agent-pipe's _load_agent_contract() silently degrades to "" and every
# agent loses its /MiOS.md identity+grounding contract. Bake the real files (WS-C
# 2026-06-15; baking is safe on BOTH image-only and git-worktree-at-/ deploys --
# unlike a tmpfiles `L+` symlink, which would clobber the tracked file in a worktree).
COPY MiOS.md AGENTS.md CLAUDE.md GEMINI.md /ctx/rootmd/
FROM ${BASE_IMAGE}
# MIOS_VERSION: parameterized from the canonical repo-root VERSION file
# via build-mios.{sh,ps1} (which reads VERSION and passes
# `--build-arg MIOS_VERSION=$(cat VERSION)`). The default tracks the
# current stamp so a manual `podman build` without --build-arg still
# produces a valid image; callers who need a different version pin it
# at the command line.
ARG MIOS_VERSION=0.2.4
LABEL org.opencontainers.image.title="MiOS"
LABEL org.opencontainers.image.description="\MiOS is a user defined, customisable Linux distro based on Fedora/uBlue/uCore"
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL org.opencontainers.image.source="https://github.com/mios-dev/MiOS"
LABEL org.opencontainers.image.version="v${MIOS_VERSION}"
LABEL containers.bootc="1"
LABEL ostree.bootable="1"
CMD ["/sbin/init"]
ARG MIOS_USER=mios
ARG MIOS_HOSTNAME=mios
ARG MIOS_FLATPAKS=
# Default AI model selection. Build-time overrides flow from
# mios.toml [ai] (resolved by build-mios.{sh,ps1} interactive prompts)
# and propagate to automation/37-ollama-prep.sh via the
# MIOS_OLLAMA_BAKE_MODELS env var. Empty disables the build-time bake.
ARG MIOS_AI_MODEL=qwen2.5-coder:7b
ARG MIOS_AI_EMBED_MODEL=nomic-embed-text
ARG MIOS_OLLAMA_BAKE_MODELS=qwen2.5-coder:7b,nomic-embed-text
# Build context is bind-mounted read-only from the `ctx` stage; the only
# writable copy lives under /tmp/build for scripts that need to mutate it.
RUN --mount=type=bind,from=ctx,source=/ctx,target=/ctx,ro \
--mount=type=cache,dst=/var/cache/libdnf5,sharing=locked \
--mount=type=cache,dst=/var/cache/dnf,sharing=locked \
set -ex; \
install -d -m 0755 /tmp/build; \
cp -a /ctx/automation /ctx/usr /ctx/etc /ctx/VERSION /ctx/bib-configs /ctx/tools /tmp/build/; \
# WS-C: bake the repo-root agent MD files to / so a clean image is grounded
# (agent-pipe reads /MiOS.md; the layered /etc + ~/.config overrides still win).
install -m 0644 /ctx/rootmd/MiOS.md /ctx/rootmd/AGENTS.md /ctx/rootmd/CLAUDE.md /ctx/rootmd/GEMINI.md /; \
# Defensive CRLF -> LF normalization. .gitattributes already pins
# *.sh / *.toml / *.conf / *.yaml / *.json / *.md to LF, but Windows
# build hosts (OneDrive sync in particular) bypass git's filter and
# leak CRLF into the working tree. A single \r in a #!/bin/bash file
# produces "$'\r': command not found" the moment bash sources it,
# which surfaces as opaque exit-127 build failures hundreds of lines
# later. Strip CRs from every text file in the writable build context
# before any script runs -- cheap, idempotent, and immune to where
# the leak originated. Binary files are skipped via grep -Iq. \
find /tmp/build -type f \
\( -name "*.sh" -o -name "*.toml" -o -name "*.conf" \
-o -name "*.yaml" -o -name "*.yml" -o -name "*.json" \
-o -name "*.md" -o -name "*.service" -o -name "*.socket" \
-o -name "*.timer" -o -name "*.target" -o -name "*.preset" \
-o -name "*.container" -o -name "*.image" -o -name "*.kube" \
-o -name "*.volume" -o -name "*.repo" -o -name "*.policy" \
-o -name "*.rules" \) \
-exec sed -i 's/\r$//' {} +; \
export MIOS_TOML=/tmp/build/usr/share/mios/mios.toml; \
bash /tmp/build/automation/lib/packages.sh >/dev/null 2>&1 || true; \
source /tmp/build/automation/lib/packages.sh; \
# Purge any stale/corrupt repo metadata left in the buildkit cache mount
# from a previous failed build (zchunk checksum errors, partial syncs, etc.)
${DNF_BIN:-dnf5} clean metadata 2>/dev/null || ${DNF_BIN:-dnf} clean metadata 2>/dev/null || true; \
install_packages_strict base; \
if [[ -n "${MIOS_FLATPAKS}" ]]; then \
echo "${MIOS_FLATPAKS}" | tr "," "\n" > /tmp/build/usr/share/mios/flatpak-list; \
fi; \
# Propagate operator-chosen model selection into 37-ollama-prep.sh.
# The build-mios.{sh,ps1} prompt sets MIOS_OLLAMA_BAKE_MODELS to the
# selected chat + embed model pair (or operator-supplied custom CSV).
export MIOS_AI_MODEL MIOS_AI_EMBED_MODEL MIOS_OLLAMA_BAKE_MODELS; \
bash /tmp/build/automation/08-system-files-overlay.sh; \
chmod +x /tmp/build/automation/build.sh /tmp/build/automation/*.sh 2>/dev/null || true; \
chmod +x /usr/libexec/mios/copy-build-log.sh 2>/dev/null || true; \
CTX=/tmp/build /tmp/build/automation/build.sh; \
dnf clean all; \
rm -rf /tmp/build; \
# /var/cache is bind-mounted by buildkit (--mount=type=cache above) for
# the duration of this RUN, so trying to rm it returns EBUSY. Skip it;
# buildkit doesn't bake cache mounts into the layer regardless.
find /var -mindepth 1 -maxdepth 1 ! -name tmp ! -name cache -exec rm -rf {} +; \
find /run -mindepth 1 -maxdepth 1 ! -name "secrets" -exec rm -rf {} + 2>/dev/null || true
RUN bootc completion bash > /etc/bash_completion.d/bootc
# System-extension pack step: intentionally a no-op when no sysext source
# trees are staged in the image. The pack tool at tools/mios-sysext-pack.sh
# consolidates one-or-more `/usr/lib/extensions/source-*` trees into a single
# monolithic SquashFS sysext (mitigation for the overlayfs stacking-depth
# limit on bootc systems). The current build is FHS-overlay-only and stages
# no sysext sources, so this step skips silently. To start packing sysexts:
# 1. Have an earlier automation/*.sh phase populate
# /usr/lib/extensions/source-<name>/{usr,etc,...} with the files to pack.
# 2. Re-enable the RUN below (un-comment), passing every populated source
# dir as a positional argument to mios-sysext-pack.sh.
#
# RUN --mount=type=bind,from=ctx,source=/ctx/tools,target=/ctx/tools,ro \
# bash /ctx/tools/mios-sysext-pack.sh /usr/lib/extensions/source-*
# ── Bake logically-bound images (ARCHITECTURAL LAW 3 -- BOUND-IMAGES) ────────
# automation/08 symlinked every Quadlet into /usr/lib/bootc/bound-images.d/,
# which makes `bootc install` REQUIRE each Quadlet's Image= to already be
# present in container storage. Nothing was actually pulling them, so
# BIB/osbuild's bootc.install-to-filesystem stage failed EVERY deployment
# artifact with: "resolving bound image docker.io/crowdsecurity/crowdsec
# :latest ...: does not resolve to an image ID" (operator-confirmed
# 2026-05-14). This RUN is the missing bake step.
#
# Pull each bound image into /usr/lib/containers/storage -- an additional
# image store in IMMUTABLE /usr. It MUST live under /usr, not /var: the
# big RUN above ends with `find /var ... -exec rm -rf` (bootc treats /var
# as ephemeral), so /var/lib/containers/storage would be wiped. The
# storage.conf layers (/etc + /usr/share) list /usr/lib/containers/storage
# under additionalimagestores, so bootc install AND the running system
# resolve bound images from it with ZERO runtime pulls.
#
# --network=host: podman needs registry egress (same reason the big RUN
# above and `mios build`'s `podman build` use it on the WSL2 dev VM).
# Image= values are already rendered by 15-render-quadlets (run inside
# build.sh, above); the ${VAR:-default} fallback form is resolved here
# defensively. Any bound image that fails to bake fails the build LOUD --
# better than shipping an image whose every deployment artifact 404s.
RUN --network=host set -eux; \
mkdir -p /tmp/inner-podman; \
echo -e '[storage]\ndriver = "overlay"\n[storage.options.overlay]\nmountopt = "nodev"' > /tmp/inner-podman/storage.conf; \
install -d -m 0755 /usr/lib/containers/storage; \
baked=0; failed=0; \
for q in /usr/lib/bootc/bound-images.d/*.container; do \
[ -e "$q" ] || continue; \
img="$(sed -n 's/^Image=//p' "$q" | head -1 | tr -d ' \r')"; \
img="$(printf '%s' "$img" | sed -E 's/\$\{[A-Za-z_][A-Za-z0-9_]*:-([^}]*)\}/\1/g')"; \
[ -n "$img" ] || continue; \
case "$img" in *'$'*) echo "SKIP unrendered Image=$img ($q)"; continue ;; esac; \
first="${img%%/*}"; \
case "$first" in *.*|*:*) ;; \
localhost) \
# localhost/<name> is podman's prefix for un-pushed \
# locally-built images. There is no actual registry at \
# `localhost` -- `podman pull localhost/foo` ALWAYS \
# fails with "pinging container registry localhost". \
# Quadlets reference localhost/ images because their \
# consumers expect to find them in the OCI store at \
# runtime; they're produced by a DIFFERENT MiOS build \
# phase (e.g. automation/45-coderun-sandbox-build.sh \
# builds mios-coderun-sandbox). Skip the bake here; \
# the producer phase is the SoT. Operator-flagged \
# 2026-05-17: bound-images bake exited 1 with \
# failed=1; culprit was localhost/mios-coderun-sandbox \
# :latest pulled from a non-existent localhost registry. \
echo "SKIP localhost/ bound image $img (built by a different phase, not pulled from a registry)"; \
continue ;; \
*) echo "ERROR: bound image '$img' has a short name (no registry in '$first'). Fix the source -- typically /usr/share/mios/mios.toml [image.sidecars] -- to include a registry prefix (docker.io/, quay.io/, ghcr.io/, codeberg.org/, ...). Quadlet: $q"; \
failed=$((failed + 1)); continue ;; \
esac; \
echo "bound-image: baking $img"; \
_pulled=0; \
for _try in 1 2 3; do \
if CONTAINERS_STORAGE_CONF=/tmp/inner-podman/storage.conf podman --root /usr/lib/containers/storage pull "$img"; then _pulled=1; break; fi; \
echo " bound-image pull attempt $_try/3 failed for $img -- retrying in 3s"; sleep 3; \
done; \
if [ "$_pulled" = 1 ]; then \
baked=$((baked + 1)); \
else \
echo "ERROR: failed to bake bound image $img after 3 attempts"; failed=$((failed + 1)); \
fi; \
done; \
echo "bound-images: baked=$baked failed=$failed"; \
test "$failed" -eq 0; \
\
# additionalimagestores must be world-READABLE (the host shares this \
# store with every unprivileged user via /etc/containers/storage.conf's \
# additionalimagestores entry). podman pull's defaults leave the per- \
# backend subdirs (overlay-images, overlay-containers, overlay-layers, \
# libpod) at mode 0700 -- root-only. Unprivileged podman invocations \
# (flatpak shim, `mios` operator shell, anything forking podman) then \
# die with: "configure storage: open .../overlay-images/images.lock: \
# permission denied". \
# \
# Only chmod the main storage directory here -- do not touch the per-driver \
# subdirectories (overlay, libpod, etc.). Mutating permissions of these \
# directories inside the build container causes buildah commit failures \
# (exit 125 / error committing container) when the image count grows (e.g. >15-21 images). \
# The deep recursion happens at boot via mios-additionalimagestores-perms.service \
# (preset-enabled, commit f5a1ac9) which runs chmod -R go+rX on the running \
# host where layer sizes and build-time commit limits do not matter. \
echo "bound-images: chmod 0755 the main storage directory"; \
chmod 0755 /usr/lib/containers/storage; \
rm -rf /tmp/inner-podman
RUN ostree container commit
# bootc container lint MUST be the final instruction (ARCHITECTURAL LAW 4).
RUN bootc container lint