freebsd-src/release/tools/vmimage.subr
Mark Johnston 89585511cc release: Add support for creating ZFS-based VM images
The change extends vmimage.subr to handle a new parameter, VMFS, which
should be equal to either "ufs" or "zfs".  When it is set to ZFS, we use
makefs to create a bootable pool populated using the same dataset layout
as bsdinstall and "poudriere image" use.  The pool can be grown using
the growfs rc.d script, just as in UFS images.

This will make it easy to provide VM and cloud images with ZFS as the
root filesystem.  So far I did not do extensive testing of cloud images;
I merely verified that creation of ZFS-based AWS AMIs works and allows
me to create amd64 and arm64 EC2 instances with ZFS as the root
filesystem.

Reviewed by:	emaste, gjb
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D34426
2022-10-28 17:00:24 -04:00

332 lines
7.7 KiB
Bash

#!/bin/sh
#
# $FreeBSD$
#
#
# Common functions for virtual machine image build scripts.
#
scriptdir=$(dirname $(realpath $0))
. ${scriptdir}/../../tools/boot/install-boot.sh
export PATH="/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin"
trap "cleanup" INT QUIT TRAP ABRT TERM
# Platform-specific large-scale setup
# Most platforms use GPT, so put that as default, then special cases
PARTSCHEME=gpt
ROOTLABEL="gpt"
case "${TARGET}:${TARGET_ARCH}" in
powerpc:powerpc*)
PARTSCHEME=mbr
ROOTLABEL="ufs"
NOSWAP=yes # Can't label swap partition with MBR, so no swap
;;
esac
err() {
printf "${@}\n"
cleanup
return 1
}
cleanup() {
if [ -c "${DESTDIR}/dev/null" ]; then
umount_loop ${DESTDIR}/dev 2>/dev/null
fi
return 0
}
vm_create_base() {
mkdir -p ${DESTDIR}
return 0
}
vm_copy_base() {
# Defunct
}
vm_install_base() {
# Installs the FreeBSD userland/kernel to the virtual machine disk.
cd ${WORLDDIR} && \
make DESTDIR=${DESTDIR} \
installworld installkernel distribution || \
err "\n\nCannot install the base system to ${DESTDIR}."
# Bootstrap etcupdate(8) and mergemaster(8) databases.
mkdir -p ${DESTDIR}/var/db/etcupdate
etcupdate extract -B \
-M "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \
-s ${WORLDDIR} -d ${DESTDIR}/var/db/etcupdate
sh ${WORLDDIR}/release/scripts/mm-mtree.sh -m ${WORLDDIR} \
-F "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \
-D ${DESTDIR}
echo '# Custom /etc/fstab for FreeBSD VM images' \
> ${DESTDIR}/etc/fstab
if [ "${VMFS}" != zfs ]; then
echo "/dev/${ROOTLABEL}/rootfs / ${VMFS} rw 1 1" \
>> ${DESTDIR}/etc/fstab
fi
if [ -z "${NOSWAP}" ]; then
echo '/dev/gpt/swapfs none swap sw 0 0' \
>> ${DESTDIR}/etc/fstab
fi
local hostname
hostname="$(echo $(uname -o) | tr '[:upper:]' '[:lower:]')"
echo "hostname=\"${hostname}\"" >> ${DESTDIR}/etc/rc.conf
if [ "${VMFS}" = zfs ]; then
echo "zfs_enable=\"YES\"" >> ${DESTDIR}/etc/rc.conf
echo "zpool_reguid=\"zroot\"" >> ${DESTDIR}/etc/rc.conf
fi
if ! [ -z "${QEMUSTATIC}" ]; then
export EMULATOR=/qemu
cp ${QEMUSTATIC} ${DESTDIR}/${EMULATOR}
fi
mkdir -p ${DESTDIR}/dev
mount -t devfs devfs ${DESTDIR}/dev
chroot ${DESTDIR} ${EMULATOR} /usr/bin/newaliases
chroot ${DESTDIR} ${EMULATOR} /bin/sh /etc/rc.d/ldconfig forcestart
umount_loop ${DESTDIR}/dev
cp /etc/resolv.conf ${DESTDIR}/etc/resolv.conf
if [ "${VMFS}" = zfs ]; then
echo "kern.geom.label.disk_ident.enable=0" >> ${DESTDIR}/boot/loader.conf
echo "zfs_load=YES" >> ${DESTDIR}/boot/loader.conf
fi
return 0
}
vm_extra_install_base() {
# Prototype. When overridden, runs extra post-installworld commands
# as needed, based on the target virtual machine image or cloud
# provider image target.
return 0
}
vm_extra_enable_services() {
if [ ! -z "${VM_RC_LIST}" ]; then
for _rcvar in ${VM_RC_LIST}; do
echo ${_rcvar}_enable="YES" >> ${DESTDIR}/etc/rc.conf
done
fi
if [ -z "${VMCONFIG}" -o -c "${VMCONFIG}" ]; then
echo 'ifconfig_DEFAULT="DHCP inet6 accept_rtadv"' >> \
${DESTDIR}/etc/rc.conf
# Expand the filesystem to fill the disk.
echo 'growfs_enable="YES"' >> ${DESTDIR}/etc/rc.conf
touch ${DESTDIR}/firstboot
fi
return 0
}
vm_extra_install_packages() {
if [ -z "${VM_EXTRA_PACKAGES}" ]; then
return 0
fi
mkdir -p ${DESTDIR}/dev
mount -t devfs devfs ${DESTDIR}/dev
chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
/usr/sbin/pkg bootstrap -y
for p in ${VM_EXTRA_PACKAGES}; do
chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
/usr/sbin/pkg install -y ${p}
done
umount_loop ${DESTDIR}/dev
return 0
}
vm_extra_install_ports() {
# Prototype. When overridden, installs additional ports within the
# virtual machine environment.
return 0
}
vm_extra_pre_umount() {
# Prototype. When overridden, performs additional tasks within the
# virtual machine environment prior to unmounting the filesystem.
# Note: When overriding this function, removing resolv.conf in the
# disk image must be included.
if ! [ -z "${QEMUSTATIC}" ]; then
rm -f ${DESTDIR}/${EMULATOR}
fi
rm -f ${DESTDIR}/etc/resolv.conf
return 0
}
vm_extra_pkg_rmcache() {
if [ -e ${DESTDIR}/usr/local/sbin/pkg ]; then
chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
/usr/local/sbin/pkg clean -y -a
fi
return 0
}
buildfs() {
local md tmppool
case "${VMFS}" in
ufs)
makefs ${MAKEFSARGS} -o label=rootfs -o version=2 -o softupdates=1 \
${VMBASE} ${DESTDIR}
;;
zfs)
makefs -t zfs ${MAKEFSARGS} \
-o poolname=zroot -o bootfs=zroot/ROOT/default -o rootpath=/ \
-o fs=zroot\;mountpoint=none \
-o fs=zroot/ROOT\;mountpoint=none \
-o fs=zroot/ROOT/default\;mountpoint=/ \
-o fs=zroot/tmp\;mountpoint=/tmp\;exec=on\;setuid=off \
-o fs=zroot/usr\;mountpoint=/usr\;canmount=off \
-o fs=zroot/usr/home \
-o fs=zroot/usr/ports\;setuid=off \
-o fs=zroot/usr/src \
-o fs=zroot/usr/obj \
-o fs=zroot/var\;mountpoint=/var\;canmount=off \
-o fs=zroot/var/audit\;setuid=off\;exec=off \
-o fs=zroot/var/log\;setuid=off\;exec=off \
-o fs=zroot/var/mail\;atime=on \
-o fs=zroot/var/tmp\;setuid=off \
${VMBASE} ${DESTDIR}
;;
*)
echo "Unexpected VMFS value '${VMFS}'"
exit 1
;;
esac
}
umount_loop() {
DIR=$1
i=0
sync
while ! umount ${DIR}; do
i=$(( $i + 1 ))
if [ $i -ge 10 ]; then
# This should never happen. But, it has happened.
echo "Cannot umount(8) ${DIR}"
echo "Something has gone horribly wrong."
return 1
fi
sleep 1
done
return 0
}
vm_create_disk() {
local BOOTFILES BOOTPARTSOFFSET FSPARTTYPE X86GPTBOOTFILE
if [ -z "${NOSWAP}" ]; then
SWAPOPT="-p freebsd-swap/swapfs::${SWAPSIZE}"
fi
if [ -n "${VM_BOOTPARTSOFFSET}" ]; then
BOOTPARTSOFFSET=":${VM_BOOTPARTSOFFSET}"
fi
case "${VMFS}" in
ufs)
FSPARTTYPE=freebsd-ufs
X86GPTBOOTFILE=i386/gptboot/gptboot
;;
zfs)
FSPARTTYPE=freebsd-zfs
X86GPTBOOTFILE=i386/gptzfsboot/gptzfsboot
;;
*)
echo "Unexpected VMFS value '${VMFS}'"
return 1
;;
esac
echo "Creating image... Please wait."
echo
BOOTFILES="$(env TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \
WITH_UNIFIED_OBJDIR=yes \
make -C ${WORLDDIR}/stand -V .OBJDIR)"
BOOTFILES="$(realpath ${BOOTFILES})"
MAKEFSARGS="-s ${VMSIZE}"
case "${TARGET}:${TARGET_ARCH}" in
amd64:amd64 | i386:i386)
ESP=yes
BOOTPARTS="-b ${BOOTFILES}/i386/pmbr/pmbr \
-p freebsd-boot/bootfs:=${BOOTFILES}/${X86GPTBOOTFILE}${BOOTPARTSOFFSET}"
ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}"
MAKEFSARGS="$MAKEFSARGS -B little"
;;
arm64:aarch64 | riscv:riscv64*)
ESP=yes
BOOTPARTS=
ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}"
MAKEFSARGS="$MAKEFSARGS -B little"
;;
powerpc:powerpc*)
ESP=no
BOOTPARTS="-p prepboot:=${BOOTFILES}/powerpc/boot1.chrp/boot1.elf -a 1"
ROOTFSPART="-p freebsd:=${VMBASE}"
if [ ${TARGET_ARCH} = powerpc64le ]; then
MAKEFSARGS="$MAKEFSARGS -B little"
else
MAKEFSARGS="$MAKEFSARGS -B big"
fi
;;
*)
echo "vmimage.subr: unsupported target '${TARGET}:${TARGET_ARCH}'" >&2
exit 1
;;
esac
if [ ${ESP} = "yes" ]; then
# Create an ESP
espfilename=$(mktemp /tmp/efiboot.XXXXXX)
make_esp_file ${espfilename} ${fat32min} ${BOOTFILES}/efi/loader_lua/loader_lua.efi
BOOTPARTS="${BOOTPARTS} -p efi/efiesp:=${espfilename}"
# Add this to fstab
mkdir -p ${DESTDIR}/boot/efi
echo "/dev/${ROOTLABEL}/efiesp /boot/efi msdosfs rw 2 2" \
>> ${DESTDIR}/etc/fstab
fi
echo "Building filesystem... Please wait."
buildfs
echo "Building final disk image... Please wait."
mkimg -s ${PARTSCHEME} -f ${VMFORMAT} \
${BOOTPARTS} \
${SWAPOPT} \
${ROOTFSPART} \
-o ${VMIMAGE}
if [ ${ESP} = "yes" ]; then
rm ${espfilename}
fi
return 0
}
vm_extra_create_disk() {
return 0
}