Add CutOS Agent installer
This commit is contained in:
Executable
+216
@@ -0,0 +1,216 @@
|
||||
#!/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
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
run_root mkdir -p "$PI_HOME"
|
||||
if [ "$TARGET_USER" != "root" ]; then
|
||||
run_root chown "$TARGET_USER:$TARGET_GROUP" "$PI_HOME"
|
||||
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
|
||||
Logs: journalctl -u pi-web -f
|
||||
Console: http://127.0.0.1:30141/
|
||||
|
||||
EOF
|
||||
Reference in New Issue
Block a user