#!/bin/bash # Copyright (c) 2023 The Flatcar Maintainers. # Use of this source code is governed by a BSD-style license that can # be found in the LICENSE file. # # Script to generate sysext. See systemd-sysext(8). Prerequisite is # that you've run build_packages and build_image. SCRIPT_ROOT=$(dirname "$(readlink -f "$0")") . "${SCRIPT_ROOT}/common.sh" || exit 1 # Script must run inside the chroot assert_inside_chroot assert_root_user default_imagedir="$(readlink -f "${SCRIPT_ROOT}/../build/images")//latest/" # All these are used to set up the 'BUILD_DIR' variable DEFINE_string board "${DEFAULT_BOARD}" \ "The board to build a sysext for." DEFINE_string metapkgs '' \ "Comma-separated list of meta-packages to build from source and install into sysext image." DEFINE_string squashfs_base '' \ "The path to the squashfs base image. Defaults to the most current image built in '${default_imagedir}/${FLATCAR_PRODUCTION_IMAGE_SYSEXT_BASE}'." DEFINE_string image_builddir '' \ "Custom directory to build the sysext in. Defaults to a 'sysext' sub-directory of the directory the squashfs base image resides in; '${default_imagedir}/sysext' by default." DEFINE_boolean strip_binaries "${FLAGS_FALSE}" \ "After installation, scan sysext root for unstripped binaries and strip these. WARNING - this can subtly break some packages, e.g. Docker (see https://github.com/moby/moby/blob/master/project/PACKAGERS.md#stripping-binaries)." DEFINE_string manglefs_script '' \ "A path to executable that will customize the rootfs of the sysext image." DEFINE_boolean generate_pkginfo "${FLAGS_FALSE}" \ "Generate an additional squashfs '_pkginfo.raw' with portage package meta-information (/var/db ...). Useful for creating sysext dependencies; see 'base_pkginfo' below." DEFINE_string base_pkginfo "" \ "Colon-separated list of pkginfo squashfs paths / files generated via 'generate_pkginfo' to base this sysext on. The corresponding base sysexts are expected to be merged with the sysext generated." DEFINE_string compression "zstd" \ "Compression to use for sysext squashfs. One of 'gzip', 'lzo', 'lz4', 'xz', or 'zstd'. Must be supported by the Flatcar squashfs kernel module in order for the sysext to work." DEFINE_string mksquashfs_opts "" \ "Additional command line options to pass to mksquashfs. See 'man 1 mksquashfs'. If is 'zstd' (the default), this option defaults to '-Xcompression-level 22 -b 512K'. Otherwise the default is empty." DEFINE_boolean ignore_version_mismatch "${FLAGS_FALSE}" \ "Ignore version mismatch between SDK board packages and base squashfs. DANGEROUS." FLAGS_HELP="USAGE: build_sysext [flags] [ ...] This script is used to build a Flatcar sysext image. The sysext will be based on an OS image build's sysext base squashfs, i.e. it is specific to a Flatcar build or release. The base squashfs can either come from a local build or downloaded from an official release. By default, the sysext will be built in a 'sysext' sub-dir of the directory the squashfs base image is in, but this can be changed with the --image_builddir option. Examples: Builds a sysext image named 'interpreters' with 'dev-lang/python' and 'dev-lang/perl' packages for the most recent production image (default architecture, likely amd64) in the defaut build directory: sudo build_sysext \\ interpreters dev-lang/python dev-lang/perl Builds a sysext image named 'oem-azure' in the 'oem-images' sub-directory with metapackage 'coreos-base/oem-azure' for the arm64 squashfs base at 'build/artifacts/flatcar_production_image_sysext.squashfs': sudo build_sysext \\ --board=arm64-usr \\ --metapkgs=coreos-base/oem-azure \\ --mangle_fs=sdk_container/src/third_party/coreos-overlay/coreos-base/oem-azure/files/manglefs.sh \\ --squashfs_base=build/artifacts/flatcar_production_image_sysext.squashfs \\ --image_builddir=oem-images \\ oem-azure Mandatory command line parameters: - name of the sysext output file. - List of existing binary packages to install. Can be omitted if --metapkgs was specified. " show_help_if_requested "$@" # Parse command line FLAGS "$@" || exit 1 eval set -- "${FLAGS_ARGV}" # Only now can we die on error. shflags functions leak non-zero error codes, # so will die prematurely if 'switch_to_strict_mode' is specified before now. switch_to_strict_mode -uo pipefail # Validate command line parameters SYSEXTNAME="${1:-}" if [[ -z "${SYSEXTNAME}" ]]; then die "No sysext name provided." fi shift if [[ -z "$FLAGS_squashfs_base" ]] ; then FLAGS_squashfs_base="$(readlink -f "${SCRIPT_ROOT}/../build/images/${FLAGS_board}/latest/${FLATCAR_PRODUCTION_IMAGE_SYSEXT_BASE}")" fi if [[ ! -f "${FLAGS_squashfs_base}" ]] ; then die "Squashfs base '${FLAGS_squashfs_base}' not found." fi if [[ -z "${FLAGS_image_builddir}" ]]; then FLAGS_image_builddir="$(dirname "${FLAGS_squashfs_base}")/sysext" fi BUILD_DIR=$(realpath "${FLAGS_image_builddir}") mkdir -p "${BUILD_DIR}" if [[ "${FLAGS_compression}" = "zstd" && -z "${FLAGS_mksquashfs_opts}" ]] ; then FLAGS_mksquashfs_opts="-Xcompression-level 22 -b 512k" fi source "${BUILD_LIBRARY_DIR}/toolchain_util.sh" || exit 1 source "${BUILD_LIBRARY_DIR}/board_options.sh" || exit 1 source "${BUILD_LIBRARY_DIR}/reports_util.sh" || exit 1 # Architecture values are taken from systemd.unit(5). declare -A SYSEXT_ARCHES SYSEXT_ARCHES['amd64-usr']='x86-64' SYSEXT_ARCHES['arm64-usr']='arm64' declare -r SYSEXT_ARCHES # Usage: _get_sysext_arch board [board...] _get_sysext_arch() { local board=${1} if [[ ${#SYSEXT_ARCHES["${board}"]} -ne 0 ]]; then echo "${SYSEXT_ARCHES["${board}"]}" else die "Unknown board '${board}'" fi } cleanup() { local dirs=( "${BUILD_DIR}/fs-root" "${BUILD_DIR}/install-root" "${BUILD_DIR}/workdir" "${BUILD_DIR}/img-rootfs" ) umount "${dirs[@]}" 2>/dev/null || true rm -rf "${dirs[@]}" || true if [[ -d "${BUILD_DIR}/base-pkginfo" ]] ; then umount "${BUILD_DIR}/base-pkginfo"/* 2>/dev/null || true rm -rf "${BUILD_DIR}/base-pkginfo" || true fi rm -rf "${BUILD_DIR}/img-pkginfo" } # Set up trap to execute cleanup() on script exit trap cleanup EXIT ARCH=$(_get_sysext_arch "${FLAGS_board}") cleanup # If we need to handle pkginfo squashfs files, create mount points under # ${BUILD_DIR}/base-pkginfo, mount the squashfs images, and add the mount paths to # the list of lowerdirs. pkginfo_lowerdirs="" if [[ -n "${FLAGS_base_pkginfo}" ]] ; then for entry in $(echo ${FLAGS_base_pkginfo} | sed 's/:/ /g'); do ppath="$(readlink -f "${entry}")" if [[ ! -f "${ppath}" ]] ; then error "--base_pkginfo contains invalid entries." error "Pkginfo file '${ppath}' does not exist." die "Full --base_pkginfo: '${FLAGS_base_pkginfo}'" fi pfile="$(basename "${ppath}")" pmdir="${BUILD_DIR}/base-pkginfo/${pfile}" mkdir -p "${pmdir}" mount -rt squashfs -o loop,nodev "${ppath}" "${pmdir}" pkginfo_lowerdirs="${pkginfo_lowerdirs}:${pmdir}" info "Added packageinfo from '${ppath}' to base layers." done fi mkdir "${BUILD_DIR}/fs-root" mount -rt squashfs -o loop,nodev "${FLAGS_squashfs_base}" "${BUILD_DIR}/fs-root" mkdir "${BUILD_DIR}/install-root" mkdir "${BUILD_DIR}/workdir" mount -t overlay overlay -o lowerdir="${BUILD_DIR}/fs-root${pkginfo_lowerdirs}",upperdir="${BUILD_DIR}/install-root",workdir="${BUILD_DIR}/workdir" "${BUILD_DIR}/install-root" REPO_BUILD_ID=$(source "${REPO_MANIFESTS_DIR}/version.txt"; echo "$FLATCAR_BUILD_ID") REPO_FLATCAR_VERSION=$(source "${REPO_MANIFESTS_DIR}/version.txt"; echo "$FLATCAR_VERSION") VERSION_BOARD=$(source "${BUILD_DIR}/fs-root/usr/lib/os-release" && echo "$VERSION") if [[ -z $REPO_BUILD_ID ]] && [[ ${COREOS_OFFICIAL:-0} -ne 1 ]]; then BASE_SQUASHFS_BUILD_ID=$(source "${BUILD_DIR}/fs-root/usr/lib/os-release" && echo -n "$BUILD_ID") info "This is a dev rebuild of an official release tag: No BUILD ID set in '${REPO_MANIFESTS_DIR}/version.txt'. Will use base squashfs BUILD ID for version check." info "Repo root FLATCAR_VERSION is '$REPO_FLATCAR_VERSION', squashfs build ID is '$BASE_SQUASHFS_BUILD_ID'" FLATCAR_VERSION="${REPO_FLATCAR_VERSION}${BASE_SQUASHFS_BUILD_ID:++}${BASE_SQUASHFS_BUILD_ID}" info "Setting FLATCAR_VERSION to '$FLATCAR_VERSION'" fi if [ "$VERSION_BOARD" != "$FLATCAR_VERSION" ]; then warn "Base squashfs version: $VERSION_BOARD" warn "SDK board packages version: $FLATCAR_VERSION" if [[ "${FLAGS_ignore_version_mismatch}" = "${FLAGS_TRUE}" ]] ; then warn "Ignoring version mismatch as requested." else die "Version mismatch between board flatcar release and SDK container flatcar release." fi fi if [[ -n "${FLAGS_metapkgs}" ]]; then mapfile -t metapkgs < <(tr ',' '\n' <<<"${FLAGS_metapkgs}") "emerge-${FLAGS_board}" --nodeps --buildpkgonly --usepkg n --verbose "${metapkgs[@]}" set -- "${metapkgs[@]}" "${@}" fi if [[ ${#} -lt 1 ]]; then error 'No packages or meta packages to install.' show_help_if_requested -h fi info "Building '${SYSEXTNAME}' squashfs with (meta-)packages '${@}' in '${BUILD_DIR}' using '${FLAGS_compression}' compression". for package; do echo "Installing package into sysext image: $package" FEATURES="-ebuild-locks binpkg-multi-instance" emerge \ --root="${BUILD_DIR}/install-root" \ --config-root="/build/${FLAGS_board}" \ --sysroot="/build/${FLAGS_board}" \ --usepkgonly \ --binpkg-respect-use=y \ --getbinpkg \ --verbose \ --jobs=${NUM_JOBS} \ "${package}" done # Make squashfs generation more reproducible. export SOURCE_DATE_EPOCH=$(stat -c '%Y' "${BUILD_DIR}/fs-root/usr/lib/os-release") # Unmount in order to get rid of the overlay umount "${BUILD_DIR}/install-root" umount "${BUILD_DIR}/fs-root" if [[ "$FLAGS_generate_pkginfo" = "${FLAGS_TRUE}" ]] ; then info " Creating pkginfo squashfs '${BUILD_DIR}/${SYSEXTNAME}_pkginfo.raw'" mkdir -p "${BUILD_DIR}/img-pkginfo/var/db" cp -R "${BUILD_DIR}/install-root/var/db/pkg" "${BUILD_DIR}/img-pkginfo/var/db/" mksquashfs "${BUILD_DIR}/img-pkginfo" "${BUILD_DIR}/${SYSEXTNAME}_pkginfo.raw" \ -noappend -xattrs-exclude '^btrfs.' -comp "${FLAGS_compression}" ${FLAGS_mksquashfs_opts} fi info "Writing ${SYSEXTNAME}_packages.txt" ROOT="${BUILD_DIR}/install-root" PORTAGE_CONFIGROOT="${BUILD_DIR}/install-root" \ equery --no-color list --format '$cpv::$repo' '*' > "${BUILD_DIR}/${SYSEXTNAME}_packages.txt" if [[ "${FLAGS_strip_binaries}" = "${FLAGS_TRUE}" ]]; then chost="$("portageq-${BOARD}" envvar CHOST)" strip="${chost}-strip" info "Stripping all non-stripped binaries in sysext using '${strip}'" # Find all non-stripped binaries, remove ':' from filepath, and strip 'em find "${BUILD_DIR}/install-root" -exec file \{\} \; \ | awk '/not stripped/ {print substr($1, 1, length($1)-1)}' \ | while read bin; do info " ${strip} ${bin}" "${strip}" "${bin}" done fi if [[ -n "${FLAGS_manglefs_script}" ]]; then if [[ ! -x "${FLAGS_manglefs_script}" ]]; then die "${FLAGS_manglefs_script} is not executable" fi "${FLAGS_manglefs_script}" "${BUILD_DIR}/install-root" fi info "Removing non-/usr directories from sysext image" for entry in "${BUILD_DIR}/install-root"/*; do if [[ "${entry}" = */usr ]]; then continue fi info " Removing ${entry##*/}" rm -rf "${entry}" done mkdir -p "${BUILD_DIR}/install-root/usr/lib/extension-release.d" version_field="${VERSION_FIELD_OVERRIDE:-VERSION_ID=${FLATCAR_VERSION_ID}}" all_fields=( 'ID=flatcar' "${version_field}" "ARCHITECTURE=${ARCH}" ) printf '%s\n' "${all_fields[@]}" >"${BUILD_DIR}/install-root/usr/lib/extension-release.d/extension-release.${SYSEXTNAME}" info "Removing opaque directory markers to always merge all contents" find "${BUILD_DIR}/install-root" -xdev -type d -exec sh -c 'if [ "$(attr -R -q -g overlay.opaque {} 2>/dev/null)" = y ]; then attr -R -r overlay.opaque {}; fi' \; info "Checking for invalid file ownership" invalid_files=$(find "${BUILD_DIR}/install-root" -user sdk -or -group sdk) if [[ -n "${invalid_files}" ]]; then die "Invalid file ownership: ${invalid_files}" fi mksquashfs "${BUILD_DIR}/install-root" "${BUILD_DIR}/${SYSEXTNAME}.raw" \ -noappend -xattrs-exclude '^btrfs.' -comp "${FLAGS_compression}" ${FLAGS_mksquashfs_opts} rm -rf "${BUILD_DIR}"/{fs-root,install-root,workdir} # Generate reports mkdir "${BUILD_DIR}/img-rootfs" mount -rt squashfs -o loop,nodev "${BUILD_DIR}/${SYSEXTNAME}.raw" "${BUILD_DIR}/img-rootfs" write_contents "${BUILD_DIR}/img-rootfs" "${BUILD_DIR}/${SYSEXTNAME}_contents.txt" write_contents_with_technical_details "${BUILD_DIR}/img-rootfs" "${BUILD_DIR}/${SYSEXTNAME}_contents_wtd.txt" write_disk_space_usage_in_paths "${BUILD_DIR}/img-rootfs" "${BUILD_DIR}/${SYSEXTNAME}_disk_usage.txt" umount "${BUILD_DIR}/img-rootfs"