1#!/bin/sh 2# 3# 4# 5# Common functions for virtual machine image build scripts. 6# 7 8scriptdir=$(dirname $(realpath $0)) 9. ${scriptdir}/../scripts/tools.subr 10. ${scriptdir}/../../tools/boot/install-boot.sh 11 12export PATH="/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:/usr/local/sbin" 13trap "cleanup" INT QUIT TRAP ABRT TERM 14 15# Platform-specific large-scale setup 16# Most platforms use GPT, so put that as default, then special cases 17PARTSCHEME=gpt 18ROOTLABEL="gpt" 19case "${TARGET}:${TARGET_ARCH}" in 20 powerpc:powerpc*) 21 PARTSCHEME=mbr 22 ROOTLABEL="ufs" 23 NOSWAP=yes # Can't label swap partition with MBR, so no swap 24 ;; 25esac 26 27err() { 28 printf "${@}\n" 29 cleanup 30 return 1 31} 32 33cleanup() { 34 if [ -c "${DESTDIR}/dev/null" ]; then 35 umount_loop ${DESTDIR}/dev 2>/dev/null 36 fi 37 38 return 0 39} 40 41metalog_add_data() { 42 local file mode type 43 44 if [ -n "${NO_ROOT}" ]; then 45 file=$1 46 if [ -f ${DESTDIR}/${file} ]; then 47 type=file 48 mode=${2:-0644} 49 elif [ -d ${DESTDIR}/${file} ]; then 50 type=dir 51 mode=${2:-0755} 52 else 53 echo "metalog_add_data: ${file} not found" >&2 54 return 1 55 fi 56 echo "${file} type=${type} uname=root gname=wheel mode=${mode}" >> \ 57 ${DESTDIR}/METALOG 58 fi 59} 60 61vm_create_base() { 62 63 mkdir -p ${DESTDIR} 64 65 return 0 66} 67 68vm_copy_base() { 69 # Defunct 70 return 0 71} 72 73vm_filter_base_packages() { 74 # Reads a list of all base system packages from stdin. 75 # Writes a list of base system packages to install to stdout. 76 grep -v -e '^FreeBSD-src.*' -e '^FreeBSD-kernel.*' 77 # There are several kernel variants available in separate packages. 78 # For VMs it is sufficient to install only the generic kernel. 79 echo "FreeBSD-kernel-man" 80 echo "FreeBSD-kernel-generic" 81 echo "FreeBSD-kernel-generic-dbg" 82} 83 84vm_extra_filter_base_packages() { 85 # Prototype. When overridden, allows further filtering of base system 86 # packages, reading package names from stdin and writing to stdout. 87 cat 88} 89 90vm_install_base() { 91 # Installs the FreeBSD userland/kernel to the virtual machine disk. 92 93 if [ -z "${NOPKGBASE}" ]; then 94 local pkg_cmd 95 pkg_cmd="pkg --rootdir ${DESTDIR} --repo-conf-dir ${PKGBASE_REPO_DIR} 96 -o ASSUME_ALWAYS_YES=yes -o IGNORE_OSVERSION=yes 97 -o INSTALL_AS_USER=yes " 98 $pkg_cmd update 99 selected=$($pkg_cmd rquery -U -r FreeBSD-base %n | \ 100 vm_filter_base_packages | vm_extra_filter_base_packages) 101 $pkg_cmd install -U -r FreeBSD-base $selected 102 else 103 cd ${WORLDDIR} && \ 104 make DESTDIR=${DESTDIR} ${INSTALLOPTS} \ 105 installworld installkernel distribution || \ 106 err "\n\nCannot install the base system to ${DESTDIR}." 107 fi 108 109 # Bootstrap etcupdate(8) database. 110 mkdir -p ${DESTDIR}/var/db/etcupdate 111 etcupdate extract -B \ 112 -M "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \ 113 -s ${WORLDDIR} -d ${DESTDIR}/var/db/etcupdate \ 114 -L /dev/stdout ${NO_ROOT:+-N} 115 if [ -n "${NO_ROOT}" ]; then 116 # Reroot etcupdate's internal METALOG to the whole tree 117 sed -n 's,^\.,./var/db/etcupdate/current,p' \ 118 ${DESTDIR}/var/db/etcupdate/current/METALOG | \ 119 env -i LC_COLLATE=C sort >> ${DESTDIR}/METALOG 120 rm ${DESTDIR}/var/db/etcupdate/current/METALOG 121 fi 122 123 echo '# Custom /etc/fstab for FreeBSD VM images' \ 124 > ${DESTDIR}/etc/fstab 125 if [ "${VMFS}" != zfs ]; then 126 echo "/dev/${ROOTLABEL}/rootfs / ${VMFS} rw,noatime 1 1" \ 127 >> ${DESTDIR}/etc/fstab 128 fi 129 if [ -z "${NOSWAP}" ]; then 130 echo '/dev/gpt/swapfs none swap sw 0 0' \ 131 >> ${DESTDIR}/etc/fstab 132 fi 133 metalog_add_data ./etc/fstab 134 135 local hostname 136 hostname="$(echo $(uname -o) | tr '[:upper:]' '[:lower:]')" 137 echo "hostname=\"${hostname}\"" >> ${DESTDIR}/etc/rc.conf 138 metalog_add_data ./etc/rc.conf 139 if [ "${VMFS}" = zfs ]; then 140 echo "zfs_enable=\"YES\"" >> ${DESTDIR}/etc/rc.conf 141 echo "zpool_reguid=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 142 echo "zpool_upgrade=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 143 echo "kern.geom.label.disk_ident.enable=0" >> ${DESTDIR}/boot/loader.conf 144 echo "zfs_load=YES" >> ${DESTDIR}/boot/loader.conf 145 metalog_add_data ./boot/loader.conf 146 fi 147 148 return 0 149} 150 151vm_emulation_setup() { 152 if [ -n "${WITHOUT_QEMU}" ]; then 153 return 0 154 fi 155 if [ -n "${QEMUSTATIC}" ]; then 156 export EMULATOR=/qemu 157 cp ${QEMUSTATIC} ${DESTDIR}/${EMULATOR} 158 fi 159 160 mkdir -p ${DESTDIR}/dev 161 mount -t devfs devfs ${DESTDIR}/dev 162 chroot ${DESTDIR} ${EMULATOR} /bin/sh /etc/rc.d/ldconfig forcestart 163 cp /etc/resolv.conf ${DESTDIR}/etc/resolv.conf 164 165 return 0 166} 167 168vm_extra_install_base() { 169 # Prototype. When overridden, runs extra post-installworld commands 170 # as needed, based on the target virtual machine image or cloud 171 # provider image target. 172 173 return 0 174} 175 176vm_extra_enable_services() { 177 if [ -n "${VM_RC_LIST}" ]; then 178 for _rcvar in ${VM_RC_LIST}; do 179 echo ${_rcvar}_enable="YES" >> ${DESTDIR}/etc/rc.conf 180 done 181 fi 182 183 if [ -z "${VMCONFIG}" -o -c "${VMCONFIG}" ]; then 184 echo 'ifconfig_DEFAULT="DHCP inet6 accept_rtadv"' >> \ 185 ${DESTDIR}/etc/rc.conf 186 # Expand the filesystem to fill the disk. 187 echo 'growfs_enable="YES"' >> ${DESTDIR}/etc/rc.conf 188 fi 189 190 return 0 191} 192 193vm_extra_install_packages() { 194 if [ -z "${VM_EXTRA_PACKAGES}" ]; then 195 return 0 196 fi 197 if [ -n "${NO_ROOT}" ]; then 198 for pkg in ${VM_EXTRA_PACKAGES}; do 199 INSTALL_AS_USER=yes \ 200 ${PKG_CMD} \ 201 -o METALOG=${DESTDIR}/METALOG.pkg \ 202 -o REPOS_DIR=${PKG_REPOS_DIR} \ 203 -o PKG_DBDIR=${DESTDIR}/var/db/pkg \ 204 -r ${DESTDIR} \ 205 install -y -r ${PKG_REPO_NAME} $pkg 206 done 207 metalog_add_data ./var/db/pkg/local.sqlite 208 else 209 if [ -n "${WITHOUT_QEMU}" ]; then 210 return 0 211 fi 212 213 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 214 /usr/sbin/pkg bootstrap -y 215 for p in ${VM_EXTRA_PACKAGES}; do 216 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 217 /usr/sbin/pkg install -y ${p} 218 done 219 fi 220 221 return 0 222} 223 224vm_extra_install_ports() { 225 # Prototype. When overridden, installs additional ports within the 226 # virtual machine environment. 227 228 return 0 229} 230 231vm_extra_pre_umount() { 232 # Prototype. When overridden, performs additional tasks within the 233 # virtual machine environment prior to unmounting the filesystem. 234 235 return 0 236} 237 238vm_emulation_cleanup() { 239 if [ -n "${WITHOUT_QEMU}" ]; then 240 return 0 241 fi 242 243 if ! [ -z "${QEMUSTATIC}" ]; then 244 rm -f ${DESTDIR}/${EMULATOR} 245 fi 246 rm -f ${DESTDIR}/etc/resolv.conf 247 umount_loop ${DESTDIR}/dev 248 return 0 249} 250 251vm_extra_pkg_rmcache() { 252 if [ -e ${DESTDIR}/usr/local/sbin/pkg ]; then 253 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 254 /usr/local/sbin/pkg clean -y -a 255 fi 256 257 return 0 258} 259 260buildfs() { 261 local md tmppool 262 263 if [ -f ${DESTDIR}/METALOG.pkg ]; then 264 cat ${DESTDIR}/METALOG.pkg >> ${DESTDIR}/METALOG 265 fi 266 267 case "${VMFS}" in 268 ufs) 269 cd ${DESTDIR} && ${MAKEFS} ${MAKEFSARGS} -o label=rootfs -o version=2 -o softupdates=1 \ 270 ${VMBASE} .${NO_ROOT:+/METALOG} 271 ;; 272 zfs) 273 cd ${DESTDIR} && ${MAKEFS} -t zfs ${MAKEFSARGS} \ 274 -o poolname=zroot -o bootfs=zroot/ROOT/default -o rootpath=/ \ 275 -o fs=zroot\;mountpoint=none \ 276 -o fs=zroot/ROOT\;mountpoint=none \ 277 -o fs=zroot/ROOT/default\;mountpoint=/\;canmount=noauto \ 278 -o fs=zroot/home\;mountpoint=/home \ 279 -o fs=zroot/tmp\;mountpoint=/tmp\;exec=on\;setuid=off \ 280 -o fs=zroot/usr\;mountpoint=/usr\;canmount=off \ 281 -o fs=zroot/usr/ports\;setuid=off \ 282 -o fs=zroot/usr/src \ 283 -o fs=zroot/usr/obj \ 284 -o fs=zroot/var\;mountpoint=/var\;canmount=off \ 285 -o fs=zroot/var/audit\;setuid=off\;exec=off \ 286 -o fs=zroot/var/crash\;setuid=off\;exec=off \ 287 -o fs=zroot/var/log\;setuid=off\;exec=off \ 288 -o fs=zroot/var/mail\;atime=on \ 289 -o fs=zroot/var/tmp\;setuid=off \ 290 ${VMBASE} .${NO_ROOT:+/METALOG} 291 ;; 292 *) 293 echo "Unexpected VMFS value '${VMFS}'" 294 exit 1 295 ;; 296 esac 297} 298 299umount_loop() { 300 DIR=$1 301 i=0 302 sync 303 while ! umount ${DIR}; do 304 i=$(( $i + 1 )) 305 if [ $i -ge 10 ]; then 306 # This should never happen. But, it has happened. 307 echo "Cannot umount(8) ${DIR}" 308 echo "Something has gone horribly wrong." 309 return 1 310 fi 311 sleep 1 312 done 313 314 return 0 315} 316 317vm_create_disk() { 318 local BOOTFILES BOOTPARTSOFFSET FSPARTTYPE X86GPTBOOTFILE 319 320 if [ -z "${NOSWAP}" ]; then 321 SWAPOPT="-p freebsd-swap/swapfs::${SWAPSIZE}" 322 fi 323 324 if [ -n "${VM_BOOTPARTSOFFSET}" ]; then 325 BOOTPARTSOFFSET=":${VM_BOOTPARTSOFFSET}" 326 fi 327 328 if [ -n "${CONFIG_DRIVE}" ]; then 329 CONFIG_DRIVE="-p freebsd/config-drive::${CONFIG_DRIVE_SIZE}" 330 fi 331 332 case "${VMFS}" in 333 ufs) 334 FSPARTTYPE=freebsd-ufs 335 X86GPTBOOTFILE=i386/gptboot/gptboot 336 ;; 337 zfs) 338 FSPARTTYPE=freebsd-zfs 339 X86GPTBOOTFILE=i386/gptzfsboot/gptzfsboot 340 ;; 341 *) 342 echo "Unexpected VMFS value '${VMFS}'" 343 return 1 344 ;; 345 esac 346 347 echo "Creating image... Please wait." 348 echo 349 350 BOOTFILES="$(env TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \ 351 WITH_UNIFIED_OBJDIR=yes \ 352 make -C ${WORLDDIR}/stand -V .OBJDIR)" 353 BOOTFILES="$(realpath ${BOOTFILES})" 354 MAKEFSARGS="-s ${VMSIZE} -D" 355 356 case "${TARGET}:${TARGET_ARCH}" in 357 amd64:amd64 | i386:i386) 358 ESP=yes 359 BOOTPARTS="-b ${BOOTFILES}/i386/pmbr/pmbr \ 360 -p freebsd-boot/bootfs:=${BOOTFILES}/${X86GPTBOOTFILE}${BOOTPARTSOFFSET}" 361 ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}" 362 MAKEFSARGS="$MAKEFSARGS -B little" 363 ;; 364 arm:armv7 | arm64:aarch64 | riscv:riscv64*) 365 ESP=yes 366 BOOTPARTS= 367 ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}" 368 MAKEFSARGS="$MAKEFSARGS -B little" 369 ;; 370 powerpc:powerpc*) 371 ESP=no 372 BOOTPARTS="-p prepboot:=${BOOTFILES}/powerpc/boot1.chrp/boot1.elf -a 1" 373 ROOTFSPART="-p freebsd:=${VMBASE}" 374 if [ ${TARGET_ARCH} = powerpc64le ]; then 375 MAKEFSARGS="$MAKEFSARGS -B little" 376 else 377 MAKEFSARGS="$MAKEFSARGS -B big" 378 fi 379 ;; 380 *) 381 echo "vmimage.subr: unsupported target '${TARGET}:${TARGET_ARCH}'" >&2 382 exit 1 383 ;; 384 esac 385 386 if [ ${ESP} = "yes" ]; then 387 # Create an ESP 388 espfilename=$(mktemp /tmp/efiboot.XXXXXX) 389 make_esp_file ${espfilename} ${fat32min} ${BOOTFILES}/efi/loader_lua/loader_lua.efi 390 BOOTPARTS="${BOOTPARTS} -p efi/efiboot0:=${espfilename}" 391 392 # Add this to fstab 393 mkdir -p ${DESTDIR}/boot/efi 394 echo "/dev/${ROOTLABEL}/efiboot0 /boot/efi msdosfs rw 2 2" \ 395 >> ${DESTDIR}/etc/fstab 396 fi 397 398 # Add a marker file which indicates that this image has never 399 # been booted. Some services run only upon the first boot. 400 touch ${DESTDIR}/firstboot 401 metalog_add_data ./firstboot 402 403 echo "Building filesystem... Please wait." 404 buildfs 405 406 echo "Building final disk image... Please wait." 407 ${MKIMG} -s ${PARTSCHEME} -f ${VMFORMAT} \ 408 ${BOOTPARTS} \ 409 ${SWAPOPT} \ 410 ${CONFIG_DRIVE} \ 411 ${ROOTFSPART} \ 412 -o ${VMIMAGE} 413 414 echo "Disk image ${VMIMAGE} created." 415 416 if [ ${ESP} = "yes" ]; then 417 rm ${espfilename} 418 fi 419 420 return 0 421} 422 423vm_extra_create_disk() { 424 425 return 0 426} 427