mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-06 20:02:49 +08:00
- 飞书渠道从 @openclaw/feishu 迁移到 @larksuite/openclaw-lark 官方插件 - 保存飞书配置时自动禁用旧 feishu 插件,防止新旧插件冲突 - 所有主要渠道(飞书/Telegram/Discord/Slack)启用配对审批UI - gateway_command 增加20s超时,超时后force-kill+fresh start - 全平台启动前端口占用检查,防止Guardian无限拉起 - Linux gateway_command 补齐 Duration 导入和 cleanup_zombie 实现 - Guardian自动守护在Tauri桌面端也启用,轮询间隔30s→15s - 微信渠道:升级操作不再弹出扫码二维码,按钮文案区分安装/升级 - 版本更新检测:CI不再将minAppVersion写死为当前版本 - 部署脚本增强OpenClaw检测,支持已安装的官方版 - 日间/夜间模式圆形扩散切换动画(View Transitions API) - API错误信息完整展示(429限流等),URL自动转可点击链接 - 第三方API接入引导优化:移除内置密钥,引导式流程 - 修复全平台 Clippy 警告(strip_prefix/dead_code/unnecessary_unwrap等) - Rust代码格式化修复(cargo fmt) - toast组件支持HTML内容渲染 - Rust后端test_model返回详细错误信息
537 lines
15 KiB
Bash
537 lines
15 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# ClawPanel Docker 部署脚本
|
|
# =============================================================================
|
|
# 功能:
|
|
# 1. 检查 Docker 环境
|
|
# 2. 构建 Docker 镜像
|
|
# 3. 启动/停止/重启容器
|
|
# 4. 查看日志
|
|
# 5. 常见问题排查
|
|
# =============================================================================
|
|
|
|
set -e
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 颜色定义
|
|
# -----------------------------------------------------------------------------
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 配置
|
|
# -----------------------------------------------------------------------------
|
|
CONTAINER_NAME="clawpanel"
|
|
IMAGE_NAME="clawpanel"
|
|
IMAGE_TAG="latest"
|
|
DEFAULT_PORT=1420
|
|
CONFIG_DIR="$HOME/.openclaw"
|
|
DATA_DIR="$(pwd)/data"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 工具函数
|
|
# -----------------------------------------------------------------------------
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
log_step() {
|
|
echo -e "${CYAN}[STEP]${NC} $1"
|
|
}
|
|
|
|
separator() {
|
|
echo "--------------------------------------------------------------------------------"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 检查 Docker 环境
|
|
# -----------------------------------------------------------------------------
|
|
check_docker() {
|
|
log_step "检查 Docker 环境..."
|
|
|
|
# 检查 Docker 是否安装
|
|
if ! command -v docker &> /dev/null; then
|
|
log_error "Docker 未安装或不在 PATH 中"
|
|
echo ""
|
|
echo "请先安装 Docker:"
|
|
echo " Ubuntu/Debian: curl -fsSL https://get.docker.com | sh"
|
|
echo " CentOS/RHEL: yum install -y docker-ce"
|
|
echo " Arch Linux: pacman -S docker"
|
|
exit 1
|
|
fi
|
|
|
|
# 检查 Docker 服务是否运行
|
|
if ! docker info &> /dev/null; then
|
|
log_error "Docker 服务未运行"
|
|
echo ""
|
|
echo "请启动 Docker 服务:"
|
|
echo " sudo systemctl start docker"
|
|
exit 1
|
|
fi
|
|
|
|
# 检查 Docker Compose
|
|
if docker compose version &> /dev/null; then
|
|
COMPOSE_CMD="docker compose"
|
|
log_info "Docker Compose v2 可用"
|
|
elif command -v docker-compose &> /dev/null; then
|
|
COMPOSE_CMD="docker-compose"
|
|
log_info "Docker Compose v1 可用"
|
|
else
|
|
log_warn "Docker Compose 未安装,部分功能可能不可用"
|
|
COMPOSE_CMD=""
|
|
fi
|
|
|
|
log_info "Docker 环境检查通过"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 检查前置要求
|
|
# -----------------------------------------------------------------------------
|
|
check_requirements() {
|
|
log_step "检查前置要求..."
|
|
|
|
# 检查构建上下文
|
|
if [ ! -f "Dockerfile" ]; then
|
|
log_error "Dockerfile 不存在,请确保在项目根目录运行此脚本"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "package.json" ]; then
|
|
log_error "package.json 不存在,请确保在项目根目录运行此脚本"
|
|
exit 1
|
|
fi
|
|
|
|
# 创建必要目录
|
|
mkdir -p "$DATA_DIR"
|
|
|
|
log_info "前置要求检查通过"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 拉取最新代码(可选)
|
|
# -----------------------------------------------------------------------------
|
|
pull_latest() {
|
|
log_step "检查更新..."
|
|
|
|
if [ -d ".git" ]; then
|
|
git fetch origin main
|
|
LOCAL=$(git rev-parse @)
|
|
REMOTE=$(git rev-parse origin/main)
|
|
|
|
if [ "$LOCAL" != "$REMOTE" ]; then
|
|
log_warn "本地版本落后于远程,是否更新?"
|
|
read -p "输入 y 更新,其他跳过: " -n 1 -r
|
|
echo
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
log_info "更新代码..."
|
|
git pull origin main
|
|
fi
|
|
else
|
|
log_info "已是最新版本"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 构建镜像
|
|
# -----------------------------------------------------------------------------
|
|
build_image() {
|
|
log_step "构建 Docker 镜像..."
|
|
|
|
# 启用 BuildKit
|
|
export DOCKER_BUILDKIT=1
|
|
|
|
# 构建镜像
|
|
log_info "构建镜像: ${IMAGE_NAME}:${IMAGE_TAG}"
|
|
|
|
if docker build \
|
|
--tag "${IMAGE_NAME}:${IMAGE_TAG}" \
|
|
--tag "${IMAGE_NAME}:latest" \
|
|
--build-arg NPM_REGISTRY=https://registry.npmmirror.com \
|
|
--progress=plain \
|
|
.; then
|
|
log_info "镜像构建成功"
|
|
else
|
|
log_error "镜像构建失败"
|
|
exit 1
|
|
fi
|
|
|
|
# 显示镜像大小
|
|
IMAGE_SIZE=$(docker images "${IMAGE_NAME}:${IMAGE_TAG}" --format "{{.Size}}")
|
|
log_info "镜像大小: $IMAGE_SIZE"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 启动容器
|
|
# -----------------------------------------------------------------------------
|
|
start_container() {
|
|
log_step "启动容器..."
|
|
|
|
# 检查容器是否已存在
|
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
log_warn "容器已在运行中"
|
|
return 0
|
|
else
|
|
log_info "容器已存在,重新启动..."
|
|
docker rm -f "$CONTAINER_NAME" > /dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
# 检查配置目录
|
|
if [ ! -d "$CONFIG_DIR" ]; then
|
|
log_warn "OpenClaw 配置目录不存在: $CONFIG_DIR"
|
|
log_info "将创建目录..."
|
|
mkdir -p "$CONFIG_DIR"
|
|
fi
|
|
|
|
# 启动容器(使用 host 网络模式)
|
|
log_info "启动容器 (host 网络模式)..."
|
|
|
|
docker run -d \
|
|
--name "$CONTAINER_NAME" \
|
|
--hostname "$CONTAINER_NAME" \
|
|
--network host \
|
|
--restart unless-stopped \
|
|
--volume "$CONFIG_DIR:/root/.openclaw" \
|
|
--volume "$DATA_DIR:/app/data" \
|
|
--env "NODE_ENV=production" \
|
|
--env "OPENCLAW_URL=http://127.0.0.1:18789" \
|
|
--env "TZ=Asia/Shanghai" \
|
|
--health-cmd "curl -f http://localhost:1420/ || exit 1" \
|
|
--health-interval "30s" \
|
|
--health-timeout "5s" \
|
|
--health-retries "3" \
|
|
--log-driver "json-file" \
|
|
--log-opt "max-size=10m" \
|
|
--log-opt "max-file=3" \
|
|
"${IMAGE_NAME}:${IMAGE_TAG}"
|
|
|
|
if [ $? -eq 0 ]; then
|
|
log_info "容器启动成功"
|
|
else
|
|
log_error "容器启动失败"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 停止容器
|
|
# -----------------------------------------------------------------------------
|
|
stop_container() {
|
|
log_step "停止容器..."
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
docker stop "$CONTAINER_NAME"
|
|
log_info "容器已停止"
|
|
else
|
|
log_warn "容器未运行"
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 重启容器
|
|
# -----------------------------------------------------------------------------
|
|
restart_container() {
|
|
log_step "重启容器..."
|
|
stop_container
|
|
sleep 2
|
|
start_container
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 删除容器
|
|
# -----------------------------------------------------------------------------
|
|
remove_container() {
|
|
log_step "删除容器..."
|
|
|
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
docker rm -f "$CONTAINER_NAME" > /dev/null 2>&1
|
|
log_info "容器已删除"
|
|
else
|
|
log_warn "容器不存在"
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 查看状态
|
|
# -----------------------------------------------------------------------------
|
|
show_status() {
|
|
log_step "容器状态:"
|
|
|
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
echo ""
|
|
docker ps -a --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
|
|
# 显示资源使用
|
|
echo ""
|
|
log_info "资源使用:"
|
|
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" "$CONTAINER_NAME" 2>/dev/null || true
|
|
else
|
|
log_warn "容器不存在"
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 查看日志
|
|
# -----------------------------------------------------------------------------
|
|
show_logs() {
|
|
log_step "容器日志 (Ctrl+C 退出):"
|
|
echo ""
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
docker logs -f "$CONTAINER_NAME"
|
|
else
|
|
log_error "容器未运行,无法查看日志"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 进入容器
|
|
# -----------------------------------------------------------------------------
|
|
enter_container() {
|
|
log_step "进入容器 shell..."
|
|
echo ""
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
docker exec -it "$CONTAINER_NAME" /bin/sh
|
|
else
|
|
log_error "容器未运行,无法进入"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 常见问题排查
|
|
# -----------------------------------------------------------------------------
|
|
troubleshoot() {
|
|
echo ""
|
|
separator
|
|
log_info "常见问题排查"
|
|
separator
|
|
echo ""
|
|
|
|
log_info "1. 检查容器是否运行:"
|
|
echo " docker ps | grep $CONTAINER_NAME"
|
|
echo ""
|
|
|
|
log_info "2. 查看容器日志:"
|
|
echo " docker logs $CONTAINER_NAME"
|
|
echo ""
|
|
|
|
log_info "3. 检查端口占用:"
|
|
echo " netstat -tlnp | grep 1420"
|
|
echo " ss -tlnp | grep 1420"
|
|
echo ""
|
|
|
|
log_info "4. 检查 OpenClaw 配置:"
|
|
echo " cat ~/.openclaw/openclaw.json"
|
|
echo ""
|
|
|
|
log_info "5. 测试 Gateway 连接:"
|
|
echo " curl http://localhost:18789/health"
|
|
echo ""
|
|
|
|
log_info "6. 重建容器:"
|
|
echo " ./docker-deploy.sh rebuild"
|
|
echo ""
|
|
|
|
log_info "7. 完全重置:"
|
|
echo " docker stop $CONTAINER_NAME && docker rm $CONTAINER_NAME"
|
|
echo " docker rmi ${IMAGE_NAME}:${IMAGE_TAG}"
|
|
echo " ./docker-deploy.sh start"
|
|
echo ""
|
|
|
|
log_info "8. 查看 OpenClaw 日志:"
|
|
echo " docker exec $CONTAINER_NAME cat /home/appuser/.openclaw/logs/gateway.log"
|
|
echo ""
|
|
|
|
separator
|
|
echo ""
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 获取本机 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"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 显示访问信息
|
|
# -----------------------------------------------------------------------------
|
|
show_access_info() {
|
|
local ip=$(get_local_ip)
|
|
|
|
echo ""
|
|
separator
|
|
log_info "部署完成!"
|
|
separator
|
|
echo ""
|
|
echo -e " ${CYAN}🌐 访问地址:${NC}"
|
|
echo " http://${ip}:1420"
|
|
echo ""
|
|
echo -e " ${CYAN}📁 配置目录:${NC}"
|
|
echo " $CONFIG_DIR"
|
|
echo ""
|
|
echo -e " ${CYAN}📋 容器名称:${NC}"
|
|
echo " $CONTAINER_NAME"
|
|
echo ""
|
|
echo " 常用命令:"
|
|
echo " ./docker-deploy.sh logs # 查看日志"
|
|
echo " ./docker-deploy.sh status # 查看状态"
|
|
echo " ./docker-deploy.sh stop # 停止"
|
|
echo " ./docker-deploy.sh start # 启动"
|
|
echo " ./docker-deploy.sh restart # 重启"
|
|
echo " ./docker-deploy.sh rebuild # 重建"
|
|
echo " ./docker-deploy.sh shell # 进入容器"
|
|
echo " ./docker-deploy.sh help # 帮助"
|
|
echo ""
|
|
separator
|
|
echo ""
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 使用 Docker Compose 方式
|
|
# -----------------------------------------------------------------------------
|
|
compose_up() {
|
|
if [ -z "$COMPOSE_CMD" ]; then
|
|
log_error "Docker Compose 不可用,请使用单机模式"
|
|
exit 1
|
|
fi
|
|
|
|
log_step "使用 Docker Compose 启动..."
|
|
|
|
if [ ! -f "docker-compose.yml" ]; then
|
|
log_error "docker-compose.yml 不存在"
|
|
exit 1
|
|
fi
|
|
|
|
$COMPOSE_CMD up -d
|
|
log_info "服务已启动"
|
|
}
|
|
|
|
compose_down() {
|
|
if [ -z "$COMPOSE_CMD" ]; then
|
|
log_error "Docker Compose 不可用"
|
|
exit 1
|
|
fi
|
|
|
|
log_step "停止 Docker Compose 服务..."
|
|
$COMPOSE_CMD down
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 显示帮助
|
|
# -----------------------------------------------------------------------------
|
|
show_help() {
|
|
echo ""
|
|
echo "ClawPanel Docker 部署脚本"
|
|
echo ""
|
|
echo "用法: $0 [命令]"
|
|
echo ""
|
|
echo "命令:"
|
|
echo " start 启动容器"
|
|
echo " stop 停止容器"
|
|
echo " restart 重启容器"
|
|
echo " rebuild 重建容器(删除并重新创建)"
|
|
echo " remove 删除容器(保留镜像)"
|
|
echo " status 查看容器状态"
|
|
echo " logs 查看容器日志"
|
|
echo " shell 进入容器 shell"
|
|
echo " troubleshoot 常见问题排查"
|
|
echo " compose 使用 Docker Compose 方式启动"
|
|
echo " help 显示帮助"
|
|
echo ""
|
|
echo "示例:"
|
|
echo " $0 start # 启动容器"
|
|
echo " $0 logs -f # 实时查看日志"
|
|
echo " $0 rebuild # 重建容器"
|
|
echo ""
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 主流程
|
|
# -----------------------------------------------------------------------------
|
|
main() {
|
|
case "${1:-help}" in
|
|
check)
|
|
check_docker
|
|
;;
|
|
build)
|
|
check_docker
|
|
check_requirements
|
|
pull_latest
|
|
build_image
|
|
;;
|
|
start)
|
|
check_docker
|
|
check_requirements
|
|
start_container
|
|
show_access_info
|
|
;;
|
|
stop)
|
|
stop_container
|
|
;;
|
|
restart)
|
|
restart_container
|
|
;;
|
|
rebuild)
|
|
check_docker
|
|
remove_container
|
|
build_image
|
|
start_container
|
|
show_access_info
|
|
;;
|
|
remove)
|
|
remove_container
|
|
;;
|
|
status)
|
|
check_docker
|
|
show_status
|
|
;;
|
|
logs)
|
|
show_logs
|
|
;;
|
|
shell)
|
|
enter_container
|
|
;;
|
|
troubleshoot)
|
|
troubleshoot
|
|
;;
|
|
compose)
|
|
check_docker
|
|
compose_up
|
|
show_access_info
|
|
;;
|
|
compose-down)
|
|
compose_down
|
|
;;
|
|
help|--help|-h)
|
|
show_help
|
|
;;
|
|
*)
|
|
log_error "未知命令: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|