slight restructure, added image creation hooks support

This commit is contained in:
Andreas Grapentin 2019-03-16 22:20:33 +01:00
parent 01052d0363
commit bc7a570477
No known key found for this signature in database
GPG Key ID: 7171986E4B745536
4 changed files with 189 additions and 67 deletions

10
README

@ -27,8 +27,16 @@ arguments to pvmbootstrap.sh:
-s size -- set the size of the created VM image (default: 64G) -s size -- set the size of the created VM image (default: 64G)
-M mirror -- set the mirror to fetch packages from -M mirror -- set the mirror to fetch packages from
(default: https://repo.parabola.nu/$repo/os/$arch) (default: https://repo.parabola.nu/$repo/os/$arch)
-H hook -- add a confiuration hook to be executed. this can be the path to
a shell script or one of the existing hooks described below
the created machine images should be bootable using pvmboot.sh The creation hooks currently supported are:
ethernet-dhcp:
this hook will setup ethernet in the VM by enabling systemd-resolved and
symlinking /etc/resolv.conf properly, as well as creating and enabling a
netctl configuration based on the ethernet-dhcp example.
virtual machine boot virtual machine boot
-------------------- --------------------

@ -0,0 +1,13 @@
#!/bin/bash
set -e
# setup systemd-resolved
systemctl start systemd-resolved.service
systemctl enable systemd-resolved.service
ln -sf /var/run/systemd/resolve/resolv.conf /etc/resolv.conf
# setup netctl for ethernet-dhcp
cp /etc/netctl/examples/ethernet-dhcp /etc/netctl/
netctl start ethernet-dhcp
netctl enable ethernet-dhcp

@ -124,12 +124,11 @@ pvm_guess_qemu_args() {
qemu_args+=( qemu_args+=(
-machine virt -machine virt
-m 2G -m 2G
-kernel "$workdir"/zImage -kernel "$workdir"/vmlinuz-linux-libre
-initrd "$workdir"/initramfs-linux.img -initrd "$workdir"/initramfs-linux-libre.img
-append "console=tty0 console=ttyAMA0 rw root=/dev/vda3" -append "console=tty0 console=ttyAMA0 rw root=/dev/vda3"
-drive "if=none,file=$1,format=raw,id=hd" -drive "if=none,file=$1,format=raw,id=hd"
-device "virtio-blk-device,drive=hd" -device "virtio-blk-device,drive=hd"
-device virtio-gpu-device
-netdev "user,id=mynet" -netdev "user,id=mynet"
-device "virtio-net-device,netdev=mynet") ;; -device "virtio-net-device,netdev=mynet") ;;
riscv64) riscv64)

@ -22,7 +22,24 @@
. "$(librelib messages)" . "$(librelib messages)"
usage() { usage() {
print "usage: %s [-h] [-s size] [-M mirror] filename arch" "${0##*/}" print "usage: %s [-h] [-s size] [-M mirror] [-H hook...] filename arch" "${0##*/}"
echo
prose " this script produces preconfigured parabola GNU/Linux-libre virtual
machine images, to be started using the accompanying pvmboot.sh
script. The created image is placed at the specified path."
echo
prose " the target architecture of the virtual machine image can be one of the
officially supported i686, x86_64 and armv7h, as well as one of the
unofficial ports for ppc64le and riscv64."
echo
prose " the creation of the virtual machine is configurable in three ways. First,
the size of the image can be configured with the -s switch, whose
value is passed verbatim to qemu-img create (default 64G). Second,
the mirror to load packages from can be configured with the -M switch,
wich is necessary for the unofficial ports, whose packages are not on
the main mirrors. Lastly, the -H switch can be passed multiple times to
specify post-install hooks to be run in the virtual machine for
install finalization. See src/hooks/ for examples."
echo echo
echo "this script is developed as part of parabola-vmbootstrap." echo "this script is developed as part of parabola-vmbootstrap."
} }
@ -40,17 +57,21 @@ pvm_native_arch() {
pvm_bootstrap() { pvm_bootstrap() {
msg "%s: starting image creation for %s" "$file" "$arch" msg "%s: starting image creation for %s" "$file" "$arch"
# create the raw image file
qemu-img create -f raw "$file" "$size" || return qemu-img create -f raw "$file" "$size" || return
# prepare for cleanup
trap 'pvm_cleanup' INT TERM RETURN trap 'pvm_cleanup' INT TERM RETURN
# mount the virtual disk
local workdir loopdev local workdir loopdev
workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return workdir="$(mktemp -d -t pvm-rootfs-XXXXXXXXXX)" || return
loopdev="$(sudo losetup -fLP --show "$file")" || return loopdev="$(sudo losetup -fLP --show "$file")" || return
# clean out the first 8MiB
sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return sudo dd if=/dev/zero of="$loopdev" bs=1M count=8 || return
# partition and mount # partition
case "$arch" in case "$arch" in
i686|x86_64) i686|x86_64)
sudo parted -s "$loopdev" \ sudo parted -s "$loopdev" \
@ -59,57 +80,61 @@ pvm_bootstrap() {
set 1 bios_grub on \ set 1 bios_grub on \
mkpart primary ext2 2MiB 514MiB \ mkpart primary ext2 2MiB 514MiB \
mkpart primary linux-swap 514MiB 4610MiB \ mkpart primary linux-swap 514MiB 4610MiB \
mkpart primary ext4 4610MiB 100% || return mkpart primary ext4 4610MiB 100% || return ;;
sudo partprobe "$loopdev"
sudo mkfs.ext2 "$loopdev"p2 || return
sudo mkswap "$loopdev"p3 || return
sudo mkfs.ext4 "$loopdev"p4 || return
sudo mount "$loopdev"p4 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p2 "$workdir"/boot || return
;;
ppc64le|riscv64)
sudo parted -s "$loopdev" \
mklabel gpt \
mkpart primary ext2 1MiB 513MiB \
set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return
sudo partprobe "$loopdev"
sudo mkfs.ext2 "$loopdev"p1 || return
sudo mkswap "$loopdev"p2 || return
sudo mkfs.ext4 "$loopdev"p3 || return
sudo mount "$loopdev"p3 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p1 "$workdir"/boot || return
;;
armv7h) armv7h)
sudo parted -s "$loopdev" \ sudo parted -s "$loopdev" \
mklabel gpt \ mklabel gpt \
mkpart ESP fat32 1MiB 513MiB \ mkpart ESP fat32 1MiB 513MiB \
set 1 boot on \ set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \ mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return mkpart primary ext4 4609MiB 100% || return ;;
ppc64le|riscv64)
sudo parted -s "$loopdev" \
mklabel gpt \
mkpart primary ext2 1MiB 513MiB \
set 1 boot on \
mkpart primary linux-swap 513MiB 4609MiB \
mkpart primary ext4 4609MiB 100% || return ;;
esac
sudo partprobe "$loopdev" # refresh partition data
sudo partprobe "$loopdev"
# make file systems
local swapdev
case "$arch" in
i686|x86_64)
sudo mkfs.ext2 "$loopdev"p2 || return
sudo mkswap "$loopdev"p3 || return
sudo mkfs.ext4 "$loopdev"p4 || return
swapdev="$loopdev"p3 ;;
armv7h)
sudo mkfs.vfat -F 32 "$loopdev"p1 || return sudo mkfs.vfat -F 32 "$loopdev"p1 || return
sudo mkswap "$loopdev"p2 || return sudo mkswap "$loopdev"p2 || return
sudo mkfs.ext4 "$loopdev"p3 || return sudo mkfs.ext4 "$loopdev"p3 || return
swapdev="$loopdev"p2 ;;
ppc64le|riscv64)
sudo mkfs.ext2 "$loopdev"p1 || return
sudo mkswap "$loopdev"p2 || return
sudo mkfs.ext4 "$loopdev"p3 || return
swapdev="$loopdev"p2 ;;
esac
# mount partitions
case "$arch" in
i686|x86_64)
sudo mount "$loopdev"p4 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p2 "$workdir"/boot || return
;;
armv7h|ppc64le|riscv64)
sudo mount "$loopdev"p3 "$workdir" || return sudo mount "$loopdev"p3 "$workdir" || return
sudo mkdir -p "$workdir"/boot || return sudo mkdir -p "$workdir"/boot || return
sudo mount "$loopdev"p1 "$workdir"/boot || return sudo mount "$loopdev"p1 "$workdir"/boot || return
;; ;;
esac esac
# setup qemu-user-static # setup qemu-user-static, if necessary
if ! pvm_native_arch "$arch"; then if ! pvm_native_arch "$arch"; then
# target arch can't execute natively, pacstrap is going to need help by qemu # target arch can't execute natively, pacstrap is going to need help by qemu
local qemu_arch local qemu_arch
@ -131,7 +156,7 @@ pvm_bootstrap() {
sudo cp -v "/usr/bin/qemu-$qemu_arch-"* "$workdir"/usr/bin || return sudo cp -v "/usr/bin/qemu-$qemu_arch-"* "$workdir"/usr/bin || return
fi fi
# pacstrap # prepare pacstrap config
local pacconf local pacconf
pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return pacconf="$(mktemp -t pvm-pacconf-XXXXXXXXXX)" || return
cat > "$pacconf" << EOF cat > "$pacconf" << EOF
@ -147,53 +172,117 @@ Server = $mirror
Server = $mirror Server = $mirror
EOF EOF
local pkg=(base) # prepare lists of packages
local pkg=(base haveged)
case "$arch" in case "$arch" in
i686|x86_64) pkg+=(grub) ;; i686|x86_64) pkg+=(grub) ;;
esac esac
local pkg_guest_cache=(ca-certificates-utils)
sudo pacstrap -GMcd -C "$pacconf" "$workdir" "${pkg[@]}" || return # pacstrap! :)
sudo pacstrap -GMc -C "$pacconf" "$workdir" "${pkg[@]}" || return
sudo pacstrap -GM -C "$pacconf" "$workdir" "${pkg_guest_cache[@]}" || return
# finalize # create an fstab
sudo swapoff --all
sudo swapon "$swapdev"
genfstab -U "$workdir" | sudo tee "$workdir"/etc/fstab
sudo swapoff "$swapdev"
sudo swapon --all
# install a boot loader
case "$arch" in case "$arch" in
i686|x86_64) i686|x86_64)
# create an fstab
sudo swapoff --all
sudo swapon "$loopdev"p3
genfstab -U "$workdir" | sudo tee "$workdir"/etc/fstab
sudo swapoff "$loopdev"p3
sudo swapon --all
# install grub to the VM # install grub to the VM
sudo sed -i 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0"/' \ sudo sed -i 's/^GRUB_CMDLINE_LINUX=.*/GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0"/' \
"$workdir"/etc/default/grub || return "$workdir"/etc/default/grub || return
sudo arch-chroot "$workdir" grub-install --target=i386-pc "$loopdev" || return sudo arch-chroot "$workdir" grub-install --target=i386-pc "$loopdev" || return
sudo arch-chroot "$workdir" grub-mkconfig -o /boot/grub/grub.cfg || return sudo arch-chroot "$workdir" grub-mkconfig -o /boot/grub/grub.cfg || return
# regenerate the chroot-botched initcpio
sudo cp "$workdir"/etc/mkinitcpio.d/linux-libre.preset{,.backup} || return
echo "default_options=\"-S autodetect\"" \
| sudo tee -a "$workdir"/etc/mkinitcpio.d/linux-libre.preset || return
sudo arch-chroot "$workdir" mkinitcpio -p linux-libre || return
sudo mv "$workdir"/etc/mkinitcpio.d/linux-libre.preset{.backup,} || return
;;
armv7h)
# create an fstab
sudo swapoff --all
sudo swapon "$loopdev"p2
genfstab -U "$workdir" | sudo tee "$workdir"/etc/fstab
sudo swapoff "$loopdev"p2
sudo swapon --all
;; ;;
riscv64) riscv64)
# FIXME: for the time being, use fedora bbl to boot # FIXME: for the time being, use fedora bbl to boot
sudo wget https://fedorapeople.org/groups/risc-v/disk-images/bbl \ sudo wget https://fedorapeople.org/groups/risc-v/disk-images/bbl \
-O "$workdir"/boot/bbl || return -O "$workdir"/boot/bbl || return
;; ;;
# armv7h has no boot loader.
# FIXME: what about ppc64le
esac esac
# regenerate the initcpio, skipping the autodetect hook
sudo cp "$workdir"/etc/mkinitcpio.d/linux-libre.preset{,.backup} || return
echo "default_options=\"-S autodetect\"" \
| sudo tee -a "$workdir"/etc/mkinitcpio.d/linux-libre.preset || return
sudo arch-chroot "$workdir" mkinitcpio -p linux-libre || return
sudo mv "$workdir"/etc/mkinitcpio.d/linux-libre.preset{.backup,} || return
# disable audit
sudo arch-chroot "$workdir" systemctl mask systemd-journald-audit.socket
# initialize the pacman keyring
sudo arch-chroot "$workdir" pacman-key --init
sudo arch-chroot "$workdir" pacman-key --populate archlinux archlinux32 archlinuxarm parabola
# enable the entropy daemon, to avoid stalling https
sudo arch-chroot "$workdir" systemctl enable haveged.service
# push hooks into the image
sudo mkdir -p "$workdir/root/hooks"
[ "${#hooks[@]}" -eq 0 ] || sudo cp -v "${hooks[@]}" "$workdir"/root/hooks/
# create a master hook script
sudo tee "$workdir"/root/hooks.sh << 'EOF'
#!/bin/bash
systemctl disable preinit.service
# fix the mkinitcpio
mkinitcpio -p linux-libre
# fix ca-certificates
pacman -U --noconfirm /var/cache/pacman/pkg/ca-certificates-utils-*.pkg.tar.xz
for hook in /root/hooks/*; do
echo "running hook \"$hook\""
. "$hook" || return
done
rm -rf /root/hooks
rm -f /root/hooks.sh
rm -f /usr/lib/systemd/system/preinit.service
EOF
# create a preinit service to run the hooks
sudo tee "$workdir"/usr/lib/systemd/system/preinit.service << 'EOF'
[Unit]
Description=Oneshot VM Preinit
After=multi-user.target
[Service]
StandardOutput=journal+console
StandardError=journal+console
ExecStart=/usr/bin/bash /root/hooks.sh
Type=oneshot
ExecStopPost=shutdown -r now
[Install]
WantedBy=multi-user.target
EOF
# enable the preinit service
sudo arch-chroot "$workdir" systemctl enable preinit.service || return
# unmount everything
pvm_cleanup pvm_cleanup
# boot the machine, and run the preinit scripts
local qemu_flags=(-no-reboot)
if [ -f "./src/pvmboot.sh" ]; then
DISPLAY='' bash ./src/pvmboot.sh "$file" "${qemu_flags[@]}" || return
elif type -p pvmboot &>/dev/null; then
DISPLAY='' pvmboot "$file" "${qemu_flags[@]}" || return
else
error "%s: pvmboot not available -- unable to run hooks" "$file"
return "$EXIT_FAILURE"
fi
} }
pvm_cleanup() { pvm_cleanup() {
@ -219,13 +308,23 @@ main() {
local size="64G" local size="64G"
local mirror="https://repo.parabola.nu/\$repo/os/\$arch" local mirror="https://repo.parabola.nu/\$repo/os/\$arch"
local hooks=()
# parse options # parse options
while getopts 'hs:M:' arg; do while getopts 'hs:M:H:' arg; do
case "$arg" in case "$arg" in
h) usage; return "$EXIT_SUCCESS";; h) usage; return "$EXIT_SUCCESS";;
s) size="$OPTARG";; s) size="$OPTARG";;
M) mirror="$OPTARG";; M) mirror="$OPTARG";;
H) if [ -e "/usr/lib/libretools/pvmbootstrap/hook-$OPTARG.sh" ]; then
hooks+=("/usr/lib/libretools/pvmbootstrap/hook-$OPTARG.sh")
elif [ -e "./src/hooks/hook-$OPTARG.sh" ]; then
hooks+=("./src/hooks/hook-$OPTARG.sh")
elif [ -e "$OPTARG" ]; then
hooks+=("$OPTARG")
else
warning "%s: hook does not exist" "$OPTARG"
fi ;;
*) usage >&2; exit "$EXIT_INVALIDARGUMENT";; *) usage >&2; exit "$EXIT_INVALIDARGUMENT";;
esac esac
done done
@ -246,6 +345,7 @@ main() {
exit "$EXIT_INVALIDARGUMENT";; exit "$EXIT_INVALIDARGUMENT";;
esac esac
# determine whether the target output file already exists
if [ -e "$file" ]; then if [ -e "$file" ]; then
warning "%s: file exists. Continue? [y/N]" "$file" warning "%s: file exists. Continue? [y/N]" "$file"
read -p " " -n 1 -r read -p " " -n 1 -r
@ -256,10 +356,12 @@ main() {
rm -f "$file" || exit rm -f "$file" || exit
fi fi
# create the virtual machine
if ! pvm_bootstrap; then if ! pvm_bootstrap; then
error "%s: bootstrap failed" "$file" error "%s: bootstrap failed" "$file"
exit "$EXIT_FAILURE" exit "$EXIT_FAILURE"
fi fi
msg "%s: bootstrap complete" "$file" msg "%s: bootstrap complete" "$file"
} }