mkarchiso 53.1 KB
Newer Older
1
#!/usr/bin/env bash
2
3
#
# SPDX-License-Identifier: GPL-3.0-or-later
4

5
6
set -e -u

7
8
# Control the environment
umask 0022
nl6720's avatar
nl6720 committed
9
export LC_ALL="C"
10
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-"$(date +%s)"}"
11

12
# Set application name from the script's file name
13
app_name="${0##*/}"
14
15

# Define global variables. All of them will be overwritten later
16
pkg_list=()
17
bootstrap_pkg_list=()
18
19
20
quiet=""
work_dir=""
out_dir=""
21
gpg_key=""
22
23
24
25
iso_name=""
iso_label=""
iso_publisher=""
iso_application=""
26
iso_version=""
27
28
29
30
install_dir=""
arch=""
pacman_conf=""
packages=""
31
32
33
bootstrap_packages=""
pacstrap_dir=""
buildmodes=()
34
bootmodes=()
35
36
airootfs_image_type=""
airootfs_image_tool_options=()
37
38
cert_list=()
sign_netboot_artifacts=""
39
declare -A file_permissions=()
40
41
# adapted from GRUB_EARLY_INITRD_LINUX_STOCK in https://git.savannah.gnu.org/cgit/grub.git/tree/util/grub-mkconfig.in
readonly ucodes=('intel-uc.img' 'intel-ucode.img' 'amd-uc.img' 'amd-ucode.img' 'early_ucode.cpio' 'microcode.cpio')
42
43


44
45
46
47
# Show an INFO message
# $1: message string
_msg_info() {
    local _msg="${1}"
48
    [[ "${quiet}" == "y" ]] || printf '[%s] INFO: %s\n' "${app_name}" "${_msg}"
49
50
}

51
52
53
54
# Show a WARNING message
# $1: message string
_msg_warning() {
    local _msg="${1}"
55
    printf '[%s] WARNING: %s\n' "${app_name}" "${_msg}" >&2
56
57
}

58
59
60
61
62
63
# Show an ERROR message then exit with status
# $1: message string
# $2: exit code number (with 0 does not exit)
_msg_error() {
    local _msg="${1}"
    local _error=${2}
64
    printf '[%s] ERROR: %s\n' "${app_name}" "${_msg}" >&2
65
    if (( _error > 0 )); then
66
        exit "${_error}"
67
68
69
    fi
}

70
71
_mount_airootfs() {
    trap "_umount_airootfs" EXIT HUP INT TERM
72
    install -d -m 0755 -- "${work_dir}/mnt/airootfs"
73
74
    _msg_info "Mounting '${pacstrap_dir}.img' on '${work_dir}/mnt/airootfs'..."
    mount -- "${pacstrap_dir}.img" "${work_dir}/mnt/airootfs"
75
    _msg_info "Done!"
76
77
}

78
_umount_airootfs() {
79
    _msg_info "Unmounting '${work_dir}/mnt/airootfs'..."
80
    umount -d -- "${work_dir}/mnt/airootfs"
81
    _msg_info "Done!"
82
    rmdir -- "${work_dir}/mnt/airootfs"
83
84
85
86
87
    trap - EXIT HUP INT TERM
}

# Show help usage, with an exit status.
# $1: exit status number.
88
_usage() {
89
    IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true
90
usage: ${app_name} [options] <profile_dir>
91
92
93
94
95
96
97
98
99
100
101
102
  options:
     -A <application> Set an application name for the ISO
                      Default: '${iso_application}'
     -C <file>        pacman configuration file.
                      Default: '${pacman_conf}'
     -D <install_dir> Set an install_dir. All files will by located here.
                      Default: '${install_dir}'
                      NOTE: Max 8 characters, use only [a-z0-9]
     -L <label>       Set the ISO volume label
                      Default: '${iso_label}'
     -P <publisher>   Set the ISO publisher
                      Default: '${iso_publisher}'
103
104
105
106
     -c [cert ..]     Provide certificates for codesigning of netboot artifacts
                      Multiple files are provided as quoted, space delimited list.
                      The first file is considered as the signing certificate,
                      the second as the key.
107
     -g <gpg_key>     Set the PGP key ID to be used for signing the rootfs image
108
     -h               This message
109
     -m [mode ..]     Build mode(s) to use (valid modes are: 'bootstrap', 'iso' and 'netboot').
110
                      Multiple build modes are provided as quoted, space delimited list.
111
112
     -o <out_dir>     Set the output directory
                      Default: '${out_dir}'
113
     -p [package ..]  Package(s) to install.
114
                      Multiple packages are provided as quoted, space delimited list.
115
116
117
118
119
     -v               Enable verbose output
     -w <work_dir>    Set the working directory
                      Default: '${work_dir}'

  profile_dir:        Directory of the archiso profile to build
120
ENDUSAGETEXT
121
    printf '%s' "${usagetext}"
122
    exit "${1}"
123
124
}

125
# Shows configuration options.
126
_show_config() {
127
    local build_date
128
    build_date="$(date --utc --iso-8601=seconds -d "@${SOURCE_DATE_EPOCH}")"
129
    _msg_info "${app_name} configuration settings"
130
131
132
    _msg_info "             Architecture:   ${arch}"
    _msg_info "        Working directory:   ${work_dir}"
    _msg_info "   Installation directory:   ${install_dir}"
133
134
    _msg_info "               Build date:   ${build_date}"
    _msg_info "         Output directory:   ${out_dir}"
135
136
    _msg_info "       Current build mode:   ${buildmode}"
    _msg_info "              Build modes:   ${buildmodes[*]}"
137
    _msg_info "                  GPG key:   ${gpg_key:-None}"
138
    _msg_info "Code signing certificates:   ${cert_list[*]}"
139
140
    _msg_info "                  Profile:   ${profile}"
    _msg_info "Pacman configuration file:   ${pacman_conf}"
141
    _msg_info "          Image file name:   ${image_name:-None}"
142
143
144
145
    _msg_info "         ISO volume label:   ${iso_label}"
    _msg_info "            ISO publisher:   ${iso_publisher}"
    _msg_info "          ISO application:   ${iso_application}"
    _msg_info "               Boot modes:   ${bootmodes[*]}"
146
147
    _msg_info "            Packages File:   ${buildmode_packages}"
    _msg_info "                 Packages:   ${buildmode_pkg_list[*]}"
148
149
}

150
# Cleanup airootfs
151
152
_cleanup_pacstrap_dir() {
    _msg_info "Cleaning up in pacstrap location..."
153

154
    # Delete all files in /boot
155
    [[ -d "${pacstrap_dir}/boot" ]] && find "${pacstrap_dir}/boot" -mindepth 1 -delete
156
    # Delete pacman database sync cache files (*.tar.gz)
157
    [[ -d "${pacstrap_dir}/var/lib/pacman" ]] && find "${pacstrap_dir}/var/lib/pacman" -maxdepth 1 -type f -delete
158
    # Delete pacman database sync cache
159
    [[ -d "${pacstrap_dir}/var/lib/pacman/sync" ]] && find "${pacstrap_dir}/var/lib/pacman/sync" -delete
160
    # Delete pacman package cache
161
    [[ -d "${pacstrap_dir}/var/cache/pacman/pkg" ]] && find "${pacstrap_dir}/var/cache/pacman/pkg" -type f -delete
162
    # Delete all log files, keeps empty dirs.
163
    [[ -d "${pacstrap_dir}/var/log" ]] && find "${pacstrap_dir}/var/log" -type f -delete
164
    # Delete all temporary files and dirs
165
    [[ -d "${pacstrap_dir}/var/tmp" ]] && find "${pacstrap_dir}/var/tmp" -mindepth 1 -delete
166
    # Delete package pacman related files.
167
    find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete
168
    # Create an empty /etc/machine-id
169
170
    rm -f -- "${pacstrap_dir}/etc/machine-id"
    printf '' > "${pacstrap_dir}/etc/machine-id"
171
172

    _msg_info "Done!"
173
}
174

175
176
# Create a squashfs image and place it in the ISO 9660 file system.
# $@: options to pass to mksquashfs
177
178
179
180
_run_mksquashfs() {
    local image_path="${isofs_dir}/${install_dir}/${arch}/airootfs.sfs"
    if [[ "${quiet}" == "y" ]]; then
        mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}" -no-progress > /dev/null
181
    else
182
        mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}"
183
184
185
    fi
}

186
187
# Create an ext4 image containing the root file system and pack it inside a squashfs image.
# Save the squashfs image on the ISO 9660 file system.
188
_mkairootfs_ext4+squashfs() {
189
    [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
190

191
    _msg_info "Creating ext4 image of 32 GiB..."
192
    if [[ "${quiet}" == "y" ]]; then
193
        mkfs.ext4 -q -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${pacstrap_dir}.img" 32G
194
    else
195
        mkfs.ext4 -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${pacstrap_dir}.img" 32G
196
    fi
197
    tune2fs -c 0 -i 0 -- "${pacstrap_dir}.img" > /dev/null
198
    _msg_info "Done!"
199
    _mount_airootfs
200
201
    _msg_info "Copying '${pacstrap_dir}/' to '${work_dir}/mnt/airootfs/'..."
    cp -aT -- "${pacstrap_dir}/" "${work_dir}/mnt/airootfs/"
202
    chown -- 0:0 "${work_dir}/mnt/airootfs/"
203
    _msg_info "Done!"
204
    _umount_airootfs
205
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
206
    _msg_info "Creating SquashFS image, this may take some time..."
207
    _run_mksquashfs "${pacstrap_dir}.img"
208
    _msg_info "Done!"
209
    rm -- "${pacstrap_dir}.img"
210
211
}

212
# Create a squashfs image containing the root file system and saves it on the ISO 9660 file system.
213
_mkairootfs_squashfs() {
214
    [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
215

216
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
217
    _msg_info "Creating SquashFS image, this may take some time..."
218
    _run_mksquashfs "${pacstrap_dir}"
nl6720's avatar
nl6720 committed
219
220
}

221
# Create an EROFS image containing the root file system and saves it on the ISO 9660 file system.
nl6720's avatar
nl6720 committed
222
223
_mkairootfs_erofs() {
    local fsuuid
224
    [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
nl6720's avatar
nl6720 committed
225
226
227
228
229
230

    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
    local image_path="${isofs_dir}/${install_dir}/${arch}/airootfs.erofs"
    # Generate reproducible file system UUID from SOURCE_DATE_EPOCH
    fsuuid="$(uuidgen --sha1 --namespace 93a870ff-8565-4cf3-a67b-f47299271a96 --name "${SOURCE_DATE_EPOCH}")"
    _msg_info "Creating EROFS image, this may take some time..."
231
    mkfs.erofs -U "${fsuuid}" "${airootfs_image_tool_options[@]}" -- "${image_path}" "${pacstrap_dir}"
232
233
234
    _msg_info "Done!"
}

235
# Create checksum file for the rootfs image.
236
_mkchecksum() {
237
    _msg_info "Creating checksum file for self-test..."
238
    cd -- "${isofs_dir}/${install_dir}/${arch}"
nl6720's avatar
nl6720 committed
239
240
241
242
243
    if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then
        sha512sum airootfs.sfs > airootfs.sha512
    elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
        sha512sum airootfs.erofs > airootfs.sha512
    fi
244
    cd -- "${OLDPWD}"
245
    _msg_info "Done!"
246
247
}

248
# GPG sign the root file system image.
249
_mksignature() {
250
    _msg_info "Signing rootfs image..."
251
    cd -- "${isofs_dir}/${install_dir}/${arch}"
252
    # always use the .sig file extension, as that is what mkinitcpio-archiso's hooks expect
nl6720's avatar
nl6720 committed
253
    if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then
254
        gpg --output airootfs.sfs.sig --detach-sign --default-key "${gpg_key}" airootfs.sfs
nl6720's avatar
nl6720 committed
255
    elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
256
        gpg --output airootfs.erofs.sig --detach-sign --default-key "${gpg_key}" airootfs.erofs
nl6720's avatar
nl6720 committed
257
    fi
258
    cd -- "${OLDPWD}"
259
260
261
    _msg_info "Done!"
}

262
# Helper function to run functions only one time.
263
# $1: function name
264
_run_once() {
265
    if [[ ! -e "${work_dir}/${run_once_mode}.${1}" ]]; then
266
        "$1"
267
        touch "${work_dir}/${run_once_mode}.${1}"
268
269
    fi
}
270

271
# Set up custom pacman.conf with custom cache and pacman hook directories.
272
_make_pacman_conf() {
273
274
275
276
    local _cache_dirs _system_cache_dirs _profile_cache_dirs
    _system_cache_dirs="$(pacman-conf CacheDir| tr '\n' ' ')"
    _profile_cache_dirs="$(pacman-conf --config "${pacman_conf}" CacheDir| tr '\n' ' ')"

277
    # Only use the profile's CacheDir, if it is not the default and not the same as the system cache dir.
278
279
280
281
282
283
284
285
286
    if [[ "${_profile_cache_dirs}" != "/var/cache/pacman/pkg" ]] && \
        [[ "${_system_cache_dirs}" != "${_profile_cache_dirs}" ]]; then
        _cache_dirs="${_profile_cache_dirs}"
    else
        _cache_dirs="${_system_cache_dirs}"
    fi

    _msg_info "Copying custom pacman.conf to work directory..."
    _msg_info "Using pacman CacheDir: ${_cache_dirs}"
287
    # take the profile pacman.conf and strip all settings that would break in chroot when using pacman -r
288
289
    # append CacheDir and HookDir to [options] section
    # HookDir is *always* set to the airootfs' override directory
290
291
292
    # see `man 8 pacman` for further info
    pacman-conf --config "${pacman_conf}" | \
        sed "/CacheDir/d;/DBPath/d;/HookDir/d;/LogFile/d;/RootDir/d;/\[options\]/a CacheDir = ${_cache_dirs}
293
        /\[options\]/a HookDir = ${pacstrap_dir}/etc/pacman.d/hooks/" > "${work_dir}/${buildmode}.pacman.conf"
294
295
}

296
# Prepare working directory and copy custom root file system files.
297
298
_make_custom_airootfs() {
    local passwd=()
299
    local filename permissions
300

301
    install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir}"
302

303
    if [[ -d "${profile}/airootfs" ]]; then
304
        _msg_info "Copying custom airootfs files..."
305
        cp -af --no-preserve=ownership,mode -- "${profile}/airootfs/." "${pacstrap_dir}"
306
307
308
        # Set ownership and mode for files and directories
        for filename in "${!file_permissions[@]}"; do
            IFS=':' read -ra permissions <<< "${file_permissions["${filename}"]}"
309
310
311
            # Prevent file path traversal outside of $pacstrap_dir
            if [[ "$(realpath -q -- "${pacstrap_dir}${filename}")" != "${pacstrap_dir}"* ]]; then
                _msg_error "Failed to set permissions on '${pacstrap_dir}${filename}'. Outside of valid path." 1
312
            # Warn if the file does not exist
313
314
            elif [[ ! -e "${pacstrap_dir}${filename}" ]]; then
                _msg_warning "Cannot change permissions of '${pacstrap_dir}${filename}'. The file or directory does not exist."
315
            else
316
                if [[ "${filename: -1}" == "/" ]]; then
317
318
                    chown -fhR -- "${permissions[0]}:${permissions[1]}" "${pacstrap_dir}${filename}"
                    chmod -fR -- "${permissions[2]}" "${pacstrap_dir}${filename}"
319
                else
320
321
                    chown -fh -- "${permissions[0]}:${permissions[1]}" "${pacstrap_dir}${filename}"
                    chmod -f -- "${permissions[2]}" "${pacstrap_dir}${filename}"
322
                fi
323
324
            fi
        done
325
        _msg_info "Done!"
326
327
328
    fi
}

329
# Install desired packages to the root file system
330
_make_packages() {
331
    _msg_info "Installing packages to '${pacstrap_dir}/'..."
332

333
334
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg"
335
        export ARCHISO_GNUPG_FD
336
    fi
337

338
    # Unset TMPDIR to work around https://bugs.archlinux.org/task/70580
339
    if [[ "${quiet}" = "y" ]]; then
340
        env -u TMPDIR pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}" &> /dev/null
341
    else
342
        env -u TMPDIR pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}"
343
344
    fi

345
346
347
348
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<&-
        unset ARCHISO_GNUPG_FD
    fi
349
350

    _msg_info "Done! Packages installed successfully."
351
352
}

353
# Customize installation.
354
355
_make_customize_airootfs() {
    local passwd=()
356

357
    if [[ -e "${profile}/airootfs/etc/passwd" ]]; then
358
        _msg_info "Copying /etc/skel/* to user homes..."
359
        while IFS=':' read -a passwd -r; do
360
            # Only operate on UIDs in range 1000–59999
361
            (( passwd[2] >= 1000 && passwd[2] < 60000 )) || continue
362
            # Skip invalid home directories
363
364
            [[ "${passwd[5]}" == '/' ]] && continue
            [[ -z "${passwd[5]}" ]] && continue
365
366
367
368
            # Prevent path traversal outside of $pacstrap_dir
            if [[ "$(realpath -q -- "${pacstrap_dir}${passwd[5]}")" == "${pacstrap_dir}"* ]]; then
                if [[ ! -d "${pacstrap_dir}${passwd[5]}" ]]; then
                    install -d -m 0750 -o "${passwd[2]}" -g "${passwd[3]}" -- "${pacstrap_dir}${passwd[5]}"
369
                fi
370
371
372
                cp -dnRT --preserve=mode,timestamps,links -- "${pacstrap_dir}/etc/skel/." "${pacstrap_dir}${passwd[5]}"
                chmod -f 0750 -- "${pacstrap_dir}${passwd[5]}"
                chown -hR -- "${passwd[2]}:${passwd[3]}" "${pacstrap_dir}${passwd[5]}"
373
            else
374
                _msg_error "Failed to set permissions on '${pacstrap_dir}${passwd[5]}'. Outside of valid path." 1
375
            fi
376
        done < "${profile}/airootfs/etc/passwd"
377
        _msg_info "Done!"
378
379
    fi

380
381
    if [[ -e "${pacstrap_dir}/root/customize_airootfs.sh" ]]; then
        _msg_info "Running customize_airootfs.sh in '${pacstrap_dir}' chroot..."
382
        _msg_warning "customize_airootfs.sh is deprecated! Support for it will be removed in a future archiso version."
383
        chmod -f -- +x "${pacstrap_dir}/root/customize_airootfs.sh"
384
385
        # Unset TMPDIR to work around https://bugs.archlinux.org/task/70580
        eval -- env -u TMPDIR arch-chroot "${pacstrap_dir}" "/root/customize_airootfs.sh"
386
        rm -- "${pacstrap_dir}/root/customize_airootfs.sh"
387
        _msg_info "Done! customize_airootfs.sh run successfully."
388
389
390
    fi
}

391
392
393
394
# Set up boot loaders
_make_bootmodes() {
    local bootmode
    for bootmode in "${bootmodes[@]}"; do
395
        _run_once "_make_bootmode_${bootmode}"
396
397
398
    done
}

399
# Copy kernel and initramfs to ISO 9660
400
_make_boot_on_iso9660() {
401
    local ucode_image
Christian Hesse's avatar
Christian Hesse committed
402
    _msg_info "Preparing kernel and initramfs for the ISO 9660 file system..."
403
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/${arch}"
404
405
    install -m 0644 -- "${pacstrap_dir}/boot/initramfs-"*".img" "${isofs_dir}/${install_dir}/boot/${arch}/"
    install -m 0644 -- "${pacstrap_dir}/boot/vmlinuz-"* "${isofs_dir}/${install_dir}/boot/${arch}/"
406

407
    for ucode_image in "${ucodes[@]}"; do
408
409
410
        if [[ -e "${pacstrap_dir}/boot/${ucode_image}" ]]; then
            install -m 0644 -- "${pacstrap_dir}/boot/${ucode_image}" "${isofs_dir}/${install_dir}/boot/"
            if [[ -e "${pacstrap_dir}/usr/share/licenses/${ucode_image%.*}/" ]]; then
411
                install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/"
412
                install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/${ucode_image%.*}/"* \
413
414
415
416
                    "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/"
            fi
        fi
    done
417
    _msg_info "Done!"
418
419
}

420
# Prepare syslinux for booting from MBR (isohybrid)
421
_make_bootmode_bios.syslinux.mbr() {
422
    _msg_info "Setting up SYSLINUX for BIOS booting from a disk..."
nl6720's avatar
nl6720 committed
423
    install -d -m 0755 -- "${isofs_dir}/syslinux"
424
425
    for _cfg in "${profile}/syslinux/"*.cfg; do
        sed "s|%ARCHISO_LABEL%|${iso_label}|g;
426
427
             s|%INSTALL_DIR%|${install_dir}|g;
             s|%ARCH%|${arch}|g" \
nl6720's avatar
nl6720 committed
428
             "${_cfg}" > "${isofs_dir}/syslinux/${_cfg##*/}"
429
    done
430
    if [[ -e "${profile}/syslinux/splash.png" ]]; then
nl6720's avatar
nl6720 committed
431
        install -m 0644 -- "${profile}/syslinux/splash.png" "${isofs_dir}/syslinux/"
432
    fi
433
434
435
    install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/"*.c32 "${isofs_dir}/syslinux/"
    install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/lpxelinux.0" "${isofs_dir}/syslinux/"
    install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/memdisk" "${isofs_dir}/syslinux/"
436

437
    _run_once _make_boot_on_iso9660
438

nl6720's avatar
nl6720 committed
439
440
    if [[ -e "${isofs_dir}/syslinux/hdt.c32" ]]; then
        install -d -m 0755 -- "${isofs_dir}/syslinux/hdt"
441
442
        if [[ -e "${pacstrap_dir}/usr/share/hwdata/pci.ids" ]]; then
            gzip -cn9 "${pacstrap_dir}/usr/share/hwdata/pci.ids" > \
nl6720's avatar
nl6720 committed
443
                "${isofs_dir}/syslinux/hdt/pciids.gz"
444
        fi
445
        find "${pacstrap_dir}/usr/lib/modules" -name 'modules.alias' -print -exec gzip -cn9 '{}' ';' -quit > \
nl6720's avatar
nl6720 committed
446
            "${isofs_dir}/syslinux/hdt/modalias.gz"
447
    fi
448
449

    # Add other aditional/extra files to ${install_dir}/boot/
450
    if [[ -e "${pacstrap_dir}/boot/memtest86+/memtest.bin" ]]; then
nl6720's avatar
nl6720 committed
451
        # rename for PXE: https://wiki.archlinux.org/title/Syslinux#Using_memtest
452
        install -m 0644 -- "${pacstrap_dir}/boot/memtest86+/memtest.bin" "${isofs_dir}/${install_dir}/boot/memtest"
453
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
454
        install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/common/GPL2/license.txt" \
455
456
            "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
    fi
457
    _msg_info "Done! SYSLINUX set up for BIOS booting from a disk successfully."
458
459
}

460
# Prepare syslinux for El-Torito booting
461
_make_bootmode_bios.syslinux.eltorito() {
462
    _msg_info "Setting up SYSLINUX for BIOS booting from an optical disc..."
nl6720's avatar
nl6720 committed
463
    install -d -m 0755 -- "${isofs_dir}/syslinux"
464
465
    install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/isolinux.bin" "${isofs_dir}/syslinux/"
    install -m 0644 -- "${pacstrap_dir}/usr/lib/syslinux/bios/isohdpfx.bin" "${isofs_dir}/syslinux/"
466

nl6720's avatar
nl6720 committed
467
    # ISOLINUX and SYSLINUX installation is shared
468
    _run_once _make_bootmode_bios.syslinux.mbr
469
470

    _msg_info "Done! SYSLINUX set up for BIOS booting from an optical disc successfully."
471
472
}

473
# Copy kernel and initramfs to FAT image
474
_make_boot_on_fat() {
475
    local ucode_image all_ucode_images=()
Christian Hesse's avatar
Christian Hesse committed
476
    _msg_info "Preparing kernel and initramfs for the FAT file system..."
477
    mmd -i "${work_dir}/efiboot.img" \
478
        "::/${install_dir}" "::/${install_dir}/boot" "::/${install_dir}/boot/${arch}"
479
480
    mcopy -i "${work_dir}/efiboot.img" "${pacstrap_dir}/boot/vmlinuz-"* \
        "${pacstrap_dir}/boot/initramfs-"*".img" "::/${install_dir}/boot/${arch}/"
481
482
483
    for ucode_image in "${ucodes[@]}"; do
        if [[ -e "${pacstrap_dir}/boot/${ucode_image}" ]]; then
            all_ucode_images+=("${pacstrap_dir}/boot/${ucode_image}")
484
485
486
        fi
    done
    if (( ${#all_ucode_images[@]} )); then
487
        mcopy -i "${work_dir}/efiboot.img" "${all_ucode_images[@]}" "::/${install_dir}/boot/"
488
    fi
489
    _msg_info "Done!"
490
491
}

492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# Create a FAT image (efiboot.img) which will serve as the EFI system partition
# $1: image size in bytes
_make_efibootimg() {
    local imgsize="0"

    # Convert from bytes to KiB and round up to the next full MiB with an additional MiB for reserved sectors.
    imgsize="$(awk 'function ceil(x){return int(x)+(x>int(x))}
            function byte_to_kib(x){return x/1024}
            function mib_to_kib(x){return x*1024}
            END {print mib_to_kib(ceil((byte_to_kib($1)+1024)/1024))}' <<< "${1}"
    )"
    # The FAT image must be created with mkfs.fat not mformat, as some systems have issues with mformat made images:
    # https://lists.gnu.org/archive/html/grub-devel/2019-04/msg00099.html
    [[ -e "${work_dir}/efiboot.img" ]] && rm -f -- "${work_dir}/efiboot.img"
    _msg_info "Creating FAT image of size: ${imgsize} KiB..."
    mkfs.fat -C -n ARCHISO_EFI "${work_dir}/efiboot.img" "${imgsize}"

    # Create the default/fallback boot path in which a boot loaders will be placed later.
    mmd -i "${work_dir}/efiboot.img" ::/EFI ::/EFI/BOOT
}

# Prepare system-boot for booting when written to a disk (isohybrid)
514
_make_bootmode_uefi-x64.systemd-boot.esp() {
515
    local _file efiboot_imgsize
516
    local _available_ucodes=()
517
    _msg_info "Setting up systemd-boot for UEFI booting..."
518

519
520
521
522
523
    for _file in "${ucodes[@]}"; do
        if [[ -e "${pacstrap_dir}/boot/${_file}" ]]; then
            _available_ucodes+=("${pacstrap_dir}/boot/${_file}")
        fi
    done
524
    # Calculate the required FAT image size in bytes
525
    efiboot_imgsize="$(du -bc \
526
527
        "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" \
528
        "${profile}/efiboot/" \
529
530
        "${pacstrap_dir}/boot/vmlinuz-"* \
        "${pacstrap_dir}/boot/initramfs-"*".img" \
531
        "${_available_ucodes[@]}" \
532
533
534
        2>/dev/null | awk 'END { print $1 }')"
    # Create a FAT image for the EFI system partition
    _make_efibootimg "$efiboot_imgsize"
535

536
    # Copy systemd-boot EFI binary to the default/fallback boot path
537
    mcopy -i "${work_dir}/efiboot.img" \
538
        "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" ::/EFI/BOOT/BOOTx64.EFI
539

540
    # Copy systemd-boot configuration files
541
542
    mmd -i "${work_dir}/efiboot.img" ::/loader ::/loader/entries
    mcopy -i "${work_dir}/efiboot.img" "${profile}/efiboot/loader/loader.conf" ::/loader/
543
544
545
546
    for _conf in "${profile}/efiboot/loader/entries/"*".conf"; do
        sed "s|%ARCHISO_LABEL%|${iso_label}|g;
             s|%INSTALL_DIR%|${install_dir}|g;
             s|%ARCH%|${arch}|g" \
547
            "${_conf}" | mcopy -i "${work_dir}/efiboot.img" - "::/loader/entries/${_conf##*/}"
548
    done
549
550

    # shellx64.efi is picked up automatically when on /
551
    if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
552
        mcopy -i "${work_dir}/efiboot.img" \
553
            "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ::/shellx64.efi
554
555
    fi

556
557
    # Copy kernel and initramfs to FAT image.
    # systemd-boot can only access files from the EFI system partition it was launched from.
558
    _make_boot_on_fat
559

560
    _msg_info "Done! systemd-boot set up for UEFI booting successfully."
561
562
}

563
# Prepare system-boot for El Torito booting
564
_make_bootmode_uefi-x64.systemd-boot.eltorito() {
565
566
    # El Torito UEFI boot requires an image containing the EFI system partition.
    # uefi-x64.systemd-boot.eltorito has the same requirements as uefi-x64.systemd-boot.esp
567
    _run_once _make_bootmode_uefi-x64.systemd-boot.esp
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595

    # Additionally set up system-boot in ISO 9660. This allows creating a medium for the live environment by using
    # manual partitioning and simply copying the ISO 9660 file system contents.
    # This is not related to El Torito booting and no firmware uses these files.
    _msg_info "Preparing an /EFI directory for the ISO 9660 file system..."
    install -d -m 0755 -- "${isofs_dir}/EFI/BOOT"

    # Copy systemd-boot EFI binary to the default/fallback boot path
    install -m 0644 -- "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${isofs_dir}/EFI/BOOT/BOOTx64.EFI"

    # Copy systemd-boot configuration files
    install -d -m 0755 -- "${isofs_dir}/loader/entries"
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${isofs_dir}/loader/"
    for _conf in "${profile}/efiboot/loader/entries/"*".conf"; do
        sed "s|%ARCHISO_LABEL%|${iso_label}|g;
             s|%INSTALL_DIR%|${install_dir}|g;
             s|%ARCH%|${arch}|g" \
            "${_conf}" > "${isofs_dir}/loader/entries/${_conf##*/}"
    done

    # edk2-shell based UEFI shell
    # shellx64.efi is picked up automatically when on /
    if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
        install -m 0644 -- "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" "${isofs_dir}/shellx64.efi"
    fi

    _msg_info "Done!"
596
597
}

598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
_validate_requirements_bootmode_bios.syslinux.mbr() {
    # bios.syslinux.mbr requires bios.syslinux.eltorito
    # shellcheck disable=SC2076
    if [[ ! " ${bootmodes[*]} " =~ ' bios.syslinux.eltorito ' ]]; then
        (( validation_error=validation_error+1 ))
        _msg_error "Using 'bios.syslinux.mbr' boot mode without 'bios.syslinux.eltorito' is not supported." 0
    fi

    # Check if the syslinux package is in the package list
    # shellcheck disable=SC2076
    if [[ ! " ${pkg_list[*]} " =~ ' syslinux ' ]]; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating '${bootmode}': The 'syslinux' package is missing from the package list!" 0
    fi

    # Check if syslinux configuration files exist
    if [[ ! -d "${profile}/syslinux" ]]; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating '${bootmode}': The '${profile}/syslinux' directory is missing!" 0
617
    else
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
        local cfgfile
        for cfgfile in "${profile}/syslinux/"*'.cfg'; do
            if [[ -e "${cfgfile}" ]]; then
                break
            else
                (( validation_error=validation_error+1 ))
                _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/syslinux/'!" 0
            fi
        done
    fi

    # Check for optional packages
    # shellcheck disable=SC2076
    if [[ ! " ${pkg_list[*]} " =~ ' memtest86+ ' ]]; then
        _msg_info "Validating '${bootmode}': 'memtest86+' is not in the package list. Memmory testing will not be available from syslinux."
633
    fi
634
}
635

636
_validate_requirements_bootmode_bios.syslinux.eltorito() {
637
    # bios.syslinux.eltorito has the exact same requirements as bios.syslinux.mbr
nl6720's avatar
nl6720 committed
638
    _validate_requirements_bootmode_bios.syslinux.mbr
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
}

_validate_requirements_bootmode_uefi-x64.systemd-boot.esp() {
    # Check if mkfs.fat is available
    if ! command -v mkfs.fat &> /dev/null; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating '${bootmode}': mkfs.fat is not available on this host. Install 'dosfstools'!" 0
    fi

    # Check if mmd and mcopy are available
    if ! { command -v mmd &> /dev/null && command -v mcopy &> /dev/null; }; then
        _msg_error "Validating '${bootmode}': mmd and/or mcopy are not available on this host. Install 'mtools'!" 0
    fi

    # Check if systemd-boot configuration files exist
    if [[ ! -d "${profile}/efiboot/loader/entries" ]]; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating '${bootmode}': The '${profile}/efiboot/loader/entries' directory is missing!" 0
    else
        if [[ ! -e "${profile}/efiboot/loader/loader.conf" ]]; then
            (( validation_error=validation_error+1 ))
            _msg_error "Validating '${bootmode}': File '${profile}/efiboot/loader/loader.conf' not found!" 0
        fi
        local conffile
        for conffile in "${profile}/efiboot/loader/entries/"*'.conf'; do
            if [[ -e "${conffile}" ]]; then
                break
            else
                (( validation_error=validation_error+1 ))
                _msg_error "Validating '${bootmode}': No configuration file found in '${profile}/efiboot/loader/entries/'!" 0
            fi
        done
    fi

    # Check for optional packages
    # shellcheck disable=SC2076
    if [[ ! " ${pkg_list[*]} " =~ ' edk2-shell ' ]]; then
        _msg_info "'edk2-shell' is not in the package list. The ISO will not contain a bootable UEFI shell."
    fi
}

_validate_requirements_bootmode_uefi-x64.systemd-boot.eltorito() {
    # uefi-x64.systemd-boot.eltorito has the exact same requirements as uefi-x64.systemd-boot.esp
    _validate_requirements_bootmode_uefi-x64.systemd-boot.esp
}

# Build airootfs filesystem image
_prepare_airootfs_image() {
    _run_once "_mkairootfs_${airootfs_image_type}"
688
    _mkchecksum
David Runge's avatar
David Runge committed
689
690
691
    if [[ -n "${gpg_key}" ]]; then
        _mksignature
    fi
692
693
}

694
695
696
697
698
699
700
701
702
703
704
705
# export build artifacts for netboot
_export_netboot_artifacts() {
    _msg_info "Exporting netboot artifacts..."
    install -d -m 0755 "${out_dir}"
    cp -a -- "${isofs_dir}/${install_dir}/" "${out_dir}/"
    _msg_info "Done!"
    du -h -- "${out_dir}/${install_dir}"
}

# sign build artifacts for netboot
_sign_netboot_artifacts() {
    local _file _dir
706
    local _files_to_sign=()
707
    _msg_info "Signing netboot artifacts..."
708
709
710
711
712
713
714
    _dir="${isofs_dir}/${install_dir}/boot/"
    for _file in "${ucodes[@]}"; do
        if [[ -e "${_dir}${_file}" ]]; then
            _files_to_sign+=("${_dir}${_file}")
        fi
    done
    for _file in "${_files_to_sign[@]}" "${_dir}${arch}/vmlinuz-"* "${_dir}${arch}/initramfs-"*.img; do
715
716
717
718
719
720
721
722
723
724
725
726
727
        openssl cms \
            -sign \
            -binary \
            -noattr \
            -in "${_file}" \
            -signer "${cert_list[0]}" \
            -inkey "${cert_list[1]}" \
            -outform DER \
            -out "${_file}".ipxe.sig
    done
    _msg_info "Done!"
}

728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
_validate_requirements_airootfs_image_type_squashfs() {
    if ! command -v mksquashfs &> /dev/null; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating '${airootfs_image_type}': mksquashfs is not available on this host. Install 'squashfs-tools'!" 0
    fi
}

_validate_requirements_airootfs_image_type_ext4+squashfs() {
    if ! { command -v mkfs.ext4 &> /dev/null && command -v tune2fs &> /dev/null; }; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating '${airootfs_image_type}': mkfs.ext4 and/or tune2fs is not available on this host. Install 'e2fsprogs'!" 0
    fi
    _validate_requirements_airootfs_image_type_squashfs
}

nl6720's avatar
nl6720 committed
743
744
745
746
747
748
749
_validate_requirements_airootfs_image_type_erofs() {
    if ! command -v mkfs.erofs; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating '${airootfs_image_type}': mkfs.erofs is not available on this host. Install 'erofs-utils'!" 0
    fi
}

750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
_validate_requirements_buildmode_all() {
    if ! command -v pacman &> /dev/null; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating build mode '${_buildmode}': pacman is not available on this host. Install 'pacman'!" 0
    fi
    if ! command -v find &> /dev/null; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating build mode '${_buildmode}': find is not available on this host. Install 'findutils'!" 0
    fi
    if ! command -v gzip &> /dev/null; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating build mode '${_buildmode}': gzip is not available on this host. Install 'gzip'!" 0
    fi
}

_validate_requirements_buildmode_bootstrap() {
    _validate_requirements_buildmode_all
    if ! command -v bsdtar &> /dev/null; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating build mode '${_buildmode}': bsdtar is not available on this host. Install 'libarchive'!" 0
    fi
}

_validate_requirements_buildmode_iso() {
    _validate_requirements_buildmode_all
    if ! command -v awk &> /dev/null; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating build mode '${_buildmode}': awk is not available on this host. Install 'awk'!" 0
    fi
}

781
782
783
784
785
786
787
788
_validate_requirements_buildmode_netboot() {
    _validate_requirements_buildmode_all
    if ! command -v openssl &> /dev/null; then
        (( validation_error=validation_error+1 ))
        _msg_error "Validating build mode '${_buildmode}': openssl is not available on this host. Install 'openssl'!" 0
    fi
}

789
790
791
792
# SYSLINUX El Torito
_add_xorrisofs_options_bios.syslinux.eltorito() {
    xorrisofs_options+=(
        # El Torito boot image for x86 BIOS
nl6720's avatar
nl6720 committed
793
        '-eltorito-boot' 'syslinux/isolinux.bin'
794
        # El Torito boot catalog file
nl6720's avatar
nl6720 committed
795
        '-eltorito-catalog' 'syslinux/boot.cat'
796
797
798
799
800
        # Required options to boot with ISOLINUX
        '-no-emul-boot' '-boot-load-size' '4' '-boot-info-table'
    )
}

801
# SYSLINUX MBR (isohybrid)
802
803
_add_xorrisofs_options_bios.syslinux.mbr() {
    xorrisofs_options+=(
nl6720's avatar
nl6720 committed
804
805
        # SYSLINUX MBR bootstrap code; does not work without "-eltorito-boot syslinux/isolinux.bin"
        '-isohybrid-mbr' "${isofs_dir}/syslinux/isohdpfx.bin"
806
807
        # When GPT is used, create an additional partition in the MBR (besides 0xEE) for sectors 0–1 (MBR
        # bootstrap code area) and mark it as bootable
808
        # May allow booting on some systems
nl6720's avatar
nl6720 committed
809
        # https://wiki.archlinux.org/title/Partitioning#Tricking_old_BIOS_into_booting_from_GPT
810
        '--mbr-force-bootable'
811
        # Move the first partition away from the start of the ISO to match the expectations of partition editors
812
813
814
815
816
817
818
819
820
821
822
823
        # May allow booting on some systems
        # https://dev.lovelyhq.com/libburnia/libisoburn/src/branch/master/doc/partition_offset.wiki
        '-partition_offset' '16'
    )
}

# systemd-boot in an attached EFI system partition
_add_xorrisofs_options_uefi-x64.systemd-boot.esp() {
    # Move the first partition away from the start of the ISO, otherwise the GPT will not be valid and ISO 9660
    # partition will not be mountable
    # shellcheck disable=SC2076
    [[ " ${xorrisofs_options[*]} " =~ ' -partition_offset ' ]] || xorrisofs_options+=('-partition_offset' '16')
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
    # Attach efiboot.img as a second partition and set its partition type to "EFI system partition"
    xorrisofs_options+=('-append_partition' '2' 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B' "${work_dir}/efiboot.img")
    # Ensure GPT is used as some systems do not support UEFI booting without it
    # shellcheck disable=SC2076
    if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.mbr ' ]]; then
        # A valid GPT prevents BIOS booting on some systems, instead use an invalid GPT (without a protective MBR).
        # The attached partition will have the EFI system partition type code in MBR, but in the invalid GPT it will
        # have a Microsoft basic partition type code.
        if [[ ! " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.eltorito ' ]]; then
            # If '-isohybrid-gpt-basdat' is specified before '-e', then the appended EFI system partition will have the
            # EFI system partition type ID/GUID in both MBR and GPT. If '-isohybrid-gpt-basdat' is specified after '-e',
            # the appended EFI system partition will have the Microsoft basic data type GUID in GPT.
            if [[ ! " ${xorrisofs_options[*]} " =~ ' -isohybrid-gpt-basdat ' ]]; then
                xorrisofs_options+=('-isohybrid-gpt-basdat')
            fi
        fi
    else
        # Use valid GPT if BIOS booting support will not be required
        xorrisofs_options+=('-appended_part_as_gpt')
    fi
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
}

# systemd-boot via El Torito
_add_xorrisofs_options_uefi-x64.systemd-boot.eltorito() {
    # shellcheck disable=SC2076
    if [[ " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.esp ' ]]; then
        # systemd-boot in an attached EFI system partition via El Torito
        xorrisofs_options+=(
            # Start a new El Torito boot entry for UEFI
            '-eltorito-alt-boot'
            # Set the second partition as the El Torito UEFI boot image
            '-e' '--interval:appended_partition_2:all::'
            # Boot image is not emulating floppy or hard disk; required for all known boot loaders
            '-no-emul-boot'
        )
859
860
861
862
863
864
865
866
867
        # A valid GPT prevents BIOS booting on some systems, use an invalid GPT instead.
        if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.mbr ' ]]; then
            # If '-isohybrid-gpt-basdat' is specified before '-e', then the appended EFI system partition will have the
            # EFI system partition type ID/GUID in both MBR and GPT. If '-isohybrid-gpt-basdat' is specified after '-e',
            # the appended EFI system partition will have the Microsoft basic data type GUID in GPT.
            if [[ ! " ${xorrisofs_options[*]} " =~ ' -isohybrid-gpt-basdat ' ]]; then
                xorrisofs_options+=('-isohybrid-gpt-basdat')
            fi
        fi
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
    else
        # The ISO will not contain a GPT partition table, so to be able to reference efiboot.img, place it as a
        # file inside the ISO 9660 file system
        install -d -m 0755 -- "${isofs_dir}/EFI/archiso"
        cp -a -- "${work_dir}/efiboot.img" "${isofs_dir}/EFI/archiso/efiboot.img"
        # systemd-boot in an embedded efiboot.img via El Torito
        xorrisofs_options+=(
            # Start a new El Torito boot entry for UEFI
            '-eltorito-alt-boot'
            # Set efiboot.img as the El Torito UEFI boot image
            '-e' 'EFI/archiso/efiboot.img'
            # Boot image is not emulating floppy or hard disk; required for all known boot loaders
            '-no-emul-boot'
        )
    fi
    # Specify where to save the El Torito boot catalog file in case it is not already set by bios.syslinux.eltorito
    # shellcheck disable=SC2076
    [[ " ${bootmodes[*]} " =~ ' bios.' ]] || xorrisofs_options+=('-eltorito-catalog' 'EFI/boot.cat')
}

888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
# Build bootstrap image
_build_bootstrap_image() {
    local _bootstrap_parent
    _bootstrap_parent="$(dirname -- "${pacstrap_dir}")"

    [[ -d "${out_dir}" ]] || install -d -- "${out_dir}"

    cd -- "${_bootstrap_parent}"

    _msg_info "Creating bootstrap image..."
    bsdtar -cf - "root.${arch}" | gzip -cn9 > "${out_dir}/${image_name}"
    _msg_info "Done!"
    du -h -- "${out_dir}/${image_name}"
    cd -- "${OLDPWD}"
}

904
# Build ISO
905
_build_iso_image() {
906
    local xorrisofs_options=()
907
    local bootmode
908

909
910
    [[ -d "${out_dir}" ]] || install -d -- "${out_dir}"

911
    [[ "${quiet}" == "y" ]] && xorrisofs_options+=('-quiet')
912

913
914
915
916
    # Add required xorrisofs options for each boot mode
    for bootmode in "${bootmodes[@]}"; do
        typeset -f "_add_xorrisofs_options_${bootmode}" &> /dev/null && "_add_xorrisofs_options_${bootmode}"
    done
917
918
919
920
921

    _msg_info "Creating ISO image..."
    xorriso -as mkisofs \
            -iso-level 3 \
            -full-iso9660-filenames \
nl6720's avatar
nl6720 committed
922
923
            -joliet \
            -joliet-long \
924
925
926
927
928
929
            -rational-rock \
            -volid "${iso_label}" \
            -appid "${iso_application}" \
            -publisher "${iso_publisher}" \
            -preparer "prepared by ${app_name}" \
            "${xorrisofs_options[@]}" \
930
            -output "${out_dir}/${image_name}" \
931
            "${isofs_dir}/"
932
    _msg_info "Done!"
933
    du -h -- "${out_dir}/${image_name}"
934
935
936
}

# Read profile's values from profiledef.sh
937
_read_profile() {
938
939
940
941
942
943
944
945
    if [[ -z "${profile}" ]]; then
        _msg_error "No profile specified!" 1
    fi
    if [[ ! -d "${profile}" ]]; then
        _msg_error "Profile '${profile}' does not exist!" 1
    elif [[ ! -e "${profile}/profiledef.sh" ]]; then
        _msg_error "Profile '${profile}' is missing 'profiledef.sh'!" 1
    else
946
947
        cd -- "${profile}"

948
949
950
951
        # Source profile's variables
        # shellcheck source=configs/releng/profiledef.sh
        . "${profile}/profiledef.sh"

952
953
954
        # Resolve paths of files that are expected to reside in the profile's directory
        [[ -n "$packages" ]] || packages="${profile}/packages.${arch}"
        packages="$(realpath -- "${packages}")"
955
956
        pacman_conf="$(realpath -- "${pacman_conf}")"

957
958
959
960
961
962
963
        # Resolve paths of files that may reside in the profile's directory
        if [[ -z "$bootstrap_packages" ]] && [[ -e "${profile}/bootstrap_packages.${arch}" ]]; then
            bootstrap_packages="${profile}/bootstrap_packages.${arch}"
            bootstrap_packages="$(realpath -- "${bootstrap_packages}")"
            pacman_conf="$(realpath -- "${pacman_conf}")"
        fi

964
        cd -- "${OLDPWD}"
965
966
    fi
}
967

968
969
# Validate set options
_validate_options() {