memory-os/setup.sh

356 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
# ──────────────────────────────────────────────────────────────────────────────
# Memory OS — Setup Script
# ──────────────────────────────────────────────────────────────────────────────
# Installs the complete Memory OS stack into your Hermes Agent.
#
# Usage:
# curl -sSL https://raw.githubusercontent.com/ClaudioDrews/memory-os/main/setup.sh | bash
#
# Or, if you already cloned the repo:
# bash setup.sh
#
# What this script does:
# 1. Checks prerequisites (Docker, Python, Hermes)
# 2. Clones the repo (if needed)
# 3. Installs Python dependencies
# 4. Creates SQLite databases (state.db, memory_store.db)
# 5. Installs the Icarus plugin
# 6. Creates wiki/vault directory structure
# 7. Starts Redis + Qdrant + Worker (Docker Compose)
# 8. Configures environment variables
# 9. Applies rulebook modifications
#
# Idempotent — safe to run multiple times.
# ──────────────────────────────────────────────────────────────────────────────
set -euo pipefail
# ── Colors ────────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BOLD='\033[1m'
NC='\033[0m'
PASS=0
FAIL=0
WARN=0
ok() { printf " ${GREEN}${NC} %s\n" "$1"; PASS=$((PASS + 1)); }
fail() { printf " ${RED}${NC} %s\n" "$1"; FAIL=$((FAIL + 1)); }
warn() { printf " ${YELLOW}⚠️${NC} %s\n" "$1"; WARN=$((WARN + 1)); }
info() { printf " 📘 %s\n" "$1"; }
banner() {
echo ""
echo -e "${BOLD}── $1 ──${NC}"
echo ""
}
# ── Detect script directory ──────────────────────────────────────────────────
# When run via curl|bash, SCRIPT_DIR is the current directory.
# When run from a cloned repo, it's the script's location.
if [ -n "${BASH_SOURCE[0]:-}" ] && [ "${BASH_SOURCE[0]}" != "bash" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
else
SCRIPT_DIR="$(pwd)"
fi
REPO_URL="https://github.com/ClaudioDrews/memory-os.git"
REPO_DIR="${HOME}/memory-os"
HERMES_HOME="${HOME}/.hermes"
VAULT_PATH="${VAULT_PATH:-${HOME}/vault}"
ENV_FILE="${HERMES_HOME}/.env"
# ──────────────────────────────────────────────────────────────────────────────
# Phase 1: Bootstrap — clone repo if needed
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 1: Bootstrap"
if [ -d "${REPO_DIR}/.git" ]; then
ok "Repo already exists at ${REPO_DIR}"
cd "${REPO_DIR}"
else
info "Cloning Memory OS..."
git clone "${REPO_URL}" "${REPO_DIR}" 2>&1 | tail -1
cd "${REPO_DIR}"
ok "Repo cloned to ${REPO_DIR}"
fi
# ──────────────────────────────────────────────────────────────────────────────
# Phase 2: Pre-flight Checks
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 2: Pre-flight Checks"
# Docker
if docker info >/dev/null 2>&1; then
ok "Docker $(docker --version | awk '{print $3}' | tr -d ',')"
else
fail "Docker is not running — install and start Docker first"
exit 1
fi
# Docker Compose
if docker compose version >/dev/null 2>&1; then
ok "Docker Compose $(docker compose version --short 2>/dev/null || echo 'ok')"
else
warn "Docker Compose plugin not detected — required to start the stack"
fi
# Python
PYTHON_VERSION=$(python3 --version 2>/dev/null | awk '{print $2}' || echo "none")
if [ "$PYTHON_VERSION" != "none" ]; then
ok "Python ${PYTHON_VERSION}"
else
fail "Python 3 not found"
exit 1
fi
# Hermes
if command -v hermes >/dev/null 2>&1 || [ -f "${HERMES_HOME}/hermes-agent/cli.py" ]; then
ok "Hermes Agent detected at ${HERMES_HOME}"
else
warn "Hermes Agent CLI not found — some features will be limited"
fi
# ──────────────────────────────────────────────────────────────────────────────
# Phase 3: Python Dependencies
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 3: Python Dependencies"
if [ -f "requirements.txt" ]; then
# Install in current environment (local user or venv)
if pip install --user -r requirements.txt --quiet 2>&1 | tail -3; then
ok "Python dependencies installed"
else
warn "pip install failed — trying with --break-system-packages"
pip install --break-system-packages -r requirements.txt --quiet 2>&1 || {
fail "Could not install Python dependencies"
exit 1
}
ok "Python dependencies installed (--break-system-packages)"
fi
else
fail "requirements.txt not found at $(pwd)"
exit 1
fi
# ──────────────────────────────────────────────────────────────────────────────
# Phase 4: SQLite Databases
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 4: Database Setup"
if [ -f "setup/setup_db.py" ]; then
python3 setup/setup_db.py 2>&1 && \
ok "SQLite databases created (state.db, memory_store.db)" || \
fail "setup_db.py failed"
else
fail "setup/setup_db.py not found"
exit 1
fi
# ──────────────────────────────────────────────────────────────────────────────
# Phase 5: Icarus Plugin
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 5: Icarus Plugin"
ICARUS_DEST="${HERMES_HOME}/plugins/icarus"
if [ -d "icarus" ]; then
mkdir -p "${HERMES_HOME}/plugins"
cp -r icarus/ "${ICARUS_DEST}/"
ok "Icarus plugin installed at ${ICARUS_DEST}"
else
fail "icarus/ directory not found"
exit 1
fi
# ──────────────────────────────────────────────────────────────────────────────
# Phase 6: Wiki + Vault Structure (BEFORE Docker — prevents root ownership)
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 6: Wiki & Vault"
mkdir -p "${VAULT_PATH}/wiki/"{raw,concepts,entities,comparisons,_meta,_archive}
mkdir -p "${VAULT_PATH}/fabric"
ok "Directory structure created at ${VAULT_PATH}"
# ──────────────────────────────────────────────────────────────────────────────
# Phase 7: Docker Stack
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 7: Docker Stack"
DOCKER_DIR="${REPO_DIR}/docker"
if [ ! -d "${DOCKER_DIR}" ]; then
fail "docker/ directory not found at ${REPO_DIR}"
exit 1
fi
cd "${DOCKER_DIR}"
# Detect OpenRouter API key from Hermes .env
OPENROUTER_KEY=""
if [ -f "${ENV_FILE}" ]; then
# Try OPENROUTER_DS_API_KEY first (Hermes default), then OPENROUTER_API_KEY
OPENROUTER_KEY=$(grep -oP 'OPENROUTER_DS_API_KEY=\K.*' "${ENV_FILE}" 2>/dev/null | head -1 || true)
if [ -z "${OPENROUTER_KEY}" ]; then
OPENROUTER_KEY=$(grep -oP 'OPENROUTER_API_KEY=\K.*' "${ENV_FILE}" 2>/dev/null | head -1 || true)
fi
fi
if [ -z "${OPENROUTER_KEY}" ]; then
echo ""
echo -e " ${YELLOW}Could not find your OpenRouter key in Hermes .env.${NC}"
echo " The worker needs it to generate embeddings."
echo ""
read -r -p " Paste your OpenRouter key (e.g. sk-or-v1-...): " OPENROUTER_KEY
echo ""
fi
# Generate random Redis password
REDIS_PW=$(openssl rand -hex 16)
# Create Docker Compose .env
cat > .env << DOCKERENV
OPENROUTER_API_KEY=${OPENROUTER_KEY}
REDIS_PASSWORD=${REDIS_PW}
QDRANT_API_KEY=
EMBEDDING_DIMS=4096
COLLECTION_NAME=knowledge_base
LOG_LEVEL=INFO
MEMORY_OS_WIKI_PATH=${VAULT_PATH}/wiki
MEMORY_OS_HERMES_HOME=${HERMES_HOME}
MEMORY_OS_FABRIC_DIR=${VAULT_PATH}/fabric
DOCKERENV
ok "docker/.env created"
# Pull pre-built images first (Redis, Qdrant) — fast
info "Downloading pre-built images (Redis, Qdrant)..."
docker compose pull redis qdrant 2>&1 | tail -3
ok "Base images downloaded"
# Build worker image — SLOW on first run (gcc + build-essential)
info "Building worker image (may take 5-10 minutes on first run)..."
info " (Future builds will use Docker cache)"
if docker compose build worker 2>&1; then
ok "Worker image built"
else
fail "Failed to build worker image"
exit 1
fi
# Start everything
info "Starting containers..."
if docker compose up -d 2>&1; then
ok "Docker stack started (redis, qdrant, worker)"
else
fail "docker compose up failed — check Docker"
exit 1
fi
# Wait for healthy
info "Waiting for services to become healthy..."
sleep 3
if docker compose ps --format json 2>/dev/null | grep -q '"Health":"healthy"'; then
ok "All services healthy"
else
warn "Services may still be starting — check with: docker compose ps"
fi
# Return to repo directory
cd "${REPO_DIR}"
# ──────────────────────────────────────────────────────────────────────────────
# Phase 8: Environment Variables
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 8: Hermes .env"
if [ ! -f "${ENV_FILE}" ]; then
warn "${ENV_FILE} not found — creating a new one"
touch "${ENV_FILE}"
fi
add_env() {
local key="$1"
local value="$2"
if grep -q "^${key}=" "${ENV_FILE}" 2>/dev/null; then
# Already exists — don't overwrite
return 0
fi
echo "${key}=${value}" >> "${ENV_FILE}"
}
add_env "FABRIC_DIR" "${VAULT_PATH}/fabric"
add_env "ICARUS_EXTRACTION_MAX_TOKENS" "4096"
add_env "ICARUS_EXTRACTION_MODEL" "deepseek/deepseek-v4-flash"
add_env "EMBEDDING_DIMS" "4096"
add_env "HERMES_AGENT_NAME" "hermes"
add_env "REDIS_PASSWORD" "${REDIS_PW}"
add_env "OPENROUTER_DS_API_KEY" "${OPENROUTER_KEY}"
ok "Environment variables added to Hermes .env"
# ──────────────────────────────────────────────────────────────────────────────
# Phase 9: Rulebook Modifications
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 9: Rulebook"
RULEBOOK="${HERMES_HOME}/rulebook.md"
if [ -f "${RULEBOOK}" ]; then
if grep -q "Memory OS amendment" "${RULEBOOK}" 2>/dev/null; then
ok "Rulebook amendments already applied"
else
info "Applying Memory OS amendments to rulebook..."
info "(See modifications/execution-agent-protocol.md for details)"
warn "Amendments NOT applied automatically — edit the rulebook manually"
warn "See: ${REPO_DIR}/modifications/execution-agent-protocol.md"
fi
else
warn "${RULEBOOK} not found — skipping modifications"
fi
# ──────────────────────────────────────────────────────────────────────────────
# Phase 10: Gateway
# ──────────────────────────────────────────────────────────────────────────────
banner "Phase 10: Gateway"
if command -v hermes >/dev/null 2>&1; then
info "Restarting Hermes gateway..."
if hermes gateway restart 2>&1; then
ok "Gateway restarted"
else
warn "Gateway restart failed — restart manually with: hermes gateway restart"
fi
else
warn "'hermes' command not available — restart the gateway manually"
fi
# ──────────────────────────────────────────────────────────────────────────────
# Summary
# ──────────────────────────────────────────────────────────────────────────────
banner "Summary"
echo " Passed: ${PASS}"
echo " Failed: ${FAIL}"
echo " Warnings: ${WARN}"
echo ""
if [ "${FAIL}" -eq 0 ]; then
echo -e " ${GREEN}${BOLD}✅ Memory OS installed successfully!${NC}"
echo ""
echo " To verify:"
echo " • /plugins → should show 'icarus'"
echo " • docker compose ps → 3 services (redis, qdrant, worker)"
echo " • fabric_brief() → fabric entries (initially empty)"
echo " • qdrant_search() → semantic search (requires populated wiki)"
echo ""
echo " Next step: add .md files to ${VAULT_PATH}/wiki/raw/"
echo " and the ingestion pipeline will index them automatically."
echo ""
else
echo -e " ${RED}${BOLD}${FAIL} error(s) found — review the output above.${NC}"
exit 1
fi