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_base_packages_list() { 74 # Output a list of package sets equivalent to what we get from 75 # "installworld installkernel distribution", aka. the full base 76 # system. 77 for S in base lib32 kernels; do 78 echo FreeBSD-set-$S 79 echo FreeBSD-set-$S-dbg 80 done 81 echo FreeBSD-set-tests 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=$(vm_base_packages_list | vm_extra_filter_base_packages) 103 $pkg_cmd install -U -r FreeBSD-base $selected 104 else 105 cd ${WORLDDIR} && \ 106 make DESTDIR=${DESTDIR} ${INSTALLOPTS} \ 107 installworld installkernel distribution || \ 108 err "\n\nCannot install the base system to ${DESTDIR}." 109 fi 110 111 # Bootstrap etcupdate(8) database. 112 mkdir -p ${DESTDIR}/var/db/etcupdate 113 etcupdate extract -B \ 114 -M "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \ 115 -s ${WORLDDIR} -d ${DESTDIR}/var/db/etcupdate \ 116 -L /dev/stdout ${NO_ROOT:+-N} 117 if [ -n "${NO_ROOT}" ]; then 118 # Reroot etcupdate's internal METALOG to the whole tree 119 sed -n 's,^\.,./var/db/etcupdate/current,p' \ 120 ${DESTDIR}/var/db/etcupdate/current/METALOG | \ 121 env -i LC_COLLATE=C sort >> ${DESTDIR}/METALOG 122 rm ${DESTDIR}/var/db/etcupdate/current/METALOG 123 fi 124 125 echo '# Custom /etc/fstab for FreeBSD VM images' \ 126 > ${DESTDIR}/etc/fstab 127 if [ "${VMFS}" != zfs ]; then 128 echo "/dev/${ROOTLABEL}/rootfs / ${VMFS} rw,noatime 1 1" \ 129 >> ${DESTDIR}/etc/fstab 130 fi 131 if [ -z "${NOSWAP}" ]; then 132 echo '/dev/gpt/swapfs none swap sw 0 0' \ 133 >> ${DESTDIR}/etc/fstab 134 fi 135 metalog_add_data ./etc/fstab 136 137 local hostname 138 hostname="$(echo $(uname -o) | tr '[:upper:]' '[:lower:]')" 139 echo "hostname=\"${hostname}\"" >> ${DESTDIR}/etc/rc.conf 140 metalog_add_data ./etc/rc.conf 141 if [ "${VMFS}" = zfs ]; then 142 echo "zfs_enable=\"YES\"" >> ${DESTDIR}/etc/rc.conf 143 echo "zpool_reguid=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 144 echo "zpool_upgrade=\"zroot\"" >> ${DESTDIR}/etc/rc.conf 145 echo "kern.geom.label.disk_ident.enable=0" >> ${DESTDIR}/boot/loader.conf 146 echo "zfs_load=YES" >> ${DESTDIR}/boot/loader.conf 147 metalog_add_data ./boot/loader.conf 148 fi 149 150 return 0 151} 152 153vm_emulation_setup() { 154 if [ -n "${WITHOUT_QEMU}" ]; then 155 return 0 156 fi 157 if [ -n "${QEMUSTATIC}" ]; then 158 export EMULATOR=/qemu 159 cp ${QEMUSTATIC} ${DESTDIR}/${EMULATOR} 160 fi 161 162 mkdir -p ${DESTDIR}/dev 163 mount -t devfs devfs ${DESTDIR}/dev 164 chroot ${DESTDIR} ${EMULATOR} /bin/sh /etc/rc.d/ldconfig forcestart 165 cp /etc/resolv.conf ${DESTDIR}/etc/resolv.conf 166 167 return 0 168} 169 170vm_extra_install_base() { 171 # Prototype. When overridden, runs extra post-installworld commands 172 # as needed, based on the target virtual machine image or cloud 173 # provider image target. 174 175 return 0 176} 177 178vm_extra_enable_services() { 179 if [ -n "${VM_RC_LIST}" ]; then 180 for _rcvar in ${VM_RC_LIST}; do 181 echo ${_rcvar}_enable="YES" >> ${DESTDIR}/etc/rc.conf 182 done 183 fi 184 185 if [ -z "${VMCONFIG}" -o -c "${VMCONFIG}" ]; then 186 echo 'ifconfig_DEFAULT="DHCP inet6 accept_rtadv"' >> \ 187 ${DESTDIR}/etc/rc.conf 188 # Expand the filesystem to fill the disk. 189 echo 'growfs_enable="YES"' >> ${DESTDIR}/etc/rc.conf 190 fi 191 192 return 0 193} 194 195vm_extra_install_packages() { 196 if [ -z "${VM_EXTRA_PACKAGES}" ]; then 197 return 0 198 fi 199 if [ -n "${NO_ROOT}" ]; then 200 for pkg in ${VM_EXTRA_PACKAGES}; do 201 INSTALL_AS_USER=yes \ 202 ${PKG_CMD} \ 203 -o ABI=${PKG_ABI} \ 204 -o METALOG=${DESTDIR}/METALOG.pkg \ 205 -o REPOS_DIR=${PKG_REPOS_DIR} \ 206 -o PKG_DBDIR=${DESTDIR}/var/db/pkg \ 207 -r ${DESTDIR} \ 208 install -y -r ${PKG_REPO_NAME} $pkg 209 done 210 metalog_add_data ./var/db/pkg/local.sqlite 211 else 212 if [ -n "${WITHOUT_QEMU}" ]; then 213 return 0 214 fi 215 216 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 217 /usr/sbin/pkg bootstrap -y 218 for p in ${VM_EXTRA_PACKAGES}; do 219 chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \ 220 /usr/sbin/pkg install -y ${p} 221 done 222 fi 223 224 return 0 225} 226 227vm_extra_install_ports() { 228 # Prototype. When overridden, installs additional ports within the 229 # virtual machine environment. 230 231 return 0 232} 233 234vm_extra_pre_umount() { 235 # Prototype. When overridden, performs additional tasks within the 236 # virtual machine environment prior to unmounting the filesystem. 237 238 return 0 239} 240 241vm_emulation_cleanup() { 242 if [ -n "${WITHOUT_QEMU}" ]; then 243 return 0 244 fi 245 246 if ! [ -z "${QEMUSTATIC}" ]; then 247 rm -f ${DESTDIR}/${EMULATOR} 248 fi 249 rm -f ${DESTDIR}/etc/resolv.conf 250 umount_loop ${DESTDIR}/dev 251 return 0 252} 253 254vm_extra_pkg_rmcache() { 255 if [ -n "${NO_ROOT}" ]; then 256 ${PKG_CMD} \ 257 -o ASSUME_ALWAYS_YES=yes \ 258 -o INSTALL_AS_USER=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