#!/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-torrents and machine definition file sudo -u lmnsynci /usr/local/bin/vm-sync get_file "${VM_NAME}.qcow2.torrent" [[ -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" } 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="/tmp/${UID}/.config" 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 "${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 # finally, create the new vm virt-install \ --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 \ ${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