mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-10 16:47:51 +08:00
448 lines
15 KiB
Bash
Executable File
448 lines
15 KiB
Bash
Executable File
#!/bin/sh
|
|
# ============================================================================
|
|
# hermes-env — Hermes Agent environment installation management script
|
|
# Usage: hermes-env setup | check | upgrade
|
|
# Environment variables: HERMES_VERSION — specify version to install
|
|
# ============================================================================
|
|
|
|
# ── Version and path constants ─────────────────────────────────────────────
|
|
HERMES_BASE="/opt/hermes"
|
|
HERMES_VENV="${HERMES_BASE}/venv"
|
|
HERMES_DATA="${HERMES_BASE}/data"
|
|
HERMES_BIN="${HERMES_VENV}/bin/hermes"
|
|
PYTHON_BIN="${HERMES_VENV}/bin/python3"
|
|
HERMES_CONFIG="/root/.hermes/config.yaml"
|
|
|
|
# PyPI mirrors
|
|
HERMES_PYPI_CN="https://mirrors.aliyun.com/pypi/simple/"
|
|
HERMES_PYPI_OFFICIAL="https://pypi.org/simple/"
|
|
HERMES_PYPI_TSINGHUA="https://pypi.tuna.tsinghua.edu.cn/simple/"
|
|
|
|
# Default API endpoint
|
|
HERMES_DEFAULT_API="https://api.boos.lat/v1"
|
|
HERMES_DEFAULT_MODEL="opus4.6"
|
|
|
|
export PATH="${HERMES_VENV}/bin:$PATH"
|
|
|
|
# ── Logging tools ──────────────────────────────────────────────────────────
|
|
log_ok() { echo " [✓] $*"; }
|
|
log_warn() { echo " [!] $*"; }
|
|
log_err() { echo " [✗] $*"; }
|
|
log_step() { echo ""; echo "=== $* ==="; }
|
|
|
|
# ── OverlayFS fix (iStoreOS Docker /opt conflict) ─────────────────────────
|
|
_fix_opt() {
|
|
mkdir -p /opt/hermes/.probe 2>/dev/null && { rmdir /opt/hermes/.probe 2>/dev/null; return 0; }
|
|
[ -d /overlay/upper/opt ] && mkdir -p /overlay/upper/opt/hermes 2>/dev/null && \
|
|
mount --bind /overlay/upper/opt /opt 2>/dev/null && return 0
|
|
return 1
|
|
}
|
|
_fix_opt
|
|
|
|
# ── Basic tools ────────────────────────────────────────────────────────────
|
|
ensure_dir() { [ -d "$1" ] && return 0; mkdir -p "$1" 2>/dev/null || { log_err "Cannot create directory: $1"; return 1; }; }
|
|
|
|
# Check available disk space
|
|
_df_avail() {
|
|
local p="$1"
|
|
while [ -n "$p" ] && [ "$p" != "/" ]; do
|
|
[ -e "$p" ] && break
|
|
p="${p%/*}"
|
|
done
|
|
[ -z "$p" ] && p="/"
|
|
df -m "$p" 2>/dev/null | tail -1 | awk '{print $(NF-2)}'
|
|
}
|
|
|
|
check_space() {
|
|
local path="$1" need_mb="$2" label="$3"
|
|
local avail
|
|
avail=$(_df_avail "$path")
|
|
[ -z "$avail" ] && return 0
|
|
if [ "$avail" -lt "$need_mb" ]; then
|
|
log_warn "${label} available space insufficient: ${avail}MB < ${need_mb}MB recommended"
|
|
else
|
|
log_ok "${label} available space: ${avail}MB"
|
|
fi
|
|
}
|
|
|
|
# Download: wget first, curl fallback
|
|
_download() {
|
|
local url="$1" out="$2"
|
|
if command -v wget >/dev/null 2>&1; then
|
|
wget -q --no-check-certificate -O "$out" "$url" 2>/dev/null
|
|
elif command -v curl >/dev/null 2>&1; then
|
|
curl -fsSL --connect-timeout 15 --max-time 120 -o "$out" "$url" 2>/dev/null
|
|
else
|
|
log_err "No download tool available (wget/curl)"; return 1
|
|
fi
|
|
}
|
|
|
|
# Try multiple mirrors, return first success
|
|
_download_first() {
|
|
local out="$1"; shift
|
|
for url in "$@"; do
|
|
[ -z "$url" ] && continue
|
|
if _download "$url" "$out"; then
|
|
local sz; sz=$(wc -c < "$out" 2>/dev/null || echo 0)
|
|
[ "$sz" -gt 1000 ] && return 0
|
|
fi
|
|
rm -f "$out"
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# ── Architecture detection ─────────────────────────────────────────────────
|
|
detect_arch() {
|
|
case "$(uname -m)" in
|
|
x86_64) echo "x86_64" ;;
|
|
aarch64) echo "aarch64" ;;
|
|
armv7l) echo "armv7l" ;;
|
|
armv6l) echo "armv6l" ;;
|
|
*) log_err "Unsupported architecture: $(uname -m)"; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
detect_libc() {
|
|
[ -f /etc/openwrt_release ] && echo "musl" && return
|
|
for f in /lib/ld-musl-*.so.1; do [ -f "$f" ] && echo "musl" && return; done
|
|
echo "glibc"
|
|
}
|
|
|
|
# ── Install Python 3 ──────────────────────────────────────────────────────
|
|
install_python() {
|
|
# Check if system python3 is available
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
local ver
|
|
ver=$(python3 --version 2>/dev/null | awk '{print $2}')
|
|
log_ok "System Python3 v${ver} found"
|
|
# Check if it's recent enough (3.8+)
|
|
local major minor
|
|
major=$(echo "$ver" | cut -d. -f1)
|
|
minor=$(echo "$ver" | cut -d. -f2)
|
|
if [ "$major" -ge 3 ] && [ "$minor" -ge 8 ] 2>/dev/null; then
|
|
log_ok "Python3 v${ver} meets requirements (>= 3.8)"
|
|
return 0
|
|
fi
|
|
log_warn "Python3 v${ver} is too old, need >= 3.8"
|
|
fi
|
|
|
|
log_step "Installing Python 3"
|
|
|
|
# Try opkg first
|
|
opkg update >/dev/null 2>&1 || true
|
|
opkg install python3 python3-pip python3-venv 2>/dev/null | tail -5 || true
|
|
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
log_ok "Python3 installed via opkg: $(python3 --version 2>/dev/null)"
|
|
return 0
|
|
fi
|
|
|
|
# Fallback: try to install minimal python3
|
|
opkg install python3-base python3-light 2>/dev/null | tail -5 || true
|
|
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
log_ok "Python3 (minimal) installed: $(python3 --version 2>/dev/null)"
|
|
# Also need pip
|
|
opkg install python3-pip 2>/dev/null || true
|
|
return 0
|
|
fi
|
|
|
|
log_err "Python3 installation failed"
|
|
log_err "Please install manually: opkg install python3 python3-pip python3-venv"
|
|
return 1
|
|
}
|
|
|
|
# ── Create virtual environment ─────────────────────────────────────────────
|
|
create_venv() {
|
|
log_step "Creating Python virtual environment"
|
|
|
|
ensure_dir "$HERMES_BASE" || exit 1
|
|
|
|
# Remove old venv if exists
|
|
if [ -d "$HERMES_VENV" ]; then
|
|
log_warn "Removing old virtual environment..."
|
|
rm -rf "$HERMES_VENV"
|
|
fi
|
|
|
|
# Create venv using system python3
|
|
local sys_python
|
|
sys_python=$(command -v python3 2>/dev/null)
|
|
if [ -z "$sys_python" ]; then
|
|
log_err "python3 not found in PATH"
|
|
return 1
|
|
fi
|
|
|
|
"$sys_python" -m venv "$HERMES_VENV" 2>/dev/null
|
|
if [ ! -x "$PYTHON_BIN" ]; then
|
|
# Try without -m venv (older python)
|
|
log_warn "python3 -m venv failed, trying virtualenv..."
|
|
pip3 install virtualenv 2>/dev/null || true
|
|
"$sys_python" -m virtualenv "$HERMES_VENV" 2>/dev/null
|
|
fi
|
|
|
|
if [ ! -x "$PYTHON_BIN" ]; then
|
|
log_err "Failed to create virtual environment"
|
|
return 1
|
|
fi
|
|
|
|
log_ok "Virtual environment created at $HERMES_VENV"
|
|
|
|
# Upgrade pip
|
|
log_ok "Upgrading pip..."
|
|
"$PYTHON_BIN" -m pip install --upgrade pip 2>/dev/null | tail -2 || true
|
|
|
|
log_ok "Python: $("$PYTHON_BIN" --version 2>/dev/null)"
|
|
}
|
|
|
|
# ── Install Hermes Agent ───────────────────────────────────────────────────
|
|
install_hermes() {
|
|
log_step "Installing Hermes Agent"
|
|
|
|
# Check if already installed
|
|
if [ -x "$HERMES_BIN" ]; then
|
|
local cur
|
|
cur=$("$HERMES_BIN" --version 2>/dev/null | head -1 | tr -d '[:space:]')
|
|
log_ok "Hermes Agent v${cur} already installed"
|
|
echo " To reinstall: hermes-env setup --force"
|
|
return 0
|
|
fi
|
|
|
|
ensure_dir "$HERMES_VENV" || return 1
|
|
|
|
# Try PyPI mirrors in order
|
|
local install_ok=false
|
|
|
|
for mirror in "$HERMES_PYPI_CN" "$HERMES_PYPI_TSINGHUA" "$HERMES_PYPI_OFFICIAL"; do
|
|
log_warn "Trying: ${mirror}"
|
|
if "$PYTHON_BIN" -m pip install hermes-agent \
|
|
-i "$mirror" \
|
|
--trusted-host "$(echo "$mirror" | sed 's|https://||;s|/.*||')" \
|
|
2>&1 | tail -10; then
|
|
install_ok=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$install_ok" = "false" ] || [ ! -x "$HERMES_BIN" ]; then
|
|
# Try without mirror flags
|
|
log_warn "Mirror install failed, trying default PyPI..."
|
|
"$PYTHON_BIN" -m pip install hermes-agent 2>&1 | tail -10 || true
|
|
[ -x "$HERMES_BIN" ] && install_ok=true
|
|
fi
|
|
|
|
if [ "$install_ok" = "false" ]; then
|
|
log_err "Hermes Agent installation failed"
|
|
log_err "Please check network connection and try again"
|
|
return 1
|
|
fi
|
|
|
|
log_ok "Hermes Agent installed: $("$HERMES_BIN" --version 2>/dev/null | head -1)"
|
|
}
|
|
|
|
# ── Initialize Hermes configuration ────────────────────────────────────────
|
|
init_hermes() {
|
|
log_step "Initializing Hermes configuration"
|
|
|
|
mkdir -p /root/.hermes 2>/dev/null
|
|
|
|
# Generate auth token
|
|
local token
|
|
token=$(uci -q get hermes.main.token 2>/dev/null || echo "")
|
|
if [ -z "$token" ]; then
|
|
token=$(head -c 24 /dev/urandom | hexdump -e '24/1 "%02x"' 2>/dev/null || openssl rand -hex 24 2>/dev/null || echo "hermes_$(date +%s)")
|
|
uci set hermes.main.token="$token" 2>/dev/null || true
|
|
uci commit hermes 2>/dev/null || true
|
|
fi
|
|
|
|
# Generate PTY token
|
|
local pty_token
|
|
pty_token=$(uci -q get hermes.main.pty_token 2>/dev/null || echo "")
|
|
if [ -z "$pty_token" ]; then
|
|
pty_token=$(head -c 16 /dev/urandom | hexdump -e '16/1 "%02x"' 2>/dev/null || openssl rand -hex 16 2>/dev/null || echo "pty_$(date +%s)")
|
|
uci set hermes.main.pty_token="$pty_token" 2>/dev/null || true
|
|
uci commit hermes 2>/dev/null || true
|
|
fi
|
|
|
|
# Write default config.yaml
|
|
local port bind api_endpoint api_key model
|
|
port=$(uci -q get hermes.main.port || echo "3000")
|
|
bind=$(uci -q get hermes.main.bind || echo "lan")
|
|
api_endpoint=$(uci -q get hermes.main.api_endpoint || echo "$HERMES_DEFAULT_API")
|
|
api_key=$(uci -q get hermes.main.api_key || echo "")
|
|
model=$(uci -q get hermes.main.model || echo "$HERMES_DEFAULT_MODEL")
|
|
|
|
local listen_host="0.0.0.0"
|
|
[ "$bind" = "loopback" ] && listen_host="127.0.0.1"
|
|
|
|
cat > /root/.hermes/config.yaml << YAMLEOF
|
|
# Hermes Agent configuration
|
|
# Initialized by luci-app-hermes on $(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
gateway:
|
|
port: ${port}
|
|
bind: ${listen_host}
|
|
auth:
|
|
mode: token
|
|
token: ${token}
|
|
|
|
api:
|
|
endpoint: "${api_endpoint}"
|
|
key: "${api_key}"
|
|
|
|
model:
|
|
primary: "${model}"
|
|
|
|
tools:
|
|
profile: coding
|
|
|
|
update:
|
|
checkOnStart: false
|
|
YAMLEOF
|
|
|
|
chmod 600 /root/.hermes/config.yaml 2>/dev/null || true
|
|
log_ok "Configuration written to /root/.hermes/config.yaml"
|
|
}
|
|
|
|
# ── Command: setup ─────────────────────────────────────────────────────────
|
|
cmd_setup() {
|
|
check_space /opt 100 "Install directory (/opt)"
|
|
check_space /tmp 100 "Temp directory (/tmp)"
|
|
|
|
# Already installed?
|
|
if [ -x "$HERMES_BIN" ]; then
|
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
echo "║ [!] Hermes Agent already installed ║"
|
|
echo "║ To reinstall, run: hermes-env setup --force ║"
|
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
echo " Hermes: $("$HERMES_BIN" --version 2>/dev/null | head -1)"
|
|
exit 0
|
|
fi
|
|
|
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
echo "║ Hermes Agent Environment Setup ║"
|
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
echo " Architecture: $(uname -m)"
|
|
echo " Libc: $(detect_libc)"
|
|
echo ""
|
|
|
|
install_python || exit 1
|
|
create_venv || exit 1
|
|
install_hermes || exit 1
|
|
init_hermes || exit 1
|
|
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════════════╗"
|
|
echo "║ Setup Complete! ║"
|
|
echo "╚══════════════════════════════════════════════════════════════╝"
|
|
echo " Hermes: $("$HERMES_BIN" --version 2>/dev/null | head -1)"
|
|
echo " Python: $("$PYTHON_BIN" --version 2>/dev/null)"
|
|
echo " Config: /root/.hermes/config.yaml"
|
|
echo ""
|
|
echo " Next steps:"
|
|
echo " 1. Edit /etc/config/hermes to set your API key"
|
|
echo " 2. Run: /etc/init.d/hermes enable"
|
|
echo " 3. Run: /etc/init.d/hermes start"
|
|
echo ""
|
|
echo " Or use the LuCI web interface to configure and start."
|
|
}
|
|
|
|
# ── Command: check ─────────────────────────────────────────────────────────
|
|
cmd_check() {
|
|
echo "=== Hermes Environment Check ==="
|
|
|
|
if [ -x "$PYTHON_BIN" ]; then
|
|
echo "Python: $("$PYTHON_BIN" --version 2>/dev/null)"
|
|
else
|
|
echo "Python: Not installed (virtual env)"
|
|
fi
|
|
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
echo "System Py: $(python3 --version 2>/dev/null)"
|
|
else
|
|
echo "System Py: Not found"
|
|
fi
|
|
|
|
if [ -x "$HERMES_BIN" ]; then
|
|
echo "Hermes: $("$HERMES_BIN" --version 2>/dev/null | head -1)"
|
|
else
|
|
echo "Hermes: Not installed"
|
|
fi
|
|
|
|
echo "VENV: $HERMES_VENV"
|
|
echo "Config: $HERMES_CONFIG"
|
|
|
|
if [ -f "$HERMES_CONFIG" ]; then
|
|
echo "Config OK: Yes"
|
|
else
|
|
echo "Config OK: No (will be created on start)"
|
|
fi
|
|
}
|
|
|
|
# ── Command: upgrade ───────────────────────────────────────────────────────
|
|
cmd_upgrade() {
|
|
if [ ! -x "$HERMES_BIN" ]; then
|
|
log_err "Hermes Agent not installed. Run: hermes-env setup"
|
|
exit 1
|
|
fi
|
|
|
|
log_step "Upgrading Hermes Agent"
|
|
|
|
local upgrade_ok=false
|
|
|
|
for mirror in "$HERMES_PYPI_CN" "$HERMES_PYPI_TSINGHUA" "$HERMES_PYPI_OFFICIAL"; do
|
|
log_warn "Trying: ${mirror}"
|
|
if "$PYTHON_BIN" -m pip install --upgrade hermes-agent \
|
|
-i "$mirror" \
|
|
--trusted-host "$(echo "$mirror" | sed 's|https://||;s|/.*||')" \
|
|
2>&1 | tail -10; then
|
|
upgrade_ok=true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ "$upgrade_ok" = "false" ]; then
|
|
log_warn "Mirror install failed, trying default PyPI..."
|
|
"$PYTHON_BIN" -m pip install --upgrade hermes-agent 2>&1 | tail -10 || true
|
|
[ -x "$HERMES_BIN" ] && upgrade_ok=true
|
|
fi
|
|
|
|
if [ "$upgrade_ok" = "true" ] && [ -x "$HERMES_BIN" ]; then
|
|
log_ok "Hermes upgraded: $("$HERMES_BIN" --version 2>/dev/null | head -1)"
|
|
else
|
|
log_err "Upgrade failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ── Command: uninstall ─────────────────────────────────────────────────────
|
|
cmd_uninstall() {
|
|
log_step "Uninstalling Hermes Agent"
|
|
|
|
/etc/init.d/hermes stop 2>/dev/null || true
|
|
/etc/init.d/hermes disable 2>/dev/null || true
|
|
uci set hermes.main.enabled=0 2>/dev/null || true
|
|
uci commit hermes 2>/dev/null || true
|
|
|
|
[ -d "$HERMES_BASE" ] && { rm -rf "$HERMES_BASE" 2>/dev/null || true; }
|
|
[ -d "/root/.hermes" ] && { rm -rf "/root/.hermes" 2>/dev/null || true; }
|
|
|
|
log_ok "Hermes Agent uninstalled"
|
|
}
|
|
|
|
# ── Main entry ─────────────────────────────────────────────────────────────
|
|
case "${1:-setup}" in
|
|
setup) cmd_setup ;;
|
|
check) cmd_check ;;
|
|
upgrade) cmd_upgrade ;;
|
|
uninstall) cmd_uninstall ;;
|
|
*)
|
|
echo "Usage: hermes-env {setup|check|upgrade|uninstall}"
|
|
echo ""
|
|
echo " setup Install Python + Hermes Agent (default)"
|
|
echo " check Check installation status"
|
|
echo " upgrade Upgrade Hermes Agent to latest version"
|
|
echo " uninstall Remove all Hermes files"
|
|
exit 1
|
|
;;
|
|
esac
|