#!/bin/bash
#
# This script starts up a VM using an ALP KVM image
#
# Written by okir@suse.com
#

OKIR_IMG_URL=https://download.opensuse.org/repositories/home:/okir:/FDE/images/
ALP_IMG_URL=https://download.opensuse.org/repositories/SUSE:/ALP/images/
D_INSTALLER_IMG_URL=https://download.opensuse.org/repositories/YaST:/Head:/D-Installer/images/iso/

# As long as ALP uses its own buildcert to sign Secure Boot related code,
# KVM needs a special set of SB variables:
UEFI_ALP_VARS_FILE="/usr/share/qemu/ovmf-x86_64-smm-alp-vars.bin"
UEFI_OKIR_VARS_FILE="/usr/share/qemu/ovmf-x86_64-smm-okir-vars.bin"

DEFAULT_IMG_CACHE="/var/tmp/alp-demo"
DEFAULT_VM_NAME="alp-demo"

# This is not true, but allows this demo to run on 15.3 upwards
ALP_OS_VARIANT=opensuse15.3
ALP_OS_VARIANT=opensusetumbleweed

ALP_IMG_PATTERN=

function usage {

	exitval=$1

	cat <<EOF
run-alp [-N vm-name] [-C cache-dir] [img-path]"
  -N vm-name, --name vm-name
        name of the VM to create [default $DEFAULT_VM_NAME]
  -C path, --cachedir path
        specify a directory to hold ALP images [default $DEFAULT_IMG_CACHE]
  --no-fde
  	Run image without full disk encryption
  --qemu
  	Run the demo image using qemu directly, rather than virt-install
  -f, --force
        if a VM of the given name was created previously, remove it
        without asking for confirmation.
  -p, --persistent
        by default, the VM created by this script will be transient,
        i.e. it will be deleted when you exit virt-viewer. If you want
        to create a persisent VM instead, use the -p flag. You will
        be able to start, stop and delete the VM using virt-manager.

img-path can be used to specify the name of an image to run.
By default, the script will try to run the current ALP-VM image from the
SUSE Open Build Service.

EOF

	exit $exitval
}

opt_vm_name="$DEFAULT_VM_NAME"
opt_cache_dir=
opt_force=false
opt_persistent=false
opt_sb_auth=alp
opt_img_flavor=kvm_encrypted
opt_run=libvirt
opt_cdrom=false

eval set -- $(getopt -l no-fde,sb-auth:,cachedir:,name:,force,persistent,help,okir,qemu,dinstall A:C:N:fh "$@")
while [ $# -gt 0 ]; do
	opt=$1; shift
	case $opt in
	--no-fde)
		opt_img_flavor=kvm;;
	-A|--sb-auth)
		opt_sb_auth=$1; shift;;
	-C|--cachedir)
		opt_cache_dir=$1; shift;;
	-N|--name)
		opt_vm_name=$1; shift;;
	-f|--force)
		opt_force=true;;
	-p|--persistent)
		opt_persistent=true;;
	-h|--help)
		usage 0;;
	--okir)
		ALP_IMG_URL=$OKIR_IMG_URL
		DEFAULT_IMG_CACHE="$DEFAULT_IMG_CACHE/okir"
		opt_sb_auth=okir;;
	--dinstall)
		ALP_IMG_URL=$D_INSTALLER_IMG_URL
		opt_img_flavor=d-installer
		ALP_IMG_PATTERN="d-installer-live.x86_64-.*-ALP-Build.*.iso"
		opt_persistent=true
		opt_cdrom=true;;
	--qemu)
		opt_run=qemu;;
	--)	break;;
	*)	echo "Unsupported option $opt" >&2
		usage 1;;
	esac
done

if [ -z "$opt_cache_dir" ]; then
	opt_cache_dir="$DEFAULT_IMG_CACHE"
fi

case $(stat -f "$opt_cache_dir" -c %T) in
nfs|nfs4|cifs)
	cat >&2 <<-EOF

	Warning: your image cache is on a remote file system. Consider specifying an
	alternative location using the -C option.

EOF
	: ;;
esac

function identify_image {

	img_pattern="$ALP_IMG_PATTERN"
	if [ -z "$ALP_IMG_PATTERN" ]; then
		img_pattern="ALP-VM.x86_64.*-${opt_img_flavor}-.*\\.qcow2"
	fi

	 wget -nv "$ALP_IMG_URL" -O - |
	 	tr '<>' '\012\012' | sed '/^a href="\(.*\)"/!d;s//\1/' |
		grep "$img_pattern\$" |
		sort -u
}

function download_image {

	declare -g ALP_IMG_LOCAL

	echo "Downloading ALP $opt_img_flavor image from $ALP_IMG_URL"
	echo "to $opt_cache_dir"

	img_name=$(identify_image)
	case $(echo "$img_name" | wc -w) in
	0)	echo "Unable to identify image" >&2
		exit 1;;
	1)	: ;;
	*)	echo "Found more than one image at $ALP_IMG_URL" >&2
		exit 1;;
	esac

	echo "Found image $img_name"
	wget -nv -N -P "$opt_cache_dir" "$ALP_IMG_URL/$img_name" || exit 1

	ALP_IMG_LOCAL="$opt_cache_dir/$img_name"
}

function user_confirm {

	msg="$1"
	while true; do
		echo -n "$msg [y/N]: "
		read word
		case $word in
		[yY])
			return 0;;
		""|[nN])
			return 1;;
		esac
	done
}

function check_system {

	okay=0

	# Check whether libvirtd is running
	eval $(systemctl show libvirtd -p ActiveState,SubState 2>/dev/null)
	if [ "$ActiveState" != active -o "$SubState" != "running" ]; then
		echo "The libvirtd service does not seem to be running; please activate it:" >&2
		echo "  systemctl start libvirtd" >&2
		okay=1
	fi

	# Should we check anything else?

	return $okay
}

function cleanup_domain {

	local vm_name=$1

	virsh dominfo "$vm_name" >/dev/null 2>&1 || return 0

	echo
	echo "A domain named $vm_name already exists"
	virsh dominfo "$vm_name"
	virsh domblklist "$vm_name"
	echo

	if ! $opt_force; then
		user_confirm "Destroy this VM and its storage?" || exit 1
	fi

	if virsh dominfo "$vm_name" | grep -qs State.*running; then
		echo "Shutting down $vm_name"
		virsh destroy $vm_name
	fi

	if virsh dominfo "$vm_name" >/dev/null 2>&1; then
		echo "Destroying $vm_name and associated storage"
		virsh undefine --nvram --remove-all-storage $vm_name
	fi

	# Instead of --remove-all-storage:
	# virsh vol-delete --pool default $vm_name.qcow2
	return 0
}

function copy_image {

	declare -g image

	local vm_name=$1
	local golden_image=$2

	suffix=${golden_image//*.}

	image="$opt_cache_dir/$vm_name.$suffix"
	if [ -f "$image" ]; then
		if ! $opt_force; then
			user_confirm "A VM image named $image already exists. Remove?" || exit 1
		fi
		rm -f "$image"
	fi

	cp -v "$golden_image" "$image" || exit 1
}

function select_firmware {

	declare -g UEFI_CODE_FILE UEFI_VARS_FILE

	# For as long as the packages in SUSE:ALP aren't signed with a recognized key,
	# we need this:
	UEFI_CODE_FILE="/usr/share/qemu/ovmf-x86_64-smm-opensuse-code.bin"
	UEFI_VARS_FILE="/usr/share/qemu/ovmf-x86_64-smm-opensuse-vars.bin"
	case "$opt_sb_auth" in
	alp)
		UEFI_VARS_FILE="$UEFI_ALP_VARS_FILE";;
	okir)
		UEFI_VARS_FILE="$UEFI_OKIR_VARS_FILE";;
	opensuse|*)
		: ;;
	esac
	echo "Using UEFI variables image $UEFI_VARS_FILE" >&2
}

function run_libvirt {

	local vm_name=$1
	local golden_image=$2

	copy_image "$@"
	select_firmware

	case "$(virsh net-info default | grep -i active)" in
	*yes*) : ;;
	*)
		echo "Starting libvirt network \"default\""
		virsh net-start default;;
	esac

	extra_options=""
	if ! $opt_persistent; then
		extra_options+=" --transient"
	fi

	image_options=
	if ! $opt_cdrom; then
		image_options="--disk $image,bus=sata"
	else
		image_options="--cdrom $image"
	fi

	set -x
	exec virt-install --name $vm_name \
		--os-variant="$ALP_OS_VARIANT" \
		--features smm=on \
		--boot hd \
		--boot "loader=$UEFI_CODE_FILE,loader_ro=yes,loader_type=pflash,loader_secure=yes,nvram_template=$UEFI_VARS_FILE" \
		--import \
		$image_options \
		--memory 2048 \
		--console pty,target_type=virtio \
		--tpm backend.type=emulator,backend.version=2.0,model=tpm-tis \
		--network network=default \
		$extra_options \
		--autoconsole graphical
}

# should behave mostly like run_libvirt, but with fewer frills (and more likely to succeed)
function run_qemu {

	local vm_name=$1
	local golden_image=$2

	copy_image "$@"
	select_firmware

	local nvram_image

	nvram_image="$opt_cache_dir/${vm_name}-VARS.bin"
	cp -v $UEFI_VARS_FILE $nvram_image

	uefi_code_options="-drive if=pflash,format=raw,readonly=on,file=$UEFI_CODE_FILE"
	uefi_vars_options="-drive if=pflash,format=raw,file=$nvram_image"

	TPMDIR=/var/tmp/alp-demo/tpm
	TPMSOCK=${TPMDIR}/swtpm-sock.$$

	# Start swtpm
	mkdir -p $TPMDIR
	swtpm socket --tpm2 --tpmstate dir=$TPMDIR \
		--ctrl type=unixio,path=$TPMSOCK \
		--log file=swtpm.log,level=20 \
		-t -d

	local tpm_options
	tpm_options="-chardev socket,id=chrtpm,path=$TPMSOCK \
		     -tpmdev emulator,id=tpm0,chardev=chrtpm \
		     -device tpm-crb,tpmdev=tpm0"

	local debug_options
	debug_options="-monitor stdio \
		     -debugcon file:debug.log -global isa-debugcon.iobase=0x402 \
		     -serial file:serial.log"

	set -x
	qemu-system-x86_64 -enable-kvm \
			   $uefi_code_options \
			   $uefi_vars_options \
			   -machine type=q35,smm=on,accel=kvm \
			   -smp 4 \
			   -device virtio-scsi-pci,id=scsi \
			   -drive if=none,id=disk1,file=${image} \
			   -device scsi-hd,drive=disk1 \
			   $debug_options \
			   -m 4096 \
			   $tpm_options \
			   -device virtio-rng-pci \
			   -net nic -net user
}

mkdir -p "$opt_cache_dir"

case $# in
0)	
	download_image;;
1)	ALP_IMG_LOCAL=$1; shift;;
*)	echo "Too many arguments" >&2
	usage 1;;
esac



check_system || exit 1
cleanup_domain $opt_vm_name

case $opt_run in
libvirt)
	run_libvirt $opt_vm_name $ALP_IMG_LOCAL;;
qemu)
	run_qemu $opt_vm_name $ALP_IMG_LOCAL;;
*)
	echo "Unknown runner $opt_run" >&2;;
esac
