#!/bin/sh
## 
## Copyright 2015-2022 The Regents of the University of California
## All rights reserved.
## 
## This file is part of Spoofer.
## 
## Spoofer is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
## 
## Spoofer is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with Spoofer.  If not, see <http://www.gnu.org/licenses/>.
## 

# git repo for web: git@gitlab.caida.org:CAIDA/WEB/www-caida-web.git
webdir="$HOME/lightweight-caida-web"
webstatic='static/projects/spoofer'
webcontent='content/projects/spoofer'
homebox="river"

stagedir=spoofer-stage

usage() {
    cat >&2 <<EOF
usage: $0 {-u|-s<dir>} [-r<cvsrev>] [<options>] <version> <dist_type>... [web]

Exports CVS revision <cvsrev> of spoofer from CVS into staging directory
"${stagedir}" and builds zero or more distributions.
But if the staging directory already exists from a
previous run, you are given the option to reuse it, which will also preserve
any distributions already built that weren't requested in this run.

After the distributions are built, you will be given instructions on next
steps (including for any distributions remaining from a previous run).

Options:
-u            binary distributions will be unsigned
-s <dir>      binary distributions will be signed using files in <dir>
-r <cvsrev>   check out cvs revision "<cvsrev>" instead of "release-<version>"
              (with each "." changed to "-").
-o <options>  additional options to pass to configure
-F            force version:  command line <version> overrides configure.ac
-t <tarball>  name of tarball file for "ubuntu" releases (defaults to tarball
              built by "src" argument)
-w <webdir>   name of working copy of caida website repo
              (default: "$webdir")
-x            reprint "next steps" from previous run, and exit

<dist_type> is one or more of the following:
  src           source distribution
  ubuntu[-<N>]  Ubuntu PPA version N, for all current Ubuntu series.  If N is
                omitted, it defaults to 1, and the debian log message will be
                "New upstream release."; otherwise you will be prompted for a
                log message.  To limit release to specific ubuntu series, set an
                environment variable e.g. SPOOFER_UBUNTU_SERIES="jammy".
  win32         Windows binary distribution (requires a i686-w64-mingw32.static
                cross-compiling environment)
  mac           macOS binary distribution (requires macOS)
  web           (not actually a distribution) update a working copy of the
                CAIDA web site with information about all distributions in
                ${stagedir}.

Typical use:
  On ubuntu box:
    release [<options>] <version> src ubuntu win32
  On mac box:
    release [<options>] <version> mac
  On ubuntu box:
    release [<options>] <version> web
EOF
}

assert() {
    local cmd="$*"
    echo "#  $cmd" >&2
    if test "$1" = "!"; then
	shift
	! "$@"
    else
	"$@"
    fi || { echo; echo "$0: assertion failed: $cmd"; exit 1; } >&2
}

finish() {
    local dist_class="$1"
    local prefix="$2"
    local version="$3"
    local suffix="$4"
    local desc="$5"
    local file="$prefix$version$suffix"

    ln -f "$file" "../downloads/$file"
    echo "${stagedir}/$dist_class/$file" >>../NEXT.${dist_class}.built
    gen_db_update "$dist_class" "https://www.caida.org/projects/spoofer/downloads/${file}"

    cat >../NEXT.${dist_class}.var <<-EOF
	dist_prefix="$prefix"
	dist_version="$version"
	dist_suffix="$suffix"
	dist_desc="$desc"
	dist_file="$file"
	dist_url="downloads/$file"
	EOF
}

update_web_pages() {
    (
        . ./NEXT.$1.var

        echo; echo "############## updating web pages for $1"
        assert test "$dist_version" = "$version"
        assert cd $webdir
        branch=$(git rev-parse --abbrev-ref HEAD)
        if test "$branch" != "spoofer-$dist_version"; then
            assert git checkout main
            assert git pull
            assert git checkout -b "spoofer-$dist_version"
        fi

        # replace filename in _index.md
        export replacement="|[${dist_file}](${dist_url})|${dist_desc}|"
        assert perl -MEnglish -pe 'if (/\Q|['"${dist_prefix}"'\E[^]]*\Q'"${dist_suffix}"'](\E([^|]*\|){2}/) { $_ = $PREMATCH . $ENV{"replacement"} . $POSTMATCH; }' $webcontent/_index.md >$webcontent/_index.md.tmp

        assert egrep -q "${dist_file}" $webcontent/_index.md.tmp
        assert mv $webcontent/_index.md.tmp $webcontent/_index.md

        # add new release to "Recent Updates" section and remove old release
        assert perl -ne "if (/<ul class=.recentupdates.>/) { print \"\$_ <li><a href='#software'>Spoofer ${dist_version} released ($(date +%Y-%m))</a></li>\\n\"; } elsif (!/Spoofer [0-9.]* released/) { print; }" $webcontent/_index.md >$webcontent/_index.md.tmp
        assert egrep -q "Spoofer ${dist_version} released" $webcontent/_index.md.tmp
        assert mv $webcontent/_index.md.tmp $webcontent/_index.md
        assert git add $webcontent/_index.md

    ) || exit $?
}

gen_db_update() {
    local dist_class="$1"
    local file="$2"
    case $dist_class in
    src)
	local WHERE="Os IS NULL AND UpgradeKey IS NULL"
	;;
    *)
	local OS=$(sed -ne '/^#define OS "\(.*\)"/s//\1/p' config.h)
	local UPGRADE_KEY=$(sed -ne '/^#define UPGRADE_KEY "\(.*\)"/s//\1/p' config.h)
	test -n "$UPGRADE_KEY" || return; # no automatic upgrade, no db update
	assert test -n "$OS"
	local WHERE="Os='${OS}' AND UpgradeKey='${UPGRADE_KEY}'"
	;;
    esac
    NVERSION=$(sed -ne '/^#define NVERSION \(.*\)/s//\1/p' config.h)
    echo "    UPDATE ClientVersions SET CurVersion=${NVERSION}, VersionStr='${version}', file='${file}' WHERE ${WHERE};" >../NEXT.${dist_class}.db
}

dump_next_steps() {
    echo '* locally:'
    if ls NEXT.*.local1 >/dev/null 2>&1; then
        cat $(ls -tr NEXT.*.local1)
    fi
    dists=$(echo $( (ls -1 NEXT.*.built) | cut -d. -f2))
    echo "    in '$(pwd)'"
    echo "        ./release $(cat ./version) web"
    echo "    in '$webdir'"
    echo "        snap run hugo server -D [--bind 0.0.0.0]"
    echo "        # check https://$(hostname):1313/projects/spoofer"
    echo "        git commit -m 'Release Spoofer $(cat ./version) for $dists' $webcontent $webstatic"
    echo "        # make pull request for branch spoofer-$(cat ./version)"
    echo "        # kill hugo (optional)"
    if ls NEXT.*.db >/dev/null 2>&1; then
        echo '* in "spoofer_test" and "spoofer" databases:'
        cat $(ls -tr NEXT.*.db)
    fi
    if ls NEXT.*.local2 >/dev/null 2>&1; then
        echo '* locally:'
        cat $(ls -tr NEXT.*.local2)
    fi
}

CONFIG_OPTIONS='--enable-prober --enable-manager'
revision=""
cs_dir="."
signed="signed"
signprefix=""
tarball=""

while test "$1"; do
    option=""
    case "$1" in
    -[rsotw])
        [ $# -lt 2 ] && echo "Option $1 requires an argument" >&2 && exit 1
        option=$1
        optarg=$2
        shift; shift;;
    -[rsotw]*)
        option=$(echo "$1" | cut -c1-2)
        optarg=$(echo "$1" | cut -c3-)
        shift;;
    -u) signed="unsigned"; signprefix="unsigned-"; shift;;
    -F) forceversion="1"; shift;;
    -x) cd $stagedir; dump_next_steps; exit 0;;
    -h) usage; exit 0;;
    --) shift; break;;
    -*) echo "unknown option $1" >&2; usage; exit 1;;
    *) break;;
    esac
    case "$option" in
    -r) revision=$optarg;;
    -s) cs_dir=$optarg;;
    -o) CONFIG_OPTIONS="$CONFIG_OPTIONS $optarg";;
    -t) tarball="$optarg";;
    -w) webdir="$optarg";;
    esac
done

version="$1"

test $# -ge 1 || { usage; exit 1; }
test -n "$version" || { usage; exit 1; }

shift

if test -z "$revision"; then
    revision="release-$(echo $version | sed 's/\./-/g')"
fi

dist_type_to_class() {
    case "$1" in
    ubuntu|ubuntu-*) echo ubuntu;;
    src|win32|mac|web) echo "${dist_type}";;
    *)
	echo "Error: unknown distibution type \"$dist_type\"" >&2
	exit 1
	;;
    esac
}

# check command line arguments
for dist_type in "$@"; do
    dist_type_to_class "$dist_type" >/dev/null
done

# make cs_dir absolute
cs_dir=$(cd "$cs_dir"; pwd;)

echo "# release version: $version"
echo "# release revision: $revision"
echo "# release types: $*"

if test $(umask) != "0022"; then
    echo "WARNING: umask is $(umask); normally it should be 0022." >&2
    echo "Press return to continue." >&2
    read line
fi

reuse=false
exportdir="spoofer-$revision"

if test -f $stagedir/version && test "$version" != $(cat $stagedir/version); then
    echo "ERROR: Version '$version' conflicts with $stagedir/version $(cat $stagedir/version)" >&2
    echo "Press return to delete existing $stagedir or interrupt to abort." >&2
    read line

elif test -d $stagedir/$exportdir; then
    echo "############## Found existing $stagedir/$exportdir"
    while true; do
	echo "\aType 'r' to reuse it, or 's' to start from scratch."
	read response
	case "$response" in
	    R|r) reuse=true; break;;
	    S|s) reuse=false; break;;
	    *) continue;;
	esac
    done
fi

if $reuse; then
    assert cd $stagedir/$exportdir
else
    echo "############## Exporting $stagedir/$exportdir"
    chmod -R u+rw $stagedir
    assert rm -rf $stagedir
    assert mkdir $stagedir
    assert mkdir $stagedir/downloads
    echo "$version" >$stagedir/version
    assert cd $stagedir
    assert cvs export -r $revision -d $exportdir WIP/spoofer
    for d in analysis bgpsrv server web; do rm -r $exportdir/$d; done
    assert cd $exportdir
    case "$version" in
	*alpha*) cat README.alpha README >README.tmp && mv README.tmp README;;
    esac
fi

# example:  m4_define([SPOOFER_VERSION], [1.4.0-beta1])
cfgversion=$(sed -ne 's/^m4_define(\[SPOOFER_VERSION\], *\[\([^]]*\)\].*/\1/p' configure.ac)
if test "$forceversion" = "1"; then
    sed -e 's/^\(m4_define(\[SPOOFER_VERSION\], *\)\[\([^]]*\)\]/\1['"$version"']/' configure.ac >configure-forced.ac || exit $?
    echo "Forced version:"
    diff configure.ac configure-forced.ac
    mv configure-forced.ac configure.ac
elif test "x$cfgversion" != "x$version"; then
    echo "ERROR: configure.ac version '$cfgversion' does not match requested version '$version'."
    exit 1
fi


### sanity checks before we start the slow process of building releases

# sanity check: copyrights
for f in prober/spoofer-prober.cc manager/scheduler/main.cpp mac-scripts/apps/postinstall.in; do
    egrep -q "Copyright.*$(date +%Y) The Regents of the University of California" $f || \
	{ echo "Error: missing/old copyright in $f" >&2; exit 1; }
done

# sanity check: debian/copyright
egrep "The Regents of the University of California" debian/copyright>reg
assert egrep -q "$(date +%Y) The Regents" reg
egrep -v "$(date +%Y) The Regents" reg && \
    { echo "Error: out of date debian/copyright?" >&2; exit 1; }
rm reg


assert ./bootstrap
assert cd .. ; # ${stagedir}

for dist_type in "$@"; do
    dist_class=$(dist_type_to_class "$dist_type")
    assert rm -f "NEXT.${dist_class}."*
    assert rm -rf ${dist_class}
    assert mkdir ${dist_class}
done


# Build the distributions
for dist_type in "$@"; do
    dist_class=$(dist_type_to_class "$dist_type")
    case $dist_class in
    src)
	echo; echo "############## building source distribution"
	assert cd src
	if test -z "$QMAKE"; then
	    for f in qmake; do
		if $f --version >/dev/null 2>&1; then QMAKE="$f"; break; fi
	    done
	fi
	if test -z "$PROTOC"; then
	    for f in protoc; do
		if $f --version >/dev/null 2>&1; then PROTOC="$f"; break; fi
	    done
	fi
	assert ../$exportdir/configure ${CONFIG_OPTIONS}
	# During "make dist", disable unneeded tools to catch unintended use.
	assert make dist CXX='bad_CXX' PROTOC='bad_PROTOC' QMAKE='bad_QMAKE'
	assert make distcheck DISTCHECK_CONFIGURE_FLAGS="${CONFIG_OPTIONS} \
	    PROTOC='$PROTOC' QMAKE='$QMAKE'"
	assert finish $dist_class spoofer- "$version" .tar.gz "Source Code"
	assert cd ..; # ${stagedir}

        # Docker
        DOCKER_USER=kenkeys
        cat >>NEXT.${dist_class}.local2 <<-EOF
	    in working copy of spoofer:
	        sudo docker build -t $DOCKER_USER/spoofer:latest -t $DOCKER_USER/spoofer:$VERSION spoofer-docker
	        sudo docker push $DOCKER_USER/spoofer:latest $DOCKER_USER/spoofer:$VERSION
	EOF

        # OpenWRT
	ver=$(echo "${version}" | sed -e 's/\.//g')
	SUM=$(sha256sum src/spoofer-${version}.tar.gz | awk '{print $1}')
        echo "$SUM" | egrep -q '^[0-9a-f]{64}$' || \
            SUM="(###### FILL IN SHA256SUM OF spoofer-${version}.tar.gz ######)"
	cat >>NEXT.${dist_class}.local2 <<-EOF
	    in '~/openwrt/openwrt-packages' (a working copy of a fork of
	    https://github.com/openwrt/packages):
	        # see https://openwrt.org/submitting-patches
	        git fetch upstream
	        git checkout master
	        git merge upstream/master    ;# sync my copy with upstream
	        git push origin master       ;# and push to my fork on github
	        git checkout -b spoofer-${ver}  ;# create new branch
	        in net/spoofer/Makefile:
	            Copyright $(date +%Y)
	            PKG_VERSION:=${version}
	            PKG_RELEASE:=1
	            PKG_HASH:=${SUM}
	            # REMOVE "include vpath.mk"
	        # make any other necessary changes
	        git add net/spoofer/Makefile
	        git commit --signoff
	            spoofer: Update to ${version}
	        # if there are multiple commits since last release, squash them
	        git push --all
	        # make a pull request
	EOF

	;;

    ubuntu)
	# See: https://help.launchpad.net/Packaging/PPA/BuildingASourcePackage
	echo; echo "############## building ubuntu distribution"

	assert test "$(lsb_release -si)" = "Ubuntu"
	assert cd ubuntu

	case "$tarball" in
	    '') tarball=../src/spoofer-$version.tar.gz;;
	    /*) : nop ;;
	    *) tarball=../../$tarball;;
	esac
	assert tar zxf "$tarball"

	assert cd spoofer-$version
	if test -n "$SPOOFER_DEBIAN"; then
	    cp -a "$SPOOFER_DEBIAN" debian; # XXX
	else
	    assert cvs checkout -r $revision -d debian WIP/spoofer/debian
	fi

	cat >>../../NEXT.${dist_class}.local2 <<-EOF
	    in '$stagedir/ubuntu'
	        copy spoofer-$version/debian/changelog to a source tree and commit it
	        # If there are new ubuntu series, update your ~/.dput.cf
	EOF

	if test -n "$SPOOFER_RELEASE_BINARY"; then
	    all_series=$(lsb_release -sc)
	elif test -n "$SPOOFER_UBUNTU_SERIES"; then
	    all_series="$SPOOFER_UBUNTU_SERIES"
        else
            ymd=$(date +"%Y-%m-%d")
            inrange() { ! test $1 '<' $2 && ! test $1 '>' $3; }
            all_series=$(echo $(
                inrange $ymd 2018-04-26 2023-04-99 && echo bionic; # LTS
                inrange $ymd 2020-04-23 2025-04-99 && echo focal; # LTS
                inrange $ymd 2021-10-14 2022-07-99 && echo impish
                inrange $ymd 2022-04-21 2027-04-99 && echo jammy; # LTS
            ))
            if test $ymd '>' 2022-10-00; then
                echo "WARNING: don't know name of current Ubuntu series" >&2
                echo "Press return to continue." >&2
                read line
            fi
	fi
	prev_series=""
	for series in $all_series; do
	    case $series in
		bionic)  ubuntu="ubuntu-18.04.1";;
		focal)   ubuntu="ubuntu-20.04.1";;
		impish)  ubuntu="ubuntu-21.10.1";;
		jammy)   ubuntu="ubuntu-22.04.1";;
		*) echo "unknown ubuntu series '$series'" >&2; exit 1;;
	    esac

	    if test -n "$prev_series"; then
		# Use the same debian/changelog, just change the series name
                # (Launchpad requires the correct series name in the changelog;
                # an override in dput.cf isn't sufficient.)
		sed -i -e "1s/~ubuntu.*) $prev_series;/~$ubuntu) $series;/" \
		    debian/changelog
            elif test "${dist_type}" = "ubuntu"; then
		# Use a default log message
		assert debchange -p -v "$version-1~$ubuntu" -D "$series" "New upstream release."
            else # "ubuntu-N"
		# Prompt for a new log message
                N=${dist_type#ubuntu-}
		assert debchange -p -v "$version-${N}~$ubuntu" -D "$series"
	    fi
	    PPA_VERSION=$(sed -ne '1s/spoofer (\(.*\)).*/\1/p' debian/changelog)
	    ORIG_VERSION=$(echo $PPA_VERSION | sed -e 's/-[0-9.]*$//')
	    if ! test -L ../spoofer_${ORIG_VERSION}.orig.tar.gz; then
		assert ln -s "$tarball" ../spoofer_${ORIG_VERSION}.orig.tar.gz
	    fi

	    # Note: to check dependencies, build in a clean chroot environment
	    # with: `pdebuild --buildresult ..`
            # Note: if running remotely without a GUI, but debuild is timing
            # out in gpg, it may be giving a GUI prompt on another screen
            # where you can't answer.  Try forcing it to use tty:
            # > sudo apt install pinentry-tty
            # > sudo update-alternatives --config pinentry
            #   (then select pinentry-tty)
	    if test -n "$SPOOFER_RELEASE_BINARY"; then
		assert debuild -i -us -uc -b
	    else
		assert debuild -S
	    fi

	    echo "${stagedir}/ubuntu/spoofer_${PPA_VERSION}"'*' >>../../NEXT.${dist_class}.built
	    cat >>../../NEXT.${dist_class}.local2 <<-EOF
	        dput spoofer-$series spoofer_${PPA_VERSION}_source.changes
		EOF
	    prev_series="$series"
	done
	cd ..; # ubuntu

        cat >../NEXT.${dist_class}.var <<-EOF
		dist_prefix="Spoofer-"
		dist_version="$version"
		dist_suffix=" PPA"
		dist_desc="Ubuntu packages for: $all_series (signed)"
		dist_file="Spoofer-$version PPA"
		dist_url="https://launchpad.net/~spoofer-dev/+archive/ubuntu/spoofer"
		EOF

	cd ..; # $stagedir
	;;

    win32)
	echo; echo "############## building $signed Windows binary distribution"
	assert cd win32
	assert cp $HOME/WIP/spoofer/win-bin/WinPcap_4_1_3.exe ../$exportdir/win-bin
	assert cp $HOME/WIP/spoofer/win-bin/vc2012u4redist_x86.exe ../$exportdir/win-bin
	(
	    PATH=/opt/mxe/usr/bin:$PATH
	    assert ../$exportdir/configure ${CONFIG_OPTIONS} \
		--host=i686-w64-mingw32.static \
		UPGRADE_KEY="https://www.caida.org/" \
		CXXFLAGS="-O2 -DNDEBUG" \
		PROTOC='/opt/mxe/usr/x86_64-pc-linux-gnu/bin/protoc'
	    assert make CS_DIR="$cs_dir" windist-${signed}
	) || exit $?
	assert finish $dist_class ${signprefix}Spoofer- "$version" -win32.exe "Windows Binary Installer (signed)"
	assert cd ..; # ${stagedir}
	;;

    mac)
	echo; echo "############## building $signed macOS binary distribution"
	assert test $(uname) = "Darwin"
	assert cd mac
	(
	    export MACOSX_DEPLOYMENT_TARGET=10.13
	    # where to find headers/libraries/tools/etc for deployment target
	    TARGETROOT="$HOME/macos-$MACOSX_DEPLOYMENT_TARGET"
	    # set PATH for protoc, productsign, qmake
	    PATH="$TARGETROOT/bin:$PATH"
	    assert ../$exportdir/configure \
		--with-openssl="$TARGETROOT" \
		${CONFIG_OPTIONS} \
		UPGRADE_KEY="https://www.caida.org/" \
		CXX='clang++ -stdlib=libc++' \
		CXXFLAGS="-arch x86_64 -O2 -I$TARGETROOT/include -DNDEBUG" \
		LDFLAGS="-arch x86_64 -L$TARGETROOT/lib"
		# PKG_CONFIG_PATH="$TARGETROOT/lib/pkgconfig" # not needed
	    assert make macdist-${signed}
	) || exit $?
	assert finish $dist_class ${signprefix}Spoofer- "$version" -macos.pkg "macOS Binary Installer (signed)"
	assert cd ..; # ${stagedir}
	;;

    web)
        # ignore at this step
        ;;

    *)
	# shouldn't happen
	echo "############## unknown distibution class \"$dist_class\"" >&2
	exit 1
	;;
    esac
done


# Update web tree
for dist_type in "$@"; do
    dist_class=$(dist_type_to_class "$dist_type")
    case $dist_class in
    src|ubuntu|win32|mac)
        # ignore at this step
        ;;

    web)
        case "$dist_version" in
        *alpha*|*beta*)
            echo "Skipping web update for alpha or beta version"
            ;;
        *)
            if ! test -d "$webdir" && test $(hostname) != "$homebox"; then
                cat <<-EOF
		    You have no "$webdir" and this is not your home box ($homebox).
		    To continue the release process, do:
                        scp -r $stagedir/NEXT.* $stagedir/downloads $homebox.caida.org:WIP/spoofer/$stagedir
		    Then, on $homebox:WIP/spoofer, you can do:
		        ./release [<options>] $version web
		EOF
            else
                # web pages
                dists=$(echo $( (ls -1 NEXT.*.built) | cut -d. -f2))
                for dist in $dists; do
                    update_web_pages "$dist"
                done
                # downloads
                echo; echo "############## updating web downloads"
                for file in downloads/*; do
                    assert cp "$file" "$webdir/$webstatic/$file"
                    assert git -C $webdir add "$webstatic/$file"
                done
                # changelog
                echo; echo "############## updating web changelog"
                assert cp "$exportdir/CHANGES" "$webdir/$webstatic/downloads/changelog.txt"
                assert git -C $webdir add "$webstatic/downloads/changelog.txt"
                git -C $webdir status
            fi
            ;;
        esac
        ;;
    esac
done


echo; echo

assert ls NEXT.*.built >/dev/null 2>&1

echo "Successfully built releases:"
cat $(ls -tr NEXT.*.built)

echo
case "$version" in
*alpha*|*beta*)
    echo 'If this were a production release, the next steps would be:'
    ;;
*)
    echo 'Next steps:'
    ;;
esac
dump_next_steps
