commit 10fb734df33462715f002fe81049a35e271cba2c Author: yankun Date: Sun Aug 2 16:28:22 2026 +0800 Add CutOS Agent installer diff --git a/README.md b/README.md new file mode 100644 index 0000000..c8407ef --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# CutOS Agent Installer + +Install CutOS Agent and the Pi web console: + +```bash +curl -fsSL https://git.cutos.ai/claw-daemon/cutos-agent/raw/main/install.sh | bash +``` + +The installer: + +- Installs `@earendil-works/pi-coding-agent@latest`. +- Installs `@agegr/pi-web`. +- Installs the default Pi extensions. +- Creates and starts `pi-web.service`. +- Runs `pi-web --no-open` from `$HOME/.pi`. + +The local console listens at: + +```text +http://127.0.0.1:30141/ +``` diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..43cb636 --- /dev/null +++ b/install.sh @@ -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 <= 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 <