|
#!/bin/bash |
|
|
|
## Creates and runs a virtual machine running Raspberry Pi OS Desktop or Lite with a GUI. |
|
## Read it's documentation at https://gist.github.com/emendir/922d6914a1705ed2e8e4e96db726c422 |
|
## Developed based on https://gist.github.com/cGandom/23764ad5517c8ec1d7cd904b923ad863 |
|
|
|
VM_NAME="RasPi" # name of the virtual machine registered in virt-manager |
|
|
|
# Directory in which the VM's disk and linux kernel images will be stored |
|
WORK_DIR=/opt/VirtualMachines/$VM_NAME |
|
|
|
## Generate the password hash using the following command: |
|
# openssl passwd -6 |
|
PASSWORD_HASH='$6$FBH3uTF54pUUDpcR$IR5cMxHRhPN.DsXCUFJpRgmWL2iAZKuTaakYq7i4Y/Qz02B8ngloRrnM2K1IPkFYPCn.f/cDm7DpJa5pSglsg0' # this value is for the password 'password' |
|
USERNAME='pi' |
|
|
|
|
|
# Download URL of the Linux kernel used |
|
KERNEL_URL=https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.34.tar.xz |
|
|
|
# Download URL of the Raspberry Pi OS image to use (the following options have been tested with the above kernel) |
|
# RPOS_IMAGE_URL=https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz |
|
RPOS_IMAGE_URL=https://downloads.raspberrypi.com/raspios_full_arm64/images/raspios_full_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-full.img.xz |
|
|
|
# Flags to force rebuilding kernel or Raspberry Pi OS images |
|
# RECREATE_RPOS_IMAGE implies REBUILD_KERNEL |
|
RECREATE_RPOS_IMAGE=false # CAREFULL: will delete any existing image |
|
REBUILD_KERNEL=false # will reuse existing kernel build files if found, delete $WORK_DIR/linux-kernel to start from scratch |
|
|
|
# amount by which to resize the the VM's Raspberry Pi OS disk image |
|
# (requires manually running `raspi-config nonint do_expand_rootfs` to apply) |
|
RPOS_IMAGE_EXTRA_SPACE=4G |
|
|
|
# location of the VM's Raspberry Pi OS disk image |
|
RPOS_IMAGE=$WORK_DIR/raspi_vm_disk.img |
|
|
|
|
|
# Setup mounting directories to edit the Raspberry Pi OS image |
|
RPI_BOOT_MNT=/mnt/rpi_boot |
|
RPI_ROOT_MNT=/mnt/rpi_root |
|
if ! [ -d $RPI_BOOT_MNT ];then |
|
sudo mkdir $RPI_BOOT_MNT |
|
fi |
|
if ! [ -d $RPI_ROOT_MNT ]; then |
|
sudo mkdir $RPI_ROOT_MNT |
|
fi |
|
|
|
if ! [ -e $WORK_DIR ];then |
|
mkdir -p $WORK_DIR |
|
fi |
|
cd $WORK_DIR || exit 1 |
|
|
|
## Setup disk image for VM with Raspberry Pi OS pre-installed |
|
RECREATED_RPOS_IMAGE=false |
|
if $RECREATE_RPOS_IMAGE || ! [ -e $RPOS_IMAGE ];then |
|
RECREATED_RPOS_IMAGE=true |
|
|
|
# delete any old images |
|
if [ -e $RPOS_IMAGE ];then |
|
sudo rm $RPOS_IMAGE |
|
fi |
|
|
|
|
|
## Download and resize Raspberry Pi OS image |
|
wget $RPOS_IMAGE_URL -O $RPOS_IMAGE.xz |
|
xz -d $RPOS_IMAGE.xz |
|
qemu-img resize -f raw $RPOS_IMAGE +$RPOS_IMAGE_EXTRA_SPACE |
|
fi |
|
|
|
# ensure we can work with the image now |
|
# we'll change its permissions again before running the VM |
|
sudo chown $USER:kvm $RPOS_IMAGE |
|
|
|
|
|
# calculate image partition details for the boot and root partitions in the Raspberry Pi OS image |
|
sector_size=$(fdisk -l $RPOS_IMAGE | grep 'Sector size' | awk '{print $4}') |
|
start_sector_boot=$(fdisk -l $RPOS_IMAGE | grep -m1 "^${RPOS_IMAGE}1" | awk '{print $2}') |
|
start_sector_root=$(fdisk -l $RPOS_IMAGE | grep -m1 "^${RPOS_IMAGE}2" | awk '{print $2}') |
|
start_sector_bytes_boot=$((sector_size * start_sector_boot)) |
|
start_sector_bytes_root=$((sector_size * start_sector_root)) |
|
|
|
|
|
if $RECREATED_RPOS_IMAGE;then |
|
## Preconfigure username & password, enable SSH |
|
# mount the first partition of the Raspberry Pi OS image (the boot partition) |
|
sudo mount -o loop,offset=$start_sector_bytes_boot $RPOS_IMAGE $RPI_BOOT_MNT |
|
echo "${USERNAME}:${PASSWORD_HASH}"| sudo tee $RPI_BOOT_MNT/userconf |
|
sudo touch $RPI_BOOT_MNT/ssh # enable SSH |
|
sudo umount $RPI_BOOT_MNT |
|
fi |
|
|
|
|
|
|
|
## Build Linux Kernel |
|
KERNEL_IMAGE=$WORK_DIR/linux-kernel/arch/arm64/boot/Image |
|
|
|
REBUILT_KERNEL=false |
|
if $REBUILD_KERNEL || $RECREATED_RPOS_IMAGE || ! [ -e $KERNEL_IMAGE ];then |
|
REBUILT_KERNEL=true |
|
# download linux kernel if necessary |
|
if ! [ -e $KERNEL_IMAGE ];then |
|
sudo rm -r linux-* # remove old files |
|
wget $KERNEL_URL -O linux-kernel.tar.xz |
|
tar xvJf linux-kernel.tar.xz |
|
rm linux-kernel.tar.xz |
|
mv linux-* linux-kernel # rename linux-VERSION to known name |
|
fi |
|
|
|
cd linux-kernel || exit 1 |
|
# create a .config file |
|
ARCH=arm64 CROSS_COMPILE=/bin/aarch64-linux-gnu- make defconfig |
|
|
|
# Use the kvm_guest config as the base defconfig, which is suitable for qemu |
|
ARCH=arm64 CROSS_COMPILE=/bin/aarch64-linux-gnu- make kvm_guest.config |
|
|
|
## manual kernel configuration DON'T DO THIS ON A MACHINE OF DIFFERENT ARCHITECTURE |
|
# make menu config |
|
sed -i 's/# CONFIG_DRM_QXL is not set/CONFIG_DRM_QXL=m/' .config |
|
sed -i 's/# CONFIG_FB_SIMPLE is not set/CONFIG_FB_SIMPLE=y/' .config |
|
sed -i 's/# CONFIG_FB_VIRTUAL is not set/CONFIG_FB_VIRTUAL=y/' .config |
|
|
|
# Build the kernel |
|
ARCH=arm64 CROSS_COMPILE=/bin/aarch64-linux-gnu- make -j8 |
|
|
|
## Install kernel modules into the guest filesystem |
|
# mount the second partition of the Raspberry Pi OS image (the root partition) |
|
sudo mount -o loop,offset=$start_sector_bytes_root $RPOS_IMAGE $RPI_ROOT_MNT |
|
# Install kernel modules into the guest filesystem |
|
ARCH=arm64 CROSS_COMPILE=/bin/aarch64-linux-gnu- sudo make modules_install INSTALL_MOD_PATH=$RPI_ROOT_MNT |
|
sudo umount $RPI_ROOT_MNT |
|
|
|
cd .. || exit 1 # return to parent directory |
|
fi |
|
|
|
|
|
sudo chown libvirt-qemu:kvm $RPOS_IMAGE |
|
|
|
|
|
|
|
## Run unregistered VM directly using qemu (CLI only) |
|
# qemu-system-aarch64 -machine virt -cpu cortex-a72 -smp 6 -m 4G \ |
|
# -kernel $KERNEL_IMAGE -append "root=/dev/vda2 rootfstype=ext4 rw panic=0 console=ttyAMA0" \ |
|
# -drive format=raw,file=$RPOS_IMAGE,if=none,id=hd0,cache=writeback \ |
|
# -device virtio-blk,drive=hd0,bootindex=0 \ |
|
# -netdev user,id=mynet,hostfwd=tcp::2222-:22 \ |
|
# -device virtio-net-pci,netdev=mynet \ |
|
# -monitor telnet:127.0.0.1:5555,server,nowait |
|
|
|
## Create registered VM for virt-manager (with graphics!) |
|
echo " |
|
<domain type='qemu'> |
|
<name>$VM_NAME</name> |
|
<memory unit='GiB'>4</memory> |
|
<vcpu placement='static'>6</vcpu> |
|
<os> |
|
<type arch='aarch64' machine='virt'>hvm</type> |
|
<kernel>$KERNEL_IMAGE</kernel> |
|
<cmdline>root=/dev/vda2 rootfstype=ext4 rw panic=0 console=ttyAMA0</cmdline> |
|
</os> |
|
<cpu mode='custom' match='exact'> |
|
<model>cortex-a72</model> |
|
</cpu> |
|
<devices> |
|
<disk type='file' device='disk'> |
|
<driver name='qemu' type='raw'/> |
|
<source file='$RPOS_IMAGE'/> |
|
<target dev='vda' bus='virtio'/> |
|
</disk> |
|
<interface type='network'> |
|
<source network='default'/> |
|
<model type='virtio'/> |
|
</interface> |
|
<filesystem type='mount' accessmode='mapped'> |
|
<source dir='$QBM_SOURCE'/> |
|
<target dir='$SHARED_FS_TAG'/> |
|
</filesystem> |
|
<serial type='pty'> |
|
<source path='/dev/pts/4'/> |
|
<target type='system-serial' port='0'> |
|
<model name='pl011'/> |
|
</target> |
|
<alias name='serial0'/> |
|
</serial> |
|
<controller type='usb' index='0' model='qemu-xhci' ports='15'> |
|
<address type='pci' domain='0x0000' bus='0x07' slot='0x00' function='0x0'/> |
|
</controller> |
|
<input type='mouse' bus='usb'/> |
|
<input type='keyboard' bus='usb'/> |
|
<graphics type='spice' autoport='yes'> |
|
<listen type='address'/> |
|
<image compression='off'/> |
|
<gl enable='no'/> |
|
</graphics> |
|
<audio id='1' type='none'/> |
|
<video> |
|
<model type='virtio' heads='1' primary='yes'/> |
|
</video> |
|
</devices> |
|
</domain> |
|
|
|
" > raspi_vm.config |
|
virsh define raspi_vm.config |
|
virsh start "$VM_NAME" |
|
|
|
|
|
|
|
BLACK='\033[0;30m' |
|
RED='\033[0;31m' |
|
GREEN='\033[0;32m' |
|
YELLOW='\033[0;33m' |
|
BLUE='\033[0;34m' |
|
MAGENTA='\033[0;35m' |
|
CYAN='\033[0;36m' |
|
WHITE='\033[0;37m' |
|
NC='\033[0m' # No Color |
|
echo -e "$YELLOW |
|
It is normal for the VM window to display while booting: |
|
'Guest has not initialized the display (yet).' |
|
|
|
Wait for a minute, after which the console log-in should display. |
|
In the virt-manager VM window, under the menu _View > Consoles > Serial 1_ you can switch to the text console while the graphics aren't running yet. |
|
|
|
$BLUE |
|
To make your VM's root filesystem take up the full disk space, run: |
|
$GREEN |
|
sudo raspi-config nonint do_expand_rootfs |
|
$NC" |
That error message comes from the
virsh
VM-management tool, but the root cause could be any previous part of the script failing, so look for error messages in the entire script's output and try to solve any errors you find.And of course, make sure you've installed all the prerequisites.
I've updated the prerequisites list after finding some dependencies that were missing.