improve fs detection - factor out common functions
This commit is contained in:
parent
8f102e6742
commit
4872be99ac
53
README
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
|
||||
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
|
||||
the path to a custom script, or one of the predefined hooks
|
||||
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
|
||||
-p <package> -- Specify additional packages to be installed in the VM image.
|
||||
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)
|
||||
|
||||
The creation hooks currently supported are:
|
||||
Pre-defined package-sets:
|
||||
minimal: base
|
||||
standard: base parabola-base
|
||||
devel: base parabola-base base-devel
|
||||
|
||||
'ethernet-dhcp':
|
||||
|
||||
This hook will setup ethernet in the VM by enabling systemd-resolved and
|
||||
openresolv properly, as well as creating and enabling a systemd-networkd
|
||||
configuration.
|
||||
Pre-defined hooks:
|
||||
ethernet-dhcp: This hook will setup ethernet in the VM by enabling
|
||||
systemd-resolved and openresolv properly, as well as creating
|
||||
and enabling a systemd-networkd configuration. (systemd only)
|
||||
|
||||
|
||||
--------------------
|
||||
@ -59,10 +66,23 @@ To boot a created virtual machine, run:
|
||||
|
||||
$> pvmboot [options] <path_to_image> [qemu-args ...]
|
||||
|
||||
The script will attempt to determine the architecture of the provided virtual
|
||||
machine image, and set the qemu executable and sane default flags for the qemu
|
||||
invocation automatically, including kvm acceleration, if available for the
|
||||
target architecture.
|
||||
The script will attempt to determine the architecture and partition layout
|
||||
of the provided virtual machine image, and set the qemu executable and sane
|
||||
default flags for the qemu invocation automatically; and will enable KVM
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
When the VM will boot into graphical mode, the serial console can be redirected
|
||||
If qemu boots into graphical mode, the serial console can be redirected
|
||||
to the host console by passing the -r option.
|
||||
|
||||
$> pvmboot -r ./an.img
|
||||
@ -104,5 +119,5 @@ pvmbootstrap always creates a /boot partition.
|
||||
for example, to generate the parabola armv7h release tarball:
|
||||
|
||||
$> img_filename=parabola-systemd-cli-armv7h-tarball-$(date +%Y.%m).img
|
||||
$> pvmbootstrap -s 1 -H ethernet-dhcp $img_filename armv7h
|
||||
$> pvm2tarball $img_filename
|
||||
$> pvmbootstrap -b minimal -H ethernet-dhcp -s 0 -S0 $img_file armv7h
|
||||
$> pvm2tarball $img_file
|
||||
|
264
src/pvm-common.sh.inc
Normal 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/>. #
|
||||
###############################################################################
|
||||
|
||||
# shellcheck source=/usr/lib/libretools/messages.sh
|
||||
source "$(librelib messages)"
|
||||
|
||||
usage() {
|
||||
usage()
|
||||
{
|
||||
print "USAGE:"
|
||||
print " pvm2tarball [-h] [-o <FILENAME>] <IMG>"
|
||||
echo
|
||||
@ -41,78 +40,16 @@ usage() {
|
||||
echo " <https://git.parabola.nu/parabola-vmbootstrap.git>"
|
||||
}
|
||||
|
||||
pvm_mount() {
|
||||
if file "$imagefile" | grep -q ' DOS/MBR '; then
|
||||
msg "mounting filesystems"
|
||||
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
|
||||
main()
|
||||
{
|
||||
pvm_check_unprivileged # exits on failure
|
||||
|
||||
# parse options
|
||||
local output
|
||||
local outfile
|
||||
while getopts 'ho:' arg; do
|
||||
case "$arg" in
|
||||
h) usage; return "$EXIT_SUCCESS";;
|
||||
o) output="$OPTARG";;
|
||||
o) outfile="$OPTARG";;
|
||||
*) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
|
||||
esac
|
||||
done
|
||||
@ -124,29 +61,16 @@ main() {
|
||||
image_filename="$(basename "$imagefile")"
|
||||
shift
|
||||
|
||||
# check for input file presence
|
||||
if [ ! -e "$imagefile" ]; then
|
||||
error "%s: file not found" "$imagefile"
|
||||
exit "$EXIT_FAILURE"
|
||||
fi
|
||||
|
||||
# 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
|
||||
if [ -e "$output" ]; then
|
||||
warning "%s: file exists. Continue? [y/N]" "$output"
|
||||
read -p " " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit "$EXIT_FAILURE"
|
||||
fi
|
||||
rm -f "$output" || exit
|
||||
fi
|
||||
# ensure that the image file exists, prompt to clobber existing output file
|
||||
pvm_check_file_exists_and_writable "$imagefile" || exit "$EXIT_FAILURE"
|
||||
pvm_prompt_clobber_file "$outfile" || exit "$EXIT_FAILURE"
|
||||
|
||||
# mount the root filesystem
|
||||
local workdir loopdev
|
||||
pvm_mount || exit
|
||||
local bootdir workdir loopdev
|
||||
pvm_mount || exit "$EXIT_FAILURE" # assumes: $imagefile , sets: $loopdev $bootdir $workdir
|
||||
|
||||
# tar the root filesystem, excluding unneeded things
|
||||
# HACKING:
|
||||
@ -156,7 +80,7 @@ main() {
|
||||
#
|
||||
# `tar -tf <tarball> | sort`
|
||||
msg "imploding tarball"
|
||||
sudo tar -c -f "$output" -C "$workdir" -X - . << EOF
|
||||
sudo tar -c -f "$outfile" -C "$workdir" -X - . << EOF
|
||||
./boot/lost+found
|
||||
./etc/.updated
|
||||
./etc/pacman.d/gnupg
|
||||
@ -169,10 +93,15 @@ main() {
|
||||
EOF
|
||||
|
||||
# give the archive back to the user
|
||||
sudo chown "$(id -u)":"$(id -g)" "$output"
|
||||
sudo chown "$(id -u)":"$(id -g)" "$outfile"
|
||||
|
||||
# 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
|
||||
|
204
src/pvmboot.sh
204
src/pvmboot.sh
@ -19,8 +19,6 @@
|
||||
# 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_RAM_MB=1000
|
||||
@ -29,14 +27,16 @@ Kernel=$DEF_KERNEL
|
||||
RedirectSerial=0
|
||||
|
||||
|
||||
usage() {
|
||||
usage()
|
||||
{
|
||||
print "USAGE:"
|
||||
print " pvmboot [-h] [-k <kernel>] [-r] <img> [qemu-args ...]"
|
||||
echo
|
||||
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
|
||||
created using pvmbootstrap. The started instances are assigned
|
||||
${DEF_RAM_MB}MB of RAM and one SMP core."
|
||||
created using pvmbootstrap. If the image was not created using pvmbootstrap,
|
||||
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
|
||||
prose "When a graphical desktop environment is available, start the machine
|
||||
normally, otherwise append -nographic to the qemu options. This behavior
|
||||
@ -63,176 +63,120 @@ usage() {
|
||||
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"
|
||||
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}')"
|
||||
pvm_guess_qemu_cmd() # assumes: $arch , sets: $qemu_cmd
|
||||
{
|
||||
case "$arch" in
|
||||
PowerPC64) arch=ppc64 ; return "$EXIT_SUCCESS" ;;
|
||||
RISC-V ) arch=riscv64 ; return "$EXIT_SUCCESS" ;;
|
||||
* ) arch="" ;;
|
||||
armv7h ) qemu_cmd="qemu-system-arm" ;;
|
||||
i686 ) qemu_cmd="qemu-system-i386" ;;
|
||||
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
|
||||
|
||||
# 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() {
|
||||
local arch
|
||||
pvm_guess_qemu_args() # assumes: $qemu_args $imagefile $arch $bootdir , appends: $qemu_args
|
||||
{
|
||||
msg "configuring the virtual machine ($arch)"
|
||||
|
||||
case "$1" in
|
||||
arm*) arch=armv7l ;;
|
||||
* ) arch="$1" ;;
|
||||
esac
|
||||
qemu_args+=(-m $DEF_RAM_MB )
|
||||
|
||||
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 [ -z "$DISPLAY" ]; then qemu_args+=(-nographic);
|
||||
elif (( ${RedirectSerial} )); then qemu_args+=(-serial "mon:stdio");
|
||||
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 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
|
||||
local kernel_console
|
||||
local kernel_tty
|
||||
case "$arch" in
|
||||
i386|x86_64|ppc64)
|
||||
qemu_args+=(-m $DEF_RAM_MB -hda "$imagefile")
|
||||
# unmount the unneeded virtual drive early
|
||||
pvm_umount ;;
|
||||
arm)
|
||||
kernel_console="console=tty0 console=ttyAMA0 "
|
||||
qemu_args+=(-machine virt
|
||||
-m $DEF_RAM_MB
|
||||
-kernel "$workdir"/vmlinuz-${Kernel}
|
||||
-initrd "$workdir"/initramfs-${Kernel}.img
|
||||
-append "${kernel_console}rw root=${root_vdev}"
|
||||
armv7h ) kernel_tty="console=tty0 console=ttyAMA0 " ;;
|
||||
i686 ) kernel_tty=$( [[ -z "$DISPLAY" ]] && echo "console=ttyS0 " ) ;;
|
||||
ppc64le) ;; # TODO:
|
||||
riscv64) ;; # TODO:
|
||||
x86_64 ) kernel_tty=$( [[ -z "$DISPLAY" ]] && echo "console=ttyS0 " ) ;;
|
||||
esac
|
||||
case "$arch" in
|
||||
armv7h ) qemu_args+=(-machine virt
|
||||
-kernel "$bootdir"/vmlinuz-${Kernel}
|
||||
-initrd "$bootdir"/initramfs-${Kernel}.img
|
||||
-append "${kernel_tty}rw root=/dev/vda$root_part_n"
|
||||
-drive "if=none,file=${imagefile},format=raw,id=hd"
|
||||
-device "virtio-blk-device,drive=hd"
|
||||
-netdev "user,id=mynet"
|
||||
-device "virtio-net-device,netdev=mynet") ;;
|
||||
riscv64)
|
||||
kernel_console=$( [ -z "$DISPLAY" ] && echo "console=ttyS0 " )
|
||||
qemu_args+=(-machine virt
|
||||
-m $DEF_RAM_MB
|
||||
-kernel "$workdir"/bbl
|
||||
-append "${kernel_console}rw root=/dev/vda"
|
||||
-drive "file=${root_vdev},format=raw,id=hd0"
|
||||
i686 ) qemu_args+=(-hda "$imagefile") ;;
|
||||
ppc64le) qemu_args+=(-hda " $imagefile") ;;
|
||||
riscv64) qemu_args+=(-machine virt
|
||||
-kernel "$bootdir"/bbl
|
||||
-append "${kernel_tty}rw root=/dev/vda"
|
||||
-drive "file=/dev/vda$root_part_n,format=raw,id=hd0"
|
||||
-device "virtio-blk-device,drive=hd0"
|
||||
-object "rng-random,filename=/dev/urandom,id=rng0"
|
||||
-device "virtio-rng-device,rng=rng0"
|
||||
-netdev "user,id=usernet"
|
||||
-device "virtio-net-device,netdev=usernet") ;;
|
||||
*)
|
||||
error "%s: unable to determine default qemu args" "$imagefile"
|
||||
return "$EXIT_FAILURE" ;;
|
||||
x86_64 ) qemu_args+=(-hda "$imagefile") ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main() {
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
error "This program must be run as a regular user"
|
||||
exit "$EXIT_NOPERMISSION"
|
||||
fi
|
||||
main() # ( [cli_options] imagefile qemu_args )
|
||||
{
|
||||
pvm_check_unprivileged # exits on failure
|
||||
|
||||
# parse options
|
||||
while getopts 'hk:r' arg; do
|
||||
case "$arg" in
|
||||
h) usage; return "$EXIT_SUCCESS";;
|
||||
k) Kernel="$OPTARG";;
|
||||
r) RedirectSerial=1;;
|
||||
*) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT";;
|
||||
h) usage; return "$EXIT_SUCCESS" ;;
|
||||
k) Kernel="$OPTARG" ;;
|
||||
r) RedirectSerial=1 ;;
|
||||
*) error "invalid argument: %s\n" "$arg"; usage >&2; exit "$EXIT_INVALIDARGUMENT" ;;
|
||||
esac
|
||||
done
|
||||
local shiftlen=$(( OPTIND - 1 ))
|
||||
shift $shiftlen
|
||||
local imagefile="$1"
|
||||
shift
|
||||
local shiftlen=$(( OPTIND - 1 )) ; shift $shiftlen ;
|
||||
local imagefile="$1" ; shift ;
|
||||
local cli_args=$@
|
||||
[ ! -n "$imagefile" ] && error "no image file specified" && 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 ...."
|
||||
local workdir loopdev
|
||||
pvm_mount || exit
|
||||
|
||||
local bootdir workdir loopdev
|
||||
local arch
|
||||
pvm_probe_arch || exit
|
||||
if [ -z "$arch" ]; then
|
||||
error "image arch is unknown: '%s'" "$arch"
|
||||
exit "$EXIT_FAILURE"
|
||||
fi
|
||||
|
||||
local qemu_cmd
|
||||
local qemu_args=()
|
||||
pvm_guess_qemu_args || exit
|
||||
qemu_args+=("$@")
|
||||
local was_error
|
||||
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 ...."
|
||||
(set -x; qemu-system-"$arch" "${qemu_args[@]}")
|
||||
# unmount the virtual disks early, for images with a bootloader
|
||||
[[ "$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
|
||||
echo -n "[?7h[0m"
|
||||
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
|
||||
readonly PKG_SET_MIN='minimal'
|
||||
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_KERNEL='linux-libre' # ASSERT: must be 'linux-libre', per 'parabola-base'
|
||||
readonly DEF_MIRROR=https://repo.parabola.nu
|
||||
readonly DEF_ROOT_MB=64000
|
||||
readonly DEF_ROOT_MB=32000
|
||||
readonly DEF_BOOT_MB=100
|
||||
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
|
||||
readonly THIS_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
readonly GUEST_CACHED_PKGS=('ca-certificates-utils')
|
||||
readonly PVM_HOOKS_SUCCESS_MSG="[hooks.sh] pre-init hooks successful"
|
||||
|
||||
# options
|
||||
BasePkgSet=$PKG_SET_STD
|
||||
BasePkgSet=$DEF_PKG_SET
|
||||
MinRootMb=$DEF_MIN_MB
|
||||
Hooks=()
|
||||
Kernels=()
|
||||
@ -57,7 +59,8 @@ SwapSizeMb=$DEF_SWAP_MB
|
||||
HasSwap=0
|
||||
|
||||
|
||||
usage() {
|
||||
usage()
|
||||
{
|
||||
print "USAGE:"
|
||||
print " pvmbootstrap [-b <base-set>] [-h] [-H <hook>] [-k <kernel>] [-M <mirror>]"
|
||||
print " [-O] [-p <package>] [-s <root_size>] [-S <swap_size>]"
|
||||
@ -110,26 +113,25 @@ usage() {
|
||||
echo " <https://git.parabola.nu/parabola-vmbootstrap.git>"
|
||||
}
|
||||
|
||||
pvm_native_arch() {
|
||||
local arch=$( [[ "$1" =~ arm.* ]] && echo 'armv7l' || echo "$1" )
|
||||
pvm_bootstrap() # assumes: $arch $imagefile $loopdev $workdir , traps: INT TERM RETURN
|
||||
{
|
||||
# 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"
|
||||
}
|
||||
|
||||
pvm_bootstrap() {
|
||||
msg "starting creation of %s image: %s" "$arch" "$imagefile"
|
||||
msg "starting build for %s image: %s" "$arch" "$imagefile"
|
||||
|
||||
# create the raw image file
|
||||
local img_mb=$(( $BootSizeMb + $SwapSizeMb + $RootSizeMb ))
|
||||
qemu-img create -f raw "$imagefile" "${img_mb}M" || return "$EXIT_FAILURE"
|
||||
|
||||
# prepare for cleanup
|
||||
trap 'pvm_cleanup' INT TERM RETURN
|
||||
trap 'pvm_bootstrap_cleanup' INT TERM RETURN
|
||||
|
||||
# mount the virtual disk
|
||||
local workdir loopdev
|
||||
workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return "$EXIT_FAILURE"
|
||||
loopdev="$(sudo losetup -fLP --show "$imagefile")" || return "$EXIT_FAILURE"
|
||||
local bootdir workdir loopdev
|
||||
pvm_setup_loopdev || return "$EXIT_FAILURE" # sets: $bootdir $workdir $loopdev
|
||||
sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return "$EXIT_FAILURE"
|
||||
|
||||
# partition
|
||||
@ -202,20 +204,19 @@ pvm_bootstrap() {
|
||||
|
||||
# setup qemu-user-static, if necessary
|
||||
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
|
||||
|
||||
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 | \
|
||||
xargs -r sudo grep -xF 'enabled' )
|
||||
if [[ -n "$qemu_user_static" ]]; then
|
||||
msg "found qemu-user-static for %s" "$arch"
|
||||
if [[ -n "$qemu_static" ]]; then
|
||||
msg "found qemu-user-static for arch: '%s'" "$qemu_arch"
|
||||
else
|
||||
error "missing qemu-user-static for %s" "$arch"
|
||||
error "missing qemu-user-static for arch: '%s'" "$qemu_arch"
|
||||
return "$EXIT_FAILURE"
|
||||
fi
|
||||
|
||||
@ -224,9 +225,8 @@ pvm_bootstrap() {
|
||||
fi
|
||||
|
||||
# prepare pacstrap config
|
||||
local pacconf repos
|
||||
pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return "$EXIT_FAILURE"
|
||||
repos=(libre core extra community pcr)
|
||||
local pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return "$EXIT_FAILURE"
|
||||
local repos=(libre core extra community pcr)
|
||||
(( $IsNonsystemd )) && repos=('nonsystemd' ${repos[@]})
|
||||
echo -e "[options]\nArchitecture = $arch" > "$pacconf"
|
||||
for repo in ${repos[@]}; do echo "[$repo]" >> "$pacconf";
|
||||
@ -235,14 +235,16 @@ pvm_bootstrap() {
|
||||
|
||||
# prepare package lists
|
||||
local kernels=( ${Kernels[@]} )
|
||||
local pkgs=( ${Pkgs[@]} ${Kernels[@]} ${OptPkgs[@]} )
|
||||
local pkgs=( ${Pkgs[@]} ${Kernels[@]} ${OptPkgs[@]} ${MANDATORY_PKGS_ALL[@]} )
|
||||
local pkgs_cached=( ${GUEST_CACHED_PKGS[@]} )
|
||||
case "$arch" in
|
||||
i686|x86_64) pkgs+=(grub) ;;
|
||||
riscv64 ) ;;
|
||||
* ) pkgs+=(haveged net-tools) ;;
|
||||
armv7h ) pkgs+=( ${MANDATORY_PKGS_armv7h[@]} ) ;;
|
||||
i686 ) pkgs+=( ${MANDATORY_PKGS_i686[@]} ) ;;
|
||||
ppc64le) pkgs+=( ${MANDATORY_PKGS_ppc64le[@]} ) ;;
|
||||
riscv64) pkgs+=( ${MANDATORY_PKGS_riscv64[@]} ) ;;
|
||||
x86_64 ) pkgs+=( ${MANDATORY_PKGS_x86_64[@]} ) ;;
|
||||
esac
|
||||
(( $IsNonsystemd )) && && pkgs+=(libelogind)
|
||||
(( $IsNonsystemd )) && [[ "$BasePkgSet" == "$PKG_SET_MIN" ]] && pkgs+=(libelogind)
|
||||
(( ! $IsNonsystemd )) && [[ "${Hooks[@]}" =~ hook-ethernet-dhcp.sh ]] && pkgs+=(dhcpcd)
|
||||
|
||||
# minimize package lists
|
||||
@ -254,6 +256,9 @@ pvm_bootstrap() {
|
||||
msg "installing packages into the work chroot"
|
||||
sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkgs[@]}" || 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
|
||||
msg "generating /etc/fstab"
|
||||
@ -270,13 +275,16 @@ pvm_bootstrap() {
|
||||
local hostname='parabola'
|
||||
local lang='en_US.UTF-8'
|
||||
msg "configuring system envoronment"
|
||||
echo "/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/hostname: " ; echo $hostname | sudo tee "$workdir"/etc/hostname ;
|
||||
echo -n "/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
|
||||
msg "installing boot loader"
|
||||
case "$arch" in
|
||||
armv7h)
|
||||
msg2 "(armv7h has no boot loader)"
|
||||
;;
|
||||
i686|x86_64)
|
||||
local grub_def_file="$workdir"/etc/default/grub
|
||||
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-mkconfig -o $grub_cfg_file || return "$EXIT_FAILURE"
|
||||
;;
|
||||
armv7h)
|
||||
echo "(armv7h has no boot loader)"
|
||||
ppc64le)
|
||||
msg2 "(ppc64le has no boot loader)"
|
||||
;;
|
||||
riscv64)
|
||||
# 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
|
||||
sudo wget $bbl_url -O "$workdir"/boot/bbl || return "$EXIT_FAILURE"
|
||||
;;
|
||||
ppc64le)
|
||||
# FIXME: what about ppc64le?
|
||||
echo "(ppc64le has no boot loader)"
|
||||
# FIXME: for the time being, use berkeley bootloader to boot
|
||||
if [[ -f /usr/lib/parabola-vmbootstrap/bbl ]]; then
|
||||
cp /usr/lib/parabola-vmbootstrap/bbl "$workdir"/boot/
|
||||
else
|
||||
error "riscv64 requires the berkeley bootloader from the 'parabola-vmbootstrap' package"
|
||||
return "$EXIT_FAILURE"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# regenerate the initcpio(s), skipping the autodetect hook
|
||||
# regenerate the initcpio(s), to skip the 'autodetect' hook
|
||||
for kernel in ${Kernels[@]}
|
||||
do
|
||||
local preset_file="$workdir"/etc/mkinitcpio.d/${kernel}.preset
|
||||
local default_options="default_options=\"-S autodetect\""
|
||||
|
||||
msg "regenerating initcpio for kernel: '${kernel}'"
|
||||
sudo cp "$preset_file"{,.backup} || 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
|
||||
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/
|
||||
(( $IsNonsystemd )) && sudo rm "$workdir"/root/hooks/hook-ethernet-dhcp.sh # systemd-only hook
|
||||
|
||||
# create a master hook script
|
||||
local hooks_success_msg="[hooks.sh] pre-init hooks successful"
|
||||
echo "hooks.sh:"
|
||||
msg2 "hooks.sh:"
|
||||
sudo tee "$workdir"/root/hooks.sh << EOF
|
||||
#!/bin/bash
|
||||
echo "[hooks.sh] boot successful - configuring ...."
|
||||
|
||||
systemctl disable preinit.service
|
||||
|
||||
# generate the locale
|
||||
locale-gen
|
||||
|
||||
@ -355,19 +360,20 @@ for hook in /root/hooks/*; do
|
||||
done
|
||||
|
||||
# clean up after yourself
|
||||
systemctl disable preinit.service
|
||||
rm -f /root/.bash_history
|
||||
rm -rf /root/hooks
|
||||
rm -f /root/hooks.sh
|
||||
rm -f /usr/lib/systemd/system/preinit.service
|
||||
rm -f /var/cache/pacman/pkg/*
|
||||
rm -f /root/.bash_history
|
||||
|
||||
# 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
|
||||
EOF
|
||||
|
||||
# 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'
|
||||
[Unit]
|
||||
Description=Oneshot VM Preinit
|
||||
@ -394,23 +400,18 @@ EOF
|
||||
sudo arch-chroot "$workdir" systemctl enable preinit.service || return "$EXIT_FAILURE"
|
||||
|
||||
# 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
|
||||
local pvmboot_cmd
|
||||
local qemu_flags=(-no-reboot)
|
||||
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[@]}")
|
||||
[[ "$(pvm_get_pvmboot_cmd)" ]] && msg "booting the VM to run the pre-init hooks" || \
|
||||
warning "unable to run pre-init hooks"
|
||||
exec 3>&1
|
||||
msg "booting the machine to run the pre-init hooks"
|
||||
DISPLAY='' "${pvmboot_cmd[@]}" | tee /dev/fd/3 | grep -q -F "$hooks_success_msg"
|
||||
pvm_boot "$imagefile" | tee /dev/fd/3 | grep -q -F "$PVM_HOOKS_SUCCESS_MSG"
|
||||
local res=$?
|
||||
exec 3>&-
|
||||
! (( $res )) || error "%s: failed to complete preinit hooks" "$imagefile"
|
||||
@ -418,28 +419,22 @@ EOF
|
||||
return $res
|
||||
}
|
||||
|
||||
pvm_cleanup() {
|
||||
pvm_bootstrap_cleanup() # sets: $pacconf , untraps: 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
|
||||
}
|
||||
|
||||
main() {
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
error "This program must be run as a regular user"
|
||||
exit "$EXIT_NOPERMISSION"
|
||||
fi
|
||||
main() # ( [cli_options] imagefile arch )
|
||||
{
|
||||
pvm_check_unprivileged # exits on failure
|
||||
|
||||
# parse options
|
||||
while getopts 'b:hH:k:M:Op:s:S:' arg; do
|
||||
@ -450,28 +445,19 @@ main() {
|
||||
Pkgs=(${STD_PKGS[@]}) ; MinRootMb=$ROOT_MB_STD ;;
|
||||
$PKG_SET_DEV) BasePkgSet=$OPTARG ; Kernels+=($DEF_KERNEL) ;
|
||||
Pkgs=(${DEV_PKGS[@]}) ; MinRootMb=$ROOT_MB_DEV ;;
|
||||
* ) warning "%s: invalid base set" "$OPTARG" ;;
|
||||
* ) warning "invalid base set: %s" "$OPTARG" ;;
|
||||
esac ;;
|
||||
h) usage; return "$EXIT_SUCCESS";;
|
||||
H) if [ -e "$THIS_DIR/hooks/hook-$OPTARG.sh" ]; then # in-tree
|
||||
Hooks+=("$THIS_DIR/hooks/hook-$OPTARG.sh")
|
||||
elif [ -e "/usr/lib/libretools/pvmbootstrap/hook-$OPTARG.sh" ]; then # installed
|
||||
Hooks+=("/usr/lib/libretools/pvmbootstrap/hook-$OPTARG.sh")
|
||||
elif [ -e "$OPTARG" ]; then
|
||||
Hooks+=("$OPTARG")
|
||||
else
|
||||
warning "%s: hook does not exist" "$OPTARG"
|
||||
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";;
|
||||
h) usage; return "$EXIT_SUCCESS" ;;
|
||||
H) Hooks+=( "$(pvm_get_hook $OPTARG)" ) ;;
|
||||
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 option: '%s'" "$arg" ; usage >&2 ; exit "$EXIT_INVALIDARGUMENT" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
local shiftlen=$(( OPTIND - 1 ))
|
||||
shift $shiftlen
|
||||
local imagefile="$1"
|
||||
@ -484,32 +470,34 @@ main() {
|
||||
RootSizeMb=$(( $RootSizeMb + (${#Kernels[@]} * 75) ))
|
||||
HasSwap=$( (( $SwapSizeMb > 0 )) && echo 1 || echo 0 )
|
||||
|
||||
msg "making $arch image: $imagefile"
|
||||
|
||||
# determine if the target arch is supported
|
||||
case "$arch" in
|
||||
i686|x86_64|armv7h) ;;
|
||||
ppc64le|riscv64 ) warning "arch %s is experimental" "$arch" ;;
|
||||
* ) error "arch %s is unsupported" "$arch"
|
||||
ppc64le|riscv64 ) warning "arch is experimental: %s" "$arch" ;;
|
||||
* ) error "arch is unsupported: %s" "$arch"
|
||||
exit "$EXIT_INVALIDARGUMENT" ;;
|
||||
esac
|
||||
|
||||
# determine whether the target output file already exists
|
||||
if [ -e "$imagefile" ]; then
|
||||
warning "%s: file exists. Continue? [y/N]" "$imagefile"
|
||||
read -p " " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
# create the virtual machine
|
||||
if pvm_bootstrap; then
|
||||
if pvm_bootstrap_preinit; then
|
||||
msg "bootstrap complete for image: %s" "$imagefile"
|
||||
exit "$EXIT_SUCCESS"
|
||||
else
|
||||
error "bootstrap complete, but preinit failed for image: %s" "$imagefile"
|
||||
exit "$EXIT_FAILURE"
|
||||
fi
|
||||
rm -f "$imagefile" || exit
|
||||
fi
|
||||
|
||||
# create the virtual machine
|
||||
if ! pvm_bootstrap; then
|
||||
else
|
||||
error "bootstrap failed for image: %s" "$imagefile"
|
||||
exit "$EXIT_FAILURE"
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user