Skip to content

Instantly share code, notes, and snippets.

Last active March 21, 2024 06:13
Show Gist options
  • Save sigmaris/dc1883f782d1ff5d74252bebf852ec50 to your computer and use it in GitHub Desktop.
Save sigmaris/dc1883f782d1ff5d74252bebf852ec50 to your computer and use it in GitHub Desktop.
Automate the installation of Debian Buster on a x86_64 QEMU 4.0.0 VM hosted on macOS
#!/bin/bash -e
if [ "$(uname -s)" != "Darwin" ]
echo "This script is for building a Debian x86_64 image to use on MacOS"
exit 1
TEMP="$(mktemp -d build.XXXXX)"
cp preseed.cfg $TEMP
pushd $TEMP
AUTHORIZED_KEYS="$(ssh-add -L)"
if [ -n "$AUTHORIZED_KEYS" ]
echo "Pre-populating authorized_keys for image"
echo "$AUTHORIZED_KEYS" > authorized_keys
ROOT_PASSWORD="$(openssl rand -base64 18)"
echo "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv"
echo "==> Randomised root password is: $ROOT_PASSWORD <=="
echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
CRYPTED_PASSWORD="$(openssl passwd -1 -salt xyz $ROOT_PASSWORD)"
echo "Running simple webserver on port 4321 for host files..."
PYTHON_PID=$(sh -c 'echo $$ ; exec >/dev/null 2>&1 ; exec python -m SimpleHTTPServer 4321' &)
echo "Running netcat to capture syslogs..."
NC_PID=$(sh -c 'echo $$ ; exec > ../installer.log 2>&1 ; exec nc -ul 10514' &)
echo "Downloading Debian Buster x86_64 netboot installer..."
curl --location --output netboot.tar.gz
mkdir -p tftpserver
pushd tftpserver
tar xzvf ../netboot.tar.gz
echo "Customising network boot parameters..."
cat > debian-installer/amd64/pxelinux.cfg/default <<EOF
serial 0
prompt 0
default autoinst
label autoinst
kernel debian-installer/amd64/linux
append initrd=debian-installer/amd64/initrd.gz auto=true priority=critical passwd/root-password-crypted=$CRYPTED_PASSWORD DEBIAN_FRONTEND=text url= log_host= log_port=10514 --- console=ttyS0
echo "Creating disk image for Debian Buster x86_64..."
qemu-img create -f qcow2 ../debian.qcow 4G
echo "Running Debian Installer..."
qemu-system-x86_64 \
-machine accel=hvf \
-cpu host \
-hda ../debian.qcow \
-netdev user,id=net0,net=,hostname=bustervm,domainname=localdomain,tftp=tftpserver,bootfile=/pxelinux.0 \
-device e1000,netdev=net0,mac=52:54:98:76:54:32 \
-boot once=n \
-m 512 \
echo "Removing temporary directory $TEMP ..."
rm -rf $TEMP
echo "Cleaning up processes..."
kill $NC_PID
#### Contents of the preconfiguration file (for buster)
### Localization
# Preseeding only locale sets language, country and locale.
d-i debian-installer/locale string en_US
# Keyboard selection.
d-i keyboard-configuration/xkb-keymap select us
# d-i keyboard-configuration/toggle select No toggling
# netcfg will choose an interface that has link if possible. This makes it
# skip displaying a list if there is more than one interface.
d-i netcfg/choose_interface select auto
# Any hostname and domain names assigned from dhcp take precedence over
# values set here. However, setting the values still prevents the questions
# from being shown, even if values come from dhcp.
d-i netcfg/get_hostname string bustervm
d-i netcfg/get_domain string localdomain
### Mirror settings
# If you select ftp, the mirror/country string does not need to be set.
#d-i mirror/protocol string ftp
d-i mirror/country string manual
d-i mirror/http/hostname string
d-i mirror/http/directory string /debian
d-i mirror/http/proxy string
# Suite to install.
d-i mirror/suite string buster
### Account setup
# Skip creation of a root account (normal user account will be able to
# use sudo).
#d-i passwd/root-login boolean false
# Alternatively, to skip creation of a normal user account.
d-i passwd/make-user boolean false
### Clock and time zone setup
# Controls whether or not the hardware clock is set to UTC.
d-i clock-setup/utc boolean true
# You may set this to any valid setting for $TZ; see the contents of
# /usr/share/zoneinfo/ for valid values.
d-i time/zone string Europe/London
# Controls whether to use NTP to set the clock during the install
d-i clock-setup/ntp boolean true
# NTP server to use. The default is almost always fine here.
#d-i clock-setup/ntp-server string
### Partitioning
## Partitioning example
# Alternatively, you may specify a disk to partition. If the system has only
# one disk the installer will default to using that, but otherwise the device
# name must be given in traditional, non-devfs format (so e.g. /dev/sda
# and not e.g. /dev/discs/disc0/disc).
# For example, to use the first SCSI/SATA hard disk:
d-i partman-auto/disk string /dev/sda
# In addition, you'll need to specify the method to use.
# The presently available methods are:
# - regular: use the usual partition types for your architecture
# - lvm: use LVM to partition the disk
# - crypto: use LVM within an encrypted partition
d-i partman-auto/method string regular
# If one of the disks that are going to be automatically partitioned
# contains an old LVM configuration, the user will normally receive a
# warning. This can be preseeded away...
d-i partman-lvm/device_remove_lvm boolean true
# The same applies to pre-existing software RAID array:
d-i partman-md/device_remove_md boolean true
# And the same goes for the confirmation to write the lvm partitions.
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
# You can choose one of the three predefined partitioning recipes:
# - atomic: all files in one partition
# - home: separate /home partition
# - multi: separate /home, /var, and /tmp partitions
d-i partman-auto/choose_recipe select atomic
# This makes partman automatically partition without confirmation.
d-i partman-md/confirm boolean true
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
### Apt setup
# You can choose to install non-free and contrib software.
d-i apt-setup/non-free boolean true
d-i apt-setup/contrib boolean true
### Package selection
# Individual additional packages to install, customise this for your needs
d-i pkgsel/include string binfmt-support build-essential debian-keyring debootstrap git openssh-server qemu-user-static parted vim
### Boot loader installation
# This is fairly safe to set, it makes grub install automatically to the MBR
# if no other operating system is detected on the machine.
d-i grub-installer/only_debian boolean true
# This one makes grub-installer install to the MBR if it also finds some other
# OS, which is less safe as it might not be able to boot that other OS.
d-i grub-installer/with_other_os boolean true
# Due notably to potential USB sticks, the location of the MBR can not be
# determined safely in general, so this needs to be specified:
d-i grub-installer/bootdev string /dev/sda
# To install to the first device (assuming it is not a USB stick):
#d-i grub-installer/bootdev string default
### Finishing up the installation
# Avoid that last message about the install being complete.
d-i finish-install/reboot_in_progress note
# This is how to make the installer shutdown when finished, but not
# reboot into the installed system.
#d-i debian-installer/exit/halt boolean true
# This will power off the machine instead of just halting it.
d-i debian-installer/exit/poweroff boolean true
### Preseeding other packages
# Linux default command line:
grub-pc grub2/linux_cmdline_default string
# GRUB timeout; for internal use
grub-pc grub-pc/timeout string 1
#### Advanced options
### Running custom commands during the installation
# d-i preseeding is inherently not secure. Nothing in the installer checks
# for attempts at buffer overflows or other exploits of the values of a
# preconfiguration file like this one. Only use preconfiguration files from
# trusted locations! To drive that home, and because it's generally useful,
# here's a way to run any shell command you'd like inside the installer,
# automatically.
# This first command is run as early as possible, just after
# preseeding is read.
#d-i preseed/early_command string anna-install some-udeb
# This command is run immediately before the partitioner starts. It may be
# useful to apply dynamic partitioner preseeding that depends on the state
# of the disks (which may not be visible when preseed/early_command runs).
#d-i partman/early_command \
# string debconf-set partman-auto/disk "$(list-devices disk | head -n1)"
# This command is run just before the install finishes, but when there is
# still a usable /target directory. You can chroot to /target and use it
# directly, or use the apt-install and in-target commands to easily install
# packages and run commands in the target system.
d-i preseed/late_command string in-target mkdir /root/.ssh ; in-target chmod 0700 /root/.ssh ; in-target wget -O /root/.ssh/authorized_keys
Copy link

armajid commented Feb 2, 2023

Awesome blog post and script! Is there any chance this script and preseed configuration file be made open source with an open source license? There is a change I would like to make that would allow this script to work with Python 3.11.0 but since there is no open source license, I believe this script and preseed configuration file is technically proprietary code. Feel free to correct me if I'm wrong!

Copy link

sigmaris commented Feb 4, 2023

These can be used under the terms of the GPL 2.0, the preseed.cfg is derived from some snippets in the Debian manual which is GPL-licensed.

BTW, this post is ~4 years old, you might want to investigate other more recent methods of building VM images such as debos and vmdb2. They can be simpler and sometimes faster than running the installer with preseed configuration like this.

Copy link

Great work!! I forked the script here and did some updates for the current versions of osx:


Thanks for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment