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 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[@]}" \