complete rewrite of pvmbootstrap (formerly create) -- support script files are now obsolete and have been removed

This commit is contained in:
Andreas Grapentin 2019-03-16 02:00:55 +01:00
parent 6fa8b39787
commit 2fe6f0a131
No known key found for this signature in database
GPG Key ID: 7171986E4B745536
8 changed files with 292 additions and 487 deletions

22
README

@ -9,20 +9,26 @@ virtual machine image creation
------------------------------
To create a new virtual machine image, run
$> sudo ./create.sh
The creation is influenced by the following environment variables:
$> ./pvmbootstrap.sh [path to image] [arch]
ARCH - the target architecture of the image. default: armv7h
where arch is one of the supported parabola arches, which are currently:
SIZE - the size of the root image. default: 64GiB
official: [ i686, x86_64, armv7h ]
unofficial: [ ppc64le, riscv64 ]
MIRROR - the mirror used to pacstrap the image, anything valid in a `Server =`
line can go here.
default: https://redirector.parabola.nu/\$repo/os/\$arch}
the script will attempt to bootstrap a virtual machine of the selected
archituecture in the output file specified. If the output file already exists,
the script will emit a warning and ask for confirmation to proceed.
The created images are stored in the build/ directory.
the creation can be influenced by providing one or more of the following
arguments to pvmbootstrap.sh:
-s size -- set the size of the created VM image (default: 64G)
-M mirror -- set the mirror to fetch packages from
(default: https://repo.parabola.nu/$repo/os/$arch)
the created machine images should be bootable using pvmboot.sh
virtual machine boot
--------------------

@ -1,47 +0,0 @@
#!/bin/bash
##############################################################################
# parabola-imagebuilder #
# #
# Copyright (C) 2017, 2018 Andreas Grapentin #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
##############################################################################
# target options
export ARCH="${ARCH:-armv7h}"
export SIZE="${SIZE:-64G}"
export MIRROR="${MIRROR:-https://redirector.parabola.nu/\$repo/os/\$arch}"
# common directories
startdir="$(pwd)"
export TOPBUILDDIR="$startdir"/build
export TOPSRCDIR="$startdir"/src
mkdir -p "$TOPBUILDDIR"
chown "$SUDO_USER" "$TOPBUILDDIR"
# shellcheck source=src/shared/common.sh
. "$TOPSRCDIR"/shared/common.sh
# sanity checks
if [ "$(id -u)" -ne 0 ]; then
die -e "$ERROR_INVOCATION" "must be root"
fi
# shellcheck source=src/qemu.sh
. "$TOPSRCDIR"/qemu.sh
qemu_make_image "$TOPBUILDDIR/parabola-$ARCH.img" "$SIZE" \
|| die "failed to prepare qemu base image"
msg "all done."

@ -50,7 +50,8 @@ pvm_mount() {
workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return
loopdev="$(sudo losetup -fLP --show "$1")" || return
sudo mount "$loopdev"p1 "$workdir" || return
sudo mount "$loopdev"p1 "$workdir" \
|| sudo mount "$loopdev"p2 "$workdir" || return
}
pvm_umount() {
@ -106,7 +107,7 @@ pvm_native_arch() {
setarch "$arch" /bin/true 2>/dev/null || return
}
pvm_build_qemu_args() {
pvm_guess_qemu_args() {
# if we're not running on X / wayland, disable graphics
if [ -z "$DISPLAY" ]; then qemu_args+=(-nographic); fi
@ -117,8 +118,7 @@ pvm_build_qemu_args() {
case "$2" in
i386|x86_64|ppc64)
qemu_args+=(-m 1G "$1")
if [ -z "$DISPLAY" ]; then qemu_args+=(-append "console=ttyS0"); fi
# unmount the drive early
# unmount the unneeded virtual drive early
pvm_umount ;;
arm)
qemu_args+=(
@ -128,22 +128,23 @@ pvm_build_qemu_args() {
-kernel "$workdir"/vmlinuz-linux-libre
-dtb "$workdir"/dtbs/linux-libre/vexpress-v2p-ca9.dtb
-initrd "$workdir"/initramfs-linux-libre.img
-append " rw root=/dev/mmcblk0p3"
-drive "if=sd,driver=raw,cache=writeback,file=$1")
if [ -z "$DISPLAY" ]; then qemu_args+=(-append " console=ttyAMA0"); fi ;;
-append "console=tty0 console=ttyAMA0 rw root=/dev/mmcblk0p3"
-drive "if=sd,driver=raw,cache=writeback,file=$1") ;;
riscv64)
qemu_args+=(
-machine virt
-m 1G
-kernel "$workdir"/bbl
-append " rw root=/dev/vda"
-append "rw root=/dev/vda"
-drive "file=${loopdev}p3,format=raw,id=hd0"
-device "virtio-blk-device,drive=hd0"
-object "rng-random,filename=/dev/urandom,id=rng0"
-device "virtio-rng-device,rng=rng0"
-device "virtio-net-device,netdev=usernet"
-netdev "user,id=usernet")
if [ -z "$DISPLAY" ]; then qemu_args+=(-append " console=ttyS0"); fi ;;
if [ -z "$DISPLAY" ]; then
qemu_args+=(-append "console=ttyS0 rw root=/dev/vda");
fi ;;
*)
error "%s: unable to determine default qemu args" "$1"
return "$EXIT_FAILURE" ;;
@ -152,7 +153,7 @@ pvm_build_qemu_args() {
main() {
if [ "$(id -u)" -eq 0 ]; then
error "This program must be run as regular user"
error "This program must be run as a regular user"
exit "$EXIT_NOPERMISSION"
fi
@ -187,7 +188,7 @@ main() {
fi
local qemu_args=()
pvm_build_qemu_args "$imagefile" "$arch" || exit
pvm_guess_qemu_args "$imagefile" "$arch" || exit
qemu_args+=("$@")
(set -x; qemu-system-"$arch" "${qemu_args[@]}")

266
src/pvmbootstrap.sh Normal file

@ -0,0 +1,266 @@
#!/bin/bash
###############################################################################
# parabola-vmbootstrap -- create and start parabola virtual machines #
# #
# Copyright (C) 2017 - 2019 Andreas Grapentin #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
###############################################################################
# shellcheck source=/usr/lib/libretools/messages.sh
. "$(librelib messages)"
usage() {
print "usage: %s [-h] [-s size] [-M mirror] filename arch" "${0##*/}"
echo
echo "this script is developed as part of parabola-vmbootstrap."
}
pvm_native_arch() {
local arch
case "$1" in
arm*) arch=armv7l;;
*) arch="$1";;
esac
setarch "$arch" /bin/true 2>/dev/null || return
}
pvm_bootstrap() {
msg "%s: starting image creation for %s" "$file" "$arch"
qemu-img create -f raw "$file" "$size" || return
trap 'pvm_cleanup' INT TERM RETURN
local workdir loopdev
workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return
loopdev="$(sudo losetup -fLP --show "$file")" || return
sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return
# partition and mount
case "$arch" in
i686|x86_64)
sudo parted -s "$loopdev" \
mklabel gpt \
mkpart primary 1MiB 2Mib \
set 1 bios_grub on \
mkpart primary ext2 2MiB 514MiB \
mkpart primary linux-swap 514MiB 4610MiB \
mkpart primary ext4 4610MiB 100% || return
sudo partprobe "$loopdev"
sudo mkfs.ext2 "$loopdev"p2 || return
sudo mkswap "$loopdev"p3 || return
sudo mkfs.ext4 "$loopdev"p4 || return
sudo mount "$loopdev"p4 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p2 "$workdir"/boot || return
;;
ppc64le|riscv64)
sudo parted -s "$loopdev" \
mklabel gpt \
mkpart primary ext2 1MiB 513MiB \
set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return
sudo partprobe "$loopdev"
sudo mkfs.ext2 "$loopdev"p1 || return
sudo mkswap "$loopdev"p2 || return
sudo mkfs.ext4 "$loopdev"p3 || return
sudo mount "$loopdev"p3 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p1 "$workdir"/boot || return
;;
armv7h)
sudo parted -s "$loopdev" \
mklabel gpt \
mkpart ESP fat32 1MiB 513MiB \
set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return
sudo partprobe "$loopdev"
sudo mkfs.vfat -F 32 "$loopdev"p1 || return
sudo mkswap "$loopdev"p2 || return
sudo mkfs.ext4 "$loopdev"p3 || return
sudo mount "$loopdev"p3 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p1 "$workdir"/boot || return
;;
esac
# setup qemu-user-static
if ! pvm_native_arch "$arch"; then
# target arch can't execute natively, pacstrap is going to need help by qemu
local qemu_arch
case "$arch" in
armv7h) qemu_arch=arm ;;
*) qemu_arch="$arch" ;;
esac
if [[ -z $(sudo grep -l -F \
-e "interpreter /usr/bin/qemu-$qemu_arch-" \
-r -- /proc/sys/fs/binfmt_misc 2>/dev/null \
| xargs -r sudo grep -xF 'enabled') ]]
then
error "%s: missing qemu-user-static for %s" "$file" "$arch"
return "$EXIT_FAILURE"
fi
sudo mkdir -p "$workdir"/usr/bin
sudo cp -v "/usr/bin/qemu-$qemu_arch-"* "$workdir"/usr/bin || return
fi
# pacstrap
local pacconf
pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return
cat > "$pacconf" << EOF
[options]
Architecture = $arch
[libre]
Server = $mirror
[core]
Server = $mirror
[extra]
Server = $mirror
[community]
Server = $mirror
EOF
local pkg=(base)
case "$arch" in
i686|x86_64) pkg+=(grub) ;;
esac
sudo pacstrap -GMcd -C "$pacconf" "$workdir" "${pkg[@]}" || return
# finalize
case "$arch" in
i686|x86_64)
# create an fstab
sudo swapoff --all
sudo swapon "$loopdev"p3
genfstab -U "$workdir" | sudo tee "$workdir"/etc/fstab
sudo swapoff "$loopdev"p3
sudo swapon --all
# install grub to the VM
sudo sed -i 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0"/' \
"$workdir"/etc/default/grub || return
sudo arch-chroot "$workdir" grub-install --target=i386-pc "$loopdev" || return
sudo arch-chroot "$workdir" grub-mkconfig -o /boot/grub/grub.cfg || return
# regenerate the chroot-botched initcpio
sudo cp "$workdir"/etc/mkinitcpio.d/linux-libre.preset{,.backup} || return
echo "default_options=\"-S autodetect\"" \
| sudo tee -a "$workdir"/etc/mkinitcpio.d/linux-libre.preset || return
sudo arch-chroot "$workdir" mkinitcpio -p linux-libre || return
sudo mv "$workdir"/etc/mkinitcpio.d/linux-libre.preset{.backup,} || return
;;
armv7h)
# create an fstab
sudo swapoff --all
sudo swapon "$loopdev"p2
genfstab -U "$workdir" | sudo tee "$workdir"/etc/fstab
sudo swapoff "$loopdev"p2
sudo swapon --all
;;
riscv64)
# FIXME: for the time being, use fedora bbl to boot
sudo wget https://fedorapeople.org/groups/risc-v/disk-images/bbl \
-O "$workdir"/boot/bbl || return
;;
esac
pvm_cleanup
}
pvm_cleanup() {
trap - INT TERM RETURN
[ -n "$pacconf" ] && rm -f "$pacconf"
unset pacconf
if [ -n "$workdir" ]; then
sudo rm -f "$workdir"/usr/bin/qemu-*
sudo umount -R "$workdir"
rmdir "$workdir"
fi
unset workdir
[ -n "$loopdev" ] && sudo losetup -d "$loopdev"
unset loopdev
}
main() {
if [ "$(id -u)" -eq 0 ]; then
error "This program must be run as a regular user"
exit "$EXIT_NOPERMISSION"
fi
local size="64G"
local mirror="https://repo.parabola.nu/\$repo/os/\$arch"
# parse options
while getopts 'hs:M:' arg; do
case "$arg" in
h) usage; return "$EXIT_SUCCESS";;
s) size="$OPTARG";;
M) mirror="$OPTARG";;
*) usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac
done
local shiftlen=$(( OPTIND - 1 ))
shift $shiftlen
if [ "$#" -ne 2 ]; then usage >&2; exit "$EXIT_INVALIDARGUMENT"; fi
local file="$1"
local arch="$2"
# determine if the target arch is supported
case "$arch" in
i686|x86_64|armv7h) ;;
ppc64le|riscv64)
warning "%s: arch %s is experimental" "$file" "$arch";;
*)
error "%s: arch %s is unsupported" "$file" "$arch"
exit "$EXIT_INVALIDARGUMENT";;
esac
if [ -e "$file" ]; then
warning "%s: file exists. Continue? [y/N]" "$file"
read -p " " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit "$EXIT_FAILURE"
fi
rm -f "$file" || exit
fi
if ! pvm_bootstrap; then
error "%s: bootstrap failed" "$file"
exit "$EXIT_FAILURE"
fi
msg "%s: bootstrap complete" "$file"
}
main "$@"

@ -1,202 +0,0 @@
#!/bin/bash
##############################################################################
# parabola-imagebuilder #
# #
# Copyright (C) 2018 Andreas Grapentin #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
##############################################################################
qemu_img_partition_and_mount_for_armv7h() {
parted -s "$1" \
mklabel gpt \
mkpart ESP fat32 1MiB 513MiB \
set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return
check_exe -r mkfs.vfat mkfs.ext4
mkfs.vfat -F 32 "${1}p1"
mkswap "${1}p2"
mkfs.ext4 "${1}p3"
mkdir -p "$2"
mount "${1}p3" "$2" || return
trap_add "umount -R $2" INT TERM EXIT
mkdir -p "$2"/boot
mount "${1}p1" "$2"/boot || return
}
qemu_img_partition_and_mount_for_riscv64() {
qemu_img_partition_and_mount_for_x86_64 "$@"
}
qemu_img_partition_and_mount_for_powerpc64le() {
qemu_img_partition_and_mount_for_x86_64 "$@"
}
qemu_img_partition_and_mount_for_i686() {
qemu_img_partition_and_mount_for_x86_64 "$@"
}
qemu_img_partition_and_mount_for_x86_64() {
parted -s "$1" \
mklabel gpt \
mkpart primary ext2 1MiB 513MiB \
set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return
check_exe mkfs.ext2 mkfs.ext4
mkfs.ext2 "${1}p1"
mkswap "${1}p2"
mkfs.ext4 "${1}p3"
mkdir -p "$2"
mount "${1}p3" "$2" || return
trap_add "umount -R $2" INT TERM EXIT
mkdir -p "$2"/boot
mount "${1}p1" "$2"/boot || return
}
qemu_img_losetup() {
echo -n "checking for free loop device ... "
loopdev=$(losetup -f --show "$1") || loopdev=no
echo "$loopdev"
partprobe "$loopdev"
[ "x$loopdev" == "xno" ] && return "$ERROR_MISSING"
trap_add "qemu_img_lorelease $loopdev" INT TERM EXIT
}
qemu_img_lorelease() {
losetup -d "$1"
}
qemu_arch_is_foreign() {
# borrowed from /usr/bin/librechroot
local setarch
case "$1" in
arm*) setarch=armv7l ;;
*) setarch="$1" ;;
esac
echo -n "checking if arch '$1' is foreign ... "
local need_qemu=no
setarch "$setarch" /bin/true 2>/dev/null || need_qemu=yes
echo "$need_qemu"
[ "x$need_qemu" == "xyes" ] || return
}
qemu_setup_user_static() {
local interpreter
case "$ARCH" in
armv7h) interpreter=/usr/bin/qemu-arm- ;;
powerpc64le) interpreter=/usr/bin/qemu-ppc64le- ;;
*) interpreter=/usr/bin/qemu-"$ARCH"- ;;
esac
if qemu_arch_is_foreign "$ARCH"; then
# target arch can't execute natively, pacstrap is going to need help by qemu
if [[ -z $(grep -l -F \
-e "interpreter $interpreter" \
-r -- /proc/sys/fs/binfmt_misc 2>/dev/null \
| xargs -r grep -xF 'enabled') ]]
then
error "unable to continue - need qemu-user-static for $ARCH"
return "$ERROR_MISSING"
fi
mkdir -p "$1"/usr/bin
cp -v "$interpreter"* "$1"/usr/bin || return
trap_add "qemu_cleanup_user_static $1"
fi
}
qemu_cleanup_user_static() {
rm -f "$1"/usr/bin/qemu-*
}
qemu_img_finalize_for_armv7h() {
true
}
qemu_img_finalize_for_riscv64() {
# for the time being, use fedora bbl to boot
wget https://fedorapeople.org/groups/risc-v/disk-images/bbl \
-O "$1"/boot/bbl
}
qemu_img_finalize_for_powerpc64le() {
true
}
qemu_img_finalize_for_i686() {
true
}
qemu_img_finalize_for_x86_64() {
true
}
qemu_make_image() {
msg "preparing parabola qemu image for $ARCH"
# skip, if already exists
check_file "$1" && return
check_exe -r parted
# write to preliminary file
local tmpfile="$1.part"
rm -f "$tmpfile"
# create an empty image
qemu-img create -f raw "$tmpfile" "$2" || return
# create a minimal pacman.conf
cat > "$TOPBUILDDIR/pacman.conf.$ARCH" << EOF
[options]
Architecture = $ARCH
[libre]
Server = $MIRROR
[core]
Server = $MIRROR
[extra]
Server = $MIRROR
[community]
Server = $MIRROR
EOF
# setup the image (in a subshell for trap management)
(
loopdev=''
qemu_img_losetup "$tmpfile" || return
dd if=/dev/zero of="$loopdev" bs=1M count=8 || return
"qemu_img_partition_and_mount_for_$ARCH" "$loopdev" "$TOPBUILDDIR"/mnt || return
qemu_setup_user_static "$TOPBUILDDIR"/mnt || return
pacstrap -GMcd -C "$TOPBUILDDIR/pacman.conf.$ARCH" "$TOPBUILDDIR"/mnt || return
"qemu_img_finalize_for_$ARCH" "$TOPBUILDDIR"/mnt || return
) || return
mv "$tmpfile" "$1"
}

@ -1,100 +0,0 @@
#!/bin/bash
##############################################################################
# parabola-imagebuilder #
# #
# Copyright (C) 2018 Andreas Grapentin #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
##############################################################################
check_exe() {
local OPTIND o r=
while getopts "r" o; do
case "$o" in
r) r=yes ;;
*) die -e "$ERROR_INVOCATION" "Usage: ${FUNCNAME[0]} [-r] program ..." ;;
esac
done
shift $((OPTIND-1))
local v res=0
for v in "$@"; do
echo -n "checking for $v in \$PATH ... "
local have_exe=yes
type -p "$v" >/dev/null || have_exe=no
echo $have_exe
if [ "x$have_exe" != "xyes" ]; then
[ "x$r" == "xyes" ] && die -e "$ERROR_MISSING" "missing $v in \$PATH"
res="$ERROR_MISSING"
fi
done
return "$res"
}
check_file() {
local OPTIND o r=
while getopts "r" o; do
case "$o" in
r) r=yes ;;
*) die -e "$ERROR_INVOCATION" "Usage: ${FUNCNAME[0]} [-r] file ..." ;;
esac
done
shift $((OPTIND-1))
local v res=0
for v in "$@"; do
echo -n "checking for $v ... "
local have_file=yes
[ -f "$v" ] || have_file=no
echo $have_file
if [ "x$have_file" != "xyes" ]; then
[ "x$r" == "xyes" ] && die -e "$ERROR_MISSING" "missing $v in filesystem"
res="$ERROR_MISSING"
fi
done
return "$res"
}
check_gpgkey() {
local OPTIND o r=
while getopts "r" o; do
case "$o" in
r) r=yes ;;
*) die -e "$ERROR_INVOCATION" "Usage: ${FUNCNAME[0]} [-r] key" ;;
esac
done
shift $((OPTIND-1))
local v res=0
for v in "$@"; do
echo -n "checking for key $v ... "
local have_key=yes
sudo -u "$SUDO_USER" gpg --list-keys "$v" &>/dev/null || have_key=no
echo $have_key
if [ "x$have_key" != "xyes" ]; then
[ "x$r" == "xyes" ] && die -e "$ERROR_MISSING" "missing $v in keyring"
res="$ERROR_MISSING"
fi
done
return "$res"
}

@ -1,68 +0,0 @@
#!/bin/bash
##############################################################################
# parabola-imagebuilder #
# #
# Copyright (C) 2018 Andreas Grapentin #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
##############################################################################
# shellcheck source=src/shared/feedback.sh
. "$TOPSRCDIR"/shared/feedback.sh
# shellcheck source=src/shared/checks.sh
. "$TOPSRCDIR"/shared/checks.sh
retry() {
local OPTIND o n=5 s=60
while getopts "n:s:" o; do
case "$o" in
n) n="$OPTARG" ;;
s) s="$OPTARG" ;;
*) die -e $ERROR_INVOCATION "Usage: ${FUNCNAME[0]} [-n tries] [-s delay] cmd ..." ;;
esac
done
shift $((OPTIND-1))
for _ in $(seq "$((n - 1))"); do
"$@" && return 0
sleep "$s"
done
"$@" || return
}
# appends a command to a trap
# source: https://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal
#
# - 1st arg: code to add
# - remaining args: names of traps to modify
#
trap_add() {
trap_add_cmd=$1; shift || fatal "${FUNCNAME[0]} usage error"
for trap_add_name in "$@"; do
trap -- "$(
# helper fn to get existing trap command from output
# of trap -p
extract_trap_cmd() { printf '%s\n' "$3"; }
# print the new trap command
printf '%s\n' "${trap_add_cmd}"
# print existing trap command with newline
eval "extract_trap_cmd $(trap -p "${trap_add_name}")"
)" "${trap_add_name}" \
|| fatal "unable to add to trap ${trap_add_name}"
done
}
# set the trace attribute for the above function. this is
# required to modify DEBUG or RETURN traps because functions don't
# inherit them unless the trace attribute is set
declare -f -t trap_add

@ -1,51 +0,0 @@
#!/bin/bash
##############################################################################
# parabola-imagebuilder #
# #
# Copyright (C) 2018 Andreas Grapentin #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
##############################################################################
# error codes
export ERROR_UNSPECIFIED=1
export ERROR_INVOCATION=2
export ERROR_MISSING=3
export ERROR_BUILDFAIL=4
export ERROR_KEYFAIL=5
# messaging functions
msg() {
echo "$(tput bold)$(tput setf 2)==>$(tput setf 7) $*$(tput sgr0)";
}
error() {
echo "$(tput bold)$(tput setf 4)==> ERROR:$(tput setf 7) $*$(tput sgr0)" 1>&2
}
die() {
local OPTIND o e="$ERROR_UNSPECIFIED"
while getopts "e:" o; do
case "$o" in
e) e="$OPTARG" ;;
*) die -e "$ERROR_INVOCATION" "Usage: ${FUNCNAME[0]} [-e status] msg ..." ;;
esac
done
shift $((OPTIND-1))
error "$@"
trap - ERR
exit "$e"
}
trap 'die "unknown error"' ERR