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