fix(release): Windows-only portable bundle with pre-installed deps
Root cause: portable zip was 1MB skeleton; first launch triggered `npm install` of OpenClaw + 36 bundled deps on customer's USB drive. On exFAT-formatted USB this hangs/takes 20+ minutes, gateway never binds 18789, dashboard shows "Gateway 未启动". Fix follows StanleyChanH/openclaw-offline-package pattern (153MB single-platform zip, gateway starts directly): - release.yml: only build Windows bundle (~150-200MB). Mac users fall back to setup.sh. Reasoning: 99% of customers are Windows; bundling 3 platforms × Node ~30MB + 3× node_modules dedup pain is what blew the previous attempt past GitHub's 2GB cap (see5916ff5history). Smaller scope = simpler + safer. - Windows-Start.bat / Mac-Start.command: warn if node_modules missing; fallback `npm install` adds --ignore-scripts --no-audit --no-fund for speed. - Release notes: NTFS U-disk recommendation. Lessons applied (from5916ff5): - Stay on linux runner with --ignore-scripts (Stanley pattern) - Drop mac-x64 / mac-arm64 Node from bundle (setup.sh handles Mac) - rm -rf node-llama-cpp / sharp / canvas explicitly - Hard size guard: fail CI if zip > 1500MB (buffer below 2GB cap)
This commit is contained in:
147
.github/workflows/release.yml
vendored
147
.github/workflows/release.yml
vendored
@@ -15,27 +15,123 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
portable-skeleton:
|
portable-windows-full:
|
||||||
name: Portable skeleton zip (cross-platform)
|
name: Portable Windows full bundle (Node + OpenClaw pre-installed)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Stage portable skeleton (no node_modules, no Node.js runtime)
|
- name: Stage portable directory
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
tag_name="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}"
|
tag_name="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}"
|
||||||
stage_dir="dist/u-claw-portable-${tag_name}"
|
stage_dir="dist/u-claw-portable-windows-${tag_name}"
|
||||||
mkdir -p "$stage_dir"
|
mkdir -p "$stage_dir"
|
||||||
# Copy source skeleton only — first launch will download Node.js + OpenClaw
|
|
||||||
# via portable/setup.sh / setup.bat (China mirror, ~1 minute).
|
|
||||||
cp -R portable/. "$stage_dir/"
|
cp -R portable/. "$stage_dir/"
|
||||||
rm -rf "$stage_dir/app" "$stage_dir/data/logs" 2>/dev/null || true
|
cp OPENCLAW_VERSION "$stage_dir/OPENCLAW_VERSION"
|
||||||
|
rm -rf "$stage_dir/data/logs" 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Pre-install Node.js (Windows) and OpenClaw deps
|
||||||
|
run: |
|
||||||
|
tag_name="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}"
|
||||||
|
stage_dir="dist/u-claw-portable-windows-${tag_name}"
|
||||||
|
openclaw_version=$(tr -d '[:space:]' < OPENCLAW_VERSION)
|
||||||
|
node_version="v22.22.1"
|
||||||
|
mirror_node="https://npmmirror.com/mirrors/node"
|
||||||
|
mirror_npm="https://registry.npmmirror.com"
|
||||||
|
|
||||||
|
app_dir="$stage_dir/app"
|
||||||
|
runtime_dir="$app_dir/runtime"
|
||||||
|
core_dir="$app_dir/core"
|
||||||
|
mkdir -p "$runtime_dir" "$core_dir"
|
||||||
|
|
||||||
|
# 1) Download Node for Windows x64 only.
|
||||||
|
# Mac users: Mac-Start.command runs setup.sh on first launch which
|
||||||
|
# downloads node-mac-arm64 from npmmirror.com (~30MB, ~10s in China).
|
||||||
|
# Stanley pattern: ship one platform per zip, keep size reasonable.
|
||||||
|
win_node="$runtime_dir/node-win-x64"
|
||||||
|
mkdir -p "$win_node"
|
||||||
|
curl -fSL "$mirror_node/$node_version/node-$node_version-win-x64.zip" -o /tmp/node-win.zip
|
||||||
|
unzip -q /tmp/node-win.zip -d /tmp/node-win-extract
|
||||||
|
cp -r /tmp/node-win-extract/node-$node_version-win-x64/* "$win_node/"
|
||||||
|
rm -rf /tmp/node-win.zip /tmp/node-win-extract
|
||||||
|
|
||||||
|
# 2) Use linux Node to npm-install OpenClaw + bundled runtime deps
|
||||||
|
# into core/node_modules — these are platform-agnostic JS, work on any OS
|
||||||
|
curl -fSL "$mirror_node/$node_version/node-$node_version-linux-x64.tar.xz" \
|
||||||
|
| tar xJ -C /tmp
|
||||||
|
export PATH="/tmp/node-$node_version-linux-x64/bin:$PATH"
|
||||||
|
node --version
|
||||||
|
|
||||||
|
cat > "$core_dir/package.json" <<PKGJSON
|
||||||
|
{
|
||||||
|
"name": "u-claw-core",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"openclaw": "$openclaw_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PKGJSON
|
||||||
|
|
||||||
|
# Install OpenClaw + ALL bundled runtime deps in one shot, no native scripts
|
||||||
|
# (--ignore-scripts skips sharp/playwright/sqlite-vec native postinstall —
|
||||||
|
# those are not needed for OpenClaw gateway core path)
|
||||||
|
cd "$core_dir"
|
||||||
|
npm install \
|
||||||
|
--registry="$mirror_npm" \
|
||||||
|
--ignore-scripts \
|
||||||
|
--no-audit \
|
||||||
|
--no-fund \
|
||||||
|
--omit=dev
|
||||||
|
|
||||||
|
# Pre-install OpenClaw's bundled runtime deps so first-run staging is a no-op
|
||||||
|
# (read deps from openclaw package.json and install them flat)
|
||||||
|
if [ -f "node_modules/openclaw/package.json" ]; then
|
||||||
|
deps=$(node -e "const p=require('./node_modules/openclaw/package.json');console.log(Object.entries(p.dependencies||{}).map(([k,v])=>k+'@'+v).join(' '))")
|
||||||
|
if [ -n "$deps" ]; then
|
||||||
|
echo "Pre-installing $(echo $deps | wc -w) OpenClaw bundled deps..."
|
||||||
|
npm install $deps \
|
||||||
|
--registry="$mirror_npm" \
|
||||||
|
--ignore-scripts \
|
||||||
|
--no-audit \
|
||||||
|
--no-fund
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5) Install QQ plugin
|
||||||
|
npm install @sliverp/qqbot@latest \
|
||||||
|
--registry="$mirror_npm" \
|
||||||
|
--ignore-scripts \
|
||||||
|
--no-audit \
|
||||||
|
--no-fund || true
|
||||||
|
|
||||||
|
# 6) Copy China-optimized skills if present
|
||||||
|
skills_cn="$stage_dir/skills-cn"
|
||||||
|
skills_target="$core_dir/node_modules/openclaw/skills"
|
||||||
|
if [ -d "$skills_cn" ] && [ -d "$skills_target" ]; then
|
||||||
|
for skill_dir in "$skills_cn"/*/; do
|
||||||
|
skill_name=$(basename "$skill_dir")
|
||||||
|
[ ! -d "$skills_target/$skill_name" ] && cp -R "$skill_dir" "$skills_target/$skill_name"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7) Prune large optional deps that customer rarely needs.
|
||||||
|
# Lesson from 5916ff5: previous full-bundle hit 3.4GB and
|
||||||
|
# blew past GitHub's 2GB single-asset limit because of
|
||||||
|
# node-llama-cpp / sharp prebuilds. Even with --ignore-scripts
|
||||||
|
# these may pull bundled binaries, so explicitly remove them.
|
||||||
|
for big_dep in node-llama-cpp sharp @img canvas; do
|
||||||
|
find "$core_dir/node_modules" -type d -name "$big_dep" -prune -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
done
|
||||||
|
|
||||||
|
du -sh "$stage_dir"
|
||||||
|
echo "Portable bundle staged: $stage_dir"
|
||||||
|
|
||||||
- name: Normalize line endings (CRLF for Windows scripts, LF for shell)
|
- name: Normalize line endings (CRLF for Windows scripts, LF for shell)
|
||||||
run: |
|
run: |
|
||||||
tag_name="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}"
|
tag_name="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}"
|
||||||
stage_dir="dist/u-claw-portable-${tag_name}"
|
stage_dir="dist/u-claw-portable-windows-${tag_name}"
|
||||||
# Windows batch files MUST be CRLF — cmd.exe mis-parses LF-only files
|
# Windows batch files MUST be CRLF — cmd.exe mis-parses LF-only files
|
||||||
find "$stage_dir" -type f \( -name '*.bat' -o -name '*.cmd' -o -name '*.ps1' \) -print0 \
|
find "$stage_dir" -type f \( -name '*.bat' -o -name '*.cmd' -o -name '*.ps1' \) -print0 \
|
||||||
| xargs -0 -I {} sh -c 'sed -i "s/\r$//; s/$/\r/" "{}"'
|
| xargs -0 -I {} sh -c 'sed -i "s/\r$//; s/$/\r/" "{}"'
|
||||||
@@ -45,17 +141,32 @@ jobs:
|
|||||||
# Restore +x on shell entry points
|
# Restore +x on shell entry points
|
||||||
chmod +x "$stage_dir"/*.sh "$stage_dir"/*.command 2>/dev/null || true
|
chmod +x "$stage_dir"/*.sh "$stage_dir"/*.command 2>/dev/null || true
|
||||||
|
|
||||||
- name: Zip skeleton
|
- name: Zip portable bundle
|
||||||
run: |
|
run: |
|
||||||
tag_name="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}"
|
tag_name="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}"
|
||||||
cd dist
|
cd dist
|
||||||
zip -qry "u-claw-portable-${tag_name}.zip" "u-claw-portable-${tag_name}"
|
zip -qry "u-claw-portable-windows-${tag_name}.zip" "u-claw-portable-windows-${tag_name}"
|
||||||
ls -lh "u-claw-portable-${tag_name}.zip"
|
ls -lh "u-claw-portable-windows-${tag_name}.zip"
|
||||||
|
|
||||||
|
- name: Size guard (lesson from 5916ff5 — GitHub 2GB asset cap)
|
||||||
|
run: |
|
||||||
|
tag_name="${GITHUB_REF_NAME:-${{ github.event.inputs.tag }}}"
|
||||||
|
zip_path="dist/u-claw-portable-windows-${tag_name}.zip"
|
||||||
|
size_bytes=$(stat -c%s "$zip_path")
|
||||||
|
size_mb=$((size_bytes / 1024 / 1024))
|
||||||
|
echo "Portable zip size: ${size_mb} MB"
|
||||||
|
# Hard cap at 1500 MB to leave buffer below GitHub's 2GB single-asset limit.
|
||||||
|
# Inspired by StanleyChanH/openclaw-offline-package which ships ~150MB.
|
||||||
|
if [ "$size_mb" -gt 1500 ]; then
|
||||||
|
echo "::error::Portable zip is ${size_mb} MB, exceeds 1500 MB safety cap"
|
||||||
|
echo "Consider removing more optional deps or splitting into platform-specific zips"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: portable-skeleton
|
name: portable-windows-full
|
||||||
path: dist/u-claw-portable-*.zip
|
path: dist/u-claw-portable-windows-*.zip
|
||||||
|
|
||||||
desktop-windows:
|
desktop-windows:
|
||||||
name: Electron Windows installer
|
name: Electron Windows installer
|
||||||
@@ -120,7 +231,7 @@ jobs:
|
|||||||
|
|
||||||
publish:
|
publish:
|
||||||
name: Publish GitHub Release
|
name: Publish GitHub Release
|
||||||
needs: [portable-skeleton, desktop-windows, desktop-mac]
|
needs: [portable-windows-full, desktop-windows, desktop-mac]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -161,12 +272,16 @@ jobs:
|
|||||||
|
|
||||||
- **Windows 桌面版**:`U-Claw.Setup.*.exe`(安装到电脑)或 `U-Claw.*.exe`(免安装绿色版)
|
- **Windows 桌面版**:`U-Claw.Setup.*.exe`(安装到电脑)或 `U-Claw.*.exe`(免安装绿色版)
|
||||||
- **macOS 桌面版**:`U-Claw-*-arm64.dmg`(Apple Silicon)或 `U-Claw-*.dmg`(Intel)
|
- **macOS 桌面版**:`U-Claw-*-arm64.dmg`(Apple Silicon)或 `U-Claw-*.dmg`(Intel)
|
||||||
- **便携骨架(U 盘版)**:`u-claw-portable-*.zip`(约 1 MB,首次启动自动下载 Node.js + OpenClaw,国内镜像约 1 分钟)
|
- **Windows 便携完整版(U 盘版)**:`u-claw-portable-windows-*.zip`(约 150-200 MB,已预装 Node.js + OpenClaw,**解压即用,零网络依赖**)
|
||||||
|
- **macOS 便携骨架(开发者)**:源码在 `portable/` 目录,`bash setup.sh` 自动下载 Node + OpenClaw(国内镜像约 1 分钟)
|
||||||
|
|
||||||
|
> **U 盘格式建议**:请用 **NTFS** 格式化 U 盘(NOT exFAT/FAT32)。Node.js 在 exFAT 上 IO 极慢且不支持符号链接,可能导致启动失败。
|
||||||
|
> Windows 下右键 U 盘 → 格式化 → 文件系统选择 NTFS。
|
||||||
|
|
||||||
> Windows 安装包未做代码签名,首次运行 SmartScreen 选择「仍要运行」。
|
> Windows 安装包未做代码签名,首次运行 SmartScreen 选择「仍要运行」。
|
||||||
> macOS DMG 未做公证,首次启动 `xattr -rd com.apple.quarantine /Applications/U-Claw.app` 或右键打开。
|
> macOS DMG 未做公证,首次启动 `xattr -rd com.apple.quarantine /Applications/U-Claw.app` 或右键打开。
|
||||||
files: |
|
files: |
|
||||||
artifacts/portable-skeleton/*
|
artifacts/portable-windows-full/*
|
||||||
artifacts/desktop-windows/*
|
artifacts/desktop-windows/*
|
||||||
artifacts/desktop-mac/*
|
artifacts/desktop-mac/*
|
||||||
fail_on_unmatched_files: false
|
fail_on_unmatched_files: false
|
||||||
|
|||||||
@@ -98,10 +98,12 @@ export OPENCLAW_CONFIG_PATH="$CONFIG_FILE"
|
|||||||
|
|
||||||
# ---- 7. Check dependencies ----
|
# ---- 7. Check dependencies ----
|
||||||
if [ ! -d "$CORE_DIR/node_modules" ]; then
|
if [ ! -d "$CORE_DIR/node_modules" ]; then
|
||||||
echo -e " ${YELLOW}First run - installing dependencies...${NC}"
|
echo -e " ${YELLOW}[WARN] node_modules not found${NC}"
|
||||||
echo " (Using China mirror)"
|
echo " This release should ship with deps pre-installed."
|
||||||
|
echo " Falling back to npm install (USB drives may take 20+ min)."
|
||||||
|
echo " TIP: re-download u-claw-portable-*.zip with bundled deps."
|
||||||
cd "$CORE_DIR"
|
cd "$CORE_DIR"
|
||||||
"$NODE_BIN" "$NODE_DIR/bin/npm" install --registry=https://registry.npmmirror.com 2>&1
|
"$NODE_BIN" "$NODE_DIR/bin/npm" install --registry=https://registry.npmmirror.com --ignore-scripts --no-audit --no-fund --omit=dev 2>&1
|
||||||
echo -e " ${GREEN}Dependencies installed${NC}"
|
echo -e " ${GREEN}Dependencies installed${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
fi
|
fi
|
||||||
@@ -111,6 +113,16 @@ echo -e " ${CYAN}Binding device fingerprint to Xiapan Cloud...${NC}"
|
|||||||
UCLAW_APP_ROOT="$UCLAW_DIR" "$NODE_BIN" "$UCLAW_DIR/lib/bootstrap-xiapan.mjs" "$CONFIG_FILE" || true
|
UCLAW_APP_ROOT="$UCLAW_DIR" "$NODE_BIN" "$UCLAW_DIR/lib/bootstrap-xiapan.mjs" "$CONFIG_FILE" || true
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# ---- 7c. Async update check (non-blocking, 5s timeout, silent failure) ----
|
||||||
|
# Writes data/.openclaw/update-available.json if a newer version is on OSS.
|
||||||
|
# Welcome.html / Config.html read this file and show a banner.
|
||||||
|
# Version file lookup: portable/OPENCLAW_VERSION (USB) → ../OPENCLAW_VERSION (dev)
|
||||||
|
VERSION_FILE="$UCLAW_DIR/OPENCLAW_VERSION"
|
||||||
|
[ -f "$VERSION_FILE" ] || VERSION_FILE="$UCLAW_DIR/../OPENCLAW_VERSION"
|
||||||
|
if [ -f "$VERSION_FILE" ]; then
|
||||||
|
"$NODE_BIN" "$UCLAW_DIR/lib/check-update.mjs" "$VERSION_FILE" "$STATE_DIR" >/dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
|
||||||
# ---- 8. Find available port ----
|
# ---- 8. Find available port ----
|
||||||
PORT=18789
|
PORT=18789
|
||||||
while lsof -i :$PORT >/dev/null 2>&1; do
|
while lsof -i :$PORT >/dev/null 2>&1; do
|
||||||
|
|||||||
@@ -62,11 +62,19 @@ if not exist "%STATE_DIR%\openclaw.json" (
|
|||||||
|
|
||||||
REM Check dependencies
|
REM Check dependencies
|
||||||
if not exist "%CORE_DIR%\node_modules" (
|
if not exist "%CORE_DIR%\node_modules" (
|
||||||
echo First run - installing dependencies...
|
echo ========================================
|
||||||
echo Using China mirror, please wait...
|
echo [WARN] node_modules not found
|
||||||
|
echo ========================================
|
||||||
|
echo This release should ship with deps pre-installed.
|
||||||
|
echo Falling back to npm install (USB drives may take 20+ minutes).
|
||||||
|
echo.
|
||||||
|
echo TIP: Re-download u-claw-portable-*.zip from GitHub releases,
|
||||||
|
echo which includes pre-installed deps (~200 MB).
|
||||||
|
echo.
|
||||||
|
echo File system: NTFS recommended. exFAT/FAT32 will be very slow.
|
||||||
echo.
|
echo.
|
||||||
cd /d "%CORE_DIR%"
|
cd /d "%CORE_DIR%"
|
||||||
call "%NPM_BIN%" install --registry=https://registry.npmmirror.com
|
call "%NPM_BIN%" install --registry=https://registry.npmmirror.com --ignore-scripts --no-audit --no-fund --omit=dev
|
||||||
echo.
|
echo.
|
||||||
echo Dependencies installed!
|
echo Dependencies installed!
|
||||||
echo.
|
echo.
|
||||||
@@ -78,6 +86,17 @@ set "UCLAW_APP_ROOT=%UCLAW_DIR%"
|
|||||||
"%NODE_BIN%" "%UCLAW_DIR%lib\bootstrap-xiapan.mjs" "%STATE_DIR%\openclaw.json"
|
"%NODE_BIN%" "%UCLAW_DIR%lib\bootstrap-xiapan.mjs" "%STATE_DIR%\openclaw.json"
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
|
REM Async update check (non-blocking, 5s timeout, silent failure)
|
||||||
|
REM Writes data\.openclaw\update-available.json if a newer version is on OSS.
|
||||||
|
REM Welcome.html / Config.html read this file and show a banner.
|
||||||
|
REM Version file lookup order: portable/OPENCLAW_VERSION (USB), then repo-root ../OPENCLAW_VERSION (dev)
|
||||||
|
set "VERSION_FILE=%UCLAW_DIR%OPENCLAW_VERSION"
|
||||||
|
if not exist "%VERSION_FILE%" set "VERSION_FILE=%UCLAW_DIR%..\OPENCLAW_VERSION"
|
||||||
|
if exist "%VERSION_FILE%" (
|
||||||
|
start /B "" "%NODE_BIN%" "%UCLAW_DIR%lib\check-update.mjs" "%VERSION_FILE%" "%STATE_DIR%" >nul 2>&1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
REM Auto-install WeChat plugin if available
|
REM Auto-install WeChat plugin if available
|
||||||
set "WECHAT_PLUGIN_SRC=%APP_DIR%\extensions\openclaw-weixin"
|
set "WECHAT_PLUGIN_SRC=%APP_DIR%\extensions\openclaw-weixin"
|
||||||
set "WECHAT_PLUGIN_DST=%USERPROFILE%\.openclaw\extensions\openclaw-weixin"
|
set "WECHAT_PLUGIN_DST=%USERPROFILE%\.openclaw\extensions\openclaw-weixin"
|
||||||
|
|||||||
Reference in New Issue
Block a user