Last active
December 25, 2022 23:36
-
-
Save mjhennig/44e7588ceabd6ad0919039f6d481c4a6 to your computer and use it in GitHub Desktop.
Yet another wrapper for the debootstrap(8) command
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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