mkarchiso 51.5 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
# adapted from GRUB_EARLY_INITRD_LINUX_STOCK in https://git.savannah.gnu.org/cgit/grub.git/tree/util/grub-mkconfig.in
readonly ucodes=('intel-uc.img' 'intel-ucode.img' 'amd-uc.img' 'amd-ucode.img' 'early_ucode.cpio' 'microcode.cpio')
42
43


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

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

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

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

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

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

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

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

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

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

    _msg_info "Done!"
173
}
174

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

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

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

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

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

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

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

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

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

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

265
# Set up custom pacman.conf with custom cache and pacman hook directories
266
_make_pacman_conf() {
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    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}"
281
    # take the profile pacman.conf and strip all settings that would break in chroot when using pacman -r
282
283
    # append CacheDir and HookDir to [options] section
    # HookDir is *always* set to the airootfs' override directory
284
285
286
    # 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}
287
        /\[options\]/a HookDir = ${pacstrap_dir}/etc/pacman.d/hooks/" > "${work_dir}/${buildmode}.pacman.conf"
288
289
290
291
292
}

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

295
    install -d -m 0755 -o 0 -g 0 -- "${pacstrap_dir}"
296

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

323
# Install desired packages to airootfs
324
_make_packages() {
325
    _msg_info "Installing packages to '${pacstrap_dir}/'..."
326

327
328
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg"
329
        export ARCHISO_GNUPG_FD
330
    fi
331
332

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

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

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

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

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

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

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

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

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

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

429
    _run_once _make_boot_on_iso9660
430

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

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

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

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

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

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

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

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

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

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

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

515
516
517
518
519
    for _file in "${ucodes[@]}"; do
        if [[ -e "${pacstrap_dir}/boot/${_file}" ]]; then
            _available_ucodes+=("${pacstrap_dir}/boot/${_file}")
        fi
    done
520
521
    # the required image size in KiB (rounded up to the next full MiB with an additional MiB for reserved sectors)
    efiboot_imgsize="$(du -bc \
522
523
        "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${pacstrap_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" \
524
        "${profile}/efiboot/" \
525
526
        "${pacstrap_dir}/boot/vmlinuz-"* \
        "${pacstrap_dir}/boot/initramfs-"*".img" \
527
        "${_available_ucodes[@]}" \
528
529
530
531
532
        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))}'
        )"
533
534
    # 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
535
    [[ -e "${work_dir}/efiboot.img" ]] && rm -f -- "${work_dir}/efiboot.img"
536
    _msg_info "Creating FAT image of size: ${efiboot_imgsize} KiB..."
537
    mkfs.fat -C -n ARCHISO_EFI "${work_dir}/efiboot.img" "$efiboot_imgsize"
538

539
540
    mmd -i "${work_dir}/efiboot.img" ::/EFI ::/EFI/BOOT
    mcopy -i "${work_dir}/efiboot.img" \
541
        "${pacstrap_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" ::/EFI/BOOT/BOOTx64.EFI
542

543
544
    mmd -i "${work_dir}/efiboot.img" ::/loader ::/loader/entries
    mcopy -i "${work_dir}/efiboot.img" "${profile}/efiboot/loader/loader.conf" ::/loader/
545
546
547
548
    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" \
549
            "${_conf}" | mcopy -i "${work_dir}/efiboot.img" - "::/loader/entries/${_conf##*/}"
550
    done
551
552

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

    # Copy kernel and initramfs
559
    _make_boot_on_fat
560

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

564
# Prepare efiboot.img::/EFI for "El Torito" EFI boot mode
565
566
567
568
_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
569
570
}

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

609
_validate_requirements_bootmode_bios.syslinux.eltorito() {
nl6720's avatar
nl6720 committed
610
    _validate_requirements_bootmode_bios.syslinux.mbr
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
655
656
657
658
659
}

_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}"
660
    _mkchecksum
David Runge's avatar
David Runge committed
661
662
663
    if [[ -n "${gpg_key}" ]]; then
        _mksignature
    fi
664
665
}

666
667
668
669
670
671
672
673
674
675
676
677
# 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
678
    local _files_to_sign=()
679
    _msg_info "Signing netboot artifacts..."
680
681
682
683
684
685
686
    _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
687
688
689
690
691
692
693
694
695
696
697
698
699
        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!"
}

700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
_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
715
716
717
718
719
720
721
_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
}

722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
_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
}

753
754
755
756
757
758
759
760
_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
}

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

# 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'
        )
831
832
833
834
835
836
837
838
839
        # 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
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
    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')
}

860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
# 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}"
}

876
# Build ISO
877
_build_iso_image() {
878
    local xorrisofs_options=()
879
    local bootmode
880

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

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

885
886
887
888
    # 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
889
890
891
892
893

    _msg_info "Creating ISO image..."
    xorriso -as mkisofs \
            -iso-level 3 \
            -full-iso9660-filenames \
nl6720's avatar
nl6720 committed
894
895
            -joliet \
            -joliet-long \
896
897
898
899
900
901
            -rational-rock \
            -volid "${iso_label}" \
            -appid "${iso_application}" \
            -publisher "${iso_publisher}" \
            -preparer "prepared by ${app_name}" \
            "${xorrisofs_options[@]}" \
902
            -output "${out_dir}/${image_name}" \
903
            "${isofs_dir}/"
904
    _msg_info "Done!"
905
    du -h -- "${out_dir}/${image_name}"
906
907
908
}

# Read profile's values from profiledef.sh
909
_read_profile() {
910
911
912
913
914
915
916
917
    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
918
919
        cd -- "${profile}"

920
921
922
923
        # Source profile's variables
        # shellcheck source=configs/releng/profiledef.sh
        . "${profile}/profiledef.sh"

924
925
926
        # Resolve paths of files that are expected to reside in the profile's directory
        [[ -n "$packages" ]] || packages="${profile}/packages.${arch}"
        packages="$(realpath -- "${packages}")"
927
928
        pacman_conf="$(realpath -- "${pacman_conf}")"

929
930
931
932
933
934
935
        # 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

936
        cd -- "${OLDPWD}"
937
938
    fi
}
939

940
941
# Validate set options
_validate_options() {
942
    local validation_error=0 bootmode _cert _buildmode
943
    local pkg_list_from_file=()
944
    local bootstrap_pkg_list_from_file=()
945
    local _override_cert_list=()
946

947
948
949
950
951
952
    _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
953
            (( validation_error=validation_error+1 ))
954
            _msg_error "No package specified in '${packages}'." 0
955
        fi
956
957
    else
        (( validation_error=validation_error+1 ))
958
959
        _msg_error "Packages file '${packages}' does not exist." 0
    fi
960

961
962
963
964
965
966
967
968
969
970
971
972
973
974
    # 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' "${boo