feat: add npm timeout, mirror support, and interactive fallback

- Add --npm-registry param for custom npm mirrors
- Add --github-mirror param for GitHub API/download proxy
- npm install now has 120s timeout with interactive fallback menu
- On timeout/failure, prompt user to switch source or use mirror
- Update docs with mirror usage examples

via [HAPI](https://hapi.run)

Co-Authored-By: HAPI <[email protected]>
This commit is contained in:
i
2026-06-01 23:15:06 +08:00
co-authored by HAPI
parent 43c4fbe1e8
commit 8cb35598af
3 changed files with 283 additions and 13 deletions
+12
View File
@@ -51,6 +51,12 @@ sudo bash install.sh --force
# 仅安装/升级 CLI,跳过 Hub Docker 配置
sudo bash install.sh --skip-hub
# 指定国内 npm 镜像
sudo bash install.sh --npm-registry https://registry.npmmirror.com
# 指定 GitHub 镜像(国内环境推荐)
sudo bash install.sh --github-mirror https://ghproxy.com --source github-official
# 显示帮助
sudo bash install.sh --help
```
@@ -76,6 +82,12 @@ sudo bash install.sh --help
# 仅安装/升级 CLI
.\install.ps1 -SkipHub
# 指定国内 npm 镜像
.\install.ps1 -NpmRegistry https://registry.npmmirror.com
# 指定 GitHub 镜像(国内环境推荐)
.\install.ps1 -GithubMirror https://ghproxy.com -Source github-official
```
---
+119 -4
View File
@@ -33,6 +33,8 @@
.\install.ps1 -Force
.\install.ps1 -Upgrade
.\install.ps1 -SkipHub
.\install.ps1 -NpmRegistry https://registry.npmmirror.com
.\install.ps1 -GithubMirror https://ghproxy.com -Source github-official
#>
[CmdletBinding()]
@@ -42,7 +44,10 @@ param(
[switch]$Force,
[switch]$Upgrade,
[switch]$SkipHub
[switch]$SkipHub,
[string]$NpmRegistry = "",
[string]$GithubMirror = ""
)
$ErrorActionPreference = "Stop"
@@ -59,8 +64,10 @@ $SOURCE_FILE = "$INSTALL_DIR\.hapi-source"
$NODE_MIN_MAJOR = 22
$NPM_PACKAGE = "@twsxtd/hapi"
$NPM_REGISTRY = if ($NpmRegistry) { $NpmRegistry } else { "https://registry.npmjs.org" }
$GITHUB_OFFICIAL_REPO = "tiann/hapi"
$GITHUB_FORK_REPO = "arkylin/Hapi-Docker"
$GITHUB_MIRROR = $GithubMirror
# ─────────────────────────────────────────
# 输出函数
@@ -71,6 +78,17 @@ function Write-Ok { param($msg) Write-Host "[OK] $msg" -ForegroundColor Gree
function Write-Warn { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow; "[WARN] $msg" | Out-File $LOG_FILE -Append }
function Write-Err { param($msg) Write-Host "[ERR] $msg" -ForegroundColor Red; "[ERR] $msg" | Out-File $LOG_FILE -Append }
# ─────────────────────────────────────────
# GitHub 镜像 URL 替换
# ─────────────────────────────────────────
function Github-Url {
param([string]$url)
if ($script:GITHUB_MIRROR) {
return "$script:GITHUB_MIRROR/$url"
}
return $url
}
# ─────────────────────────────────────────
# 刷新环境变量 PATH
# ─────────────────────────────────────────
@@ -170,17 +188,23 @@ function Get-RemoteNpmVersion {
function Get-RemoteGithubVersion {
param([string]$repo)
$apiUrl = "https://api.github.com/repos/$repo/releases/latest"
$apiUrl = Github-Url -url "https://api.github.com/repos/$repo/releases/latest"
Write-Info "查询 GitHub Release: $repo ..."
if ($script:GITHUB_MIRROR) { Write-Info "使用镜像: $script:GITHUB_MIRROR" }
try {
$response = Invoke-RestMethod -Uri $apiUrl -TimeoutSec 10 -ErrorAction Stop
$ver = $response.tag_name -replace '^v',''
Write-Ok "GitHub Release 最新版本: v$ver"
return $ver
} catch {
if ($_.Exception.Response.StatusCode -eq 403) {
if ($_.Exception.Response -and $_.Exception.Response.StatusCode -eq 403) {
Write-Warn "GitHub API rate limit exceeded,请设置 GITHUB_TOKEN 或稍后重试"
} else {
Write-Warn "无法连接 GitHub API: $_"
if (-not $script:GITHUB_MIRROR) {
Write-Info "提示: 如果 GitHub 访问困难,可使用 -GithubMirror 参数指定镜像"
Write-Info " 例如: -GithubMirror https://ghproxy.com"
}
}
return $null
}
@@ -251,6 +275,8 @@ function Match-GithubAsset {
}
Write-Ok "匹配到 asset: $($bestAsset.name)"
# 应用镜像到下载 URL
$bestAsset.browser_download_url = Github-Url -url $bestAsset.browser_download_url
return $bestAsset
}
@@ -343,8 +369,31 @@ function Install-FromGithub {
# ─────────────────────────────────────────
function Install-FromNpm {
Write-Info "从 npm registry 安装 HAPI CLI..."
Write-Info "Registry: $script:NPM_REGISTRY"
npm install -g $script:NPM_PACKAGE --registry=https://registry.npmjs.org | Out-File $LOG_FILE -Append
# npm install 带 120 秒超时
$npmTimeout = 120
Write-Info "npm install 超时设置: ${npmTimeout}s"
$proc = Start-Process -FilePath "npm" -ArgumentList "install", "-g", $script:NPM_PACKAGE, "--registry=$script:NPM_REGISTRY" -PassThru -WindowStyle Hidden -RedirectStandardOutput $LOG_FILE -RedirectStandardError $LOG_FILE
$timedOut = $null
try {
$timedOut = -not $proc.WaitForExit($npmTimeout * 1000)
} catch {}
if ($timedOut) {
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
Write-Warn "npm install 超时 (${npmTimeout}秒),网络可能较慢"
Handle-NpmTimeout
return $?
}
if ($proc.ExitCode -ne 0) {
Write-Warn "npm install 失败 (退出码: $($proc.ExitCode))"
Handle-NpmTimeout
return $?
}
Update-EnvPath
$hapi = Get-Command "hapi" -ErrorAction SilentlyContinue
@@ -371,6 +420,71 @@ function Install-FromNpm {
return $true
}
# ─────────────────────────────────────────
# npm 超时/失败后的处理
# ─────────────────────────────────────────
function Handle-NpmTimeout {
Write-Host ""
Write-Warn "npm 安装遇到问题,可能的原因:"
Write-Host " 1. 网络较慢或连接不稳定"
Write-Host " 2. npm registry 被墙或访问受限"
Write-Host ""
Write-Info "请选择操作:"
Write-Host " 1) 切换到 GitHub Release 安装(无需 npm,推荐国内环境)"
Write-Host " 2) 使用国内 npm 镜像重试(如淘宝/中科大镜像)"
Write-Host " 3) 跳过 HAPI CLI 安装,继续其他步骤"
Write-Host " 4) 退出"
Write-Host ""
do {
$choice = Read-Host "请输入选项 [1-4]"
switch ($choice) {
"1" {
Write-Info "切换到 GitHub Release 官方来源..."
$script:SELECTED_SOURCE = "github-official"
$script:REMOTE_VERSION = Get-RemoteGithubVersion -repo $GITHUB_OFFICIAL_REPO
if ($script:REMOTE_VERSION) {
Install-FromGithub -repo $GITHUB_OFFICIAL_REPO
return $?
}
return $false
}
"2" {
Write-Host ""
Write-Info "可选的国内 npm 镜像:"
Write-Host " 1) 淘宝镜像 (registry.npmmirror.com)"
Write-Host " 2) 中科大镜像 (mirrors.ustc.edu.cn/npm)"
Write-Host " 3) 腾讯云镜像 (mirrors.cloud.tencent.com/npm)"
Write-Host " 4) 自定义"
Write-Host ""
$mirrorChoice = Read-Host "请选择镜像 [1-4]"
switch ($mirrorChoice) {
"1" { $script:NPM_REGISTRY = "https://registry.npmmirror.com" }
"2" { $script:NPM_REGISTRY = "https://mirrors.ustc.edu.cn/npm" }
"3" { $script:NPM_REGISTRY = "https://mirrors.cloud.tencent.com/npm" }
"4" {
$customRegistry = Read-Host "请输入 npm registry 地址"
$script:NPM_REGISTRY = $customRegistry
}
default { $script:NPM_REGISTRY = "https://registry.npmmirror.com" }
}
Write-Info "使用镜像: $script:NPM_REGISTRY"
Install-FromNpm
return $?
}
"3" {
Write-Warn "跳过 HAPI CLI 安装"
return $true
}
"4" {
Write-Err "安装中止"
return $false
}
default { Write-Warn "无效选项" }
}
} while ($true)
}
# ─────────────────────────────────────────
# 选择安装来源
# ─────────────────────────────────────────
@@ -765,6 +879,7 @@ function Print-Result {
Write-Host " 升级命令:"
Write-Host " .\install.ps1 -Upgrade"
Write-Host " .\install.ps1 -Source github-official -Upgrade"
Write-Host " .\install.ps1 -GithubMirror https://ghproxy.com -Upgrade"
Write-Host ""
Write-Host " 日志文件: $LOG_FILE"
Write-Host ""
+152 -9
View File
@@ -12,6 +12,8 @@
# sudo bash install.sh --force # 强制重新安装
# sudo bash install.sh --upgrade # 仅升级模式
# sudo bash install.sh --skip-hub # 跳过 Hub Docker 配置
# sudo bash install.sh --npm-registry https://registry.npmmirror.com
# sudo bash install.sh --github-mirror https://ghproxy.com
# sudo bash install.sh --help # 显示帮助
set -e
@@ -28,8 +30,10 @@ SOURCE_FILE="$INSTALL_DIR/.hapi-source"
NODE_MIN_VERSION=22
NPM_PACKAGE="@twsxtd/hapi"
NPM_REGISTRY="https://registry.npmjs.org"
GITHUB_OFFICIAL_REPO="tiann/hapi"
GITHUB_FORK_REPO="arkylin/Hapi-Docker"
GITHUB_MIRROR=""
# ─────────────────────────────────────────
# 颜色
@@ -50,6 +54,8 @@ SOURCE=""
FORCE=false
UPGRADE_ONLY=false
SKIP_HUB=false
NPM_REGISTRY="https://registry.npmjs.org"
GITHUB_MIRROR=""
# 运行时状态
DETECTED_PLATFORM=""
@@ -86,6 +92,8 @@ HAPI 一键安装脚本 (Linux)
--force 强制重新安装(即使版本相同)
--upgrade 仅升级模式(已是最新则退出)
--skip-hub 跳过 Hub Docker 配置(仅安装/升级 CLI)
--npm-registry URL 自定义 npm registry(如 https://registry.npmmirror.com)
--github-mirror URL 自定义 GitHub 镜像前缀(如 https://ghproxy.com)
--help, -h 显示此帮助
示例:
@@ -93,6 +101,8 @@ HAPI 一键安装脚本 (Linux)
sudo bash install.sh --source npm # 使用 npm 安装
sudo bash install.sh --source github-official # 使用官方 GitHub Release
sudo bash install.sh --force # 强制重装
sudo bash install.sh --npm-registry https://registry.npmmirror.com
sudo bash install.sh --github-mirror https://ghproxy.com --source github-official
EOF
}
@@ -122,6 +132,22 @@ parse_args() {
SKIP_HUB=true
shift
;;
--npm-registry)
if [[ -z "${2:-}" ]]; then
err "--npm-registry 需要参数值"
exit 1
fi
NPM_REGISTRY="$2"
shift 2
;;
--github-mirror)
if [[ -z "${2:-}" ]]; then
err "--github-mirror 需要参数值"
exit 1
fi
GITHUB_MIRROR="$2"
shift 2
;;
--help|-h)
show_help
exit 0
@@ -162,6 +188,20 @@ detect_arch() {
esac
}
# ─────────────────────────────────────────
# GitHub 镜像 URL 替换
# ─────────────────────────────────────────
github_url() {
local url="$1"
if [[ -n "$GITHUB_MIRROR" ]]; then
# 镜像前缀格式: https://ghproxy.com
# 将 https://github.com/xxx 替换为 https://ghproxy.com/https://github.com/xxx
echo "${GITHUB_MIRROR}/${url}"
else
echo "$url"
fi
}
# ─────────────────────────────────────────
# JSON 解析(优先 jq,回退 Python3)
# ─────────────────────────────────────────
@@ -274,14 +314,21 @@ get_remote_npm_version() {
get_remote_github_version() {
local repo="$1"
local api_url="https://api.github.com/repos/$repo/releases/latest"
local api_url=$(github_url "https://api.github.com/repos/$repo/releases/latest")
info "查询 GitHub Release: $repo ..."
[[ -n "$GITHUB_MIRROR" ]] && info "使用镜像: $GITHUB_MIRROR"
local response
response=$(curl -sL --connect-timeout 10 "$api_url" 2>/dev/null || true)
if [[ -z "$response" ]]; then
warn "无法连接 GitHub API,请检查网络或设置代理"
if [[ -z "$GITHUB_MIRROR" && -t 0 ]]; then
echo ""
info "提示: 如果 GitHub 访问困难,可使用 --github-mirror 参数指定镜像"
info " 例如: --github-mirror https://ghproxy.com"
fi
return 1
fi
@@ -429,15 +476,19 @@ install_from_github() {
asset_name=$(echo "$match_result" | head -1)
asset_url=$(echo "$match_result" | tail -1)
info "下载: $asset_url"
# 应用镜像
local download_url
download_url=$(github_url "$asset_url")
info "下载: $download_url"
[[ -n "$GITHUB_MIRROR" && "$download_url" != "$asset_url" ]] && info "(原始: $asset_url)"
# 创建目录
mkdir -p "$HAPI_BIN_DIR"
local download_path="$HAPI_BIN_DIR/$asset_name"
# 下载
if ! curl -fsSL --connect-timeout 30 --progress-bar "$asset_url" -o "$download_path"; then
err "下载失败: $asset_url"
if ! curl -fsSL --connect-timeout 30 --progress-bar "$download_url" -o "$download_path"; then
err "下载失败: $download_url"
return 1
fi
ok "下载完成: $asset_name"
@@ -492,7 +543,7 @@ EOF
# 验证安装
local installed_ver
installed_ver=$("$HAPI_BIN_DIR/hapi" --version 2>/dev/null | sed -E 's/^v?//' | head -1 || true)
installed_ver=$("$HAPI_BIN_DIR/hapi" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true)
if [[ -z "$installed_ver" ]]; then
warn "无法验证 HAPI CLI 版本,但文件已安装"
installed_ver="$REMOTE_VERSION"
@@ -511,13 +562,37 @@ EOF
# ─────────────────────────────────────────
install_from_npm() {
info "从 npm registry 安装 HAPI CLI..."
info "Registry: $NPM_REGISTRY"
if ! command -v npm &>/dev/null; then
err "npm 不可用,请先安装 Node.js"
return 1
fi
npm install -g "$NPM_PACKAGE" --registry=https://registry.npmjs.org >>"$LOG_FILE" 2>&1
# npm install 带 120 秒超时
local npm_timeout=120
info "npm install 超时设置: ${npm_timeout}s"
if command -v timeout &>/dev/null; then
timeout $npm_timeout npm install -g "$NPM_PACKAGE" --registry="$NPM_REGISTRY" >>"$LOG_FILE" 2>&1
local npm_exit=$?
if [[ $npm_exit -eq 124 ]]; then
warn "npm install 超时 (${npm_timeout}秒),网络可能较慢"
handle_npm_timeout
return $?
elif [[ $npm_exit -ne 0 ]]; then
warn "npm install 失败 (退出码: $npm_exit)"
handle_npm_timeout
return $?
fi
else
npm install -g "$NPM_PACKAGE" --registry="$NPM_REGISTRY" >>"$LOG_FILE" 2>&1
if [[ $? -ne 0 ]]; then
warn "npm install 失败"
handle_npm_timeout
return $?
fi
fi
if ! command -v hapi &>/dev/null; then
export PATH="/usr/local/bin:$PATH"
@@ -530,16 +605,83 @@ install_from_npm() {
local installed_ver
installed_ver=$(hapi --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
# 记录版本和来源
local hapi_path
hapi_path=$(command -v hapi 2>/dev/null || true)
echo "$installed_ver" > "$VERSION_FILE"
echo "npm" > "$SOURCE_FILE"
ok "HAPI CLI v$installed_ver 安装成功 (来源: npm, 路径: $hapi_path)"
return 0
}
# ─────────────────────────────────────────
# npm 超时/失败后的处理
# ─────────────────────────────────────────
handle_npm_timeout() {
if [[ ! -t 0 ]]; then
err "非交互式环境,无法选择替代方案。建议使用 GitHub Release 来源: --source github-official"
return 1
fi
echo ""
warn "npm 安装遇到问题,可能的原因:"
echo " 1. 网络较慢或连接不稳定"
echo " 2. npm registry 被墙或访问受限"
echo ""
info "请选择操作:"
echo " 1) 切换到 GitHub Release 安装(无需 npm,推荐国内环境)"
echo " 2) 使用国内 npm 镜像重试(如淘宝/中科大镜像)"
echo " 3) 跳过 HAPI CLI 安装,继续其他步骤"
echo " 4) 退出"
echo ""
while true; do
read -rp "请输入选项 [1-4]: " choice
case "$choice" in
1)
info "切换到 GitHub Release 官方来源..."
SELECTED_SOURCE="github-official"
# 重新获取远程版本
if get_remote_github_version "$GITHUB_OFFICIAL_REPO"; then
install_from_github "$GITHUB_OFFICIAL_REPO"
return $?
fi
return 1
;;
2)
echo ""
info "可选的国内 npm 镜像:"
echo " 1) 淘宝镜像 (registry.npmmirror.com)"
echo " 2) 中科大镜像 (mirrors.ustc.edu.cn/npm)"
echo " 3) 腾讯云镜像 (mirrors.cloud.tencent.com/npm)"
echo " 4) 自定义"
echo ""
read -rp "请选择镜像 [1-4]: " mirror_choice
case "$mirror_choice" in
1) NPM_REGISTRY="https://registry.npmmirror.com" ;;
2) NPM_REGISTRY="https://mirrors.ustc.edu.cn/npm" ;;
3) NPM_REGISTRY="https://mirrors.cloud.tencent.com/npm" ;;
4)
read -rp "请输入 npm registry 地址: " custom_registry
NPM_REGISTRY="$custom_registry"
;;
*) NPM_REGISTRY="https://registry.npmmirror.com" ;;
esac
info "使用镜像: $NPM_REGISTRY"
install_from_npm
return $?
;;
3)
warn "跳过 HAPI CLI 安装"
return 0
;;
4)
err "安装中止"
return 1
;;
*) warn "无效选项" ;;
esac
done
}
# ─────────────────────────────────────────
# 选择安装来源
# ─────────────────────────────────────────
@@ -982,6 +1124,7 @@ print_result() {
echo -e " ${BOLD}升级命令:${NC}"
echo -e " sudo bash install.sh --upgrade"
echo -e " sudo bash install.sh --source github-official --upgrade"
echo -e " sudo bash install.sh --github-mirror https://ghproxy.com --upgrade"
echo ""
echo -e " ${BOLD}日志文件:${NC} $LOG_FILE"
echo ""