]> git.ipfire.org Git - people/ms/bricklayer.git/blob - src/bricklayer-master.in
backend: Send all Pakfire messages up to the logger
[people/ms/bricklayer.git] / src / bricklayer-master.in
1 #!/bin/bash
2 ###############################################################################
3 # #
4 # Bricklayer - An Installer for IPFire #
5 # Copyright (C) 2021 IPFire Development Team #
6 # #
7 # This program is free software; you can redistribute it and/or #
8 # modify it under the terms of the GNU General Public License #
9 # as published by the Free Software Foundation; either version 2 #
10 # of the License, or (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 # #
20 ###############################################################################
21
22 # Pakfire configuration file
23 PAKFIRE_CONFIG="/etc/pakfire/distros/ipfire3.conf"
24
25 # Packages to install in the live system
26 PACKAGES=(
27 # Install ourselves
28 "@PACKAGE@ >= @VERSION@"
29
30 "system-release"
31 "dracut"
32 "kernel"
33 "/etc/os-release"
34 )
35
36 BUILDSYSTEM_PACKAGES=(
37 "system-release"
38 "filesystem"
39 "device-mapper"
40 "dracut"
41 "grub"
42 "kernel"
43 )
44
45 # Compress as best as we can
46 ZSTD_COMPRESSION_LEVEL=22
47
48 # Grub modules to install
49 GRUB_MODULES=(
50 all_video
51 gfxmenu
52 iso9660
53 linux
54 linux16
55 ls
56 normal
57 search
58 )
59
60 # The modules will only be installed for legacy boot
61 GRUB_BIOS_MODULES=(
62 biosdisk
63 )
64
65 make_label() {
66 echo "BL_$(date -u "+%Y%m%d%H%M")"
67 }
68
69 allocate_file() {
70 local path="${1}"
71 local size="${2}"
72
73 dd if=/dev/zero of="${path}" bs=1k count="${size}"
74 }
75
76 make_buildsystem() {
77 local path="${1}"
78
79 local packages=(
80 "${BUILDSYSTEM_PACKAGES[@]}"
81 )
82
83 # Install packages
84 if ! pakfire --arch="${arch}" --config="${PAKFIRE_CONFIG}" --root="${path}" -y \
85 install --without-recommended "${packages[@]}"; then
86 echo "Could not install build system" >&2
87 return 1
88 fi
89 }
90
91 in_buildsystem() {
92 if [ -z "${buildsystem}" ]; then
93 echo "Build system isn't set up" >&2
94 return 1
95 fi
96
97 pakfire --config="${PAKFIRE_CONFIG}" --root="${buildsystem}" execute "$@"
98 }
99
100 make_live_system_image() {
101 local filename="${1}"
102 local os_release="${2}"
103 shift 2
104
105 local tempdir="$(mktemp -d)"
106
107 # Install a very basic system
108 if ! pakfire --arch="${arch}" --config="${PAKFIRE_CONFIG}" --root="${tempdir}" -y \
109 install --without-recommended "${PACKAGES[@]}" "$@"; then
110 echo "Could not install live system" >&2
111 rm -rf "${tempdir}"
112 return 1
113 fi
114
115 # Copy /etc/os-release
116 if ! cat "${tempdir}/etc/os-release" > "${os_release}"; then
117 echo "Could not extract /etc/os-release" >&2
118 rm -rf "${tempdir}"
119 return 1
120 fi
121
122 # Create a squashfs image
123 if ! mksquashfs "${tempdir}" "${filename}" \
124 -comp zstd -Xcompression-level "${ZSTD_COMPRESSION_LEVEL}"; then
125 rm -rf "${tempdir}"
126 return 1
127 fi
128
129 # Cleanup
130 rm -rf "${tempdir}"
131 return 0
132 }
133
134 make_boot_catalog() {
135 local path="${1}"
136
137 allocate_file "${path}" 2
138 }
139
140 make_grub_bios_image() {
141 local path="${1}"
142
143 local tempdir="$(mktemp -d)"
144
145 # Create a standalone image
146 if ! in_buildsystem --bind="${tempdir}" \
147 grub-mkimage \
148 --format="${grub_arch}-pc" \
149 --prefix="/boot/grub" \
150 --output="${tempdir}/core.img" \
151 "${GRUB_BIOS_MODULES[@]}" \
152 "${GRUB_MODULES[@]}"; then
153 rm -rf "${tempdir}"
154 return 1
155 fi
156
157 # Append cdboot.img with the generated core.img
158 if ! cat "${buildsystem}/usr/lib/grub/${grub_arch}-pc/cdboot.img" "${tempdir}/core.img" \
159 > "${path}"; then
160 unlink "${tempdir}" "${path}"
161 return 1
162 fi
163
164 # Cleanup
165 rm -rf "${tempdir}"
166
167 return 0
168 }
169
170 make_grub_config() {
171 local path="${1}"
172 local name="${2}"
173 local class="${3}"
174 local arch="${4}"
175 local label="${5}"
176
177 # Make kernel commandline
178 local commandline=(
179 # Tell dracut where to find the filesystem
180 "root=live:LABEL=${label}"
181
182 # Be less verbose during boot
183 "quiet"
184 )
185
186 cat > "${path}" <<EOF
187 # Bricklayer GRUB configuration
188
189 # Load font
190 loadfont unicode
191
192 terminal_output gfxterm
193 insmod gfxmenu
194
195 # Try loading video output
196 insmod all_video
197
198 # Select the first entry
199 set default=0
200
201 # Wait for 60 seconds for the user to make a decision
202 set timeout=60
203
204 menuentry 'Install ${name} (${arch})' --class ${class} --id install {
205 linux /boot/vmlinuz ${commandline[@]} installer
206 initrd /boot/initramfs.img
207 }
208
209 submenu 'Other Installation Options -->' {
210 menuentry 'Unattended installation' --class ${class} --id install.unattended {
211 linux /boot/vmlinuz ${commandline[@]} installer installer.unattended
212 initrd /boot/initramfs.img
213 }
214 }
215 EOF
216 }
217
218 make_grub_efi_config() {
219 local label="${1}"
220
221 echo "search.fs_label \"${label}\" root"
222 echo "set prefix=(\$root)/boot/grub"
223 }
224
225 make_grub_efi_image() {
226 local path="${1}"
227 local efiboot="${2}"
228 local label="${3}"
229
230 local config="$(mktemp)"
231
232 # Write early GRUB configuration
233 make_grub_efi_config "${label}" > "${config}"
234
235 # Generate a GRUB image
236 if ! in_buildsystem --bind="${tempdir}" --bind="${config}" \
237 grub-mkimage \
238 --format="${arch}-efi" \
239 --prefix="/EFI/BOOT" \
240 --output="${path}" \
241 --config="${config}" \
242 "${GRUB_MODULES[@]}"; then
243 echo "Could not generate GRUB EFI image" >&2
244 rm -f "${config}"
245 return 1
246 fi
247
248 # Remove configuration
249 rm -f "${config}"
250
251 # Allocate file
252 if ! allocate_file "${efiboot}" 1440; then
253 return 1
254 fi
255
256 # Format it with FAT
257 if ! mkdosfs -F 12 -n "BRICKLAYER" "${efiboot}"; then
258 echo "Could not format the EFI filesystem" >&2
259 rm -f "${efiboot}"
260 return 1
261 fi
262
263 local tempdir="$(mktemp -d)"
264
265 # Mount the disk
266 if ! mount -o loop "${efiboot}" "${tempdir}"; then
267 echo "Could not mount EFI filesystem" >&2
268 rm -rf "${efiboot}" "${tempdir}"
269 return 1
270 fi
271
272 mkdir -p "${tempdir}/EFI/BOOT"
273
274 # Copy GRUB EFI image onto the FAT partition
275 if ! cp -- "${path}" "${tempdir}/EFI/BOOT/${path##*/}"; then
276 echo "Could not copy GRUB EFI image onto the FAT filesystem" >&2
277 rm -rf "${efiboot}" "${tempdir}"
278 return 1
279 fi
280
281 # Umount the filesystem
282 if ! umount "${tempdir}"; then
283 rm -rf "${efiboot}" "${tempdir}"
284 return 1
285 fi
286
287 # Cleanup
288 rm -rf "${tempdir}"
289
290 return 0
291 }
292
293 find_kernel_release() {
294 local file
295 for file in ${buildsystem}/usr/lib/modules/*; do
296 if [ -d "${file}" ]; then
297 basename "${file}"
298 return 0
299 fi
300 done
301
302 return 1
303 }
304
305 install_kernel_image() {
306 local kernel_release="${1}"
307 local filename="${2}"
308
309 # Copy the kernel image
310 if ! cp -v "${buildsystem}/boot/vmlinuz-${kernel_release}" "${filename}"; then
311 echo "Could not install kernel image (release ${kernel_release})" >&2
312 return 1
313 fi
314
315 return 0
316 }
317
318 install_initramfs() {
319 local kernel_release="${1}"
320 local filename="${2}"
321
322 local args=(
323 --verbose
324
325 # Generate a ramdisk that can run on many hosts
326 --no-hostonly
327
328 --kmoddir "/usr/lib/modules/${kernel_release}"
329
330 # Add the live image module to mount our squashfs system
331 --add "dmsquash-live"
332
333 # Only enable these modules
334 --modules "base kernel-modules"
335
336 # Add support for squashfs
337 --filesystems "squashfs"
338 )
339
340 # The output is currently thrown away because dracut is broken and generates a lot
341 # of messages which slow down the build process...
342 if ! in_buildsystem --bind="${tempdir}" \
343 dracut "${args[@]}" "${filename}" "${kernel_release}"; then
344 unlink "${filename}"
345 return 1
346 fi
347
348 return 0
349 }
350
351 mkimage() {
352 local buildsystem="${1}"
353 local label="${2}"
354 local filename="${3}"
355 local r=0
356
357 # Create a temporary working directory
358 local tempdir="$(mktemp -d)"
359
360 # Create a directory for all the boot stuff
361 mkdir -p "${tempdir}/boot"
362
363 # Create argument list for xorriso
364 local args=(
365 # Emulate running as mkisofs
366 -as mkisofs
367
368 # Generate Joilet directory information
369 -J
370
371 # Generate rationalized Rock Ridge directory information
372 -r
373
374 # Be less verbose
375 #-quiet
376
377 # The name of the volume
378 -volid "${label}"
379
380 # Set ISO9660 conformance level
381 -iso-level 3
382
383 # Where to write the output to?
384 -output "${filename}"
385
386 # What files to package?
387 "${tempdir}"
388 )
389
390 # Determine GRUB architecture
391 local grub_arch="${arch}"
392 case "${arch}" in
393 aarch64)
394 grub_arch="arm64"
395 ;;
396
397 x86_64)
398 grub_arch="i386"
399 ;;
400 esac
401
402 # Find the kernel release
403 local kernel_release="$(find_kernel_release)"
404 if [ -z "${kernel_release}" ]; then
405 echo "Could not find kernel release" >&2
406 return 1
407 fi
408
409 # Install the kernel image
410 if ! install_kernel_image "${kernel_release}" "${tempdir}/boot/vmlinuz"; then
411 rm -rf "${tempdir}"
412 return 1
413 fi
414
415 # Create live ramdisk
416 if ! install_initramfs "${kernel_release}" "${tempdir}/boot/initramfs.img"; then
417 rm -rf "${tempdir}"
418 return 1
419 fi
420
421 # Generate GRUB BIOS image
422 if [ "${has_bios_boot}" = "true" ]; then
423 if ! make_grub_bios_image "${tempdir}/boot/biosboot.img"; then
424 rm -rf "${tempdir}"
425 return 1
426 fi
427
428 # Make boot catalog
429 if ! make_boot_catalog "${tempdir}/boot/boot.catalog"; then
430 rm -rf "${tempdir}"
431 return 1
432 fi
433
434 # Append to xorriso command line
435 args+=(
436 -no-emul-boot
437 -boot-load-size 4
438 -boot-info-table
439 -b "boot/biosboot.img"
440 -c "boot/boot.catalog"
441
442 # Enable hybrid boot mode
443 --grub2-boot-info
444 --grub2-mbr "/usr/lib/grub/${grub_arch}-pc/boot_hybrid.img"
445 )
446 fi
447
448 # Generate GRUB EFI image
449 if [ "${has_efi_boot}" = "true" ]; then
450 local grub_efi_arch="${arch}"
451
452 case "${arch}" in
453 aarch64)
454 grub_efi_arch="a64"
455 ;;
456 x86_64)
457 grub_efi_arch="x64"
458 ;;
459 esac
460
461 # Create a directory for EFI stuff
462 mkdir -p "${tempdir}/EFI/BOOT"
463
464 # Generate a GRUB EFI image
465 if ! make_grub_efi_image \
466 "${tempdir}/EFI/BOOT/boot${grub_efi_arch}.efi" \
467 "${tempdir}/boot/efiboot.img" \
468 "${label}"; then
469 rm -rf "${tempdir}"
470 return 1
471 fi
472
473 # Append to xorriso command line
474 args+=(
475 -eltorito-alt-boot
476 -e "boot/efiboot.img"
477 -no-emul-boot
478 -isohybrid-gpt-basdat
479 )
480 fi
481
482 local os_release="$(mktemp)"
483
484 # Create the live system image
485 if ! make_live_system_image "${tempdir}/squashfs.img" "${os_release}" \
486 "kernel = ${kernel_release}"; then
487 rm -rf "${tempdir}" "${os_release}"
488 return 1
489 fi
490
491 # Source /etc/os-release
492 . "${os_release}"
493 rm -f "${os_release}"
494
495 # Check if all necessary variables are set
496 local variable
497 for variable in PRETTY_NAME ID; do
498 if [ -n "${!variable}" ]; then
499 echo "WARNING: ${variable} is not set in /etc/os-release" >&2
500 fi
501 done
502
503 mkdir -p "${tempdir}/boot/grub"
504
505 # Generate GRUB configuration
506 if ! make_grub_config "${tempdir}/boot/grub/grub.cfg" \
507 "${PRETTY_NAME}" "${ID}" "${arch}" "${label}"; then
508 echo "Could not generate GRUB configuration" >&2
509 rm -rf "${tempdir}"
510 return 1
511 fi
512
513 mkdir -p "${tempdir}/boot/grub/fonts"
514
515 # Install GRUB fonts
516 local font
517 for font in unicode.pf2; do
518 if ! cp -- "${buildsystem}/usr/share/grub/${font}" \
519 "${tempdir}/boot/grub/fonts/${font}"; then
520 echo "Could not install font '${font}'" >&2
521 rm -rf "${tempdir}"
522 return 1
523 fi
524 done
525
526 # Master the ISO file
527 if ! xorriso "${args[@]}"; then
528 r=1
529 fi
530
531 # Cleanup
532 rm -rf "${tempdir}" "${os_release}"
533
534 return "${r}"
535 }
536
537 main() {
538 local arch="$(uname -m)"
539 local filename
540 local label
541
542 while [ $# -gt 0 ]; do
543 case "${1}" in
544 --arch=*)
545 arch="${1#*=}"
546 ;;
547
548 --label=*)
549 label="${1#*=}"
550 ;;
551
552 *.iso)
553 if [ -n "${filename}" ]; then
554 echo "Filename has already been set" >&2
555 return 2
556 fi
557
558 filename="${1}"
559 ;;
560
561 *)
562 echo "Unhandled argument: ${1}" >&2
563 return 2
564 ;;
565 esac
566 shift
567 done
568
569 # Was filename set?
570 if [ -z "${filename}" ]; then
571 echo "You have not specified a target filename" >&2
572 return 2
573 fi
574
575 # Generate a random label if nothing was set
576 if [ -z "${label}" ]; then
577 label="$(make_label)"
578 fi
579
580 # Check if the label is < 16 characters
581 if [ "${#label}" -gt 16 ]; then
582 echo "The label cannot be longer than 16 characters" >&2
583 return 2
584 fi
585
586 local has_bios_boot="false"
587 local has_efi_boot="false"
588
589 # Configure some architecture-dependent environment variables
590 case "${arch}" in
591 aarch64)
592 has_efi_boot="true"
593 ;;
594
595 x86_64)
596 has_bios_boot="true"
597 has_efi_boot="true"
598 ;;
599
600 # Throw an error on unhandled architectures
601 *)
602 echo "Unknown architecture: ${arch}" >&2
603 return 2
604 ;;
605 esac
606
607 local buildsystem="$(mktemp -d)"
608
609 # Install a system with all tools we need
610 if ! make_buildsystem "${buildsystem}"; then
611 return 1
612 fi
613
614 # Make image and delete it if something went wrong
615 if ! mkimage "${buildsystem}" "${label}" "${filename}"; then
616 rm -rf "${buildsystem}" "${filename}"
617 return 1
618 fi
619
620 # Remove temporary system
621 rm -rf "${buildsystem}"
622
623 return 0
624 }
625
626 main "$@" || exit $?