Files
clawpanel/scripts/linux-deploy.sh
晴天 46cd8ae01e fix(deploy): 修复 Linux 全新系统部署 npm 缓存权限问题 (#236)
现象:全新 Ubuntu 上执行一键部署脚本后,npm install 报大量
TAR_ENTRY_ERROR / ENOTEMPTY / EACCES 错误,node_modules 处于半损坏状态,
导致后续 vite build 找不到 picomatch 等依赖。

根因:OpenClaw 安装阶段使用 sudo -E npm install -g 安装全局包时,
会在用户主目录 ~/.npm 缓存目录里留下 root 拥有的文件,
后续用户态的 npm install 无法写缓存 → 安装中断 → node_modules 残缺。

修复:
- 新增 fix_npm_cache_permissions:检测 ~/.npm 中是否存在 root 拥有的文件,
  自动执行 chown 恢复当前用户权限(root/普通用户分别走 chown 与 sudo chown)
- install_clawpanel 调用一次权限预检,并对 npm install 失败场景增加一次
  清理 node_modules 后的重试,避免半损坏目录污染构建
- 升级路径里补充对不完整 node_modules 的探测(缺少 .package-lock.json 即清理)
2026-04-20 16:01:43 +08:00

407 lines
12 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
set -e
echo "=========================================="
echo " ClawPanel Web 版 一键部署脚本"
echo " 在 Linux 上通过浏览器管理 OpenClaw"
echo "=========================================="
echo ""
PANEL_PORT=1420
REPO_URL="https://github.com/qingchencloud/clawpanel.git"
REPO_URL_GITEE="https://gitee.com/QtCodeCreators/clawpanel.git"
NPM_REGISTRY="https://registry.npmmirror.com"
# 检测权限模式
if [ "$(id -u)" = "0" ]; then
IS_ROOT=true
INSTALL_DIR="/opt/clawpanel"
SYSTEMD_DIR="/etc/systemd/system"
echo "🔑 以 root 身份运行,安装到 $INSTALL_DIR"
else
IS_ROOT=false
INSTALL_DIR="$HOME/.local/share/clawpanel"
SYSTEMD_DIR="$HOME/.config/systemd/user"
echo "👤 以普通用户身份运行,安装到 $INSTALL_DIR"
fi
# 带权限执行(安装系统包时需要)
run_pkg_cmd() {
if [ "$IS_ROOT" = true ]; then
"$@"
else
sudo "$@"
fi
}
# 检测系统
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
OS=$ID
OS_LIKE=$ID_LIKE
elif [ -f /etc/redhat-release ]; then
OS="centos"
else
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
fi
ARCH=$(uname -m)
echo "🖥️ 系统: $OS $ARCH"
# ARM 架构检测和提示
case "$ARCH" in
aarch64|arm64)
echo "✅ ARM64 架构Web 模式和 Docker 模式均支持"
;;
armv7*|armhf)
echo "⚠️ ARM 32位 ($ARCH)Web 模式可用Docker 镜像仅支持 arm64"
;;
armv6*)
echo "⚠️ ARM v6 ($ARCH):内存和性能可能不足,建议升级到 ARM64 设备"
;;
x86_64|amd64)
;;
*)
echo " 架构: $ARCH"
;;
esac
}
# 安装 Node.js
install_node() {
if command -v node &> /dev/null; then
local node_major=$(node -v | sed 's/v//' | cut -d. -f1)
if [ "$node_major" -ge 18 ]; then
echo "✅ Node.js $(node -v) 已安装"
return 0
else
echo "⚠️ Node.js $(node -v) 版本过低,需要 18+"
fi
fi
echo "📦 安装 Node.js 22 LTS..."
case "$OS" in
ubuntu|debian|linuxmint|pop)
curl -fsSL https://deb.nodesource.com/setup_22.x | run_pkg_cmd bash -
run_pkg_cmd apt-get install -y nodejs
;;
centos|rhel|fedora|rocky|alma)
curl -fsSL https://rpm.nodesource.com/setup_22.x | run_pkg_cmd bash -
run_pkg_cmd yum install -y nodejs
;;
alpine)
run_pkg_cmd apk add nodejs npm git
;;
arch|manjaro)
run_pkg_cmd pacman -Sy --noconfirm nodejs npm git
;;
*)
echo "❌ 不支持自动安装 Node.js请手动安装后重试"
echo " 参考: https://nodejs.org/en/download/"
exit 1
;;
esac
echo "✅ Node.js $(node -v) 安装完成"
}
# 安装 Git
install_git() {
if command -v git &> /dev/null; then
echo "✅ Git 已安装"
return 0
fi
echo "📦 安装 Git..."
case "$OS" in
ubuntu|debian|linuxmint|pop)
run_pkg_cmd apt-get update && run_pkg_cmd apt-get install -y git
;;
centos|rhel|fedora|rocky|alma)
run_pkg_cmd yum install -y git
;;
alpine)
run_pkg_cmd apk add git
;;
arch|manjaro)
run_pkg_cmd pacman -Sy --noconfirm git
;;
esac
echo "✅ Git 安装完成"
}
# 查找 openclaw 可执行文件(兼容各种安装方式)
find_openclaw() {
# 1. 直接在 PATH 中查找
if command -v openclaw &> /dev/null; then
echo "$(command -v openclaw)"
return 0
fi
# 2. 常见 npm 全局安装路径
local candidates=(
"/usr/local/bin/openclaw"
"/usr/bin/openclaw"
"$HOME/.npm-global/bin/openclaw"
"$HOME/.local/bin/openclaw"
)
# 3. 从 npm prefix 获取(不使用 sudo避免触发密码提示
local npm_prefix=$(npm config get prefix 2>/dev/null)
if [ -n "$npm_prefix" ]; then
candidates+=("$npm_prefix/bin/openclaw")
fi
for p in "${candidates[@]}"; do
if [ -x "$p" ]; then
echo "$p"
return 0
fi
done
return 1
}
# 检测 OpenClaw 版本来源(官方 vs 汉化版)
detect_openclaw_source() {
local oc_bin="$1"
local ver=$("$oc_bin" --version 2>/dev/null || echo "")
if echo "$ver" | grep -qi "zh\|汉化\|chinese"; then
echo "chinese"
else
echo "official"
fi
}
# 安装 OpenClaw
install_openclaw() {
local oc_path=$(find_openclaw)
if [ -n "$oc_path" ]; then
local oc_ver=$("$oc_path" --version 2>/dev/null || echo "未知版本")
local oc_src=$(detect_openclaw_source "$oc_path")
if [ "$oc_src" = "chinese" ]; then
echo "✅ OpenClaw 汉化版已安装: $oc_ver (${oc_path})"
else
echo "✅ OpenClaw 已安装: $oc_ver (${oc_path})"
fi
# 确保 openclaw 在 PATH 中(防止后续步骤找不到)
if ! command -v openclaw &> /dev/null; then
export PATH="$(dirname "$oc_path"):$PATH"
echo " 已将 $(dirname "$oc_path") 加入 PATH"
fi
else
echo "📦 安装 OpenClaw 汉化版..."
if [ "$IS_ROOT" = true ]; then
npm install -g @qingchencloud/openclaw-zh --registry "$NPM_REGISTRY" || \
npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmjs.org
else
sudo -E npm install -g @qingchencloud/openclaw-zh --registry "$NPM_REGISTRY" || \
sudo -E npm install -g @qingchencloud/openclaw-zh --registry https://registry.npmjs.org
fi
echo "✅ OpenClaw 安装完成"
fi
# 初始化配置(如果不存在)
if [ ! -f "$HOME/.openclaw/openclaw.json" ]; then
echo "🔧 初始化 OpenClaw 配置..."
openclaw init 2>/dev/null || true
fi
}
# 修复 npm 缓存权限(曾用 sudo npm install 导致缓存被 root 拥有)
fix_npm_cache_permissions() {
local npm_cache_dir="$HOME/.npm"
if [ -d "$npm_cache_dir" ]; then
# 检查是否存在 root 拥有的文件
local root_files=$(find "$npm_cache_dir" -uid 0 2>/dev/null | head -1)
if [ -n "$root_files" ]; then
echo "⚠️ 检测到 npm 缓存中存在 root 权限文件,正在修复..."
if [ "$IS_ROOT" = true ]; then
chown -R "$(stat -c '%u:%g' "$HOME")" "$npm_cache_dir" 2>/dev/null || true
else
sudo chown -R "$(id -u):$(id -g)" "$npm_cache_dir" 2>/dev/null || true
fi
echo "✅ npm 缓存权限已修复"
fi
fi
}
# 克隆并安装 ClawPanel
install_clawpanel() {
# 预检 npm 缓存权限(#236: 全新系统部署时 npm cache 可能被 root 污染)
fix_npm_cache_permissions
if [ -d "$INSTALL_DIR" ] && [ -f "$INSTALL_DIR/package.json" ]; then
echo "📦 ClawPanel 已存在,更新中..."
cd "$INSTALL_DIR"
git pull origin main 2>/dev/null || true
# 清理可能损坏的 node_modules上次 npm install 失败残留)
if [ -d "node_modules" ] && [ ! -f "node_modules/.package-lock.json" ]; then
echo "⚠️ 检测到不完整的 node_modules清理后重新安装..."
rm -rf node_modules
fi
npm install --registry "$NPM_REGISTRY" || {
echo "⚠️ npm install 失败,清理 node_modules 后重试..."
rm -rf node_modules
npm install --registry "$NPM_REGISTRY"
}
else
echo "📦 克隆 ClawPanel..."
mkdir -p "$INSTALL_DIR"
if ! git clone "$REPO_URL" "$INSTALL_DIR" 2>/dev/null; then
echo "⚠️ GitHub 克隆失败,切换到 Gitee 国内镜像..."
git clone "$REPO_URL_GITEE" "$INSTALL_DIR"
fi
cd "$INSTALL_DIR"
npm install --registry "$NPM_REGISTRY" || {
echo "⚠️ npm install 失败,清理 node_modules 后重试..."
rm -rf node_modules
npm install --registry "$NPM_REGISTRY"
}
fi
# 生产构建(生成优化后的静态文件)
echo "📦 构建生产版本..."
cd "$INSTALL_DIR"
npx vite build
echo "✅ ClawPanel 安装完成: $INSTALL_DIR"
echo "✅ 启动命令: npm run serve"
}
# 创建 systemd 服务
setup_systemd() {
if ! command -v systemctl &> /dev/null; then
echo "⚠️ systemd 不可用,请手动启动:"
echo " cd $INSTALL_DIR && npm run serve -- --port $PANEL_PORT"
return 0
fi
echo "🔧 创建 systemd 服务..."
mkdir -p "$SYSTEMD_DIR"
if [ "$IS_ROOT" = true ]; then
cat > "$SYSTEMD_DIR/clawpanel.service" << EOF
[Unit]
Description=ClawPanel Web - OpenClaw Management Panel
After=network.target
[Service]
Type=simple
User=$(whoami)
WorkingDirectory=$INSTALL_DIR
ExecStart=$(which node) scripts/serve.js --port $PANEL_PORT
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=HOME=$HOME
Environment=PATH=$HOME/.npm-global/bin:$HOME/.local/bin:$HOME/.volta/bin:$(dirname $(which node)):$PATH
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable clawpanel
systemctl start clawpanel
else
cat > "$SYSTEMD_DIR/clawpanel.service" << EOF
[Unit]
Description=ClawPanel Web - OpenClaw Management Panel
After=network.target
[Service]
Type=simple
WorkingDirectory=$INSTALL_DIR
ExecStart=$(which node) scripts/serve.js --port $PANEL_PORT
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
Environment=HOME=$HOME
Environment=PATH=$HOME/.npm-global/bin:$HOME/.local/bin:$HOME/.volta/bin:$(dirname $(which node)):$PATH
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable clawpanel
systemctl --user start clawpanel
# 允许用户服务在未登录时继续运行
loginctl enable-linger "$(whoami)" 2>/dev/null || true
fi
echo "✅ systemd 服务已创建并启动"
}
# 获取本机 IP
get_local_ip() {
ip route get 1 2>/dev/null | awk '{print $7; exit}' || \
hostname -I 2>/dev/null | awk '{print $1}' || \
echo "localhost"
}
# 生成默认访问密码
setup_default_password() {
local config_dir="$HOME/.openclaw"
local config_file="$config_dir/clawpanel.json"
mkdir -p "$config_dir"
# 已存在配置且有密码则跳过
if [ -f "$config_file" ]; then
local existing_pw=$(grep -o '"accessPassword"[[:space:]]*:[[:space:]]*"[^"]*"' "$config_file" | head -1)
if [ -n "$existing_pw" ]; then
echo " 已有访问密码,跳过生成"
DEFAULT_PASSWORD=""
return
fi
fi
DEFAULT_PASSWORD="123456"
cat > "$config_file" <<EOF
{
"accessPassword": "123456",
"mustChangePassword": true
}
EOF
echo "✅ 已设置默认访问密码: 123456"
}
# 主流程
main() {
detect_os
echo ""
install_git
install_node
install_openclaw
install_clawpanel
setup_default_password
setup_systemd
local ip=$(get_local_ip)
if [ "$IS_ROOT" = true ]; then
local ctl_cmd="systemctl"
else
local ctl_cmd="systemctl --user"
fi
echo ""
echo "=========================================="
echo " ✅ ClawPanel Web 版部署完成!"
echo "=========================================="
echo ""
echo " 🌐 访问地址: http://${ip}:${PANEL_PORT}"
echo " 📁 安装目录: $INSTALL_DIR"
echo " 📋 配置目录: $HOME/.openclaw/"
if [ -n "$DEFAULT_PASSWORD" ]; then
echo ""
echo " 🔑 默认访问密码: $DEFAULT_PASSWORD"
echo " ⚠️ 首次登录后会要求修改密码,请妥善保管新密码!"
fi
echo ""
echo " 常用命令:"
echo " $ctl_cmd status clawpanel # 查看状态"
echo " $ctl_cmd restart clawpanel # 重启面板"
if [ "$IS_ROOT" = true ]; then
echo " journalctl -u clawpanel -f # 查看日志"
else
echo " journalctl --user -u clawpanel -f # 查看日志"
fi
echo ""
echo " 用浏览器打开上面的地址,即可管理 OpenClaw。"
echo "=========================================="
}
main "$@"