#!/bin/bash

#####################################################################
# This script takes a package, downloads all the dependencies and creates
# a small repository from this.
#####################################################################

set -eu

[ -e /etc/debian/debug ] && set -x

#############################################################################
# Reset
Color_Off='\033[0m'       # Text Reset

# Regular Colors
Black='\033[0;30m'        # Black
Red='\033[0;31m'          # Red
Green='\033[0;32m'        # Green
Yellow='\033[0;33m'       # Yellow
Blue='\033[0;34m'         # Blue
Purple='\033[0;35m'       # Purple
Cyan='\033[0;36m'         # Cyan
White='\033[0;37m'        # White

# Bold
BBlack='\033[1;30m'       # Black
BRed='\033[1;31m'         # Red
BGreen='\033[1;32m'       # Green
BYellow='\033[1;33m'      # Yellow
BBlue='\033[1;34m'        # Blue
BPurple='\033[1;35m'      # Purple
BCyan='\033[1;36m'        # Cyan
BWhite='\033[1;37m'       # White

# Underline
UBlack='\033[4;30m'       # Black
URed='\033[4;31m'         # Red
UGreen='\033[4;32m'       # Green
UYellow='\033[4;33m'      # Yellow
UBlue='\033[4;34m'        # Blue
UPurple='\033[4;35m'      # Purple
UCyan='\033[4;36m'        # Cyan
UWhite='\033[4;37m'       # White

# Background
On_Black='\033[40m'       # Black
On_Red='\033[41m'         # Red
On_Green='\033[42m'       # Green
On_Yellow='\033[43m'      # Yellow
On_Blue='\033[44m'        # Blue
On_Purple='\033[45m'      # Purple
On_Cyan='\033[46m'        # Cyan
On_White='\033[47m'       # White

# High Intensity
IBlack='\033[0;90m'       # Black
IRed='\033[0;91m'         # Red
IGreen='\033[0;92m'       # Green
IYellow='\033[0;93m'      # Yellow
IBlue='\033[0;94m'        # Blue
IPurple='\033[0;95m'      # Purple
ICyan='\033[0;96m'        # Cyan
IWhite='\033[0;97m'       # White

# Bold High Intensity
BIBlack='\033[1;90m'      # Black
BIRed='\033[1;91m'        # Red
BIGreen='\033[1;92m'      # Green
BIYellow='\033[1;93m'     # Yellow
BIBlue='\033[1;94m'       # Blue
BIPurple='\033[1;95m'     # Purple
BICyan='\033[1;96m'       # Cyan
BIWhite='\033[1;97m'      # White

# High Intensity backgrounds
On_IBlack='\033[0;100m'   # Black
On_IRed='\033[0;101m'     # Red
On_IGreen='\033[0;102m'   # Green
On_IYellow='\033[0;103m'  # Yellow
On_IBlue='\033[0;104m'    # Blue
On_IPurple='\033[0;105m'  # Purple
On_ICyan='\033[0;106m'    # Cyan
On_IWhite='\033[0;107m'   # White
#############################################################################

#####################################################################
# Variable definitions
#####################################################################

# Options that can be set from the command line
option_package=telnet,tcpdump,traceroute,nmap
option_version=0.1
option_graphics=false
option_debian_suite="stable"
option_debian_release=bullseye
option_debian_architecture=amd64

DEBIAN_MIRROR="http://deb.debian.org/"
TIMESTAMP="$(date +%F)-$(date +%H%M%S)"

function show_help {
  echo """
NAME
       package-to-repo-run - Debian Self-Extracting addon generator

SYNOPSIS
       package-to-repo-run --package vim

DESCRIPTION
       package-to-repo-run is a tool to generate a self-extracting repo
       containing one or multiple package with all dependencies.

       -a --architecture
       The architecture the create the self extracting repository for

       -p --package
       The package to use as the top level dependency (can be comma
       separated list, no spaces)

       -h --help
       Show this help message.

       -r --release
       The suite to build against. Can be stable, testing or unstable

       -s --suite
       The suite to build against. Can be stable, testing or unstable

       -v --version
       The version to embed in the self-extracting file
"""
exit 0
}


ARGS=$(getopt -o "a:p:hr:s:S:v:" -l "architecture:,package:,help,release:,suite:,system:,version:" -n "package-to-repo-run" -- "$@")

eval set -- "$ARGS"

while true; do
  case $1 in
    -h|--help)
      show_help
      exit;;
    -a|--architecture)
      option_debian_architecture=$2
      shift;
      shift;;
    -p|--package)
      option_package=$2
      shift;
      shift;;
    -r|--release)
      option_debian_release=$2
      shift;
      shift;;
    -s|--suite)
      option_debian_suite=$2
      haystack="unstable testing stable"
      [[ " ${haystack} " =~ .*\ ${option_debian_suite}\ .* ]] || { echo "${option_debian_suite} is not valid, needs to be in ${haystack}."; exit 1; }
      shift;
      shift;;
    -S|--system)
      option_package=$2
      shift;
      shift;;
    -v|--version)
      option_version=$2
      shift;
      shift;;
    --)
      shift
      break;;
  esac
done

# Internally used directories.
CURRENT_DIR=$(pwd)
WORKING_DIR=$(mktemp -d -p "${CURRENT_DIR}" -t ptr-XXXXX)

REPO_BASE=${WORKING_DIR}/localcd
PACKAGE_CACHE=${WORKING_DIR}/package-cache
ARCHIVE_ARTIFACTS=${WORKING_DIR}/archive-artifacts

# FIXME: These perl packages need to be added since they are not
# downloaded. Probably because they are core.
APT_CUSTOM_HOME=${WORKING_DIR}

#####################################################################
# Functions
#####################################################################

# am_i_root
#
# test if the script is run as root
function am_i_root {
  echo "Original user (UID) is $(id -u -r), effective user (EUID) is $(id -u)"

  if [[ $(id -u) -ne 0 ]]; then
    echo "This script must be run as root" 1>&2
    exit 1
  fi
}

function cleanup_working_dir {
  # Remove clutter. This are cache directories and previous builds
  [ -d "${REPO_BASE}" ] && rm -rf "${REPO_BASE}"
  [ -d "${PACKAGE_CACHE}" ] && rm -rf "${PACKAGE_CACHE}"
  [ -d "${APT_CUSTOM_HOME}" ] && rm -rf "${APT_CUSTOM_HOME}"

  [ -f config-deb ] && rm -f config-deb
  # [ -n "${TEMP_INITRD}" ] && [ -d "${TEMP_INITRD}" ] && rm -rf "${TEMP_INITRD}"

  echo "Cleanup done."
}

function download_packages_for_iso {
  mkdir -p "${PACKAGE_CACHE}"

  chmod a+rwx "${PACKAGE_CACHE}"
  cd "${PACKAGE_CACHE}"

  # See what the dependencies are and download them
  for package in ${option_package//,/ }; do
    # This is a loop to retry 5 times. With the addition of
    # APT::Acquire::Retries in the configuration file, this has become
    # just an extra safe guard.
    cnt=0
    while [ ${cnt} -lt 5 ]; do
      apt-get -c "${APT_CUSTOM_HOME}"/etc/apt/apt.cfg download $(apt-cache -c "${APT_CUSTOM_HOME}"/etc/apt/apt.cfg depends --recurse --important "${package}" | grep "^\w" | cut -f 1 -d \:  | sort -u ) \
        && { echo "Package ${package} downloaded with dependencies"; break; } \
        || { echo "Could not download all packages from ${DEBIAN_MIRROR} mirror"; let cnt=cnt+1; }
    done
    [ ${cnt} -lt 5 ] || { echo "Could not download the packages, even after ${cnt} retries."; exit 1; }
  done
  cd ..
}

function exclude_blacklisted_packages {
  BLACKLISTED_PACKAGES="apt libapt-inst2.0 libapt-pkg5.0"
  for package in ${BLACKLISTED_PACKAGES}; do
    find "${PACKAGE_CACHE}" -name "${package}_*.deb" -delete
    echo "Blacklisted package ${package} deleted."
  done
}

function store_file {
  i=$1

  # store the packages all in main, not in contrib nor in non-free
  echo "Inspecting ${i}, ..."
  ldir=$(dirname $(apt-cache -c "${APT_CUSTOM_HOME}"/etc/apt/apt.cfg show "${i}" |grep Filename |head -n 1 | cut -f 2 -d \: | sed -e 's/\/non-free\//\/main\//' -e 's/\/contrib\//\/main\//' ))
  mkdir -p "${REPO_BASE}"/"${ldir}"
  mv "${PACKAGE_CACHE}"/"${i}"_*.deb "${REPO_BASE}"/"${ldir}"/
}

function move_downloaded_package_into_iso {
  # For all the package names, create a directory in the iso and store
  # the files there.
  N=8
  i=0
  for package in $(find "${PACKAGE_CACHE}" -type f -name '*.deb' | sort -z | while read i; do dpkg -f "${i}" Package; done); do
    ((i=i%N)) || : ; ((i++==0)) && wait || :
    store_file "${package}" &
  done

  # All tasks should be finished
  wait
}

function prepare_system_apt {

  for dir in /var/lib/dpkg/ /etc/apt/sources.list.d/ /var/lib/apt/lists/partial /etc/apt/preferences.d/; do
    mkdir -p "${APT_CUSTOM_HOME}/${dir}"
  done
  touch "${APT_CUSTOM_HOME}"/var/lib/dpkg/status

  # The default debian keys, just in case
  for key in  648ACFD622F3D138 0E98404D386FA1D9 605C66F00D6C9793; do
    apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "${key}" &> /dev/nulll
  done
  # copy in the trust db, apt-key does not work well with the -c flag
  cp /etc/apt/trusted.gpg "${APT_CUSTOM_HOME}/etc/apt/"
  cp -a /etc/apt/trusted.gpg.d "${APT_CUSTOM_HOME}/etc/apt/"

  echo """Dir \"${APT_CUSTOM_HOME}\"
{
};
Acquire
{
  Retries \"20\";
};
APT
{
  Architecture \"${option_debian_architecture}\";
}
""" > "${APT_CUSTOM_HOME}/etc/apt/apt.cfg"

  # The builder needs to have a higher priority than the mirror to
  # make certain these packages are preferred (the packages only sync
  # nightly from the builder to the mirror, so last moment fixes need to
  # be accepted.
  echo """Package: *
Pin: origin ${DEBIAN_MIRROR}
Pin-Priority: 500
""" > "${APT_CUSTOM_HOME}/etc/apt/preferences.d/99debian-package-to-repo-run"

  echo """
deb ${DEBIAN_MIRROR}/debian/ ${option_debian_release} main
""" > "${APT_CUSTOM_HOME}/etc/apt/sources.list.d/debian-package-to-repo-run-${option_debian_release}.list"

  DEBIAN_FRONTEND=noninteractive apt-get -c "${APT_CUSTOM_HOME}/etc/apt/apt.cfg" clean
  DEBIAN_FRONTEND=noninteractive apt-get -c "${APT_CUSTOM_HOME}/etc/apt/apt.cfg" -y update \
    || { echo "There was a problem updating the Debian repositories."; }
}

function regenerate_iso_repository {
  # Update the Release files and MD5 files of the remastered CD
  echo """
# A config-deb file.

# Points to where the unpacked DVD-1 is.
Dir {
  ArchiveDir \"${REPO_BASE}\";
};

# Sets the top of the .deb directory tree.
TreeDefault {
  Directory \"pool/\";
};

# The location for a Packages file.
BinDirectory \"pool/main\" {
  Packages \"dists/${option_debian_release}/main/binary-${option_debian_architecture}/Packages\";
};

# We are only interested in .deb files (.udeb for udeb files).
Default {
  Packages {
    Extensions \".deb\";
  };
};
""" > config-deb


echo """
APT {
  FTPArchive {
    Release {
      Origin \"Debian\";
      Label \"Debian\";
      Suite \"stable\";
      Version \"10.10\";
      Codename \"${option_debian_release}\";
      Architectures \"${option_debian_architecture}\";
      Components \"main contrib non-free\";
    };
  };
};
""" > config-release.conf

  mkdir -p ${REPO_BASE}/dists/${option_debian_release}/main/binary-${option_debian_architecture}/
  apt-ftparchive generate config-deb
  # sed -i '/MD5Sum:/,$d' ${REPO_BASE}/dists/${option_debian_release}/Release || { echo "Could not find Release file"; }
  apt-ftparchive -c config-release.conf release ${REPO_BASE}/dists/${option_debian_release} >> ${REPO_BASE}/dists/${option_debian_release}/Release
  cd ${REPO_BASE}
  md5sum `find ! -name "md5sum.txt" ! -path "./isolinux/*" -follow -type f` > md5sum.txt

  ARCHIVE_FILE=${WORKING_DIR}/repository-archive-${option_package//,/-}-${option_version}-${option_debian_suite}-${option_debian_architecture}-$(date +%Y%m%d-%H%M%S).tar.gz
  tar cfz ${ARCHIVE_FILE} -C ${REPO_BASE} .

  cd ..
}

function is_installed {
  # Simple helper function to test if a package is installed. If not,
  # install it (using Debian package management).
  dpkg -s "$1" &> /dev/null

  if [ $? -eq 0 ]; then
    :
  else
    echo "Package \"$1\" is NOT installed, installing!"
    DEBIAN_FRONTEND=noninteractive apt-get -y install "$1"
  fi
}

function clean {
  # clear the trap
  trap - EXIT

  cleanup_working_dir

  # Kill all child processes when this process gets killed
  kill $(jobs -p) || { echo "Nothing to kill :-("; }

  rm -rf "${WORKING_DIR:?}"
}

generate_self_extract() {
  SELF_EXTRACT_TEMPLATE="/usr/share/debian-package-build/self-extract-template/self-extract.tmpl"
  [ -f "${SELF_EXTRACT_TEMPLATE}" ] \
    || { echo "Could not find template file"; exit 1; }

  SELF_EXTRACT_DIR=$(mktemp -d -p "$(pwd)")

  cp "${SELF_EXTRACT_TEMPLATE}" "${SELF_EXTRACT_DIR}"/"${option_package//,/-}"
  sed -i \
    -e "s/<TEMPLATE-DEBIAN-RELEASE>/${option_debian_release}/g" \
    -e "s/<TEMPLATE-DEBIAN-ARCHITECTURE>/${option_debian_architecture}/g" \
    -e "s/<TEMPLATE-PACKAGE-NAME>/${option_package}/g" \
    "${SELF_EXTRACT_DIR}"/"${option_package//,/-}"

  mkdir -p "${SELF_EXTRACT_DIR}"/repositories
  cp "${ARCHIVE_FILE}" "${SELF_EXTRACT_DIR}"/repositories

  makeself \
    "${SELF_EXTRACT_DIR}" \
    ${CURRENT_DIR}/debian-add-on-"${option_package//,/-}"-"${option_debian_release}"-"${option_debian_suite}"_${option_debian_architecture}_${TIMESTAMP}.run \
    "Debian ${option_debian_release}/${option_debian_suite} ${option_package//,/-} Software Add-On" \
    "./${option_package//,/-}"

  rm -rf "${SELF_EXTRACT_DIR}"
}

#####################################################################
# Start functionality of script
#####################################################################

# Test if the script is run as root, this is needed for the modification
# of the initrd file (CPIO).
# FIXME: investigate fakeroot, this should be enough
am_i_root
cleanup_working_dir
trap clean EXIT
prepare_system_apt
download_packages_for_iso
exclude_blacklisted_packages
move_downloaded_package_into_iso
regenerate_iso_repository
generate_self_extract

exit 0
