mkarchiso 30.4 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
override_gpg_key=""
24

25
26
27
28
# profile defaults
profile=""
iso_name="${app_name}"
iso_label="${app_name^^}"
29
override_iso_label=""
30
iso_publisher="${app_name}"
31
override_iso_publisher=""
32
iso_application="${app_name} iso"
33
override_iso_application=""
34
35
iso_version=""
install_dir="${app_name}"
36
override_install_dir=""
37
38
arch="$(uname -m)"
pacman_conf="/etc/pacman.conf"
39
override_pacman_conf=""
40
41
42
bootmodes=()


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

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

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

69
_chroot_init() {
70
    install -d -m 0755 -o 0 -g 0 -- "${airootfs_dir}"
71
    _pacman base syslinux
72
73
74
}

_chroot_run() {
75
    eval -- arch-chroot "${airootfs_dir}" "${run_cmd}"
76
77
}

78
79
_mount_airootfs() {
    trap "_umount_airootfs" EXIT HUP INT TERM
80
81
    install -d -m 0755 -- "${work_dir}/mnt/airootfs"
    _msg_info "Mounting '${airootfs_dir}.img' on '${work_dir}/mnt/airootfs'..."
82
    mount -- "${airootfs_dir}.img" "${work_dir}/mnt/airootfs"
83
    _msg_info "Done!"
84
85
}

86
_umount_airootfs() {
87
    _msg_info "Unmounting '${work_dir}/mnt/airootfs'..."
88
    umount -d -- "${work_dir}/mnt/airootfs"
89
    _msg_info "Done!"
90
    rmdir -- "${work_dir}/mnt/airootfs"
91
92
93
    trap - EXIT HUP INT TERM
}

94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
_mount_efibootimg() {
    trap "_umount_efibootimg" EXIT HUP INT TERM
    install -d -m 0755 -- "${work_dir}/mnt/efiboot"
    _msg_info "Mounting '${isofs_dir}/EFI/archiso/efiboot.img' on '${work_dir}/mnt/efiboot'..."
    mount -- "${isofs_dir}/EFI/archiso/efiboot.img" "${work_dir}/mnt/efiboot"
    _msg_info "Done!"
}

_umount_efibootimg() {
    _msg_info "Unmounting '${work_dir}/mnt/efiboot'..."
    umount -d -- "${work_dir}/mnt/efiboot"
    _msg_info "Done!"
    rmdir -- "${work_dir}/mnt/efiboot"
    trap - EXIT HUP INT TERM
}

110
111
# Show help usage, with an exit status.
# $1: exit status number.
112
_usage() {
113
    IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
usage ${app_name} [options] <profile_dir or legacy_command>
  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}'
     -c <comp_type>   Set SquashFS compression type (gzip, lzma, lzo, xz, zstd)
                      Default: '${sfs_comp}'
     -g <gpg_key>     Set the GPG key to be used for signing the sqashfs image
     -h               This message
     -o <out_dir>     Set the output directory
                      Default: '${out_dir}'
     -p PACKAGE(S)    Package(s) to install, can be used multiple times
     -r <run_cmd>     Set a command to be run in chroot (only relevant for for command_run)
                      NOTE: Deprecated, will be removed with archiso v49
     -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}'
     -v               Enable verbose output
     -w <work_dir>    Set the working directory
                      Default: '${work_dir}'

  profile_dir:        Directory of the archiso profile to build

  legacy_command:     Legacy build.sh command
                      NOTE: Deprecated, will be removed with archiso v49
    command_init
        initialize a chroot for building
    command_install
        install packages to the chroot
    command_run
        run a command in the chroot
    command_prepare
        cleanup and prepare the airootfs
    command_pkglist
        create a list of packages installed on the medium
    command_iso
        create the ISO
160
ENDUSAGETEXT
161
    printf '%s' "${usagetext}"
162
    exit "${1}"
163
164
}

165
# Shows configuration according to command mode.
166
167
# $1: build_profile | init | install | run | prepare | iso
_show_config() {
168
    local _mode="$1"
169
    _msg_info "${app_name} configuration settings"
170
171
172
173
174
    _msg_info "                  Command:   ${command_name}"
    _msg_info "             Architecture:   ${arch}"
    _msg_info "        Working directory:   ${work_dir}"
    _msg_info "   Installation directory:   ${install_dir}"
    case "${_mode}" in
175
176
177
178
179
180
181
182
183
184
185
186
        build_profile)
            _msg_info "         Output directory:   ${out_dir}"
            _msg_info "                  GPG key:   ${gpg_key:-None}"
            _msg_info "                  Profile:   ${profile}"
            _msg_info "Pacman configuration file:   ${pacman_conf}"
            _msg_info "          Image file name:   ${img_name}"
            _msg_info "         ISO volume label:   ${iso_label}"
            _msg_info "            ISO publisher:   ${iso_publisher}"
            _msg_info "          ISO application:   ${iso_application}"
            _msg_info "               Boot modes:   ${bootmodes[*]}"
            _msg_info "                 Packages:   ${pkg_list[*]}"
            ;;
187
        init)
188
            _msg_info "Pacman configuration file:   ${pacman_conf}"
189
190
            ;;
        install)
191
192
            _msg_info "                  GPG key:   ${gpg_key:-None}"
            _msg_info "Pacman configuration file:   ${pacman_conf}"
193
            _msg_info "                 Packages:   ${pkg_list[*]}"
194
            ;;
195
196
197
        run)
            _msg_info "              Run command:   ${run_cmd}"
            ;;
198
        prepare)
199
            _msg_info "                  GPG key:   ${gpg_key:-None}"
200
            ;;
201
202
        pkglist)
            ;;
203
        iso)
204
205
206
207
208
            _msg_info "         Output directory:   ${out_dir}"
            _msg_info "          Image file name:   ${img_name}"
            _msg_info "         ISO volume label:   ${iso_label}"
            _msg_info "            ISO publisher:   ${iso_publisher}"
            _msg_info "          ISO application:   ${iso_application}"
209
            ;;
210
    esac
211
    [[ "${quiet}" == "y" ]] || printf '\n'
212
}
213

214
# Install desired packages to airootfs
215
_pacman() {
216
    _msg_info "Installing packages to '${airootfs_dir}/'..."
217
218

    if [[ "${quiet}" = "y" ]]; then
219
        pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" "$@" &> /dev/null
220
    else
221
        pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" "$@"
222
    fi
223

224
    _msg_info "Done! Packages installed successfully."
225
226
}

227
# Cleanup airootfs
228
_cleanup() {
229
    _msg_info "Cleaning up what we can on airootfs..."
230

231
    # Delete all files in /boot
232
    if [[ -d "${airootfs_dir}/boot" ]]; then
233
        find "${airootfs_dir}/boot" -mindepth 1 -delete
234
235
    fi
    # Delete pacman database sync cache files (*.tar.gz)
236
237
    if [[ -d "${airootfs_dir}/var/lib/pacman" ]]; then
        find "${airootfs_dir}/var/lib/pacman" -maxdepth 1 -type f -delete
238
239
    fi
    # Delete pacman database sync cache
240
241
    if [[ -d "${airootfs_dir}/var/lib/pacman/sync" ]]; then
        find "${airootfs_dir}/var/lib/pacman/sync" -delete
242
243
    fi
    # Delete pacman package cache
244
245
    if [[ -d "${airootfs_dir}/var/cache/pacman/pkg" ]]; then
        find "${airootfs_dir}/var/cache/pacman/pkg" -type f -delete
246
247
    fi
    # Delete all log files, keeps empty dirs.
248
249
    if [[ -d "${airootfs_dir}/var/log" ]]; then
        find "${airootfs_dir}/var/log" -type f -delete
250
251
    fi
    # Delete all temporary files and dirs
252
253
    if [[ -d "${airootfs_dir}/var/tmp" ]]; then
        find "${airootfs_dir}/var/tmp" -mindepth 1 -delete
254
    fi
255
    # Delete package pacman related files.
256
    find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete
257
    # Create an empty /etc/machine-id
258
    printf '' > "${airootfs_dir}/etc/machine-id"
259
260

    _msg_info "Done!"
261
}
262

263
# Makes a ext4 filesystem inside a SquashFS from a source directory.
264
_mkairootfs_img() {
265
266
    if [[ ! -e "${airootfs_dir}" ]]; then
        _msg_error "The path '${airootfs_dir}' does not exist" 1
267
268
    fi

269
    _msg_info "Creating ext4 image of 32 GiB..."
270
    if [[ "${quiet}" == "y" ]]; then
271
        mkfs.ext4 -q -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img" 32G
272
    else
273
        mkfs.ext4 -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img" 32G
274
    fi
275
    tune2fs -c 0 -i 0 -- "${airootfs_dir}.img" > /dev/null
276
    _msg_info "Done!"
277
    _mount_airootfs
278
279
    _msg_info "Copying '${airootfs_dir}/' to '${work_dir}/mnt/airootfs/'..."
    cp -aT -- "${airootfs_dir}/" "${work_dir}/mnt/airootfs/"
280
    chown -- 0:0 "${work_dir}/mnt/airootfs/"
281
    _msg_info "Done!"
282
    _umount_airootfs
283
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
284
285
    _msg_info "Creating SquashFS image, this may take some time..."
    if [[ "${quiet}" = "y" ]]; then
286
        mksquashfs "${airootfs_dir}.img" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
287
            -comp "${sfs_comp}" -no-progress > /dev/null
288
    else
289
        mksquashfs "${airootfs_dir}.img" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
290
            -comp "${sfs_comp}"
291
292
    fi
    _msg_info "Done!"
293
    rm -- "${airootfs_dir}.img"
294
295
}

296
# Makes a SquashFS filesystem from a source directory.
297
_mkairootfs_sfs() {
298
299
    if [[ ! -e "${airootfs_dir}" ]]; then
        _msg_error "The path '${airootfs_dir}' does not exist" 1
300
301
    fi

302
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
303
304
    _msg_info "Creating SquashFS image, this may take some time..."
    if [[ "${quiet}" = "y" ]]; then
305
        mksquashfs "${airootfs_dir}" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
306
            -comp "${sfs_comp}" -no-progress > /dev/null
307
    else
308
        mksquashfs "${airootfs_dir}" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
309
            -comp "${sfs_comp}"
310
311
312
313
    fi
    _msg_info "Done!"
}

314
_mkchecksum() {
315
    _msg_info "Creating checksum file for self-test..."
316
    cd -- "${isofs_dir}/${install_dir}/${arch}"
317
    sha512sum airootfs.sfs > airootfs.sha512
318
    cd -- "${OLDPWD}"
319
    _msg_info "Done!"
320
321
}

322
323
_mksignature() {
    _msg_info "Signing SquashFS image..."
324
    cd -- "${isofs_dir}/${install_dir}/${arch}"
325
    gpg --detach-sign --default-key "${gpg_key}" airootfs.sfs
326
    cd -- "${OLDPWD}"
327
328
329
    _msg_info "Done!"
}

330
331
332
333
334
335
336
# Helper function to run functions only one time.
_run_once() {
    if [[ ! -e "${work_dir}/build.${1}" ]]; then
        "$1"
        touch "${work_dir}/build.${1}"
    fi
}
337

338
339
340
341
342
343
344
345
346
347
348
# 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() {
    local passwd=()
349
350
351

    install -d -m 0755 -o 0 -g 0 -- "${airootfs_dir}"

352
    if [[ -d "${profile}/airootfs" ]]; then
353
        _msg_info "Copying custom custom airootfs files and setting up user home directories..."
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
        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
373
        _msg_info "Done!"
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
    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=()
393

394
    if [[ -e "${profile}/airootfs/etc/passwd" ]]; then
395
        _msg_info "Copying /etc/skel/* to user homes..."
396
        while IFS=':' read -a passwd -r; do
397
398
            [[ "${passwd[5]}" == '/' ]] && continue
            [[ -z "${passwd[5]}" ]] && continue
399
400
401
402
            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"
403
        _msg_info "Done!"
404
405
406
    fi

    if [[ -e "${airootfs_dir}/root/customize_airootfs.sh" ]]; then
407
        _msg_info "Running customize_airootfs.sh in '${airootfs_dir}' chroot..."
408
409
        _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"
410
        _chroot_run
411
        rm -- "${airootfs_dir}/root/customize_airootfs.sh"
412
        _msg_info "Done! customize_airootfs.sh run successfully."
413
414
415
    fi
}

416
417
418
419
420
421
422
423
424
425
426
427
# 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
}

428
# Prepare kernel/initramfs ${install_dir}/boot/
429
_make_boot_on_iso() {
430
431
    _msg_info "Preparing kernel and intramfs for the ISO 9660 file system..."
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/${arch}"
432
433
434
435
    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/"
436
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/intel-ucode/"
437
438
439
440
441
        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/"
442
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/amd-ucode/"
443
444
445
        install -m 0644 -- "${airootfs_dir}/usr/share/licenses/amd-ucode/"* \
            "${isofs_dir}/${install_dir}/boot/licenses/amd-ucode/"
    fi
446
    _msg_info "Done!"
447
448
449
}

# Prepare /${install_dir}/boot/syslinux
450
_make_boot_bios.syslinux.mbr() {
451
452
    _msg_info "Setting up SYSLINUX for BIOS booting from a disk..."
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/syslinux"
453
454
    for _cfg in "${profile}/syslinux/"*.cfg; do
        sed "s|%ARCHISO_LABEL%|${iso_label}|g;
455
456
457
             s|%INSTALL_DIR%|${install_dir}|g;
             s|%ARCH%|${arch}|g" \
             "${_cfg}" > "${isofs_dir}/${install_dir}/boot/syslinux/${_cfg##*/}"
458
    done
459
460
461
    if [[ -e "${profile}/syslinux/splash.png" ]]; then
        install -m 0644 -- "${profile}/syslinux/splash.png" "${isofs_dir}/${install_dir}/boot/syslinux/"
    fi
462
463
464
    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/"
465

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

469
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/syslinux/hdt"
470
471
472
473
    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"
474
475
476
477
478

    # 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"
479
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
480
481
482
        install -m 0644 -- "${airootfs_dir}/usr/share/licenses/common/GPL2/license.txt" \
            "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
    fi
483
    _msg_info "Done! SYSLINUX set up for BIOS booting from a disk successfully."
484
485
486
}

# Prepare /isolinux
487
_make_boot_bios.syslinux.eltorito() {
488
489
    _msg_info "Setting up SYSLINUX for BIOS booting from an optical disc..."
    install -d -m 0755 -- "${isofs_dir}/isolinux"
490
491
492
493
    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"
494
495
496
    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/"
497
498
499

    # isolinux.cfg loads syslinux.cfg
    _run_once _make_boot_bios.syslinux.mbr
500
501

    _msg_info "Done! SYSLINUX set up for BIOS booting from an optical disc successfully."
502
503
504
505
}

# Prepare /EFI on ISO-9660
_make_efi() {
506
507
    _msg_info "Preparing an /EFI directory for the ISO 9660 file system..."
    install -d -m 0755 -- "${isofs_dir}/EFI/BOOT"
508
509
510
    install -m 0644 -- "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${isofs_dir}/EFI/BOOT/BOOTx64.EFI"

511
    install -d -m 0755 -- "${isofs_dir}/loader/entries"
512
513
514
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${isofs_dir}/loader/"

    sed "s|%ARCHISO_LABEL%|${iso_label}|g;
515
516
         s|%INSTALL_DIR%|${install_dir}|g;
         s|%ARCH%|${arch}|g" \
517
518
519
520
521
        "${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 /
522
523
524
    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
525
    _msg_info "Done!"
526
527
}

528
529
# Prepare kernel/initramfs on efiboot.img
_make_boot_on_fat() {
530
531
532
533
    _msg_info "Preparing kernel and intramfs for the FAT file system..."
    install -d -m 0755 -- "${work_dir}/mnt/efiboot/EFI/archiso"
    install -m 0644 -- "${airootfs_dir}/boot/vmlinuz-linux" "${work_dir}/mnt/efiboot/EFI/archiso/"
    install -m 0644 -- "${airootfs_dir}/boot/archiso.img" "${work_dir}/mnt/efiboot/EFI/archiso/"
534
    if [[ -e "${airootfs_dir}/boot/intel-ucode.img" ]]; then
535
        install -m 0644 -- "${airootfs_dir}/boot/intel-ucode.img" "${work_dir}/mnt/efiboot/EFI/archiso/"
536
537
    fi
    if [[ -e "${airootfs_dir}/boot/amd-ucode.img" ]]; then
538
        install -m 0644 -- "${airootfs_dir}/boot/amd-ucode.img" "${work_dir}/mnt/efiboot/EFI/archiso/"
539
    fi
540
    _msg_info "Done!"
541
542
543
544
}

# Prepare efiboot.img::/EFI for EFI boot mode
_make_boot_uefi-x64.systemd-boot.esp() {
545
546
    _msg_info "Setting up systemd-boot for UEFI booting..."
    install -d -m 0755 -- "${isofs_dir}/EFI/archiso"
547
548
    mkfs.fat -C -n ARCHISO_EFI "${isofs_dir}/EFI/archiso/efiboot.img" 65536

549
    _mount_efibootimg
550

551
    install -d -m 0755 -- "${work_dir}/mnt/efiboot/EFI/BOOT"
552
    install -m 0644 -- "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
553
        "${work_dir}/mnt/efiboot/EFI/BOOT/BOOTx64.EFI"
554

555
556
    install -d -m 0755 -- "${work_dir}/mnt/efiboot/loader/entries"
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${work_dir}/mnt/efiboot/loader/"
557
558

    sed "s|%ARCHISO_LABEL%|${iso_label}|g;
559
560
         s|%INSTALL_DIR%|${install_dir}|g;
         s|%ARCH%|${arch}|g" \
561
        "${profile}/efiboot/loader/entries/archiso-x86_64-cd.conf" > \
562
        "${work_dir}/mnt/efiboot/loader/entries/archiso-x86_64.conf"
563
564

    # shellx64.efi is picked up automatically when on /
565
    if [[ -e "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
566
        install -m 0644 -- "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" "${work_dir}/mnt/efiboot/shellx64.efi"
567
568
569
    fi

    # Copy kernel and initramfs
570
    _run_once _make_boot_on_fat
571

572
573
574
    _umount_efibootimg

    _msg_info "Done! systemd-boot set up for UEFI booting successfully."
575
576
}

577
578
579
580
581
582
583
# 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
}

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# 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() {
599
600
    local xorrisofs_options=()

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

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
    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 \
nl6720's avatar
nl6720 committed
635
636
            -joliet \
            -joliet-long \
637
638
639
640
641
642
643
644
            -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}/"
645
646
    _msg_info "Done!"
    du -h -- "${out_dir}/${img_name}"
647
648
649
}

# Read profile's values from profiledef.sh
650
_read_profile() {
651
652
653
654
655
656
657
658
    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
659
660
        cd -- "${profile}"

661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
        # Source profile's variables
        # shellcheck source=configs/releng/profiledef.sh
        . "${profile}/profiledef.sh"

        # 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}")

        cd -- "${OLDPWD}"
    fi
}

677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
# set overrides from mkarchiso option parameters, if present
_set_overrides() {
    if [[ -n "$override_iso_label" ]]; then
        iso_label="$override_iso_label"
    fi
    if [[ -n "$override_iso_publisher" ]]; then
        iso_publisher="$override_iso_publisher"
    fi
    if [[ -n "$override_iso_application" ]]; then
        iso_application="$override_iso_application"
    fi
    if [[ -n "$override_install_dir" ]]; then
        install_dir="$override_install_dir"
    fi
    if [[ -n "$override_pacman_conf" ]]; then
        pacman_conf="$override_pacman_conf"
    fi
    if [[ -n "$override_gpg_key" ]]; then
        gpg_key="$override_gpg_key"
    fi
}


700
701
702
703
704
705
706
707
_export_gpg_publickey() {
    if [[ -n "${gpg_key}" ]]; then
        gpg --batch --output "${work_dir}/pubkey.gpg" --export "${gpg_key}"
    fi
}


_make_pkglist() {
708
    install -d -m 0755 -- "${isofs_dir}/${install_dir}"
709
    _msg_info "Creating a list of installed packages on live-enviroment..."
710
    pacman -Q --sysroot "${airootfs_dir}" > "${isofs_dir}/${install_dir}/pkglist.${arch}.txt"
711
    _msg_info "Done!"
712
}
713

714
715
command_pkglist() {
    _show_config "${FUNCNAME[0]#command_}"
716
    _make_pkglist
717
718
}

719
# Create an ISO9660 filesystem from "iso" directory.
720
command_iso() {
721
    bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito')
722

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

728
    _show_config "${FUNCNAME[0]#command_}"
729
    _make_iso
730
731
}

732
# create airootfs.sfs filesystem, and push it in "iso" directory.
733
734
command_prepare() {
    _show_config "${FUNCNAME[0]#command_}"
735
736

    _cleanup
737
    _make_prepare
738
739
}

740
# Install packages on airootfs.
741
# A basic check to avoid double execution/reinstallation is done via hashing package names.
742
command_install() {
743
744
745
746
    if [[ ! -f "${pacman_conf}" ]]; then
        _msg_error "Pacman config file '${pacman_conf}' does not exist" 1
    fi

747
    if (( ${#pkg_list[@]} == 0 )); then
748
749
750
751
        _msg_error "Packages must be specified" 0
        _usage 1
    fi

752
    _show_config "${FUNCNAME[0]#command_}"
753

754
    _make_packages
755
756
}

757
command_init() {
758
    _show_config "${FUNCNAME[0]#command_}"
759
760
761
762
    _chroot_init
}

command_run() {
763
    _show_config "${FUNCNAME[0]#command_}"
764
765
    _chroot_run
}
766

767
768
769
command_build_profile() {
    # Set up essential directory paths
    airootfs_dir="${work_dir}/${arch}/airootfs"
770
771
772
773
    isofs_dir="${work_dir}/iso"
    # Set ISO file name
    img_name="${iso_name}-${iso_version}-${arch}.iso"

774
775
776
777
778
    _show_config "${FUNCNAME[0]#command_}"

    # Create working directory
    [[ -d "${work_dir}" ]] || install -d -- "${work_dir}"

779
780
781
782
783
784
    _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
785
    _make_bootmodes
786
787
788
    _run_once _cleanup
    _run_once _make_prepare
    _run_once _make_iso
789
790
}

791
while getopts 'p:r:C:L:P:A:D:w:o:s:c:g:vh?' arg; do
792
    case "${arg}" in
793
794
        p)
            read -r -a opt_pkg_list <<< "${OPTARG}"
795
796
            pkg_list+=("${opt_pkg_list[@]}")
            ;;
797
        r) run_cmd="${OPTARG}" ;;
798
799
800
801
802
        C) override_pacman_conf="$(realpath -- "${OPTARG}")" ;;
        L) override_iso_label="${OPTARG}" ;;
        P) override_iso_publisher="${OPTARG}" ;;
        A) override_iso_application="${OPTARG}" ;;
        D) override_install_dir="${OPTARG}" ;;
803
804
        w) work_dir="$(realpath -- "${OPTARG}")" ;;
        o) out_dir="$(realpath -- "${OPTARG}")" ;;
805
        s) sfs_mode="${OPTARG}" ;;
806
        c) sfs_comp="${OPTARG}" ;;
807
        g) override_gpg_key="${OPTARG}" ;;
808
809
810
811
812
813
814
815
816
        v) quiet="n" ;;
        h|?) _usage 0 ;;
        *)
            _msg_error "Invalid argument '${arg}'" 0
            _usage 1
            ;;
    esac
done

817
818
819
820
if (( EUID != 0 )); then
    _msg_error "${app_name} must be run as root." 1
fi

821
822
shift $((OPTIND - 1))

823
if (( $# < 1 )); then
824
825
    _msg_error "No command specified" 0
    _usage 1
826
fi
827
828
command_name="${1}"

829
# Set directory path defaults for legacy commands
830
airootfs_dir="${work_dir}/airootfs"
831
isofs_dir="${work_dir}/iso"
832

833
case "${command_name}" in
834
    init)
835
836
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
        _set_overrides
837
838
839
        command_init
        ;;
    install)
840
841
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
        _set_overrides
842
843
844
        command_install
        ;;
    run)
845
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
846
        command_run
847
848
        ;;
    prepare)
849
850
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
        _set_overrides
851
852
        command_prepare
        ;;
853
    pkglist)
854
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
855
856
        command_pkglist
        ;;
857
    iso)
858
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
859
        if (( $# < 2 )); then
860
861
862
            _msg_error "No image specified" 0
            _usage 1
        fi
863
        img_name="${2}"
864
        _set_overrides
865
866
867
        command_iso
        ;;
    *)
868
869
870
871
872
873
874
        # NOTE: we call read_profile here, assuming that the first non-option parameter is a profile directory
        # This way we can retain backwards compatibility with legacy build.sh scripts until we deprecate the old way of
        # calling mkarchiso with named parameters in v49
        profile="$(realpath -- "${command_name}")"
        _read_profile
        _set_overrides
        command_build_profile
875
876
        ;;
esac
877
878

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