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 ABI=${PKG_ABI} -o INSTALL_AS_USER=yes " 98 if [ -n "${NO_ROOT}" ]; then 99 pkg_cmd="$pkg_cmd -o METALOG=METALOG" 100 fi 101 $pkg_cmd update 102 selected=$($pkg_cmd rquery -U -r FreeBSD-base %n | \ 103 vm_filter_base_packages | vm_extra_filter_base_packages) 104 $pkg_cmd install -U -r FreeBSD-base $selected 105 else 106 cd ${WORLDDIR} && \ 107 make DESTDIR=${DESTDIR} ${INSTALLOPTS} \ 108 installworld installkernel distribution || \ 109 err "\n\nCannot install the base system to ${DESTDIR}." 110 fi 111 112 # Bootstrap etcupdate(8) database. 113 mkdir -p ${DESTDIR}/var/db/etcupdate 114 etcupdate extract -B \ 115 -M "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \ 116 -s ${WORLDDIR} -d ${DESTDIR}/var/db/etcupdate \ 117 -L /dev/stdout ${NO_ROOT:+-N} 118 if [ -n "${NO_ROOT}" ]; then 119 # Reroot etcupdate's internal METALOG to the whole tree 120 sed -n 's,^\.,./var/db/etcupdate/current,p' \ 121 ${DESTDIR}/var/db/etcupdate/current/METALOG | \ 122 env -i LC_COLLATE=C sort >> ${DESTDIR}/METALOG 123 rm ${DESTDIR}/var/db/etcupdate/current/METALOG 124 fi 125 126 echo '# Custom /etc/fstab for FreeBSD VM images' \ 127 > ${DESTDIR}/etc/fstab 128 if [ "${VMFS}" != zfs ]; then 129 echo "/dev/${ROOTLABEL}/rootfs / ${VMFS} rw,noatime 1 1" \ 130 >> ${DESTDIR}/etc/fstab 131 fi 132 if [ -z "${NOSWAP}" ]; then 133 echo '/dev/gpt/swapfs none swap sw 0 0' \ 134 >> ${DESTDIR}/etc/fstab 135 fi 136 metalog_add_data ./etc/fstab 137 138 local hostname 139 hostname="$(echo $(uname -o) | tr '[:upper:]' '[:lower:]')" 140 echo "hostname=\"${hostname}\"" >> ${DESTDIR}/etc/rc.conf 141 metalog_add_data ./etc/rc.conf 142 if [ "${VMFS}" = zfs ]; then 143 echo "zfs_enable=\"YES\"" >> ${DESTDIR}/etc/rc.conf 144 echo "zpool_reguid=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 145 echo "zpool_upgrade=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 146 echo "kern.geom.label.disk_ident.enable=0" >> ${DESTDIR}/boot/loader.conf 147 echo "zfs_load=YES" >> ${DESTDIR}/boot/loader.conf 148 metalog_add_data ./boot/loader.conf 149 fi 150 151 return 0 152} 153 154vm_emulation_setup() { 155 if [ -n "${WITHOUT_QEMU}" ]; then 156 return 0 157 fi 158 if [ -n "${QEMUSTATIC}" ]; then 159 export EMULATOR=/qemu 160 cp ${QEMUSTATIC} ${DESTDIR}/${EMULATOR} 161 fi 162 163 mkdir -p ${DESTDIR}/dev 164 mount -t devfs devfs ${DESTDIR}/dev 165 chroot ${DESTDIR} ${EMULATOR} /bin/sh /etc/rc.d/ldconfig forcestart 166 cp /etc/resolv.conf ${DESTDIR}/etc/resolv.conf 167 168 return 0 169} 170 171vm_extra_install_base() { 172 # Prototype. When overridden, runs extra post-installworld commands 173 # as needed, based on the target virtual machine image or cloud 174 # provider image target. 175 176 return 0 177} 178 179vm_extra_enable_services() { 180 if [ -n "${VM_RC_LIST}" ]; then 181 for _rcvar in ${VM_RC_LIST}; do 182 echo ${_rcvar}_enable="YES" >> ${DESTDIR}/etc/rc.conf 183 done 184 fi 185 186 if [ -z "${VMCONFIG}" -o -c "${VMCONFIG}" ]; then 187 echo 'ifconfig_DEFAULT="DHCP inet6 accept_rtadv"' >> \ 188 ${DESTDIR}/etc/rc.conf 189 # Expand the filesystem to fill the disk. 190 echo 'growfs_enable="YES"' >> ${DESTDIR}/etc/rc.conf 191 fi 192 193 return 0 194} 195 196vm_extra_install_packages() { 197 if [ -z "${VM_EXTRA_PACKAGES}" ]; then 198 return 0 199 fi 200 if [ -n "${NO_ROOT}" ]; then 201 for pkg in ${VM_EXTRA_PACKAGES}; do 202 INSTALL_AS_USER=yes \ 203 ${PKG_CMD} \ 204 -o ABI=${PKG_ABI} \ 205 -o METALOG=${DESTDIR}/METALOG.pkg \ 206 -o REPOS_DIR=${PKG_REPOS_DIR} \ 207 -o PKG_DBDIR=${DESTDIR}/var/db/pkg \ 208 -r ${DESTDIR} \ 209 install -y -r ${PKG_REPO_NAME} $pkg 210 done 211 metalog_add_data ./var/db/pkg/local.sqlite 212 else 213 if [ -n "${WITHOUT_QEMU}" ]; then 214 return 0 215 fi 216 217 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 218 /usr/sbin/pkg bootstrap -y 219 for p in ${VM_EXTRA_PACKAGES}; do 220 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 221 /usr/sbin/pkg install -y ${p} 222 done 223 fi 224 225 return 0 226} 227 228vm_extra_install_ports() { 229 # Prototype. When overridden, installs additional ports within the 230 # virtual machine environment. 231 232 return 0 233} 234 235vm_extra_pre_umount() { 236 # Prototype. When overridden, performs additional tasks within the 237 # virtual machine environment prior to unmounting the filesystem. 238 239 return 0 240} 241 242vm_emulation_cleanup() { 243 if [ -n "${WITHOUT_QEMU}" ]; then 244 return 0 245 fi 246 247 if ! [ -z "${QEMUSTATIC}" ]; then 248 rm -f ${DESTDIR}/${EMULATOR} 249 fi 250 rm -f ${DESTDIR}/etc/resolv.conf 251 umount_loop ${DESTDIR}/dev 252 return 0 253} 254 255vm_extra_pkg_rmcache() { 256 if [ -n "${NO_ROOT}" ]; then 257 ${PKG_CMD} \ 258 -o ASSUME_ALWAYS_YES=yes \ 259 -r ${DESTDIR} \ 260 clean -y -a 261 else 262 if [ -e ${DESTDIR}/usr/local/sbin/pkg ]; then 263 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 264 /usr/local/sbin/pkg clean -y -a 265 fi 266 fi 267 268 return 0 269} 270 271buildfs() { 272 local md tmppool 273 274 if [ -f ${DESTDIR}/METALOG.pkg ]; then 275 cat ${DESTDIR}/METALOG.pkg >> ${DESTDIR}/METALOG 276 fi 277 278 case "${VMFS}" in 279 ufs) 280 cd ${DESTDIR} && ${MAKEFS} ${MAKEFSARGS} -o label=rootfs -o version=2 -o softupdates=1 \ 281 ${VMBASE} .${NO_ROOT:+/METALOG} 282 ;; 283 zfs) 284 cd ${DESTDIR} && ${MAKEFS} -t zfs ${MAKEFSARGS} \ 285 -o poolname=zroot -o bootfs=zroot/ROOT/default -o rootpath=/ \ 286 -o fs=zroot\;mountpoint=none \ 287 -o fs=zroot/ROOT\;mountpoint=none \ 288 -o fs=zroot/ROOT/default\;mountpoint=/\;canmount=noauto \ 289 -o fs=zroot/home\;mountpoint=/home \ 290 -o fs=zroot/tmp\;mountpoint=/tmp\;exec=on\;setuid=off \ 291 -o fs=zroot/usr\;mountpoint=/usr\;canmount=off \ 292 -o fs=zroot/usr/ports\;setuid=off \ 293 -o fs=zroot/usr/src \ 294 -o fs=zroot/usr/obj \ 295 -o fs=zroot/var\;mountpoint=/var\;canmount=off \ 296 -o fs=zroot/var/audit\;setuid=off\;exec=off \ 297 -o fs=zroot/var/crash\;setuid=off\;exec=off \ 298 -o fs=zroot/var/log\;setuid=off\;exec=off \ 299 -o fs=zroot/var/mail\;atime=on \ 300 -o fs=zroot/var/tmp\;setuid=off \ 301 ${VMBASE} .${NO_ROOT:+/METALOG} 302 ;; 303 *) 304 echo "Unexpected VMFS value '${VMFS}'" 305 exit 1 306 ;; 307 esac 308} 309 310umount_loop() { 311 DIR=$1 312 i=0 313 sync 314 while ! umount ${DIR}; do 315 i=$(( $i + 1 )) 316 if [ $i -ge 10 ]; then 317 # This should never happen. But, it has happened. 318 echo "Cannot umount(8) ${DIR}" 319 echo "Something has gone horribly wrong." 320 return 1 321 fi 322 sleep 1 323 done 324 325 return 0 326} 327 328vm_create_disk() { 329 local BOOTFILES BOOTPARTSOFFSET FSPARTTYPE X86GPTBOOTFILE 330 331 if [ -z "${NOSWAP}" ]; then 332 SWAPOPT="-p freebsd-swap/swapfs::${SWAPSIZE}" 333 fi 334 335 if [ -n "${VM_BOOTPARTSOFFSET}" ]; then 336 BOOTPARTSOFFSET=":${VM_BOOTPARTSOFFSET}" 337 fi 338 339 if [ -n "${CONFIG_DRIVE}" ]; then 340 CONFIG_DRIVE="-p freebsd/config-drive::${CONFIG_DRIVE_SIZE}" 341 fi 342 343 case "${VMFS}" in 344 ufs) 345 FSPARTTYPE=freebsd-ufs 346 X86GPTBOOTFILE=i386/gptboot/gptboot 347 ;; 348 zfs) 349 FSPARTTYPE=freebsd-zfs 350 X86GPTBOOTFILE=i386/gptzfsboot/gptzfsboot 351 ;; 352 *) 353 echo "Unexpected VMFS value '${VMFS}'" 354 return 1 355 ;; 356 esac 357 358 echo "Creating image... Please wait." 359 echo 360 361 BOOTFILES="$(env TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \ 362 WITH_UNIFIED_OBJDIR=yes \ 363 make -C ${WORLDDIR}/stand -V .OBJDIR)" 364 BOOTFILES="$(realpath ${BOOTFILES})" 365 MAKEFSARGS="-s ${VMSIZE} -D" 366 367 case "${TARGET}:${TARGET_ARCH}" in 368 amd64:amd64 | i386:i386) 369 ESP=yes 370 BOOTPARTS="-b ${BOOTFILES}/i386/pmbr/pmbr \ 371 -p freebsd-boot/bootfs:=${BOOTFILES}/${X86GPTBOOTFILE}${BOOTPARTSOFFSET}" 372 ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}" 373 MAKEFSARGS="$MAKEFSARGS -B little" 374 ;; 375 arm:armv7 | arm64:aarch64 | riscv:riscv64*) 376 ESP=yes 377 BOOTPARTS= 378 ROOTFSPART="-p ${FSPARTTYPE}/rootfs:=${VMBASE}" 379 MAKEFSARGS="$MAKEFSARGS -B little" 380 ;; 381 powerpc:powerpc*) 382 ESP=no 383 BOOTPARTS="-p prepboot:=${BOOTFILES}/powerpc/boot1.chrp/boot1.elf -a 1" 384 ROOTFSPART="-p freebsd:=${VMBASE}" 385 if [ ${TARGET_ARCH} = powerpc64le ]; then 386 MAKEFSARGS="$MAKEFSARGS -B little" 387 else 388 MAKEFSARGS="$MAKEFSARGS -B big" 389 fi 390 ;; 391 *) 392 echo "vmimage.subr: unsupported target '${TARGET}:${TARGET_ARCH}'" >&2 393 exit 1 394 ;; 395 esac 396 397 if [ ${ESP} = "yes" ]; then 398 # Create an ESP 399 espfilename=$(mktemp /tmp/efiboot.XXXXXX) 400 make_esp_file ${espfilename} ${fat32min} ${BOOTFILES}/efi/loader_lua/loader_lua.efi 401 BOOTPARTS="${BOOTPARTS} -p efi/efiboot0:=${espfilename}" 402 403 # Add this to fstab 404 mkdir -p ${DESTDIR}/boot/efi 405 echo "/dev/${ROOTLABEL}/efiboot0 /boot/efi msdosfs rw 2 2" \ 406 >> ${DESTDIR}/etc/fstab 407 fi 408 409 # Add a marker file which indicates that this image has never 410 # been booted. Some services run only upon the first boot. 411 touch ${DESTDIR}/firstboot 412 metalog_add_data ./firstboot 413 414 echo "Building filesystem... Please wait." 415 buildfs 416 417 echo "Building final disk image... Please wait." 418 ${MKIMG} -s ${PARTSCHEME} -f ${VMFORMAT} \ 419 ${BOOTPARTS} \ 420 ${SWAPOPT} \ 421 ${CONFIG_DRIVE} \ 422 ${ROOTFSPART} \ 423 -o ${VMIMAGE} 424 425 echo "Disk image ${VMIMAGE} created." 426 427 if [ ${ESP} = "yes" ]; then 428 rm ${espfilename} 429 fi 430 431 return 0 432} 433 434vm_extra_create_disk() { 435 436 return 0 437} 438