Skill: lerwee/docker-lwops-deployer (version 1.0.0)

--- name: docker-lwops-deployer description: | 本地 Docker 容器自动化部署技能，支持乐维监控 8.1 环境的一键部署。 适用于：本地开发环境搭建、测试环境快速部署、容器化服务管理。 优势：自动检测系统架构、智能端口管理、Docker 自动安装、cgroup 兼容性处理。 user-invocable: true metadata: openclaw: emoji: "🐳" requires: bins: - bash env: [] config: [] homepage: https://github.com/lwops/docker-lwops-deployer examples: - description: 部署默认容器 input: 部署一个乐维监控 8.1 容器 - description: 检查 Docker 环境 input: 检查系统是否安装了 Docker - description: 重新部署容器 input: 重新部署 lwops_rocky8_image_8.1 容器 - description: 查看容器状态 input: 查看乐维监控容器的运行状态 - description: ARM 架构部署 input: 在 ARM 服务器上部署乐维监控容器 - description: 完整部署流程 input: 帮我部署一个完整的乐维监控 8.1 环境 - description: 快速部署 input: 启动乐维监控 Docker 容器 - description: 智能端口分配 input: 部署乐维监控容器，自动分配可用端口 - description: 容器信息查询 input: 查询容器的访问地址和端口映射 - description: 自动安装 Docker input: 安装 Docker 并部署乐维监控容器 --- # Docker LwOps 部署技能 自动化部署乐维监控 8.1 Docker 容器的本地技能，支持一键安装和智能配置。 ## 功能特性 - 🔧 **Docker 自动安装**：自动检测系统类型并安装 Docker - 🏗️ **架构智能检测**：支持 x86_64 和 aarch64 架构 - 🔌 **智能端口分配**：自动检测端口冲突并分配可用端口 - ⚙️ **cgroup 兼容**：自动处理 cgroup v1/v2 兼容性问题 - 🚀 **一键部署**：快速启动乐维监控 8.1 容器环境 - 📊 **详细反馈**：输出容器访问地址和端口映射信息 ## 使用方式 当用户请求部署乐维监控容器、检查 Docker 环境、或管理相关容器时，此技能会被自动调用。 ## 使用场景 ### 何时使用此技能 当你的查询包含以下特征时，应该使用此技能： 1. **乐维监控相关部署**：明确提到"乐维监控"、"乐维8.1"、"lwops"等关键词 2. **容器部署操作**：请求部署、启动、创建 Docker 容器 3. **环境管理**：检查 Docker 安装状态、容器运行状态 4. **容器操作**：重新部署、查看状态、查询访问地址 ### 常见使用场景 #### 场景 1：首次部署 - 示例：`部署一个乐维监控 8.1 容器` - 说明：自动安装 Docker（如果需要），拉取镜像并启动容器 #### 场景 2：重新部署 - 示例：`重新部署 lwops_rocky8_image_8.1 容器` - 说明：删除现有容器并重新创建 #### 场景 3：智能端口分配 - 示例：`部署乐维监控容器，自动分配可用端口` - 说明：检测端口冲突并自动使用可用端口 #### 场景 4：查询容器信息 - 示例：`查询容器的访问地址和端口映射` - 说明：获取容器状态和访问 URL ### 与其他工具的区别 | 特性 | 本技能 | 手动部署 | |------|--------|----------| | Docker 安装 | 自动检测并安装 | 需要手动操作 | | 架构适配 | 自动选择镜像 | 需要手动指定 | | 端口管理 | 自动检测冲突 | 需要手动检查 | | cgroup 处理 | 自动兼容 | 需要手动配置 | ### 最佳实践 1. **权限要求**：确保用户有 sudo 权限（安装 Docker 需要） 2. **网络环境**：确保能访问华为云镜像仓库（swr.cn-south-1.myhuaweicloud.com） 3. **端口规划**：默认使用 8000 和 8081 端口，如有冲突会自动调整 4. **容器管理**：容器名称固定为 `lwops_rocky8_image_8.1`，重复部署会自动替换 ## 配置 ### 无需配置 此技能不需要任何环境变量或配置参数，开箱即用。 ### 系统要求 - **操作系统**：Ubuntu、Debian、CentOS、RHEL、Fedora、Arch Linux - **架构支持**：x86_64（amd64）、aarch64（arm64） - **权限**：sudo 权限（用于安装 Docker 和管理容器） - **网络**：能访问华为云 SWR 镜像仓库 ## 技术实现 ### 输入格式 此技能接收自然语言输入，无需特定的 JSON 格式。例如： - "部署乐维监控 8.1 容器" - "重新部署容器" - "查看容器状态" ### 输出格式 成功时： ```json { "success": true, "data": { "container_name": "lwops_rocky8_image_8.1", "container_id": "a1b2c3d4e5f6", "status": "running", "architecture": "x86_64", "image": "swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_x86_image:latest", "host_ip": "192.168.1.100", "ports": { "http": { "container_port": 80, "host_port": 8000, "url": "http://192.168.1.100:8000" }, "https": { "container_port": 8081, "host_port": 8081, "url": "http://192.168.1.100:8081" } }, "cgroup_version": "v1", "cgroup_mount_mode": "ro", "timestamp": "2026-03-24T10:30:00Z" }, "message": "容器部署成功" } ``` 失败时： ```json { "success": false, "error": "DockerNotInstalled", "message": "Docker 未安装，正在尝试自动安装...", "suggestions": [ "Ubuntu/Debian: sudo apt-get install docker.io", "CentOS/RHEL: sudo yum install docker", "访问 https://docs.docker.com/get-docker/ 获取详细安装指南" ], "timestamp": "2026-03-24T10:30:00Z" } ``` ## 容器信息 ### 镜像地址 - **x86_64 架构**：`swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_x86_image:latest` - **aarch64 架构**：`swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_arm_image:latest` ### 端口映射 - **容器端口 80** → **宿主机端口 8000**（默认，可自动调整） - **容器端口 8081** → **宿主机端口 8081**（默认，可自动调整） ### 容器配置 - **容器名称**：`lwops_rocky8_image_8.1` - **启动参数**：`--privileged`（特权模式） - **cgroup 挂载**：`/sys/fs/cgroup:/sys/fs/cgroup:ro`（v1）或 `:rw`（v2） - **启动命令**：`/usr/sbin/init` ## 故障排除 ### 问题 1：Docker 安装失败 **症状**：提示无法安装 Docker **解决方案**： 1. 检查是否有 sudo 权限 2. 检查系统是否支持（参考系统要求） 3. 手动安装 Docker：访问 https://docs.docker.com/get-docker/ ### 问题 2：端口被占用 **症状**：无法启动容器，端口冲突 **解决方案**： 1. 技能会自动分配可用端口 2. 查看输出中的 `ports` 字段获取实际端口 3. 停止占用端口的程序：`sudo lsof -i :8000` ### 问题 3：容器启动失败 **症状**：容器创建后立即退出 **解决方案**： 1. 检查 Docker 日志：`docker logs lwops_rocky8_image_8.1` 2. 检查 cgroup 版本兼容性 3. 确保使用 `--privileged` 参数 ### 问题 4：无法访问容器服务 **症状**：容器运行但无法访问 Web 界面 **解决方案**： 1. 检查防火墙设置 2. 确认端口映射：`docker port lwops_rocky8_image_8.1` 3. 检查容器状态：`docker ps` ## 相关链接 - **乐维监控官网**：https://www.lwops.cn - **Docker 官方文档**：https://docs.docker.com/ - **问题反馈**：https://github.com/lwops/docker-lwops-deployer/issues

path: INSTALL.md
content: # 安装指南

本文档详细介绍如何安装和配置 Docker LwOps 部署技能。

## 📋 目录

- [前置要求](#前置要求)
- [安装步骤](#安装步骤)
- [验证安装](#验证安装)
- [配置选项](#配置选项)
- [卸载技能](#卸载技能)
- [常见问题](#常见问题)

## 前置要求

### 系统要求

#### 支持的操作系统

**基于 Debian/Ubuntu：**
- ✅ Ubuntu 18.04 LTS 及更高版本
- ✅ Debian 10 Buster 及更高版本
- ✅ Deepin (深度操作系统)
- ✅ Linux Mint
- ✅ elementaryOS

**基于 RHEL/CentOS：**
- ✅ CentOS 7/8
- ✅ RHEL 7/8
- ✅ Rocky Linux 8+
- ✅ AlmaLinux 8+
- ✅ Anolis OS (龙蜥)
- ✅ openEuler (欧拉)
- ✅ Kylin (麒麟)
- ✅ Oracle Linux
- ✅ Scientific Linux
- ✅ VzLinux
- ✅ TencentOS

**其他：**
- ✅ Fedora 30+
- ✅ Arch Linux 及衍生发行版（Manjaro、EndeavourOS 等）

#### 系统架构

- ✅ x86_64 (amd64)
- ✅ aarch64 (arm64)

#### 软件要求

- **Bash**: >= 4.0
- **sudo**: 权限管理（用于安装 Docker）
- **网络**: 能够访问华为云 SWR 镜像仓库

### 权限要求

此技能需要 sudo 权限来：
- 安装 Docker（如果未安装）
- 管理 Docker 容器
- 绑定特权端口（< 1024）

## 安装步骤

### 方法 1：通过 OpenClaw UI 安装（推荐）

1. **打开 OpenClaw 设置**

   在 OpenClaw 中进入设置 → 技能管理

2. **添加本地技能**

   点击"添加技能" → "从本地添加"

3. **选择技能目录**

   浏览到 `docker-lwops-deployer` 目录并选择

4. **启用技能**

   勾选"启用此技能"并保存

### 方法 2：手动安装

1. **找到 OpenClaw 技能目录**

```bash
# 通常位于以下位置之一
~/.openclaw/skills/
/usr/local/share/openclaw/skills/
```

2. **复制技能目录**

```bash
# 如果从 Git 仓库克隆
git clone <repository-url> docker-lwops-deployer
cp -r docker-lwops-deployer ~/.openclaw/skills/

# 或手动复制
cp -r /path/to/docker-lwops-deployer ~/.openclaw/skills/
```

3. **设置执行权限**

```bash
cd ~/.openclaw/skills/docker-lwops-deployer
chmod +x wrapper.sh execute.sh
chmod +x lib/*.sh
```

4. **配置技能（可选）**

编辑 `~/.openclaw/openclaw.json`：

```json
{
  "skills": {
    "entries": {
      "docker-lwops-deployer": {
        "enabled": true,
        "priority": 100
      }
    }
  }
}
```

### 方法 3：从源码安装

1. **克隆仓库**

```bash
git clone https://github.com/lwops/docker-lwops-deployer.git
cd docker-lwops-deployer
```

2. **检查文件结构**

```bash
ls -la
# 应该看到：
# SKILL.md
# wrapper.sh
# execute.sh
# lib/
# README.md
# INSTALL.md
```

3. **设置执行权限**

```bash
chmod +x wrapper.sh execute.sh
chmod +x lib/*.sh
```

4. **复制到技能目录**

```bash
cp -r . ~/.openclaw/skills/docker-lwops-deployer/
```

## 验证安装

### 1. 检查文件权限

```bash
cd ~/.openclaw/skills/docker-lwops-deployer
ls -l

# 确保以下文件有执行权限：
# -rwxr-xr-x wrapper.sh
# -rwxr-xr-x execute.sh
# -rwxr-xr-x lib/system-utils.sh
# -rwxr-xr-x lib/network-utils.sh
# -rwxr-xr-x lib/docker-utils.sh
# -rwxr-xr-x lib/output-utils.sh
```

### 2. 测试技能

```bash
# 直接测试
./wrapper.sh "{}"
```

预期输出：
```json
{
  "success": false,
  "error": "...",
  "message": "...",
  ...
}
```

（这是正常的，因为没有实际的部署请求）

### 3. 在 OpenClaw 中测试

启动 OpenClaw 并输入：

```
你: 检查系统是否安装了 Docker
```

如果技能正确安装，它会：
1. 检查 Docker 安装状态
2. 如果未安装，自动安装
3. 输出 JSON 格式的结果

## 配置选项

### 环境变量

此技能不需要任何环境变量配置。

### 可选配置

#### 1. 修改默认端口

编辑 `execute.sh`，修改以下常量：

```bash
DEFAULT_HOST_PORT1=8000  # HTTP 端口
DEFAULT_HOST_PORT2=8081  # HTTPS 端口
```

#### 2. 修改容器名称

编辑 `execute.sh`，修改容器名称常量：

```bash
CONTAINER_NAME="lwops_rocky8_image_8.1"
```

#### 3. 修改镜像地址

编辑 `lib/system-utils.sh`，修改 `get_image_name` 函数：

```bash
get_image_name() {
    local arch="$1"
    case "$arch" in
        x86_64)
            echo "your-registry/lwops_rocky8_x86_image:latest"
            ;;
        aarch64)
            echo "your-registry/lwops_rocky8_arm_image:latest"
            ;;
    esac
}
```

## 卸载技能

### 1. 停止并删除容器

```bash
# 停止容器
sudo docker stop lwops_rocky8_image_8.1

# 删除容器
sudo docker rm -f lwops_rocky8_image_8.1

# 可选：删除镜像
sudo docker rmi swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_x86_image:latest
```

### 2. 从 OpenClaw 卸载

**通过 UI**：
1. 打开 OpenClaw 设置
2. 进入技能管理
3. 找到 "docker-lwops-deployer"
4. 点击"禁用"然后"删除"

**手动卸载**：
```bash
rm -rf ~/.openclaw/skills/docker-lwops-deployer
```

### 3. 清理配置（可选）

编辑 `~/.openclaw/openclaw.json`，删除相关配置：

```json
{
  "skills": {
    "entries": {
      // 删除 "docker-lwops-deployer" 条目
    }
  }
}
```

## 常见问题

### Q1: 技能安装后无法调用

**A**: 检查以下几点：

1. 确认技能已启用
2. 检查文件权限是否正确
3. 查看 OpenClaw 日志是否有错误
4. 重启 OpenClaw

### Q2: 提示 "Bash version too old"

**A**: 升级 Bash 到 4.0 或更高版本：

```bash
# Ubuntu/Debian
sudo apt-get install bash

# CentOS/RHEL
sudo yum install bash

# Arch Linux
sudo pacman -S bash
```

### Q3: sudo 权限问题

**A**: 有几种解决方案：

1. **配置 sudo 无密码**（推荐用于自动化）：
```bash
echo "$USER ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$USER
sudo chmod 440 /etc/sudoers.d/$USER
```

2. **将用户添加到 docker 组**（避免使用 sudo）：
```bash
sudo usermod -aG docker $USER
newgrp docker
```

3. **手动输入密码**：在运行时输入 sudo 密码

### Q4: 无法访问华为云镜像仓库

**A**: 检查网络连接：

```bash
# 测试 DNS 解析
nslookup swr.cn-south-1.myhuaweicloud.com

# 测试网络连接
ping swr.cn-south-1.myhuaweicloud.com

# 测试 HTTPS 连接
curl -I https://swr.cn-south-1.myhuaweicloud.com
```

如果无法访问，可能需要：
- 检查防火墙设置
- 配置代理
- 使用 VPN

### Q5: 容器启动后无法访问

**A**: 检查以下几点：

1. **确认容器运行**：
```bash
sudo docker ps | grep lwops_rocky8_image_8.1
```

2. **检查端口映射**：
```bash
sudo docker port lwops_rocky8_image_8.1
```

3. **检查防火墙**：
```bash
# Ubuntu/Debian
sudo ufw status

# CentOS/RHEL
sudo firewall-cmd --list-ports
```

4. **检查容器日志**：
```bash
sudo docker logs lwops_rocky8_image_8.1
```

### Q6: 提示"另一个部署任务正在进行中"

**A**: 这是正常的并发控制保护机制。

**解决方案**:

1. **等待当前部署完成**
   - 技能会显示预计剩余时间
   - 通常部署需要 2-5 分钟

2. **查看部署进度**：
```bash
sudo docker logs lwops_rocky8_image_8.1
```

3. **如果确认没有其他部署**，手动删除锁：
```bash
rm -f /tmp/docker-lwops-deployer/deploy.lock
```

4. **检查是否有僵尸进程**：
```bash
# 查看锁文件内容
cat /tmp/docker-lwops-deployer/deploy.lock

# 检查进程是否存在
ps -fp <PID>
```

**注意**: 多个用户同时调用技能时，只有一个会执行部署，其他用户会收到提示。这是正常的保护机制，防止部署冲突。

### Q7: cgroup v2 兼容性问题

**A**: 技能会自动检测并处理 cgroup 版本：

- **cgroup v1**: 使用 `:ro` 挂载模式
- **cgroup v2**: 使用 `:rw` 挂载模式并提示用户

如果仍有问题，可以：
1. 检查内核启动参数
2. 查看 Docker 日志
3. 尝试重启 Docker 服务

### Q8: 技能输出格式不正确

**A**: 确保以下几点：

1. 检查是否使用了最新版本的技能
2. 查看 STDERR 输出（错误信息输出到 STDERR）
3. 验证 JSON 格式：
```bash
./wrapper.sh "{}" | jq .
```

### Q9: 磁盘空间不足

**A**: 清理 Docker 资源：

```bash
# 清理未使用的镜像
sudo docker image prune -a

# 清理未使用的容器
sudo docker container prune

# 清理未使用的卷
sudo docker volume prune

# 清理所有未使用的资源
sudo docker system prune -a
```

## 技术支持

如果遇到其他问题：

1. 查看 [README.md](README.md) 中的故障排除部分
2. 查看 [容器日志](#q5-容器启动后无法访问)
3. 提交 Issue：https://github.com/lwops/docker-lwops-deployer/issues
4. 联系技术支持：[REDACTED_SECRET]

## 更新日志

### v1.0.0 (2026-03-24)

- ✨ 初始版本发布
- 🎯 支持 x86_64 和 aarch64 架构
- 🔧 自动安装 Docker
- 🔌 智能端口分配
- ⚙️ cgroup 兼容性处理


path: README.md
content: # Docker LwOps 部署技能

自动化部署乐维监控 8.1 Docker 容器的 OpenClaw 技能。

## 📋 项目概述

这是一个符合 OpenClaw 规范的本地技能，用于一键部署乐维监控 8.1 环境。该技能能够：

- 🔧 自动检测并安装 Docker（如果未安装）
- 🏗️ 根据系统架构（x86_64/aarch64）选择对应的镜像
- 🔌 智能检测端口冲突并自动分配可用端口
- ⚙️ 自动处理 cgroup v1/v2 兼容性问题
- 🔒 智能锁定机制，防止多用户同时部署冲突
- 🚀 快速启动容器并提供访问地址

## 🎯 适用场景

- 本地开发环境搭建
- 测试环境快速部署
- 容器化服务管理
- CI/CD 流水线集成

## 📦 系统要求

### 支持的操作系统

**基于 Debian/Ubuntu：**
- ✅ Ubuntu 18.04 LTS 及更高版本
- ✅ Debian 10 Buster 及更高版本
- ✅ Deepin (深度操作系统)
- ✅ Linux Mint
- ✅ elementaryOS

**基于 RHEL/CentOS：**
- ✅ CentOS 7/8
- ✅ RHEL 7/8
- ✅ Rocky Linux 8+
- ✅ AlmaLinux 8+
- ✅ Anolis OS (龙蜥)
- ✅ openEuler (欧拉)
- ✅ Kylin (麒麟)
- ✅ Oracle Linux
- ✅ Scientific Linux
- ✅ VzLinux
- ✅ TencentOS

**其他：**
- ✅ Fedora 30+
- ✅ Arch Linux 及衍生发行版（Manjaro、EndeavourOS 等）

### 系统架构

- ✅ x86_64 (amd64)
- ✅ aarch64 (arm64)

### 其他要求

- Bash >= 4.0
- sudo 权限（用于安装 Docker 和管理容器）
- 网络连接（访问华为云 SWR 镜像仓库）

## 🚀 快速开始

### 安装技能

1. **克隆或下载技能目录**

```bash
cd ~/.openclaw/skills/
git clone <repository-url> docker-lwops-deployer
```

或手动复制技能目录到 OpenClaw 技能目录。

2. **设置执行权限**

```bash
cd ~/.openclaw/skills/docker-lwops-deployer
chmod +x wrapper.sh execute.sh
chmod +x lib/*.sh
```

3. **验证安装**

```bash
./wrapper.sh "{}"
```

### 使用技能

在 OpenClaw 中，你可以直接使用自然语言调用此技能：

```
你: 部署一个乐维监控 8.1 容器
```

或

```
你: 重新部署 lwops_rocky8_image_8.1 容器
```

## 📖 使用示例

### 示例 1：首次部署

```
你: 部署一个完整的乐维监控 8.1 环境
```

**技能会自动：**
1. 检查 Docker 是否安装，未安装则自动安装
2. 检测系统架构
3. 拉取对应的镜像
4. 检查端口并分配可用端口
5. 启动容器
6. 输出访问地址

### 示例 2：重新部署

```
你: 重新部署乐维监控容器
```

**技能会：**
1. 删除现有容器
2. 重新创建并启动容器
3. 输出新的访问信息

### 示例 3：查询容器信息

```
你: 查询容器的访问地址和端口映射
```

**技能会输出：**
- 容器 ID
- 运行状态
- 端口映射
- 访问 URL

### 示例 4：检查环境

```
你: 检查系统是否安装了 Docker
```

**技能会：**
- 检查 Docker 安装状态
- 未安装则自动安装
- 确保 Docker 服务运行

### 示例 5：智能端口分配

```
你: 部署乐维监控容器，自动分配可用端口
```

**技能会：**
- 检查默认端口（8000、8081）是否被占用
- 如被占用，自动查找并使用可用端口
- 输出实际使用的端口

## 🔧 技术架构

### 技术栈

- **语言**: Bash (Shell Script)
- **依赖**: 无外部依赖（纯 Bash 实现）
- **容器**: Docker

### 目录结构

```
docker-lwops-deployer/
├── SKILL.md                  # 技能定义文件
├── wrapper.sh                # Shell 包装器
├── execute.sh                # 核心执行脚本
├── lib/                      # 函数库
│   ├── system-utils.sh       # 系统检测函数
│   ├── network-utils.sh      # 网络和端口检测函数
│   ├── docker-utils.sh       # Docker 操作函数
│   └── output-utils.sh       # 输出格式化函数
├── README.md                 # 项目说明（本文件）
└── INSTALL.md                # 安装指南
```

### 执行流程

```
用户输入
    ↓
wrapper.sh（包装器）
    ↓
execute.sh（核心逻辑）
    ↓
├─ 检查系统兼容性
├─ 确保 Docker 安装并运行
├─ 检测系统架构
├─ 拉取镜像
├─ 智能分配端口
├─ 检测 cgroup 版本
├─ 启动容器
└─ 输出结果
```

## 📊 输出格式

### 成功输出

```json
{
  "success": true,
  "data": {
    "container_name": "lwops_rocky8_image_8.1",
    "container_id": "a1b2c3d4e5f6",
    "status": "running",
    "architecture": "x86_64",
    "image": "swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_x86_image:latest",
    "host_ip": "192.168.1.100",
    "ports": {
      "http": {
        "container_port": 80,
        "host_port": 8000,
        "url": "http://192.168.1.100:8000"
      },
      "https": {
        "container_port": 8081,
        "host_port": 8081,
        "url": "http://192.168.1.100:8081"
      }
    },
    "cgroup_version": "v1",
    "cgroup_mount_mode": "ro",
    "timestamp": "2026-03-24T10:30:00Z"
  },
  "message": "容器部署成功",
  "timestamp": "2026-03-24T10:30:00Z"
}
```

### 失败输出

```json
{
  "success": false,
  "error": "DockerNotInstalled",
  "message": "Docker 未安装，正在尝试自动安装...",
  "suggestions": [
    "Ubuntu/Debian: sudo apt-get install docker.io",
    "CentOS/RHEL: sudo yum install docker",
    "访问 https://docs.docker.com/get-docker/ 获取详细安装指南"
  ],
  "timestamp": "2026-03-24T10:30:00Z"
}
```

## 🐳 容器信息

### 镜像地址

- **x86_64 架构**: `swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_x86_image:latest`
- **aarch64 架构**: `swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_arm_image:latest`

### 端口映射

- **容器端口 80** → **宿主机端口 8000**（默认，可自动调整）
- **容器端口 8081** → **宿主机端口 8081**（默认，可自动调整）

### 容器配置

- **容器名称**: `lwops_rocky8_image_8.1`
- **启动参数**: `--privileged`（特权模式）
- **cgroup 挂载**: `/sys/fs/cgroup:/sys/fs/cgroup:ro`（v1）或 `:rw`（v2）
- **启动命令**: `/usr/sbin/init`

## 🔍 故障排除

### 问题 1：Docker 安装失败

**症状**: 提示无法安装 Docker

**解决方案**:
1. 检查是否有 sudo 权限
2. 检查系统是否支持（参考系统要求）
3. 手动安装 Docker：访问 https://docs.docker.com/get-docker/

### 问题 2：端口被占用

**症状**: 无法启动容器，端口冲突

**解决方案**:
1. 技能会自动分配可用端口
2. 查看输出中的 `ports` 字段获取实际端口
3. 停止占用端口的程序：`sudo lsof -i :8000`

### 问题 3：容器启动失败

**症状**: 容器创建后立即退出

**解决方案**:
1. 检查 Docker 日志：`sudo docker logs lwops_rocky8_image_8.1`
2. 检查 cgroup 版本兼容性
3. 确保使用 `--privileged` 参数

### 问题 4：无法访问容器服务

**症状**: 容器运行但无法访问 Web 界面

**解决方案**:
1. 检查防火墙设置
2. 确认端口映射：`docker port lwops_rocky8_image_8.1`
3. 检查容器状态：`docker ps`

### 问题 5：权限不足

**症状**: 提示需要 sudo 权限

**解决方案**:
1. 使用 sudo 运行命令
2. 将用户添加到 sudo 组：`sudo usermod -aG sudo $USER`
3. 配置 sudo 无密码：`echo "$USER ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$USER`

## 📚 相关文档

- [安装指南](INSTALL.md)
- [OpenClaw 技能开发指南](https://github.com/openclaw/skills)
- [乐维监控官方文档](https://www.lwops.cn)
- [Docker 官方文档](https://docs.docker.com/)

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

## 📄 许可证

MIT License

## 🔗 相关链接

- **乐维监控官网**: https://www.lwops.cn
- **Docker 官方文档**: https://docs.docker.com/
- **问题反馈**: https://github.com/lwops/docker-lwops-deployer/issues
- **OpenClaw 项目**: https://github.com/openclaw/openclaw


path: wrapper.sh
content: #!/bin/bash
#
# OpenClaw 技能包装器
# 用于调用实际的执行脚本
#

set -e  # 遇到错误立即退出

# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# 执行脚本路径
EXECUTE_SCRIPT="$SCRIPT_DIR/execute.sh"

# ==================================================
# 依赖检查
# ==================================================

# 检查 Bash 是否可用
if ! command -v bash &> /dev/null; then
    echo '{"success": false, "error": "BashNotFound", "message": "未找到 Bash。请安装 Bash >= 4.0"}' >&2
    exit 1
fi

# 检查 Bash 版本
BASH_VERSION_MAJOR=${BASH_VERSINFO[0]}
BASH_VERSION_MINOR=${BASH_VERSINFO[1]}
if [ "$BASH_VERSION_MAJOR" -lt 4 ]; then
    echo "{\"success\": false, \"error\": \"BashVersionTooOld\", \"message\": \"需要 Bash >= 4.0，当前版本：$BASH_VERSION\"}" >&2
    exit 1
fi

# ==================================================
# 文件检查
# ==================================================

if [ ! -f "$EXECUTE_SCRIPT" ]; then
    echo "{\"success\": false, \"error\": \"ExecuteScriptNotFound\", \"message\": \"未找到执行脚本：$EXECUTE_SCRIPT\"}" >&2
    exit 1
fi

# 检查执行权限
if [ ! -x "$EXECUTE_SCRIPT" ]; then
    chmod +x "$EXECUTE_SCRIPT"
fi

# ==================================================
# 调用执行脚本
# ==================================================

# 将输入传递给执行脚本
if [ $# -eq 0 ]; then
    # 没有参数，从 STDIN 读取
    bash "$EXECUTE_SCRIPT"
else
    # 有参数，传递第一个参数作为输入
    bash "$EXECUTE_SCRIPT" "$1"
fi

# 传递退出码
exit $?


path: execute.sh
content: #!/bin/bash
#
# Docker LwOps 部署技能 - 核心执行脚本
# 功能：自动化部署乐维监控 8.1 Docker 容器
#

set -e  # 遇到错误立即退出

# ============================================================
# 脚本目录和路径
# ============================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="$SCRIPT_DIR/lib"

# ============================================================
# 加载函数库
# ============================================================

source "$LIB_DIR/system-utils.sh"
source "$LIB_DIR/network-utils.sh"
source "$LIB_DIR/docker-utils.sh"
source "$LIB_DIR/output-utils.sh"
source "$LIB_DIR/lock-utils.sh"

# ============================================================
# 常量定义
# ============================================================

CONTAINER_NAME="lwops_rocky8_image_8.1"
DEFAULT_CONTAINER_PORT1=80
DEFAULT_CONTAINER_PORT2=8081
DEFAULT_HOST_PORT1=8000
DEFAULT_HOST_PORT2=8081

# ============================================================
# 主函数
# ============================================================

main() {
    # 读取输入（从参数或 STDIN）
    local input=""
    if [ $# -eq 0 ]; then
        # 从 STDIN 读取
        input=$(cat)
    else
        input="$1"
    fi

    # ========================================================
    # 0. 检查容器状态（快速路径）
    # ========================================================

    output_progress "检查容器状态..."

    # 检查容器是否已存在并运行
    if docker_is_running "$CONTAINER_NAME"; then
        output_progress "容器已在运行，直接返回信息"

        local host_ip=$(get_host_ip)
        local container_info=$(docker_get_container_info "$CONTAINER_NAME")

        # 提取端口信息
        local port_80=$(echo "$container_info" | grep -o '"80"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\([^"]*\)".*/\1/')
        local port_8081=$(echo "$container_info" | grep -o '"8081"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\([^"]*\)".*/\1/')

        local result=$(cat <<EOF
{
  "container_name": "$CONTAINER_NAME",
  "container_id": "$(sudo docker ps -a --filter "name=$CONTAINER_NAME" --format '{{.ID}}')",
  "status": "already_running",
  "message": "容器已在运行，无需重新部署",
  "host_ip": "$host_ip",
  "access_urls": {
    "http": "http://$host_ip:$port_80",
    "https": "http://$host_ip:$port_8081"
  }
}
EOF
)

        output_success "$result" "容器已在运行"
        exit 0
    fi

    # ========================================================
    # 1. 获取部署锁
    # ========================================================

    output_progress "获取部署锁..."

    # 先检查锁状态
    local lock_status=$(get_lock_status)
    local lock_status_value=$(echo "$lock_status" | grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\([^"]*\)".*/\1/')

    if [ "$lock_status_value" = "locked" ]; then
        local lock_age=$(echo "$lock_status" | grep -o '"age_seconds"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*: *\([0-9]*\).*/\1/')
        local lock_pid=$(echo "$lock_status" | grep -o '"pid"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\([^"]*\)".*/\1/')
        local remaining=$(echo "$lock_status" | grep -o '"remaining_seconds"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*: *\([0-9]*\).*/\1/')

        local suggestions='[
            "等待当前部署完成（预计还需 '$remaining' 秒）",
            "查看部署进度: sudo docker logs '$CONTAINER_NAME'",
            "检查是否有其他用户正在部署"
        ]'

        output_error "DeploymentInProgress" "另一个部署任务正在进行中（PID: $lock_pid，已运行 ${lock_age} 秒）" "$suggestions"
        exit 1
    fi

    # 尝试获取锁（带超时）
    if ! acquire_lock_with_timeout 300; then
        local suggestions='[
            "检查是否有僵尸进程占用锁",
            "手动删除锁文件: rm -f /tmp/docker-lwops-deployer/deploy.lock",
            "等待几分钟后重试"
        ]'
        output_error "LockTimeout" "获取部署锁超时，可能存在死锁" "$suggestions"
        exit 1
    fi

    output_progress "成功获取部署锁"

    # 确保退出时释放锁
    trap cleanup_lock EXIT INT TERM

    # ========================================================
    # 2. 检查系统兼容性
    # ========================================================

    output_progress "检查系统兼容性..."

    # 检测操作系统
    local os=$(detect_os)
    if [ "$os" = "unknown" ]; then
        local suggestions='[
            "检查系统是否为 RHEL/CentOS 衍生版",
            "检查 /etc/os-release 文件内容",
            "手动安装 Docker 后重新运行",
            "访问 https://github.com/lwops/docker-lwops-deployer/issues 反馈"
        ]'
        output_error "UnsupportedOS" "不支持的操作系统，请手动安装 Docker" "$suggestions"
        exit 1
    fi

    output_progress "操作系统: $os"

    # ========================================================
    # 2. 检查 sudo 权限
    # ========================================================

    output_progress "检查权限..."

    if ! require_root; then
        local suggestions='["使用 sudo 运行此脚本", "将用户添加到 sudo 组: sudo usermod -aG sudo \$USER", "配置 sudo 无密码: echo \"\$USER ALL=(ALL) NOPASSWD: ALL\" | sudo tee /etc/sudoers.d/\$USER"]'
        output_error "PermissionDenied" "需要 sudo 权限来安装和管理 Docker" "$suggestions"
        exit 1
    fi

    # ========================================================
    # 3. 确保 Docker 已安装并运行
    # ========================================================

    output_progress "检查 Docker 环境..."

    if ! ensure_docker_installed; then
        local suggestions='["Ubuntu/Debian: sudo apt-get install docker.io", "CentOS/RHEL: sudo yum install docker", "访问 https://docs.docker.com/get-docker/ 获取详细安装指南"]'
        output_error "DockerInstallFailed" "Docker 安装失败" "$suggestions"
        exit 1
    fi

    if ! ensure_docker_running; then
        local suggestions='["启动 Docker: sudo systemctl start docker", "检查 Docker 日志: sudo journalctl -u docker", "重启系统后重试"]'
        output_error "DockerNotRunning" "Docker 服务无法启动" "$suggestions"
        exit 1
    fi

    # ========================================================
    # 4. 检测系统架构
    # ========================================================

    output_progress "检测系统架构..."

    local arch=$(detect_architecture)
    if [ "$arch" = "unknown" ]; then
        local suggestions='["支持的架构: x86_64 (amd64), aarch64 (arm64)", "使用 uname -m 查看您的系统架构"]'
        output_error "UnsupportedArchitecture" "不支持的系统架构" "$suggestions"
        exit 1
    fi

    output_progress "系统架构: $arch"

    # ========================================================
    # 5. 获取镜像名称
    # ========================================================

    local image=$(get_image_name "$arch")
    if [ -z "$image" ]; then
        output_error "InvalidArchitecture" "无法获取镜像名称" "[]"
        exit 1
    fi

    output_progress "目标镜像: $image"

    # ========================================================
    # 6. 拉取镜像
    # ========================================================

    output_progress "拉取 Docker 镜像..."

    if ! docker_pull "$image"; then
        local suggestions='["检查网络连接", "检查镜像地址是否正确", "尝试手动拉取: sudo docker pull $image"]'
        output_error "ImagePullFailed" "镜像拉取失败" "$suggestions"
        exit 1
    fi

    # ========================================================
    # 7. 智能分配端口
    # ========================================================

    output_progress "检查端口可用性..."

    local ports_json=$(allocate_ports "$DEFAULT_CONTAINER_PORT1" "$DEFAULT_CONTAINER_PORT2" "$DEFAULT_HOST_PORT1" "$DEFAULT_HOST_PORT2")
    if [ $? -ne 0 ]; then
        local suggestions='["释放被占用的端口", "检查防火墙设置", "使用其他端口"]'
        output_error "PortAllocationFailed" "无法分配可用端口" "$suggestions"
        exit 1
    fi

    local host_port1=$(echo "$ports_json" | get_json_number "host_port1")
    local host_port2=$(echo "$ports_json" | get_json_number "host_port2")

    output_progress "端口映射: ${host_port1}:80, ${host_port2}:8081"

    # ========================================================
    # 8. 检测 cgroup 版本
    # ========================================================

    output_progress "检测 cgroup 版本..."

    local cgroup_version=$(detect_cgroup_version)
    local cgroup_mode="ro"
    if [ "$cgroup_version" = "v2" ]; then
        cgroup_mode="rw"
        output_warning "检测到 cgroup v2，使用读写模式挂载"
    fi

    output_progress "cgroup 版本: $cgroup_version, 挂载模式: $cgroup_mode"

    # ========================================================
    # 9. 启动容器
    # ========================================================

    output_progress "启动容器: $CONTAINER_NAME"

    if ! docker_start_container "$CONTAINER_NAME" "$image" "$host_port1" "$host_port2" "$cgroup_mode"; then
        local suggestions='["检查 Docker 日志: sudo docker logs $CONTAINER_NAME", "检查镜像是否完整", "尝试重新拉取镜像"]'
        output_error "ContainerStartFailed" "容器启动失败" "$suggestions"
        exit 1
    fi

    # ========================================================
    # 10. 等待容器完全启动
    # ========================================================

    output_progress "等待容器启动..."

    local max_wait=30
    local waited=0
    while [ $waited -lt $max_wait ]; do
        if docker_is_running "$CONTAINER_NAME"; then
            break
        fi
        sleep 1
        waited=$((waited + 1))
    done

    if ! docker_is_running "$CONTAINER_NAME"; then
        local suggestions='["检查容器日志: sudo docker logs $CONTAINER_NAME", "检查系统资源是否充足", "尝试重启 Docker 服务"]'
        output_error "ContainerNotRunning" "容器启动后未运行" "$suggestions"
        exit 1
    fi

    # ========================================================
    # 11. 获取主机 IP
    # ========================================================

    output_progress "获取访问地址..."

    local host_ip=$(get_host_ip)

    # ========================================================
    # 12. 输出成功结果
    # ========================================================

    local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

    local result=$(cat <<EOF
{
  "container_name": "$CONTAINER_NAME",
  "container_id": "$(sudo docker ps -a --filter "name=$CONTAINER_NAME" --format '{{.ID}}')",
  "status": "running",
  "architecture": "$arch",
  "image": "$image",
  "host_ip": "$host_ip",
  "ports": {
    "http": {
      "container_port": $DEFAULT_CONTAINER_PORT1,
      "host_port": $host_port1,
      "url": "http://$host_ip:$host_port1"
    },
    "https": {
      "container_port": $DEFAULT_CONTAINER_PORT2,
      "host_port": $host_port2,
      "url": "http://$host_ip:$host_port2"
    }
  },
  "cgroup_version": "$cgroup_version",
  "cgroup_mount_mode": "$cgroup_mode",
  "timestamp": "$timestamp"
}
EOF
)

    output_success "$result" "容器部署成功"

    # ========================================================
    # 13. 输出访问信息（便于用户查看）
    # ========================================================

    echo "" >&2
    echo "========================================" >&2
    echo "容器部署成功！" >&2
    echo "========================================" >&2
    echo "容器名称: $CONTAINER_NAME" >&2
    echo "容器 ID: $(sudo docker ps -a --filter "name=$CONTAINER_NAME" --format '{{.ID}}')" >&2
    echo "系统架构: $arch" >&2
    echo "" >&2
    echo "访问地址：" >&2
    echo "  - HTTP:  http://$host_ip:$host_port1" >&2
    echo "  - HTTPS: http://$host_ip:$host_port2" >&2
    echo "" >&2
    echo "管理命令：" >&2
    echo "  - 查看日志: sudo docker logs $CONTAINER_NAME" >&2
    echo "  - 进入容器: sudo docker exec -it $CONTAINER_NAME bash" >&2
    echo "  - 停止容器: sudo docker stop $CONTAINER_NAME" >&2
    echo "  - 启动容器: sudo docker start $CONTAINER_NAME" >&2
    echo "  - 删除容器: sudo docker rm -f $CONTAINER_NAME" >&2
    echo "========================================" >&2

    exit 0
}

# ============================================================
# 执行主函数
# ============================================================

main "$@"


path: lib/docker-utils.sh
content: #!/bin/bash
#
# Docker 操作工具函数库
# 提供 Docker 安装、容器管理等功能
#

# 检查 Docker 是否已安装
# 返回值: 0=已安装, 1=未安装
check_docker_installed() {
    if command -v docker &> /dev/null; then
        return 0
    else
        return 1
    fi
}

# 安装 Docker（Ubuntu/Debian）
install_docker_ubuntu() {
    echo "正在为 Ubuntu/Debian 系统安装 Docker..."

    # 更新包索引
    sudo apt-get update -y || {
        echo "无法更新包索引"
        return 1
    }

    # 安装必要的依赖
    sudo apt-get install -y \
        ca-certificates \
        curl \
        gnupg \
        lsb-release || {
        echo "无法安装依赖包"
        return 1
    }

    # 添加 Docker 官方 GPG 密钥
    sudo mkdir -p /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg || {
        echo "无法添加 Docker GPG 密钥"
        return 1
    }

    # 设置 Docker 仓库
    echo \
      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
      $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null || {
        echo "无法设置 Docker 仓库"
        return 1
    }

    # 更新包索引
    sudo apt-get update -y || {
        echo "无法更新包索引"
        return 1
    }

    # 安装 Docker
    sudo apt-get install -y docker-ce docker-ce-cli containerd.io || {
        echo "无法安装 Docker"
        return 1
    }

    echo "Docker 安装成功"
    return 0
}

# 安装 Docker（CentOS/RHEL）
install_docker_centos() {
    echo "正在为 CentOS/RHEL 系统安装 Docker..."

    # 安装必要的依赖
    sudo yum install -y yum-utils || {
        echo "无法安装 yum-utils"
        return 1
    }

    # 添加 Docker 仓库
    sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo || {
        echo "无法添加 Docker 仓库"
        return 1
    }

    # 安装 Docker
    sudo yum install -y docker-ce docker-ce-cli containerd.io || {
        echo "无法安装 Docker"
        return 1
    }

    echo "Docker 安装成功"
    return 0
}

# 安装 Docker（Fedora）
install_docker_fedora() {
    echo "正在为 Fedora 系统安装 Docker..."

    # 安装必要的依赖
    sudo dnf -y install dnf-plugins-core || {
        echo "无法安装 dnf-plugins-core"
        return 1
    }

    # 添加 Docker 仓库
    sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo || {
        echo "无法添加 Docker 仓库"
        return 1
    }

    # 安装 Docker
    sudo dnf install -y docker-ce docker-ce-cli containerd.io || {
        echo "无法安装 Docker"
        return 1
    }

    echo "Docker 安装成功"
    return 0
}

# 安装 Docker（Arch Linux）
install_docker_arch() {
    echo "正在为 Arch Linux 系统安装 Docker..."

    # 安装 Docker
    sudo pacman -S --noconfirm docker || {
        echo "无法安装 Docker"
        return 1
    }

    echo "Docker 安装成功"
    return 0
}

# 确保 Docker 已安装
# 返回值: 0=成功, 1=失败
ensure_docker_installed() {
    if check_docker_installed; then
        echo "Docker 已安装"
        return 0
    fi

    echo "Docker 未安装，正在尝试自动安装..."

    local os=$(detect_os)
    local result=0

    case "$os" in
        ubuntu|debian)
            install_docker_ubuntu
            result=$?
            ;;
        centos)
            install_docker_centos
            result=$?
            ;;
        fedora)
            install_docker_fedora
            result=$?
            ;;
        arch)
            install_docker_arch
            result=$?
            ;;
        *)
            echo "不支持的操作系统: $os"
            return 1
            ;;
    esac

    if [ $result -eq 0 ]; then
        echo "Docker 安装完成"
        return 0
    else
        echo "Docker 安装失败"
        return 1
    fi
}

# 确保 Docker 服务运行
# 返回值: 0=运行中, 1=未运行
ensure_docker_running() {
    # 检查 Docker 服务状态
    if ! sudo systemctl is-active --quiet docker; then
        echo "正在启动 Docker 服务..."
        sudo systemctl start docker || {
            echo "无法启动 Docker 服务"
            return 1
        }
    fi

    # 设置 Docker 开机自启
    sudo systemctl enable docker >/dev/null 2>&1

    echo "Docker 服务运行中"
    return 0
}

# 拉取 Docker 镜像
# 参数: $1 - 镜像名称
# 返回值: 0=成功, 1=失败
docker_pull() {
    local image="$1"

    if [ -z "$image" ]; then
        echo "错误：镜像名称不能为空"
        return 1
    fi

    echo "正在拉取镜像: $image"

    if sudo docker pull "$image"; then
        echo "镜像拉取成功"
        return 0
    else
        echo "镜像拉取失败"
        return 1
    fi
}

# 启动 Docker 容器
# 参数:
#   $1 - 容器名称
#   $2 - 镜像名称
#   $3 - 宿主机端口1
#   $4 - 宿主机端口2
#   $5 - cgroup 挂载模式 (ro|rw)
# 返回值: 0=成功, 1=失败
docker_start_container() {
    local container_name="$1"
    local image="$2"
    local host_port1="$3"
    local host_port2="$4"
    local cgroup_mode="$5"

    if [ -z "$container_name" ] || [ -z "$image" ]; then
        echo "错误：容器名称和镜像名称不能为空"
        return 1
    fi

    # 检查容器是否已存在
    if sudo docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        echo "容器已存在，正在删除..."
        sudo docker rm -f "$container_name" >/dev/null 2>&1 || {
            echo "无法删除现有容器"
            return 1
        }
    fi

    echo "正在启动容器: $container_name"

    # 启动容器
    if sudo docker run -d \
        --name "$container_name" \
        --privileged \
        -p "${host_port1}:80" \
        -p "${host_port2}:8081" \
        --hostname "$container_name" \
        -v /sys/fs/cgroup:/sys/fs/cgroup:${cgroup_mode} \
        "$image" \
        /usr/sbin/init; then

        echo "容器启动成功"
        return 0
    else
        echo "容器启动失败"
        return 1
    fi
}

# 获取容器信息
# 参数: $1 - 容器名称
# 返回: JSON 格式的容器信息
docker_get_container_info() {
    local container_name="$1"

    if [ -z "$container_name" ]; then
        echo "{}"
        return 1
    fi

    # 检查容器是否存在
    if ! sudo docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
        echo "{}"
        return 1
    fi

    # 获取容器 ID
    local container_id=$(sudo docker ps -a --filter "name=$container_name" --format '{{.ID}}')

    # 获取容器状态
    local status=$(sudo docker inspect --format '{{.State.Status}}' "$container_name" 2>/dev/null || echo "unknown")

    # 获取端口映射
    local ports_json=$(sudo docker port "$container_name" 2>/dev/null)

    # 解析端口映射
    local port_80_host=""
    local port_8081_host=""

    if echo "$ports_json" | grep -q "80/tcp"; then
        port_80_host=$(echo "$ports_json" | grep "80/tcp" | awk -F: '{print $2}')
    fi

    if echo "$ports_json" | grep -q "8081/tcp"; then
        port_8081_host=$(echo "$ports_json" | grep "8081/tcp" | awk -F: '{print $2}')
    fi

    # 获取镜像名称
    local image=$(sudo docker inspect --format '{{.Config.Image}}' "$container_name" 2>/dev/null || echo "")

    # 生成 JSON 输出
    cat <<EOF
{
  "container_name": "$container_name",
  "container_id": "$container_id",
  "status": "$status",
  "image": "$image",
  "ports": {
    "80": "$port_80_host",
    "8081": "$port_8081_host"
  }
}
EOF
}

# 检查容器是否运行
# 参数: $1 - 容器名称
# 返回值: 0=运行中, 1=未运行
docker_is_running() {
    local container_name="$1"

    if [ -z "$container_name" ]; then
        return 1
    fi

    if sudo docker ps --format '{{.Names}}' | grep -q "^${container_name}$"; then
        return 0
    else
        return 1
    fi
}

# 停止容器
# 参数: $1 - 容器名称
# 返回值: 0=成功, 1=失败
docker_stop_container() {
    local container_name="$1"

    if [ -z "$container_name" ]; then
        return 1
    fi

    if sudo docker stop "$container_name" >/dev/null 2>&1; then
        return 0
    else
        return 1
    fi
}

# 删除容器
# 参数: $1 - 容器名称
# 返回值: 0=成功, 1=失败
docker_remove_container() {
    local container_name="$1"

    if [ -z "$container_name" ]; then
        return 1
    fi

    if sudo docker rm -f "$container_name" >/dev/null 2>&1; then
        return 0
    else
        return 1
    fi
}


path: lib/lock-utils.sh
content: #!/bin/bash
#
# 锁管理工具函数库
# 提供文件锁功能，防止并发部署冲突
#

# 锁文件目录
LOCK_DIR="/tmp/docker-lwops-deployer"
LOCK_FILE="$LOCK_DIR/deploy.lock"
LOCK_TIMEOUT=300  # 5分钟超时

# 初始化锁目录
init_lock() {
    if [ ! -d "$LOCK_DIR" ]; then
        mkdir -p "$LOCK_DIR" || {
            echo "无法创建锁目录: $LOCK_DIR" >&2
            return 1
        }
    fi
    return 0
}

# 获取锁（非阻塞模式）
# 返回值: 0=成功, 1=失败（锁已被占用）
acquire_lock() {
    init_lock || return 1

    # 尝试创建锁文件（原子操作）
    if ( set -o noclobber; echo "$$" > "$LOCK_FILE" ) 2>/dev/null; then
        return 0
    else
        return 1
    fi
}

# 获取锁（阻塞模式，带超时）
# 参数: $1 - 超时时间（秒），默认 300
# 返回值: 0=成功, 1=超时
acquire_lock_with_timeout() {
    local timeout="${1:-$LOCK_TIMEOUT}"
    local elapsed=0
    local interval=2  # 每 2 秒检查一次

    init_lock || return 1

    while [ $elapsed -lt $timeout ]; do
        if acquire_lock; then
            return 0
        fi

        # 获取锁的持有者信息
        local lock_pid=$(cat "$LOCK_FILE" 2>/dev/null)
        local lock_age=0
        if [ -n "$lock_pid" ]; then
            # 检查进程是否还在运行
            if ps -p "$lock_pid" >/dev/null 2>&1; then
                lock_age=$(ps -o etimes= -p "$lock_pid" 2>/dev/null | tr -d ' ')
                lock_age=${lock_age:-0}
            else
                # 进程已死，清理锁文件
                rm -f "$LOCK_FILE" 2>/dev/null
                if acquire_lock; then
                    return 0
                fi
            fi
        fi

        # 等待一段时间再试
        sleep $interval
        elapsed=$((elapsed + interval))
    done

    return 1
}

# 释放锁
release_lock() {
    rm -f "$LOCK_FILE" 2>/dev/null
    return 0
}

# 获取锁的状态信息
# 返回: JSON 格式的锁状态
get_lock_status() {
    local status="unlocked"
    local pid=""
    local age=0
    local remaining_time=0

    if [ -f "$LOCK_FILE" ]; then
        pid=$(cat "$LOCK_FILE" 2>/dev/null)
        if [ -n "$pid" ] && ps -p "$pid" >/dev/null 2>&1; then
            status="locked"
            age=$(ps -o etimes= -p "$pid" 2>/dev/null | tr -d ' ')
            age=${age:-0}
            remaining_time=$((LOCK_TIMEOUT - age))
        else
            # 僵尸锁，清理它
            rm -f "$LOCK_FILE" 2>/dev/null
        fi
    fi

    cat <<EOF
{
  "status": "$status",
  "pid": "$pid",
  "age_seconds": $age,
  "remaining_seconds": $remaining_time,
  "lock_file": "$LOCK_FILE"
}
EOF
}

# 清理锁（用于脚本退出时的清理）
cleanup_lock() {
    release_lock
}

# 显示锁状态的友好信息
show_lock_status() {
    local status=$(get_lock_status)
    local status_value=$(echo "$status" | grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\([^"]*\)".*/\1/')

    echo "========================================"
    echo "部署锁状态"
    echo "========================================"

    if [ "$status_value" = "locked" ]; then
        local pid=$(echo "$status" | grep -o '"pid"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"\([^"]*\)".*/\1/')
        local age=$(echo "$status" | grep -o '"age_seconds"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*: *\([0-9]*\).*/\1/')
        local remaining=$(echo "$status" | grep -o '"remaining_seconds"[[:space:]]*:[[:space:]]*[0-9]*' | sed 's/.*: *\([0-9]*\).*/\1/')

        echo "状态: 已锁定"
        echo "持有者进程: $pid"
        echo "已运行时间: ${age} 秒"
        echo "预计剩余时间: ${remaining} 秒"
        echo ""
        echo "查看进程详情: ps -fp $pid"
        echo "查看部署日志: sudo docker logs lwops_rocky8_image_8.1"
    else
        echo "状态: 未锁定"
        echo "当前没有部署任务在执行"
    fi

    echo "========================================"
}


path: lib/output-utils.sh
content: #!/bin/bash
#
# 输出格式化工具函数库
# 提供 JSON 输出、错误处理等功能
#

# 输出成功结果（JSON 格式）
# 参数:
#   $1 - 数据（JSON 格式）
#   $2 - 消息（可选）
# 输出到: STDOUT
output_success() {
    local data="$1"
    local message="${2:-操作成功}"
    local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

    cat <<EOF
{
  "success": true,
  "data": $data,
  "message": "$message",
  "timestamp": "$timestamp"
}
EOF
}

# 输出错误信息（JSON 格式）
# 参数:
#   $1 - 错误类型
#   $2 - 错误消息
#   $3 - 建议数组（可选，JSON 数组格式）
# 输出到: STDERR
output_error() {
    local error_type="$1"
    local error_message="$2"
    local suggestions="${3:-[]}"
    local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

    cat >&2 <<EOF
{
  "success": false,
  "error": "$error_type",
  "message": "$error_message",
  "suggestions": $suggestions,
  "timestamp": "$timestamp"
}
EOF
}

# 解析 JSON 输入（简化版）
# 注意：这是一个简化的 JSON 解析器，不支持复杂嵌套
# 参数: $1 - JSON 字符串
# 参数: $2 - 键名
# 返回值: 对应的值
parse_json() {
    local json="$1"
    local key="$2"

    # 使用 grep 和 sed 提取值
    # 支持：字符串、数字、布尔值
    local value=$(echo "$json" | sed -n "s/.*\"$key\"\s*:\s*\(\"\([^\"]*\)\"\|\\([0-9]*\\)\\|\\(true\\|false\\|null\)\\).*/\\2\\3\\4/p" | head -1)

    echo "$value"
}

# 从 JSON 中获取字符串值
# 参数: $1 - JSON 字符串
# 参数: $2 - 键名
# 返回值: 对应的字符串值
get_json_string() {
    local json="$1"
    local key="$2"

    echo "$json" | grep -o "\"$key\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | sed 's/.*: *"\([^"]*\)".*/\1/'
}

# 从 JSON 中获取数字值
# 参数: $1 - JSON 字符串
# 参数: $2 - 键名
# 返回值: 对应的数字值
get_json_number() {
    local json="$1"
    local key="$2"

    echo "$json" | grep -o "\"$key\"[[:space:]]*:[[:space:]]*[0-9]*" | sed 's/.*: *\([0-9]*\).*/\1/'
}

# 从 JSON 中获取布尔值
# 参数: $1 - JSON 字符串
# 参数: $2 - 键名
# 返回值: 对应的布尔值（true|false）
get_json_bool() {
    local json="$1"
    local key="$2"

    echo "$json" | grep -o "\"$key\"[[:space:]]*:[[:space:]]*\(true\|false\)" | sed 's/.*: *\(true\|false\).*/\1/'
}

# 验证 JSON 格式
# 参数: $1 - JSON 字符串
# 返回值: 0=有效, 1=无效
validate_json() {
    local json="$1"

    # 尝试解析 JSON（如果 Python 可用）
    if command -v python3 &> /dev/null; then
        echo "$json" | python3 -m json.tool >/dev/null 2>&1
        return $?
    elif command -v python &> /dev/null; then
        echo "$json" | python -m json.tool >/dev/null 2>&1
        return $?
    elif command -v jq &> /dev/null; then
        echo "$json" | jq . >/dev/null 2>&1
        return $?
    else
        # 简单验证：检查是否以 { 开头，以 } 结尾
        if [[ "$json" =~ ^\{.*\}$ ]]; then
            return 0
        else
            return 1
        fi
    fi
}

# 转义 JSON 字符串
# 参数: $1 - 原始字符串
# 返回值: 转义后的字符串
escape_json_string() {
    local str="$1"

    # 转义特殊字符
    str="${str//\\/\\\\}"  # 反斜杠
    str="${str//\"/\\\"}"  # 双引号
    str="${str//$'\n'/\\n}"  # 换行
    str="${str//$'\r'/\\r}"  # 回车
    str="${str//$'\t'/\\t}"  # 制表符

    echo "$str"
}

# 构建键值对 JSON
# 参数: 奇数个参数，格式：key1 value1 key2 value2 ...
# 返回值: JSON 对象字符串
build_json() {
    local json=""
    local count=$#

    if [ $((count % 2)) -ne 0 ]; then
        echo "{}"
        return 1
    fi

    local i=1
    while [ $i -le $count ]; do
        local key="${!i}"
        local i=$((i + 1))
        local value="${!i}"

        if [ $i -eq 2 ]; then
            json="{\"$key\": \"$value\""
        else
            json="$json, \"$key\": \"$value\""
        fi

        i=$((i + 1))
    done

    json="$json}"
    echo "$json"
}

# 输出日志（调试用）
# 参数: $1 - 日志级别（INFO|WARN|ERROR）
# 参数: $2 - 日志消息
# 输出到: STDERR
log() {
    local level="$1"
    local message="$2"
    local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

    echo "[$timestamp] [$level] $message" >&2
}

# 输出进度信息
# 参数: $1 - 进度消息
# 输出到: STDERR
output_progress() {
    local message="$1"
    echo "[INFO] $message" >&2
}

# 输出警告信息
# 参数: $1 - 警告消息
# 输出到: STDERR
output_warning() {
    local message="$1"
    echo "[WARN] $message" >&2
}


path: lib/system-utils.sh
content: #!/bin/bash
#
# 系统检测工具函数库
# 提供系统类型、架构、cgroup 等检测功能
#

# 检测操作系统类型
# 返回值: ubuntu|debian|centos|rhel|fedora|arch|unknown
detect_os() {
    if [ -f /etc/os-release ]; then
        . /etc/os-release

        # 方法 1：优先使用 ID_LIKE 字段（自动检测兼容性）
        if [ -n "$ID_LIKE" ]; then
            # ID_LIKE 是空格或逗号分隔的列表
            # 将逗号替换为空格，统一处理
            local id_like=$(echo "$ID_LIKE" | tr ',' ' ')

            for parent_id in $id_like; do
                case "$parent_id" in
                    rhel|centos)
                        echo "centos"
                        return 0
                        ;;
                    debian)
                        echo "debian"
                        return 0
                        ;;
                    ubuntu)
                        echo "ubuntu"
                        return 0
                        ;;
                    fedora)
                        echo "fedora"
                        return 0
                        ;;
                    arch)
                        echo "arch"
                        return 0
                        ;;
                esac
            done
        fi

        # 方法 2：显式匹配（用于 ID_LIKE 不存在或未覆盖的情况）
        case "$ID" in
            ubuntu)
                echo "ubuntu"
                ;;
            debian)
                echo "debian"
                ;;
            # RHEL/CentOS 衍生系统
            centos|rhel|rocky|almalinux|anolis|openeuler|kylin|oracle|scientific|vzlinux|tencentos)
                echo "centos"
                ;;
            fedora)
                echo "fedora"
                ;;
            # Arch 衍生系统
            arch|manjaro|endeavouros)
                echo "arch"
                ;;
            # Debian 衍生系统
            deepin|linuxmint|elementaryos)
                echo "debian"
                ;;
            *)
                echo "unknown"
                ;;
        esac
    elif [ -f /etc/redhat-release ]; then
        # 传统 RHEL/CentOS 检测方法
        echo "centos"
    elif [ -f /etc/debian_version ]; then
        # 传统 Debian 检测方法
        echo "debian"
    else
        echo "unknown"
    fi
}

# 检测系统架构
# 返回值: x86_64|aarch64|unknown
detect_architecture() {
    local arch=$(uname -m)
    case "$arch" in
        x86_64|amd64)
            echo "x86_64"
            ;;
        aarch64|arm64)
            echo "aarch64"
            ;;
        *)
            echo "unknown"
            ;;
    esac
}

# 根据架构获取镜像名称
# 参数: $1 - 架构 (x86_64|aarch64)
# 返回值: 镜像名称
get_image_name() {
    local arch="$1"
    case "$arch" in
        x86_64)
            echo "swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_x86_image:latest"
            ;;
        aarch64)
            echo "swr.cn-south-1.myhuaweicloud.com/cloud-lwops/lwops_rocky8_arm_image:latest"
            ;;
        *)
            echo ""
            ;;
    esac
}

# 检测 cgroup 版本
# 返回值: v1|v2
detect_cgroup_version() {
    if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
        echo "v2"
    else
        echo "v1"
    fi
}

# 检查 sudo 权限
# 返回值: 0=有权限, 1=无权限
check_sudo() {
    if [ "$EUID" -eq 0 ]; then
        # 已经是 root 用户
        return 0
    elif sudo -n true 2>/dev/null; then
        # sudo 有 nopasswd 权限
        return 0
    else
        # 需要密码或没有权限
        return 1
    fi
}

# 检查是否为 root 或有 sudo 权限
# 返回值: 0=有权限, 1=无权限
require_root() {
    if ! check_sudo; then
        return 1
    fi
    return 0
}

# 获取系统信息摘要
# 返回 JSON 格式的系统信息
get_system_info() {
    local os=$(detect_os)
    local arch=$(detect_architecture)
    local cgroup=$(detect_cgroup_version)

    cat <<EOF
{
  "os": "$os",
  "architecture": "$arch",
  "cgroup_version": "$cgroup"
}
EOF
}

# 获取详细的系统信息（用于调试）
# 输出详细的操作系统检测信息
get_system_info_debug() {
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        cat <<EOF
操作系统检测详情：
  ID: $ID
  ID_LIKE: $ID_LIKE
  NAME: $NAME
  VERSION: $VERSION
  检测结果: $(detect_os)
EOF
    else
        echo "无法找到 /etc/os-release 文件"
    fi
}


path: lib/network-utils.sh
content: #!/bin/bash
#
# 网络和端口检测工具函数库
# 提供端口检测、IP 获取等功能
#

# 检查端口是否可用
# 参数: $1 - 端口号
# 返回值: 0=可用, 1=被占用
is_port_available() {
    local port="$1"
    local in_use=false

    # 方法1: 使用 ss 命令（推荐）
    if command -v ss &> /dev/null; then
        if ss -tuln | grep -q ":$port "; then
            in_use=true
        fi
    # 方法2: 使用 netstat 命令
    elif command -v netstat &> /dev/null; then
        if netstat -tuln 2>/dev/null | grep -q ":$port "; then
            in_use=true
        fi
    # 方法3: 使用 lsof 命令
    elif command -v lsof &> /dev/null; then
        if lsof -i ":$port" &> /dev/null; then
            in_use=true
        fi
    # 方法4: 尝试连接端口
    else
        if timeout 1 bash -c "echo >/dev/tcp/127.0.0.1/$port" 2>/dev/null; then
            in_use=true
        fi
    fi

    if [ "$in_use" = true ]; then
        return 1
    else
        return 0
    fi
}

# 查找可用端口
# 参数: $1 - 起始端口（默认 8000）
# 返回值: 可用端口号
find_available_port() {
    local start_port="${1:-8000}"
    local port="$start_port"
    local max_attempts=100
    local attempt=0

    while [ $attempt -lt $max_attempts ]; do
        if is_port_available "$port"; then
            echo "$port"
            return 0
        fi
        port=$((port + 1))
        attempt=$((attempt + 1))
    done

    # 未找到可用端口
    return 1
}

# 智能分配端口
# 参数:
#   $1 - 容器端口1（默认 80）
#   $2 - 容器端口2（默认 8081）
#   $3 - 宿主机起始端口1（默认 8000）
#   $4 - 宿主机起始端口2（默认 8081）
# 返回: JSON 格式的端口映射
allocate_ports() {
    local container_port1="${1:-80}"
    local container_port2="${2:-8081}"
    local host_start1="${3:-8000}"
    local host_start2="${4:-8081}"

    local host_port1="$host_start1"
    local host_port2="$host_start2"

    # 检查并分配第一个端口
    if ! is_port_available "$host_port1"; then
        host_port1=$(find_available_port "$host_port1")
        if [ -z "$host_port1" ]; then
            return 1
        fi
    fi

    # 检查并分配第二个端口
    if ! is_port_available "$host_port2"; then
        host_port2=$(find_available_port "$host_port2")
        if [ -z "$host_port2" ]; then
            return 1
        fi
    fi

    cat <<EOF
{
  "container_port1": $container_port1,
  "host_port1": $host_port1,
  "container_port2": $container_port2,
  "host_port2": $host_port2
}
EOF
}

# 获取主机 IP 地址
# 返回值: 主机 IP 地址
get_host_ip() {
    local ip=""

    # 方法1: 使用 hostname -I
    if command -v hostname &> /dev/null; then
        ip=$(hostname -I 2>/dev/null | awk '{print $1}')
        if [ -n "$ip" ]; then
            echo "$ip"
            return 0
        fi
    fi

    # 方法2: 使用 ip 命令
    if command -v ip &> /dev/null; then
        ip=$(ip route get 1 2>/dev/null | awk '{print $7}' | head -1)
        if [ -n "$ip" ]; then
            echo "$ip"
            return 0
        fi
    fi

    # 方法3: 使用 ifconfig 命令
    if command -v ifconfig &> /dev/null; then
        ip=$(ifconfig 2>/dev/null | grep "inet " | grep -v "127.0.0.1" | awk '{print $2}' | head -1)
        if [ -n "$ip" ]; then
            echo "$ip"
            return 0
        fi
    fi

    # 方法4: 解析 /proc/net/route（适用于没有 ip 命令的系统）
    if [ -f /proc/net/route ]; then
        ip=$(awk '$2 == "00000000" {print $1}' /proc/net/route | head -1)
        if [ -n "$ip" ]; then
            # 将十六进制 IP 转换为点分十进制
            ip=$(echo "$ip" | sed 's/../0x& /g' | \
                awk '{printf "%d.%d.%d.%d", and($1,0xFF), rshift(and($1,0xFF00),8), rshift(and($1,0xFF0000),16), rshift(and($1,0xFF000000),24)}')
            if [ "$ip" != "0.0.0.0" ]; then
                echo "$ip"
                return 0
            fi
        fi
    fi

    # 如果都失败，返回 localhost
    echo "127.0.0.1"
    return 0
}

# 检查网络连接
# 参数: $1 - 主机, $2 - 端口
# 返回值: 0=可连接, 1=无法连接
check_connection() {
    local host="$1"
    local port="$2"

    if timeout 5 bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
        return 0
    else
        return 1
    fi
}

# 检查 DNS 解析
# 参数: $1 - 域名
# 返回值: 0=可解析, 1=无法解析
check_dns() {
    local domain="$1"

    if command -v nslookup &> /dev/null; then
        if nslookup "$domain" &> /dev/null; then
            return 0
        fi
    elif command -v host &> /dev/null; then
        if host "$domain" &> /dev/null; then
            return 0
        fi
    elif command -v dig &> /dev/null; then
        if dig "$domain" &> /dev/null; then
            return 0
        fi
    fi

    return 1
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)