usage() {
cat 1>&2 <<EOF
USAGE:
install [FLAGS] [OPTIONS]
If the GITHUB_TOKEN environment variable is set, it will be used for the API call to GitHub.
FLAGS:
-f, --force Force overwriting an existing binary
-h, --help Show this message and exit.
OPTIONS:
--to <PATH> Where to install the binary. [required]
--bin <NAME> The binary to extract from the release tarball. [default: repository name]
--tag <TAG> The release version to install. [default: latest release]
--target <ARCH> Install the release compiled for <ARCH>. [default: current host]
--init <URL> The config file URL to initialize from. [default: none]
EOF
}
usage_err() {
usage
1>&2 echo
err "$@"
}
ok() {
printf '\33[1;32minfo\33[0m: %s\n' "$1"
}
warn() {
printf '\33[1;33mwarning\33[0m: %s\n' "$1"
}
err() {
printf '\33[1;31merror\33[0m: %s\n' "$1" >&2
exit 1
}
check_cmd() {
command -v "$1" > /dev/null 2>&1
}
need_cmd() {
if ! check_cmd "$1"; then
err "need '$1' (command not found)"
fi
}
ensure() {
if ! "$@"; then err "command failed: $*"; fi
}
download() {
local _url=$1; shift
local _curl_arg
local _dld
if [ "$1" = "--progress" ]; then
shift
_curl_arg="--progress-bar"
else
_curl_arg="--silent"
fi
if check_cmd curl; then
_dld=curl
elif check_cmd wget; then
_dld=wget
else
_dld='curl or wget' fi
if [ "$_url" = --check ]; then
need_cmd "$_dld"
elif [ "$_dld" = curl ]; then
curl $_curl_arg --proto '=https' --show-error --fail --location "$@" "$_url"
elif [ "$_dld" = wget ]; then
wget --no-verbose --https-only "$@" "$_url"
else
err "unknown downloader" fi
}
_LATEST_RELEASE_INFO=""
get_latest_release_info() {
local _repo=$1
local _url="https://api.github.com/repos/$_repo/releases/latest"
local _json
if [ -z "$_LATEST_RELEASE_INFO" ]; then
if [ -z "$GITHUB_TOKEN" ]; then
_json=$(download "$_url")
else
_json=$(download "$_url" --header "Authorization: Bearer $GITHUB_TOKEN")
fi
if test $? -ne 0; then
err "failed to determine latest release for repository '$_repo'"
else
_LATEST_RELEASE_INFO="$_json"
fi
fi
RETVAL="$_LATEST_RELEASE_INFO"
}
get_tag() {
local _repo=$1
local _tag
need_cmd grep
need_cmd cut
get_latest_release_info "$_repo"
_tag=$(echo "$RETVAL" | grep "tag_name" | cut -f 4 -d '"')
RETVAL="$_tag"
}
get_targets() {
local _repo=$1
local _targets
need_cmd grep
need_cmd cut
get_latest_release_info "$_repo"
_targets=$(echo "$RETVAL" | grep 'name' | cut -f 4 -d '"')
RETVAL="$_targets"
}
get_bitness() {
need_cmd head
local _current_exe_head
_current_exe_head=$(head -c 5 /proc/self/exe )
if [ "$_current_exe_head" = "$(printf '\177ELF\001')" ]; then
echo 32
elif [ "$_current_exe_head" = "$(printf '\177ELF\002')" ]; then
echo 64
else
err "unknown platform bitness"
fi
}
get_endianness() {
local cputype=$1
local suffix_eb=$2
local suffix_el=$3
need_cmd head
need_cmd tail
local _current_exe_endianness
_current_exe_endianness="$(head -c 6 /proc/self/exe | tail -c 1)"
if [ "$_current_exe_endianness" = "$(printf '\001')" ]; then
echo "${cputype}${suffix_el}"
elif [ "$_current_exe_endianness" = "$(printf '\002')" ]; then
echo "${cputype}${suffix_eb}"
else
err "unknown platform endianness"
fi
}
get_architecture() {
local _ostype _cputype _bitness _arch _clibtype
_ostype="$(uname -s)"
_cputype="$(uname -m)"
_clibtype="musl"
if [ "$_ostype" = Linux ]; then
if [ "$(uname -o)" = Android ]; then
_ostype=Android
fi
if ldd --version 2>&1 | grep -q 'musl'; then
_clibtype="musl"
fi
fi
if [ "$_ostype" = Darwin ] && [ "$_cputype" = i386 ]; then
if sysctl hw.optional.x86_64 | grep -q ': 1'; then
_cputype=x86_64
fi
fi
if [ "$_ostype" = SunOS ]; then
if [ "$(/usr/bin/uname -o)" = illumos ]; then
_ostype=illumos
fi
if [ "$_cputype" = i86pc ]; then
_cputype="$(isainfo -n)"
fi
fi
case "$_ostype" in
Android)
_ostype=linux-android
;;
Linux)
_ostype=unknown-linux-$_clibtype
_bitness=$(get_bitness)
;;
FreeBSD)
_ostype=unknown-freebsd
;;
NetBSD)
_ostype=unknown-netbsd
;;
DragonFly)
_ostype=unknown-dragonfly
;;
Darwin)
_ostype=apple-darwin
;;
illumos)
_ostype=unknown-illumos
;;
MINGW* | MSYS* | CYGWIN*)
_ostype=pc-windows-gnu
;;
*)
err "unrecognized OS type: $_ostype"
;;
esac
case "$_cputype" in
i386 | i486 | i686 | i786 | x86)
_cputype=i686
;;
xscale | arm)
_cputype=arm
if [ "$_ostype" = "linux-android" ]; then
_ostype=linux-androideabi
fi
;;
armv6l)
_cputype=arm
if [ "$_ostype" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
armv7l | armv8l)
_cputype=armv7
if [ "$_ostype" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
aarch64 | arm64)
_cputype=aarch64
;;
x86_64 | x86-64 | x64 | amd64)
_cputype=x86_64
;;
mips)
_cputype=$(get_endianness mips '' el)
;;
mips64)
if [ "$_bitness" -eq 64 ]; then
_ostype="${_ostype}abi64"
_cputype=$(get_endianness mips64 '' el)
fi
;;
ppc)
_cputype=powerpc
;;
ppc64)
_cputype=powerpc64
;;
ppc64le)
_cputype=powerpc64le
;;
s390x)
_cputype=s390x
;;
riscv64)
_cputype=riscv64gc
;;
*)
err "unknown CPU type: $_cputype"
esac
if [ "${_ostype}" = unknown-linux-gnu ] && [ "${_bitness}" -eq 32 ]; then
case $_cputype in
x86_64)
_cputype=i686
;;
mips64)
_cputype=$(get_endianness mips '' el)
;;
powerpc64)
_cputype=powerpc
;;
aarch64)
_cputype=armv7
if [ "$_ostype" = "linux-android" ]; then
_ostype=linux-androideabi
else
_ostype="${_ostype}eabihf"
fi
;;
riscv64gc)
err "riscv64 with 32-bit userland unsupported"
;;
esac
fi
if [ "$_ostype" = "unknown-linux-gnueabihf" ] && [ "$_cputype" = armv7 ]; then
if ensure grep '^Features' /proc/cpuinfo | grep -q -v neon; then
_cputype=arm
fi
fi
_arch="${_cputype}-${_ostype}"
RETVAL="$_arch"
}
get_target() {
local _repo=$1
local _this_target
local _this_target_musl
local _avail_targets
local _musl_avail=false
get_architecture
_this_target="$RETVAL"
_this_target_musl="${_this_target/gnu/musl}"
get_targets "$_repo"
read -r -a _avail_targets -d '' <<< "$RETVAL"
for _target in "${_avail_targets[@]}"; do
if echo "$_target" | grep -q "$_this_target"; then
RETVAL="$_this_target"
return
elif echo "$_target" | grep -q "$_this_target_musl"; then
_musl_avail=true
fi
done
if [ "$_musl_avail" = true ]; then
RETVAL="$_this_target_musl"
return
else
err "current target $_this_target is not available for download"
fi
}
main() {
local _name _bin _tag _target _dest _url _filename _td _tf _to _init_url
local _force=false
local _repo=wfxr/rpk
while test $# -gt 0; do
case $1 in
--force | -f)
_force=true
;;
--help | -h)
usage
exit 0
;;
--bin)
shift
if [ -z "$1" ]; then
usage_err "'--bin' option requires an argument"
fi
_bin=$1
;;
--tag)
shift
if [ -z "$1" ]; then
usage_err "'--tag' option requires an argument"
fi
_tag=$1
;;
--target)
shift
if [ -z "$1" ]; then
usage_err "'--target' option requires an argument"
fi
_target=$1
;;
--to)
shift
if [ -z "$1" ]; then
usage_err "'--to' option requires an argument"
fi
_to=$1
;;
--init)
shift
if [ -z "$1" ]; then
usage_err "'--init' option requires an argument"
fi
_init_url=$1
;;
*)
;;
esac
shift
done
need_cmd install
need_cmd mkdir
need_cmd mktemp
need_cmd rm
need_cmd tar
download --check
if [ -z "$_to" ]; then
err "destination directory must be specified using '--to'"
fi
_name="${_repo#*/}"
if [ -z "$_bin" ]; then
_bin=$_name
fi
_dest="$_to/$_bin"
if [ -e "$_dest" ] && [ $_force = false ]; then
err "$_dest already exists, use '-f' or '--force' to replace"
fi
if [ -z "$_tag" ]; then
get_tag "$_repo" || return 1
_tag="$RETVAL"
ok "latest release: $_tag"
fi
if [ -z "$_target" ]; then
get_target "$_repo" || return 1
_target="$RETVAL"
ok "found valid target: $_target"
fi
_filename="$_name-$_tag-$_target.tar.gz"
_url="https://github.com/$_repo/releases/download/$_tag/$_filename"
_td=$(mktemp -d || mktemp -d -t tmp)
trap "rm -rf '$_td'" EXIT
ok "downloading: $_filename"
if ! download "$_url" --progress | tar xz -C "$_td"; then
err "failed to download and extract $_url"
fi
if [ -f "$_td/$_bin" ]; then
_tf="$_td/$_bin"
else
for f in "$_td/$_name"*"/$_bin"; do
_tf="$f"
done
if [ -z "$_tf" ]; then
err "failed to find $_bin binary in artifact"
fi
fi
if ! mkdir -p "$_to"; then
err "failed to create $_to"
fi
if ! install -m 755 "$_tf" "$_dest"; then
err "failed to install $_bin binary to $_dest"
fi
ok "installed: $_dest"
if [ -n "$_init_url" ]; then
$_dest init --from "$_init_url"
fi
}
main "$@" || exit 1