#!/bin/sh
# Copyright (c) 2017, 2021, 2023, 2024 Jonas 'Sortie' Termansen.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# tix-upgrade
# Upgrade operating system and ports.

set -e

cachedir=""
cancel=false
clean=false
collection=/
download_only=false
fetch_options=
sysroot=""
upgrade=--upgrade
upgrade_ports=false
upgrade_system=false
wait=""

dashdash=
previous_option=
for argument do
  if test -n "$previous_option"; then
    eval $previous_option=\$argument
    previous_option=
    shift
    continue
  fi

  case $argument in
  *=?*) parameter=$(expr "X$argument" : '[^=]*=\(.*\)' || true) ;;
  *=)   parameter= ;;
  *)    parameter=yes ;;
  esac

  case $dashdash$argument in
  --) dashdash=yes ;;
  -w) wait=-w ;;
  --cachedir=*) cachedir=$parameter ;;
  --cachedir) previous_option=cachedir ;;
  --cancel) cancel=true ;;
  --clean) clean=true ;;
  --collection=*) collection=$parameter ;;
  --collection) previous_option=collection ;;
  --download-only) download_only=true ;;
  --fetch-options=*) fetch_options="$parameter" ;;
  --fetch-options) previous_option=fetch_options ;;
  --insecure-downgrade-to-http) fetch_options="$fetch_options $argument" ;;
  --insecure-no-check-certificate) fetch_options="$fetch_options $argument" ;;
  --no-upgrade) upgrade= ;;
  --ports) upgrade_ports=true ;;
  --system) upgrade_system=true ;;
  --sysroot) previous_option=sysroot ;;
  --sysroot=*) sysroot=$parameter ;;
  --wait) wait=--wait ;;
  -*) echo "$0: unrecognized option $argument" >&2
      exit 1 ;;
  *) break ;;
  esac

  shift
done

if test -n "$previous_option"; then
  echo "$0: option '$argument' requires an argument" >&2
  exit 1
fi

if [ 0 -lt $# ]; then
  echo "$0: Unexpected extra operand: $1" >&2
  exit 1
fi

conf() {
  sed -E -e 's/([a-zA-Z0-9_]+) *? *= */\U\1=/' \
         -e 's/=yes$/=true/' -e 's/no$/=false/' "$3" | \
  tix-vars -d "$2" - "$4"
}

escape_extended_regex_sed() {
  printf "%s\n" "$1" | sed -E -e 's/[[$()*?\+.^{|}'"$2"']/\\\0/g'
}

collection=$(cd "$collection" && pwd)

if ! $upgrade_ports && ! $upgrade_system; then
  upgrade_ports=true
  upgrade_system=true
  if [ -e "$collection/etc/upgrade.conf" ]; then
    upgrade_ports=$(conf -d true "$collection/etc/upgrade.conf" PORTS)
    upgrade_system=$(conf -d true "$collection/etc/upgrade.conf" SYSTEM)
  fi
fi

# If this isn't a system installation, only upgrade the ports.
if [ ! -e "$collection/tix/manifest/system" ]; then
  upgrade_system=false
fi

case "$upgrade_system$upgrade_ports" in
truefalse) what_to_upgrade=--system;;
falsetrue) what_to_upgrade=--ports;;
*) what_to_upgrade=;;
esac

if [ -z "$cachedir" ]; then
  cachedir="${collection%/}/var/cache/tix"
fi

if $cancel || $clean; then
  echo "Removing cache directory: $cachedir"
  rm -rf -- "$cachedir"
fi

if $cancel; then
  sysmerge -t "$collection" --cancel
  exit
fi
mkdir -p -- "$cachedir"
mkdir -p -- "$cachedir/new"

# Fetch the latest official signed release.sh and its matching sha256sum file.
tix-fetch $fetch_options \
          --collection="$collection" \
          --output-release-file="$cachedir/new/release.sh" \
          --output-sha256sum="$cachedir/new/sha256sum" \
          --output-upgrade-file="$cachedir/new/upgrade.sh" \
          $upgrade

# If release.sh or sha256sum changed, clean the cache directory of downloads
# that were currently in progress as they might not have the right checksums.
if [ ! -e "$cachedir/release.sh" ] ||
   [ ! -e "$cachedir/sha256sum" ] ||
   [ ! -e "$cachedir/upgrade.sh" ] ||
   ! (cd "$cachedir/new" && sha256sum release.sh sha256sum upgrade.sh) |
     (cd "$cachedir" && sha256sum -cs); then
  rm -rf -- "$cachedir/boot"
  rm -rf -- "$cachedir/repository"
  rm -rf -- "$cachedir/sysroot"
fi

# Store the new release.sh and sha256sum files so we can resume the download
# if cancelled and these files still match.
mv -- "$cachedir/new/release.sh" "$cachedir/release.sh"
mv -- "$cachedir/new/sha256sum" "$cachedir/sha256sum"
mv -- "$cachedir/new/upgrade.sh" "$cachedir/upgrade.sh"
rm -rf -- "$cachedir/new"

# Check if we're upgrading to a new release.
UPGRADE_SIG_URL=$(tix-vars -d '' "$cachedir/upgrade.sh" UPGRADE_SIG_URL)
if [ -n "$UPGRADE_SIG_URL" ]; then
  UPGRADE_CHANNEL=$(tix-vars "$cachedir/upgrade.sh" UPGRADE_CHANNEL)
  UPGRADE_KEY=$(tix-vars "$cachedir/upgrade.sh" UPGRADE_KEY)
  UPGRADE_NAME=$(tix-vars "$cachedir/upgrade.sh" UPGRADE_NAME)
  if [ -n "$upgrade" ]; then
    echo "Upgrading to $UPGRADE_NAME."
  else
    echo "Ignoring available upgrade to $UPGRADE_NAME."
  fi
fi

# Decide what binary packages to upgrade.
installed_packages=$(LC_ALL=C ls -- "$collection/tix/tixinfo")
if $upgrade_system && $upgrade_ports; then
  upgrade_packages="$installed_packages"
else
  upgrade_packages=
  for package in $installed_packages; do
    is_system=$(tix-vars -d false "$collection/tix/tixinfo/$package" SYSTEM)
    if ($upgrade_system && [ "$is_system" = true ]) ||
       ($upgrade_ports && [ "$is_system" = false ]); then
      upgrade_packages="$upgrade_packages $package"
    fi
  done
fi

# TODO: Tracking sets like minimal/basic/full with new mandatory or recommended ports.

mkdir -p -- "$cachedir/repository"

tix-fetch $fetch_options \
          --collection="$collection" \
          --input-release-file="$cachedir/release.sh" \
          --input-sha256sum="$cachedir/sha256sum" \
          --repository-metadata -O "$cachedir/repository" renames.list

# Follow RENAMES recursively to handle renames, splits, and deletions.
rename() {
  if grep -Eq "^$(escape_extended_regex_sed "$1"):" \
          "$cachedir/repository/renames.list"; then
    for new in $(grep -E "^$(escape_extended_regex_sed "$1"):" \
                      "$cachedir/repository/renames.list" |
                 grep -Eo '[^:]*$'); do
      rename "$new"
    done
  else
    echo "$1" | grep -Eo '^[^@]*'
  fi
}

# Determine the final package list after the renames.
packages=""
for package in $(LC_ALL=C ls -- "$collection/tix/tixinfo"); do
  edition=$(tix-vars -d 1 "$collection/tix/tixinfo/$package" EDITION)
  packages="$packages $(rename "$package@$edition")"
done

# Sort and deduplicate the package list and check for existence.
packages=$(for package in $packages; do
             # The package exists upstream if it has a hash.
             if [ -n "$(tix-fetch $fetch_options \
                          --collection="$collection" \
                          --input-release-file="$cachedir/release.sh" \
                          --input-sha256sum="$cachedir/sha256sum" \
                          --sha256 --package -- $package)" ]; then
               echo $package
             fi
           done | LC_ALL=C sort -u)

# Fetch each binary package from the mirror.
for package in $packages; do
  tix-fetch $fetch_options \
            --collection="$collection" \
            --input-release-file="$cachedir/release.sh" \
            --input-sha256sum="$cachedir/sha256sum" \
            -c --package -O "$cachedir/repository" -- $package
done

# Stop if only downloading.
if $download_only; then
  exit
fi

rm -rf -- "$cachedir/sysroot"
mkdir -p -- "$cachedir/sysroot"

# Forward the upgrade metadata.
UPGRADE_SIG_URL=$(tix-vars -d '' "$cachedir/upgrade.sh" UPGRADE_SIG_URL)
if [ -n $upgrade ] && [ -n "$UPGRADE_SIG_URL" ]; then
  mkdir -p -- "$cachedir/etc"
  # TODO: More flexible and simple model.
  cat > "$cachedir/etc/upgrade.conf" << EOF
channel = $UPGRADE_CHANNEL
release_key = $UPGRADE_KEY
release_sig_url = $UPGRADE_SIG_URL
EOF
fi

# Extract the binary packages into the sysroot.
for package in $packages; do
  echo "Extracting $package.tix.tar.xz..."
  tar -C "$cachedir/sysroot" -xJf "$cachedir/repository/$package.tix.tar.xz"
  rm -f "$cachedir/repository/$package.tix.tar.xz"
done

# Merge the new sysroot onto the installation.
sysmerge -t "$collection" --full $what_to_upgrade $wait "$cachedir/sysroot"

rm -rf -- "$cachedir/repository"
rm -rf -- "$cachedir/sysroot"
