
For working exam-mode we need to block direct internet access by firewall. Users have to use squid-proxy on firewall, which can be disabled for exam-users. To allow VM-traffic (anonymous user), we use a local squid server with users kerberos-ticket to authenticate on the parent squid. When using VMs on teacherdevices offsite, the local squid has to use direct internet access. So we need two squid configs. When switching between offsite and onsite, the squid has to be restartet with corresponding config.
349 lines
12 KiB
Bash
Executable file
349 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-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
|
|
sudo /usr/local/bin/desktop-sync
|
|
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
|