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:
dongsheng123132
2026-03-10 22:10:28 +08:00
parent 6f7af5fa49
commit 6c1bff5572
4 changed files with 314 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
{
"gateway": {
"mode": "local",
"auth": { "token": "uclaw" }
}
}

33
portable/migrate.js Normal file
View 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
View 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
View 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..."