#!/bin/bash

## Copyright (C) 2025 - 2025 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## This script is executed on the host with the purpose of setting up
## required volume directories and executing the docker run command with any given arguments.

## TODO:
## amd64 images build under a Debian 12 Docker image end up with no BIOS bootloader due to lsblk malfunctioning #348
## https://github.com/grml/grml-debootstrap/issues/348
## related:
## lsblk fails to report partition type UUIDs within a privileged Debian 12 container #50304
## https://github.com/moby/moby/issues/50304

set -x
set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

HOST_USER="$(id -u)"
DOCKER_USER="user"
COMMAND="./derivative-maker"
DOCKER_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" && pwd )"
SOURCE_VOLUME="$( dirname -- "${DOCKER_DIR}" )"
BINARY_VOLUME="$HOME/binary_mnt"
CACHER_VOLUME="$HOME/approx_cache_mnt"
KEY_VOLUME="$HOME/.key_mnt"
IMG="Kicksecure/derivative-maker-docker"
## Arch-qualify the image tag so an amd64 and an arm64 image can never collide
## in a shared docker store or image cache. The image is built natively on the
## runner, so its architecture is the host's; 'uname --machine' (e.g. x86_64 /
## aarch64) names it unambiguously. ci/dm-docker-image-cache derives the same
## reference.
image_arch="$(uname --machine)"
image_ref="${IMG}:${image_arch}"
[[ -v CI ]] || CI=""

if [ "${CI:-}" = "true" ]; then
  sudo_options_maybe+=("--non-interactive")
fi
[[ -v sudo_options_maybe ]] || sudo_options_maybe=()

sudo "${sudo_options_maybe[@]}" test -d /usr


print_usage() {
  cat -- << EOF
  Usage: $0 [ options ]

  General Options:
    -b|--build-step  : build-step in derivative-maker/build-steps.d/
    -c|--custom      : custom command or shell compliant command chain
    --binary-mount   : Changes the binary artifact directory
    --cacher-mount   : Changes the package cache directory
    --key-mount      : Changes the keystore directory
    -h|--help        : print usage dialog
EOF
}

volume_prepare() {
  declare -a -- VOLUMES

  ## CACHER_VOLUME is owned by the in-container 'approx' user; its uid:gid is
  ## pinned to 101:102 at image build (derivative-maker-docker-setup) so this
  ## literal stays correct. Kept in sync with ci/approx-cache-sidecar.
  VOLUMES=(
    "${CACHER_VOLUME}" "101:102" "770"
    "${BINARY_VOLUME}" "${HOST_USER}:${HOST_USER}" "770"
    "${KEY_VOLUME}" "${HOST_USER}:${HOST_USER}" "700"
  )

  while (( ${#VOLUMES[@]} > 0 )); do
    if [ ! -d "${VOLUMES[0]}" ]; then
      mkdir --parents -- "${VOLUMES[0]}"
      sudo "${sudo_options_maybe[@]}" -- chown --recursive -- "${VOLUMES[1]}" "${VOLUMES[0]}"
      sudo "${sudo_options_maybe[@]}" -- chmod --recursive -- "${VOLUMES[2]}" "${VOLUMES[0]}"
    fi

    VOLUMES=("${VOLUMES[@]:3}")
  done
}

build_docker_image() {
  ## Hoist the subshell out of the command arguments so 'set -o errexit'
  ## catches a failing 'id' instead of silently passing an empty --build-arg.
  local dm_uid
  dm_uid="$(id -u)"
  if [ -z "$(docker images --quiet -- "${image_ref}" 2> /dev/null)" ]; then
    sudo \
    "${sudo_options_maybe[@]}" \
    -- \
      docker \
        build \
        --build-arg \
        DM_UID="${dm_uid}" \
        --tag \
        "${image_ref}" \
        "${DOCKER_DIR}"
  fi
}

## TODO: Consider what should happen when derivative-maker-docker-run is run without arguments.

while true; do
  case "${1:-}" in
    -b|--build-step)
      COMMAND="build-steps.d/${2}"
      shift 2
      ;;
    -c|--custom)
      COMMAND="${2}"
      shift 2
      ;;
    --binary-mount)
      BINARY_VOLUME="${2}"
      shift 2
      ;;
    --cacher-mount)
      CACHER_VOLUME="${2}"
      shift 2
      ;;
    --key-mount)
      KEY_VOLUME="${2}"
      shift 2
      ;;
    -h|--help)
      print_usage
      exit 0
      ;;
    --)
      shift
      break
      ;;
    *)
      break
      ;;
  esac
done

## The container intentionally does NOT run derivative-update to fetch/merge
## git submodules. It builds the source tree exactly as bind-mounted from the
## host (SOURCE_VOLUME); keeping that checkout current is the host's
## responsibility.

build_docker_image

volume_prepare

sudo "${sudo_options_maybe[@]}" -- modprobe -a loop dm_mod

docker_run_opts=()

## In CI environments (e.g. Ansible shell tasks), stdin is /dev/null and
## there is no controlling terminal.  'docker run --interactive --tty'
## requires a real TTY on stdin; without one Docker prints
## "the input device is not a TTY" and exits non-zero.
if [ "${CI:-}" = "true" ]; then
  ## Keep --tty: it allocates a pty so the systemd entrypoint service streams
  ## build output to docker stdout (live CI logs). Drop --interactive: it
  ## needs a real host TTY. --env CI=true tells the container it is headless.
  docker_run_opts+=( --tty --env 'CI=true' )
else
  docker_run_opts+=( --interactive --tty )
fi

sudo \
  "${sudo_options_maybe[@]}" \
  -- \
    docker \
      run \
      --name derivative-maker-docker \
      "${docker_run_opts[@]}" \
      --rm \
      --privileged \
      --env 'flavor_meta_packages_to_install=' \
      --env 'install_package_list=' \
      --env 'DERIVATIVE_APT_REPOSITORY_OPTS=' \
      --volume "${SOURCE_VOLUME}:/home/${DOCKER_USER}/derivative-maker" \
      --volume "${BINARY_VOLUME}:/home/${DOCKER_USER}/derivative-binary" \
      --volume "${CACHER_VOLUME}:/var/cache/approx-derivative-maker" \
      --volume "${KEY_VOLUME}:/home/${DOCKER_USER}/.gnupg" \
      "${image_ref}" \
        sudo \
          --non-interactive \
          --preserve-env \
          -u "${DOCKER_USER}" \
          user_name="${DOCKER_USER}" \
          -- \
            "/usr/bin/derivative-maker-docker-start" "${COMMAND}" "${@}"
