262 lines
6.5 KiB
Bash
Executable File
262 lines
6.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# CutOS Agent installer
|
|
# Run: curl -fsSL https://git.cutos.ai/claw-daemon/cutos-agent/raw/main/install.sh | bash
|
|
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
info() { echo -e "${GREEN}[cutos-agent]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[cutos-agent]${NC} $*"; }
|
|
error() { echo -e "${RED}[cutos-agent]${NC} $*" >&2; exit 1; }
|
|
|
|
require_command() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
error "$1 not found. Please install $1 and run this installer again."
|
|
fi
|
|
}
|
|
|
|
run_root() {
|
|
if [ "${EUID:-$(id -u)}" -eq 0 ]; then
|
|
"$@"
|
|
return
|
|
fi
|
|
|
|
require_command sudo
|
|
sudo "$@"
|
|
}
|
|
|
|
detect_user() {
|
|
if [ "${EUID:-$(id -u)}" -eq 0 ]; then
|
|
if [ -n "${SUDO_USER:-}" ] && [ "${SUDO_USER}" != "root" ]; then
|
|
printf '%s' "$SUDO_USER"
|
|
else
|
|
printf 'root'
|
|
fi
|
|
else
|
|
id -un
|
|
fi
|
|
}
|
|
|
|
detect_home() {
|
|
local user="$1"
|
|
local home=""
|
|
|
|
if command -v getent >/dev/null 2>&1; then
|
|
home="$(getent passwd "$user" | cut -d: -f6 || true)"
|
|
fi
|
|
|
|
if [ -z "$home" ]; then
|
|
home="${HOME:-}"
|
|
fi
|
|
|
|
if [ -z "$home" ]; then
|
|
error "Cannot determine home directory for user $user"
|
|
fi
|
|
|
|
printf '%s' "$home"
|
|
}
|
|
|
|
npm_global_install() {
|
|
info "Installing $* ..."
|
|
|
|
if npm install -g "$@"; then
|
|
return
|
|
fi
|
|
|
|
if [ "${EUID:-$(id -u)}" -eq 0 ]; then
|
|
error "npm install failed: $*"
|
|
fi
|
|
|
|
warn "Retrying global npm install with sudo ..."
|
|
run_root env PATH="$PATH" npm install -g "$@"
|
|
}
|
|
|
|
run_as_target_user() {
|
|
if [ "$TARGET_USER" = "root" ]; then
|
|
HOME="$TARGET_HOME" "$@"
|
|
return
|
|
fi
|
|
|
|
if [ "${EUID:-$(id -u)}" -eq 0 ]; then
|
|
runuser -u "$TARGET_USER" -- env HOME="$TARGET_HOME" PATH="$PATH" "$@"
|
|
else
|
|
HOME="$TARGET_HOME" "$@"
|
|
fi
|
|
}
|
|
|
|
install_pi_package() {
|
|
local package="$1"
|
|
info "Installing Pi extension: $package"
|
|
run_as_target_user "$PI_BIN" install "$package"
|
|
}
|
|
|
|
resolve_bin() {
|
|
local name="$1"
|
|
local found=""
|
|
|
|
found="$(command -v "$name" || true)"
|
|
if [ -n "$found" ]; then
|
|
printf '%s' "$found"
|
|
return
|
|
fi
|
|
|
|
local prefix=""
|
|
prefix="$(npm prefix -g 2>/dev/null || true)"
|
|
if [ -n "$prefix" ] && [ -x "$prefix/bin/$name" ]; then
|
|
printf '%s' "$prefix/bin/$name"
|
|
return
|
|
fi
|
|
|
|
if [ -x "/usr/local/bin/$name" ]; then
|
|
printf '%s' "/usr/local/bin/$name"
|
|
return
|
|
fi
|
|
|
|
if [ -x "/usr/bin/$name" ]; then
|
|
printf '%s' "/usr/bin/$name"
|
|
return
|
|
fi
|
|
|
|
error "Cannot find $name after installation"
|
|
}
|
|
|
|
write_pi_web_service() {
|
|
local pi_web_bin="$1"
|
|
local service_file="/etc/systemd/system/pi-web.service"
|
|
|
|
info "Writing pi-web systemd service ..."
|
|
run_root tee "$service_file" >/dev/null <<EOF
|
|
[Unit]
|
|
Description=Pi Web Console
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=$TARGET_USER
|
|
Environment=HOME=$TARGET_HOME
|
|
WorkingDirectory=$PI_HOME
|
|
ExecStart=$pi_web_bin --no-open
|
|
Restart=always
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
run_root systemctl daemon-reload
|
|
run_root systemctl enable pi-web.service
|
|
run_root systemctl restart pi-web.service
|
|
}
|
|
|
|
configure_clawd_agent_type() {
|
|
local config_dir="/etc/clawd"
|
|
local env_file="$config_dir/env"
|
|
local models_config="$TARGET_HOME/.pi/agent/models.json"
|
|
|
|
info "Configuring clawd AGENT_TYPE=cutos-agent ..."
|
|
run_root mkdir -p "$config_dir"
|
|
|
|
if run_root test -f "$env_file"; then
|
|
if run_root grep -q '^[[:space:]]*AGENT_TYPE=' "$env_file"; then
|
|
run_root sed -i 's/^[[:space:]]*AGENT_TYPE=.*/AGENT_TYPE=cutos-agent/' "$env_file"
|
|
elif run_root grep -q '^[[:space:]]*# AGENT_TYPE=' "$env_file"; then
|
|
run_root sed -i 's/^[[:space:]]*# AGENT_TYPE=.*/AGENT_TYPE=cutos-agent/' "$env_file"
|
|
else
|
|
printf '\nAGENT_TYPE=cutos-agent\n' | run_root tee -a "$env_file" >/dev/null
|
|
fi
|
|
|
|
if run_root grep -q '^[[:space:]]*PI_MODELS_CONFIG=' "$env_file"; then
|
|
run_root sed -i "s|^[[:space:]]*PI_MODELS_CONFIG=.*|PI_MODELS_CONFIG=$models_config|" "$env_file"
|
|
elif run_root grep -q '^[[:space:]]*# PI_MODELS_CONFIG=' "$env_file"; then
|
|
run_root sed -i "s|^[[:space:]]*# PI_MODELS_CONFIG=.*|PI_MODELS_CONFIG=$models_config|" "$env_file"
|
|
else
|
|
printf 'PI_MODELS_CONFIG=%s\n' "$models_config" | run_root tee -a "$env_file" >/dev/null
|
|
fi
|
|
else
|
|
run_root tee "$env_file" >/dev/null <<EOF
|
|
# clawd environment (loaded by systemd EnvironmentFile)
|
|
AGENT_TYPE=cutos-agent
|
|
PI_MODELS_CONFIG=$models_config
|
|
EOF
|
|
fi
|
|
|
|
if command -v systemctl >/dev/null 2>&1 && run_root systemctl cat clawd.service >/dev/null 2>&1; then
|
|
info "Restarting clawd to apply AGENT_TYPE ..."
|
|
if ! run_root systemctl restart clawd.service; then
|
|
warn "clawd restart failed. Check logs with: journalctl -u clawd -n 50 --no-pager"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
require_command node
|
|
require_command npm
|
|
|
|
NODE_VER="$(node -e "process.stdout.write(process.versions.node)")"
|
|
NODE_MAJOR="$(printf '%s' "$NODE_VER" | cut -d. -f1)"
|
|
if [ "$NODE_MAJOR" -lt 18 ]; then
|
|
error "Node.js $NODE_VER is too old. Please install Node.js >= 18."
|
|
fi
|
|
info "Node.js $NODE_VER OK"
|
|
|
|
TARGET_USER="$(detect_user)"
|
|
TARGET_HOME="$(detect_home "$TARGET_USER")"
|
|
TARGET_GROUP="$(id -gn "$TARGET_USER" 2>/dev/null || printf '%s' "$TARGET_USER")"
|
|
PI_HOME="$TARGET_HOME/.pi"
|
|
|
|
info "Installing for user: $TARGET_USER"
|
|
info "Pi working directory: $PI_HOME"
|
|
|
|
configure_clawd_agent_type
|
|
|
|
run_root mkdir -p "$PI_HOME"
|
|
run_root mkdir -p "$PI_HOME/agent"
|
|
if [ "$TARGET_USER" != "root" ]; then
|
|
run_root chown "$TARGET_USER:$TARGET_GROUP" "$PI_HOME"
|
|
run_root chown "$TARGET_USER:$TARGET_GROUP" "$PI_HOME/agent"
|
|
fi
|
|
|
|
npm_global_install @earendil-works/pi-coding-agent@latest --allow-scripts=@google/genai,protobufjs,sharp
|
|
npm_global_install @agegr/pi-web
|
|
|
|
PI_BIN="$(resolve_bin pi)"
|
|
PI_WEB_BIN="$(resolve_bin pi-web)"
|
|
|
|
info "pi CLI: $PI_BIN"
|
|
info "pi-web: $PI_WEB_BIN"
|
|
|
|
install_pi_package npm:pi-wechat-assistant
|
|
install_pi_package npm:@jay-zod/speakturbo
|
|
install_pi_package npm:pi-codex-image-gen
|
|
install_pi_package npm:@mammothb/pi-office
|
|
install_pi_package npm:@zosmaai/pi-llm-wiki
|
|
install_pi_package npm:pi-git-commands-extension
|
|
install_pi_package npm:@xjuai/pi-feishu-lark
|
|
install_pi_package npm:pi-ocr
|
|
install_pi_package npm:pi-schedule-prompt
|
|
|
|
write_pi_web_service "$PI_WEB_BIN"
|
|
|
|
sleep 2
|
|
if systemctl is-active --quiet pi-web.service; then
|
|
info "pi-web is running"
|
|
else
|
|
warn "pi-web failed to start. Check logs with: journalctl -u pi-web -n 50 --no-pager"
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
CutOS Agent install complete.
|
|
|
|
Pi home: $PI_HOME
|
|
Service: pi-web.service
|
|
clawd env: /etc/clawd/env
|
|
Logs: journalctl -u pi-web -f
|
|
Console: http://127.0.0.1:30141/
|
|
|
|
EOF
|