Skip to content

Instantly share code, notes, and snippets.

@mjhennig
Last active December 25, 2022 23:36
Show Gist options
  • Select an option

  • Save mjhennig/44e7588ceabd6ad0919039f6d481c4a6 to your computer and use it in GitHub Desktop.

Select an option

Save mjhennig/44e7588ceabd6ad0919039f6d481c4a6 to your computer and use it in GitHub Desktop.
Yet another wrapper for the debootstrap(8) command
#!/bin/sh
# vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 textwidth=80:
# Mathias J. Hennig wrote this script and it's manual page. As long as
# you retain this notice you can do whatever you want with this stuff.
# If we meet some day, and you think this stuff is worth it, you can buy
# me a beer in return.
# Ensure the script to abort on error
set -e
DI_BASENAME="`basename \"$0\"`"
DI_FORMAT="raw"
DI_INCLUDE="less"
DI_MIRROR="http://httpredir.debian.org/debian"
DI_RELEASE="stable"
DI_SIZE="4g"
DI_TEMPDIR="/tmp/$DI_BASENAME.$$"
DI_TRAP="trap - EXIT"
# Print the usage section of the CLI help message
di_usage() {
cat <<END
Usage: $DI_BASENAME [-f ...] [-i ...] [-s ...] \$target
$DI_BASENAME -h
$DI_BASENAME -f qcow2 -i vim,less -r stable debian.img
END
}
# Print the options section of the CLI help message
di_options() {
cat <<END
Options:
-f \$format
The image format to use. Translates directly to the -f option of the
qemu-img(1) executable. Defaults to "$DI_FORMAT".
-h Print this message and exit gracefully.
-i \$packages
A comma-separated list of packages to include. Translates to the -i
option of debootstrap(8). Multiple occurences are concatenated, and the
default "$DI_INCLUDE" is always included.
-m \$mirror
The APT source URL to fetch packages from. Used as the mirror option of
of debootstrap(8). Defaults to "$DI_MIRROR".
-r \$release
The release code or symbolic name of the release within the mirror. Used
as debootstrap(8) suite option. Defaults to "$DI_RELEASE".
-s \$size
The intended size of the image. Translates directly to the size option
of the qemu-img(1) create command. Defaults to "$DI_SIZE".
END
}
# Print the composed CLI help message
di_help() {
di_usage
echo
di_options
echo
}
# Echo commands before execution, optionally with super-user privileges (-s)
# or within the image chroot(8) directory (-c)
di_call() {
case "$1" in
-c) shift; set -- sudo chroot "$DI_TEMPDIR" "$@";;
-s) shift; set -- sudo "$@";;
esac
echo "\$" "$@"
"$@"
}
# Run commands when the script ends, in reversed definition order. Supports
# the -c and -s options analogous to di_call
di_trap() {
DI_TRAP="di_call $@; $DI_TRAP"
}
# Register cleanup commands to be scheduled with di_trap
trap "set +e; eval \"\$DI_TRAP\"" EXIT HUP INT QUIT PIPE TERM
# Option handling using standard shell builtins
while POSIXLY_CORRECT=1 getopts "f:hi:m:r:s" DI_OPTION; do
case "$DI_OPTION" in
f) shift; DI_FORMAT="$1";;
h) di_help; exit;;
i) shift; DI_INCLUDE="$DI_INCLUDE,$1";;
m) shift; DI_MIRROR="$1";;
r) shift; DI_RELEASE="$1";;
s) shift; DI_SIZE="$1";;
?) echo; di_help >&2; exit 1;;
esac
shift
done
# Fetch the image's target path from the remaining arguments
case $# in
1) DI_NAME="$1";;
0) echo "Missing image target path" >&2; di_usage >&2; exit 1;;
*) echo "Too many arguments" >&2; di_usage >&2; exit 1;;
esac
# Create the actual image file
di_call qemu-img create -f "$DI_FORMAT" "$DI_NAME" "$DI_SIZE"
# Ensure the NBD module is loaded
di_call -s modprobe nbd max_part=16
# Find a network block device to use with the image
for DI_DEVICE in /dev/nbd?; do
if di_call -s qemu-nbd -f "$DI_FORMAT" -c "$DI_DEVICE" "$DI_NAME"; then
di_trap -s qemu-nbd -d $DI_DEVICE
break
else
DI_DEVICE=
fi
done
# Abort in case no network block device worked out
if [ -z "$DI_DEVICE" ]; then
echo "Failed to connect /dev/nbd\* device" >&2
exit 2
fi
# Format the image (TODO: make this customizable)
di_call -s sfdisk "$DI_DEVICE" <<END
;
END
# Format the partition (TODO: make this customizable)
di_call -s mkfs.ext4 -L CORE -O ^metadata_csum "${DI_DEVICE}p1"
# Capture the partition's UUID for later reference
DI_UUID=`sudo blkid -s UUID -o value "${DI_DEVICE}p1"`
# Create a temporary directory for mounting & chrooting the partition, and
# ensure it is removed when the script ends
di_call mkdir "$DI_TEMPDIR"
di_trap rmdir "$DI_TEMPDIR"
# Mount the network block device partitiom to the directory and
# ensure it is unmounted when the script ents
di_call -s mount "${DI_DEVICE}p1" "$DI_TEMPDIR"
di_trap -s umount "$DI_TEMPDIR"
# The actual invocation of debootstrap, may require future extension as well
di_call -s debootstrap --include="$DI_INCLUDE" "$DI_RELEASE" "$DI_TEMPDIR" "$DI_MIRROR"
# Extend /etc/fstab to include the root file system
di_call -s tee -a "$DI_TEMPDIR/etc/fstab" <<END
UUID=$DI_UUID / ext4 errors=remount-ro 0 1
END
# Extend /etc/network/interfaces to include eth0 (TODO: make DHCP optional)
di_call -s tee -a "$DI_TEMPDIR/etc/network/interfaces" <<END
auto eth0
iface eth0 inet dhcp
END
# Bind the device index for use within chroot later
di_call -s mount --bind /dev "$DI_TEMPDIR/dev"
di_trap -s umount "$DI_TEMPDIR/dev"
# Bind the process FS for use within the chroot
di_call -c mount -t proc none /proc
di_trap -c umount /proc
# Bind the system FS for use within the chroot
di_call -c mount -t sysfs none /sys
di_trap -c umount /sys
# TODO: Avoid the creation of a root password entirely!
di_call -c passwd root <<END
changeme
changeme
END
# Manually ensure the UUID device mapping to be present, in order to allow for
# grub(8) to use UUIDs within the grub.cfg
di_call -s partx "$DI_DEVICE"
di_call -s ln -s "../../`basename \"$DI_DEVICE\"`p1" "/dev/disk/by-uuid/$DI_UUID"
di_trap -s rm "/dev/disk/by-uuid/$DI_UUID"
# The APT run requires a set of environment variables one cannot pass directly
# to chroot(8), hence the sudo(8) style is used here with some code duplication
di_call -s LANG=C DEBIAN_FRONTEND=noninteractive \
chroot "$DI_TEMPDIR" apt-get -y install linux-image-amd64 grub-pc
# Finally install and configure grub(8) with required moduled and support for
# the serial console (for initial login with KVM)
di_call -c grub-install "$DI_DEVICE" --modules "biosdisk part_msdos"
di_call -c sed -i 's/GRUB_CMDLINE_LINUX_DEFAULT="/\0console=ttyS0,115200 /' /etc/default/grub
di_call -c update-grub 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment