mkarchiso 51.3 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
9
10
# Control the environment
umask 0022
export LANG="C"
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


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

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

56
57
58
59
60
61
# 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}
62
    printf '[%s] ERROR: %s\n' "${app_name}" "${_msg}" >&2
63
    if (( _error > 0 )); then
64
        exit "${_error}"
65
66
67
    fi
}

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

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

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

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

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

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

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

    _msg_info "Done!"
171
}
172

173
174
175
176
_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
177
    else
178
        mksquashfs "$@" "${image_path}" -noappend "${airootfs_image_tool_options[@]}"
179
180
181
    fi
}

182
# Makes a ext4 filesystem inside a SquashFS from a source directory.
183
_mkairootfs_ext4+squashfs() {
184
    [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
185

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

207
# Makes a SquashFS filesystem from a source directory.
208
_mkairootfs_squashfs() {
209
    [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
210

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

# Makes an EROFS file system from a source directory.
_mkairootfs_erofs() {
    local fsuuid
219
    [[ -e "${pacstrap_dir}" ]] || _msg_error "The path '${pacstrap_dir}' does not exist" 1
nl6720's avatar
nl6720 committed
220
221
222
223
224
225

    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..."
226
    mkfs.erofs -U "${fsuuid}" "${airootfs_image_tool_options[@]}" -- "${image_path}" "${pacstrap_dir}"
227
228
229
    _msg_info "Done!"
}

230
_mkchecksum() {
231
    _msg_info "Creating checksum file for self-test..."
232
    cd -- "${isofs_dir}/${install_dir}/${arch}"
nl6720's avatar
nl6720 committed
233
234
235
236
237
    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
238
    cd -- "${OLDPWD}"
239
    _msg_info "Done!"
240
241
}

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

255
256
# Helper function to run functions only one time.
_run_once() {
257
    if [[ ! -e "${work_dir}/${run_once_mode}.${1}" ]]; then
258
        "$1"
259
        touch "${work_dir}/${run_once_mode}.${1}"
260
261
    fi
}
262

263
# Set up custom pacman.conf with custom cache and pacman hook directories
264
_make_pacman_conf() {
265
266
267
268
269
270
271
272
273
274
275
276
277
278
    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' ' ')"

    # only use the profile's CacheDir, if it is not the default and not the same as the system cache dir
    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}"
279
    # take the profile pacman.conf and strip all settings that would break in chroot when using pacman -r
280
281
    # append CacheDir and HookDir to [options] section
    # HookDir is *always* set to the airootfs' override directory
282
283
284
    # 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}
285
        /\[options\]/a HookDir = ${pacstrap_dir}/etc/pacman.d/hooks/" > "${work_dir}/${buildmode}.pacman.conf"
286
287
288
289
290
}

# Prepare working directory and copy custom airootfs files (airootfs)
_make_custom_airootfs() {
    local passwd=()
291
    local filename permissions
292

293
    install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir}"
294

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

321
# Install desired packages to airootfs
322
_make_packages() {
323
    local envvars_in_chroot=("SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}")
324
    _msg_info "Installing packages to '${pacstrap_dir}/'..."
325

326
327
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg"
328
        envvars_in_chroot+=("ARCHISO_GNUPG_FD=${ARCHISO_GNUPG_FD}")
329
    fi
330
331

    if [[ "${quiet}" = "y" ]]; then
332
        env -i "${envvars_in_chroot[@]}" pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}" &> /dev/null
333
    else
334
        env -i "${envvars_in_chroot[@]}" pacstrap -C "${work_dir}/${buildmode}.pacman.conf" -c -G -M -- "${pacstrap_dir}" "${buildmode_pkg_list[@]}"
335
336
    fi

337
338
339
340
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<&-
        unset ARCHISO_GNUPG_FD
    fi
341
342

    _msg_info "Done! Packages installed successfully."
343
344
345
346
347
}

# Customize installation (airootfs)
_make_customize_airootfs() {
    local passwd=()
348

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

372
373
    if [[ -e "${pacstrap_dir}/root/customize_airootfs.sh" ]]; then
        _msg_info "Running customize_airootfs.sh in '${pacstrap_dir}' chroot..."
374
        _msg_warning "customize_airootfs.sh is deprecated! Support for it will be removed in a future archiso version."
375
        chmod -f -- +x "${pacstrap_dir}/root/customize_airootfs.sh"
376
        eval -- env -i "SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH}" arch-chroot "${pacstrap_dir}" "/root/customize_airootfs.sh"
377
        rm -- "${pacstrap_dir}/root/customize_airootfs.sh"
378
        _msg_info "Done! customize_airootfs.sh run successfully."
379
380
381
    fi
}

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

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

    for ucode_image in {intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio}; do
399
400
401
        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
402
                install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/"
403
                install -m 0644 -- "${pacstrap_dir}/usr/share/licenses/${ucode_image%.*}/"* \
404
405
406
407
                    "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/"
            fi
        fi
    done
408
    _msg_info "Done!"
409
410
}

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

428
    _run_once _make_boot_on_iso9660
429

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

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

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

nl6720's avatar
nl6720 committed
458
    # ISOLINUX and SYSLINUX installation is shared
459
    _run_once _make_bootmode_bios.syslinux.mbr
460
461

    _msg_info "Done! SYSLINUX set up for BIOS booting from an optical disc successfully."
462
463
464
}

# Prepare /EFI on ISO-9660
465
_make_efi_dir_on_iso9660() {
466
467
    _msg_info "Preparing an /EFI directory for the ISO 9660 file system..."
    install -d -m 0755 -- "${isofs_dir}/EFI/BOOT"
468
    install -m 0644 -- "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
469
470
        "${isofs_dir}/EFI/BOOT/BOOTx64.EFI"

471
    install -d -m 0755 -- "${isofs_dir}/loader/entries"
472
473
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${isofs_dir}/loader/"

474
475
476
477
478
479
    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
480
481
482

    # edk2-shell based UEFI shell
    # shellx64.efi is picked up automatically when on /
483
484
    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"
485
    fi
486
    _msg_info "Done!"
487
488
}

489
490
# Prepare kernel/initramfs on efiboot.img
_make_boot_on_fat() {
491
    local ucode_image all_ucode_images=()
Christian Hesse's avatar
Christian Hesse committed
492
    _msg_info "Preparing kernel and initramfs for the FAT file system..."
493
    mmd -i "${work_dir}/efiboot.img" \
494
        "::/${install_dir}" "::/${install_dir}/boot" "::/${install_dir}/boot/${arch}"
495
496
    mcopy -i "${work_dir}/efiboot.img" "${pacstrap_dir}/boot/vmlinuz-"* \
        "${pacstrap_dir}/boot/initramfs-"*".img" "::/${install_dir}/boot/${arch}/"
497
    for ucode_image in \
498
        "${pacstrap_dir}/boot/"{intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio}
499
500
501
502
503
504
    do
        if [[ -e "${ucode_image}" ]]; then
            all_ucode_images+=("${ucode_image}")
        fi
    done
    if (( ${#all_ucode_images[@]} )); then
505
        mcopy -i "${work_dir}/efiboot.img" "${all_ucode_images[@]}" "::/${install_dir}/boot/"
506
    fi
507
    _msg_info "Done!"
508
509
510
}

# Prepare efiboot.img::/EFI for EFI boot mode
511
_make_bootmode_uefi-x64.systemd-boot.esp() {
512
    local efiboot_imgsize="0"
513
    _msg_info "Setting up systemd-boot for UEFI booting..."
514

515
516
    # the required image size in KiB (rounded up to the next full MiB with an additional MiB for reserved sectors)
    efiboot_imgsize="$(du -bc \
517
518
        "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" \
519
        "${profile}/efiboot/" \
520
521
522
        "${pacstrap_dir}/boot/vmlinuz-"* \
        "${pacstrap_dir}/boot/initramfs-"*".img" \
        "${pacstrap_dir}/boot/"{intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio} \
523
524
525
526
527
        2>/dev/null | 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))}'
        )"
528
529
    # 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
530
    [[ -e "${work_dir}/efiboot.img" ]] && rm -f -- "${work_dir}/efiboot.img"
531
    _msg_info "Creating FAT image of size: ${efiboot_imgsize} KiB..."
532
    mkfs.fat -C -n ARCHISO_EFI "${work_dir}/efiboot.img" "$efiboot_imgsize"
533

534
535
    mmd -i "${work_dir}/efiboot.img" ::/EFI ::/EFI/BOOT
    mcopy -i "${work_dir}/efiboot.img" \
536
        "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" ::/EFI/BOOT/BOOTx64.EFI
537

538
539
    mmd -i "${work_dir}/efiboot.img" ::/loader ::/loader/entries
    mcopy -i "${work_dir}/efiboot.img" "${profile}/efiboot/loader/loader.conf" ::/loader/
540
541
542
543
    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" \
544
            "${_conf}" | mcopy -i "${work_dir}/efiboot.img" - "::/loader/entries/${_conf##*/}"
545
    done
546
547

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

    # Copy kernel and initramfs
554
    _make_boot_on_fat
555

556
    _msg_info "Done! systemd-boot set up for UEFI booting successfully."
557
558
}

559
# Prepare efiboot.img::/EFI for "El Torito" EFI boot mode
560
561
562
563
_make_bootmode_uefi-x64.systemd-boot.eltorito() {
    _run_once _make_bootmode_uefi-x64.systemd-boot.esp
    # Set up /EFI on ISO-9660 to allow preparing an installation medium by manually copying files
    _run_once _make_efi_dir_on_iso9660
564
565
}

566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
_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
585
    else
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
        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."
601
    fi
602
}
603

604
_validate_requirements_bootmode_bios.syslinux.eltorito() {
nl6720's avatar
nl6720 committed
605
    _validate_requirements_bootmode_bios.syslinux.mbr
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
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
}

_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}"
655
    _mkchecksum
David Runge's avatar
David Runge committed
656
657
658
    if [[ -n "${gpg_key}" ]]; then
        _mksignature
    fi
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
688
# 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
    _msg_info "Signing netboot artifacts..."
    _dir="${isofs_dir}/${install_dir}/"
    for _file in "${_dir}/boot/"*ucode.img "${_dir}/boot/${arch}/vmlinuz-"* "${_dir}/boot/${arch}/initramfs-"*.img; do
        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!"
}

689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
_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
704
705
706
707
708
709
710
_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
}

711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
_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
}

742
743
744
745
746
747
748
749
_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
}

750
751
752
753
# SYSLINUX El Torito
_add_xorrisofs_options_bios.syslinux.eltorito() {
    xorrisofs_options+=(
        # El Torito boot image for x86 BIOS
nl6720's avatar
nl6720 committed
754
        '-eltorito-boot' 'syslinux/isolinux.bin'
755
        # El Torito boot catalog file
nl6720's avatar
nl6720 committed
756
        '-eltorito-catalog' 'syslinux/boot.cat'
757
758
759
760
761
762
763
764
        # Required options to boot with ISOLINUX
        '-no-emul-boot' '-boot-load-size' '4' '-boot-info-table'
    )
}

# SYSLINUX MBR
_add_xorrisofs_options_bios.syslinux.mbr() {
    xorrisofs_options+=(
nl6720's avatar
nl6720 committed
765
766
        # SYSLINUX MBR bootstrap code; does not work without "-eltorito-boot syslinux/isolinux.bin"
        '-isohybrid-mbr' "${isofs_dir}/syslinux/isohdpfx.bin"
767
768
        # 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
769
        # May allow booting on some systems
nl6720's avatar
nl6720 committed
770
        # https://wiki.archlinux.org/title/Partitioning#Tricking_old_BIOS_into_booting_from_GPT
771
        '--mbr-force-bootable'
772
        # Move the first partition away from the start of the ISO to match the expectations of partition editors
773
774
775
776
777
778
779
780
781
782
783
784
        # 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')
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
    # 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
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
}

# 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'
        )
820
821
822
823
824
825
826
827
828
        # 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
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
    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')
}

849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
# 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}"
}

865
# Build ISO
866
_build_iso_image() {
867
    local xorrisofs_options=()
868
    local bootmode
869

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

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

874
875
876
877
    # 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
878
879
880
881
882

    _msg_info "Creating ISO image..."
    xorriso -as mkisofs \
            -iso-level 3 \
            -full-iso9660-filenames \
nl6720's avatar
nl6720 committed
883
884
            -joliet \
            -joliet-long \
885
886
887
888
889
890
            -rational-rock \
            -volid "${iso_label}" \
            -appid "${iso_application}" \
            -publisher "${iso_publisher}" \
            -preparer "prepared by ${app_name}" \
            "${xorrisofs_options[@]}" \
891
            -output "${out_dir}/${image_name}" \
892
            "${isofs_dir}/"
893
    _msg_info "Done!"
894
    du -h -- "${out_dir}/${image_name}"
895
896
897
}

# Read profile's values from profiledef.sh
898
_read_profile() {
899
900
901
902
903
904
905
906
    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
907
908
        cd -- "${profile}"

909
910
911
912
        # Source profile's variables
        # shellcheck source=configs/releng/profiledef.sh
        . "${profile}/profiledef.sh"

913
914
915
        # Resolve paths of files that are expected to reside in the profile's directory
        [[ -n "$packages" ]] || packages="${profile}/packages.${arch}"
        packages="$(realpath -- "${packages}")"
916
917
        pacman_conf="$(realpath -- "${pacman_conf}")"

918
919
920
921
922
923
924
        # 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

925
        cd -- "${OLDPWD}"
926
927
    fi
}
928

929
930
# Validate set options
_validate_options() {
931
    local validation_error=0 bootmode _cert _buildmode
932
    local pkg_list_from_file=()
933
    local bootstrap_pkg_list_from_file=()
934
    local _override_cert_list=()
935

936
937
938
939
940
941
    _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
942
            (( validation_error=validation_error+1 ))
943
            _msg_error "No package specified in '${packages}'." 0
944
        fi
945
946
    else
        (( validation_error=validation_error+1 ))
947
948
        _msg_error "Packages file '${packages}' does not exist." 0
    fi
949

950
951
952
953
954
955
956
957
958
959
960
961
962
963
    # 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
964
    fi
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
    if [[ "${sign_netboot_artifacts}" == "y" ]]; then
        # Check if the certificate files exist
        for _cert in "${cert_list[@]}"; do
            if [[ -e "${_cert}" ]]; then
                _override_cert_list+=("$(realpath -- "${_cert}")")
            else
                (( validation_error=validation_error+1 ))
                _msg_error "File '${_cert}' does not exist." 0
            fi
        done
        cert_list=("${_override_cert_list[@]}")
        # Check if there are at least two certificate files
        if (( "${#cert_list[@]}" < 2 )); then
            (( validation_error=validation_error+1 ))
            _msg_error "Two certificates are required for codesigning, but '${cert_list[*]}' is provided." 0
        fi
    fi