assuming the kvm host is up and running already
this is almost a script
as root
guest=bookworm mkdir -p /data/guests/$guest/lala/ cd /data/guests/$guest/ qemu-img create -f qcow2 $guest.qcow2 25G modprobe nbd max_part=8 device=/dev/nbd0 qemu-nbd --connect=$device $guest.qcow2
manual partitioning
fdisk $device n ENTER ENTER ENTER +24G n ENTER ENTER ENTER ENTER t 2 82 (Linux swap) a 1 w
create swap and filesystems
mkswap ${device}p2
mkfs.ext4 ${device}p1
mount ${device}p1 lala/
from an ubuntu system
apt install debian-archive-keyring ls -lF /usr/share/keyrings/debian-archive-keyring.gpg # debian-keyring #ls -lF /usr/share/keyrings/debian-keyring.gpg
make sure you’ve got the relevant pubkey in there
gpg1 < /usr/share/keyrings/debian-archive-keyring.gpg
in case you don’t have the distro release you’re looking for, get it manually
apt purge debian-keyring debian-archive-keyring wget http://ftp.de.debian.org/debian/pool/main/d/debian-archive-keyring/debian-archive-keyring_2025.1_all.deb dpkg -i debian-archive-keyring_2025.1_all.deb
country_code=ru
cat /etc/apt/sources.list
curl -I http://ftp.$country_code.debian.org/debian/
df -hT | grep -E 'loop|nbd'
time debootstrap --arch=amd64 --force-check-gpg \
$guest lala http://ftp.$country_code.debian.org/debian/
# 2m17.567s
du -sh lala/
# 361M bookworm
cat lala/etc/fstab cat > lala/etc/fstab <<EOF # override /dev/vda1 / ext4 defaults,noatime,nodiratime 0 0 /dev/vda2 none swap sw 0 0 proc /proc proc defaults 0 0 tmpfs /tmp tmpfs rw,nodev,nosuid,noatime,relatime 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 EOF
cat lala/etc/hostname
echo $guest > lala/etc/hostname
echo "127.0.0.1 $guest" >> lala/etc/hosts
rmdir lala/etc/network/interfaces.d/
vi lala/etc/network/interfaces
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 192.168.122.9/24
gateway 192.168.122.1
cat lala/etc/resolv.conf
echo nameserver 192.168.122.1 > lala/etc/resolv.conf
we need a kernel in there as this is KVM, not XEN
mount -o bind /dev lala/dev mount -o bind /dev/pts lala/dev/pts mount -o bind /sys lala/sys mount -o bind /proc lala/proc chroot lala/ bash apt update apt install locales dpkg-reconfigure locales ==> en_US.UTF-8 perl -e exit apt install mlocate net-tools openssh-server manpages man-db # ifupdown apt install linux-image-cloud-amd64 # btrfs-progs # linux-image-kvm # linux-image-amd64
those symlinks should have been created by the linux-image package already
ls -lF /vmlinuz /initrd.img
make sure whatever additional module you need at boot-time is in there
#grep -i btrfs /boot/config-* grep -i virtio_console /boot/config-* cd /boot/ for f in initrd.img-*; do $f $f.dist; done cd /etc/initramfs-tools/ cp -pi modules modules.dist vi modules #btrfs virtio_console update-initramfs -k all -u apt install initramfs-tools #lsinitramfs /boot/initrd.img-* | grep -i btrfs lsinitramfs /boot/initrd.img-* | grep -i console
make sure the serial console prompt goes serial
systemctl list-unit-files | grep tty systemctl disable getty@tty1.service systemctl enable serial-getty@tty1.service ls -lF /etc/systemd/system/getty.target.wants/
eventually reach the guest console without a password
passwd -d root
ready to leave the chroot
^D
now setup the kvm guest boot-loader and kernel-time serial console
ls -lh /usr/lib/syslinux/mbr/mbr.bin
dd if=/usr/lib/syslinux/mbr/mbr.bin of=$device
mkdir lala/boot/syslinux/
extlinux --install lala/boot/syslinux --device ${device}p1
cp -f /usr/lib/syslinux/modules/bios/mboot.c32 lala/boot/syslinux/
cp -f /usr/lib/syslinux/modules/bios/libcom32.c32 lala/boot/syslinux/
vi lala/boot/syslinux/syslinux.cfg
serial 0 115200
console 1
nohalt 1
default linux
prompt 1
timeout 50
label linux
linux /vmlinuz
initrd /initrd.img
append root=/dev/vda1 ro console=ttyS0,115200n8 net.ifnames=0 biosdevname=0 mitigations=off
umount -R lala/
rmdir lala/
#btrfsck ${device}p1
fsck.ext4 ${device}p1
#losetup --detach $device
qemu-nbd -d $device
ls -lhF $guest.qcow2
du -sh $guest.qcow2
# 1.5G bookworm
ls -lhF /usr/bin/qemu-system-x86_64
cat > $guest.xml <<EOF
<domain type='kvm'>
<name>$guest</name>
<memory unit='GiB'>1</memory>
<currentMemory unit='GiB'>1</currentMemory>
<vcpu placement='static'>1</vcpu>
<os>
<type arch='x86_64' machine='q35'>hvm</type>
<boot dev='hd'/>
</os>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/data/guests/$guest/$guest.qcow2'/>
<target dev='vda' bus='virtio'/>
</disk>
<interface type='bridge'>
<source bridge='virbr0'/>
<model type='virtio'/>
</interface>
<serial type='pty'>
<target type='isa-serial' port='0'>
<model name='isa-serial'/>
</target>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
</console>
</devices>
<features>
<acpi/>
</features>
<pm>
<suspend-to-disk enabled='yes'/>
<suspend-to-mem enabled='yes'/>
</pm>
</domain>
EOF
and in case you need to debug booting on the crappy vga console
– (within devices section)
<graphics type='vnc' port='5900' sharePolicy='allow-exclusive'>
<listen type='address' address='127.0.0.1'/>
</graphics>
virsh list --all virsh create $guest.xml --console ^]
maybe add your SSH pubkey in there and we’re good
ping opendns.com poweroff
as root
virsh list --all | grep $guest # empty cd /data/guests/ time nice tar cSf /data/templates/$guest.tar $guest/ ls -lh /data/templates/$guest.tar # 1,1G
eventually make it available for GNS3
grub-install: error: unknown filesystem.
==> you need to see the partition, use losetup for that
while trying to launch the guest as user
error: Failed to create domain from bookworm.xml error: internal error: /usr/lib/qemu/qemu-bridge-helper --use-vnet --br=virbr0 --fd=31: failed to communicate with bridge helper: Transport endpoint is not connected stderr=failed to parse default acl file `/etc/qemu/bridge.conf'
==> I don’t remember how I solved that one
https://stackoverflow.com/questions/21596384/cannot-disable-systemd-serial-getty-service
https://unix.stackexchange.com/questions/447070/serial-getty-error-messages