This is a trusted virtualization environment. Which is to say, the script sets up assuming that everything in your home directory can broadly be accessed and touched. The SELinux policy changes are also not specific - we will be allowing any KVM process to access some of your home files including your SSH keys. If you want to do something more specific or more secure, or you want to play around with untrusted VMs and settings, then you'll need a different configuration.
You need NetworkManager configured to use systemd-resolved.
/etc/NetworkManager/NetworkManager.conf resolves systemd-resolved.
virsh -c qemu:///system net-edit default Add the line: <domain name='default.libvirt' localOnly='yes' />
A complete confirguraiton will look something like:
<network xmlns:dnsmasq='http://libvirt.org/schemas/network/dnsmasq/1.0'>
<name>default</name>
<forward mode='nat'/>
<bridge name='virbr0' stp='on' delay='0'/>
<mac address='52:54:00:a0:27:2f'/>
<domain name='default.libvirt' localOnly='yes'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.10' end='192.168.122.200'/>
</dhcp>
</ip>
<dnsmasq:options>
<!-- this configuration is for handling an internal DNS server at this statically assigned address -->
<dnsmasq:option value="server=/default.libvirt/192.168.122.2"/>
</dnsmasq:options>
</network>The dnsmasq options ensure that changing hostnames are reflected into dnsmasq by libvirt.
virsh -c qemu:///system net-destroy default
virsh -c qemu:///system net-start default You need to edit /etc/NetworkManager/NetworkManager.conf to look something like:
[main]
#plugins=ifcfg-rh
plugins=keyfile
dns=systemd-resolved
[keyfile]
unmanaged-devices=interface-name:virbr*This keeps it away from the libvirt bridge interface.
mkdir /etc/systemd/resolved.conf.d
cat << EOF > /etc/systemd/resolved.conf.d/libvirt-redirect.conf
[Resolve]
DNS=192.168.122.1%virbr0#default.libvirt
EOF
systemctl restart systemd-resolvedThis configuration defines requests for the DNSMasq libvirt bridge via the sepcial default.libvirt subdomain to be routed to dnsmasq by systemd-resolved. This is necessary because that bridge is going to route traffic back to systemd-resolved as well.
cat << EOF > /etc/qemu/bridge.conf
allow virbr0
EOF
chown root:root /etc/qemu/bridge.conf
chmod 0644 /etc/qemu/bridge.conf
chmod u+s /usr/lib/qemu/qemu-bridge-helper/usr/share/swtpm/swtpm-create-user-config-files
sudo aa-complain /usr/bin/swtpm
AppArmor profile is broken on Ubuntu 22.04
The following policy enables the virtual machines we launch to touch the home directory on the host.
Compile and install the host SELinux policy:
checkmodule -M -m -o kvmboot.mod kvmboot.te
semodule_package -o kvmboot.pp -m kvmboot.mod
sudo semodule -i kvmboot.ppThis policy grants common sense access which allows read-everything but prevents updates to config files and other non-userdata files.
function libvirt_default_pool() {
virsh -c qemu:///session pool-dumpxml default | xmlstarlet sel -t -m '//path' -v . -n
}./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-2k19/virtio 2k19
./prepare-windows-iso \
--add-boot-drivers generated/virtio-2k19 \
--add-drivers generated/virtio-2k19 \
--add-drivers downloaded/cloudbase-init \
--add-drivers win-common/extra \
--add-drivers win-common/cloudbase \
--add-drivers win-2k19-server-standard/extra \
downloaded/17763.737.190906-2324.rs5_release_svc_refresh_SERVER_EVAL_x64FRE_en-us_1.iso \
$(libvirt_default_pool)/win-2k19-Unattended-Virtio.iso \
win-2k19-server-standard/autounattend.xmlThen launch to install the base image:
./kvmboot --efi --windows --installer win-2k19-Unattended-Virtio.iso Win2k19-BaseThen spawn a cloud image to test with:
./kvmboot --efi --windows lci.Win2k19-Base.root.qcow2 t1./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-2k22/virtio 2k22
./prepare-windows-iso \
--add-boot-drivers generated/virtio-2k22 \
--add-drivers generated/virtio-2k22 \
--add-drivers downloaded/cloudbase-init \
--add-drivers win-common/extra \
--add-drivers win-common/cloudbase \
--add-drivers win-2k22-server-standard/extra \
downloaded/SERVER_EVAL_x64FRE_en-us.iso \
$(libvirt_default_pool)/win-2k22-Unattended-Virtio.iso \
win-2k22-server-standard/autounattend.xmlThen launch to install the base image:
./kvmboot --efi --windows --installer win-2k22-Unattended-Virtio.iso Win2k22-BaseThen spawn a cloud image to test with:
./kvmboot --efi --windows lci.Win2k22-Base.root.qcow2 t1./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-w10/virtio w10
./prepare-windows-iso \
--add-boot-drivers generated/virtio-w10 \
--add-drivers generated/virtio-w10 \
--add-drivers downloaded/cloudbase-init \
--add-drivers win-common/extra \
--add-drivers win-common/cloudbase \
--add-drivers win-10-image/extra \
downloaded/Win10_20H2_v2_English_x64.iso \
$(libvirt_default_pool)/win-10-unattended-virtio-image.iso \
win-10-image/autounattend.xmlThen launch to install the base image:
./kvmboot --efi --windows --installer win-10-unattended-virtio-image.iso win10-baseThen spawn a cloud image to test with:
./kvmboot --efi --windows lci.win10-base.root.qcow2 t1./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-w10/virtio w10
./prepare-windows-iso \
--add-boot-drivers generated/virtio-w10 \
--add-drivers generated/virtio-w10 \
--add-drivers downloaded/cloudbase-init \
--add-drivers win-common/extra \
--add-drivers win-common/cloudbase \
--add-drivers win-10-image-updated/extra \
downloaded/Win10_20H2_v2_English_x64.iso \
$(libvirt_default_pool)/win-10-unattended-virtio-image-updated.iso \
win-10-image-updated/autounattend.xmlThen launch to install the base image:
./kvmboot --efi --windows --installer win-10-unattended-virtio-image-updated.iso win10-baseThen spawn a cloud image to test with:
./kvmboot --efi --windows lci.win10-base.root.qcow2 t1This version skips the cloudbase-init step, and leaves you with an image which provisions once the user sets up an account.
./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-w10/virtio w10
./prepare-windows-iso \
--add-boot-drivers generated/virtio-w10 \
--add-drivers generated/virtio-w10 \
--add-drivers win-common/extra \
--add-drivers win-10-user/extra \
downloaded/Win10_20H2_v2_English_x64.iso \
$(libvirt_default_pool)/win-10-unattended-virtio-user.iso \
win-10-user/autounattend.xmlThe image that comes up should be able to be SSH'd into Powershell using your default SSH key. The user will have your name but be administrator.
Then launch to install the base image:
./kvmboot --efi --windows --installer win-10-unattended-virtio-user.iso win10-userThis version skips the cloudbase-init step, and leaves you with an image which provisions once the user sets up an account.
./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-w11/virtio w11
./prepare-windows-iso \
--add-boot-drivers generated/virtio-w11 \
--add-drivers generated/virtio-w11 \
--add-drivers win-common/extra \
--add-drivers win-11-user/extra \
downloaded/Win11_23H2_English_x64v2.iso \
$(libvirt_default_pool)/win-11-unattended-virtio-user.iso \
win-11-user/autounattend.xmlThe image that comes up should be able to be SSH'd into Powershell using your default SSH key. The user will have your name but be administrator.
Then launch to install the base image:
./kvmboot --efi --windows --installer win-11-unattended-virtio-user.iso win11-base./prepare-virtio-driver-tree --skip-json downloaded/virtio-win-0.1.171.iso generated/virtio-w7/virtio w7
./prepare-windows-iso \
--add-boot-drivers generated/virtio-w7 \
--add-drivers generated/virtio-w7 \
--add-drivers downloaded/cloudbase-init \
--add-drivers win-common/extra \
--add-drivers win-common/cloudbase \
--add-drivers win-7-image/extra \
downloaded/en_windows_7_professional_with_sp1_vl_build_x64_dvd_u_677791.iso \
$(libvirt_default_pool)/win-7-unattended-virtio-image-updated.iso \
win-7-image/autounattend.xmlThen launch to install the base image:
./kvmboot --cpus 1 --efi --windows --installer win-7-unattended-virtio-image-updated.iso win7-baseNote: For some reason Windows can't hack a multi-core install. This image will also fail with a driver signing problem which doesn't have a clean solution in Linux land. You need to F8 around and disable enforcement until updates apply.
Then spawn a cloud image to test with:
./kvmboot --efi --windows lci.win7-base.root.qcow2 t1Very similar to Windows 7:
# This isn't a typo - to get Win 8 to install, we need to load the Win 7 drivers for storage.
./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-w7/virtio w7
./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-w10/virtio w10
./prepare-windows-iso \
--add-boot-drivers generated/virtio-w7 \
--add-drivers generated/virtio-w10 \
--add-drivers generated/virtio-w10 \
--add-drivers downloaded/cloudbase-init \
--add-drivers win-common/extra \
--add-drivers win-common/cloudbase \
--add-drivers win-8-image/extra \
downloaded/en_windows_8_1_x64_dvd_2707217.iso \
$(libvirt_default_pool)/win-8-unattended-virtio-image-updated.iso \
win-8-image/autounattend.xmlThen launch to install the base image:
./kvmboot --efi --windows --installer win-8-unattended-virtio-image-updated.iso win8-base./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-w7/virtio w7
./prepare-virtio-driver-tree downloaded/virtio-win.iso generated/virtio-w10/virtio w10
./prepare-windows-iso \
--add-boot-drivers generated/virtio-w7 \
--add-drivers generated/virtio-w10 \
--add-drivers downloaded/cloudbase-init \
--add-drivers win-common/extra \
--add-drivers win-common/cloudbase \
--add-drivers win-8-user/extra \
downloaded/en_windows_8_1_x64_dvd_2707217.iso \
$(libvirt_default_pool)/win-8-unattended-virtio-user-updated.iso \
win-8-user/autounattend.xml
Then launch to install the base image:
./kvmboot --efi --windows --installer win-8-unattended-virtio-user-updated.iso win8-userRun the following to build an autoinstalling Ubuntu 24.04 image. The reason to do this over using the cloud-init images is to get a ZFS filesystem.
./prepare-ubuntu-iso.sh downloaded/ubuntu-24.04-desktop-amd64.iso \
$(libvirt_default_pool)/ubuntu-24.04-unattended-virtio-user.iso \
ubuntu-24.04-desktop/autoinstall.yamlNote: the ZFS encryption password is hardcoded as "defaultpassword" and should be changed once you launch the image.
Then launch to install:
./kvmboot --efi --video --installer ubuntu-24.04-unattended-virtio-user.iso ubuntu-desktopRun the following to build an autoinstalling Ubuntu 24.04 image. The reason to do this over using the cloud-init images is to get a ZFS filesystem. The resultant autoinstall is setup with cloud-init.
./prepare-ubuntu-iso.sh ~/opt/iso/ubuntu-24.04.3-live-server-amd64.iso \
$(libvirt_default_pool)/ubuntu-24.04-unattended-virtio-server.iso \
ubuntu-24.04-server/autoinstall.yaml
No encryption is enabled on this image - it is intended to be run as a VM.
Then launch to install:
./kvmboot --efi --video --installer ubuntu-24.04-unattended-virtio-server.iso ubuntu-serverRun the following to build an autoinstalling Fedora 41 Cinnamon Desktop linux, which is initialized with an encrypted filesystem and cloud init.
This needs a network installer image e.g. https://download.fedoraproject.org/pub/fedora/linux/releases/42/Everything/x86_64/iso/Fedora-Everything-netinst-x86_64-42-1.1.iso
./prepare-fedora-iso.sh downloaded/Fedora-Everything-netinst-x86_64-42-1.1.iso \
$(libvirt_default_pool)/fedora-42-cinnamon-autoinstall.iso \
fedora-cinnamon-desktop/ks.cfgThen launch to install:
./kvmboot --efi --video --installer fedora-42-cinnamon-autoinstall.iso fedora-desktopConvert the image file to qcow2 file if you need to (highly recommend: 3.4GB -> 400mb with compression):
qemu-img convert -c -p -O qcow2 $(libvirt_pool Downloads)/ubuntu-core-24-amd64.img \
$(libvirt_pool iso)/ubuntu-core-24-amd64.qcow2./kvmboot --efi --video ubuntu-core-24-amd64.qcow2 ubuntu-coreThis can be done by inserting a file into C:\ProgramData\ssh\administrators_authorized_keys
For the user without internet access, and without a WSUS/SCCM server, it is apparently almost impossible to handle optional Windows components. The only practical solution is to install ALL of them on a sample Windows image, and use that as the source for the rest of them:
Get-WindowsOptionalFeature -Online | Select-Object FeatureName | ForEach-Object {
Enable-WindowsOptionalFeature -Online -FeatureName $_.FeatureName -All } Then copy the disk image (or just C:\Windows) to your offline environment.
msiexec /i CloudbaseInitSetup_x64.msi /qn /l*v log.txt CLOUDBASEINITCONFFOLDER="C:\" LOGGINGSERIALPORTNAME="COM1" BINFOLDER="C:\bin" LOGFOLDER="C:\log" USERNAME="admin1" INJECTMETADATAPASSWORD="TRUE" USERGROUPS="Administrators" LOGGINGSERIALPORTNAME="COM2" LOCALSCRIPTSFOLDER="C:\localscripts"
There are also MAAS related parameters that you can define. Here is the list: MAASMETADATAURL, MAASOAUTHCONSUMERKEY, MAASOAUTHCONSUMERSECRET, MAASOAUTHTOKENKEY, MAASOAUTHTOKENSECRET.
Note: for some reason this doesn't launch with Start-Process in Powershell.
cd C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf
C:\Windows\System32\sysprep\sysprep.exe /generalize /oobe /unattend:Unattend.xml