feat: v1.1 portable architecture (app/data separation)
New directory structure: - app/core/ = OpenClaw program - app/runtime/ = Node.js - data/ = user config, memory, backups (never overwritten on upgrade) - system/ = migration scripts Features: - Auto port detection (18789-18799) - macOS Gatekeeper auto-fix (xattr quarantine removal) - Windows UTF-8 BOM + CRLF encoding - Config auto-migration on startup - Platform-specific packages (Mac ARM64 + Windows x64)
This commit is contained in:
6
portable/default-config.json
Normal file
6
portable/default-config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"gateway": {
|
||||
"mode": "local",
|
||||
"auth": { "token": "uclaw" }
|
||||
}
|
||||
}
|
||||
33
portable/migrate.js
Normal file
33
portable/migrate.js
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
// U-Claw config migration script
|
||||
// Runs on every startup to ensure config compatibility
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const dataDir = process.argv[2] || path.join(__dirname, '..', 'data');
|
||||
const configPath = path.join(dataDir, 'config.json');
|
||||
|
||||
if (!fs.existsSync(configPath)) process.exit(0);
|
||||
|
||||
try {
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
|
||||
// Ensure gateway section exists
|
||||
if (!config.gateway) {
|
||||
config.gateway = { mode: 'local', auth: { token: 'uclaw' } };
|
||||
}
|
||||
|
||||
// Ensure auth token exists
|
||||
if (!config.gateway.auth || !config.gateway.auth.token) {
|
||||
config.gateway.auth = { token: 'uclaw' };
|
||||
}
|
||||
|
||||
// Future migrations go here:
|
||||
// if (!config._version) { config._version = '1.1'; ... }
|
||||
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
} catch (e) {
|
||||
// Don't crash on migration errors
|
||||
console.error('Migration warning:', e.message);
|
||||
}
|
||||
102
portable/start.bat
Normal file
102
portable/start.bat
Normal file
@@ -0,0 +1,102 @@
|
||||
@echo off
|
||||
chcp 65001 >/dev/null 2>&1
|
||||
title U-Claw - Portable AI Agent
|
||||
|
||||
echo.
|
||||
echo ========================================
|
||||
echo U-Claw v1.1 - Portable AI Agent
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
set "UCLAW_DIR=%~dp0"
|
||||
set "APP_DIR=%UCLAW_DIR%app"
|
||||
set "CORE_DIR=%APP_DIR%\core"
|
||||
set "DATA_DIR=%UCLAW_DIR%data"
|
||||
set "NODE_DIR=%APP_DIR%\runtime\node-win-x64"
|
||||
set "NODE_BIN=%NODE_DIR%\node.exe"
|
||||
set "NPM_BIN=%NODE_DIR%\npm.cmd"
|
||||
|
||||
set "OPENCLAW_HOME=%DATA_DIR%"
|
||||
set "OPENCLAW_STATE_DIR=%DATA_DIR%"
|
||||
set "OPENCLAW_CONFIG_PATH=%DATA_DIR%\config.json"
|
||||
|
||||
REM Check runtime
|
||||
if not exist "%NODE_BIN%" (
|
||||
echo [ERROR] Node.js runtime not found
|
||||
echo Please ensure app\runtime\node-win-x64 is complete
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "tokens=*" %%v in ('"%NODE_BIN%" --version') do set NODE_VER=%%v
|
||||
echo Node.js: %NODE_VER%
|
||||
echo.
|
||||
|
||||
set "PATH=%NODE_DIR%;%NODE_DIR%\node_modules\.bin;%PATH%"
|
||||
|
||||
REM Init data
|
||||
if not exist "%DATA_DIR%" mkdir "%DATA_DIR%"
|
||||
if not exist "%DATA_DIR%\memory" mkdir "%DATA_DIR%\memory"
|
||||
if not exist "%DATA_DIR%\backups" mkdir "%DATA_DIR%\backups"
|
||||
|
||||
REM Default config
|
||||
if not exist "%DATA_DIR%\config.json" (
|
||||
echo First run - creating default config...
|
||||
echo {"gateway":{"mode":"local","auth":{"token":"uclaw"}}} > "%DATA_DIR%\config.json"
|
||||
echo Config created
|
||||
echo.
|
||||
)
|
||||
|
||||
REM Check dependencies
|
||||
if not exist "%CORE_DIR%\node_modules" (
|
||||
echo First run - installing dependencies...
|
||||
echo Using China mirror, please wait...
|
||||
echo.
|
||||
cd /d "%CORE_DIR%"
|
||||
call "%NPM_BIN%" install --registry=https://registry.npmmirror.com
|
||||
echo.
|
||||
echo Dependencies installed!
|
||||
echo.
|
||||
)
|
||||
|
||||
if not exist "%CORE_DIR%\dist" (
|
||||
echo First run - building...
|
||||
cd /d "%CORE_DIR%"
|
||||
call "%NPM_BIN%" run build
|
||||
echo.
|
||||
)
|
||||
|
||||
REM Find available port
|
||||
set PORT=18789
|
||||
:check_port
|
||||
netstat -an | findstr ":%PORT% " | findstr "LISTENING" >/dev/null 2>&1
|
||||
if %errorlevel%==0 (
|
||||
echo Port %PORT% in use, trying next...
|
||||
set /a PORT+=1
|
||||
if %PORT% gtr 18799 (
|
||||
echo No available port 18789-18799
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
goto :check_port
|
||||
)
|
||||
|
||||
echo Starting OpenClaw on port %PORT%...
|
||||
echo DO NOT close this window!
|
||||
echo.
|
||||
echo ----------------------------------------
|
||||
echo Browser will open automatically.
|
||||
echo First time setup in web console:
|
||||
echo 1. Choose AI model (DeepSeek / Kimi / Qwen)
|
||||
echo 2. Enter API Key
|
||||
echo 3. Connect chat platform (QQ / Feishu / DingTalk)
|
||||
echo ----------------------------------------
|
||||
echo.
|
||||
|
||||
cd /d "%CORE_DIR%"
|
||||
start "" http://127.0.0.1:%PORT%/#token=uclaw
|
||||
"%NODE_BIN%" openclaw.mjs gateway run --allow-unconfigured --force --port %PORT%
|
||||
|
||||
echo.
|
||||
echo OpenClaw stopped.
|
||||
pause
|
||||
173
portable/start.command
Executable file
173
portable/start.command
Executable file
@@ -0,0 +1,173 @@
|
||||
#!/bin/bash
|
||||
# ============================================================
|
||||
# U-Claw - Portable AI Agent
|
||||
# Double-click to start / 双击启动
|
||||
# ============================================================
|
||||
|
||||
UCLAW_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
APP_DIR="$UCLAW_DIR/app"
|
||||
CORE_DIR="$APP_DIR/core"
|
||||
DATA_DIR="$UCLAW_DIR/data"
|
||||
SYSTEM_DIR="$UCLAW_DIR/system"
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
CYAN='\033[0;36m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
clear
|
||||
echo ""
|
||||
echo -e "${CYAN}"
|
||||
echo " ╔══════════════════════════════════════╗"
|
||||
echo " ║ 🦞 U-Claw v1.1 ║"
|
||||
echo " ║ Portable AI Agent ║"
|
||||
echo " ╚══════════════════════════════════════╝"
|
||||
echo -e "${NC}"
|
||||
|
||||
# ---- 1. Detect CPU & set runtime ----
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "arm64" ]; then
|
||||
NODE_DIR="$APP_DIR/runtime/node-mac-arm64"
|
||||
echo -e " ${GREEN}Apple Silicon (M series)${NC}"
|
||||
else
|
||||
NODE_DIR="$APP_DIR/runtime/node-mac-x64"
|
||||
echo -e " ${GREEN}Intel Mac${NC}"
|
||||
fi
|
||||
|
||||
NODE_BIN="$NODE_DIR/bin/node"
|
||||
export PATH="$NODE_DIR/bin:$PATH"
|
||||
|
||||
# ---- 2. Remove macOS quarantine ----
|
||||
if xattr -l "$NODE_BIN" 2>/dev/null | grep -q "com.apple.quarantine"; then
|
||||
echo -e " ${YELLOW}Removing macOS security restriction...${NC}"
|
||||
xattr -rd com.apple.quarantine "$UCLAW_DIR" 2>/dev/null || true
|
||||
echo -e " ${GREEN}Done${NC}"
|
||||
fi
|
||||
|
||||
# ---- 3. Check runtime ----
|
||||
if [ ! -f "$NODE_BIN" ]; then
|
||||
echo -e " ${RED}Error: Node.js runtime not found${NC}"
|
||||
echo " Please ensure app/runtime/ is complete"
|
||||
read -p " Press Enter to exit..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_VER=$("$NODE_BIN" --version)
|
||||
echo -e " Node.js: ${GREEN}${NODE_VER}${NC}"
|
||||
echo ""
|
||||
|
||||
# ---- 4. Check & init data ----
|
||||
mkdir -p "$DATA_DIR/memory" "$DATA_DIR/backups" "$DATA_DIR/logs"
|
||||
|
||||
if [ ! -f "$DATA_DIR/config.json" ] && [ ! -f "$DATA_DIR/.openclaw/openclaw.json" ]; then
|
||||
echo -e " ${YELLOW}First run - creating default config...${NC}"
|
||||
mkdir -p "$DATA_DIR/.openclaw"
|
||||
cat > "$DATA_DIR/.openclaw/openclaw.json" << 'CFGEOF'
|
||||
{
|
||||
"gateway": {
|
||||
"mode": "local",
|
||||
"auth": { "token": "uclaw" }
|
||||
}
|
||||
}
|
||||
CFGEOF
|
||||
echo -e " ${GREEN}Config created${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ---- 5. Set environment (portable mode) ----
|
||||
STATE_DIR="$DATA_DIR/.openclaw"
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
# Sync config to where OpenClaw expects it
|
||||
if [ -f "$DATA_DIR/config.json" ] && [ ! -f "$STATE_DIR/openclaw.json" ]; then
|
||||
cp "$DATA_DIR/config.json" "$STATE_DIR/openclaw.json"
|
||||
fi
|
||||
|
||||
export OPENCLAW_HOME="$DATA_DIR"
|
||||
export OPENCLAW_STATE_DIR="$STATE_DIR"
|
||||
export OPENCLAW_CONFIG_PATH="$STATE_DIR/openclaw.json"
|
||||
|
||||
# ---- 6. Run migration if exists ----
|
||||
if [ -f "$SYSTEM_DIR/migrate.js" ]; then
|
||||
"$NODE_BIN" "$SYSTEM_DIR/migrate.js" "$DATA_DIR" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ---- 7. Check dependencies ----
|
||||
if [ ! -d "$CORE_DIR/node_modules" ]; then
|
||||
echo -e " ${YELLOW}First run - installing dependencies...${NC}"
|
||||
echo " (Using China mirror)"
|
||||
cd "$CORE_DIR"
|
||||
"$NODE_BIN" "$NODE_DIR/bin/npm" install --registry=https://registry.npmmirror.com 2>&1
|
||||
echo -e " ${GREEN}Dependencies installed${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ ! -d "$CORE_DIR/dist" ]; then
|
||||
echo -e " ${YELLOW}First run - building...${NC}"
|
||||
cd "$CORE_DIR"
|
||||
"$NODE_BIN" "$NODE_DIR/bin/npm" run build 2>&1
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ---- 8. Find available port ----
|
||||
PORT=18789
|
||||
while lsof -i :$PORT >/dev/null 2>&1; do
|
||||
echo -e " ${YELLOW}Port $PORT in use, trying next...${NC}"
|
||||
PORT=$((PORT + 1))
|
||||
if [ $PORT -gt 18799 ]; then
|
||||
echo -e " ${RED}No available port (18789-18799)${NC}"
|
||||
read -p " Press Enter to exit..."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Update config with correct port if changed
|
||||
if [ $PORT -ne 18789 ]; then
|
||||
"$NODE_BIN" -e "
|
||||
const fs = require('fs');
|
||||
const p = '$DATA_DIR/config.json';
|
||||
const c = JSON.parse(fs.readFileSync(p, 'utf8'));
|
||||
c.gateway = c.gateway || {};
|
||||
c.gateway.port = $PORT;
|
||||
fs.writeFileSync(p, JSON.stringify(c, null, 2));
|
||||
" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ---- 9. Start gateway ----
|
||||
echo -e " ${CYAN}Starting OpenClaw on port $PORT...${NC}"
|
||||
echo " Do NOT close this window."
|
||||
echo ""
|
||||
|
||||
cd "$CORE_DIR"
|
||||
|
||||
TOKEN=$(python3 -c "import json,os; p='$STATE_DIR/openclaw.json' if os.path.exists('$STATE_DIR/openclaw.json') else '$DATA_DIR/config.json'; d=json.load(open(p)); print(d.get('gateway',{}).get('auth',{}).get('token','uclaw'))" 2>/dev/null || echo "uclaw")
|
||||
|
||||
"$NODE_BIN" openclaw.mjs gateway run --allow-unconfigured --force --port $PORT &
|
||||
GW_PID=$!
|
||||
|
||||
# ---- 10. Wait & open browser ----
|
||||
for i in $(seq 1 30); do
|
||||
sleep 0.5
|
||||
if curl -s -o /dev/null -w '' "http://127.0.0.1:$PORT/" 2>/dev/null; then
|
||||
URL="http://127.0.0.1:$PORT/#token=${TOKEN}"
|
||||
echo ""
|
||||
echo -e " ${GREEN}✅ Started successfully!${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}Dashboard: ${URL}${NC}"
|
||||
echo ""
|
||||
echo -e " ${YELLOW}First time? Configure in the web console:${NC}"
|
||||
echo " 1. Choose AI model (DeepSeek / Kimi / Qwen)"
|
||||
echo " 2. Enter API Key"
|
||||
echo " 3. Connect chat platform (QQ / Feishu / DingTalk)"
|
||||
echo ""
|
||||
open "$URL" 2>/dev/null
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
wait $GW_PID
|
||||
|
||||
echo ""
|
||||
echo -e " ${YELLOW}OpenClaw stopped.${NC}"
|
||||
read -p " Press Enter to close..."
|
||||
Reference in New Issue
Block a user