lmn-client/roles/lmn_vm/files/vm-run
2025-04-03 07:22:10 +02:00

374 lines
12 KiB
Bash
Executable file

#!/usr/bin/bash
# create and run clone
set -eu
show_help() {
cat << EOF >&2
Usage: $(basename "$0") [options] vmname"
Create a new clone, start the vm (if not yet running) and run virt-viewer.
Squid-Proxy will be started too.
options:
-n|--new new clone will be created, even if exists
-p|--persistent new clone will be created persistent, so available after reboot too
-s|--system qemu:///system instead of default qemu:///session
--no-viewer start without viewer
--heads n number of displays
--memory sizeMB memory size in MB
--cpu num number of CPUs
--os OS operating system (win10|linux|..)
--data-disk size additional data-disk
--bridge virbrX additional network interface on bridge virbrX
--uid uid set uid on guest
--gid gid set gid on guest
--macvtap additional network interface on device macvtap
--options options additional options for virt-install command
EOF
}
exit_script() {
echo "run-vm.sh ${VM_NAME} terminated by trap!" >> "/tmp/${UID}/exit-run-vm.log"
virsh --connect="${QEMU}" destroy "${VM_NAME}-clone"
trap - SIGHUP SIGINT SIGTERM # clear the trap
kill -- -$$ # Sends SIGTERM to child/sub processes
}
check_images() {
# sync vm-torrent and TPM data
sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${VM_NAME}.qcow2.torrent" "${VM_NAME}.permall"
[[ -f "${VM_SYSDIR}/${VM_NAME}.qcow2" ]] && sudo -u lmnsynci /usr/local/bin/vm-sync delete_outdated_image "${VM_NAME}.qcow2"
BACKINGARRAY=()
imgfile="${VM_SYSDIR}/${VM_NAME}.qcow2" && [[ -f "${VM_DIR}/${VM_NAME}.qcow2" ]] && imgfile="${VM_DIR}/${VM_NAME}.qcow2"
BACKINGARRAY+=("${imgfile}")
echo "Imgfile=$imgfile"
if [[ ! -f "${imgfile}" ]] || ! qemu-img info -U "${imgfile}" | grep "file format: qcow2"; then
if [[ ! -f "${VM_SYSDIR}/${VM_NAME}.qcow2.torrent" ]]; then
echo "no base VM disk '${VM_NAME}.qcow2' found and/or ${VM_NAME} not found on server" >&2
exit 1
fi
# sync vm-disk image by torrent
echo "Try to sync VM ${VM_NAME} by torrent"
sudo -u lmnsynci /usr/local/bin/vm-sync get_image "${VM_NAME}"
fi
backingfile=$(qemu-img info -U "${imgfile}" | grep "^backing file:" | cut -d ' ' -f 3)
while [[ -n "${backingfile}" ]]; do
echo "Backingfile required: ${backingfile}"
imgfile="${VM_SYSDIR}/${backingfile}" && [[ -f "${VM_DIR}/${backingfile}" ]] && imgfile="${VM_DIR}/${backingfile}"
BACKINGARRAY+=("${imgfile}")
sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${backingfile}.torrent"
[[ -f "${VM_SYSDIR}/${backingfile}" ]] && sudo -u lmnsynci /usr/local/bin/vm-sync delete_outdated_image "${backingfile}"
if [[ ! -f "${imgfile}" ]] || ! qemu-img info -U "${imgfile}" | grep "file format: qcow2"; then
# sync vm-disk image by torrent
echo "Try to sync backingfile ${backingfile} by torrent"
sudo -u lmnsynci /usr/local/bin/vm-sync get_image "${backingfile%.qcow2}"
fi
backingfile=$(qemu-img info -U "${imgfile}" | grep "^backing file:" | cut -d ' ' -f 3)
done
echo "VM-Image and required backingfiles available"
echo "Now, let's check the images."
# Check VM-Images in reverse order
for ((i=${#BACKINGARRAY[@]}-1; i>=0; i--))
do
echo "Checking ${BACKINGARRAY[$i]}"
if ! qemu-img check -U "${BACKINGARRAY[$i]}" 2>/dev/null; then
echo "check failed!"
echo "sync ${BACKINGARRAY[$i]} again"
sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${BACKINGARRAY[$i]}.torrent"
sudo -u lmnsynci /usr/local/bin/vm-sync get_image "$(basename "${BACKINGARRAY[$i]}" .qcow2)"
fi
done
echo "VM-Image and required backingfiles available and checked"
sudo -u lmnsynci /usr/local/bin/vm-sync update_usage_information ${BACKINGARRAY[*]}
}
create_clone() {
local VM_NAME="$1"
if ! [[ -f "${VM_SYSDIR}/${VM_NAME}.qcow2" || -f "${VM_DIR}/${VM_NAME}.qcow2" ]]; then
echo "qcow2 File does not exists." >&2
exit 1
fi
# Create User-VM-Dir and link system VM-Images
[[ -d "${VM_DIR}" ]] || mkdir -p "${VM_DIR}"
if [[ "${PERSISTENT}" -eq 1 ]]; then
sudo /usr/local/bin/vm-link-images -p
else
sudo /usr/local/bin/vm-link-images
fi
# Create backing file
cd "${VM_DIR}"
qemu-img create -f qcow2 -F qcow2 -b "${VM_NAME}.qcow2" "${VM_NAME}-clone.qcow2"
if [[ -f "${VM_SYSDIR}/${VM_NAME}.permall" ]]; then
# Copy tpm file
if [[ ! -f "${VM_NAME}.permall" ]]; then
echo "copy tpm-file"
cp "${VM_SYSDIR}/${VM_NAME}.permall" .
fi
# create tpm-clone file
echo "create tpm-clone-file"
cp "${VM_NAME}.permall" "${VM_NAME}-clone.permall"
fi
}
create_printerlist() {
## Prepare .printerlist.csv
mkdir -p "${VM_MEDIADIR}"
chgrp "$(id -g)" "${VM_MEDIADIR}"
echo "Name;IppURL" > "${VM_MEDIADIR}/.printerlist.csv"
for p in $(lpstat -v | cut -f 3 -d" " | sed 's/:$//'); do
echo "$p;ipp://192.168.122.1/printers/$p" >> "${VM_MEDIADIR}/.printerlist.csv"
done
}
create_mountlist() {
if id | grep -q teachers; then
NETHOME=/srv/samba/schools/default-school/teachers/$USER
else
NETHOME=(/srv/samba/schools/default-school/students/*/"$USER")
fi
NETHOME="${NETHOME#/srv/samba/schools}"
cat << EOF > "/lmn/media/${USER}/.mounts.csv"
Drive;Remotepath
H;\\\\10.190.1.1${NETHOME//\//\\}
T;\\\\10.190.1.1\default-school\share
EOF
echo "${USER}" > "/lmn/media/${USER}/.user"
}
start_virtiofsd() {
# BEGIN temporary fix, while linux-starter are not migrated to --uid and --gid
if [[ "$LIBVIRTOSINFO" =~ debian.* ]]; then
[[ "$GUEST_UID" == 0 ]] && GUEST_UID=1010
[[ "$GUEST_GID" == 0 ]] && GUEST_GID=1010
fi
# END temporary fix
socket="/run/user/$(id -u $USER)/virtiofs-${VM_NAME}.sock"
systemd-run --user /usr/local/bin/virtiofsd --uid-map=:${GUEST_UID}:${UID}:1: --gid-map=:${GUEST_GID}:$(id -g):1: \
--socket-path "$socket" --shared-dir "/lmn/media/${USER}" --syslog
}
ask_really_persistent() {
cat << EOF >&2
!!!!!!!!!!!!!!! Wichtig !!!!!!!!!!!!!!
Auf dem Computer existiert noch keine persistente VM mit dem Namen ${VM_NAME}.
Das Anlegen persistenter Maschinen sollte nur auf Computern geschehen,
die dem jeweiligen Benutzer zugeordnet sind.
In Klassenzimmern oder Computerräumen ist das Verwenden persistenter
Maschinen normalerweise nicht sinnvoll und sprengt die verfügbaren
Festplattenkapazität.
EOF
read -rp "Ist die Installation einer persistenten VM wirklich gewünscht? ja/nein " answer
if [[ "${answer,,}" == "ja" ]]; then
VM_DIR="${VM_DIR_PERSISTENT}"
echo "Die VM ${VM_NAME} wird persistent auf der Festplatte angelegt!"
sleep 5
else
PERSISTENT=0;
VM_DIR="${VM_DIR_CONF}"
echo "Die VM ${VM_NAME} wird nicht persistent gestartet!"
sleep 5
fi
}
QEMU='qemu:///session'
NEWCLONE=0
PERSISTENT=0
LIBVIRTOSINFO="win10"
LIBVIRTOPTS=""
NO_VIEWER=0
GUEST_UID=0
GUEST_GID=0
source /etc/lmn/vm.conf
VM_DIR_CONF="${VM_DIR}"
TEMP=$(getopt -o no:ps --long new,no-viewer,options:,persistent,system,memory:,data-disk:,heads:,cpu:,bridge:,macvtap,os:,uid:,gid:,help -n $0 -- "$@")
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
eval set -- "$TEMP"
while true; do
case "$1" in
-p | --persistent )
PERSISTENT=1;
VM_DIR="${VM_DIR_PERSISTENT}"
shift
;;
-n | --new )
NEWCLONE=1
shift
;;
-s | --system )
QEMU='qemu:///system'
shift
;;
-o | --options )
LIBVIRTOPTS=$2
shift 2
;;
--no-viewer )
NO_VIEWER=1
shift
;;
--data-disk )
LIBVIRTOPTS="${LIBVIRTOPTS} --disk ${VM_DIR}/data.qcow2,size=$2,sparse=yes"
shift 2
;;
--heads )
for i in $(seq $2)
do
LIBVIRTOPTS="${LIBVIRTOPTS} --video model.heads=$i"
done
shift 2
;;
--memory )
mem_avail=$(sed -En "s/^MemAvailable:\s+([0-9]+)\s+kB/\1/p" /proc/meminfo)
mem_avail=$((mem_avail / 1024 - 2048))
if (( $2 < mem_avail )); then
LIBVIRTOPTS="${LIBVIRTOPTS} --memory $2"
else
LIBVIRTOPTS="${LIBVIRTOPTS} --memory ${mem_avail}"
fi
shift 2
;;
--cpu )
#cpu=$(sed -En "0,/^cpu cores/s/^cpu cores\s+:\s+([0-9]+)/\1/p" /proc/cpuinfo)
cpu=$(lscpu | grep "^CPU(s):" | sed 's/.* //g')
if (( $2 < cpu )); then
LIBVIRTOPTS="${LIBVIRTOPTS} --vcpu $2"
else
LIBVIRTOPTS="${LIBVIRTOPTS} --vcpu ${cpu}"
fi
shift 2
;;
--bridge )
if ip link | grep $2; then
LIBVIRTOPTS="${LIBVIRTOPTS} --network=bridge=$2,model.type=virtio"
fi
shift 2
;;
--macvtap )
for interface in $(ip link | sed -En 's/.*(macvtap-.*)@.*/\1/p'); do
mac="$(ip link | grep -A1 "${interface}" | \
sed -nE "s%\s+link/ether ([[:xdigit:]:]{17}) .+%\1%p")"
type="ethernet,mac=${mac},target.dev=${interface},xpath1.set=./target/@managed=no,model.type=virtio"
LIBVIRTOPTS="${LIBVIRTOPTS} --network type=$type"
done
shift
;;
--os )
LIBVIRTOSINFO=$2
shift 2
;;
--uid )
GUEST_UID=$2
shift 2
;;
--gid )
GUEST_GID=$2
shift 2
;;
--help )
show_help
exit 1
;;
-- ) shift; break ;;
* ) break ;;
esac
done
# if less than one arguments supplied, display usage
if [[ $# -ne 1 ]] ; then
show_help
exit 1
fi
VM_NAME=$1
systemctl --user restart usersquid.service &
# check, if persistent VM is really wanted
if [[ "${PERSISTENT}" == 1 ]] && [[ ! -f "${VM_DIR_PERSISTENT}/${VM_NAME}.qcow2" ]]; then
ask_really_persistent
fi
# because virsh has problems with long pathnames, using diffent configdir
export XDG_CONFIG_HOME="/var/tmp/vm/${UID}"
if ! virsh --connect="${QEMU}" list | grep "${VM_NAME}-clone"; then
echo "VM not yet running."
# only when school-network is reachable
if nslookup "${SEEDBOX_HOST}"; then
check_images
fi
if [[ "${NEWCLONE}" = 1 ]] || [[ ! -f "${VM_DIR}/${VM_NAME}-clone.qcow2" ]]; then
create_clone "${VM_NAME}"
fi
# delete the old vm
virsh --connect=qemu:///session undefine --nvram "${VM_NAME}-clone" || echo "${VM_NAME}-clone did not exist"
#trap exit_script SIGHUP SIGINT SIGTERM
create_printerlist
create_mountlist
# start virtiofsd-service
[[ "${QEMU}" = 'qemu:///session' ]] && start_virtiofsd
uuid=$(openssl rand -hex 16)
uuid="${uuid:0:8}-${uuid:8:4}-${uuid:12:4}-${uuid:16:4}-${uuid:20:12}"
if [[ -f "${VM_DIR}/${VM_NAME}-clone.permall" ]]; then
mkdir -p "/var/tmp/vm/${UID}/.config/libvirt/qemu/swtpm/${uuid}/tpm2/"
ln "${VM_DIR}/${VM_NAME}-clone.permall" "/var/tmp/vm/${UID}/.config/libvirt/qemu/swtpm/${uuid}/tpm2/tpm2-00.permall"
LIBVIRTOPTS="${LIBVIRTOPTS} --tpm backend.type=emulator,backend.version=2.0,model=tpm-crb "
fi
# finally, create the new vm
virt-install \
--uuid="${uuid}" \
--osinfo "${LIBVIRTOSINFO}" \
--name "${VM_NAME}-clone" \
--import \
--clock hpet_present=yes,hypervclock_present=yes \
--features hyperv.synic.state=on,xpath1.set=./hyperv/vpindex/@state=on,xpath2.set=./hyperv/stimer/@state=on \
--memorybacking source.type=memfd,access.mode=shared \
--disk "${VM_DIR}/${VM_NAME}-clone.qcow2",driver.discard=unmap,target.bus=scsi,cache=writeback \
--network=bridge=virbr0,model.type=virtio \
--filesystem driver.type=virtiofs,accessmode=passthrough,target.dir=virtiofs,xpath1.set=./source/@socket="/run/user/${UID}/virtiofs-${VM_NAME}.sock" \
--controller type=scsi,model=virtio-scsi \
--check path_in_use=off \
--connect="${QEMU}" \
--noautoconsole \
--redirdev usb,type=spicevmc \
--redirdev usb,type=spicevmc \
--redirdev usb,type=spicevmc \
--redirdev usb,type=spicevmc \
--redirdev usb,type=spicevmc \
${LIBVIRTOPTS}
# --dry-run \
# --print-xml \
# > /tmp/vm.xml
# --features hyperv.synic.state=on,xpath1.set=./hyperv/vpindex/@state=on,xpath2.set=./hyperv/stimer/@state=on \
# --network type=ethernet,target.dev=vm-macvtap,xpath1.set=./target/@managed=no \
# virsh --connect="${QEMU}" start "${VM_NAME}-clone"
fi
if [[ $NO_VIEWER == 0 ]] ; then
echo "starting viewer"
trap exit_script SIGHUP SIGINT SIGTERM
virt-viewer --connect="${QEMU}" --full-screen "${VM_NAME}-clone"
fi