ISO/archiso/mkarchiso
nl6720 0387b253c8
archiso/mkarchiso: general bash improvements
Quote all variables.
Terminate option processing using '--' for commands that support it.
Do not hardcode file descriptor.
Compare integers with arithmetic comparison instead of string comparison.
Replace echo with printf.
Use heredoc for usage text.
Don't print INFO messages when quiet is set.
Export SOURCE_DATE_EPOCH.
2020-08-17 14:22:24 +03:00

458 lines
14 KiB
Bash
Executable File

#!/bin/bash
#
# SPDX-License-Identifier: GPL-3.0-or-later
set -e -u
# Control the environment
umask 0022
export LANG="C"
export SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-"$(date +%s)"}"
app_name="${0##*/}"
arch="$(uname -m)"
pkg_list=()
run_cmd=""
quiet="y"
pacman_conf="/etc/pacman.conf"
iso_label="ARCH_$(date +%Y%m)"
iso_publisher="Arch Linux <http://www.archlinux.org>"
iso_application="Arch Linux Live/Rescue CD"
install_dir="arch"
work_dir="work"
out_dir="out"
sfs_mode="sfs"
sfs_comp="xz"
gpg_key=""
# Show an INFO message
# $1: message string
_msg_info() {
local _msg="${1}"
[[ "${quiet}" == "y" ]] || printf '[%s] INFO: %s\n' "${app_name}" "${_msg}"
}
# 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}
printf '\n[%s] ERROR: %s\n\n' "${app_name}" "${_msg}" >&2
if (( _error > 0 )); then
exit "${_error}"
fi
}
_chroot_init() {
mkdir -p -- "${work_dir}/airootfs"
_pacman base syslinux
}
_chroot_run() {
eval -- arch-chroot "${work_dir}/airootfs" "${run_cmd}"
}
_mount_airootfs() {
trap "_umount_airootfs" EXIT HUP INT TERM
mkdir -p -- "${work_dir}/mnt/airootfs"
_msg_info "Mounting '${work_dir}/airootfs.img' on '${work_dir}/mnt/airootfs'"
mount -- "${work_dir}/airootfs.img" "${work_dir}/mnt/airootfs"
_msg_info "Done!"
}
_umount_airootfs() {
_msg_info "Unmounting '${work_dir}/mnt/airootfs'"
umount -d -- "${work_dir}/mnt/airootfs"
_msg_info "Done!"
rmdir -- "${work_dir}/mnt/airootfs"
trap - EXIT HUP INT TERM
}
# Show help usage, with an exit status.
# $1: exit status number.
_usage () {
IFS='' read -r -d '' usagetext <<ENDUSAGETEXT || true
usage ${app_name} [options] command <command options>
general options:
-p PACKAGE(S) Package(s) to install, can be used multiple times
-r <command> Run <command> inside airootfs
-C <file> Config file for pacman.
Default: '${pacman_conf}'
-L <label> Set a label for the disk
Default: '${iso_label}'
-P <publisher> Set a publisher for the disk
Default: '${iso_publisher}'
-A <application> Set an application name for the disk
Default: '${iso_application}'
-D <install_dir> Set an install_dir. All files will by located here.
Default: '${install_dir}'
NOTE: Max 8 characters, use only [a-z0-9]
-w <work_dir> Set the working directory
Default: '${work_dir}'
-o <out_dir> Set the output directory
Default: '${out_dir}'
-s <sfs_mode> Set SquashFS image mode (img or sfs)
img: prepare airootfs.sfs for dm-snapshot usage
sfs: prepare airootfs.sfs for overlayfs usage
Default: '${sfs_mode}'
-c <comp_type> Set SquashFS compression type (gzip, lzma, lzo, xz, zstd)
Default: '${sfs_comp}'
-v Enable verbose output
-h This message
commands:
init
Make base layout and install base group
install
Install all specified packages (-p)
run
run command specified by -r
prepare
build all images
pkglist
make a pkglist.txt of packages installed on airootfs
iso <image name>
build an iso image from the working dir
ENDUSAGETEXT
printf '%s\n' "${usagetext}"
exit "${1}"
}
# Shows configuration according to command mode.
# $1: init | install | run | prepare | iso
_show_config () {
local _mode="$1"
printf '\n'
_msg_info "Configuration settings"
_msg_info " Command: ${command_name}"
_msg_info " Architecture: ${arch}"
_msg_info " Working directory: ${work_dir}"
_msg_info " Installation directory: ${install_dir}"
case "${_mode}" in
init)
_msg_info " Pacman config file: ${pacman_conf}"
;;
install)
_msg_info " Pacman config file: ${pacman_conf}"
_msg_info " Packages: ${pkg_list[*]}"
;;
run)
_msg_info " Run command: ${run_cmd}"
;;
prepare)
;;
pkglist)
;;
iso)
_msg_info " Image name: ${img_name}"
_msg_info " Disk label: ${iso_label}"
_msg_info " Disk publisher: ${iso_publisher}"
_msg_info " Disk application: ${iso_application}"
;;
esac
printf '\n'
}
# Install desired packages to airootfs
_pacman () {
_msg_info "Installing packages to '${work_dir}/airootfs/'..."
if [[ "${quiet}" = "y" ]]; then
pacstrap -C "${pacman_conf}" -c -G -M -- "${work_dir}/airootfs" "$@" &> /dev/null
else
pacstrap -C "${pacman_conf}" -c -G -M -- "${work_dir}/airootfs" "$@"
fi
_msg_info "Packages installed successfully!"
}
# Cleanup airootfs
_cleanup () {
_msg_info "Cleaning up what we can on airootfs..."
# Delete initcpio image(s)
if [[ -d "${work_dir}/airootfs/boot" ]]; then
find "${work_dir}/airootfs/boot" -type f -name '*.img' -delete
fi
# Delete kernel(s)
if [[ -d "${work_dir}/airootfs/boot" ]]; then
find "${work_dir}/airootfs/boot" -type f -name 'vmlinuz*' -delete
fi
# Delete pacman database sync cache files (*.tar.gz)
if [[ -d "${work_dir}/airootfs/var/lib/pacman" ]]; then
find "${work_dir}/airootfs/var/lib/pacman" -maxdepth 1 -type f -delete
fi
# Delete pacman database sync cache
if [[ -d "${work_dir}/airootfs/var/lib/pacman/sync" ]]; then
find "${work_dir}/airootfs/var/lib/pacman/sync" -delete
fi
# Delete pacman package cache
if [[ -d "${work_dir}/airootfs/var/cache/pacman/pkg" ]]; then
find "${work_dir}/airootfs/var/cache/pacman/pkg" -type f -delete
fi
# Delete all log files, keeps empty dirs.
if [[ -d "${work_dir}/airootfs/var/log" ]]; then
find "${work_dir}/airootfs/var/log" -type f -delete
fi
# Delete all temporary files and dirs
if [[ -d "${work_dir}/airootfs/var/tmp" ]]; then
find "${work_dir}/airootfs/var/tmp" -mindepth 1 -delete
fi
# Delete package pacman related files.
find "${work_dir}" \( -name '*.pacnew' -o -name '*.pacsave' -o -name '*.pacorig' \) -delete
_msg_info "Done!"
}
# Makes a ext4 filesystem inside a SquashFS from a source directory.
_mkairootfs_img () {
if [[ ! -e "${work_dir}/airootfs" ]]; then
_msg_error "The path '${work_dir}/airootfs' does not exist" 1
fi
_msg_info "Creating ext4 image of 32GiB..."
truncate -s 32G -- "${work_dir}/airootfs.img"
if [[ "${quiet}" == "y" ]]; then
mkfs.ext4 -q -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${work_dir}/airootfs.img"
else
mkfs.ext4 -O '^has_journal,^resize_inode' -E 'lazy_itable_init=0' -m 0 -F -- "${work_dir}/airootfs.img"
fi
tune2fs -c 0 -i 0 -- "${work_dir}/airootfs.img" &> /dev/null
_msg_info "Done!"
_mount_airootfs
_msg_info "Copying '${work_dir}/airootfs/' to '${work_dir}/mnt/airootfs/'..."
cp -aT -- "${work_dir}/airootfs/" "${work_dir}/mnt/airootfs/"
chown root:root -- "${work_dir}/mnt/airootfs/"
_msg_info "Done!"
_umount_airootfs
mkdir -p -- "${work_dir}/iso/${install_dir}/${arch}"
_msg_info "Creating SquashFS image, this may take some time..."
if [[ "${quiet}" = "y" ]]; then
mksquashfs "${work_dir}/airootfs.img" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \
-comp "${sfs_comp}" -no-progress &> /dev/null
else
mksquashfs "${work_dir}/airootfs.img" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \
-comp "${sfs_comp}"
fi
_msg_info "Done!"
rm -- "${work_dir}/airootfs.img"
}
# Makes a SquashFS filesystem from a source directory.
_mkairootfs_sfs () {
if [[ ! -e "${work_dir}/airootfs" ]]; then
_msg_error "The path '${work_dir}/airootfs' does not exist" 1
fi
mkdir -p -- "${work_dir}/iso/${install_dir}/${arch}"
_msg_info "Creating SquashFS image, this may take some time..."
if [[ "${quiet}" = "y" ]]; then
mksquashfs "${work_dir}/airootfs" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \
-comp "${sfs_comp}" -no-progress &> /dev/null
else
mksquashfs "${work_dir}/airootfs" "${work_dir}/iso/${install_dir}/${arch}/airootfs.sfs" -noappend \
-comp "${sfs_comp}"
fi
_msg_info "Done!"
}
_mkchecksum () {
_msg_info "Creating checksum file for self-test..."
cd -- "${work_dir}/iso/${install_dir}/${arch}"
sha512sum airootfs.sfs > airootfs.sha512
cd -- "${OLDPWD}"
_msg_info "Done!"
}
_mksignature () {
_msg_info "Creating signature file..."
cd -- "${work_dir}/iso/${install_dir}/${arch}"
gpg --detach-sign --default-key "${gpg_key}" airootfs.sfs
cd -- "${OLDPWD}"
_msg_info "Done!"
}
command_pkglist () {
_show_config pkglist
_msg_info "Creating a list of installed packages on live-enviroment..."
pacman -Q --sysroot "${work_dir}/airootfs" > "${work_dir}/iso/${install_dir}/pkglist.${arch}.txt"
_msg_info "Done!"
}
# Create an ISO9660 filesystem from "iso" directory.
command_iso () {
local _iso_efi_boot_args=()
if [[ ! -f "${work_dir}/iso/isolinux/isolinux.bin" ]]; then
_msg_error "The file '${work_dir}/iso/isolinux/isolinux.bin' does not exist." 1
fi
if [[ ! -f "${work_dir}/iso/isolinux/isohdpfx.bin" ]]; then
_msg_error "The file '${work_dir}/iso/isolinux/isohdpfx.bin' does not exist." 1
fi
# If exists, add an EFI "El Torito" boot image (FAT filesystem) to ISO-9660 image.
if [[ -f "${work_dir}/iso/EFI/archiso/efiboot.img" ]]; then
_iso_efi_boot_args+=(
'-eltorito-alt-boot'
'-e' 'EFI/archiso/efiboot.img'
'-no-emul-boot'
'-isohybrid-gpt-basdat'
)
fi
_show_config iso
mkdir -p -- "${out_dir}"
_msg_info "Creating ISO image..."
local _qflag=""
if [[ "${quiet}" == "y" ]]; then
xorriso -as mkisofs -quiet \
-iso-level 3 \
-full-iso9660-filenames \
-rational-rock \
-volid "${iso_label}" \
-appid "${iso_application}" \
-publisher "${iso_publisher}" \
-preparer "prepared by ${app_name}" \
-eltorito-boot isolinux/isolinux.bin \
-eltorito-catalog isolinux/boot.cat \
-no-emul-boot -boot-load-size 4 -boot-info-table \
-isohybrid-mbr "${work_dir}/iso/isolinux/isohdpfx.bin" \
"${_iso_efi_boot_args[@]}" \
-output "${out_dir}/${img_name}" \
"${work_dir}/iso/"
else
xorriso -as mkisofs \
-iso-level 3 \
-full-iso9660-filenames \
-rational-rock \
-volid "${iso_label}" \
-appid "${iso_application}" \
-publisher "${iso_publisher}" \
-preparer "prepared by ${app_name}" \
-eltorito-boot isolinux/isolinux.bin \
-eltorito-catalog isolinux/boot.cat \
-no-emul-boot -boot-load-size 4 -boot-info-table \
-isohybrid-mbr "${work_dir}/iso/isolinux/isohdpfx.bin" \
"${_iso_efi_boot_args[@]}" \
-output "${out_dir}/${img_name}" \
"${work_dir}/iso/"
fi
_msg_info "Done! | $(ls -sh -- "${out_dir}/${img_name}")"
}
# create airootfs.sfs filesystem, and push it in "iso" directory.
command_prepare () {
_show_config prepare
_cleanup
if [[ "${sfs_mode}" == "sfs" ]]; then
_mkairootfs_sfs
else
_mkairootfs_img
fi
_mkchecksum
if [[ "${gpg_key}" ]]; then
_mksignature
fi
}
# Install packages on airootfs.
# A basic check to avoid double execution/reinstallation is done via hashing package names.
command_install () {
if [[ ! -f "${pacman_conf}" ]]; then
_msg_error "Pacman config file '${pacman_conf}' does not exist" 1
fi
if (( ${#pkg_list[@]} == 0 )); then
_msg_error "Packages must be specified" 0
_usage 1
fi
_show_config install
_pacman "${pkg_list[@]}"
}
command_init() {
_show_config init
_chroot_init
}
command_run() {
_show_config run
_chroot_run
}
while getopts 'p:r:C:L:P:A:D:w:o:s:c:g:vh' arg; do
case "${arg}" in
p)
read -r -a opt_pkg_list <<< "${OPTARG}"
pkg_list+=("${opt_pkg_list[@]}")
;;
r) run_cmd="${OPTARG}" ;;
C) pacman_conf="$(realpath -- "${OPTARG}")" ;;
L) iso_label="${OPTARG}" ;;
P) iso_publisher="${OPTARG}" ;;
A) iso_application="${OPTARG}" ;;
D) install_dir="${OPTARG}" ;;
w) work_dir="$(realpath -- "${OPTARG}")" ;;
o) out_dir="$(realpath -- "${OPTARG}")" ;;
s) sfs_mode="${OPTARG}" ;;
c) sfs_comp="${OPTARG}" ;;
g) gpg_key="${OPTARG}" ;;
v) quiet="n" ;;
h|?) _usage 0 ;;
*)
_msg_error "Invalid argument '${arg}'" 0
_usage 1
;;
esac
done
if (( EUID != 0 )); then
_msg_error "${app_name} must be run as root." 1
fi
shift $((OPTIND - 1))
if (( $# < 1 )); then
_msg_error "No command specified" 0
_usage 1
fi
command_name="${1}"
case "${command_name}" in
init)
command_init
;;
install)
command_install
;;
run)
command_run
;;
prepare)
command_prepare
;;
pkglist)
command_pkglist
;;
iso)
if (( $# < 2 )); then
_msg_error "No image specified" 0
_usage 1
fi
img_name="${2}"
command_iso
;;
*)
_msg_error "Invalid command name '${command_name}'" 0
_usage 1
;;
esac
# vim:ts=4:sw=4:et: