@beplus/be 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/be +175 -32
- package/package.json +1 -1
package/bin/be
CHANGED
|
@@ -25,6 +25,16 @@ verbose_log() {
|
|
|
25
25
|
fi
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
#
|
|
29
|
+
# warn_log <type> <msg>
|
|
30
|
+
# Like verbose_log, but shown even with --quiet: for trouble be works around, such as a download
|
|
31
|
+
# it is about to try again.
|
|
32
|
+
#
|
|
33
|
+
|
|
34
|
+
warn_log() {
|
|
35
|
+
>&2 printf " ${SGR_CYAN}%10s${SGR_RESET} : ${SGR_FAINT}%s${SGR_RESET}\n" "$1" "$2"
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
#
|
|
29
39
|
# Exit with the given <msg ...>
|
|
30
40
|
#
|
|
@@ -64,7 +74,7 @@ function n_grep() {
|
|
|
64
74
|
# Setup and state
|
|
65
75
|
#
|
|
66
76
|
|
|
67
|
-
VERSION="v0.
|
|
77
|
+
VERSION="v0.12.0"
|
|
68
78
|
|
|
69
79
|
BE_PREFIX="${BE_PREFIX-/usr/local}"
|
|
70
80
|
BE_PREFIX=${BE_PREFIX%/}
|
|
@@ -103,7 +113,7 @@ if [[ -t 1 ]]; then
|
|
|
103
113
|
else
|
|
104
114
|
CURL_OPTIONS+=( "--silent" )
|
|
105
115
|
fi
|
|
106
|
-
WGET_OPTIONS=( "-q"
|
|
116
|
+
WGET_OPTIONS=( "-q" )
|
|
107
117
|
|
|
108
118
|
# Set by set_active_beplus
|
|
109
119
|
g_active_beplus=
|
|
@@ -142,6 +152,21 @@ else
|
|
|
142
152
|
readonly SGR_CYAN=
|
|
143
153
|
fi
|
|
144
154
|
|
|
155
|
+
# A download that fails in a way the next attempt may not is tried again (see fetch), up to
|
|
156
|
+
# BE_DOWNLOAD_ATTEMPTS attempts in all: GitHub, which hosts the beplus CLI releases, answers 504
|
|
157
|
+
# now and then, most often to Actions runners. The first retry waits BE_DOWNLOAD_RETRY_DELAY
|
|
158
|
+
# seconds, and each later one twice as long as the one before, so the defaults wait 5, 10, 20 and
|
|
159
|
+
# 40 seconds. BE_DOWNLOAD_ATTEMPTS=1 turns retrying off.
|
|
160
|
+
|
|
161
|
+
BE_DOWNLOAD_ATTEMPTS="${BE_DOWNLOAD_ATTEMPTS:-5}"
|
|
162
|
+
BE_DOWNLOAD_RETRY_DELAY="${BE_DOWNLOAD_RETRY_DELAY:-5}"
|
|
163
|
+
# No leading zero: arithmetic would read 08 as a bad octal number.
|
|
164
|
+
[[ "${BE_DOWNLOAD_ATTEMPTS}" =~ ^[1-9][0-9]*$ ]] \
|
|
165
|
+
|| abort "BE_DOWNLOAD_ATTEMPTS is '${BE_DOWNLOAD_ATTEMPTS}', not a whole number of 1 or more (1 turns retrying off)"
|
|
166
|
+
[[ "${BE_DOWNLOAD_RETRY_DELAY}" =~ ^(0|[1-9][0-9]*)$ ]] \
|
|
167
|
+
|| abort "BE_DOWNLOAD_RETRY_DELAY is '${BE_DOWNLOAD_RETRY_DELAY}', not a whole number of seconds"
|
|
168
|
+
readonly BE_DOWNLOAD_ATTEMPTS BE_DOWNLOAD_RETRY_DELAY
|
|
169
|
+
|
|
145
170
|
#
|
|
146
171
|
# set_arch <arch> to override $(uname -a)
|
|
147
172
|
#
|
|
@@ -629,7 +654,7 @@ install() {
|
|
|
629
654
|
# sha256sum writes it; v1.0.5 lists `<name>: <hash>`.
|
|
630
655
|
local sums_url="${url%/*}/SHA256SUMS"
|
|
631
656
|
local sums
|
|
632
|
-
sums="$(
|
|
657
|
+
sums="$(fetch text "${sums_url}")" || abort "failed to download SHA256SUMS for $version (${sums_url})"
|
|
633
658
|
local checksum="$(echo "${sums}" | awk -v name="${url##*/}" '$2 == name { print $1; exit } $1 == name ":" { print $2; exit }')"
|
|
634
659
|
[[ -n "${checksum}" ]] || abort "no checksum for ${url##*/} in ${sums_url}"
|
|
635
660
|
|
|
@@ -641,7 +666,7 @@ install() {
|
|
|
641
666
|
|
|
642
667
|
log fetch "$url"
|
|
643
668
|
local archive="${dir}/${url##*/}"
|
|
644
|
-
if !
|
|
669
|
+
if ! fetch file "${url}" "${archive}"; then
|
|
645
670
|
rm -f "${archive}"
|
|
646
671
|
abort "failed to download archive for $version"
|
|
647
672
|
fi
|
|
@@ -675,19 +700,158 @@ set_quiet() {
|
|
|
675
700
|
|
|
676
701
|
#
|
|
677
702
|
# Synopsis: do_get [option...] url
|
|
678
|
-
# Call curl or wget with combination of global and passed options.
|
|
703
|
+
# Call curl or wget with combination of global and passed options, once: doctor shows the command.
|
|
704
|
+
# Downloads go through fetch, which tries again.
|
|
679
705
|
#
|
|
680
706
|
|
|
681
707
|
function do_get() {
|
|
682
708
|
if command -v curl &> /dev/null; then
|
|
683
709
|
curl "${CURL_OPTIONS[@]}" "$@"
|
|
684
710
|
elif command -v wget &> /dev/null; then
|
|
685
|
-
wget "${WGET_OPTIONS[@]}" "$@"
|
|
711
|
+
wget "${WGET_OPTIONS[@]}" -O- "$@"
|
|
712
|
+
else
|
|
713
|
+
abort "curl or wget command required"
|
|
714
|
+
fi
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
#
|
|
718
|
+
# Synopsis: fetch head|text|file url [file]
|
|
719
|
+
# Fetch <url> with curl, or with wget where there is no curl:
|
|
720
|
+
# head ask for the headers only, to check that it is there
|
|
721
|
+
# text output the body, such as a release index or SHA256SUMS
|
|
722
|
+
# file save the body as <file>, with a progress bar where be shows one
|
|
723
|
+
# An attempt that fails in a way the next one may not (is_transient_failure) is tried again, up
|
|
724
|
+
# to BE_DOWNLOAD_ATTEMPTS attempts in all, the waits doubling from BE_DOWNLOAD_RETRY_DELAY
|
|
725
|
+
# seconds. Returns the status of the last attempt.
|
|
726
|
+
#
|
|
727
|
+
|
|
728
|
+
function fetch() {
|
|
729
|
+
local mode="$1"
|
|
730
|
+
local url="$2"
|
|
731
|
+
local body="/dev/null"
|
|
732
|
+
if [[ "${mode}" == "file" ]]; then
|
|
733
|
+
body="$3"
|
|
734
|
+
elif [[ "${mode}" == "text" ]]; then
|
|
735
|
+
# Output only once an attempt succeeds, so that a failed one leaves no part of a body behind.
|
|
736
|
+
body="$(mktemp)" || return 1
|
|
737
|
+
fi
|
|
738
|
+
|
|
739
|
+
local attempt=1
|
|
740
|
+
local delay="${BE_DOWNLOAD_RETRY_DELAY}"
|
|
741
|
+
local code
|
|
742
|
+
local status
|
|
743
|
+
while true; do
|
|
744
|
+
# Start the body afresh: curl leaves the file as it was when an attempt receives nothing.
|
|
745
|
+
[[ "${body}" != "/dev/null" ]] && : > "${body}"
|
|
746
|
+
code="$(fetch_attempt "${mode}" "${url}" "${body}")"
|
|
747
|
+
status=$?
|
|
748
|
+
[[ "${status}" -eq 0 ]] && break
|
|
749
|
+
is_transient_failure "${status}" "${code}" || break
|
|
750
|
+
if [[ "${attempt}" -ge "${BE_DOWNLOAD_ATTEMPTS}" ]]; then
|
|
751
|
+
if [[ "${attempt}" -gt 1 ]]; then
|
|
752
|
+
warn_log "gave up" "${url} $(display_fetch_failure "${status}" "${code}") on attempt ${attempt} of ${BE_DOWNLOAD_ATTEMPTS}"
|
|
753
|
+
fi
|
|
754
|
+
break
|
|
755
|
+
fi
|
|
756
|
+
local unit="seconds"
|
|
757
|
+
[[ "${delay}" -eq 1 ]] && unit="second"
|
|
758
|
+
warn_log "retrying" "${url} $(display_fetch_failure "${status}" "${code}"); attempt $(( attempt + 1 )) of ${BE_DOWNLOAD_ATTEMPTS} in ${delay} ${unit}"
|
|
759
|
+
sleep "${delay}"
|
|
760
|
+
attempt=$(( attempt + 1 ))
|
|
761
|
+
delay=$(( delay * 2 ))
|
|
762
|
+
done
|
|
763
|
+
|
|
764
|
+
if [[ "${mode}" == "text" ]]; then
|
|
765
|
+
[[ "${status}" -eq 0 ]] && cat "${body}"
|
|
766
|
+
rm -f "${body}"
|
|
767
|
+
fi
|
|
768
|
+
return "${status}"
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
#
|
|
772
|
+
# Synopsis: fetch_attempt head|text|file url body
|
|
773
|
+
# One request for <url>, as fetch describes, with the body written into the file <body>. Outputs
|
|
774
|
+
# the HTTP status of the last response (000 when none came) and returns curl's or wget's status.
|
|
775
|
+
#
|
|
776
|
+
|
|
777
|
+
function fetch_attempt() {
|
|
778
|
+
local mode="$1"
|
|
779
|
+
local url="$2"
|
|
780
|
+
local body="$3"
|
|
781
|
+
if command -v curl &> /dev/null; then
|
|
782
|
+
local options=( "${CURL_OPTIONS[@]}" )
|
|
783
|
+
case "${mode}" in
|
|
784
|
+
head) options+=( "--silent" "--head" ) ;;
|
|
785
|
+
# --silent to suppress progress et al
|
|
786
|
+
text) options=( "--silent" "--compressed" "${options[@]}" ) ;;
|
|
787
|
+
esac
|
|
788
|
+
curl "${options[@]}" --output "${body}" --write-out '%{http_code}' "${url}"
|
|
789
|
+
elif command -v wget &> /dev/null; then
|
|
790
|
+
# One try: fetch does the trying again (GNU wget would retry a 504 twenty times itself, but
|
|
791
|
+
# not a 503). --server-response prints each response's status line on stderr.
|
|
792
|
+
local options=( "${WGET_OPTIONS[@]}" "--tries=1" "--server-response" )
|
|
793
|
+
if [[ "${mode}" == "head" ]]; then
|
|
794
|
+
options+=( "--spider" )
|
|
795
|
+
else
|
|
796
|
+
options+=( "-O" "${body}" )
|
|
797
|
+
fi
|
|
798
|
+
local response
|
|
799
|
+
response="$(wget "${options[@]}" "${url}" 2>&1)"
|
|
800
|
+
local status=$?
|
|
801
|
+
echo "${response}" | awk '$1 ~ /^HTTP\// { code = $2 } END { print (code == "" ? "000" : code) }'
|
|
802
|
+
return "${status}"
|
|
686
803
|
else
|
|
687
804
|
abort "curl or wget command required"
|
|
688
805
|
fi
|
|
689
806
|
}
|
|
690
807
|
|
|
808
|
+
#
|
|
809
|
+
# Synopsis: is_transient_failure status http-code
|
|
810
|
+
# Whether an attempt that failed with curl's or wget's <status>, after the server answered
|
|
811
|
+
# <http-code> (000 for no answer), may succeed when made again: the server answered 408, 429, 500,
|
|
812
|
+
# 502, 503 or 504, or it gave no full answer, because its name did not resolve, it could not be
|
|
813
|
+
# reached, the connection dropped or it timed out. Any other answer, such as a 404, is final, and
|
|
814
|
+
# so is a failure of be's own, such as a file it cannot write.
|
|
815
|
+
#
|
|
816
|
+
|
|
817
|
+
function is_transient_failure() {
|
|
818
|
+
case "$2" in
|
|
819
|
+
408|429|500|502|503|504) return 0 ;;
|
|
820
|
+
[45][0-9][0-9]) return 1 ;;
|
|
821
|
+
esac
|
|
822
|
+
if command -v curl &> /dev/null; then
|
|
823
|
+
# https://curl.se/libcurl/c/libcurl-errors.html: the proxy or host did not resolve (5, 6), no
|
|
824
|
+
# connection (7), an HTTP/2 or HTTP/3 error (16, 92, 95), a partial body (18), a timeout
|
|
825
|
+
# (28), a failed TLS handshake (35), an empty reply (52), sending or receiving failed (55, 56).
|
|
826
|
+
case "$1" in
|
|
827
|
+
5|6|7|16|18|28|35|52|55|56|92|95) return 0 ;;
|
|
828
|
+
esac
|
|
829
|
+
else
|
|
830
|
+
# GNU wget's "network failure". BusyBox wget exits 1 for any failure, so there only what the
|
|
831
|
+
# server answered is tried again.
|
|
832
|
+
[[ "$1" -eq 4 ]] && return 0
|
|
833
|
+
fi
|
|
834
|
+
return 1
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
#
|
|
838
|
+
# Synopsis: display_fetch_failure status http-code
|
|
839
|
+
# How a fetch attempt failed, for a log message.
|
|
840
|
+
#
|
|
841
|
+
|
|
842
|
+
function display_fetch_failure() {
|
|
843
|
+
case "$2" in
|
|
844
|
+
[45][0-9][0-9]) echo "answered HTTP $2" ;;
|
|
845
|
+
*)
|
|
846
|
+
if command -v curl &> /dev/null; then
|
|
847
|
+
echo "failed (curl exit $1)"
|
|
848
|
+
else
|
|
849
|
+
echo "failed (wget exit $1)"
|
|
850
|
+
fi
|
|
851
|
+
;;
|
|
852
|
+
esac
|
|
853
|
+
}
|
|
854
|
+
|
|
691
855
|
#
|
|
692
856
|
# Synopsis: display_sha256 file
|
|
693
857
|
# sha256sum where it exists, otherwise shasum, which macOS has. Both hash stdin rather than a
|
|
@@ -705,23 +869,6 @@ function display_sha256() {
|
|
|
705
869
|
echo "${line%% *}"
|
|
706
870
|
}
|
|
707
871
|
|
|
708
|
-
#
|
|
709
|
-
# Synopsis: do_get_index [option...] url
|
|
710
|
-
# Call curl or wget with combination of global and passed options,
|
|
711
|
-
# with options tweaked to be more suitable for getting index.
|
|
712
|
-
#
|
|
713
|
-
|
|
714
|
-
function do_get_index() {
|
|
715
|
-
if command -v curl &> /dev/null; then
|
|
716
|
-
# --silent to suppress progress et al
|
|
717
|
-
curl --silent --compressed "${CURL_OPTIONS[@]}" "$@"
|
|
718
|
-
elif command -v wget &> /dev/null; then
|
|
719
|
-
wget "${WGET_OPTIONS[@]}" "$@"
|
|
720
|
-
else
|
|
721
|
-
abort "curl or wget command required"
|
|
722
|
-
fi
|
|
723
|
-
}
|
|
724
|
-
|
|
725
872
|
#
|
|
726
873
|
# Synopsis: remove_versions version ...
|
|
727
874
|
#
|
|
@@ -819,13 +966,8 @@ function exec_with_version() {
|
|
|
819
966
|
#
|
|
820
967
|
|
|
821
968
|
function is_ok() {
|
|
822
|
-
#
|
|
823
|
-
|
|
824
|
-
if command -v curl &> /dev/null; then
|
|
825
|
-
do_get --silent --head "$1" > /dev/null || return 1
|
|
826
|
-
else
|
|
827
|
-
do_get --spider "$1" > /dev/null || return 1
|
|
828
|
-
fi
|
|
969
|
+
# Both curl and wget follow redirects, which GitHub answers release downloads with.
|
|
970
|
+
fetch head "$1"
|
|
829
971
|
}
|
|
830
972
|
|
|
831
973
|
#
|
|
@@ -1044,7 +1186,7 @@ function get_latest_resolved_version() {
|
|
|
1044
1186
|
display_remote_index() {
|
|
1045
1187
|
local index_url="${g_mirror_url}/index.tab"
|
|
1046
1188
|
# tail to remove header line
|
|
1047
|
-
|
|
1189
|
+
fetch text "${index_url}" | tail -n +2 | cut -f 1,3,10
|
|
1048
1190
|
if [[ "${PIPESTATUS[0]}" -ne 0 ]]; then
|
|
1049
1191
|
# Reminder: abort will only exit subshell, but consistent error display
|
|
1050
1192
|
abort "failed to download version index (${index_url})"
|
|
@@ -1132,7 +1274,7 @@ function display_remote_versions() {
|
|
|
1132
1274
|
else [0, 0, 0, 0, 0, []]
|
|
1133
1275
|
end;'
|
|
1134
1276
|
|
|
1135
|
-
|
|
1277
|
+
fetch text "${index_url}" \
|
|
1136
1278
|
| jq -r "${jq_semver_key} [ ${jq_release_filter} | .name ] | sort_by(semver_key) | reverse | .[]" \
|
|
1137
1279
|
| n_grep -E "${match}" \
|
|
1138
1280
|
| awk "NR<=${match_count}" \
|
|
@@ -1237,6 +1379,7 @@ function show_diagnostics() {
|
|
|
1237
1379
|
echo "install destination: ${BE_PREFIX}"
|
|
1238
1380
|
[[ -n "${BE_PREFIX}" ]] && echo "PATH: ${PATH}"
|
|
1239
1381
|
echo "ls-remote max matches: ${BE_MAX_REMOTE_MATCHES}"
|
|
1382
|
+
echo "download attempts: ${BE_DOWNLOAD_ATTEMPTS}, the first retry after ${BE_DOWNLOAD_RETRY_DELAY} seconds"
|
|
1240
1383
|
|
|
1241
1384
|
printf "\nProxy\n"
|
|
1242
1385
|
# disable "var is referenced but not assigned": https://github.com/koalaman/shellcheck/wiki/SC2154
|
package/package.json
CHANGED