• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/bin/bash
2
3# This is a script to build a Debian image that can run in a VM created via AVF.
4# TODOs:
5# - Add Android-specific packages via a new class
6# - Use a stable release from debian-cloud-images
7
8SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
9
10show_help() {
11	echo "Usage: sudo $0 [OPTION]... [FILE]"
12	echo "Builds a debian image and save it to FILE. [sudo is required]"
13	echo "Options:"
14	echo "-h         Print usage and this help message and exit."
15	echo "-a ARCH    Architecture of the image [default is host arch: $(uname -m)]"
16	echo "-g         Use Debian generic kernel [default is our custom kernel]"
17	echo "-r         Release mode build"
18	echo "-u         Set VM boot mode to u-boot [default is to load kernel directly]"
19	echo "-w         Save temp work directory [for debugging]"
20}
21
22check_sudo() {
23	if [ "$EUID" -ne 0 ]; then
24		echo "Please run as root." ; exit 1
25	fi
26}
27
28parse_options() {
29	while getopts "a:ghruw" option; do
30		case ${option} in
31			h)
32				show_help ; exit
33				;;
34			a)
35				arch="$OPTARG"
36				;;
37			g)
38				use_generic_kernel=1
39				;;
40			r)
41				mode=release
42				;;
43			u)
44				uboot=1
45				;;
46			w)
47				save_workdir=1
48				;;
49			*)
50				echo "Invalid option: $OPTARG" ; exit 1
51				;;
52		esac
53	done
54	case "$arch" in
55		aarch64)
56			debian_arch="arm64"
57			;;
58		x86_64)
59			debian_arch="amd64"
60			;;
61		*)
62			echo "Invalid architecture: $arch" ; exit 1
63			;;
64	esac
65	if [[ "${*:$OPTIND:1}" ]]; then
66		output="${*:$OPTIND:1}"
67	fi
68}
69
70prepare_build_id() {
71	if [ -z "${KOKORO_BUILD_NUMBER}" ]; then
72		echo eng-$(hostname)-$(date --utc)
73	else
74		echo ${KOKORO_BUILD_NUMBER}
75	fi
76}
77
78install_prerequisites() {
79	apt update
80	packages=(
81		apt-utils
82		automake
83		binfmt-support
84		build-essential
85		ca-certificates
86		cmake
87		curl
88		debsums
89		dosfstools
90		fai-server
91		fai-setup-storage
92		fdisk
93		git
94		libjson-c-dev
95		libtool
96		libwebsockets-dev
97		make
98		protobuf-compiler
99		python3
100		python3-libcloud
101		python3-marshmallow
102		python3-pytest
103		python3-yaml
104		qemu-user-static
105		qemu-utils
106		sudo
107		udev
108	)
109	if [[ "$arch" == "aarch64" ]]; then
110		packages+=(
111			gcc-aarch64-linux-gnu
112			libc6-dev-arm64-cross
113			qemu-system-arm
114		)
115	else
116		packages+=(
117			qemu-system
118		)
119	fi
120
121	if [[ "$uboot" != 1 ]]; then
122		packages+=(
123			libguestfs-tools
124			linux-image-generic
125		)
126	fi
127
128	if [[ "$use_generic_kernel" != 1 ]]; then
129		packages+=(
130			bc
131			bison
132			debhelper
133			dh-exec
134			flex
135			gcc-12
136			kernel-wedge
137			libelf-dev
138			libpci-dev
139			lz4
140			pahole
141			python3-jinja2
142			python3-docutils
143			quilt
144			rsync
145		)
146		if [[ "$arch" == "aarch64" ]]; then
147			packages+=(
148				gcc-arm-linux-gnueabihf
149				gcc-12-aarch64-linux-gnu
150			)
151		fi
152	fi
153
154	DEBIAN_FRONTEND=noninteractive \
155	apt install --no-install-recommends --assume-yes "${packages[@]}"
156
157	if [ ! -f $"HOME"/.cargo/bin/cargo ]; then
158		curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
159	fi
160
161	source "$HOME"/.cargo/env
162	rustup target add "${arch}"-unknown-linux-gnu
163	cargo install cargo-license
164	cargo install cargo-deb
165}
166
167download_debian_cloud_image() {
168	local ver=38da93fe
169	local prj=debian-cloud-images
170	local url="https://salsa.debian.org/cloud-team/${prj}/-/archive/${ver}/${prj}-${ver}.tar.gz"
171	local outdir="${debian_cloud_image}"
172
173	mkdir -p "${outdir}"
174	wget -O - "${url}" | tar xz -C "${outdir}" --strip-components=1
175}
176
177build_rust_as_deb() {
178	pushd "$SCRIPT_DIR/../../guest/$1" > /dev/null
179	cargo deb \
180		--target "${arch}-unknown-linux-gnu" \
181		--output "${debian_cloud_image}/localdebs"
182	popd > /dev/null
183}
184
185build_ttyd() {
186	local ttyd_version=1.7.7
187	local url="https://github.com/tsl0922/ttyd/archive/refs/tags/${ttyd_version}.tar.gz"
188	cp -r "$SCRIPT_DIR/ttyd" "${workdir}/ttyd"
189
190	pushd "${workdir}" > /dev/null
191	wget "${url}" -O - | tar xz
192	cp ttyd/* ttyd-${ttyd_version}/scripts
193	pushd "$workdir/ttyd-${ttyd_version}" > /dev/null
194	bash -c "env BUILD_TARGET=${arch} ./scripts/cross-build.sh"
195	mkdir -p "${dst}/files/usr/local/bin/ttyd"
196	cp "/tmp/stage/${arch}-linux-musl/bin/ttyd" "${dst}/files/usr/local/bin/ttyd/AVF"
197	chmod 777 "${dst}/files/usr/local/bin/ttyd/AVF"
198	mkdir -p "${dst}/files/usr/share/doc/ttyd"
199	cp LICENSE "${dst}/files/usr/share/doc/ttyd/copyright"
200	popd > /dev/null
201	popd > /dev/null
202}
203
204copy_android_config() {
205	local src
206	local dst
207	src="$SCRIPT_DIR/fai_config"
208	dst="${config_space}"
209
210	cp -R "${src}"/* "${dst}"
211	cp "$SCRIPT_DIR/image.yaml" "${resources_dir}"
212
213	cp -R "$SCRIPT_DIR/localdebs/" "${debian_cloud_image}/"
214	build_ttyd
215	build_rust_as_deb forwarder_guest
216	build_rust_as_deb forwarder_guest_launcher
217	build_rust_as_deb shutdown_runner
218	build_rust_as_deb storage_balloon_agent
219}
220
221package_custom_kernel() {
222	if [[ "$use_generic_kernel" == 1 ]]; then
223		# NOTE: For bpfcc-tools, install generic headers for the generic kernel.
224		cat > "${config_space}/package_config/LAST" <<EOF
225PACKAGES install
226linux-headers-generic
227EOF
228		return
229	fi
230
231	# NOTE: Prevent FAI from installing a default Debian kernel, by removing
232	#       linux-image meta package names from arch-specific class files.
233	sed -i "/linux-image.*-${debian_arch}/d" \
234	    "${config_space}/package_config/${debian_arch^^}"
235
236	local deb_base_url="https://deb.debian.org/debian"
237	local deb_security_base_url="https://security.debian.org/debian-security"
238
239	local pool_dir="pool/main/l/linux"
240	local ksrc_base_url="${deb_base_url}/${pool_dir}"
241	local ksrc_security_base_url="${deb_security_base_url}/${pool_dir}"
242
243	# NOTE: 6.1 is the latest LTS kernel for which Debian's kernel build scripts
244	#       work on Python 3.10, the default version on our Ubuntu 22.04 builders.
245	#
246	#       We track the latest Debian stable kernel version for the 6.1 branch,
247	#       which can be found at:
248	#       https://packages.debian.org/stable/linux-source-6.1
249	local debian_kver="6.1.123-1"
250
251	local dsc_file="linux_${debian_kver}.dsc"
252	local orig_ksrc_file="linux_${debian_kver%-*}.orig.tar.xz"
253	local debian_ksrc_file="linux_${debian_kver}.debian.tar.xz"
254
255	# 0. Grab the kernel sources, and the latest debian keyrings
256	mkdir -p "${workdir}/kernel"
257	pushd "${workdir}/kernel" > /dev/null
258
259	wget "${ksrc_security_base_url}/${dsc_file}" || \
260	wget "${ksrc_base_url}/${dsc_file}"
261
262	wget "${ksrc_security_base_url}/${orig_ksrc_file}" || \
263	wget "${ksrc_base_url}/${orig_ksrc_file}"
264
265	wget "${ksrc_security_base_url}/${debian_ksrc_file}" || \
266	wget "${ksrc_base_url}/${debian_ksrc_file}"
267
268	rsync -az --progress keyring.debian.org::keyrings/keyrings/ /usr/share/keyrings/
269
270	# 1. Verify, extract and merge patches into the original kernel sources
271	dpkg-source --require-strong-checksums \
272	            --require-valid-signature \
273	            --extract "${dsc_file}"
274	pushd "linux-${debian_kver%-*}" > /dev/null
275
276	local kpatches_src="$SCRIPT_DIR/kernel/patches"
277	cp -r "${kpatches_src}/avf" debian/patches/
278	cat "${kpatches_src}/series" >> debian/patches/series
279	./debian/rules orig
280
281	local custom_flavour="avf"
282	local debarch_flavour="${custom_flavour}-${debian_arch}"
283
284	local abi_kver="$(sed -nE 's;Package: linux-support-(.*);\1;p' debian/control)"
285	local abi_flavour="${abi_kver}-${debarch_flavour}"
286
287	# 2. Define our custom flavour and regenerate control file
288	# NOTE: Our flavour extends Debian's `cloud` config on the `none` featureset.
289	cp "$SCRIPT_DIR/kernel/config" \
290	   debian/config/${debian_arch}/config.${debarch_flavour}
291
292	sed -z "s;\[base\]\nflavours:;[base]\nflavours:\n ${debarch_flavour};" \
293	    -i debian/config/${debian_arch}/none/defines
294	cat >> debian/config/${debian_arch}/none/defines <<EOF
295[${debarch_flavour}_image]
296configs:
297 config.cloud
298 ${debian_arch}/config.${debarch_flavour}
299EOF
300	cat >> debian/config/${debian_arch}/defines <<EOF
301[${debarch_flavour}_description]
302hardware: ${arch} AVF
303hardware-long: ${arch} Android Virtualization Framework
304EOF
305	./debian/rules debian/control || true
306
307	# 3. Build the kernel and generate Debian packages
308	./debian/rules source
309	[[ "$arch" == "$(uname -m)" ]] || export $(dpkg-architecture -a $debian_arch)
310	make -j$(nproc) -f debian/rules.gen \
311	     "binary-arch_${debian_arch}_none_${debarch_flavour}"
312
313	# 4. Copy the packages to localdebs and add their names to package_config/AVF
314	popd > /dev/null
315	cp "linux-headers-${abi_flavour}_${debian_kver}_${debian_arch}.deb" \
316	   "linux-image-${abi_flavour}-unsigned_${debian_kver}_${debian_arch}.deb" \
317	   "${debian_cloud_image}/localdebs/"
318	popd > /dev/null
319	cat >> "${config_space}/package_config/AVF" <<EOF
320linux-headers-${abi_flavour}
321linux-image-${abi_flavour}-unsigned
322EOF
323}
324
325run_fai() {
326	# NOTE: Prevent FAI from installing grub packages and running related scripts,
327	#       if we are loading the kernel directly.
328	if [[ "$uboot" != 1 ]]; then
329		sed -i "/shim-signed/d ; /grub.*${debian_arch}.*/d" \
330		    "${config_space}/package_config/${debian_arch^^}"
331		rm "${config_space}/scripts/SYSTEM_BOOT/20-grub"
332	fi
333
334	local out="${raw_disk_image}"
335	make -C "${debian_cloud_image}" "image_bookworm_nocloud_${debian_arch}"
336	mv "${debian_cloud_image}/image_bookworm_nocloud_${debian_arch}.raw" "${out}"
337}
338
339generate_output_package() {
340	fdisk -l "${raw_disk_image}"
341	local root_partition_num=1
342	local efi_partition_num=15
343
344	local vm_config="$SCRIPT_DIR/vm_config.json"
345	if [[ "$uboot" == 1 ]]; then
346		vm_config="$SCRIPT_DIR/vm_config.u-boot.json"
347	fi
348
349	pushd ${workdir} > /dev/null
350
351	echo ${build_id} > build_id
352
353	loop=$(losetup -f --show --partscan $raw_disk_image)
354	dd if="${loop}p$root_partition_num" of=root_part
355	dd if="${loop}p$efi_partition_num" of=efi_part
356	losetup -d "${loop}"
357
358	cp ${vm_config} vm_config.json
359	# TODO(b/363985291): remove this when ballooning is supported on generic kernel
360	if [[ "$use_generic_kernel" == 1 ]] && [[ "$arch" == "aarch64" ]]; then
361		sed -i 's/"auto_memory_balloon": true/"auto_memory_balloon": false/g' vm_config.json
362	fi
363
364	sed -i "s/{root_part_guid}/$(sfdisk --part-uuid $raw_disk_image $root_partition_num)/g" vm_config.json
365	sed -i "s/{efi_part_guid}/$(sfdisk --part-uuid $raw_disk_image $efi_partition_num)/g" vm_config.json
366
367	contents=(
368		build_id
369		root_part
370		efi_part
371		vm_config.json
372	)
373
374	if [[ "$uboot" != 1 ]]; then
375		rm -f vmlinuz* initrd.img*
376		virt-get-kernel -a "${raw_disk_image}"
377		mv vmlinuz* vmlinuz
378		mv initrd.img* initrd.img
379		contents+=(
380			vmlinuz
381			initrd.img
382		)
383	fi
384
385	popd > /dev/null
386
387	# --sparse option isn't supported in apache-commons-compress
388	tar czv -f ${output} -C ${workdir} "${contents[@]}"
389}
390
391clean_up() {
392	[ "$save_workdir" -eq 1 ] || rm -rf "${workdir}"
393}
394
395set -e
396trap clean_up EXIT
397
398output=images.tar.gz
399workdir=$(mktemp -d)
400raw_disk_image=${workdir}/image.raw
401build_id=$(prepare_build_id)
402debian_cloud_image=${workdir}/debian_cloud_image
403debian_version=bookworm
404config_space=${debian_cloud_image}/config_space/${debian_version}
405resources_dir=${debian_cloud_image}/src/debian_cloud_images/resources
406arch="$(uname -m)"
407mode=debug
408save_workdir=0
409use_generic_kernel=0
410uboot=0
411
412parse_options "$@"
413check_sudo
414install_prerequisites
415download_debian_cloud_image
416copy_android_config
417package_custom_kernel
418run_fai
419generate_output_package
420