github.com/anchore/syft@v1.38.2/install.sh (about)

     1  #!/bin/sh
     2  # note: we require errors to propagate (don't set -e)
     3  set -u
     4  
     5  PROJECT_NAME=syft
     6  OWNER=anchore
     7  REPO="${PROJECT_NAME}"
     8  GITHUB_DOWNLOAD_PREFIX=https://github.com/${OWNER}/${REPO}/releases/download
     9  INSTALL_SH_BASE_URL=https://get.anchore.io/${PROJECT_NAME}
    10  LEGACY_INSTALL_SH_BASE_URL=https://raw.githubusercontent.com/${OWNER}/${PROJECT_NAME}
    11  PROGRAM_ARGS=$@
    12  
    13  # signature verification options
    14  
    15  # the location to the cosign binary (allowed to be overridden by the user)
    16  COSIGN_BINARY=${COSIGN_BINARY:-cosign}
    17  VERIFY_SIGN=false
    18  # this is the earliest tag in the repo where cosign sign-blob was introduced in the release process (see the goreleaser config)
    19  VERIFY_SIGN_SUPPORTED_VERSION=v0.104.0
    20  # this is the earliest tag in the repo where the -v flag was introduced to this install.sh script
    21  VERIFY_SIGN_FLAG_VERSION=v1.6.0
    22  
    23  # do not change the name of this parameter (this must always be backwards compatible)
    24  DOWNLOAD_TAG_INSTALL_SCRIPT=${DOWNLOAD_TAG_INSTALL_SCRIPT:-true}
    25  
    26  # ------------------------------------------------------------------------
    27  # https://github.com/client9/shlib - portable posix shell functions
    28  # Public domain - http://unlicense.org
    29  # https://github.com/client9/shlib/blob/master/LICENSE.md
    30  # but credit (and pull requests) appreciated.
    31  # ------------------------------------------------------------------------
    32  
    33  is_command() (
    34    command -v "$1" >/dev/null
    35  )
    36  
    37  echo_stderr() (
    38    echo "$@" 1>&2
    39  )
    40  
    41  _logp=2
    42  log_set_priority() {
    43    _logp="$1"
    44  }
    45  
    46  log_priority() (
    47    if test -z "$1"; then
    48      echo "$_logp"
    49      return
    50    fi
    51    [ "$1" -le "$_logp" ]
    52  )
    53  
    54  init_colors() {
    55    RED=''
    56    BLUE=''
    57    PURPLE=''
    58    BOLD=''
    59    RESET=''
    60    # check if stdout is a terminal
    61    if test -t 1 && is_command tput; then
    62        # see if it supports colors
    63        ncolors=$(tput colors)
    64        if test -n "$ncolors" && test $ncolors -ge 8; then
    65          RED='\033[0;31m'
    66          BLUE='\033[0;34m'
    67          PURPLE='\033[0;35m'
    68          BOLD='\033[1m'
    69          RESET='\033[0m'
    70        fi
    71    fi
    72  }
    73  
    74  init_colors
    75  
    76  log_tag() (
    77    case $1 in
    78      0) echo "${RED}${BOLD}[error]${RESET}" ;;
    79      1) echo "${RED}[warn]${RESET}" ;;
    80      2) echo "[info]${RESET}" ;;
    81      3) echo "${BLUE}[debug]${RESET}" ;;
    82      4) echo "${PURPLE}[trace]${RESET}" ;;
    83      *) echo "[$1]" ;;
    84    esac
    85  )
    86  
    87  
    88  log_trace_priority=4
    89  log_trace() (
    90    priority=$log_trace_priority
    91    log_priority "$priority" || return 0
    92    echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
    93  )
    94  
    95  log_debug_priority=3
    96  log_debug() (
    97    priority=$log_debug_priority
    98    log_priority "$priority" || return 0
    99    echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
   100  )
   101  
   102  log_info_priority=2
   103  log_info() (
   104    priority=$log_info_priority
   105    log_priority "$priority" || return 0
   106    echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
   107  )
   108  
   109  log_warn_priority=1
   110  log_warn() (
   111    priority=$log_warn_priority
   112    log_priority "$priority" || return 0
   113    echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
   114  )
   115  
   116  log_err_priority=0
   117  log_err() (
   118    priority=$log_err_priority
   119    log_priority "$priority" || return 0
   120    echo_stderr "$(log_tag $priority)" "${@}" "${RESET}"
   121  )
   122  
   123  uname_os_check() (
   124    os=$1
   125    case "$os" in
   126      darwin) return 0 ;;
   127      dragonfly) return 0 ;;
   128      freebsd) return 0 ;;
   129      linux) return 0 ;;
   130      android) return 0 ;;
   131      nacl) return 0 ;;
   132      netbsd) return 0 ;;
   133      openbsd) return 0 ;;
   134      plan9) return 0 ;;
   135      solaris) return 0 ;;
   136      windows) return 0 ;;
   137    esac
   138    log_err "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
   139    return 1
   140  )
   141  
   142  uname_arch_check() (
   143    arch=$1
   144    case "$arch" in
   145      386) return 0 ;;
   146      amd64) return 0 ;;
   147      arm64) return 0 ;;
   148      armv5) return 0 ;;
   149      armv6) return 0 ;;
   150      armv7) return 0 ;;
   151      ppc64) return 0 ;;
   152      ppc64le) return 0 ;;
   153      mips) return 0 ;;
   154      mipsle) return 0 ;;
   155      mips64) return 0 ;;
   156      mips64le) return 0 ;;
   157      s390x) return 0 ;;
   158      amd64p32) return 0 ;;
   159    esac
   160    log_err "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value.  Please file bug report at https://github.com/client9/shlib"
   161    return 1
   162  )
   163  
   164  unpack() (
   165    archive=$1
   166  
   167    log_trace "unpack(archive=${archive})"
   168  
   169    case "${archive}" in
   170      *.tar.gz | *.tgz) tar --no-same-owner -xzf "${archive}" ;;
   171      *.tar) tar --no-same-owner -xf "${archive}" ;;
   172      *.zip) unzip -q "${archive}" ;;
   173      *.dmg) extract_from_dmg "${archive}" ;;
   174      *)
   175        log_err "unpack unknown archive format for ${archive}"
   176        return 1
   177        ;;
   178    esac
   179  )
   180  
   181  extract_from_dmg() (
   182    dmg_file=$1
   183  
   184    mount_point="/Volumes/tmp-dmg"
   185    hdiutil attach -quiet -nobrowse -mountpoint "${mount_point}" "${dmg_file}"
   186    cp -fR "${mount_point}/." ./
   187    hdiutil detach -quiet -force "${mount_point}"
   188  )
   189  
   190  http_download_curl() (
   191    local_file=$1
   192    source_url=$2
   193    header=$3
   194  
   195    log_trace "http_download_curl(local_file=$local_file, source_url=$source_url, header=$header)"
   196  
   197    if [ -z "$header" ]; then
   198      code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
   199    else
   200      code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
   201    fi
   202  
   203    if [ "$code" != "200" ]; then
   204      log_err "received HTTP status=$code for url='$source_url'"
   205      return 1
   206    fi
   207    return 0
   208  )
   209  
   210  http_download_wget() (
   211    local_file=$1
   212    source_url=$2
   213    header=$3
   214  
   215    log_trace "http_download_wget(local_file=$local_file, source_url=$source_url, header=$header)"
   216  
   217    if [ -z "$header" ]; then
   218      wget -q -O "$local_file" "$source_url"
   219    else
   220      wget -q --header "$header" -O "$local_file" "$source_url"
   221    fi
   222  )
   223  
   224  http_download() (
   225    log_debug "http_download(url=$2)"
   226    if is_command curl; then
   227      http_download_curl "$@"
   228      return
   229    elif is_command wget; then
   230      http_download_wget "$@"
   231      return
   232    fi
   233    log_err "http_download unable to find wget or curl"
   234    return 1
   235  )
   236  
   237  http_copy() (
   238    tmp=$(mktemp)
   239    http_download "${tmp}" "$1" "$2" || return 1
   240    body=$(cat "$tmp")
   241    rm -f "${tmp}"
   242    echo "$body"
   243  )
   244  
   245  hash_sha256() (
   246    TARGET=${1:-/dev/stdin}
   247    if is_command gsha256sum; then
   248      hash=$(gsha256sum "$TARGET") || return 1
   249      echo "$hash" | cut -d ' ' -f 1
   250    elif is_command sha256sum; then
   251      hash=$(sha256sum "$TARGET") || return 1
   252      echo "$hash" | cut -d ' ' -f 1
   253    elif is_command shasum; then
   254      hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
   255      echo "$hash" | cut -d ' ' -f 1
   256    elif is_command openssl; then
   257      hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
   258      echo "$hash" | cut -d ' ' -f a
   259    else
   260      log_err "hash_sha256 unable to find command to compute sha-256 hash"
   261      return 1
   262    fi
   263  )
   264  
   265  hash_sha256_verify() (
   266    target=$1
   267    checksums=$2
   268    if [ -z "$checksums" ]; then
   269      log_err "hash_sha256_verify checksum file not specified as argument"
   270      return 1
   271    fi
   272    target_basename=${target##*/}
   273    want=$(grep "${target_basename}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
   274    if [ -z "$want" ]; then
   275      log_err "hash_sha256_verify unable to find checksum for '${target}' in '${checksums}'"
   276      return 1
   277    fi
   278    got=$(hash_sha256 "$target")
   279    if [ "$want" != "$got" ]; then
   280      log_err "hash_sha256_verify checksum for '$target' did not verify ${want} vs $got"
   281      return 1
   282    fi
   283  )
   284  
   285  # ------------------------------------------------------------------------
   286  # End of functions from https://github.com/client9/shlib
   287  # ------------------------------------------------------------------------
   288  
   289  # asset_file_exists [path]
   290  #
   291  # returns 1 if the given file does not exist
   292  #
   293  asset_file_exists() (
   294    path="$1"
   295    if [ ! -f "${path}" ]; then
   296        return 1
   297    fi
   298  )
   299  
   300  
   301  # github_release_json [owner] [repo] [version]
   302  #
   303  # outputs release json string
   304  #
   305  github_release_json() (
   306    owner=$1
   307    repo=$2
   308    version=$3
   309    test -z "$version" && version="latest"
   310    giturl="https://github.com/${owner}/${repo}/releases/${version}"
   311    json=$(http_copy "$giturl" "Accept:application/json")
   312  
   313    log_trace "github_release_json(owner=${owner}, repo=${repo}, version=${version}) returned '${json}'"
   314  
   315    test -z "$json" && return 1
   316    echo "${json}"
   317  )
   318  
   319  # extract_value [key-value-pair]
   320  #
   321  # outputs value from a colon delimited key-value pair
   322  #
   323  extract_value() (
   324    key_value="$1"
   325    IFS=':' read -r _ value << EOF
   326  ${key_value}
   327  EOF
   328    echo "$value"
   329  )
   330  
   331  # extract_json_value [json] [key]
   332  #
   333  # outputs value of the key from the given json string
   334  #
   335  extract_json_value() (
   336    json="$1"
   337    key="$2"
   338    key_value=$(echo "${json}" | grep  -o "\"$key\":[^,]*[,}]" | tr -d '",}')
   339  
   340    extract_value "$key_value"
   341  )
   342  
   343  # github_release_tag [release-json]
   344  #
   345  # outputs release tag string
   346  #
   347  github_release_tag() (
   348    json="$1"
   349    tag=$(extract_json_value "${json}" "tag_name")
   350    test -z "$tag" && return 1
   351    echo "$tag"
   352  )
   353  
   354  # github_release_asset_url [release-url-prefix] [name] [version] [output-dir] [filename]
   355  #
   356  # outputs the url to the release asset
   357  #
   358  github_release_asset_url() (
   359    download_url="$1"
   360    name="$2"
   361    version="$3"
   362    filename="$4"
   363  
   364    complete_filename="${name}_${version}_${filename}"
   365    complete_url="${download_url}/${complete_filename}"
   366  
   367    echo "${complete_url}"
   368  )
   369  
   370  # download_github_release_checksums_files [release-url-prefix] [name] [version] [output-dir] [filename]
   371  #
   372  # outputs path to the downloaded checksums related file
   373  #
   374  download_github_release_checksums_files() (
   375    download_url="$1"
   376    name="$2"
   377    version="$3"
   378    output_dir="$4"
   379    filename="$5"
   380  
   381    log_trace "download_github_release_checksums_files(url=${download_url}, name=${name}, version=${version}, output_dir=${output_dir}, filename=${filename})"
   382  
   383    complete_filename="${name}_${version}_${filename}"
   384    complete_url=$(github_release_asset_url "${download_url}" "${name}" "${version}" "${filename}")
   385    output_path="${output_dir}/${complete_filename}"
   386  
   387    http_download "${output_path}" "${complete_url}" ""
   388    asset_file_exists "${output_path}"
   389  
   390    log_trace "download_github_release_checksums_files() returned '${output_path}' for file '${complete_filename}'"
   391  
   392    echo "${output_path}"
   393  )
   394  
   395  # download_github_release_checksums [release-url-prefix] [name] [version] [output-dir]
   396  #
   397  # outputs path to the downloaded checksums file
   398  #
   399  download_github_release_checksums() (
   400    download_github_release_checksums_files "$@" "checksums.txt"
   401  )
   402  
   403  # github_release_checksums_sig_url [release-url-prefix] [name] [version]
   404  #
   405  # outputs the url to the release checksums signature file
   406  #
   407  github_release_checksums_sig_url() (
   408    github_release_asset_url "$@" "checksums.txt.sig"
   409  )
   410  
   411  # github_release_checksums_cert_url [release-url-prefix] [name] [version]
   412  #
   413  # outputs the url to the release checksums certificate file
   414  #
   415  github_release_checksums_cert_url() (
   416    github_release_asset_url "$@" "checksums.txt.pem"
   417  )
   418  
   419  # search_for_asset [checksums-file-path] [name] [os] [arch] [format]
   420  #
   421  # outputs name of the asset to download
   422  #
   423  search_for_asset() (
   424    checksum_path="$1"
   425    name="$2"
   426    os="$3"
   427    arch="$4"
   428    format="$5"
   429  
   430    log_trace "search_for_asset(checksum-path=${checksum_path}, name=${name}, os=${os}, arch=${arch}, format=${format})"
   431  
   432    asset_glob="${name}_.*_${os}_${arch}.${format}"
   433    output_path=$(grep -o "${asset_glob}" "${checksum_path}" || true)
   434  
   435    log_trace "search_for_asset() returned '${output_path}'"
   436  
   437    echo "${output_path}"
   438  )
   439  
   440  # uname_os
   441  #
   442  # outputs an adjusted os value
   443  #
   444  uname_os() (
   445    os=$(uname -s | tr '[:upper:]' '[:lower:]')
   446    case "$os" in
   447      cygwin_nt*) os="windows" ;;
   448      mingw*) os="windows" ;;
   449      msys_nt*) os="windows" ;;
   450    esac
   451  
   452    uname_os_check "$os"
   453  
   454    log_trace "uname_os() returned '${os}'"
   455  
   456    echo "$os"
   457  )
   458  
   459  # uname_arch
   460  #
   461  # outputs an adjusted architecture value
   462  #
   463  uname_arch() (
   464    arch=$(uname -m)
   465    case $arch in
   466      x86_64) arch="amd64" ;;
   467      x86) arch="386" ;;
   468      i686) arch="386" ;;
   469      i386) arch="386" ;;
   470      aarch64) arch="arm64" ;;
   471      armv5*) arch="armv5" ;;
   472      armv6*) arch="armv6" ;;
   473      armv7*) arch="armv7" ;;
   474    esac
   475  
   476    uname_arch_check "${arch}"
   477  
   478    log_trace "uname_arch() returned '${arch}'"
   479  
   480    echo "${arch}"
   481  )
   482  
   483  # get_release_tag [owner] [repo] [tag]
   484  #
   485  # outputs tag string
   486  #
   487  get_release_tag() (
   488    owner="$1"
   489    repo="$2"
   490    tag="$3"
   491  
   492    log_trace "get_release_tag(owner=${owner}, repo=${repo}, tag=${tag})"
   493  
   494    json=$(github_release_json "${owner}" "${repo}" "${tag}")
   495    real_tag=$(github_release_tag "${json}")
   496    if test -z "${real_tag}"; then
   497      return 1
   498    fi
   499  
   500    log_trace "get_release_tag() returned '${real_tag}'"
   501  
   502    echo "${real_tag}"
   503  )
   504  
   505  # tag_to_version [tag]
   506  #
   507  # outputs version string
   508  #
   509  tag_to_version() (
   510    tag="$1"
   511    value="${tag#v}"
   512  
   513    log_trace "tag_to_version(tag=${tag}) returned '${value}'"
   514  
   515    echo "$value"
   516  )
   517  
   518  # get_binary_name [os] [arch] [default-name]
   519  #
   520  # outputs a the binary string name
   521  #
   522  get_binary_name() (
   523    os="$1"
   524    arch="$2"
   525    binary="$3"
   526    original_binary="${binary}"
   527  
   528    case "${os}" in
   529      windows) binary="${binary}.exe" ;;
   530    esac
   531  
   532    log_trace "get_binary_name(os=${os}, arch=${arch}, binary=${original_binary}) returned '${binary}'"
   533  
   534    echo "${binary}"
   535  )
   536  
   537  
   538  # get_format_name [os] [arch] [default-format]
   539  #
   540  # outputs an adjusted file format
   541  #
   542  get_format_name() (
   543    os="$1"
   544    arch="$2"
   545    format="$3"
   546    original_format="${format}"
   547  
   548    case ${os} in
   549      windows) format=zip ;;
   550    esac
   551  
   552    log_trace "get_format_name(os=${os}, arch=${arch}, format=${original_format}) returned '${format}'"
   553  
   554    echo "${format}"
   555  )
   556  
   557  # download_and_install_asset [release-url-prefix] [download-path] [install-path] [name] [os] [arch] [version] [format] [binary]
   558  #
   559  # attempts to download the archive and install it to the given path.
   560  #
   561  download_and_install_asset() (
   562    download_url="$1"
   563    download_path="$2"
   564    install_path=$3
   565    name="$4"
   566    os="$5"
   567    arch="$6"
   568    version="$7"
   569    format="$8"
   570    binary="$9"
   571  
   572    if ! asset_filepath=$(download_asset "${download_url}" "${download_path}" "${name}" "${os}" "${arch}" "${version}" "${format}"); then
   573        log_err "could not download asset for os='${os}' arch='${arch}' format='${format}'"
   574        return 1
   575    fi
   576  
   577    # don't continue if we couldn't download an asset
   578    if [ -z "${asset_filepath}" ]; then
   579        log_err "could not find release asset for os='${os}' arch='${arch}' format='${format}' "
   580        return 1
   581    fi
   582  
   583    install_asset "${asset_filepath}" "${install_path}" "${binary}"
   584  )
   585  
   586  # verify_sign [checksums-file-path] [certificate-reference] [signature-reference] [version]
   587  #
   588  # attempts verify the signature of the checksums file from the release workflow in Github Actions run against the main branch.
   589  #
   590  verify_sign() {
   591    checksums_file=$1
   592    cert_reference=$2
   593    sig_reference=$3
   594  
   595    log_trace "verifying artifact $1"
   596  
   597    log_file=$(mktemp)
   598  
   599    ${COSIGN_BINARY} \
   600      verify-blob "$checksums_file" \
   601        --certificate "$cert_reference" \
   602        --signature "$sig_reference" \
   603        --certificate-identity "https://github.com/${OWNER}/${REPO}/.github/workflows/release.yaml@refs/heads/main" \
   604        --certificate-oidc-issuer "https://token.actions.githubusercontent.com" > "${log_file}" 2>&1
   605  
   606    if [ $? -ne 0 ]; then
   607      log_err "$(cat "${log_file}")"
   608      rm -f "${log_file}"
   609      return 1
   610    fi
   611  
   612    rm -f "${log_file}"
   613  }
   614  
   615  
   616  # download_asset [release-url-prefix] [download-path] [name] [os] [arch] [version] [format] [binary]
   617  #
   618  # outputs the path to the downloaded asset asset_filepath
   619  #
   620  download_asset() (
   621    download_url="$1"
   622    destination="$2"
   623    name="$3"
   624    os="$4"
   625    arch="$5"
   626    version="$6"
   627    format="$7"
   628  
   629    log_trace "download_asset(url=${download_url}, destination=${destination}, name=${name}, os=${os}, arch=${arch}, version=${version}, format=${format})"
   630  
   631    checksums_filepath=$(download_github_release_checksums "${download_url}" "${name}" "${version}" "${destination}")
   632  
   633    log_trace "checksums content:\n$(cat ${checksums_filepath})"
   634  
   635    asset_filename=$(search_for_asset "${checksums_filepath}" "${name}" "${os}" "${arch}" "${format}")
   636  
   637    # don't continue if we couldn't find a matching asset from the checksums file
   638    if [ -z "${asset_filename}" ]; then
   639        return 1
   640    fi
   641  
   642    if [ "$VERIFY_SIGN" = true ]; then
   643      checksum_sig_file_url=$(github_release_checksums_sig_url "${download_url}" "${name}" "${version}")
   644      log_trace "checksums signature url: ${checksum_sig_file_url}"
   645  
   646      checksums_cert_file_url=$(github_release_checksums_cert_url "${download_url}" "${name}" "${version}")
   647      log_trace "checksums certificate url: ${checksums_cert_file_url}"
   648  
   649      if ! verify_sign "${checksums_filepath}" "${checksums_cert_file_url}" "${checksum_sig_file_url}"; then
   650        log_err "signature verification failed"
   651        return 1
   652      fi
   653      log_info "signature verification succeeded"
   654    fi
   655  
   656    asset_url="${download_url}/${asset_filename}"
   657    asset_filepath="${destination}/${asset_filename}"
   658    http_download "${asset_filepath}" "${asset_url}" ""
   659  
   660    hash_sha256_verify "${asset_filepath}" "${checksums_filepath}"
   661  
   662    log_trace "download_asset_by_checksums_file() returned '${asset_filepath}'"
   663  
   664    echo "${asset_filepath}"
   665  )
   666  
   667  # install_asset [asset-path] [destination-path] [binary]
   668  #
   669  install_asset() (
   670    asset_filepath="$1"
   671    destination="$2"
   672    binary="$3"
   673  
   674    log_trace "install_asset(asset=${asset_filepath}, destination=${destination}, binary=${binary})"
   675  
   676    # don't continue if we don't have anything to install
   677    if [ -z "${asset_filepath}" ]; then
   678        return
   679    fi
   680  
   681    archive_dir=$(dirname "${asset_filepath}")
   682  
   683    # unarchive the downloaded archive to the temp dir
   684    (cd "${archive_dir}" && unpack "${asset_filepath}")
   685  
   686    # create the destination dir
   687    test ! -d "${destination}" && install -d "${destination}"
   688  
   689    # install the binary to the destination dir
   690    install "${archive_dir}/${binary}" "${destination}/"
   691  )
   692  
   693  # compare two semver strings. Returns 0 if version1 >= version2, 1 otherwise.
   694  # Note: pre-release (-) and metadata (+) are not supported.
   695  compare_semver() {
   696      # remove leading 'v' if present
   697      version1=${1#v}
   698      version2=${2#v}
   699  
   700      IFS=. read -r major1 minor1 patch1 <<EOF
   701  $version1
   702  EOF
   703      IFS=. read -r major2 minor2 patch2 <<EOF
   704  $version2
   705  EOF
   706  
   707      if [ "$major1" -gt "$major2" ]; then
   708          return 0
   709      elif [ "$major1" -lt "$major2" ]; then
   710          return 1
   711      fi
   712  
   713      if [ "$minor1" -gt "$minor2" ]; then
   714          return 0
   715      elif [ "$minor1" -lt "$minor2" ]; then
   716          return 1
   717      fi
   718  
   719      if [ "$patch1" -gt "$patch2" ]; then
   720          return 0
   721      elif [ "$patch1" -lt "$patch2" ]; then
   722          return 1
   723      fi
   724  
   725      # versions are equal
   726      return 0
   727  }
   728  
   729  prep_signature_verification() {
   730    version="$1"
   731  
   732    if [ "$VERIFY_SIGN" != true ]; then
   733      return 0
   734    fi
   735  
   736    # is there any cryptographic material produced at release that we can use for signature verification?
   737    if ! compare_semver "$version" "$VERIFY_SIGN_SUPPORTED_VERSION"; then
   738      log_err "${PROJECT_NAME} release '$version' does not support signature verification"
   739      log_err "you can still install ${PROJECT_NAME} by removing the -v flag or using a release that supports signature verification (>= '$VERIFY_SIGN_SUPPORTED_VERSION')"
   740      log_err "aborting installation"
   741      return 1
   742    else
   743      log_trace "${PROJECT_NAME} release '$version' supports signature verification (>= '$VERIFY_SIGN_SUPPORTED_VERSION')"
   744    fi
   745  
   746    # will invoking an earlier version of this script work (considering the -v flag)?
   747    if ! compare_semver "$version" "$VERIFY_SIGN_FLAG_VERSION"; then
   748      # the -v argument did not always exist, so we cannot be guaranteed that invoking an earlier version of this script
   749      # will work (error with "illegal option -v"). However, the user requested signature verification, so we will
   750      # attempt to install the application with this version of the script (keeping signature verification).
   751      DOWNLOAD_TAG_INSTALL_SCRIPT=false
   752      log_debug "provided version install script does not support -v flag (>= '$VERIFY_SIGN_FLAG_VERSION'), using current script for installation"
   753    else
   754      log_trace "provided version install script supports -v flag (>= '$VERIFY_SIGN_FLAG_VERSION')"
   755    fi
   756  
   757    # check to see if the cosign binary is installed
   758    if is_command "${COSIGN_BINARY}"; then
   759      log_trace "${COSIGN_BINARY} binary is installed"
   760    else
   761      log_err "signature verification is requested but ${COSIGN_BINARY} binary is not installed (see https://docs.sigstore.dev/system_config/installation/ to install it)"
   762      return 1
   763    fi
   764  }
   765  
   766  main() (
   767    # parse arguments
   768  
   769    # note: never change default install directory (this must always be backwards compatible)
   770    install_dir=${install_dir:-./bin}
   771  
   772    # note: never change the program flags or arguments (this must always be backwards compatible)
   773    while getopts "b:dvh?x" arg; do
   774      case "$arg" in
   775        b) install_dir="$OPTARG" ;;
   776        d)
   777          if [ "$_logp" = "$log_info_priority" ]; then
   778            # -d == debug
   779            log_set_priority $log_debug_priority
   780          else
   781            # -dd (or -ddd...) == trace
   782            log_set_priority $log_trace_priority
   783          fi
   784          ;;
   785        v) VERIFY_SIGN=true;;
   786        h | \?)
   787          cat <<EOF
   788  Download and install a released binary for ${OWNER}/${REPO} from the github releases page
   789  
   790  Usage: $0 [-v] [-b DIR] [-d] [TAG]
   791    -b DIR  the installation directory (defaults to ./bin)
   792    -d      turns on debug logging
   793    -dd     turns on trace logging
   794    -v      verify checksum signature (requires cosign binary to be installed).
   795    TAG     the specific release to use (if missing, then the latest will be used)
   796  EOF
   797          exit 0
   798        ;;
   799        x) set -x ;;
   800      esac
   801    done
   802    shift $((OPTIND - 1))
   803  
   804    set +u
   805    tag=$1
   806  
   807    if [ -z "${tag}" ]; then
   808      log_info "checking github for the current release tag"
   809      tag=""
   810    else
   811      log_info "checking github for release tag='${tag}'"
   812    fi
   813    set -u
   814  
   815    if ! tag=$(get_release_tag "${OWNER}" "${REPO}" "${tag}"); then
   816        log_err "unable to find tag='${tag}'"
   817        log_err "do not specify a version or select a valid version from https://github.com/${OWNER}/${REPO}/releases"
   818        return 1
   819    fi
   820  
   821    # run the application
   822  
   823    version=$(tag_to_version "${tag}")
   824    os=$(uname_os)
   825    arch=$(uname_arch)
   826    format=$(get_format_name "${os}" "${arch}" "tar.gz")
   827    binary=$(get_binary_name "${os}" "${arch}" "${PROJECT_NAME}")
   828    download_url="${GITHUB_DOWNLOAD_PREFIX}/${tag}"
   829  
   830    if ! prep_signature_verification "$version"; then
   831        return 1
   832    fi
   833  
   834    # we always use the install.sh script that is associated with the tagged release. Why? the latest install.sh is not
   835    # guaranteed to be able to install every version of the application. We use the DOWNLOAD_TAG_INSTALL_SCRIPT env var
   836    # to indicate if we should continue processing with the existing script or to download the script from the given tag.
   837    if [ "${DOWNLOAD_TAG_INSTALL_SCRIPT}" = "true" ]; then
   838        export DOWNLOAD_TAG_INSTALL_SCRIPT=false
   839        log_info "fetching release script for tag='${tag}'"
   840        if ! install_script=$(http_copy "${INSTALL_SH_BASE_URL}/${tag}/install.sh" ""); then
   841            log_warn "failed to fetch from ${INSTALL_SH_BASE_URL}, trying fallback URL"
   842            install_script=$(http_copy "${LEGACY_INSTALL_SH_BASE_URL}/${tag}/install.sh" "")
   843        fi
   844        echo "${install_script}" | sh -s -- ${PROGRAM_ARGS}
   845        exit $?
   846    fi
   847  
   848    log_info "using release tag='${tag}' version='${version}' os='${os}' arch='${arch}'"
   849  
   850    download_dir=$(mktemp -d)
   851    trap 'rm -rf -- "$download_dir"' EXIT
   852  
   853    log_debug "downloading files into ${download_dir}"
   854  
   855    # don't continue if we couldn't install the asset
   856    if ! download_and_install_asset "${download_url}" "${download_dir}" "${install_dir}" "${PROJECT_NAME}" "${os}" "${arch}" "${version}" "${format}" "${binary}"; then
   857        log_err "failed to install ${PROJECT_NAME}"
   858        return 1
   859    fi
   860  
   861    log_info "installed ${install_dir}/${binary}"
   862  )
   863  
   864  # entrypoint
   865  
   866  set +u
   867  if [ -z "${TEST_INSTALL_SH}" ]; then
   868    set -u
   869    main "$@"
   870    exit $?
   871  fi
   872  set -u