uniform return values and better logging

This commit is contained in:
bill-auger 2019-12-22 19:50:19 -05:00
parent 50d32d2cba
commit 906535e29d
3 changed files with 139 additions and 96 deletions

@ -39,7 +39,9 @@ usage() {
} }
pvm_mount() { pvm_mount() {
if ! file "$imagefile" | grep -q ' DOS/MBR '; then if file "$imagefile" | grep -q ' DOS/MBR '; then
msg "mounting filesystems"
else
error "%s: does not seem to be a raw qemu image." "$imagefile" error "%s: does not seem to be a raw qemu image." "$imagefile"
return "$EXIT_FAILURE" return "$EXIT_FAILURE"
fi fi
@ -60,7 +62,9 @@ pvm_mount() {
sudo umount "$workdir" sudo umount "$workdir"
done done
if [ -z "$rootpart" ]; then if [ -n "$rootpart" ]; then
msg "found root filesystem partition: %s" "$rootpart"
else
error "%s: unable to determine root partition." "$imagefile" error "%s: unable to determine root partition." "$imagefile"
return "$EXIT_FAILURE" return "$EXIT_FAILURE"
fi fi
@ -68,7 +72,9 @@ pvm_mount() {
# find the boot partition # find the boot partition
bootpart="$(findmnt -senF "$workdir"/etc/fstab /boot | awk '{print $2}')" bootpart="$(findmnt -senF "$workdir"/etc/fstab /boot | awk '{print $2}')"
if [ -z "$bootpart" ]; then if [ -n "$bootpart" ]; then
msg "found boot filesystem partition: %s" "$bootpart"
else
error "%s: unable to determine boot partition." "$imagefile" error "%s: unable to determine boot partition." "$imagefile"
return "$EXIT_FAILURE" return "$EXIT_FAILURE"
fi fi
@ -78,6 +84,8 @@ pvm_mount() {
} }
pvm_umount() { pvm_umount() {
msg "un-mounting filesystems"
trap - INT TERM EXIT trap - INT TERM EXIT
[ -n "$workdir" ] && (sudo umount -R "$workdir"; rmdir "$workdir") [ -n "$workdir" ] && (sudo umount -R "$workdir"; rmdir "$workdir")
@ -98,7 +106,7 @@ main() {
case "$arg" in case "$arg" in
h) usage; return "$EXIT_SUCCESS";; h) usage; return "$EXIT_SUCCESS";;
o) output="$OPTARG";; o) output="$OPTARG";;
*) usage >&2; exit "$EXIT_INVALIDARGUMENT";; *) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac esac
done done
local shiftlen=$(( OPTIND - 1 )) local shiftlen=$(( OPTIND - 1 ))
@ -140,6 +148,7 @@ main() {
# archlinuxarm and the generated parabola tarball through: # archlinuxarm and the generated parabola tarball through:
# #
# `tar -tf <tarball> | sort` # `tar -tf <tarball> | sort`
msg "imploding tarball"
sudo tar -c -f "$output" -C "$workdir" -X - . << EOF sudo tar -c -f "$output" -C "$workdir" -X - . << EOF
./boot/lost+found ./boot/lost+found
./etc/.updated ./etc/.updated

@ -28,23 +28,23 @@ usage() {
print "USAGE: %s [-h] <img> [qemu-args ...]" "${0##*/}" print "USAGE: %s [-h] <img> [qemu-args ...]" "${0##*/}"
prose "Determine the architecture of <img> and boot it using qemu. <img> is assumed prose "Determine the architecture of <img> and boot it using qemu. <img> is assumed
to be a valid, raw-formatted parabola virtual machine image, ideally to be a valid, raw-formatted parabola virtual machine image, ideally
created using pvmbootstrap. The started instance is assigned created using pvmbootstrap. The started instances are assigned
${DEF_RAM_MB}MB of RAM and one SMP core." ${DEF_RAM_MB}MB of RAM and one SMP core."
echo echo
prose "When a graphical desktop environment is available, start the machine prose "When a graphical desktop environment is available, start the machine
normally, otherwise append -nographic to the qemu options. This behavior normally, otherwise append -nographic to the qemu options. This behavior
can be forced by unsetting DISPLAY manually, for example through:" can be forced by unsetting DISPLAY manually, for example through:"
echo echo
echo " DISPLAY= ${0##*/} ./an.img" echo " DISPLAY= ${0##*/} IMG ..."
echo echo
prose "When the architecture of <img> is compatible with the host architecture, prose "When the architecture of IMG is compatible with the host architecture,
append -enable-kvm to the qemu arguments." append -enable-kvm to the qemu arguments."
echo echo
prose "Further arguments provided after <img> will be passed unmodified to the prose "Further arguments provided after IMG will be passed unmodified to the
qemu invocation. This can be used to allocate more resources to the virtual qemu invocation. This can be used to allocate more resources to the virtual
machine, for example:" machine, for example:"
echo echo
echo " ${0##*/} ./an.img -m 2G -smp 2" echo " ${0##*/} IMG -m 2G -smp 2"
echo echo
echo "Supported options:" echo "Supported options:"
echo " -h Display this help and exit" echo " -h Display this help and exit"
@ -59,15 +59,17 @@ pvm_mount() {
return "$EXIT_FAILURE" return "$EXIT_FAILURE"
fi fi
msg "mounting filesystems"
trap 'pvm_umount' INT TERM EXIT trap 'pvm_umount' INT TERM EXIT
workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return "$EXIT_FAILURE"
loopdev="$(sudo losetup -fLP --show "$1")" || return loopdev="$(sudo losetup -fLP --show "$1")" || return "$EXIT_FAILURE"
sudo mount "$loopdev"p1 "$workdir" \ sudo mount "$loopdev"p1 "$workdir" \
|| sudo mount "$loopdev"p2 "$workdir" || return || sudo mount "$loopdev"p2 "$workdir" || return "$EXIT_FAILURE"
} }
pvm_umount() { pvm_umount() {
msg "un-mounting filesystems"
trap - INT TERM EXIT trap - INT TERM EXIT
[ -n "$workdir" ] && (sudo umount "$workdir"; rmdir "$workdir") [ -n "$workdir" ] && (sudo umount "$workdir"; rmdir "$workdir")
@ -81,30 +83,30 @@ pvm_probe_arch() {
kernel=$(find "$workdir" -maxdepth 1 -type f -iname '*vmlinu*' | head -n1) kernel=$(find "$workdir" -maxdepth 1 -type f -iname '*vmlinu*' | head -n1)
if [ -z "$kernel" ]; then if [ -z "$kernel" ]; then
warning "%s: unable to find kernel binary" "$1" warning "%s: unable to find kernel binary" "$1"
return return "$EXIT_FAILURE"
fi fi
# attempt to get kernel arch from elf header # attempt to get kernel arch from elf header
arch="$(readelf -h "$kernel" 2>/dev/null | grep Machine | awk '{print $2}')" arch="$(readelf -h "$kernel" 2>/dev/null | grep Machine | awk '{print $2}')"
case "$arch" in case "$arch" in
PowerPC64) arch=ppc64; return;; PowerPC64) arch=ppc64; return "$EXIT_SUCCESS";;
RISC-V) arch=riscv64; return;; RISC-V ) arch=riscv64; return "$EXIT_SUCCESS";;
*) arch="";; * ) arch="";;
esac esac
# attempt to get kernel arch from objdump # attempt to get kernel arch from objdump
arch="$(objdump -f "$kernel" 2>/dev/null | grep architecture: | awk '{print $2}' | tr -d ',')" arch="$(objdump -f "$kernel" 2>/dev/null | grep architecture: | awk '{print $2}' | tr -d ',')"
case "$arch" in case "$arch" in
i386) arch=i386; return;; i386 ) arch=i386; return "$EXIT_SUCCESS";;
i386:*) arch=x86_64; return;; i386:*) arch=x86_64; return "$EXIT_SUCCESS";;
*) arch="";; * ) arch="";;
esac esac
# attempt to get kernel arch from file magic # attempt to get kernel arch from file magic
arch="$(file "$kernel")" arch="$(file "$kernel")"
case "$arch" in case "$arch" in
*"ARM boot executable"*) arch=arm; return;; *"ARM boot executable"*) arch=arm; return "$EXIT_SUCCESS";;
*) arch="";; * ) arch="";;
esac esac
# no more ideas; giving up. # no more ideas; giving up.
@ -173,23 +175,24 @@ main() {
while getopts 'h' arg; do while getopts 'h' arg; do
case "$arg" in case "$arg" in
h) usage; return "$EXIT_SUCCESS";; h) usage; return "$EXIT_SUCCESS";;
*) usage >&2; exit "$EXIT_INVALIDARGUMENT";; *) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac esac
done done
local shiftlen=$(( OPTIND - 1 )) local shiftlen=$(( OPTIND - 1 ))
shift $shiftlen shift $shiftlen
local imagefile="$1" local imagefile="$1"
shift shift
[ ! -e "$imagefile" ] && error "%s: file not found" "$imagefile" && exit "$EXIT_FAILURE" [ ! -n "$imagefile" ] && error "no image file specified" && exit "$EXIT_FAILURE"
[ ! -e "$imagefile" ] && error "image file not found: '%s'" "$imagefile" && exit "$EXIT_FAILURE"
msg "initializing ...."
local workdir loopdev local workdir loopdev
pvm_mount "$imagefile" || exit pvm_mount "$imagefile" || exit
local arch local arch
pvm_probe_arch "$imagefile" || exit pvm_probe_arch "$imagefile" || exit
if [ -z "$arch" ]; then if [ -z "$arch" ]; then
error "%s: arch is unknown" "$imagefile" error "image arch is unknown: '%s'" "$arch"
exit "$EXIT_FAILURE" exit "$EXIT_FAILURE"
fi fi
@ -197,9 +200,11 @@ main() {
pvm_guess_qemu_args "$imagefile" "$arch" || exit pvm_guess_qemu_args "$imagefile" "$arch" || exit
qemu_args+=("$@") qemu_args+=("$@")
msg "booting ...."
(set -x; qemu-system-"$arch" "${qemu_args[@]}") (set -x; qemu-system-"$arch" "${qemu_args[@]}")
# clean up the terminal, in case SeaBIOS did something weird # clean up the terminal, in case SeaBIOS did something weird
msg "cleaning up ...."
echo -n "[?7h" echo -n "[?7h"
pvm_umount pvm_umount
} }

@ -65,25 +65,26 @@ usage() {
pvm_native_arch() { pvm_native_arch() {
local arch=$( [[ "$1" =~ arm.* ]] && echo 'armv7l' || echo "$1" ) local arch=$( [[ "$1" =~ arm.* ]] && echo 'armv7l' || echo "$1" )
setarch "$arch" /bin/true 2>/dev/null || return setarch "$arch" /bin/true 2>/dev/null || return "$EXIT_FAILURE"
} }
pvm_bootstrap() { pvm_bootstrap() {
msg "%s: starting image creation for %s" "$file" "$arch" msg "starting creation of %s image: %s" "$arch" "$file"
# create the raw image file # create the raw image file
qemu-img create -f raw "$file" "${ImgSizeGb}G" || return qemu-img create -f raw "$file" "${ImgSizeGb}G" || return "$EXIT_FAILURE"
# prepare for cleanup # prepare for cleanup
trap 'pvm_cleanup' INT TERM RETURN trap 'pvm_cleanup' INT TERM RETURN
# mount the virtual disk # mount the virtual disk
local workdir loopdev local workdir loopdev
workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE"
loopdev="$(sudo losetup -fLP --show "$file")" || return loopdev="$(sudo losetup -fLP --show "$file")" || return "$EXIT_FAILURE"
sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return "$EXIT_FAILURE"
# partition # partition
msg "partitioning blank image"
case "$arch" in case "$arch" in
i686|x86_64) i686|x86_64)
sudo parted -s "$loopdev" \ sudo parted -s "$loopdev" \
@ -92,21 +93,21 @@ pvm_bootstrap() {
set 1 bios_grub on \ set 1 bios_grub on \
mkpart primary ext2 2MiB 514MiB \ mkpart primary ext2 2MiB 514MiB \
mkpart primary linux-swap 514MiB 4610MiB \ mkpart primary linux-swap 514MiB 4610MiB \
mkpart primary ext4 4610MiB 100% || return ;; mkpart primary ext4 4610MiB 100% || return "$EXIT_FAILURE" ;;
armv7h) armv7h)
sudo parted -s "$loopdev" \ sudo parted -s "$loopdev" \
mklabel gpt \ mklabel gpt \
mkpart ESP fat32 1MiB 513MiB \ mkpart ESP fat32 1MiB 513MiB \
set 1 boot on \ set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \ mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return ;; mkpart primary ext4 4609MiB 100% || return "$EXIT_FAILURE" ;;
ppc64le|riscv64) ppc64le|riscv64)
sudo parted -s "$loopdev" \ sudo parted -s "$loopdev" \
mklabel gpt \ mklabel gpt \
mkpart primary ext2 1MiB 513MiB \ mkpart primary ext2 1MiB 513MiB \
set 1 boot on \ set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \ mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return ;; mkpart primary ext4 4609MiB 100% || return "$EXIT_FAILURE" ;;
esac esac
# refresh partition data # refresh partition data
@ -114,35 +115,37 @@ pvm_bootstrap() {
# make file systems # make file systems
local swapdev local swapdev
msg "creating target filesystems"
case "$arch" in case "$arch" in
i686|x86_64) i686|x86_64)
sudo mkfs.ext2 "$loopdev"p2 || return sudo mkfs.ext2 "$loopdev"p2 || return "$EXIT_FAILURE"
sudo mkswap "$loopdev"p3 || return sudo mkswap "$loopdev"p3 || return "$EXIT_FAILURE"
sudo mkfs.ext4 "$loopdev"p4 || return sudo mkfs.ext4 "$loopdev"p4 || return "$EXIT_FAILURE"
swapdev="$loopdev"p3 ;; swapdev="$loopdev"p3 ;;
armv7h) armv7h)
sudo mkfs.vfat -F 32 "$loopdev"p1 || return sudo mkfs.vfat -F 32 "$loopdev"p1 || return "$EXIT_FAILURE"
sudo mkswap "$loopdev"p2 || return sudo mkswap "$loopdev"p2 || return "$EXIT_FAILURE"
sudo mkfs.ext4 "$loopdev"p3 || return sudo mkfs.ext4 "$loopdev"p3 || return "$EXIT_FAILURE"
swapdev="$loopdev"p2 ;; swapdev="$loopdev"p2 ;;
ppc64le|riscv64) ppc64le|riscv64)
sudo mkfs.ext2 "$loopdev"p1 || return sudo mkfs.ext2 "$loopdev"p1 || return "$EXIT_FAILURE"
sudo mkswap "$loopdev"p2 || return sudo mkswap "$loopdev"p2 || return "$EXIT_FAILURE"
sudo mkfs.ext4 "$loopdev"p3 || return sudo mkfs.ext4 "$loopdev"p3 || return "$EXIT_FAILURE"
swapdev="$loopdev"p2 ;; swapdev="$loopdev"p2 ;;
esac esac
# mount partitions # mount partitions
msg "mounting target partitions"
case "$arch" in case "$arch" in
i686|x86_64) i686|x86_64)
sudo mount "$loopdev"p4 "$workdir" || return sudo mount "$loopdev"p4 "$workdir" || return "$EXIT_FAILURE"
sudo mkdir -p "$workdir"/boot || return sudo mkdir -p "$workdir"/boot || return "$EXIT_FAILURE"
sudo mount "$loopdev"p2 "$workdir"/boot || return sudo mount "$loopdev"p2 "$workdir"/boot || return "$EXIT_FAILURE"
;; ;;
armv7h|ppc64le|riscv64) armv7h|ppc64le|riscv64)
sudo mount "$loopdev"p3 "$workdir" || return sudo mount "$loopdev"p3 "$workdir" || return "$EXIT_FAILURE"
sudo mkdir -p "$workdir"/boot || return sudo mkdir -p "$workdir"/boot || return "$EXIT_FAILURE"
sudo mount "$loopdev"p1 "$workdir"/boot || return sudo mount "$loopdev"p1 "$workdir"/boot || return "$EXIT_FAILURE"
;; ;;
esac esac
@ -158,18 +161,20 @@ pvm_bootstrap() {
local qemu_user_static=$(sudo grep -l -F -e "interpreter /usr/bin/qemu-$qemu_arch-" \ local qemu_user_static=$(sudo grep -l -F -e "interpreter /usr/bin/qemu-$qemu_arch-" \
-r -- /proc/sys/fs/binfmt_misc 2>/dev/null | \ -r -- /proc/sys/fs/binfmt_misc 2>/dev/null | \
xargs -r sudo grep -xF 'enabled' ) xargs -r sudo grep -xF 'enabled' )
if [[ -z "$qemu_user_static" ]]; then if [[ -n "$qemu_user_static" ]]; then
error "%s: missing qemu-user-static for %s" "$file" "$arch" msg "found qemu-user-static for %s" "$arch"
else
error "missing qemu-user-static for %s" "$arch"
return "$EXIT_FAILURE" return "$EXIT_FAILURE"
fi fi
sudo mkdir -p "$workdir"/usr/bin sudo mkdir -p "$workdir"/usr/bin
sudo cp -v "/usr/bin/qemu-$qemu_arch-"* "$workdir"/usr/bin || return sudo cp -v "/usr/bin/qemu-$qemu_arch-"* "$workdir"/usr/bin || return "$EXIT_FAILURE"
fi fi
# prepare pacstrap config # prepare pacstrap config
local pacconf repos local pacconf repos
pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return "$EXIT_FAILURE"
repos=('libre' 'core' 'extra' 'community' 'pcr') repos=('libre' 'core' 'extra' 'community' 'pcr')
echo -e "[options]\nArchitecture = $arch\n\n" > "$pacconf" echo -e "[options]\nArchitecture = $arch\n\n" > "$pacconf"
for repo in ${repos[@]}; do echo -e "[$repo]\nServer = $Mirror\n" >> "$pacconf"; done; for repo in ${repos[@]}; do echo -e "[$repo]\nServer = $Mirror\n" >> "$pacconf"; done;
@ -203,10 +208,12 @@ pvm_bootstrap() {
local pkg_guest_cache=(ca-certificates-utils) local pkg_guest_cache=(ca-certificates-utils)
# pacstrap! :) # pacstrap! :)
sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkgs[@]}" || return msg "installing packages into the work chroot"
sudo pacstrap -GM -C "$pacconf" "$workdir" "${pkg_guest_cache[@]}" || return sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkgs[@]}" || return "$EXIT_FAILURE"
sudo pacstrap -GM -C "$pacconf" "$workdir" "${pkg_guest_cache[@]}" || return "$EXIT_FAILURE"
# create an fstab # create an fstab
msg "generating /etc/fstab"
case "$arch" in case "$arch" in
riscv64) ;; riscv64) ;;
*) *)
@ -217,14 +224,16 @@ pvm_bootstrap() {
sudo swapon --all ;; sudo swapon --all ;;
esac esac
# produce a hostname # configure the system envoronment
echo "parabola" | sudo tee "$workdir"/etc/hostname local hostname='parabola'
local lang='en_US.UTF-8'
# produce an /etc/locale.conf msg "configuring system envoronment"
echo "LANG=en_US.UTF-8" | sudo tee "$workdir"/etc/locale.conf echo "/etc/hostname: " ; echo $hostname | sudo tee "$workdir"/etc/hostname ;
sudo sed -i 's/#en_US.UTF-8/en_US.UTF-8/' "$workdir"/etc/locale.gen echo "/etc/locale.conf: " ; echo "LANG=$lang" | sudo tee "$workdir"/etc/locale.conf ;
sudo sed -i "s/#${lang}/${lang}/" "$workdir"/etc/locale.gen
# install a boot loader # install a boot loader
msg "installing boot loader"
case "$arch" in case "$arch" in
i686|x86_64) i686|x86_64)
local grub_def_file="$workdir"/etc/default/grub local grub_def_file="$workdir"/etc/default/grub
@ -232,48 +241,57 @@ pvm_bootstrap() {
# enable serial console # enable serial console
local field=GRUB_CMDLINE_LINUX_DEFAULT local field=GRUB_CMDLINE_LINUX_DEFAULT
local value="console=tty0 console=ttyS0" local value="console=tty0 console=ttyS0"
sudo sed -i "s/.*$field=.*/$field=\"$value\"/" "$grub_def_file" || return sudo sed -i "s/.*$field=.*/$field=\"$value\"/" "$grub_def_file" || return "$EXIT_FAILURE"
# disable boot menu timeout # disable boot menu timeout
local field=GRUB_TIMEOUT local field=GRUB_TIMEOUT
local value=0 local value=0
sudo sed -i "s/.*$field=.*/$field=$value/" "$grub_def_file" || return sudo sed -i "s/.*$field=.*/$field=$value/" "$grub_def_file" || return "$EXIT_FAILURE"
# install grub to the VM # install grub to the VM
sudo arch-chroot "$workdir" grub-install "$loopdev" || return sudo arch-chroot "$workdir" grub-install "$loopdev" || return "$EXIT_FAILURE"
sudo arch-chroot "$workdir" grub-mkconfig -o $grub_cfg_file || return sudo arch-chroot "$workdir" grub-mkconfig -o $grub_cfg_file || return "$EXIT_FAILURE"
;;
armv7h)
echo "(armv7h has no boot loader)"
;; ;;
riscv64) riscv64)
# FIXME: for the time being, use fedora bbl to boot # FIXME: for the time being, use fedora bbl to boot
warning "(riscv64 requires a blob - downloading it now)"
local bbl_url=https://fedorapeople.org/groups/risc-v/disk-images/bbl local bbl_url=https://fedorapeople.org/groups/risc-v/disk-images/bbl
sudo wget $bbl_url -O "$workdir"/boot/bbl || return sudo wget $bbl_url -O "$workdir"/boot/bbl || return "$EXIT_FAILURE"
;;
ppc64le)
# FIXME: what about ppc64le?
echo "(ppc64le has no boot loader)"
;; ;;
# armv7h has no boot loader.
# FIXME: what about ppc64le
esac esac
# regenerate the initcpio, skipping the autodetect hook # regenerate the initcpio, skipping the autodetect hook
local preset_file="$workdir"/etc/mkinitcpio.d/linux-libre.preset local kernel='linux-libre'
sudo cp "$preset_file"{,.backup} || return local preset_file="$workdir"/etc/mkinitcpio.d/${kernel}.preset
echo "default_options=\"-S autodetect\"" | sudo tee -a "$preset_file" || return local default_options="default_options=\"-S autodetect\""
sudo arch-chroot "$workdir" mkinitcpio -p linux-libre || return msg "regenerating initcpio for kernel: '${kernel}'"
sudo mv "$preset_file"{.backup,} || return sudo cp "$preset_file"{,.backup} || return "$EXIT_FAILURE"
echo "$default_options" | sudo tee -a "$preset_file" > /dev/null || return "$EXIT_FAILURE"
# disable audit sudo arch-chroot "$workdir" mkinitcpio -p ${kernel} || return "$EXIT_FAILURE"
sudo arch-chroot "$workdir" systemctl mask systemd-journald-audit.socket sudo mv "$preset_file"{.backup,} || return "$EXIT_FAILURE"
# initialize the pacman keyring # initialize the pacman keyring
msg "initializing the pacman keyring"
sudo arch-chroot "$workdir" pacman-key --init sudo arch-chroot "$workdir" pacman-key --init
sudo arch-chroot "$workdir" pacman-key --populate archlinux archlinux32 archlinuxarm parabola sudo arch-chroot "$workdir" pacman-key --populate archlinux archlinux32 archlinuxarm parabola
# enable the entropy daemon, to avoid stalling https
sudo arch-chroot "$workdir" systemctl enable haveged.service
# push hooks into the image # push hooks into the image
msg "preparing hooks"
sudo mkdir -p "$workdir/root/hooks" sudo mkdir -p "$workdir/root/hooks"
[ "${#Hooks[@]}" -eq 0 ] || sudo cp -v "${Hooks[@]}" "$workdir"/root/hooks/ [ "${#Hooks[@]}" -eq 0 ] || sudo cp -v "${Hooks[@]}" "$workdir"/root/hooks/
# create a master hook script # create a master hook script
sudo tee "$workdir"/root/hooks.sh << 'EOF' local hooks_success_msg="[hooks.sh] pre-init hooks successful"
echo "hooks.sh:"
sudo tee "$workdir"/root/hooks.sh << EOF
#!/bin/bash #!/bin/bash
echo "[hooks.sh] boot successful - configuring ...."
systemctl disable preinit.service systemctl disable preinit.service
# generate the locale # generate the locale
@ -287,8 +305,8 @@ pacman -U --noconfirm /var/cache/pacman/pkg/ca-certificates-utils-*.pkg.tar.xz
# run the hooks # run the hooks
for hook in /root/hooks/*; do for hook in /root/hooks/*; do
echo "running hook \"$hook\"" echo "[hooks.sh] running hook: '\$(basename \$hook)'"
source "$hook" || return source "\$hook" || return
done done
# clean up after yourself # clean up after yourself
@ -299,10 +317,11 @@ rm -f /var/cache/pacman/pkg/*
rm -f /root/.bash_history rm -f /root/.bash_history
# report success :) # report success :)
echo "preinit hooks successful" echo "$hooks_success_msg"
EOF EOF
# create a preinit service to run the hooks # create a pre-init service to run the hooks
echo "preinit.service:"
sudo tee "$workdir"/usr/lib/systemd/system/preinit.service << 'EOF' sudo tee "$workdir"/usr/lib/systemd/system/preinit.service << 'EOF'
[Unit] [Unit]
Description=Oneshot VM Preinit Description=Oneshot VM Preinit
@ -313,19 +332,26 @@ StandardOutput=journal+console
StandardError=journal+console StandardError=journal+console
ExecStart=/usr/bin/bash /root/hooks.sh ExecStart=/usr/bin/bash /root/hooks.sh
Type=oneshot Type=oneshot
ExecStopPost=echo "powering off"
ExecStopPost=shutdown -r now ExecStopPost=shutdown -r now
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
EOF EOF
# enable the preinit service # configure services
sudo arch-chroot "$workdir" systemctl enable preinit.service || return msg "configuring services"
# disable audit
sudo arch-chroot "$workdir" systemctl mask systemd-journald-audit.socket
# enable the entropy daemon, to avoid stalling https
sudo arch-chroot "$workdir" systemctl enable haveged.service
# enable the pre-init service
sudo arch-chroot "$workdir" systemctl enable preinit.service || return "$EXIT_FAILURE"
# unmount everything # unmount everything
pvm_cleanup pvm_cleanup
# boot the machine to run the preinit hooks # boot the machine to run the pre-init hooks
local pvmboot_cmd local pvmboot_cmd
local qemu_flags=(-no-reboot) local qemu_flags=(-no-reboot)
if [ -f "./src/pvmboot.sh" ]; then if [ -f "./src/pvmboot.sh" ]; then
@ -333,13 +359,13 @@ EOF
elif type -p pvmboot &>/dev/null; then elif type -p pvmboot &>/dev/null; then
pvmboot_cmd=('pvmboot') pvmboot_cmd=('pvmboot')
else else
error "%s: pvmboot not available -- unable to run hooks" "$file" error "pvmboot not available -- unable to run hooks"
return "$EXIT_FAILURE" return "$EXIT_FAILURE"
fi fi
pvmboot_cmd+=("$file" "${qemu_flags[@]}") pvmboot_cmd+=("$file" "${qemu_flags[@]}")
exec 3>&1 exec 3>&1
msg "booting the machine to run the pre-init hooks" msg "booting the machine to run the pre-init hooks"
DISPLAY='' "${pvmboot_cmd[@]}" | tee /dev/fd/3 | grep -q "preinit hooks successful" DISPLAY='' "${pvmboot_cmd[@]}" | tee /dev/fd/3 | grep -q -F "$hooks_success_msg"
local res=$? local res=$?
exec 3>&- exec 3>&-
! (( $res )) || error "%s: failed to complete preinit hooks" "$file" ! (( $res )) || error "%s: failed to complete preinit hooks" "$file"
@ -350,6 +376,7 @@ EOF
pvm_cleanup() { pvm_cleanup() {
trap - INT TERM RETURN trap - INT TERM RETURN
msg "cleaning up"
[ -n "$pacconf" ] && rm -f "$pacconf" [ -n "$pacconf" ] && rm -f "$pacconf"
unset pacconf unset pacconf
if [ -n "$workdir" ]; then if [ -n "$workdir" ]; then
@ -384,7 +411,7 @@ main() {
M) Mirror="$OPTARG";; M) Mirror="$OPTARG";;
O) Init="-openrc";; O) Init="-openrc";;
s) ImgSizeGb="$(sed 's|[^0-9]||g' <<<$OPTARG)";; s) ImgSizeGb="$(sed 's|[^0-9]||g' <<<$OPTARG)";;
*) usage >&2; exit "$EXIT_INVALIDARGUMENT";; *) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac esac
done done
@ -392,16 +419,18 @@ main() {
shift $shiftlen shift $shiftlen
local file="$1" local file="$1"
local arch="$2" local arch="$2"
[ "$#" -ne 2 ] && usage >&2 && exit "$EXIT_INVALIDARGUMENT" local has_params=$( [ "$#" -eq 2 ] && echo 1 || echo 0 )
[ "$ImgSizeGb" -lt $MIN_GB ] && usage >&2 && exit "$EXIT_INVALIDARGUMENT" local has_space=$( [ "$ImgSizeGb" -ge $MIN_GB ] && echo 1 || echo 0 )
(( ! $has_params )) && error "insufficient arguments" && usage >&2 && exit "$EXIT_INVALIDARGUMENT"
(( ! $has_space )) && error "image size too small" && usage >&2 && exit "$EXIT_INVALIDARGUMENT"
# determine if the target arch is supported # determine if the target arch is supported
case "$arch" in case "$arch" in
i686|x86_64|armv7h) ;; i686|x86_64|armv7h) ;;
ppc64le|riscv64) ppc64le|riscv64)
warning "%s: arch %s is experimental" "$file" "$arch";; warning "arch %s is experimental" "$arch";;
*) *)
error "%s: arch %s is unsupported" "$file" "$arch" error "arch %s is unsupported" "$arch"
exit "$EXIT_INVALIDARGUMENT";; exit "$EXIT_INVALIDARGUMENT";;
esac esac
@ -418,11 +447,11 @@ main() {
# create the virtual machine # create the virtual machine
if ! pvm_bootstrap; then if ! pvm_bootstrap; then
error "%s: bootstrap failed" "$file" error "bootstrap failed for image: %s" "$file"
exit "$EXIT_FAILURE" exit "$EXIT_FAILURE"
fi fi
msg "%s: bootstrap complete" "$file" msg "bootstrap complete for image: %s" "$file"
} }
main "$@" main "$@"