0d53b9abe1
It seems that metadata generation take about 40 seconds. If we submit a new version of package with deleting previous one, users will observe 404 errors until new metadata will be published.
515 lines
17 KiB
Bash
Executable File
515 lines
17 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
##
|
|
## Package uploader for Bintray.
|
|
##
|
|
## Leonid Plyushch <leonid.plyushch@gmail.com> (C) 2019
|
|
##
|
|
## This program 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.
|
|
##
|
|
## This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
##
|
|
|
|
set -e
|
|
|
|
TERMUX_PACKAGES_BASEDIR=$(realpath "$(dirname "$0")/../")
|
|
if [ ! -d "$TERMUX_PACKAGES_BASEDIR/packages" ]; then
|
|
echo "[!] Cannot find directory 'packages'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# In this variable a package metadata will be stored.
|
|
declare -gA PACKAGE_METADATA
|
|
|
|
# Initialize default configuration.
|
|
DEBFILES_DIR_PATH="$TERMUX_PACKAGES_BASEDIR/debs"
|
|
PACKAGE_DELETE_MODE=false
|
|
|
|
# Bintray-specific configuration.
|
|
BINTRAY_REPO_NAME="termux-packages-24"
|
|
BINTRAY_REPO_GITHUB="termux/termux-packages"
|
|
BINTRAY_REPO_DISTRIBUTION="stable"
|
|
BINTRAY_REPO_COMPONENT="main"
|
|
|
|
# Bintray credentials that should be set as external environment
|
|
# variables by user.
|
|
: "${BINTRAY_USERNAME:=""}"
|
|
: "${BINTRAY_API_KEY:=""}"
|
|
: "${BINTRAY_GPG_SUBJECT:=""}"
|
|
: "${BINTRAY_GPG_PASSPHRASE:=""}"
|
|
|
|
# If BINTRAY_GPG_SUBJECT is not specified, then signing will be
|
|
# done with gpg key of subject '$BINTRAY_USERNAME'.
|
|
if [ -z "$BINTRAY_GPG_SUBJECT" ]; then
|
|
BINTRAY_GPG_SUBJECT="$BINTRAY_USERNAME"
|
|
fi
|
|
|
|
# Packages are built and uploaded for Termux organisation.
|
|
BINTRAY_SUBJECT="termux"
|
|
|
|
# Check dependencies.
|
|
if [ -z "$(command -v curl)" ]; then
|
|
echo "[!] Package 'curl' is not installed."
|
|
exit 1
|
|
fi
|
|
if [ -z "$(command -v find)" ]; then
|
|
echo "[!] Package 'findutils' is not installed."
|
|
exit 1
|
|
fi
|
|
if [ -z "$(command -v grep)" ]; then
|
|
echo "[!] Package 'grep' is not installed."
|
|
exit 1
|
|
fi
|
|
if [ -z "$(command -v jq)" ]; then
|
|
echo "[!] Package 'jq' is not installed."
|
|
exit 1
|
|
fi
|
|
|
|
###################################################################
|
|
|
|
json_metadata_dump() {
|
|
local pkg_licenses
|
|
|
|
SAVEIFS=$IFS; IFS=",";
|
|
for license in ${PACKAGE_METADATA['LICENSES']}; do
|
|
pkg_licenses+="\"$(echo "${license}" | sed -r 's/^\s*(\S+(\s+\S+)*)\s*$/\1/')\","
|
|
done
|
|
pkg_licenses=${pkg_licenses%%,}; IFS=$SAVEIFS;
|
|
|
|
cat << EOF
|
|
{
|
|
"name": "${PACKAGE_METADATA['NAME']}",
|
|
"desc": "${PACKAGE_METADATA['DESCRIPTION']}",
|
|
"version": "${PACKAGE_METADATA['VERSION_FULL']}",
|
|
"licenses": [${pkg_licenses}],
|
|
"vcs_url": "https://github.com/${BINTRAY_REPO_GITHUB}",
|
|
"website_url": "${PACKAGE_METADATA['WEBSITE_URL']}",
|
|
"issue_tracker_url": "https://github.com/${BINTRAY_REPO_GITHUB}/issues",
|
|
"github_repo": "${BINTRAY_REPO_GITHUB}",
|
|
"public_download_numbers": "true",
|
|
"public_stats": "false"
|
|
}
|
|
EOF
|
|
}
|
|
|
|
delete_package() {
|
|
local package_name=$1
|
|
local curl_response
|
|
local http_status_code
|
|
local api_response_message
|
|
|
|
echo -n "[@] Deleting published package '$package_name' from remote... " >&2
|
|
curl_response=$(
|
|
curl \
|
|
--silent \
|
|
--user "${BINTRAY_USERNAME}:${BINTRAY_API_KEY}" \
|
|
--request DELETE \
|
|
--write-out "|%{http_code}" \
|
|
"https://api.bintray.com/packages/${BINTRAY_SUBJECT}/${BINTRAY_REPO_NAME}/${package_name}"
|
|
)
|
|
|
|
http_status_code=$(echo "$curl_response" | cut -d'|' -f2)
|
|
api_response_message=$(echo "$curl_response" | cut -d'|' -f1 | jq -r .message)
|
|
|
|
case "$http_status_code" in
|
|
200)
|
|
echo "done" >&2
|
|
;;
|
|
404)
|
|
echo "no-need" >&2
|
|
;;
|
|
*)
|
|
echo "failure" >&2
|
|
echo "[!] $api_response_message" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
upload_package() {
|
|
local package_name=$1
|
|
local http_status_code
|
|
local api_response_message
|
|
declare -A debfiles_catalog
|
|
|
|
for arch in all aarch64 arm i686 x86_64; do
|
|
# Regular package.
|
|
debfiles_catalog["${package_name}_${PACKAGE_METADATA['VERSION_FULL']}_${arch}.deb"]=${arch}
|
|
|
|
# Development package.
|
|
debfiles_catalog["${package_name}-dev_${PACKAGE_METADATA['VERSION_FULL']}_${arch}.deb"]=${arch}
|
|
|
|
# Discover subpackages.
|
|
for file in $(find "$TERMUX_PACKAGES_BASEDIR/packages/$package_name/" -maxdepth 1 -type f -iname \*.subpackage.sh | sort); do
|
|
file=$(basename "$file")
|
|
debfiles_catalog["${file%%.subpackage.sh}_${PACKAGE_METADATA['VERSION_FULL']}_${arch}.deb"]=${arch}
|
|
done
|
|
|
|
unset debfiles
|
|
done
|
|
|
|
# Filter out nonexistent files.
|
|
for item in "${!debfiles_catalog[@]}"; do
|
|
if [ ! -f "$DEBFILES_DIR_PATH/$item" ]; then
|
|
unset debfiles_catalog["$item"]
|
|
fi
|
|
done
|
|
|
|
# Verify that our catalog is not empty.
|
|
if [ ${#debfiles_catalog[@]} -eq 0 ]; then
|
|
echo "[!] No *.deb files to upload." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Create new entry for package.
|
|
echo -n "[@] Creating entry for version '${PACKAGE_METADATA['VERSION_FULL']}' of package '$package_name'... " >&2
|
|
curl_response=$(
|
|
curl \
|
|
--silent \
|
|
--user "${BINTRAY_USERNAME}:${BINTRAY_API_KEY}" \
|
|
--request POST \
|
|
--header "Content-Type: application/json" \
|
|
--data "$(json_metadata_dump)" \
|
|
--write-out "|%{http_code}" \
|
|
"https://api.bintray.com/packages/${BINTRAY_SUBJECT}/${BINTRAY_REPO_NAME}"
|
|
)
|
|
|
|
http_status_code=$(echo "$curl_response" | cut -d'|' -f2)
|
|
api_response_message=$(echo "$curl_response" | cut -d'|' -f1 | jq -r .message)
|
|
|
|
case "$http_status_code" in
|
|
201)
|
|
echo "done" >&2
|
|
;;
|
|
409)
|
|
echo "no-need" >&2
|
|
;;
|
|
*)
|
|
echo "failure" >&2
|
|
echo "[!] $api_response_message" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
for item in "${!debfiles_catalog[@]}"; do
|
|
local package_arch=${debfiles_catalog[$item]}
|
|
|
|
echo -n "[*] Uploading '$item'... " >&2
|
|
curl_response=$(
|
|
curl \
|
|
--silent \
|
|
--user "${BINTRAY_USERNAME}:${BINTRAY_API_KEY}" \
|
|
--request PUT \
|
|
--header "X-Bintray-Debian-Distribution: $BINTRAY_REPO_DISTRIBUTION" \
|
|
--header "X-Bintray-Debian-Component: $BINTRAY_REPO_COMPONENT" \
|
|
--header "X-Bintray-Debian-Architecture: $package_arch" \
|
|
--header "X-Bintray-Package: ${package_name}" \
|
|
--header "X-Bintray-Version: ${PACKAGE_METADATA['VERSION_FULL']}" \
|
|
--upload-file "$DEBFILES_DIR_PATH/$item" \
|
|
--write-out "|%{http_code}" \
|
|
"https://api.bintray.com/content/${BINTRAY_SUBJECT}/${BINTRAY_REPO_NAME}/${package_arch}/${item}"
|
|
)
|
|
|
|
http_status_code=$(echo "$curl_response" | cut -d'|' -f2)
|
|
api_response_message=$(echo "$curl_response" | cut -d'|' -f1 | jq -r .message)
|
|
|
|
case "$http_status_code" in
|
|
201)
|
|
echo "done" >&2
|
|
;;
|
|
409)
|
|
echo "unchanged" >&2
|
|
;;
|
|
*)
|
|
echo "failure" >&2
|
|
echo "[!] $api_response_message" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Publishing package only after uploading all it's files. This will prevent
|
|
# spawning multiple metadata-generation jobs and will allow to sign metadata
|
|
# with maintainer's key.
|
|
echo -n "[@] Publishing package '$package_name'... " >&2
|
|
curl_response=$(
|
|
curl \
|
|
--silent \
|
|
--user "${BINTRAY_USERNAME}:${BINTRAY_API_KEY}" \
|
|
--request POST \
|
|
--header "Content-Type: application/json" \
|
|
--data "{\"subject\":\"${BINTRAY_GPG_SUBJECT}\",\"passphrase\":\"$BINTRAY_GPG_PASSPHRASE\"}" \
|
|
--write-out "|%{http_code}" \
|
|
"https://api.bintray.com/content/${BINTRAY_SUBJECT}/${BINTRAY_REPO_NAME}/${package_name}/${PACKAGE_METADATA['VERSION_FULL']}/publish"
|
|
)
|
|
|
|
http_status_code=$(echo "$curl_response" | cut -d'|' -f2)
|
|
api_response_message=$(echo "$curl_response" | cut -d'|' -f1 | jq -r .message)
|
|
|
|
case "$http_status_code" in
|
|
200)
|
|
echo "done" >&2
|
|
;;
|
|
*)
|
|
echo "failure" >&2
|
|
echo "[!] $api_response_message" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
extract_variable_from_buildsh() {
|
|
local extracted_value
|
|
local variable_name
|
|
variable_name=$1
|
|
|
|
extracted_value=$(
|
|
set -o noglob
|
|
|
|
# When sourcing external code, do not expose variables
|
|
# with sensitive information.
|
|
unset BINTRAY_API_KEY
|
|
unset BINTRAY_GPG_PASSPHRASE
|
|
unset BINTRAY_GPG_SUBJECT
|
|
unset BINTRAY_SUBJECT
|
|
unset BINTRAY_USERNAME
|
|
|
|
[ -e "$TERMUX_PACKAGES_BASEDIR/scripts/properties.sh" ] && . "$TERMUX_PACKAGES_BASEDIR/scripts/properties.sh"
|
|
. "$TERMUX_PACKAGES_BASEDIR/packages/$package_name/build.sh"
|
|
echo "${!variable_name}"
|
|
set +o noglob
|
|
)
|
|
|
|
echo "$extracted_value"
|
|
}
|
|
|
|
process_packages() {
|
|
local package_name
|
|
local buildsh_path
|
|
|
|
for package_name in "$@"; do
|
|
buildsh_path="$TERMUX_PACKAGES_BASEDIR/packages/$package_name/build.sh"
|
|
|
|
if [ -f "$buildsh_path" ]; then
|
|
PACKAGE_METADATA["NAME"]="$package_name"
|
|
|
|
PACKAGE_METADATA["LICENSES"]=$(extract_variable_from_buildsh "TERMUX_PKG_LICENSE" "$buildsh_path")
|
|
if [ -z "${PACKAGE_METADATA['LICENSES']}" ]; then
|
|
echo "[!] Mandatory field 'TERMUX_PKG_LICENSE' of package '$package_name' is empty." >&2
|
|
exit 1
|
|
elif grep -qP '.*custom.*' <(echo "${PACKAGE_METADATA['LICENSES']}"); then
|
|
echo "[!] Package '$package_name' has custom license, skipping." >&2
|
|
continue
|
|
fi
|
|
|
|
PACKAGE_METADATA["DESCRIPTION"]=$(extract_variable_from_buildsh "TERMUX_PKG_DESCRIPTION" "$buildsh_path")
|
|
if [ -z "${PACKAGE_METADATA['DESCRIPTION']}" ]; then
|
|
echo "[!] Mandatory field 'TERMUX_PKG_DESCRIPTION' of package '$package_name' is empty." >&2
|
|
exit 1
|
|
fi
|
|
|
|
PACKAGE_METADATA["WEBSITE_URL"]=$(extract_variable_from_buildsh "TERMUX_PKG_HOMEPAGE" "$buildsh_path")
|
|
if [ -z "${PACKAGE_METADATA['WEBSITE_URL']}" ]; then
|
|
echo "[!] Mandatory field 'TERMUX_PKG_HOMEPAGE' of package '$package_name' is empty." >&2
|
|
exit 1
|
|
fi
|
|
|
|
PACKAGE_METADATA["VERSION"]=$(extract_variable_from_buildsh "TERMUX_PKG_VERSION" "$buildsh_path")
|
|
if [ -z "${PACKAGE_METADATA['VERSION']}" ]; then
|
|
echo "[!] Mandatory field 'TERMUX_PKG_VERSION' of package '$package_name' is empty." >&2
|
|
exit 1
|
|
fi
|
|
|
|
PACKAGE_METADATA["REVISION"]=$(extract_variable_from_buildsh "TERMUX_PKG_REVISION" "$buildsh_path")
|
|
if [ -n "${PACKAGE_METADATA['REVISION']}" ]; then
|
|
PACKAGE_METADATA["VERSION_FULL"]="${PACKAGE_METADATA['VERSION']}-${PACKAGE_METADATA['REVISION']}"
|
|
else
|
|
if [ "${PACKAGE_METADATA['VERSION']}" != "${PACKAGE_METADATA['VERSION']/-/}" ]; then
|
|
PACKAGE_METADATA["VERSION_FULL"]="${PACKAGE_METADATA['VERSION']}-0"
|
|
else
|
|
PACKAGE_METADATA["VERSION_FULL"]="${PACKAGE_METADATA['VERSION']}"
|
|
fi
|
|
fi
|
|
else
|
|
echo "[!] Cannot find 'build.sh' for package '$package_name'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if $PACKAGE_DELETE_MODE; then
|
|
delete_package "$package_name"
|
|
else
|
|
upload_package "$package_name"
|
|
fi
|
|
done
|
|
|
|
# In deletion mode we need to do metadata recalculation separately
|
|
# to ensure that it will be signed with maintainer's key.
|
|
if $PACKAGE_DELETE_MODE; then
|
|
local curl_response
|
|
local http_status_code
|
|
local api_response_message
|
|
|
|
echo -n "[@] Requesting metadata recalculation... " >&2
|
|
curl_response=$(
|
|
curl \
|
|
--silent \
|
|
--user "${BINTRAY_USERNAME}:${BINTRAY_API_KEY}" \
|
|
--request POST \
|
|
--header "Content-Type: application/json" \
|
|
--data "{\"subject\":\"${BINTRAY_GPG_SUBJECT}\",\"passphrase\":\"$BINTRAY_GPG_PASSPHRASE\"}" \
|
|
--write-out "|%{http_code}" \
|
|
"https://api.bintray.com/calc_metadata/${BINTRAY_SUBJECT}/${BINTRAY_REPO_NAME}/"
|
|
)
|
|
|
|
http_status_code=$(echo "$curl_response" | cut -d'|' -f2)
|
|
api_response_message=$(echo "$curl_response" | cut -d'|' -f1 | jq -r .message)
|
|
|
|
case "$http_status_code" in
|
|
202)
|
|
echo "done" >&2
|
|
;;
|
|
*)
|
|
echo "failure" >&2
|
|
echo "[!] $api_response_message" >&2
|
|
;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
show_usage() {
|
|
{
|
|
echo
|
|
echo "Usage: package_uploader.sh [OPTIONS] [package name] ..."
|
|
echo
|
|
echo "A command line client for Bintray designed for managing"
|
|
echo "Termux *.deb packages."
|
|
echo
|
|
echo "=========================================================="
|
|
echo
|
|
echo "Primarily indended to be used by Gitlab CI for automatic"
|
|
echo "package uploads but it can be used for manual uploads too."
|
|
echo
|
|
echo "Before using this script, check that you have all"
|
|
echo "necessary credentials for accessing repository."
|
|
echo
|
|
echo "Credentials are specified via environment variables:"
|
|
echo
|
|
echo " BINTRAY_USERNAME - User name."
|
|
echo " BINTRAY_API_KEY - User's API key."
|
|
echo " BINTRAY_GPG_SUBJECT - Owner of GPG key."
|
|
echo " BINTRAY_GPG_PASSPHRASE - GPG key passphrase."
|
|
echo
|
|
echo "=========================================================="
|
|
echo
|
|
echo "Options:"
|
|
echo
|
|
echo " -d, --delete Completely delete the selected"
|
|
echo " packages from the repository instead"
|
|
echo " of uploading."
|
|
echo
|
|
echo " -h, --help Print this help."
|
|
echo
|
|
echo " -p, --path [path] Specify a directory containing *.deb"
|
|
echo " files ready for uploading."
|
|
echo " Default is './debs'."
|
|
echo
|
|
echo "=========================================================="
|
|
} >&2
|
|
}
|
|
|
|
###################################################################
|
|
|
|
while getopts ":-:hdp:" opt; do
|
|
case "$opt" in
|
|
-)
|
|
case "$OPTARG" in
|
|
delete)
|
|
PACKAGE_DELETE_MODE=true
|
|
;;
|
|
help)
|
|
show_usage
|
|
exit 0
|
|
;;
|
|
path)
|
|
DEBFILES_DIR_PATH="${!OPTIND}"
|
|
OPTIND=$((OPTIND + 1))
|
|
|
|
if [ -z "$DEBFILES_DIR_PATH" ]; then
|
|
echo "[!] Option '--${OPTARG}' requires argument." >&2
|
|
show_usage
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -d "$DEBFILES_DIR_PATH" ]; then
|
|
echo "[!] Directory '$DEBFILES_DIR_PATH' is not exist." >&2
|
|
show_usage
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "[!] Invalid option '$OPTARG'." >&2
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
d)
|
|
PACKAGE_DELETE_MODE=true
|
|
;;
|
|
h)
|
|
show_usage
|
|
exit 0
|
|
;;
|
|
p)
|
|
DEBFILES_DIR_PATH="${OPTARG}"
|
|
if [ ! -d "$DEBFILES_DIR_PATH" ]; then
|
|
echo "[!] Directory '$DEBFILES_DIR_PATH' is not exist." >&2
|
|
show_usage
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "[!] Invalid option '-${OPTARG}'." >&2
|
|
show_usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
shift $((OPTIND - 1))
|
|
|
|
if [ $# -gt 0 ]; then
|
|
# These variables should never be changed.
|
|
readonly DEBFILES_DIR_PATH
|
|
readonly PACKAGE_DELETE_MODE
|
|
readonly TERMUX_PACKAGES_BASEDIR
|
|
|
|
# Without Bintray credentials this script is useless.
|
|
if [ -z "$BINTRAY_USERNAME" ]; then
|
|
echo "[!] Variable 'BINTRAY_USERNAME' is not set." >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "$BINTRAY_API_KEY" ]; then
|
|
echo "[!] Variable 'BINTRAY_API_KEY' is not set." >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "$BINTRAY_GPG_SUBJECT" ]; then
|
|
echo "[!] Variable 'BINTRAY_GPG_SUBJECT' is not set." >&2
|
|
exit 1
|
|
fi
|
|
|
|
process_packages "$@"
|
|
exit 0
|
|
else
|
|
echo "[!] No packages specified." >&2
|
|
show_usage
|
|
exit 1
|
|
fi
|