#!/bin/bash

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

error_handler() {
   true "\
###########################################################
## ERROR: Something went wrong. Please report this bug!
##
## BASH_COMMAND: $BASH_COMMAND
###########################################################\
"
   exit 1
}

pre_upgrade_error() {
   true "\
ERROR: An error was encountered during upgrade preparation:
The following command failed:
BASH_COMMAND: $BASH_COMMAND
The system is in a broken state prior upgrade.
Therefore aborting upgrade."
   exit 1
}

post_upgrade_failure() {
   true "\
ERROR: An error was encountered after release upgrade:
The following command failed:
BASH_COMMAND: $BASH_COMMAND"
   exit 1
}

meta_package_detect() {
   if [ ! "$meta_package" = "" ]; then
      return 0
   fi
   if test -e /usr/share/qubes/marker-vm ; then
      if test -e /usr/share/whonix/marker ; then
         if test -e /usr/share/anon-gw-base-files/gateway ; then
            meta_package=qubes-whonix-gateway
            if meta_package_installed_check ; then return 0 ; fi
         fi
         if test -e /usr/share/anon-ws-base-files/workstation ; then
            meta_package=qubes-whonix-workstation
            if meta_package_installed_check ; then return 0 ; fi
         fi
      elif test -e /usr/share/kicksecure/marker ; then
         meta_package=kicksecure-qubes-gui
         if meta_package_installed_check ; then return 0 ; fi
         meta_package=kicksecure-qubes-cli
         if meta_package_installed_check ; then return 0 ; fi
      fi
   else
      if test -e /usr/share/whonix/marker ; then
         if test -e /usr/share/anon-gw-base-files/gateway ; then
            meta_package=non-qubes-whonix-gateway-xfce
            if meta_package_installed_check ; then return 0 ; fi
            meta_package=non-qubes-whonix-gateway-cli
            if meta_package_installed_check ; then return 0 ; fi
         elif test -e /usr/share/anon-ws-base-files/workstation ; then
            meta_package=non-qubes-whonix-workstation-xfce
            if meta_package_installed_check ; then return 0 ; fi
            meta_package=non-qubes-whonix-workstation-cli
            if meta_package_installed_check ; then return 0 ; fi
         fi
      elif test -e /usr/share/kicksecure/marker ; then
         meta_package=kicksecure-xfce
         if meta_package_installed_check ; then return 0 ; fi
         meta_package=kicksecure-cli
         if meta_package_installed_check ; then return 0 ; fi
      fi
   fi
   meta_package=""
}

meta_package_installed_check() {
   if dpkg --status "$meta_package" &> /dev/null ; then
      return 0
   fi
   return 1
}

set -x
set -e
set -o pipefail

shopt -s nullglob

version="2.0"

[[ -v source_codename ]] || source_codename=bullseye
[[ -v target_codename ]] || target_codename=bookworm

true "INFO: (release-upgrade version: $version) Release upgrade from Debian $source_codename to Debian $target_codename in progress..."

trap "error_handler" ERR

if [ "$(id -u)" != "0" ]; then
   true "ERROR: Must run as root. Run:"
   true "sudo $0"
   exit 112
fi

export DEBDEBUG=1

true "INFO: Checking if tor@default systemd unit is running..."
if ! systemctl --no-pager status tor@default ; then
   true "INFO: tor@default is not running, trying to restart..."
   systemctl --no-pager restart tor@default || true
fi

if ! test -f /etc/apt/sources.list.d/debian.list ; then
   true "ERROR: File /etc/apt/sources.list.d/debian.list does not exist! Aborting."
   exit 1
fi

if ! test -f /etc/apt/sources.list.d/derivative.list ; then
   true "ERROR: File /etc/apt/sources.list.d/derivative.list does not exist!

Derivative repository is not enabled. Enable it first before proceeding.

See:
/wiki/Project-APT-Repository

Aborting."
   exit 1
fi

derivative_list_contents="$(cat "/etc/apt/sources.list.d/derivative.list" | grep --invert-match '\#' | grep --invert-match '^$')" || true
if [ "$derivative_list_contents" = "" ]; then
   true "ERROR: File /etc/apt/sources.list.d/derivative.list has no valid entries.

(Maybe only out commented (#) and empty lines.)

Derivative repository is not enabled. Enable it first before proceeding.

See:
/wiki/Project-APT-Repository

Aborting."
   exit 1
fi

## Not ideal because of hardcoded domain names.
if ! echo "$derivative_list_contents" | grep --quiet "kicksecure.com" ; then
   if ! echo "$derivative_list_contents" | grep --quiet "w5j6stm77zs6652pgsij4awcjeel3eco7kvipheu6mtr623eyyehj4yd.onion" ; then
      true "ERROR: File /etc/apt/sources.list.d/derivative.list does not contain the Kicksecure repository.
Kicksecure repository is not enabled. Enable it first before proceeding.

See:
https://www.kicksecure.com/wiki/Project-APT-Repository

Aborting."
      exit 1
   fi
fi

if ! echo "$derivative_list_contents" | grep --quiet "whonix.org" ; then
   if ! echo "$derivative_list_contents" | grep --quiet "http://deb.w5j6stm77zs6652pgsij4awcjeel3eco7kvipheu6mtr623eyyehj4yd.onion" ; then
      if test -e /usr/share/whonix/marker ; then
         true "ERROR: File /etc/apt/sources.list.d/derivative.list does not contain the Whonix repository.
Whonix repository is not enabled. Enable it first before proceeding.

See:
https://www.whonix.org/wiki/Project-APT-Repository

Aborting."
         exit 1
      fi
   fi
fi

meta_package_detect

if [ "$meta_package" = "" ]; then
   true "ERROR: no installed meta package detected!"
   exit 1
fi

true "INFO: meta_package detected: $meta_package"

trap "pre_upgrade_error" ERR

true "INFO: Checking potential issues and attempt to fix if any (pre)..."
apt-get-noninteractive --yes --no-install-recommends --fix-broken install || true
true "INFO: Checking potential issues and attempt to fix if any (pre)..."
dpkg-noninteractive --configure -a || true
true "INFO: Checking potential issues and attempt to fix if any (pre)..."
apt-get-noninteractive --yes --no-install-recommends --fix-broken install || true

true "INFO: Running sanity test (pre)..."
## Do not continue with release upgrade if there are any dpkg issues.
dpkg-noninteractive --audit
true "INFO: Running sanity test (pre)..."
dpkg-noninteractive --configure -a

true "INFO: Showing unofficial packages packages (excluding Whonix and Qubes) (pre):"
apt-forktracer | grep --invert-match "whonix:" | grep --invert-match "Qubes Debian:" || true

true "INFO: Running deborphan (pre)..."
deborphan

true "INFO: Fetching package lists (1/2)..."

## TODO: This can fail if the oldstable repository is no longer available.
## https://forums.whonix.org/t/solved-in-place-release-upgrade-not-succeeding-too-late/20003
if ! apt-get update ; then
   true "\
ERROR:
Could not fetch package lists. Networking issues?
Therefore aborting upgrade.
Recommendation: perform a Standard 'everyday' upgrade beforehand as per:
/wiki/Operating_System_Software_and_Updates"
   exit 1
fi

## dpkg-noninteractive is provided by package usability-misc.

true "INFO: Running sanity test..."
dpkg-noninteractive --audit
true "INFO: Running sanity test..."
dpkg-noninteractive --configure -a

trap "error_handler" ERR

true "INFO: Backports, testing, unstable should be disabled during release upgrade.
Removing files which are mentioned in the wiki, if any...
/wiki/Install_Software"
rm -f /etc/apt/sources.list.d/backports.list
rm -f /etc/apt/sources.list.d/testing.list
rm -f /etc/apt/sources.list.d/unstable.list
rm -f /etc/apt/apt.conf.d/70defaultrelease

true "INFO: Change APT suite from '$source_codename' to '$target_codename'..."
for file_name in /etc/apt/sources.list /etc/apt/sources.list.d/* ; do
   LANG=C str_replace "$source_codename" "$target_codename" "$file_name"
done

LANG=C str_replace "$target_codename/updates" "$target_codename-security" "/etc/apt/sources.list.d/debian.list"

## Get $target_codename apt package lists.
true "INFO: Fetching package lists (2/2)..."
if ! apt-get update ; then
   true "\
ERROR: An error was encountered during download of release upgrade package lists. Recommendation:
- Fix network connection and retry.
- Check the following files:
/etc/apt/sources.list
/etc/apt/sources.list.d/debian.list
/etc/apt/sources.list.d/derivative.list
Check all other files in /etc/apt/sources.list.d if any."
   exit 1
fi

true "INFO: Download release upgrade..."
if ! apt-get-noninteractive --yes --no-install-recommends --download-only full-upgrade ; then
   true "\
ERROR: An error was encountered during download of release upgrade packages. Recommendation:
Fix network connection and retry."
   exit 1
fi

true "INFO: Simulating release upgrade..."
apt-get-noninteractive --no-install-recommends --simulate full-upgrade
true "INFO: Checking if release upgrade is safe or would result in removal of the main meta package... This will take a moment..."
## Disable xtrace because the following output would be too much of an output flood.
set +x
while read -r -d $'\n' line ; do
   read -r first_word second_word _ <<< "$line"
   if [ ! "$first_word" = "Remv" ]; then
      continue
   fi
   if [ ! "$second_word" = "$meta_package" ]; then
      continue
   fi
   ## `echo`, not `true` because "set +x" was set above.
   echo "ERROR: Release upgrade would remove meta package $meta_package. Release upgrade aborted!"
   exit 1
done < <( apt-get-noninteractive --no-install-recommends --simulate full-upgrade )
set -x

if [ "$1" = "downloadonly" ]; then
   true "INFO: downloadonly. Stop."
   exit 0
fi

true "INFO: Install release upgrade..."
if ! apt-get-noninteractive --yes --no-install-recommends full-upgrade ; then
   true "\
ERROR: An error was encountered during installation of release upgrade. See above."
   exit 1
fi

trap "post_upgrade_failure" ERR

true "INFO: Checking potential issues and attempt to fix if any (post)..."
apt-get-noninteractive --yes --no-install-recommends --fix-broken install || true
true "INFO: Checking potential issues and attempt to fix if any (post)..."
dpkg-noninteractive --configure -a || true
true "INFO: Checking potential issues and attempt to fix if any (post)..."
apt-get-noninteractive --yes --no-install-recommends --fix-broken install || true

#true "INFO: Setting a list of traditional dummy packages to automatically installed so these can be removed the next time the user runs apt autoremove."
## '>/dev/null' to hide confusing messages such as "libcomerr2 can not be marked as it is not installed."
## '|| true' since non-essential.
#apt-mark auto currently-non-TODO-remove >/dev/null || true

true "INFO: Running sanity test (post)..."
dpkg-noninteractive --audit
true "INFO: Running sanity test (post)..."
dpkg-noninteractive --configure -a

true "INFO: Restart legacy-dist service..."
service legacy-dist restart

true "INFO: Running sanity test (post)..."
dpkg-noninteractive --audit
true "INFO: Running sanity test (post)..."
dpkg-noninteractive --configure -a

true "INFO: Showing unofficial packages packages (excluding Whonix and Qubes) (post):"
apt-forktracer | grep --invert-match "whonix:" | grep --invert-match "Qubes Debian:" || true

true "INFO: Running deborphan (post)..."
deborphan

## Risky. User should review and do manually.
## /wiki/Debian_Packages#Re-install_Meta_Packages_and_Safely_Run_Autoremove
#apt-get-noninteractive --yes autoremove
#dpkg-noninteractive --audit
#dpkg-noninteractive --configure -a

## Currently not required.
#rm -f /var/cache/anon-base-files/first-boot-skel.done || true
#/usr/libexec/helper-scripts/first-boot-skel || true

true "INFO: OK. (release-upgrade version: $version) Release upgrade success."
