#!/usr/bin/env bash
# Librerelease
# Uploads packages and releases them

# Copyright (C) 2010-2012 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
# Copyright (C) 2010-2013 Nicolás Reynolds <fauno@parabola.nu>
# Copyright (C) 2013 Michał Masłowski <mtjm@mtjm.eu>
# Copyright (C) 2013-2014, 2017 Luke Shumaker <lukeshu@sbcglobal.net>
#
# For just the create_signature() function:
#   Copyright (C) 2006-2013 Pacman Development Team <pacman-dev@archlinux.org>
#   Copyright (C) 2002-2006 Judd Vinet <jvinet@zeroflux.org>
#   Copyright (C) 2005 Aurelien Foret <orelien@chez.com>
#   Copyright (C) 2006 Miklos Vajna <vmiklos@frugalware.org>
#   Copyright (C) 2005 Christian Hamar <krics@linuxforum.hu>
#   Copyright (C) 2006 Alex Smith <alex@alex-smith.me.uk>
#   Copyright (C) 2006 Andras Voroskoi <voroskoi@frugalware.org>
#
# License: GNU GPLv3+
#
# This file is part of Parabola.
#
# Parabola 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.
#
# Parabola 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 Parabola. If not, see <http://www.gnu.org/licenses/>.

# create_signature() is taken from pacman:makepkg, which is GPLv2+,
# so we take the '+' to combine it with our GPLv3+.

. "$(librelib messages)"
. "$(librelib conf)"

dryrun=""
readonly rsync_flags=(
	--no-group
	--no-perms
	--copy-links
	--hard-links
	--partial
	--human-readable
	--progress
	-e "ssh -p 51011"
)

# Functions ####################################################################

list0_files() {
	find -L "${WORKDIR}/staging" -type f -not -name '*.lock' \
		-exec realpath -z --relative-to="${WORKDIR}/staging" {} +
}

# This function is taken almost verbatim from makepkg
create_signature() {
	local ret=0
	local filename="$1"
	msg "Signing package..."

	local SIGNWITHKEY=()
	if [[ -n $GPGKEY ]]; then
		SIGNWITHKEY=(-u "${GPGKEY}")
	fi
	# The signature will be generated directly in ascii-friendly format
	gpg --detach-sign --use-agent "${SIGNWITHKEY[@]}" "$filename" || ret=$?


	if (( ! ret )); then
		msg2 "Created signature file %s." "$filename.sig"
	else
		error "Failed to sign package file."
		return $ret
	fi
}

sign_packages() {
	IFS=$'\n'
	local files=($(find "${WORKDIR}/staging/" \
	                    \( -type d -name "${ABSLIBREDEST##*/}" \) -prune \
	                 -o \( -type f -not -iname '*.sig'         \) -print))
	local file
	for file in "${files[@]}"; do
		if [[ -f "${file}.sig" ]]; then
			msg2 "File signature found, verifying..."

			# Verify that the signature is correct, else remove for re-signing
			if ! gpg --quiet --verify "${file}.sig" >/dev/null 2>&1; then
				error "Failed!  Re-signing..."
				rm -f "${file}.sig"
			fi
		fi

		if ! [[ -f "${file}.sig" ]]; then
			create_signature "$file" || return 2
		fi
	done
}

# Clean everything if not in dry-run mode
clean_files() {
	local file_list=$1

	local rmcmd=(rm -fv)
	if [[ -n "${dryrun}" ]]; then
		rmcmd=(printf "$(_ "removed '%s' (dry-run)")\n")
	fi

	msg "Removing files from local staging directory"
	cd "${WORKDIR}/staging" && xargs -0r -a "$file_list" "${rmcmd[@]}"
	cd "${WORKDIR}/staging" && find . -mindepth 1 -type d -empty \
		-exec rmdir -p {} + 2>/dev/null
}

################################################################################

usage() {
	print "Usage: %s [OPTIONS]" "${0##*/}"
	echo
	prose 'This script uploads packages on $WORKDIR/staging
	       to the Hyperbola server.'
	echo
	print "Options:"
	flag '-c'            'Clean; delete packages in $WORKDIR/staging'
	flag '-l'            "List; list packages but not upload them"

	flag '-n'            "Dry-run; don't actually do anything"
	flag '-h'            "Show this message"
}

main() {
	if [[ -w / ]]; then
		error "This program should be run as regular user"
		return 1
	fi

	# Parse options
	local mode="release_packages"
	while getopts 'clunh' arg; do
		case $arg in
			c) mode=clean ;;
			l) mode=pretty_print_packages ;;
			n) dryrun="--dry-run" ;;
			h) mode=usage ;;
			*) usage >&2; return 1 ;;
		esac
	done
	shift $((OPTIND - 1))
	if [[ $# != 0 ]]; then
		usage >&2
		return 1
	fi

	if [[ $mode == usage ]]; then
		usage
		return 0
	fi

	load_files makepkg
	check_vars makepkg GPGKEY
	load_files libretools
	check_vars libretools WORKDIR REPODEST ABSLIBREDEST || return 1
	REPODEST+='/staging/'
	# The following settings are actually optional
	#check_vars libretools HOOKPRERELEASE HOOKPOSTRELEASE || return 1

	"$mode"
}

# The different modes (sans 'usage') ###########################################

pretty_print_packages() {
	find "$WORKDIR/staging/" -mindepth 1 -maxdepth 1 -type d -not -empty | sort |
	while read -r path; do
		msg2 "${path##*/}"
		cd "$path"
		find -L . -type f -not -name '*.lock' | sed 's|^\./|     |' | sort
	done
}

clean() {
	lock 8 "${WORKDIR}/staging.lock" \
		'Waiting for an exclusive lock on the staging directory'

	local file_list
	file_list="$(mktemp -t "${0##*/}.XXXXXXXXXX")"
	trap "$(printf 'rm -f -- %q' "$file_list")" EXIT
	list0_files > "$file_list"

	lock_close 8

	clean_files "$file_list"
}

release_packages() {
	if [[ -n $HOOKPRERELEASE ]]; then
		msg "Running HOOKPRERELEASE..."
		plain '%s' "${HOOKPRERELEASE}"
		bash -c "${HOOKPRERELEASE}"
	fi

	lock 8 "${WORKDIR}/staging.lock" \
		'Waiting for an exclusive lock on the staging directory'

	sign_packages || return 1

	# Make the permissions of the packages 644 otherwise the user will get access
	# denied error when they try to download (rsync --no-perms doesn't seem to
	# work).
	find "${WORKDIR}/staging" -type f -exec chmod 644 {} +
	find "${WORKDIR}/staging" -type d -exec chmod 755 {} +

	local file_list="$(mktemp -t ${0##*/}.XXXXXXXXXX)"
	trap "$(printf 'rm -f -- %q' "$file_list")" EXIT
	list0_files > "$file_list"

	lock_close 8

	msg "%s to upload" "$(cd "${WORKDIR}/staging" && du -hc --files0-from="$file_list" | sed -n '$s/\t.*//p')"
	msg "Uploading packages..."
	xargs -0r -a "$file_list" dirname -z | ssh -p 51011 "${REPODEST%%:*}" "$(printf 'mkdir -p -- %q && cd %q && xargs -0r mkdir -pv --' "${REPODEST#*:}"{,})"
	if ! rsync ${dryrun} "${rsync_flags[@]}" \
		-0 --files-from="$file_list" \
		"${WORKDIR}/staging" \
		"${REPODEST}/"
	then
		error "Sync failed, try again"
		return 1
	fi

	clean_files "$file_list"

	if [[ -n $HOOKPOSTRELEASE ]]; then
		msg "Running HOOKPOSTRELEASE..."
		plain '%s' "${HOOKPOSTRELEASE}"
		bash -c "${HOOKPOSTRELEASE}"
	fi

	return 0
}

main "$@"
