Skip to content

Instantly share code, notes, and snippets.

@pdxjohnny
Last active October 6, 2020 17:52
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 pdxjohnny/a0dc3a58b4651dc3761bee65a198a80d to your computer and use it in GitHub Desktop.
Save pdxjohnny/a0dc3a58b4651dc3761bee65a198a80d to your computer and use it in GitHub Desktop.
Use bzImage of built kernel with fedora container
#!/usr/bin/env bash
#
# DESCRIPTION
#
# Run a virtual machine using the bzImage of a compiled Linux kernel
# with the base fedora container image as the file system.
#
# ENVIRONMENT VARIABLES
#
# This section details environment variables this script uses and shows
# an example for each of how you'd override the default using `export`.
#
# BZ_IMAGE (default: "${HOME}/linux/arch/x86/boot/bzImage")
# Build a kernel or give the path to a built bzImage via the BZ_IMAGE
# environment variable.
#
# $ export BZ_IMAGE="${HOME}/linux/arch/x86/boot/bzImage"
#
# QEMU (default: "qemu-system-x86_64")
# Path to the qemu binary that should be used. Do not set this to use the
# on installed within your PATH.
#
# $ export QEMU="${HOME}/qemu/build/x86_64-softmmu/qemu-system-x86_64"
#
# VM_DISK (default: "~/vm/image.qcow2")
# Path to the virtual disk image we'll be creating to store the guest file
# system.
#
# $ export VM_DISK="${HOME}/my-guest-image.qcow2"
#
# VM_DEV (default: "/dev/nbd0")
# Path to the virtual disk image we'll be creating to store the guest file
# system.
#
# $ export VM_DISK="${HOME}/my-guest-image.qcow2"
#
# CHROOT (default: "~/vm/chroot")
# Path to the directory we'll use to access the guest file system from the
# host when the guest is not running.
#
# $ export CHROOT="${HOME}/my-guest-fs"
#
# CMDLINE (default: "console=ttyS0 root=/dev/sda3 rw resume=/dev/sda2 init=/usr/bin/init.sh")
# The kernel command line for the kernel we're going to run.
#
# $ export CMDLINE="nokaslr console=ttyS0 root=/dev/sda3 rw resume=/dev/sda2 init=/usr/bin/init.sh"
#
# DOWNLOAD
#
# $ curl -o run-vm.sh -fSL https://gist.github.com/pdxjohnny/a0dc3a58b4651dc3761bee65a198a80d/raw/da2f456c9ecbb56bf84ee30c8c83a2762e86fb43/run-vm.sh
# $ echo "eb5c49fb0aff6b3293ad6f4bd8c7a9c32df97f40d3c8c4fe404b72c1e9c283b44e714be493ce88b5f22e5bb717b8f71d run-vm.sh" | sha384sum -c -
#
# USAGE
#
# You can make the script executable or run it with bash. The script
# requries that the user running it be able to run the sudo command.
#
# $ chmod 755 run-vm.sh
# $ ./run-vm.sh
#
# $ bash run-vm.sh
# Echo every command in this cript to standard error before running it.
set -x
# Exit non-zero from this script if we encounter any errors
set -e
# Virtual machine disk image where virtual machine filesystem is stored
VM_DISK=${VM_DISK:-"${HOME}/vm/image.qcow2"}
# Block device we use as an intermediary to mount the guest filesystem from host
VM_DEV=${VM_DEV:-"/dev/nbd0"}
# The directory where we mount the guest filesystem on the host for access and
# modification when not in use by the guest
CHROOT=${CHROOT:-"${HOME}/vm/chroot"}
# Linux kernel bzImage file to boot from
BZ_IMAGE=${BZ_IMAGE:-"${HOME}/linux/arch/x86/boot/bzImage"}
# Linux kernel command line
CMDLINE=${CMDLINE:-"console=ttyS0 root=/dev/sda3 rw resume=/dev/sda2 init=/usr/bin/init.sh"}
# Location of qemu binary to use
QEMU=${QEMU:-"qemu-system-x86_64"}
# Load the network block device kernel module
sudo modprobe nbd max_part=8
# Unmount the virtual disk image if it is currently mounted
sudo umount -R "${CHROOT}" || echo "Image was not mounted at ${CHROOT}"
# Disconnect the network block device
sudo qemu-nbd --disconnect "${VM_DEV}" || echo "Image was not connected as nbd"
mount_image() {
sudo qemu-nbd --connect="${VM_DEV}" "${VM_DISK}"
sudo mount "${VM_DEV}p3" "${CHROOT}"
sudo mount "${VM_DEV}p1" "${CHROOT}/boot"
}
unmount_image() {
sudo sync
sudo umount -R "${CHROOT}"
sudo qemu-nbd --disconnect "${VM_DEV}"
}
# Check if the block device we are going to use to mount the virtual disk image
# already exists
if [ -b "${VM_DEV}" ]; then
echo "VM_DEV already exists: ${VM_DEV}" >&2
# exit 1
fi
# Create the virtual disk image and populate it if it does not exist
if [ ! -f "${VM_DISK}" ]; then
mkdir -p "${CHROOT}"
mkdir -p "$(dirname ${VM_DISK})"
# Create the virtual disk image
qemu-img create -f qcow2 "${VM_DISK}" 20G
# Use the QEMU guest utils network block device utility to mount the virtual
# disk image as the $VM_DEV device
sudo qemu-nbd --connect="${VM_DEV}" "${VM_DISK}"
# Partition the block device
sudo parted "${VM_DEV}" << 'EOF'
mklabel gpt
mkpart primary fat32 1MiB 261MiB
set 1 esp on
mkpart primary linux-swap 261MiB 10491MiB
mkpart primary ext4 10491MiB 100%
EOF
# EFI partition
sudo mkfs.fat -F32 "${VM_DEV}p1"
# swap space
sudo mkswap "${VM_DEV}p2"
# Linux root partition
sudo mkfs.ext4 "${VM_DEV}p3"
sudo mount "${VM_DEV}p3" "${CHROOT}"
# Boot partiion
sudo mkdir "${CHROOT}/boot"
sudo mount "${VM_DEV}p1" "${CHROOT}/boot"
# Directory for docker image extraction utilities
utildir="$(mktemp -d)"
export PATH="${utildir}:${PATH}"
# https://github.com/genuinetools/reg
reg="${utildir}/reg"
export REG_SHA256="ade837fc5224acd8c34732bf54a94f579b47851cc6a7fd5899a98386b782e228"
curl -fSL "https://github.com/genuinetools/reg/releases/download/v0.16.1/reg-linux-amd64" \
-o "${reg}"
echo "${REG_SHA256} ${reg}" | sha256sum -c -
chmod a+x "${reg}"
# Image to download
IMAGE="fedora"
# Registry to download from
REGISTRY="registry.fedoraproject.org"
# SHA digest of image's top layer
DIGEST=$(reg manifest "${REGISTRY}/${IMAGE}" \
| grep digest \
| head -n 2 \
| tail -n 1 \
| sed -e 's/.*sha/sha/' -e 's/"//g')
# Download container and extract it to chroot
reg layer "${REGISTRY}/${IMAGE}@${DIGEST}" | sudo tar xz -C "${CHROOT}"
# Put our init script in the guest file system
# Find the path to bash to use as the interpreter
BASH_PATH=$(cd ${CHROOT} && find . -name bash | grep bin/ | head -n 1)
echo "#!${BASH_PATH:1}" | sudo tee "${CHROOT}/usr/bin/init.sh"
# Write out the rest of the script
sudo tee -a "${CHROOT}/usr/bin/init.sh" <<'EOF'
set -xe
if [[ -f /etc/environment ]]; then
source /etc/environment
fi
export PATH="/usr/sbin:/usr/bin:${PATH}"
if [[ ! -d /sys/firmware ]]; then
mkdir -p /sys
mount -t sysfs sysfs /sys -n
if [ -d /sys/firmware/efi/efivars ]; then
mount -t efivarfs efivarfs /sys/firmware/efi/efivars
fi
mkdir -p /proc
mount -t proc proc /proc -n
mkdir -p /run
mount -t tmpfs tmpfs /run -n
ulimit -u unlimited
/usr/sbin/swapon /dev/sda2
# Need vfat loaded to mount boot partition
# mount /dev/sda1 /boot
fi
cat /proc/cmdline
EOF
# Execute bash as the last thing in the init script
echo "exec bash" | sudo tee -a "${CHROOT}/usr/bin/init.sh"
# Make the script executable
sudo chmod 755 "${CHROOT}/usr/bin/init.sh"
# Consider doing something like this to add the vfat module to the guest image
# https://gist.github.com/pdxjohnny/aed9d495e606a816f4dfa51db6c2ebd4#install-kernel-to-chroot
# export INSTALL_MOD_PATH="${CHROOT}"
# export INSTALL_PATH=${INSTALL_MOD_PATH}/boot
# cd /path/to/kernel/build/dir
# make -j $(($(nproc)*4))
# make install
# make modules_install -j $(($(nproc)*4))
# Remove util dir
rm -rf "${utildir}"
# Unmount the virtual disk image so the virtual machine can use it
unmount_image
fi
# Mount the guest file system on the host when we exit the guest
trap mount_image EXIT
# Run the guest
"${QEMU}" \
-smp cpus=2 \
-m 8192M \
-kernel "${BZ_IMAGE}" \
-append "${CMDLINE}" \
-enable-kvm \
-nographic \
-cpu host \
-drive file="${VM_DISK}",index=0,media=disk,format=qcow2 $@
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment