mkarchiso 31.3 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
94
95
    trap - EXIT HUP INT TERM
}

# Show help usage, with an exit status.
# $1: exit status number.
96
_usage() {
97
    IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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
118
     -r <run_cmd>     Set a command to be run in chroot (only relevant for legacy 'run' command)
119
120
121
122
123
124
125
126
127
128
129
130
131
                      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
132
    init
133
        initialize a chroot for building
134
    install
135
        install packages to the chroot
136
    run
137
        run a command in the chroot
138
    prepare
139
        cleanup and prepare the airootfs
140
    pkglist
141
        create a list of packages installed on the medium
142
    iso
143
        create the ISO
144
ENDUSAGETEXT
145
    printf '%s' "${usagetext}"
146
    exit "${1}"
147
148
}

149
# Shows configuration according to command mode.
150
151
# $1: build_profile | init | install | run | prepare | iso
_show_config() {
152
    local _mode="$1"
153
    _msg_info "${app_name} configuration settings"
154
    [[ ! -d "${command_name}" ]] && _msg_info "           Legacy Command:   ${command_name}"
155
156
157
158
    _msg_info "             Architecture:   ${arch}"
    _msg_info "        Working directory:   ${work_dir}"
    _msg_info "   Installation directory:   ${install_dir}"
    case "${_mode}" in
159
160
161
162
163
164
165
166
167
168
169
170
        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[*]}"
            ;;
171
        init)
172
            _msg_info "Pacman configuration file:   ${pacman_conf}"
173
174
            ;;
        install)
175
176
            _msg_info "                  GPG key:   ${gpg_key:-None}"
            _msg_info "Pacman configuration file:   ${pacman_conf}"
177
            _msg_info "                 Packages:   ${pkg_list[*]}"
178
            ;;
179
180
181
        run)
            _msg_info "              Run command:   ${run_cmd}"
            ;;
182
        prepare)
183
            _msg_info "                  GPG key:   ${gpg_key:-None}"
184
            ;;
185
186
        pkglist)
            ;;
187
        iso)
188
189
190
191
192
            _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}"
193
            ;;
194
    esac
195
    [[ "${quiet}" == "y" ]] || printf '\n'
196
}
197

198
# Install desired packages to airootfs
199
_pacman() {
200
    _msg_info "Installing packages to '${airootfs_dir}/'..."
201
202

    if [[ "${quiet}" = "y" ]]; then
203
        pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" "$@" &> /dev/null
204
    else
205
        pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" "$@"
206
    fi
207

208
    _msg_info "Done! Packages installed successfully."
209
210
}

211
# Cleanup airootfs
212
_cleanup() {
213
    _msg_info "Cleaning up what we can on airootfs..."
214

215
    # Delete all files in /boot
216
    if [[ -d "${airootfs_dir}/boot" ]]; then
217
        find "${airootfs_dir}/boot" -mindepth 1 -delete
218
219
    fi
    # Delete pacman database sync cache files (*.tar.gz)
220
221
    if [[ -d "${airootfs_dir}/var/lib/pacman" ]]; then
        find "${airootfs_dir}/var/lib/pacman" -maxdepth 1 -type f -delete
222
223
    fi
    # Delete pacman database sync cache
224
225
    if [[ -d "${airootfs_dir}/var/lib/pacman/sync" ]]; then
        find "${airootfs_dir}/var/lib/pacman/sync" -delete
226
227
    fi
    # Delete pacman package cache
228
229
    if [[ -d "${airootfs_dir}/var/cache/pacman/pkg" ]]; then
        find "${airootfs_dir}/var/cache/pacman/pkg" -type f -delete
230
231
    fi
    # Delete all log files, keeps empty dirs.
232
233
    if [[ -d "${airootfs_dir}/var/log" ]]; then
        find "${airootfs_dir}/var/log" -type f -delete
234
235
    fi
    # Delete all temporary files and dirs
236
237
    if [[ -d "${airootfs_dir}/var/tmp" ]]; then
        find "${airootfs_dir}/var/tmp" -mindepth 1 -delete
238
    fi
239
    # Delete package pacman related files.
240
    find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete
241
    # Create an empty /etc/machine-id
242
    printf '' > "${airootfs_dir}/etc/machine-id"
243
244

    _msg_info "Done!"
245
}
246

247
# Makes a ext4 filesystem inside a SquashFS from a source directory.
248
_mkairootfs_img() {
249
250
    if [[ ! -e "${airootfs_dir}" ]]; then
        _msg_error "The path '${airootfs_dir}' does not exist" 1
251
252
    fi

253
    _msg_info "Creating ext4 image of 32 GiB..."
254
    if [[ "${quiet}" == "y" ]]; then
255
        mkfs.ext4 -q -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img" 32G
256
    else
257
        mkfs.ext4 -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${airootfs_dir}.img" 32G
258
    fi
259
    tune2fs -c 0 -i 0 -- "${airootfs_dir}.img" > /dev/null
260
    _msg_info "Done!"
261
    _mount_airootfs
262
263
    _msg_info "Copying '${airootfs_dir}/' to '${work_dir}/mnt/airootfs/'..."
    cp -aT -- "${airootfs_dir}/" "${work_dir}/mnt/airootfs/"
264
    chown -- 0:0 "${work_dir}/mnt/airootfs/"
265
    _msg_info "Done!"
266
    _umount_airootfs
267
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
268
269
    _msg_info "Creating SquashFS image, this may take some time..."
    if [[ "${quiet}" = "y" ]]; then
270
        mksquashfs "${airootfs_dir}.img" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
271
            -comp "${sfs_comp}" -no-progress > /dev/null
272
    else
273
        mksquashfs "${airootfs_dir}.img" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
274
            -comp "${sfs_comp}"
275
276
    fi
    _msg_info "Done!"
277
    rm -- "${airootfs_dir}.img"
278
279
}

280
# Makes a SquashFS filesystem from a source directory.
281
_mkairootfs_sfs() {
282
283
    if [[ ! -e "${airootfs_dir}" ]]; then
        _msg_error "The path '${airootfs_dir}' does not exist" 1
284
285
    fi

286
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/${arch}"
287
288
    _msg_info "Creating SquashFS image, this may take some time..."
    if [[ "${quiet}" = "y" ]]; then
289
        mksquashfs "${airootfs_dir}" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
290
            -comp "${sfs_comp}" -no-progress > /dev/null
291
    else
292
        mksquashfs "${airootfs_dir}" "${isofs_dir}/${install_dir}/${arch}/airootfs.sfs" -noappend \
293
            -comp "${sfs_comp}"
294
295
296
297
    fi
    _msg_info "Done!"
}

298
_mkchecksum() {
299
    _msg_info "Creating checksum file for self-test..."
300
    cd -- "${isofs_dir}/${install_dir}/${arch}"
301
    sha512sum airootfs.sfs > airootfs.sha512
302
    cd -- "${OLDPWD}"
303
    _msg_info "Done!"
304
305
}

306
307
_mksignature() {
    _msg_info "Signing SquashFS image..."
308
    cd -- "${isofs_dir}/${install_dir}/${arch}"
309
    gpg --detach-sign --default-key "${gpg_key}" airootfs.sfs
310
    cd -- "${OLDPWD}"
311
312
313
    _msg_info "Done!"
}

314
315
316
317
318
319
320
# Helper function to run functions only one time.
_run_once() {
    if [[ ! -e "${work_dir}/build.${1}" ]]; then
        "$1"
        touch "${work_dir}/build.${1}"
    fi
}
321

322
323
324
325
326
327
328
329
330
331
332
# 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=()
333
334
335

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

336
    if [[ -d "${profile}/airootfs" ]]; then
337
        _msg_info "Copying custom custom airootfs files and setting up user home directories..."
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
        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
357
        _msg_info "Done!"
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
    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=()
377

378
    if [[ -e "${profile}/airootfs/etc/passwd" ]]; then
379
        _msg_info "Copying /etc/skel/* to user homes..."
380
        while IFS=':' read -a passwd -r; do
381
            (( passwd[2] >= 1000 && passwd[2] < 60000 )) || continue
382
383
            [[ "${passwd[5]}" == '/' ]] && continue
            [[ -z "${passwd[5]}" ]] && continue
384
385
            cp -dnRT --preserve=mode,timestamps,links -- "${airootfs_dir}/etc/skel" "${airootfs_dir}${passwd[5]}"
            chmod -f 0750 -- "${airootfs_dir}${passwd[5]}"
386
387
388
            chown -hR -- "${passwd[2]}:${passwd[3]}" "${airootfs_dir}${passwd[5]}"

        done < "${profile}/airootfs/etc/passwd"
389
        _msg_info "Done!"
390
391
392
    fi

    if [[ -e "${airootfs_dir}/root/customize_airootfs.sh" ]]; then
393
        _msg_info "Running customize_airootfs.sh in '${airootfs_dir}' chroot..."
394
395
        _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"
396
        _chroot_run
397
        rm -- "${airootfs_dir}/root/customize_airootfs.sh"
398
        _msg_info "Done! customize_airootfs.sh run successfully."
399
400
401
    fi
}

402
403
404
405
406
407
408
409
410
411
412
413
# 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
}

414
# Prepare kernel/initramfs ${install_dir}/boot/
415
_make_boot_on_iso() {
416
    local ucode_image
417
418
    _msg_info "Preparing kernel and intramfs for the ISO 9660 file system..."
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/${arch}"
419
420
421
422
423
424
425
426
427
428
429
430
431
    install -m 0644 -- "${airootfs_dir}/boot/initramfs-"*".img" "${isofs_dir}/${install_dir}/boot/${arch}/"
    install -m 0644 -- "${airootfs_dir}/boot/vmlinuz-"* "${isofs_dir}/${install_dir}/boot/${arch}/"

    for ucode_image in {intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio}; do
        if [[ -e "${airootfs_dir}/boot/${ucode_image}" ]]; then
            install -m 0644 -- "${airootfs_dir}/boot/${ucode_image}" "${isofs_dir}/${install_dir}/boot/"
            if [[ -e "${airootfs_dir}/usr/share/licenses/${ucode_image%.*}/" ]]; then
                install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/"
                install -m 0644 -- "${airootfs_dir}/usr/share/licenses/${ucode_image%.*}/"* \
                    "${isofs_dir}/${install_dir}/boot/licenses/${ucode_image%.*}/"
            fi
        fi
    done
432
    _msg_info "Done!"
433
434
435
}

# Prepare /${install_dir}/boot/syslinux
436
_make_boot_bios.syslinux.mbr() {
437
438
    _msg_info "Setting up SYSLINUX for BIOS booting from a disk..."
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/syslinux"
439
440
    for _cfg in "${profile}/syslinux/"*.cfg; do
        sed "s|%ARCHISO_LABEL%|${iso_label}|g;
441
442
443
             s|%INSTALL_DIR%|${install_dir}|g;
             s|%ARCH%|${arch}|g" \
             "${_cfg}" > "${isofs_dir}/${install_dir}/boot/syslinux/${_cfg##*/}"
444
    done
445
446
447
    if [[ -e "${profile}/syslinux/splash.png" ]]; then
        install -m 0644 -- "${profile}/syslinux/splash.png" "${isofs_dir}/${install_dir}/boot/syslinux/"
    fi
448
449
450
    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/"
451

452
    _run_once _make_boot_on_iso
453

454
455
456
457
458
459
460
461
462
    if [[ -e "${isofs_dir}/${install_dir}/boot/syslinux/hdt.c32" ]]; then
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/syslinux/hdt"
        if [[ -e "${airootfs_dir}/usr/share/hwdata/pci.ids" ]]; then
            gzip -c -9 "${airootfs_dir}/usr/share/hwdata/pci.ids" > \
                "${isofs_dir}/${install_dir}/boot/syslinux/hdt/pciids.gz"
        fi
        find "${airootfs_dir}/usr/lib/modules" -name 'modules.alias' -print -exec gzip -c -9 '{}' ';' -quit > \
            "${isofs_dir}/${install_dir}/boot/syslinux/hdt/modalias.gz"
    fi
463
464
465
466
467

    # 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"
468
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
469
470
471
        install -m 0644 -- "${airootfs_dir}/usr/share/licenses/common/GPL2/license.txt" \
            "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
    fi
472
    _msg_info "Done! SYSLINUX set up for BIOS booting from a disk successfully."
473
474
475
}

# Prepare /isolinux
476
_make_boot_bios.syslinux.eltorito() {
477
478
    _msg_info "Setting up SYSLINUX for BIOS booting from an optical disc..."
    install -d -m 0755 -- "${isofs_dir}/isolinux"
479
480
481
482
483
484
    for _cfg in "${profile}/isolinux/"*".cfg"; do
        sed "s|%ARCHISO_LABEL%|${iso_label}|g;
             s|%INSTALL_DIR%|${install_dir}|g;
             s|%ARCH%|${arch}|g" \
             "${_cfg}" > "${isofs_dir}/isolinux/${_cfg##*/}"
    done
485
486
487
    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/"
488
489
490

    # isolinux.cfg loads syslinux.cfg
    _run_once _make_boot_bios.syslinux.mbr
491
492

    _msg_info "Done! SYSLINUX set up for BIOS booting from an optical disc successfully."
493
494
495
496
}

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

502
    install -d -m 0755 -- "${isofs_dir}/loader/entries"
503
504
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${isofs_dir}/loader/"

505
506
507
508
509
510
    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
511
512
513

    # edk2-shell based UEFI shell
    # shellx64.efi is picked up automatically when on /
514
515
516
    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
517
    _msg_info "Done!"
518
519
}

520
521
# Prepare kernel/initramfs on efiboot.img
_make_boot_on_fat() {
522
    local ucode_image all_ucode_images=()
523
    _msg_info "Preparing kernel and intramfs for the FAT file system..."
524
525
    mmd -i "${isofs_dir}/EFI/archiso/efiboot.img" \
        "::/${install_dir}" "::/${install_dir}/boot" "::/${install_dir}/boot/${arch}"
526
527
528
529
530
531
532
533
534
535
536
    mcopy -i "${isofs_dir}/EFI/archiso/efiboot.img" "${airootfs_dir}/boot/vmlinuz-"* \
        "${airootfs_dir}/boot/initramfs-"*".img" "::/${install_dir}/boot/${arch}/"
    for ucode_image in \
        "${airootfs_dir}/boot/"{intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio}
    do
        if [[ -e "${ucode_image}" ]]; then
            all_ucode_images+=("${ucode_image}")
        fi
    done
    if (( ${#all_ucode_images[@]} )); then
        mcopy -i "${isofs_dir}/EFI/archiso/efiboot.img" "${all_ucode_images[@]}" "::/${install_dir}/boot/"
537
    fi
538
    _msg_info "Done!"
539
540
541
542
}

# Prepare efiboot.img::/EFI for EFI boot mode
_make_boot_uefi-x64.systemd-boot.esp() {
543
    local efiboot_imgsize="0"
544
545
    _msg_info "Setting up systemd-boot for UEFI booting..."
    install -d -m 0755 -- "${isofs_dir}/EFI/archiso"
546

547
548
    # the required image size in KiB (rounded up to the next full MiB with an additional MiB for reserved sectors)
    efiboot_imgsize="$(du -bc \
549
550
551
552
553
554
        "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
        "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" \
        "${profile}/efiboot/" \
        "${airootfs_dir}/boot/vmlinuz-"* \
        "${airootfs_dir}/boot/initramfs-"*".img" \
        "${airootfs_dir}/boot/"{intel-uc.img,intel-ucode.img,amd-uc.img,amd-ucode.img,early_ucode.cpio,microcode.cpio} \
555
556
557
558
559
        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))}'
        )"
560
561
    # 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
562
    _msg_info "Creating FAT image of size: ${efiboot_imgsize} KiB..."
563
    mkfs.fat -C -n ARCHISO_EFI "${isofs_dir}/EFI/archiso/efiboot.img" "$efiboot_imgsize"
564

565
566
567
    mmd -i "${isofs_dir}/EFI/archiso/efiboot.img" ::/EFI ::/EFI/BOOT
    mcopy -i "${isofs_dir}/EFI/archiso/efiboot.img" \
        "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" ::/EFI/BOOT/BOOTx64.EFI
568

569
570
    mmd -i "${isofs_dir}/EFI/archiso/efiboot.img" ::/loader ::/loader/entries
    mcopy -i "${isofs_dir}/EFI/archiso/efiboot.img" "${profile}/efiboot/loader/loader.conf" ::/loader/
571
572
573
574
575
576
    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}" | mcopy -i "${isofs_dir}/EFI/archiso/efiboot.img" - "::/loader/entries/${_conf##*/}"
    done
577
578

    # shellx64.efi is picked up automatically when on /
579
    if [[ -e "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
580
581
        mcopy -i "${isofs_dir}/EFI/archiso/efiboot.img" \
            "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ::/shellx64.efi
582
583
584
    fi

    # Copy kernel and initramfs
585
    _run_once _make_boot_on_fat
586

587
    _msg_info "Done! systemd-boot set up for UEFI booting successfully."
588
589
}

590
591
592
593
594
595
596
# 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
}

597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
# 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() {
612
613
    local xorrisofs_options=()

614
615
    [[ -d "${out_dir}" ]] || install -d -- "${out_dir}"

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
    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
648
649
            -joliet \
            -joliet-long \
650
651
652
653
654
655
656
657
            -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}/"
658
659
    _msg_info "Done!"
    du -h -- "${out_dir}/${img_name}"
660
661
662
}

# Read profile's values from profiledef.sh
663
_read_profile() {
664
665
666
667
668
669
670
671
    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
672
673
        cd -- "${profile}"

674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
        # 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
}

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


713
714
715
716
717
718
719
720
_export_gpg_publickey() {
    if [[ -n "${gpg_key}" ]]; then
        gpg --batch --output "${work_dir}/pubkey.gpg" --export "${gpg_key}"
    fi
}


_make_pkglist() {
721
    install -d -m 0755 -- "${isofs_dir}/${install_dir}"
722
    _msg_info "Creating a list of installed packages on live-enviroment..."
723
    pacman -Q --sysroot "${airootfs_dir}" > "${isofs_dir}/${install_dir}/pkglist.${arch}.txt"
724
    _msg_info "Done!"
725
}
726

727
728
command_pkglist() {
    _show_config "${FUNCNAME[0]#command_}"
729
    _make_pkglist
730
731
}

732
# Create an ISO9660 filesystem from "iso" directory.
733
command_iso() {
734
    bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito')
735

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

741
    _show_config "${FUNCNAME[0]#command_}"
742
    _make_iso
743
744
}

745
# create airootfs.sfs filesystem, and push it in "iso" directory.
746
747
command_prepare() {
    _show_config "${FUNCNAME[0]#command_}"
748
749

    _cleanup
750
    _make_prepare
751
752
}

753
# Install packages on airootfs.
754
# A basic check to avoid double execution/reinstallation is done via hashing package names.
755
command_install() {
756
757
758
759
    if [[ ! -f "${pacman_conf}" ]]; then
        _msg_error "Pacman config file '${pacman_conf}' does not exist" 1
    fi

760
    if (( ${#pkg_list[@]} == 0 )); then
761
762
763
764
        _msg_error "Packages must be specified" 0
        _usage 1
    fi

765
    _show_config "${FUNCNAME[0]#command_}"
766

767
    _make_packages
768
769
}

770
command_init() {
771
    _show_config "${FUNCNAME[0]#command_}"
772
773
774
775
    _chroot_init
}

command_run() {
776
    _show_config "${FUNCNAME[0]#command_}"
777
778
    _chroot_run
}
779

780
781
782
command_build_profile() {
    # Set up essential directory paths
    airootfs_dir="${work_dir}/${arch}/airootfs"
783
784
785
786
    isofs_dir="${work_dir}/iso"
    # Set ISO file name
    img_name="${iso_name}-${iso_version}-${arch}.iso"

787
788
789
790
791
    _show_config "${FUNCNAME[0]#command_}"

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

792
793
794
795
796
797
    _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
798
    _make_bootmodes
799
800
801
    _run_once _cleanup
    _run_once _make_prepare
    _run_once _make_iso
802
803
}

804
while getopts 'p:r:C:L:P:A:D:w:o:s:c:g:vh?' arg; do
805
    case "${arg}" in
806
807
        p)
            read -r -a opt_pkg_list <<< "${OPTARG}"
808
809
            pkg_list+=("${opt_pkg_list[@]}")
            ;;
810
        r) run_cmd="${OPTARG}" ;;
811
812
813
814
815
        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}" ;;
816
817
        w) work_dir="$(realpath -- "${OPTARG}")" ;;
        o) out_dir="$(realpath -- "${OPTARG}")" ;;
818
        s) sfs_mode="${OPTARG}" ;;
819
        c) sfs_comp="${OPTARG}" ;;
820
        g) override_gpg_key="${OPTARG}" ;;
821
822
823
824
825
826
827
828
829
        v) quiet="n" ;;
        h|?) _usage 0 ;;
        *)
            _msg_error "Invalid argument '${arg}'" 0
            _usage 1
            ;;
    esac
done

830
831
832
833
834
if (( OPTIND <= 1 )); then
    _msg_error "No command specified" 0
    _usage 1
fi

835
836
837
838
if (( EUID != 0 )); then
    _msg_error "${app_name} must be run as root." 1
fi

839
840
841
shift $((OPTIND - 1))
command_name="${1}"

842
# Set directory path defaults for legacy commands
843
airootfs_dir="${work_dir}/airootfs"
844
isofs_dir="${work_dir}/iso"
845

846
case "${command_name}" in
847
    init)
848
849
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
        _set_overrides
850
851
852
        command_init
        ;;
    install)
853
854
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
        _set_overrides
855
856
857
        command_install
        ;;
    run)
858
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
859
        command_run
860
861
        ;;
    prepare)
862
863
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
        _set_overrides
864
865
        command_prepare
        ;;
866
    pkglist)
867
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
868
869
        command_pkglist
        ;;
870
    iso)
871
        _msg_warning "The '${command_name}' command is deprecated! It will be removed with archiso v49."
872
        if (( $# < 2 )); then
873
874
875
            _msg_error "No image specified" 0
            _usage 1
        fi
876
        img_name="${2}"
877
        _set_overrides
878
879
880
        command_iso
        ;;
    *)
881
882
883
884
885
886
887
        # 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
888
889
        ;;
esac
890
891

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