Just wanted to share this Bash script which I use to run my Qemu-KVM virtual machines.
Content:
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 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[@]}" \
