mkarchiso 26.7 KB
Newer Older
1
#!/bin/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
# mkarchiso defaults
13
app_name="${0##*/}"
14
pkg_list=()
15
run_cmd=""
16
quiet="y"
17
18
work_dir="work"
out_dir="out"
19
img_name="${app_name}.iso"
20
sfs_mode="sfs"
21
sfs_comp="xz"
22
gpg_key=""
23

24
25
26
27
28
29
30
31
32
33
34
35
36
# profile defaults
profile=""
iso_name="${app_name}"
iso_label="${app_name^^}"
iso_publisher="${app_name}"
iso_application="${app_name} iso"
iso_version=""
install_dir="${app_name}"
arch="$(uname -m)"
pacman_conf="/etc/pacman.conf"
bootmodes=()


37
38
39
40
# Show an INFO message
# $1: message string
_msg_info() {
    local _msg="${1}"
41
42
    [[ "${quiet}" == "y" ]] || printf '[%s] INFO: %s\n' "${app_name}" "${_msg}"

43
44
}

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

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

64
_chroot_init() {
65
    mkdir -p -- "${airootfs_dir}"
66
    _pacman base syslinux
67
68
69
}

_chroot_run() {
70
    eval -- arch-chroot "${airootfs_dir}" "${run_cmd}"
71
72
}

73
74
_mount_airootfs() {
    trap "_umount_airootfs" EXIT HUP INT TERM
75
    mkdir -p -- "${work_dir}/mnt/airootfs"
76
77
    _msg_info "Mounting '${airootfs_dir}.img' on '${work_dir}/mnt/airootfs'"
    mount -- "${airootfs_dir}.img" "${work_dir}/mnt/airootfs"
78
    _msg_info "Done!"
79
80
}

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

# Show help usage, with an exit status.
# $1: exit status number.
91
92
93
94
_usage () {
    IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true
usage ${app_name} [options] command <command options>
 general options:
95
    -B <profile_dir> Directory of the archiso profile to build
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    -p PACKAGE(S)    Package(s) to install, can be used multiple times
    -C <file>        Config file for pacman.
                     Default: '${pacman_conf}'
    -L <label>       Set a label for the disk
                     Default: '${iso_label}'
    -P <publisher>   Set a publisher for the disk
                     Default: '${iso_publisher}'
    -A <application> Set an application name for the disk
                     Default: '${iso_application}'
    -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]
    -w <work_dir>    Set the working directory
                     Default: '${work_dir}'
    -o <out_dir>     Set the output directory
                     Default: '${out_dir}'
    -s <sfs_mode>    Set SquashFS image mode (img or sfs)
                     img: prepare airootfs.sfs for dm-snapshot usage
                     sfs: prepare airootfs.sfs for overlayfs usage
                     Default: '${sfs_mode}'
    -c <comp_type>   Set SquashFS compression type (gzip, lzma, lzo, xz, zstd)
                     Default: '${sfs_comp}'
    -v               Enable verbose output
    -h               This message
 commands:
121
122
   build_profile
      build an iso image from a profile
123
124
ENDUSAGETEXT
    printf '%s\n' "${usagetext}"
125
    exit "${1}"
126
127
}

128
# Shows configuration according to command mode.
129
# $1: init | install | run | prepare | iso
130
131
_show_config () {
    local _mode="$1"
132
    printf '\n'
133
134
135
136
137
138
    _msg_info "Configuration settings"
    _msg_info "                  Command:   ${command_name}"
    _msg_info "             Architecture:   ${arch}"
    _msg_info "        Working directory:   ${work_dir}"
    _msg_info "   Installation directory:   ${install_dir}"
    case "${_mode}" in
139
140
141
142
        init)
            _msg_info "       Pacman config file:   ${pacman_conf}"
            ;;
        install)
143
            _msg_info "       Pacman config file:   ${pacman_conf}"
144
            _msg_info "                 Packages:   ${pkg_list[*]}"
145
            ;;
146
147
148
        run)
            _msg_info "              Run command:   ${run_cmd}"
            ;;
149
150
        prepare)
            ;;
151
152
        pkglist)
            ;;
153
154
155
156
157
158
        iso)
            _msg_info "               Image name:   ${img_name}"
            _msg_info "               Disk label:   ${iso_label}"
            _msg_info "           Disk publisher:   ${iso_publisher}"
            _msg_info "         Disk application:   ${iso_application}"
            ;;
159
    esac
160
    printf '\n'
161
}
162

163
# Install desired packages to airootfs
164
_pacman () {
165
    _msg_info "Installing packages to '${airootfs_dir}/'..."
166
167

    if [[ "${quiet}" = "y" ]]; then
168
        pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" "$@" &> /dev/null
169
    else
170
        pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" "$@"
171
    fi
172

173
    _msg_info "Packages installed successfully!"
174
175
}

176
# Cleanup airootfs
177
_cleanup () {
178
    _msg_info "Cleaning up what we can on airootfs..."
179

180
    # Delete all files in /boot
181
    if [[ -d "${airootfs_dir}/boot" ]]; then
182
        find "${airootfs_dir}/boot" -mindepth 1 -delete
183
184
    fi
    # Delete pacman database sync cache files (*.tar.gz)
185
186
    if [[ -d "${airootfs_dir}/var/lib/pacman" ]]; then
        find "${airootfs_dir}/var/lib/pacman" -maxdepth 1 -type f -delete
187
188
    fi
    # Delete pacman database sync cache
189
190
    if [[ -d "${airootfs_dir}/var/lib/pacman/sync" ]]; then
        find "${airootfs_dir}/var/lib/pacman/sync" -delete
191
192
    fi
    # Delete pacman package cache
193
194
    if [[ -d "${airootfs_dir}/var/cache/pacman/pkg" ]]; then
        find "${airootfs_dir}/var/cache/pacman/pkg" -type f -delete
195
196
    fi
    # Delete all log files, keeps empty dirs.
197
198
    if [[ -d "${airootfs_dir}/var/log" ]]; then
        find "${airootfs_dir}/var/log" -type f -delete
199
200
    fi
    # Delete all temporary files and dirs
201
202
    if [[ -d "${airootfs_dir}/var/tmp" ]]; then
        find "${airootfs_dir}/var/tmp" -mindepth 1 -delete
203
    fi
204
    # Delete package pacman related files.
205
    find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete
206
    _msg_info "Done!"
207
}
208

209
210
# Makes a ext4 filesystem inside a SquashFS from a source directory.
_mkairootfs_img () {
211
212
    if [[ ! -e "${airootfs_dir}" ]]; then
        _msg_error "The path '${airootfs_dir}' does not exist" 1
213
214
    fi

215
    _msg_info "Creating ext4 image of 32GiB..."
216
    truncate -s 32G -- "${airootfs_dir}.img"
217
    if [[ "${quiet}" == "y" ]]; then
218
        mkfs.ext4 -q -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img"
219
    else
220
        mkfs.ext4 -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img"
221
    fi
222
    tune2fs -c 0 -i 0 -- "${airootfs_dir}.img" &> /dev/null
223
    _msg_info "Done!"
224
    _mount_airootfs
225
226
    _msg_info "Copying '${airootfs_dir}/' to '${work_dir}/mnt/airootfs/'..."
    cp -aT -- "${airootfs_dir}/" "${work_dir}/mnt/airootfs/"
227
    chown root:root -- "${work_dir}/mnt/airootfs/"
228
    _msg_info "Done!"
229
    _umount_airootfs
230
    mkdir -p -- "${isofs_dir}/${install_dir}/${arch}"
231
232
    _msg_info "Creating SquashFS image, this may take some time..."
    if [[ "${quiet}" = "y" ]]; then
233
        mksquashfs "${airootfs_dir}.img" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
234
            -comp "${sfs_comp}" -no-progress &> /dev/null
235
    else
236
        mksquashfs "${airootfs_dir}.img" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
237
            -comp "${sfs_comp}"
238
239
    fi
    _msg_info "Done!"
240
    rm -- "${airootfs_dir}.img"
241
242
}

243
244
# Makes a SquashFS filesystem from a source directory.
_mkairootfs_sfs () {
245
246
    if [[ ! -e "${airootfs_dir}" ]]; then
        _msg_error "The path '${airootfs_dir}' does not exist" 1
247
248
    fi

249
    mkdir -p -- "${isofs_dir}/${install_dir}/${arch}"
250
251
    _msg_info "Creating SquashFS image, this may take some time..."
    if [[ "${quiet}" = "y" ]]; then
252
        mksquashfs "${airootfs_dir}" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
253
            -comp "${sfs_comp}" -no-progress &> /dev/null
254
    else
255
        mksquashfs "${airootfs_dir}" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
256
            -comp "${sfs_comp}"
257
258
259
260
    fi
    _msg_info "Done!"
}

261
262
_mkchecksum () {
    _msg_info "Creating checksum file for self-test..."
263
    cd -- "${isofs_dir}/${install_dir}/${arch}"
264
    sha512sum airootfs.sfs > airootfs.sha512
265
    cd -- "${OLDPWD}"
266
    _msg_info "Done!"
267
268
}

269
270
_mksignature () {
    _msg_info "Creating signature file..."
271
    cd -- "${isofs_dir}/${install_dir}/${arch}"
272
    gpg --detach-sign --default-key "${gpg_key}" airootfs.sfs
273
    cd -- "${OLDPWD}"
274
275
276
    _msg_info "Done!"
}

277
278
279
280
281
282
283
# Helper function to run functions only one time.
_run_once() {
    if [[ ! -e "${work_dir}/build.${1}" ]]; then
        "$1"
        touch "${work_dir}/build.${1}"
    fi
}
284

285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# Set up custom pacman.conf with current cache directories.
_make_pacman_conf() {
    local _cache_dirs
    _cache_dirs="$(pacman-conf CacheDir)"
    sed -r "s|^#?\\s*CacheDir.+|CacheDir    = ${_cache_dirs[*]//$'\n'/ }|g" \
        "${pacman_conf}" > "${work_dir}/pacman.conf"
}

# Prepare working directory and copy custom airootfs files (airootfs)
_make_custom_airootfs() {
    mkdir -m 755 -- "${airootfs_dir}"

    local passwd=()
    if [[ -d "${profile}/airootfs" ]]; then
        cp -af --no-preserve=ownership -- "${profile}/airootfs/." "${airootfs_dir}"

        [[ -e "${airootfs_dir}/etc/shadow" ]] && chmod -f 0400 -- "${airootfs_dir}/etc/shadow"
        [[ -e "${airootfs_dir}/etc/gshadow" ]] && chmod -f 0400 -- "${airootfs_dir}/etc/gshadow"

        # Set up user home directories and permissions
        if [[ -e "${airootfs_dir}/etc/passwd" ]]; then
            while IFS=':' read -a passwd -r; do
                [[ "${passwd[5]}" == '/' ]] && continue
                [[ -z "${passwd[5]}" ]] && continue

                if [[ -d "${airootfs_dir}${passwd[5]}" ]]; then
                    chown -hR -- "${passwd[2]}:${passwd[3]}" "${airootfs_dir}${passwd[5]}"
                    chmod -f 0750 -- "${airootfs_dir}${passwd[5]}"
                else
                    install -d -m 0750 -o "${passwd[2]}" -g "${passwd[3]}" -- "${airootfs_dir}${passwd[5]}"
                fi
             done < "${airootfs_dir}/etc/passwd"
        fi
    fi
}

# Packages (airootfs)
_make_packages() {
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<>"${work_dir}/pubkey.gpg"
        export ARCHISO_GNUPG_FD
    fi
    _pacman "${pkg_list[@]}"
    if [[ -n "${gpg_key}" ]]; then
        exec {ARCHISO_GNUPG_FD}<&-
        unset ARCHISO_GNUPG_FD
    fi
}

# Customize installation (airootfs)
_make_customize_airootfs() {
    local passwd=()
    if [[ -e "${profile}/airootfs/etc/passwd" ]]; then
        while IFS=':' read -a passwd -r; do
            if [[ "${passwd[5]}" == '/' ]]; then
                continue
            fi
            cp -RdT --preserve=mode,timestamps,links -- "${airootfs_dir}/etc/skel" "${airootfs_dir}${passwd[5]}"
            chown -hR -- "${passwd[2]}:${passwd[3]}" "${airootfs_dir}${passwd[5]}"

        done < "${profile}/airootfs/etc/passwd"
    fi

    if [[ -e "${airootfs_dir}/root/customize_airootfs.sh" ]]; then
        _msg_warning "customize_airootfs.sh is deprecated! Support for it will be removed in a future archiso version."
        local run_cmd="/root/customize_airootfs.sh"
        local work_dir="${work_dir}/${arch}"
        command_run
        rm -- "${airootfs_dir}/root/customize_airootfs.sh"
    fi
}

357
358
359
360
361
362
363
364
365
366
367
368
# Set up boot loaders
_make_bootmodes() {
    local bootmode
    for bootmode in "${bootmodes[@]}"; do
        if typeset -f "_make_boot_${bootmode}" &> /dev/null; then
            _run_once "_make_boot_${bootmode}"
        else
            _msg_error "${bootmode} is not a valid boot mode" 1
        fi
    done
}

369
# Prepare kernel/initramfs ${install_dir}/boot/
370
_make_boot_on_iso() {
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
    mkdir -p -- "${isofs_dir}/${install_dir}/boot/${arch}"
    install -m 0644 -- "${airootfs_dir}/boot/archiso.img" "${isofs_dir}/${install_dir}/boot/${arch}/"
    install -m 0644 -- "${airootfs_dir}/boot/vmlinuz-linux" "${isofs_dir}/${install_dir}/boot/${arch}/"
    if [[ -e "${airootfs_dir}/boot/intel-ucode.img" ]]; then
        install -m 0644 -- "${airootfs_dir}/boot/intel-ucode.img" "${isofs_dir}/${install_dir}/boot/"
        mkdir -p "${isofs_dir}/${install_dir}/boot/licenses/intel-ucode/"
        install -m 0644 -- "${airootfs_dir}/usr/share/licenses/intel-ucode/"* \
            "${isofs_dir}/${install_dir}/boot/licenses/intel-ucode/"
    fi
    if [[ -e "${airootfs_dir}/boot/amd-ucode.img" ]]; then
        install -m 0644 -- "${airootfs_dir}/boot/amd-ucode.img" "${isofs_dir}/${install_dir}/boot/"
        mkdir -p "${isofs_dir}/${install_dir}/boot/licenses/amd-ucode/"
        install -m 0644 -- "${airootfs_dir}/usr/share/licenses/amd-ucode/"* \
            "${isofs_dir}/${install_dir}/boot/licenses/amd-ucode/"
    fi
}

# Prepare /${install_dir}/boot/syslinux
389
_make_boot_bios.syslinux.mbr() {
390
391
392
    mkdir -p "${isofs_dir}/${install_dir}/boot/syslinux"
    for _cfg in "${profile}/syslinux/"*.cfg; do
        sed "s|%ARCHISO_LABEL%|${iso_label}|g;
393
394
395
             s|%INSTALL_DIR%|${install_dir}|g;
             s|%ARCH%|${arch}|g" \
             "${_cfg}" > "${isofs_dir}/${install_dir}/boot/syslinux/${_cfg##*/}"
396
    done
397
398
399
    if [[ -e "${profile}/syslinux/splash.png" ]]; then
        install -m 0644 -- "${profile}/syslinux/splash.png" "${isofs_dir}/${install_dir}/boot/syslinux/"
    fi
400
401
402
    install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/"*.c32 "${isofs_dir}/${install_dir}/boot/syslinux/"
    install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/lpxelinux.0" "${isofs_dir}/${install_dir}/boot/syslinux/"
    install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/memdisk" "${isofs_dir}/${install_dir}/boot/syslinux/"
403
404
405
406

    _make_boot_on_iso
    _uname_r=$(file -b "${isofs_dir}/${install_dir}/boot/${arch}/vmlinuz-linux" | awk 'f{print;f=0} /version/{f=1}' RS=' ')

407
408
409
410
411
    mkdir -p "${isofs_dir}/${install_dir}/boot/syslinux/hdt"
    gzip -c -9 "${airootfs_dir}/usr/share/hwdata/pci.ids" > \
        "${isofs_dir}/${install_dir}/boot/syslinux/hdt/pciids.gz"
    gzip -c -9 "${airootfs_dir}/usr/lib/modules/${_uname_r}/modules.alias" > \
        "${isofs_dir}/${install_dir}/boot/syslinux/hdt/modalias.gz"
412
413
414
415
416
417
418
419
420

    # Add other aditional/extra files to ${install_dir}/boot/
    if [[ -e "${airootfs_dir}/boot/memtest86+/memtest.bin" ]]; then
        # rename for PXE: https://wiki.archlinux.org/index.php/Syslinux#Using_memtest
        install -m 0644 -- "${airootfs_dir}/boot/memtest86+/memtest.bin" "${isofs_dir}/${install_dir}/boot/memtest"
        mkdir -p "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
        install -m 0644 -- "${airootfs_dir}/usr/share/licenses/common/GPL2/license.txt" \
            "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
    fi
421
422
423
}

# Prepare /isolinux
424
_make_boot_bios.syslinux.eltorito() {
425
    mkdir -p "${isofs_dir}/isolinux"
426
427
428
429
    sed "s|%ARCHISO_LABEL%|${iso_label}|g;
         s|%INSTALL_DIR%|${install_dir}|g;
         s|%ARCH%|${arch}|g" \
         "${profile}/isolinux/isolinux.cfg" > "${isofs_dir}/isolinux/isolinux.cfg"
430
431
432
    install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/isolinux.bin" "${isofs_dir}/isolinux/"
    install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/isohdpfx.bin" "${isofs_dir}/isolinux/"
    install -m 0644 -- "${airootfs_dir}/usr/lib/syslinux/bios/ldlinux.c32" "${isofs_dir}/isolinux/"
433
434
435

    # isolinux.cfg loads syslinux.cfg
    _run_once _make_boot_bios.syslinux.mbr
436
437
438
439
440
441
442
443
444
445
446
447
}

# Prepare /EFI on ISO-9660
_make_efi() {
    mkdir -p "${isofs_dir}/EFI/BOOT"
    install -m 0644 -- "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${isofs_dir}/EFI/BOOT/BOOTx64.EFI"

    mkdir -p "${isofs_dir}/loader/entries"
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${isofs_dir}/loader/"

    sed "s|%ARCHISO_LABEL%|${iso_label}|g;
448
449
         s|%INSTALL_DIR%|${install_dir}|g;
         s|%ARCH%|${arch}|g" \
450
451
452
453
454
        "${profile}/efiboot/loader/entries/archiso-x86_64-usb.conf" > \
        "${isofs_dir}/loader/entries/archiso-x86_64.conf"

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

460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# Prepare kernel/initramfs on efiboot.img
_make_boot_on_fat() {
    mkdir -p "${work_dir}/efiboot/EFI/archiso"
    install -m 0644 -- "${airootfs_dir}/boot/vmlinuz-linux" "${work_dir}/efiboot/EFI/archiso/"
    install -m 0644 -- "${isofs_dir}/${install_dir}/boot/${arch}/archiso.img" "${work_dir}/efiboot/EFI/archiso/"
    if [[ -e "${airootfs_dir}/boot/intel-ucode.img" ]]; then
        install -m 0644 -- "${airootfs_dir}/boot/intel-ucode.img" "${work_dir}/efiboot/EFI/archiso/"
    fi
    if [[ -e "${airootfs_dir}/boot/amd-ucode.img" ]]; then
        install -m 0644 -- "${airootfs_dir}/boot/amd-ucode.img" "${work_dir}/efiboot/EFI/archiso/"
    fi
}

# Prepare efiboot.img::/EFI for EFI boot mode
_make_boot_uefi-x64.systemd-boot.esp() {
475
476
477
478
479
480
481
482
483
484
485
486
487
488
    mkdir -p "${isofs_dir}/EFI/archiso"
    mkfs.fat -C -n ARCHISO_EFI "${isofs_dir}/EFI/archiso/efiboot.img" 65536

    mkdir -p "${work_dir}/efiboot"
    mount "${isofs_dir}/EFI/archiso/efiboot.img" "${work_dir}/efiboot"

    mkdir -p "${work_dir}/efiboot/EFI/BOOT"
    install -m 0644 -- "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${work_dir}/efiboot/EFI/BOOT/BOOTx64.EFI"

    mkdir -p "${work_dir}/efiboot/loader/entries"
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${work_dir}/efiboot/loader/"

    sed "s|%ARCHISO_LABEL%|${iso_label}|g;
489
490
         s|%INSTALL_DIR%|${install_dir}|g;
         s|%ARCH%|${arch}|g" \
491
492
493
494
        "${profile}/efiboot/loader/entries/archiso-x86_64-cd.conf" > \
        "${work_dir}/efiboot/loader/entries/archiso-x86_64.conf"

    # shellx64.efi is picked up automatically when on /
495
496
497
498
499
500
    if [[ -e "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
        install -m 0644 -- "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" "${work_dir}/efiboot/shellx64.efi"
    fi

    # Copy kernel and initramfs
    _make_boot_on_fat
501

502
503
504
    umount -d "${work_dir}/efiboot"
}

505
506
507
508
509
510
511
# Prepare efiboot.img::/EFI for "El Torito" EFI boot mode
_make_boot_uefi-x64.systemd-boot.eltorito() {
    _run_once _make_boot_uefi-x64.systemd-boot.esp
    # Set up /EFI on ISO-9660
    _run_once _make_efi
}

512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
# Build airootfs filesystem image
_make_prepare() {
    if [[ "${sfs_mode}" == "sfs" ]]; then
        _mkairootfs_sfs
    else
        _mkairootfs_img
    fi
    _mkchecksum
    if [[ "${gpg_key}" ]]; then
      _mksignature
    fi
}

# Build ISO
_make_iso() {
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
    local xorrisofs_options=()

    if [[ "${quiet}" == "y" ]]; then
        xorrisofs_options+=('-quiet')
    fi
    # shellcheck disable=SC2076
    if [[ " ${bootmodes[*]} " =~ ' bios.syslinux.' ]]; then
        if [[ ! -f "${isofs_dir}/isolinux/isolinux.bin" ]]; then
            _msg_error "The file '${isofs_dir}/isolinux/isolinux.bin' does not exist." 1
        fi
        if [[ ! -f "${isofs_dir}/isolinux/isohdpfx.bin" ]]; then
            _msg_error "The file '${isofs_dir}/isolinux/isohdpfx.bin' does not exist." 1
        fi
        xorrisofs_options+=(
            '-eltorito-boot' 'isolinux/isolinux.bin'
            '-eltorito-catalog' 'isolinux/boot.cat'
            '-no-emul-boot' '-boot-load-size' '4' '-boot-info-table'
            '-isohybrid-mbr' "${isofs_dir}/isolinux/isohdpfx.bin"
        )
    fi
    # shellcheck disable=SC2076
    if [[ " ${bootmodes[*]} " =~ ' uefi-x64.systemd-boot.' ]]; then
        xorrisofs_options+=(
            '-eltorito-alt-boot'
            '-e' 'EFI/archiso/efiboot.img'
            '-no-emul-boot'
            '-isohybrid-gpt-basdat'
        )
    fi

    _msg_info "Creating ISO image..."
    xorriso -as mkisofs \
            -iso-level 3 \
            -full-iso9660-filenames \
            -rational-rock \
            -volid "${iso_label}" \
            -appid "${iso_application}" \
            -publisher "${iso_publisher}" \
            -preparer "prepared by ${app_name}" \
            "${xorrisofs_options[@]}" \
            -output "${out_dir}/${img_name}" \
            "${isofs_dir}/"
    _msg_info "Done! | $(du -h -- "${out_dir}/${img_name}")"
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
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
}

# Read profile's values from profiledef.sh
_read_profile () {
    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
        # Source profile's variables
        # shellcheck source=configs/releng/profiledef.sh
        . "${profile}/profiledef.sh"
        cd -- "${profile}"

        # Resolve paths
        packages="$(realpath -- "${profile}/packages.${arch}")"
        pacman_conf="$(realpath -- "${pacman_conf}")"

        # Enumerate packages
        [[ -e "${packages}" ]] || _msg_error "File '${packages}' does not exist!" 1
        mapfile -t pkg_list < <(sed '/^[[:blank:]]*#.*/d;s/#.*//;/^[[:blank:]]*$/d' "${packages}")
        if (( ${#pkg_list[@]} == 0 )); then
            _msg_error "'${packages}' does not list any packages!" 1
        fi

        cd -- "${OLDPWD}"
    fi
}

_set_up_directories() {
    local directory
    for directory in "${work_dir}" "${out_dir}" "${work_dir}/${arch}" "${isofs_dir}" "${isofs_dir}/${install_dir}"; do
        [[ -d "${directory}" ]] || mkdir -m 0755 -- "${directory}"
    done
}

_print_settings() {
    _msg_info "${app_name} configuration settings"
    _msg_info "    Command:                   ${command_name}"
    _msg_info "    Working directory:         ${work_dir}"
    _msg_info "    Output directory:          ${out_dir}"
    _msg_info "    GPG key:                   ${gpg_key:-None}"
    _msg_info "Profile configuration settings"
    _msg_info "    Profile:                   ${profile}"
    _msg_info "    Architecture:              ${arch}"
    _msg_info "    Image name:                ${img_name}"
    _msg_info "    Disk label:                ${iso_label}"
    _msg_info "    Disk publisher:            ${iso_publisher}"
    _msg_info "    Disk application:          ${iso_application}"
    _msg_info "    Installation directory:    ${install_dir}"
    _msg_info "    Pacman config file:        ${pacman_conf}"
    _msg_info "    Packages:                  ${pkg_list[*]}"
    _msg_info "    Boot modes:                ${bootmodes[*]}"
}

_export_gpg_publickey() {
    if [[ -n "${gpg_key}" ]]; then
        gpg --batch --output "${work_dir}/pubkey.gpg" --export "${gpg_key}"
    fi
}


_make_pkglist() {
636
    _msg_info "Creating a list of installed packages on live-enviroment..."
637
    pacman -Q --sysroot "${airootfs_dir}" > "${isofs_dir}/${install_dir}/pkglist.${arch}.txt"
638
    _msg_info "Done!"
639
}
640

641
642
643
command_pkglist () {
    _show_config pkglist
    _make_pkglist
644
645
}

646
# Create an ISO9660 filesystem from "iso" directory.
647
command_iso () {
648
    bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito')
649

650
    # If exists, add an EFI "El Torito" boot image (FAT filesystem) to ISO-9660 image.
651
    if [[ -f "${isofs_dir}/EFI/archiso/efiboot.img" ]]; then
652
        bootmodes+=('uefi-x64.systemd-boot.esp' 'uefi-x64.systemd-boot.eltorito')
653
654
    fi

655
    _show_config iso
656
    mkdir -p -- "${out_dir}"
657
    _make_iso
658
659
}

660
# create airootfs.sfs filesystem, and push it in "iso" directory.
661
662
663
664
command_prepare () {
    _show_config prepare

    _cleanup
665
    _make_prepare
666
667
}

668
# Install packages on airootfs.
669
# A basic check to avoid double execution/reinstallation is done via hashing package names.
670
command_install () {
671
672
673
674
    if [[ ! -f "${pacman_conf}" ]]; then
        _msg_error "Pacman config file '${pacman_conf}' does not exist" 1
    fi

675
    if (( ${#pkg_list[@]} == 0 )); then
676
677
678
679
        _msg_error "Packages must be specified" 0
        _usage 1
    fi

680
    _show_config install
681

682
    _make_packages
683
684
}

685
686
687
688
689
690
691
692
693
command_init() {
    _show_config init
    _chroot_init
}

command_run() {
    _show_config run
    _chroot_run
}
694

695
696
697
command_build_profile() {
    # Set up essential directory paths
    airootfs_dir="${work_dir}/${arch}/airootfs"
698
699
700
701
702
703
704
705
706
707
708
709
    isofs_dir="${work_dir}/iso"
    # Set ISO file name
    img_name="${iso_name}-${iso_version}-${arch}.iso"

    _print_settings
    _run_once _set_up_directories
    _run_once _make_pacman_conf
    _run_once _export_gpg_publickey
    _run_once _make_custom_airootfs
    _run_once _make_packages
    _run_once _make_customize_airootfs
    _run_once _make_pkglist
710
    _make_bootmodes
711
712
713
    _run_once _cleanup
    _run_once _make_prepare
    _run_once _make_iso
714
715
716
}

while getopts 'B:p:r:C:L:P:A:D:w:o:s:c:g:vh' arg; do
717
    case "${arg}" in
718
719
        B)
            profile="$(realpath -- "${OPTARG}")"
720
            _read_profile
721
            ;;
722
723
        p)
            read -r -a opt_pkg_list <<< "${OPTARG}"
724
725
            pkg_list+=("${opt_pkg_list[@]}")
            ;;
726
        r) run_cmd="${OPTARG}" ;;
727
        C) pacman_conf="$(realpath -- "${OPTARG}")" ;;
728
729
730
731
        L) iso_label="${OPTARG}" ;;
        P) iso_publisher="${OPTARG}" ;;
        A) iso_application="${OPTARG}" ;;
        D) install_dir="${OPTARG}" ;;
732
733
        w) work_dir="$(realpath -- "${OPTARG}")" ;;
        o) out_dir="$(realpath -- "${OPTARG}")" ;;
734
        s) sfs_mode="${OPTARG}" ;;
735
        c) sfs_comp="${OPTARG}" ;;
736
        g) gpg_key="${OPTARG}" ;;
737
738
739
740
741
742
743
744
745
        v) quiet="n" ;;
        h|?) _usage 0 ;;
        *)
            _msg_error "Invalid argument '${arg}'" 0
            _usage 1
            ;;
    esac
done

746
747
748
749
if (( EUID != 0 )); then
    _msg_error "${app_name} must be run as root." 1
fi

750
751
shift $((OPTIND - 1))

752
if (( $# < 1 )); then
753
754
    _msg_error "No command specified" 0
    _usage 1
755
fi
756
757
command_name="${1}"

758
759
# Set directory path defaults
airootfs_dir="${work_dir}/airootfs"
760
isofs_dir="${work_dir}/iso"
761

762
case "${command_name}" in
763
    init)
764
        _msg_warning "The '${command_name}' command is deprecated!"
765
766
767
        command_init
        ;;
    install)
768
        _msg_warning "The '${command_name}' command is deprecated!"
769
770
771
        command_install
        ;;
    run)
772
        _msg_warning "The '${command_name}' command is deprecated!"
773
        command_run
774
775
        ;;
    prepare)
776
        _msg_warning "The '${command_name}' command is deprecated!"
777
778
        command_prepare
        ;;
779
    pkglist)
780
        _msg_warning "The '${command_name}' command is deprecated!"
781
782
        command_pkglist
        ;;
783
    iso)
784
        _msg_warning "The '${command_name}' command is deprecated!"
785
        if (( $# < 2 )); then
786
787
788
            _msg_error "No image specified" 0
            _usage 1
        fi
789
        img_name="${2}"
790
791
        command_iso
        ;;
792
793
794
    build_profile)
        command_build_profile
        ;;
795
796
797
798
799
    *)
        _msg_error "Invalid command name '${command_name}'" 0
        _usage 1
        ;;
esac
800
801

# vim:ts=4:sw=4:et: