blob: f751b300ecd56f88b9062a4f691d7f671abf5bbc [file] [log] [blame] [edit]
#!/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" <<EOF
PACKAGES install
linux-headers-generic
EOF
return
fi
# NOTE: Prevent FAI from installing a default Debian kernel, by removing
# linux-image meta package names from arch-specific class files.
sed -i "/linux-image.*-${debian_arch}/d" \
"${config_space}/package_config/${debian_arch^^}"
local deb_base_url="https://deb.debian.org/debian"
local deb_security_base_url="https://security.debian.org/debian-security"
local pool_dir="pool/main/l/linux"
local ksrc_base_url="${deb_base_url}/${pool_dir}"
local ksrc_security_base_url="${deb_security_base_url}/${pool_dir}"
# NOTE: 6.1 is the latest LTS kernel for which Debian's kernel build scripts
# work on Python 3.10, the default version on our Ubuntu 22.04 builders.
#
# We track the latest Debian stable kernel version for the 6.1 branch,
# which can be found at:
# https://packages.debian.org/stable/linux-source-6.1
local debian_kver="6.1.123-1"
local dsc_file="linux_${debian_kver}.dsc"
local orig_ksrc_file="linux_${debian_kver%-*}.orig.tar.xz"
local debian_ksrc_file="linux_${debian_kver}.debian.tar.xz"
# 0. Grab the kernel sources, and the latest debian keyrings
mkdir -p "${workdir}/kernel"
pushd "${workdir}/kernel" > /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 <<EOF
[${debarch_flavour}_image]
configs:
config.cloud
${debian_arch}/config.${debarch_flavour}
EOF
cat >> debian/config/${debian_arch}/defines <<EOF
[${debarch_flavour}_description]
hardware: ${arch} AVF
hardware-long: ${arch} Android Virtualization Framework
EOF
./debian/rules debian/control || true
# 3. Build the kernel and generate Debian packages
./debian/rules source
[[ "$arch" == "$(uname -m)" ]] || export $(dpkg-architecture -a $debian_arch)
make -j$(nproc) -f debian/rules.gen \
"binary-arch_${debian_arch}_none_${debarch_flavour}"
# 4. Copy the packages to localdebs and add their names to package_config/AVF
popd > /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" <<EOF
linux-headers-${abi_flavour}
linux-image-${abi_flavour}-unsigned
EOF
}
run_fai() {
# NOTE: Prevent FAI from installing grub packages and running related scripts,
# if we are loading the kernel directly.
if [[ "$uboot" != 1 ]]; then
sed -i "/shim-signed/d ; /grub.*${debian_arch}.*/d" \
"${config_space}/package_config/${debian_arch^^}"
rm "${config_space}/scripts/SYSTEM_BOOT/20-grub"
fi
local out="${raw_disk_image}"
make -C "${debian_cloud_image}" "image_bookworm_nocloud_${debian_arch}"
mv "${debian_cloud_image}/image_bookworm_nocloud_${debian_arch}.raw" "${out}"
}
generate_output_package() {
fdisk -l "${raw_disk_image}"
local root_partition_num=1
local efi_partition_num=15
local vm_config="$SCRIPT_DIR/vm_config.json"
if [[ "$uboot" == 1 ]]; then
vm_config="$SCRIPT_DIR/vm_config.u-boot.json"
fi
pushd ${workdir} > /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