diff --git a/scripts/ai-setup.sh b/scripts/ai-setup.sh index 37810d5..bde7973 100755 --- a/scripts/ai-setup.sh +++ b/scripts/ai-setup.sh @@ -17,6 +17,29 @@ PROXY_BIN="$BIN_DIR/claude-code-proxy" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" GLOBAL_RULES_SOURCE="$SCRIPT_DIR/home-configs/GLOBAL_RULES.md" +# ── 0. Определение ОС и архитектуры (глобально) ───────────── +# bash 3.2 на macOS: lowercasing через case, не через ${var,,} +OS_KERNEL="$(uname -s)" # Linux | Darwin | ... +OS_TAG="" +ARCH_TAG="" +case "$OS_KERNEL" in + Linux) OS_TAG="linux" ;; + Darwin) OS_TAG="darwin" ;; + *) OS_TAG="" ;; +esac +case "$(uname -m)" in + x86_64) ARCH_TAG="amd64" ;; + arm64|aarch64) ARCH_TAG="arm64" ;; + *) ARCH_TAG="" ;; +esac + +# Портабельный sed -i: BSD (macOS) требует суффикс, GNU — нет +if [ "$OS_KERNEL" = "Darwin" ]; then + SED_INPLACE=(-i '') +else + SED_INPLACE=(-i) +fi + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' info() { echo -e "${CYAN}[INFO]${NC} $*"; } success() { echo -e "${GREEN}[OK]${NC} $*"; } @@ -30,6 +53,20 @@ if [ "$EUID" -eq 0 ]; then exit 1 fi +# ── Homebrew shellenv (macOS) ─────────────────────────────── +# brew может быть установлен, но не в PATH текущего шелла (особенно +# после свежей установки). Подключаем shellenv, чтобы node/npm/jq +# стали видны через command -v в этой же сессии. +if [ "$OS_KERNEL" = "Darwin" ]; then + for _brew_prefix in /opt/homebrew /usr/local "$HOME/.homebrew"; do + if [ -x "$_brew_prefix/bin/brew" ]; then + eval "$("$_brew_prefix/bin/brew" shellenv)" + break + fi + done + unset _brew_prefix +fi + info "Проверяю зависимости (python3)..." if ! command -v python3 &>/dev/null; then err "Python 3 не установлен, но он требуется для работы скрипта." @@ -38,8 +75,10 @@ success "Python 3 найден" info "Проверяю зависимости (jq)..." if ! command -v jq &>/dev/null; then - info "Устанавливаю jq (нужен sudo)..." - if command -v apt-get &>/dev/null; then + info "Устанавливаю jq..." + if command -v brew &>/dev/null; then + brew install jq + elif command -v apt-get &>/dev/null; then sudo apt-get install -y jq elif command -v dnf &>/dev/null; then sudo dnf install -y jq @@ -53,25 +92,19 @@ else warn "jq не найден. statusline-command.sh и некоторые функции могут не работать." fi -# ── 1. npm prefix в домашнюю папку ────────────────────────── -info "Настраиваю npm prefix..." -mkdir -p "$NPM_GLOBAL" -npm config set prefix "$NPM_GLOBAL" -success "npm prefix -> $NPM_GLOBAL" - -export PATH="$NPM_GLOBAL/bin:$BIN_DIR:$PATH" - -# ── 2. Node.js ─────────────────────────────────────────────── +# ── 1. Node.js ─────────────────────────────────────────────── info "Проверяю Node.js..." if ! command -v node &>/dev/null; then - info "Попытка установки Node.js (нужен sudo)..." - if command -v apt-get &>/dev/null; then + info "Пытаюсь установить Node.js..." + if command -v brew &>/dev/null; then + brew install node + elif command -v apt-get &>/dev/null; then curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt-get install -y nodejs elif command -v dnf &>/dev/null; then sudo dnf install -y nodejs else - warn "Не удалось определить пакетный менеджер. Установите Node.js вручную." + warn "Не удалось определить пакетный менеджер. Установите Homebrew (https://brew.sh) или Node.js вручную." fi fi if command -v node &>/dev/null; then @@ -80,6 +113,19 @@ else warn "Node.js не найден. Некоторые функции могут не работать." fi +# ── 2. npm prefix в домашнюю папку ────────────────────────── +# После установки Node, чтобы npm гарантированно был доступен. +info "Настраиваю npm prefix..." +mkdir -p "$NPM_GLOBAL" +if command -v npm &>/dev/null; then + npm config set prefix "$NPM_GLOBAL" + success "npm prefix -> $NPM_GLOBAL" +else + warn "npm не найден — пропускаю настройку prefix (установите Node.js)." +fi + +export PATH="$NPM_GLOBAL/bin:$BIN_DIR:$PATH" + # ── 3. Claude Code ─────────────────────────────────────────── info "Проверяю Claude Code..." if ! command -v claude &>/dev/null; then @@ -99,12 +145,9 @@ mkdir -p "$BIN_DIR" install_proxy() { info "Устанавливаю claude-code-proxy..." - ARCH=$(uname -m) - case "$ARCH" in - x86_64) ARCH_TAG="amd64" ;; - aarch64) ARCH_TAG="arm64" ;; - *) err "Неизвестная архитектура: $ARCH" ;; - esac + if [ -z "$OS_TAG" ] || [ -z "$ARCH_TAG" ]; then + err "Неизвестная платформа: kernel=$OS_KERNEL arch=$ARCH_TAG" + fi LATEST=$(curl -fsSL "https://api.github.com/repos/raine/claude-code-proxy/releases/latest" \ | grep '"tag_name"' | sed 's/.*"tag_name": *"\(.*\)".*/\1/') @@ -112,7 +155,7 @@ install_proxy() { TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT - URL="https://github.com/raine/claude-code-proxy/releases/download/${LATEST}/claude-code-proxy-linux-${ARCH_TAG}.tar.gz" + URL="https://github.com/raine/claude-code-proxy/releases/download/${LATEST}/claude-code-proxy-${OS_TAG}-${ARCH_TAG}.tar.gz" info "Скачиваю $URL" curl -fsSL "$URL" -o "$TMP/proxy.tar.gz" || err "Не удалось скачать claude-code-proxy" tar -xzf "$TMP/proxy.tar.gz" -C "$TMP" @@ -590,7 +633,11 @@ PATHEOF fi } add_path_to_rc "$HOME/.bashrc" -[ -f "$HOME/.zshrc" ] && add_path_to_rc "$HOME/.zshrc" +# На macOS дефолтный шелл — zsh, но ~/.zshrc может не существовать. +# Гарантируем наличие файла, иначе PATH не добавится и лаунчеры будут +# недоступны без ручного `exec`. +[ -f "$HOME/.zshrc" ] || touch "$HOME/.zshrc" +add_path_to_rc "$HOME/.zshrc" # ── 8. Генерация Standalone скриптов ──────────────────────── @@ -983,6 +1030,7 @@ PYEOF #!/usr/bin/env bash # Minimal task-completion sound fallback ( + afplay /System/Library/Sounds/Glass.aiff 2>/dev/null || paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null @@ -1074,6 +1122,7 @@ _setup_codex_sound_hooks() { #!/usr/bin/env bash # Minimal Codex turn-completion sound. ( + afplay /System/Library/Sounds/Glass.aiff 2>/dev/null || paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null @@ -1084,6 +1133,7 @@ SOUNDEOF #!/usr/bin/env bash # Minimal Codex context auto-compact warning sound. ( + afplay /System/Library/Sounds/Basso.aiff 2>/dev/null || paplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null || pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null || aplay /usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga 2>/dev/null @@ -1746,6 +1796,7 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/ai-api-helpers.sh" 2>/dev/ "$agy_bin" --dangerously-skip-permissions "$@" agy_exit=$? ( + afplay /System/Library/Sounds/Glass.aiff 2>/dev/null || paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null @@ -1754,7 +1805,7 @@ exit $agy_exit GEMINIEOF chmod +x "$BIN_DIR/ai-gemini" # Подменяем путь к agy, если BIN_DIR отличается от ~/.local/bin -[ "$BIN_DIR" != "$HOME/.local/bin" ] && sed -i "s|\$HOME/\.local/bin|\$HOME/${BIN_DIR#$HOME/}|g" "$BIN_DIR/ai-gemini" +[ "$BIN_DIR" != "$HOME/.local/bin" ] && sed "${SED_INPLACE[@]}" "s|\$HOME/\.local/bin|\$HOME/${BIN_DIR#$HOME/}|g" "$BIN_DIR/ai-gemini" # === ai-cursor === cat > "$BIN_DIR/ai-cursor" << 'CURSOREOF' @@ -1790,6 +1841,7 @@ _build_ai_sys_prompt > /dev/null "$cursor_bin" --force "$@" cursor_exit=$? ( + afplay /System/Library/Sounds/Glass.aiff 2>/dev/null || paplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || pw-play --volume=1 /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null || aplay /usr/share/sounds/freedesktop/stereo/complete.oga 2>/dev/null @@ -1798,7 +1850,7 @@ exit $cursor_exit CURSOREOF chmod +x "$BIN_DIR/ai-cursor" # Подменяем путь к agent, если BIN_DIR отличается от ~/.local/bin -[ "$BIN_DIR" != "$HOME/.local/bin" ] && sed -i "s|\$HOME/\.local/bin|\$HOME/${BIN_DIR#$HOME/}|g" "$BIN_DIR/ai-cursor" +[ "$BIN_DIR" != "$HOME/.local/bin" ] && sed "${SED_INPLACE[@]}" "s|\$HOME/\.local/bin|\$HOME/${BIN_DIR#$HOME/}|g" "$BIN_DIR/ai-cursor" # === ai-claude === cat > "$BIN_DIR/ai-claude" << 'CLAUDEEOF' diff --git a/setup.sh b/setup.sh index e2bafe1..ee867db 100755 --- a/setup.sh +++ b/setup.sh @@ -3,6 +3,11 @@ # Мастер-скрипт. Запускай от обычного пользователя (sudo попросит сам где нужно). cd "$(dirname "$0")" +# Платформа: на macOS сетевые функции (ru-bypass/kill switch) неприменимы +# (они построены на Linux ipset/ufw/systemd; Amnezia на маке — клиент с utun). +IS_MAC=0 +[ "$(uname -s)" = "Darwin" ] && IS_MAC=1 + BLD='\033[1m' GRN='\033[0;32m' YEL='\033[0;33m' @@ -14,18 +19,25 @@ LOG="$HOME/.config/ai-setup/setup.log" _log() { printf '%s [%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" "$2" >> "$LOG"; } if command -v whiptail >/dev/null 2>&1; then + MENU_ARGS=() + MENU_ARGS+=("1" "AI-инструменты (установить лаунчеры + ключи)") + if [ "$IS_MAC" -eq 0 ]; then + MENU_ARGS+=("2" "Сеть: ru-bypass + kill switch") + fi + MENU_ARGS+=("" "─── Дополнительно ───────────────────────────") + if [ "$IS_MAC" -eq 0 ]; then + MENU_ARGS+=("3" "Отключить kill switch (прямой доступ без VPN)") + MENU_ARGS+=("4" "Включить kill switch (восстановить защиту)") + fi + MENU_ARGS+=("5" "Статус (Amnezia, UFW, AI инструменты, ключи)") + if [ "$IS_MAC" -eq 0 ]; then + MENU_ARGS+=("6" "Проверить сеть (маршрутизация, geo)") + fi + MENU_ARGS+=("7" "Обновить (git pull + перегенерация скриптов)") + MENU_ARGS+=("8" "Android CLI (установить Google Android CLI)") choice=$(whiptail --title "AI Setup" \ --menu "Выбери действие (стрелки + Enter):" 24 70 10 \ - "1" "AI-инструменты (установить лаунчеры + ключи)" \ - "2" "Сеть: ru-bypass + kill switch" \ - "" "─── Дополнительно ───────────────────────────" \ - "3" "Отключить kill switch (прямой доступ без VPN)" \ - "4" "Включить kill switch (восстановить защиту)" \ - "5" "Статус (Amnezia, UFW, AI инструменты, ключи)" \ - "6" "Проверить сеть (маршрутизация, geo)" \ - "7" "Обновить (git pull + перегенерация скриптов)" \ - "8" "Android CLI (установить Google Android CLI)" \ - 3>&1 1>&2 2>&3) || exit 0 + "${MENU_ARGS[@]}" 3>&1 1>&2 2>&3) || exit 0 echo "" else echo "" @@ -37,26 +49,32 @@ else echo -e " ${GRY}Устанавливает ai-claude, ai-gpt, ai-deepseek, ai-kimi, ai-glm, ai-gemini и др.${CLR}" echo -e " ${GRY}Запрашивает API-ключи. Запускать один раз.${CLR}" echo "" - echo -e " ${BLD}2) Сеть: ru-bypass + kill switch${CLR}" - echo -e " ${GRY}.ru сайты (ozon, госуслуги) — напрямую с российским IP.${CLR}" - echo -e " ${GRY}*.loc офисные адреса — тоже напрямую.${CLR}" - echo -e " ${GRY}Всё остальное — только через Amnezia (kill switch).${CLR}" - echo -e " ${GRY}Запускать один раз на каждой машине.${CLR}" - echo "" + if [ "$IS_MAC" -eq 0 ]; then + echo -e " ${BLD}2) Сеть: ru-bypass + kill switch${CLR}" + echo -e " ${GRY}.ru сайты (ozon, госуслуги) — напрямую с российским IP.${CLR}" + echo -e " ${GRY}*.loc офисные адреса — тоже напрямую.${CLR}" + echo -e " ${GRY}Всё остальное — только через Amnezia (kill switch).${CLR}" + echo -e " ${GRY}Запускать один раз на каждой машине.${CLR}" + echo "" + fi echo -e "${YEL}Дополнительно (по необходимости):${CLR}" echo "" - echo -e " ${BLD}3) Отключить kill switch${CLR}" - echo -e " ${GRY}Временно — когда нужен прямой доступ без VPN (российский IP).${CLR}" - echo "" - echo -e " ${BLD}4) Включить kill switch${CLR}" - echo -e " ${GRY}Вернуть защиту обратно после отключения.${CLR}" - echo "" + if [ "$IS_MAC" -eq 0 ]; then + echo -e " ${BLD}3) Отключить kill switch${CLR}" + echo -e " ${GRY}Временно — когда нужен прямой доступ без VPN (российский IP).${CLR}" + echo "" + echo -e " ${BLD}4) Включить kill switch${CLR}" + echo -e " ${GRY}Вернуть защиту обратно после отключения.${CLR}" + echo "" + fi echo -e " ${BLD}5) Статус${CLR}" echo -e " ${GRY}Amnezia, UFW, сервисы, установленные AI инструменты и ключи.${CLR}" echo "" - echo -e " ${BLD}6) Проверить сеть${CLR}" - echo -e " ${GRY}Тесты маршрутизации: .ru напрямую, остальное через Amnezia.${CLR}" - echo "" + if [ "$IS_MAC" -eq 0 ]; then + echo -e " ${BLD}6) Проверить сеть${CLR}" + echo -e " ${GRY}Тесты маршрутизации: .ru напрямую, остальное через Amnezia.${CLR}" + echo "" + fi echo -e " ${BLD}7) Обновить${CLR}" echo -e " ${GRY}git pull + перегенерация всех скриптов в ~/bin (или ~/.local/bin).${CLR}" echo "" @@ -75,6 +93,7 @@ case "$choice" in bash scripts/ai-setup.sh ;; 2) + if [ "$IS_MAC" -eq 1 ]; then echo "Неприменимо на macOS"; exit 0; fi echo -e "${GRY}Нужно указать параметры твоей локальной сети:${CLR}" echo -e "${GRY} GATEWAY — IP домашнего/офисного роутера (через него пойдёт .ru трафик напрямую)${CLR}" echo -e "${GRY} DEV — сетевой интерфейс (wifi или провод), через который ты подключён к роутеру${CLR}" @@ -131,6 +150,7 @@ case "$choice" in sudo GATEWAY="$gw" DEV="$dev" LOCAL_DNS="$local_dns" AMNEZIA_SERVER="$amn_srv" KILL_SWITCH_EXCEPTIONS="$ks_exc" USER_HOME="$HOME" bash scripts/ru-bypass.sh ;; 3) + if [ "$IS_MAC" -eq 1 ]; then echo "Неприменимо на macOS"; exit 0; fi echo -e "${YEL}Перед этим выйди из Claude Code — сессия сменит IP.${CLR}" echo -n "Продолжить? [y/N] " read -r confirm @@ -138,12 +158,14 @@ case "$choice" in sudo USER_HOME="$HOME" bash scripts/ks-off.sh ;; 4) + if [ "$IS_MAC" -eq 1 ]; then echo "Неприменимо на macOS"; exit 0; fi sudo USER_HOME="$HOME" bash scripts/ks-on.sh ;; 5) echo -e "${BLD}=== Статус ===${CLR}" echo "" + if [ "$IS_MAC" -eq 0 ]; then echo -e "${BLD}Сеть:${CLR}" if ip link show amn0 &>/dev/null; then echo -e " ${GRN}✓${CLR} Amnezia (amn0) подключена" @@ -173,6 +195,12 @@ case "$choice" in fi echo "" + else + echo -e "${BLD}Сеть:${CLR}" + echo -e " ${GRY}(ru-bypass/kill switch неприменимы на macOS)${CLR}" + echo "" + fi + echo -e "${BLD}AI инструменты:${CLR}" for cmd in ai-claude ai-gpt ai-deepseek ai-kimi ai-glm ai-openrouter ai-opencode ai-gemini android; do if command -v "$cmd" &>/dev/null; then @@ -216,6 +244,7 @@ case "$choice" in fi ;; 6) + if [ "$IS_MAC" -eq 1 ]; then echo "Неприменимо на macOS"; exit 0; fi bash tests/test_network.sh ;; 7)