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

docs(prd): 添加自动更新部署包版本需求文档并移除旧文档

- 添加新的自动更新部署包版本需求文档
- 移除自动打包上传需求文档
- 移除远程更新程序需求文档及计划执行文档
- 更新远程部署脚本支持多架构配置
- 添加ARM-Ubuntu部署配置和sudo权限支持
- 优化SSH命令执行函数支持sudo模式
上级 e2634b4e
......@@ -52,7 +52,8 @@ CONFIGS = {
'license_path': r'E:\自动化部署\X86-5.52\license.zip',
'output_dir': r'E:\自动化部署\X86-5.52',
'timeout': 3600, # 60分钟
'label': 'X86 (192.168.5.52)',
'label': 'X86-欧拉 (192.168.5.52)',
'use_sudo': False,
},
'arm': {
'host': '192.168.9.75',
......@@ -67,10 +68,31 @@ CONFIGS = {
'license_path': r'E:\自动化部署\ARM-9.75\license.zip',
'output_dir': r'E:\自动化部署\ARM-9.75',
'timeout': 5400, # 90分钟(ARM Java启动慢)
'label': 'ARM (192.168.9.75)',
'label': 'ARM-欧拉 (192.168.9.75)',
'use_sudo': False,
},
'arm_ubuntu': {
'host': '192.168.9.76',
'username': 'admin',
'password': 'Ubains@123',
'deploy_dir': '/data/arm_offline_auto_unifiedPlatform',
'deploy_pkg': 'arm_offline_auto_unifiedPlatform.tar.gz',
'deploy_md5': 'arm_offline_auto_unifiedPlatform.tar.gz.md5',
'deploy_script': 'arm_new_auto.sh',
'deploy_answers': "y\ny\ny\ny\ny\ny\ny\nn\n",
'deploy_log': '/data/arm_offline_auto_unifiedPlatform/deploy_output.log',
'license_path': r'E:\自动化部署\ARM-9.76\license.zip',
'output_dir': r'E:\自动化部署\ARM-9.76',
'timeout': 5400, # 90分钟(ARM Java启动慢)
'label': 'ARM-Ubuntu (192.168.9.76)',
'use_sudo': True,
'sudo_password': 'Ubains@123',
}
}
# 所有架构列表
ALL_ARCHS = ['x86', 'arm', 'arm_ubuntu']
# 超管配置
ADMIN_USER = 'superadmin'
ADMIN_PASS = 'Ubains@1357'
......@@ -124,12 +146,21 @@ def ssh_connect(host, username='root', password='Ubains@123', timeout=30):
return ssh
def ssh_exec(ssh, cmd, timeout=300):
"""执行SSH命令并返回结果"""
def ssh_exec(ssh, cmd, timeout=300, use_sudo=False, sudo_password=None):
"""执行SSH命令并返回结果。若use_sudo=True,通过sudo -S执行"""
if use_sudo and sudo_password:
# 用sudo -S执行,通过stdin传入密码
escaped_cmd = cmd.replace("'", "'\\''")
actual_cmd = f"echo '{sudo_password}' | sudo -S bash -c '{escaped_cmd}'"
stdin, stdout, stderr = ssh.exec_command(actual_cmd, timeout=timeout)
else:
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=timeout)
out = stdout.read().decode('utf-8', errors='replace')
err = stderr.read().decode('utf-8', errors='replace')
exit_code = stdout.channel.recv_exit_status()
# 过滤sudo密码提示
if use_sudo and '[sudo]' in out:
out = out.split('\n', 1)[-1] if '\n' in out else out
return exit_code, out, err
......@@ -180,6 +211,12 @@ def phase_deploy_single(arch):
logger = Logger(f"{arch}_deploy")
results = {'arch': arch, 'steps': {}, 'containers': [], 'errors': []}
start_time = time.time()
use_sudo = cfg.get('use_sudo', False)
sudo_password = cfg.get('sudo_password')
# 创建带sudo支持的命令执行器
def run(cmd, timeout=300):
return ssh_exec(ssh, cmd, timeout, use_sudo, sudo_password)
logger.log(f"========== {cfg['label']} 部署开始 ==========")
......@@ -187,6 +224,10 @@ def phase_deploy_single(arch):
try:
ssh = ssh_connect(cfg['host'], cfg['username'], cfg['password'])
logger.log("SSH连接成功")
if use_sudo:
# 验证sudo权限
_, sudo_test, _ = run("whoami")
logger.log(f"sudo权限验证: {sudo_test.strip()}")
except Exception as e:
logger.log(f"SSH连接失败: {e}", "ERROR")
results['errors'].append(f"SSH连接失败: {e}")
......@@ -195,7 +236,7 @@ def phase_deploy_single(arch):
try:
# 步骤1:磁盘检查
logger.log("【步骤1】磁盘检查")
_, out, _ = ssh_exec(ssh, "df -h /data")
_, out, _ = run("df -h /data")
if '/data' in out:
logger.log("[OK] /data 分区存在")
results['steps']['disk'] = True
......@@ -209,25 +250,21 @@ def phase_deploy_single(arch):
pkg = cfg['deploy_pkg']
md5 = cfg['deploy_md5']
# 检查标准命名是否存在,不存在则检查_new命名
_, check_out, _ = ssh_exec(ssh, f"ls -la /data/{pkg} /data/{md5} 2>&1")
_, check_out, _ = run(f"ls -la /data/{pkg} /data/{md5} 2>&1")
if 'No such file' in check_out or '无法访问' in check_out:
# 尝试_new后缀
pkg_new = pkg.replace('.tar.gz', '_new.tar.gz')
md5_new = md5.replace('.tar.gz.md5', '_new.tar.gz.md5')
_, check_new, _ = ssh_exec(ssh, f"ls -la /data/{pkg_new} /data/{md5_new} 2>&1")
_, check_new, _ = run(f"ls -la /data/{pkg_new} /data/{md5_new} 2>&1")
if 'No such file' in check_new or '无法访问' in check_new:
logger.log("[FAIL] 部署包不存在(标准名和_new名都找不到)", "ERROR")
results['steps']['package'] = False
results['errors'].append("部署包不存在")
else:
# 创建软链接到标准命名
logger.log(f"找到_new命名包,创建软链接到标准名")
ssh_exec(ssh, f"ln -sf /data/{pkg_new} /data/{pkg}")
ssh_exec(ssh, f"ln -sf /data/{md5_new} /data/{md5}")
run(f"ln -sf /data/{pkg_new} /data/{pkg}")
run(f"ln -sf /data/{md5_new} /data/{md5}")
logger.log(f"[OK] 软链接创建完成: {pkg} -> {pkg_new}")
# MD5校验
_, md5_out, _ = ssh_exec(ssh, f"cd /data && md5sum -c {md5}")
_, md5_out, _ = run(f"cd /data && md5sum -c {md5}")
if 'OK' in md5_out or '成功' in md5_out:
logger.log("[OK] MD5校验通过")
results['steps']['package'] = True
......@@ -235,7 +272,7 @@ def phase_deploy_single(arch):
logger.log(f"[WARN] MD5校验结果: {md5_out.strip()}", "WARN")
results['steps']['package'] = True
else:
_, md5_out, _ = ssh_exec(ssh, f"cd /data && md5sum -c {cfg['deploy_md5']}")
_, md5_out, _ = run(f"cd /data && md5sum -c {cfg['deploy_md5']}")
if 'OK' in md5_out or '成功' in md5_out:
logger.log("[OK] MD5校验通过")
results['steps']['package'] = True
......@@ -244,42 +281,47 @@ def phase_deploy_single(arch):
results['steps']['package'] = True
# ARM前置处理:调整Nacos cron间隔防止竞态
if arch == 'arm':
if arch in ('arm', 'arm_ubuntu'):
logger.log("【ARM前置】检查Nacos cron监控间隔")
_, cron_out, _ = ssh_exec(ssh,
"crontab -l 2>/dev/null | grep -i nacos || echo 'no_nacos_cron'")
_, cron_out, _ = run("crontab -l 2>/dev/null | grep -i nacos || echo 'no_nacos_cron'")
if 'nacos' in cron_out.lower() and '*/3' in cron_out:
logger.log("检测到3分钟Nacos cron,调整为10分钟")
ssh_exec(ssh,
"crontab -l 2>/dev/null | sed 's|\\*/3 \\* \\* \\* \\*|*/10 * * * *|g' | crontab -")
run("crontab -l 2>/dev/null | sed 's|\\*/3 \\* \\* \\* \\*|*/10 * * * *|g' | crontab -")
logger.log("[OK] Nacos cron间隔已调整为10分钟")
# 步骤3:解压(禁止中断)
logger.log("【步骤3】解压部署包(禁止中断!)")
exit_code, check_out, _ = ssh_exec(ssh, f"ls {cfg['deploy_dir']}/{cfg['deploy_script']} 2>&1")
exit_code, check_out, _ = run(f"ls {cfg['deploy_dir']}/{cfg['deploy_script']} 2>&1")
if exit_code == 0:
logger.log("[OK] 部署脚本已存在,跳过解压")
results['steps']['extract'] = True
else:
# 解压部署包(统一使用脚本方式避免shell嵌套引号问题)
extract_script = f"""#!/bin/bash
cd /data
tar -zxvf {cfg['deploy_pkg']}
echo "EXTRACT_DONE"
"""
ssh_exec(ssh, f"cat > /tmp/extract.sh << 'EOF'\n{extract_script}\nEOF")
if use_sudo:
# sudo环境:写脚本后用sudo执行
ssh_exec(ssh, f"cat > /tmp/extract.sh << 'EXTRACT_EOF'\n{extract_script}\nEXTRACT_EOF")
ssh_exec(ssh, "chmod +x /tmp/extract.sh")
ssh_exec(ssh,
f"nohup bash -c 'echo {sudo_password} | sudo -S /tmp/extract.sh' "
f"</dev/null >/data/extract_output.log 2>&1 & echo $!")
else:
ssh_exec(ssh, f"cat > /tmp/extract.sh << 'EXTRACT_EOF'\n{extract_script}\nEXTRACT_EOF")
ssh_exec(ssh, "chmod +x /tmp/extract.sh")
output = ssh_exec_bg(ssh,
ssh_exec_bg(ssh,
"nohup /tmp/extract.sh </dev/null > /data/extract_output.log 2>&1 & echo $!")
pid = output.strip().split('\n')[-1].strip()
logger.log(f"解压进程PID: {pid}")
# 等待解压完成
logger.log("解压进行中...")
max_wait = 1200
waited = 0
while waited < max_wait:
time.sleep(10)
waited += 10
_, done_out, _ = ssh_exec(ssh,
_, done_out, _ = run(
"grep 'EXTRACT_DONE' /data/extract_output.log 2>/dev/null && echo 'done' || echo 'running'")
if waited % 60 == 0:
logger.log(f"解压进行中... {waited}秒/{max_wait}秒")
......@@ -292,16 +334,17 @@ echo "EXTRACT_DONE"
results['steps']['extract'] = False
results['errors'].append("解压超时")
else:
# 赋权
exit_code, _, _ = ssh_exec(ssh, f"cd {cfg['deploy_dir']} && chmod 755 *.sh")
run(f"cd {cfg['deploy_dir']} && chmod 755 *.sh")
logger.log("[OK] 脚本赋权完成")
results['steps']['extract'] = True
# 步骤4:运行部署脚本
logger.log(f"【步骤4】运行 {cfg['deploy_script']} --all(禁止中断!)")
deploy_start = time.time()
ssh_exec(ssh, f"rm -f {cfg['deploy_log']}")
run(f"rm -f {cfg['deploy_log']}")
if use_sudo:
# ARM-Ubuntu需要sudo运行部署脚本
launcher = f"""#!/bin/bash
cd {cfg['deploy_dir']}
export TERM=dumb
......@@ -310,17 +353,30 @@ echo "DEPLOY_SCRIPT_FINISHED"
"""
ssh_exec(ssh, f"cat > /tmp/run_deploy.sh << 'DEPLOY_EOF'\n{launcher}\nDEPLOY_EOF")
ssh_exec(ssh, "chmod +x /tmp/run_deploy.sh")
output = ssh_exec_bg(ssh,
# 用sudo执行部署脚本
ssh_exec_bg(ssh,
f"nohup bash -c 'echo {sudo_password} | sudo -S /tmp/run_deploy.sh' "
f"</dev/null >{cfg['deploy_log']} 2>&1 & echo $!")
else:
launcher = f"""#!/bin/bash
cd {cfg['deploy_dir']}
export TERM=dumb
printf '{cfg['deploy_answers']}' | ./{cfg['deploy_script']} --all
echo "DEPLOY_SCRIPT_FINISHED"
"""
ssh_exec(ssh, f"cat > /tmp/run_deploy.sh << 'DEPLOY_EOF'\n{launcher}\nDEPLOY_EOF")
ssh_exec(ssh, "chmod +x /tmp/run_deploy.sh")
ssh_exec_bg(ssh,
f"nohup /tmp/run_deploy.sh </dev/null >{cfg['deploy_log']} 2>&1 & echo $!")
pid = output.strip().split('\n')[-1].strip()
logger.log(f"部署进程PID: {pid}")
# 查找实际PID
if not (pid and pid.isdigit()):
time.sleep(5)
_, pid_out, _ = ssh_exec(ssh, f"pgrep -f '{cfg['deploy_script']}' | head -1")
pid = pid_out.strip()
if use_sudo:
ssh_exec_bg(ssh,
f"nohup /tmp/run_deploy.sh </dev/null >{cfg['deploy_log']} 2>&1 & echo $!")
else:
ssh_exec_bg(ssh,
f"nohup /tmp/run_deploy.sh </dev/null >{cfg['deploy_log']} 2>&1 & echo $!")
pid = 'monitoring'
logger.log("部署脚本已启动,监控日志进度...")
# 监控部署进度
max_wait = cfg['timeout']
......@@ -334,9 +390,9 @@ echo "DEPLOY_SCRIPT_FINISHED"
if current_min > last_report_min and current_min % 5 == 0:
last_report_min = current_min
_, lines_out, _ = ssh_exec(ssh, f"wc -l {cfg['deploy_log']} 2>/dev/null || echo '0'")
_, lines_out, _ = run(f"wc -l {cfg['deploy_log']} 2>/dev/null || echo '0'")
log_lines = lines_out.strip().split()[0] if lines_out.strip() else '0'
_, tail_out, _ = ssh_exec(ssh,
_, tail_out, _ = run(
f"tail -10 {cfg['deploy_log']} 2>/dev/null | grep -E "
f"'部署|安装|启动|完成|成功|失败|ERROR|容器|服务' | tail -3")
logger.log(f"[进度] {current_min}分钟 | 日志行数: {log_lines}")
......@@ -346,19 +402,17 @@ echo "DEPLOY_SCRIPT_FINISHED"
logger.log(f" > {line.strip()}")
# 检查完成标志
_, finish_out, _ = ssh_exec(ssh,
_, finish_out, _ = run(
f"grep 'DEPLOY_SCRIPT_FINISHED' {cfg['deploy_log']} 2>/dev/null && echo 'FINISHED' || echo 'RUNNING'")
if 'FINISHED' in finish_out:
logger.log("[OK] 部署脚本执行完成")
break
# 检查进程是否还存在
if pid and pid.isdigit():
_, ps_out, _ = ssh_exec(ssh,
f"ps -p {pid} > /dev/null 2>&1 && echo 'running' || echo 'done'")
# 检查进程是否还在运行
_, ps_out, _ = run(f"pgrep -f '{cfg['deploy_script']}' > /dev/null 2>&1 && echo 'running' || echo 'done'")
if 'done' in ps_out:
time.sleep(5)
_, finish2, _ = ssh_exec(ssh,
_, finish2, _ = run(
f"grep 'DEPLOY_SCRIPT_FINISHED' {cfg['deploy_log']} 2>/dev/null && echo 'FINISHED' || echo 'NOT_FINISHED'")
if 'FINISHED' in finish2 or 'source /etc/profile' in finish2:
logger.log("[OK] 部署脚本执行完成")
......@@ -370,7 +424,7 @@ echo "DEPLOY_SCRIPT_FINISHED"
results['errors'].append(f"部署超时({max_wait}秒)")
# 执行 source /etc/profile
ssh_exec(ssh, "source /etc/profile")
run("source /etc/profile")
deploy_time = int((time.time() - deploy_start) / 60)
logger.log(f"部署用时: {deploy_time}分钟")
......@@ -379,7 +433,7 @@ echo "DEPLOY_SCRIPT_FINISHED"
# 步骤5:容器验证
logger.log("【步骤5】容器状态检查")
_, docker_out, _ = ssh_exec(ssh, "docker ps --format 'table {{.Names}}\\t{{.Status}}'")
_, docker_out, _ = run("docker ps --format 'table {{.Names}}\\t{{.Status}}'")
container_lines = [l for l in docker_out.split('\n') if l.strip() and 'NAMES' not in l]
results['containers'] = [l.strip() for l in container_lines]
results['container_count'] = len(container_lines)
......@@ -388,9 +442,9 @@ echo "DEPLOY_SCRIPT_FINISHED"
logger.log(f" {line}")
# ARM EMQX 专项验证
if arch == 'arm':
if arch in ('arm', 'arm_ubuntu'):
logger.log("【ARM专项】验证EMQX容器架构")
_, emqx_out, _ = ssh_exec(ssh, "docker inspect uemqx --format '{{.Image}}' 2>&1")
_, emqx_out, _ = run("docker inspect uemqx --format '{{.Image}}' 2>&1")
logger.log(f"EMQX镜像: {emqx_out.strip()}")
_, emqx_log, _ = ssh_exec(ssh, "docker logs uemqx --tail 5 2>&1")
if 'exec format error' in emqx_log:
......@@ -402,8 +456,7 @@ echo "DEPLOY_SCRIPT_FINISHED"
results['emqx_ok'] = True
# 检查是否有退出的容器
_, exited_out, _ = ssh_exec(ssh,
"docker ps -a --filter 'status=exited' --format '{{.Names}} {{.Status}}'")
_, exited_out, _ = run("docker ps -a --filter 'status=exited' --format '{{.Names}} {{.Status}}'")
if exited_out.strip():
logger.log(f"[WARN] 已退出的容器:\n{exited_out}", "WARN")
......@@ -420,16 +473,16 @@ echo "DEPLOY_SCRIPT_FINISHED"
def phase_deploy():
"""并行部署X86和ARM(阶段1-3)"""
"""并行部署X86 + ARM-欧拉 + ARM-Ubuntu(阶段1-3)"""
print("\n" + "=" * 60)
print("阶段 1-3:并行部署 X86 + ARM")
print("阶段 1-3:并行部署 3台服务器")
print("=" * 60)
results = {}
with ThreadPoolExecutor(max_workers=2) as pool:
with ThreadPoolExecutor(max_workers=3) as pool:
futures = {
pool.submit(phase_deploy_single, 'x86'): 'x86',
pool.submit(phase_deploy_single, 'arm'): 'arm',
pool.submit(phase_deploy_single, arch): arch
for arch in ALL_ARCHS
}
for future in as_completed(futures):
arch = futures[future]
......@@ -451,7 +504,7 @@ def phase_verify(deploy_results=None):
results = {}
for arch in ['x86', 'arm']:
for arch in ALL_ARCHS:
cfg = CONFIGS[arch]
logger = Logger(f"{arch}_verify")
r = {'api': {}, 'logs': {}, 'errors': []}
......@@ -466,7 +519,9 @@ def phase_verify(deploy_results=None):
# API测试
try:
ssh = ssh_connect(cfg['host'])
ssh = ssh_connect(cfg['host'], cfg['username'], cfg['password'])
use_sudo = cfg.get('use_sudo', False)
sudo_password = cfg.get('sudo_password')
base_url = f"https://{cfg['host']}"
for endpoint in API_ENDPOINTS:
......@@ -481,7 +536,6 @@ def phase_verify(deploy_results=None):
resp = requests.get(url, verify=False, timeout=30)
if any(kw in resp.text for kw in keywords):
logger.log(f" [OK] {name} 正常 (第{retry}次)")
# 成功后等30秒再确认
time.sleep(30)
resp2 = requests.get(url, verify=False, timeout=30)
if any(kw in resp2.text for kw in keywords):
......@@ -503,7 +557,8 @@ def phase_verify(deploy_results=None):
# 日志检查
for svc_name, log_path in SERVICE_LOGS.items():
_, out, _ = ssh_exec(ssh, f"tail -100 {log_path} 2>/dev/null | grep -ic 'error\\|exception'")
_, out, _ = ssh_exec(ssh, f"tail -100 {log_path} 2>/dev/null | grep -ic 'error\\|exception'",
use_sudo=use_sudo, sudo_password=sudo_password)
count = int(out.strip()) if out.strip().isdigit() else -1
if count > 0:
logger.log(f"[WARN] {svc_name}: {count}条异常", "WARN")
......@@ -535,20 +590,27 @@ def phase_security():
results = {}
for arch in ['x86', 'arm']:
for arch in ALL_ARCHS:
cfg = CONFIGS[arch]
logger = Logger(f"{arch}_security")
r = {'nacos': {}, 'emqx': {}, 'errors': []}
use_sudo = cfg.get('use_sudo', False)
sudo_password = cfg.get('sudo_password')
logger.log(f"========== {cfg['label']} 安全修复 ==========")
try:
ssh = ssh_connect(cfg['host'])
ssh = ssh_connect(cfg['host'], cfg['username'], cfg['password'])
# 创建带sudo支持的执行器
def run(cmd, timeout=300):
return ssh_exec(ssh, cmd, timeout, use_sudo, sudo_password)
# ---- 1. Nacos 认证绕过修复 ----
logger.log("### 1. Nacos 认证绕过修复 ###")
# 生成随机身份密钥
identity_key = ''.join(
secrets.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
for _ in range(32))
......@@ -560,13 +622,11 @@ def phase_security():
logger.log(f"Identity Key: {identity_key}")
logger.log(f"Token Secret: {token_secret[:20]}...")
# 备份
ssh_exec(ssh, 'docker exec unacos cp /home/nacos/conf/application.properties '
run('docker exec unacos cp /home/nacos/conf/application.properties '
'/home/nacos/conf/application.properties.bak')
ssh_exec(ssh, 'docker cp unacos:/home/nacos/conf/application.properties /tmp/nacos-backup.properties')
run('docker cp unacos:/home/nacos/conf/application.properties /tmp/nacos-backup.properties')
logger.log("Nacos配置已备份")
# 修改配置
cmds = [
f"docker exec unacos sed -i 's|nacos.core.auth.server.identity.key=.*|"
f"nacos.core.auth.server.identity.key={identity_key}|g' /home/nacos/conf/application.properties",
......@@ -576,55 +636,51 @@ def phase_security():
f"nacos.core.auth.plugin.nacos.token.secret.key={token_secret}|g' /home/nacos/conf/application.properties",
]
for cmd in cmds:
ssh_exec(ssh, cmd)
run(cmd)
# 验证配置已更新
_, verify_out, _ = ssh_exec(ssh,
_, verify_out, _ = run(
'docker exec unacos grep "identity.key\\|identity.value\\|token.secret" '
'/home/nacos/conf/application.properties')
logger.log(f"Nacos配置更新:\n{verify_out}")
# 重启Nacos
logger.log("重启Nacos...")
ssh_exec(ssh, 'docker restart unacos')
run('docker restart unacos')
time.sleep(30)
_, status_out, _ = ssh_exec(ssh,
'curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8848/nacos/')
_, status_out, _ = run('curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8848/nacos/')
logger.log(f"Nacos状态: {status_out.strip()}")
# 重启Java(token变更需要重新连接)
logger.log("重启Java服务...")
ssh_exec(ssh, 'docker restart ujava2')
run('docker restart ujava2')
logger.log("Java重启中(约2-5分钟)")
# Nginx限制Nacos访问
logger.log("配置Nginx限制Nacos访问...")
_, check_out, _ = ssh_exec(ssh,
_, check_out, _ = run(
'docker exec unginx grep "allow 172.17" /etc/nginx/conf.d/unified443.conf | head -1')
if not check_out.strip():
ssh_exec(ssh,
run(
'docker exec unginx bash -c "'
'sed -i \'\\/location \\/nacos\\//,/proxy_pass/ { /proxy_set_header Host/i\\\\'
' allow 172.17.0.0/16;\\n allow 127.0.0.1;\\n deny all; }\' '
'/etc/nginx/conf.d/unified443.conf"')
_, test_out, _ = ssh_exec(ssh, 'docker exec unginx nginx -t 2>&1')
_, test_out, _ = run('docker exec unginx nginx -t 2>&1')
if 'successful' in test_out:
ssh_exec(ssh, 'docker exec unginx nginx -s reload')
run('docker exec unginx nginx -s reload')
logger.log("[OK] Nginx配置已重载")
else:
logger.log(f"[WARN] Nginx配置测试失败: {test_out.strip()}", "WARN")
# 验证Nacos修复
_, old_out, _ = ssh_exec(ssh,
_, old_out, _ = run(
'curl -s -w "\\nHTTP:%{http_code}" '
'"http://127.0.0.1:8848/nacos/v1/auth/users?search=blur&pageNo=1&pageSize=10" '
'-H "nacos: nacos"')
r['nacos']['old_header_rejected'] = '403' in old_out
logger.log(f"旧nacos:nacos头: {'被拒绝(403)' if '403' in old_out else '未拒绝'}")
_, new_out, _ = ssh_exec(ssh,
_, new_out, _ = run(
f'curl -s -w "\\nHTTP:%{{http_code}}" '
f'"http://127.0.0.1:8848/nacos/v1/auth/users?search=blur&pageNo=1&pageSize=10" '
f'-H "{identity_key}: {identity_value}"')
......@@ -640,9 +696,8 @@ def phase_security():
# ---- 2. EMQX MQTT弱密码修复 ----
logger.log("### 2. EMQX MQTT弱密码修复 ###")
# ARM需要先验证EMQX是否正常运行
if arch == 'arm':
_, emqx_log, _ = ssh_exec(ssh, "docker logs uemqx --tail 5 2>&1")
if arch in ('arm', 'arm_ubuntu'):
_, emqx_log, _ = run("docker logs uemqx --tail 5 2>&1")
if 'exec format error' in emqx_log:
logger.log("[SKIP] ARM EMQX架构不兼容,跳过MQTT修复", "WARN")
r['emqx']['skipped'] = True
......@@ -653,7 +708,6 @@ def phase_security():
else:
logger.log("[OK] ARM EMQX运行正常,继续修复")
# 启用密码认证
auth_config = '''authentication = [
{
backend = "built_in_database"
......@@ -666,14 +720,13 @@ def phase_security():
}
]
'''
ssh_exec(ssh, f"cat > /tmp/enable_auth.conf << 'EOFCONFIG'\n{auth_config}EOFCONFIG")
ssh_exec(ssh, 'docker cp /tmp/enable_auth.conf uemqx:/tmp/enable_auth.conf')
_, load_out, _ = ssh_exec(ssh,
run(f"cat > /tmp/enable_auth.conf << 'EOFCONFIG'\n{auth_config}EOFCONFIG")
run('docker cp /tmp/enable_auth.conf uemqx:/tmp/enable_auth.conf')
_, load_out, _ = run(
'docker exec uemqx emqx ctl conf load --replace /tmp/enable_auth.conf 2>&1')
logger.log(f"EMQX认证配置: {load_out.strip()}")
# 获取EMQX管理token并注册凭据
_, token_out, _ = ssh_exec(ssh,
_, token_out, _ = run(
'''docker exec uemqx curl -s -X POST "http://127.0.0.1:18083/api/v5/login" '''
'''-H "Content-Type: application/json" '''
'''-d '{"username": "admin", "password": "Admin@2026Secure"}' '''
......@@ -682,7 +735,7 @@ def phase_security():
token = token_out.strip()
if token and 'ey' in token:
_, reg_out, _ = ssh_exec(ssh,
_, reg_out, _ = run(
f'''docker exec uemqx curl -s -X POST '''
f'''"http://127.0.0.1:18083/api/v5/authentication/password_based:built_in_database/users" '''
f'''-H "Content-Type: application/json" '''
......@@ -690,8 +743,7 @@ def phase_security():
f'''-d '{{"user_id": "mqtt@cmdb", "password": "mqtt@webpassw0RD", "is_superuser": false}}' ''')
logger.log(f"MQTT凭据注册: {reg_out[:100]}")
# 验证
_, emqx_status, _ = ssh_exec(ssh, 'docker exec uemqx emqx ctl status 2>&1')
_, emqx_status, _ = run('docker exec uemqx emqx ctl status 2>&1')
r['emqx']['status'] = emqx_status.strip()
r['emqx']['fixed'] = True
logger.log(f"[OK] EMQX状态: {emqx_status.strip()}")
......@@ -716,24 +768,34 @@ def phase_package():
results = {}
for arch in ['x86', 'arm']:
for arch in ALL_ARCHS:
cfg = CONFIGS[arch]
logger = Logger(f"{arch}_package")
r = {'files': {}, 'errors': []}
use_sudo = cfg.get('use_sudo', False)
sudo_password = cfg.get('sudo_password')
logger.log(f"========== {cfg['label']} 打包输出 ==========")
try:
ssh = ssh_connect(cfg['host'])
ssh = ssh_connect(cfg['host'], cfg['username'], cfg['password'])
def run(cmd, timeout=300):
return ssh_exec(ssh, cmd, timeout, use_sudo, sudo_password)
# 压缩部署目录
pkg_name = cfg['deploy_pkg']
md5_name = cfg['deploy_md5']
logger.log(f"压缩部署目录: {cfg['deploy_dir']} -> {pkg_name}")
logger.log("压缩过程可能需要较时间,请耐心等待...")
logger.log("压缩过程可能需要较时间,请耐心等待...")
# 后台压缩
if use_sudo:
compress_cmd = (
f"cd /data && nohup bash -c 'echo {sudo_password} | sudo -S tar -zcvf {pkg_name} "
f"{cfg['deploy_dir'].split('/')[-1]}/' </dev/null "
f">/data/compress_output.log 2>&1 & echo $!"
)
else:
compress_cmd = (
f"cd /data && nohup tar -zcvf {pkg_name} "
f"{cfg['deploy_dir'].split('/')[-1]}/ </dev/null "
......@@ -743,16 +805,14 @@ def phase_package():
pid = pid_out.strip()
logger.log(f"压缩进程PID: {pid}")
# 监控压缩进度
max_wait = 3600
waited = 0
while waited < max_wait:
time.sleep(30)
waited += 30
# 检查进程是否还在运行
if pid and pid.strip().isdigit():
_, ps_out, _ = ssh_exec(ssh,
_, ps_out, _ = run(
f"ps -p {pid.strip()} > /dev/null 2>&1 && echo 'running' || echo 'done'")
if 'done' in ps_out:
logger.log("[OK] 压缩完成")
......@@ -768,17 +828,14 @@ def phase_package():
results[arch] = r
continue
# 生成MD5
logger.log("生成MD5校验文件...")
ssh_exec(ssh, f"cd /data && md5sum {pkg_name} > {md5_name}")
_, md5_out, _ = ssh_exec(ssh, f"cat /data/{md5_name}")
run(f"cd /data && md5sum {pkg_name} > {md5_name}")
_, md5_out, _ = run(f"cat /data/{md5_name}")
logger.log(f"MD5: {md5_out.strip()}")
# 检查文件大小
_, size_out, _ = ssh_exec(ssh, f"ls -lh /data/{pkg_name}")
_, size_out, _ = run(f"ls -lh /data/{pkg_name}")
logger.log(f"文件大小: {size_out.strip()}")
# SFTP下载到本地
local_dir = cfg['output_dir']
os.makedirs(local_dir, exist_ok=True)
......@@ -787,12 +844,10 @@ def phase_package():
logger.log(f"下载到本地: {local_dir}")
# 下载MD5文件(小文件)
sftp = ssh.open_sftp()
sftp.get(f'/data/{md5_name}', local_md5)
logger.log(f"[OK] MD5文件已下载: {local_md5}")
# 下载部署包(大文件,带进度)
logger.log(f"下载部署包(大文件,可能需要较长时间)...")
remote_size = sftp.stat(f'/data/{pkg_name}').st_size
downloaded = [0]
......@@ -800,7 +855,7 @@ def phase_package():
def progress(transferred, total):
downloaded[0] = transferred
if transferred - last_report[0] > 100 * 1024 * 1024: # 每100MB报告一次
if transferred - last_report[0] > 100 * 1024 * 1024:
pct = transferred * 100 // total
mb = transferred // (1024 * 1024)
total_mb = total // (1024 * 1024)
......@@ -843,13 +898,13 @@ def generate_reports(deploy_results, verify_results, security_results, package_r
report = f"""# 部署分析报告
**报告时间**: {date_display}
**X86服务器**: 192.168.5.52
**ARM服务器**: 192.168.9.75
**X86服务器**: 192.168.5.52(欧拉)
**ARM服务器**: 192.168.9.75(欧拉)、192.168.9.76(Ubuntu)
## 一、部署执行
"""
for arch in ['x86', 'arm']:
for arch in ALL_ARCHS:
cfg = CONFIGS[arch]
dr = deploy_results.get(arch, {})
report += f"### {cfg['label']}\n\n"
......@@ -869,7 +924,7 @@ def generate_reports(deploy_results, verify_results, security_results, package_r
report += f"{c}\n"
report += "```\n"
if arch == 'arm':
if arch in ('arm', 'arm_ubuntu'):
emqx_ok = dr.get('emqx_ok', None)
if emqx_ok is not None:
report += f"- **EMQX状态**: {'正常' if emqx_ok else '异常(架构不匹配)'}\n"
......@@ -884,7 +939,7 @@ def generate_reports(deploy_results, verify_results, security_results, package_r
# 验证结果
report += "## 二、服务验证\n\n"
for arch in ['x86', 'arm']:
for arch in ALL_ARCHS:
cfg = CONFIGS[arch]
vr = verify_results.get(arch, {})
report += f"### {cfg['label']}\n\n"
......@@ -914,8 +969,11 @@ def generate_reports(deploy_results, verify_results, security_results, package_r
report += f"- **部署执行**: {'全部通过' if all_deploy_ok else '存在问题'}\n"
report += f"- **API验证**: {'全部通过' if all_api_ok else '存在问题'}\n"
report += f"- **部署用时**: X86 {deploy_results.get('x86', {}).get('deploy_time', 'N/A')}min, "
report += f"ARM {deploy_results.get('arm', {}).get('deploy_time', 'N/A')}min\n"
deploy_time_parts = []
for arch in ALL_ARCHS:
dt = deploy_results.get(arch, {}).get('deploy_time', 'N/A')
deploy_time_parts.append(f"{CONFIGS[arch]['label']} {dt}min")
report += f"- **部署用时**: {', '.join(deploy_time_parts)}\n"
if all_deploy_ok and all_api_ok:
report += "\n**结论**: 部署验收通过\n"
......@@ -935,7 +993,7 @@ def generate_reports(deploy_results, verify_results, security_results, package_r
## 一、漏洞扫描与修复
"""
for arch in ['x86', 'arm']:
for arch in ALL_ARCHS:
cfg = CONFIGS[arch]
sr = security_results.get(arch, {})
sec_report += f"### {cfg['label']}\n\n"
......@@ -1002,7 +1060,7 @@ def main():
return 1
print("=" * 60)
print(f"X86 + ARM 双架构统一自动化部署 - 模式: {mode}")
print(f"3台服务器统一自动化部署 - 模式: {mode}")
print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
......@@ -1019,7 +1077,7 @@ def main():
print("\n" + "=" * 60)
print("部署完成 - 容器状态摘要")
print("=" * 60)
for arch in ['x86', 'arm']:
for arch in ALL_ARCHS:
dr = deploy_results.get(arch, {})
count = dr.get('container_count', 0)
errors = dr.get('errors', [])
......@@ -1036,8 +1094,9 @@ def main():
print("部署阶段完成!请执行授权操作:")
print("使用 Chrome DevTools 进行浏览器自动化授权上传")
print("=" * 60)
print(f"\nX86: https://192.168.5.52/#/LoginConfig")
print(f"ARM: https://192.168.9.75/#/LoginConfig")
print(f"\nX86-欧拉: https://192.168.5.52/#/LoginConfig")
print(f"ARM-欧拉: https://192.168.9.75/#/LoginConfig")
print(f"ARM-Ubuntu: https://192.168.9.76/#/LoginConfig")
print(f"超管: {ADMIN_USER} / {ADMIN_PASS}")
print(f"验证码: {CAPTCHA}")
print(f"\n授权完成后运行: python full_deploy.py --verify")
......
# 容器升级需求说明文档
## 📋 概述
本脚本主要用于将主服务器上根据不同系统要求进行更新自动化部署包程序,压缩后上传至公司网盘:
1. **`server_pakage_upload.sh`**: 脚本路径:E:\GithubData\自动化\ubains-module-test\辅助工具\脚本工具\自动化部署打包上传\server_pakage_upload.sh
### 背景
目前系统支持多个部署系统的打包,需要区分两种平台环境:
- **新统一平台**:使用 `/data/` 目录结构
## 🎯 功能实现总览
### 打包脚本 (`pakage_upload.sh`)
需求功能点:
1、询问用户选择需要打包的系统类型(会议预定系统、运维集控系统、语音转录系统、电子桌牌系统、无纸化信令服务)。✅ 已完成待验证
2、根据选择的系统类型,执行相应的打包操作。
- 会议预定系统:前后端服务包 容器镜像包 ✅ 已完成待验证
- 运维集控系统:前后端服务包、容器镜像包 ✅ 已完成待验证
- 语音转录系统:前后端服务包、容器镜像包 ✅ 已完成待验证
- 电子桌牌系统:前后端服务包、容器镜像包
- 无纸化信令服务:前后端服务包、容器镜像包
3、打包成tar.gz格式并增加md5格式校验后,将打包后的文件上传至公司网盘。✅ 已完成待验证
#### 详细功能描述:
1、询问用户选择需要打包的系统类型:
- 使用 `select` 命令,将系统类型列表作为选项,让用户进行选择。
2、根据选择的系统类型,执行相应的打包操作:
- 自动化部署包路径:'/data/offline_auto_unifiedPlatform'(暂时不区分系统名称)
- 会议预定系统:
前后端服务包:
1、将/data/services/api/auth/auth-sso-auth目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/auth/auth-sso-auth目录下
2、将/data/services/api/auth/auth-sso-gatway目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/auth/auth-sso-gatway目录下
3、将/data/services/api/auth/auth-sso-system目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/auth/auth-sso-system目录下
4、将/data/services/api/java-meeting/java-meeting2.0目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-meeting2.0目录下
5、将/data/services/api/java-meeting/java-meeting3.0目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-meeting3.0目录下
6、将/data/services/api/java-meeting/java-meeting-extapi目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-meeting-extapi目录下
7、将/data/services/api/java-meeting/java-message-scheduling目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-message-scheduling目录下
8、将/data/services/api/java-meeting/java-mqtt目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-mqtt目录下
9、将/data/services/api/java-meeting/java-quartz目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-quartz目录下
10、将/data/services/web/pc/pc-vue2-ai目录下的static文件夹、index.html文件更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-ai目录下
11、将/data/services/web/pc/pc-vue2-backstage目录下的static文件夹、index.html文件更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-backstage目录下
12、将/data/services/web/pc/pc-vue2-main目录下的static文件夹、index.html文件和js文件更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-main目录下
13、将/data/services/web/pc/pc-vue2-meetingControl目录下的static文件夹、index.html文件更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-meetingControl目录下
14、将/data/services/web/pc/pc-vue2-meetngV2目录下的static文件夹、index.html文件和js文件更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-meetngV2目录下
15、将/data/services/web/pc/pc-vue2-meetngV3目录下的static文件夹、index.html文件和js文件更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-meetngV3目录下
16、将/data/services/web/pc/pc-vue2-platform目录下的static文件夹、index.html文件和temp文件夹更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-platform目录下
容器镜像包:
1、将/data/temp目录下的docker-20.10.7.tgz和docker.service和docker文件夹更新至/data/offline_auto_unifiedPlatform/data/temp目录下
2、将/data/temp目录下的umysql.tar.gz、devops_voice.sql、huazhao2.sql、nacos_mysql.sql、offline.sql更新至/data/offline_auto_unifiedPlatform/data/temp目录下
3、将/data/temp目录下的redis8.2.2.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
4、将/data/temp目录下的uemqx5.8.4.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
5、将/data/temp目录下的ufastdfs.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
6、将/data/temp目录下的nacos-server-v2.5.2.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
7、将/data/temp目录下的nginx-1.29.3.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
8、将/data/temp目录下的chrony.conf更新至/data/offline_auto_unifiedPlatform/data/temp目录下
9、将/data/temp目录下的jdk-8u472-linux-x64.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
10、将/data/temp目录下的java1.8.0_472.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
上传网盘路径:
\\192.168.9.9\研发管理\调试打包路径\运维集控系统
- 运维集控系统:
前后端服务包:
1、将/data/services/api/python-cmdb目录下的cmdb和UbainsDevOps文件夹更新至/data/offline_auto_unifiedPlatform/data/services/api/python-cmdb
2、将/data/services/web/pc/pc-vue2-moniter/目录下的index.html、module文件夹和static文件夹更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-moniter目录下
3、将/data/services/api/java-meeting/java-meeting-extapi目录下的jar包更新中至/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-meeting-extapi目录下
4、将/data/services/web/pc/pc-vue2-backstage目录下的static文件夹、index.html文件更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-backstage目录下
5、将/data/services/web/pc/pc-vue2-main目录下的static文件夹、index.html文件和js文件更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-main目录下
6、将/data/services/web/pc/pc-vue2-platform目录下的static文件夹、index.html文件和temp文件夹更新中至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-platform目录下
容器镜像包:
1、将/data/temp目录下的docker-20.10.7.tgz和docker.service和docker文件夹更新至/data/offline_auto_unifiedPlatform/data/temp目录下
2、将/data/temp目录下的umysql.tar.gz、devops_voice.sql、huazhao2.sql、nacos_mysql.sql、offline.sql更新至/data/offline_auto_unifiedPlatform/data/temp目录下
3、将/data/temp目录下的redis8.2.2.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
4、将/data/temp目录下的uemqx5.8.4.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
5、将/data/temp目录下的ufastdfs.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
6、将/data/temp目录下的nacos-server-v2.5.2.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
7、将/data/temp目录下的nginx-1.29.3.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
8、将/data/temp目录下的chrony.conf更新至/data/offline_auto_unifiedPlatform/data/temp目录下
9、将/data/temp目录下的jdk-8u472-linux-x64.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
10、将/data/temp目录下的python_v15.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
上传网盘路径:
\\192.168.9.9\研发管理\调试打包路径\运维集控系统
- 语音转录系统:
前后端服务包:
1、将/data/services/api/python-voice目录下的所有文件更新至/data/offline_auto_unifiedPlatform/data/services/api/python-voice
2、将/data/services/web/pc/pc-vue2-voice/pc-vue2-voice目录下的index.html和static文件夹更新至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-voice/pc-vue2-voice目录下
3、将/data/services/api/java-meeting/java-meeting-extapi目录下的jar包更新至/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-meeting-extapi目录下
4、将/data/services/web/pc/pc-vue2-backstage目录下的static文件夹、index.html文件更新至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-backstage目录下
5、将/data/services/web/pc/pc-vue2-main目录下的static文件夹、index.html文件和js文件更新至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-main目录下
6、将/data/services/web/pc/pc-vue2-platform目录下的static文件夹、index.html文件和temp文件夹更新至/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-platform目录下
7、将/data/third_party/iFlyTrans目录下iflytek和ubains文件夹更新至/data/offline_auto_unifiedPlatform/data/third_party/iFlyTrans
容器镜像包:
1、将/data/temp目录下的docker-20.10.7.tgz和docker.service和docker文件夹更新至/data/offline_auto_unifiedPlatform/data/temp目录下
2、将/data/temp目录下的umysql.tar.gz、devops_voice.sql、huazhao2.sql、nacos_mysql.sql、offline.sql更新至/data/offline_auto_unifiedPlatform/data/temp目录下
3、将/data/temp目录下的redis8.2.2.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
4、将/data/temp目录下的uemqx5.8.4.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
5、将/data/temp目录下的ufastdfs.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
6、将/data/temp目录下的nacos-server-v2.5.2.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
7、将/data/temp目录下的nginx-1.29.3.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
8、将/data/temp目录下的uvoice3.tar.gz更新至/data/offline_auto_unifiedPlatform/data/temp目录下
9、将/data/temp目录下的iFlyTrans_server_bag文件夹更新至/data/offline_auto_unifiedPlatform/data/temp目录下
上传网盘路径:
\\192.168.9.9\研发管理\调试打包路径\语音转录系统
3、打包成tar.gz格式并增加md5格式校验后,将打包后的文件上传至公司网盘。
将/data/offline_auto_unifiedPlatform目录打包成tar.gz格式并增加md5格式校验后,将打包后的文件上传至公司网盘。
### 更新说明
- 已实现语音转录系统的前后端服务包和容器镜像包功能。
- 请验证相关功能是否符合需求。
## 规范获取
代码规范:Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md
问题总结:Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md
方法总结:Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md
文档规范:Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md
测试规范:Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md
\ No newline at end of file
# 自动更新部署包版本
## 服务器信息
### 测试服务器
- IP:192.168.5.44
- 端口:22
- 账号:root
- 密码:Ubains@123
### 打包服务器
- IP:192.168.5.68
- 端口:22
- 账号:root
- 密码:Ubains@123
- 自动化部署包存放目录:/data/
- 自动化部署包名称:offline_auto_unifiedPlatform
- 自动化部署包目录:/data/offline_auto_unifiedPlatform
## 测试服务器的服务目录信息
### 会议预定
#### 前端服务目录:
- ai包:/data/services/web/pc/pc-vue2-ai
- index.html
- static文件夹
- 后台包:/data/services/web/pc/pc-vue2-backstage
- index.html
- static文件夹
- main包:/data/services/web/pc/pc-vue2-main
- index.html
- static文件夹
- meetngV2包:/data/services/web/pc/pc-vue2-meetngV2
- index.html
- static文件夹
- meetngV3包:/data/services/web/pc/pc-vue2-meetngV3
- index.html
- static文件夹
- meetingControl:/data/services/web/pc/pc-vue2-meetingControl
- index.html
- static文件夹
- monitor包:/data/services/web/pc/pc-vue2-moniter
- index.html
- static文件夹
- module文件夹
- platform包:/data/services/web/pc/pc-vue2-platform
- index.html
- static文件夹
- temp文件夹
- voice包:/data/services/web/pc/pc-vue2-voice/pc-vue2-voice
- index.html
- static文件夹
- h5-meeting:/data/services/web/h5/h5-uniapp-meeting
- index.html
- static文件夹
- h5-moniter:/data/services/web/h5/h5-uniapp-moniter
- index.html
- static文件夹
- h5-platform-mobile:/data/services/web/h5/h5-uniapp-platform/meeting-mobile
- assets文件夹
- index.html
- static文件夹
- h5-platform-platform-mobile:/data/services/web/h5/h5-uniapp-platform/unified-platform-mobile
- index.html
- static文件夹
### 后端服务目录:
- auth包:/data/services/api/auth/auth-sso-aut
- ubains-auth.jar
- gatway包:/data/services/api/auth/auth-sso-gatway
- ubains-gateway.jar
- system包:/data/services/api/auth/auth-sso-system
- ubains-modules-system.jar
- java2.0包:/data/services/api/java-meeting/java-meeting2.0
- ubains-meeting-inner-api-1.0-SNAPSHOT.jar
- java-extapi包:/data/services/api/java-meeting/java-meeting-extapi
- ubains-meeting-api-1.0-SNAPSHOT.jar
- java-scheduling包:/data/services/api/java-meeting/java-message-scheduling
- ubains-meeting-message-scheduling-1.0-SNAPSHOT.jar
- java-mqtt包:/data/services/api/java-meeting/java-mqtt
- ubains-meeting-mqtt-1.0-SNAPSHOT.jar
- java-quartz包:/data/services/api/java-meeting/java-quartz
- ubains-meeting-quartz-1.0-SNAPSHOT.jar
- cmdb包:/data/services/api/python-cmdb
- cmdb文件夹
- UbainsDevOps文件夹
- voice包:/data/services/api/python-voice
- UbainsDevOps文件夹
- uvoice文件夹
## 打包服务器的服务目录信息
### 会议预定
#### 前端服务目录:
- ai包:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-ai
- index.html
- static文件夹
- 后台包:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-backstage
- index.html
- static文件夹
- main包:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-main
- index.html
- static文件夹
- meetngV2包:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-meetngV2
- index.html
- static文件夹
- meetngV3包:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-meetngV3
- index.html
- static文件夹
- meetingControl:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-meetingControl
- index.html
- static文件夹
- monitor包:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-moniter
- index.html
- static文件夹
- module文件夹
- platform包:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-platform
- index.html
- static文件夹
- temp文件夹
- voice包:/data/offline_auto_unifiedPlatform/data/services/web/pc/pc-vue2-voice/pc-vue2-voice
- index.html
- static文件夹
- h5-meeting:/data/offline_auto_unifiedPlatform/data/services/web/h5/h5-uniapp-meeting
- index.html
- static文件夹
- h5-moniter:/data/offline_auto_unifiedPlatform/data/services/web/h5/h5-uniapp-moniter
- index.html
- static文件夹
- h5-platform-mobile:/data/offline_auto_unifiedPlatform/data/services/web/h5/h5-uniapp-platform/meeting-mobile
- assets文件夹
- index.html
- static文件夹
- h5-platform-platform-mobile:/data/offline_auto_unifiedPlatform/data/services/web/h5/h5-uniapp-platform/unified-platform-mobile
- index.html
- static文件夹
### 后端服务目录:
- auth包:/data/offline_auto_unifiedPlatform/data/services/api/auth/auth-sso-aut
- ubains-auth.jar
- gatway包:/data/offline_auto_unifiedPlatform/data/services/api/auth/auth-sso-gatway
- ubains-gateway.jar
- system包:/data/offline_auto_unifiedPlatform/data/services/api/auth/auth-sso-system
- ubains-modules-system.jar
- java2.0包:/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-meeting2.0
- ubains-meeting-inner-api-1.0-SNAPSHOT.jar
- java-extapi包:/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-meeting-extapi
- ubains-meeting-api-1.0-SNAPSHOT.jar
- java-scheduling包:/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-message-scheduling
- ubains-meeting-message-scheduling-1.0-SNAPSHOT.jar
- java-mqtt包:/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-mqtt
- ubains-meeting-mqtt-1.0-SNAPSHOT.jar
- java-quartz包:/data/offline_auto_unifiedPlatform/data/services/api/java-meeting/java-quartz
- ubains-meeting-quartz-1.0-SNAPSHOT.jar
- cmdb包:/data/offline_auto_unifiedPlatform/data/services/api/python-cmdb
- cmdb文件夹
- UbainsDevOps文件夹
- voice包:/data/offline_auto_unifiedPlatform/data/services/api/python-voice
- UbainsDevOps文件夹
- uvoice文件夹
## 部署包更新说明
- 前端包更新说明:
- 前端包更新时需要将打包服务器上原static中的config.json或config.js文件覆盖到更新的服务包中的static目录下。
- jar格式后端包更新说明
- 直接覆盖更新
- cmdb文件夹后端包更新说明:
- 需要将原文件夹下bus/config/settingbus.conf覆盖到更新的服务包原路径。
- uvoice文件夹后端包更新说明:
- 需要将原文件夹下bus/config/settingbus.conf覆盖到更新的服务包原路径。
- 所有服务更新操作完毕后需将/data/目录下的offline_auto_unifiedPlatform文件夹打压缩成tar.gz格式,并增加md5格式校验。
- 将tar.gz格式文件与md5格式文件拷贝至网盘目录:[\\192.168.9.9\发布版本\03服务器部署\15新统一平台\X86部署包\全量版\版本更新-待验证]
- 拷贝到网盘目录后将打包服务器上的tar.gz和md5格式文件清理。
## 核验材料
1. 所有的操作需日志记录说明
2. 所有操作结束后需输出报告说明,报告以md格式存储。
# 远程更新程序
## 代码路径
- 代码路径:[AuxiliaryTool/ScriptTool/RemoteUpdate/remote_update.ps1]
## 功能需求
### 功能目标
**目标:** 通过powershell执行后一键更新系统前后端服务,并执行服务备份与更新操作,生成服务更新报告。
### 需求描述
#### 功能描述
- 通过powershell执行ssh服务器连接后,选择更新服务一键更新系统前后端服务,并执行服务备份与更新操作,生成服务更新报告。
### 远程程序更新逻辑
#### 交互步骤
- 程序交互方式:
- 服务器信息:
- 服务器IP地址:用户手动输入
- ssh端口号:用户手动输入
- 登录用户名:用户手动输入
- 登录密码:用户手动输入
- 连接超时:30秒,可重拾3次,3次连接失败,则退出程序。
- 打印服务器信息。
- 执行ssh连接服务器,获取服务器信息。
- 连接ssh服务器,增加超时处理。
- 获取服务器操作系统、系统架构信息。
- 获取服务器时间。
- 获取服务器home目录剩余空间,如剩余空间小于5GB,则打印警告并退出程序。
- 获取服务器当前平台类型,通过目录进行判断。
- 新统一平台类型:/data/services
- 传统平台类型:/var/www/
- 如两者目录都存在,则警告,并退出程序。
- 如两者目录都不存在,则警告,并退出程序。
- 打印服务器信息。
- 系统类型选择:
- 通过用户手动输入编号,选择对应系统类型。
- 系统类型列表:1、预定系统;2、运维集控系统;3、讯飞转录系统。4、统一平台系统
- 系统类型选择错误:重新选择系统类型。
- 打印所选系统类型。
- 服务更新类型选择:
- 通过用户手动输入编号,选择对应服务更新类型。
- 服务更新类型列表:1、前端更新;2、后端更新;3、全量更新
- 服务更新类型选择错误:重新选择服务更新类型。
- 更新类型映射表:读取[AuxiliaryTool/ScriptTool/RemoteUpdate/update_type_mapping.json]
- 打印所选更新类型。
- 创建服务器更新目录:
- 通过mkdir -p /home/update创建更新目录,用以存放更新包。
- 创建失败:打印创建失败信息,并重新创建,重试3次,3次失败,则退出程序。
- 通过plink.exe上传更新包:[update_package.zip]
- 读取更新包的路径映射表获取:[AuxiliaryTool/ScriptTool/RemoteUpdate/update_package_old.json][AuxiliaryTool/ScriptTool/RemoteUpdate/update_package_new.json]
- 备份数据库:
- 上传数据库备份脚本:[AuxiliaryTool/ScriptTool/RemoteUpdate/backup_db.sh]
- 执行数据库备份脚本:[AuxiliaryTool/ScriptTool/RemoteUpdate/backup_db.sh]
- 导出数据库:通过pscp将服务器上的数据库备份文件导出到本地Sqlbackup目录。
- 打印备份数据库信息。
- 备份前后端包:
- 创建服务器备份目录/home/backup:
- 根据更新类型映射表获取对应包路径,将服务器上的原服务包复制到服务器/home/backup目录下。
- 压缩服务包:将/home/backup目录压缩成zip格式文件,并通过pscp导出到本地Servicebackup目录。
- 打印备份前后端包信息。
- 更新程序上传:
- 将更新程序脚本[AuxiliaryTool/ScriptTool/RemoteUpdate/service_update.sh]上传至服务器/home/update目录下。
- 通过plink.exe执行更新程序脚本:[AuxiliaryTool/ScriptTool/RemoteUpdate/service_update.sh],传入服务更新类型参数。
- service_update.sh脚本主要用于接收服务更新类型参数,并执行更新程序,不存在回滚机制。
- 打印更新程序信息。
- 整理记录至远端更新程序报告文档[AuxiliaryTool/ScriptTool/RemoteUpdate/reports],以上信息需整理至远端更新程序报告文档。
#### 脚本路径
- 当前文档脚本路径为项目代码路径,实际执行时需以当前powershell执行脚本所在目录为根目录。
#### 更新报告输出规则
- 报告输出格式:输出md文档格式。
- 报告输出位置:输出到[AuxiliaryTool/ScriptTool/RemoteUpdate/reports]
- 报告命名规则:{服务器地址}_更新测试报告_{日期}.md。
- 报告编号规则:{服务器地址}-{日期}
## 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
- 方法总结: `Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
- 文档规范: `Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
---
\ No newline at end of file
# 计划执行文档
## 需求文档
- 需求文档: `Docs/PRD/远程更新程序/需求/_PRD_远程程序更新脚本_需求文档.md`
## 实现计划
### 1. 需求概述
开发一个 PowerShell 脚本 `remote_update.ps1`,实现远程服务器程序的自动更新功能,包括服务器连接、信息获取、备份、更新和报告生成。
### 2. 文件结构
```
AuxiliaryTool/ScriptTool/RemoteUpdate/
├── remote_update.ps1 # 主程序脚本
├── config/
│ ├── update_type_mapping.json # 更新类型映射表
│ ├── update_package_old.json # 旧版本包路径映射
│ └── update_package_new.json # 新版本包路径映射
├── scripts/
│ ├── backup_db.sh # 数据库备份脚本
│ └── service_update.sh # 服务更新脚本
├── packages/ # 更新包存放目录
├── reports/ # 报告输出目录
├── Sqlbackup/ # 数据库备份导出目录
└── Servicebackup/ # 服务包备份导出目录
```
### 3. 实现步骤
#### 步骤1:脚本初始化
- 定义脚本根目录(当前执行脚本所在目录)
- 创建必要的目录结构(reports、Sqlbackup、Servicebackup)
- 定义全局变量(日志级别、超时时间、重试次数等)
- 设置控制台输出编码为 UTF-8
#### 步骤2:用户输入服务器信息
- 提示用户输入服务器IP地址
- 提示用户输入SSH端口号(默认22)
- 提示用户输入登录用户名
- 提示用户输入登录密码(使用掩码输入)
- 验证输入信息不为空
#### 步骤3:SSH连接服务器
- 使用 plink.exe 连接服务器
- 设置连接超时时间为30秒
- 实现3次重试机制
- 连接失败打印错误信息并退出程序
#### 步骤4:获取服务器信息
- 获取操作系统信息(`uname -a`
- 获取系统架构信息(`uname -m`
- 获取服务器时间(`date`
- 获取home目录剩余空间(`df -h /home`
- 判断平台类型(检查 `/data/services``/var/www/` 目录)
- 打印服务器信息(格式化输出)
**平台类型判断规则**
- 优先检查 `/data/services`,存在则判定为新统一平台
- 其次检查 `/var/www/`,存在则判定为传统平台
- 两者都存在或都不存在:警告并退出程序
#### 步骤5:磁盘空间检查
- 解析home目录剩余空间
- 转换为GB单位
- 小于5GB:打印警告并退出程序
- 大于等于5GB:继续执行
#### 步骤6:系统类型选择
- 显示系统类型列表:
```
请选择系统类型:
1. 预定系统
2. 运维集控系统
3. 讯飞转录系统
4. 统一平台系统
```
- 提示用户输入编号
- 验证输入有效性(1-4)
- 无效输入:提示错误并重新选择
- 打印所选系统类型
#### 步骤7:服务更新类型选择
- 显示更新类型列表:
```
请选择服务更新类型:
1. 前端更新
2. 后端更新
3. 全量更新
```
- 提示用户输入编号
- 验证输入有效性(1-3)
- 无效输入:提示错误并重新选择
- 读取 `config/update_type_mapping.json` 获取映射信息
- 打印所选更新类型
#### 步骤8:创建服务器更新目录
- 执行命令:`mkdir -p /home/update`
- 检查创建结果
- 创建失败:打印错误信息,重试(最多3次)
- 3次失败:退出程序
#### 步骤9:上传更新包
- 读取 `config/update_package_old.json` 和 `config/update_package_new.json`
- 根据系统类型和更新类型获取更新包路径
- 使用 pscp.exe 上传更新包到服务器 `/home/update` 目录
- 打印上传进度和结果
**日志格式**:
```
[INFO] 正在上传更新包...
[OK] 更新包上传成功:update_package.zip
```
#### 步骤10:备份数据库
- 上传 `scripts/backup_db.sh` 到服务器
- 执行数据库备份脚本
- 等待备份完成
- 使用 pscp.exe 导出数据库备份文件到本地 `Sqlbackup` 目录
- 打印备份结果
**日志格式**:
```
[INFO] 正在上传数据库备份脚本...
[OK] 数据库备份脚本上传成功
[INFO] 正在执行数据库备份...
[OK] 数据库备份成功:/home/backup/db_20260311_143022.sql
[INFO] 正在导出数据库备份文件...
[OK] 数据库备份文件导出成功:Sqlbackup/192.168.1.100_db_20260311_143022.sql
```
#### 步骤11:备份前后端包
- 创建服务器备份目录 `/home/backup`
- 根据更新类型映射表获取原服务包路径
- 复制服务包到 `/home/backup` 目录
- 压缩服务包为 zip 格式
- 使用 pscp.exe 导出备份文件到本地 `Servicebackup` 目录
- 打印备份结果
**日志格式**:
```
[INFO] 正在创建备份目录 /home/backup...
[OK] 备份目录创建成功
[INFO] 正在复制服务包...
[OK] 服务包复制成功:/data/services/booking → /home/backup/booking
[INFO] 正在压缩服务包...
[OK] 服务包压缩成功:/home/backup/booking_backup.zip
[INFO] 正在导出备份文件...
[OK] 备份文件导出成功:Servicebackup/192.168.1.100_booking_backup.zip
```
#### 步骤12:执行更新程序
- 上传 `scripts/service_update.sh` 到服务器 `/home/update` 目录
- 通过 plink.exe 执行更新脚本,传入更新类型参数
- 等待执行完成
- 获取执行结果(返回值)
- 打印更新结果
**日志格式**:
```
[INFO] 正在上传更新脚本...
[OK] 更新脚本上传成功:service_update.sh
[INFO] 正在执行更新脚本...
[OK] 更新脚本执行成功,返回值:0
[INFO] 正在检查服务状态...
[OK] 服务状态正常:进程运行中,端口监听中
```
#### 步骤13:生成更新报告
- 收集所有执行信息
- 按 Markdown 格式生成报告
- 保存到 `reports` 目录
- 文件命名:`{服务器地址}_更新测试报告_{日期}.md`
**报告模板**:
```markdown
# {服务器地址}更新报告
## 基本信息
- 报告编号:{服务器地址}-{日期}
- 服务器IP:{IP}
- SSH端口:{端口}
- 系统类型:{预定系统/运维集控系统/讯飞转录系统/统一平台系统}
- 更新类型:{前端更新/后端更新/全量更新}
- 执行时间:{开始时间} - {结束时间}
## 服务器信息
- 操作系统:{OS}
- 系统架构:{架构}
- 服务器时间:{时间}
- Home目录剩余空间:{空间} GB
- 平台类型:{新统一平台/传统平台}
## 执行记录
### 数据库备份
- 上传脚本:{成功/失败}
- 执行备份:{成功/失败}
- 备份文件:{文件名}
- 导出本地:{本地路径}
### 服务包备份
- 创建备份目录:{成功/失败}
- 复制服务包:{成功/失败}
- 压缩备份:{成功/失败}
- 导出本地:{本地路径}
### 更新执行
- 上传更新包:{成功/失败}
- 上传更新脚本:{成功/失败}
- 执行更新脚本:{成功/失败}
- 返回值:{返回值}
- 服务状态:{状态描述}
## 总结
- 更新结果:{成功/失败}
- 备注:{备注信息}
---
报告生成时间:{时间戳}
```
#### 步骤14:错误处理
- SSH连接失败:打印错误,重试3次
- 磁盘空间不足:打印警告,退出程序
- 平台类型判断失败:打印警告,退出程序
- 目录创建失败:重试3次,失败退出
- 上传/执行失败:记录错误,继续执行或退出
#### 步骤15:日志输出规范
- INFO(信息):正常操作步骤
- WARN(警告):警告信息(如空间不足但可继续)
- ERROR(错误):错误信息(如连接失败)
- OK(成功):操作成功确认
**日志格式**:
```
[INFO] 描述信息
[WARN] 警告信息
[ERROR] 错误信息
[OK] 成功确认
```
### 4. 依赖工具
| 工具 | 用途 |
|------|------|
| plink.exe | SSH连接和命令执行 |
| pscp.exe | 文件上传/下载 |
### 5. 代码规范遵循
- [x] 所有代码添加中文注释
- [x] 详细日志记录(INFO/WARN/ERROR/OK)
- [x] 函数化设计,便于维护
- [x] 错误处理完善
### 6. 测试验证
- [ ] 测试正常更新流程
- [ ] 测试SSH连接失败重试
- [ ] 测试磁盘空间不足
- [ ] 测试平台类型判断异常
- [ ] 测试更新包上传失败
- [ ] 测试报告生成
### 7. 预期输出示例
```
============================================================
远程程序更新脚本
============================================================
请输入服务器信息:
服务器IP地址: 192.168.1.100
SSH端口号[22]: 22
登录用户名: root
登录密码: ********
[INFO] 正在连接服务器 192.168.1.100:22...
[OK] 连接成功
============================================================
服务器信息
============================================================
操作系统: Linux 5.4.0-x86_64
系统架构: x86_64
服务器时间: 2026-03-11 14:30:22
Home剩余空间: 25.3 GB
平台类型: 新统一平台
请选择系统类型:
1. 预定系统
2. 运维集控系统
3. 讯飞转录系统
4. 统一平台系统
请输入编号[1-4]: 1
[OK] 已选择:预定系统
请选择服务更新类型:
1. 前端更新
2. 后端更新
3. 全量更新
请输入编号[1-3]: 1
[OK] 已选择:前端更新
[INFO] 正在创建更新目录...
[OK] 更新目录创建成功
[INFO] 正在上传更新包...
[OK] 更新包上传成功
[INFO] 正在上传数据库备份脚本...
[OK] 数据库备份脚本上传成功
[INFO] 正在执行数据库备份...
[OK] 数据库备份成功
[INFO] 正在导出数据库备份文件...
[OK] 数据库备份文件导出成功:Sqlbackup/192.168.1.100_db_20260311_143022.sql
[INFO] 正在备份服务包...
[OK] 服务包备份成功
[INFO] 正在导出备份文件...
[OK] 备份文件导出成功:Servicebackup/192.168.1.100_booking_backup.zip
[INFO] 正在上传更新脚本...
[OK] 更新脚本上传成功
[INFO] 正在执行更新脚本...
[OK] 更新脚本执行成功
[INFO] 正在生成更新报告...
[OK] 更新报告已生成:reports/192.168.1.100_更新测试报告_20260311.md
============================================================
更新完成
============================================================
更新结果: 成功
报告位置: reports/192.168.1.100_更新测试报告_20260311.md
============================================================
```
---
## 执行记录
### 任务清单
- [ ] Step 1: 创建脚本文件和目录结构
- [ ] Step 2: 实现脚本初始化和全局变量
- [ ] Step 3: 实现用户输入服务器信息
- [ ] Step 4: 实现SSH连接和重试机制
- [ ] Step 5: 实现服务器信息获取
- [ ] Step 6: 实现磁盘空间检查
- [ ] Step 7: 实现系统类型选择
- [ ] Step 8: 实现更新类型选择
- [ ] Step 9: 实现更新目录创建
- [ ] Step 10: 实现更新包上传
- [ ] Step 11: 实现数据库备份
- [ ] Step 12: 实现服务包备份
- [ ] Step 13: 实现更新程序执行
- [ ] Step 14: 实现报告生成
- [ ] Step 15: 测试验证
---
*文档创建时间:2026-03-11*
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论