feat: add one-click Docker setup for OpenClaw + Mem0
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
# OpenClaw + Mem0 Docker Setup
|
||||
# Copy this file to .env and fill in your values:
|
||||
# cp .env.example .env
|
||||
|
||||
# ── Required (one of these depending on mode) ───────────────────────────────
|
||||
|
||||
# Platform mode: Mem0 Cloud API key from https://app.mem0.ai
|
||||
MEM0_API_KEY=
|
||||
|
||||
# OSS mode: OpenAI key for embeddings, LLM, and the OpenClaw agent
|
||||
# Platform mode: only needed if you want a specific LLM for the agent itself
|
||||
OPENAI_API_KEY=
|
||||
|
||||
# ── Optional ─────────────────────────────────────────────────────────────────
|
||||
|
||||
# User ID for memory scoping (defaults to your OS username)
|
||||
MEM0_USER_ID=
|
||||
|
||||
# Web UI port (default: 18789)
|
||||
OPENCLAW_PORT=18789
|
||||
|
||||
# Config directory on host (default: ~/.openclaw)
|
||||
# OPENCLAW_CONFIG_DIR=~/.openclaw
|
||||
|
||||
# Timezone (default: UTC)
|
||||
TZ=UTC
|
||||
@@ -0,0 +1,102 @@
|
||||
# OpenClaw + Mem0 One-Click Docker Setup
|
||||
|
||||
Get a working OpenClaw agent with Mem0 persistent memory in under 2 minutes.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine with Compose v2
|
||||
- One of:
|
||||
- **Platform mode**: A Mem0 API key from [app.mem0.ai](https://app.mem0.ai)
|
||||
- **OSS mode**: An OpenAI API key
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mem0ai/mem0 && cd mem0/openclaw/docker
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
The setup script will:
|
||||
1. Ask you to choose Platform or Open-Source mode
|
||||
2. Prompt for your API key(s)
|
||||
3. Start the containers automatically
|
||||
|
||||
Then open **http://localhost:18789** to start chatting.
|
||||
|
||||
## What Gets Deployed
|
||||
|
||||
### Platform Mode
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ OpenClaw (port 18789) │
|
||||
│ + Mem0 Plugin │──→ Mem0 Cloud (app.mem0.ai)
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
### OSS Mode
|
||||
```
|
||||
┌─────────────────────────┐ ┌─────────────────┐
|
||||
│ OpenClaw (port 18789) │ │ Qdrant │
|
||||
│ + Mem0 Plugin │────→│ (port 6333) │
|
||||
└─────────────────────────┘ │ Vector Storage │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Modes Explained
|
||||
|
||||
| Feature | Platform (Mem0 Cloud) | Open-Source (Self-Hosted) |
|
||||
|---------|----------------------|--------------------------|
|
||||
| Setup | API key only | API key + Qdrant container |
|
||||
| Vector storage | Managed by Mem0 | Local Qdrant |
|
||||
| LLM for extraction | Managed by Mem0 | Your OpenAI key |
|
||||
| Graph memory | Supported | Not yet |
|
||||
| Dashboard | app.mem0.ai | CLI commands |
|
||||
|
||||
## Configuration
|
||||
|
||||
After setup, your config lives at `~/.openclaw/openclaw.json`. You can edit it directly.
|
||||
|
||||
Key settings in the `openclaw-mem0` plugin section:
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| `autoCapture` | `true` | Auto-store conversation facts |
|
||||
| `autoRecall` | `true` | Auto-retrieve relevant memories |
|
||||
| `searchThreshold` | `0.5` | Minimum similarity score (0-1) |
|
||||
| `topK` | `5` | Max memories to retrieve |
|
||||
|
||||
To change the port or other Docker settings, edit `.env` in this directory.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Port 18789 already in use**
|
||||
```bash
|
||||
lsof -i :18789
|
||||
# Kill the process or change OPENCLAW_PORT in .env
|
||||
```
|
||||
|
||||
**API key errors**
|
||||
- Platform: Verify your key at [app.mem0.ai](https://app.mem0.ai)
|
||||
- OSS: Ensure `OPENAI_API_KEY` is valid and has credits
|
||||
|
||||
**Containers won't start**
|
||||
```bash
|
||||
docker compose logs
|
||||
# or for OSS mode:
|
||||
docker compose -f docker-compose.yml -f docker-compose.oss.yml logs
|
||||
```
|
||||
|
||||
**Docker not found**
|
||||
Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) or follow the [engine install guide](https://docs.docker.com/engine/install/).
|
||||
|
||||
**Plugin not loading**
|
||||
```bash
|
||||
# Reinstall the plugin manually
|
||||
docker compose run --rm openclaw openclaw plugins install @mem0/openclaw-mem0
|
||||
```
|
||||
|
||||
## Links
|
||||
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [Mem0 OpenClaw Plugin](https://github.com/mem0ai/mem0/tree/main/openclaw)
|
||||
- [OpenClaw Documentation](https://openclaw.com/docs)
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"plugins": {
|
||||
"openclaw-mem0": {
|
||||
"mode": "open-source",
|
||||
"userId": "__MEM0_USER_ID__",
|
||||
"autoCapture": true,
|
||||
"autoRecall": true,
|
||||
"searchThreshold": 0.5,
|
||||
"topK": 5,
|
||||
"oss": {
|
||||
"vectorStore": {
|
||||
"provider": "qdrant",
|
||||
"config": {
|
||||
"host": "qdrant",
|
||||
"port": 6333
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"plugins": {
|
||||
"openclaw-mem0": {
|
||||
"mode": "platform",
|
||||
"apiKey": "${MEM0_API_KEY}",
|
||||
"userId": "__MEM0_USER_ID__",
|
||||
"autoCapture": true,
|
||||
"autoRecall": true,
|
||||
"searchThreshold": 0.5,
|
||||
"topK": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
services:
|
||||
qdrant:
|
||||
image: qdrant/qdrant:latest
|
||||
volumes:
|
||||
- qdrant_data:/qdrant/storage
|
||||
# No host port exposure — only accessible via Docker network (openclaw → qdrant:6333)
|
||||
# Uncomment the next line for debugging: ports: ["127.0.0.1:6333:6333"]
|
||||
expose:
|
||||
- "6333"
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "bash -c ':> /dev/tcp/localhost/6333' || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
openclaw:
|
||||
depends_on:
|
||||
qdrant:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
qdrant_data:
|
||||
@@ -0,0 +1,22 @@
|
||||
services:
|
||||
openclaw:
|
||||
image: ghcr.io/openclaw/openclaw:latest
|
||||
environment:
|
||||
HOME: /home/node
|
||||
TERM: xterm-256color
|
||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||
MEM0_API_KEY: ${MEM0_API_KEY:-}
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- ${OPENCLAW_CONFIG_DIR:-~/.openclaw}:/home/node/.openclaw
|
||||
ports:
|
||||
# Bound to localhost only for security — change to "18789:18789" for LAN access
|
||||
- "127.0.0.1:${OPENCLAW_PORT:-18789}:18789"
|
||||
restart: unless-stopped
|
||||
command: ["node", "dist/index.js", "gateway", "--bind", "lan", "--port", "18789"]
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:18789/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
Executable
+268
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# OpenClaw + Mem0 One-Click Setup
|
||||
# ============================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}"
|
||||
OPENCLAW_PORT="${OPENCLAW_PORT:-18789}"
|
||||
|
||||
# Colors (disabled if not a terminal)
|
||||
if [ -t 1 ]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
else
|
||||
RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC=''
|
||||
fi
|
||||
|
||||
info() { echo -e "${BLUE}ℹ${NC} $*"; }
|
||||
ok() { echo -e "${GREEN}✔${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}⚠${NC} $*"; }
|
||||
error() { echo -e "${RED}✖${NC} $*" >&2; }
|
||||
header() { echo -e "\n${BOLD}$*${NC}"; }
|
||||
|
||||
# ============================================================================
|
||||
# Step 1: Check prerequisites
|
||||
# ============================================================================
|
||||
|
||||
header "Checking prerequisites..."
|
||||
|
||||
if ! command -v docker &>/dev/null; then
|
||||
error "Docker is not installed."
|
||||
echo " Install Docker Desktop: https://www.docker.com/products/docker-desktop/"
|
||||
echo " Or Docker Engine: https://docs.docker.com/engine/install/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker compose version &>/dev/null; then
|
||||
error "Docker Compose v2 is not available."
|
||||
echo " Ensure you have Docker Compose v2 (comes with Docker Desktop)."
|
||||
echo " Docs: https://docs.docker.com/compose/install/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker info &>/dev/null 2>&1; then
|
||||
error "Docker daemon is not running. Please start Docker and try again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ok "Docker and Docker Compose are available."
|
||||
|
||||
# Check for port conflict
|
||||
if command -v lsof &>/dev/null && lsof -i :"$OPENCLAW_PORT" &>/dev/null; then
|
||||
warn "Port $OPENCLAW_PORT is already in use."
|
||||
echo " Check what's using it: lsof -i :$OPENCLAW_PORT"
|
||||
echo " Or set a different port: OPENCLAW_PORT=18790 ./setup.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Step 2: Check for existing config
|
||||
# ============================================================================
|
||||
|
||||
if [ -f "$OPENCLAW_CONFIG_DIR/openclaw.json" ]; then
|
||||
warn "Existing config found at $OPENCLAW_CONFIG_DIR/openclaw.json"
|
||||
read -rp " Overwrite with new config? [y/N] " overwrite
|
||||
if [[ ! "$overwrite" =~ ^[Yy]$ ]]; then
|
||||
info "Keeping existing config. Skipping config generation."
|
||||
SKIP_CONFIG=true
|
||||
else
|
||||
SKIP_CONFIG=false
|
||||
fi
|
||||
else
|
||||
SKIP_CONFIG=false
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Step 3: Choose mode
|
||||
# ============================================================================
|
||||
|
||||
header "Choose your setup mode:"
|
||||
echo ""
|
||||
echo " 1) Platform — Uses Mem0 Cloud (managed storage, dashboard at app.mem0.ai)"
|
||||
echo " 2) OSS — Self-hosted with local Qdrant (fully offline-capable)"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
read -rp "Enter 1 or 2: " mode_choice
|
||||
case "$mode_choice" in
|
||||
1) MODE="platform"; break ;;
|
||||
2) MODE="oss"; break ;;
|
||||
*) echo " Please enter 1 or 2." ;;
|
||||
esac
|
||||
done
|
||||
|
||||
ok "Mode: $MODE"
|
||||
|
||||
# ============================================================================
|
||||
# Step 4: Collect API keys
|
||||
# ============================================================================
|
||||
|
||||
header "API Key Configuration"
|
||||
|
||||
MEM0_API_KEY=""
|
||||
OPENAI_API_KEY=""
|
||||
|
||||
if [ "$MODE" = "platform" ]; then
|
||||
echo " Platform mode requires a Mem0 API key from https://app.mem0.ai"
|
||||
echo ""
|
||||
while true; do
|
||||
read -rp " Mem0 API Key: " MEM0_API_KEY
|
||||
if [ -n "$MEM0_API_KEY" ]; then break; fi
|
||||
warn "API key is required for platform mode."
|
||||
done
|
||||
|
||||
echo ""
|
||||
read -rp " OpenAI API Key (optional, for agent LLM — press Enter to skip): " OPENAI_API_KEY
|
||||
else
|
||||
echo " OSS mode requires an OpenAI API key for embeddings, LLM, and the agent."
|
||||
echo ""
|
||||
while true; do
|
||||
read -rp " OpenAI API Key: " OPENAI_API_KEY
|
||||
if [ -n "$OPENAI_API_KEY" ]; then break; fi
|
||||
warn "API key is required for OSS mode."
|
||||
done
|
||||
fi
|
||||
|
||||
ok "API keys configured."
|
||||
|
||||
# ============================================================================
|
||||
# Step 5: User ID
|
||||
# ============================================================================
|
||||
|
||||
DEFAULT_USER_ID="$(whoami)"
|
||||
read -rp " User ID for memory scoping [${DEFAULT_USER_ID}]: " MEM0_USER_ID
|
||||
MEM0_USER_ID="${MEM0_USER_ID:-$DEFAULT_USER_ID}"
|
||||
ok "User ID: $MEM0_USER_ID"
|
||||
|
||||
# ============================================================================
|
||||
# Step 6: Generate .env
|
||||
# ============================================================================
|
||||
|
||||
header "Generating configuration..."
|
||||
|
||||
cat > "$SCRIPT_DIR/.env" <<EOF
|
||||
# Generated by setup.sh on $(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
MEM0_API_KEY=${MEM0_API_KEY}
|
||||
OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||
MEM0_USER_ID=${MEM0_USER_ID}
|
||||
OPENCLAW_PORT=${OPENCLAW_PORT}
|
||||
OPENCLAW_CONFIG_DIR=${OPENCLAW_CONFIG_DIR}
|
||||
TZ=${TZ:-UTC}
|
||||
EOF
|
||||
|
||||
ok "Created .env"
|
||||
|
||||
# ============================================================================
|
||||
# Step 7: Generate OpenClaw config
|
||||
# ============================================================================
|
||||
|
||||
if [ "$SKIP_CONFIG" = false ]; then
|
||||
mkdir -p "$OPENCLAW_CONFIG_DIR"
|
||||
|
||||
if [ "$MODE" = "platform" ]; then
|
||||
TEMPLATE="$SCRIPT_DIR/config/openclaw.platform.json"
|
||||
else
|
||||
TEMPLATE="$SCRIPT_DIR/config/openclaw.oss.json"
|
||||
fi
|
||||
|
||||
# Substitute the user ID placeholder (apiKey uses env var resolution at runtime)
|
||||
sed "s/__MEM0_USER_ID__/${MEM0_USER_ID}/g" "$TEMPLATE" > "$OPENCLAW_CONFIG_DIR/openclaw.json"
|
||||
|
||||
ok "Created $OPENCLAW_CONFIG_DIR/openclaw.json"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Step 8: Install Mem0 plugin
|
||||
# ============================================================================
|
||||
|
||||
header "Installing Mem0 plugin..."
|
||||
|
||||
COMPOSE_CMD="docker compose"
|
||||
if [ "$MODE" = "oss" ]; then
|
||||
COMPOSE_CMD="docker compose -f docker-compose.yml -f docker-compose.oss.yml"
|
||||
fi
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
if $COMPOSE_CMD run --rm openclaw openclaw plugins install @mem0/openclaw-mem0 2>/dev/null; then
|
||||
ok "Mem0 plugin installed."
|
||||
else
|
||||
warn "Plugin install via Docker failed. This may be normal on first run."
|
||||
echo " You can install manually later:"
|
||||
echo " $COMPOSE_CMD run --rm openclaw openclaw plugins install @mem0/openclaw-mem0"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Step 9: Copy onboarding skill
|
||||
# ============================================================================
|
||||
|
||||
SKILL_DIR="$OPENCLAW_CONFIG_DIR/workspace/skills/mem0-onboarding"
|
||||
mkdir -p "$SKILL_DIR"
|
||||
cp "$SCRIPT_DIR/skills/mem0-onboarding/SKILL.md" "$SKILL_DIR/SKILL.md"
|
||||
ok "Onboarding skill installed."
|
||||
|
||||
# ============================================================================
|
||||
# Step 10: Start containers
|
||||
# ============================================================================
|
||||
|
||||
header "Starting OpenClaw..."
|
||||
|
||||
$COMPOSE_CMD up -d
|
||||
|
||||
# ============================================================================
|
||||
# Step 11: Health check
|
||||
# ============================================================================
|
||||
|
||||
info "Waiting for OpenClaw to be ready..."
|
||||
|
||||
HEALTH_URL="http://127.0.0.1:${OPENCLAW_PORT}/healthz"
|
||||
TIMEOUT=90
|
||||
ELAPSED=0
|
||||
INTERVAL=3
|
||||
|
||||
while [ $ELAPSED -lt $TIMEOUT ]; do
|
||||
if curl -sf "$HEALTH_URL" &>/dev/null; then
|
||||
echo ""
|
||||
ok "OpenClaw is ready!"
|
||||
break
|
||||
fi
|
||||
printf "."
|
||||
sleep $INTERVAL
|
||||
ELAPSED=$((ELAPSED + INTERVAL))
|
||||
done
|
||||
|
||||
if [ $ELAPSED -ge $TIMEOUT ]; then
|
||||
echo ""
|
||||
warn "Health check timed out after ${TIMEOUT}s."
|
||||
echo " OpenClaw may still be starting. Check logs:"
|
||||
echo " cd $SCRIPT_DIR && $COMPOSE_CMD logs -f"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# Done!
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
header "Setup complete!"
|
||||
echo ""
|
||||
echo " Open the web UI: ${BOLD}http://localhost:${OPENCLAW_PORT}${NC}"
|
||||
echo " Mode: ${MODE}"
|
||||
echo " User ID: ${MEM0_USER_ID}"
|
||||
echo " Config: ${OPENCLAW_CONFIG_DIR}/openclaw.json"
|
||||
echo ""
|
||||
echo " ${BLUE}Tip:${NC} Your first chat will trigger the Mem0 onboarding skill."
|
||||
echo " Try telling the agent something about yourself to test memory!"
|
||||
echo ""
|
||||
echo " Useful commands:"
|
||||
echo " View logs: cd $SCRIPT_DIR && $COMPOSE_CMD logs -f"
|
||||
echo " Stop: cd $SCRIPT_DIR && $COMPOSE_CMD down"
|
||||
echo " Restart: cd $SCRIPT_DIR && $COMPOSE_CMD restart"
|
||||
echo ""
|
||||
@@ -0,0 +1,37 @@
|
||||
# Mem0 Memory Onboarding
|
||||
|
||||
Welcome! This OpenClaw agent has **persistent memory** powered by Mem0. It remembers things across sessions so you never have to repeat yourself.
|
||||
|
||||
## How It Works
|
||||
|
||||
- **Auto-Capture**: After each conversation turn, important facts are automatically extracted and stored.
|
||||
- **Auto-Recall**: Before each turn, relevant memories are retrieved to give context.
|
||||
|
||||
## Quick Demo — Try These 3 Steps
|
||||
|
||||
### Step 1: Tell me something about yourself
|
||||
Share a preference, a project you're working on, or a fact about yourself. For example:
|
||||
> "I prefer TypeScript over JavaScript and I'm building a SaaS app called Acme."
|
||||
|
||||
### Step 2: Confirm storage
|
||||
After you share, I'll confirm that the information was captured. You can also check with:
|
||||
> "What do you remember about me?"
|
||||
|
||||
### Step 3: Verify persistence
|
||||
Start a **new session** (close this chat and open a new one), then ask:
|
||||
> "What do you know about me?"
|
||||
|
||||
Your memories carry over — that's the magic of persistent memory.
|
||||
|
||||
## Power User Commands
|
||||
|
||||
If your OpenClaw setup includes the Mem0 CLI commands:
|
||||
|
||||
- `openclaw mem0 search "query"` — Search your memories
|
||||
- `openclaw mem0 stats` — View memory statistics
|
||||
- `openclaw mem0 list` — List recent memories
|
||||
|
||||
## Learn More
|
||||
|
||||
- [Mem0 Documentation](https://docs.mem0.ai)
|
||||
- [OpenClaw Plugin README](https://github.com/mem0ai/mem0/tree/main/openclaw)
|
||||
Executable
+280
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# Test script for OpenClaw + Mem0 Docker setup
|
||||
# Validates everything works without needing real API keys
|
||||
# ============================================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
TESTS=()
|
||||
|
||||
pass() { PASS=$((PASS + 1)); TESTS+=("✔ $1"); echo " ✔ $1"; }
|
||||
fail() { FAIL=$((FAIL + 1)); TESTS+=("✖ $1: $2"); echo " ✖ $1: $2"; }
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo " OpenClaw + Mem0 Docker Setup — Test Suite"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
|
||||
# ── Prerequisites ────────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "▸ Prerequisites"
|
||||
|
||||
if command -v docker &>/dev/null; then
|
||||
pass "Docker installed"
|
||||
else
|
||||
fail "Docker installed" "docker not found"
|
||||
fi
|
||||
|
||||
if docker compose version &>/dev/null; then
|
||||
pass "Docker Compose v2 available"
|
||||
else
|
||||
fail "Docker Compose v2 available" "docker compose not found"
|
||||
fi
|
||||
|
||||
if docker info &>/dev/null 2>&1; then
|
||||
pass "Docker daemon running"
|
||||
else
|
||||
fail "Docker daemon running" "daemon not responding"
|
||||
fi
|
||||
|
||||
# ── File structure ───────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "▸ File structure"
|
||||
|
||||
EXPECTED_FILES=(
|
||||
"setup.sh"
|
||||
"docker-compose.yml"
|
||||
"docker-compose.oss.yml"
|
||||
".env.example"
|
||||
"config/openclaw.platform.json"
|
||||
"config/openclaw.oss.json"
|
||||
"skills/mem0-onboarding/SKILL.md"
|
||||
"README.md"
|
||||
)
|
||||
|
||||
for f in "${EXPECTED_FILES[@]}"; do
|
||||
if [ -f "$SCRIPT_DIR/$f" ]; then
|
||||
pass "File exists: $f"
|
||||
else
|
||||
fail "File exists: $f" "missing"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -x "$SCRIPT_DIR/setup.sh" ]; then
|
||||
pass "setup.sh is executable"
|
||||
else
|
||||
fail "setup.sh is executable" "not executable"
|
||||
fi
|
||||
|
||||
# ── Bash syntax ──────────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "▸ Script syntax"
|
||||
|
||||
if bash -n "$SCRIPT_DIR/setup.sh" 2>&1; then
|
||||
pass "setup.sh syntax valid"
|
||||
else
|
||||
fail "setup.sh syntax valid" "syntax errors"
|
||||
fi
|
||||
|
||||
# ── JSON validity ────────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "▸ JSON configs"
|
||||
|
||||
for json_file in config/openclaw.platform.json config/openclaw.oss.json; do
|
||||
if python3 -c "import json; json.load(open('$SCRIPT_DIR/$json_file'))" 2>/dev/null; then
|
||||
pass "$json_file is valid JSON"
|
||||
else
|
||||
fail "$json_file is valid JSON" "parse error"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check platform config has expected fields
|
||||
if python3 -c "
|
||||
import json, sys
|
||||
c = json.load(open('$SCRIPT_DIR/config/openclaw.platform.json'))
|
||||
p = c['plugins']['openclaw-mem0']
|
||||
assert p['mode'] == 'platform', 'wrong mode'
|
||||
assert p['apiKey'] == '\${MEM0_API_KEY}', 'apiKey not using env var'
|
||||
assert p['userId'] == '__MEM0_USER_ID__', 'userId placeholder missing'
|
||||
assert p['autoCapture'] is True
|
||||
assert p['autoRecall'] is True
|
||||
" 2>/dev/null; then
|
||||
pass "Platform config has correct structure"
|
||||
else
|
||||
fail "Platform config has correct structure" "missing or wrong fields"
|
||||
fi
|
||||
|
||||
# Check OSS config has Qdrant vector store
|
||||
if python3 -c "
|
||||
import json
|
||||
c = json.load(open('$SCRIPT_DIR/config/openclaw.oss.json'))
|
||||
p = c['plugins']['openclaw-mem0']
|
||||
assert p['mode'] == 'open-source', 'wrong mode'
|
||||
vs = p['oss']['vectorStore']
|
||||
assert vs['provider'] == 'qdrant'
|
||||
assert vs['config']['host'] == 'qdrant'
|
||||
assert vs['config']['port'] == 6333
|
||||
" 2>/dev/null; then
|
||||
pass "OSS config has Qdrant vector store"
|
||||
else
|
||||
fail "OSS config has Qdrant vector store" "missing or wrong fields"
|
||||
fi
|
||||
|
||||
# ── Docker Compose validation ────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "▸ Docker Compose validation"
|
||||
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
# Create a temporary .env so compose can resolve variables
|
||||
TEMP_ENV=false
|
||||
if [ ! -f .env ]; then
|
||||
cat > .env <<'EOF'
|
||||
MEM0_API_KEY=test-key-for-validation
|
||||
OPENAI_API_KEY=test-key-for-validation
|
||||
MEM0_USER_ID=testuser
|
||||
OPENCLAW_PORT=18789
|
||||
OPENCLAW_CONFIG_DIR=/tmp/openclaw-test
|
||||
TZ=UTC
|
||||
EOF
|
||||
TEMP_ENV=true
|
||||
fi
|
||||
|
||||
if docker compose config --quiet 2>/dev/null; then
|
||||
pass "docker-compose.yml is valid"
|
||||
else
|
||||
fail "docker-compose.yml is valid" "$(docker compose config 2>&1 | tail -1)"
|
||||
fi
|
||||
|
||||
if docker compose -f docker-compose.yml -f docker-compose.oss.yml config --quiet 2>/dev/null; then
|
||||
pass "docker-compose.oss.yml override is valid"
|
||||
else
|
||||
fail "docker-compose.oss.yml override is valid" "$(docker compose -f docker-compose.yml -f docker-compose.oss.yml config 2>&1 | tail -1)"
|
||||
fi
|
||||
|
||||
# Verify services are correctly defined
|
||||
PLATFORM_SERVICES=$(docker compose config --services 2>/dev/null)
|
||||
if echo "$PLATFORM_SERVICES" | grep -q "openclaw"; then
|
||||
pass "Platform mode defines 'openclaw' service"
|
||||
else
|
||||
fail "Platform mode defines 'openclaw' service" "service not found"
|
||||
fi
|
||||
|
||||
OSS_SERVICES=$(docker compose -f docker-compose.yml -f docker-compose.oss.yml config --services 2>/dev/null)
|
||||
if echo "$OSS_SERVICES" | grep -q "qdrant"; then
|
||||
pass "OSS mode adds 'qdrant' service"
|
||||
else
|
||||
fail "OSS mode adds 'qdrant' service" "service not found"
|
||||
fi
|
||||
|
||||
# Verify port binding is localhost-only
|
||||
PORT_CONFIG=$(docker compose config 2>/dev/null | grep -A5 "published" | head -10)
|
||||
if docker compose config 2>/dev/null | grep -q "127.0.0.1"; then
|
||||
pass "Port is bound to 127.0.0.1 (localhost only)"
|
||||
else
|
||||
fail "Port is bound to 127.0.0.1 (localhost only)" "binding not found"
|
||||
fi
|
||||
|
||||
# Verify healthcheck is defined
|
||||
if docker compose config 2>/dev/null | grep -q "healthz"; then
|
||||
pass "Healthcheck is configured"
|
||||
else
|
||||
fail "Healthcheck is configured" "healthcheck not found"
|
||||
fi
|
||||
|
||||
# ── Docker image pull test ───────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "▸ Docker image availability"
|
||||
|
||||
# Just check if the image reference is parseable (don't actually pull — too slow for a test)
|
||||
IMAGE=$(docker compose config 2>/dev/null | grep "image:" | head -1 | awk '{print $2}')
|
||||
if [ -n "$IMAGE" ]; then
|
||||
pass "OpenClaw image reference: $IMAGE"
|
||||
else
|
||||
fail "OpenClaw image reference" "could not extract image name"
|
||||
fi
|
||||
|
||||
QDRANT_IMAGE=$(docker compose -f docker-compose.yml -f docker-compose.oss.yml config 2>/dev/null | grep "image:" | grep qdrant | awk '{print $2}')
|
||||
if [ -n "$QDRANT_IMAGE" ]; then
|
||||
pass "Qdrant image reference: $QDRANT_IMAGE"
|
||||
else
|
||||
fail "Qdrant image reference" "could not extract image name"
|
||||
fi
|
||||
|
||||
# ── setup.sh simulation ─────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "▸ setup.sh config generation simulation"
|
||||
|
||||
# Test the sed substitution that setup.sh does
|
||||
TEST_CONFIG_DIR=$(mktemp -d)
|
||||
sed "s/__MEM0_USER_ID__/testuser/g" "$SCRIPT_DIR/config/openclaw.platform.json" > "$TEST_CONFIG_DIR/openclaw.json"
|
||||
|
||||
if python3 -c "
|
||||
import json
|
||||
c = json.load(open('$TEST_CONFIG_DIR/openclaw.json'))
|
||||
p = c['plugins']['openclaw-mem0']
|
||||
assert p['userId'] == 'testuser', f'expected testuser, got {p[\"userId\"]}'
|
||||
assert p['apiKey'] == '\${MEM0_API_KEY}', 'apiKey should remain as env var template'
|
||||
" 2>/dev/null; then
|
||||
pass "sed substitution works correctly (userId baked, apiKey templated)"
|
||||
else
|
||||
fail "sed substitution" "userId not replaced or apiKey was wrongly replaced"
|
||||
fi
|
||||
|
||||
rm -rf "$TEST_CONFIG_DIR"
|
||||
|
||||
# OSS config substitution
|
||||
TEST_CONFIG_DIR=$(mktemp -d)
|
||||
sed "s/__MEM0_USER_ID__/testuser/g" "$SCRIPT_DIR/config/openclaw.oss.json" > "$TEST_CONFIG_DIR/openclaw.json"
|
||||
|
||||
if python3 -c "
|
||||
import json
|
||||
c = json.load(open('$TEST_CONFIG_DIR/openclaw.json'))
|
||||
p = c['plugins']['openclaw-mem0']
|
||||
assert p['userId'] == 'testuser'
|
||||
assert p['oss']['vectorStore']['config']['host'] == 'qdrant'
|
||||
" 2>/dev/null; then
|
||||
pass "OSS config sed substitution works correctly"
|
||||
else
|
||||
fail "OSS config sed substitution" "fields incorrect after substitution"
|
||||
fi
|
||||
|
||||
rm -rf "$TEST_CONFIG_DIR"
|
||||
|
||||
# ── Cleanup ──────────────────────────────────────────────────────────────────
|
||||
|
||||
if [ "$TEMP_ENV" = true ]; then
|
||||
rm -f "$SCRIPT_DIR/.env"
|
||||
fi
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
TOTAL=$((PASS + FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo " All $TOTAL tests passed ✔"
|
||||
else
|
||||
echo " $PASS/$TOTAL passed, $FAIL failed"
|
||||
echo ""
|
||||
for t in "${TESTS[@]}"; do
|
||||
if [[ "$t" == "✖"* ]]; then
|
||||
echo " $t"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
exit $FAIL
|
||||
Reference in New Issue
Block a user