dced05a847
I see cases where a stale loop device stays around and fills up my partition as image file is still in use and does not get unlinked. Explicitly detach loop device on umount to fix that. Signed-off-by: Christian Hesse <mail@eworm.de>
419 lines
13 KiB
Bash
Executable File
419 lines
13 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -e -u
|
|
|
|
export LANG=C
|
|
|
|
app_name=${0##*/}
|
|
arch=$(uname -m)
|
|
pkg_list=""
|
|
run_cmd=""
|
|
quiet="y"
|
|
pacman_conf="/etc/pacman.conf"
|
|
export 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="img"
|
|
sfs_comp="xz"
|
|
|
|
# Show an INFO message
|
|
# $1: message string
|
|
_msg_info() {
|
|
local _msg="${1}"
|
|
echo "[mkarchiso] INFO: ${_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}
|
|
echo
|
|
echo "[mkarchiso] ERROR: ${_msg}"
|
|
echo
|
|
if [[ ${_error} -gt 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 ()
|
|
{
|
|
echo "usage ${app_name} [options] command <command options>"
|
|
echo " general options:"
|
|
echo " -p PACKAGE(S) Package(s) to install, can be used multiple times"
|
|
echo " -r <command> Run <command> inside airootfs"
|
|
echo " -C <file> Config file for pacman."
|
|
echo " Default: '${pacman_conf}'"
|
|
echo " -L <label> Set a label for the disk"
|
|
echo " Default: '${iso_label}'"
|
|
echo " -P <publisher> Set a publisher for the disk"
|
|
echo " Default: '${iso_publisher}'"
|
|
echo " -A <application> Set an application name for the disk"
|
|
echo " Default: '${iso_application}'"
|
|
echo " -D <install_dir> Set an install_dir. All files will by located here."
|
|
echo " Default: '${install_dir}'"
|
|
echo " NOTE: Max 8 characters, use only [a-z0-9]"
|
|
echo " -w <work_dir> Set the working directory"
|
|
echo " Default: '${work_dir}'"
|
|
echo " -o <out_dir> Set the output directory"
|
|
echo " Default: '${out_dir}'"
|
|
echo " -s <sfs_mode> Set SquashFS image mode (img or sfs)"
|
|
echo " img: prepare airootfs.sfs for dm-snapshot usage"
|
|
echo " sfs: prepare airootfs.sfs for overlayfs usage"
|
|
echo " Default: ${sfs_mode}"
|
|
echo " -c <comp_type> Set SquashFS compression type (gzip, lzma, lzo, xz)"
|
|
echo " Default: '${sfs_comp}'"
|
|
echo " -v Enable verbose output"
|
|
echo " -h This message"
|
|
echo " commands:"
|
|
echo " init"
|
|
echo " Make base layout and install base group"
|
|
echo " install"
|
|
echo " Install all specified packages (-p)"
|
|
echo " run"
|
|
echo " run command specified by -r"
|
|
echo " prepare"
|
|
echo " build all images"
|
|
echo " pkglist"
|
|
echo " make a pkglist.txt of packages installed on airootfs"
|
|
echo " iso <image name>"
|
|
echo " build an iso image from the working dir"
|
|
exit ${1}
|
|
}
|
|
|
|
# Shows configuration according to command mode.
|
|
# $1: init | install | run | prepare | iso
|
|
_show_config () {
|
|
local _mode="$1"
|
|
echo
|
|
_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
|
|
echo
|
|
}
|
|
|
|
# Install desired packages to airootfs
|
|
_pacman ()
|
|
{
|
|
_msg_info "Installing packages to '${work_dir}/airootfs/'..."
|
|
|
|
if [[ "${quiet}" = "y" ]]; then
|
|
pacstrap -C "${pacman_conf}" -c -d -G -M "${work_dir}/airootfs" $* &> /dev/null
|
|
else
|
|
pacstrap -C "${pacman_conf}" -c -d -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"
|
|
local _qflag=""
|
|
if [[ ${quiet} == "y" ]]; then
|
|
_qflag="-q"
|
|
fi
|
|
mkfs.ext4 ${_qflag} -O ^has_journal,^resize_inode -E lazy_itable_init=0 -m 0 -F "${work_dir}/airootfs.img"
|
|
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/"
|
|
_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}" -no-progress
|
|
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}" -no-progress
|
|
fi
|
|
_msg_info "Done!"
|
|
}
|
|
|
|
_mkchecksum () {
|
|
_msg_info "Creating checksum file for self-test..."
|
|
cd "${work_dir}/iso/${install_dir}/${arch}"
|
|
md5sum airootfs.sfs > airootfs.md5
|
|
cd ${OLDPWD}
|
|
_msg_info "Done!"
|
|
}
|
|
|
|
command_pkglist () {
|
|
_show_config pkglist
|
|
|
|
_msg_info "Creating a list of installed packages on live-enviroment..."
|
|
pacman -Sl -r "${work_dir}/airootfs" --config "${pacman_conf}" | \
|
|
awk '/\[installed\]$/ {print $1 "/" $2 "-" $3}' > \
|
|
"${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
|
|
_qflag="-quiet"
|
|
fi
|
|
xorriso -as mkisofs ${_qflag} \
|
|
-iso-level 3 \
|
|
-full-iso9660-filenames \
|
|
-volid "${iso_label}" \
|
|
-appid "${iso_application}" \
|
|
-publisher "${iso_publisher}" \
|
|
-preparer "prepared by mkarchiso" \
|
|
-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/"
|
|
_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
|
|
}
|
|
|
|
# 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
|
|
|
|
#trim spaces
|
|
pkg_list="$(echo ${pkg_list})"
|
|
|
|
if [[ -z ${pkg_list} ]]; 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
|
|
}
|
|
|
|
if [[ ${EUID} -ne 0 ]]; then
|
|
_msg_error "This script must be run as root." 1
|
|
fi
|
|
|
|
while getopts 'p:r:C:L:P:A:D:w:o:s:c:vh' arg; do
|
|
case "${arg}" in
|
|
p) pkg_list="${pkg_list} ${OPTARG}" ;;
|
|
r) run_cmd="${OPTARG}" ;;
|
|
C) pacman_conf="${OPTARG}" ;;
|
|
L) iso_label="${OPTARG}" ;;
|
|
P) iso_publisher="${OPTARG}" ;;
|
|
A) iso_application="${OPTARG}" ;;
|
|
D) install_dir="${OPTARG}" ;;
|
|
w) work_dir="${OPTARG}" ;;
|
|
o) out_dir="${OPTARG}" ;;
|
|
s) sfs_mode="${OPTARG}" ;;
|
|
c) sfs_comp="${OPTARG}" ;;
|
|
v) quiet="n" ;;
|
|
h|?) _usage 0 ;;
|
|
*)
|
|
_msg_error "Invalid argument '${arg}'" 0
|
|
_usage 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
shift $((OPTIND - 1))
|
|
|
|
if [[ $# -lt 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 [[ $# -lt 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:
|