#!/bin/bash # This is a script to build a Debian image that can run in a VM created via AVF. # TODOs: # - Add Android-specific packages via a new class # - Use a stable release from debian-cloud-images SCRIPT_DIR="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" show_help() { echo "Usage: sudo $0 [OPTION]... [FILE]" echo "Builds a debian image and save it to FILE. [sudo is required]" echo "Options:" echo "-h Print usage and this help message and exit." echo "-a ARCH Architecture of the image [default is host arch: $(uname -m)]" echo "-g Use Debian generic kernel [default is our custom kernel]" echo "-r Release mode build" echo "-u Set VM boot mode to u-boot [default is to load kernel directly]" echo "-w Save temp work directory [for debugging]" } check_sudo() { if [ "$EUID" -ne 0 ]; then echo "Please run as root." ; exit 1 fi } parse_options() { while getopts "a:ghruw" option; do case ${option} in h) show_help ; exit ;; a) arch="$OPTARG" ;; g) use_generic_kernel=1 ;; r) mode=release ;; u) uboot=1 ;; w) save_workdir=1 ;; *) echo "Invalid option: $OPTARG" ; exit 1 ;; esac done case "$arch" in aarch64) debian_arch="arm64" ;; x86_64) debian_arch="amd64" ;; *) echo "Invalid architecture: $arch" ; exit 1 ;; esac if [[ "${*:$OPTIND:1}" ]]; then output="${*:$OPTIND:1}" fi } prepare_build_id() { if [ -z "${KOKORO_BUILD_NUMBER}" ]; then echo eng-$(hostname)-$(date --utc) else echo ${KOKORO_BUILD_NUMBER} fi } install_prerequisites() { apt update packages=( apt-utils automake binfmt-support build-essential ca-certificates cmake curl debsums dosfstools fai-server fai-setup-storage fdisk git libjson-c-dev libtool libwebsockets-dev make protobuf-compiler python3 python3-libcloud python3-marshmallow python3-pytest python3-yaml qemu-user-static qemu-utils sudo udev ) if [[ "$arch" == "aarch64" ]]; then packages+=( gcc-aarch64-linux-gnu libc6-dev-arm64-cross qemu-system-arm ) else packages+=( qemu-system ) fi if [[ "$uboot" != 1 ]]; then packages+=( libguestfs-tools linux-image-generic ) fi if [[ "$use_generic_kernel" != 1 ]]; then packages+=( bc bison debhelper dh-exec flex gcc-12 kernel-wedge libelf-dev libpci-dev lz4 pahole python3-jinja2 python3-docutils quilt rsync ) if [[ "$arch" == "aarch64" ]]; then packages+=( gcc-arm-linux-gnueabihf gcc-12-aarch64-linux-gnu ) fi fi DEBIAN_FRONTEND=noninteractive \ apt install --no-install-recommends --assume-yes "${packages[@]}" if [ ! -f $"HOME"/.cargo/bin/cargo ]; then curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y fi source "$HOME"/.cargo/env rustup target add "${arch}"-unknown-linux-gnu cargo install cargo-license cargo install cargo-deb } download_debian_cloud_image() { local ver=38da93fe local prj=debian-cloud-images local url="https://salsa.debian.org/cloud-team/${prj}/-/archive/${ver}/${prj}-${ver}.tar.gz" local outdir="${debian_cloud_image}" mkdir -p "${outdir}" wget -O - "${url}" | tar xz -C "${outdir}" --strip-components=1 } build_rust_as_deb() { pushd "$SCRIPT_DIR/../../guest/$1" > /dev/null cargo deb \ --target "${arch}-unknown-linux-gnu" \ --output "${debian_cloud_image}/localdebs" popd > /dev/null } build_ttyd() { local ttyd_version=1.7.7 local url="https://github.com/tsl0922/ttyd/archive/refs/tags/${ttyd_version}.tar.gz" cp -r "$SCRIPT_DIR/ttyd" "${workdir}/ttyd" pushd "${workdir}" > /dev/null wget "${url}" -O - | tar xz cp ttyd/* ttyd-${ttyd_version}/scripts pushd "$workdir/ttyd-${ttyd_version}" > /dev/null bash -c "env BUILD_TARGET=${arch} ./scripts/cross-build.sh" mkdir -p "${dst}/files/usr/local/bin/ttyd" cp "/tmp/stage/${arch}-linux-musl/bin/ttyd" "${dst}/files/usr/local/bin/ttyd/AVF" chmod 777 "${dst}/files/usr/local/bin/ttyd/AVF" mkdir -p "${dst}/files/usr/share/doc/ttyd" cp LICENSE "${dst}/files/usr/share/doc/ttyd/copyright" popd > /dev/null popd > /dev/null } copy_android_config() { local src local dst src="$SCRIPT_DIR/fai_config" dst="${config_space}" cp -R "${src}"/* "${dst}" cp "$SCRIPT_DIR/image.yaml" "${resources_dir}" cp -R "$SCRIPT_DIR/localdebs/" "${debian_cloud_image}/" build_ttyd build_rust_as_deb forwarder_guest build_rust_as_deb forwarder_guest_launcher build_rust_as_deb shutdown_runner build_rust_as_deb storage_balloon_agent } package_custom_kernel() { if [[ "$use_generic_kernel" == 1 ]]; then # NOTE: For bpfcc-tools, install generic headers for the generic kernel. cat > "${config_space}/package_config/LAST" < /dev/null wget "${ksrc_security_base_url}/${dsc_file}" || \ wget "${ksrc_base_url}/${dsc_file}" wget "${ksrc_security_base_url}/${orig_ksrc_file}" || \ wget "${ksrc_base_url}/${orig_ksrc_file}" wget "${ksrc_security_base_url}/${debian_ksrc_file}" || \ wget "${ksrc_base_url}/${debian_ksrc_file}" rsync -az --progress keyring.debian.org::keyrings/keyrings/ /usr/share/keyrings/ # 1. Verify, extract and merge patches into the original kernel sources dpkg-source --require-strong-checksums \ --require-valid-signature \ --extract "${dsc_file}" pushd "linux-${debian_kver%-*}" > /dev/null local kpatches_src="$SCRIPT_DIR/kernel/patches" cp -r "${kpatches_src}/avf" debian/patches/ cat "${kpatches_src}/series" >> debian/patches/series ./debian/rules orig local custom_flavour="avf" local debarch_flavour="${custom_flavour}-${debian_arch}" local abi_kver="$(sed -nE 's;Package: linux-support-(.*);\1;p' debian/control)" local abi_flavour="${abi_kver}-${debarch_flavour}" # 2. Define our custom flavour and regenerate control file # NOTE: Our flavour extends Debian's `cloud` config on the `none` featureset. cp "$SCRIPT_DIR/kernel/config" \ debian/config/${debian_arch}/config.${debarch_flavour} sed -z "s;\[base\]\nflavours:;[base]\nflavours:\n ${debarch_flavour};" \ -i debian/config/${debian_arch}/none/defines cat >> debian/config/${debian_arch}/none/defines <> debian/config/${debian_arch}/defines < /dev/null cp "linux-headers-${abi_flavour}_${debian_kver}_${debian_arch}.deb" \ "linux-image-${abi_flavour}-unsigned_${debian_kver}_${debian_arch}.deb" \ "${debian_cloud_image}/localdebs/" popd > /dev/null cat >> "${config_space}/package_config/AVF" < /dev/null echo ${build_id} > build_id loop=$(losetup -f --show --partscan $raw_disk_image) dd if="${loop}p$root_partition_num" of=root_part dd if="${loop}p$efi_partition_num" of=efi_part losetup -d "${loop}" cp ${vm_config} vm_config.json # TODO(b/363985291): remove this when ballooning is supported on generic kernel if [[ "$use_generic_kernel" == 1 ]] && [[ "$arch" == "aarch64" ]]; then sed -i 's/"auto_memory_balloon": true/"auto_memory_balloon": false/g' vm_config.json fi sed -i "s/{root_part_guid}/$(sfdisk --part-uuid $raw_disk_image $root_partition_num)/g" vm_config.json sed -i "s/{efi_part_guid}/$(sfdisk --part-uuid $raw_disk_image $efi_partition_num)/g" vm_config.json contents=( build_id root_part efi_part vm_config.json ) if [[ "$uboot" != 1 ]]; then rm -f vmlinuz* initrd.img* virt-get-kernel -a "${raw_disk_image}" mv vmlinuz* vmlinuz mv initrd.img* initrd.img contents+=( vmlinuz initrd.img ) fi popd > /dev/null # --sparse option isn't supported in apache-commons-compress tar czv -f ${output} -C ${workdir} "${contents[@]}" } clean_up() { [ "$save_workdir" -eq 1 ] || rm -rf "${workdir}" } set -e trap clean_up EXIT output=images.tar.gz workdir=$(mktemp -d) raw_disk_image=${workdir}/image.raw build_id=$(prepare_build_id) debian_cloud_image=${workdir}/debian_cloud_image debian_version=bookworm config_space=${debian_cloud_image}/config_space/${debian_version} resources_dir=${debian_cloud_image}/src/debian_cloud_images/resources arch="$(uname -m)" mode=debug save_workdir=0 use_generic_kernel=0 uboot=0 parse_options "$@" check_sudo install_prerequisites download_debian_cloud_image copy_android_config package_custom_kernel run_fai generate_output_package