#!/bin/bash
# SPDX-License-Identifier: MIT

set -ex

HOST_PORT=0.0.0.0:3000
STORAGE_PATHS="attachments avatars lfs packages repo-archive repo-avatars"
DIR=/tmp/forgejo-upgrades
SELF_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}:  '

function maybe_sudo() {
    if test $(id -u) != 0 ; then
	SUDO=sudo
    fi
}

function dependencies() {
    if ! which curl daemon > /dev/null ; then
	maybe_sudo
	$SUDO apt-get install -y -qq curl daemon
    fi
}

function build() {
    local version=$1
    local semver=$2

    if ! test -f $DIR/forgejo-$version ; then
	mkdir -p $DIR
	make VERSION=v$version GITEA_VERSION=v$version FORGEJO_VERSION=$semver TAGS='bindata sqlite sqlite_unlock_notify' generate gitea
	mv gitea $DIR/forgejo-$version
    fi
}

function build_all() {
    test -f Makefile
    build 1.20.3-0 5.0.2+0-gitea-1.20.3
    build 1.21.0-0 6.0.0+0-gitea-1.21.0
}

function wait_for() {
    rm -f $DIR/setup-forgejo.out
    success=false
    for delay in 1 1 5 5 15 ; do
	if "$@" >> $DIR/setup-forgejo.out 2>&1 ; then
	    success=true
	    break
	fi
	cat $DIR/setup-forgejo.out
	echo waiting $delay
	sleep $delay
    done
    if test $success = false ; then
	cat $DIR/setup-forgejo.out
	return 1
    fi
}

function download() {
    local version=$1

    if ! test -f $DIR/forgejo-$version ; then
	mkdir -p $DIR
	wget -O $DIR/forgejo-$version --quiet https://codeberg.org/forgejo/forgejo/releases/download/v$version/forgejo-$version-linux-amd64
	chmod +x $DIR/forgejo-$version
    fi
}

function cleanup_logs() {
    local work_path=$DIR/forgejo-work-path

    rm -f $DIR/*.log
    rm -f $work_path/log/*.log
}

function start() {
    local version=$1

    download $version
    local work_path=$DIR/forgejo-work-path
    daemon --chdir=$DIR --unsafe --env="TERM=$TERM" --env="HOME=$HOME" --env="PATH=$PATH" --pidfile=$DIR/forgejo-pid --errlog=$DIR/forgejo-err.log --output=$DIR/forgejo-out.log -- $DIR/forgejo-$version --config $work_path/app.ini --work-path $work_path
    if ! wait_for grep 'Starting server on' $work_path/log/forgejo.log ; then
	cat $DIR/*.log
	cat $work_path/log/*.log
	return 1
    fi
    create_user $version
    $work_path/forgejo-api http://${HOST_PORT}/api/v1/version
}

function create_user() {
    local version=$1

    local work_path=$DIR/forgejo-work-path

    if test -f $work_path/forgejo-token; then
	return
    fi

    local user=root
    local password=admin1234
    local cli="$DIR/forgejo-$version --config $work_path/app.ini --work-path $work_path"
    $cli admin user create --admin --username "$user" --password "$password" --email "$user@example.com"
    local scopes="--scopes all"
    if echo $version | grep --quiet 1.18. ; then
	scopes=""
    fi
    $cli admin user generate-access-token -u $user --raw $scopes > $work_path/forgejo-token
    ( echo -n 'Authorization: token ' ; cat $work_path/forgejo-token ) > $work_path/forgejo-header
    ( echo "#!/bin/sh" ; echo 'curl -sS -H "Content-Type: application/json" -H @'$work_path/forgejo-header' "$@"' ) > $work_path/forgejo-api && chmod +x $work_path/forgejo-api
}

function stop() {
    if test -f $DIR/forgejo-pid ; then
	local pid=$(cat $DIR/forgejo-pid)
	kill -TERM $pid
	pidwait $pid || true
	for delay in 1 1 2 2 5 5 ; do
	    if ! test -f $DIR/forgejo-pid ; then
		break
	    fi
	    sleep $delay
	done
	! test -f $DIR/forgejo-pid
    fi
    cleanup_logs
}

function reset() {
    local config=$1
    local work_path=$DIR/forgejo-work-path
    rm -fr $work_path
    mkdir -p $work_path
    WORK_PATH=$work_path envsubst < $SELF_DIR/$config-app.ini > $work_path/app.ini
}

function verify_storage() {
    local work_path=$DIR/forgejo-work-path

    for path in ${STORAGE_PATHS} ; do
	test -d $work_path/data/$path
    done
}

function cleanup_storage() {
    local work_path=$DIR/forgejo-work-path

    for path in ${STORAGE_PATHS} ; do
	rm -fr $work_path/data/$path
    done
}

function test_downgrade_1.20.2_fails() {
    local work_path=$DIR/forgejo-work-path

    echo "================ See also https://codeberg.org/forgejo/forgejo/pulls/1225"


    echo "================ downgrading from 1.20.3-0 to 1.20.2-0 fails"
    stop
    reset default
    start 1.20.3-0
    stop
    download 1.20.2-0
    timeout 60 $DIR/forgejo-1.20.2-0 --config $work_path/app.ini --work-path $work_path || true
    if ! grep --fixed-strings --quiet 'use the newer database' $work_path/log/forgejo.log ; then
	cat $work_path/log/forgejo.log
	return 1
    fi
}

function test_bug_storage_merged() {
    local work_path=$DIR/forgejo-work-path

    echo "================ See also https://codeberg.org/forgejo/forgejo/pulls/1225"

    echo "================ using < 1.20.3-0 and [storage].PATH merge all storage"
    for version in 1.18.5-0 1.19.4-0 1.20.2-0 ; do
	stop
	reset merged
	start $version
	for path in ${STORAGE_PATHS} ; do
	    ! test -d $work_path/data/$path
	done
	for path in ${STORAGE_PATHS} ; do
	    ! test -d $work_path/merged/$path
	done
	test -d $work_path/merged
    done
    stop

    echo "================ upgrading from 1.20.2-0 with [storage].PATH fails"
    download 1.20.3-0
    timeout 60 $DIR/forgejo-1.20.3-0 --config $work_path/app.ini --work-path $work_path || true
    if ! grep --fixed-strings --quiet '[storage].PATH is set and may create storage issues' $work_path/log/forgejo.log ; then
	cat $work_path/log/forgejo.log
	return 1
    fi
}

function test_bug_storage_misplace() {
    local work_path=$DIR/forgejo-work-path

    echo "================ See also https://codeberg.org/forgejo/forgejo/pulls/1225"

    echo "================ using < 1.20 and conflicting sections misplace storage"
    for version in 1.18.5-0 1.19.4-0 ; do
	stop
	reset misplace
	start $version
	#
	# some storage are where they should be
	#
	test -d $work_path/data/packages
	test -d $work_path/data/repo-archive
	test -d $work_path/data/attachments
	#
	# others are under APP_DATA_PATH
	#
	test -d $work_path/elsewhere/lfs
	test -d $work_path/elsewhere/avatars
	test -d $work_path/elsewhere/repo-avatars
    done

    echo "================ using < 1.20.[12]-0 and conflicting sections ignores [storage.*]"
    for version in 1.20.2-0 ; do
	stop
	reset misplace
	start $version
	for path in ${STORAGE_PATHS} ; do
	    test -d $work_path/elsewhere/$path
	done
    done

    stop

    echo "================ upgrading from 1.20.2-0 with conflicting sections fails"
    download 1.20.3-0
    timeout 60 $DIR/forgejo-1.20.3-0 --config $work_path/app.ini --work-path $work_path || true
    for path in ${STORAGE_PATHS} ; do
	if ! grep --fixed-strings --quiet "[storage.$path] may conflict" $work_path/log/forgejo.log ; then
	    cat $work_path/log/forgejo.log
	    return 1
	fi
    done
}

function test_successful_upgrades() {
    for config in default specific ; do
	echo "================ using $config app.ini"
	reset $config

	for version in 1.18.5-0 1.19.4-0 1.20.2-0 1.20.3-0 1.21.0-0 ; do
	    echo "================ run $version"
	    cleanup_storage
	    start $version
	    verify_storage
	    stop
	done
    done
}

function test_upgrades() {
    stop
    dependencies
    build_all
    test_successful_upgrades
    test_bug_storage_misplace
    test_bug_storage_merged
    test_downgrade_1.20.2_fails    
}

"$@"