#!/usr/bin/env bash # Librerelease # Uploads packages and releases them # Copyright (C) 2010-2012 Joshua Ismael Haase Hernández (xihh) # Copyright (C) 2010-2013 Nicolás Reynolds # Copyright (C) 2013 Michał Masłowski # Copyright (C) 2013-2014, 2017 Luke Shumaker # # For just the create_signature() function: # Copyright (C) 2006-2013 Pacman Development Team # Copyright (C) 2002-2006 Judd Vinet # Copyright (C) 2005 Aurelien Foret # Copyright (C) 2006 Miklos Vajna # Copyright (C) 2005 Christian Hamar # Copyright (C) 2006 Alex Smith # Copyright (C) 2006 Andras Voroskoi # # 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 . # create_signature() is taken from pacman:makepkg, which is GPLv2+, # so we take the '+' to combine it with our GPLv3+. set -euE . "$(librelib messages)" . "$(librelib conf)" setup_traps dryrun="" upload_only=false readonly rsync_flags=( --no-group --no-perms --copy-links --hard-links --partial --human-readable --progress ) # 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=$EXIT_SUCCESS local filename="$1" msg "Signing package..." local SIGNWITHKEY=() if [[ -n $GPGKEY ]]; then SIGNWITHKEY=(-u "${GPGKEY}") fi gpg --detach-sign --use-agent "${SIGNWITHKEY[@]}" --no-armor "$filename" &>/dev/null || ret=$EXIT_FAILURE 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 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[@]}" find . -depth -mindepth 1 -type d \ -exec rmdir --ignore-fail-on-non-empty -- '{}' + ) ################################################################################ usage() { print "Usage: %s [OPTIONS]" "${0##*/}" print 'Upload packages in $WORKDIR/staging to the Parabola server' echo print "Options:" flag '-c' 'Clean; delete packages in $WORKDIR/staging' flag '-l' "List; list packages but not upload them" flag '-u' "Upload-only; do not run db-update on the server" 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 $EXIT_NOPERMISSION fi # Parse options local mode="release_packages" while getopts 'clunh' arg; do case $arg in c) mode=clean ;; l) mode=pretty_print_packages ;; u) upload_only=true ;; n) dryrun="--dry-run" ;; h) mode=usage ;; *) usage >&2; return $EXIT_INVALIDARGUMENT ;; esac done shift $((OPTIND - 1)) if [[ $# != 0 ]]; then usage >&2 return $EXIT_INVALIDARGUMENT fi if [[ $mode == usage ]]; then usage return $EXIT_SUCCESS fi declare -i ret=0 load_conf makepkg.conf GPGKEY || ret=$? load_conf libretools.conf WORKDIR REPODEST ABSLIBREDEST || ret=$? # and HOOK{PRE,POST}RELEASE, which are optional [[ $ret = 0 ]] || exit $ret local re_url='^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$' local re_authority='^(([^@]*)@)?([^][@:]*|\[[^]]*\])(:([0-9]*))?$' local REPODEST_ok=false if [[ "$REPODEST" =~ $re_url ]]; then REPODEST_ok=true REPODEST_scheme=${BASH_REMATCH[2]} REPODEST_authority=${BASH_REMATCH[4]} REPODEST_path=${BASH_REMATCH[5]} REPODEST_query=${BASH_REMATCH[7]} REPODEST_fragment=${BASH_REMATCH[9]} if [[ "$REPODEST_authority" =~ $re_authority ]]; then REPODEST_userinfo=${BASH_REMATCH[2]} REPODEST_host=${BASH_REMATCH[3]} REPODEST_port=${BASH_REMATCH[5]} if [[ "$REPODEST_host" = '['*']' ]]; then REPODEST_host=${REPODEST_HOST#'['} REPODEST_host=${REPODEST_HOST#']'} fi else REPODEST_ok=false fi [[ $REPODEST_scheme == ssh ]] || REPODEST_ok=false [[ -n $REPODEST_host ]] || REPODEST_ok=false [[ -n $REPODEST_path ]] || REPODEST_ok=false fi if ! $REPODEST_ok; then error 'The format of libretools.conf:REPODEST has changed.' plain 'Merge the /etc/libretools.conf.pacnew file!' return $EXIT_NOTCONFIGURED fi if [[ "$REPODEST_path" = '/~'* ]]; then if [[ "$REPODEST_path" = '/~/'* ]]; then REPODEST_path=${REPODEST_path#'/~/'} else error 'Unfortunately, `~user` home-directory expansion is not (yet?) supported in libretools.conf:REPODEST' return $EXIT_NOTCONFIGURED fi fi REPODEST_userhost="${REPODEST_userinfo:+${REPODEST_userinfo%%:*}@}${REPODEST_host}" "$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 # 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 ${REPODEST_port:+-p "$REPODEST_port"} "${REPODEST_userhost}" "$(printf 'mkdir -p -- %q && cd %q && xargs -0r mkdir -pv --' "${REPODEST_path}"{,})" if ! rsync ${dryrun} "${rsync_flags[@]}" \ -e "ssh ${REPODEST_port:+-p $REPODEST_port}" \ -0 --files-from="$file_list" \ "${WORKDIR}/staging" \ "$REPODEST_userhost:$REPODEST_path/" then error "Sync failed, try again" return $EXIT_FAILURE fi clean_files "$file_list" if $upload_only; then return $EXIT_SUCCESS fi msg "Running db-update on repos" ssh ${REPODEST_port:+-p "$REPODEST_port"} "${REPODEST_userhost}" "$(printf 'STAGING=%q db-update' "$REPODEST_path")" if [[ -n $HOOKPOSTRELEASE ]]; then msg "Running HOOKPOSTRELEASE..." plain '%s' "${HOOKPOSTRELEASE}" bash -c "${HOOKPOSTRELEASE}" fi return $EXIT_SUCCESS } main "$@"