fix portable config and qqbot packaging

This commit is contained in:
hfshfg
2026-04-30 19:47:57 +08:00
parent a42a1269a4
commit b57aeb068c
6 changed files with 236 additions and 22 deletions

View File

@@ -81,6 +81,67 @@ cd path\to\u-claw\bootable
.\4-copy-to-usb.ps1 .\4-copy-to-usb.ps1
``` ```
## 自动脚本失败时的手动兜底流程
> 由 @wzf9 在 issue #28 反馈整理,适合 Ventoy/ISO 自动下载失败、网络不稳定或需要离线制作的场景。
1. **Ventoy 下载或安装失败**
- 手动下载 Ventoy Windows 版https://github.com/ventoy/Ventoy/releases
- 解压后运行 `Ventoy2Disk.exe`
- 选择目标 U 盘并点击 Install
- 注意:这一步会格式化 U 盘,先备份数据
2. **Ubuntu ISO 自动下载失败**
- 手动下载 Ubuntu 24.04.4 Desktop ISO
https://releases.ubuntu.com/24.04/ubuntu-24.04.4-desktop-amd64.iso
- 手动下载 SHA256SUMS
https://releases.ubuntu.com/24.04/SHA256SUMS
- 将 ISO 放到 `bootable\.download-cache\ubuntu-24.04.4-desktop-amd64.iso`
- 可用 PowerShell 校验哈希:
```powershell
(Get-FileHash -Algorithm SHA256 ".\.download-cache\ubuntu-24.04.4-desktop-amd64.iso").Hash -eq "3a4c9877b483ab46d7c3fbe165a0db275e1ae3cfe56a5657e5a47c2f99a99d1e"
```
3. **继续创建持久化文件**
```powershell
.\3-create-persistence.ps1
```
如果脚本提示没有 WSL 或只能创建空文件,首次进入 Ubuntu 后需要手动格式化:
```bash
sudo mkfs.ext4 -F -L casper-rw /media/*/Ventoy/persistence.dat
```
格式化完成后重启,持久化才会生效。
4. **拷贝到 U 盘**
- 优先运行:
```powershell
.\4-copy-to-usb.ps1
```
- 如果自动拷贝失败,也可以手动放到 Ventoy 数据分区根目录:
| 本地路径 | U 盘目标 |
|---------|----------|
| `bootable\linux-setup\` | `u-claw-linux\` |
| `bootable\ventoy\` | `ventoy\` |
| `bootable\.download-cache\persistence.dat` | `persistence.dat` |
| `bootable\.download-cache\ubuntu-24.04.4-desktop-amd64.iso` | `ubuntu-24.04.4-desktop-amd64.iso` |
5. **目标电脑启动**
- 从 U 盘启动Ventoy 菜单选择 Ubuntu
- 如遇 Secure Boot 拦截,进 BIOS 关闭 Secure Boot
- 进入 Ubuntu 桌面后运行:
```bash
sudo bash /media/*/Ventoy/u-claw-linux/setup-openclaw.sh
```
## 每一步做了什么 ## 每一步做了什么
### Step 1: 写入 Ventoy 引导 (`1-prepare-usb.ps1`) ### Step 1: 写入 Ventoy 引导 (`1-prepare-usb.ps1`)

View File

@@ -48,16 +48,51 @@ h1 .lobster { color: #ff6b35; }
.buy-link:hover { text-decoration: underline; } .buy-link:hover { text-decoration: underline; }
/* Forms */ /* Forms */
.form-group { margin-bottom: 16px; } .form-group { display: block; margin-bottom: 16px; clear: both; }
label { display: block; color: #ccc; margin-bottom: 6px; font-size: 0.9em; } .form-group label {
input[type="text"], input[type="password"] { display: block;
width: 100%; padding: 11px 14px; background: #222; border: 1px solid #444; border-radius: 8px; width: auto;
color: #fff; font-size: 0.95em; outline: none; transition: border-color 0.2s; float: none;
color: #ccc;
margin-bottom: 8px;
font-size: 0.9em;
}
.form-group input,
input[type="text"],
input[type="password"] {
display: block;
width: 100%;
min-width: 0;
min-height: 46px;
padding: 11px 14px;
background: #222;
border: 1px solid #444;
border-radius: 8px;
color: #fff;
font-size: 0.95em;
line-height: 1.3;
outline: none;
transition: border-color 0.2s;
} }
input:focus { border-color: #ff6b35; } input:focus { border-color: #ff6b35; }
.input-wrap { position: relative; } .input-wrap { position: relative; display: block; width: 100%; min-width: 0; }
.input-wrap input { padding-right: 44px; } .input-wrap input { padding-right: 64px; }
.toggle-pw { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); background: none; border: none; color: #888; cursor: pointer; font-size: 0.85em; } .toggle-pw {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
min-width: 38px;
height: 30px;
background: #2d2d2d;
border: 1px solid #444;
border-radius: 6px;
color: #ccc;
cursor: pointer;
font-size: 0.82em;
line-height: 28px;
text-align: center;
}
.toggle-pw:hover { color: #ccc; } .toggle-pw:hover { color: #ccc; }
.hint { color: #888; font-size: 0.8em; margin-top: 6px; line-height: 1.5; } .hint { color: #888; font-size: 0.8em; margin-top: 6px; line-height: 1.5; }
.hint a { color: #ff6b35; text-decoration: none; } .hint a { color: #ff6b35; text-decoration: none; }
@@ -359,10 +394,10 @@ let selectedBase = '';
let selectedModel = ''; let selectedModel = '';
// --- Model cards --- // --- Model cards ---
document.querySelectorAll('.model-card').forEach(card => { document.querySelectorAll('#step1 .model-card').forEach(card => {
card.addEventListener('click', (e) => { card.addEventListener('click', (e) => {
if (e.target.closest('.buy-link')) return; // don't select when clicking buy link if (e.target.closest('.buy-link')) return; // don't select when clicking buy link
document.querySelectorAll('.model-card').forEach(c => c.classList.remove('selected')); document.querySelectorAll('#step1 .model-card').forEach(c => c.classList.remove('selected'));
card.classList.add('selected'); card.classList.add('selected');
selectedProvider = card.dataset.provider; selectedProvider = card.dataset.provider;
selectedBase = card.dataset.base; selectedBase = card.dataset.base;
@@ -375,7 +410,7 @@ document.getElementById('nextStep1').addEventListener('click', () => goStep(2));
// --- Steps --- // --- Steps ---
function goStep(n) { function goStep(n) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active')); document.querySelectorAll('.section:not(.skills-section)').forEach(s => s.classList.remove('active'));
document.getElementById('step' + n).classList.add('active'); document.getElementById('step' + n).classList.add('active');
// Keep skills section visible // Keep skills section visible
document.querySelectorAll('.steps .step').forEach((s, i) => { document.querySelectorAll('.steps .step').forEach((s, i) => {
@@ -393,7 +428,7 @@ function setupStep2() {
const providerNames = { const providerNames = {
minimax: 'MiniMax', kimi: 'Kimi (月之暗面)', deepseek: 'DeepSeek', minimax: 'MiniMax', kimi: 'Kimi (月之暗面)', deepseek: 'DeepSeek',
zhipu: '智谱 GLM', qwen: '通义千问', doubao: '豆包', zai: '智谱 GLM', qwen: '通义千问', doubao: '豆包',
openai: 'OpenAI', anthropic: 'Anthropic Claude', groq: 'Groq', openai: 'OpenAI', anthropic: 'Anthropic Claude', groq: 'Groq',
siliconflow: '硅基流动', custom: '自定义' siliconflow: '硅基流动', custom: '自定义'
}; };
@@ -401,7 +436,7 @@ function setupStep2() {
document.getElementById('step2desc').textContent = `请填写 ${name} 的 API Key`; document.getElementById('step2desc').textContent = `请填写 ${name} 的 API Key`;
// Buy links // Buy links
const card = document.querySelector(`.model-card[data-provider="${selectedProvider}"]`); const card = document.querySelector(`#step1 .model-card[data-provider="${selectedProvider}"]`);
const buyLink = card ? card.querySelector('.buy-link') : null; const buyLink = card ? card.querySelector('.buy-link') : null;
const linksDiv = document.getElementById('providerLinks'); const linksDiv = document.getElementById('providerLinks');
linksDiv.innerHTML = ''; linksDiv.innerHTML = '';

View File

@@ -139,6 +139,24 @@ if "!USE_OPENCLAW!"=="usb" (
echo OpenClaw 下载安装完成! echo OpenClaw 下载安装完成!
) )
set "QQ_DIR=%INSTALL_TARGET%\core\node_modules\@sliverp\qqbot"
if exist "!QQ_DIR!" (
if not exist "!QQ_DIR!\dist\index.js" (
echo 编译 QQbot 插件运行文件...
pushd "!QQ_DIR!"
call "!INSTALL_NPM!" install --include=dev --registry=%MIRROR% >nul 2>&1
call "!INSTALL_NPM!" run build >nul 2>&1
call "!INSTALL_NPM!" prune --omit=dev >nul 2>&1
popd
)
if exist "!QQ_DIR!\node_modules\openclaw" rmdir /s /q "!QQ_DIR!\node_modules\openclaw" 2>nul
if exist "!QQ_DIR!\dist\index.js" (
echo QQbot 插件运行文件已就绪!
) else (
echo [WARNING] QQbot 插件缺少 dist\index.js
)
)
REM ---- Copy extensions (WeChat plugin etc.) ---- REM ---- Copy extensions (WeChat plugin etc.) ----
if exist "%APP_DIR%\extensions\openclaw-weixin\openclaw.plugin.json" ( if exist "%APP_DIR%\extensions\openclaw-weixin\openclaw.plugin.json" (
echo Installing WeChat plugin... echo Installing WeChat plugin...

View File

@@ -48,16 +48,51 @@ h1 .lobster { color: #ff6b35; }
.buy-link:hover { text-decoration: underline; } .buy-link:hover { text-decoration: underline; }
/* Forms */ /* Forms */
.form-group { margin-bottom: 16px; } .form-group { display: block; margin-bottom: 16px; clear: both; }
label { display: block; color: #ccc; margin-bottom: 6px; font-size: 0.9em; } .form-group label {
input[type="text"], input[type="password"] { display: block;
width: 100%; padding: 11px 14px; background: #222; border: 1px solid #444; border-radius: 8px; width: auto;
color: #fff; font-size: 0.95em; outline: none; transition: border-color 0.2s; float: none;
color: #ccc;
margin-bottom: 8px;
font-size: 0.9em;
}
.form-group input,
input[type="text"],
input[type="password"] {
display: block;
width: 100%;
min-width: 0;
min-height: 46px;
padding: 11px 14px;
background: #222;
border: 1px solid #444;
border-radius: 8px;
color: #fff;
font-size: 0.95em;
line-height: 1.3;
outline: none;
transition: border-color 0.2s;
} }
input:focus { border-color: #ff6b35; } input:focus { border-color: #ff6b35; }
.input-wrap { position: relative; } .input-wrap { position: relative; display: block; width: 100%; min-width: 0; }
.input-wrap input { padding-right: 44px; } .input-wrap input { padding-right: 70px; }
.toggle-pw { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); background: none; border: none; color: #888; cursor: pointer; font-size: 0.85em; } .toggle-pw {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
min-width: 44px;
height: 30px;
background: #2d2d2d;
border: 1px solid #444;
border-radius: 6px;
color: #ccc;
cursor: pointer;
font-size: 0.82em;
line-height: 28px;
text-align: center;
}
.toggle-pw:hover { color: #ccc; } .toggle-pw:hover { color: #ccc; }
.hint { color: #888; font-size: 0.8em; margin-top: 6px; line-height: 1.5; } .hint { color: #888; font-size: 0.8em; margin-top: 6px; line-height: 1.5; }
.hint a { color: #ff6b35; text-decoration: none; } .hint a { color: #ff6b35; text-decoration: none; }

View File

@@ -97,6 +97,8 @@ for %%p in (arm64 x64) do (
:skip_mac_download :skip_mac_download
set "NPM_BIN=%NODE_TARGET%\npm.cmd"
REM ---- 2. Install OpenClaw ---- REM ---- 2. Install OpenClaw ----
if exist "%CORE_DIR%\node_modules\openclaw" goto skip_openclaw_install if exist "%CORE_DIR%\node_modules\openclaw" goto skip_openclaw_install
@@ -107,7 +109,6 @@ if not exist "%CORE_DIR%\package.json" (
echo { "name": "u-claw-core", "version": "1.0.0", "private": true, "dependencies": { "openclaw": "latest" } } > "%CORE_DIR%\package.json" echo { "name": "u-claw-core", "version": "1.0.0", "private": true, "dependencies": { "openclaw": "latest" } } > "%CORE_DIR%\package.json"
) )
set "NPM_BIN=%NODE_TARGET%\npm.cmd"
cd /d "%CORE_DIR%" cd /d "%CORE_DIR%"
call "%NPM_BIN%" install --prefix "%CORE_DIR%" --registry="%MIRROR%" call "%NPM_BIN%" install --prefix "%CORE_DIR%" --registry="%MIRROR%"
@@ -132,6 +133,27 @@ goto qq_install_done
echo [OK] QQ Plugin exists, skipping echo [OK] QQ Plugin exists, skipping
:qq_install_done :qq_install_done
set "QQ_DIR=%CORE_DIR%\node_modules\@sliverp\qqbot"
if not exist "%QQ_DIR%" goto qq_build_done
if exist "%QQ_DIR%\dist\index.js" goto qq_build_cleanup
echo [BUILD] Building QQ Plugin runtime files...
pushd "%QQ_DIR%"
call "%NPM_BIN%" install --include=dev --registry="%MIRROR%" >nul 2>&1
call "%NPM_BIN%" run build >nul 2>&1
call "%NPM_BIN%" prune --omit=dev >nul 2>&1
popd
:qq_build_cleanup
if exist "%QQ_DIR%\node_modules\openclaw" rmdir /s /q "%QQ_DIR%\node_modules\openclaw" 2>nul
if exist "%QQ_DIR%\dist\index.js" (
echo [OK] QQ Plugin runtime files ready
) else (
echo [WARNING] QQ Plugin dist\index.js is missing
)
:qq_build_done
REM ---- 4. Install China-optimized skills ---- REM ---- 4. Install China-optimized skills ----
set "SKILLS_CN=%SCRIPT_DIR%skills-cn" set "SKILLS_CN=%SCRIPT_DIR%skills-cn"
set "SKILLS_TARGET=%CORE_DIR%\node_modules\openclaw\skills" set "SKILLS_TARGET=%CORE_DIR%\node_modules\openclaw\skills"

View File

@@ -141,6 +141,47 @@ function Install-TarNodeRuntime {
} }
} }
function Ensure-QQPluginBuild {
param(
[string]$CoreDir,
[string]$NpmCmd
)
$qqDir = Join-Path $CoreDir "node_modules\@sliverp\qqbot"
if (-not (Test-Path -Path $qqDir -PathType Container)) {
return
}
$distIndex = Join-Path $qqDir "dist\index.js"
if (-not (Test-Path -Path $distIndex -PathType Leaf)) {
Write-Step "->" "Building QQ plugin runtime files..." "Cyan"
Push-Location $qqDir
try {
& $NpmCmd install --include=dev --registry=$mirror
& $NpmCmd run build
& $NpmCmd prune --omit=dev
}
catch {
Write-Step "WARN" "QQ plugin build command failed; continuing setup." "Yellow"
}
finally {
Pop-Location
}
}
$nestedOpenClaw = Join-Path $qqDir "node_modules\openclaw"
if (Test-Path -Path $nestedOpenClaw -PathType Container) {
Remove-Item -Path $nestedOpenClaw -Recurse -Force -ErrorAction SilentlyContinue
}
if (Test-Path -Path $distIndex -PathType Leaf) {
Write-Step "OK" "QQ plugin runtime files are ready." "Green"
}
else {
Write-Step "WARN" "QQ plugin is installed but dist/index.js is missing." "Yellow"
}
}
Write-Host "" Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan
Write-Host " U-Claw Portable Setup" -ForegroundColor Cyan Write-Host " U-Claw Portable Setup" -ForegroundColor Cyan
@@ -207,6 +248,8 @@ else {
Write-Step "OK" "QQ plugin installation finished." "Green" Write-Step "OK" "QQ plugin installation finished." "Green"
} }
Ensure-QQPluginBuild -CoreDir $coreDir -NpmCmd $npmCmd
$skillsCn = Join-Path $scriptDir "skills-cn" $skillsCn = Join-Path $scriptDir "skills-cn"
$skillsTarget = Join-Path $coreDir "node_modules\openclaw\skills" $skillsTarget = Join-Path $coreDir "node_modules\openclaw\skills"