|
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"
|