Skip to main content

max / alloy

6.6 KB · 157 lines History Blame Raw
1 #!/usr/bin/env bash
2 #
3 # installtime.sh — can the installer layer a package into the target it just
4 # installed, or does that have to wait for the machine's first boot?
5 #
6 # installtime.sh
7 #
8 # The question this answers. Alloy's hotfixable components cannot ship in the
9 # base image (see README), so an installed machine has to acquire them some
10 # other way, and Max chose 2026-08-14 that they travel on the ISO rather than
11 # over the network. That leaves one sub-decision: whether `alloy install` lays
12 # them down while the target is still mounted, or whether a first-boot service
13 # does it on the installed machine.
14 #
15 # Layering at install time is worth a measurement because it removes a whole
16 # moving part: no first-boot unit, no stamp file to decide whether it already
17 # ran, no extra reboot in front of a user who just finished installing, and no
18 # ordering constraint against greetd. `rpm-ostree install` takes --sysroot, so
19 # it is at least plausible. Whether it works against a target that has never
20 # been booted is the thing to find out.
21 #
22 # What it does: install n1 (a base carrying nothing), mount the result, put a
23 # repo and its .repo file inside the target, run rpm-ostree against that
24 # sysroot from a container, unmount, boot it, and see whether the package is
25 # there on the very first boot.
26 #
27 # ## RESULT 2026-08-14: NOT PROVEN, AND FIRST BOOT WAS CHOSEN INSTEAD
28 #
29 # It does not work from this harness, and the reason is D-Bus rather than
30 # anything about ostree. `rpm-ostree install` is a client that talks to
31 # rpm-ostreed over the system bus, and --sysroot does not make it standalone.
32 # Three attempts, each getting one step further:
33 #
34 # no bus at all error: Loading sysroot: Connecting to system bus:
35 # Could not connect: No such file or directory
36 # dbus-daemon --system error: Failed to invoke RegisterClient:
37 # GDBus.Error...Spawn.ChildExited: Launch helper
38 # exited with unknown return code 1
39 # daemon started by error: Object does not exist at path "/"
40 # hand on that sysroot
41 #
42 # The measurement was abandoned there rather than pushed further, because the
43 # first-boot shape was already proven by the ordinary cases in README.md and
44 # the difference between them is one reboot.
45 #
46 # **Do not read this as "install-time layering is impossible."** The failures
47 # above are a container with no systemd fighting D-Bus activation. The real
48 # `alloy install` runs inside the ISO's live environment, which is a full
49 # Fedora system with systemd, a system bus and rpm-ostree already present, so
50 # the thing that blocked this harness may simply not be there. What stays
51 # genuinely unproven is whether rpm-ostreed will operate on a sysroot that is
52 # not the one it is running from. If the first-boot reboot ever becomes worth
53 # removing, that is the question to answer, and the right place to answer it is
54 # from a booted live ISO rather than from here.
55 set -euo pipefail
56
57 # shellcheck source=build/layertest/common.sh
58 . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common.sh"
59
60 # shellcheck source=build/privilege.sh
61 . "$HERE/../privilege.sh"
62
63 MNT="$STATE/target"
64
65 cleanup() {
66 privc umount "$MNT" 2>/dev/null || true
67 [ -n "${LOOP:-}" ] && privc losetup -d "$LOOP" 2>/dev/null
68 true
69 }
70 trap cleanup EXIT
71
72 "$HERE/install-disk.sh" n1
73
74 # -------------------------------------------------------------- mount it
75 # -P so the partition table is read and /dev/loopNp* appear. bootc's generic
76 # ext4 layout is ESP, boot, root; the root partition is the last and the big
77 # one, and it is the one carrying ostree/.
78 say ""
79 say "mounting the installed target"
80 LOOP="$(privc losetup --find --show -P "$STATE/disk.raw")"
81 privc mkdir -p "$MNT"
82
83 ROOT=""
84 for part in "$LOOP"p*; do
85 privc mount "$part" "$MNT" 2>/dev/null || continue
86 if privc test -d "$MNT/ostree"; then ROOT="$part"; break; fi
87 privc umount "$MNT"
88 done
89 [ -n "$ROOT" ] || die "no partition on $LOOP carries an ostree sysroot"
90 say "sysroot is $ROOT"
91
92 # --------------------------------------------- put the repo inside the target
93 # /var, not /usr: /usr is the immutable ostree tree and belongs to the image,
94 # while /var is the machine's own writable state and survives every upgrade.
95 # This is where the ISO's copy of our RPMs would land during a real install.
96 #
97 # The deployment's /etc is where rpm-ostree reads repo config from, and under
98 # ostree that is a per-deployment directory rather than a symlink into /var.
99 DEPLOY="$(privc sh -c "ls -d $MNT/ostree/deploy/*/deploy/*/ | head -1")"
100 [ -n "$DEPLOY" ] || die "no deployment under $MNT/ostree/deploy"
101 say "deployment is ${DEPLOY#"$MNT"}"
102
103 privc mkdir -p "$MNT/ostree/deploy/default/var/lib/alloy" 2>/dev/null || true
104 VAR="$(privc sh -c "ls -d $MNT/ostree/deploy/*/var | head -1")"
105 privc mkdir -p "$VAR/lib/alloy"
106 privc cp -r "$STATE/repo" "$VAR/lib/alloy/rpm"
107
108 # file:// and not http://, which is the whole point: no network at install time
109 # and none at first boot either.
110 privc tee "$DEPLOY/etc/yum.repos.d/alloy-local.repo" >/dev/null <<'EOF'
111 [alloy-local]
112 name=Alloy components, from the installer medium
113 baseurl=file:///var/lib/alloy/rpm
114 enabled=1
115 gpgcheck=0
116 EOF
117
118 # ------------------------------------------------------ layer into the target
119 # In a container because the dev host has no rpm-ostree. --privileged and the
120 # host mount namespace, because it is about to operate on a mounted filesystem
121 # and drive device nodes underneath it.
122 say ""
123 say "=== rpm-ostree install --sysroot against the unbooted target ==="
124 set +e
125 privc podman run --rm --privileged --pid=host \
126 --security-opt label=type:unconfined_t \
127 -v /dev:/dev \
128 -v "$MNT:/target" \
129 registry.fedoraproject.org/fedora:43 bash -c '
130 dnf -y install rpm-ostree >/dev/null 2>&1
131 rpm-ostree --sysroot=/target install alloy-demo 2>&1 | tail -20
132 exit "${PIPESTATUS[0]}"
133 '
134 RESULT=$?
135 set -e
136 say "rpm-ostree exit: $RESULT"
137
138 cleanup
139 trap - EXIT
140
141 # ------------------------------------------------------------- boot and look
142 say ""
143 say "booting the target"
144 nohup "$HERE/vm.sh" >"$STATE/qemu.log" 2>&1 &
145 sleep 50
146
147 say "--- first boot, never booted before this ---"
148 # `|| true` on the last command, not decoration. `rpm -q` exits 1 for a package
149 # that is not installed, which is a perfectly good answer here and is exactly
150 # the answer this script expects to see; without it the absent case reports
151 # "guest did not come back" about a guest that came back and said so.
152 "$HERE/sshx" 'echo -n "base-mark: "; cat /usr/share/base-mark; \
153 echo -n "binary: "; alloy-demo 2>&1 || echo "(absent)"; \
154 echo -n "rpm -q: "; rpm -q alloy-demo 2>&1 || true' \
155 || die "guest did not come back"
156 "$HERE/sshx" 'rpm-ostree status --json' | python3 "$HERE/readstate.py"
157