#!/bin/bash # OpenClaw Jarvis-Like Memory System - Installation Script # This script sets up the complete memory system from scratch set -e # Colors for output GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration WORKSPACE_DIR="${WORKSPACE_DIR:-$HOME/.openclaw/workspace}" USER_ID="${USER_ID:-$(whoami)}" REDIS_HOST="${REDIS_HOST:-127.0.0.1}" REDIS_PORT="${REDIS_PORT:-6379}" QDRANT_URL="${QDRANT_URL:-http://127.0.0.1:6333}" OLLAMA_URL="${OLLAMA_URL:-http://127.0.0.1:11434}" # Optional toggles (avoid touching host config during tests) SKIP_CRON="${SKIP_CRON:-0}" SKIP_HEARTBEAT="${SKIP_HEARTBEAT:-0}" SKIP_QDRANT_INIT="${SKIP_QDRANT_INIT:-0}" # If Redis/Qdrant/Ollama aren't reachable, attempt to start them via docker compose (recommended). START_DOCKER="${START_DOCKER:-1}" # Backup directory BACKUP_DIR="$WORKSPACE_DIR/.backups" TIMESTAMP=$(date +%Y%m%d_%H%M%S) BACKUP_PREFIX="$BACKUP_DIR/install_${TIMESTAMP}" echo "═══════════════════════════════════════════════════════════════" echo " OpenClaw Jarvis-Like Memory System - Installer" echo "═══════════════════════════════════════════════════════════════" echo "" echo -e "${BLUE}Backup Location: $BACKUP_DIR${NC}" echo "" # Create backup directory mkdir -p "$BACKUP_DIR" # Function to backup a file before modifying backup_file() { local file="$1" local backup_name="$2" if [ -f "$file" ]; then cp "$file" "$backup_name" echo -e "${GREEN} ✓ Backed up: $(basename $file) → $(basename $backup_name)${NC}" return 0 fi return 1 } # Helpers have_cmd() { command -v "$1" >/dev/null 2>&1; } need_sudo() { if [ "$(id -u)" -eq 0 ]; then return 1 fi return 0 } run_root() { if need_sudo; then sudo "$@" else "$@" fi } install_pkg_debian() { local pkgs=("$@") run_root apt-get update -y run_root apt-get install -y "${pkgs[@]}" } install_docker_debian() { # Prefer distro packages for speed/simplicity. install_pkg_debian ca-certificates curl gnupg lsb-release if ! have_cmd docker; then install_pkg_debian docker.io run_root systemctl enable --now docker >/dev/null 2>&1 || true fi # docker compose v2 plugin (preferred) if ! docker compose version >/dev/null 2>&1; then install_pkg_debian docker-compose-plugin || true fi # fallback: docker-compose v1 if ! docker compose version >/dev/null 2>&1 && ! have_cmd docker-compose; then install_pkg_debian docker-compose || true fi } # Step 1: Check/install system dependencies echo -e "${YELLOW}[1/10] Checking system dependencies...${NC}" if ! have_cmd curl; then echo " • Installing curl" if have_cmd apt-get; then install_pkg_debian curl else echo -e "${RED} ✗ Missing curl and no supported package manager detected.${NC}" exit 1 fi fi # Useful for quick testing (optional) if ! have_cmd redis-cli; then echo " • Installing redis-cli (redis-tools)" if have_cmd apt-get; then install_pkg_debian redis-tools fi fi if ! have_cmd python3; then echo " • Installing python3" if have_cmd apt-get; then install_pkg_debian python3 python3-pip python3-venv else echo -e "${RED} ✗ Python 3 not found. Please install Python 3.8+${NC}" exit 1 fi fi if ! have_cmd pip3; then echo " • Installing pip3" if have_cmd apt-get; then install_pkg_debian python3-pip python3-venv else echo -e "${RED} ✗ pip3 not found. Please install python3-pip${NC}" exit 1 fi fi # Docker is optional but recommended (used by docker-compose.yml) if ! have_cmd docker; then echo " • Docker not found — installing" if have_cmd apt-get; then install_docker_debian else echo -e "${RED} ✗ Docker not found and no supported package manager detected.${NC}" echo " Install Docker manually, then re-run install.sh" exit 1 fi else echo " ✓ Docker found" fi # Docker compose (v2 plugin preferred) if docker compose version >/dev/null 2>&1; then echo " ✓ docker compose (v2) found" elif have_cmd docker-compose; then echo " ✓ docker-compose (v1) found" else echo " • docker compose not found — attempting install" if have_cmd apt-get; then install_docker_debian fi fi PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}') echo " ✓ Python $PYTHON_VERSION found" # Step 2: Create directory structure + copy blueprint files echo -e "${YELLOW}[2/10] Creating directory structure...${NC}" mkdir -p "$WORKSPACE_DIR"/{skills/{mem-redis,qdrant-memory,task-queue}/scripts,memory,MEMORY_DEF,docs,config} touch "$WORKSPACE_DIR/memory/.gitkeep" # Copy scripts/docs from this repo into workspace (portable install) SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" cp -r "$SCRIPT_DIR/skills"/* "$WORKSPACE_DIR/skills/" 2>/dev/null || true cp -r "$SCRIPT_DIR/docs"/* "$WORKSPACE_DIR/docs/" 2>/dev/null || true cp -r "$SCRIPT_DIR/config"/* "$WORKSPACE_DIR/config/" 2>/dev/null || true echo " ✓ Directories created and files copied" # Step 3: Install Python dependencies (PEP 668-safe) echo -e "${YELLOW}[3/10] Installing Python dependencies...${NC}" REQ_FILE="$(dirname "$0")/requirements.txt" PYTHON_BIN="python3" pip_user_install() { if [ -f "$REQ_FILE" ]; then pip3 install --user -r "$REQ_FILE" else pip3 install --user redis qdrant-client requests urllib3 fi } pip_venv_install() { local venv_dir="$WORKSPACE_DIR/.venv" python3 -m venv "$venv_dir" "$venv_dir/bin/pip" install --upgrade pip setuptools wheel >/dev/null 2>&1 || true if [ -f "$REQ_FILE" ]; then "$venv_dir/bin/pip" install -r "$REQ_FILE" else "$venv_dir/bin/pip" install redis qdrant-client requests urllib3 fi PYTHON_BIN="$venv_dir/bin/python" } # Try user install first (fast path) if pip_user_install >/dev/null 2>&1; then echo " ✓ Dependencies installed (pip --user)" else echo " ℹ️ pip --user install blocked (PEP 668). Creating venv in $WORKSPACE_DIR/.venv" pip_venv_install echo " ✓ Dependencies installed (venv)" fi # Step 4: Test infrastructure connectivity echo -e "${YELLOW}[4/10] Testing infrastructure...${NC}" docker_compose_up() { local compose_dir="$(cd "$(dirname "$0")" && pwd)" if docker compose version >/dev/null 2>&1; then run_root docker compose -f "$compose_dir/docker-compose.yml" up -d elif have_cmd docker-compose; then run_root docker-compose -f "$compose_dir/docker-compose.yml" up -d fi } redis_ok=0 qdrant_ok=0 ollama_ok=0 # Test Redis if "$PYTHON_BIN" -c "import redis; r=redis.Redis(host='$REDIS_HOST', port=$REDIS_PORT); r.ping()" 2>/dev/null; then redis_ok=1 fi # Test Qdrant if curl -s "$QDRANT_URL/collections" >/dev/null 2>&1; then qdrant_ok=1 fi # Test Ollama if curl -s "$OLLAMA_URL/api/tags" >/dev/null 2>&1; then ollama_ok=1 fi if [ "$START_DOCKER" = "1" ] && { [ $redis_ok -eq 0 ] || [ $qdrant_ok -eq 0 ] || [ $ollama_ok -eq 0 ]; }; then echo " ℹ️ Infrastructure not reachable; attempting to start via docker compose" docker_compose_up || true sleep 2 if "$PYTHON_BIN" -c "import redis; r=redis.Redis(host='$REDIS_HOST', port=$REDIS_PORT); r.ping()" 2>/dev/null; then redis_ok=1; fi if curl -s "$QDRANT_URL/collections" >/dev/null 2>&1; then qdrant_ok=1; fi if curl -s "$OLLAMA_URL/api/tags" >/dev/null 2>&1; then ollama_ok=1; fi fi if [ $redis_ok -eq 1 ]; then echo " ✓ Redis connection OK" else echo -e "${RED} ✗ Redis connection failed ($REDIS_HOST:$REDIS_PORT)${NC}" fi if [ $qdrant_ok -eq 1 ]; then echo " ✓ Qdrant connection OK" else echo -e "${RED} ✗ Qdrant connection failed ($QDRANT_URL)${NC}" fi if [ $ollama_ok -eq 1 ]; then echo " ✓ Ollama connection OK" else echo -e "${YELLOW} ⚠️ Ollama not reachable ($OLLAMA_URL)${NC}" echo " Embeddings will fail until Ollama is running with snowflake-arctic-embed2." fi # Step 5: Backup existing files before modifying echo "" echo -e "${YELLOW}[5/10] Creating backups of existing files...${NC}" BACKUP_COUNT=0 # Backup existing crontab if crontab -l 2>/dev/null >/dev/null; then crontab -l > "${BACKUP_PREFIX}_crontab.bak.rush" 2>/dev/null echo -e "${GREEN} ✓ Backed up crontab → .backups/install_${TIMESTAMP}_crontab.bak.rush${NC}" ((BACKUP_COUNT++)) else echo " ℹ️ No existing crontab to backup" fi # Backup existing HEARTBEAT.md if backup_file "$WORKSPACE_DIR/HEARTBEAT.md" "${BACKUP_PREFIX}_HEARTBEAT.md.bak.rush"; then ((BACKUP_COUNT++)) fi # Backup existing .memory_env if backup_file "$WORKSPACE_DIR/.memory_env" "${BACKUP_PREFIX}_memory_env.bak.rush"; then ((BACKUP_COUNT++)) fi if [ $BACKUP_COUNT -eq 0 ]; then echo " ℹ️ No existing files to backup (fresh install)" else echo -e "${GREEN} ✓ $BACKUP_COUNT file(s) backed up to $BACKUP_DIR${NC}" fi # Step 6: Create environment configuration echo "" echo -e "${YELLOW}[6/10] Creating environment configuration...${NC}" cat > "$WORKSPACE_DIR/.memory_env" </dev/null > "$CRON_FILE" || true # Add memory backup cron jobs if not present if ! grep -q "cron_backup.py" "$CRON_FILE" 2>/dev/null; then echo "" >> "$CRON_FILE" echo "# Memory System - Daily backup (3:00 AM)" >> "$CRON_FILE" echo "0 3 * * * cd $WORKSPACE_DIR && $PYTHON_BIN skills/mem-redis/scripts/cron_backup.py >> /var/log/memory-backup.log 2>&1 || true" >> "$CRON_FILE" fi if ! grep -q "sliding_backup.sh" "$CRON_FILE" 2>/dev/null; then echo "" >> "$CRON_FILE" echo "# Memory System - File backup (3:30 AM)" >> "$CRON_FILE" echo "30 3 * * * $WORKSPACE_DIR/skills/qdrant-memory/scripts/sliding_backup.sh >> /var/log/memory-backup.log 2>&1 || true" >> "$CRON_FILE" fi crontab "$CRON_FILE" rm "$CRON_FILE" echo " ✓ Cron jobs configured" fi # Step 9: Create HEARTBEAT.md template echo -e "${YELLOW}[9/10] Creating HEARTBEAT.md...${NC}" if [ "$SKIP_HEARTBEAT" = "1" ]; then echo " ℹ️ SKIP_HEARTBEAT=1 set; skipping HEARTBEAT.md write" else cat > "$WORKSPACE_DIR/HEARTBEAT.md" <<'EOF' # HEARTBEAT.md - Memory System Automation ## Memory Buffer (Every Heartbeat) Saves current session context to Redis buffer: ```bash python3 ~/.openclaw/workspace/skills/mem-redis/scripts/save_mem.py --user-id YOUR_USER_ID ``` ## Daily Backup Schedule - **3:00 AM**: Redis buffer → Qdrant flush - **3:30 AM**: File-based sliding backup ## Manual Commands | Command | Action | |---------|--------| | `save mem` | Save all context to Redis | | `save q` | Store immediately to Qdrant | | `q ` | Search memories | EOF echo " ✓ HEARTBEAT.md created" fi # Create backup manifest echo "" echo -e "${YELLOW}Creating backup manifest...${NC}" MANIFEST_FILE="${BACKUP_PREFIX}_MANIFEST.txt" cat > "$MANIFEST_FILE" <> "$MANIFEST_FILE" fi done cat >> "$MANIFEST_FILE" <