feat: ask install directory, support non-root on Linux

- Remove forced root check; detect user and default to ~/.local/hapi
  for non-root, /opt/hapi for root
- Interactive prompt to confirm or customize install directory
- Non-root users can install CLI but get warned about Docker/systemd
  limitations
- Windows: ask to confirm C:\hapi or enter custom path
- Hub/autostart options grayed out when prerequisites unavailable

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

Co-Authored-By: HAPI <[email protected]>
This commit is contained in:
i
2026-06-02 00:02:33 +08:00
co-authored by HAPI
parent 96290bee6a
commit 14791602d6
2 changed files with 380 additions and 174 deletions
+264 -113
View File
@@ -5,28 +5,28 @@
# 支持:版本检测、多途径升级、GitHub Release 自动匹配架构
#
# 用法:
# sudo bash install.sh # 交互式管理
# sudo bash install.sh --source npm # 指定安装来源
# sudo bash install.sh --source github-official # 指定官方 GitHub Release
# sudo bash install.sh --source github-fork # 指定本仓库 GitHub Release
# 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 # 显示帮助
# bash install.sh # 交互式管理
# bash install.sh --install-dir ~/hapi # 指定安装目录
# bash install.sh --source npm # 指定安装来源
# bash install.sh --source github-official # 指定官方 GitHub Release
# bash install.sh --force # 强制重新安装
# bash install.sh --upgrade # 仅升级模式
# bash install.sh --skip-hub # 跳过 Hub Docker 配置
# bash install.sh --npm-registry https://registry.npmmirror.com
# bash install.sh --github-mirror https://ghproxy.com
# bash install.sh --help # 显示帮助
set -e
# ─────────────────────────────────────────
# 配置常量
# 默认配置(会在 setup_install_dir 中被覆盖)
# ─────────────────────────────────────────
INSTALL_DIR="/opt/hapi"
HAPI_BIN_DIR="$INSTALL_DIR/bin"
HAPI_DATA="$INSTALL_DIR/hapi-data"
INSTALL_DIR=""
HAPI_BIN_DIR=""
HAPI_DATA=""
LOG_FILE="/tmp/hapi-install.log"
VERSION_FILE="$INSTALL_DIR/.hapi-version"
SOURCE_FILE="$INSTALL_DIR/.hapi-source"
VERSION_FILE=""
SOURCE_FILE=""
NODE_MIN_VERSION=22
NPM_PACKAGE="@twsxtd/hapi"
@@ -46,7 +46,6 @@ BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
NC='\033[0m'
BOLD='\033[1m'
DIM='\033[2m'
# ─────────────────────────────────────────
# 参数
@@ -57,6 +56,7 @@ UPGRADE_ONLY=false
SKIP_HUB=false
NPM_REGISTRY="https://registry.npmjs.org"
GITHUB_MIRROR=""
INSTALL_DIR_ARG=""
# ─────────────────────────────────────────
# 运行时状态
@@ -67,6 +67,8 @@ INSTALLED_VERSION=""
INSTALLED_SOURCE=""
REMOTE_VERSION=""
SELECTED_SOURCE=""
IS_ROOT=false
HAS_DOCKER=false
# 系统状态
STATUS_CLI_INSTALLED=false
@@ -100,9 +102,10 @@ show_help() {
cat <<'EOF'
HAPI 管理脚本 (Linux)
用法: sudo bash install.sh [选项]
用法: bash install.sh [选项]
选项:
--install-dir DIR 指定安装目录(默认: root=/opt/hapi, 用户=~/.local/hapi)
--source SOURCE 指定 HAPI CLI 安装来源
npm - npm registry (默认)
github-official - tiann/hapi GitHub Release
@@ -110,16 +113,15 @@ 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)
--npm-registry URL 自定义 npm registry
--github-mirror URL 自定义 GitHub 镜像前缀
--help, -h 显示此帮助
示例:
sudo bash install.sh # 交互式管理
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
bash install.sh # 交互式管理
bash install.sh --install-dir ~/my-hapi # 自定义安装目录
bash install.sh --source npm # 使用 npm 安装
bash install.sh --upgrade # 快速升级
EOF
}
@@ -129,6 +131,14 @@ EOF
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--install-dir)
if [[ -z "${2:-}" ]]; then
err "--install-dir 需要参数值"
exit 1
fi
INSTALL_DIR_ARG="$2"
shift 2
;;
--source)
if [[ -z "${2:-}" ]]; then
err "--source 需要参数值"
@@ -205,6 +215,58 @@ detect_arch() {
esac
}
# ─────────────────────────────────────────
# 设置安装目录
# ─────────────────────────────────────────
setup_install_dir() {
# 检测是否 root
if [[ "$(id -u)" -eq 0 ]]; then
IS_ROOT=true
fi
# 确定默认安装目录
local default_dir=""
if [[ "$IS_ROOT" == true ]]; then
default_dir="/opt/hapi"
else
default_dir="$HOME/.local/hapi"
fi
# 如果命令行指定了目录,直接使用
if [[ -n "$INSTALL_DIR_ARG" ]]; then
INSTALL_DIR="$INSTALL_DIR_ARG"
elif [[ ! -t 0 ]]; then
# 非交互式,使用默认
INSTALL_DIR="$default_dir"
else
# 交互式,询问用户
echo ""
info "请选择 HAPI 安装目录"
echo " 默认: $default_dir"
if [[ "$IS_ROOT" == true ]]; then
echo " (root 用户默认安装到 /opt/hapi)"
else
echo " (非 root 用户默认安装到 ~/.local/hapi)"
fi
echo ""
read -rp "按 Enter 使用默认,或输入自定义路径: " user_dir
if [[ -n "$user_dir" ]]; then
# 展开 ~ 为 HOME
INSTALL_DIR="${user_dir/#\~/$HOME}"
else
INSTALL_DIR="$default_dir"
fi
fi
# 设置相关路径
HAPI_BIN_DIR="$INSTALL_DIR/bin"
HAPI_DATA="$INSTALL_DIR/hapi-data"
VERSION_FILE="$INSTALL_DIR/.hapi-version"
SOURCE_FILE="$INSTALL_DIR/.hapi-source"
ok "安装目录: $INSTALL_DIR"
}
# ─────────────────────────────────────────
# GitHub 镜像 URL 替换
# ─────────────────────────────────────────
@@ -496,15 +558,10 @@ install_from_github() {
chmod +x "$HAPI_BIN_DIR/hapi"
if [[ ":$PATH:" != *":$HAPI_BIN_DIR:"* ]]; then
export PATH="$HAPI_BIN_DIR:$PATH"
cat > /etc/profile.d/hapi.sh <<EOF
# HAPI CLI PATH
export PATH="$HAPI_BIN_DIR:\$PATH"
EOF
ok "已添加 $HAPI_BIN_DIR 到 PATH"
fi
# 添加到 PATH
add_to_path "$HAPI_BIN_DIR"
# 验证安装
local installed_ver
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
@@ -519,6 +576,44 @@ EOF
return 0
}
# ─────────────────────────────────────────
# 添加目录到 PATH
# ─────────────────────────────────────────
add_to_path() {
local dir="$1"
if [[ ":$PATH:" == *":$dir:"* ]]; then
return 0
fi
export PATH="$dir:$PATH"
if [[ "$IS_ROOT" == true ]]; then
cat > /etc/profile.d/hapi.sh <<EOF
# HAPI CLI PATH
export PATH="$dir:\$PATH"
EOF
ok "已添加 $dir 到系统 PATH"
else
# 添加到用户 shell 配置
local shell_rc=""
if [[ -n "$ZSH_VERSION" ]] || [[ "$SHELL" == */zsh ]]; then
shell_rc="$HOME/.zshrc"
elif [[ -n "$BASH_VERSION" ]] || [[ "$SHELL" == */bash ]]; then
shell_rc="$HOME/.bashrc"
fi
if [[ -n "$shell_rc" && -f "$shell_rc" ]]; then
if ! grep -q "$dir" "$shell_rc" 2>/dev/null; then
echo "" >> "$shell_rc"
echo "# HAPI CLI PATH" >> "$shell_rc"
echo "export PATH=\"$dir:\$PATH\"" >> "$shell_rc"
ok "已添加 $dir 到 $shell_rc"
fi
fi
fi
}
# ─────────────────────────────────────────
# 从 npm 安装
# ─────────────────────────────────────────
@@ -591,6 +686,12 @@ install_nodejs() {
info "未检测到 Node.js,开始安装 v$NODE_MIN_VERSION..."
fi
if [[ "$IS_ROOT" != true ]]; then
warn "非 root 用户无法自动安装 Node.js"
warn "请手动安装 Node.js v$NODE_MIN_VERSION+: https://nodejs.org"
return 1
fi
local node_arch=""
case "$DETECTED_ARCH" in
x64) node_arch="x64" ;;
@@ -631,34 +732,16 @@ install_nodejs() {
}
# ─────────────────────────────────────────
# 检查/安装 Docker
# 检查 Docker 权限
# ─────────────────────────────────────────
install_docker() {
step "检查 Docker"
check_docker() {
if command -v docker &>/dev/null; then
local docker_ver
docker_ver=$(docker --version 2>/dev/null | awk '{print $3}' | tr -d ',' || echo "installed")
ok "Docker $docker_ver 已安装"
return 0
if docker info &>/dev/null; then
HAS_DOCKER=true
return 0
fi
fi
info "正在安装 Docker..."
curl -fsSL https://get.docker.com | sh >>"$LOG_FILE" 2>&1
systemctl enable docker >>"$LOG_FILE" 2>&1
systemctl start docker >>"$LOG_FILE" 2>&1
for i in {1..30}; do
docker info &>/dev/null && break
sleep 1
done
if ! docker info &>/dev/null; then
err "Docker 启动失败"
return 1
fi
ok "Docker 安装并启动成功"
HAS_DOCKER=false
}
# ─────────────────────────────────────────
@@ -712,6 +795,15 @@ select_source() {
# 配置 HAPI Hub
# ─────────────────────────────────────────
setup_hub() {
if [[ "$HAS_DOCKER" != true ]]; then
err "Docker 不可用,无法配置 Hub"
if [[ "$IS_ROOT" != true ]]; then
warn "提示: 将当前用户加入 docker 组,然后重新登录:"
warn " sudo usermod -aG docker \$USER"
fi
return 1
fi
step "配置 HAPI Hub"
mkdir -p "$INSTALL_DIR" "$HAPI_DATA"
@@ -785,15 +877,41 @@ stop_hub() {
return 0
fi
if [[ "$HAS_DOCKER" != true ]]; then
err "Docker 不可用"
return 1
fi
step "停止 HAPI Hub"
(cd "$INSTALL_DIR" && docker compose down >>"$LOG_FILE" 2>&1)
ok "HAPI Hub 已停止"
}
# ─────────────────────────────────────────
# 启动 HAPI Hub
# ─────────────────────────────────────────
start_hub_only() {
if [[ "$HAS_DOCKER" != true ]]; then
err "Docker 不可用"
return 1
fi
if [[ -f "$INSTALL_DIR/docker-compose.yml" ]]; then
(cd "$INSTALL_DIR" && docker compose up -d >>"$LOG_FILE" 2>&1)
ok "HAPI Hub 已启动"
fi
}
# ─────────────────────────────────────────
# 创建 systemd 服务
# ─────────────────────────────────────────
setup_systemd() {
if [[ "$IS_ROOT" != true ]]; then
warn "非 root 用户无法设置系统级开机自启"
warn "可以使用用户级 systemd,但需要手动配置"
return 1
fi
step "创建 systemd 开机自启服务"
cat > /etc/systemd/system/hapi-hub.service <<EOF
@@ -857,6 +975,11 @@ EOF
# 启用开机自启
# ─────────────────────────────────────────
enable_autostart() {
if [[ "$IS_ROOT" != true ]]; then
warn "非 root 用户无法设置系统级开机自启"
return 1
fi
step "启用开机自启"
if [[ -f /etc/systemd/system/hapi-hub.service ]]; then
@@ -878,6 +1001,11 @@ enable_autostart() {
# 禁用开机自启
# ─────────────────────────────────────────
disable_autostart() {
if [[ "$IS_ROOT" != true ]]; then
warn "非 root 用户无法修改系统级开机自启"
return 1
fi
step "禁用开机自启"
if [[ -f /etc/systemd/system/hapi-hub.service ]]; then
@@ -950,19 +1078,23 @@ collect_status() {
# Hub 状态
if [[ -f "$INSTALL_DIR/docker-compose.yml" ]]; then
STATUS_HUB_CONFIGURED=true
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^hapi-hub$'; then
STATUS_HUB_RUNNING=true
if [[ "$HAS_DOCKER" == true ]]; then
if docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^hapi-hub$'; then
STATUS_HUB_RUNNING=true
fi
fi
fi
# 自启动状态
if [[ -f /etc/systemd/system/hapi-hub.service ]]; then
STATUS_AUTOSTART_HUB_ENABLED=$(systemctl is-enabled hapi-hub 2>/dev/null || echo "unknown")
STATUS_AUTOSTART_HUB_ACTIVE=$(systemctl is-active hapi-hub 2>/dev/null || echo "unknown")
fi
if [[ -f /etc/systemd/system/hapi-runner.service ]]; then
STATUS_AUTOSTART_RUNNER_ENABLED=$(systemctl is-enabled hapi-runner 2>/dev/null || echo "unknown")
STATUS_AUTOSTART_RUNNER_ACTIVE=$(systemctl is-active hapi-runner 2>/dev/null || echo "unknown")
if [[ "$IS_ROOT" == true ]]; then
if [[ -f /etc/systemd/system/hapi-hub.service ]]; then
STATUS_AUTOSTART_HUB_ENABLED=$(systemctl is-enabled hapi-hub 2>/dev/null || echo "unknown")
STATUS_AUTOSTART_HUB_ACTIVE=$(systemctl is-active hapi-hub 2>/dev/null || echo "unknown")
fi
if [[ -f /etc/systemd/system/hapi-runner.service ]]; then
STATUS_AUTOSTART_RUNNER_ENABLED=$(systemctl is-enabled hapi-runner 2>/dev/null || echo "unknown")
STATUS_AUTOSTART_RUNNER_ACTIVE=$(systemctl is-active hapi-runner 2>/dev/null || echo "unknown")
fi
fi
}
@@ -1007,10 +1139,19 @@ print_status() {
autostart_runner="${STATUS_AUTOSTART_RUNNER_ENABLED} (${runner_active_color}${STATUS_AUTOSTART_RUNNER_ACTIVE}${NC})"
fi
local user_info=""
if [[ "$IS_ROOT" == true ]]; then
user_info="root"
else
user_info="$(whoami)"
fi
echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║${NC} ${BOLD}HAPI 系统状态${NC} ${BOLD}║${NC}"
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════════╣${NC}"
echo -e "${BOLD}║${NC} ${DIM}用户: $user_info | 目录: $INSTALL_DIR${NC} ${BOLD}║${NC}"
echo -e "${BOLD}║${NC} ${BOLD}║${NC}"
echo -e "${BOLD}║${NC} ${BOLD}HAPI CLI${NC} ${BOLD}║${NC}"
echo -e "${BOLD}║${NC} 状态: ${cli_color}${cli_status}${NC}${remote_hint} ${BOLD}║${NC}"
if [[ "$STATUS_CLI_INSTALLED" == true ]]; then
@@ -1048,7 +1189,7 @@ main_menu() {
local cmp
cmp=$(compare_versions "$STATUS_CLI_VERSION" "$STATUS_REMOTE_VERSION")
if [[ "$cmp" == "2" ]]; then
echo " $i) ${GREEN}升级 HAPI CLI${NC} (v$STATUS_CLI_VERSION -> v$STATUS_REMOTE_VERSION)"
echo -e " $i) ${GREEN}升级 HAPI CLI${NC} (v$STATUS_CLI_VERSION -> v$STATUS_REMOTE_VERSION)"
options+=("upgrade")
((i++))
fi
@@ -1056,47 +1197,55 @@ main_menu() {
echo " $i) 覆盖安装 HAPI CLI"
options+=("reinstall")
((i++))
echo " $i) ${RED}卸载 HAPI CLI${NC}"
echo -e " $i) ${RED}卸载 HAPI CLI${NC}"
options+=("uninstall")
((i++))
else
echo " $i) ${GREEN}安装 HAPI CLI${NC}"
echo -e " $i) ${GREEN}安装 HAPI CLI${NC}"
options+=("install")
((i++))
fi
echo ""
if [[ "$STATUS_HUB_CONFIGURED" == false ]]; then
echo " $i) 配置并启动 HAPI Hub"
options+=("hub")
((i++))
else
if [[ "$STATUS_HUB_RUNNING" == true ]]; then
echo " $i) 停止 HAPI Hub"
options+=("stop_hub")
if [[ "$HAS_DOCKER" == true ]]; then
if [[ "$STATUS_HUB_CONFIGURED" == false ]]; then
echo " $i) 配置并启动 HAPI Hub"
options+=("hub")
((i++))
else
echo " $i) 启动 HAPI Hub"
options+=("start_hub")
if [[ "$STATUS_HUB_RUNNING" == true ]]; then
echo " $i) 停止 HAPI Hub"
options+=("stop_hub")
else
echo " $i) 启动 HAPI Hub"
options+=("start_hub")
fi
((i++))
fi
((i++))
else
echo -e " ${DIM}Hub 管理 (Docker 不可用)${NC}"
fi
echo ""
if [[ -n "$STATUS_AUTOSTART_HUB_ENABLED" || -n "$STATUS_AUTOSTART_RUNNER_ENABLED" ]]; then
if [[ "$STATUS_AUTOSTART_HUB_ENABLED" == "enabled" || "$STATUS_AUTOSTART_RUNNER_ENABLED" == "enabled" ]]; then
echo " $i) 禁用开机自启"
options+=("disable_autostart")
if [[ "$IS_ROOT" == true ]]; then
if [[ -n "$STATUS_AUTOSTART_HUB_ENABLED" || -n "$STATUS_AUTOSTART_RUNNER_ENABLED" ]]; then
if [[ "$STATUS_AUTOSTART_HUB_ENABLED" == "enabled" || "$STATUS_AUTOSTART_RUNNER_ENABLED" == "enabled" ]]; then
echo " $i) 禁用开机自启"
options+=("disable_autostart")
else
echo " $i) 启用开机自启"
options+=("enable_autostart")
fi
((i++))
else
echo " $i) 启用开机自启"
options+=("enable_autostart")
echo " $i) 设置开机自启"
options+=("setup_autostart")
((i++))
fi
((i++))
else
echo " $i) 设置开机自启"
options+=("setup_autostart")
((i++))
echo -e " ${DIM}开机自启 (需要 root)${NC}"
fi
echo ""
@@ -1115,7 +1264,6 @@ main_menu() {
exit 0
fi
# 验证选项
if [[ -z "$choice" ]] || ! [[ "$choice" =~ ^[0-9]+$ ]]; then
warn "无效选项"
continue
@@ -1138,7 +1286,6 @@ main_menu() {
select_source
info "安装来源: $SELECTED_SOURCE"
# 获取远程版本
if ! get_remote_version "$SELECTED_SOURCE"; then
err "无法获取远程版本"
read -rp "按 Enter 继续..."
@@ -1162,7 +1309,6 @@ main_menu() {
upgrade)
if [[ -z "$STATUS_REMOTE_VERSION" ]]; then
# 获取远程版本
local upgrade_source="$STATUS_CLI_SOURCE"
[[ "$upgrade_source" == github:* ]] && upgrade_source="${upgrade_source#github:}"
[[ "$upgrade_source" == "npm" ]] && SELECTED_SOURCE="npm"
@@ -1215,17 +1361,13 @@ main_menu() {
;;
hub)
install_docker
setup_hub
collect_status
read -rp "按 Enter 继续..."
;;
start_hub)
if [[ -f "$INSTALL_DIR/docker-compose.yml" ]]; then
(cd "$INSTALL_DIR" && docker compose up -d >>"$LOG_FILE" 2>&1)
ok "HAPI Hub 已启动"
fi
start_hub_only
collect_status
read -rp "按 Enter 继续..."
;;
@@ -1239,8 +1381,7 @@ main_menu() {
setup_autostart|enable_autostart)
if [[ "$STATUS_HUB_CONFIGURED" == false ]]; then
warn "Hub 未配置,先执行配置并启动 HAPI Hub"
install_docker
setup_hub
setup_hub || true
fi
setup_systemd
collect_status
@@ -1265,12 +1406,6 @@ main_menu() {
check_prerequisites() {
step "前置检查"
if [[ "$(id -u)" -ne 0 ]]; then
err "请使用 sudo 或 root 权限运行"
exit 1
fi
ok "root 权限确认"
detect_platform
detect_arch
ok "平台: $DETECTED_PLATFORM, 架构: $DETECTED_ARCH"
@@ -1282,12 +1417,24 @@ check_prerequisites() {
fi
done
ok "基础工具检查通过"
check_docker
if [[ "$HAS_DOCKER" == true ]]; then
ok "Docker 可用"
else
warn "Docker 不可用"
if [[ "$IS_ROOT" != true ]]; then
warn "提示: 将当前用户加入 docker 组,然后重新登录:"
warn " sudo usermod -aG docker \$USER"
fi
fi
}
# ─────────────────────────────────────────
# 快速模式:向后兼容的命令行参数
# ─────────────────────────────────────────
run_quick_mode() {
setup_install_dir
check_prerequisites
SELECTED_SOURCE=""
@@ -1331,7 +1478,7 @@ run_quick_mode() {
fi
if [[ "$SELECTED_SOURCE" == "npm" ]]; then
install_nodejs
install_nodejs || true
fi
case "$SELECTED_SOURCE" in
@@ -1340,9 +1487,12 @@ run_quick_mode() {
github-fork) install_from_github "$GITHUB_FORK_REPO" ;;
esac
install_docker
[[ "$SKIP_HUB" != true ]] && setup_hub
[[ "$SKIP_HUB" != true ]] && setup_systemd
if [[ "$HAS_DOCKER" == true && "$SKIP_HUB" != true ]]; then
setup_hub
if [[ "$IS_ROOT" == true ]]; then
setup_systemd
fi
fi
}
# ─────────────────────────────────────────
@@ -1353,8 +1503,8 @@ main() {
: > "$LOG_FILE"
# 如果带有旧版参数(--upgrade / --force / --source),进入快速模式
if [[ "$UPGRADE_ONLY" == true || "$FORCE" == true || -n "$SOURCE" || "$SKIP_HUB" == true ]]; then
# 如果带有旧版参数,进入快速模式
if [[ "$UPGRADE_ONLY" == true || "$FORCE" == true || -n "$SOURCE" || "$SKIP_HUB" == true || -n "$INSTALL_DIR_ARG" ]]; then
echo ""
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║${NC} HAPI 快速模式 ${BOLD}║${NC}"
@@ -1371,6 +1521,7 @@ main() {
echo -e "${BOLD}╚══════════════════════════════════════════════════════════════════╝${NC}"
echo ""
setup_install_dir
check_prerequisites
collect_status