#!/bin/bash basedir="`dirname "$0"`/.." TAPUTIL="$basedir/bin/taputil" SUDO="/usr/bin/sudo" #VM_NAME="xp.intra.ogris.net" if [ -z "$VM_NAME" ]; then script="`basename "$0"`" script=${script%%.sh} VM_NAME=${script##[0-9]} port=${script%%$VM_NAME} fi VM_QEMU=${VM_QEMU:-"qemu-system-x86_64"} VM_VNC=${VM_VNC:-$port} VM_MEM=${VM_MEM:-2048} VM_CPU=${VM_CPU:-"host"} VM_CPU_NOKVM=${VM_CPU_NOKVM:-""} VM_MACHINE=${VM_MACHINE:-"pc"} VM_CPUS=${VM_CPUS:-2} VM_CDROMS=${VM_CDROMS:-1} VM_EXTRA=${VM_EXTRA:-""} VM_SOUNDHW=${VM_SOUNDHW:-"ac97"} VM_ALSADEV=${VM_ALSADEV:-"default"} VM_LAUNCH_VNC=${VM_LAUNCH_VNC:-1} VM_LAUNCH_SERIAL=${VM_LAUNCH_SERIAL:-0} VM_LAUNCH_STOP=${VM_LAUNCH_STOP:-0} VM_VIRTIO_SCSI=${VM_VIRTIO_SCSI:-0} VM_UEFI=${VM_UEFI:-0} VM_UEFI_FW=${VM_UEFI_FW:-"/usr/share/edk2-ovmf/OVMF_CODE.fd"} VM_VGA=${VM_VGA:-""} VM_USB=${VM_USB:-0} VM_LOCALTIME=${VM_LOCALTIME:-0} VM_LANGUAGE=${VM_LANGUAGE:-"de"} VM_USB_KEYBOARD=${VM_USB_KEYBOARD:-""} VM_USB_MOUSE=${VM_USB_MOUSE:-""} VM_PCI_PASSTHROUGH=${VM_PCI_PASSTHROUGH:-""} VM_FILE_NAME0=${VM_FILE_NAME0:-$VM_NAME} #VM_FILE_TYPE0="virtio" #VM_FILE_CACHE0="none" #VM_FILE_FORMAT0="raw" #... #VM_FILE_NAME9=... #VM_FILE_TYPE9=... #VM_FILE_CACHE9=... #VM_FILE_FORMAT9=... #VM_NET_MAC0=${VM_NET_MAC0:-"52:54:00:00:0$VM_MAC:00"} #VM_NET_TAP0=${VM_NET_TAP0:-"0$VM_VNC"} VM_NET_TYPE0=${VM_NET_TYPE0:-"virtio-net"} #VM_NET_BRIDGE0={$VM_NET_BRIDGE0:-"00"} #... #VM_NET_MAC3=${VM_NET_MAC0:-"52:54:00:00:0$VM_MAC:03"} #VM_NET_TAP3=... #VM_NET_TYPE3=... #VM_NET_BRIDGE3=... VM_PORT=${VM_PORT:-$((VM_VNC+6666))} VM_SERIAL=${VM_SERIAL:-$((VM_VNC+5666))} VM_MAC=${VM_MAC:-$VM_VNC} VM_PIDDIR=${VM_PIDDIR:-"/tmp/kvmlib-$UID"} VM_PID=${VM_PID:-"$VM_PIDDIR/$VM_NAME.pid"} # for remote shutdown via net rpc or serial console: #VM_SHUT_USER="administrator" #VM_SHUT_PASS="secret" # for remote shutdown via net rpc: #VM_SHUT_DOMAIN="XP" #VM_SHUT_HOST="192.168.1.13" # for remote shutdown via serial console: #VM_SHUT_JUNOS="1" [ -z "$VM_NAME" ] && { echo "no VM_NAME" >&2; exit 1; } [ -z "$VM_VNC" ] && { echo "no VM_VNC" >&2; exit 1; } [ -x "$TAPUTIL" ] || { echo "no $TAPUTIL" >&2; exit 1; } [ -x "$SUDO" ] || { echo "no $SUDO" >&2; exit 1; } [ -z "$UID" ] && { echo "no UID set" >&2; exit 1; } if [ ! -d "$VM_PIDDIR" ]; then mkdir -p -m 0700 "$VM_PIDDIR" || exit 1 fi if [ `stat -c %a-%u $VM_PIDDIR` != "700-$UID" ]; then echo "$VM_PIDDIR is not owned by you and/or has lax permissions" >&2 exit 1 fi function vm_vnc() { local xpos=$((VM_VNC%2*850)) local ypos=$((VM_VNC/2*650)) nohup vncviewer :"$VM_VNC" -geometry "+$xpos+$ypos" >/dev/null 2>&1 & } function vm_serial() { exec telnet localhost "$VM_SERIAL" } function vm_nics() { local mode="$1" local nicmac local nictap local nictype local nicbridge local bridge local port local i nics="" for i in {0..9}; do eval nicmac=\$VM_NET_MAC$i eval nictap=\$VM_NET_TAP$i eval nictype=\$VM_NET_TYPE$i eval nicbridge=\$VM_NET_BRIDGE$i if [ -z "$nicmac" -a -z "$nictap" -a -z "$nictype" -a \ -z "$nicbridge" ]; then continue fi nicmac=${nicmac:-"52:54:00:00:0$VM_MAC:0$((i+1))"} bridge=`printf %02d $i` port=`printf %02d $VM_VNC` nictap=${nictap:-"${bridge}${port}"} nictype=${nictype:-"virtio-net"} nicbridge=${nicbridge:-"${bridge}"} if [ "$nictype" != "none" ]; then nics="$nics -device $nictype,mac=$nicmac,netdev=nic$i -netdev tap" nics="$nics,id=nic$i,ifname=tap$nictap,script=no,downscript=no" "$SUDO" "$TAPUTIL" "$mode" "$nictap" "$nicbridge" || exit 1 fi done } function vm_start() { local files local filename local filetype local filecache local i local have_cdrom local cdroms echo "Starting $VM_NAME" vm_nics "add" files="" for i in {0..9}; do eval filename=\$VM_FILE_NAME$i eval filetype=\$VM_FILE_TYPE$i eval filecache=\$VM_FILE_CACHE$i eval fileformat=\$VM_FILE_FORMAT$i filetype=${filetype:-"virtio"} filecache=${filecache:-"none"} fileformat=${fileformat:-"raw"} if [ -n "$filename" -a "$filename" != "none" -a \ "$filename" = "${filename#/}" -a \ "$filename" = "${filename#iscsi://}" ]; then filename="$basedir/disks/$filename.$fileformat" fi if [ -n "$filename" -a "$filename" != "none" ]; then files="$files -drive format=$fileformat,cache=$filecache,file=$filename" # files="$files,if=$filetype,discard=unmap,detect-zeroes=unmap" files="$files,if=$filetype,discard=unmap" fi done have_cdrom=-1 for i in "$@"; do [ "$i" = "-cdrom" ] && have_cdrom=2 done cdroms="" for i in {0..3}; do if [ $i -lt $VM_CDROMS -a $have_cdrom -ne $i ]; then cdroms="$cdroms -drive readonly=on,media=cdrom,if=ide,bus=$((i/2)),unit=$((i%2))" fi done serial="" if [ -n "$VM_SERIAL" -a "$VM_SERIAL" != "0" ]; then serial="-serial telnet::$VM_SERIAL,server,nowait,nodelay" fi if [ "$VM_SOUNDHW" = "intel-hda" -o "$VM_SOUNDHW" = "ich9-intel-hda" ]; then sound="-device $VM_SOUNDHW -device hda-output,audiodev=1" elif [ -n "$VM_SOUNDHW" ]; then sound="-device $VM_SOUNDHW,audiodev=1" fi audio="" if [ -n "$VM_ALSADEV" ]; then audio="-audiodev alsa,id=1,out.dev=$VM_ALSADEV,out.buffer-length=341315" audio="$audio,out.period-length=21333,out.try-poll=false" fi cpu="$VM_CPU" if [ -n "$VM_CPU_NOKVM" -a "$VM_CPU_NOKVM" != "0" ]; then cpu="$cpu,kvm=off" fi virtio_scsi="" if [ -n "$VM_VIRTIO_SCSI" -a "$VM_VIRTIO_SCSI" != "0" ]; then virtio_scsi="-device virtio-scsi" fi uefi="" if [ -n "$VM_UEFI" -a "$VM_UEFI" != "0" -a -n "$VM_UEFI_FW" ]; then uefi="-drive if=pflash,format=raw,readonly=on,file=$VM_UEFI_FW" fi vga="" if [ -n "$VM_VGA" ]; then vga="-vga $VM_VGA" fi usb_keyboard="" if [ -n "$VM_USB_KEYBOARD" ]; then usb_keyboard="-object input-linux,id=kbd1,grab_all=on,repeat=on" usb_keyboard="$usb_keyboard,evdev=/dev/input/by-id" usb_keyboard="$usb_keyboard/usb-${VM_USB_KEYBOARD}-event-kbd" VM_USB=1 fi usb_mouse="" if [ -n "$VM_USB_MOUSE" ]; then usb_mouse="-object input-linux,id=mouse1,grab_all=on,repeat=on" usb_mouse="$usb_mouse,evdev=/dev/input/by-id" usb_mouse="$usb_mouse/usb-${VM_USB_MOUSE}-event-mouse" VM_USB=1 fi usb="" if [ -n "$VM_USB" -a "$VM_USB" != "0" ]; then usb="-usb" fi rtc="utc" if [ -n "$VM_LOCALTIME" -a "$VM_LOCALTIME" != "0" ]; then rtc="localtime" fi pci_passthrough="" last_parent_devid="" for devid in $VM_PCI_PASSTHROUGH; do parent_devid="${devid%.*}" if [ -n "$last_parent_devid" -a \ "$last_parent_devid" = "$parent_devid" ]; then pci_passthrough="$pci_passthrough,multifunction=on" fi pci_passthrough="$pci_passthrough -device vfio-pci,host=$devid" last_parent_devid="$parent_devid" done qemu-system-x86_64 --enable-kvm -rtc base=$rtc -daemonize -m "$VM_MEM" \ -name "$VM_NAME" -cpu "$cpu" -machine "$VM_MACHINE" -smp "$VM_CPUS" \ -k $VM_LANGUAGE \ $virtio_scsi \ $uefi \ $vga \ $usb \ $usb_keyboard \ $usb_mouse \ $pci_passthrough \ $files \ $nics \ $cdroms \ $serial \ $audio \ $sound \ -device virtio-balloon \ -vnc :"$VM_VNC" -monitor tcp::"$VM_PORT",server,nowait \ -pidfile "$VM_PID" \ $VM_EXTRA \ "$@" sleep 1 if [ "$VM_LAUNCH_VNC" = "1" ]; then vm_vnc fi echo >/dev/tcp/localhost/$VM_PORT if [ "$VM_LAUNCH_SERIAL" = "1" ]; then vm_serial fi if [ "$VM_LAUNCH_STOP" = "1" ]; then i=$(xmessage -title "$VM_NAME" -print -buttons yes,no "Stop $VM_NAME?") if [ "$i" = "yes" ]; then vm_stop vm_nics "del" fi fi } function vm_kill() { echo "Killing $VM_NAME" p=`cat "$VM_PID"` || return rm -f "$VM_PID" kill -9 "$p" } function vm_term() { echo "Terminating $VM_NAME" p=`cat "$VM_PID"` || return kill "$p" echo -n "waiting 10 seconds for $VM_NAME to get terminated" for i in {0..10}; do sleep 1 echo -n "." p=`cat "$VM_PID" 2>/dev/null` || { echo "ok"; return; } kill -0 "$p" 2>/dev/null || { echo "ok"; rm -f "$VM_PID"; return; } done vm_kill } function vm_quit() { local i local p echo "Quitting $VM_NAME" ( sleep 1 echo quit sleep 1 ) >/dev/tcp/localhost/$VM_PORT echo -n "waiting 30 seconds for $VM_NAME to quit" for i in {0..30}; do sleep 1 echo -n "." p=`cat "$VM_PID" 2>/dev/null` || { echo "ok"; return; } kill -0 "$p" 2>/dev/null || { echo "ok"; rm -f "$VM_PID"; return; } done vm_term } function vm_stop() { local i local p echo "Stopping $VM_NAME" [ -n "$VM_SHUT_HOST" ] && net rpc shutdown -f \ -U "$VM_SHUT_DOMAIN"'\'"$VM_SHUT_USER"'%'"$VM_SHUT_PASS" -t 2 \ -I "$VM_SHUT_HOST" if [ -n "$VM_SHUT_JUNOS" -a -n "$VM_SERIAL" -a "$VM_SERIAL" -ne "0" ]; then pkill -f "telnet\s+localhost\s+$VM_SERIAL" ( sleep 1 echo "exit" sleep 1 echo sleep 1 echo sleep 1 echo "$VM_SHUT_USER" sleep 1 echo "$VM_SHUT_PASS" sleep 1 echo "request system power-off" sleep 1 echo "yes" sleep 1 ) >/dev/tcp/localhost/"$VM_SERIAL" fi ( sleep 1 echo system_powerdown sleep 1 ) >/dev/tcp/localhost/$VM_PORT echo -n "waiting 180 seconds for $VM_NAME to shutdown" if [ -n "$VM_SHUT_JUNOS" -a -n "$VM_SERIAL" -a "$VM_SERIAL" -ne "0" ]; then echo ". showing serial output:" starttime=`date +%s` while read -t $((starttime+180-`date +%s`)) line; do echo "$line" | tr -cd "[[:print:]][[:space:]]" if [ "${line#Please press any key to reboot}" != "$line" ]; then break fi done /dev/null` || { echo "ok"; return; } kill -0 "$p" 2>/dev/null || { echo "ok"; rm -f "$VM_PID"; return; } done fi vm_quit } function vm_cmd() { if [ -z "$1" ]; then exec telnet localhost $VM_PORT else shift ( sleep 1 echo "$@" sleep 1 ) | telnet localhost $VM_PORT fi } function vm_status() { echo -n "$VM_NAME: " p=`cat "$VM_PID" 2>/dev/null` || { echo "not running"; return; } kill -0 "$p" 2>/dev/null || { echo "not running"; return; } echo "pid=$p" } function vm_cdrom() { ( sleep 1 echo change "$2" \""$1"\" sleep 1 ) >/dev/tcp/localhost/$VM_PORT } function vm_eject() { ( sleep 1 echo eject "$1" sleep 1 ) >/dev/tcp/localhost/$VM_PORT } case "$1" in start) shift vm_start "$@" ;; startnovnc) shift VM_LAUNCH_VNC="0" vm_start "$@" ;; stop) vm_stop vm_nics "del" ;; startstop) shift VM_LAUNCH_STOP="1" vm_start "$@" ;; startnovncstop) shift VM_LAUNCH_VNC="0" VM_LAUNCH_STOP="1" vm_start "$@" ;; status) vm_status ;; restart) shift vm_stop && \ vm_nics "del" && \ vm_start "$@" ;; quit) vm_quit vm_nics "del" ;; term) vm_term vm_nics "del" ;; kill) vm_kill vm_nics "del" ;; vnc) vm_vnc ;; serial) vm_serial ;; cmd) vm_cmd "$@" ;; cdrom) shift if [ -z "$1" ]; then echo "Need path to ISO file" >&2 exit 1 fi if [ -z "$2" ]; then cdrom="ide1-cd0" else i="$2" cdrom="ide$((i/2))-cd$((i%2))" fi vm_cdrom "$1" "$cdrom" ;; eject) if [ -z "$1" ]; then cdrom="ide1-cd0" else i="$1" cdrom="ide$((i/2))-cd$((i%2))" fi vm_eject "$cdrom" ;; version) echo "kvmlib.sh 20230428" ;; help) cat < [ option ... ] Possible commands are: start [ option ... ] startnovnc [ option ... ] stop startstop [ option ... ] startnovncstop [ option ... ] status restart [ option ... ] quit term kill vnc serial cmd cdrom [] eject [] version help Any option is directly passed to qemu, e.g.: $0 start -cdrom /tmp/freebsd.iso -boot d Also see https://ogris.de/kvm/ EOF ;; *) cat <&2 Unknown command: $1 See $0 help for a complete list of commands. EOF exit 1 ;; esac