| 46 |
46 |
|
SIZE_KB="$(du -sk "$ROOTFS" | cut -f1)"
|
| 47 |
47 |
|
# Slack for the ext4 metadata plus room for the overlay's early writes.
|
| 48 |
48 |
|
IMG_MB=$(( SIZE_KB / 1024 + 1536 ))
|
| 49 |
|
- |
mkdir -p "$WORK/LiveOS"
|
| 50 |
|
- |
truncate -s "${IMG_MB}M" "$WORK/LiveOS/rootfs.img"
|
| 51 |
|
- |
mkfs.ext4 -q -L Alloy -d "$ROOTFS" "$WORK/LiveOS/rootfs.img"
|
|
49 |
+ |
# The LiveOS directory has to exist *inside* the squashfs, so mksquashfs is
|
|
50 |
+ |
# pointed at its parent: it takes the contents of the directory it is given
|
|
51 |
+ |
# as the squashfs root, so compressing LiveOS/ directly puts rootfs.img at
|
|
52 |
+ |
# the top level and dracut fails with "Failed to find a root filesystem in
|
|
53 |
+ |
# .../squashfs.img" while looking for LiveOS/rootfs.img.
|
|
54 |
+ |
mkdir -p "$WORK/sqroot/LiveOS"
|
|
55 |
+ |
truncate -s "${IMG_MB}M" "$WORK/sqroot/LiveOS/rootfs.img"
|
|
56 |
+ |
mkfs.ext4 -q -L Alloy -d "$ROOTFS" "$WORK/sqroot/LiveOS/rootfs.img"
|
| 52 |
57 |
|
|
| 53 |
|
- |
say "compressing squashfs.img (this is the long part)"
|
| 54 |
|
- |
mksquashfs "$WORK/LiveOS" "$WORK/iso/LiveOS/squashfs.img" \
|
| 55 |
|
- |
-noappend -no-progress -comp zstd -Xcompression-level 19 -b 1M \
|
| 56 |
|
- |
-e rootfs.img.tmp
|
| 57 |
|
- |
rm -rf "$WORK/LiveOS"
|
|
58 |
+ |
# zstd level 19 costs minutes of saturated CPU for a few percent of size,
|
|
59 |
+ |
# which is worth it for a release and pure waste when the question is
|
|
60 |
+ |
# whether the thing boots at all. ALLOY_ISO_FAST picks the cheap level.
|
|
61 |
+ |
if [ "${ALLOY_ISO_FAST:-0}" = "1" ]; then
|
|
62 |
+ |
COMP_LEVEL=3
|
|
63 |
+ |
say "compressing squashfs.img (fast mode, level $COMP_LEVEL)"
|
|
64 |
+ |
else
|
|
65 |
+ |
COMP_LEVEL=19
|
|
66 |
+ |
say "compressing squashfs.img (this is the long part)"
|
|
67 |
+ |
fi
|
|
68 |
+ |
mksquashfs "$WORK/sqroot" "$WORK/iso/LiveOS/squashfs.img" \
|
|
69 |
+ |
-noappend -no-progress -comp zstd -Xcompression-level "$COMP_LEVEL" -b 1M \
|
|
70 |
+ |
-processors "$(nproc)"
|
|
71 |
+ |
rm -rf "$WORK/sqroot"
|
| 58 |
72 |
|
|
| 59 |
73 |
|
# ---------------------------------------------------------------------
|
| 60 |
74 |
|
# 2. Kernel and a live initramfs.
|
| 65 |
79 |
|
say "kernel and initramfs"
|
| 66 |
80 |
|
cp "$ROOTFS/usr/lib/modules/$KVER/vmlinuz" "$WORK/iso/boot/vmlinuz"
|
| 67 |
81 |
|
|
|
82 |
+ |
# The image's own dracut config is for booting an installed ostree system:
|
|
83 |
+ |
# /usr/lib/dracut/dracut.conf.d/*bootc* add the ostree and bootc modules,
|
|
84 |
+ |
# which assume an ostree root and fail here trying to install /root. A live
|
|
85 |
+ |
# medium has no deployment to find, so start from no distro config at all
|
|
86 |
+ |
# (--conf /dev/null, empty --confdir) and name what this initramfs needs.
|
|
87 |
+ |
mkdir -p "$WORK/empty-conf"
|
| 68 |
88 |
|
dracut --force --no-hostonly --nomdadmconf --nolvmconf \
|
|
89 |
+ |
--conf /dev/null \
|
|
90 |
+ |
--confdir "$WORK/empty-conf" \
|
| 69 |
91 |
|
--kver "$KVER" \
|
| 70 |
92 |
|
--kmoddir "$ROOTFS/usr/lib/modules/$KVER" \
|
| 71 |
93 |
|
--add "dmsquash-live" \
|
|
94 |
+ |
--omit "ostree bootc" \
|
| 72 |
95 |
|
--add-drivers "squashfs loop overlay iso9660 sr_mod sd_mod usb_storage virtio_blk virtio_scsi virtio_pci" \
|
| 73 |
96 |
|
"$WORK/iso/boot/initramfs.img"
|
| 74 |
97 |
|
|
|
98 |
+ |
# dracut reports module failures on stderr and still exits 0, so a broken
|
|
99 |
+ |
# initramfs ships looking like a success. Check the result instead.
|
|
100 |
+ |
#
|
|
101 |
+ |
# Expect one error from this that does not matter: dracut-install cannot
|
|
102 |
+ |
# install `/root`, because in the bootc layout /root is a symlink to
|
|
103 |
+ |
# var/roothome and nothing has mounted a var here. dracut carries on and
|
|
104 |
+ |
# the initramfs is complete.
|
|
105 |
+ |
#
|
|
106 |
+ |
# grep -c rather than grep -q: grep -q exits at the first match, which
|
|
107 |
+ |
# SIGPIPEs lsinitrd, and under `set -o pipefail` that reads as a failed
|
|
108 |
+ |
# check on a perfectly good initramfs. It cost a build to work that out.
|
|
109 |
+ |
[ -s "$WORK/iso/boot/initramfs.img" ] || { echo "dracut produced no initramfs" >&2; exit 1; }
|
|
110 |
+ |
found="$(lsinitrd "$WORK/iso/boot/initramfs.img" 2>/dev/null | grep -c dmsquash || true)"
|
|
111 |
+ |
[ "${found:-0}" -gt 0 ] \
|
|
112 |
+ |
|| { echo "initramfs has no dmsquash-live; it cannot mount the live root" >&2; exit 1; }
|
|
113 |
+ |
say "initramfs $(du -h "$WORK/iso/boot/initramfs.img" | cut -f1), dmsquash-live present"
|
|
114 |
+ |
|
| 75 |
115 |
|
# ---------------------------------------------------------------------
|
| 76 |
116 |
|
# 3. The image the installer deploys.
|
| 77 |
117 |
|
# ---------------------------------------------------------------------
|
| 92 |
132 |
|
# reads to find the squashfs.
|
| 93 |
133 |
|
# ---------------------------------------------------------------------
|
| 94 |
134 |
|
say "grub"
|
| 95 |
|
- |
CMDLINE="root=live:CDLABEL=$VOLID rd.live.image rd.live.overlay.overlayfs=1 alloy.installer quiet loglevel=3"
|
|
135 |
+ |
# console= twice on purpose. The kernel writes to every console listed, so
|
|
136 |
+ |
# serial gets a full log for headless debugging, while /dev/console resolves
|
|
137 |
+ |
# to the last one named and so stays on the screen the user is looking at.
|
|
138 |
+ |
# Reversed, the installer's own output would go down the serial line and the
|
|
139 |
+ |
# monitor would sit black. The installer renders on tty1 explicitly
|
|
140 |
+ |
# (alloy-installer.service, TTYPath) and does not depend on this ordering.
|
|
141 |
+ |
CONSOLES="console=ttyS0,115200 console=tty0"
|
|
142 |
+ |
# selinux=0 on the live medium.
|
|
143 |
+ |
#
|
|
144 |
+ |
# The live rootfs.img is built by mkfs.ext4 from a container rootfs, and it
|
|
145 |
+ |
# carries SELinux labels only if the machine doing the building has SELinux
|
|
146 |
+ |
# to derive them from. fw13 is Pop!_OS and has none, so every file in the
|
|
147 |
+ |
# live root is unlabeled and systemd refuses to start at all: "Failed to
|
|
148 |
+ |
# allocate manager object: Permission denied", then it freezes, with no
|
|
149 |
+ |
# hint that labelling is what is missing.
|
|
150 |
+ |
#
|
|
151 |
+ |
# This is a real limitation of the ISO, not just of the live boot: an
|
|
152 |
+ |
# installer that cannot label cannot label the target either. Building on a
|
|
153 |
+ |
# Fedora host is what fixes it properly. Until then the ISO installs
|
|
154 |
+ |
# systems that need `enforcing=0` to boot, which is recorded in the wiki
|
|
155 |
+ |
# note alloy-build-notes.
|
|
156 |
+ |
SELINUX_OFF="selinux=0"
|
|
157 |
+ |
CMDLINE="root=live:CDLABEL=$VOLID rd.live.image rd.live.overlay.overlayfs=1 alloy.installer $CONSOLES $SELINUX_OFF quiet loglevel=3"
|
| 96 |
158 |
|
|
| 97 |
159 |
|
cat > "$WORK/iso/boot/grub/grub.cfg" <<EOF
|
| 98 |
160 |
|
set default=0
|
| 104 |
166 |
|
}
|
| 105 |
167 |
|
|
| 106 |
168 |
|
menuentry "Install Alloy (verbose, for diagnosing a failed boot)" {
|
| 107 |
|
- |
linux /boot/vmlinuz root=live:CDLABEL=$VOLID rd.live.image rd.live.overlay.overlayfs=1 alloy.installer rd.debug
|
|
169 |
+ |
linux /boot/vmlinuz root=live:CDLABEL=$VOLID rd.live.image rd.live.overlay.overlayfs=1 alloy.installer $CONSOLES $SELINUX_OFF rd.debug rd.shell systemd.log_level=debug
|
|
170 |
+ |
initrd /boot/initramfs.img
|
|
171 |
+ |
}
|
|
172 |
+ |
|
|
173 |
+ |
# Not a boot option: a rescue shell in the initramfs, for when the live root
|
|
174 |
+ |
# will not mount at all and there is nothing else to ask.
|
|
175 |
+ |
menuentry "Initramfs shell" {
|
|
176 |
+ |
linux /boot/vmlinuz root=live:CDLABEL=$VOLID rd.live.image $CONSOLES rd.break=pre-mount
|
| 108 |
177 |
|
initrd /boot/initramfs.img
|
| 109 |
178 |
|
}
|
| 110 |
179 |
|
EOF
|
| 118 |
187 |
|
configfile /boot/grub/grub.cfg
|
| 119 |
188 |
|
EOF
|
| 120 |
189 |
|
|
| 121 |
|
- |
# Signed shim first, so the ISO boots with Secure Boot on. grubx64.efi is
|
| 122 |
|
- |
# what shim chains to and the name is not arbitrary.
|
| 123 |
|
- |
cp "$ROOTFS/boot/efi/EFI/BOOT/BOOTX64.EFI" "$WORK/iso/EFI/BOOT/BOOTX64.EFI" 2>/dev/null \
|
| 124 |
|
- |
|| cp /boot/efi/EFI/BOOT/BOOTX64.EFI "$WORK/iso/EFI/BOOT/BOOTX64.EFI" 2>/dev/null \
|
| 125 |
|
- |
|| cp /usr/share/shim/x64/shimx64.efi "$WORK/iso/EFI/BOOT/BOOTX64.EFI"
|
| 126 |
|
- |
cp /usr/share/shim/x64/mmx64.efi "$WORK/iso/EFI/BOOT/mmx64.efi" 2>/dev/null || true
|
| 127 |
|
- |
|
|
190 |
+ |
# Our own GRUB *is* BOOTX64.EFI, with no shim in front of it.
|
|
191 |
+ |
#
|
|
192 |
+ |
# The signed shim and signed grubx64.efi that shim expects to chain to both
|
|
193 |
+ |
# ship in RPMs whose files a bootc image strips out of /boot, and the
|
|
194 |
+ |
# packages still read as installed, so reinstalling them is the only way to
|
|
195 |
+ |
# get the bytes back. Not worth it here: a self-built GRUB is unsigned
|
|
196 |
+ |
# either way, so this ISO needs Secure Boot off regardless of whether shim
|
|
197 |
+ |
# is in front of it. Signing is a distribution problem and distribution is
|
|
198 |
+ |
# not set up (GO task 0d7505b5); when it is, this is where shim goes.
|
| 128 |
199 |
|
grub2-mkimage \
|
| 129 |
200 |
|
--format=x86_64-efi \
|
| 130 |
201 |
|
--prefix="/EFI/BOOT" \
|
| 131 |
|
- |
--output="$WORK/iso/EFI/BOOT/grubx64.efi" \
|
|
202 |
+ |
--output="$WORK/iso/EFI/BOOT/BOOTX64.EFI" \
|
| 132 |
203 |
|
part_gpt part_msdos fat iso9660 udf normal linux echo all_video test \
|
| 133 |
204 |
|
search search_label search_fs_uuid search_fs_file gfxterm gfxterm_background \
|
| 134 |
205 |
|
configfile loadenv chain efi_gop efi_uga ls cat halt reboot minicmd \
|