mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
fix: improve install scripts robustness and edge cases
install.sh: - Fix NodeSource GPG key overwrite issue (rm before dearmor) - Fix systemd ExecStart missing node binary prefix - Add SUSE/openSUSE support (zypper-based) - Add local changes check before git reset --hard - Add curl/wget detection with fallback support - Use shallow clone (--depth 1) for faster install - Update Node.js target to v22 (current LTS) - Add CLAUDEMAN_NODE_VERSION env var for customization - Add run_as_root/ensure_sudo helpers for consistent privilege handling - Add cleanup trap with helpful failure message - Add Windows detection with WSL guidance - Fix PATH substring check (was using grep -qx incorrectly) - Add stdin TTY check for non-interactive piped input - Update Claude CLI install to official curl installer - Add armv7 architecture support (Raspberry Pi) - Quieter apt/dnf output with -qq flags scripts/postinstall.js: - Fix command detection for Windows (where vs command -v) - Add Fedora/Arch/Alpine install instructions for screen - Add Windows/WSL guidance - Show Claude CLI path when found - Refactor with clear sections and helper functions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+305
-125
@@ -8,6 +8,7 @@
|
|||||||
# CLAUDEMAN_NONINTERACTIVE=1 - Skip all prompts (for CI/automation)
|
# CLAUDEMAN_NONINTERACTIVE=1 - Skip all prompts (for CI/automation)
|
||||||
# CLAUDEMAN_INSTALL_DIR - Custom install directory (default: ~/.claudeman/app)
|
# CLAUDEMAN_INSTALL_DIR - Custom install directory (default: ~/.claudeman/app)
|
||||||
# CLAUDEMAN_SKIP_SYSTEMD=1 - Skip systemd service setup prompt
|
# CLAUDEMAN_SKIP_SYSTEMD=1 - Skip systemd service setup prompt
|
||||||
|
# CLAUDEMAN_NODE_VERSION - Node.js major version to install (default: 22)
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ set -euo pipefail
|
|||||||
INSTALL_DIR="${CLAUDEMAN_INSTALL_DIR:-$HOME/.claudeman/app}"
|
INSTALL_DIR="${CLAUDEMAN_INSTALL_DIR:-$HOME/.claudeman/app}"
|
||||||
REPO_URL="https://github.com/Ark0N/claudeman.git"
|
REPO_URL="https://github.com/Ark0N/claudeman.git"
|
||||||
MIN_NODE_VERSION=18
|
MIN_NODE_VERSION=18
|
||||||
|
TARGET_NODE_VERSION="${CLAUDEMAN_NODE_VERSION:-22}"
|
||||||
NONINTERACTIVE="${CLAUDEMAN_NONINTERACTIVE:-0}"
|
NONINTERACTIVE="${CLAUDEMAN_NONINTERACTIVE:-0}"
|
||||||
SKIP_SYSTEMD="${CLAUDEMAN_SKIP_SYSTEMD:-0}"
|
SKIP_SYSTEMD="${CLAUDEMAN_SKIP_SYSTEMD:-0}"
|
||||||
|
|
||||||
@@ -34,25 +36,29 @@ CLAUDE_SEARCH_PATHS=(
|
|||||||
# Color Output (from scripts/screen-manager.sh pattern)
|
# Color Output (from scripts/screen-manager.sh pattern)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Check if terminal supports colors
|
setup_colors() {
|
||||||
if [[ -t 1 ]] && [[ -n "${TERM:-}" ]] && command -v tput &>/dev/null; then
|
# Check if terminal supports colors
|
||||||
ncolors=$(tput colors 2>/dev/null || echo 0)
|
if [[ -t 1 ]] && [[ -n "${TERM:-}" ]] && command -v tput &>/dev/null; then
|
||||||
if [[ "$ncolors" -ge 8 ]]; then
|
local ncolors
|
||||||
RED='\033[0;31m'
|
ncolors=$(tput colors 2>/dev/null || echo 0)
|
||||||
GREEN='\033[0;32m'
|
if [[ "$ncolors" -ge 8 ]]; then
|
||||||
YELLOW='\033[1;33m'
|
RED='\033[0;31m'
|
||||||
BLUE='\033[0;34m'
|
GREEN='\033[0;32m'
|
||||||
CYAN='\033[0;36m'
|
YELLOW='\033[1;33m'
|
||||||
MAGENTA='\033[0;35m'
|
BLUE='\033[0;34m'
|
||||||
BOLD='\033[1m'
|
CYAN='\033[0;36m'
|
||||||
DIM='\033[2m'
|
MAGENTA='\033[0;35m'
|
||||||
NC='\033[0m'
|
BOLD='\033[1m'
|
||||||
else
|
DIM='\033[2m'
|
||||||
RED='' GREEN='' YELLOW='' BLUE='' CYAN='' MAGENTA='' BOLD='' DIM='' NC=''
|
NC='\033[0m'
|
||||||
|
return
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
# No color support
|
||||||
RED='' GREEN='' YELLOW='' BLUE='' CYAN='' MAGENTA='' BOLD='' DIM='' NC=''
|
RED='' GREEN='' YELLOW='' BLUE='' CYAN='' MAGENTA='' BOLD='' DIM='' NC=''
|
||||||
fi
|
}
|
||||||
|
|
||||||
|
setup_colors
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Output Helpers
|
# Output Helpers
|
||||||
@@ -67,7 +73,7 @@ success() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
warn() {
|
warn() {
|
||||||
echo -e "${YELLOW}Warning:${NC} $1"
|
echo -e "${YELLOW}Warning:${NC} $1" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
error() {
|
error() {
|
||||||
@@ -79,6 +85,20 @@ die() {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Cleanup on Failure
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local exit_code=$?
|
||||||
|
if [[ $exit_code -ne 0 ]]; then
|
||||||
|
error "Installation failed. Partial installation may remain at $INSTALL_DIR"
|
||||||
|
error "To retry, run the installer again or remove the directory manually."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# System Detection
|
# System Detection
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -89,6 +109,9 @@ detect_os() {
|
|||||||
case "$os" in
|
case "$os" in
|
||||||
Darwin) echo "macos" ;;
|
Darwin) echo "macos" ;;
|
||||||
Linux) echo "linux" ;;
|
Linux) echo "linux" ;;
|
||||||
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
|
die "Windows is not supported directly. Please use WSL (Windows Subsystem for Linux)."
|
||||||
|
;;
|
||||||
*) die "Unsupported operating system: $os" ;;
|
*) die "Unsupported operating system: $os" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -99,13 +122,25 @@ detect_arch() {
|
|||||||
case "$arch" in
|
case "$arch" in
|
||||||
x86_64|amd64) echo "x64" ;;
|
x86_64|amd64) echo "x64" ;;
|
||||||
aarch64|arm64) echo "arm64" ;;
|
aarch64|arm64) echo "arm64" ;;
|
||||||
|
armv7l) echo "armv7" ;;
|
||||||
*) die "Unsupported architecture: $arch" ;;
|
*) die "Unsupported architecture: $arch" ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
detect_linux_distro() {
|
detect_linux_distro() {
|
||||||
if [[ ! -f /etc/os-release ]]; then
|
if [[ ! -f /etc/os-release ]]; then
|
||||||
echo "unknown"
|
# Fallback detection for older systems
|
||||||
|
if [[ -f /etc/debian_version ]]; then
|
||||||
|
echo "debian"
|
||||||
|
elif [[ -f /etc/redhat-release ]]; then
|
||||||
|
echo "fedora"
|
||||||
|
elif [[ -f /etc/arch-release ]]; then
|
||||||
|
echo "arch"
|
||||||
|
elif [[ -f /etc/alpine-release ]]; then
|
||||||
|
echo "alpine"
|
||||||
|
else
|
||||||
|
echo "unknown"
|
||||||
|
fi
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -114,16 +149,16 @@ detect_linux_distro() {
|
|||||||
source /etc/os-release
|
source /etc/os-release
|
||||||
|
|
||||||
case "${ID:-}" in
|
case "${ID:-}" in
|
||||||
debian|ubuntu|linuxmint|pop|elementary|zorin|kali)
|
debian|ubuntu|linuxmint|pop|elementary|zorin|kali|raspbian)
|
||||||
echo "debian"
|
echo "debian"
|
||||||
;;
|
;;
|
||||||
fedora|rhel|centos|rocky|alma|ol)
|
fedora|rhel|centos|rocky|alma|ol|amzn)
|
||||||
echo "fedora"
|
echo "fedora"
|
||||||
;;
|
;;
|
||||||
arch|manjaro|endeavouros|garuda)
|
arch|manjaro|endeavouros|garuda|artix)
|
||||||
echo "arch"
|
echo "arch"
|
||||||
;;
|
;;
|
||||||
opensuse*|suse*)
|
opensuse*|sles|suse)
|
||||||
echo "suse"
|
echo "suse"
|
||||||
;;
|
;;
|
||||||
alpine)
|
alpine)
|
||||||
@@ -142,6 +177,42 @@ detect_linux_distro() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Prerequisite Checks
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
check_curl_or_wget() {
|
||||||
|
if command -v curl &>/dev/null; then
|
||||||
|
DOWNLOADER="curl"
|
||||||
|
return 0
|
||||||
|
elif command -v wget &>/dev/null; then
|
||||||
|
DOWNLOADER="wget"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
download() {
|
||||||
|
local url="$1"
|
||||||
|
local output="$2"
|
||||||
|
|
||||||
|
if [[ "$DOWNLOADER" == "curl" ]]; then
|
||||||
|
curl -fsSL "$url" -o "$output"
|
||||||
|
else
|
||||||
|
wget -q "$url" -O "$output"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
download_to_stdout() {
|
||||||
|
local url="$1"
|
||||||
|
|
||||||
|
if [[ "$DOWNLOADER" == "curl" ]]; then
|
||||||
|
curl -fsSL "$url"
|
||||||
|
else
|
||||||
|
wget -qO- "$url"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Dependency Checks
|
# Dependency Checks
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -206,16 +277,39 @@ get_claude_path() {
|
|||||||
# Dependency Installation
|
# Dependency Installation
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
ensure_sudo() {
|
||||||
|
if [[ $EUID -eq 0 ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ! command -v sudo &>/dev/null; then
|
||||||
|
die "sudo is required but not installed. Please install packages manually or run as root."
|
||||||
|
fi
|
||||||
|
# Validate sudo access
|
||||||
|
if ! sudo -v 2>/dev/null; then
|
||||||
|
die "Failed to obtain sudo privileges."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_as_root() {
|
||||||
|
if [[ $EUID -eq 0 ]]; then
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
sudo "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
install_node_macos() {
|
install_node_macos() {
|
||||||
info "Installing Node.js via Homebrew..."
|
info "Installing Node.js via Homebrew..."
|
||||||
|
|
||||||
if ! command -v brew &>/dev/null; then
|
if ! command -v brew &>/dev/null; then
|
||||||
info "Installing Homebrew first..."
|
info "Installing Homebrew first..."
|
||||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
/bin/bash -c "$(download_to_stdout https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
|
|
||||||
# Add Homebrew to PATH for Apple Silicon
|
# Add Homebrew to PATH for Apple Silicon
|
||||||
if [[ -f /opt/homebrew/bin/brew ]]; then
|
if [[ -f /opt/homebrew/bin/brew ]]; then
|
||||||
eval "$(/opt/homebrew/bin/brew shellenv)"
|
eval "$(/opt/homebrew/bin/brew shellenv)"
|
||||||
|
elif [[ -f /usr/local/bin/brew ]]; then
|
||||||
|
eval "$(/usr/local/bin/brew shellenv)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -223,89 +317,120 @@ install_node_macos() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_node_debian() {
|
install_node_debian() {
|
||||||
info "Installing Node.js via NodeSource..."
|
info "Installing Node.js v$TARGET_NODE_VERSION via NodeSource..."
|
||||||
|
|
||||||
# Check if we can use sudo
|
ensure_sudo
|
||||||
if ! command -v sudo &>/dev/null; then
|
|
||||||
die "sudo is required to install Node.js. Please install sudo or Node.js manually."
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install prerequisites
|
# Install prerequisites
|
||||||
sudo apt-get update
|
run_as_root apt-get update -qq
|
||||||
sudo apt-get install -y ca-certificates curl gnupg
|
run_as_root apt-get install -y -qq ca-certificates curl gnupg
|
||||||
|
|
||||||
# Add NodeSource repository
|
# Setup NodeSource repository (new method)
|
||||||
sudo mkdir -p /etc/apt/keyrings
|
run_as_root mkdir -p /etc/apt/keyrings
|
||||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
|
||||||
|
|
||||||
NODE_MAJOR=20
|
# Remove old key if exists to avoid conflicts
|
||||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
run_as_root rm -f /etc/apt/keyrings/nodesource.gpg
|
||||||
|
|
||||||
sudo apt-get update
|
download_to_stdout https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | run_as_root gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||||
sudo apt-get install -y nodejs
|
|
||||||
|
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$TARGET_NODE_VERSION.x nodistro main" | run_as_root tee /etc/apt/sources.list.d/nodesource.list > /dev/null
|
||||||
|
|
||||||
|
run_as_root apt-get update -qq
|
||||||
|
run_as_root apt-get install -y -qq nodejs
|
||||||
}
|
}
|
||||||
|
|
||||||
install_node_fedora() {
|
install_node_fedora() {
|
||||||
info "Installing Node.js via dnf..."
|
info "Installing Node.js v$TARGET_NODE_VERSION via NodeSource..."
|
||||||
|
|
||||||
if ! command -v sudo &>/dev/null; then
|
ensure_sudo
|
||||||
die "sudo is required to install Node.js. Please install sudo or Node.js manually."
|
|
||||||
fi
|
|
||||||
|
|
||||||
sudo dnf install -y nodejs npm
|
# Use NodeSource for consistent version across Fedora versions
|
||||||
|
download_to_stdout "https://rpm.nodesource.com/setup_$TARGET_NODE_VERSION.x" | run_as_root bash -
|
||||||
|
run_as_root dnf install -y nodejs
|
||||||
}
|
}
|
||||||
|
|
||||||
install_node_arch() {
|
install_node_arch() {
|
||||||
info "Installing Node.js via pacman..."
|
info "Installing Node.js via pacman..."
|
||||||
|
|
||||||
if ! command -v sudo &>/dev/null; then
|
ensure_sudo
|
||||||
die "sudo is required to install Node.js. Please install sudo or Node.js manually."
|
run_as_root pacman -Sy --noconfirm nodejs npm
|
||||||
fi
|
|
||||||
|
|
||||||
sudo pacman -Sy --noconfirm nodejs npm
|
# Verify version is sufficient
|
||||||
|
local version
|
||||||
|
version=$(node --version 2>/dev/null | sed 's/^v//' | cut -d. -f1)
|
||||||
|
if [[ "$version" -lt "$MIN_NODE_VERSION" ]]; then
|
||||||
|
warn "Arch package nodejs is v$version, which is older than required v$MIN_NODE_VERSION"
|
||||||
|
warn "Consider using nvm or the nodejs-lts-* package instead"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
install_node_alpine() {
|
install_node_alpine() {
|
||||||
info "Installing Node.js via apk..."
|
info "Installing Node.js via apk..."
|
||||||
|
|
||||||
if [[ $EUID -eq 0 ]]; then
|
run_as_root apk add --no-cache nodejs npm
|
||||||
apk add --no-cache nodejs npm
|
|
||||||
else
|
# Verify version
|
||||||
if ! command -v sudo &>/dev/null; then
|
local version
|
||||||
die "sudo is required to install Node.js. Please install sudo or Node.js manually."
|
version=$(node --version 2>/dev/null | sed 's/^v//' | cut -d. -f1)
|
||||||
fi
|
if [[ "$version" -lt "$MIN_NODE_VERSION" ]]; then
|
||||||
sudo apk add --no-cache nodejs npm
|
warn "Alpine package nodejs is v$version, which is older than required v$MIN_NODE_VERSION"
|
||||||
|
warn "Consider using a newer Alpine version or building from source"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_node_suse() {
|
||||||
|
info "Installing Node.js v$TARGET_NODE_VERSION via NodeSource..."
|
||||||
|
|
||||||
|
ensure_sudo
|
||||||
|
|
||||||
|
# Use NodeSource for openSUSE
|
||||||
|
download_to_stdout "https://rpm.nodesource.com/setup_$TARGET_NODE_VERSION.x" | run_as_root bash -
|
||||||
|
run_as_root zypper install -y nodejs
|
||||||
|
}
|
||||||
|
|
||||||
install_screen_macos() {
|
install_screen_macos() {
|
||||||
info "Installing GNU Screen via Homebrew..."
|
info "Installing GNU Screen via Homebrew..."
|
||||||
|
|
||||||
|
# macOS has a built-in screen but it's very outdated
|
||||||
|
if command -v screen &>/dev/null; then
|
||||||
|
local builtin_version
|
||||||
|
builtin_version=$(screen --version 2>&1 | head -1 || echo "unknown")
|
||||||
|
if [[ "$builtin_version" == *"Apple"* ]] || [[ ! "$builtin_version" == *"GNU"* ]]; then
|
||||||
|
info "Upgrading from macOS built-in screen to GNU Screen..."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
brew install screen
|
brew install screen
|
||||||
}
|
}
|
||||||
|
|
||||||
install_screen_debian() {
|
install_screen_debian() {
|
||||||
info "Installing GNU Screen via apt..."
|
info "Installing GNU Screen via apt..."
|
||||||
sudo apt-get update
|
ensure_sudo
|
||||||
sudo apt-get install -y screen
|
run_as_root apt-get update -qq
|
||||||
|
run_as_root apt-get install -y -qq screen
|
||||||
}
|
}
|
||||||
|
|
||||||
install_screen_fedora() {
|
install_screen_fedora() {
|
||||||
info "Installing GNU Screen via dnf..."
|
info "Installing GNU Screen via dnf..."
|
||||||
sudo dnf install -y screen
|
ensure_sudo
|
||||||
|
run_as_root dnf install -y screen
|
||||||
}
|
}
|
||||||
|
|
||||||
install_screen_arch() {
|
install_screen_arch() {
|
||||||
info "Installing GNU Screen via pacman..."
|
info "Installing GNU Screen via pacman..."
|
||||||
sudo pacman -Sy --noconfirm screen
|
ensure_sudo
|
||||||
|
run_as_root pacman -Sy --noconfirm screen
|
||||||
}
|
}
|
||||||
|
|
||||||
install_screen_alpine() {
|
install_screen_alpine() {
|
||||||
info "Installing GNU Screen via apk..."
|
info "Installing GNU Screen via apk..."
|
||||||
if [[ $EUID -eq 0 ]]; then
|
run_as_root apk add --no-cache screen
|
||||||
apk add --no-cache screen
|
}
|
||||||
else
|
|
||||||
sudo apk add --no-cache screen
|
install_screen_suse() {
|
||||||
fi
|
info "Installing GNU Screen via zypper..."
|
||||||
|
ensure_sudo
|
||||||
|
run_as_root zypper install -y screen
|
||||||
}
|
}
|
||||||
|
|
||||||
install_git_macos() {
|
install_git_macos() {
|
||||||
@@ -315,27 +440,32 @@ install_git_macos() {
|
|||||||
|
|
||||||
install_git_debian() {
|
install_git_debian() {
|
||||||
info "Installing Git via apt..."
|
info "Installing Git via apt..."
|
||||||
sudo apt-get update
|
ensure_sudo
|
||||||
sudo apt-get install -y git
|
run_as_root apt-get update -qq
|
||||||
|
run_as_root apt-get install -y -qq git
|
||||||
}
|
}
|
||||||
|
|
||||||
install_git_fedora() {
|
install_git_fedora() {
|
||||||
info "Installing Git via dnf..."
|
info "Installing Git via dnf..."
|
||||||
sudo dnf install -y git
|
ensure_sudo
|
||||||
|
run_as_root dnf install -y git
|
||||||
}
|
}
|
||||||
|
|
||||||
install_git_arch() {
|
install_git_arch() {
|
||||||
info "Installing Git via pacman..."
|
info "Installing Git via pacman..."
|
||||||
sudo pacman -Sy --noconfirm git
|
ensure_sudo
|
||||||
|
run_as_root pacman -Sy --noconfirm git
|
||||||
}
|
}
|
||||||
|
|
||||||
install_git_alpine() {
|
install_git_alpine() {
|
||||||
info "Installing Git via apk..."
|
info "Installing Git via apk..."
|
||||||
if [[ $EUID -eq 0 ]]; then
|
run_as_root apk add --no-cache git
|
||||||
apk add --no-cache git
|
}
|
||||||
else
|
|
||||||
sudo apk add --no-cache git
|
install_git_suse() {
|
||||||
fi
|
info "Installing Git via zypper..."
|
||||||
|
ensure_sudo
|
||||||
|
run_as_root zypper install -y git
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -351,6 +481,13 @@ prompt_yes_no() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check if stdin is a terminal
|
||||||
|
if [[ ! -t 0 ]]; then
|
||||||
|
# Non-interactive, use default
|
||||||
|
[[ "$default" == "y" ]]
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
local yn_hint
|
local yn_hint
|
||||||
if [[ "$default" == "y" ]]; then
|
if [[ "$default" == "y" ]]; then
|
||||||
yn_hint="[Y/n]"
|
yn_hint="[Y/n]"
|
||||||
@@ -359,13 +496,13 @@ prompt_yes_no() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
echo -en "${CYAN}$prompt${NC} $yn_hint "
|
echo -en "${CYAN}$prompt${NC} $yn_hint " >&2
|
||||||
read -r answer
|
read -r answer
|
||||||
answer="${answer:-$default}"
|
answer="${answer:-$default}"
|
||||||
case "$answer" in
|
case "$answer" in
|
||||||
[Yy]|[Yy][Ee][Ss]) return 0 ;;
|
[Yy]|[Yy][Ee][Ss]) return 0 ;;
|
||||||
[Nn]|[Nn][Oo]) return 1 ;;
|
[Nn]|[Nn][Oo]) return 1 ;;
|
||||||
*) echo "Please answer yes or no." ;;
|
*) echo "Please answer yes or no." >&2 ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
@@ -387,12 +524,21 @@ detect_shell_profile() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
bash)
|
bash)
|
||||||
if [[ -f "$HOME/.bashrc" ]]; then
|
# macOS uses .bash_profile, Linux typically uses .bashrc
|
||||||
echo "$HOME/.bashrc"
|
if [[ "$(uname -s)" == "Darwin" ]]; then
|
||||||
elif [[ -f "$HOME/.bash_profile" ]]; then
|
if [[ -f "$HOME/.bash_profile" ]]; then
|
||||||
echo "$HOME/.bash_profile"
|
echo "$HOME/.bash_profile"
|
||||||
|
else
|
||||||
|
echo "$HOME/.profile"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "$HOME/.profile"
|
if [[ -f "$HOME/.bashrc" ]]; then
|
||||||
|
echo "$HOME/.bashrc"
|
||||||
|
elif [[ -f "$HOME/.bash_profile" ]]; then
|
||||||
|
echo "$HOME/.bash_profile"
|
||||||
|
else
|
||||||
|
echo "$HOME/.profile"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
fish)
|
fish)
|
||||||
@@ -410,13 +556,13 @@ add_to_path() {
|
|||||||
profile=$(detect_shell_profile)
|
profile=$(detect_shell_profile)
|
||||||
|
|
||||||
# Check if already in PATH
|
# Check if already in PATH
|
||||||
if echo "$PATH" | tr ':' '\n' | grep -qx "$bin_dir"; then
|
if [[ ":$PATH:" == *":$bin_dir:"* ]]; then
|
||||||
info "PATH already includes $bin_dir"
|
info "PATH already includes $bin_dir"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if already in profile
|
# Check if already in profile
|
||||||
if [[ -f "$profile" ]] && grep -q "$bin_dir" "$profile" 2>/dev/null; then
|
if [[ -f "$profile" ]] && grep -qF "$bin_dir" "$profile" 2>/dev/null; then
|
||||||
info "PATH export already in $profile"
|
info "PATH export already in $profile"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -430,6 +576,8 @@ add_to_path() {
|
|||||||
shell_name="$(basename "${SHELL:-/bin/bash}")"
|
shell_name="$(basename "${SHELL:-/bin/bash}")"
|
||||||
|
|
||||||
if [[ "$shell_name" == "fish" ]]; then
|
if [[ "$shell_name" == "fish" ]]; then
|
||||||
|
echo "" >> "$profile"
|
||||||
|
echo "# Added by Claudeman installer" >> "$profile"
|
||||||
echo "fish_add_path $bin_dir" >> "$profile"
|
echo "fish_add_path $bin_dir" >> "$profile"
|
||||||
else
|
else
|
||||||
echo "" >> "$profile"
|
echo "" >> "$profile"
|
||||||
@@ -452,6 +600,10 @@ setup_systemd_service() {
|
|||||||
|
|
||||||
mkdir -p "$service_dir"
|
mkdir -p "$service_dir"
|
||||||
|
|
||||||
|
# Find node binary path
|
||||||
|
local node_path
|
||||||
|
node_path=$(command -v node)
|
||||||
|
|
||||||
# Create service file
|
# Create service file
|
||||||
cat > "$service_file" << EOF
|
cat > "$service_file" << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
@@ -460,11 +612,12 @@ After=network.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=$INSTALL_DIR/dist/index.js web --https
|
ExecStart=$node_path $INSTALL_DIR/dist/index.js web --https
|
||||||
WorkingDirectory=$HOME
|
WorkingDirectory=$HOME
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=10
|
RestartSec=10
|
||||||
Environment=NODE_ENV=production
|
Environment=NODE_ENV=production
|
||||||
|
Environment=PATH=$PATH
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=default.target
|
WantedBy=default.target
|
||||||
@@ -474,7 +627,7 @@ EOF
|
|||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
|
|
||||||
# Enable service
|
# Enable service
|
||||||
systemctl --user enable claudeman-web.service
|
systemctl --user enable claudeman-web.service 2>/dev/null || true
|
||||||
|
|
||||||
# Enable lingering (allows service to run after logout)
|
# Enable lingering (allows service to run after logout)
|
||||||
if command -v loginctl &>/dev/null; then
|
if command -v loginctl &>/dev/null; then
|
||||||
@@ -485,18 +638,40 @@ EOF
|
|||||||
info "Start with: systemctl --user start claudeman-web"
|
info "Start with: systemctl --user start claudeman-web"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Installation Helpers
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
install_dependency() {
|
||||||
|
local dep_name="$1"
|
||||||
|
local os="$2"
|
||||||
|
local distro="$3"
|
||||||
|
|
||||||
|
local install_func="install_${dep_name}_${distro:-$os}"
|
||||||
|
|
||||||
|
# Try distro-specific first, then OS-level
|
||||||
|
if [[ "$os" == "macos" ]]; then
|
||||||
|
install_func="install_${dep_name}_macos"
|
||||||
|
elif ! declare -f "$install_func" &>/dev/null; then
|
||||||
|
die "Don't know how to install $dep_name on $distro. Please install it manually."
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$install_func"
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Main Installation
|
# Main Installation
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
print_banner() {
|
print_banner() {
|
||||||
echo -e "${CYAN}${BOLD}"
|
echo -e "${CYAN}${BOLD}"
|
||||||
echo " ____ _ _ "
|
cat << 'EOF'
|
||||||
echo " / ___|| | __ _ _ _ __| | ___ _ __ ___ __ _ _ __ "
|
____ _ _
|
||||||
echo "| | | |/ _\` | | | |/ _\` |/ _ \\ '_ \` _ \\ / _\` | '_ \\ "
|
/ ___| | __ _ _ _ __| | ___ _ __ ___ __ _ _ __
|
||||||
echo "| |____| | (_| | |_| | (_| | __/ | | | | | (_| | | | | "
|
| | | |/ _` | | | |/ _` |/ _ \ '_ ` _ \ / _` | '_ \
|
||||||
echo " \\_____|_|\\__,_|\\__,_|\\__,_|\\___|_| |_| |_|\\__,_|_| |_| "
|
| |___| | (_| | |_| | (_| | __/ | | | | | (_| | | | |
|
||||||
echo ""
|
\____|_|\__,_|\__,_|\__,_|\___|_| |_| |_|\__,_|_| |_|
|
||||||
|
EOF
|
||||||
echo -e "${NC}${DIM} The missing control plane for Claude Code${NC}"
|
echo -e "${NC}${DIM} The missing control plane for Claude Code${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
@@ -504,6 +679,11 @@ print_banner() {
|
|||||||
main() {
|
main() {
|
||||||
print_banner
|
print_banner
|
||||||
|
|
||||||
|
# Check for curl/wget first
|
||||||
|
if ! check_curl_or_wget; then
|
||||||
|
die "curl or wget is required but neither is installed. Please install one first."
|
||||||
|
fi
|
||||||
|
|
||||||
# Detect system
|
# Detect system
|
||||||
local os arch distro=""
|
local os arch distro=""
|
||||||
os=$(detect_os)
|
os=$(detect_os)
|
||||||
@@ -524,14 +704,7 @@ main() {
|
|||||||
info "Checking Git..."
|
info "Checking Git..."
|
||||||
if ! check_git; then
|
if ! check_git; then
|
||||||
if prompt_yes_no "Git is not installed. Install it now?"; then
|
if prompt_yes_no "Git is not installed. Install it now?"; then
|
||||||
case "$os-$distro" in
|
install_dependency "git" "$os" "$distro"
|
||||||
macos-*) install_git_macos ;;
|
|
||||||
linux-debian) install_git_debian ;;
|
|
||||||
linux-fedora) install_git_fedora ;;
|
|
||||||
linux-arch) install_git_arch ;;
|
|
||||||
linux-alpine) install_git_alpine ;;
|
|
||||||
*) die "Don't know how to install Git on this system. Please install it manually." ;;
|
|
||||||
esac
|
|
||||||
else
|
else
|
||||||
die "Git is required to install Claudeman."
|
die "Git is required to install Claudeman."
|
||||||
fi
|
fi
|
||||||
@@ -548,15 +721,11 @@ main() {
|
|||||||
warn "Node.js $node_version is installed but version $MIN_NODE_VERSION+ is required."
|
warn "Node.js $node_version is installed but version $MIN_NODE_VERSION+ is required."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if prompt_yes_no "Install Node.js $MIN_NODE_VERSION+?"; then
|
if prompt_yes_no "Install Node.js v$TARGET_NODE_VERSION?"; then
|
||||||
case "$os-$distro" in
|
install_dependency "node" "$os" "$distro"
|
||||||
macos-*) install_node_macos ;;
|
|
||||||
linux-debian) install_node_debian ;;
|
# Rehash to pick up new node
|
||||||
linux-fedora) install_node_fedora ;;
|
hash -r 2>/dev/null || true
|
||||||
linux-arch) install_node_arch ;;
|
|
||||||
linux-alpine) install_node_alpine ;;
|
|
||||||
*) die "Don't know how to install Node.js on this system. Please install it manually." ;;
|
|
||||||
esac
|
|
||||||
else
|
else
|
||||||
die "Node.js $MIN_NODE_VERSION+ is required to run Claudeman."
|
die "Node.js $MIN_NODE_VERSION+ is required to run Claudeman."
|
||||||
fi
|
fi
|
||||||
@@ -575,14 +744,7 @@ main() {
|
|||||||
info "Checking GNU Screen..."
|
info "Checking GNU Screen..."
|
||||||
if ! check_screen; then
|
if ! check_screen; then
|
||||||
if prompt_yes_no "GNU Screen is not installed. Install it now?"; then
|
if prompt_yes_no "GNU Screen is not installed. Install it now?"; then
|
||||||
case "$os-$distro" in
|
install_dependency "screen" "$os" "$distro"
|
||||||
macos-*) install_screen_macos ;;
|
|
||||||
linux-debian) install_screen_debian ;;
|
|
||||||
linux-fedora) install_screen_fedora ;;
|
|
||||||
linux-arch) install_screen_arch ;;
|
|
||||||
linux-alpine) install_screen_alpine ;;
|
|
||||||
*) die "Don't know how to install GNU Screen on this system. Please install it manually." ;;
|
|
||||||
esac
|
|
||||||
else
|
else
|
||||||
die "GNU Screen is required for session persistence."
|
die "GNU Screen is required for session persistence."
|
||||||
fi
|
fi
|
||||||
@@ -598,7 +760,7 @@ main() {
|
|||||||
echo -e " ${DIM}Claudeman requires Claude CLI to manage AI sessions.${NC}"
|
echo -e " ${DIM}Claudeman requires Claude CLI to manage AI sessions.${NC}"
|
||||||
echo -e " ${DIM}Install it with:${NC}"
|
echo -e " ${DIM}Install it with:${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${CYAN}npm install -g @anthropic-ai/claude-code${NC}"
|
echo -e " ${CYAN}curl -fsSL https://claude.ai/install.sh | bash${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e " ${DIM}Or see: https://docs.anthropic.com/en/docs/claude-code${NC}"
|
echo -e " ${DIM}Or see: https://docs.anthropic.com/en/docs/claude-code${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -619,14 +781,26 @@ main() {
|
|||||||
if [[ -d "$INSTALL_DIR/.git" ]]; then
|
if [[ -d "$INSTALL_DIR/.git" ]]; then
|
||||||
info "Existing installation found, updating..."
|
info "Existing installation found, updating..."
|
||||||
cd "$INSTALL_DIR"
|
cd "$INSTALL_DIR"
|
||||||
git fetch --quiet origin
|
|
||||||
git reset --hard origin/master --quiet
|
# Check for local changes
|
||||||
|
if ! git diff --quiet 2>/dev/null || ! git diff --staged --quiet 2>/dev/null; then
|
||||||
|
warn "Local changes detected in $INSTALL_DIR"
|
||||||
|
if prompt_yes_no "Discard local changes and update?" "n"; then
|
||||||
|
git fetch --quiet origin
|
||||||
|
git reset --hard origin/master --quiet
|
||||||
|
else
|
||||||
|
info "Keeping existing installation, skipping update"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
git fetch --quiet origin
|
||||||
|
git reset --hard origin/master --quiet
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# Create parent directory
|
# Create parent directory
|
||||||
mkdir -p "$(dirname "$INSTALL_DIR")"
|
mkdir -p "$(dirname "$INSTALL_DIR")"
|
||||||
|
|
||||||
# Clone repository
|
# Clone repository (shallow for speed)
|
||||||
git clone --quiet "$REPO_URL" "$INSTALL_DIR"
|
git clone --quiet --depth 1 "$REPO_URL" "$INSTALL_DIR"
|
||||||
cd "$INSTALL_DIR"
|
cd "$INSTALL_DIR"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -637,10 +811,10 @@ main() {
|
|||||||
# ========================================================================
|
# ========================================================================
|
||||||
|
|
||||||
info "Installing dependencies..."
|
info "Installing dependencies..."
|
||||||
npm install --quiet --no-fund --no-audit
|
npm install --quiet --no-fund --no-audit 2>/dev/null || npm install --no-fund --no-audit
|
||||||
|
|
||||||
info "Building..."
|
info "Building..."
|
||||||
npm run build --quiet
|
npm run build --quiet 2>/dev/null || npm run build
|
||||||
|
|
||||||
success "Build complete"
|
success "Build complete"
|
||||||
|
|
||||||
@@ -653,9 +827,15 @@ main() {
|
|||||||
|
|
||||||
# Create symlink in a common PATH location if possible
|
# Create symlink in a common PATH location if possible
|
||||||
local symlink_dir="$HOME/.local/bin"
|
local symlink_dir="$HOME/.local/bin"
|
||||||
if [[ -d "$symlink_dir" ]] || mkdir -p "$symlink_dir" 2>/dev/null; then
|
mkdir -p "$symlink_dir" 2>/dev/null || true
|
||||||
|
if [[ -d "$symlink_dir" ]]; then
|
||||||
ln -sf "$INSTALL_DIR/dist/index.js" "$symlink_dir/claudeman"
|
ln -sf "$INSTALL_DIR/dist/index.js" "$symlink_dir/claudeman"
|
||||||
info "Created symlink: $symlink_dir/claudeman"
|
info "Created symlink: $symlink_dir/claudeman"
|
||||||
|
|
||||||
|
# Add ~/.local/bin to PATH if not already there
|
||||||
|
if [[ ":$PATH:" != *":$symlink_dir:"* ]]; then
|
||||||
|
add_to_path "$symlink_dir"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
@@ -664,7 +844,7 @@ main() {
|
|||||||
|
|
||||||
if [[ "$os" == "linux" ]] && [[ "$SKIP_SYSTEMD" != "1" ]] && command -v systemctl &>/dev/null; then
|
if [[ "$os" == "linux" ]] && [[ "$SKIP_SYSTEMD" != "1" ]] && command -v systemctl &>/dev/null; then
|
||||||
echo ""
|
echo ""
|
||||||
if prompt_yes_no "Set up systemd service for auto-start?"; then
|
if prompt_yes_no "Set up systemd service for auto-start?" "n"; then
|
||||||
setup_systemd_service
|
setup_systemd_service
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -705,14 +885,14 @@ main() {
|
|||||||
|
|
||||||
if ! check_claude; then
|
if ! check_claude; then
|
||||||
echo -e " ${YELLOW}${BOLD}Reminder:${NC} Install Claude CLI to start using Claudeman:"
|
echo -e " ${YELLOW}${BOLD}Reminder:${NC} Install Claude CLI to start using Claudeman:"
|
||||||
echo -e " ${CYAN}npm install -g @anthropic-ai/claude-code${NC}"
|
echo -e " ${CYAN}curl -fsSL https://claude.ai/install.sh | bash${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Remind to reload shell if PATH was modified
|
# Remind to reload shell if PATH was modified
|
||||||
local profile
|
local profile
|
||||||
profile=$(detect_shell_profile)
|
profile=$(detect_shell_profile)
|
||||||
if ! echo "$PATH" | tr ':' '\n' | grep -qx "$bin_dir"; then
|
if [[ ":$PATH:" != *":$bin_dir:"* ]]; then
|
||||||
echo -e " ${DIM}Restart your shell or run: source $profile${NC}"
|
echo -e " ${DIM}Restart your shell or run: source $profile${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
|
|||||||
+161
-52
@@ -1,87 +1,196 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Claudeman postinstall verification script
|
||||||
|
* Runs after `npm install` to check environment readiness
|
||||||
|
*/
|
||||||
|
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
import { homedir } from 'os';
|
import { homedir, platform } from 'os';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
// Colors (with fallback for no-color environments)
|
// ============================================================================
|
||||||
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
// Configuration
|
||||||
const green = (s) => useColor ? `\x1b[32m${s}\x1b[0m` : s;
|
// ============================================================================
|
||||||
const yellow = (s) => useColor ? `\x1b[33m${s}\x1b[0m` : s;
|
|
||||||
const red = (s) => useColor ? `\x1b[31m${s}\x1b[0m` : s;
|
|
||||||
const bold = (s) => useColor ? `\x1b[1m${s}\x1b[0m` : s;
|
|
||||||
|
|
||||||
console.log(bold('Claudeman postinstall check...'));
|
const MIN_NODE_VERSION = 18;
|
||||||
|
|
||||||
|
// Claude CLI search paths (must match src/session.ts)
|
||||||
|
const home = homedir();
|
||||||
|
const CLAUDE_SEARCH_PATHS = [
|
||||||
|
join(home, '.local/bin/claude'),
|
||||||
|
join(home, '.claude/local/claude'),
|
||||||
|
'/usr/local/bin/claude',
|
||||||
|
join(home, '.npm-global/bin/claude'),
|
||||||
|
join(home, 'bin/claude'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Colors (with fallback for no-color environments)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
green: (s) => useColor ? `\x1b[32m${s}\x1b[0m` : s,
|
||||||
|
yellow: (s) => useColor ? `\x1b[33m${s}\x1b[0m` : s,
|
||||||
|
red: (s) => useColor ? `\x1b[31m${s}\x1b[0m` : s,
|
||||||
|
cyan: (s) => useColor ? `\x1b[36m${s}\x1b[0m` : s,
|
||||||
|
bold: (s) => useColor ? `\x1b[1m${s}\x1b[0m` : s,
|
||||||
|
dim: (s) => useColor ? `\x1b[2m${s}\x1b[0m` : s,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Utility Functions
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a command exists in PATH
|
||||||
|
* Works on Unix and Windows
|
||||||
|
*/
|
||||||
|
function commandExists(cmd) {
|
||||||
|
try {
|
||||||
|
const checkCmd = platform() === 'win32' ? `where ${cmd}` : `command -v ${cmd}`;
|
||||||
|
execSync(checkCmd, { stdio: 'pipe' });
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get install instructions for screen based on platform
|
||||||
|
*/
|
||||||
|
function getScreenInstallInstructions() {
|
||||||
|
const os = platform();
|
||||||
|
|
||||||
|
if (os === 'darwin') {
|
||||||
|
return [
|
||||||
|
' macOS: brew install screen',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os === 'linux') {
|
||||||
|
return [
|
||||||
|
' Ubuntu/Debian: sudo apt install screen',
|
||||||
|
' Fedora/RHEL: sudo dnf install screen',
|
||||||
|
' Arch Linux: sudo pacman -S screen',
|
||||||
|
' Alpine: sudo apk add screen',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os === 'win32') {
|
||||||
|
return [
|
||||||
|
' Windows: Use WSL (Windows Subsystem for Linux)',
|
||||||
|
' GNU Screen is not available on native Windows',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [' Please install GNU Screen for your platform'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Main Checks
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
console.log(colors.bold('Claudeman postinstall check...'));
|
||||||
|
console.log('');
|
||||||
|
|
||||||
let hasWarnings = false;
|
let hasWarnings = false;
|
||||||
|
let hasErrors = false;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// 1. Check Node.js version >= 18
|
// 1. Check Node.js version >= 18
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
const nodeVersion = process.versions.node;
|
const nodeVersion = process.versions.node;
|
||||||
const majorVersion = parseInt(nodeVersion.split('.')[0], 10);
|
const majorVersion = parseInt(nodeVersion.split('.')[0], 10);
|
||||||
|
|
||||||
if (majorVersion < 18) {
|
if (majorVersion < MIN_NODE_VERSION) {
|
||||||
console.log(red(`✗ Node.js v${nodeVersion} is too old (requires >=18)`));
|
console.log(colors.red(`✗ Node.js v${nodeVersion} is too old`));
|
||||||
process.exit(1);
|
console.log(colors.dim(` Minimum required: v${MIN_NODE_VERSION}`));
|
||||||
|
console.log('');
|
||||||
|
hasErrors = true;
|
||||||
} else {
|
} else {
|
||||||
console.log(green(`✓ Node.js v${nodeVersion}`) + ' (meets >=18 requirement)');
|
console.log(colors.green(`✓ Node.js v${nodeVersion}`) + colors.dim(` (meets >=v${MIN_NODE_VERSION} requirement)`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// 2. Check if GNU Screen is installed
|
// 2. Check if GNU Screen is installed
|
||||||
let screenFound = false;
|
// ----------------------------------------------------------------------------
|
||||||
try {
|
|
||||||
execSync('which screen', { stdio: 'pipe' });
|
if (commandExists('screen')) {
|
||||||
screenFound = true;
|
console.log(colors.green('✓ GNU Screen found'));
|
||||||
console.log(green('✓ GNU Screen found'));
|
} else {
|
||||||
} catch {
|
hasWarnings = true;
|
||||||
hasWarnings = true;
|
console.log(colors.yellow('⚠ GNU Screen not found'));
|
||||||
console.log(yellow('⚠ GNU Screen not found'));
|
console.log(colors.dim(' Screen is required for session persistence.'));
|
||||||
console.log(' Install:');
|
console.log(colors.dim(' Install:'));
|
||||||
console.log(' Ubuntu/Debian: sudo apt install screen');
|
for (const instruction of getScreenInstallInstructions()) {
|
||||||
console.log(' macOS: brew install screen');
|
console.log(colors.dim(instruction));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// 3. Check if Claude CLI is found
|
// 3. Check if Claude CLI is found
|
||||||
const home = homedir();
|
// ----------------------------------------------------------------------------
|
||||||
const claudePaths = [
|
|
||||||
join(home, '.local/bin/claude'),
|
|
||||||
join(home, '.claude/local/claude'),
|
|
||||||
'/usr/local/bin/claude',
|
|
||||||
join(home, '.npm-global/bin/claude'),
|
|
||||||
join(home, 'bin/claude'),
|
|
||||||
];
|
|
||||||
|
|
||||||
let claudeFound = false;
|
let claudeFound = false;
|
||||||
|
let claudePath = null;
|
||||||
|
|
||||||
// First try `which claude`
|
// First try PATH lookup
|
||||||
try {
|
if (commandExists('claude')) {
|
||||||
execSync('which claude', { stdio: 'pipe' });
|
claudeFound = true;
|
||||||
claudeFound = true;
|
try {
|
||||||
} catch {
|
const checkCmd = platform() === 'win32' ? 'where claude' : 'command -v claude';
|
||||||
// Check known paths
|
claudePath = execSync(checkCmd, { stdio: 'pipe', encoding: 'utf-8' }).trim().split('\n')[0];
|
||||||
for (const p of claudePaths) {
|
} catch {
|
||||||
if (existsSync(p)) {
|
// Ignore, we know it exists
|
||||||
claudeFound = true;
|
}
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
// Check known paths if not found in PATH
|
||||||
|
if (!claudeFound) {
|
||||||
|
for (const p of CLAUDE_SEARCH_PATHS) {
|
||||||
|
if (existsSync(p)) {
|
||||||
|
claudeFound = true;
|
||||||
|
claudePath = p;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claudeFound) {
|
if (claudeFound) {
|
||||||
console.log(green('✓ Claude CLI found'));
|
const pathInfo = claudePath ? colors.dim(` (${claudePath})`) : '';
|
||||||
|
console.log(colors.green('✓ Claude CLI found') + pathInfo);
|
||||||
} else {
|
} else {
|
||||||
hasWarnings = true;
|
hasWarnings = true;
|
||||||
console.log(yellow('⚠ Claude CLI not found'));
|
console.log(colors.yellow('⚠ Claude CLI not found'));
|
||||||
console.log(' Install: curl -fsSL https://claude.ai/install.sh | bash');
|
console.log(colors.dim(' Claude CLI is required to run AI sessions.'));
|
||||||
|
console.log(colors.dim(' Install:'));
|
||||||
|
console.log(colors.cyan(' curl -fsSL https://claude.ai/install.sh | bash'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print next steps
|
// ----------------------------------------------------------------------------
|
||||||
|
// Print Summary and Next Steps
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(bold('Next steps:'));
|
|
||||||
console.log(' npm run build');
|
if (hasErrors) {
|
||||||
console.log(' claudeman web');
|
console.log(colors.red(colors.bold('Installation cannot proceed due to errors above.')));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(colors.bold('Next steps:'));
|
||||||
|
console.log(colors.dim(' 1. Build: ') + colors.cyan('npm run build'));
|
||||||
|
console.log(colors.dim(' 2. Start: ') + colors.cyan('claudeman web --https'));
|
||||||
|
console.log(colors.dim(' 3. Open: ') + colors.cyan('https://localhost:3000'));
|
||||||
|
|
||||||
if (hasWarnings) {
|
if (hasWarnings) {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(yellow('Note: Resolve warnings above for full functionality.'));
|
console.log(colors.yellow('Note: Resolve warnings above for full functionality.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
|||||||
Reference in New Issue
Block a user