#!/bin/sh # 此文件由 tools/build.sh 从 src/install.sh 和 src/lib 生成:请修改 src/ 下的文件后重新生成。 # ============================================================================== # - 采用更安全的“下载到本地再执行”模式。 # - 自动清理临时文件。 # - 强制 Root 执行,并自动安装依赖。 # ============================================================================== # --- 0. 清理机制 --- # 使用 trap 命令,确保脚本在任何情况下退出时(正常结束、出错、被中断), # 都能自动删除临时文件。临时文件名由 mktemp 随机生成,防止本地攻击者预建符号链接。 TMP_FILE=$(mktemp /tmp/install_payload.XXXXXX) || { echo "错误:无法创建临时文件。" >&2; exit 1; } trap 'rm -f "$TMP_FILE"' EXIT # --- 1. 权限检查 --- if [ "$(id -u)" -ne 0 ]; then echo "错误:此脚本必须以 root 用户身份运行。" >&2 exit 1 fi # --- 2. 操作系统检测 --- OS_FAMILY="" if [ -f /etc/os-release ]; then . /etc/os-release OS_ID=$(echo "$ID" | tr '[:upper:]' '[:lower:]') ID_LIKE=$(echo "$ID_LIKE" | tr '[:upper:]' '[:lower:]') if echo "$ID_LIKE" | grep -q "debian" || [ "$OS_ID" = "debian" ] || [ "$OS_ID" = "ubuntu" ]; then OS_FAMILY="debian" elif echo "$ID_LIKE" | grep -q "rhel" || echo "$ID_LIKE" | grep -q "centos" || [ "$OS_ID" = "centos" ] || [ "$OS_ID" = "rhel" ] || [ "$OS_ID" = "fedora" ]; then OS_FAMILY="rhel" elif [ "$OS_ID" = "alpine" ]; then OS_FAMILY="alpine" fi fi if [ -z "$OS_FAMILY" ]; then echo "错误:无法确定操作系统类型。" >&2; exit 1; fi # --- 3. 依赖自检与自动安装 --- ensure_packages() { for pkg in "$@"; do if ! command -v "$pkg" >/dev/null 2>&1; then echo "--> 未找到命令 '$pkg',正在尝试自动安装..." case "$OS_FAMILY" in debian) apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq "$pkg" ;; rhel) if command -v dnf >/dev/null 2>&1; then dnf install -y "$pkg"; else yum install -y "$pkg"; fi ;; alpine) apk add --no-cache "$pkg" ;; esac if ! command -v "$pkg" >/dev/null 2>&1; then echo "错误:自动安装 '$pkg' 失败,请手动安装后重试。" >&2; exit 1; else echo "--> '$pkg' 安装成功。"; fi fi done } # --- 4. 执行主逻辑 --- ensure_packages "curl" # 可以用同名环境变量换成镜像地址(测试时指向本地的文件) DEBIAN_URL="${SNELL_SCRIPT_URL:-https://snell.jinqians.com}" CENTOS_URL="${SNELL_CENTOS_SCRIPT_URL:-https://snell-centos.jinqians.com}" ALPINE_URL="${SNELL_DOCKER_SCRIPT_URL:-https://snell-docker.jinqians.com}" # 语法检查:优先用 bash -n(可解析 sh 语法),无 bash 时退回 sh -n check_syntax() { if command -v bash >/dev/null 2>&1; then bash -n "$1" else sh -n "$1" fi } main() { url=$1 os_name=$2 shell_to_use=$3 echo "--------------------------------------------------" echo "系统类型: $os_name" echo "远程脚本: $url" echo "操作模式: 下载脚本到 $TMP_FILE 后再执行" echo "--------------------------------------------------" printf "您确定要继续吗? [y/N]: " read -r choice case "$choice" in y|Y) echo "--> 正在下载脚本到 $TMP_FILE ..." # 使用 -o 选项将内容输出到文件,-f 选项让 curl 在服务器出错时静默失败 if ! curl -sSL -f -o "$TMP_FILE" "$url"; then echo "错误:下载脚本失败,请检查 URL 或网络连接。" >&2 # trap 会自动清理失败下载的空文件 exit 1 fi # 完整性校验:文件非空 + 语法检查,不通过则丢弃并退出 if [ ! -s "$TMP_FILE" ]; then echo "错误:下载的文件为空,已中止执行。" >&2 exit 1 fi if ! check_syntax "$TMP_FILE" 2>/dev/null; then echo "错误:下载的脚本未通过语法检查,已丢弃。" >&2 exit 1 fi echo "--> 下载文件校验通过。" echo "--> 下载完成。正在赋予执行权限..." chmod +x "$TMP_FILE" echo "--> 正在执行本地脚本 $TMP_FILE ..." # 直接执行本地脚本 "$TMP_FILE" echo "--> 本地脚本执行完毕。" ;; *) echo "--> 操作已取消。" exit 0 ;; esac } case "$OS_FAMILY" in debian) main "$DEBIAN_URL" "Debian/Ubuntu" "bash" ;; rhel) main "$CENTOS_URL" "CentOS/RHEL" "bash" ;; alpine) main "$ALPINE_URL" "Alpine" "sh" ;; esac # trap 会在脚本最后一步执行,清理文件 exit 0