#!/usr/bin/env bash # ============================================================================ # CyberStrikeAI 工具一键安装脚本 # ---------------------------------------------------------------------------- # 解决问题: 工具执行失败: exec: "xxx": executable file not found in $PATH # 跑 skill 时不同工具缺失导致任务无法继续 (issue #139) # # 工作方式: # 1. 自动扫描 tools/*.yaml 中声明的所有工具 (name + command) # 2. 在 Kali / Debian / Ubuntu / Parrot 上优先使用 apt 安装 # 3. apt 找不到的, 依次降级到 pip / gem / go install / GitHub release # 4. 已安装的自动跳过, 不会重复执行 # # 用法: # ./install-tools.sh # 安装所有工具 # ./install-tools.sh --check # 只检查, 不安装 # ./install-tools.sh --list # 列出所有工具及状态 # ./install-tools.sh --only nmap,gau # 只安装指定工具 # ./install-tools.sh --skip msfvenom # 跳过指定工具 # ./install-tools.sh --dry-run # 模拟运行 # ./install-tools.sh --method apt # 强制使用某种安装方式 # ./install-tools.sh --no-sudo # 不使用 sudo (用于已是 root 的环境) # ./install-tools.sh --verbose # 显示安装命令输出 (排错用) # ./install-tools.sh --install-bash # macOS: 用 Homebrew 安装 bash 4+ 并继续 # # 环境变量 (可选): # PIP_INDEX_URL 自定义 pip 镜像源 (未设置时: 中文环境用清华源, 其他用 pypi.org) # GOPROXY 自定义 Go 模块代理 (未设置时: 中文环境用 goproxy.cn) # INSTALL_PREFIX 自定义二进制安装目录 (默认 /usr/local/bin) # VERBOSE 设为 1 等同 --verbose # # 平台说明: # 主力支持 Kali / Debian / Ubuntu (apt). macOS 仅 pip/go/GitHub 部分可用. # ============================================================================ # bash 4+ 必需 (关联数组). macOS 自带 3.2 — 自动寻找 Homebrew bash 或 --install-bash __csai_find_bash4() { local c prefix for c in \ "${BASH4:-}" \ /opt/homebrew/bin/bash \ /usr/local/opt/bash/bin/bash \ /usr/local/bin/bash; do [[ -z "$c" || ! -x "$c" ]] && continue if "$c" -c '((BASH_VERSINFO[0] >= 4))' 2>/dev/null; then echo "$c" return 0 fi done if command -v brew >/dev/null 2>&1; then prefix="$(brew --prefix bash 2>/dev/null)" || true c="${prefix}/bin/bash" if [[ -n "$prefix" && -x "$c" ]] && "$c" -c '((BASH_VERSINFO[0] >= 4))' 2>/dev/null; then echo "$c" return 0 fi fi return 1 } __csai_reexec_bash4() { local b4 b4="$(__csai_find_bash4)" [[ -n "$b4" ]] || return 1 exec "$b4" "$0" "$@" } if ((BASH_VERSINFO[0] < 4)); then want_install_bash=0 for arg in "$@"; do [[ "$arg" == "--install-bash" ]] && want_install_bash=1 done if [[ $want_install_bash -eq 1 ]]; then if ! command -v brew >/dev/null 2>&1; then echo "[-] --install-bash 需要 Homebrew: https://brew.sh" >&2 exit 1 fi echo "[*] 通过 Homebrew 安装 bash 4+ ..." brew install bash __csai_reexec_bash4 "$@" || true fi if ! __csai_reexec_bash4 "$@"; then echo "[!] 此脚本需要 bash 4.0+, 当前是: $BASH_VERSION" >&2 if command -v brew >/dev/null 2>&1; then echo " macOS 一键修复: ./install-tools.sh --install-bash --list" >&2 echo " 或手动: brew install bash" >&2 else echo " macOS: 安装 Homebrew 后运行 ./install-tools.sh --install-bash" >&2 fi echo " Kali/Debian/Ubuntu: 默认 bash 5.x, 直接运行即可" >&2 exit 1 fi fi set -uo pipefail ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" TOOLS_DIR="$ROOT_DIR/tools" # ---------------------------------------------------------------------------- # 颜色与日志 # ---------------------------------------------------------------------------- RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' GRAY='\033[0;90m' NC='\033[0m' info() { echo -e "${BLUE}[i]${NC} $*"; } success() { echo -e "${GREEN}[+]${NC} $*"; } warning() { echo -e "${YELLOW}[!]${NC} $*"; } error() { echo -e "${RED}[-]${NC} $*"; } note() { echo -e "${CYAN}[*]${NC} $*"; } dim() { echo -e "${GRAY} $*${NC}"; } VERBOSE="${VERBOSE:-0}" log_run() { if [[ "$VERBOSE" -eq 1 ]]; then "$@" else "$@" >/dev/null 2>&1 fi } # ---------------------------------------------------------------------------- # 默认源 (按语言环境选择镜像, 可被环境变量覆盖) # ---------------------------------------------------------------------------- is_zh_locale() { [[ "${LANG:-}${LC_ALL:-}" == *zh_* ]] } if [[ -z "${PIP_INDEX_URL:-}" ]]; then if is_zh_locale; then PIP_INDEX_URL="https://pypi.tuna.tsinghua.edu.cn/simple" else PIP_INDEX_URL="https://pypi.org/simple" fi fi if [[ -z "${GOPROXY:-}" ]]; then if is_zh_locale; then GOPROXY="https://goproxy.cn,direct" else GOPROXY="https://proxy.golang.org,direct" fi fi INSTALL_PREFIX="${INSTALL_PREFIX:-/usr/local/bin}" # 状态统计 declare -A STATUS=() # name -> ok|skip|fail|manual declare -A METHOD=() # name -> method used declare -A SKIP_REASON=() TOTAL=0 OK_COUNT=0 SKIP_COUNT=0 FAIL_COUNT=0 MANUAL_COUNT=0 # ---------------------------------------------------------------------------- # 参数解析 # ---------------------------------------------------------------------------- MODE="install" # install | check | list | dry-run ONLY_TOOLS="" SKIP_TOOLS="" FORCE_METHOD="" USE_SUDO="auto" usage() { sed -n '2,36p' "$0" | sed 's/^# \{0,1\}//' exit 0 } while [[ $# -gt 0 ]]; do case "$1" in --check) MODE="check"; shift ;; --list) MODE="list"; shift ;; --dry-run) MODE="dry-run"; shift ;; --only) ONLY_TOOLS="${2:-}"; shift 2 ;; --skip) SKIP_TOOLS="${2:-}"; shift 2 ;; --method) FORCE_METHOD="${2:-}"; shift 2 ;; --no-sudo) USE_SUDO="no"; shift ;; --sudo) USE_SUDO="yes"; shift ;; --verbose|-v) VERBOSE=1; shift ;; --install-bash) shift ;; # 已在启动阶段处理 -h|--help) usage ;; *) error "未知参数: $1"; usage ;; esac done # ---------------------------------------------------------------------------- # 系统检测 # ---------------------------------------------------------------------------- DISTRO_ID="" DISTRO_LIKE="" DISTRO_FAMILY="" # debian | macos | other PKG_MGR="" # apt | brew | unknown SUDO_CMD="" detect_distro() { if [[ "$OSTYPE" == "darwin"* ]]; then DISTRO_FAMILY="macos" PKG_MGR="brew" DISTRO_ID="macos" return fi if [[ -f /etc/os-release ]]; then # shellcheck disable=SC1091 . /etc/os-release DISTRO_ID="${ID:-unknown}" DISTRO_LIKE="${ID_LIKE:-}" elif [[ -f /etc/lsb-release ]]; then # shellcheck disable=SC1091 . /etc/lsb-release DISTRO_ID="${DISTRIB_ID:-unknown}" else DISTRO_ID="unknown" fi case "$DISTRO_ID" in kali) DISTRO_FAMILY="debian" ;; parrot*|debian|ubuntu|linuxmint|elementary|pop|deepin) DISTRO_FAMILY="debian" ;; centos|rhel|fedora|rocky|alma|amazon) DISTRO_FAMILY="rhel" ;; arch|manjaro|endeavouros) DISTRO_FAMILY="arch" ;; *) if [[ "$DISTRO_LIKE" == *debian* ]]; then DISTRO_FAMILY="debian" elif [[ "$DISTRO_LIKE" == *rhel* ]]; then DISTRO_FAMILY="rhel" else DISTRO_FAMILY="other" fi ;; esac case "$DISTRO_FAMILY" in debian) PKG_MGR="apt" ;; rhel) PKG_MGR="dnf" ;; arch) PKG_MGR="pacman" ;; esac } setup_sudo() { if [[ $EUID -eq 0 ]]; then SUDO_CMD="" return fi case "$USE_SUDO" in yes) SUDO_CMD="sudo" ;; no) if [[ $EUID -ne 0 ]]; then error "需要 root 权限才能继续, 但 --no-sudo 已指定" error "请以 root 身份运行, 或去掉 --no-sudo" exit 1 fi SUDO_CMD="" ;; auto) if command -v sudo >/dev/null 2>&1; then SUDO_CMD="sudo" else if [[ $EUID -ne 0 ]]; then error "需要 root 权限, 但系统未安装 sudo" exit 1 fi SUDO_CMD="" fi ;; esac } # ---------------------------------------------------------------------------- # 工具映射表 # ---------------------------------------------------------------------------- # 字段: name|cmd|apt|pip|gem|go|github_repo|binary_url|note # - name: tools/*.yaml 中的 name # - cmd: 实际可执行文件名 (用于 command -v 检测) # - apt: Debian/Kali 包名 (空表示跳过) # - pip: PyPI 包名 (空表示跳过) # - gem: Ruby gem 包名 # - go: go install 路径 (github.com/xxx/cmd@latest) # - github_repo: GitHub 仓库 owner/repo 用于下载 release (格式 owner/repo) # - binary_url: 自定义二进制直链 # - note: 备注 # # 优先级: apt > brew(自动) > pip(映射+PyPI自动探测) > gem > go > github release # 非 apt 平台: 按 apt/name/cmd 依次尝试 Homebrew; 再按 pip列/name/cmd/apt 探测 PyPI # ---------------------------------------------------------------------------- get_tool_map() { cat <<'EOF' amass|amass|amass|||||Kali 自带 angr|python3||angr|||||二进制较重, 首次编译耗时长 api-schema-analyzer|spectral|||||||需要 Node.js (npm install) arjun|arjun|arjun|arjun||||Kali 自带 arp-scan|arp-scan|arp-scan|||||Kali 自带 binwalk|binwalk|binwalk|||||Kali 自带 bloodhound|bloodhound-python||bloodhound|||或 apt:bloodhound checkov|checkov||checkov||||需 pip checksec|checksec|checksec|||||Kali 自带 clair|clair|||||quay/clair|需 Docker 或单独下载二进制 cloudmapper|cloudmapper||cloudmapper|||| dalfox|dalfox|dalfox|||github.com/hahwul/dalfox/v2|| dirsearch|dirsearch|dirsearch|dirsearch|||| dnsenum|dnsenum|dnsenum|||||Kali 自带 dnslog|python3||||||||内部 Python 包装, 无需单独安装 dotdotpwn|dotdotpwn|dotdotpwn|||||Kali 自带 enum4linux-ng|enum4linux-ng|enum4linux-ng|||||Kali 自带 exec|sh||||||||内置 sh, 跳过 execute-python-script|/bin/bash||||||||内置 bash, 跳过 exiftool|exiftool|libimage-exiftool-perl||||apt 包名: libimage-exiftool-perl falco|falco|falco|||||Kali 自带 feroxbuster|feroxbuster|feroxbuster|||github.com/epi052/feroxbuster|可能需 GitHub release ffuf|ffuf|ffuf|||||Kali 自带 fierce|fierce|fierce|fierce|||| fofa_search|python3||requests||||fofa-py (需 API key) foremost|foremost|foremost|||||Kali 自带 fscan|fscan|||||shelldigger/fscan|国内开源, GitHub release gau|gau|gau|||github.com/lc/gau|| gdb|gdb|gdb|||||Kali 自带 ghidra|analyzeHeadless|ghidra|||||包较大, 可能需手动确认 gobuster|gobuster|gobuster|||||Kali 自带 graphql-scanner|graphqlmap||graphqlmap|||||或 git clone hashcat|hashcat|hashcat|||||Kali 自带 hashpump|hashpump|hashpump|||||Kali 自带 http-framework-test|python3||requests||||内置 Python 包装 hydra|hydra|hydra|||||Kali 自带 impacket|python3|python3-impacket|impacket||||优先 apt install-python-package|/bin/bash||||||||内置, 跳过 jaeles|jaeles|jaeles|||github.com/jaeles-project/jaeles|| john|john|john|||||Kali 自带 jwt-analyzer|jwt_tool|jwt|jwt-tool|||apt 包 jwt 提供 jwt_tool katana|katana|katana|||github.com/projectdiscovery/katana/v2|| kube-bench|kube-bench|kube-bench|||||Kali 自带 kube-hunter|kube-hunter||kube-hunter|||pip 安装 libc-database|python3|libc-database||||niklasb/libc-database||git clone 较慢 lightx|lightx|||||zyylhn/lightx|需 GitHub release linpeas|linpeas.sh||||||从 GitHub 下载脚本到 /usr/local/bin masscan|masscan|masscan|||||Kali 自带 metasploit|python3|metasploit-framework|||||依赖 msfconsole msfvenom|msfvenom|metasploit-framework|||||由 metasploit-framework 提供 nbtscan|nbtscan|nbtscan|||||Kali 自带 netexec|netexec|netexec|netexec|||| nikto|nikto|nikto|||||Kali 自带 nmap|nmap|nmap|||||Kali 自带 nuclei|nuclei|nuclei|||github.com/projectdiscovery/nuclei/v3/cmd/nuclei|| objdump|objdump|binutils|||||由 binutils 提供 one-gadget|one_gadget|one-gadget|one_gadget|||apt one-gadget pacu|pacu||pacu|||| paramspider|paramspider||paramspider|||| prowler|prowler||prowler|||| pwninit|pwninit|pwninit|||github.com/io12/pwninit|| pwntools|python3||pwntools|||较重 quake_search|python3||requests||||quake 需 API key query_execution_result|internal:query_execution_result||||||||内置, 跳过 radare2|r2|radare2|||||apt 包 radare2 提供 r2 responder|python3|responder||||Kali 自带 responder ropgadget|ROPgadget||ROPgadget|||pip 安装 ropper|ropper|ropper|ropper|||Kali 自带 rpcclient|python3|samba-common-bin||||samba-common-bin 提供 rpcclient rustscan|rustscan|rustscan|||github.com/RustScan/RustScan|| scout-suite|scout||scoutsuite|||| shodan_search|python3||shodan|||需 API key smbmap|smbmap|smbmap|||||Kali 自带 sqlmap|sqlmap|sqlmap|||||Kali 自带 steghide|steghide|steghide|||||Kali 自带 strings|strings|binutils|||||由 binutils 提供 subfinder|subfinder|subfinder|||github.com/projectdiscovery/subfinder/v2/cmd/subfinder|| terrascan|terrascan|terrascan|||||Kali 自带 trivy|trivy|trivy|||||Kali 自带 volatility3|volatility3|volatility3|volatility3|||Kali 自带 wafw00f|wafw00f|wafw00f|||||Kali 自带 waybackurls|waybackurls|waybackurls|||github.com/tomnomnom/waybackurls|| wpscan|wpscan|wpscan|||||Kali 自带 x8|x8|x8|||github.com/Sh1Yo/x8|| xsser|xsser|xsser|||||Kali 自带 xxd|xxd|vim-gtk3|xxd||||Kali 装 vim 即可 zap|zap-cli||zapcli|||pip 安装 zoomeye_search|python3||zoomeye-sdk|||zoomeye 需 API key zsteg|zsteg|zsteg|zsteg|||apt:ruby-zsteg EOF } # 把映射加载到数组 (容忍列数不齐, 自动纠正常见错位) declare -A M=() load_map() { local line name rest cmd apt pip gem go gh bin_url note local nfields while IFS= read -r line || [[ -n "$line" ]]; do [[ -z "$line" || "$line" == \#* ]] && continue name="${line%%|*}" rest="${line#*|}" # 补齐 cmd..note 共 8 段 (bash 3.2 不用 local -a) nfields=$(awk -F'|' '{print NF}' <<< "$rest") while (( nfields < 8 )); do rest="${rest}|" nfields=$((nfields + 1)) done IFS='|' read -r cmd apt pip gem go gh bin_url note <<< "$rest" # gem 列误填 GitHub owner/repo → 提升到 gh if [[ "$gem" == */* && -z "$go" && -z "$gh" ]]; then gh="$gem" gem="" fi # gh 列误填备注 (无 /) → 移到 note if [[ -n "$gh" && "$gh" != */* && -z "$bin_url" && -z "$note" ]]; then note="$gh" gh="" fi # bin_url 列误填备注 → 移到 note if [[ -n "$bin_url" && -z "$note" && "$bin_url" != http* ]]; then note="$bin_url" bin_url="" fi M["$name|cmd"]="$cmd" M["$name|apt"]="$apt" M["$name|pip"]="$pip" M["$name|gem"]="$gem" M["$name|go"]="$go" M["$name|gh"]="$gh" M["$name|bin"]="$bin_url" M["$name|note"]="$note" done < <(get_tool_map) } # 从 tools/*.yaml 抽取 name 与 command declare -A YAML_CMD=() declare -A YAML_ENABLED=() discover_tools() { local f name cmd enabled for f in "$TOOLS_DIR"/*.yaml "$TOOLS_DIR"/*.yml; do [[ -f "$f" ]] || continue name=$(grep -E "^name:" "$f" | head -1 | sed -E 's/^name:[[:space:]]*"?([^"]+)"?.*/\1/') cmd=$(grep -E "^command:" "$f" | head -1 | sed -E 's/^command:[[:space:]]*"?([^"]+)"?.*/\1/') enabled=$(grep -E "^enabled:" "$f" | head -1 | sed -E 's/^enabled:[[:space:]]*"?([^"]+)"?.*/\1/') if [[ -n "$name" ]]; then YAML_CMD["$name"]="$cmd" YAML_ENABLED["$name"]="${enabled:-true}" fi done } validate_tool_coverage() { local name for name in "${!YAML_CMD[@]}"; do if [[ -z "${M[$name|cmd]:-}" ]]; then warning "tools/${name}.yaml 未纳入 install-tools 映射表, 将不会自动安装" fi done } # ---------------------------------------------------------------------------- # 检测工具是否已安装 (按工具名判断, 避免 python3 包装器误报) # ---------------------------------------------------------------------------- resolve_cmd() { local name="$1" echo "${M[$name|cmd]:-${YAML_CMD[$name]:-$name}}" } cmd_on_path() { local cmd="$1" [[ -n "$cmd" ]] && command -v "$cmd" >/dev/null 2>&1 } is_builtin_tool() { case "$1" in exec|execute-python-script|install-python-package|query_execution_result|dnslog|http-framework-test) return 0 ;; esac return 1 } is_installed() { local name="$1" local cmd="${2:-$(resolve_cmd "$name")}" case "$name" in angr) python3 -c "import angr" >/dev/null 2>&1 && return 0 ;; pwntools) python3 -c "import pwn" >/dev/null 2>&1 && return 0 ;; impacket) python3 -c "import impacket" >/dev/null 2>&1 && return 0 ;; metasploit) cmd_on_path msfconsole && return 0 ;; msfvenom) cmd_on_path msfvenom && return 0 ;; responder) cmd_on_path responder && return 0 ;; rpcclient) cmd_on_path rpcclient && return 0 ;; bloodhound) cmd_on_path bloodhound-python && return 0 ;; shodan_search) python3 -c "import shodan" >/dev/null 2>&1 && return 0 ;; zoomeye_search) python3 -c "import zoomeye" >/dev/null 2>&1 && return 0 ;; fofa_search|quake_search) python3 -c "import requests" >/dev/null 2>&1 && return 0 ;; libc-database) [[ -d /usr/share/libc-database || -d "$HOME/libc-database" ]] && return 0 ;; api-schema-analyzer) cmd_on_path spectral && return 0 ;; linpeas) [[ -x "$INSTALL_PREFIX/linpeas.sh" ]] && return 0 cmd_on_path linpeas.sh && return 0 ;; paramspider) cmd_on_path paramspider && return 0 ;; pacu) cmd_on_path pacu && return 0 ;; prowler) cmd_on_path prowler && return 0 ;; checkov) cmd_on_path checkov && return 0 ;; scout-suite) cmd_on_path scout && return 0 ;; graphql-scanner) cmd_on_path graphqlmap && return 0 ;; cloudmapper) cmd_on_path cloudmapper && return 0 ;; dnslog|http-framework-test) cmd_on_path python3 && return 0 ;; esac # go install 产物可能在 ~/go/bin if [[ -x "$HOME/go/bin/$cmd" ]]; then return 0 fi # 通用二进制检测 (排除包装器命令) case "$cmd" in python3|/bin/bash|sh|internal:*|'') return 1 ;; esac cmd_on_path "$cmd" } # ---------------------------------------------------------------------------- # 各种安装方法 # ---------------------------------------------------------------------------- have_sudo() { [[ -n "$SUDO_CMD" || $EUID -eq 0 ]] } install_via_apt() { local pkg="$1" [[ -z "$pkg" ]] && return 1 if ! have_sudo; then warning "无 root 权限, 跳过 apt 安装: $pkg" return 1 fi # 检查包是否已存在 if log_run $SUDO_CMD apt-cache show "$pkg"; then note "apt 安装: $pkg" log_run $SUDO_CMD DEBIAN_FRONTEND=noninteractive apt-get install -y "$pkg" return $? fi return 1 } # 是否为可尝试安装的包名 (排除包装器/路径/内部命令) is_installable_name() { local n="$1" [[ -n "$n" ]] || return 1 case "$n" in python3|/bin/bash|sh|internal:*|*/*|*:*|analyzeHeadless|linpeas.sh) return 1 ;; esac return 0 } pypi_exists() { local pkg="$1" [[ -n "$pkg" ]] || return 1 log_run curl -fsSL "https://pypi.org/pypi/${pkg}/json" -o /dev/null } # 依次尝试多个候选名, 去重 try_install_brew() { local seen="" c for c in "$@"; do is_installable_name "$c" || continue [[ " $seen " == *" $c "* ]] && continue seen="$seen $c" install_via_brew "$c" && return 0 done return 1 } try_install_pip() { local explicit="$1" seen="" c shift # 映射表 pip 列: 直接安装, 不探测 PyPI if [[ -n "$explicit" ]] && is_installable_name "$explicit"; then install_via_pip "$explicit" && return 0 fi # 自动探测: name / cmd / apt 列, 须在 PyPI 存在 for c in "$@"; do is_installable_name "$c" || continue [[ " $seen " == *" $c "* ]] && continue seen="$seen $c" pypi_exists "$c" || continue install_via_pip "$c" && return 0 done return 1 } install_via_brew() { local pkg="$1" [[ -z "$pkg" ]] && return 1 if ! command -v brew >/dev/null 2>&1; then return 1 fi if brew list "$pkg" &>/dev/null; then note "brew 已安装: $pkg" return 0 fi if ! brew info "$pkg" &>/dev/null; then return 1 fi note "brew 安装: $pkg" log_run brew install "$pkg" return $? } install_via_pip() { local pkg="$1" [[ -z "$pkg" ]] && return 1 note "pip 安装: $pkg" PIP_DISABLE_PIP_VERSION_CHECK=1 log_run pip3 install --index-url "$PIP_INDEX_URL" \ --break-system-packages --quiet "$pkg" local rc=$? if [[ $rc -ne 0 ]]; then log_run pip3 install --index-url "$PIP_INDEX_URL" --quiet "$pkg" rc=$? fi return $rc } install_via_gem() { local pkg="$1" [[ -z "$pkg" ]] && return 1 if ! command -v gem >/dev/null 2>&1; then warning "gem 不可用, 跳过: $pkg" return 1 fi if ! have_sudo; then warning "无 root 权限, 跳过 gem 安装: $pkg" return 1 fi note "gem 安装: $pkg" log_run $SUDO_CMD gem install "$pkg" --no-document return $? } install_via_go() { local pkg="$1" [[ -z "$pkg" ]] && return 1 if ! command -v go >/dev/null 2>&1; then warning "go 未安装, 跳过: $pkg" return 1 fi note "go install: $pkg" GOPROXY="$GOPROXY" log_run go install "$pkg@latest" local rc=$? # 确保 ~/go/bin 在 PATH 提示里 if [[ $rc -eq 0 ]] && [[ -d "$HOME/go/bin" ]]; then # 不强行修改用户 shell, 仅提示 : fi return $rc } install_via_github_release() { local repo="$1" local cmd="$2" [[ -z "$repo" ]] && return 1 if ! command -v curl >/dev/null 2>&1; then warning "curl 未安装, 无法下载 GitHub release" return 1 fi if ! have_sudo; then warning "无 root 权限, 跳过 GitHub release 安装: $repo" return 1 fi note "GitHub release 下载: $repo" local platform="${DISTRO_FAMILY:-linux}" local api="https://api.github.com/repos/${repo}/releases/latest" local tmp tmp=$(mktemp -d) if ! log_run curl -fsSL "$api" -o "$tmp/release.json"; then rm -rf "$tmp" return 1 fi local url url=$(python3 - "$tmp/release.json" "$platform" <<'PY' 2>/dev/null import json, re, sys data = json.load(open(sys.argv[1])) platform = sys.argv[2] assets = data.get("assets", []) if platform == "macos": patterns = [ r'darwin.*arm64', r'macos.*arm64', r'apple.*arm64', r'darwin.*amd64', r'macos.*amd64', r'darwin.*x86_64', r'darwin', r'macos', ] else: patterns = [ r'linux.*amd64', r'linux.*x86_64', r'linux.*64bit', r'.*linux.*x64', r'_linux$', r'linux', ] for p in patterns: for a in assets: n = a.get("name", "").lower() if re.search(p, n) and n.endswith(('.tar.gz', '.tgz', '.zip')): print(a["browser_download_url"]) sys.exit(0) for a in assets: n = a.get("name", "").lower() if n.endswith(('.tar.gz', '.tgz', '.zip')): print(a["browser_download_url"]) sys.exit(0) PY ) if [[ -z "$url" ]]; then rm -rf "$tmp" warning "未找到合适的 release 资产: $repo (platform=$platform)" return 1 fi local fname fname=$(basename "$url") note "下载: $url" if ! log_run curl -fsSL "$url" -o "$tmp/$fname"; then rm -rf "$tmp" return 1 fi # 解压 case "$fname" in *.tar.gz|*.tgz) tar -xzf "$tmp/$fname" -C "$tmp" 2>/dev/null || { rm -rf "$tmp"; return 1; } ;; *.zip) unzip -q "$tmp/$fname" -d "$tmp" 2>/dev/null || { rm -rf "$tmp"; return 1; } ;; *) rm -rf "$tmp"; return 1 ;; esac # 找可执行文件 local exec_target="" if [[ -n "$cmd" ]]; then exec_target=$(find "$tmp" -type f -name "$cmd" -executable 2>/dev/null | head -1) fi if [[ -z "$exec_target" ]]; then # 退一步: 找任意可执行文件 exec_target=$(find "$tmp" -type f -executable 2>/dev/null \ | grep -Ev '\.(md|txt|json|yaml|yml|sum|sha|pem|1)$' \ | head -1) fi if [[ -z "$exec_target" ]]; then rm -rf "$tmp" warning "解压后未找到可执行文件: $repo" return 1 fi log_run $SUDO_CMD install -m 0755 "$exec_target" "$INSTALL_PREFIX/$cmd" local rc=$? rm -rf "$tmp" return $rc } install_linpeas_script() { # linpeas 是单个 .sh 脚本, 直接 curl 下来放到 /usr/local/bin if ! command -v curl >/dev/null 2>&1; then return 1 fi if ! have_sudo; then warning "无 root 权限, 跳过 linpeas 下载" return 1 fi local tmpdir="${TMPDIR:-/tmp}" note "下载 linpeas.sh 到 $INSTALL_PREFIX/" if log_run curl -fsSL "https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh" \ -o "$tmpdir/linpeas.sh"; then log_run $SUDO_CMD install -m 0755 "$tmpdir/linpeas.sh" "$INSTALL_PREFIX/linpeas.sh" \ && { rm -f "$tmpdir/linpeas.sh"; return 0; } fi log_run curl -fsSL "https://raw.githubusercontent.com/peass-ng/PEASS-ng/master/linPEAS/linpeas.sh" \ -o "$tmpdir/linpeas.sh" \ && log_run $SUDO_CMD install -m 0755 "$tmpdir/linpeas.sh" "$INSTALL_PREFIX/linpeas.sh" local rc=$? rm -f "$tmpdir/linpeas.sh" return $rc } install_spectral() { # spectral 来自 npm if ! command -v npm >/dev/null 2>&1; then warning "npm 未安装, 无法装 spectral (请先装 Node.js)" return 1 fi note "npm 安装: @stoplight/spectral-cli" log_run npm install -g @stoplight/spectral-cli return $? } # ---------------------------------------------------------------------------- # 安装单个工具 # ---------------------------------------------------------------------------- install_tool() { local name="$1" local cmd cmd="$(resolve_cmd "$name")" local apt_pkg="${M[$name|apt]:-}" local pip_pkg="${M[$name|pip]:-}" local gem_pkg="${M[$name|gem]:-}" local go_pkg="${M[$name|go]:-}" local gh_repo="${M[$name|gh]:-}" local note="${M[$name|note]:-}" # 跳过内置/内部工具 if is_builtin_tool "$name"; then if [[ -z "$apt_pkg" && -z "$pip_pkg" && -z "$gh_repo" && -z "$go_pkg" ]]; then STATUS["$name"]="skip" SKIP_REASON["$name"]="内置工具, 运行时由 Python 包装" SKIP_COUNT=$((SKIP_COUNT+1)) return 0 fi fi # 已安装 if is_installed "$name" "$cmd"; then if [[ "$MODE" == "install" ]]; then STATUS["$name"]="skip" SKIP_REASON["$name"]="已存在 ($cmd)" SKIP_COUNT=$((SKIP_COUNT+1)) dim "$name: 已安装 ($cmd)" else STATUS["$name"]="ok" OK_COUNT=$((OK_COUNT+1)) fi return 0 fi if [[ "$MODE" == "check" || "$MODE" == "list" ]]; then STATUS["$name"]="fail" FAIL_COUNT=$((FAIL_COUNT+1)) return 0 fi if [[ "$MODE" == "dry-run" ]]; then echo " [DRY] $name (cmd=$cmd) -> apt/brew=$apt_pkg pip=$pip_pkg gem=$gem_pkg go=$go_pkg gh=$gh_repo" STATUS["$name"]="dry" return 0 fi # 选择安装方法 local tried=() local rc=1 if [[ -z "$FORCE_METHOD" || "$FORCE_METHOD" == "apt" ]]; then if [[ "$PKG_MGR" == "apt" && -n "$apt_pkg" ]]; then tried+=("apt") install_via_apt "$apt_pkg" && { rc=0; METHOD["$name"]="apt"; } fi fi if [[ $rc -ne 0 && ( -z "$FORCE_METHOD" || "$FORCE_METHOD" == "brew" ) ]]; then if [[ "$PKG_MGR" == "brew" ]]; then tried+=("brew") try_install_brew "$apt_pkg" "$name" "$cmd" && { rc=0; METHOD["$name"]="brew"; } fi fi if [[ $rc -ne 0 && ( -z "$FORCE_METHOD" || "$FORCE_METHOD" == "pip" ) ]]; then tried+=("pip") try_install_pip "$pip_pkg" "$name" "$cmd" "$apt_pkg" \ && { rc=0; METHOD["$name"]="pip"; } fi if [[ $rc -ne 0 && ( -z "$FORCE_METHOD" || "$FORCE_METHOD" == "gem" ) ]]; then if [[ -n "$gem_pkg" ]]; then tried+=("gem") install_via_gem "$gem_pkg" && { rc=0; METHOD["$name"]="gem"; } fi fi if [[ $rc -ne 0 && ( -z "$FORCE_METHOD" || "$FORCE_METHOD" == "go" ) ]]; then if [[ -n "$go_pkg" ]]; then tried+=("go") install_via_go "$go_pkg" && { rc=0; METHOD["$name"]="go"; } fi fi if [[ $rc -ne 0 && ( -z "$FORCE_METHOD" || "$FORCE_METHOD" == "github" ) ]]; then if [[ -n "$gh_repo" ]]; then tried+=("github") install_via_github_release "$gh_repo" "$cmd" && { rc=0; METHOD["$name"]="github"; } fi fi # 特殊情况 if [[ $rc -ne 0 ]]; then case "$name" in linpeas) install_linpeas_script && { rc=0; METHOD["$name"]="github-script"; } ;; api-schema-analyzer) install_spectral && { rc=0; METHOD["$name"]="npm"; } ;; esac fi if [[ $rc -eq 0 ]] && is_installed "$name" "$cmd"; then STATUS["$name"]="ok" OK_COUNT=$((OK_COUNT+1)) success "$name 安装成功 (${METHOD[$name]:-unknown})" return 0 fi STATUS["$name"]="fail" FAIL_COUNT=$((FAIL_COUNT+1)) if [[ -n "$note" ]]; then warning "$name 安装失败 (尝试: ${tried[*]:-无}). 备注: $note" else warning "$name 安装失败 (尝试: ${tried[*]:-无})" fi return 1 } # ---------------------------------------------------------------------------- # 过滤: --only / --skip # ---------------------------------------------------------------------------- should_handle() { local name="$1" if [[ -n "$ONLY_TOOLS" ]]; then [[ ",$ONLY_TOOLS," == *",$name,"* ]] && return 0 || return 1 fi if [[ -n "$SKIP_TOOLS" ]]; then [[ ",$SKIP_TOOLS," == *",$name,"* ]] && return 1 || return 0 fi return 0 } # ---------------------------------------------------------------------------- # 主流程 # ---------------------------------------------------------------------------- main() { detect_distro setup_sudo load_map discover_tools validate_tool_coverage echo "" echo "============================================================" echo " CyberStrikeAI 工具安装器" echo "============================================================" note "项目根: $ROOT_DIR" note "系统: $DISTRO_ID (family=$DISTRO_FAMILY, pkg=$PKG_MGR)" note "模式: $MODE" note "pip 源: $PIP_INDEX_URL" note "go 代理: $GOPROXY" if [[ -n "$FORCE_METHOD" ]]; then note "强制方式: $FORCE_METHOD"; fi [[ -n "$ONLY_TOOLS" ]] && note "白名单: $ONLY_TOOLS" [[ -n "$SKIP_TOOLS" ]] && note "黑名单: $SKIP_TOOLS" if [[ "$DISTRO_FAMILY" == "macos" && "$MODE" == "install" ]]; then warning "macOS 无 apt, 将自动尝试: brew → pip(PyPI 探测) → go → GitHub" fi echo "" # 累计工具列表 (按映射表的顺序) local names=() while IFS='|' read -r name _; do [[ -z "$name" || "$name" == \#* ]] && continue # 优先用 yaml 里的 (排除 yaml 关闭的) if [[ "${YAML_ENABLED[$name]:-true}" == "false" ]]; then STATUS["$name"]="skip" SKIP_REASON["$name"]="yaml 中 enabled=false" continue fi names+=("$name") done < <(get_tool_map) TOTAL=${#names[@]} if [[ "$MODE" == "list" ]]; then info "检测 $TOTAL 个工具状态..." echo "" else info "准备处理 $TOTAL 个工具..." echo "" fi local i=0 for name in "${names[@]}"; do i=$((i+1)) if ! should_handle "$name"; then STATUS["$name"]="skip" SKIP_REASON["$name"]="被 --skip / 未在 --only" SKIP_COUNT=$((SKIP_COUNT+1)) continue fi if [[ "$MODE" != "list" ]]; then printf "${GRAY}[%d/%d]${NC} " "$i" "$TOTAL" fi install_tool "$name" done if [[ "$MODE" == "list" ]]; then echo "" echo " 工具名 | 命令 | 状态 | 说明" echo " --------------------------------+--------------+-----------+--------" for name in "${names[@]}"; do local cmd note_text st st_color cmd="$(resolve_cmd "$name")" note_text="${M[$name|note]:-}" st="${STATUS[$name]:-?}" case "$st" in ok) st_color="${GREEN} OK ${NC}" ;; skip) st_color="${GRAY}SKIP${NC}" ;; fail) st_color="${RED}FAIL${NC}" ;; *) st_color="${YELLOW} ? ${NC}" ;; esac printf " %-32s | %-13s| %b | %s\n" "$name" "$cmd" "$st_color" "$note_text" done fi echo "" print_summary } print_summary() { echo "============================================================" echo " 安装结果汇总" echo "============================================================" if [[ "$MODE" == "check" || "$MODE" == "list" ]]; then info "总计: $TOTAL | ✅ 已就绪: $OK_COUNT | ⏭ 跳过: $SKIP_COUNT | ❌ 缺失: $FAIL_COUNT" else info "总计: $TOTAL | ✅ 成功: $OK_COUNT | ⏭ 跳过: $SKIP_COUNT | ❌ 失败: $FAIL_COUNT" fi echo "" if [[ $FAIL_COUNT -gt 0 ]]; then note "失败工具:" for name in "${!STATUS[@]}"; do [[ "${STATUS[$name]}" == "fail" ]] && echo " - $name" done | sort echo "" note "常见补救:" if [[ "$DISTRO_FAMILY" == "macos" ]]; then dim " • macOS: brew install <工具名> 或 pip3 install <工具名>" dim " • 部分工具仅 Kali/apt 提供, macOS 请用 pip/go 或手动安装" else dim " • 在 Kali 上先运行: sudo apt update" fi dim " • 确认外网可达, 或设置代理: HTTPS_PROXY=http://your-proxy:port" dim " • 大型工具如 ghidra/clair 可手动安装" dim " • API 类工具 (fofa/shodan/zoomeye/quake) 需自行申请并配置 API key" fi # PATH 提示 if [[ -d "$HOME/go/bin" ]] && [[ ":$PATH:" != *":$HOME/go/bin:"* ]]; then echo "" warning "go install 安装的二进制在: $HOME/go/bin" warning "请把它加入 PATH, 例如: echo 'export PATH=\$HOME/go/bin:\$PATH' >> ~/.bashrc" fi } main # check 模式: 有缺失工具时返回非零退出码 (便于 CI) if [[ "$MODE" == "check" && $FAIL_COUNT -gt 0 ]]; then exit 1 fi