improve fs detection - factor out common functions

This commit is contained in:
bill-auger 2020-02-08 18:25:10 -05:00
parent 8f102e6742
commit 4872be99ac
5 changed files with 522 additions and 382 deletions

53
README

@ -26,6 +26,8 @@ the script will emit a warning and ask for confirmation to proceed.
The creation can be influenced by providing one or more of the following The creation can be influenced by providing one or more of the following
options to pvmbootstrap: options to pvmbootstrap:
-b <base-set> -- Select one of the pre-defined package-sets described below
(default: 'standard')
-H <hook> -- Enable a hook to customize the created image. This can be -H <hook> -- Enable a hook to customize the created image. This can be
the path to a custom script, or one of the predefined hooks the path to a custom script, or one of the predefined hooks
described below. The VM will boot the newly created image, described below. The VM will boot the newly created image,
@ -39,16 +41,21 @@ options to pvmbootstrap:
-O -- Bootstrap an openrc system instead of a systemd one -O -- Bootstrap an openrc system instead of a systemd one
-p <package> -- Specify additional packages to be installed in the VM image. -p <package> -- Specify additional packages to be installed in the VM image.
This option can be specified multiple times. This option can be specified multiple times.
-s <img_size> -- Set the size (in GB) of the VM image (minimum: 1, default: 64) -s <root_size> -- Set the size (in MB) of the root partition (default: 32000).
If this is 0 (or less than the <base-set> requires),
the VM image will be the smallest size possible,
fit to the <base-set>; and any -p <package> will be ignored.
-S <swap_size> -- Set the size (in MB) of the swap partition (default: 0) -S <swap_size> -- Set the size (in MB) of the swap partition (default: 0)
The creation hooks currently supported are: Pre-defined package-sets:
minimal: base
standard: base parabola-base
devel: base parabola-base base-devel
'ethernet-dhcp': Pre-defined hooks:
ethernet-dhcp: This hook will setup ethernet in the VM by enabling
This hook will setup ethernet in the VM by enabling systemd-resolved and systemd-resolved and openresolv properly, as well as creating
openresolv properly, as well as creating and enabling a systemd-networkd and enabling a systemd-networkd configuration. (systemd only)
configuration.
-------------------- --------------------
@ -59,10 +66,23 @@ To boot a created virtual machine, run:
$> pvmboot [options] <path_to_image> [qemu-args ...] $> pvmboot [options] <path_to_image> [qemu-args ...]
The script will attempt to determine the architecture of the provided virtual The script will attempt to determine the architecture and partition layout
machine image, and set the qemu executable and sane default flags for the qemu of the provided virtual machine image, and set the qemu executable and sane
invocation automatically, including kvm acceleration, if available for the default flags for the qemu invocation automatically; and will enable KVM
target architecture. acceleration, if available for the target architecture.
The pvmbootstrap script creates a boot partition formatted with either the
vfat or ext2 filesystems, and a root partition formatted with the ext4
filesystems. If the specified image was not created using pvmbootstrap, the
partition detection will fail unless the image coforms to this expected schema.
The first vfat or ext2 filesystem detected, will be considered as the boot
partition; and the first ext4 filesystem detected, will be considered as the
root partition.
The default kernel installed by pvmbootstrap is 'linux-libre'.
The -k option ca be used to specify an alternate kernel to boot.
$> pvmboot -k linux-libre-lts ./an.img
Additionally, the script will evaluate the DISPLAY environment variable to Additionally, the script will evaluate the DISPLAY environment variable to
determine whether a graphical desktop environment is available, and will start determine whether a graphical desktop environment is available, and will start
@ -71,12 +91,7 @@ unsetting DISPLAY before executing the script:
$> DISPLAY= pvmboot ./an.img $> DISPLAY= pvmboot ./an.img
The default kernel installed by pvmbootstrap is 'linux-libre'. If qemu boots into graphical mode, the serial console can be redirected
The -k option ca be used to specify an alternate kernel to boot.
$> pvmboot -k linux-libre-lts ./an.img
When the VM will boot into graphical mode, the serial console can be redirected
to the host console by passing the -r option. to the host console by passing the -r option.
$> pvmboot -r ./an.img $> pvmboot -r ./an.img
@ -104,5 +119,5 @@ pvmbootstrap always creates a /boot partition.
for example, to generate the parabola armv7h release tarball: for example, to generate the parabola armv7h release tarball:
$> img_filename=parabola-systemd-cli-armv7h-tarball-$(date +%Y.%m).img $> img_filename=parabola-systemd-cli-armv7h-tarball-$(date +%Y.%m).img
$> pvmbootstrap -s 1 -H ethernet-dhcp $img_filename armv7h $> pvmbootstrap -b minimal -H ethernet-dhcp -s 0 -S0 $img_file armv7h
$> pvm2tarball $img_filename $> pvm2tarball $img_file

264
src/pvm-common.sh.inc Normal file

@ -0,0 +1,264 @@
# readonly DATA_IMG=./pvmdata.img # optional large qemu disk
readonly THIS_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
readonly UNPRIVILEGED_ERR_MSG="This program must be run as a regular user"
readonly MOUNTS_ERR_MSG="some PVM mountpoints are mounted - possibly orphans from a previous run - unmount them first"
readonly MOUNTVARS_ERR_MSG="FIXME: pvm_setup_loopdev() was called in an improper state - one of [ \$bootdir , \$workdir , \$loopdev ] is already set"
# shellcheck source=/usr/lib/libretools/messages.sh
source "$(librelib messages)"
pvm_get_pvmboot_cmd()
{
local intree_cmd=$THIS_DIR/pvmboot.sh
local installed_cmd='pvmboot'
[[ -f "$intree_cmd" ]] && echo "$intree_cmd" && return "$EXIT_SUCCESS"
type -p $installed_cmd &>/dev/null && echo "$installed_cmd" && return "$EXIT_SUCCESS"
error "can not find pvmboot" && return "$EXIT_FAILURE"
}
pvm_get_hook() # ( hook_name )
{
local hook_name=$1
local intree_dir=$THIS_DIR/hooks
local installed_dir=/usr/lib/parabola-vmbootstrap
local hook_filename=hook-$hook_name.sh
local locations=( "$intree_dir/$hook_filename" "$installed_dir/$hook_filename" "$hook_name" '' )
local location
for location in "${locations[@]}" ; do [[ -f "$location" ]] && break ; done ;
[[ "$location" ]] && echo "$location" || warning "no such hook: '%s'" "$hook_name"
[[ "$location" ]] && return "$EXIT_SUCCESS" || return "$EXIT_FAILURE"
}
pvm_find_part_n() # ( imagefile fs_types ) , sets: $part_n
{
local imagefile="$1" ; shift ;
local fs_types="$@"
# try locating the partition by filesystem type
for fs_type in $fs_types
do local part_data=$(parted "$imagefile" print 2> /dev/null | grep $fs_type | head -n 1)
part_n=$( echo $part_data | cut -d ' ' -f 1 )
! [[ "$part_n" =~ ^[0-9]+$ ]] && part_n='' || break
done
[[ "$part_n" =~ ^[0-9]+$ ]] && return "$EXIT_SUCCESS" || return "$EXIT_FAILURE"
}
pvm_find_boot_part_n() # ( imagefile ) , sets: boot_part_n
{
local imagefile="$1"
local part_n
pvm_find_part_n "$imagefile" fat32 ext2 || return "$EXIT_FAILURE"
boot_part_n=$part_n
return "$EXIT_SUCCESS"
}
pvm_find_root_part_n() # ( imagefile ) , sets: root_part_n
{
local imagefile="$1"
local part_n
pvm_find_part_n "$imagefile" ext4 || return "$EXIT_FAILURE"
root_part_n=$part_n
return "$EXIT_SUCCESS"
}
pvm_check_unprivileged() # exits on failure
{
[[ "$(id -u)" -eq 0 ]] && error "$UNPRIVILEGED_ERR_MSG" && exit "$EXIT_NOPERMISSION"
}
pvm_native_arch() # ( arch )
{
local arch=$1
local native_arch=$( [[ "$arch" =~ arm.* ]] && echo 'armv7l' || echo "$arch" )
setarch "$native_arch" /bin/true 2>/dev/null && return "$EXIT_SUCCESS" || \
return "$EXIT_FAILURE"
}
pvm_check_file_exists_writable() # (file_path [ is_error_if_not_exists ])
{
local file_path="$1"
local is_error_if_not_exists=$( [[ "$2" == 'true' ]] && echo 1 || echo 0 )
if [[ -e "$file_path" ]]
then if [[ -w "$file_path" ]]
then return "$EXIT_SUCCESS"
else error "file exists but is not writable: '%s'" "$file_path"
return "$EXIT_FAILURE"
fi
elif (( ! $is_error_if_not_exists ))
then return "$EXIT_SUCCESS"
else error "no such file: %s" "$file_path"
return "$EXIT_FAILURE"
fi
}
pvm_check_file_exists_and_writable() # (file_path)
{
local file_path="$1"
pvm_check_file_exists_writable $file_path true && return "$EXIT_SUCCESS" || \
return "$EXIT_FAILURE"
}
pvm_check_file_not_exists_or_writable() # (file_path)
{
local file_path="$1"
pvm_check_file_exists_writable $file_path && return "$EXIT_SUCCESS" || \
return "$EXIT_FAILURE"
}
pvm_prompt_clobber_file() # (file_path)
{
local file_path="$1"
if pvm_check_file_not_exists_or_writable "$file_path"
then if [[ -e "$file_path" ]]
then warning "file exists: '%s'\nContinue? [y/N]" "$file_path"
read -p " " -n 1 -r ; echo ;
[[ $REPLY =~ ^[Yy]$ ]] || return "$EXIT_FAILURE"
rm -f "$file_path" || return "$EXIT_FAILURE"
fi
return "$EXIT_SUCCESS"
else return "$EXIT_FAILURE"
fi
}
pvm_setup_loopdev() # assumes: $imagefile , sets: $bootdir $workdir $loopdev , traps: INT TERM EXIT
{
if file "$imagefile" | grep -Eq ': (data|DOS/MBR )'; then
if [[ -z "${bootdir}${workdir}${loopdev}" ]]; then
pvm_check_no_mounts && msg "creating loopback devices" || return "$EXIT_FAILURE"
else
error "$MOUNTVARS_ERR_MSG"
return "$EXIT_FAILURE"
fi
else
error "not a raw qemu image: '%s'" "$imagefile"
return "$EXIT_FAILURE"
fi
trap 'pvm_cleanup' INT TERM EXIT
# setup the loopback device
bootdir="$(mktemp -d -t pvm-bootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE"
workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE"
loopdev="$(sudo losetup -fLP --show "$imagefile")" || return "$EXIT_FAILURE"
return "$EXIT_SUCCESS"
}
pvm_mount() # assumes: $imagefile $loopdev $bootdir $workdir
{
pvm_setup_loopdev || return "$EXIT_FAILURE" # sets: $bootdir $workdir $loopdev
# find boot and root filesystem partitions
local boot_part_n root_part_n
pvm_find_boot_part_n "$imagefile" || return "$EXIT_FAILURE" # sets: $boot_part_n
pvm_find_root_part_n "$imagefile" || return "$EXIT_FAILURE" # sets: $root_part_n
# mount boot and root filesystems
msg "mounting image filesystems"
sudo mount "$loopdev"p$boot_part_n "$bootdir" || return "$EXIT_FAILURE"
sudo mount "$loopdev"p$root_part_n "$workdir" || return "$EXIT_FAILURE"
return "$EXIT_SUCCESS"
}
pvm_umount() # unsets: $bootdir $workdir
{
[[ "${bootdir}${workdir}${loopdev}" ]] && msg "un-mounting image filesystems"
(sudo umount "$workdir"/boot && rmdir "$workdir") 2> /dev/null
(sudo umount "$bootdir" && rmdir "$bootdir") 2> /dev/null
(sudo umount "$workdir" && rmdir "$workdir") 2> /dev/null
unset bootdir
unset workdir
}
pvm_cleanup() # unsets: $loopdev , untraps: INT TERM EXIT
{
trap - INT TERM EXIT
pvm_umount
sudo losetup -d "$loopdev"
pvm_check_no_mounts || return "$EXIT_FAILURE"
unset loopdev
return "$EXIT_SUCCESS"
}
pvm_check_no_mounts() # assumes: $imagefile
{
local n_pvm_mounts=$( mount | grep /tmp/pvm | wc --lines )
local n_loop_devs=$( sudo losetup --associated $imagefile | wc --lines )
local are_any_mounts=$( (( $n_pvm_mounts + $n_loop_devs )) && echo 1 || echo 0 )
(( $are_any_mounts )) && error "$MOUNTS_ERR_MSG" && return "$EXIT_FAILURE" || \
return "$EXIT_SUCCESS"
}
pvm_probe_arch() # assumes: $bootdir $workdir $imagefile , sets: $arch
{
msg "detecting CPU architecture for image"
local kernel=$(find "$bootdir" -maxdepth 1 -type f -iname '*vmlinu*' | head -n1)
local guest_arch
if [ -n "$kernel" ]; then
msg2 "found kernel binary: %s" "$kernel"
else
warning "%s: unable to find kernel binary" "$imagefile"
return "$EXIT_FAILURE"
fi
guest_arch="$(readelf -h "$workdir"/bin/true 2>/dev/null | \
grep Machine | sed 's|[^:]*:\s*\([^:,]*\).*|\1|')"
case "$guest_arch" in
ARM ) arch=armv7h ;;
i386|i386:*|*\ 80386) arch=i686 ;;
PowerPC64 ) arch=ppc64le ;;
RISC-V ) arch=riscv64 ;;
x86_64|*\ X86-64 ) arch=x86_64 ;;
* ) arch='' ;;
esac
if [[ "$arch" ]]; then
msg2 "detected guest \`/bin/true\` arch: '%s'=>'%s'" "$guest_arch" "$arch"
return "$EXIT_SUCCESS"
else
error "image arch is unknown: '%s'" "$guest_arch"
return "$EXIT_FAILURE"
fi
}
pvm_boot() # ( imagefile qemu_args )
{
local imagefile="$1" ; shift ;
local qemu_args=(-no-reboot $@)
local pvmboot_cmd=$(pvm_get_pvmboot_cmd)
local was_error=$?
[[ "$pvmboot_cmd" ]] || return $EXIT_FAILURE
DISPLAY='' "$pvmboot_cmd" "$imagefile" "${qemu_args[@]}" ; was_error=$? ;
(( ! $was_error )) && return $EXIT_SUCCESS || return $EXIT_FAILURE
}

@ -19,10 +19,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # # along with this program. If not, see <http://www.gnu.org/licenses/>. #
############################################################################### ###############################################################################
# shellcheck source=/usr/lib/libretools/messages.sh
source "$(librelib messages)"
usage() { usage()
{
print "USAGE:" print "USAGE:"
print " pvm2tarball [-h] [-o <FILENAME>] <IMG>" print " pvm2tarball [-h] [-o <FILENAME>] <IMG>"
echo echo
@ -41,78 +40,16 @@ usage() {
echo " <https://git.parabola.nu/parabola-vmbootstrap.git>" echo " <https://git.parabola.nu/parabola-vmbootstrap.git>"
} }
pvm_mount() { main()
if file "$imagefile" | grep -q ' DOS/MBR '; then {
msg "mounting filesystems" pvm_check_unprivileged # exits on failure
else
error "%s: does not seem to be a raw qemu image." "$imagefile"
return "$EXIT_FAILURE"
fi
trap 'pvm_umount' INT TERM EXIT
workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return
loopdev="$(sudo losetup -fLP --show "$imagefile")" || return
# find the root partition
local part rootpart bootpart
for part in "$loopdev"p*; do
sudo mount "$part" "$workdir" || continue
if [ -f "$workdir"/etc/fstab ]; then
rootpart="$part"
break
fi
sudo umount "$workdir"
done
if [ -n "$rootpart" ]; then
msg "found root filesystem partition: %s" "$rootpart"
else
error "%s: unable to determine root partition." "$imagefile"
return "$EXIT_FAILURE"
fi
# find the boot partition
if (( $(find /boot/ -name initramfs-* | wc -l) > 0 )) && \
(( $(find /boot/ -name vmlinuz-* | wc -l) > 0 )); then
msg "found /boot on root filesystem partition"
else
bootpart="$(findmnt -senF "$workdir"/etc/fstab /boot | awk '{print $2}')"
if [ -n "$bootpart" ]; then
# mount and be happy
msg "found boot filesystem partition: %s" "$bootpart"
sudo mount "$bootpart" "$workdir"/boot || return "$EXIT_FAILURE"
else
error "%s: unable to determine boot filesystem partition." "$imagefile"
return "$EXIT_FAILURE"
fi
fi
}
pvm_umount() {
msg "un-mounting filesystems"
trap - INT TERM EXIT
[ -n "$workdir" ] && (sudo umount -R "$workdir"; rmdir "$workdir")
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
# parse options # parse options
local output local outfile
while getopts 'ho:' arg; do while getopts 'ho:' arg; do
case "$arg" in case "$arg" in
h) usage; return "$EXIT_SUCCESS";; h) usage; return "$EXIT_SUCCESS";;
o) output="$OPTARG";; o) outfile="$OPTARG";;
*) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";; *) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac esac
done done
@ -124,29 +61,16 @@ main() {
image_filename="$(basename "$imagefile")" image_filename="$(basename "$imagefile")"
shift shift
# check for input file presence
if [ ! -e "$imagefile" ]; then
error "%s: file not found" "$imagefile"
exit "$EXIT_FAILURE"
fi
# determine output file # determine output file
[ -n "$output" ] || output="${image_filename%.img*}.tar.gz" [ -n "$outfile" ] || outfile="$(dirname $imagefile)/${image_filename%.img*}.tar.gz"
# check for output file presence # ensure that the image file exists, prompt to clobber existing output file
if [ -e "$output" ]; then pvm_check_file_exists_and_writable "$imagefile" || exit "$EXIT_FAILURE"
warning "%s: file exists. Continue? [y/N]" "$output" pvm_prompt_clobber_file "$outfile" || exit "$EXIT_FAILURE"
read -p " " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit "$EXIT_FAILURE"
fi
rm -f "$output" || exit
fi
# mount the root filesystem # mount the root filesystem
local workdir loopdev local bootdir workdir loopdev
pvm_mount || exit pvm_mount || exit "$EXIT_FAILURE" # assumes: $imagefile , sets: $loopdev $bootdir $workdir
# tar the root filesystem, excluding unneeded things # tar the root filesystem, excluding unneeded things
# HACKING: # HACKING:
@ -156,7 +80,7 @@ main() {
# #
# `tar -tf <tarball> | sort` # `tar -tf <tarball> | sort`
msg "imploding tarball" msg "imploding tarball"
sudo tar -c -f "$output" -C "$workdir" -X - . << EOF sudo tar -c -f "$outfile" -C "$workdir" -X - . << EOF
./boot/lost+found ./boot/lost+found
./etc/.updated ./etc/.updated
./etc/pacman.d/gnupg ./etc/pacman.d/gnupg
@ -169,10 +93,15 @@ main() {
EOF EOF
# give the archive back to the user # give the archive back to the user
sudo chown "$(id -u)":"$(id -g)" "$output" sudo chown "$(id -u)":"$(id -g)" "$outfile"
# cleanup # cleanup
pvm_umount pvm_cleanup
} }
main "$@"
if source /usr/lib/parabola-vmbootstrap/pvm-common.sh.inc 2> /dev/null || \
source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"/pvm-common.sh.inc 2> /dev/null
then main "$@"
else echo "can not find pvm-common.sh.inc" && exit 1
fi

@ -19,8 +19,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # # along with this program. If not, see <http://www.gnu.org/licenses/>. #
############################################################################### ###############################################################################
# shellcheck source=/usr/lib/libretools/messages.sh
source "$(librelib messages)"
readonly DEF_KERNEL='linux-libre' # ASSERT: must be 'linux-libre', per 'parabola-base' readonly DEF_KERNEL='linux-libre' # ASSERT: must be 'linux-libre', per 'parabola-base'
readonly DEF_RAM_MB=1000 readonly DEF_RAM_MB=1000
@ -29,14 +27,16 @@ Kernel=$DEF_KERNEL
RedirectSerial=0 RedirectSerial=0
usage() { usage()
{
print "USAGE:" print "USAGE:"
print " pvmboot [-h] [-k <kernel>] [-r] <img> [qemu-args ...]" print " pvmboot [-h] [-k <kernel>] [-r] <img> [qemu-args ...]"
echo echo
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 instances are assigned created using pvmbootstrap. If the image was not created using pvmbootstrap,
${DEF_RAM_MB}MB of RAM and one SMP core." the boot partition must be vfat or ext2, and the root partition must be ext4
The machine instance is assigned ${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
@ -63,176 +63,120 @@ usage() {
echo " <https://git.parabola.nu/parabola-vmbootstrap.git>" echo " <https://git.parabola.nu/parabola-vmbootstrap.git>"
} }
pvm_mount() {
if ! file "$imagefile" | grep -q ' DOS/MBR '; then
error "%s: does not seem to be a raw qemu image." "$imagefile"
return "$EXIT_FAILURE"
fi
msg "mounting filesystems" pvm_guess_qemu_cmd() # assumes: $arch , sets: $qemu_cmd
trap 'pvm_umount' INT TERM EXIT {
workdir="$(mktemp -d -t pvm-XXXXXXXXXX)" || return "$EXIT_FAILURE"
loopdev="$(sudo losetup -fLP --show "$imagefile")" || return "$EXIT_FAILURE"
sudo mount "$loopdev"p1 "$workdir" || \
sudo mount "$loopdev"p2 "$workdir" || return "$EXIT_FAILURE"
}
pvm_umount() {
trap - INT TERM EXIT
[ -n "$workdir" ] && (sudo umount "$workdir"; rmdir "$workdir")
[ -n "$loopdev" ] && sudo losetup -d "$loopdev"
unset workdir
unset loopdev
}
pvm_probe_arch() {
local kernel
kernel=$(find "$workdir" -maxdepth 1 -type f -iname '*vmlinu*' | head -n1)
if [ -z "$kernel" ]; then
warning "%s: unable to find kernel binary" "$imagefile"
return "$EXIT_FAILURE"
fi
# attempt to get kernel arch from elf header
arch="$(readelf -h "$kernel" 2>/dev/null | grep Machine | awk '{print $2}')"
case "$arch" in case "$arch" in
PowerPC64) arch=ppc64 ; return "$EXIT_SUCCESS" ;; armv7h ) qemu_cmd="qemu-system-arm" ;;
RISC-V ) arch=riscv64 ; return "$EXIT_SUCCESS" ;; i686 ) qemu_cmd="qemu-system-i386" ;;
* ) arch="" ;; ppc64le) qemu_cmd="qemu-system-ppc64" ;;
riscv64) qemu_cmd="qemu-system-riscv64" ;;
x86_64 ) qemu_cmd="qemu-system-x86_64" ;;
* ) error "unknown image arch: '%s'" "$arch" ; return "$EXIT_FAILURE" ;;
esac esac
# attempt to get kernel arch from objdump
arch="$(objdump -f "$kernel" 2>/dev/null | grep architecture: | awk '{print $2}' | tr -d ',')"
case "$arch" in
i386 ) arch=i386 ; return "$EXIT_SUCCESS" ;;
i386:*) arch=x86_64 ; return "$EXIT_SUCCESS" ;;
* ) arch="" ;;
esac
# attempt to get kernel arch from file magic
arch="$(file "$kernel")"
case "$arch" in
*"ARM boot executable"*) arch=arm ; return "$EXIT_SUCCESS" ;;
* ) arch="" ;;
esac
# no more ideas; giving up.
} }
pvm_native_arch() { pvm_guess_qemu_args() # assumes: $qemu_args $imagefile $arch $bootdir , appends: $qemu_args
local arch {
msg "configuring the virtual machine ($arch)"
case "$1" in qemu_args+=(-m $DEF_RAM_MB )
arm*) arch=armv7l ;;
* ) arch="$1" ;;
esac
setarch "$arch" /bin/true 2>/dev/null || return # optional large qemu disk
} qemu_args+=( $( [[ -w $DATA_IMG ]] && echo "-hdb $DATA_IMG" ) )
pvm_guess_qemu_args() {
# if we're not running on X / wayland, disable graphics # if we're not running on X / wayland, disable graphics
if [ -z "$DISPLAY" ]; then qemu_args+=(-nographic); if [ -z "$DISPLAY" ]; then qemu_args+=(-nographic);
elif (( ${RedirectSerial} )); then qemu_args+=(-serial "mon:stdio"); elif (( ${RedirectSerial} )); then qemu_args+=(-serial "mon:stdio");
fi fi
# find root filesystem partition
local root_part_n
pvm_find_root_part_n "$imagefile" || return "$EXIT_FAILURE" # sets: $root_part_n
# if we're running a supported arch, enable kvm # if we're running a supported arch, enable kvm
if pvm_native_arch "$arch"; then qemu_args+=(-enable-kvm); fi if pvm_native_arch "$arch"; then qemu_args+=(-enable-kvm); fi
# find root filesystem partition (necessary for arches without bootloader)
local root_loopdev_n=$(echo $(parted "$imagefile" print 2> /dev/null | grep ext4) | cut -d ' ' -f 1)
local root_loopdev="$loopdev"p$root_loopdev_n
local root_vdev=/dev/vda$root_loopdev_n
if [[ -b "$root_loopdev" ]]
then
msg "found root filesystem loop device: %s" "$root_loopdev"
else
error "%s: unable to determine root filesystem loop device" "$imagefile"
return "$EXIT_FAILURE"
fi
# set arch-specific args # set arch-specific args
local kernel_console local kernel_tty
case "$arch" in case "$arch" in
i386|x86_64|ppc64) armv7h ) kernel_tty="console=tty0 console=ttyAMA0 " ;;
qemu_args+=(-m $DEF_RAM_MB -hda "$imagefile") i686 ) kernel_tty=$( [[ -z "$DISPLAY" ]] && echo "console=ttyS0 " ) ;;
# unmount the unneeded virtual drive early ppc64le) ;; # TODO:
pvm_umount ;; riscv64) ;; # TODO:
arm) x86_64 ) kernel_tty=$( [[ -z "$DISPLAY" ]] && echo "console=ttyS0 " ) ;;
kernel_console="console=tty0 console=ttyAMA0 " esac
qemu_args+=(-machine virt case "$arch" in
-m $DEF_RAM_MB armv7h ) qemu_args+=(-machine virt
-kernel "$workdir"/vmlinuz-${Kernel} -kernel "$bootdir"/vmlinuz-${Kernel}
-initrd "$workdir"/initramfs-${Kernel}.img -initrd "$bootdir"/initramfs-${Kernel}.img
-append "${kernel_console}rw root=${root_vdev}" -append "${kernel_tty}rw root=/dev/vda$root_part_n"
-drive "if=none,file=${imagefile},format=raw,id=hd" -drive "if=none,file=${imagefile},format=raw,id=hd"
-device "virtio-blk-device,drive=hd" -device "virtio-blk-device,drive=hd"
-netdev "user,id=mynet" -netdev "user,id=mynet"
-device "virtio-net-device,netdev=mynet") ;; -device "virtio-net-device,netdev=mynet") ;;
riscv64) i686 ) qemu_args+=(-hda "$imagefile") ;;
kernel_console=$( [ -z "$DISPLAY" ] && echo "console=ttyS0 " ) ppc64le) qemu_args+=(-hda " $imagefile") ;;
qemu_args+=(-machine virt riscv64) qemu_args+=(-machine virt
-m $DEF_RAM_MB -kernel "$bootdir"/bbl
-kernel "$workdir"/bbl -append "${kernel_tty}rw root=/dev/vda"
-append "${kernel_console}rw root=/dev/vda" -drive "file=/dev/vda$root_part_n,format=raw,id=hd0"
-drive "file=${root_vdev},format=raw,id=hd0" -device "virtio-blk-device,drive=hd0"
-device "virtio-blk-device,drive=hd0" -object "rng-random,filename=/dev/urandom,id=rng0"
-object "rng-random,filename=/dev/urandom,id=rng0" -device "virtio-rng-device,rng=rng0"
-device "virtio-rng-device,rng=rng0" -netdev "user,id=usernet"
-netdev "user,id=usernet" -device "virtio-net-device,netdev=usernet") ;;
-device "virtio-net-device,netdev=usernet") ;; x86_64 ) qemu_args+=(-hda "$imagefile") ;;
*)
error "%s: unable to determine default qemu args" "$imagefile"
return "$EXIT_FAILURE" ;;
esac esac
} }
main() { main() # ( [cli_options] imagefile qemu_args )
if [ "$(id -u)" -eq 0 ]; then {
error "This program must be run as a regular user" pvm_check_unprivileged # exits on failure
exit "$EXIT_NOPERMISSION"
fi
# parse options # parse options
while getopts 'hk:r' arg; do while getopts 'hk:r' arg; do
case "$arg" in case "$arg" in
h) usage; return "$EXIT_SUCCESS";; h) usage; return "$EXIT_SUCCESS" ;;
k) Kernel="$OPTARG";; k) Kernel="$OPTARG" ;;
r) RedirectSerial=1;; r) RedirectSerial=1 ;;
*) error "invalid argument: %s\n" "$arg"; 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" ; shift ;
local imagefile="$1" local cli_args=$@
shift [ ! -n "$imagefile" ] && error "no image file specified" && 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"
[ ! -e "$imagefile" ] && error "image file not found: '%s'" "$imagefile" && exit "$EXIT_FAILURE" [ ! -w "$imagefile" ] && error "image file not writable: %s" "$imagefile" && exit "$EXIT_FAILURE"
msg "initializing ...." msg "initializing ...."
local workdir loopdev local bootdir workdir loopdev
pvm_mount || exit
local arch local arch
pvm_probe_arch || exit local qemu_cmd
if [ -z "$arch" ]; then
error "image arch is unknown: '%s'" "$arch"
exit "$EXIT_FAILURE"
fi
local qemu_args=() local qemu_args=()
pvm_guess_qemu_args || exit local was_error
qemu_args+=("$@") pvm_mount || exit "$EXIT_FAILURE" # assumes: $imagefile , sets: $loopdev $bootdir $workdir
pvm_probe_arch || exit "$EXIT_FAILURE" # assumes: $bootdir $workdir $imagefile , sets: $arch
pvm_guess_qemu_cmd || exit "$EXIT_FAILURE" # assumes: $arch , sets: $qemu_cmd
pvm_guess_qemu_args || exit "$EXIT_FAILURE" # assumes: $qemu_args $imagefile $arch $bootdir , appends: $qemu_args
msg "booting VM ...." # unmount the virtual disks early, for images with a bootloader
(set -x; qemu-system-"$arch" "${qemu_args[@]}") [[ "$arch" =~ ^i686$|^x86_64$|^ppc64le$ ]] && pvm_cleanup
msg "booting the virtual machine ...."
(set -x; $qemu_cmd "${qemu_args[@]}" $cli_args) ; was_error=$? ;
# clean up the terminal, in case SeaBIOS did something weird # clean up the terminal, in case SeaBIOS did something weird
echo -n "[?7h" echo -n "[?7h"
pvm_umount pvm_cleanup
(( ! $was_error )) && exit "$EXIT_SUCCESS" || exit "$EXIT_FAILURE"
} }
main "$@"
if source /usr/lib/parabola-vmbootstrap/pvm-common.sh.inc 2> /dev/null || \
source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"/pvm-common.sh.inc 2> /dev/null
then main "$@"
else echo "can not find pvm-common.sh.inc" && exit 1
fi

@ -20,10 +20,6 @@
############################################################################### ###############################################################################
# shellcheck source=/usr/lib/libretools/messages.sh
source "$(librelib messages)"
# defaults # defaults
readonly PKG_SET_MIN='minimal' readonly PKG_SET_MIN='minimal'
readonly PKG_SET_STD='standard' readonly PKG_SET_STD='standard'
@ -34,16 +30,22 @@ readonly DEV_PKGS=('base' 'parabola-base' 'base-devel') ; readonly ROOT_MB_DEV=1
readonly DEF_PKGS=(${STD_PKGS[@]} ) ; readonly DEF_MIN_MB=$ROOT_MB_STD ; readonly DEF_PKGS=(${STD_PKGS[@]} ) ; readonly DEF_MIN_MB=$ROOT_MB_STD ;
readonly DEF_KERNEL='linux-libre' # ASSERT: must be 'linux-libre', per 'parabola-base' readonly DEF_KERNEL='linux-libre' # ASSERT: must be 'linux-libre', per 'parabola-base'
readonly DEF_MIRROR=https://repo.parabola.nu readonly DEF_MIRROR=https://repo.parabola.nu
readonly DEF_ROOT_MB=64000 readonly DEF_ROOT_MB=32000
readonly DEF_BOOT_MB=100 readonly DEF_BOOT_MB=100
readonly DEF_SWAP_MB=0 readonly DEF_SWAP_MB=0
readonly MANDATORY_PKGS_ALL=( )
readonly MANDATORY_PKGS_armv7h=( haveged net-tools )
readonly MANDATORY_PKGS_i686=( haveged net-tools grub )
readonly MANDATORY_PKGS_ppc64le=( haveged net-tools )
readonly MANDATORY_PKGS_riscv64=( )
readonly MANDATORY_PKGS_x86_64=( haveged net-tools grub )
# misc # misc
readonly THIS_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
readonly GUEST_CACHED_PKGS=('ca-certificates-utils') readonly GUEST_CACHED_PKGS=('ca-certificates-utils')
readonly PVM_HOOKS_SUCCESS_MSG="[hooks.sh] pre-init hooks successful"
# options # options
BasePkgSet=$PKG_SET_STD BasePkgSet=$DEF_PKG_SET
MinRootMb=$DEF_MIN_MB MinRootMb=$DEF_MIN_MB
Hooks=() Hooks=()
Kernels=() Kernels=()
@ -57,7 +59,8 @@ SwapSizeMb=$DEF_SWAP_MB
HasSwap=0 HasSwap=0
usage() { usage()
{
print "USAGE:" print "USAGE:"
print " pvmbootstrap [-b <base-set>] [-h] [-H <hook>] [-k <kernel>] [-M <mirror>]" print " pvmbootstrap [-b <base-set>] [-h] [-H <hook>] [-k <kernel>] [-M <mirror>]"
print " [-O] [-p <package>] [-s <root_size>] [-S <swap_size>]" print " [-O] [-p <package>] [-s <root_size>] [-S <swap_size>]"
@ -110,27 +113,26 @@ usage() {
echo " <https://git.parabola.nu/parabola-vmbootstrap.git>" echo " <https://git.parabola.nu/parabola-vmbootstrap.git>"
} }
pvm_native_arch() { pvm_bootstrap() # assumes: $arch $imagefile $loopdev $workdir , traps: INT TERM RETURN
local arch=$( [[ "$1" =~ arm.* ]] && echo 'armv7l' || echo "$1" ) {
# prompt to clobber if the target output file already exists
pvm_check_no_mounts || return "$EXIT_FAILURE"
mkdir -p "$(dirname "$imagefile")" || return "$EXIT_FAILURE"
pvm_prompt_clobber_file "$imagefile" || return "$EXIT_FAILURE"
setarch "$arch" /bin/true 2>/dev/null || return "$EXIT_FAILURE" msg "starting build for %s image: %s" "$arch" "$imagefile"
}
pvm_bootstrap() {
msg "starting creation of %s image: %s" "$arch" "$imagefile"
# create the raw image file # create the raw image file
local img_mb=$(( $BootSizeMb + $SwapSizeMb + $RootSizeMb )) local img_mb=$(( $BootSizeMb + $SwapSizeMb + $RootSizeMb ))
qemu-img create -f raw "$imagefile" "${img_mb}M" || return "$EXIT_FAILURE" qemu-img create -f raw "$imagefile" "${img_mb}M" || return "$EXIT_FAILURE"
# prepare for cleanup # prepare for cleanup
trap 'pvm_cleanup' INT TERM RETURN trap 'pvm_bootstrap_cleanup' INT TERM RETURN
# mount the virtual disk # mount the virtual disk
local workdir loopdev local bootdir workdir loopdev
workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE" pvm_setup_loopdev || return "$EXIT_FAILURE" # sets: $bootdir $workdir $loopdev
loopdev="$(sudo losetup -fLP --show "$imagefile")" || return "$EXIT_FAILURE" sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return "$EXIT_FAILURE"
sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return "$EXIT_FAILURE"
# partition # partition
local boot_begin="$( [[ "$arch" =~ i686|x86_64 ]] && echo 2 || echo 1 )MiB" local boot_begin="$( [[ "$arch" =~ i686|x86_64 ]] && echo 2 || echo 1 )MiB"
@ -202,20 +204,19 @@ pvm_bootstrap() {
# setup qemu-user-static, if necessary # setup qemu-user-static, if necessary
if ! pvm_native_arch "$arch"; then if ! pvm_native_arch "$arch"; then
# target arch can't execute natively, pacstrap is going to need help by qemu
local qemu_arch local qemu_arch
case "$arch" in case "$arch" in
armv7h) qemu_arch=arm ;; armv7h) qemu_arch=arm ;;
* ) qemu_arch="$arch" ;; * ) qemu_arch="$arch" ;;
esac esac
local qemu_user_static=$(sudo grep -l -F -e "interpreter /usr/bin/qemu-$qemu_arch-" \ local qemu_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 [[ -n "$qemu_user_static" ]]; then if [[ -n "$qemu_static" ]]; then
msg "found qemu-user-static for %s" "$arch" msg "found qemu-user-static for arch: '%s'" "$qemu_arch"
else else
error "missing qemu-user-static for %s" "$arch" error "missing qemu-user-static for arch: '%s'" "$qemu_arch"
return "$EXIT_FAILURE" return "$EXIT_FAILURE"
fi fi
@ -224,9 +225,8 @@ pvm_bootstrap() {
fi fi
# prepare pacstrap config # prepare pacstrap config
local pacconf repos local pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return "$EXIT_FAILURE"
pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return "$EXIT_FAILURE" local repos=(libre core extra community pcr)
repos=(libre core extra community pcr)
(( $IsNonsystemd )) && repos=('nonsystemd' ${repos[@]}) (( $IsNonsystemd )) && repos=('nonsystemd' ${repos[@]})
echo -e "[options]\nArchitecture = $arch" > "$pacconf" echo -e "[options]\nArchitecture = $arch" > "$pacconf"
for repo in ${repos[@]}; do echo "[$repo]" >> "$pacconf"; for repo in ${repos[@]}; do echo "[$repo]" >> "$pacconf";
@ -234,15 +234,17 @@ pvm_bootstrap() {
done done
# prepare package lists # prepare package lists
local kernels=( ${Kernels[@]} ) local kernels=( ${Kernels[@]} )
local pkgs=( ${Pkgs[@]} ${Kernels[@]} ${OptPkgs[@]} ) local pkgs=( ${Pkgs[@]} ${Kernels[@]} ${OptPkgs[@]} ${MANDATORY_PKGS_ALL[@]} )
local pkgs_cached=( ${GUEST_CACHED_PKGS[@]} ) local pkgs_cached=( ${GUEST_CACHED_PKGS[@]} )
case "$arch" in case "$arch" in
i686|x86_64) pkgs+=(grub) ;; armv7h ) pkgs+=( ${MANDATORY_PKGS_armv7h[@]} ) ;;
riscv64 ) ;; i686 ) pkgs+=( ${MANDATORY_PKGS_i686[@]} ) ;;
* ) pkgs+=(haveged net-tools) ;; ppc64le) pkgs+=( ${MANDATORY_PKGS_ppc64le[@]} ) ;;
riscv64) pkgs+=( ${MANDATORY_PKGS_riscv64[@]} ) ;;
x86_64 ) pkgs+=( ${MANDATORY_PKGS_x86_64[@]} ) ;;
esac esac
(( $IsNonsystemd )) && && pkgs+=(libelogind) (( $IsNonsystemd )) && [[ "$BasePkgSet" == "$PKG_SET_MIN" ]] && pkgs+=(libelogind)
(( ! $IsNonsystemd )) && [[ "${Hooks[@]}" =~ hook-ethernet-dhcp.sh ]] && pkgs+=(dhcpcd) (( ! $IsNonsystemd )) && [[ "${Hooks[@]}" =~ hook-ethernet-dhcp.sh ]] && pkgs+=(dhcpcd)
# minimize package lists # minimize package lists
@ -254,6 +256,9 @@ pvm_bootstrap() {
msg "installing packages into the work chroot" msg "installing packages into the work chroot"
sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkgs[@]}" || return "$EXIT_FAILURE" sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkgs[@]}" || return "$EXIT_FAILURE"
sudo pacstrap -GM -C "$pacconf" "$workdir" "${pkgs_cached[@]}" || return "$EXIT_FAILURE" sudo pacstrap -GM -C "$pacconf" "$workdir" "${pkgs_cached[@]}" || return "$EXIT_FAILURE"
msg2 "creating a list of installed packages"
pacman -Sl -r "$workdir/" --config "$pacconf" | \
awk '/\[installed\]$/ {print $1 "/" $2 "-" $3}' > $(dirname $imagefile)/pkglist.txt
# create an fstab # create an fstab
msg "generating /etc/fstab" msg "generating /etc/fstab"
@ -270,13 +275,16 @@ pvm_bootstrap() {
local hostname='parabola' local hostname='parabola'
local lang='en_US.UTF-8' local lang='en_US.UTF-8'
msg "configuring system envoronment" msg "configuring system envoronment"
echo "/etc/hostname: " ; echo $hostname | sudo tee "$workdir"/etc/hostname ; echo -n "/etc/hostname: " ; echo $hostname | sudo tee "$workdir"/etc/hostname ;
echo "/etc/locale.conf: " ; echo "LANG=$lang" | sudo tee "$workdir"/etc/locale.conf ; echo -n "/etc/locale.conf: " ; echo "LANG=$lang" | sudo tee "$workdir"/etc/locale.conf ;
sudo sed -i "s/#${lang}/${lang}/" "$workdir"/etc/locale.gen sudo sed -i "s/#${lang}/${lang}/" "$workdir"/etc/locale.gen
# install a boot loader # install a boot loader
msg "installing boot loader" msg "installing boot loader"
case "$arch" in case "$arch" in
armv7h)
msg2 "(armv7h has no boot loader)"
;;
i686|x86_64) i686|x86_64)
local grub_def_file="$workdir"/etc/default/grub local grub_def_file="$workdir"/etc/default/grub
local grub_cfg_file=/boot/grub/grub.cfg local grub_cfg_file=/boot/grub/grub.cfg
@ -292,26 +300,26 @@ pvm_bootstrap() {
sudo arch-chroot "$workdir" grub-install "$loopdev" || return "$EXIT_FAILURE" sudo arch-chroot "$workdir" grub-install "$loopdev" || return "$EXIT_FAILURE"
sudo arch-chroot "$workdir" grub-mkconfig -o $grub_cfg_file || return "$EXIT_FAILURE" sudo arch-chroot "$workdir" grub-mkconfig -o $grub_cfg_file || return "$EXIT_FAILURE"
;; ;;
armv7h) ppc64le)
echo "(armv7h has no boot loader)" msg2 "(ppc64le has no boot loader)"
;; ;;
riscv64) riscv64)
# FIXME: for the time being, use fedora bbl to boot # FIXME: for the time being, use berkeley bootloader to boot
warning "(riscv64 requires a blob - downloading it now)" if [[ -f /usr/lib/parabola-vmbootstrap/bbl ]]; then
local bbl_url=https://fedorapeople.org/groups/risc-v/disk-images/bbl cp /usr/lib/parabola-vmbootstrap/bbl "$workdir"/boot/
sudo wget $bbl_url -O "$workdir"/boot/bbl || return "$EXIT_FAILURE" else
;; error "riscv64 requires the berkeley bootloader from the 'parabola-vmbootstrap' package"
ppc64le) return "$EXIT_FAILURE"
# FIXME: what about ppc64le? fi
echo "(ppc64le has no boot loader)"
;; ;;
esac esac
# regenerate the initcpio(s), skipping the autodetect hook # regenerate the initcpio(s), to skip the 'autodetect' hook
for kernel in ${Kernels[@]} for kernel in ${Kernels[@]}
do do
local preset_file="$workdir"/etc/mkinitcpio.d/${kernel}.preset local preset_file="$workdir"/etc/mkinitcpio.d/${kernel}.preset
local default_options="default_options=\"-S autodetect\"" local default_options="default_options=\"-S autodetect\""
msg "regenerating initcpio for kernel: '${kernel}'" msg "regenerating initcpio for kernel: '${kernel}'"
sudo cp "$preset_file"{,.backup} || return "$EXIT_FAILURE" sudo cp "$preset_file"{,.backup} || return "$EXIT_FAILURE"
echo "$default_options" | sudo tee -a "$preset_file" > /dev/null || return "$EXIT_FAILURE" echo "$default_options" | sudo tee -a "$preset_file" > /dev/null || return "$EXIT_FAILURE"
@ -326,19 +334,16 @@ pvm_bootstrap() {
# push hooks into the image # push hooks into the image
msg "preparing hooks" 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/
(( $IsNonsystemd )) && sudo rm "$workdir"/root/hooks/hook-ethernet-dhcp.sh # systemd-only hook (( $IsNonsystemd )) && sudo rm "$workdir"/root/hooks/hook-ethernet-dhcp.sh # systemd-only hook
# create a master hook script # create a master hook script
local hooks_success_msg="[hooks.sh] pre-init hooks successful" msg2 "hooks.sh:"
echo "hooks.sh:"
sudo tee "$workdir"/root/hooks.sh << EOF sudo tee "$workdir"/root/hooks.sh << EOF
#!/bin/bash #!/bin/bash
echo "[hooks.sh] boot successful - configuring ...." echo "[hooks.sh] boot successful - configuring ...."
systemctl disable preinit.service
# generate the locale # generate the locale
locale-gen locale-gen
@ -355,19 +360,20 @@ for hook in /root/hooks/*; do
done done
# clean up after yourself # clean up after yourself
systemctl disable preinit.service
rm -f /root/.bash_history
rm -rf /root/hooks rm -rf /root/hooks
rm -f /root/hooks.sh rm -f /root/hooks.sh
rm -f /usr/lib/systemd/system/preinit.service rm -f /usr/lib/systemd/system/preinit.service
rm -f /var/cache/pacman/pkg/* rm -f /var/cache/pacman/pkg/*
rm -f /root/.bash_history
# report success :) # report success :)
echo "$hooks_success_msg - powering off" echo "$PVM_HOOKS_SUCCESS_MSG - powering off"
[[ -e "/usr/lib/libretools/common.sh" ]] && rm -f /usr/lib/libretools/common.sh [[ -e "/usr/lib/libretools/common.sh" ]] && rm -f /usr/lib/libretools/common.sh
EOF EOF
# create a pre-init service to run the hooks # create a pre-init service to run the hooks
echo "preinit.service:" msg2 "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
@ -394,23 +400,18 @@ EOF
sudo arch-chroot "$workdir" systemctl enable preinit.service || return "$EXIT_FAILURE" sudo arch-chroot "$workdir" systemctl enable preinit.service || return "$EXIT_FAILURE"
# unmount everything # unmount everything
pvm_cleanup pvm_bootstrap_cleanup
}
pvm_bootstrap_preinit() # assumes: $imagefile
{
pvm_check_no_mounts || return "$EXIT_FAILURE"
# boot the machine to run the pre-init hooks # boot the machine to run the pre-init hooks
local pvmboot_cmd [[ "$(pvm_get_pvmboot_cmd)" ]] && msg "booting the VM to run the pre-init hooks" || \
local qemu_flags=(-no-reboot) warning "unable to run pre-init hooks"
if [ -f "$THIS_DIR/pvmboot.sh" ]; then # in-tree
pvmboot_cmd=("$THIS_DIR/pvmboot.sh")
elif type -p pvmboot &>/dev/null; then # installed
pvmboot_cmd=('pvmboot')
else
error "pvmboot not available -- unable to run hooks"
return "$EXIT_FAILURE"
fi
pvmboot_cmd+=("$imagefile" "${qemu_flags[@]}")
exec 3>&1 exec 3>&1
msg "booting the machine to run the pre-init hooks" pvm_boot "$imagefile" | tee /dev/fd/3 | grep -q -F "$PVM_HOOKS_SUCCESS_MSG"
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" "$imagefile" ! (( $res )) || error "%s: failed to complete preinit hooks" "$imagefile"
@ -418,28 +419,22 @@ EOF
return $res return $res
} }
pvm_cleanup() { pvm_bootstrap_cleanup() # sets: $pacconf , untraps: INT TERM RETURN
{
trap - INT TERM RETURN trap - INT TERM RETURN
[ -n "${workdir}${loopdev}${pacconf}" ] && msg "cleaning up" [[ "${workdir}${pacconf}" ]] && msg "cleaning up"
[[ -n "$workdir" ]] && sudo rm -f "$workdir"/usr/bin/qemu-*C
[[ -n "$pacconf" ]] && rm -f "$pacconf"
pvm_cleanup || return "$EXIT_FAILURE"
if [ -n "$workdir" ]; then
sudo rm -f "$workdir"/usr/bin/qemu-*C
sudo umount -R "$workdir" 2> /dev/null
rmdir "$workdir"
fi
if [ -n "$loopdev" ]; then sudo losetup -d "$loopdev"; fi;
if [ -n "$pacconf" ]; then rm -f "$pacconf"; fi;
unset workdir
unset loopdev
unset pacconf unset pacconf
} }
main() { main() # ( [cli_options] imagefile arch )
if [ "$(id -u)" -eq 0 ]; then {
error "This program must be run as a regular user" pvm_check_unprivileged # exits on failure
exit "$EXIT_NOPERMISSION"
fi
# parse options # parse options
while getopts 'b:hH:k:M:Op:s:S:' arg; do while getopts 'b:hH:k:M:Op:s:S:' arg; do
@ -450,28 +445,19 @@ main() {
Pkgs=(${STD_PKGS[@]}) ; MinRootMb=$ROOT_MB_STD ;; Pkgs=(${STD_PKGS[@]}) ; MinRootMb=$ROOT_MB_STD ;;
$PKG_SET_DEV) BasePkgSet=$OPTARG ; Kernels+=($DEF_KERNEL) ; $PKG_SET_DEV) BasePkgSet=$OPTARG ; Kernels+=($DEF_KERNEL) ;
Pkgs=(${DEV_PKGS[@]}) ; MinRootMb=$ROOT_MB_DEV ;; Pkgs=(${DEV_PKGS[@]}) ; MinRootMb=$ROOT_MB_DEV ;;
* ) warning "%s: invalid base set" "$OPTARG" ;; * ) warning "invalid base set: %s" "$OPTARG" ;;
esac ;; esac ;;
h) usage; return "$EXIT_SUCCESS";; h) usage; return "$EXIT_SUCCESS" ;;
H) if [ -e "$THIS_DIR/hooks/hook-$OPTARG.sh" ]; then # in-tree H) Hooks+=( "$(pvm_get_hook $OPTARG)" ) ;;
Hooks+=("$THIS_DIR/hooks/hook-$OPTARG.sh") k) Kernels+=($OPTARG) ;;
elif [ -e "/usr/lib/libretools/pvmbootstrap/hook-$OPTARG.sh" ]; then # installed M) Mirror="$OPTARG" ;;
Hooks+=("/usr/lib/libretools/pvmbootstrap/hook-$OPTARG.sh") O) IsNonsystemd=0 ;; # TODO:
elif [ -e "$OPTARG" ]; then p) OptPkgs+=($OPTARG) ;;
Hooks+=("$OPTARG") s) RootSizeMb="$(sed 's|[^0-9]||g' <<<$OPTARG)" ;;
else S) SwapSizeMb="$(sed 's|[^0-9]||g' <<<$OPTARG)" ;;
warning "%s: hook does not exist" "$OPTARG" *) error "invalid option: '%s'" "$arg" ; usage >&2 ; exit "$EXIT_INVALIDARGUMENT" ;;
fi ;;
k) Kernels+=($OPTARG);;
M) Mirror="$OPTARG";;
O) IsNonsystemd=0;; # TODO:
p) OptPkgs+=($OPTARG);;
s) RootSizeMb="$(sed 's|[^0-9]||g' <<<$OPTARG)";;
S) SwapSizeMb="$(sed 's|[^0-9]||g' <<<$OPTARG)";;
*) 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"
@ -484,32 +470,34 @@ main() {
RootSizeMb=$(( $RootSizeMb + (${#Kernels[@]} * 75) )) RootSizeMb=$(( $RootSizeMb + (${#Kernels[@]} * 75) ))
HasSwap=$( (( $SwapSizeMb > 0 )) && echo 1 || echo 0 ) HasSwap=$( (( $SwapSizeMb > 0 )) && echo 1 || echo 0 )
msg "making $arch image: $imagefile"
# 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 ) warning "arch %s is experimental" "$arch" ;; ppc64le|riscv64 ) warning "arch is experimental: %s" "$arch" ;;
* ) error "arch %s is unsupported" "$arch" * ) error "arch is unsupported: %s" "$arch"
exit "$EXIT_INVALIDARGUMENT" ;; exit "$EXIT_INVALIDARGUMENT" ;;
esac esac
# determine whether the target output file already exists # create the virtual machine
if [ -e "$imagefile" ]; then if pvm_bootstrap; then
warning "%s: file exists. Continue? [y/N]" "$imagefile" if pvm_bootstrap_preinit; then
read -p " " -n 1 -r msg "bootstrap complete for image: %s" "$imagefile"
echo exit "$EXIT_SUCCESS"
if [[ ! $REPLY =~ ^[Yy]$ ]]; then else
error "bootstrap complete, but preinit failed for image: %s" "$imagefile"
exit "$EXIT_FAILURE" exit "$EXIT_FAILURE"
fi fi
rm -f "$imagefile" || exit else
fi
# create the virtual machine
if ! pvm_bootstrap; then
error "bootstrap failed for image: %s" "$imagefile" error "bootstrap failed for image: %s" "$imagefile"
exit "$EXIT_FAILURE" exit "$EXIT_FAILURE"
fi fi
msg "bootstrap complete for image: %s" "$imagefile"
} }
main "$@"
if source /usr/lib/parabola-vmbootstrap/pvm-common.sh.inc 2> /dev/null || \
source "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"/pvm-common.sh.inc 2> /dev/null
then main "$@"
else echo "can not find pvm-common.sh.inc" && exit 1
fi