Qemu-KVM Start Script

Just wanted to share this Bash script which I use to run my Qemu-KVM virtual machines.

Content:

  1. VM template script
  2. Live image script

 

1. VM template script

#!/bin/bash

# License: AGPLv3




################################################################################
################################################################################
# HELP SECTION

# USAGE NOTES
# - section for VM specific notes
# - e.g. VM password: some-password
# - e.g. IP on NIC 1: 10.0.2.15/24
# - Serial Linux boot parameter (ATTENTION: open shell!): console=ttyS0 ignore_loglevel

# BASIC USAGE
#
# Tested Qemu version: 6.1.1
#
# 1. Install needed software: qemu, tmux, socat (optional)
# 2. Create a new folder. (should be one folder per VM)
#    Use one folder per VM. Put all needed file for that VM into that folder!
#    ATTENTION: The full path to that folder and the folders name should
#      only include these letters (no spaces!): A-Z a-z 0-9 _ -
# 3. Copy this script into a text file named "run.bash" inside the VM folder.
# 4. Make run.bash executeable: chmod +x run.bash
# 5. Adjust the options below.
#    Important options (most important first):
#    - ram_mb: Memory size
#    - cpus: Number of CPU cores
#    - basetime: Adjust if you're running a Windows guest.
#    - sshport, smbclientport and spiceport: Must be an unused TCP port!
#        (change if you run more than 1 VM simultanously)
# 6. Create HDD image inside that folder (name must be "hd0.qcow2"):
#      qemu-img create -f qcow2 hd0.qcow2 10G
# 7. Create link to or copy installation ISO (if needed).
#    Name must be: installation.iso
#    Same for "virtio-win.iso" if you're running a Windows guest. (see below)
# 8. Start run.bash. Either via filemanager or from shell: ./run.bash
# 9. If the VM doesn't start, comment out "tmux_cmd=..." and run ./bash.run
#    in a terminal to see the error output.

# CONTROL A RUNNING QEMU VM
#
# Send command via socket and get STDOUT:
#   echo 'help' | socat STDIO UNIX-CONNECT:"${mydir}"/monitor.sock; echo
#   echo 'info usb' | socat STDIO UNIX-CONNECT:"${mydir}"/monitor.sock; echo
# Send command via Tmux (no direct STDOUT feedback):
#   tmux send-keys -t "${vmname}" 'eject ide1-cd0' enter




################################################################################
################################################################################
# CONFIGURATION

# Get the directory this script is in.
mydir="$(dirname "$(readlink -f "${0}")")"

notmux="${1}"  # set to "notmux" to disable tmux


# Set vm name by directory name.
vmname="$(basename "${mydir}")"
# Set vm name manually.
#vmname='DebianVM'

# system qemu
qemu='qemu-system-x86_64'
# custom build qemu
#qemu=~/'opt/qemu/bin/qemu-system-x86_64'

# boot: MBR (empty) or UEFI (OVMF firmware)
bios_ary=()
#bios_ary=(-bios /usr/share/ovmf/OVMF.fd)          # Debian-12
#bios_ary=(-bios /usr/share/qemu/ovmf-x86_64.bin)  # openSUSE-15.5

# - guest os specific settings -
# Linux / UNIX BIOS clock mode
basetime=utc
# Windows time and driver CD (Linux has all drivers)
# Driver CD download and more information:
#   https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso
#   https://docs.fedoraproject.org/en-US/quick-docs/creating-windows-virtual-machines-using-virtio-drivers/index.html
#basetime=localtime

unset smbclientport
unset sshport
unset spiceport
unset spicepw
# host ssh and smb/cifs port forwarding
# "0" automatically chooses a free port on the host.
#   Find via: ss -tnlp | grep qemu
# Comment out port setting to disable the port forwarding.
# connect via:
#   ssh -p 50022 root@127.0.0.1
#   spicy -h 127.0.0.1 -p 55900
smbclientport=0
sshport=0
# Change gl=on to gl=off below. (gl isn't compatible with Spice)
#spiceport=0
# password for Spice (much more versatile then VNC)
spicepw='secure'

# memory (RAM) in MB (G postfix for GB - e.g.: ram_mb=2G)
ram_mb=2048
unset balloon_mb
# Actual memory in MB if guests has memory balloon driver. (no postfix, only MB allowed!)
#   Windows-11 hangs about 20 seconds / GB of decreased memory on shutdown/reboot.
#   https://gitlab.com/qemu-project/qemu/-/issues/2556
#balloon_mb=1536
# Disable balloon if guest has out of memory error (guest gets full ram_mb).
deflate_on_oom=false

# CPU cores
cpus=2
# For Qemu <= 6.* # cpus=2,sockets=1,cores=2 #### hints for modifying the start command below # -vga (high resolution) # Linux: virtio, std, vmware, cirrus # Windows: qxl, std # Alternatives: # -device cirrus-vga # -device bochs-display,id=video0,vgamem=16777216,xres=1024,yres=768 # Add the following lines to get a direct network interface. # You can also bridge tap0 via "brctl". # You must create the virtual interface in advance on the host. # sudo ip tuntap add dev tap0 mode tap user "${USER}" # #-device virtio-net-pci,mac=52:54:00:00:00:02,netdev=tap.2 \ #-netdev tap,script=no,downscript=no,ifname=tap0,id=tap.2 \ ################################################################################ ################################################################################ # PRE-START AUTOMATION # https://www.spice-space.org/multiple-clients.html # This feature is still experimental, it is not expected to work # correctly under different client bandwidths, although it should not # crash the server. export SPICE_DEBUG_ALLOW_MC=1 # Usage: free_tcp_port(START, NAME) # START: port to start with # NAME: name for output function free_tcp_port { port="${1}" while [ -n "$(ss -tan4H "sport = ${port}" | grep -E '^LISTEN\s')" ]; do port="$((port+1))" done echo "${2}": "${port}" 1>&2
	echo "${port}"
}
if [ "${smbclientport}" == '0' ]; then smbclientport="$(free_tcp_port '50022' 'smbclientport'         )"; fi
if [ "${sshport}" == '0' ];       then sshport="$(      free_tcp_port "$((smbclientport+1))" 'sshport')"; fi
if [ "${spiceport}" == '0' ];     then spiceport="$(    free_tcp_port "$((sshport+1))" 'spiceport'    )"; fi

unset sshstr
if [ -n "${sshport}" ]; then
	sshstr=",hostfwd=tcp:127.0.0.1:${sshport}-:22"
fi

unset smbclientstr
if [ -n "${smbclientport}" ]; then
	smbclientstr=",hostfwd=tcp:127.0.0.1:${smbclientport}-:445"
fi

spicestr=()
if [ -n "${spiceport}" ]; then
	spicestr[0]='-object'
	spicestr[1]="secret,id=spicesec0,data=${spicepw}"
	spicestr[2]='-spice'
	spicestr[3]="port=${spiceport},addr=127.0.0.1,ipv4=on,ipv6=off,password-secret=spicesec0"
	spicestr[4]='-device'
	spicestr[5]="ich9-usb-ehci1,id=usb"
	spicestr[6]='-device'
	spicestr[7]="ich9-usb-uhci1,masterbus=usb.0,firstport=0,multifunction=on"
	spicestr[8]='-chardev'
	spicestr[9]="spicevmc,name=usbredir,id=usbredirchardev1"
	spicestr[10]='-device'
	spicestr[11]="usb-redir,chardev=usbredirchardev1,id=usbredirdev1"
fi


unset tmux_cmd
# disable tmux to see errors on the shell
if [ "${notmux}" != notmux ]; then
  tmux_cmd="tmux new -d -s ${vmname} "
fi

# SMB/CIFS network share: \\10.0.2.4\qemu
if [ ! -e "${mydir}"/shared-folder ]; then
	mkdir "${mydir}"/shared-folder
fi

inst_iso="${mydir}/installation.iso"
inst_iso_cmd=''
if [ -f "${inst_iso}" ]; then
	inst_iso_cmd=",format=raw,file=${inst_iso}"
fi

virtio_iso="${mydir}/virtio-win.iso"
virtio_iso_cmd=()
if [ -f "${virtio_iso}" ]; then
	virtio_iso_cmd[0]='-drive'
	virtio_iso_cmd[1]="if=ide,media=cdrom,index=3,id=ide3,format=raw,file=${virtio_iso}"
fi

if [ -n "${balloon_mb}" ]; then
	bash -c "sleep 4; echo 'balloon ${balloon_mb}' | socat STDIO UNIX-CONNECT:'${mydir}'/monitor.sock >/dev/null" & disown
fi




################################################################################
################################################################################
# START QEMU

# Commenting out a line:     $(true -parameter value)
${tmux_cmd}nice ionice -c3 "${qemu}" \
\
-boot order=c,menu=on \
"${bios_ary[@]}" \
-drive file="${mydir}"/hd0.qcow2,discard=unmap,detect-zeroes=unmap,media=disk,if=virtio,index=0,format=qcow2,id=virtio0 \
-drive if=ide,media=cdrom,index=2,id=ide2"${inst_iso_cmd}" \
"${virtio_iso_cmd[@]}" \
\
-device virtio-net-pci,mac=52:54:00:00:00:01,netdev=user.1,id=virtio-net-pci.0 \
-netdev user"${sshstr}${smbclientstr}",id=user.1,hostname="${vmname}",smb="${mydir}"/shared-folder/ \
\
"${spicestr[@]}" \
\
-cpu host \
-smp "${cpus}" \
-m "${ram_mb}" \
-device virtio-balloon,id=virtio-balloon.0,deflate-on-oom="${deflate_on_oom}" \
-machine pc,mem-merge=on,accel=kvm,smm=off \
\
-device virtio-vga,id=video0,xres=1280,yres=800 \
-k de \
-device nec-usb-xhci,id=xhci \
-usb \
-device usb-tablet,id=usb-tablet.0 \
-rtc base="${basetime}" \
\
-nodefaults \
-monitor vc \
-monitor stdio \
-monitor unix:"${mydir}"/monitor.sock,server,nowait \
-serial unix:"${mydir}"/serial.sock,server,nowait \
-serial vc \
\
-display gtk,gl=on,zoom-to-fit=on,window-close=off,show-tabs=on,show-cursor=off \
-name "${vmname}" \

 

2. Live image script

#!/bin/bash

# License: AGPLv3

# Script to simply start up a Linux live image.
# Optionally with a writeable HDD image added.

# Add boot options:
# ADJUST ACCORDING TO USED DISK IMAGE!
# (for i in sendkey ret tab spc c o n s o l e equal t t y S 0 ret; do echo "sendkey $i"; done) | \
#   socat STDIO UNIX-CONNECT:"${HOME}"/test-vm_22_monitor.sock; echo
# Connects ~/test-vm_${id}_serial.sock for Debian-12 live images.


qemu='qemu-system-x86_64'
#qemu=~/'opt/qemu/bin/qemu-system-x86_64'




help () {
	echo
	echo 'Usage: test-vm.bash ID IMAGE.ISO [HDD.qcow2]'
	echo '       ID is an integer between 10 and 99. (example 22)'
	echo '       SSH port will be 127.0.0.1:$(50000 + $ID)'
	echo '       Set ID to "a" to automatically choose a port which will be printed to STDOUT.'
	echo '       SMB/CIFS network share: \\10.0.2.4\qemu'
}

id="${1}"
iso="${2}"
hdd="${3}"

if [ "${id}" == "a" ]; then
	id=50022  # valid tcp port
	while [ -n "$(ss -tan4H "sport = ${port}" | grep -E '^LISTEN\s')" ]; do
		id="$((id+1))"
	done
	echo "ID: ${id}"
	id="$((id-50000))"
elif [[ ! "${id}" =~ ^[0-9]+$ ]] || [ "${id}" -lt 10 ] || [ "${id}" -gt 99 ]; then
	echo "Invalid id: ${id}"
	help
	exit 1
fi

if [ ! -f "${iso}" ] && [ ! -r "${iso}" ]; then
	echo "Not a readable file: ${iso}"
	help
	exit 1
fi

if [ -n "${hdd}" ] && [ ! -f "${iso}" ] && [ ! -r "${iso}" ] && [ ! -w "${iso}" ]; then
	echo "Not a readable and writeable file: ${hdd}"
	help
	exit 1
fi

unset tmux_cmd
tmux_cmd="tmux new -d -s ${id} "

bios_ary=()
# Debian
#bios_ary=(-bios /usr/share/ovmf/OVMF.fd)
# openSUSE
#bios_ary=(-bios /usr/share/qemu/ovmf-x86_64.bin)

monitor_sock="${HOME}/test-vm_${id}_monitor.sock"
serial_sock="${HOME}/test-vm_${id}_serial.sock"
# Serial Linux boot parameter (ATTENTION: open shell!): console=ttyS0 ignore_loglevel




if [ -n "${hdd}" ]; then
	name_ext="${hdd##*.}"
	hdd_ary=(-drive file="${hdd}",media=disk,if=none,format="${name_ext##*.}",id=ide1,index=1 -device ide-hd,drive=ide1,id=devide1,bus=ide.0,bootindex=2)
	#hdd_ary=(-drive file="${hdd}",media=disk,if=none,format="${name_ext##*.}",id=ide1,index=1,readonly=on -device virtio-blk-pci,drive=ide1,id=devide1,bus=ide.0,bootindex=2)
else
	hdd_ary=()
fi

# Unplugs "virtual cable" on second NIC.
bash -c "sleep 4; echo 'set_link virtio-net-pci.2 off' | socat STDIO UNIX-CONNECT:'${monitor_sock}' >/dev/null" & disown


# Commenting out a line: "$(true -parameter value)"
${tmux_cmd}nice ionice -c3 "${qemu}" \
\
-nodefaults \
-monitor vc \
-monitor stdio \
-monitor unix:"${monitor_sock}",server,nowait \
-serial unix:"${serial_sock}",server,nowait \
-serial vc \
-k de \
-name test_500"${id}" \
-display gtk,gl=on,zoom-to-fit=on,window-close=off,show-tabs=on,show-cursor=off \
\
-device virtio-vga-gl,id=video0,xres=1024,yres=768 \
-rtc base=utc \
-device virtio-balloon,id=virtio-balloon.0,deflate-on-oom=false \
-m 8G \
-machine pc,mem-merge=on,accel=kvm,smm=off \
-smp 2 \
-cpu host \
\
-usb \
-device usb-tablet,id=usb-tablet.0 \
-device nec-usb-xhci,id=xhci \
\
-device virtio-net-pci,netdev=user.1,addr=14,mac=52:54:00:84:49:01,id=virtio-net-pci.1 \
-netdev user,hostfwd=tcp:127.0.0.1:500"${id}"-:22,id=user.1,smb=/home/moritz/Virtuelle_Maschinen/shared-folder/,restrict=off \
-device virtio-net-pci,addr=15,mac=52:54:00:84:49:02,id=virtio-net-pci.2 \
\
-boot menu=on,order=d \
"${bios_ary[@]}" \
-drive file="${iso}",media=cdrom,readonly=on,if=none,index=2,id=ide2 \
-device ide-cd,drive=ide2,id=devide2,bus=ide.0,bootindex=1 \
-drive media=cdrom,readonly=on,if=ide,index=3,id=ide3 \
"${hdd_ary[@]}" \