xref: /freebsd/release/tools/vmimage.subr (revision c203bd70b5957f85616424b6fa374479372d06e3)
1#!/bin/sh
2#
3# $FreeBSD$
4#
5#
6# Common functions for virtual machine image build scripts.
7#
8
9scriptdir=$(dirname $(realpath $0))
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
15write_partition_layout() {
16	if [ -z "${NOSWAP}" ]; then
17		SWAPOPT="-p freebsd-swap/swapfs::${SWAPSIZE}"
18	fi
19
20	BOOTFILES="$(env TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH} \
21		WITH_UNIFIED_OBJDIR=yes \
22		make -C ${WORLDDIR}/stand -V .OBJDIR)"
23	BOOTFILES="$(realpath ${BOOTFILES})"
24
25	case "${TARGET}:${TARGET_ARCH}" in
26		amd64:amd64 | i386:i386)
27			ESP=yes
28			SCHEME=gpt
29			BOOTPARTS="-b ${BOOTFILES}/i386/pmbr/pmbr \
30				   -p freebsd-boot/bootfs:=${BOOTFILES}/i386/gptboot/gptboot"
31			ROOTFSPART="-p freebsd-ufs/rootfs:=${VMBASE}"
32			;;
33		arm64:aarch64 | riscv:riscv64*)
34			ESP=yes
35			SCHEME=gpt
36			BOOTPARTS=
37			ROOTFSPART="-p freebsd-ufs/rootfs:=${VMBASE}"
38			;;
39		powerpc:powerpc*)
40			ESP=no
41			SCHEME=apm
42			BOOTPARTS="-p apple-boot/bootfs:=${BOOTFILES}/powerpc/boot1.chrp/boot1.hfs"
43			ROOTFSPART="-p freebsd-ufs/rootfs:=${VMBASE}"
44			;;
45		*)
46			echo "vmimage.subr: unsupported target '${TARGET}:${TARGET_ARCH}'" >&2
47			exit 1
48			;;
49	esac
50
51	if [ ${ESP} = "yes" ]; then
52		# Create an ESP
53		espfilename=$(mktemp /tmp/efiboot.XXXXXX)
54		make_esp_file ${espfilename} ${fat32min} ${BOOTFILES}/efi/loader_lua/loader_lua.efi
55		BOOTPARTS="${BOOTPARTS} -p efi:=${espfilename}"
56	fi
57
58	mkimg -s ${SCHEME} -f ${VMFORMAT} \
59		${BOOTPARTS} \
60		${SWAPOPT} \
61		${ROOTFSPART} \
62		-o ${VMIMAGE}
63
64	if [ ${ESP} = "yes" ]; then
65		rm ${espfilename}
66	fi
67
68	return 0
69}
70
71err() {
72	printf "${@}\n"
73	cleanup
74	return 1
75}
76
77cleanup() {
78	if [ -c "${DESTDIR}/dev/null" ]; then
79		umount_loop ${DESTDIR}/dev 2>/dev/null
80	fi
81	umount_loop ${DESTDIR}
82	if [ ! -z "${mddev}" ]; then
83		mdconfig -d -u ${mddev}
84	fi
85
86	return 0
87}
88
89vm_create_base() {
90	# Creates the UFS root filesystem for the virtual machine disk,
91	# written to the formatted disk image with mkimg(1).
92
93	mkdir -p ${DESTDIR}
94	truncate -s ${VMSIZE} ${VMBASE}
95	mddev=$(mdconfig -f ${VMBASE})
96	newfs -L rootfs /dev/${mddev}
97	mount /dev/${mddev} ${DESTDIR}
98
99	return 0
100}
101
102vm_copy_base() {
103	# Creates a new UFS root filesystem and copies the contents of the
104	# current root filesystem into it.  This produces a "clean" disk
105	# image without any remnants of files which were created temporarily
106	# during image-creation and have since been deleted (e.g., downloaded
107	# package archives).
108
109	mkdir -p ${DESTDIR}/old
110	mdold=$(mdconfig -f ${VMBASE})
111	mount /dev/${mdold} ${DESTDIR}/old
112
113	truncate -s ${VMSIZE} ${VMBASE}.tmp
114	mkdir -p ${DESTDIR}/new
115	mdnew=$(mdconfig -f ${VMBASE}.tmp)
116	newfs -L rootfs /dev/${mdnew}
117	mount /dev/${mdnew} ${DESTDIR}/new
118
119	tar -cf- -C ${DESTDIR}/old . | tar -xUf- -C ${DESTDIR}/new
120
121	umount_loop /dev/${mdold}
122	rmdir ${DESTDIR}/old
123	mdconfig -d -u ${mdold}
124
125	umount_loop /dev/${mdnew}
126	rmdir ${DESTDIR}/new
127	tunefs -n enable /dev/${mdnew}
128	mdconfig -d -u ${mdnew}
129	mv ${VMBASE}.tmp ${VMBASE}
130}
131
132vm_install_base() {
133	# Installs the FreeBSD userland/kernel to the virtual machine disk.
134
135	cd ${WORLDDIR} && \
136		make DESTDIR=${DESTDIR} \
137		installworld installkernel distribution || \
138		err "\n\nCannot install the base system to ${DESTDIR}."
139
140	# Bootstrap etcupdate(8) and mergemaster(8) databases.
141	mkdir -p ${DESTDIR}/var/db/etcupdate
142	etcupdate extract -B \
143		-M "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \
144		-s ${WORLDDIR} -d ${DESTDIR}/var/db/etcupdate
145	sh ${WORLDDIR}/release/scripts/mm-mtree.sh -m ${WORLDDIR} \
146		-F "TARGET=${TARGET} TARGET_ARCH=${TARGET_ARCH}" \
147		-D ${DESTDIR}
148
149	echo '# Custom /etc/fstab for FreeBSD VM images' \
150		> ${DESTDIR}/etc/fstab
151	echo "/dev/${ROOTLABEL}/rootfs   /       ufs     rw      1       1" \
152		>> ${DESTDIR}/etc/fstab
153	if [ -z "${NOSWAP}" ]; then
154		echo '/dev/gpt/swapfs  none    swap    sw      0       0' \
155			>> ${DESTDIR}/etc/fstab
156	fi
157
158	local hostname
159	hostname="$(echo $(uname -o) | tr '[:upper:]' '[:lower:]')"
160	echo "hostname=\"${hostname}\"" >> ${DESTDIR}/etc/rc.conf
161
162	if ! [ -z "${QEMUSTATIC}" ]; then
163		export EMULATOR=/qemu
164		cp ${QEMUSTATIC} ${DESTDIR}/${EMULATOR}
165	fi
166
167	mkdir -p ${DESTDIR}/dev
168	mount -t devfs devfs ${DESTDIR}/dev
169	chroot ${DESTDIR} ${EMULATOR} /usr/bin/newaliases
170	chroot ${DESTDIR} ${EMULATOR} /bin/sh /etc/rc.d/ldconfig forcestart
171	umount_loop ${DESTDIR}/dev
172
173	cp /etc/resolv.conf ${DESTDIR}/etc/resolv.conf
174
175	return 0
176}
177
178vm_extra_install_base() {
179	# Prototype.  When overridden, runs extra post-installworld commands
180	# as needed, based on the target virtual machine image or cloud
181	# provider image target.
182
183	return 0
184}
185
186vm_extra_enable_services() {
187	if [ ! -z "${VM_RC_LIST}" ]; then
188		for _rcvar in ${VM_RC_LIST}; do
189			echo ${_rcvar}_enable="YES" >> ${DESTDIR}/etc/rc.conf
190		done
191	fi
192
193	if [ -z "${VMCONFIG}" -o -c "${VMCONFIG}" ]; then
194		echo 'ifconfig_DEFAULT="DHCP inet6 accept_rtadv"' >> \
195			${DESTDIR}/etc/rc.conf
196		# Expand the filesystem to fill the disk.
197		echo 'growfs_enable="YES"' >> ${DESTDIR}/etc/rc.conf
198		touch ${DESTDIR}/firstboot
199	fi
200
201	return 0
202}
203
204vm_extra_install_packages() {
205	if [ -z "${VM_EXTRA_PACKAGES}" ]; then
206		return 0
207	fi
208	mkdir -p ${DESTDIR}/dev
209	mount -t devfs devfs ${DESTDIR}/dev
210	chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
211		/usr/sbin/pkg bootstrap -y
212	chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
213		/usr/sbin/pkg install -y ${VM_EXTRA_PACKAGES}
214	umount_loop ${DESTDIR}/dev
215
216	return 0
217}
218
219vm_extra_install_ports() {
220	# Prototype.  When overridden, installs additional ports within the
221	# virtual machine environment.
222
223	return 0
224}
225
226vm_extra_pre_umount() {
227	# Prototype.  When overridden, performs additional tasks within the
228	# virtual machine environment prior to unmounting the filesystem.
229	# Note: When overriding this function, removing resolv.conf in the
230	# disk image must be included.
231
232	if ! [ -z "${QEMUSTATIC}" ]; then
233		rm -f ${DESTDIR}/${EMULATOR}
234	fi
235	rm -f ${DESTDIR}/etc/resolv.conf
236	return 0
237}
238
239vm_extra_pkg_rmcache() {
240	if [ -e ${DESTDIR}/usr/local/sbin/pkg ]; then
241		chroot ${DESTDIR} ${EMULATOR} env ASSUME_ALWAYS_YES=yes \
242			/usr/local/sbin/pkg clean -y -a
243	fi
244
245	return 0
246}
247
248umount_loop() {
249	DIR=$1
250	i=0
251	sync
252	while ! umount ${DIR}; do
253		i=$(( $i + 1 ))
254		if [ $i -ge 10 ]; then
255			# This should never happen.  But, it has happened.
256			echo "Cannot umount(8) ${DIR}"
257			echo "Something has gone horribly wrong."
258			return 1
259		fi
260		sleep 1
261	done
262
263	return 0
264}
265
266vm_create_disk() {
267	echo "Creating image...  Please wait."
268	echo
269
270	write_partition_layout || return 1
271
272	return 0
273}
274
275vm_extra_create_disk() {
276
277	return 0
278}
279
280