mkarchiso 28.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

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
    [[ "${quiet}" == "y" ]] || printf '[%s] INFO: %s\n' "${app_name}" "${_msg}"
42
43
}

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

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

63
_chroot_init() {
64
    install -d -m 0755 -o 0 -g 0 -- "${airootfs_dir}"
65
    _pacman base syslinux
66
67
68
}

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

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

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

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
_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
}

104
105
# Show help usage, with an exit status.
# $1: exit status number.
106
_usage() {
107
108
109
    IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true
usage ${app_name} [options] command <command options>
 general options:
110
    -B <profile_dir> Directory of the archiso profile to build
111
    -p PACKAGE(S)    Package(s) to install, can be used multiple times
112
    -C <file>        pacman configuration file.
113
                     Default: '${pacman_conf}'
114
    -L <label>       Set the ISO volume label
115
                     Default: '${iso_label}'
116
    -P <publisher>   Set a ISO publisher
117
                     Default: '${iso_publisher}'
118
    -A <application> Set an application name for the ISO
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
                     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:
136
137
   build_profile
      build an iso image from a profile
138
ENDUSAGETEXT
139
    printf '%s' "${usagetext}"
140
    exit "${1}"
141
142
}

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

192
# Install desired packages to airootfs
193
_pacman() {
194
    _msg_info "Installing packages to '${airootfs_dir}/'..."
195
196

    if [[ "${quiet}" = "y" ]]; then
197
        pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" "$@" &> /dev/null
198
    else
199
        pacstrap -C "${pacman_conf}" -c -G -M -- "${airootfs_dir}" "$@"
200
    fi
201

202
    _msg_info "Done! Packages installed successfully."
203
204
}

205
# Cleanup airootfs
206
_cleanup() {
207
    _msg_info "Cleaning up what we can on airootfs..."
208

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

    _msg_info "Done!"
239
}
240

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

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

274
# Makes a SquashFS filesystem from a source directory.
275
_mkairootfs_sfs() {
276
277
    if [[ ! -e "${airootfs_dir}" ]]; then
        _msg_error "The path '${airootfs_dir}' does not exist" 1
278
279
    fi

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

292
_mkchecksum() {
293
    _msg_info "Creating checksum file for self-test..."
294
    cd -- "${isofs_dir}/${install_dir}/${arch}"
295
    sha512sum airootfs.sfs > airootfs.sha512
296
    cd -- "${OLDPWD}"
297
    _msg_info "Done!"
298
299
}

300
301
_mksignature() {
    _msg_info "Signing SquashFS image..."
302
    cd -- "${isofs_dir}/${install_dir}/${arch}"
303
    gpg --detach-sign --default-key "${gpg_key}" airootfs.sfs
304
    cd -- "${OLDPWD}"
305
306
307
    _msg_info "Done!"
}

308
309
310
311
312
313
314
# Helper function to run functions only one time.
_run_once() {
    if [[ ! -e "${work_dir}/build.${1}" ]]; then
        "$1"
        touch "${work_dir}/build.${1}"
    fi
}
315

316
317
318
319
320
321
322
323
324
325
326
# 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=()
327
328
329

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

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

372
    if [[ -e "${profile}/airootfs/etc/passwd" ]]; then
373
        _msg_info "Copying /etc/skel/* to user homes..."
374
        while IFS=':' read -a passwd -r; do
375
376
            [[ "${passwd[5]}" == '/' ]] && continue
            [[ -z "${passwd[5]}" ]] && continue
377
378
379
380
            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"
381
        _msg_info "Done!"
382
383
384
    fi

    if [[ -e "${airootfs_dir}/root/customize_airootfs.sh" ]]; then
385
        _msg_info "Running customize_airootfs.sh in '${airootfs_dir}' chroot..."
386
387
        _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"
388
        _chroot_run
389
        rm -- "${airootfs_dir}/root/customize_airootfs.sh"
390
        _msg_info "Done! customize_airootfs.sh run successfully."
391
392
393
    fi
}

394
395
396
397
398
399
400
401
402
403
404
405
# 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
}

406
# Prepare kernel/initramfs ${install_dir}/boot/
407
_make_boot_on_iso() {
408
409
    _msg_info "Preparing kernel and intramfs for the ISO 9660 file system..."
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/${arch}"
410
411
412
413
    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/"
414
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/intel-ucode/"
415
416
417
418
419
        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/"
420
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/amd-ucode/"
421
422
423
        install -m 0644 -- "${airootfs_dir}/usr/share/licenses/amd-ucode/"* \
            "${isofs_dir}/${install_dir}/boot/licenses/amd-ucode/"
    fi
424
    _msg_info "Done!"
425
426
427
}

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

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

447
    install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/syslinux/hdt"
448
449
450
451
    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"
452
453
454
455
456

    # 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"
457
        install -d -m 0755 -- "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
458
459
460
        install -m 0644 -- "${airootfs_dir}/usr/share/licenses/common/GPL2/license.txt" \
            "${isofs_dir}/${install_dir}/boot/licenses/memtest86+/"
    fi
461
    _msg_info "Done! SYSLINUX set up for BIOS booting from a disk successfully."
462
463
464
}

# Prepare /isolinux
465
_make_boot_bios.syslinux.eltorito() {
466
467
    _msg_info "Setting up SYSLINUX for BIOS booting from an optical disc..."
    install -d -m 0755 -- "${isofs_dir}/isolinux"
468
469
470
471
    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"
472
473
474
    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/"
475
476
477

    # isolinux.cfg loads syslinux.cfg
    _run_once _make_boot_bios.syslinux.mbr
478
479

    _msg_info "Done! SYSLINUX set up for BIOS booting from an optical disc successfully."
480
481
482
483
}

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

489
    install -d -m 0755 -- "${isofs_dir}/loader/entries"
490
491
492
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${isofs_dir}/loader/"

    sed "s|%ARCHISO_LABEL%|${iso_label}|g;
493
494
         s|%INSTALL_DIR%|${install_dir}|g;
         s|%ARCH%|${arch}|g" \
495
496
497
498
499
        "${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 /
500
501
502
    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
503
    _msg_info "Done!"
504
505
}

506
507
# Prepare kernel/initramfs on efiboot.img
_make_boot_on_fat() {
508
509
510
511
    _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/"
512
    if [[ -e "${airootfs_dir}/boot/intel-ucode.img" ]]; then
513
        install -m 0644 -- "${airootfs_dir}/boot/intel-ucode.img" "${work_dir}/mnt/efiboot/EFI/archiso/"
514
515
    fi
    if [[ -e "${airootfs_dir}/boot/amd-ucode.img" ]]; then
516
        install -m 0644 -- "${airootfs_dir}/boot/amd-ucode.img" "${work_dir}/mnt/efiboot/EFI/archiso/"
517
    fi
518
    _msg_info "Done!"
519
520
521
522
}

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

527
    _mount_efibootimg
528

529
    install -d -m 0755 -- "${work_dir}/mnt/efiboot/EFI/BOOT"
530
    install -m 0644 -- "${airootfs_dir}/usr/lib/systemd/boot/efi/systemd-bootx64.efi" \
531
        "${work_dir}/mnt/efiboot/EFI/BOOT/BOOTx64.EFI"
532

533
534
    install -d -m 0755 -- "${work_dir}/mnt/efiboot/loader/entries"
    install -m 0644 -- "${profile}/efiboot/loader/loader.conf" "${work_dir}/mnt/efiboot/loader/"
535
536

    sed "s|%ARCHISO_LABEL%|${iso_label}|g;
537
538
         s|%INSTALL_DIR%|${install_dir}|g;
         s|%ARCH%|${arch}|g" \
539
        "${profile}/efiboot/loader/entries/archiso-x86_64-cd.conf" > \
540
        "${work_dir}/mnt/efiboot/loader/entries/archiso-x86_64.conf"
541
542

    # shellx64.efi is picked up automatically when on /
543
    if [[ -e "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" ]]; then
544
        install -m 0644 -- "${airootfs_dir}/usr/share/edk2-shell/x64/Shell_Full.efi" "${work_dir}/mnt/efiboot/shellx64.efi"
545
546
547
    fi

    # Copy kernel and initramfs
548
    _run_once _make_boot_on_fat
549

550
551
552
    _umount_efibootimg

    _msg_info "Done! systemd-boot set up for UEFI booting successfully."
553
554
}

555
556
557
558
559
560
561
# 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
}

562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
# 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() {
577
578
    local xorrisofs_options=()

579
580
    [[ -d "${out_dir}" ]] || install -d -- "${out_dir}"

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
    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
613
614
            -joliet \
            -joliet-long \
615
616
617
618
619
620
621
622
            -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}/"
623
624
    _msg_info "Done!"
    du -h -- "${out_dir}/${img_name}"
625
626
627
}

# Read profile's values from profiledef.sh
628
_read_profile() {
629
630
631
632
633
634
635
636
    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
637
638
        cd -- "${profile}"

639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
        # 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
}

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


_make_pkglist() {
663
    install -d -m 0755 -- "${isofs_dir}/${install_dir}"
664
    _msg_info "Creating a list of installed packages on live-enviroment..."
665
    pacman -Q --sysroot "${airootfs_dir}" > "${isofs_dir}/${install_dir}/pkglist.${arch}.txt"
666
    _msg_info "Done!"
667
}
668

669
670
command_pkglist() {
    _show_config "${FUNCNAME[0]#command_}"
671
    _make_pkglist
672
673
}

674
# Create an ISO9660 filesystem from "iso" directory.
675
command_iso() {
676
    bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito')
677

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

683
    _show_config "${FUNCNAME[0]#command_}"
684
    _make_iso
685
686
}

687
# create airootfs.sfs filesystem, and push it in "iso" directory.
688
689
command_prepare() {
    _show_config "${FUNCNAME[0]#command_}"
690
691

    _cleanup
692
    _make_prepare
693
694
}

695
# Install packages on airootfs.
696
# A basic check to avoid double execution/reinstallation is done via hashing package names.
697
command_install() {
698
699
700
701
    if [[ ! -f "${pacman_conf}" ]]; then
        _msg_error "Pacman config file '${pacman_conf}' does not exist" 1
    fi

702
    if (( ${#pkg_list[@]} == 0 )); then
703
704
705
706
        _msg_error "Packages must be specified" 0
        _usage 1
    fi

707
    _show_config "${FUNCNAME[0]#command_}"
708

709
    _make_packages
710
711
}

712
command_init() {
713
    _show_config "${FUNCNAME[0]#command_}"
714
715
716
717
    _chroot_init
}

command_run() {
718
    _show_config "${FUNCNAME[0]#command_}"
719
720
    _chroot_run
}
721

722
723
724
command_build_profile() {
    # Set up essential directory paths
    airootfs_dir="${work_dir}/${arch}/airootfs"
725
726
727
728
    isofs_dir="${work_dir}/iso"
    # Set ISO file name
    img_name="${iso_name}-${iso_version}-${arch}.iso"

729
730
731
732
733
    _show_config "${FUNCNAME[0]#command_}"

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

734
735
736
737
738
739
    _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
740
    _make_bootmodes
741
742
743
    _run_once _cleanup
    _run_once _make_prepare
    _run_once _make_iso
744
745
}

746
while getopts 'B:p:r:C:L:P:A:D:w:o:s:c:g:vh:?' arg; do
747
    case "${arg}" in
748
749
        B)
            profile="$(realpath -- "${OPTARG}")"
750
            _read_profile
751
            ;;
752
753
        p)
            read -r -a opt_pkg_list <<< "${OPTARG}"
754
755
            pkg_list+=("${opt_pkg_list[@]}")
            ;;
756
        r) run_cmd="${OPTARG}" ;;
757
        C) pacman_conf="$(realpath -- "${OPTARG}")" ;;
758
759
760
761
        L) iso_label="${OPTARG}" ;;
        P) iso_publisher="${OPTARG}" ;;
        A) iso_application="${OPTARG}" ;;
        D) install_dir="${OPTARG}" ;;
762
763
        w) work_dir="$(realpath -- "${OPTARG}")" ;;
        o) out_dir="$(realpath -- "${OPTARG}")" ;;
764
        s) sfs_mode="${OPTARG}" ;;
765
        c) sfs_comp="${OPTARG}" ;;
766
        g) gpg_key="${OPTARG}" ;;
767
768
769
770
771
772
773
774
775
        v) quiet="n" ;;
        h|?) _usage 0 ;;
        *)
            _msg_error "Invalid argument '${arg}'" 0
            _usage 1
            ;;
    esac
done

776
777
778
779
if (( EUID != 0 )); then
    _msg_error "${app_name} must be run as root." 1
fi

780
781
shift $((OPTIND - 1))

782
if (( $# < 1 )); then
783
784
    _msg_error "No command specified" 0
    _usage 1
785
fi
786
787
command_name="${1}"

788
# Set directory path defaults for legacy commands
789
airootfs_dir="${work_dir}/airootfs"
790
isofs_dir="${work_dir}/iso"
791

792
case "${command_name}" in
793
    init)
794
        _msg_warning "The '${command_name}' command is deprecated!"
795
796
797
        command_init
        ;;
    install)
798
        _msg_warning "The '${command_name}' command is deprecated!"
799
800
801
        command_install
        ;;
    run)
802
        _msg_warning "The '${command_name}' command is deprecated!"
803
        command_run
804
805
        ;;
    prepare)
806
        _msg_warning "The '${command_name}' command is deprecated!"
807
808
        command_prepare
        ;;
809
    pkglist)
810
        _msg_warning "The '${command_name}' command is deprecated!"
811
812
        command_pkglist
        ;;
813
    iso)
814
        _msg_warning "The '${command_name}' command is deprecated!"
815
        if (( $# < 2 )); then
816
817
818
            _msg_error "No image specified" 0
            _usage 1
        fi
819
        img_name="${2}"
820
821
        command_iso
        ;;
822
823
824
    build_profile)
        command_build_profile
        ;;
825
826
827
828
829
    *)
        _msg_error "Invalid command name '${command_name}'" 0
        _usage 1
        ;;
esac
830
831

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