#!/bin/bash
# Copyright 2011-2026 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

# WAZO_UPGRADE_ROOT prefixes every absolute path; empty in production
lib_directory="${WAZO_UPGRADE_ROOT:-}/usr/share/wazo-upgrade"
upgrade_incomplete_file="${WAZO_UPGRADE_ROOT:-}/var/lib/wazo-upgrade/upgrade-incomplete"
asterisk_modules_directory="${WAZO_UPGRADE_ROOT:-}/usr/lib/asterisk/modules"
: ${WAZO_CONFD_PORT:='9486'}

APT_OPTIONS='-o DPkg::Lock::Timeout=15'

# force all debian upgrade to non interactive mode
export DEBIAN_FRONTEND=noninteractive
export APT_LISTCHANGES_FRONTEND=none

# Exit statuses: 1 pre-stop or any other fatal error, 2 post-stop,
# 3 pre-start, 4 post-start (non-aborting)
fatal_error() {
	message=$1
	exit_status=${2:-1}
	echo "******************* ERROR *************************"
	echo "An error occurred during the upgrade."
	echo "Please check /var/log/wazo-upgrade.log for details."
	[ -n "$message" ] && echo -e "Error: $message"
	echo "******************* ERROR *************************"
	exit "$exit_status"
}

is_package_installed() {
	[ "$(dpkg-query -W -f '${Status}' "$1" 2>/dev/null)" = 'install ok installed' ]
}

# Sets $failed_script and returns non-zero on the first script failure
run_upgrade_scripts() {
	local state=$1
	local directory="$lib_directory/$state.d"
	failed_script=''
	if [ ! -d "$directory" ]; then
		failed_script=$directory
		return 1
	fi
	shopt -qs nullglob  # avoid failing when action directory has no scripts
	for script in "$directory"/*; do
		echo "Executing upgrade script $script..."
		if ! $script; then
			failed_script=$script
			break
		fi
	done
	shopt -qu nullglob
	[ -z "$failed_script" ]
}

pre_stop() {
	if ! run_upgrade_scripts pre-stop; then
		fatal_error "Failed to execute $failed_script\nPlease, fix this issue before re-executing wazo-upgrade" 1
	fi
}

post_stop() {
	if ! run_upgrade_scripts post-stop; then
		start_wazo
		fatal_error "Failed to execute $failed_script\nPlease, fix this issue then re-execute wazo-upgrade to complete the upgrade" 2
	fi
}

# A stopped Wazo is a worse outage than a partially upgraded one
pre_start() {
	if ! run_upgrade_scripts pre-start; then
		start_wazo
		fatal_error "Failed to execute $failed_script\nThe system is only partially upgraded.\nPlease, fix this issue then re-execute wazo-upgrade to complete the upgrade" 3
	fi
}

# The system is already upgraded and running: report failures without aborting
post_start() {
	local directory="$lib_directory/post-start.d"
	local failed_scripts=''
	local status
	[ -d "$directory" ] || failed_scripts=" $directory"
	shopt -qs nullglob  # avoid failing when post-start directory has no scripts
	for script in "$directory"/*; do
		echo "Executing upgrade script $script..."
		$script
		status=$?
		if [ $status -ne 0 ]; then
			echo -e "\t**Error** running $script: non-zero exit status $status"
			failed_scripts="$failed_scripts $script"
		fi
	done
	shopt -qu nullglob
	if [ -n "$failed_scripts" ]; then
		echo "****************** WARNING ************************"
		echo -e "Failed to execute the following scripts:$failed_scripts"
		echo "The system has been upgraded and Wazo services are running."
		echo "Please check /var/log/wazo-upgrade.log for details."
		echo "****************** WARNING ************************"
		exit 4
	fi
}

execute() {
	cmd=$*
	$cmd
	if [ $? -ne 0 ]; then
		start_wazo
		fatal_error
	fi
}

stop_wazo() {
	local status=0
	wazo-service stop || status=$?
	wazo-service disable || status=$?
	return $status
}

start_wazo() {
	local status=0
	wazo-service enable || status=$?
	wazo-service restart || status=$?
	return $status
}

upgrade() {
	export WAZO_VERSION_INSTALLED=$(wazo_version_installed)
	export WAZO_VERSION_CANDIDATE=$(wazo_version_candidate)
	export XIVO_VERSION_INSTALLED=$WAZO_VERSION_INSTALLED
	export XIVO_VERSION_CANDIDATE=$WAZO_VERSION_CANDIDATE
	touch "$upgrade_incomplete_file"
	pre_stop
	if ! stop_wazo; then
		start_wazo
		fatal_error "Wazo services failed to stop\nPlease, fix this issue then re-execute wazo-upgrade"
	fi
	post_stop
	echo "Upgrading wazo..."
	execute apt-get install $APT_OPTIONS -o Dpkg::Options::="--force-confnew" -y postgresql-15
	execute apt-get install $APT_OPTIONS -o Dpkg::Options::="--force-confnew" -y wazo-platform
	execute apt-get install $APT_OPTIONS -o Dpkg::Options::="--force-confnew" -y xivo-config
	execute apt-get install $APT_OPTIONS -o Dpkg::Options::="--force-confnew" -y rabbitmq-server
	execute apt-mark auto $APT_OPTIONS postgresql-15 xivo-config rabbitmq-server
	execute apt-get dist-upgrade $APT_OPTIONS -y
	execute apt-get autoremove $APT_OPTIONS -y
	wazo-check-conffiles
	pre_start
	# The marker file is kept while Wazo is down to allow a retry
	if ! start_wazo; then
		fatal_error "Wazo services failed to start\nPlease, fix this issue then re-execute wazo-upgrade to complete the upgrade"
	fi
	rm -f "$upgrade_incomplete_file"
	post_start
}

display_wazo_version() {
	echo "installed version : $(wazo_version_installed)"
	echo "proposed update	: $(wazo_version_candidate)"
}

debian_version_installed() {
	cut -d. -f1 /etc/debian_version
}

display_previous_upgrade_notice() {
	if [ "$resuming_failed_upgrade" -eq 1 ]; then
		cat <<-EOF

		WARNING: the previous upgrade did not complete. Upgrade scripts will be
		re-executed during this upgrade.

		EOF
	fi
}

upgrading_system() {
	display_wazo_version
	display_previous_upgrade_notice
	display_asterisk_notice
	if [ $force -eq 0 ]; then
		read -p 'Would you like to upgrade your system (all services will be restarted) [Y/n]? ' answer
		answer="${answer:-Y}"
		if [ "$answer" != 'y' -a "$answer" != 'Y' ]; then
			exit
		fi
	fi
	upgrade
}

display_asterisk_notice() {
	ast_version=$(dpkg-query -W -f '${Version}' asterisk 2>/dev/null)
	case "$ast_version" in
		8:19.*)
			ast_major_version="19"
			;;
		*)
			ast_major_version="unknown"
			;;
	esac
	if dpkg --compare-versions "$ast_version" le 8:20; then
		cat <<-EOF

		Asterisk will be upgraded from version $ast_major_version to 20. You might be impacted if you have:

		* custom Asterisk configuration (other than custom dialplan)
		* custom application using AMI or ARI
		* custom Asterisk modules (e.g. codec_g729a.so)

		If you find yourself in one of these cases, you should make sure that
		your customizations still work with Asterisk 20. Please refer to the
		Wazo upgrade notes for more information.

		EOF

		custom_modules=$(wazo-asterisk-custom-modules)
		if [ -n "$custom_modules" ]; then
			move_custom_asterisk_modules "$custom_modules"
			cat <<-EOF
			WARNING: custom Asterisk modules detected:

			$custom_modules

			Since these modules will not work with Asterisk 20 and might cause major
			instability issues, they have been moved from /usr/lib/asterisk/modules to
			/tmp. To continue using these modules, you will have to install (or recompile)
			the Asterisk 20 version of these modules.

		EOF
		fi
	fi
}

move_custom_asterisk_modules() {
	for module in $1; do
		mv "$asterisk_modules_directory/$module" "${WAZO_UPGRADE_ROOT:-}/tmp"
	done
}

wazo_version_installed() {
	echo "$(LANG='C' apt-cache policy $APT_OPTIONS wazo-platform | grep Installed | grep -oE '[0-9]{2}\.[0-9]+(\.[0-9]+)?' | head -n1)"
}

wazo_version_candidate() {
	echo "$(LANG='C' apt-cache policy $APT_OPTIONS wazo-platform | grep Candidate | grep -oE '[0-9]{2}\.[0-9]+(\.[0-9]+)?' | head -n1)"
}

check_database_migration_is_enabled() {
	for package in xivo-manage-db wazo-auth; do
		db_skip=$(debconf-show "$package" 2>/dev/null | grep "$package/db-skip:" | awk '{print $NF}')
		if [ "$db_skip" = 'true' ]; then
			cat <<-EOF
			ERROR: database migrations are disabled ($package/db-skip is "true").

			To re-enable them, run:

			    echo "$package $package/db-skip boolean false" | debconf-set-selections

			then run wazo-upgrade again.
			EOF
			exit 1
		fi
	done
}

check_wizard_has_been_run() {
	# The check cannot run with Wazo stopped, and the wizard was run before
	[ "$resuming_failed_upgrade" -eq 1 ] && return

	if ! is_wazo_configured; then
  		echo "ERROR: You must configure Wazo by running the wizard (POST /api/confd/1.1/wizard) before using wazo-upgrade"
		exit 1
	fi
}

is_wazo_configured() {
    if ! systemctl is-active --quiet wazo-confd; then
		echo "ERROR: wazo-confd is not running: cannot check if the wizard has been run."
		exit 1
	fi
	# wazo-confd has no HTTPS since wazo-20.07
	for scheme in 'http' 'https'; do
		if [ -n "$(curl -skX GET --header 'Accept: application/json' "$scheme://localhost:$WAZO_CONFD_PORT/1.1/wizard" | grep '\"configured\"[[:space:]]*:[[:space:]]*true')" ]; then
			return
		fi
	done
	return 1
}

usage() {
	cat <<-EOF
	usage: wazo-upgrade [-d] [-f] [-h]
		-d: only download packages
		-f: force yes
		-h: print usage
	EOF
}

main() {
	while getopts :dfh opt
	do
		case ${opt} in
			d) download_only=1;;
			f) force=1;;
			h)
				usage
				exit 0
			;;
			'?')
				echo "${0} : option ${OPTARG} is not valid" >&2
				usage
				exit 1
			;;
		esac
	done
	download_only="${download_only:-"0"}"
	force="${force:-"0"}"

	# Set before the current upgrade re-creates the marker file
	resuming_failed_upgrade=0
	[ -f "$upgrade_incomplete_file" ] && resuming_failed_upgrade=1

	check_database_migration_is_enabled
	check_wizard_has_been_run

	if [ $download_only -eq 0 ]; then
		upgrading_system
	else
		apt-get -y -d dist-upgrade $APT_OPTIONS
	fi
}

# Allows tests to source this file without side effects
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
	PATH=/bin:/usr/bin:/sbin:/usr/sbin
	main "$@"
fi
