#!/usr/bin/env bash if ! type -f bpkg-utils &>/dev/null; then echo "error: bpkg-utils not found, aborting" exit 1 fi # shellcheck source=lib/utils/utils.sh source "$(which bpkg-utils)" # shellcheck source=lib/realpath/realpath.sh bpkg_exec_or_exit bpkg-realpath && source "$(which bpkg-realpath)" # shellcheck source=lib/getdeps/getdeps.sh bpkg_exec_or_exit bpkg-getdeps && source "$(which bpkg-getdeps)" bpkg_initrc let prevent_prune=0 let install_dev=0 let force_actions=${BPKG_FORCE_ACTIONS:-0} let needs_global=0 ## output usage usage () { echo 'usage: bpkg-install [directory]' echo ' or: bpkg-install [-h|--help]' echo ' or: bpkg-install [-d|--dev]' echo ' or: bpkg-install [-g|--global] [-f|--force] ...' echo ' or: bpkg-install [-g|--global] [-f|--force] .../' } save_remote_file () { local auth_param dirname path url url="$1" path="$2" auth_param="${3:-}" dirname="$(dirname "$path")" # Make sure directory exists if [[ ! -d "$dirname" ]];then mkdir -p "$dirname" fi if [[ "$auth_param" ]];then curl --silent -L -o "$path" -u "$auth_param" "$url" else curl --silent -L -o "$path" "$url" fi return $? } url_exists () { local auth_param exists status url url="$1" auth_param="${2:-}" exists=0 if [[ "$auth_param" ]];then status=$(curl --silent -L -w '%{http_code}' -o '/dev/null' -u "$auth_param" "$url") result="$?" else status=$(curl --silent -L -w '%{http_code}' -o '/dev/null' "$url") result="$?" fi # In some rare cases, curl will return CURLE_WRITE_ERROR (23) when writing # to `/dev/null`. In such a case we do not care that such an error occured. # We are only interested in the status, which *will* be available regardless. if [[ '0' != "$result" && '23' != "$result" ]] || (( status >= 400 )); then exists=1 fi return "$exists" } fetch () { local auth_param url url="$1" auth_param="${2:-}" if [[ "$auth_param" ]];then curl --silent -L -u "$auth_param" "$url" else curl --silent -L "$url" fi return $? } ## Install a bash package bpkg_install () { local pkgs=() local did_fail=1 for opt in "$@"; do case "$opt" in -h|--help) usage return 0 ;; -g|--global) shift needs_global=1 ;; -f|--force) shift force_actions=1 ;; -d|--dev) shift install_dev=1 ;; --no-prune) shift prevent_prune=1 ;; *) if [[ '-' != "${opt:0:1}" ]]; then pkgs+=("$opt") shift else bpkg_error "Unknown option \`$opt'" return 1 fi ;; esac done export BPKG_FORCE_ACTIONS=$force_actions BPKG_DEPS_EXEC="bpkg_getdeps" if (( 1 == install_dev )); then BPKG_DEPS_EXEC="${BPKG_DEPS_EXEC} --dev" fi ## ensure there is a package to install if (( ${#pkgs[@]} == 0 )); then ${BPKG_DEPS_EXEC} return $? fi echo for pkg in "${pkgs[@]}"; do if test -d "$(bpkg_realpath "$pkg" 2>/dev/null)"; then if ! (cd "$pkg" && ${BPKG_DEPS_EXEC}); then return 1 fi did_fail=0 continue fi ## Check each remote in order local i=0 for remote in "${BPKG_REMOTES[@]}"; do local git_remote=${BPKG_GIT_REMOTES[$i]} if bpkg_install_from_remote "$pkg" "$remote" "$git_remote" $needs_global $install_dev; then did_fail=0 break elif [[ "$?" == '2' ]]; then bpkg_error 'fatal error occurred during install' return 1 fi i=$((i+1)) done done if ((${#pkgs[@]} == 0)); then bpkg_error 'no packages supplied' return 1 fi if (( did_fail == 1 )); then bpkg_error 'package not found on any remote' return 1 fi return 0 } ## try to install a package from a specific remote ## returns values: ## 0: success ## 1: the package was not found on the remote ## 2: a fatal error occurred bpkg_install_from_remote () { local pkg=$1 local remote=$2 local git_remote=$3 local needs_global=$4 local install_dev=$5 local url='' local uri='' local version='' local json='' local user='' local name='' local repo='' local version='' local auth_param='' local has_pkg_json=0 local package_file='' declare -a pkg_parts=() declare -a remote_parts=() declare -a scripts=() declare -a files=() ## get version if available { OLDIFS="$IFS" IFS="@" # shellcheck disable=SC2206 pkg_parts=($pkg) IFS="$OLDIFS" } if [[ ${#pkg_parts[@]} -eq 1 ]]; then version='master' #info "Using latest (master)" elif [[ ${#pkg_parts[@]} -eq 2 ]]; then name="${pkg_parts[0]}" version="${pkg_parts[1]}" else bpkg_error 'Error parsing package version' return 1 fi ## split by user name and repo { OLDIFS="$IFS" IFS='/' # shellcheck disable=SC2206 pkg_parts=($pkg) IFS="$OLDIFS" } if [[ ${#pkg_parts[@]} -eq 1 ]]; then user="$BPKG_PACKAGE_DEFAULT_USER" name="${pkg_parts[0]}" elif [[ ${#pkg_parts[@]} -eq 2 ]]; then user="${pkg_parts[0]}" name="${pkg_parts[1]}" else bpkg_error 'Unable to determine package name' return 1 fi ## clean up name of weird trailing ## versions and slashes name=${name/@*//} name=${name////} ## check to see if remote is raw with oauth (GHE) if [[ "${remote:0:10}" == "raw-oauth|" ]]; then bpkg_info 'Using OAUTH basic with content requests' OLDIFS="$IFS" IFS="'|'" local remote_parts=("$remote") IFS="$OLDIFS" local token=${remote_parts[1]} remote=${remote_parts[2]} auth_param="$token:x-oauth-basic" uri="/$user/$name/raw/$version" ## If git remote is a URL, and doesn't contain token information, we ## inject it into the @host field if [[ "$git_remote" == https://* ]] && [[ "$git_remote" != *x-oauth-basic* ]] && [[ "$git_remote" != *$token* ]]; then git_remote=${git_remote/https:\/\//https:\/\/$token:x-oauth-basic@} fi else uri="/$user/$name/$version" fi ## clean up extra slashes in uri uri=${uri/\/\///} bpkg_info "Install $uri from remote $remote [$git_remote]" ## Ensure remote is reachable ## If a remote is totally down, this will be considered a fatal ## error since the user may have intended to install the package ## from the broken remote. { if ! url_exists "$remote" "$auth_param"; then bpkg_error "Remote unreachable: $remote" return 2 fi } ## build url url="$remote/$uri" local nonce="$(date +%s)" if url_exists "$url/bpkg.json?$nonce" "$auth_param"; then ## read 'bpkg.json' json=$(fetch "$url/bpkg.json?$nonce" "$auth_param") package_file='bpkg.json' has_pkg_json=1 elif url_exists "$url/package.json?$nonce" "$auth_param"; then ## read 'package.json' json=$(fetch "$url/package.json?$nonce" "$auth_param") package_file='package.json' has_pkg_json=1 fi if (( 0 == has_pkg_json )); then ## check to see if there's a Makefile. If not, this is not a valid package if ! url_exists "$url/Makefile?$nonce" "$auth_param"; then bpkg_warn "Makefile not found, skipping remote: $url" return 1 fi fi if (( 1 == has_pkg_json )); then ## get package name from 'bpkg.json' or 'package.json' name="$( echo -n "$json" | bpkg-json -b | grep -m 1 '"name"' | awk '{ $1=""; print $0 }' | tr -d '\"' | tr -d ' ' )" ## get package name from 'bpkg.json' or 'package.json' repo="$( echo -n "$json" | bpkg-json -b | grep -m 1 '"repo"' | awk '{ $1=""; print $0 }' | tr -d '\"' | tr -d ' ' )" ## check if forced global if [[ "$(echo -n "$json" | bpkg-json -b | grep '\["global"\]' | awk '{ print $2 }' | tr -d '"')" == 'true' ]]; then needs_global=1 fi ## construct scripts array { scripts=($(echo -n "$json" | bpkg-json -b | grep '\["scripts' | awk '{ print $2 }' | tr -d '"')) ## create array by splitting on newline OLDIFS="$IFS" IFS=$'\n' # shellcheck disable=SC2206 scripts=(${scripts[@]}) IFS="$OLDIFS" } ## construct files array { files=($(echo -n "$json" | bpkg-json -b | grep '\["files' | awk '{ print $2 }' | tr -d '"')) ## create array by splitting on newline OLDIFS="$IFS" IFS=$'\n' files=("${files[@]}") IFS="$OLDIFS" } fi if [ -n "$repo" ]; then repo_url="$git_remote/$repo.git" else repo_url="$git_remote/$user/$name.git" fi ## build global if needed if (( 1 == needs_global )); then if (( has_pkg_json > 0 )); then ## install bin if needed build="$(echo -n "$json" | bpkg-json -b -f='"install"')" build=${build#\"} build=${build%\"} fi if [[ -z "$build" ]]; then bpkg_warn 'Missing build script' bpkg_warn 'Trying "make install"...' build='make install' fi if [ -z "$PREFIX" ]; then if [ "$USER" == "root" ]; then PREFIX="/usr/local" else PREFIX="$HOME/.local" fi build="env PREFIX=$PREFIX $build" fi {( ## go to tmp dir cd "$([[ -n "$TMPDIR" ]] && echo "$TMPDIR" || echo /tmp)" || return $? ## prune existing ( (( 0 == prevent_prune )) && rm -rf "$name-$version") ## shallow clone bpkg_info "Cloning $repo_url to $(pwd)/$name-$version" (test -d "$name-$version" || git clone "$repo_url" "$name-$version" 2>/dev/null) && ( ## move into directory cd "$name-$version" && ( ## checkout to branch version or checkout into ## branch 'main' just in case 'master' was renamed git checkout "$version" 2>/dev/null || ([ "$version" = "master" ] && git checkout main 2>/dev/null) || (git checkout master 2>/dev/null) ) ## build bpkg_info "Performing install: \`$build'" mkdir -p "$PREFIX"/{bin,lib} build_output=$(eval "$build") echo "$build_output" ) ## clean up if (( 0 == prevent_prune )); then rm -rf "$name-$version" fi )} ## perform local install otherwise else ## copy 'bpkg.json' or 'package.json' over save_remote_file "$url/$package_file" "$BPKG_PACKAGE_DEPS/$name/$package_file" "$auth_param" ## make '$BPKG_PACKAGE_DEPS/' directory if possible mkdir -p "$BPKG_PACKAGE_DEPS/$name" ## make '$BPKG_PACKAGE_DEPS/bin' directory if possible mkdir -p "$BPKG_PACKAGE_DEPS/bin" # install package dependencies bpkg_info "Install dependencies for $name" BPKG_DEPS_EXEC="bpkg_getdeps" if (( 1 == install_dev )); then BPKG_DEPS_EXEC="${BPKG_DEPS_EXEC} --dev" fi (cd "$BPKG_PACKAGE_DEPS/$name" && ${BPKG_DEPS_EXEC}) ## grab each script and place in deps directory for script in "${scripts[@]}"; do ( if [[ "$script" ]];then local scriptname="$(echo "$script" | xargs basename )" bpkg_info "fetch" "$url/$script" bpkg_warn "BPKG_PACKAGE_DEPS is '$BPKG_PACKAGE_DEPS'" bpkg_info "write" "$BPKG_PACKAGE_DEPS/$name/$script" save_remote_file "$url/$script" "$BPKG_PACKAGE_DEPS/$name/$script" "$auth_param" scriptname="${scriptname%.*}" bpkg_info "$scriptname to PATH" "$BPKG_PACKAGE_DEPS/bin/$scriptname" if (( force_actions == 1 )); then ln -sf "../$name/$script" "$BPKG_PACKAGE_DEPS/bin/$scriptname" else if test -f "$BPKG_PACKAGE_DEPS/bin/$scriptname"; then bpkg_warn "'$BPKG_PACKAGE_DEPS/bin/$scriptname' already exists. Overwrite? (yN)" read -r yn case $yn in Yy) rm -f "$BPKG_PACKAGE_DEPS/bin/$scriptname" ;; *) return 1; esac fi ln -s "../$name/$script" "$BPKG_PACKAGE_DEPS/bin/$scriptname" fi chmod u+x "$BPKG_PACKAGE_DEPS/bin/$scriptname" fi ) done if [[ "${#files[@]}" -gt '0' ]]; then ## grab each file and place in correct directory for file in "${files[@]}"; do ( if [[ "$file" ]];then bpkg_info "fetch" "$url/$file" bpkg_warn "BPKG_PACKAGE_DEPS is '$BPKG_PACKAGE_DEPS'" bpkg_info "write" "$BPKG_PACKAGE_DEPS/$name/$file" save_remote_file "$url/$file" "$BPKG_PACKAGE_DEPS/$name/$file" "$auth_param" fi ) done fi fi return 0 } ## Use as lib or perform install if [[ ${BASH_SOURCE[0]} != "$0" ]]; then export -f bpkg_install elif bpkg_validate; then bpkg_install "$@" exit $? else #param validation failed exit $? fi