#!/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")/<BOARD>/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_string manglefs_script '' \
  "A path to executable that will customize the rootfs of the sysext image."
DEFINE_boolean ignore_version_mismatch "${FLAGS_FALSE}" \
  "Ignore version mismatch between SDK board packages and base squashfs. DANGEROUS."

FLAGS_HELP="USAGE: build_sysext [flags] <sysext_name> <binary_package> [<binary_package> ...]

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:
  <sysext_name>     - name of the sysext output file.
  <binary_package>  - 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}"

# 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}"

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
}

set -euo pipefail

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
}

# Set up trap to execute cleanup() on script exit
trap cleanup EXIT

ARCH=$(_get_sysext_arch "${FLAGS_board}")
cleanup

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",upperdir="${BUILD_DIR}/install-root",workdir="${BUILD_DIR}/workdir" "${BUILD_DIR}/install-root"
VERSION_BOARD=$(grep "^VERSION=" ${BUILD_DIR}/fs-root/usr/lib/os-release | cut -d = -f 2-)
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}' with (meta-)packages '${@}' in '${BUILD_DIR}'".

for package; do
  echo "Installing package into sysext image: $package"
  FEATURES="-ebuild-locks" emerge \
    --root="${BUILD_DIR}/install-root" \
    --config-root="/build/${FLAGS_board}"  \
    --sysroot="/build/${FLAGS_board}"  \
    --root-deps=rdeps \
    --usepkgonly \
    --verbose \
    "${package}"
done

# Unmount in order to get rid of the overlay
umount "${BUILD_DIR}/install-root"
umount "${BUILD_DIR}/fs-root"

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}"
mksquashfs "${BUILD_DIR}/install-root" "${BUILD_DIR}/${SYSEXTNAME}.raw" -noappend
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"
