Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/scripts/lib/hesabix_mirrors.sh

504 lines
17 KiB
Shell

#!/usr/bin/env bash
# Interactive / status UI for PyPI and Flutter package mirrors.
HESABIX_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=hesabix_deploy_env.sh
source "${HESABIX_LIB_DIR}/hesabix_deploy_env.sh"
# shellcheck source=../mirror_config.sh
source "${HESABIX_LIB_DIR}/../mirror_config.sh"
declare -A HESABIX_MIRROR_HTTP_CODE=()
hesabix_mirrors_norm_url() {
local url="${1:-}"
url="${url%"${url##*[![:space:]]}"}"
url="${url#"${url%%[![:space:]]*}"}"
printf '%s' "${url%/}"
}
hesabix_mirrors_urls_equal() {
local a b
a="$(hesabix_mirrors_norm_url "${1:-}")"
b="$(hesabix_mirrors_norm_url "${2:-}")"
[[ -n "$a" && "$a" == "$b" ]]
}
hesabix_mirrors_format_status() {
local code="${1:-000}"
case "$code" in
000|"") printf '%s' "unreachable" ;;
2*) printf '%s' "ok" ;;
301|302|303|307|308) printf '%s' "ok" ;;
403) printf '%s' "blocked (403)" ;;
404) printf '%s' "not found (404)" ;;
429) printf '%s' "quota exceeded (429)" ;;
5*) printf '%s' "server error (${code})" ;;
*) printf '%s' "failed (${code})" ;;
esac
}
hesabix_mirrors_probe_urls() {
local -a urls=()
local url i dir code
HESABIX_MIRROR_HTTP_CODE=()
for url in "$@"; do
url="$(hesabix_mirrors_norm_url "$url")"
[[ -n "$url" ]] || continue
urls+=("$url")
done
[[ ${#urls[@]} -gt 0 ]] || return 0
dir="$(mktemp -d)"
for i in "${!urls[@]}"; do
url="${urls[$i]}"
(
code="$(curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 5 --max-time 10 -L --max-redirs 4 "${url}/" 2>/dev/null || true)"
printf '%s' "${code:-000}" > "${dir}/${i}"
) &
done
wait
for i in "${!urls[@]}"; do
code="$(cat "${dir}/${i}" 2>/dev/null || true)"
HESABIX_MIRROR_HTTP_CODE["${urls[$i]}"]="${code:-000}"
done
rm -rf "${dir}"
}
# id|label|url
hesabix_mirrors_pip_catalog() {
printf '%s\n' \
"hesabix|Hesabix (Iran)|${HESABIX_PIP_INDEX_URL}" \
"official|Official PyPI|https://pypi.org/simple" \
"tuna|Tsinghua (China)|https://pypi.tuna.tsinghua.edu.cn/simple" \
"aliyun|Aliyun (China)|https://mirrors.aliyun.com/pypi/simple" \
"devneeds|Devneeds|https://pypi.devneeds.ir/simple"
}
hesabix_mirrors_pub_catalog() {
printf '%s\n' \
"hesabix|Hesabix (Iran)|${HESABIX_PUB_HOSTED_URL}" \
"pub_azs|pub-azs.ir (Iran)|https://pub-azs.ir" \
"flutter_io_cn|China (flutter-io.cn)|https://pub.flutter-io.cn" \
"tuna|Tsinghua (China)|https://mirrors.tuna.tsinghua.edu.cn/dart-pub" \
"sjtu|SJTU (China)|https://mirror.sjtu.edu.cn/dart-pub" \
"official|Official pub.dev|https://pub.dev" \
"devneeds|Devneeds|https://dart.devneeds.ir"
}
hesabix_mirrors_storage_catalog() {
printf '%s\n' \
"hesabix|Hesabix (Iran)|${HESABIX_FLUTTER_STORAGE_BASE_URL}" \
"pub_azs|pub-azs.ir (Iran)|https://pub-azs.ir" \
"flutter_io_cn|China (storage.flutter-io.cn)|https://storage.flutter-io.cn" \
"tuna|Tsinghua (China)|https://mirrors.tuna.tsinghua.edu.cn/flutter" \
"sjtu|SJTU (China)|https://mirror.sjtu.edu.cn" \
"official|Official Google|https://storage.googleapis.com" \
"devneeds|Devneeds|https://flutter.devneeds.ir"
}
hesabix_mirrors_catalog_has_url() {
local want="$1" line id label url
while IFS= read -r line; do
[[ -n "$line" ]] || continue
IFS='|' read -r id label url <<<"$line"
if hesabix_mirrors_urls_equal "$url" "$want"; then
return 0
fi
done
return 1
}
# Prints numbered menu rows: n|id|label|url|selected(0/1)
hesabix_mirrors_build_menu() {
local catalog_fn="$1"
local current_url="$2"
local n=1
local line id label url selected
local found_current=0
current_url="$(hesabix_mirrors_norm_url "$current_url")"
if [[ -n "$current_url" ]] && ! "$catalog_fn" | hesabix_mirrors_catalog_has_url "$current_url"; then
printf '%s\n' "${n}|custom|Current custom|${current_url}|1"
found_current=1
n=$((n + 1))
fi
while IFS= read -r line; do
[[ -n "$line" ]] || continue
IFS='|' read -r id label url <<<"$line"
url="$(hesabix_mirrors_norm_url "$url")"
selected=0
if [[ "$found_current" -eq 0 ]] && hesabix_mirrors_urls_equal "$url" "$current_url"; then
selected=1
found_current=1
fi
printf '%s\n' "${n}|${id}|${label}|${url}|${selected}"
n=$((n + 1))
done < <("$catalog_fn")
printf '%s\n' "${n}|custom_input|Custom URL||0"
}
hesabix_mirrors_print_menu() {
local title="$1"
local current_url="$2"
shift 2
local row n id label url selected mark status code
echo
echo "=== ${title} ==="
if [[ -n "$current_url" ]]; then
echo "Current: ${current_url}"
else
echo "Current: (not set)"
fi
echo
while IFS= read -r row; do
[[ -n "$row" ]] || continue
IFS='|' read -r n id label url selected <<<"$row"
mark=" "
[[ "$selected" == "1" ]] && mark="*"
if [[ "$id" == "custom_input" ]]; then
printf ' %s [%s] %s\n' "$mark" "$n" "$label"
continue
fi
code="${HESABIX_MIRROR_HTTP_CODE[$(hesabix_mirrors_norm_url "$url")]:-000}"
status="$(hesabix_mirrors_format_status "$code")"
printf ' %s [%s] %s\n %s — %s\n' "$mark" "$n" "$label" "$url" "$status"
done
echo " * = currently saved"
}
hesabix_mirrors_collect_probe_urls() {
local line id label url
while IFS= read -r line; do
IFS='|' read -r id label url <<<"$line"
[[ -n "$url" ]] && printf '%s\n' "$(hesabix_mirrors_norm_url "$url")"
done
}
hesabix_mirrors_flutter_preset_for_urls() {
local pub storage
pub="$(hesabix_mirrors_norm_url "${1:-}")"
storage="$(hesabix_mirrors_norm_url "${2:-}")"
if hesabix_mirrors_urls_equal "$pub" "${HESABIX_PUB_HOSTED_URL}" \
&& hesabix_mirrors_urls_equal "$storage" "${HESABIX_FLUTTER_STORAGE_BASE_URL}"; then
printf '%s' "hesabix"; return 0
fi
if hesabix_mirrors_urls_equal "$pub" "https://pub-azs.ir" \
&& hesabix_mirrors_urls_equal "$storage" "https://pub-azs.ir"; then
printf '%s' "pub_azs"; return 0
fi
if hesabix_mirrors_urls_equal "$pub" "https://pub.flutter-io.cn" \
&& hesabix_mirrors_urls_equal "$storage" "https://storage.flutter-io.cn"; then
printf '%s' "flutter_io_cn"; return 0
fi
if hesabix_mirrors_urls_equal "$pub" "https://mirrors.tuna.tsinghua.edu.cn/dart-pub" \
&& hesabix_mirrors_urls_equal "$storage" "https://mirrors.tuna.tsinghua.edu.cn/flutter"; then
printf '%s' "tuna"; return 0
fi
if hesabix_mirrors_urls_equal "$pub" "https://mirror.sjtu.edu.cn/dart-pub" \
&& hesabix_mirrors_urls_equal "$storage" "https://mirror.sjtu.edu.cn"; then
printf '%s' "sjtu"; return 0
fi
if hesabix_mirrors_urls_equal "$pub" "https://pub.dev" \
&& hesabix_mirrors_urls_equal "$storage" "https://storage.googleapis.com"; then
printf '%s' "official"; return 0
fi
if hesabix_mirrors_urls_equal "$pub" "https://dart.devneeds.ir" \
&& hesabix_mirrors_urls_equal "$storage" "https://flutter.devneeds.ir"; then
printf '%s' "custom"; return 0
fi
printf '%s' "custom"
}
hesabix_mirrors_pip_preset_for_url() {
local url
url="$(hesabix_mirrors_norm_url "${1:-}")"
case "$url" in
"${HESABIX_PIP_INDEX_URL%/}"|"${HESABIX_PIP_INDEX_URL}") printf '%s' "hesabix" ;;
"https://pypi.org/simple") printf '%s' "official" ;;
"https://pypi.tuna.tsinghua.edu.cn/simple") printf '%s' "tuna" ;;
"https://mirrors.aliyun.com/pypi/simple") printf '%s' "aliyun" ;;
*) printf '%s' "custom" ;;
esac
}
hesabix_mirrors_pick_from_menu() {
local prompt="$1"
local menu="$2"
local current_url="$3"
local choice row n id label url selected
local max=0
while IFS= read -r row; do
IFS='|' read -r n id label url selected <<<"$row"
[[ "$n" =~ ^[0-9]+$ ]] || continue
(( n > max )) && max=$n
done <<<"$menu"
while true; do
read -rp "${prompt} [Enter = keep current]: " choice
if [[ -z "$choice" ]]; then
printf '%s' "$(hesabix_mirrors_norm_url "$current_url")"
return 0
fi
if [[ ! "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > max )); then
echo "Invalid number. Choose between 1 and ${max}." >&2
continue
fi
while IFS= read -r row; do
IFS='|' read -r n id label url selected <<<"$row"
[[ "$n" == "$choice" ]] || continue
if [[ "$id" == "custom_input" ]]; then
local custom=""
read -rp "Full mirror URL: " custom
custom="$(hesabix_mirrors_norm_url "$custom")"
if [[ -z "$custom" ]]; then
echo "Empty URL; keeping current selection." >&2
printf '%s' "$(hesabix_mirrors_norm_url "$current_url")"
return 0
fi
printf '%s' "$custom"
return 0
fi
printf '%s' "$(hesabix_mirrors_norm_url "$url")"
return 0
done <<<"$menu"
done
}
hesabix_mirrors_resolve_choice() {
local raw="$1"
local kind="$2"
local line id label url
raw="$(hesabix_mirrors_norm_url "$raw")"
[[ -n "$raw" ]] || return 1
if [[ "$raw" == http://* || "$raw" == https://* ]]; then
printf '%s' "$raw"
return 0
fi
raw="${raw,,}"
raw="${raw//-/_}"
local catalog_fn=""
case "$kind" in
pip) catalog_fn=hesabix_mirrors_pip_catalog ;;
pub) catalog_fn=hesabix_mirrors_pub_catalog ;;
storage) catalog_fn=hesabix_mirrors_storage_catalog ;;
*) return 1 ;;
esac
while IFS= read -r line; do
IFS='|' read -r id label url <<<"$line"
id="${id,,}"
if [[ "$id" == "$raw" || "$id" == "${raw//_/-}" ]]; then
printf '%s' "$(hesabix_mirrors_norm_url "$url")"
return 0
fi
done < <("$catalog_fn")
return 1
}
hesabix_mirrors_load() {
hesabix_load_deploy_env || return 1
hesabix_apply_pip_mirror_env >/dev/null 2>&1 || true
hesabix_apply_flutter_mirror_env >/dev/null 2>&1 || true
}
hesabix_mirrors_show() {
hesabix_mirrors_load || return 1
local pip_url pub_url storage_url
pip_url="$(hesabix_mirrors_norm_url "${PIP_INDEX_URL:-}")"
pub_url="$(hesabix_mirrors_norm_url "${PUB_HOSTED_URL:-}")"
storage_url="$(hesabix_mirrors_norm_url "${FLUTTER_STORAGE_BASE_URL:-}")"
echo "Checking mirror availability..."
local -a probe=()
mapfile -t probe < <(
{
hesabix_mirrors_pip_catalog
hesabix_mirrors_pub_catalog
hesabix_mirrors_storage_catalog
} | hesabix_mirrors_collect_probe_urls
[[ -n "$pip_url" ]] && printf '%s\n' "$pip_url"
[[ -n "$pub_url" ]] && printf '%s\n' "$pub_url"
[[ -n "$storage_url" ]] && printf '%s\n' "$storage_url"
)
local -A uniq=()
local -a unique_urls=()
local u
for u in "${probe[@]}"; do
u="$(hesabix_mirrors_norm_url "$u")"
[[ -n "$u" && -z "${uniq[$u]:-}" ]] || continue
uniq[$u]=1
unique_urls+=("$u")
done
hesabix_mirrors_probe_urls "${unique_urls[@]}"
local pip_menu pub_menu storage_menu
pip_menu="$(hesabix_mirrors_build_menu hesabix_mirrors_pip_catalog "$pip_url")"
pub_menu="$(hesabix_mirrors_build_menu hesabix_mirrors_pub_catalog "$pub_url")"
storage_menu="$(hesabix_mirrors_build_menu hesabix_mirrors_storage_catalog "$storage_url")"
echo
echo "Hesabix mirror status"
echo "Saved in: ${HESABIX_DEPLOY_ENV}"
echo "Python preset: ${PIP_MIRROR:-—}"
echo "Flutter preset: ${FLUTTER_MIRROR:-—}"
hesabix_mirrors_print_menu "Python (pip / PyPI)" "$pip_url" <<<"$pip_menu"
hesabix_mirrors_print_menu "Flutter pub (Dart packages)" "$pub_url" <<<"$pub_menu"
hesabix_mirrors_print_menu "Flutter storage (engine / SDK artifacts)" "$storage_url" <<<"$storage_menu"
}
hesabix_mirrors_apply_selection() {
local pip_url pub_url storage_url
pip_url="$(hesabix_mirrors_norm_url "$1")"
pub_url="$(hesabix_mirrors_norm_url "$2")"
storage_url="$(hesabix_mirrors_norm_url "$3")"
[[ -n "$pip_url" && -n "$pub_url" && -n "$storage_url" ]] || {
echo "Python, Flutter pub, and Flutter storage URLs are all required." >&2
return 1
}
export PIP_INDEX_URL
PIP_INDEX_URL="$(hesabix_normalize_pip_index_url "$pip_url")"
export PIP_MIRROR
PIP_MIRROR="$(hesabix_mirrors_pip_preset_for_url "$PIP_INDEX_URL")"
export PUB_HOSTED_URL="$pub_url"
export FLUTTER_STORAGE_BASE_URL="$storage_url"
export FLUTTER_MIRROR
FLUTTER_MIRROR="$(hesabix_mirrors_flutter_preset_for_urls "$pub_url" "$storage_url")"
unset PIP_EXTRA_INDEX_URL
hesabix_persist_mirror_to_deploy_env "${HESABIX_DEPLOY_ENV}"
hesabix_configure_pip_mirror >/dev/null 2>&1 || true
echo
echo "Mirrors saved:"
echo " Python: ${PIP_MIRROR} — ${PIP_INDEX_URL}"
echo " Flutter pub: ${PUB_HOSTED_URL}"
echo " Flutter storage: ${FLUTTER_STORAGE_BASE_URL}"
echo " Flutter preset: ${FLUTTER_MIRROR}"
}
hesabix_mirrors_log_current() {
echo "Active mirrors:"
echo " Python: ${PIP_INDEX_URL:-—}"
echo " Flutter pub: ${PUB_HOSTED_URL:-—}"
echo " Flutter storage: ${FLUTTER_STORAGE_BASE_URL:-—}"
}
hesabix_mirrors_prompt_before_update() {
if [[ "${HESABIX_SKIP_MIRROR_PROMPT:-0}" == "1" || "${HESABIX_NONINTERACTIVE:-0}" == "1" ]]; then
hesabix_mirrors_load || true
echo "Mirror prompt skipped (non-interactive)."
hesabix_mirrors_log_current
return 0
fi
if [[ ! -t 0 ]]; then
hesabix_mirrors_load || true
echo "stdin is not a TTY; using saved mirrors."
hesabix_mirrors_log_current
return 0
fi
echo
echo "Before updating, choose Python and Flutter mirrors."
echo "You can change them each time. Enter keeps the saved selection."
hesabix_mirrors_set_interactive
}
hesabix_mirrors_set_interactive() {
hesabix_mirrors_show || return 1
local pip_url pub_url storage_url
pip_url="$(hesabix_mirrors_norm_url "${PIP_INDEX_URL:-}")"
pub_url="$(hesabix_mirrors_norm_url "${PUB_HOSTED_URL:-}")"
storage_url="$(hesabix_mirrors_norm_url "${FLUTTER_STORAGE_BASE_URL:-}")"
local pip_menu pub_menu storage_menu
pip_menu="$(hesabix_mirrors_build_menu hesabix_mirrors_pip_catalog "$pip_url")"
pub_menu="$(hesabix_mirrors_build_menu hesabix_mirrors_pub_catalog "$pub_url")"
storage_menu="$(hesabix_mirrors_build_menu hesabix_mirrors_storage_catalog "$storage_url")"
echo
echo "Enter a number. Enter keeps the current selection."
local new_pip new_pub new_storage
new_pip="$(hesabix_mirrors_pick_from_menu "Python" "$pip_menu" "$pip_url")"
new_pub="$(hesabix_mirrors_pick_from_menu "Flutter pub" "$pub_menu" "$pub_url")"
new_storage="$(hesabix_mirrors_pick_from_menu "Flutter storage" "$storage_menu" "$storage_url")"
hesabix_mirrors_apply_selection "$new_pip" "$new_pub" "$new_storage"
}
hesabix_mirrors_set_from_args() {
hesabix_mirrors_load || return 1
local pip_url pub_url storage_url
pip_url="$(hesabix_mirrors_norm_url "${PIP_INDEX_URL:-}")"
pub_url="$(hesabix_mirrors_norm_url "${PUB_HOSTED_URL:-}")"
storage_url="$(hesabix_mirrors_norm_url "${FLUTTER_STORAGE_BASE_URL:-}")"
local arg val resolved
while [[ $# -gt 0 ]]; do
arg="$1"
case "$arg" in
--pip)
[[ $# -ge 2 ]] || { echo "--pip requires a value." >&2; return 1; }
val="$2"; shift 2
resolved="$(hesabix_mirrors_resolve_choice "$val" pip)" || {
echo "Unknown Python mirror: ${val}" >&2; return 1
}
pip_url="$resolved"
;;
--pub|--flutter-pub)
[[ $# -ge 2 ]] || { echo "${arg} requires a value." >&2; return 1; }
val="$2"; shift 2
resolved="$(hesabix_mirrors_resolve_choice "$val" pub)" || {
echo "Unknown Flutter pub mirror: ${val}" >&2; return 1
}
pub_url="$resolved"
;;
--storage|--flutter-storage)
[[ $# -ge 2 ]] || { echo "${arg} requires a value." >&2; return 1; }
val="$2"; shift 2
resolved="$(hesabix_mirrors_resolve_choice "$val" storage)" || {
echo "Unknown Flutter storage mirror: ${val}" >&2; return 1
}
storage_url="$resolved"
;;
--flutter)
[[ $# -ge 2 ]] || { echo "--flutter requires a value." >&2; return 1; }
val="$2"; shift 2
resolved="$(hesabix_mirrors_resolve_choice "$val" pub)" || {
echo "Unknown Flutter mirror: ${val}" >&2; return 1
}
pub_url="$resolved"
resolved="$(hesabix_mirrors_resolve_choice "$val" storage)" || {
echo "No Flutter storage URL defined for ${val}." >&2; return 1
}
storage_url="$resolved"
;;
*)
echo "Unknown option: ${arg}" >&2
echo "Usage: hesabix -mirrors set [--pip NAME|URL] [--pub NAME|URL] [--storage NAME|URL]" >&2
return 1
;;
esac
done
hesabix_mirrors_apply_selection "$pip_url" "$pub_url" "$storage_url"
}
hesabix_mirrors_command() {
local action="${1:-}"
shift || true
case "$action" in
""|set)
if [[ $# -gt 0 ]]; then
hesabix_mirrors_set_from_args "$@"
elif [[ -t 0 ]]; then
hesabix_mirrors_set_interactive
else
hesabix_mirrors_show
fi
;;
show|status)
hesabix_mirrors_show
;;
*)
echo "Invalid -mirrors action: ${action}. Use: show|set" >&2
return 1
;;
esac
}