Last active
March 11, 2025 22:31
-
-
Save charmoniumQ/64d991b0a92f0f62daa3a47eaea2593e to your computer and use it in GitHub Desktop.
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
#! /usr/bin/env nix-shell | |
#! nix-shell -i bash -p coreutils qemu parted e2fsprogs dosfstools debootstrap grub2 util-linux | |
set -e -x -o nounset | |
########### Notes ########### | |
# | |
# - You don't have to use Nix to run this script; just be sure to have the above packages installed. | |
# | |
# - [This Stack Overflow post](https://unix.stackexchange.com/questions/719367) | |
# implies that EFI does not work without extra firmware (e.g., OVMF) and | |
# emulated flash memory, but BIOS on GPT should. [This Gentoo | |
# Wiki](https://wiki.gentoo.org/wiki/QEMU#Preparation_of_a_bootable_disk_image_from_scratch) | |
# page suggests that BIOS on MBR will work. Their original scripts didn't | |
# work, and no combination of {(EFI, GPT), (BIOS, GPT), (BIOS, MBR)} x | |
# {Ubuntu, Debian} implemented in this script seems to work. | |
# | |
# - This script will call sudo. If I can get this version to work, I will | |
# consider modifying it to not requires super-user. | |
# | |
# - Invoke like `./qemu-img.sh 2>&1 | tee log` to get a debugging log. | |
########### Inputs ########### | |
QEMU_ARCH="${QEMU_ARCH:-x86_64}" | |
IMAGE="${IMAGE:-qemu-debian}" | |
IMAGE_SIZE="${QEMU_IMAGE_SIZE:-20G}" | |
DEBIAN_ARCH="${DEBIAN_ARCH:-amd64}" | |
DEBIAN_VARIANT="${DEBIAN_VARIANT:-debian}" | |
GRUB_MODE="${GRUB_MODE:-bios}" | |
PARTITION_TABLE="${PARTITION_TABLE:-mbr}" | |
ROOT_FS="${ROOT_FS:-ext2}" | |
HOSTNAME="${QEMU_HOSTNAME:-qemu-debian}" | |
########### Input validation ########### | |
if [[ "$DEBIAN_VARIANT" != "ubuntu" && "$DEBIAN_VARIANT" != "debian" ]]; then | |
echo "Invalid DEBIAN_VARIANT: Choose 'ubuntu' or 'debian'" | |
exit 1 | |
fi | |
if [[ "$GRUB_MODE" != "bios" && "$GRUB_MODE" != "efi" ]]; then | |
echo "Invalid GRUB_MODE: Choose 'bios' or 'efi'" | |
exit 1 | |
fi | |
if [[ "$PARTITION_TABLE" != "gpt" && "$PARTITION_TABLE" != "mbr" ]]; then | |
echo "Invalid PARTITION_TABLE: Choose 'gpt' or 'mbr'" | |
exit 1 | |
fi | |
if [ "$DEBIAN_VARIANT" = "ubuntu" ]; then | |
DEBIAN_MIRROR=http://us.archive.ubuntu.com/ubuntu/ | |
DEBIAN_COMPONENTS="main,restricted,universe,multiverse" | |
DEBIAN_VERSION=jammy | |
DEBIAN_COMPONENTS_SPACE=$(echo $DEBIAN_COMPONENTS | tr , ' ') | |
DEBIAN_SOURCES=" | |
deb $DEBIAN_MIRROR $DEBIAN_VERSION $DEBIAN_COMPONENTS_SPACE | |
deb $DEBIAN_MIRROR $DEBIAN_VERSION-updates $DEBIAN_COMPONENTS_SPACE | |
" | |
else | |
# Try docker run --rm debian:bookworm sh -c 'apt-get update && apt-get install -y netselect-apt && netselect-apt' | |
DEBIAN_MIRROR=http://ftp.us.debian.org/debian/ | |
DEBIAN_COMPONENTS="main,contrib,non-free,non-free-firmware" | |
DEBIAN_VERSION=bookworm | |
DEBIAN_COMPONENTS_SPACE=$(echo $DEBIAN_COMPONENTS | tr , ' ') | |
DEBIAN_SOURCES=" | |
deb $DEBIAN_MIRROR $DEBIAN_VERSION $DEBIAN_COMPONENTS_SPACE | |
deb $DEBIAN_MIRROR $DEBIAN_VERSION-updates $DEBIAN_COMPONENTS_SPACE | |
deb https://security.debian.org/debian-security $DEBIAN_VERSION-security $DEBIAN_COMPONENTS_SPACE | |
" | |
fi | |
########### Create QEMU image ########### | |
MOUNT_DIR="$(mktemp --directory)" | |
rm --force "${IMAGE}.raw" "${IMAGE}.qcow2" | |
qemu-img create -f raw "${IMAGE}.raw" "$IMAGE_SIZE" | |
LOOP_DEV=$(sudo losetup --show --find "${IMAGE}.raw") | |
########### Create disk partitions ########### | |
# See https://wiki.archlinux.org/title/Partitioning | |
# See https://wiki.archlinux.org/title/Parted | |
if [ "$PARTITION_TABLE" = gpt ] && [ "$GRUB_MODE" = bios ]; then | |
# https://unix.stackexchange.com/questions/719367/configure-grub-to-load-in-qemu | |
sudo parted --script "$LOOP_DEV" \ | |
mklabel gpt \ | |
mkpart non-fs '0%' 550MiB \ | |
set 1 bios_grub on \ | |
mkpart primary ext4 550MiB 100% \ | |
ROOT_PART="${LOOP_DEV}p2" | |
elif [ "$PARTITION_TABLE" = gpt ] && [ "$GRUB_MODE" = efi ]; then | |
# https://unix.stackexchange.com/a/719958/59973 | |
sudo parted --script "$LOOP_DEV" \ | |
mklabel gpt \ | |
mkpart efi fat32 '0%' 550MiB \ | |
set 1 esp on \ | |
mkpart primary ext4 550MiB 100% \ | |
BOOT_PART="${LOOP_DEV}p1" | |
ROOT_PART="${LOOP_DEV}p2" | |
elif [ "$PARTITION_TABLE" = mbr ] && [ "$GRUB_MODE" = bios ]; then | |
# https://wiki.gentoo.org/wiki/QEMU#Preparation_of_a_bootable_disk_image_from_scratch | |
sudo parted --script "$LOOP_DEV" \ | |
mklabel msdos \ | |
mkpart primary ext4 1MiB 100% \ | |
set 1 boot on \ | |
ROOT_PART="${LOOP_DEV}p1" | |
elif [ "$PARTITION_TABLE" = mbr ] && [ "$GRUB_MODE" = efi ]; then | |
echo "Not supported" | |
exit 1 | |
fi | |
if [ "${BOOT_PART:-}" ]; then | |
BOOT_UUID="blkid -s UUID -o value ${BOOT_PART}" | |
fi | |
ROOT_UUID="blkid -s UUID -o value ${ROOT_PART}" | |
sudo fdisk -l "$LOOP_DEV" | |
sudo partprobe "$LOOP_DEV" | |
sudo "mkfs.$ROOT_FS" -L root "$ROOT_PART" | |
########### Mount fs ########### | |
lsblk -o NAME,UUID,PARTUUID,LABEL,PARTLABEL,MOUNTPOINT | |
sudo mkdir --parents "$MOUNT_DIR" | |
sudo mount "$ROOT_PART" "$MOUNT_DIR" | |
if [ -n "${BOOT_PART:-}" ]; then | |
sudo mkdir --parents "$MOUNT_DIR/boot/efi" | |
sudo mount "$BOOT_PART" "$MOUNT_DIR/boot/efi" | |
fi | |
########### Run debootstrap ########### | |
if [ "$GRUB_MODE" = efi ]; then | |
if [ "$DEBIAN_ARCH" = amd64 ]; then | |
grub_target=x86_64-efi | |
else | |
grub_target="$DEBIAN_ARCH-efi" | |
fi | |
# sudo grub-install "--target=$grub_target" "--efi-directory=$MOUNT_DIR/boot/efi" --bootloader-id=debian "$LOOP_DEV" | |
grub_args="--target=$grub_target --efi-directory=/boot/efi --bootloader-id=debian" | |
grub_pkg="grub-$DEBIAN_ARCH-efi" | |
else | |
if [ "$DEBIAN_ARCH" = amd64 ]; then | |
grub_target=i386-pc | |
else | |
grub_target="$DEBIAN_ARCH-pc" | |
fi | |
# sudo grub-install "--target=$grub_target" "--boot-directory=$MOUNT_DIR/boot/grub" "$LOOP_DEV" | |
grub_args="--target=$grub_target --boot-directory=/boot/grub" | |
grub_pkg=grub-pc | |
fi | |
if [ "$DEBIAN_VARIANT" = ubuntu ]; then | |
linux_pkg=linux-image-generic | |
else | |
linux_pkg="linux-image-$DEBIAN_ARCH" | |
fi | |
mkdir --parents /tmp/debootstrap | |
sudo debootstrap \ | |
"--arch=$DEBIAN_ARCH" \ | |
"--include=$linux_pkg,$grub_pkg" \ | |
--cache-dir=/tmp/debootstrap \ | |
--variant=minbase \ | |
"--components=$DEBIAN_COMPONENTS" \ | |
"$DEBIAN_VERSION" "$MOUNT_DIR" "$DEBIAN_MIRROR" | |
########### Set up required files ########### | |
# Following https://wiki.debian.org/chroot | |
# Prevents dpkg from starting daemons in the chroot | |
# cat <<EOF | sudo tee "$MOUNT_DIR/usr/sbin/policy-rc.d" | |
# #!/bin/sh | |
# exit 101 | |
# EOF | |
# sudo chmod a+x "$MOUNT_DIR/usr/sbin/policy-rc.d" | |
# The ischroot command is buggy and does not detect that it is running in a chroot | |
sudo cp "${MOUNT_DIR}/bin/true" "${MOUNT_DIR}/usr/bin/ischroot" | |
# See https://wiki.archlinux.org/title/EFI_system_partition#Typical_mount_points | |
# See https://wiki.archlinux.org/title/Fstab#Identifying_file_systems | |
# I promise not to mount any drives with the same label in this QMEU VM. | |
if [ -n "${BOOT_UUID:-}" ]; then | |
cat <<EOF | sudo tee "$MOUNT_DIR/etc/fstab" | |
UUID=$BOOT_UUID /boot/efi vfat defaults 0 2 | |
EOF | |
fi | |
cat <<EOF | sudo tee --append "$MOUNT_DIR/etc/fstab" | |
UUID=$ROOT_UUID / $ROOT_FS errors=remount-ro 0 1 | |
EOF | |
echo "$HOSTNAME" | sudo tee "$MOUNT_DIR/etc/hostname" | |
cat <<EOF | sudo tee "$MOUNT_DIR/etc/hosts" | |
127.0.0.1 localhost $HOSTNAME | |
::1 localhost ip6-localhost ip6-loopback $HOSTNAME | |
ff02::1 ip6-allnodes | |
ff02::2 ip6-allrouters | |
EOF | |
echo "$DEBIAN_SOURCES" | sudo tee "$MOUNT_DIR/etc/apt/sources.list" | |
sudo cp /etc/resolv.conf "$MOUNT_DIR/etc/resolv.conf" | |
cat <<EOF | sudo tee "$MOUNT_DIR/chroot-install.sh" | |
#!/usr/bin/env bash | |
set -e -x | |
# Set up root user | |
echo "root:root" | chpasswd | |
# Install GRUB | |
grub-install $grub_args "$LOOP_DEV" | |
update-grub | |
ls / | |
ls /boot | |
# Debugging | |
grep menuentry /boot/grub/grub.cfg | |
grep vmlinuz /boot/grub/grub.cfg | |
grep initrd /boot/grub/grub.cfg | |
exit | |
EOF | |
sudo chmod +x "$MOUNT_DIR/chroot-install.sh" | |
########### Configure system inside chroot ########### | |
# See https://wiki.archlinux.org/title/Chroot#Using_chroot | |
# See https://superuser.com/questions/165116/mount-dev-proc-sys-in-a-chroot-environment | |
# See https://askubuntu.com/questions/1122975/difference-between-rbind-and-bind-in-mounting | |
# See https://superuser.com/a/111215/110096 | |
sudo mount -t proc /proc "$MOUNT_DIR/proc" | |
sudo mount -t sysfs /sys "$MOUNT_DIR/sys" | |
sudo mount --make-rslave --rbind /dev "$MOUNT_DIR/dev" | |
# sudo mount --make-rslave --rbind /run "$MOUNT_DIR/run" | |
sudo chroot "$MOUNT_DIR" /usr/bin/env "PATH=/bin:/sbin" /chroot-install.sh | |
sudo grep menuentry "$MOUNT_DIR/boot/grub/grub.cfg" | |
sudo grep vmlinuz "$MOUNT_DIR/boot/grub/grub.cfg" | |
sudo grep initrd "$MOUNT_DIR/boot/grub/grub.cfg" | |
########### Clean up ########### | |
sudo rm "$MOUNT_DIR/chroot-install.sh" | |
# sudo rm "$MOUNT_DIR/usr/sbin/policy-rc.d" | |
sudo rm "${MOUNT_DIR}/bin/true" "${MOUNT_DIR}/usr/bin/ischroot" | |
sudo umount --recursive "$MOUNT_DIR" | |
sudo losetup --detach "$LOOP_DEV" | |
rmdir "$MOUNT_DIR" | |
########### Run ########### | |
"qemu-system-$QEMU_ARCH" \ | |
-drive file="${IMAGE}.raw",format=raw \ | |
-boot order=d \ | |
-nographic \ | |
-serial mon:stdio | |
########### Fancier run ########### | |
# qemu-img convert -f raw -O qcow2 "${IMAGE}.raw" "${IMAGE}.qcow2" | |
# rm --force "${IMAGE}.raw" | |
# mkdir --parents shared | |
# "qemu-system-$QEMU_ARCH" \ | |
# -cpu host \ | |
# -enable-kvm \ | |
# -hda "${IMAGE}.qcow2" \ | |
# -virtfs "local,path=$PWD/shared,mount_tag=host0,security_model=mapped,id=host0" \ | |
# -boot d \ | |
# -nographic \ | |
# -serial mon:stdio \ | |
# -m 4G |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment