Skip to content

Instantly share code, notes, and snippets.

@jgru
Last active April 3, 2022 12:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgru/62d405ccbec9a9eaaac02666beef3968 to your computer and use it in GitHub Desktop.
Save jgru/62d405ccbec9a9eaaac02666beef3968 to your computer and use it in GitHub Desktop.
Scripts using debootstrap to build Debian-based VMs

Build Debian-based VMs with debootstrap

This gist contains two scripts to simple quickly build UEFI-based or BIOS-based Debian/Ubuntu VMs stored as qcow2-files – mainly intended for using those with QEMU/KVM. This is done by relying on the mighty debootstrap and qemu-utils, especially qemu-nbd.

The scripts are basically updated version of K. Trzcinski’s work.

Dependencies

Ensure, that you have the following packages installed:

apt install -y qemu-utils \
               debootstrap \
               grub-pc-bin \
               ovmf # for UEFI-booting 

It is strongly recommended to rely on apt-cacher-ng to build up the build process:

apt install apt-cacher-ng 
# Eventually, check that apt-cacher-ng is running
ss -tln | grep 3142 

Usage

VM_NAME=debian-test
VM_FILE=${VM_NAME}.qcow2
RELEASE=bookworm 
USER=user
PW=pass123

# Create a qcow2
qemu-img create -f qcow2 $VM_FILE 4G

# Build UEFI-based VM 
./build-uefi-based-debian-vm.sh $VM_FILE $VM_NAME $RELEASE $USER $PW

# Run a UEFI-based via qemu
qemu-system-x86_64 -bios /usr/share/ovmf/OVMF.fd -net nic -net user -m 2G -smp cores=1,cpus=1 -drive file=$VM_FILE

Alternatively, build and run a BIOS-based VM like so:

# Build BIOS-based VM 
./build-bios-based-debian-vm.sh $VM_FILE $VM_NAME $RELEASE $USER $PW

# Run a BIOS-based via qemu
qemu-system-x86_64 -net nic -net user -m 2G -smp cores=1,cpus=1 -drive file=$VM_FILE
#!/bin/bash
clean_debian() {
[ "$MNT_DIR" != "" ] && chroot $MNT_DIR umount /proc/ /sys/ /dev/
sleep 1s
[ "$MNT_DIR" != "" ] && umount -l $MNT_DIR
sleep 1s
[ "$DISK" != "" ] && qemu-nbd -d $DISK
sleep 1s
[ "$MNT_DIR" != "" ] && rm -r $MNT_DIR
}
fail() {
clean_debian
echo ""
echo "FAILED: $1"
exit 1
}
cancel() {
fail "CTRL-C detected"
}
if [ $# -lt 5 ]
then
echo "author: K. Trzcinski updated for EFI by jgru"
echo "license: GPL"
echo "usage: $0 <image-file> <hostname> <release> <user> <password> [optional debootstrap args]" 1>&2
exit 1
fi
FILE=$1
HOSTNAME=$2
RELEASE=$3
USER=$4
PW=$5
shift 5
trap cancel INT
echo "Installing $RELEASE into $FILE..."
MNT_DIR=`mktemp`
rm $MNT_DIR
mkdir $MNT_DIR
DISK=
echo "Looking for nbd device..."
modprobe nbd max_part=16 || fail "failed to load nbd module into kernel"
for i in /dev/nbd*
do
if qemu-nbd -c $i $FILE
then
DISK=$i
break
fi
done
[ "$DISK" == "" ] && fail "no nbd device available"
echo "Connected $FILE to $DISK"
echo "Partitioning $DISK..."
parted -s -a optimal -- $DISK \
mklabel msdos \
mkpart primary ext4 1MiB 100% \
set 1 boot on
partprobe $DISK
sleep 2
echo "Creating root partition..."
#mkfs.ext4 -L BOOT ${DISK}p1 || fail "cannot create /boot ext4"
mkfs.ext4 -L ROOT ${DISK}p1 || fail "cannot create / ext4"
echo "Mounting root partition..."
mount ${DISK}p1 $MNT_DIR || fail "cannot mount /"
echo "Installing Debian $RELEASE..."
debootstrap --verbose --include=sudo,less,openssh-server $* $RELEASE $MNT_DIR http://localhost:3142/ftp2.de.debian.org/debian/ || fail "cannot install $RELEASE into $DISK"
echo "Configuring system..."
UUID=$(blkid ${DISK}p1 | cut -d" " -f2 | sed 's/"//g')
cat <<EOF > $MNT_DIR/etc/fstab
$UUID / ext4 errors=remount-ro 0 1
EOF
echo $HOSTNAME > $MNT_DIR/etc/hostname
cat <<EOF > $MNT_DIR/etc/hosts
127.0.0.1 localhost
127.0.1.1 $HOSTNAME
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF
cat <<EOF > $MNT_DIR/etc/network/interfaces
auto lo
iface lo inet loopback
auto ens3
iface ens3 inet dhcp
EOF
mount --bind /dev/ $MNT_DIR/dev || fail "cannot bind /dev"
chroot $MNT_DIR mount -t proc none /proc || fail "cannot mount /proc"
chroot $MNT_DIR mount -t sysfs none /sys || fail "cannot mount /sys"
# Install kernel and grub
LANG=C DEBIAN_FRONTEND=noninteractive chroot $MNT_DIR apt-get install -y -q linux-image-amd64 grub-pc || fail "cannot install linux-image and grub"
chroot $MNT_DIR grub-install $DISK --no-floppy || fail "cannot install grub"
chroot $MNT_DIR update-grub || fail "cannot update grub"
chroot $MNT_DIR adduser --disabled-password --gecos "" "$USER" || fail "cannot create used in chroot"
chroot $MNT_DIR /sbin/usermod -aG sudo $USER || fail "cannot add user to sudoers-group in chroot"
chroot $MNT_DIR chpasswd <<< "$USER:$PW" || fail "cannot change password in chroot"
echo "SUCCESS!"
clean_debian
exit 0
#!/bin/bash
clean_debian() {
[ "$MNT_DIR" != "" ] && chroot $MNT_DIR umount /proc/ /sys/ /dev/ #/boot/
sleep 1s
[ "$MNT_DIR" != "" ] && umount -l $MNT_DIR/boot/efi
[ "$MNT_DIR" != "" ] && umount -l $MNT_DIR
sleep 1s
[ "$DISK" != "" ] && qemu-nbd -d $DISK
sleep 1s
[ "$MNT_DIR" != "" ] && rm -r $MNT_DIR
}
fail() {
clean_debian
echo ""
echo "FAILED: $1"
exit 1
}
cancel() {
fail "CTRL-C detected"
}
if [ $# -lt 3 ]
then
echo "author: K. Trzcinski updated for EFI by jgru"
echo "license: GPL"
echo "usage: $0 <image-file> <hostname> <release> <user> <password> [optional debootstrap args]" 1>&2
exit 1
fi
FILE=$1
HOSTNAME=$2
RELEASE=$3
USER=$4
PW=$5
shift 5
trap cancel INT
echo "Installing $RELEASE into $FILE..."
MNT_DIR=`mktemp`
rm $MNT_DIR
mkdir $MNT_DIR
DISK=
echo "Looking for nbd device..."
modprobe nbd max_part=16 || fail "failed to load nbd module into kernel"
for i in /dev/nbd*
do
if qemu-nbd -c $i $FILE
then
DISK=$i
break
fi
done
[ "$DISK" == "" ] && fail "no nbd device available"
echo "Connected $FILE to $DISK"
echo "Partitioning $DISK..."
parted -s -a optimal -- $DISK \
mklabel gpt \
mkpart primary fat32 1MiB 270MiB \
mkpart primary ext4 1GiB -0 \
name 1 uefi \
name 2 root \
set 1 msftdata on || fail "failed to partition"
partprobe $DISK
sleep 2
echo "Creating partitions..."
mkfs -t fat -F 32 -n EFI ${DISK}p1 || fail "cannot create / efi"
mkfs -t ext4 -L root ${DISK}p2 || fail "cannot create / ext4"
echo "Mounting root partition..."
mount ${DISK}p2 $MNT_DIR || fail "cannot mount /"
echo "Installing Debian $RELEASE..."
debootstrap --verbose --include=sudo,less,,openssh-server $* $RELEASE $MNT_DIR http://localhost:3142/ftp2.de.debian.org/debian/ || fail "cannot install $RELEASE into $DISK"
echo "Configuring system..."
EFI_UUID=$(blkid ${DISK}p1 | cut -d" " -f2 | sed 's/"//g')
ROOT_UUID=$(blkid ${DISK}p2 | cut -d" " -f2 | sed 's/"//g')
cat <<EOF > $MNT_DIR/etc/fstab
$ROOT_UUID / ext4 errors=remount-ro 0 1
EOF
#$EFI_UUID /boot/efi vfat defaults 0 1
echo $HOSTNAME > $MNT_DIR/etc/hostname
cat <<EOF > $MNT_DIR/etc/hosts
127.0.0.1 localhost
127.0.1.1 $HOSTNAME
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF
cat <<EOF > $MNT_DIR/etc/network/interfaces
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
EOF
mount --bind /dev/ $MNT_DIR/dev || fail "cannot bind /dev"
[[ -d $MNT_DIR/boot/efi ]] || mkdir $MNT_DIR/boot/efi || fail "cannot create /boot/efi in chroot"
mount -t vfat ${DISK}p1 $MNT_DIR/boot/efi || fail "cannot /boot/efi-part"
chroot $MNT_DIR mount -t proc none /proc || fail "cannot mount /proc"
chroot $MNT_DIR mount -t sysfs none /sys || fail "cannot mount /sys"
# Install kernel and grub
LANG=C DEBIAN_FRONTEND=noninteractive chroot $MNT_DIR apt-get install -y -q linux-image-amd64 grub-efi-amd64 || fail "cannot install linux-image and grub"
chroot $MNT_DIR debconf-set-selections <<< "grub-efi-amd64 grub2/update_nvram boolean false" || fail "cannot update debconf"
chroot $MNT_DIR sed -i -e 's/^\\(GRUB_CMDLINE_LINUX="[^"]*\\)"$/\\1 console=ttyS0"/' /etc/default/grub || fail "cannot customize grub cmdline"
# Tell GRUB to use the serial console
chroot $MNT_DIR cat - >>/etc/default/grub <<EOF || fail "cannot modify /etc/default/grub"
GRUB_TERMINAL="serial"
GRUB_SERIAL_COMMAND="serial --unit=0 --speed=9600 --stop=1"
EOF
chroot $MNT_DIR grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=debian --recheck --no-nvram --removable || fail "cannot install grub"
chroot $MNT_DIR update-grub || fail "cannot update grub"
chroot $MNT_DIR systemctl enable serial-getty@ttyS0.service || fail "cannot activate serial via systemd"
chroot $MNT_DIR adduser --disabled-password --gecos "" "$USER" || fail "cannot create used in chroot"
chroot $MNT_DIR /sbin/usermod -aG sudo $USER || fail "cannot add user to sudoers-group in chroot"
chroot $MNT_DIR chpasswd <<< "$USER:$PW" || fail "cannot change password in chroot"
echo "Finishing grub installation..." # Needed to function correctly
grub-install $DISK --target=x86_64-efi --root-directory=$MNT_DIR || fail "cannot reinstall grub"
echo "SUCCESS!"
clean_debian
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment