提交 079adfda authored 作者: 陈泽健's avatar 陈泽健

refactor(base): 优化基础模块并移除冗余日志代码

- 在 browser_init 后增加 sleep(15) 以确保环境稳定
- 注释掉 get_remote_log_with_paramiko 的主函数调用块
- 注释掉 LogCollector 的主函数调用及使用示例
- 移除 create_test_xlsx.py 文件及相关用例生成逻辑
- 删除兰州用例.json配置文件
- 移除兰州登录功能开发相关文档
上级 76a8ce14
# -*- coding: utf-8 -*-
"""
根据模板用例 Excel,在同一个工作簿中新建 Sheet,并从 JSON 配置文件中读取用例数据写入。
后续只需要维护 JSON 文件即可复用。
"""
import os
import json
from copy import copy
from openpyxl import load_workbook
from openpyxl.styles import Alignment
# ===== 1. 配置路径(改为相对当前脚本的路径) =====
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEMPLATE_PATH = os.path.join(BASE_DIR, "用例文件", "兰州中石化项目测试用例20251203.xlsx")
NEW_SHEET_NAME = "兰州登录MQTT用例" # 新建的 Sheet 名
CASES_FILE = os.path.join(BASE_DIR, "config", "兰州用例.json") # 用例配置文件(JSON)
# 与你表头对应的顺序(根据截图)
headers_order = [
"序号", "功能模块", "功能类别", "用例编号", "功能描述", "用例等级",
"功能编号", "用例名称", "预置条件", "操作步骤", "JSON", "预期结果",
"测试结果", "测试结论", "日志截屏", "备注",
]
def load_cases():
"""从 JSON 配置文件加载用例列表。"""
if not os.path.exists(CASES_FILE):
raise FileNotFoundError(f"找不到用例配置文件: {CASES_FILE}")
with open(CASES_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, list):
raise ValueError("用例配置文件的根节点必须是列表(List)")
return data
def main():
if not os.path.exists(TEMPLATE_PATH):
print("找不到模板文件:", TEMPLATE_PATH)
return
try:
cases = load_cases()
except Exception as e:
print("加载用例配置失败:", e)
return
wb = load_workbook(TEMPLATE_PATH)
# 以第一个sheet作为模板
template_sheet = wb.worksheets[0]
# 如果新sheet已存在就先删除
if NEW_SHEET_NAME in wb.sheetnames:
ws_new = wb[NEW_SHEET_NAME]
wb.remove(ws_new)
ws_new = wb.create_sheet(NEW_SHEET_NAME)
# ===== 复制表头(仅值 + 简单样式) =====
# 假设模板表头在第3行(根据截图),如有偏差可以改成2或其它
header_row_index = 3
for col_idx, cell in enumerate(template_sheet[header_row_index], start=1):
new_cell = ws_new.cell(row=1, column=col_idx, value=cell.value)
if cell.has_style:
new_cell.font = copy(cell.font)
new_cell.fill = copy(cell.fill)
new_cell.border = copy(cell.border)
new_cell.alignment = copy(cell.alignment)
new_cell.number_format = cell.number_format
ws_new.freeze_panes = "B2"
# ===== 写入用例数据(预置条件/操作步骤自动换行,JSON列统一留空) =====
row = 2
for case in cases:
for col_idx, header in enumerate(headers_order, start=1):
val = case.get(header, "")
if header in ("预置条件", "操作步骤") and isinstance(val, str):
# 把分号+空格变成换行,Excel 中会显示为多行
val = val.replace("; ", "\n")
if header == "JSON":
# JSON 列统一留空
val = ""
ws_new.cell(row=row, column=col_idx, value=val)
row += 1
# ===== 对“预置条件”和“操作步骤”开启自动换行 =====
col_idx_pre = headers_order.index("预置条件") + 1
col_idx_steps = headers_order.index("操作步骤") + 1
for r in range(1, row):
cell_pre = ws_new.cell(row=r, column=col_idx_pre)
cell_steps = ws_new.cell(row=r, column=col_idx_steps)
cell_pre.alignment = Alignment(wrap_text=True, vertical="top")
cell_steps.alignment = Alignment(wrap_text=True, vertical="top")
# 调整列宽
for col in ws_new.columns:
max_len = 0
col_letter = col[0].column_letter
for c in col:
v = c.value
if v is None:
continue
l = len(str(v))
if l > max_len:
max_len = l
ws_new.column_dimensions[col_letter].width = min(max_len + 2, 60)
wb.save(TEMPLATE_PATH)
print("已在原文件中创建新Sheet:", NEW_SHEET_NAME)
if __name__ == "__main__":
main()
\ No newline at end of file
# 容器远程升级与镜像管理系统需求说明文档
## 📋 概述
本系统由三个核心 Shell 脚本组成,用于实现容器化服务的远程部署与升级管理:
1. **`remote_update.sh`**:远程升级控制脚本,负责从主服务器向目标服务器传输镜像和部署脚本,并触发远端部署流程
2. **`container_update.sh`**:容器部署执行脚本,支持交互式和命令行两种模式,可单独执行进行本地容器部署
3. **`upload_to_nas.sh`**:镜像打包上传脚本,负责将容器镜像打包并上传至公司 NAS 网盘
### 背景
目前系统支持多种容器服务(Java、Redis、EMQX、Python、Nacos、Nginx)的部署与升级,需要区分两种平台环境:
- **新统一平台**:使用 `/data/` 目录结构
- **传统平台**:使用 `/var/www/` 目录结构
**现有功能**
- ✅ 远程升级指定服务器的容器版本 `[已实现]`
1. java容器远程更新(传统平台和新统一平台已验证通过)
2. uemqx容器(传统平台和新统一平台已验证通过)
3. uredis容器(传统平台和新统一平台已验证通过)
4. upython容器()
5. nacos容器(新统一平台已验证通过)
6. nginx容器(新统一平台已验证通过)
7. mysql容器(未验证)
- ✅ 自动校验目标服务器架构(仅支持 x86)`[已实现]`
- ✅ 自动递增容器编号避免命名冲突 `[已实现]`
- ✅ 支持多种容器类型的差异化部署 `[已实现]`
- ✅ 支持预设服务器和手动输入服务器信息(IP/端口/用户名/密码)`[已实现]`
- ✅ EMQX 文件同步(配置、数据、日志目录)`[已实现]`
- ✅ Nginx 文件同步(配置、HTML、证书目录)`[已实现]`
- ✅ 自动校验目标服务器是新统一平台目录还是传统平台目录,通过宿主机上的目录来判断,存在/data/services目录的是新统一平台,不存在的则是传统平台 `[已实现]`
- ✅ 自动校验目标服务器上的容器是否已更新 `[已实现]`
- ✅ 远程更新完成后需要将目标服务器上的镜像包清理 `[已实现]`
- ⏸️ Python 文件同步 `[功能保留,暂不启用]`
- ✅ 将主服务器上的容器镜像及部署脚本打包上传至网盘 `[已实现]`
- ✅ container_update.sh 支持单独执行(交互式模式)`[已实现]`
**待实现功能**
- ❌ 最终将sh脚本改为电脑命令行工具上可执行的脚本格式 `[待最后开发]`
---
## 🎯 功能实现总览
> 最后更新时间:2025-12-07
### 远程升级功能 (`remote_update.sh`)
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 服务器选择 | 支持预设服务器列表和手动输入(IP/端口/用户名/密码) | ✅ 已实现 |
| 自定义端口 | 支持自定义 SSH 端口(默认 22) | ✅ 已实现 |
| 架构校验 | 校验目标服务器是否为 x86 架构 | ✅ 已实现 |
| 镜像传输 | 自动传输镜像文件和部署脚本 | ✅ 已实现 |
| 容器停止 | 自动停止远端旧容器 | ✅ 已实现 |
| 平台识别 | 自动检测目标服务器平台类型(检测 /data/services 目录) | ✅ 已实现 |
| 版本校验 | 自动校验远端容器镜像版本是否已更新 | ✅ 已实现 |
| EMQX 同步 | 同步 EMQX 配置、数据、日志目录 | ✅ 已实现 |
| Python 同步 | 同步 Python 代码和配置 | ⏸️ 暂停 |
| Nginx 同步 | 同步 Nginx 配置、HTML、证书 | ✅ 已实现 |
| 容器编号 | 自动递增容器编号 | ✅ 已实现 |
| 远端执行 | 调用远端部署脚本 | ✅ 已实现 |
| 镜像清理 | 部署完成后自动清理远端镜像包和部署脚本 | ✅ 已实现 |
### 容器部署功能 (`container_update.sh`)
| 容器类型 | 新平台 | 传统平台 | 备注 |
|----------|--------|----------|------|
| Java (ujava) | ✅ | ✅ | 完整端口映射和目录挂载 |
| Redis (uredis) | ✅ | ✅ | 支持配置迁移和端口释放 |
| EMQX (uemqx) | ✅ | ✅ | 多端口映射,完整目录挂载 |
| Python (upython) | ✅ | ✅ | 容器部署正常,暂不同步文件 |
| Nacos (unacos) | ✅ | ❌ | 仅支持新平台,单机模式 |
| Nginx (unginx) | ✅ | ❌ | 仅支持新平台,完整目录挂载 |
### 已新增功能
| 功能 | 描述 | 状态 |
|------|------|------|
| 平台自动检测 | 通过检测 /data/services 目录自动识别新/传统平台 | ✅ 已实现 |
| 版本校验 | 自动校验远端容器镜像是否已更新,避免重复部署 | ✅ 已实现 |
| 镜像清理 | 部署完成后自动清理远端镜像包和部署脚本 | ✅ 已实现 |
| 镜像打包上传 | 将镜像目录打包并上传至公司 NAS 网盘 | ✅ 已实现 |
| 交互式部署 | container_update.sh 支持交互式模式,可单独执行 | ✅ 已实现 |
### 镜像上传功能 (`upload_to_nas.sh`)
| 功能模块 | 描述 | 状态 |
|----------|------|------|
| 目录打包 | 将指定目录压缩为 tar.gz 格式 | ✅ 已实现 |
| NAS 挂载 | 自动挂载公司 SMB 网盘 | ✅ 已实现 |
| 进度显示 | 打包和上传过程显示进度条 | ✅ 已实现 |
| 密码加密 | 网盘密码使用 base64 加密存储 | ✅ 已实现 |
### 待开发功能
| 功能 | 描述 | 状态 |
|------|------|------|
| 批量部署 | 一次升级多台服务器 | ❌ 待开发 |
| 版本管理 | 记录部署版本和时间 | ❌ 待开发 |
---
## 📝 业务操作流程
### 一、远程容器升级操作流程
以下是运维人员在主服务器上执行远程容器升级的完整操作步骤:
#### 步骤 1:启动远程升级脚本
在主服务器的镜像目录下,执行远程升级脚本:
```bash
cd /path/to/container-images
./remote_update.sh
```
#### 步骤 2:选择要升级的容器类型
脚本会显示可选的容器列表,根据提示输入对应编号:
```
可选择的容器:
[1] ujava - Java 服务容器
[2] uemqx - EMQX 消息队列容器
[3] uredis - Redis 缓存容器
[4] upython - Python 服务容器
[5] nacos - Nacos 注册中心容器
[6] unginx - Nginx 反向代理容器
请输入容器编号: _
```
#### 步骤 3:选择目标服务器
脚本提供两种方式选择目标服务器:
**方式一:选择预设服务器**
```
可选择的目标服务器:
[1] 标准版预定运维服务器 (192.168.5.48 root)
[2] 阿曼项目预定服务器 (192.168.5.67 root)
[3] 标准版预定运维测试发布服务器 (192.168.5.47 root)
[0] 手动输入服务器信息
请输入服务器编号: _
```
**方式二:手动输入服务器信息(输入 0)**
```
请输入目标服务器 IP 地址: 192.168.1.100
请输入 SSH 端口号 [默认 22]: 22
请输入登录用户名 [默认 root]: root
请输入登录密码: ********
```
#### 步骤 4:等待架构校验
脚本会自动连接目标服务器,校验其 CPU 架构是否为 x86 系列:
```
[2025-12-07 10:30:00] [INFO] 开始校验远端架构 (192.168.1.100)
[2025-12-07 10:30:02] [INFO] 远端架构 x86_64 校验通过
```
> ⚠️ 如果目标服务器为 ARM 架构,脚本将终止执行并提示错误。
#### 步骤 5:等待文件传输
脚本自动将镜像文件和部署脚本传输到目标服务器:
```
[2025-12-07 10:30:05] [INFO] 创建远端目录 /home/containerUpdate
[2025-12-07 10:30:06] [INFO] 传输镜像与部署脚本到远端目录
java1.8.0_472.tar.gz 100% 1.2GB 50.0MB/s 00:24
container_update.sh 100% 45KB 10.0MB/s 00:00
```
#### 步骤 6:确认旧容器处理
脚本会自动检测并停止远端的旧版本容器:
```
[2025-12-07 10:31:00] [INFO] 查找并停止远端旧容器
[2025-12-07 10:31:02] [INFO] 检测到旧容器 ujava2,执行停止
[2025-12-07 10:31:05] [INFO] ✅ 旧容器 ujava2 已停止
```
#### 步骤 7:自动检测平台类型
脚本会自动检测目标服务器的平台类型(通过检查 `/data/services` 目录是否存在):
```
[2025-12-07 10:30:10] [INFO] 自动检测目标服务器平台类型...
[2025-12-07 10:30:11] [INFO] ✅ 检测到 /data/services 目录存在,自动识别为新统一平台
```
| 检测结果 | 平台类型 | 目录结构 |
|----------|----------|----------|
| 存在 `/data/services` | 新统一平台 | `/data/` 目录 |
| 不存在 `/data/services` | 传统平台 | `/var/www/` 目录 |
> 💡 如果自动检测失败,脚本会回退到手动确认模式。
#### 步骤 8:等待资产同步(部分容器)
根据容器类型,脚本会同步相关配置文件和数据目录:
| 容器 | 同步内容 | 状态 | 备注 |
|------|----------|------|------|
| EMQX | 配置文件、数据目录、日志目录 | ✅ 已实现 | 自动备份远端现有目录 |
| Python | ~~代码目录、配置文件~~ | ⏸️ 暂停 | **暂不同步,功能保留待后续启用** |
| Nginx | 配置文件、HTML、证书目录 | ✅ 已实现 | 仅支持新统一平台 |
```
[2025-12-07 10:31:10] [INFO] 准备同步 EMQX 目录
[2025-12-07 10:31:12] [INFO] 远端目录已备份到 /data/middleware/emqx_backup_20251207_103112
[2025-12-07 10:31:15] [INFO] 使用 rsync 同步 EMQX 目录
[2025-12-07 10:31:30] [INFO] EMQX 目录同步完成
```
#### 步骤 9:等待远端部署执行
脚本在远端服务器上执行容器部署:
```
[2025-12-07 10:31:35] [INFO] 新容器名称确定为 ujava3
[2025-12-07 10:31:36] [INFO] 开始执行远端部署脚本
[2025-12-07 10:31:40] [INFO] 🔍 检查 Java 镜像是否存在...
[2025-12-07 10:31:42] [INFO] ❌ 镜像不存在,开始加载离线包...
[2025-12-07 10:32:00] [INFO] 🎉 镜像加载成功
[2025-12-07 10:32:02] [INFO] 🚀 正在启动 Java 容器: ujava3 ...
[2025-12-07 10:32:10] [INFO] ✅ Java 容器启动成功,等待初始化...
[2025-12-07 10:32:18] [INFO] 🎉 Java 服务部署完成!
```
#### 步骤 10:确认部署结果
部署完成后,脚本会输出容器状态信息:
```
NAMES STATUS PORTS
ujava3 Up 8 seconds 0.0.0.0:8085->8085/tcp, ...
[2025-12-07 10:32:20] [INFO] 远端部署执行完成
```
#### 步骤 11:自动清理远端镜像包
部署完成后,脚本会自动清理远端服务器上的镜像包和部署脚本:
```
[2025-12-07 10:32:21] [INFO] 开始清理远端镜像包...
[2025-12-07 10:32:22] [INFO] ✅ 远端镜像包清理完成
[2025-12-07 10:32:22] [INFO] 已删除镜像包: /home/containerUpdate/java1.8.0_472.tar.gz
[2025-12-07 10:32:22] [INFO] 已删除部署脚本: /home/containerUpdate/container_update.sh
[2025-12-07 10:32:22] [INFO] 远程升级流程结束
```
> 💡 清理过程会自动删除镜像包、部署脚本,如果目录为空还会删除整个目录。
---
### 二、镜像打包上传网盘操作流程
以下是将容器镜像打包并上传至公司 NAS 网盘的完整操作步骤:
#### 步骤 1:启动打包上传脚本
在镜像目录下执行上传脚本:
```bash
cd /path/to/container-images
./upload_to_nas.sh
```
或指定要打包的目录:
```bash
./upload_to_nas.sh /data/temp/container-images
```
#### 步骤 2:等待目录打包
脚本会自动将目录压缩为 tar.gz 格式,并显示打包进度:
```
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:00] [INFO] 容器镜像打包上传工具
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:00] [INFO] 源目录: /data/temp/container-images
[2025-12-08 10:30:00] [INFO] 打包文件: /tmp/container-images_20251208_103000.tar.gz
[2025-12-08 10:30:00] [INFO] 目标网盘: \\192.168.9.9\home\容器镜像包
[2025-12-08 10:30:00] [INFO] ==================================================================
[2025-12-08 10:30:01] [INFO] [步骤 1/3] 打包目录...
[2025-12-08 10:30:01] [INFO] 开始打包目录: /data/temp/container-images
1.2GiB 0:00:24 [50.0MiB/s] [================================>] 100%
[2025-12-08 10:30:25] [INFO] ✅ 打包完成,文件大小: 1.2G
```
#### 步骤 3:等待 NAS 挂载
脚本自动挂载公司 NAS 网盘:
```
[2025-12-08 10:30:26] [INFO] [步骤 2/3] 挂载 NAS...
[2025-12-08 10:30:26] [INFO] 正在挂载 NAS: //192.168.9.9/home
[2025-12-08 10:30:27] [INFO] ✅ NAS 挂载成功
```
#### 步骤 4:等待文件上传
上传过程会显示进度条:
```
[2025-12-08 10:30:28] [INFO] [步骤 3/3] 上传文件...
[2025-12-08 10:30:28] [INFO] 开始上传文件到 NAS...
[2025-12-08 10:30:28] [INFO] 源文件: /tmp/container-images_20251208_103000.tar.gz
[2025-12-08 10:30:28] [INFO] 目标: //192.168.9.9/home/容器镜像包/container-images_20251208_103000.tar.gz
1.2GiB 0:01:30 [13.6MiB/s] [================================>] 100%
[2025-12-08 10:32:00] [INFO] ✅ 上传完成!
[2025-12-08 10:32:00] [INFO] 文件路径: \\192.168.9.9\home\容器镜像包\container-images_20251208_103000.tar.gz
[2025-12-08 10:32:00] [INFO] 文件大小: 1.2G
```
#### 步骤 5:确认上传结果
```
[2025-12-08 10:32:01] [INFO] 清理临时文件...
[2025-12-08 10:32:01] [INFO] ==================================================================
[2025-12-08 10:32:01] [INFO] 🎉 全部完成!
[2025-12-08 10:32:01] [INFO] ==================================================================
```
#### 网盘信息
| 配置项 | 值 |
|--------|-----|
| 服务器地址 | `\\192.168.9.9\home` |
| 账号 | 陈泽键 |
| 上传目录 | 容器镜像包/ |
| 协议 | SMB/CIFS |
#### 命令行选项
```bash
# 显示帮助
./upload_to_nas.sh -h
# 指定打包文件名
./upload_to_nas.sh -n java_container_v6 /data/temp/java
# 指定上传子目录
./upload_to_nas.sh -d 测试包 /data/temp/test
```
---
### 三、本地容器部署操作流程(交互式模式)
`container_update.sh` 脚本支持两种运行模式,可单独在目标服务器上执行:
#### 交互式模式(推荐)
直接运行脚本,无需参数:
```bash
./container_update.sh
```
##### 步骤 1:选择容器类型
```
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] 容器部署工具 - 交互式模式
[2025-12-08 10:00:00] [INFO] ==================================================================
[2025-12-08 10:00:00] [INFO] 可选择的容器类型:
[1] ujava
[2] uemqx
[3] uredis
[4] upython
[5] unacos
[6] unginx
请输入容器编号 (1-6): 1
[2025-12-08 10:00:02] [INFO] 已选择容器类型: ujava
```
##### 步骤 2:自动检测平台类型
```
[2025-12-08 10:00:02] [INFO] 自动检测平台类型...
[2025-12-08 10:00:02] [INFO] ✅ 检测到 /data/services 目录,识别为新统一平台
当前识别为 new 平台,是否正确? (y/n) [默认 y]: y
```
##### 步骤 3:自动搜索镜像文件
```
[2025-12-08 10:00:03] [INFO] 搜索镜像文件: java1.8.0_472.tar.gz
[2025-12-08 10:00:03] [INFO] ✅ 找到镜像文件: /data/temp/java1.8.0_472.tar.gz
[2025-12-08 10:00:03] [INFO] 新容器名称: ujava3
```
##### 步骤 4:确认并执行部署
```
[2025-12-08 10:00:03] [INFO] ==================================================================
[2025-12-08 10:00:03] [INFO] 部署信息确认:
[2025-12-08 10:00:03] [INFO] 容器类型: ujava
[2025-12-08 10:00:03] [INFO] 容器名称: ujava3
[2025-12-08 10:00:03] [INFO] 平台类型: new
[2025-12-08 10:00:03] [INFO] 镜像文件: /data/temp/java1.8.0_472.tar.gz
[2025-12-08 10:00:03] [INFO] ==================================================================
确认开始部署? (y/n) [默认 y]: y
[2025-12-08 10:00:05] [INFO] 开始部署 ujava (容器: ujava3, 平台: new, 镜像: /data/temp/java1.8.0_472.tar.gz)
```
#### 命令行模式
适用于脚本调用或自动化场景:
```bash
# 基本用法
./container_update.sh ujava3 /data/temp/java1.8.0_472.tar.gz --new-platform
# 传统平台
./container_update.sh ujava3 /data/temp/java1.8.0_472.tar.gz
```
---
## 🔧 remote_update.sh 脚本详细分析
### 1. 脚本概述
| 属性 | 说明 |
|------|------|
| **文件位置** | `c:\Users\UBAINS\Desktop\新的容器镜像\remote_update.sh` |
| **脚本用途** | 远程升级指定服务器的容器版本 |
| **执行环境** | 主服务器(Linux/Bash 环境) |
### 2. 核心配置定义
#### 2.1 服务器信息配置
```bash
#!/usr/bin/env bash
# 远程升级指定服务器的容器版本脚本
# 实现内容:
# 1. 校验目标服务器架构是否为 x86
# 2. 将当前目录下的镜像文件与部署脚本同步至远端目录
# 3. 停止远端旧容器
# 4. 交互询问目标服务器是否属于新统一平台
# 5. 自动递增容器编号并执行远端部署脚本
set -euo pipefail
REMOTE_ARCH_ALLOW_REGEX='^(x86_64|amd64|i386|i686)$'
declare -A SERVER_IP SERVER_USER SERVER_PASS
SERVER_IP["1"]="192.168.5.48"
SERVER_USER["1"]="root"
SERVER_PASS["1"]="Ubains@123"
SERVER_IP["2"]="192.168.5.67"
SERVER_USER["2"]="root"
SERVER_PASS["2"]="Ubains@123"
SERVER_IP["3"]="192.168.5.47"
SERVER_USER["3"]="root"
SERVER_PASS["3"]="Ubains@1234"
declare -A SERVER_DESC
SERVER_DESC["1"]="标准版预定运维服务器"
SERVER_DESC["2"]="阿曼项目预定服务器"
SERVER_DESC["3"]="标准版预定运维测试发布服务器"
```
**配置说明**
| 配置项 | 说明 |
|--------|------|
| `REMOTE_ARCH_ALLOW_REGEX` | 允许的架构正则表达式(仅 x86 系列) |
| `SERVER_IP` | 目标服务器 IP 地址关联数组 |
| `SERVER_USER` | 目标服务器登录用户名 |
| `SERVER_PASS` | 目标服务器登录密码 |
| `SERVER_DESC` | 服务器描述信息 |
> 💡 **扩展功能**:除了预设服务器外,脚本支持手动输入目标服务器信息(输入编号 `0`),包括:
> - 服务器 IP 地址
> - SSH 端口号(默认 22)
> - 登录用户名(默认 root)
> - 登录密码
#### 2.2 容器与镜像映射配置
```bash
CONTAINER_OPTIONS=(ujava uemqx uredis upython nacos unginx)
declare -A CONTAINER_IMAGE
CONTAINER_IMAGE["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE["uemqx"]="uemqx5.8.4.tar.gz"
CONTAINER_IMAGE["uredis"]="redis8.2.2.tar.gz"
CONTAINER_IMAGE["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE["nacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE["unginx"]="/data/temp/nginx-1.29.3.tar.gz"
```
**容器映射说明**
| 容器名称 | 镜像文件 | 说明 | 状态 |
|----------|----------|------|------|
| `ujava` | `java1.8.0_472.tar.gz` | Java 服务容器 | ✅ |
| `uemqx` | `uemqx5.8.4.tar.gz` | EMQX 消息队列容器 | ✅ |
| `uredis` | `redis8.2.2.tar.gz` | Redis 缓存容器 | ✅ |
| `upython` | `python_v15.tar.gz` | Python 服务容器 | ✅ |
| `nacos` | `nacos-server-v2.5.2.tar.gz` | Nacos 注册中心容器 | ✅ |
| `unginx` | `/data/temp/nginx-1.29.3.tar.gz` | Nginx 反向代理容器(使用绝对路径) | ✅ |
### 3. 核心功能模块
#### 3.1 SSH/SCP 执行函数
```bash
ssh_exec() {
sshpass -p "${REMOTE_PASS}" ssh -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT} "$@"
}
scp_exec() {
sshpass -p "${REMOTE_PASS}" scp -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT} "$@"
}
```
**功能说明**
- 使用 `sshpass` 实现非交互式密码认证
- 设置 `StrictHostKeyChecking=no` 跳过主机密钥验证
- 配置 30 秒连接超时
#### 3.2 EMQX 资产同步函数
```bash
sync_emqx_assets() {
local remote_dir="$1"
local platform_label="$2"
if [[ ! -d "${LOCAL_EMQX_DIR}" ]]; then
log ERROR "本地 EMQX 目录不存在: ${LOCAL_EMQX_DIR}"
exit 11
fi
log INFO "准备同步 EMQX 目录 (${LOCAL_EMQX_DIR} -> ${REMOTE_HOST}:${remote_dir})"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p '${remote_dir}'"
# 备份远端现有目录
local backup_cmd
backup_cmd=$(cat <<'EOF'
set -e
TARGET_DIR="$1"
if [[ -d "${TARGET_DIR}" && -n "$(ls -A "${TARGET_DIR}")" ]]; then
ts=$(date +%Y%m%d_%H%M%S)
backup="${TARGET_DIR}_backup_${ts}"
cp -r "${TARGET_DIR}" "${backup}"
echo "${backup}"
fi
EOF
)
local backup_path
backup_path="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "bash -s '${remote_dir}'" <<<"${backup_cmd}" || true)"
if [[ -n "${backup_path}" ]]; then
log INFO "远端目录已备份到 ${backup_path}"
else
log INFO "远端目录为空或不存在,跳过备份"
fi
# 优先使用 rsync,备选 scp
if command -v rsync >/dev/null 2>&1; then
log INFO "使用 rsync 同步 EMQX 目录"
sshpass -p "${REMOTE_PASS}" rsync -az --delete \
-e "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT}" \
"${LOCAL_EMQX_DIR}/" "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"
else
log WARN "未检测到 rsync,改用 scp 拷贝 EMQX 目录"
scp_exec -r "${LOCAL_EMQX_DIR}/." "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"
fi
# 设置远端配置文件权限
log INFO "EMQX 目录同步完成,开始设置远端配置文件权限"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" \
"if [[ -d '${remote_dir}/config' ]]; then
cd '${remote_dir}/config' && ls *.conf >/dev/null 2>&1 && chmod +x *.conf || true
else
echo 'WARN: config 目录不存在'
fi"
log INFO "远端 EMQX 配置权限处理完成 (${platform_label} 平台)"
}
```
**同步流程**
1. 检查本地 EMQX 目录是否存在
2. 在远端创建目标目录
3. 备份远端现有目录(带时间戳)
4. 优先使用 `rsync` 同步(支持增量传输),备选 `scp`
5. 设置远端配置文件权限
#### 3.3 Python 资产同步函数
```bash
sync_python_assets() {
# ... 同步逻辑类似 EMQX
# 特殊处理:自动替换配置中的 IP 地址
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" \
"find '${remote_dir}' -type f -name '*.conf' -exec sed -i 's/192\\.168\\.5\\.44/${REMOTE_HOST}/g' {} +" || true
# 特殊处理:保留 authFile.uas 认证文件
}
```
**特殊处理**
- 自动将配置文件中的 `192.168.5.44` 替换为目标服务器 IP
- 从备份目录回拷 `authFile.uas` 认证文件
#### 3.4 Nginx 资产同步函数
```bash
sync_nginx_assets() {
# ... 同步逻辑
# 仅支持新统一平台
}
```
### 4. 主执行流程
#### 4.1 架构校验
```bash
log INFO "开始校验远端架构 (${REMOTE_HOST})"
ARCH_OUTPUT="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "uname -m" 2>&1)"
SSH_STATUS=$?
if [[ ${SSH_STATUS} -ne 0 ]]; then
log ERROR "无法获取远端架构,请检查网络或权限"
log ERROR "SSH 输出: ${ARCH_OUTPUT}"
exit 4
fi
ARCH="$(printf '%s' "${ARCH_OUTPUT}" | tr -d '\r' | awk 'NF {last=$0} END {print last}')"
if [[ ! "${ARCH}" =~ ${REMOTE_ARCH_ALLOW_REGEX} ]]; then
log ERROR "远端架构为 ${ARCH},非 x86,终止操作"
exit 5
fi
log INFO "远端架构 ${ARCH} 校验通过"
```
#### 4.2 镜像与脚本传输
```bash
log INFO "创建远端目录 ${REMOTE_DIR}"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p '${REMOTE_DIR}'"
log INFO "传输镜像与部署脚本到远端目录"
# nginx 使用绝对路径,需要特殊处理
if [[ "${IMAGE_FILE}" = /* ]]; then
# 绝对路径:先复制到临时位置,再传输
TEMP_IMAGE="/tmp/$(basename "${IMAGE_FILE}")"
cp "${IMAGE_FILE}" "${TEMP_IMAGE}"
scp_exec "${TEMP_IMAGE}" "${DEPLOY_SCRIPT}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
rm -f "${TEMP_IMAGE}"
# 在远端将镜像文件移动到正确位置
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" \
"mkdir -p /data/temp && mv '${REMOTE_DIR}/$(basename "${IMAGE_FILE}")' /data/temp/"
else
# 相对路径:直接传输
scp_exec "${IMAGE_FILE}" "${DEPLOY_SCRIPT}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
fi
```
#### 4.3 旧容器停止
```bash
log INFO "查找并停止远端旧容器"
CURRENT_CONTAINER=$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" \
"docker ps --format '{{.Names}}' | grep -E '^${CONTAINER_PREFIX}([0-9]+)?$' | sort -V | tail -n1" || true)
if [[ -n "${CURRENT_CONTAINER}" ]]; then
log INFO "检测到旧容器 ${CURRENT_CONTAINER},执行停止"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "docker stop '${CURRENT_CONTAINER}'"
else
log INFO "未找到匹配 ${CONTAINER_PREFIX}${CONTAINER_PREFIX}[数字] 的运行容器"
fi
```
#### 4.4 平台类型自动检测与资产同步
```bash
# 自动检测平台类型(通过检查 /data/services 目录是否存在)
log INFO "自动检测目标服务器平台类型..."
PLATFORM_CHECK_OUTPUT="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "[ -d /data/services ] && echo 'NEW_PLATFORM' || echo 'OLD_PLATFORM'" 2>/dev/null || echo "CHECK_FAILED")"
if [[ "${PLATFORM_CHECK_RESULT}" == "NEW_PLATFORM" ]]; then
PLATFORM_FLAG="--new-platform"
PLATFORM_TYPE="new"
log INFO "✅ 检测到 /data/services 目录存在,自动识别为新统一平台"
elif [[ "${PLATFORM_CHECK_RESULT}" == "OLD_PLATFORM" ]]; then
PLATFORM_FLAG=""
PLATFORM_TYPE="old"
log INFO "✅ 未检测到 /data/services 目录,自动识别为传统平台"
else
# 自动检测失败时回退到手动确认模式
read -rp "目标服务器是否为新统一平台? (y/n): " PLATFORM_INPUT
# ...
fi
if [[ "${CONTAINER_PREFIX}" == "uemqx" ]]; then
if [[ "${PLATFORM_TYPE}" == "new" ]]; then
REMOTE_EMQX_DIR="/data/middleware/emqx"
else
REMOTE_EMQX_DIR="/var/www/emqx"
fi
sync_emqx_assets "${REMOTE_EMQX_DIR}" "${PLATFORM_TYPE}"
fi
# ... Python 和 Nginx 类似处理
```
**平台目录对照表**
| 容器 | 新统一平台目录 | 传统平台目录 |
|------|----------------|--------------|
| EMQX | `/data/middleware/emqx` | `/var/www/emqx` |
| Python | `/data/services/api/python-cmdb` | `/var/www/html` |
| Nginx | `/data/middleware/nginx` | 仅支持新平台 |
#### 4.5 容器编号递增与远端部署执行
```bash
if [[ -n "${CURRENT_CONTAINER}" ]]; then
LAST_NUM="${CURRENT_CONTAINER##*[!0-9]}"
if [[ -z "${LAST_NUM}" ]]; then
LAST_NUM=0
fi
NEXT_NUM=$((LAST_NUM + 1))
else
NEXT_NUM=1
fi
NEW_CONTAINER="${CONTAINER_PREFIX}${NEXT_NUM}"
log INFO "新容器名称确定为 ${NEW_CONTAINER}"
# 构建远端执行命令
REMOTE_CMD=$(cat <<EOF
set -euo pipefail
cd '${REMOTE_DIR}'
sed -i 's/\r$//' '${DEPLOY_NAME}' 2>/dev/null || true
chmod +x '${DEPLOY_NAME}'
./'${DEPLOY_NAME}' '${NEW_CONTAINER}' '${REMOTE_IMAGE_PATH}'${PLATFORM_TAIL}
EOF
)
log INFO "开始执行远端部署脚本"
sshpass -p "${REMOTE_PASS}" ssh -tt \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=${SSH_TIMEOUT} \
"${REMOTE_USER}@${REMOTE_HOST}" "${REMOTE_CMD}"
```
---
## 🔧 container_update.sh 脚本详细分析
### 1. 脚本概述
| 属性 | 说明 |
|------|------|
| **文件位置** | `c:\Users\UBAINS\Desktop\新的容器镜像\container_update.sh` |
| **脚本用途** | 在目标服务器上执行容器部署 |
| **执行环境** | 目标服务器(由 remote_update.sh 触发) |
### 2. 命令行参数解析
```bash
main() {
if [[ $# -lt 2 ]]; then
usage
exit 1
fi
local container_name="$1"
local image_archive="$2"
shift 2
local platform_type="old"
while [[ $# -gt 0 ]]; do
case "$1" in
--new-platform)
platform_type="new"
;;
*)
usage
exit 1
;;
esac
shift
done
# 容器前缀解析
local container_prefix
if [[ "${container_name}" =~ ^([a-zA-Z]+)[0-9]+$ ]]; then
container_prefix="${BASH_REMATCH[1]}"
else
container_prefix="${container_name}"
fi
}
```
**参数说明**
| 参数 | 必选 | 说明 |
|------|------|------|
| `容器名称` | ✅ | 如 `ujava3`,用于指定 docker 容器名称 |
| `镜像包` | ✅ | 离线镜像 tar 包路径 |
| `--new-platform` | ❌ | 目标服务器为新统一平台时添加 |
### 3. 部署函数路由
```bash
local deploy_fn
case "${container_prefix}" in
ujava)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="java_newplatform_x86"
else
deploy_fn="java_oldplatform_x86"
fi
;;
uredis)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="redis_newplatform_x86"
else
deploy_fn="redis_oldplatform_x86"
fi
;;
uemqx)
# ...
;;
upython)
# ...
;;
unacos)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="nacos_newplatform_x86"
else
log ERROR "Nacos 服务仅支持新统一平台,请使用 --new-platform 参数"
exit 4
fi
;;
unginx)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="nginx_newplatform_x86"
else
log ERROR "Nginx 服务仅支持新统一平台,请使用 --new-platform 参数"
exit 4
fi
;;
esac
```
**部署函数映射表**
| 容器前缀 | 新平台函数 | 传统平台函数 | 实现状态 |
|----------|------------|--------------|----------|
| `ujava` | `java_newplatform_x86` | `java_oldplatform_x86` | ✅ 已实现 |
| `uredis` | `redis_newplatform_x86` | `redis_oldplatform_x86` | ✅ 已实现 |
| `uemqx` | `emqx_newplatform_x86` | `emqx_oldplatform_x86` | ✅ 已实现 |
| `upython` | `python_newplatform_x86` | `python_oldplatform_x86` | ✅ 已实现 (无文件同步) |
| `unacos` | `nacos_newplatform_x86` | ❌ 不支持 | ✅ 已实现 |
| `unginx` | `nginx_newplatform_x86` | ❌ 不支持 | ✅ 已实现 |
### 4. 端口释放函数
```bash
release_port_if_busy() {
local target_port="$1"
local publish_info
publish_info="$($sudoset lsof -i TCP:${target_port} -sTCP:LISTEN -t || true)"
if [[ -z "${publish_info}" ]]; then
log "INFO" "端口 ${target_port} 当前空闲"
return 0
fi
log "WARN" "检测到端口 ${target_port} 被占用,尝试释放"
while read -r pid; do
[[ -z "${pid}" ]] && continue
if $sudoset kill -9 "${pid}"; then
log "INFO" "已结束占用端口 ${target_port} 的进程 PID=${pid}"
else
log "ERROR" "结束进程 ${pid} 失败"
return 1
fi
done <<< "${publish_info}"
}
```
### 5. Java 容器部署函数详解
#### 5.1 新统一平台部署
```bash
java_newplatform_x86() {
# 配置项
local host_api="/data/services/api"
local host_web="/data/services/web"
local host_nginx_log="/data/middleware/nginx/nginx_log"
local host_fdfs_data="/var/fdfs/storage/data"
local port_args=(
"-p 8085:8085"
"-p 8993:8993" "-p 8994:8994" "-p 8995:8995"
"-p 8999:8999"
"-p 8719:8719" "-p 8720:8720"
"-p 9204:9204" "-p 9200:9200" "-p 9201:9201"
"-p 9905:9905" "-p 9911:9911" "-p 9908:9908"
"-p 9906:9906" "-p 9907:9907" "-p 9909:9909" "-p 9910:9910"
"-p 30880:30880" "-p 30881:30881" "-p 30882:30882"
"-p 30883:30883" "-p 30884:30884"
)
# 部署流程
# 1. 检查旧容器并停止
# 2. 检查/加载镜像
# 3. 启动新容器
# 4. 验证容器运行状态
}
```
**新平台 Java 容器端口映射**
| 端口 | 用途 |
|------|------|
| 8085 | 主 API 服务 |
| 8993-8995 | 辅助服务端口 |
| 8999 | 业务服务端口 |
| 8719-8720 | Sentinel 相关 |
| 9200-9204 | 内部通信端口 |
| 9905-9911 | 业务服务端口组 |
| 30880-30884 | 扩展服务端口 |
**挂载目录**
| 宿主机目录 | 容器目录 | 说明 |
|------------|----------|------|
| `/data/services/api` | `/var/www/java/api` | API 代码目录 |
| `/data/services/web` | `/var/www/java/web` | Web 资源目录 |
| `/data/middleware/nginx/nginx_log` | `/usr/local/nginx/logs` | Nginx 日志 |
| `/var/fdfs/storage/data` | `/var/fdfs/storage/data` | FastDFS 存储 |
#### 5.2 传统平台部署
```bash
java_oldplatform_x86() {
# 配置项
local host_java="/var/www/java"
local host_nginx_log="/var/www/java/nginx-conf.d/nginx_log"
local port_args=(
"-p554:554" # RTSP
"-p1935:1935" # RTMP
"-p10000:10000"
"-p2333:2333" "-p2334:2334"
"-p8997:8997" "-p8998:8998"
"-p8079:8079"
"-p443:443" # HTTPS
"-p8080:8080" # HTTP
"-p8085:8085" "-p8086:8086" "-p8087:8087" "-p8088:8088"
"-p8889:8889"
"-p8999:8999"
)
}
```
### 6. Redis 容器部署函数
```bash
redis_oldplatform_x86() {
# 特殊处理:备份旧配置并重命名
cp "${old_redis_conf}" "${old_redis_conf}bak"
mv "${old_redis_conf}" "${new_redis_conf}"
# 使用 host 网络模式
$sudoset docker run -itd \
--network host \
-v "${new_redis_conf}:/etc/redis/redis.conf" \
# ...
}
redis_newplatform_x86() {
# 配置路径差异
local redis_conf_host="/data/middleware/redis/config/redis.conf"
local redis_data_host="/data/middleware/redis/data"
}
```
**Redis 配置对比**
| 配置项 | 新平台 | 传统平台 |
|--------|--------|----------|
| 配置文件 | `/data/middleware/redis/config/redis.conf` | `/var/www/redis/redis-8.2.2.conf` |
| 数据目录 | `/data/middleware/redis/data` | `/var/www/redis/data` |
| 网络模式 | `host` | `host` |
### 7. EMQX 容器部署函数
```bash
emqx_newplatform_x86() {
# 端口映射
local ports=(1883 8083 8084 8883 18083)
# 目录挂载
-v "${emqx_config_host}:/opt/emqx/etc" \
-v "${emqx_data_host}:/opt/emqx/data" \
-v "${emqx_log_host}:/opt/emqx/log" \
}
```
**EMQX 端口说明**
| 端口 | 协议 | 用途 |
|------|------|------|
| 1883 | MQTT | TCP 连接 |
| 8083 | MQTT/WS | WebSocket 连接 |
| 8084 | MQTT/WSS | 安全 WebSocket |
| 8883 | MQTTS | 安全 MQTT |
| 18083 | HTTP | Dashboard 管理界面 |
### 8. Python 容器部署函数
```bash
python_newplatform_x86() {
local host_app_dir="/data/services/api/python-cmdb"
local ports=(-p 8000:8000 -p 8002:8002)
}
python_oldplatform_x86() {
local host_html_dir="/var/www/html"
local ports=(-p 8000:8000 -p 8002:8002 -p 8443:8443 -p 9009:9009)
# 特殊处理:容器启动后触发应用
$sudoset docker exec "${container_name}" /bin/bash -c \
"cd /var/www/html && nohup python manage.py runserver 0.0.0.0:8000 >/tmp/python_manage.log 2>&1 &" || true
}
```
### 9. Nacos 容器部署函数
```bash
nacos_newplatform_x86() {
# 单机模式部署
-e MODE=standalone \
-v "${host_data_dir}:/home/nacos" \
# 端口
-p 8848:8848 \ # HTTP API
-p 9848:9848 \ # gRPC
}
```
### 10. Nginx 容器部署函数
```bash
nginx_newplatform_x86() {
# 目录预检查与创建
local required_dirs=(
"/data/middleware/nginx/log"
"/data/middleware/nginx/data/cache"
"/data/middleware/nginx/data/html"
"/data/middleware/nginx/config"
"/data/services/web"
"/data/security/nginx_cert"
)
# 权限设置
$sudoset chown -R 1000:1000 /data/middleware/nginx/log
$sudoset chown -R 1000:1000 /data/middleware/nginx/data/cache
$sudoset chmod -R 700 /data/middleware/nginx/data/cache
}
```
---
## 🔄 完整部署流程图
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 主服务器执行 remote_update.sh │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤1] 交互选择 │
│ - 选择容器类型 (ujava/uemqx/uredis/upython/nacos/unginx) │
│ - 选择目标服务器: │
│ • 方式一:选择预设服务器 (1/2/3) │
│ • 方式二:输入 0 手动填写服务器信息 │
│ → 输入服务器 IP 地址 │
│ → 输入 SSH 端口号(默认 22) │
│ → 输入登录用户名和密码 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤2] 架构校验 │
│ - SSH 连接目标服务器(支持自定义端口) │
│ - 执行 uname -m 获取架构 │
│ - 校验是否为 x86_64/amd64/i386/i686 │
│ - 非 x86 架构则终止 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤3] 文件传输 │
│ - 创建远端目录 /home/containerUpdate │
│ - SCP 传输镜像文件 (*.tar.gz) │
│ - SCP 传输部署脚本 (container_update.sh) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤4] 停止旧容器 │
│ - docker ps 查找 ${container_prefix}[数字] 格式的容器 │
│ - docker stop 停止最新版本容器 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤5] 平台类型自动检测 ✅ 已实现 │
│ - 检测远端 /data/services 目录是否存在 │
│ - 存在 → 自动识别为新统一平台,使用 /data/ 目录结构 │
│ - 不存在 → 自动识别为传统平台,使用 /var/www/ 目录结构 │
│ - 检测失败 → 回退到手动确认模式 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤6] 资产同步 (根据容器类型) [部分已实现] │
│ - EMQX: 同步配置文件、数据目录、日志目录 ✅ 已实现 │
│ - Python: 【暂不同步】代码保留,后续启用 ⏸️ 暂停 │
│ - Nginx: 同步配置、HTML、证书目录 ✅ 已实现 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤7] 计算新容器名称 │
│ - 获取当前最新容器编号 │
│ - 编号 + 1 = 新容器名称 │
│ - 例: ujava2 → ujava3 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤8] 执行远端部署 │
│ - SSH 连接目标服务器 │
│ - 处理脚本换行符 (dos2unix) │
│ - 执行: ./container_update.sh ${新容器名} ${镜像路径} [--new-platform] │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 目标服务器执行 container_update.sh │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤1] 解析参数,确定部署函数 │
│ - 解析容器前缀 (ujava/uredis/...) │
│ - 根据平台类型选择对应函数 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤2] 检查/停止同名容器 │
│ - docker ps -a 检查容器状态 │
│ - 运行中 → docker stop │
│ - 已存在但未运行 → 报错退出 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤3] 检查/加载镜像 │
│ - docker images 检查镜像是否存在 │
│ - 不存在 → docker load -i ${镜像包} │
│ - docker tag 设置镜像标签 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤4] 端口检查与释放 │
│ - lsof 检查端口占用 │
│ - kill -9 释放占用端口的进程 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤5] 启动新容器 │
│ - docker run -d 启动容器 │
│ - 配置端口映射、目录挂载、重启策略等 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [子步骤6] 验证部署结果 │
│ - sleep 等待容器初始化 │
│ - docker ps 检查容器是否运行 │
│ - 输出容器状态信息 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤9] 清理远端镜像包 ✅ 已实现 │
│ - 删除远端镜像包文件 │
│ - 删除远端部署脚本 │
│ - 如目录为空则删除目录 │
└─────────────────────────────────────────────────────────────────────────┘
[部署完成]
```
---
## 📦 镜像打包上传至网盘功能
### 1. 功能概述
`upload_to_nas.sh` 脚本实现将容器镜像目录打包并上传至公司 NAS 网盘的功能。
### 2. 脚本配置
```bash
#!/usr/bin/env bash
# 容器镜像打包上传至公司网盘脚本
# 网盘配置
NAS_SERVER="192.168.9.9"
NAS_SHARE="home"
NAS_USER="陈泽键"
# 密码使用 base64 加密存储
NAS_PASS_ENCRYPTED="Mm01ZUwuTUA="
NAS_UPLOAD_DIR="容器镜像包"
```
### 3. 核心功能模块
#### 3.1 目录打包
```bash
pack_current_dir() {
# 使用 pv 显示打包进度
tar -cf - -C "$(dirname "${source_dir}")" "$(basename "${source_dir}")" | \
pv -s "${dir_size}" -p -t -e -r | \
gzip > "${pack_name}"
}
```
#### 3.2 NAS 挂载
```bash
mount_nas() {
# 解密密码
nas_pass="$(echo "${NAS_PASS_ENCRYPTED}" | base64 -d)"
# 挂载 SMB/CIFS 共享
sudo mount -t cifs "//${NAS_SERVER}/${NAS_SHARE}" "${NAS_MOUNT_POINT}" \
-o "username=${NAS_USER},password=${nas_pass},iocharset=utf8,vers=2.0"
}
```
#### 3.3 文件上传(带进度显示)
```bash
upload_to_nas() {
# 使用 pv 显示上传进度
pv -p -t -e -r "${source_file}" | sudo tee "${target_file}" > /dev/null
}
```
### 4. 使用方法
```bash
# 打包并上传当前目录
./upload_to_nas.sh
# 打包并上传指定目录
./upload_to_nas.sh /data/temp/container-images
# 指定打包文件名
./upload_to_nas.sh -n java_container_v6 /data/temp/java
# 指定上传子目录
./upload_to_nas.sh -d 测试包 /data/temp/test
```
### 5. 依赖要求
| 依赖 | 用途 | 必需 |
|------|------|------|
| cifs-utils | SMB/CIFS 挂载支持 | ✅ 是 |
| pv | 进度条显示 | ❌ 否(无则不显示进度) |
安装依赖:
```bash
# CentOS/RHEL
yum install -y cifs-utils pv
# Ubuntu/Debian
apt install -y cifs-utils pv
```
---
## 🔧 container_update.sh 交互式模式
### 1. 功能概述
`container_update.sh` 现支持两种运行模式:
| 模式 | 触发方式 | 适用场景 |
|------|----------|----------|
| 交互式模式 | 无参数运行 | 手动在目标服务器执行部署 |
| 命令行模式 | 带参数运行 | 远程调用或自动化脚本 |
### 2. 交互式模式流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 执行 ./container_update.sh │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤1] 选择容器类型 │
│ - 显示可选容器列表 (1-6) │
│ - 用户输入编号选择 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤2] 自动检测平台类型 │
│ - 检测本地 /data/services 目录 │
│ - 自动识别新/传统平台 │
│ - 用户可手动修正 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤3] 搜索镜像文件 │
│ - 在常用路径搜索对应镜像 │
│ - 未找到则提示手动输入 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤4] 确定容器名称 │
│ - 检测现有同类型容器 │
│ - 自动递增编号 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ [步骤5] 确认并执行部署 │
│ - 显示部署信息摘要 │
│ - 用户确认后执行 │
└─────────────────────────────────────────────────────────────────────────┘
```
### 3. 镜像文件搜索路径
脚本会按以下顺序搜索镜像文件:
1. 当前目录 `./`
2. `/data/temp/`
3. `/home/containerUpdate/`
### 4. 容器与镜像映射
```bash
declare -A CONTAINER_IMAGE_MAP
CONTAINER_IMAGE_MAP["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE_MAP["uemqx"]="uemqx5.8.4.tar.gz"
CONTAINER_IMAGE_MAP["uredis"]="redis8.2.2.tar.gz"
CONTAINER_IMAGE_MAP["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE_MAP["unacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE_MAP["unginx"]="nginx-1.29.3.tar.gz"
```
---
## 📦 待实现功能(已废弃的旧方案参考)
以下是早期规划的方案,已被 `upload_to_nas.sh` 替代:
### 旧方案:package_upload.sh
```bash
#!/usr/bin/env bash
# 镜像打包上传脚本(旧方案,已废弃)
# 配置区
PACKAGE_DIR="/data/temp/packages"
UPLOAD_REMOTE="webdav:ubains-packages" # rclone 远程配置名称
# 容器与镜像映射
declare -A CONTAINER_IMAGE
CONTAINER_IMAGE["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE["uemqx"]="uemqx5.8.4.tar.gz"
CONTAINER_IMAGE["uredis"]="redis8.2.2.tar.gz"
CONTAINER_IMAGE["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE["nacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE["unginx"]="/data/temp/nginx-1.29.3.tar.gz"
# 打包函数
package_container() {
local container_type="$1"
local timestamp=$(date +%Y%m%d_%H%M%S)
local package_name="${container_type}_${timestamp}.tar.gz"
# 创建临时打包目录
local temp_dir=$(mktemp -d)
# 复制镜像文件
local image_file="${CONTAINER_IMAGE[${container_type}]}"
if [[ "${image_file}" = /* ]]; then
cp "${image_file}" "${temp_dir}/"
else
cp "./${image_file}" "${temp_dir}/"
fi
# 复制部署脚本
cp "./container_update.sh" "${temp_dir}/"
cp "./remote_update.sh" "${temp_dir}/"
# 添加版本说明文件
cat > "${temp_dir}/VERSION.txt" <<EOF
Container: ${container_type}
Image: ${image_file}
Package Time: ${timestamp}
EOF
# 打包
mkdir -p "${PACKAGE_DIR}"
tar -czvf "${PACKAGE_DIR}/${package_name}" -C "${temp_dir}" .
# 清理临时目录
rm -rf "${temp_dir}"
echo "${PACKAGE_DIR}/${package_name}"
}
# 上传函数
upload_to_cloud() {
local package_path="$1"
if command -v rclone >/dev/null 2>&1; then
rclone copy "${package_path}" "${UPLOAD_REMOTE}/"
else
log "ERROR" "未安装 rclone,无法上传"
return 1
fi
}
# 主流程
main() {
# 选择容器类型
# 打包
# 上传
# 清理本地打包文件(可选)
}
main "$@"
```
#### 2.2 支持的上传方式
| 上传方式 | 工具 | 配置要求 |
|----------|------|----------|
| WebDAV | rclone | 配置 WebDAV 远程 |
| 阿里云 OSS | rclone/ossutil | 配置 AccessKey |
| 腾讯云 COS | rclone/coscmd | 配置 SecretId |
| 百度网盘 | bypy | OAuth 授权 |
| FTP/SFTP | lftp/scp | 配置账号密码 |
---
## ⚠️ 注意事项与安全建议
### 1. 安全风险
| 风险项 | 当前状态 | 建议改进 |
|--------|----------|----------|
| 明文密码存储 | ⚠️ 脚本内硬编码密码 | 使用 SSH 密钥认证或加密配置文件 |
| `StrictHostKeyChecking=no` | ⚠️ 跳过主机验证 | 首次连接后保存 known_hosts |
| 端口释放 `kill -9` | ⚠️ 强制杀进程 | 增加进程确认机制 |
### 2. 运维建议
1. **日志记录**:建议将所有操作日志写入文件,便于问题排查
2. **回滚机制**:保留备份目录,支持一键回滚
3. **健康检查**:部署后增加服务健康检查(HTTP 探针/TCP 探针)
4. **通知机制**:部署完成后发送通知(钉钉/企业微信/邮件)
### 3. 扩展建议
1. **批量部署**:支持一次性升级多台服务器
2. **版本管理**:记录每次部署的镜像版本、部署时间
3. **配置分离**:将服务器信息、镜像映射等配置抽取到独立配置文件
---
## 📝 附录
### A. 退出码说明
| 退出码 | 含义 |
|--------|------|
| 0 | 成功 |
| 1 | 参数错误 |
| 2 | 镜像文件不存在 |
| 3 | 部署脚本不存在 |
| 4 | SSH 连接失败 |
| 5 | 架构不匹配 |
| 6 | 平台类型输入无效 |
| 7 | 服务器编号不存在 |
| 8 | sshpass 未安装 |
| 9 | 容器编号无效 |
| 10 | 容器未配置镜像映射 |
| 11-19 | 资产同步相关错误 |
| 20 | Nginx 仅支持新平台 |
### B. 常用命令速查
```bash
# 远程升级 Java 容器到服务器1(新平台)
./remote_update.sh
# 选择 1 (ujava) → 选择 1 (服务器) → 输入 y (新平台)
# 手动执行部署脚本
./container_update.sh ujava3 /home/containerUpdate/java1.8.0_472.tar.gz --new-platform
# 查看容器状态
docker ps --filter "name=ujava"
# 查看容器日志
docker logs -f ujava3
```
#!/usr/bin/env bash
set -euo pipefail
log() {
local level="$1"; shift
printf '[%(%F %T)T] [%s] %s\n' -1 "${level}" "$*"
}
usage() {
cat <<'EOF'
用法:
container_update.sh [选项] [容器名称] [镜像包]
运行模式:
1. 交互式模式(无参数):
./container_update.sh
脚本会引导选择容器类型、平台类型,自动检测镜像文件
2. 命令行模式(带参数):
./container_update.sh <容器名称> <镜像包> [--new-platform]
参数说明:
容器名称 如 ujava3,用于指定 docker 容器名称
镜像包 离线镜像 tar 包路径,若为相对路径则基于当前目录
--new-platform 目标服务器为新统一平台时添加该参数
-i, --interactive 强制进入交互式模式
脚本会根据容器前缀与平台类型调用对应部署函数,并自动使用提供的镜像包执行 docker load。
EOF
}
# 容器与镜像映射配置
declare -A CONTAINER_IMAGE_MAP
CONTAINER_IMAGE_MAP["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE_MAP["uemqx"]="uemqx5.8.4.tar.gz"
CONTAINER_IMAGE_MAP["uredis"]="redis8.2.2.tar.gz"
CONTAINER_IMAGE_MAP["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE_MAP["unacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE_MAP["unginx"]="nginx-1.29.3.tar.gz"
# 交互式模式
interactive_mode() {
log "INFO" "=================================================================="
log "INFO" "容器部署工具 - 交互式模式"
log "INFO" "=================================================================="
# 选择容器类型
local container_options=(ujava uemqx uredis upython unacos unginx)
echo ""
log "INFO" "可选择的容器类型:"
for idx in "${!container_options[@]}"; do
local option_index=$((idx + 1))
printf " [%s] %s\n" "${option_index}" "${container_options[idx]}"
done
echo ""
local container_key
read -rp "请输入容器编号 (1-6): " container_key
if ! [[ "${container_key}" =~ ^[1-6]$ ]]; then
log "ERROR" "容器编号无效,请输入 1-6"
exit 1
fi
local container_prefix="${container_options[$((container_key - 1))]}"
log "INFO" "已选择容器类型: ${container_prefix}"
# 自动检测平台类型
local platform_type="old"
log "INFO" "自动检测平台类型..."
if [[ -d "/data/services" ]]; then
platform_type="new"
log "INFO" "✅ 检测到 /data/services 目录,识别为新统一平台"
else
platform_type="old"
log "INFO" "✅ 未检测到 /data/services 目录,识别为传统平台"
fi
# 确认平台类型
echo ""
read -rp "当前识别为 ${platform_type} 平台,是否正确? (y/n) [默认 y]: " platform_confirm
platform_confirm="${platform_confirm:-y}"
if [[ "${platform_confirm}" =~ ^[nN]$ ]]; then
if [[ "${platform_type}" == "new" ]]; then
platform_type="old"
else
platform_type="new"
fi
log "INFO" "已切换为 ${platform_type} 平台"
fi
# 检查 Nacos 和 Nginx 平台限制
if [[ "${container_prefix}" == "unacos" || "${container_prefix}" == "unginx" ]]; then
if [[ "${platform_type}" == "old" ]]; then
log "ERROR" "${container_prefix} 仅支持新统一平台"
exit 4
fi
fi
# 查找镜像文件
local image_file="${CONTAINER_IMAGE_MAP[${container_prefix}]}"
local image_path=""
# 搜索镜像文件的可能位置
local search_paths=(
"$(pwd)/${image_file}"
"/data/temp/${image_file}"
"/home/containerUpdate/${image_file}"
"$(pwd)"
)
log "INFO" "搜索镜像文件: ${image_file}"
for path in "${search_paths[@]}"; do
if [[ -f "${path}" ]]; then
image_path="${path}"
break
elif [[ -d "${path}" ]]; then
# 在目录中搜索
local found_file
found_file=$(find "${path}" -maxdepth 1 -name "${image_file}" -type f 2>/dev/null | head -n1)
if [[ -n "${found_file}" ]]; then
image_path="${found_file}"
break
fi
fi
done
if [[ -z "${image_path}" || ! -f "${image_path}" ]]; then
log "WARN" "未找到默认镜像文件: ${image_file}"
echo ""
read -rp "请输入镜像文件路径: " image_path
if [[ ! -f "${image_path}" ]]; then
log "ERROR" "镜像文件不存在: ${image_path}"
exit 3
fi
else
log "INFO" "✅ 找到镜像文件: ${image_path}"
fi
# 确定容器名称(自动递增编号)
local existing_containers
existing_containers=$(docker ps -a --format '{{.Names}}' | grep -E "^${container_prefix}[0-9]*$" | sort -V | tail -n1 || true)
local new_container_name
if [[ -n "${existing_containers}" ]]; then
local last_num="${existing_containers##*[!0-9]}"
if [[ -z "${last_num}" ]]; then
last_num=0
fi
local next_num=$((last_num + 1))
new_container_name="${container_prefix}${next_num}"
else
new_container_name="${container_prefix}1"
fi
log "INFO" "新容器名称: ${new_container_name}"
# 确认部署
echo ""
log "INFO" "=================================================================="
log "INFO" "部署信息确认:"
log "INFO" " 容器类型: ${container_prefix}"
log "INFO" " 容器名称: ${new_container_name}"
log "INFO" " 平台类型: ${platform_type}"
log "INFO" " 镜像文件: ${image_path}"
log "INFO" "=================================================================="
echo ""
read -rp "确认开始部署? (y/n) [默认 y]: " deploy_confirm
deploy_confirm="${deploy_confirm:-y}"
if [[ ! "${deploy_confirm}" =~ ^[yY]$ ]]; then
log "INFO" "用户取消部署"
exit 0
fi
# 设置全局变量并执行部署
DEPLOY_CONTAINER_NAME="${new_container_name}"
DEPLOY_IMAGE_TAR="${image_path}"
# 调用部署函数
local deploy_fn
case "${container_prefix}" in
ujava)
deploy_fn="java_${platform_type}platform_x86"
;;
uredis)
deploy_fn="redis_${platform_type}platform_x86"
;;
uemqx)
deploy_fn="emqx_${platform_type}platform_x86"
;;
upython)
deploy_fn="python_${platform_type}platform_x86"
;;
unacos)
deploy_fn="nacos_newplatform_x86"
;;
unginx)
deploy_fn="nginx_newplatform_x86"
;;
esac
log "INFO" "开始部署 ${container_prefix} (容器: ${DEPLOY_CONTAINER_NAME}, 平台: ${platform_type}, 镜像: ${DEPLOY_IMAGE_TAR})"
"${deploy_fn}"
log "INFO" "部署流程结束"
}
main() {
# 检查是否为交互式模式
if [[ $# -eq 0 ]] || [[ "$1" == "-i" ]] || [[ "$1" == "--interactive" ]]; then
interactive_mode
return 0
fi
# 命令行模式
if [[ $# -lt 2 ]]; then
usage
exit 1
fi
local container_name="$1"
local image_archive="$2"
shift 2
local platform_type="old"
while [[ $# -gt 0 ]]; do
case "$1" in
--new-platform)
platform_type="new"
;;
*)
usage
exit 1
;;
esac
shift
done
if [[ -z "${container_name}" ]]; then
log ERROR "容器名称不能为空"
exit 2
fi
local image_tar_path
if [[ "${image_archive}" = /* ]]; then
image_tar_path="${image_archive}"
else
image_tar_path="$(pwd)/${image_archive}"
fi
if [[ ! -f "${image_tar_path}" ]]; then
log ERROR "镜像文件不存在: ${image_tar_path}"
exit 3
fi
local container_prefix
if [[ "${container_name}" =~ ^([a-zA-Z]+)[0-9]+$ ]]; then
container_prefix="${BASH_REMATCH[1]}"
else
container_prefix="${container_name}"
fi
local deploy_fn
case "${container_prefix}" in
ujava)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="java_newplatform_x86"
else
deploy_fn="java_oldplatform_x86"
fi
;;
uredis)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="redis_newplatform_x86"
else
deploy_fn="redis_oldplatform_x86"
fi
;;
uemqx)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="emqx_newplatform_x86"
else
deploy_fn="emqx_oldplatform_x86"
fi
;;
upython)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="python_newplatform_x86"
else
deploy_fn="python_oldplatform_x86"
fi
;;
nacos|unacos)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="nacos_newplatform_x86"
else
log ERROR "Nacos 服务仅支持新统一平台,请使用 --new-platform 参数"
exit 4
fi
;;
unginx)
if [[ "${platform_type}" == "new" ]]; then
deploy_fn="nginx_newplatform_x86"
else
log ERROR "Nginx 服务仅支持新统一平台,请使用 --new-platform 参数"
exit 4
fi
;;
*)
log ERROR "容器 ${container_prefix} 暂未实现部署逻辑"
exit 4
;;
esac
DEPLOY_CONTAINER_NAME="${container_name}"
DEPLOY_IMAGE_TAR="${image_tar_path}"
log INFO "开始部署 ${container_prefix} (容器: ${DEPLOY_CONTAINER_NAME}, 平台: ${platform_type}, 镜像: ${DEPLOY_IMAGE_TAR})"
"${deploy_fn}"
log INFO "部署流程结束"
}
release_port_if_busy() {
local target_port="$1"
local publish_info
publish_info="$($sudoset lsof -i TCP:${target_port} -sTCP:LISTEN -t || true)"
if [[ -z "${publish_info}" ]]; then
log "INFO" "端口 ${target_port} 当前空闲"
return 0
fi
log "WARN" "检测到端口 ${target_port} 被占用,尝试释放"
while read -r pid; do
[[ -z "${pid}" ]] && continue
if $sudoset kill -9 "${pid}"; then
log "INFO" "已结束占用端口 ${target_port} 的进程 PID=${pid}"
else
log "ERROR" "结束进程 ${pid} 失败"
return 1
fi
done <<< "${publish_info}"
}
# --------------------------------------------------------------------------------------------------
# Java 容器部署函数(摘自 new_auto.sh -> java_x86),供后续本地或远端脚本复用。
# 新增逻辑:如检测到旧容器运行,则执行停止与移除操作后再部署,避免名称冲突。
# --------------------------------------------------------------------------------------------------
# 新统一平台的部署函数
java_newplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Java 服务 (Docker 版, x86)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-ujava2}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/java1.8.0_472.tar.gz}"
local image_name="139.9.60.86:5000/ujava:v6"
local image_id="a8ba5fa12b8e"
local host_api="/data/services/api"
local host_web="/data/services/web"
local host_nginx_log="/data/middleware/nginx/nginx_log"
local host_fdfs_data="/var/fdfs/storage/data"
local port_args=(
-p 8085:8085
-p 8993:8993 -p 8994:8994 -p 8995:8995
-p 8999:8999
-p 8719:8719 -p 8720:8720
-p 9204:9204 -p 9200:9200 -p 9201:9201
-p 9905:9905 -p 9911:9911 -p 9908:9908
-p 9906:9906 -p 9907:9907 -p 9909:9909 -p 9910:9910
-p 30880:30880 -p 30881:30881 -p 30882:30882
-p 30883:30883 -p 30884:30884
)
log "INFO" "🔍 检查 Java 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 正在运行,准备停止"
$sudoset docker stop "${container_name}"
log "INFO" "✅ 旧容器 ${container_name} 已停止"
elif $sudoset docker ps -a --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 存在但未运行,跳过重启以免覆盖"
log "INFO" "🛈 如需重新部署,请手动处理旧容器"
return 1
fi
log "INFO" "🔍 检查 Java 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在。"
else
log "INFO" "❌ 镜像 ${image_name} 不存在,开始加载离线包..."
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if $sudoset docker load -i "${image_tar}"; then
log "INFO" "🎉 镜像加载成功"
else
log "ERROR" "⛔ 镜像加载失败,请检查文件完整性"
return 1
fi
$sudoset docker tag "${image_id}" "${image_name}" 2>/dev/null || true
log "INFO" "🏷️ 镜像已标记为 ${image_name}"
fi
log "INFO" "🚀 正在启动 Java 容器: ${container_name} ..."
$sudoset docker run -itd \
--privileged \
-v "${host_api}:/var/www/java/api" \
-v "${host_web}:/var/www/java/web" \
-v "${host_nginx_log}:/usr/local/nginx/logs" \
-v /etc/localtime:/etc/localtime:ro \
-v "${host_fdfs_data}:/var/fdfs/storage/data" \
"${port_args[@]}" \
--restart=always \
--mac-address="02:42:ac:11:00:02" \
--name "${container_name}" \
--entrypoint "/var/www/java/api/start.sh" \
"${image_name}"
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
log "INFO" "✅ Java 容器启动成功,等待初始化..."
sleep 8
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 Java 服务部署完成!"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
# 传统平台的部署函数
java_oldplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Java 服务 (Docker 版, x86)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-ujava2}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/java1.8.0_472.tar.gz}"
local image_name="139.9.60.86:5000/ujava:v6"
local image_id="a8ba5fa12b8e"
local host_java="/var/www/java"
local host_nginx_log="/var/www/java/nginx-conf.d/nginx_log"
local host_fdfs_data="/var/fdfs/storage/data"
local port_args=(
-p 554:554
-p 1935:1935
-p 10000:10000
-p 2333:2333
-p 2334:2334
-p 8997:8997
-p 8998:8998
-p 8079:8079
-p 443:443
-p 8080:8080
-p 8085:8085
-p 8086:8086
-p 8087:8087
-p 8088:8088
-p 8889:8889
-p 8999:8999
)
log "INFO" "🔍 检查 Java 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 正在运行,准备停止"
$sudoset docker stop "${container_name}"
log "INFO" "✅ 旧容器 ${container_name} 已停止"
elif $sudoset docker ps -a --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 存在但未运行,跳过重启以免覆盖"
log "INFO" "🛈 如需重新部署,请手动处理旧容器"
return 1
fi
log "INFO" "🔍 检查 Java 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在。"
else
log "INFO" "❌ 镜像 ${image_name} 不存在,开始加载离线包..."
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if $sudoset docker load -i "${image_tar}"; then
log "INFO" "🎉 镜像加载成功"
else
log "ERROR" "⛔ 镜像加载失败,请检查文件完整性"
return 1
fi
$sudoset docker tag "${image_id}" "${image_name}" 2>/dev/null || true
log "INFO" "🏷️ 镜像已标记为 ${image_name}"
fi
log "INFO" "🚀 正在启动 Java 容器: ${container_name} ..."
$sudoset docker run -itd \
--privileged \
-v "${host_java}:/var/www/java" \
-v "${host_nginx_log}:/usr/local/nginx/logs" \
-v /etc/localtime:/etc/localtime:ro \
-v "${host_fdfs_data}:/var/fdfs/storage/data" \
"${port_args[@]}" \
--restart=always \
--mac-address="02:42:ac:11:00:02" \
--name "${container_name}" \
"${image_name}" \
/var/www/java/start.sh
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
log "INFO" "✅ Java 容器启动成功,等待初始化..."
sleep 8
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 Java 服务部署完成!"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
# --------------------------------------------------------------------------------------------------
# Redis 容器部署函数(新平台 + 传统平台)
# --------------------------------------------------------------------------------------------------
redis_oldplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Redis 服务 (Docker 版, x86, 传统平台)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-uredis}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/redis8.2.2.tar.gz}"
local image_name="139.9.60.86:5000/redis:v3"
local image_id="3bd8c109f88b"
local redis_conf_host="/var/www/redis/redis-8.2.2.conf"
local redis_data_host="/var/www/redis/data"
local redis_port="6379"
local old_redis_conf="/var/www/redis/redis-6.0.3.conf"
local new_redis_conf="/var/www/redis/redis-8.2.2.conf"
log "INFO" "🔍 检查 Redis 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "✅ 容器 '${container_name}' 已在运行"
return 0
fi
log "INFO" "🔍 检查 Redis 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在"
else
log "WARN" "❌ 镜像不存在,开始加载离线包"
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if $sudoset docker load -i "${image_tar}"; then
log "INFO" "🎉 镜像加载成功"
else
log "ERROR" "⛔ 镜像加载失败"
return 1
fi
$sudoset docker tag "${image_id}" "${image_name}" 2>/dev/null || true
fi
#检查端口是否被占用,如果有就杀死
release_port_if_busy "${redis_port}"
# 处理配置文件迁移(传统平台需要将旧版本配置重命名为新版本)
if [[ -f "${new_redis_conf}" ]]; then
log "INFO" "✅ 新版本配置文件 ${new_redis_conf} 已存在,跳过配置迁移"
elif [[ -f "${old_redis_conf}" ]]; then
log "INFO" "备份旧配置,并将旧配置命名为新版本配置"
cp "${old_redis_conf}" "${old_redis_conf}.bak"
mv "${old_redis_conf}" "${new_redis_conf}"
log "INFO" "✅ 配置文件迁移完成"
else
log "ERROR" "⛔ 未找到 Redis 配置文件 (${old_redis_conf}${new_redis_conf})"
log "ERROR" "请确保 /var/www/redis/ 目录下存在有效的 Redis 配置文件"
return 1
fi
log "INFO" "🚀 正在启动 Redis 容器: ${container_name}"
$sudoset docker run -itd \
--name "${container_name}" \
--restart=always \
--security-opt seccomp=unconfined \
--network host \
-v "${new_redis_conf}:/etc/redis/redis.conf" \
-v "${redis_data_host}:/data" \
-v "/etc/localtime:/etc/localtime:ro" \
"${image_name}" \
redis-server /etc/redis/redis.conf --appendonly yes
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
log "INFO" "✅ Redis 容器启动成功,等待初始化..."
sleep 5
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 Redis 部署完成!"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
redis_newplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Redis 服务 (Docker 版, x86, 新平台)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-uredis}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/redis8.2.2.tar.gz}"
local image_name="139.9.60.86:5000/redis:v3"
local image_id="3bd8c109f88b"
local redis_conf_host="/data/middleware/redis/config/redis.conf"
local redis_data_host="/data/middleware/redis/data"
local redis_port="6379"
log "INFO" "🔍 检查 Redis 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "✅ 容器 '${container_name}' 已在运行"
return 0
fi
log "INFO" "🔍 检查 Redis 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在"
else
log "WARN" "❌ 镜像不存在,开始加载离线包"
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if $sudoset docker load -i "${image_tar}"; then
log "INFO" "🎉 镜像加载成功"
else
log "ERROR" "⛔ 镜像加载失败"
return 1
fi
$sudoset docker tag "${image_id}" "${image_name}" 2>/dev/null || true
fi
#检查端口是否被占用,如果有就杀死
release_port_if_busy "${redis_port}"
log "INFO" "🚀 正在启动 Redis 容器: ${container_name}"
$sudoset docker run -itd \
--name "${container_name}" \
--restart=always \
--security-opt seccomp=unconfined \
--network host \
-v "${redis_conf_host}:/etc/redis/redis.conf" \
-v "${redis_data_host}:/data" \
-v "/etc/localtime:/etc/localtime:ro" \
"${image_name}" \
redis-server /etc/redis/redis.conf --appendonly yes
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
log "INFO" "✅ Redis 容器启动成功,等待初始化..."
sleep 5
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 Redis 部署完成!"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
# --------------------------------------------------------------------------------------------------
# EMQX 容器部署函数(新平台 + 传统平台)
# --------------------------------------------------------------------------------------------------
emqx_oldplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 EMQX 服务 (Docker 版, x86, 传统平台)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-uemqx2}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/uemqx5.8.4.tar.gz}"
local image_name="139.9.60.86:5000/uemqx:v2"
local emqx_config_host="/var/www/emqx/config"
local emqx_data_host="/var/www/emqx/data"
local emqx_log_host="/var/www/emqx/log"
local source_emqx_dir="/data/middleware/emqx"
local target_emqx_dir="/var/www/emqx"
local backup_dir="${target_emqx_dir}_backup_$(date +%Y%m%d_%H%M%S)"
log "INFO" "🔍 检查 EMQX 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 正在运行,准备停止"
$sudoset docker stop "${container_name}"
log "INFO" "✅ 旧容器 ${container_name} 已停止"
fi
log "INFO" "📂 假设远端 EMQX 目录已同步完成 (由 remote_update.sh 负责)"
log "INFO" "🔍 检查 EMQX 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在"
else
log "WARN" "❌ 镜像不存在,开始加载离线包"
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if $sudoset docker load -i "${image_tar}"; then
log "INFO" "🎉 镜像加载成功"
else
log "ERROR" "⛔ 镜像加载失败"
return 1
fi
fi
# 检查并释放端口
local ports=(1883 8083 8084 8883 18083)
for port in "${ports[@]}"; do
release_port_if_busy "${port}"
done
log "INFO" "🚀 正在启动 EMQX 容器: ${container_name}"
$sudoset docker run -d \
--name "${container_name}" \
--mac-address="02:42:ac:12:00:06" \
--privileged \
--cap-add ALL \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--user root \
-p 1883:1883 \
-p 8083:8083 \
-p 8084:8084 \
-p 8883:8883 \
-p 18083:18083 \
-v "${emqx_config_host}:/opt/emqx/etc" \
-v "${emqx_data_host}:/opt/emqx/data" \
-v "${emqx_log_host}:/opt/emqx/log" \
--restart=always \
"${image_name}"
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
log "INFO" "✅ EMQX 容器启动成功,等待初始化..."
sleep 5
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 EMQX 部署完成!"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
emqx_newplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 EMQX 服务 (Docker 版, x86, 新统一平台)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-uemqx2}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/uemqx5.8.4.tar.gz}"
local image_name="139.9.60.86:5000/uemqx:v2"
local emqx_config_host="/data/middleware/emqx/config"
local emqx_data_host="/data/middleware/emqx/data"
local emqx_log_host="/data/middleware/emqx/log"
local source_emqx_dir="/data/middleware/emqx"
local target_emqx_dir="/data/middleware/emqx"
local backup_dir="${target_emqx_dir}_backup_$(date +%Y%m%d_%H%M%S)"
log "INFO" "🔍 检查 EMQX 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 正在运行,准备停止"
$sudoset docker stop "${container_name}"
log "INFO" "✅ 旧容器 ${container_name} 已停止"
fi
log "INFO" "📂 假设远端 EMQX 目录已同步完成 (由 remote_update.sh 负责)"
log "INFO" "🔍 检查 EMQX 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在"
else
log "WARN" "❌ 镜像不存在,开始加载离线包"
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if $sudoset docker load -i "${image_tar}"; then
log "INFO" "🎉 镜像加载成功"
else
log "ERROR" "⛔ 镜像加载失败"
return 1
fi
fi
# 检查并释放端口
local ports=(1883 8083 8084 8883 18083)
for port in "${ports[@]}"; do
release_port_if_busy "${port}"
done
log "INFO" "🚀 正在启动 EMQX 容器: ${container_name}"
$sudoset docker run -d \
--name "${container_name}" \
--mac-address="02:42:ac:12:00:06" \
--privileged \
--cap-add ALL \
--security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
--user root \
-p 1883:1883 \
-p 8083:8083 \
-p 8084:8084 \
-p 8883:8883 \
-p 18083:18083 \
-v "${emqx_config_host}:/opt/emqx/etc" \
-v "${emqx_data_host}:/opt/emqx/data" \
-v "${emqx_log_host}:/opt/emqx/log" \
--restart=always \
"${image_name}"
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
log "INFO" "✅ EMQX 容器启动成功,等待初始化..."
sleep 5
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 EMQX 部署完成!"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
# --------------------------------------------------------------------------------------------------
# Python 容器部署函数(新平台 + 传统平台)
# --------------------------------------------------------------------------------------------------
python_newplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Python 服务 (Docker 版, x86, 新平台)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-upython}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/python_v15.tar.gz}"
local image_name="139.9.60.86:5000/upython:v15"
local host_app_dir="/data/services/api/python-cmdb"
local container_app_dir="/var/www/html"
local container_start_cmd="/var/www/html/start.sh"
local mac_addr="02:42:ac:11:00:07"
local ports=(-p 8000:8000 -p 8002:8002)
if [[ ! -d "${host_app_dir}" ]]; then
log "ERROR" "⛔ 应用目录不存在: ${host_app_dir}"
return 1
fi
if [[ ! -f "${host_app_dir}/start.sh" ]]; then
log "ERROR" "⛔ 启动脚本缺失: ${host_app_dir}/start.sh"
return 1
fi
log "INFO" "🔍 检查 Python 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 正在运行,准备停止"
$sudoset docker stop "${container_name}"
log "INFO" "✅ 旧容器 ${container_name} 已停止"
elif $sudoset docker ps -a --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 存在但未运行,避免覆盖"
log "INFO" "🛈 如需重新部署,请手动清理旧容器"
return 1
fi
log "INFO" "🔍 检查 Python 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在"
else
log "INFO" "📦 镜像缺失,开始加载: ${image_tar}"
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if ! $sudoset docker load -i "${image_tar}"; then
log "ERROR" "⛔ 镜像加载失败"
return 1
fi
log "INFO" "🎉 镜像加载完成"
fi
log "INFO" "🚀 正在启动 Python 容器: ${container_name}"
$sudoset docker run -d \
--name "${container_name}" \
--restart=always \
--mac-address="${mac_addr}" \
--privileged \
"${ports[@]}" \
-v "${host_app_dir}:${container_app_dir}" \
-v "/etc/localtime:/etc/localtime:ro" \
"${image_name}" \
"${container_start_cmd}"
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
sleep 5
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 Python 服务部署完成 (新平台)"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
python_oldplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Python 服务 (Docker 版, x86, 传统平台)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-upython}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/python_v14.tar.gz}"
local image_name="139.9.60.86:5000/upython:v14"
local host_html_dir="/var/www/html"
local container_start_cmd="/var/www/html/start.sh"
local mac_addr="02:42:ac:11:00:06"
local ports=(-p 8000:8000 -p 8002:8002 -p 8443:8443 -p 9009:9009)
if [[ ! -d "${host_html_dir}" ]]; then
log "ERROR" "⛔ 目录不存在: ${host_html_dir}"
return 1
fi
if [[ ! -f "${host_html_dir}/start.sh" ]]; then
log "ERROR" "⛔ 启动脚本缺失: ${host_html_dir}/start.sh"
return 1
fi
log "INFO" "🔍 检查 Python 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 正在运行,准备停止"
$sudoset docker stop "${container_name}"
log "INFO" "✅ 旧容器 ${container_name} 已停止"
elif $sudoset docker ps -a --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 存在但未运行,避免覆盖"
log "INFO" "🛈 如需重新部署,请手动清理旧容器"
return 1
fi
log "INFO" "🔍 检查 Python 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在"
else
log "INFO" "📦 镜像缺失,开始加载: ${image_tar}"
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if ! $sudoset docker load -i "${image_tar}"; then
log "ERROR" "⛔ 镜像加载失败"
return 1
fi
log "INFO" "🎉 镜像加载完成"
fi
log "INFO" "🚀 正在启动 Python 容器: ${container_name}"
$sudoset docker run -d \
--name "${container_name}" \
--restart=always \
--mac-address="${mac_addr}" \
--privileged \
"${ports[@]}" \
-v "${host_html_dir}:/var/www/html" \
-v "/etc/localtime:/etc/localtime:ro" \
"${image_name}" \
"${container_start_cmd}"
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
sleep 5
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 Python 服务部署完成 (传统平台)"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
log "INFO" "▶️ 触发容器内应用启动命令"
$sudoset docker exec "${container_name}" /bin/bash -c "cd /var/www/html && nohup python manage.py runserver 0.0.0.0:8000 >/tmp/python_manage.log 2>&1 &" || true
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
# --------------------------------------------------------------------------------------------------
# Nacos 容器部署函数(仅新平台)
# --------------------------------------------------------------------------------------------------
nacos_newplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Nacos Server (Standalone) - x86, 新统一平台"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-unacos}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/nacos-server-v2.5.2.tar.gz}"
local image_tag="nacos-server:v2.5.2"
local host_data_dir="/data/middleware/nacos"
log "INFO" "🔍 检查 Nacos 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 正在运行,准备停止"
$sudoset docker stop "${container_name}"
log "INFO" "✅ 旧容器 ${container_name} 已停止"
elif $sudoset docker ps -a --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 存在但未运行,跳过部署以免覆盖"
log "INFO" "🛈 如需重新部署,请手动处理旧容器"
return 1
fi
log "INFO" "🔍 检查 Nacos 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "^${image_tag}$"; then
log "INFO" "✅ 镜像 ${image_tag} 已存在"
else
log "WARN" "❌ 镜像不存在,开始加载离线包"
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if $sudoset docker load -i "${image_tar}"; then
log "INFO" "🎉 镜像加载成功"
else
log "ERROR" "⛔ 镜像加载失败,请检查文件是否完整或已损坏"
return 1
fi
fi
# 检查并释放端口
local ports=(8848 9848)
for port in "${ports[@]}"; do
release_port_if_busy "${port}"
done
log "INFO" "🚀 正在启动 Nacos 容器: ${container_name}"
$sudoset docker run -d \
--name "${container_name}" \
--restart=always \
-p 8848:8848 \
-p 9848:9848 \
-e MODE=standalone \
-v "${host_data_dir}:/home/nacos" \
--mac-address="02:42:ac:11:00:10" \
"${image_tag}"
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ Nacos 容器启动失败"
return 1
fi
log "INFO" "✅ Nacos 容器已启动,名称: ${container_name}"
# 状态确认(容器是否仍在运行)
sleep 5
if ! $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "ERROR" "⛔ 容器启动后退出,请检查日志: $sudoset docker logs ${container_name}"
return 1
fi
# 获取本机 IP 地址
local server_ip=$(ip addr show | grep -E 'inet\s+(192\.168|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|10\.)' | awk '{print $2}' | cut -d/ -f1 | head -n1)
if [[ -z "${server_ip}" ]]; then
# 回退到 hostname -I
server_ip=$(hostname -I | awk '{print $1}')
fi
# 输出访问信息
if [[ -n "${server_ip}" ]]; then
log "INFO" "✅ Nacos 部署完成!"
log "🎉 可通过浏览器访问: http://${server_ip}:8848/nacos"
log "💡 默认账号密码: nacos / nacos"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "INFO" "🎉 Nacos 部署完成。"
log "💡 默认账号密码: nacos / nacos"
log "📌 无法获取服务器 IP,请手动确认访问地址: http://<your-ip>:8848/nacos"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
fi
return 0
}
# --------------------------------------------------------------------------------------------------
# Nginx 容器部署函数(仅新平台)
# --------------------------------------------------------------------------------------------------
nginx_newplatform_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Nginx (离线编译安装) - x86"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="${DEPLOY_CONTAINER_NAME:-unginx}"
local image_tar="${DEPLOY_IMAGE_TAR:-/data/temp/nginx-1.29.3.tar.gz}"
local nginx_version="1.29.3"
local nginx_image="nginx:${nginx_version}"
local required_dirs=(
"/data/middleware/nginx/log"
"/data/middleware/nginx/data/cache"
"/data/middleware/nginx/data/html"
"/data/middleware/nginx/config"
"/data/services/web"
"/data/security/nginx_cert"
)
# 目录预检查,缺失则创建
for dir in "${required_dirs[@]}"; do
if [ ! -d "$dir" ]; then
log "WARN" "目录 $dir 不存在,自动创建"
$sudoset mkdir -p "$dir" || {
log "ERROR" "目录 $dir 创建失败"
return 1
}
fi
done
# 创建 nginx 用户和组(如果不存在)
if ! getent group nginx >/dev/null 2>&1; then
log "INFO" "创建 nginx 组..."
$sudoset groupadd -r nginx
else
log "INFO" "nginx 组已存在,跳过创建"
fi
if ! id nginx >/dev/null 2>&1; then
log "INFO" "创建 nginx 用户..."
$sudoset useradd -r -g nginx -s /sbin/nologin -d /var/cache/nginx nginx
else
log "INFO" "nginx 用户已存在,跳过创建"
fi
# 确认 nginx 用户存在
if ! id nginx >/dev/null 2>&1; then
log "ERROR" "nginx 用户创建失败"
return 1
fi
log "INFO" "nginx 用户验证通过"
#赋予权限
$sudoset chown -R nginx:nginx /data/middleware/nginx/data/cache
$sudoset chmod -R 755 /data/middleware/nginx/data/cache
# 赋予权限
$sudoset chown -R 1000:1000 /data/middleware/nginx/log
$sudoset chown -R 1000:1000 /data/middleware/nginx/data/cache
$sudoset chmod -R 700 /data/middleware/nginx/data/cache
# 校验镜像包
if [ ! -s "$image_tar" ]; then
log "ERROR" "离线包 $image_tar 不存在或为空"
return 1
fi
# 加载镜像文件
if ! $sudoset docker load -i "$image_tar"; then
log "ERROR" "镜像加载失败"
return 1
fi
log "INFO" "镜像加载成功"
# 检查容器是否存在,如果存在则停止并删除
if $sudoset docker ps --format '{{.Names}}' | grep -qw "$container_name"; then
log "WARN" "检测到容器 $container_name 正在运行,准备停止"
$sudoset docker stop "$container_name"
log "INFO" "容器 $container_name 已停止"
fi
# 生成容器
if ! $sudoset docker run -d \
--name "$container_name" \
--mac-address="02:42:ac:11:00:25" \
--restart=always \
-p 443:443 \
-v /data/middleware/nginx/config:/etc/nginx/conf.d \
-v /data/middleware/nginx/data/html:/usr/share/nginx/html \
-v /data/middleware/nginx/log:/var/log/nginx \
-v /data/middleware/nginx/data/cache:/var/cache/nginx \
-v /data/services/web:/data/services/web:rw \
-v /data/security/nginx_cert:/data/security/nginx_cert:ro \
"$nginx_image"; then
log "ERROR" "容器 $container_name 启动失败"
return 1
fi
log "INFO" "容器 $container_name 启动成功"
return 0
}
main "$@"
\ No newline at end of file
#!/usr/bin/env bash
# 远程升级指定服务器的容器版本脚本
# 实现内容:
# 1. 校验目标服务器架构是否为 x86
# 2. 将当前目录下的镜像文件与部署脚本同步至远端目录
# 3. 停止远端旧容器
# 4. 交互询问目标服务器是否属于新统一平台
# 5. 自动递增容器编号并执行远端部署脚本
set -euo pipefail
REMOTE_ARCH_ALLOW_REGEX='^(x86_64|amd64|i386|i686)$'
declare -A SERVER_IP SERVER_USER SERVER_PASS
SERVER_IP["1"]="192.168.5.48"
SERVER_USER["1"]="root"
SERVER_PASS["1"]="Ubains@123"
SERVER_IP["2"]="192.168.5.67"
SERVER_USER["2"]="root"
SERVER_PASS["2"]="Ubains@123"
SERVER_IP["3"]="192.168.5.47"
SERVER_USER["3"]="root"
SERVER_PASS["3"]="Ubains@1234"
declare -A SERVER_DESC
SERVER_DESC["1"]="标准版预定运维服务器"
SERVER_DESC["2"]="阿曼项目预定服务器"
SERVER_DESC["3"]="标准版预定运维测试发布服务器"
log() {
local level="$1"; shift
printf '[%(%F %T)T] [%s] %s\n' -1 "${level}" "$*"
}
DEFAULT_REMOTE_DIR="/home/containerUpdate"
LOCAL_EMQX_DIR="/data/middleware/emqx"
LOCAL_PYTHON_DIR="/data/services/api/python-cmdb"
LOCAL_NGINX_DIR="/data/middleware/nginx"
usage() {
cat <<'EOF'
用法:
remote_update.sh [远端目录]
参数说明:
远端目录 在目标服务器上存放镜像与部署脚本的目录,默认 /home/containerUpdate
部署脚本 固定为 container_update.sh (位于当前目录)
脚本会弹出列表供选择内置服务器 (含 IP / 用户 / 密码) 以及容器
镜像文件将根据容器自动映射,如 ujava→java1.8.0_472.tar.gz
EOF
exit 1
}
if [[ $# -gt 1 ]]; then
usage
fi
if [[ $# -eq 1 ]]; then
REMOTE_DIR="$1"
else
REMOTE_DIR="${DEFAULT_REMOTE_DIR}"
log INFO "未指定远端目录,默认使用 ${REMOTE_DIR}"
fi
DEPLOY_SCRIPT="container_update.sh"
if [[ ! -f "${DEPLOY_SCRIPT}" ]]; then
log ERROR "部署脚本 ${DEPLOY_SCRIPT} 不存在"
exit 3
fi
DEPLOY_NAME="$(basename "${DEPLOY_SCRIPT}")"
CONTAINER_OPTIONS=(ujava uemqx uredis upython unacos unginx)
declare -A CONTAINER_IMAGE
CONTAINER_IMAGE["ujava"]="java1.8.0_472.tar.gz"
CONTAINER_IMAGE["uemqx"]="uemqx5.8.4.tar.gz"
CONTAINER_IMAGE["uredis"]="redis8.2.2.tar.gz"
CONTAINER_IMAGE["upython"]="python_v15.tar.gz"
CONTAINER_IMAGE["unacos"]="nacos-server-v2.5.2.tar.gz"
CONTAINER_IMAGE["unginx"]="/data/temp/nginx-1.29.3.tar.gz"
# 容器对应的 Docker 镜像名称(用于版本校验)
declare -A CONTAINER_DOCKER_IMAGE
CONTAINER_DOCKER_IMAGE["ujava"]="139.9.60.86:5000/ujava:v6"
CONTAINER_DOCKER_IMAGE["uemqx"]="139.9.60.86:5000/uemqx:v2"
CONTAINER_DOCKER_IMAGE["uredis"]="139.9.60.86:5000/redis:v3"
CONTAINER_DOCKER_IMAGE["upython_new"]="139.9.60.86:5000/upython:v15"
CONTAINER_DOCKER_IMAGE["upython_old"]="139.9.60.86:5000/upython:v14"
CONTAINER_DOCKER_IMAGE["unacos"]="nacos-server:v2.5.2"
CONTAINER_DOCKER_IMAGE["unginx"]="nginx:1.29.3"
log INFO "可选择的容器:"
for idx in "${!CONTAINER_OPTIONS[@]}"; do
option_index=$((idx + 1))
printf " [%s] %s\n" "${option_index}" "${CONTAINER_OPTIONS[idx]}"
done
read -rp "请输入容器编号: " CONTAINER_KEY
if ! [[ "${CONTAINER_KEY}" =~ ^[1-6]$ ]]; then
log ERROR "容器编号无效,请输入 1-6"
exit 9
fi
CONTAINER_PREFIX="${CONTAINER_OPTIONS[$((CONTAINER_KEY - 1))]}"
log INFO "已选择容器 ${CONTAINER_PREFIX}"
IMAGE_FILE="${CONTAINER_IMAGE[${CONTAINER_PREFIX}]}"
if [[ -z "${IMAGE_FILE}" ]]; then
log ERROR "容器 ${CONTAINER_PREFIX} 未配置镜像映射"
exit 10
fi
# nginx 使用绝对路径,其他容器使用相对路径
if [[ "${IMAGE_FILE}" = /* ]]; then
# 绝对路径
if [[ ! -f "${IMAGE_FILE}" ]]; then
log ERROR "镜像文件 ${IMAGE_FILE} 不存在"
exit 2
fi
IMAGE_NAME="$(basename "${IMAGE_FILE}")"
else
# 相对路径(当前目录)
if [[ ! -f "${IMAGE_FILE}" ]]; then
log ERROR "镜像文件 ${IMAGE_FILE} 不存在,请放置于当前目录后重试"
exit 2
fi
IMAGE_NAME="$(basename "${IMAGE_FILE}")"
fi
SERVER_KEYS=(1 2 3)
log INFO "可选择的目标服务器:"
for key in "${SERVER_KEYS[@]}"; do
printf " [%s] %s (%s %s)\n" "${key}" "${SERVER_DESC[${key}]}" "${SERVER_IP[${key}]}" "${SERVER_USER[${key}]}"
done
printf " [0] 手动输入服务器信息\n"
read -rp "请输入服务器编号: " SERVER_KEY
# 手动输入服务器信息
if [[ "${SERVER_KEY}" == "0" ]]; then
log INFO "进入手动输入模式"
read -rp "请输入目标服务器 IP 地址: " REMOTE_HOST
if [[ -z "${REMOTE_HOST}" ]]; then
log ERROR "服务器 IP 地址不能为空"
exit 7
fi
read -rp "请输入 SSH 端口号 [默认 22]: " SSH_PORT_INPUT
SSH_PORT="${SSH_PORT_INPUT:-22}"
read -rp "请输入登录用户名 [默认 root]: " REMOTE_USER_INPUT
REMOTE_USER="${REMOTE_USER_INPUT:-root}"
read -rsp "请输入登录密码: " REMOTE_PASS
echo "" # 换行
if [[ -z "${REMOTE_PASS}" ]]; then
log ERROR "登录密码不能为空"
exit 7
fi
REMOTE_DESC="手动输入服务器"
log INFO "已配置目标服务器: ${REMOTE_USER}@${REMOTE_HOST}:${SSH_PORT}"
elif [[ -z "${SERVER_IP[${SERVER_KEY}]:-}" ]]; then
log ERROR "编号 ${SERVER_KEY} 不存在,请重新运行脚本"
exit 7
else
REMOTE_HOST="${SERVER_IP[${SERVER_KEY}]}"
REMOTE_USER="${SERVER_USER[${SERVER_KEY}]}"
REMOTE_PASS="${SERVER_PASS[${SERVER_KEY}]}"
REMOTE_DESC="${SERVER_DESC[${SERVER_KEY}]}"
SSH_PORT="22"
log INFO "已选择 ${REMOTE_DESC} (${REMOTE_HOST})"
fi
if ! command -v sshpass >/dev/null 2>&1; then
log ERROR "未安装 sshpass,无法使用密码方式登陆"
exit 8
fi
SSH_TIMEOUT=30
ssh_exec() {
sshpass -p "${REMOTE_PASS}" ssh -p "${SSH_PORT}" -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT} "$@"
}
# 先测试 SSH 连接
log INFO "测试 SSH 连接..."
TEST_OUTPUT="$(sshpass -p "${REMOTE_PASS}" ssh -p "${SSH_PORT}" -o StrictHostKeyChecking=no -o ConnectTimeout=10 "${REMOTE_USER}@${REMOTE_HOST}" "echo CONNECTION_OK" 2>&1)" || TEST_STATUS=$?
TEST_STATUS=${TEST_STATUS:-0}
# 检查输出中是否包含 CONNECTION_OK(忽略 MOTD 等欢迎信息)
if [[ ${TEST_STATUS} -ne 0 ]] || ! echo "${TEST_OUTPUT}" | grep -q "CONNECTION_OK"; then
log ERROR "SSH 连接失败!"
log ERROR "输出信息: ${TEST_OUTPUT}"
log ERROR "请检查: 1) IP地址是否正确 2) 端口是否正确 3) 密码是否正确 4) 网络是否可达"
exit 4
fi
log INFO "SSH 连接测试通过"
scp_exec() {
sshpass -p "${REMOTE_PASS}" scp -P "${SSH_PORT}" -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT} "$@"
}
sync_emqx_assets() {
local remote_dir="$1"
local platform_label="$2"
if [[ ! -d "${LOCAL_EMQX_DIR}" ]]; then
log ERROR "本地 EMQX 目录不存在: ${LOCAL_EMQX_DIR}"
exit 11
fi
log INFO "准备同步 EMQX 目录 (${LOCAL_EMQX_DIR} -> ${REMOTE_HOST}:${remote_dir})"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p '${remote_dir}'"
local backup_cmd
backup_cmd=$(cat <<'EOF'
set -e
TARGET_DIR="$1"
if [[ -d "${TARGET_DIR}" && -n "$(ls -A "${TARGET_DIR}")" ]]; then
ts=$(date +%Y%m%d_%H%M%S)
backup="${TARGET_DIR}_backup_${ts}"
cp -r "${TARGET_DIR}" "${backup}"
echo "${backup}"
fi
EOF
)
local backup_path
backup_path="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "bash -s '${remote_dir}'" <<<"${backup_cmd}" || true)"
if [[ -n "${backup_path}" ]]; then
log INFO "远端目录已备份到 ${backup_path}"
else
log INFO "远端目录为空或不存在,跳过备份"
fi
if command -v rsync >/dev/null 2>&1; then
log INFO "使用 rsync 同步 EMQX 目录"
if ! sshpass -p "${REMOTE_PASS}" rsync -az --delete -e "ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT}" "${LOCAL_EMQX_DIR}/" "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"; then
log ERROR "rsync 同步失败"
exit 12
fi
else
log WARN "未检测到 rsync,改用 scp 拷贝 EMQX 目录"
if ! scp_exec -r "${LOCAL_EMQX_DIR}/." "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"; then
log ERROR "scp 拷贝失败"
exit 13
fi
fi
log INFO "EMQX 目录同步完成,开始设置远端配置文件权限"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "if [[ -d '${remote_dir}/config' ]]; then cd '${remote_dir}/config' && ls *.conf >/dev/null 2>&1 && chmod +x *.conf || true; else echo 'WARN: config 目录不存在'; fi"
log INFO "远端 EMQX 配置权限处理完成 (${platform_label} 平台)"
}
sync_python_assets() {
local remote_dir="$1"
local platform_label="$2"
if [[ ! -d "${LOCAL_PYTHON_DIR}" ]]; then
log ERROR "本地 Python 目录不存在: ${LOCAL_PYTHON_DIR}"
exit 14
fi
log INFO "准备同步 Python 目录 (${LOCAL_PYTHON_DIR} -> ${REMOTE_HOST}:${remote_dir})"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p '${remote_dir}'"
local backup_cmd
backup_cmd=$(cat <<'EOF'
set -e
TARGET_DIR="$1"
if [[ -d "${TARGET_DIR}" && -n "$(ls -A "${TARGET_DIR}")" ]]; then
ts=$(date +%Y%m%d_%H%M%S)
backup="${TARGET_DIR}_backup_${ts}"
cp -r "${TARGET_DIR}" "${backup}"
echo "${backup}"
fi
EOF
)
local backup_path
backup_path="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "bash -s '${remote_dir}'" <<<"${backup_cmd}" || true)"
if [[ -n "${backup_path}" ]]; then
log INFO "远端 Python 目录已备份到 ${backup_path}"
else
log INFO "远端 Python 目录为空或不存在,跳过备份"
fi
if command -v rsync >/dev/null 2>&1; then
log INFO "使用 rsync 同步 Python 目录"
if ! sshpass -p "${REMOTE_PASS}" rsync -az --delete -e "ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT}" "${LOCAL_PYTHON_DIR}/" "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"; then
log ERROR "rsync 同步 Python 目录失败"
exit 15
fi
else
log WARN "未检测到 rsync,改用 scp 拷贝 Python 目录"
if ! scp_exec -r "${LOCAL_PYTHON_DIR}/." "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"; then
log ERROR "scp 拷贝 Python 目录失败"
exit 16
fi
fi
log INFO "开始更新 Python 配置中的 IP 地址 (${platform_label} 平台)"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "find '${remote_dir}' -type f -name '*.conf' -exec sed -i 's/192\\.168\\.5\\.44/${REMOTE_HOST}/g' {} +" || true
log INFO "Python 目录同步完成"
if [[ -n "${backup_path}" ]]; then
log INFO "尝试从备份目录保留认证文件 authFile.uas"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "
set -e
if [[ -f '${backup_path}/authFile.uas' ]]; then
cp '${backup_path}/authFile.uas' '${remote_dir}/authFile.uas'
echo 'INFO: 已将 authFile.uas 回拷至新目录'
else
echo 'WARN: 备份目录内未找到 authFile.uas,跳过回拷'
fi
" || log WARN "authFile.uas 回拷时出现问题,需人工检查"
else
log INFO "无历史备份目录,跳过 authFile.uas 回拷"
fi
}
# --------------------------------------------------------------------------------------------------
# 校验远端服务器镜像版本,判断是否已更新
# 参数:
# $1 - 容器前缀 (如 ujava, uredis)
# $2 - 平台类型 (new/old),仅 upython 需要区分
# 返回:
# 0 - 远端未更新或用户选择继续
# 1 - 远端已更新且用户选择跳过
# --------------------------------------------------------------------------------------------------
check_remote_image_version() {
local container_prefix="$1"
local platform_type="$2"
local expected_image
# 根据容器类型和平台类型获取期望的镜像名称
if [[ "${container_prefix}" == "upython" ]]; then
if [[ "${platform_type}" == "new" ]]; then
expected_image="${CONTAINER_DOCKER_IMAGE["upython_new"]}"
else
expected_image="${CONTAINER_DOCKER_IMAGE["upython_old"]}"
fi
else
expected_image="${CONTAINER_DOCKER_IMAGE["${container_prefix}"]}"
fi
if [[ -z "${expected_image}" ]]; then
log WARN "未配置容器 ${container_prefix} 的镜像版本信息,跳过版本校验"
return 0
fi
log INFO "检查远端服务器镜像版本..."
local remote_images
remote_images="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "docker images --format '{{.Repository}}:{{.Tag}}'" 2>/dev/null || true)"
if echo "${remote_images}" | grep -qw "${expected_image}"; then
log WARN "⚠️ 检测到远端服务器已存在目标镜像: ${expected_image}"
log WARN "⚠️ 目标服务器可能已完成更新"
# 额外检查是否有相关容器在运行
local running_container
running_container="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "docker ps --format '{{.Names}}' | grep -E '^${container_prefix}([0-9]+)?$' | head -n1" 2>/dev/null || true)"
if [[ -n "${running_container}" ]]; then
# 获取运行容器使用的镜像
local container_image
container_image="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "docker inspect --format '{{.Config.Image}}' '${running_container}'" 2>/dev/null || true)"
if [[ "${container_image}" == "${expected_image}" ]]; then
log WARN "⚠️ 检测到容器 ${running_container} 正在使用目标镜像 ${expected_image}"
log WARN "⚠️ 目标服务器该容器已是最新版本,无需更新"
read -rp "是否仍要继续更新? (y/n): " CONTINUE_INPUT
case "${CONTINUE_INPUT}" in
y|Y)
log INFO "用户选择继续更新"
return 0
;;
n|N)
log INFO "用户选择跳过更新"
return 1
;;
*)
log ERROR "输入无效,终止操作"
exit 6
;;
esac
fi
fi
read -rp "远端已有目标镜像,是否仍要继续更新? (y/n): " CONTINUE_INPUT
case "${CONTINUE_INPUT}" in
y|Y)
log INFO "用户选择继续更新"
return 0
;;
n|N)
log INFO "用户选择跳过更新"
return 1
;;
*)
log ERROR "输入无效,终止操作"
exit 6
;;
esac
else
log INFO "✅ 远端服务器尚未安装目标镜像 ${expected_image},继续更新流程"
return 0
fi
}
sync_nginx_assets() {
local remote_dir="$1"
local platform_label="$2"
if [[ ! -d "${LOCAL_NGINX_DIR}" ]]; then
log ERROR "本地 Nginx 目录不存在: ${LOCAL_NGINX_DIR}"
exit 17
fi
log INFO "准备同步 Nginx 目录 (${LOCAL_NGINX_DIR} -> ${REMOTE_HOST}:${remote_dir})"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p '${remote_dir}'"
local backup_cmd
backup_cmd=$(cat <<'EOF'
set -e
TARGET_DIR="$1"
if [[ -d "${TARGET_DIR}" && -n "$(ls -A "${TARGET_DIR}")" ]]; then
ts=$(date +%Y%m%d_%H%M%S)
backup="${TARGET_DIR}_backup_${ts}"
cp -r "${TARGET_DIR}" "${backup}"
echo "${backup}"
fi
EOF
)
local backup_path
backup_path="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "bash -s '${remote_dir}'" <<<"${backup_cmd}" || true)"
if [[ -n "${backup_path}" ]]; then
log INFO "远端 Nginx 目录已备份到 ${backup_path}"
else
log INFO "远端 Nginx 目录为空或不存在,跳过备份"
fi
if command -v rsync >/dev/null 2>&1; then
log INFO "使用 rsync 同步 Nginx 目录"
if ! sshpass -p "${REMOTE_PASS}" rsync -az --delete -e "ssh -p ${SSH_PORT} -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT}" "${LOCAL_NGINX_DIR}/" "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"; then
log ERROR "rsync 同步 Nginx 目录失败"
exit 18
fi
else
log WARN "未检测到 rsync,改用 scp 拷贝 Nginx 目录"
if ! scp_exec -r "${LOCAL_NGINX_DIR}/." "${REMOTE_USER}@${REMOTE_HOST}:${remote_dir}/"; then
log ERROR "scp 拷贝 Nginx 目录失败"
exit 19
fi
fi
log INFO "Nginx 目录同步完成 (${platform_label} 平台)"
}
log INFO "开始校验远端架构 (${REMOTE_HOST})"
ARCH_OUTPUT="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "uname -m" 2>&1)" || SSH_STATUS=$?
SSH_STATUS=${SSH_STATUS:-0}
if [[ ${SSH_STATUS} -ne 0 ]]; then
log ERROR "无法获取远端架构,请检查网络或权限"
log ERROR "SSH 输出: ${ARCH_OUTPUT}"
log ERROR "请确认: 1) IP地址是否正确 2) 端口是否正确 3) 密码是否正确 4) 网络是否可达"
exit 4
fi
ARCH="$(printf '%s' "${ARCH_OUTPUT}" | tr -d '\r' | awk 'NF {last=$0} END {print last}')"
if [[ ! "${ARCH}" =~ ${REMOTE_ARCH_ALLOW_REGEX} ]]; then
log ERROR "远端架构为 ${ARCH},非 x86,终止操作"
exit 5
fi
log INFO "远端架构 ${ARCH} 校验通过"
# 自动检测平台类型(通过检查 /data/services 目录是否存在)
log INFO "自动检测目标服务器平台类型..."
PLATFORM_CHECK_OUTPUT="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "[ -d /data/services ] && echo 'NEW_PLATFORM' || echo 'OLD_PLATFORM'" 2>/dev/null || echo "CHECK_FAILED")"
PLATFORM_CHECK_RESULT="$(echo "${PLATFORM_CHECK_OUTPUT}" | tr -d '\r' | awk 'NF {last=$0} END {print last}')"
if [[ "${PLATFORM_CHECK_RESULT}" == "NEW_PLATFORM" ]]; then
PLATFORM_FLAG="--new-platform"
PLATFORM_TYPE="new"
log INFO "✅ 检测到 /data/services 目录存在,自动识别为新统一平台"
elif [[ "${PLATFORM_CHECK_RESULT}" == "OLD_PLATFORM" ]]; then
PLATFORM_FLAG=""
PLATFORM_TYPE="old"
log INFO "✅ 未检测到 /data/services 目录,自动识别为传统平台"
else
log WARN "⚠️ 自动检测平台类型失败,切换为手动确认模式"
read -rp "目标服务器是否为新统一平台? (y/n): " PLATFORM_INPUT
case "${PLATFORM_INPUT}" in
y|Y) PLATFORM_FLAG="--new-platform" ; PLATFORM_TYPE="new" ; log INFO "标记为新统一平台" ;;
n|N) PLATFORM_FLAG="" ; PLATFORM_TYPE="old" ; log INFO "标记为传统平台" ;;
*) log ERROR "输入无效,请输入 y 或 n"; exit 6 ;;
esac
fi
# 校验远端镜像版本,判断是否已更新
if ! check_remote_image_version "${CONTAINER_PREFIX}" "${PLATFORM_TYPE}"; then
log INFO "用户取消更新,流程结束"
exit 0
fi
log INFO "创建远端目录 ${REMOTE_DIR}"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p '${REMOTE_DIR}'"
log INFO "传输镜像与部署脚本到远端目录"
# nginx 使用绝对路径,需要特殊处理
if [[ "${IMAGE_FILE}" = /* ]]; then
# 绝对路径:先复制到临时位置,再传输
TEMP_IMAGE="/tmp/$(basename "${IMAGE_FILE}")"
cp "${IMAGE_FILE}" "${TEMP_IMAGE}"
scp_exec "${TEMP_IMAGE}" "${DEPLOY_SCRIPT}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
rm -f "${TEMP_IMAGE}"
# 在远端将镜像文件移动到正确位置
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "mkdir -p /data/temp && mv '${REMOTE_DIR}/$(basename "${IMAGE_FILE}")' /data/temp/$(basename "${IMAGE_FILE}")"
else
# 相对路径:直接传输
scp_exec "${IMAGE_FILE}" "${DEPLOY_SCRIPT}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/"
fi
log INFO "查找并停止远端旧容器"
CURRENT_CONTAINER=$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "docker ps --format '{{.Names}}' | grep -E '^${CONTAINER_PREFIX}([0-9]+)?$' | sort -V | tail -n1" || true)
if [[ -n "${CURRENT_CONTAINER}" ]]; then
log INFO "检测到旧容器 ${CURRENT_CONTAINER},执行停止"
ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "docker stop '${CURRENT_CONTAINER}'"
else
log INFO "未找到匹配 ${CONTAINER_PREFIX}${CONTAINER_PREFIX}[数字] 的运行容器"
fi
if [[ "${CONTAINER_PREFIX}" == "uemqx" ]]; then
if [[ "${PLATFORM_TYPE}" == "new" ]]; then
REMOTE_EMQX_DIR="/data/middleware/emqx"
else
REMOTE_EMQX_DIR="/var/www/emqx"
fi
sync_emqx_assets "${REMOTE_EMQX_DIR}" "${PLATFORM_TYPE}"
fi
# Python 容器暂不同步文件,功能保留待后续启用
# if [[ "${CONTAINER_PREFIX}" == "upython" ]]; then
# if [[ "${PLATFORM_TYPE}" == "new" ]]; then
# REMOTE_PYTHON_DIR="/data/services/api/python-cmdb"
# else
# REMOTE_PYTHON_DIR="/var/www/html"
# fi
# sync_python_assets "${REMOTE_PYTHON_DIR}" "${PLATFORM_TYPE}"
# fi
if [[ "${CONTAINER_PREFIX}" == "upython" ]]; then
log INFO "Python 容器暂不执行文件同步,仅部署容器"
fi
if [[ "${CONTAINER_PREFIX}" == "unginx" ]]; then
if [[ "${PLATFORM_TYPE}" == "new" ]]; then
REMOTE_NGINX_DIR="/data/middleware/nginx"
sync_nginx_assets "${REMOTE_NGINX_DIR}" "${PLATFORM_TYPE}"
else
log ERROR "Nginx 容器仅支持新统一平台,请重新选择"
exit 20
fi
fi
if [[ -n "${CURRENT_CONTAINER}" ]]; then
LAST_NUM="${CURRENT_CONTAINER##*[!0-9]}"
if [[ -z "${LAST_NUM}" ]]; then
LAST_NUM=0
fi
NEXT_NUM=$((LAST_NUM + 1))
else
NEXT_NUM=1
fi
NEW_CONTAINER="${CONTAINER_PREFIX}${NEXT_NUM}"
log INFO "新容器名称确定为 ${NEW_CONTAINER}"
if [[ -n "${PLATFORM_FLAG}" ]]; then
PLATFORM_TAIL=" '${PLATFORM_FLAG}'"
else
PLATFORM_TAIL=""
fi
# 根据镜像文件路径类型确定远端镜像路径
if [[ "${IMAGE_FILE}" = /* ]]; then
# 绝对路径:使用远端绝对路径
REMOTE_IMAGE_PATH="/data/temp/${IMAGE_NAME}"
else
# 相对路径:使用远端目录下的文件
REMOTE_IMAGE_PATH="${REMOTE_DIR}/${IMAGE_NAME}"
fi
REMOTE_CMD=$(
cat <<EOF
set -euo pipefail
cd '${REMOTE_DIR}'
sed -i 's/\r$//' '${DEPLOY_NAME}' 2>/dev/null || true
chmod +x '${DEPLOY_NAME}'
./'${DEPLOY_NAME}' '${NEW_CONTAINER}' '${REMOTE_IMAGE_PATH}'${PLATFORM_TAIL}
EOF
)
log INFO "开始执行远端部署脚本"
sshpass -p "${REMOTE_PASS}" ssh -tt -p "${SSH_PORT}" -o StrictHostKeyChecking=no -o ConnectTimeout=${SSH_TIMEOUT} "${REMOTE_USER}@${REMOTE_HOST}" "${REMOTE_CMD}"
log INFO "远端部署执行完成"
# 清理远端镜像包和部署脚本
log INFO "开始清理远端镜像包..."
CLEANUP_CMD=$(cat <<EOF
set -e
# 清理镜像包
if [[ -f "${REMOTE_IMAGE_PATH}" ]]; then
rm -f "${REMOTE_IMAGE_PATH}"
echo "已删除镜像包: ${REMOTE_IMAGE_PATH}"
fi
# 清理部署脚本
if [[ -f "${REMOTE_DIR}/${DEPLOY_NAME}" ]]; then
rm -f "${REMOTE_DIR}/${DEPLOY_NAME}"
echo "已删除部署脚本: ${REMOTE_DIR}/${DEPLOY_NAME}"
fi
# 如果远端目录为空,则删除目录
if [[ -d "${REMOTE_DIR}" ]] && [[ -z "\$(ls -A '${REMOTE_DIR}')" ]]; then
rmdir "${REMOTE_DIR}"
echo "已删除空目录: ${REMOTE_DIR}"
fi
EOF
)
CLEANUP_OUTPUT="$(ssh_exec "${REMOTE_USER}@${REMOTE_HOST}" "${CLEANUP_CMD}" 2>&1)" || CLEANUP_STATUS=$?
CLEANUP_STATUS=${CLEANUP_STATUS:-0}
if [[ ${CLEANUP_STATUS} -eq 0 ]]; then
log INFO "✅ 远端镜像包清理完成"
# 输出清理详情
echo "${CLEANUP_OUTPUT}" | while read -r line; do
[[ -n "${line}" ]] && log INFO " ${line}"
done
else
log WARN "⚠️ 远端镜像包清理过程中出现警告,但不影响部署结果"
fi
log INFO "远程升级流程结束"
# --------------------------------------------------------------------------------------------------
# Java 容器部署函数(摘自 new_auto.sh -> java_x86),供后续本地或远端脚本复用。
# 新增逻辑:如检测到旧容器运行,则执行停止与移除操作后再部署,避免名称冲突。
# --------------------------------------------------------------------------------------------------
java_x86() {
log "INFO" "=================================================================="
log "INFO" "开始部署 Java 服务 (Docker 版, x86)"
log "INFO" "=================================================================="
: "${sudoset:=}"
local container_name="ujava2"
local image_tar="/data/temp/java1.8.0_472.tar.gz"
local image_name="139.9.60.86:5000/ujava:v6"
local image_id="a8ba5fa12b8e"
local host_api="/data/services/api"
local host_web="/data/services/web"
local host_nginx_log="/data/middleware/nginx/nginx_log"
local host_fdfs_data="/var/fdfs/storage/data"
local port_args=(
"-p 8085:8085"
"-p 8993:8993" "-p 8994:8994" "-p 8995:8995"
"-p 8999:8999"
"-p 8719:8719" "-p 8720:8720"
"-p 9204:9204" "-p 9200:9200" "-p 9201:9201"
"-p 9905:9905" "-p 9911:9911" "-p 9908:9908"
"-p 9906:9906" "-p 9907:9907" "-p 9909:9909" "-p 9910:9910"
"-p 30880:30880" "-p 30881:30881" "-p 30882:30882"
"-p 30883:30883" "-p 30884:30884"
)
log "INFO" "🔍 检查 Java 容器是否已运行..."
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 正在运行,准备停止"
$sudoset docker stop "${container_name}"
log "INFO" "✅ 旧容器 ${container_name} 已停止"
elif $sudoset docker ps -a --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "WARN" "⚠️ 检测到旧容器 '${container_name}' 存在但未运行,跳过重启以免覆盖"
log "INFO" "🛈 如需重新部署,请手动处理旧容器"
return 1
fi
log "INFO" "🔍 检查 Java 镜像是否存在..."
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -wq "${image_name}"; then
log "INFO" "✅ 镜像 ${image_name} 已存在。"
else
log "INFO" "❌ 镜像 ${image_name} 不存在,开始加载离线包..."
if [[ ! -f "${image_tar}" ]]; then
log "ERROR" "⛔ 镜像文件不存在: ${image_tar}"
return 1
fi
if $sudoset docker load -i "${image_tar}"; then
log "INFO" "🎉 镜像加载成功"
else
log "ERROR" "⛔ 镜像加载失败,请检查文件完整性"
return 1
fi
$sudoset docker tag "${image_id}" "${image_name}" 2>/dev/null || true
log "INFO" "🏷️ 镜像已标记为 ${image_name}"
fi
log "INFO" "🚀 正在启动 Java 容器: ${container_name} ..."
$sudoset docker run -itd \
--privileged \
-v "${host_api}:/var/www/java/api" \
-v "${host_web}:/var/www/java/web" \
-v "${host_nginx_log}:/usr/local/nginx/logs" \
-v /etc/localtime:/etc/localtime:ro \
-v "${host_fdfs_data}:/var/fdfs/storage/data" \
"${port_args[@]}" \
--restart=always \
--mac-address="02:42:ac:11:00:02" \
--name "${container_name}" \
"${image_name}" \
/var/www/java/api/start.sh
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器启动失败"
return 1
fi
log "INFO" "✅ Java 容器启动成功,等待初始化..."
sleep 8
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^${container_name}$"; then
log "INFO" "🎉 Java 服务部署完成!"
$sudoset docker ps --filter "name=${container_name}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
else
log "ERROR" "⛔ 容器启动后未运行,请检查日志: docker logs ${container_name}"
return 1
fi
return 0
}
\ No newline at end of file
#!/usr/bin/env bash
# 容器镜像打包上传至公司网盘脚本
# 功能:
# 1. 将当前目录压缩成 tar.gz 格式文件
# 2. 上传到公司 NAS 网盘(SMB 协议)
# 3. 显示上传进度
set -euo pipefail
# ================================
# 配置区(密码已加密存储)
# ================================
NAS_SERVER="192.168.9.9"
NAS_SHARE="home"
NAS_USER="陈泽键"
NAS_PASS_ENCRYPTED="Mm01ZUwuTUA="
NAS_MOUNT_POINT="/mnt/nas_upload"
# 上传目录(网盘上的目标目录)
NAS_UPLOAD_DIR="容器镜像包"
log() {
local level="$1"; shift
printf '[%(%F %T)T] [%s] %s\n' -1 "${level}" "$*"
}
# 解密密码
decrypt_password() {
echo "${NAS_PASS_ENCRYPTED}" | base64 -d
}
# 检查依赖
check_dependencies() {
local missing_deps=()
if ! command -v cifs-utils >/dev/null 2>&1 && ! command -v mount.cifs >/dev/null 2>&1; then
# 检查是否可以挂载 cifs
if ! grep -q cifs /proc/filesystems 2>/dev/null; then
missing_deps+=("cifs-utils")
fi
fi
if ! command -v pv >/dev/null 2>&1; then
log "WARN" "未安装 pv 工具,将使用 cp 命令(无进度显示)"
fi
if [[ ${#missing_deps[@]} -gt 0 ]]; then
log "ERROR" "缺少依赖: ${missing_deps[*]}"
log "INFO" "请执行: yum install -y ${missing_deps[*]} 或 apt install -y ${missing_deps[*]}"
return 1
fi
return 0
}
# 挂载 NAS
mount_nas() {
local nas_pass
nas_pass="$(decrypt_password)"
# 创建挂载点
if [[ ! -d "${NAS_MOUNT_POINT}" ]]; then
log "INFO" "创建挂载点: ${NAS_MOUNT_POINT}"
sudo mkdir -p "${NAS_MOUNT_POINT}"
fi
# 检查是否已挂载
if mountpoint -q "${NAS_MOUNT_POINT}" 2>/dev/null; then
log "INFO" "NAS 已挂载,跳过挂载步骤"
return 0
fi
log "INFO" "正在挂载 NAS: //${NAS_SERVER}/${NAS_SHARE}"
# 挂载 SMB/CIFS 共享
if ! sudo mount -t cifs "//${NAS_SERVER}/${NAS_SHARE}" "${NAS_MOUNT_POINT}" \
-o "username=${NAS_USER},password=${nas_pass},iocharset=utf8,vers=2.0"; then
log "ERROR" "NAS 挂载失败"
log "INFO" "请检查: 1) 网络连接 2) 账号密码 3) 共享路径"
return 1
fi
log "INFO" "✅ NAS 挂载成功"
return 0
}
# 卸载 NAS
unmount_nas() {
if mountpoint -q "${NAS_MOUNT_POINT}" 2>/dev/null; then
log "INFO" "正在卸载 NAS..."
sudo umount "${NAS_MOUNT_POINT}" || true
fi
}
# 打包当前目录
pack_current_dir() {
local pack_name="$1"
local source_dir="$2"
log "INFO" "开始打包目录: ${source_dir}"
log "INFO" "目标文件: ${pack_name}"
# 获取目录大小用于进度估算
local dir_size
dir_size=$(du -sb "${source_dir}" 2>/dev/null | awk '{print $1}')
if command -v pv >/dev/null 2>&1; then
# 使用 pv 显示打包进度
tar -cf - -C "$(dirname "${source_dir}")" "$(basename "${source_dir}")" | \
pv -s "${dir_size}" -p -t -e -r | \
gzip > "${pack_name}"
else
# 无 pv 时直接打包
tar -czvf "${pack_name}" -C "$(dirname "${source_dir}")" "$(basename "${source_dir}")"
fi
if [[ -f "${pack_name}" ]]; then
local file_size
file_size=$(du -h "${pack_name}" | awk '{print $1}')
log "INFO" "✅ 打包完成,文件大小: ${file_size}"
return 0
else
log "ERROR" "打包失败"
return 1
fi
}
# 上传文件到 NAS(带进度显示)
upload_to_nas() {
local source_file="$1"
local target_dir="${NAS_MOUNT_POINT}/${NAS_UPLOAD_DIR}"
# 确保目标目录存在
if [[ ! -d "${target_dir}" ]]; then
log "INFO" "创建目标目录: ${target_dir}"
sudo mkdir -p "${target_dir}"
fi
local file_name
file_name=$(basename "${source_file}")
local target_file="${target_dir}/${file_name}"
log "INFO" "开始上传文件到 NAS..."
log "INFO" "源文件: ${source_file}"
log "INFO" "目标: //${NAS_SERVER}/${NAS_SHARE}/${NAS_UPLOAD_DIR}/${file_name}"
# 获取文件大小
local file_size
file_size=$(stat -c%s "${source_file}" 2>/dev/null || stat -f%z "${source_file}" 2>/dev/null)
if command -v pv >/dev/null 2>&1; then
# 使用 pv 显示上传进度
pv -p -t -e -r "${source_file}" | sudo tee "${target_file}" > /dev/null
else
# 无 pv 时使用 cp 并显示简单进度
log "INFO" "正在上传(无进度显示)..."
sudo cp "${source_file}" "${target_file}"
fi
# 验证上传结果
if [[ -f "${target_file}" ]]; then
local uploaded_size
uploaded_size=$(sudo du -h "${target_file}" | awk '{print $1}')
log "INFO" "✅ 上传完成!"
log "INFO" " 文件路径: \\\\${NAS_SERVER}\\${NAS_SHARE}\\${NAS_UPLOAD_DIR}\\${file_name}"
log "INFO" " 文件大小: ${uploaded_size}"
return 0
else
log "ERROR" "上传失败,目标文件不存在"
return 1
fi
}
# 显示使用说明
usage() {
cat <<'EOF'
用法:
upload_to_nas.sh [选项] [目录路径]
选项:
-h, --help 显示帮助信息
-n, --name 指定打包文件名(不含扩展名)
-d, --dir 指定上传到网盘的子目录
参数:
目录路径 要打包上传的目录,默认为当前目录
示例:
# 打包并上传当前目录
./upload_to_nas.sh
# 打包并上传指定目录
./upload_to_nas.sh /data/temp/container-images
# 指定打包文件名
./upload_to_nas.sh -n java_container_v6 /data/temp/java
网盘信息:
服务器: \\192.168.9.9\home
账号: 陈泽键
上传目录: 容器镜像包/
EOF
}
# 清理函数
cleanup() {
unmount_nas
}
# 主函数
main() {
local source_dir="."
local pack_name=""
local custom_upload_dir=""
# 解析参数
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-n|--name)
pack_name="$2"
shift 2
;;
-d|--dir)
custom_upload_dir="$2"
shift 2
;;
-*)
log "ERROR" "未知选项: $1"
usage
exit 1
;;
*)
source_dir="$1"
shift
;;
esac
done
# 设置自定义上传目录
if [[ -n "${custom_upload_dir}" ]]; then
NAS_UPLOAD_DIR="${custom_upload_dir}"
fi
# 转换为绝对路径
source_dir=$(cd "${source_dir}" && pwd)
if [[ ! -d "${source_dir}" ]]; then
log "ERROR" "目录不存在: ${source_dir}"
exit 1
fi
# 生成打包文件名
if [[ -z "${pack_name}" ]]; then
local dir_name
dir_name=$(basename "${source_dir}")
local timestamp
timestamp=$(date +%Y%m%d_%H%M%S)
pack_name="${dir_name}_${timestamp}"
fi
local pack_file="/tmp/${pack_name}.tar.gz"
log "INFO" "=================================================================="
log "INFO" "容器镜像打包上传工具"
log "INFO" "=================================================================="
log "INFO" "源目录: ${source_dir}"
log "INFO" "打包文件: ${pack_file}"
log "INFO" "目标网盘: \\\\${NAS_SERVER}\\${NAS_SHARE}\\${NAS_UPLOAD_DIR}"
log "INFO" "=================================================================="
# 设置清理钩子
trap cleanup EXIT
# 检查依赖
if ! check_dependencies; then
exit 1
fi
# 步骤1: 打包目录
log "INFO" "[步骤 1/3] 打包目录..."
if ! pack_current_dir "${pack_file}" "${source_dir}"; then
exit 1
fi
# 步骤2: 挂载 NAS
log "INFO" "[步骤 2/3] 挂载 NAS..."
if ! mount_nas; then
exit 1
fi
# 步骤3: 上传文件
log "INFO" "[步骤 3/3] 上传文件..."
if ! upload_to_nas "${pack_file}"; then
exit 1
fi
# 清理临时文件
log "INFO" "清理临时文件..."
rm -f "${pack_file}"
log "INFO" "=================================================================="
log "INFO" "🎉 全部完成!"
log "INFO" "=================================================================="
return 0
}
main "$@"
......@@ -942,6 +942,7 @@ def dingding_send_message(latest_report, title, mobile, ding_type):
# 调用测试结果获取函数
browser_init("兰州中石化项目测试环境")
sleep(15)
wd = GSTORE['wd']
# print(latest_report)
test_result = get_test_result(latest_report, wd)
......@@ -2118,21 +2119,21 @@ def get_remote_log_with_paramiko(host, username, private_key_path, passphrase, l
if 'client' in locals():
client.close()
if __name__ == "__main__":
host = "192.168.5.218" # 替换为你的服务器 IP 或域名
username = "root" # 替换为你的用户名
# private_key_path = "C:\\Users\\29194\\.ssh\\id_rsa" # 替换为你的私钥文件路径
private_key_path = "C:/Users/29194/.ssh/id_rsa"
passphrase = "Ubains@123" # 确保这是你的私钥文件的正确 passphrase
log_path = "/var/www/java/api-java-meeting2.0/logs/ubains-INFO-AND-ERROR.log" # 替换为你要读取的日志文件路径
filter_word = "" # 替换为你想要过滤的关键字
log_content = get_remote_log_with_paramiko(host, username, private_key_path, passphrase, log_path)
if log_content:
print(log_content)
else:
print("Failed to retrieve log content.")
# if __name__ == "__main__":
# host = "192.168.5.218" # 替换为你的服务器 IP 或域名
# username = "root" # 替换为你的用户名
# # private_key_path = "C:\\Users\\29194\\.ssh\\id_rsa" # 替换为你的私钥文件路径
# private_key_path = "C:/Users/29194/.ssh/id_rsa"
# passphrase = "Ubains@123" # 确保这是你的私钥文件的正确 passphrase
# log_path = "/var/www/java/api-java-meeting2.0/logs/ubains-INFO-AND-ERROR.log" # 替换为你要读取的日志文件路径
# filter_word = "" # 替换为你想要过滤的关键字
#
# log_content = get_remote_log_with_paramiko(host, username, private_key_path, passphrase, log_path)
#
# if log_content:
# print(log_content)
# else:
# print("Failed to retrieve log content.")
......@@ -2228,24 +2229,24 @@ class LogCollector:
print("Log collection thread terminated.")
# 使用示例
if __name__ == "__main__":
host = "192.168.5.218"
username = "root"
private_key_path = "C:\\Users\\29194\\.ssh\\id_rsa" # 替换为你的私钥文件路径
passphrase = "Ubains@123" # 替换为你的 passphrase
log_path = "/var/www/java/api-java-meeting2.0/logs/ubains-INFO-AND-ERROR.log"
output_file = "collected_logs.txt"
collector = LogCollector(host, username, private_key_path, passphrase, log_path)
# 开始收集日志
collector.start_collection()
# 假设一段时间后停止收集日志
try:
while True:
time.sleep(1) # 保持主程序运行
except KeyboardInterrupt:
print("Stopping log collection due to user interrupt.")
collector.stop_collection(output_file)
print("日志收集完成!")
\ No newline at end of file
# if __name__ == "__main__":
# host = "192.168.5.218"
# username = "root"
# private_key_path = "C:\\Users\\29194\\.ssh\\id_rsa" # 替换为你的私钥文件路径
# passphrase = "Ubains@123" # 替换为你的 passphrase
# log_path = "/var/www/java/api-java-meeting2.0/logs/ubains-INFO-AND-ERROR.log"
# output_file = "collected_logs.txt"
#
# collector = LogCollector(host, username, private_key_path, passphrase, log_path)
#
# # 开始收集日志
# collector.start_collection()
#
# # 假设一段时间后停止收集日志
# try:
# while True:
# time.sleep(1) # 保持主程序运行
# except KeyboardInterrupt:
# print("Stopping log collection due to user interrupt.")
# collector.stop_collection(output_file)
# print("日志收集完成!")
\ No newline at end of file
......@@ -49,19 +49,19 @@ class MeetingManageQuickTest:
if element_type == "click":
safe_click((locator_type, locator_value), wd)
sleep(2)
SELENIUM_LOG_SCREEN(wd, "75")
# SELENIUM_LOG_SCREEN(wd, "75")
elif element_type == "input":
safe_send_keys((locator_type, locator_value), element_value, wd)
sleep(2)
SELENIUM_LOG_SCREEN(wd, "75")
# SELENIUM_LOG_SCREEN(wd, "75")
elif element_type == "SwitchWindow":
# 将字符转换为int类型
element_value = int(element_value)
wd.switch_to.window(wd.window_handles[element_value])
sleep(2)
SELENIUM_LOG_SCREEN(wd, "75")
# SELENIUM_LOG_SCREEN(wd, "75")
elif element_type == "login":
# 退出系统登录
......
......@@ -86,7 +86,7 @@ start_workers(3)
# 定时执行预定系统测试任务
schedule.every().day.at("10:00").do(run_task, run_automation_test, report_title="预定系统测试报告", report_url_prefix="http://nat.ubainsyun.com:31136", test_case="预定系统测试", ding_type="标准版巡检")
# 定时每一小时执行一次预定快速测试任务
schedule.every(2).hours.do(run_task, run_automation_test, report_title="预定系统快速测试测试报告", report_url_prefix="http://nat.ubainsyun.com:31136", test_case="预定系统快速测试", ding_type="标准版巡检")
schedule.every(0.5).hours.do(run_task, run_automation_test, report_title="预定系统快速测试测试报告", report_url_prefix="http://nat.ubainsyun.com:31136", test_case="预定系统快速测试", ding_type="标准版巡检")
# 定时执行展厅巡检任务
# schedule.every().day.at("07:45").do(run_task, run_automation_test, report_title="展厅巡检测试报告", report_url_prefix="http://nat.ubainsyun.com:31136", test_case="展厅巡检", ding_type="展厅巡检")
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论