Files
server-manager/tools/install.sh

418 lines
15 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────
# ServerManager AI Integration Installer for Linux/macOS (headless / no-GUI)
#
# Installs for each target home:
# - ssh.py + encryption.py -> ~/.server-connections/
# - Claude /ssh skill -> ~/.claude/commands/
# - Codex server-manager skill -> ~/.codex/skills/server-manager/
# - Gemini server-manager skill -> ~/.gemini/skills/server-manager/
# - codex-ssh / gemini-ssh wrappers -> ~/.server-connections/
# - CLAUDE.md / GEMINI.md (if available) -> ~/.claude/ / ~/.gemini/
#
# Optional per-target local config copy:
# - servers.json + settings.json -> ~/.server-connections/
#
# Notes:
# - servers.json is NEVER downloaded remotely.
# - --all-users installs code/skills/wrappers for discovered homes, but skips
# copying servers.json to avoid replicating credentials between users.
# - Gemini also supports ~/.agents/skills, but this installer avoids placing
# the same skill in both ~/.gemini/skills and ~/.agents/skills by default
# because Gemini reports that as a duplicate-skill conflict.
#
# Usage:
# bash install.sh
# bash install.sh /path/to/server-manager
# bash install.sh --source-dir /path/to/server-manager --target-home /root
# bash install.sh --all-users --source-dir /path/to/server-manager
# ─────────────────────────────────────────────────────────────────────
set -euo pipefail
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; }
step() { echo -e "\n${CYAN}━━━ $* ━━━${NC}"; }
usage() {
cat <<USAGE
ServerManager AI integration installer
Options:
--source-dir PATH Use local repo as source of files
--target-home PATH Install into a specific user's home
--all-users Install into all discovered user homes on this machine
--install-agents-mirror Also mirror Gemini skill into ~/.agents/skills
-h, --help Show this help
Positional compatibility:
install.sh /path/to/server-manager # same as --source-dir
USAGE
}
GITEA_RAW="https://git.sensey24.ru/aibot777/server-manager/raw/branch/master"
SRC_DIR=""
TARGET_HOME="${SERVER_MANAGER_TARGET_HOME:-${TARGET_HOME:-$HOME}}"
INSTALL_ALL_USERS=0
INSTALL_AGENTS_MIRROR=0
while [[ $# -gt 0 ]]; do
case "$1" in
--source-dir)
SRC_DIR="$2"
shift 2
;;
--target-home)
TARGET_HOME="$2"
shift 2
;;
--all-users)
INSTALL_ALL_USERS=1
shift
;;
--install-agents-mirror)
INSTALL_AGENTS_MIRROR=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
if [[ -z "$SRC_DIR" ]]; then
SRC_DIR="$1"
shift
else
error "Неизвестный аргумент: $1"
usage
exit 2
fi
;;
esac
done
echo -e "${CYAN}"
echo "╔══════════════════════════════════════════════════════╗"
echo "║ ServerManager AI Integration Installer (headless) ║"
echo "║ Claude + Codex + Gemini ║"
echo "╚══════════════════════════════════════════════════════╝"
echo -e "${NC}"
step "1/5 Проверка Python"
PYTHON=""
for cmd in python3 python; do
if command -v "$cmd" &>/dev/null; then
if "$cmd" - <<'PY' &>/dev/null
import sys
raise SystemExit(0 if sys.version_info >= (3, 8) else 1)
PY
then
PYTHON="$cmd"
ok "Python найден: $($cmd --version)"
break
fi
fi
done
if [[ -z "$PYTHON" ]]; then
error "Python 3.8+ не найден"
exit 1
fi
PIP=""
for cmd in pip3 pip; do
if command -v "$cmd" &>/dev/null; then
PIP="$cmd"
break
fi
done
if [[ -z "$PIP" ]]; then
if $PYTHON -m pip --version &>/dev/null; then
PIP="$PYTHON -m pip"
else
error "pip не найден"
exit 1
fi
fi
ok "pip найден: $($PIP --version 2>&1 | head -1)"
resolve_home() {
"$PYTHON" - "$1" <<'PY'
import os, sys
print(os.path.abspath(os.path.expanduser(sys.argv[1])))
PY
}
TARGET_HOME="$(resolve_home "$TARGET_HOME")"
step "2/5 Установка Python-зависимостей"
CLI_DEPS=(
"paramiko>=3.4.0"
"cryptography>=41.0.0"
"pymysql>=1.1.0"
"psycopg2-binary>=2.9.9"
"redis>=5.0.0"
"requests>=2.31.0"
)
for dep in "${CLI_DEPS[@]}"; do
pkg=$(echo "$dep" | sed 's/[>=<].*//')
if $PYTHON -c "import $pkg" 2>/dev/null; then
ok "$pkg уже установлен"
else
info "Устанавливаю $dep..."
if $PIP install "$dep" --quiet 2>/dev/null; then
ok "$dep установлен"
else
warn "Не удалось установить $dep (попробуйте: $PIP install $dep)"
fi
fi
done
copy_or_download() {
local src_relative="$1"
local dst="$2"
local perms="$3"
local desc="$4"
mkdir -p "$(dirname "$dst")"
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/$src_relative" ]]; then
cp "$SRC_DIR/$src_relative" "$dst"
chmod "$perms" "$dst" 2>/dev/null || true
ok "$desc (из $SRC_DIR)"
return 0
fi
local url="$GITEA_RAW/$src_relative"
if command -v curl &>/dev/null; then
if curl -fsSL -o "$dst" "$url" 2>/dev/null; then
if [[ -s "$dst" ]] && ! head -1 "$dst" | grep -qi '<!doctype\|<html'; then
chmod "$perms" "$dst" 2>/dev/null || true
ok "$desc (скачан с Gitea)"
return 0
fi
rm -f "$dst"
fi
elif command -v wget &>/dev/null; then
if wget -q -O "$dst" "$url" 2>/dev/null; then
if [[ -s "$dst" ]] && ! head -1 "$dst" | grep -qi '<!doctype\|<html'; then
chmod "$perms" "$dst" 2>/dev/null || true
ok "$desc (скачан с Gitea)"
return 0
fi
rm -f "$dst"
fi
fi
warn "$desc — не удалось скачать. Скопируйте вручную."
return 1
}
install_skill_tree() {
local prefix="$1"
local dst_root="$2"
shift 2
mkdir -p "$dst_root"
local rel
for rel in "$@"; do
copy_or_download "$prefix/$rel" "$dst_root/$rel" 644 "$prefix/$rel" || true
done
find "$dst_root/scripts" -type f -name '*.sh' -exec chmod 755 {} + 2>/dev/null || true
find "$dst_root/scripts" -type f -name '*.cmd' -exec chmod 644 {} + 2>/dev/null || true
}
discover_homes() {
local homes=()
local uname_s
uname_s="$(uname -s 2>/dev/null || echo Linux)"
if [[ "$INSTALL_ALL_USERS" -eq 1 ]]; then
if [[ "$uname_s" == "Darwin" ]]; then
[[ -d /var/root ]] && homes+=("/var/root")
if [[ -d /Users ]]; then
while IFS= read -r -d '' d; do homes+=("$d"); done < <(find /Users -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
fi
else
[[ -d /root ]] && homes+=("/root")
if [[ -d /home ]]; then
while IFS= read -r -d '' d; do homes+=("$d"); done < <(find /home -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
fi
fi
else
homes+=("$TARGET_HOME")
fi
printf '%s\n' "${homes[@]}" | awk 'NF && !seen[$0]++'
}
step "3/5 Подготовка директорий"
TARGET_HOMES=()
while IFS= read -r home; do
[[ -n "$home" ]] || continue
TARGET_HOMES+=("$home")
ok "target home: $home"
done < <(discover_homes)
if [[ "${#TARGET_HOMES[@]}" -eq 0 ]]; then
error "Не удалось определить target home"
exit 1
fi
step "4/5 Установка файлов"
CODEX_SKILL_FILES=(
"SKILL.md"
"references/command-matrix.md"
"references/project.md"
"scripts/codex-ssh-wrapper.sh"
"scripts/codex-ssh-wrapper.cmd"
"scripts/server-manager-doctor.sh"
"scripts/server-manager-doctor.cmd"
)
GEMINI_SKILL_FILES=(
"SKILL.md"
"references/command-matrix.md"
"references/project.md"
"scripts/gemini-ssh-wrapper.sh"
"scripts/gemini-ssh-wrapper.cmd"
"scripts/server-manager-gemini-doctor.sh"
"scripts/server-manager-gemini-doctor.cmd"
)
for HOME_DIR in "${TARGET_HOMES[@]}"; do
CONN_DIR="$HOME_DIR/.server-connections"
CLAUDE_DIR="$HOME_DIR/.claude"
COMMANDS_DIR="$CLAUDE_DIR/commands"
CODEX_DIR="$HOME_DIR/.codex/skills/server-manager"
GEMINI_DIR="$HOME_DIR/.gemini"
GEMINI_SKILL_DIR="$GEMINI_DIR/skills/server-manager"
AGENTS_DIR="$HOME_DIR/.agents/skills/server-manager"
mkdir -p "$CONN_DIR" "$COMMANDS_DIR" "$CODEX_DIR" "$GEMINI_SKILL_DIR"
chmod 700 "$CONN_DIR" 2>/dev/null || true
info "Устанавливаю в $HOME_DIR"
copy_or_download "tools/ssh.py" "$CONN_DIR/ssh.py" 755 "ssh.py"
copy_or_download "core/encryption.py" "$CONN_DIR/encryption.py" 644 "encryption.py"
copy_or_download "tools/skill-ssh.md" "$COMMANDS_DIR/ssh.md" 644 "ssh.md (скилл /ssh)"
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/CLAUDE.md" ]]; then
cp "$SRC_DIR/CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md"
chmod 644 "$CLAUDE_DIR/CLAUDE.md"
ok "CLAUDE.md"
elif [[ ! -f "$CLAUDE_DIR/CLAUDE.md" ]]; then
copy_or_download "CLAUDE.md" "$CLAUDE_DIR/CLAUDE.md" 644 "CLAUDE.md" || true
fi
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/GEMINI.md" ]]; then
cp "$SRC_DIR/GEMINI.md" "$GEMINI_DIR/GEMINI.md"
chmod 644 "$GEMINI_DIR/GEMINI.md"
ok "GEMINI.md"
elif [[ ! -f "$GEMINI_DIR/GEMINI.md" ]]; then
copy_or_download "GEMINI.md" "$GEMINI_DIR/GEMINI.md" 644 "GEMINI.md" || true
fi
install_skill_tree ".codex/skills/server-manager" "$CODEX_DIR" "${CODEX_SKILL_FILES[@]}"
install_skill_tree ".gemini/skills/server-manager" "$GEMINI_SKILL_DIR" "${GEMINI_SKILL_FILES[@]}"
if [[ "$INSTALL_AGENTS_MIRROR" -eq 1 ]]; then
mkdir -p "$AGENTS_DIR"
install_skill_tree ".gemini/skills/server-manager" "$AGENTS_DIR" "${GEMINI_SKILL_FILES[@]}"
ok "agents skill mirror"
elif [[ -d "$AGENTS_DIR" ]]; then
rm -rf "$AGENTS_DIR"
ok "removed stale agents skill mirror to avoid Gemini conflict"
fi
if [[ -f "$CODEX_DIR/scripts/codex-ssh-wrapper.sh" ]]; then
cp "$CODEX_DIR/scripts/codex-ssh-wrapper.sh" "$CONN_DIR/codex-ssh"
chmod 755 "$CONN_DIR/codex-ssh"
ok "codex-ssh wrapper"
else
copy_or_download ".codex/skills/server-manager/scripts/codex-ssh-wrapper.sh" "$CONN_DIR/codex-ssh" 755 "codex-ssh wrapper" || true
fi
if [[ -f "$GEMINI_SKILL_DIR/scripts/gemini-ssh-wrapper.sh" ]]; then
cp "$GEMINI_SKILL_DIR/scripts/gemini-ssh-wrapper.sh" "$CONN_DIR/gemini-ssh"
chmod 755 "$CONN_DIR/gemini-ssh"
ok "gemini-ssh wrapper"
else
copy_or_download ".gemini/skills/server-manager/scripts/gemini-ssh-wrapper.sh" "$CONN_DIR/gemini-ssh" 755 "gemini-ssh wrapper" || true
fi
if [[ "$INSTALL_ALL_USERS" -eq 0 ]]; then
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/servers.json" ]]; then
cp "$SRC_DIR/servers.json" "$CONN_DIR/servers.json"
chmod 600 "$CONN_DIR/servers.json"
ok "servers.json (зашифрованный)"
elif [[ ! -f "$CONN_DIR/servers.json" ]]; then
warn "servers.json не найден для $HOME_DIR — скопируйте вручную"
fi
if [[ -n "$SRC_DIR" && -f "$SRC_DIR/settings.json" ]]; then
cp "$SRC_DIR/settings.json" "$CONN_DIR/settings.json"
chmod 600 "$CONN_DIR/settings.json"
ok "settings.json"
elif [[ ! -f "$CONN_DIR/settings.json" ]]; then
echo '{"language":"en","check_interval":60}' > "$CONN_DIR/settings.json"
chmod 600 "$CONN_DIR/settings.json"
ok "settings.json (создан по умолчанию)"
fi
else
warn "all-users mode: servers.json/settings.json не копируются автоматически для $HOME_DIR"
fi
done
step "5/5 Проверка установки"
ALL_OK=true
for HOME_DIR in "${TARGET_HOMES[@]}"; do
CONN_DIR="$HOME_DIR/.server-connections"
COMMANDS_DIR="$HOME_DIR/.claude/commands"
CODEX_DIR="$HOME_DIR/.codex/skills/server-manager"
GEMINI_SKILL_DIR="$HOME_DIR/.gemini/skills/server-manager"
info "Проверка $HOME_DIR"
[[ -x "$CONN_DIR/ssh.py" ]] && ok "ssh.py — исполняемый" || { error "ssh.py — не найден или не исполняемый"; ALL_OK=false; }
[[ -f "$CONN_DIR/encryption.py" ]] && ok "encryption.py" || { error "encryption.py — не найден"; ALL_OK=false; }
[[ -f "$COMMANDS_DIR/ssh.md" ]] && ok "Claude /ssh skill" || warn "Claude /ssh skill — не найден"
[[ -f "$CODEX_DIR/SKILL.md" ]] && ok "Codex skill" || { warn "Codex skill — не найден"; ALL_OK=false; }
[[ -x "$CONN_DIR/codex-ssh" ]] && ok "codex-ssh wrapper" || { warn "codex-ssh wrapper — не найден"; ALL_OK=false; }
[[ -f "$GEMINI_SKILL_DIR/SKILL.md" ]] && ok "Gemini skill" || { warn "Gemini skill — не найден"; ALL_OK=false; }
[[ -x "$CONN_DIR/gemini-ssh" ]] && ok "gemini-ssh wrapper" || { warn "gemini-ssh wrapper — не найден"; ALL_OK=false; }
done
echo ""
echo -e "${CYAN}━━━ Готово ━━━${NC}"
echo ""
if $ALL_OK; then
echo -e "${GREEN}Установка завершена успешно!${NC}"
else
echo -e "${YELLOW}Установка завершена с предупреждениями.${NC}"
fi
echo ""
echo "Установлено для home:"
printf ' - %s\n' "${TARGET_HOMES[@]}"
echo ""
echo "Использование:"
echo " python3 ~/.server-connections/ssh.py --list"
echo " ~/.server-connections/codex-ssh --list"
echo " ~/.server-connections/gemini-ssh --list"
echo ""
echo "Claude skill: ~/.claude/commands/ssh.md"
echo "Codex skill: ~/.codex/skills/server-manager/"
echo "Gemini skill: ~/.gemini/skills/server-manager/"
if [[ "$INSTALL_AGENTS_MIRROR" -eq 1 ]]; then
echo "Mirror skill: ~/.agents/skills/server-manager/"
fi