mkarchiso 52.7 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
70
71
    fi
}

# Show help usage, with an exit status.
# $1: exit status number.
72
_usage() {
73
    IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true
74
usage: ${app_name} [options] <profile_dir>
75
76
77
78
79
80
81
82
83
84
85
86
  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}'
87
88
89
90
     -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.
91
     -g <gpg_key>     Set the PGP key ID to be used for signing the rootfs image
92
     -h               This message
93
     -m [mode ..]     Build mode(s) to use (valid modes are: 'bootstrap', 'iso' and 'netboot').
94
                      Multiple build modes are provided as quoted, space delimited list.
95
96
     -o <out_dir>     Set the output directory
                      Default: '${out_dir}'
97
     -p [package ..]  Package(s) to install.
98
                      Multiple packages are provided as quoted, space delimited list.
99
100
101
102
103
     -v               Enable verbose output
     -w <work_dir>    Set the working directory
                      Default: '${work_dir}'

  profile_dir:        Directory of the archiso profile to build
104
ENDUSAGETEXT
105
    printf '%s' "${usagetext}"
106
    exit "${1}"
107
108
}

109
# Shows configuration options.
110
_show_config() {
111
    local build_date
112
    build_date="$(date --utc --iso-8601=seconds -d "@${SOURCE_DATE_EPOCH}")"
113
    _msg_info "${app_name} configuration settings"
114
115
116
    _msg_info "             Architecture:   ${arch}"
    _msg_info "        Working directory:   ${work_dir}"
    _msg_info "   Installation directory:   ${install_dir}"
117
118
    _msg_info "               Build date:   ${build_date}"
    _msg_info "         Output directory:   ${out_dir}"
119
120
    _msg_info "       Current build mode:   ${buildmode}"
    _msg_info "              Build modes:   ${buildmodes[*]}"
121
    _msg_info "                  GPG key:   ${gpg_key:-None}"
122
    _msg_info "Code signing certificates:   ${cert_list[*]}"
123
124
    _msg_info "                  Profile:   ${profile}"
    _msg_info "Pacman configuration file:   ${pacman_conf}"
125
    _msg_info "          Image file name:   ${image_name:-None}"
126
127
128
129
    _msg_info "         ISO volume label:   ${iso_label}"
    _msg_info "            ISO publisher:   ${iso_publisher}"
    _msg_info "          ISO application:   ${iso_application}"
    _msg_info "               Boot modes:   ${bootmodes[*]}"
130
131
    _msg_info "            Packages File:   ${buildmode_packages}"
    _msg_info "                 Packages:   ${buildmode_pkg_list[*]}"
132
133
}

134
# Cleanup airootfs
135
136
_cleanup_pacstrap_dir() {
    _msg_info "Cleaning up in pacstrap location..."
137

138
    # Delete all files in /boot
139
    [[ -d "${pacstrap_dir}/boot" ]] && find "${pacstrap_dir}/boot" -mindepth 1 -delete
140
    # Delete pacman database sync cache files (*.tar.gz)
141
    [[ -d "${pacstrap_dir}/var/lib/pacman" ]] && find "${pacstrap_dir}/var/lib/pacman" -maxdepth 1 -type f -delete
142
    # Delete pacman database sync cache
143
    [[ -d "${pacstrap_dir}/var/lib/pacman/sync" ]] && find "${pacstrap_dir}/var/lib/pacman/sync" -delete
144
    # Delete pacman package cache
145
    [[ -d "${pacstrap_dir}/var/cache/pacman/pkg" ]] && find "${pacstrap_dir}/var/cache/pacman/pkg" -type f -delete
146
    # Delete all log files, keeps empty dirs.
147
    [[ -d "${pacstrap_dir}/var/log" ]] && find "${pacstrap_dir}/var/log" -type f -delete
148
    # Delete all temporary files and dirs
149
    [[ -d "${pacstrap_dir}/var/tmp" ]] && find "${pacstrap_dir}/var/tmp" -mindepth 1 -delete
150
    # Delete package pacman related files.
151
    find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete
152
    # Create an empty /etc/machine-id
153
154
    rm -f -- "${pacstrap_dir}/etc/machine-id"
    printf '' > "${pacstrap_dir}/etc/machine-id"
155
156

    _msg_info "Done!"
157
}
158

159
160
# Create a squashfs image and place it in the ISO 9660 file system.
# $@: options to pass to mksquashfs
161
162
163
164
_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
165
    else
166
        mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}"
167
168
169
    fi
}

170
171
# 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.
172
_mkairootfs_ext4+squashfs() {
173
    local ext4_hash_seed mkfs_ext4_options=()
174
    [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
175

176
177
178
179
180
181
182
183
184
185
186
187
188
189
    _msg_info "Creating ext4 image of 32 GiB and copying '${pacstrap_dir}/' to it..."

    ext4_hash_seed="$(uuidgen --sha1 --namespace 93a870ff-8565-4cf3-a67b-f47299271a96 \
        --name "${SOURCE_DATE_EPOCH} ext4 hash seed")"
    mkfs_ext4_options=(
        '-d' "${pacstrap_dir}"
        '-O' '^has_journal,^resize_inode'
        '-E' "lazy_itable_init=0,root_owner=0:0,hash_seed=${ext4_hash_seed}"
        '-m' '0'
        '-F'
        '-U' 'clear'
    )
    [[ ! "${quiet}" == "y" ]] || mkfs_ext4_options+=('-q')
    E2FSPROGS_FAKE_TIME="${SOURCE_DATE_EPOCH}" mkfs.ext4 "${mkfs_ext4_options[@]}" -- "${pacstrap_dir}.img" 32G
190
    tune2fs -c 0 -i 0 -- "${pacstrap_dir}.img" > /dev/null
191
    _msg_info "Done!"
192

193
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
194
    _msg_info "Creating SquashFS image, this may take some time..."
195
    _run_mksquashfs "${pacstrap_dir}.img"
196
    _msg_info "Done!"
197
    rm -- "${pacstrap_dir}.img"
198
199
}

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

204
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
205
    _msg_info "Creating SquashFS image, this may take some time..."
206
    _run_mksquashfs "${pacstrap_dir}"
nl6720's avatar
nl6720 committed
207
208
}

209
# Create an EROFS image containing the root file system and saves it on the ISO 9660 file system.
nl6720's avatar
nl6720 committed
210
211
_mkairootfs_erofs() {
    local fsuuid
212
    [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
nl6720's avatar
nl6720 committed
213
214
215
216
217
218

    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..."
219
    mkfs.erofs -U "${fsuuid}" "${airootfs_image_tool_options[@]}" -- "${image_path}" "${pacstrap_dir}"
220
221
222
    _msg_info "Done!"
}

223
# Create checksum file for the rootfs image.
224
_mkchecksum() {
225
    _msg_info "Creating checksum file for self-test..."
226
    cd -- "${isofs_dir}/${install_dir}/${arch}"
nl6720's avatar
nl6720 committed
227
228
229
230
231
    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
232
    cd -- "${OLDPWD}"
233
    _msg_info "Done!"
234
235
}

236
# GPG sign the root file system image.
237
_mksignature() {
238
    _msg_info "Signing rootfs image..."
239
    cd -- "${isofs_dir}/${install_dir}/${arch}"
240
    # always use the .sig file extension, as that is what mkinitcpio-archiso's hooks expect
nl6720's avatar
nl6720 committed
241
    if [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" ]]; then
242
        gpg --output airootfs.sfs.sig --detach-sign --default-key "${gpg_key}" airootfs.sfs
nl6720's avatar
nl6720 committed
243
    elif [[ -e "${isofs_dir}/${install_dir}/${arch}/airootfs.erofs" ]]; then
244
        gpg --output airootfs.erofs.sig --detach-sign --default-key "${gpg_key}" airootfs.erofs
nl6720's avatar
nl6720 committed
245
    fi
246
    cd -- "${OLDPWD}"
247
248
249
    _msg_info "Done!"
}

250
# Helper function to run functions only one time.
251
# $1: function name
252
_run_once() {
253
    if [[ ! -e "${work_dir}/${run_once_mode}.${1}" ]]; then
254
        "$1"
255
        touch "${work_dir}/${run_once_mode}.${1}"
256
257
    fi
}
258

259
# Set up custom pacman.conf with custom cache and pacman hook directories.
260
_make_pacman_conf() {
261
262
263
264
    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' ' ')"

265
    # Only use the profile's CacheDir, if it is not the default and not the same as the system cache dir.
266
267
268
269
270
271
272
273
274
    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}"
275
    # take the profile pacman.conf and strip all settings that would break in chroot when using pacman -r
276
277
    # append CacheDir and HookDir to [options] section
    # HookDir is *always* set to the airootfs' override directory
278
279
280
    # 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}
281
        /\[options\]/a HookDir = ${pacstrap_dir}/etc/pacman.d/hooks/" > "${work_dir}/${buildmode}.pacman.conf"
282
283
}

284
# Prepare working directory and copy custom root file system files.
285
286
_make_custom_airootfs() {
    local passwd=()
287
    local filename permissions
288

289
    install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir}"
290

291
    if [[ -d "${profile}/airootfs" ]]; then
292
        _msg_info "Copying custom airootfs files..."
293
        cp -af --no-preserve=ownership,mode -- "${profile}/airootfs/." "${pacstrap_dir}"
294
295
296
        # Set ownership and mode for files and directories
        for filename in "${!file_permissions[@]}"; do
            IFS=':' read -ra permissions <<< "${file_permissions["${filename}"]}"
297
298
299
            # 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
300
            # Warn if the file does not exist
301
302
            elif [[ ! -e "${pacstrap_dir}${filename}" ]]; then
                _msg_warning "Cannot change permissions of '${pacstrap_dir}${filename}'. The file or directory does not exist."
303
            else
304
                if [[ "${filename: -1}" == "/" ]]; then
305
306
                    chown -fhR -- "${permissions[0]}:${permissions[1]}" "${pacstrap_dir}${filename}"
                    chmod -fR -- "${permissions[2]}" "${pacstrap_dir}${filename}"
307
                else
308
309
                    chown -fh -- "${permissions[0]}:${permissions[1]}" "${pacstrap_dir}${filename}"
                    chmod -f -- "${permissions[2]}" "${pacstrap_dir}${filename}"
310
                fi
311
312
            fi
        done
313
        _msg_info "Done!"
314
315
316
    fi
}

317
# Install desired packages to the root file system
318
_make_packages() {
319
    _msg_info "Installing packages to '${pacstrap_dir}/'..."
320

321
322
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg"
323
        export ARCHISO_GNUPG_FD
324
    fi
325

326
    # Unset TMPDIR to work around https://bugs.archlinux.org/task/70580
327
    if [[ "${quiet}" = "y" ]]; then
328
        env -u TMPDIR pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}" &> /dev/null
329
    else
330
        env -u TMPDIR pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}"
331
332
    fi

333
334
335
336
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<&-
        unset ARCHISO_GNUPG_FD
    fi
337
338

    _msg_info "Done! Packages installed successfully."
339
340
}

341
# Customize installation.
342
343
_make_customize_airootfs() {
    local passwd=()
344

345
    if [[ -e "${profile}/airootfs/etc/passwd" ]]; then
346
        _msg_info "Copying /etc/skel/* to user homes..."
347
        while IFS=':' read -a passwd -r; do
348
            # Only operate on UIDs in range 1000–59999
349
            (( passwd[2] >= 1000 && passwd[2] < 60000 )) || continue
350
            # Skip invalid home directories
351
352
            [[ "${passwd[5]}" == '/' ]] && continue
            [[ -z "${passwd[5]}" ]] && continue
353
354
355
356
            # 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]}"
357
                fi
358
359
360
                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]}"
361
            else
362
                _msg_error "Failed to set permissions on '${pacstrap_dir}${passwd[5]}'. Outside of valid path." 1
363
            fi
364
        done < "${profile}/airootfs/etc/passwd"
365
        _msg_info "Done!"
366
367
    fi

368
369
    if [[ -e "${pacstrap_dir}/root/customize_airootfs.sh" ]]; then
        _msg_info "Running customize_airootfs.sh in '${pacstrap_dir}' chroot..."
370
        _msg_warning "customize_airootfs.sh is deprecated! Support for it will be removed in a future archiso version."
371
        chmod -f -- +x "${pacstrap_dir}/root/customize_airootfs.sh"
372
373
        # Unset TMPDIR to work around https://bugs.archlinux.org/task/70580
        eval -- env -u TMPDIR arch-chroot "${pacstrap_dir}" "/root/customize_airootfs.sh"
374
        rm -- "${pacstrap_dir}/root/customize_airootfs.sh"
375
        _msg_info "Done! customize_airootfs.sh run successfully."
376
377
378
    fi
}

379
380
381
382
# Set up boot loaders
_make_bootmodes() {
    local bootmode
    for bootmode in "${bootmodes[@]}"; do
383
        _run_once "_make_bootmode_${bootmode}"
384
385
386
    done
}

387
# Copy kernel and initramfs to ISO 9660
388
_make_boot_on_iso9660() {
389
    local ucode_image
Christian Hesse's avatar
Christian Hesse committed
390
    _msg_info "Preparing kernel and initramfs for the ISO 9660 file system..."
391
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/${arch}"
392
393
    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}/"
394

395
    for ucode_image in "${ucodes[@]}"; do
396
397
398
        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
399
                install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/"
400
                install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/${ucode_image%.*}/"* \
401
402
403
404
                    "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/"
            fi
        fi
    done
405
    _msg_info "Done!"
406
407
}

408
# Prepare syslinux for booting from MBR (isohybrid)
409
_make_bootmode_bios.syslinux.mbr() {
410
    _msg_info "Setting up SYSLINUX for BIOS booting from a disk..."
nl6720's avatar
nl6720 committed
411
    install -d -m 0755 -- "${isofs_dir}/syslinux"
412
413
    for _cfg in "${profile}/syslinux/"*.cfg; do
        sed "s|%ARCHISO_LABEL%|${iso_label}|g;
414
415
             s|%INSTALL_DIR%|${install_dir}|g;
             s|%ARCH%|${arch}|g" \
nl6720's avatar
nl6720 committed
416
             "${_cfg}" > "${isofs_dir}/syslinux/${_cfg##*/}"
417
    done
418
    if [[ -e "${profile}/syslinux/splash.png" ]]; then
nl6720's avatar
nl6720 committed
419
        install -m 0644 -- "${profile}/syslinux/splash.png" "${isofs_dir}/syslinux/"
420
    fi
421
422
423
    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/"
424

425
    _run_once _make_boot_on_iso9660
426

nl6720's avatar
nl6720 committed
427
428
    if [[ -e "${isofs_dir}/syslinux/hdt.c32" ]]; then
        install -d -m 0755 -- "${isofs_dir}/syslinux/hdt"
429
430
        if [[ -e "${pacstrap_dir}/usr/share/hwdata/pci.ids" ]]; then
            gzip -cn9 "${pacstrap_dir}/usr/share/hwdata/pci.ids" > \
nl6720's avatar
nl6720 committed
431
                "${isofs_dir}/syslinux/hdt/pciids.gz"
432
        fi
433
        find "${pacstrap_dir}/usr/lib/modules" -name 'modules.alias' -print -exec gzip -cn9 '{}' ';' -quit > \
nl6720's avatar
nl6720 committed
434
            "${isofs_dir}/syslinux/hdt/modalias.gz"
435
    fi
436
437

    # Add other aditional/extra files to ${install_dir}/boot/
438
    if [[ -e "${pacstrap_dir}/boot/memtest86+/memtest.bin" ]]; then
nl6720's avatar
nl6720 committed
439
        # rename for PXE: https://wiki.archlinux.org/title/Syslinux#Using_memtest
440
        install -m 0644 -- "${pacstrap_dir}/boot/memtest86+/memtest.bin" "${isofs_dir}/${install_dir}/boot/memtest"
441
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
442
        install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/common/GPL2/license.txt" \
443
444
            "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
    fi
445
    _msg_info "Done! SYSLINUX set up for BIOS booting from a disk successfully."
446
447
}

448
# Prepare syslinux for El-Torito booting
449
_make_bootmode_bios.syslinux.eltorito() {
450
    _msg_info "Setting up SYSLINUX for BIOS booting from an optical disc..."
nl6720's avatar
nl6720 committed
451
    install -d -m 0755 -- "${isofs_dir}/syslinux"
452
453
    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/"
454

nl6720's avatar
nl6720 committed
455
    # ISOLINUX and SYSLINUX installation is shared
456
    _run_once _make_bootmode_bios.syslinux.mbr
457
458

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

461
# Copy kernel and initramfs to FAT image
462
_make_boot_on_fat() {
463
    local ucode_image all_ucode_images=()
Christian Hesse's avatar
Christian Hesse committed
464
    _msg_info "Preparing kernel and initramfs for the FAT file system..."
465
    mmd -i "${work_dir}/efiboot.img" \
466
        "::/${install_dir}" "::/${install_dir}/boot" "::/${install_dir}/boot/${arch}"
467
468
    mcopy -i "${work_dir}/efiboot.img" "${pacstrap_dir}/boot/vmlinuz-"* \
        "${pacstrap_dir}/boot/initramfs-"*".img" "::/${install_dir}/boot/${arch}/"
469
470
471
    for ucode_image in "${ucodes[@]}"; do
        if [[ -e "${pacstrap_dir}/boot/${ucode_image}" ]]; then
            all_ucode_images+=("${pacstrap_dir}/boot/${ucode_image}")
472
473
474
        fi
    done
    if (( ${#all_ucode_images[@]} )); then
475
        mcopy -i "${work_dir}/efiboot.img" "${all_ucode_images[@]}" "::/${install_dir}/boot/"
476
    fi
477
    _msg_info "Done!"
478
479
}

480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# 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)
502
_make_bootmode_uefi-x64.systemd-boot.esp() {
503
    local _file efiboot_imgsize
504
    local _available_ucodes=()
505
    _msg_info "Setting up systemd-boot for UEFI booting..."
506

507
508
509
510
511
    for _file in "${ucodes[@]}"; do
        if [[ -e "${pacstrap_dir}/boot/${_file}" ]]; then
            _available_ucodes+=("${pacstrap_dir}/boot/${_file}")
        fi
    done
512
    # Calculate the required FAT image size in bytes
513
    efiboot_imgsize="$(du -bc \
514
515
        "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" \
516
        "${profile}/efiboot/" \
517
518
        "${pacstrap_dir}/boot/vmlinuz-"* \
        "${pacstrap_dir}/boot/initramfs-"*".img" \
519
        "${_available_ucodes[@]}" \
520
521
522
        2>/dev/null | awk 'END { print $1 }')"
    # Create a FAT image for the EFI system partition
    _make_efibootimg "$efiboot_imgsize"
523

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

528
    # Copy systemd-boot configuration files
529
530
    mmd -i "${work_dir}/efiboot.img" ::/loader ::/loader/entries
    mcopy -i "${work_dir}/efiboot.img" "${profile}/efiboot/loader/loader.conf" ::/loader/
531
532
533
534
    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" \
535
            "${_conf}" | mcopy -i "${work_dir}/efiboot.img" - "::/loader/entries/${_conf##*/}"
536
    done
537
538

    # shellx64.efi is picked up automatically when on /
539
    if [[ -e "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
540
        mcopy -i "${work_dir}/efiboot.img" \
541
            "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ::/shellx64.efi
542
543
    fi

544
545
    # Copy kernel and initramfs to FAT image.
    # systemd-boot can only access files from the EFI system partition it was launched from.
546
    _make_boot_on_fat
547

548
    _msg_info "Done! systemd-boot set up for UEFI booting successfully."
549
550
}

551
# Prepare system-boot for El Torito booting
552
_make_bootmode_uefi-x64.systemd-boot.eltorito() {
553
554
    # 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
555
    _run_once _make_bootmode_uefi-x64.systemd-boot.esp
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583

    # 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!"
584
585
}

586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
_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
605
    else
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
        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."
621
    fi
622
}
623

624
_validate_requirements_bootmode_bios.syslinux.eltorito() {
625
    # bios.syslinux.eltorito has the exact same requirements as bios.syslinux.mbr
nl6720's avatar
nl6720 committed
626
    _validate_requirements_bootmode_bios.syslinux.mbr
627
628
629
630
631
632
633
634
635
636
637
638
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
}

_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}"
676
    _mkchecksum
David Runge's avatar
David Runge committed
677
678
679
    if [[ -n "${gpg_key}" ]]; then
        _mksignature
    fi
680
681
}

682
683
684
685
686
687
688
689
690
691
692
693
# 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
694
    local _files_to_sign=()
695
    _msg_info "Signing netboot artifacts..."
696
697
698
699
700
701
702
    _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
703
704
705
706
707
708
709
710
711
712
713
714
715
        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!"
}

716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
_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
731
732
733
734
735
736
737
_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
}

738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
_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
}

769
770
771
772
773
774
775
776
_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
}

777
778
779
780
# SYSLINUX El Torito
_add_xorrisofs_options_bios.syslinux.eltorito() {
    xorrisofs_options+=(
        # El Torito boot image for x86 BIOS
nl6720's avatar
nl6720 committed
781
        '-eltorito-boot' 'syslinux/isolinux.bin'
782
        # El Torito boot catalog file
nl6720's avatar
nl6720 committed
783
        '-eltorito-catalog' 'syslinux/boot.cat'
784
785
786
787
788
        # Required options to boot with ISOLINUX
        '-no-emul-boot' '-boot-load-size' '4' '-boot-info-table'
    )
}

789
# SYSLINUX MBR (isohybrid)
790
791
_add_xorrisofs_options_bios.syslinux.mbr() {
    xorrisofs_options+=(
nl6720's avatar
nl6720 committed
792
793
        # SYSLINUX MBR bootstrap code; does not work without "-eltorito-boot syslinux/isolinux.bin"
        '-isohybrid-mbr' "${isofs_dir}/syslinux/isohdpfx.bin"
794
795
        # 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
796
        # May allow booting on some systems
nl6720's avatar
nl6720 committed
797
        # https://wiki.archlinux.org/title/Partitioning#Tricking_old_BIOS_into_booting_from_GPT
798
        '--mbr-force-bootable'
799
        # Move the first partition away from the start of the ISO to match the expectations of partition editors
800
801
802
803
804
805
806
807
808
809
810
811
        # 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')
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
    # 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
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
}

# 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'
        )
847
848
849
850
851
852
853
854
855
        # 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
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
    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')
}

876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
# 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}"
}

892
# Build ISO
893
_build_iso_image() {
894
    local xorrisofs_options=()
895
    local bootmode
896

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

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

901
902
903
904
    # 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
905
906
907
908
909

    _msg_info "Creating ISO image..."
    xorriso -as mkisofs \
            -iso-level 3 \
            -full-iso9660-filenames \
nl6720's avatar
nl6720 committed
910
911
            -joliet \
            -joliet-long \
912
913
914
915
916
917
            -rational-rock \
            -volid "${iso_label}" \
            -appid "${iso_application}" \
            -publisher "${iso_publisher}" \
            -preparer "prepared by ${app_name}" \
            "${xorrisofs_options[@]}" \
918
            -output "${out_dir}/${image_name}" \
919
            "${isofs_dir}/"
920
    _msg_info "Done!"
921
    du -h -- "${out_dir}/${image_name}"
922
923
924
}

# Read profile's values from profiledef.sh
925
_read_profile() {
926
927
928
929
930
931
932
933
    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
934
935
        cd -- "${profile}"

936
937
938
939
        # Source profile's variables
        # shellcheck source=configs/releng/profiledef.sh
        . "${profile}/profiledef.sh"

940
941
942
        # Resolve paths of files that are expected to reside in the profile's directory
        [[ -n "$packages" ]] || packages="${profile}/packages.${arch}"
        packages="$(realpath -- "${packages}")"
943
944
        pacman_conf="$(realpath -- "${pacman_conf}")"

945
946
947
948
949
950
951
        # 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

952
        cd -- "${OLDPWD}"
953
954
    fi
}
955

956
957
# Validate set options
_validate_options() {
958
    local validation_error=0 bootmode _cert _buildmode
959
    local pkg_list_from_file=()
960
    local bootstrap_pkg_list_from_file=()
961
    local _override_cert_list=()
962

963
964
965
966
967
968
    _msg_info "Validating options..."
    # Check if the package list file exists and read packages from it
    if [[ -e "${packages}" ]]; then
        mapfile -t pkg_list_from_file < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}")
        pkg_list+=("${pkg_list_from_file[@]}")
        if (( ${#pkg_list_from_file} < 1 )); then
969
            (( validation_error=validation_error+1 ))
970
            _msg_error "No package specified in '${packages}'." 0
971
        fi
972
973
    else
        (( validation_error=validation_error+1 ))
974
975
        _msg_error "Packages file '${packages}' does not exist." 0
    fi
976

977
978
979
980
981
982
983
984
985
986
987
988
989
990
    # Check if packages for the bootstrap image are specified
    if [[ "${buildmodes[*]}" == *bootstrap* ]]; then
        if [[ -e "${bootstrap_packages}" ]]; then
            mapfile -t bootstrap_pkg_list_from_file < \
                <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${bootstrap_packages}")
            bootstrap_pkg_list+=("${bootstrap_pkg_list_from_file[@]}")
            if (( ${#bootstrap_pkg_list_from_file} < 1 )); then
                (( validation_error=validation_error+1 ))
                _msg_error "No package specified in '${bootstrap_packages}'." 0
            fi
        else
            (( validation_error=validation_error+1 ))
            _msg_error "Bootstrap packages file '${bootstrap_packages}' does not exist." 0
        fi
991
    fi