max / alloy
| 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 | |
| 56 | |
| 57 | # shellcheck source=build/layertest/common.sh |
| 58 | |
| 59 | |
| 60 | # shellcheck source=build/privilege.sh |
| 61 | |
| 62 | |
| 63 | MNT="/target" |
| 64 | |
| 65 | |
| 66 | || |
| 67 | [ && |
| 68 | |
| 69 | } |
| 70 | |
| 71 | |
| 72 | |
| 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 | |
| 79 | |
| 80 | LOOP="" |
| 81 | |
| 82 | |
| 83 | ROOT="" |
| 84 | for; do |
| 85 | || continue |
| 86 | if ; then ROOT=""; break; fi |
| 87 | |
| 88 | done |
| 89 | [ || |
| 90 | |
| 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="" |
| 100 | [ || |
| 101 | |
| 102 | |
| 103 | || |
| 104 | VAR="" |
| 105 | |
| 106 | |
| 107 | |
| 108 | # file:// and not http://, which is the whole point: no network at install time |
| 109 | # and none at first boot either. |
| 110 | |
| 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 | |
| 123 | |
| 124 | |
| 125 | |
| 126 | --security-opt label=type:unconfined_t \ |
| 127 | -v /dev:/dev \ |
| 128 | -v ":/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 | |
| 136 | |
| 137 | |
| 138 | |
| 139 | |
| 140 | |
| 141 | # ------------------------------------------------------------- boot and look |
| 142 | |
| 143 | |
| 144 | & |
| 145 | |
| 146 | |
| 147 | |
| 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 | |
| 153 | echo -n "binary: "; alloy-demo 2>&1 || echo "(absent)"; \ |
| 154 | echo -n "rpm -q: "; rpm -q alloy-demo 2>&1 || true' \ |
| 155 | || |
| 156 | | |
| 157 |