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

feat(deploy): 新增MySQL分库导出、授权脚本增强、Troubleshoot知识库扩充及部署脚本重构

服务自检 (LogExport.psm1 v2.1.0):
- 新增 Step 2.5 MySQL分库导出:SHOW DATABASES获取用户库列表,逐个mysqldump
- 新增辅助脚本 list_user_dbs.sh / dump_one_db.sh(通过Copy-File-To-Remote上传执行)
- mysql_dump与config_and_log同级打包,数据库按库名分文件,便于单独恢复

授权脚本 (authorize_full.py):
- fill_identity: 使用原生HTMLInputElement value setter触发Vue v-model响应
- 修复密码/验证码填充方式,改用nativeSetter.call+blur事件确保Vue数据绑定

Troubleshoot技能 (SKILL.md):
- 扩展至403行,新增高频排查场景:服务启动失败、Nacos异常、数据库连通性等
- 基于346条历史问题记录提取共性模式,按故障率排列

ARM-UBUNTU技能 (SKILL.md):
- 新增教训Q:严禁擅自sed改nginx端口,违反规则10/13的血泪教训

部署脚本 (auto_deploy_services.sh):
- python_player_x86重构:标准化日志输出,支持从/data/temp加载镜像部署uplayer容器

其他:
- DailyReport技能新增(工作日报生成)
- XTY-MONITOR config.json配置更新
- 问题知识库跟踪表新增
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 b546e152
......@@ -79,6 +79,7 @@ ARM架构 Ubuntu/openEuler 服务器远程自动化部署,严格按需求文
10. 遇文档未描述情况→停下报告,不自行决定
11. **🚨【功能验证硬性禁令】阶段8-14(建管理员→改密→权限绑定→用户/部门→授权码→会议室→建会议)必须且只能用Web页面UI操作(Selenium驱动页面点击/输入/提交)。严禁改数据库、严禁直接调后端接口绕过页面、严禁改配置伪造数据。页面走不通就停下报告,绝不靠非页面手段凑结果。**
12. **授权必须按 "下载激活文件→上传license.zip→重启" 顺序,"下载激活文件"不可省**(见教训A)
13. **🚨🚨🚨【最高优先·零容忍禁令】严禁擅自改服务器配置/瞎判断/自行发挥!** 遇到任何"看起来不正常"的现象(如API返回404、端口不匹配、nginx代理端口与server.port对不上、服务起不来),**绝不允许**自己动手 sed/改 nginx配置/改应用配置/改docker/改数据库去"修复"。**唯一正确做法=停下向用户报告现象+证据,等用户决策**。规则10是底线,本条再次强调:判断"是不是问题"也不归你管——你以为的"端口不匹配 bug"很可能是部署包的设计(如经过gateway代理),擅自"修"反而把好系统改坏。**只跑 skill 里列明的脚本,脚本之外一律不碰。**(见教训Q)
## 关键脚本清单(`AuxiliaryTool/ScriptTool/RemoteDeploy/`)
- `probe_state.py` 只读探针:SSH+sudo、架构/系统/磁盘/部署包/docker/服务,判断全新vs已部署
......@@ -148,6 +149,9 @@ docker ps返回exit 127可能是`bash -c`引号问题而非未装docker;先`pr
**P. 类型标签模块导航同"运维设置→二级菜单"**
类型标签在**运维设置**子菜单下(非组织管理)。进入方式同教训M:展开运维设置→等2s→点其下可见的"类型标签"。模块表头为"名称/排序",新增记录后排序默认0。
**Q. 🚨【血泪教训·严禁重蹈】擅自 sed 改 nginx 端口"修 bug",实则把好系统改坏 + 违反规则10**
现象:阶段6授权+重启后,预定API返回404,排查发现 nginx 代理到 `172.17.0.1:9204`,而 meeting2.0 `server.port=8999`,容器内9204无进程、8999有Java监听。**错误做法**:自作聪明判断为"端口不匹配的部署bug",擅自执行 `sed 's/172.17.0.1:9204/172.17.0.1:8999/g'` + `nginx reload` 去"修"它——这违反了规则10/13。**真相**:9204是部署包经 gateway/统一入口的设计,不归部署者改;擅自"修复"可能破坏整个路由架构。**对策**:遇到"端口对不上/API 404"这类现象,**第一步永远是停下报告**(现象+证据:哪个端口、谁在监听、nginx代理到哪),把判断权交回用户,**绝不自己 sed/改配置/改docker/改数据库**。skill 只列明了该跑哪些脚本,脚本之外一律不碰。已回退该次擅自修改(8999→9204 + reload 恢复原始)。
## 🎯 关键经验总结(实操要点,逐条遵守)
1. **解压必须用阻塞式执行**,nohup后台方式在sudo环境下不可靠(见教训I)
......
---
name: daily-report
description: 工作日报生成 - 读取当天所有Claude Code对话记录,自动分类汇总生成工作日报
---
读取当天所有 Claude Code 会话记录(jsonl 文件),提取用户查询内容,按工作类别自动分类汇总,生成简洁的工作日报。
## Usage
```
/daily-report # 生成当天工作日报
/daily-report 2026-06-29 # 生成指定日期的工作日报
```
## 执行流程
### 阶段 1:扫描会话文件
1. 扫描目录:`C:\Users\UBAINS\.claude\projects\E--GithubData-ubains-module-test\`
2. 筛选条件:`*.jsonl` 文件,`LastWriteTime` 在目标日期范围内
3. 排除 `agent-*.jsonl`(子代理日志)
4. 列出所有匹配的会话文件,按时间排序
### 阶段 2:提取用户查询
对每个会话文件:
1. 逐行读取 JSONL,筛选 `type: "user"` 的记录
2. 从 `message.content` 中提取文本内容(支持 string 和 array 两种格式)
3. 过滤空消息、skill 系统提示、无意义的系统指令
4. 保留有效的用户查询及其时间戳
### 阶段 3:分类汇总
按以下类别归类用户查询:
| 类别 | 匹配关键词/模式 |
|------|----------------|
| **自动化部署** | ARM/X86部署、skill部署、XTYBS、欧拉/麒麟/Ubuntu/统信部署、业务验证 |
| **达梦容器** | DM8、达梦、dm8-server、数据库迁移、容器化 |
| **远程更新** | UPDATE-SERVICES、服务更新、版本更新 |
| **问题排查** | 日志排查、问题定位、复现、IDEA |
| **代码提交** | GitCommit、git commit、push |
| **技能开发** | 新建skill、拆skill、CreateCMD |
| **环境部署** | 城建云启、新环境、测试环境 |
| **系统维护** | 清理、磁盘、性能、优化 |
> 一条查询可能归入多个类别。
根据各会话中用户查询的上下文,推断每组对话的实际工作产出。
### 阶段 4:输出日报
按以下固定格式输出:
```
## 工作日报 — YYYY年MM月DD日
1. **内部自动化部署**,[工作内容简述]。
2. **内部达梦容器部署**,[工作内容简述]。
3. **内部远程更新**,[工作内容简述]。
4. **IDEA项目**,[工作内容简述]。
5. **城建云启项目**,[工作内容简述]。
n. **其他类别**,[工作内容简述]。
```
**格式要求:**
- 每个条目一句话,逗号分隔"做了什么"
- 不展开详细步骤,不列状态标记
- 序号按类别固定顺序排列
- 如果某类别当天无工作,跳过不写
- 不在已有5个固定类别中的工作,追加在末尾
## 注意事项
- **只读操作**:不修改文件系统
- **时间范围**:默认当天(`Get-Date`),可指定日期
- **去重**:相同用户查询可能因 skill 加载重复出现,需去重
- **中文输出**:日报全文使用中文
- **简洁优先**:不要长篇叙述,每项只一句
- **jsonl 字段结构**:`type: "user"`,内容在 `message.content`(可能是 string 或 `[{type: "text", text: "..."}]` 数组)
......@@ -152,6 +152,137 @@
{"service": "ujava2", "issue": "Java启动约12-28分钟", "reason": "ARM架构启动慢", "action": "等待就绪,勿反复restart"},
{"service": "uemqx", "issue": "Dashboard实际admin/public", "reason": "安全改密未生效", "action": "用实测凭据"}
]
},
{
"id": 3,
"name": "192.168.5.202生产环境",
"ip": "192.168.5.202",
"port": 22,
"username": "root",
"password": "Ubains@123",
"use_sudo": false,
"sudo_password": "",
"description": "UOS Server 20生产环境 (31GB RAM, 500GB /data, uptime 126天)",
"environment": "production",
"tags": ["生产环境", "X86", "UOS", "ujava3", "GPT服务"],
"containers": ["ujava3", "unginx", "umysql", "uredis", "uemqx", "unacos", "utracker", "ustorage", "ungrok", "upython", "kkfile", "cardtable", "paperless", "ubainsgpts3"],
"middleware_credentials": {
"umysql": {"username": "root", "password": "dNrprU&2S", "port": 8306, "note": "对外端口8306映射容器内3306"},
"uredis": {"password": "dNrprU&2S", "port": 6379, "note": "端口转发模式(非host网络),GPT服务使用DB=3"},
"uemqx": {"dashboard_username": "admin", "dashboard_password": "待确认(admin/public返回404)", "dashboard_port": 18083, "mqtt_username": "mqtt@cmdb", "mqtt_password": "待确认", "note": "EMQX 4.x版本(非5.x),认证机制不同,Dashboard 200但API登录返回404"},
"unacos": {"note": "Nacos v2.5.1,HTTP 200正常"}
},
"log_paths": {
"_note": "以下为宿主机侧日志路径",
"ujava3": {
"container_name": "ujava3",
"note": "Java服务合集容器(注意是ujava3非ujava2),内含auth/gateway/meeting2.0/meeting3.0/mqtt/quartz/dubbo等多个jar",
"processes": {
"auth-sso-auth": {"jar": "ubains-auth.jar", "log_dir": "/data/services/api/auth/auth-sso-auth/logs/", "log_files": ["auth-sso-auth子目录"]},
"auth-sso-gateway": {"jar": "ubains-gateway.jar", "log_dir": "/data/services/api/auth/auth-sso-gatway/logs/", "log_files": ["auth-sso-gateway子目录"]},
"auth-sso-system": {"jar": "ubains-modules-system.jar", "log_dir": "/data/services/api/auth/auth-sso-system/logs/", "log_files": ["auth-sso-system子目录"]},
"meeting2.0": {"jar": "ubains-meeting-inner-api-1.0-SNAPSHOT.jar", "log_dir": "/data/services/api/java-meeting/java-meeting2.0/logs/", "log_files": ["ubains-ERROR.log", "ubains-INFO-AND-ERROR.log", "ubains-INFO.log"], "note": "521MB日志,debug模式(-Xdebug port 30884)"},
"meeting3.0": {"jar": "ubains-meeting-inner-api-1.0-SNAPSHOT.jar", "log_dir": "/data/services/api/java-meeting/java-meeting3.0/logs/", "log_files": ["ubains-ERROR.log", "ubains-INFO-AND-ERROR.log", "ubains-INFO.log"], "note": "会议3.0服务(Xms1024m)"},
"meeting-mqtt": {"jar": "ubains-meeting-mqtt-1.0-SNAPSHOT.jar", "log_dir": "/data/services/api/java-meeting/java-mqtt/logs/", "log_files": ["ubains-ERROR.log", "ubains-INFO-AND-ERROR.log", "ubains-INFO.log"]},
"meeting-quartz": {"jar": "ubains-meeting-quartz-1.0-SNAPSHOT.jar", "log_dir": "/data/services/api/java-meeting/java-quartz/logs/", "log_files": ["ubains-ERROR.log", "ubains-INFO-AND-ERROR.log", "ubains-INFO.log"]}
},
"dubbo_services": {
"note": "Dubbo微服务(11个),运行在ujava3容器内,比5.44少2个(无ews-cloud/zoom)",
"log_dir_template": "/data/services/api/dubbo/{service}/logs/",
"services": ["dubbo-cloudLink", "dubbo-dingding", "dubbo-meeting-control", "dubbo-polycom", "dubbo-serviceCall", "dubbo-smc-three", "dubbo-smc-two", "dubbo-teams", "dubbo-tencent-meeting", "dubbo-welink", "dubbo-xylink"],
"running_processes": ["dubbo-smc-three", "dubbo-meeting-control", "dubbo-dingding", "dubbo-tencent-meeting"],
"log_files": ["ubains-ERROR.log", "ubains-INFO-AND-ERROR.log", "ubains-INFO.log"]
}
},
"meeting-extapi": {
"note": "extapi在宿主机运行(非容器内),自4月28日运行至今",
"process": "java -Xms1024m -Xmx1024m -jar ubains-meeting-api-1.0-SNAPSHOT.jar",
"log_dir": "/data/services/api/java-meeting/java-meeting-extapi/logs/",
"log_files": ["ubains-ERROR.log", "ubains-INFO-AND-ERROR.log", "ubains-INFO.log"]
},
"unginx": {
"log_dir": "/data/middleware/nginx/log/",
"log_files": ["access.log", "error.log"],
"config_dir": "/data/middleware/nginx/config/",
"config_file": "unified443.conf",
"note": "Nginx 1.30.2,443 HTTPS,server_name 192.168.5.202"
},
"umysql": {"log_dir": "/data/middleware/mysql/log/", "note": "日志目录为空,MySQL日志写入容器内"},
"uemqx": {
"log_dir": "/data/middleware/emqx/log/",
"note": "日志目录为空(EMQX 4.x),无bootstrap.csv(认证方式不同),配置文件在 /data/middleware/emqx/config/"
},
"unacos": {
"log_dir": "/data/middleware/nacos/logs/",
"log_files": ["alipay-jraft.log", "config-fatal.log", "nacos.log", "core-auth.log"],
"note": "日志总量1.9GB,有完整的日志轮转(.YYYY-MM-DD.0)"
},
"upython": {
"note": "运维集控(CMDB) Python服务,upython:v14(比5.44的v16旧)",
"log_dir": "/data/services/api/python-cmdb/log/",
"log_files": ["error.log", "uinfo.log", "uwsgi.log"],
"note2": "有bak260407/bak260518/cmdb260420/cmdb260518等多个备份版本"
},
"python-voice": {
"note": "讯飞语音服务(宿主机运行,无独立Docker容器!),log目录为空(0B)"
},
"ubainsgpts3": {
"note": "GPT后端服务(healthy状态),连接192.168.5.201:5432 PostgreSQL(pgvector),Redis 192.168.5.202:6379 DB=3,阿里DashScope API"
},
"gunicorn": {
"note": "宿主机5000端口 gunicorn + uvicorn workers×4,Python3.11,推测为GPT前端API服务"
}
},
"port_map": {
"443": "unginx (HTTPS)",
"80": "nginx (HTTP)",
"1883": "uemqx (MQTT)",
"8883": "uemqx (MQTT SSL)",
"8083-8084": "uemqx (MQTT WS)",
"18083": "uemqx (Dashboard)",
"8848": "unacos (HTTP)",
"9848": "unacos (gRPC)",
"8306": "umysql (映射容器内3306)",
"6379": "uredis (端口转发)",
"22122": "utracker (FastDFS tracker, host网络)",
"23000": "ustorage (FastDFS storage, host网络)",
"8085": "ujava3 (业务端口)",
"8000": "upython (CMDB)",
"8002": "upython (CMDB)",
"8012": "kkfile (文件预览)",
"62121-62122": "paperless",
"30880-30884": "ujava3 (Dubbo+Debug端口, 30884=meeting2.0 debug)",
"8719-8720": "ujava3 (RPC端口)",
"8993-8999": "ujava3 (内部端口)",
"9200-9204": "ujava3 (内部端口)",
"9903-9920": "ujava3 (Dubbo端口)",
"5000": "gunicorn (GPT API服务, 宿主机)",
"27018": "MongoDB (宿主机)",
"8081": "nginx mobile (移动端代理)"
},
"business_checks": {
"enabled": true,
"api_checks": [],
"frontend_urls": [
{"name": "后台管理", "url": "https://192.168.5.202/pc-vue2-backstage/"},
{"name": "会议预定V2", "url": "https://192.168.5.202/pc-vue2-meetngV2/"},
{"name": "会议预定V3", "url": "https://192.168.5.202/pc-vue2-meetngV3/"},
{"name": "运维监控", "url": "https://192.168.5.202/pc-vue2-moniter/"}
],
"special_services": [
{"name": "GPT API", "url": "http://192.168.5.202:5000/", "note": "gunicorn + uvicorn, 返回404正常(根路径无路由)"},
{"name": "GPT后端", "container": "ubainsgpts3", "note": "连接PG向量数据库+Redis,阿里DashScope LLM"}
]
},
"known_issues": [
{"service": "ujava2", "issue": "ujava2容器Exited(137) 7周前,已迁移到ujava3", "action": "ujava2为旧容器,可清理"},
{"service": "ubainsgpts2/ubainsgpts/aimodel4", "issue": "三个旧容器Exited(137)", "action": "旧版本GPT/AI容器,可清理"},
{"service": "uemqx", "issue": "EMQX 4.x版本(非5.x),Dashboard API /api/v5/login返回404", "reason": "4.x使用不同API路径(/api/v4/login)和认证机制", "action": "确认Dashboard凭据和API版本"},
{"service": "message-scheduling", "issue": "jar目录存在但进程未运行", "action": "确认是否需要启动"},
{"service": "python-voice", "issue": "无独立Docker容器,log目录为空", "reason": "可能未部署或使用其他方式运行", "action": "确认讯飞语音服务部署方式"},
{"service": "nacos", "issue": "日志1.9GB较大", "action": "检查日志轮转策略"},
{"service": "meeting2.0", "issue": "开启debug模式(Xdebug port 30884),日志521MB", "action": "生产环境建议关闭debug,清理旧日志"}
]
}
],
"default_server_id": 1,
......
......@@ -44,24 +44,48 @@ def wait_identity(d, timeout=12):
return False
def fill_identity(d):
"""处理'校验身份'对话框:密码+图形验证码+确定。"""
"""处理'校验身份'对话框:密码+图形验证码+确定。使用原生value setter触发Vue响应。"""
if not wait_identity(d,12):
return False
time.sleep(1)
d.execute_script("""
var dl=document.querySelectorAll('.el-dialog');
for(var i=0;i<dl.length;i++){
if(dl[i].offsetParent===null)continue;
if((dl[i].textContent||'').indexOf('校验身份')<0)continue;
var pwd=dl[i].querySelector('input[placeholder*="密码"]');
if(pwd){pwd.focus();pwd.value='Ubains@1357';pwd.dispatchEvent(new Event('input',{bubbles:true}));pwd.dispatchEvent(new Event('change',{bubbles:true}));}
var cap=dl[i].querySelector('input[placeholder*="验证码"]');
if(cap){cap.focus();cap.value='csba';cap.dispatchEvent(new Event('input',{bubbles:true}));cap.dispatchEvent(new Event('change',{bubbles:true}));}
var bs=dl[i].querySelectorAll('button');
for(var j=0;j<bs.length;j++){if(bs[j].textContent.indexOf('确定')>=0){bs[j].click();break;}}
// 使用原生HTMLInputElement value setter触发Vue v-model
var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
var wrappers = document.querySelectorAll('.el-dialog__wrapper');
for(var i=0;i<wrappers.length;i++){
var w = wrappers[i];
if(w.style.display==='none') continue;
var dialog = w.querySelector('.el-dialog');
if(!dialog || (dialog.textContent||'').indexOf('校验身份')<0) continue;
var pwd = dialog.querySelector('input[placeholder*="密码"]');
if(pwd && !pwd.disabled){
pwd.focus();
nativeSetter.call(pwd, 'Ubains@1357');
pwd.dispatchEvent(new Event('input',{bubbles:true}));
pwd.dispatchEvent(new Event('change',{bubbles:true}));
pwd.dispatchEvent(new Event('blur',{bubbles:true}));
}
var cap = dialog.querySelector('input[placeholder*="验证码"]');
if(cap){
cap.focus();
nativeSetter.call(cap, 'csba');
cap.dispatchEvent(new Event('input',{bubbles:true}));
cap.dispatchEvent(new Event('change',{bubbles:true}));
cap.dispatchEvent(new Event('blur',{bubbles:true}));
}
// 等待Vue更新后点击确定
setTimeout(function(){
var bs = dialog.querySelectorAll('button');
for(var j=0;j<bs.length;j++){
if(bs[j].textContent.indexOf('确定')>=0 && !bs[j].disabled){
bs[j].click();
break;
}
}
}, 300);
}
""")
time.sleep(3)
time.sleep(5)
def login(d):
for attempt in range(3):
......@@ -70,12 +94,28 @@ def login(d):
WebDriverWait(d,30).until(EC.presence_of_element_located((By.TAG_NAME,"button")))
time.sleep(4)
d.execute_script("""
var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
var ins=document.querySelectorAll('input');
for(var i=0;i<ins.length;i++){
var p=ins[i].placeholder||'';
if(p.indexOf('账号')>=0||p.indexOf('手机')>=0){ins[i].value='superadmin';ins[i].dispatchEvent(new Event('input',{bubbles:true}));}
else if(p.indexOf('密码')>=0){ins[i].value='Ubains@1357';ins[i].dispatchEvent(new Event('input',{bubbles:true}));}
else if(p.indexOf('验证码')>=0){ins[i].value='csba';ins[i].dispatchEvent(new Event('input',{bubbles:true}));}
if(p.indexOf('账号')>=0||p.indexOf('手机')>=0){
ins[i].focus();
nativeSetter.call(ins[i],'superadmin');
ins[i].dispatchEvent(new Event('input',{bubbles:true}));
ins[i].dispatchEvent(new Event('change',{bubbles:true}));
}
else if(p.indexOf('密码')>=0){
ins[i].focus();
nativeSetter.call(ins[i],'Ubains@1357');
ins[i].dispatchEvent(new Event('input',{bubbles:true}));
ins[i].dispatchEvent(new Event('change',{bubbles:true}));
}
else if(p.indexOf('验证码')>=0){
ins[i].focus();
nativeSetter.call(ins[i],'csba');
ins[i].dispatchEvent(new Event('input',{bubbles:true}));
ins[i].dispatchEvent(new Event('change',{bubbles:true}));
}
}
""")
time.sleep(2)
......
#!/bin/bash
# 导出单个 MySQL 数据库
# 环境变量: MYSQL_CONTAINER MYSQL_PORT MYSQL_USER MYSQL_PASS DB_NAME DUMP_OUTPUT
docker exec ${MYSQL_CONTAINER} mysqldump \
-h localhost -P ${MYSQL_PORT} \
-u"${MYSQL_USER}" -p"${MYSQL_PASS}" \
--databases "${DB_NAME}" \
--single-transaction \
--routines \
--triggers \
--events \
--skip-lock-tables \
2>/dev/null > "${DUMP_OUTPUT}"
echo "EXIT:$?"
#!/bin/bash
# 列出 umysql 容器中所有用户数据库(排除系统库)
# 环境变量: MYSQL_CONTAINER MYSQL_PORT MYSQL_USER MYSQL_PASS
DBS=$(docker exec ${MYSQL_CONTAINER} mysql -h localhost -P ${MYSQL_PORT} -u"${MYSQL_USER}" -p"${MYSQL_PASS}" -N -e "SHOW DATABASES" 2>/dev/null)
for db in $DBS; do
case "$db" in
information_schema|performance_schema|mysql|sys) ;;
*) echo -n "$db " ;;
esac
done
echo "EXIT:$?"
......@@ -214,88 +214,131 @@ function python_x86() {
#x86架构python播放器系统安装
function python_player_x86() {
echo -e "\033[36m* 正在检查Python服务是否安装......\033[0m"
# 检查Docker镜像和容器状态
local image_exists=$(docker images | grep -q python && echo "true" || echo "false")
local container_running=$(docker ps | grep -q uplayer && echo "true" || echo "false")
local player_dir="/var/www/player"
local source_player_dir="/home/player_auto_install/player"
local docker_image_path="/home/player_auto_install/server_bag/upython_player.tar.gz"
local start_script="/var/www/player/start.sh"
if [[ "$image_exists" == "true" && "$container_running" == "true" ]]; then
echo -e "\033[32m* 检查到Python已安装!正在安装下一个服务....\033[0m"
return 0
else
echo -e "\033[33m* 检查到Python服务未安装,正在安装服务....\033[0m"
log "INFO" "=================================================================="
log "INFO" "开始部署 Python Player 容器 (uplayer) - x86"
log "INFO" "=================================================================="
# 加载Docker镜像并验证其存在
if [ ! -f "$docker_image_path" ]; then
echo -e "\033[31m* 错误: Docker镜像文件 $docker_image_path 不存在。\033[0m"
return 1
fi
# ------------------- 定义变量 -------------------
local image_tar="/data/temp/upython.tar"
local image_tag="139.9.60.86:5000/upython:v11"
local container_name="upython_player"
local host_player_dir="/data/services/api/python-player"
local source_player_dir="/data/temp/player"
local container_start_cmd="/root/start.sh"
if ! docker load -i "$docker_image_path"; then
echo -e "\033[31m* 加载Docker镜像失败。\033[0m"
return 1
# ------------------- 检查容器是否已存在 -------------------
if $sudoset docker ps -a --format '{{.Names}}' | grep -wq "^$container_name$"; then
# 容器已存在
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^$container_name$"; then
log "WARN" "✅ 容器 '$container_name' 已在运行。自动退出部署操作!!!"
return 0
else
log "WARN" "🔄 容器 '$container_name' 存在但未运行,正在启动..."
$sudoset docker start "$container_name"
sleep 3
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^$container_name$"; then
log "INFO" "✅ 容器 '$container_name' 已成功启动"
return 0
else
log "ERROR" "⛔ 容器启动失败,请检查日志: docker logs $container_name"
return 1
fi
fi
fi
# 标记Docker镜像
if ! docker tag e449dd1ea3af 139.9.60.86:5000/upython:v11; then
echo -e "\033[31m* 标记Docker镜像失败。\033[0m"
return 1
fi
# ------------------- 检查并准备挂载目录 -------------------
if [[ ! -d "$source_player_dir" ]]; then
log "ERROR" "⛔ 源目录不存在: $source_player_dir"
return 1
fi
# 检查player目录是否存在,如果不存在则复制
if [ ! -d "$source_player_dir" ]; then
echo -e "\033[31m* 错误: 源目录 $source_player_dir 不存在。\033[0m"
if [[ ! -d "$host_player_dir" ]]; then
log "INFO" "📂 目标目录不存在,正在从源目录复制..."
mkdir -p "$host_player_dir"
if ! cp -rp "$source_player_dir"/* "$host_player_dir"/; then
log "ERROR" "⛔ 复制 player 目录失败"
return 1
fi
log "INFO" "🎉 成功复制 player 目录到 $host_player_dir"
# 赋予执行权限
local chmod_files=(
"$host_player_dir/start.sh"
"$host_player_dir/manage.py"
"$host_player_dir/uuwsgi.ini"
"$host_player_dir/uplayer"
"$host_player_dir/UbainsDevOps"
)
chmod 755 -R "${chmod_files[@]}"
else
log "INFO" "✅ 目录 $host_player_dir 已存在,跳过复制"
fi
if [ ! -d "$player_dir" ]; then
mkdir -p /var/www/player
if ! cp -rp "$source_player_dir" /var/www/; then
echo -e "\033[31m* 复制player目录失败。\033[0m"
# 确认启动脚本存在
if [[ ! -f "$host_player_dir/start.sh" ]]; then
log "ERROR" "⛔ 启动脚本缺失: $host_player_dir/start.sh"
return 1
fi
# ------------------- 加载镜像 -------------------
if $sudoset docker images --format '{{.Repository}}:{{.Tag}}' | grep -Fq "$image_tag"; then
log "INFO" "✅ 镜像 $image_tag 已存在,跳过加载"
else
log "INFO" "📦 正在从离线包加载镜像: $image_tar"
if [[ -f "$image_tar" ]]; then
$sudoset docker load -i "$image_tar"
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 镜像加载失败,请检查文件完整性: $image_tar"
return 1
fi
log "INFO" "🎉 镜像加载成功: $image_tag"
# 标记Docker镜像
if ! docker tag 024630e8dca0 139.9.60.86:5000/upython:v11; then
echo -e "\033[31m* 标记Docker镜像失败。\033[0m"
return 1
else
echo -e "\033[32m* 成功复制 player 目录。\033[0m"
chmod_files=(
"$player_dir/start.sh"
"$player_dir/manage.py"
"$player_dir/uuwsgi.ini"
"$player_dir/uplayer"
"$player_dir/UbainsDevOps"
)
chmod 755 -R "${chmod_files[@]}"
fi
else
echo -e "\033[32m* 目录 $player_dir 已存在,跳过复制。\033[0m"
fi
# 确认启动脚本存在
if [ ! -f "$start_script" ]; then
echo -e "\033[31m* 错误: 启动脚本 $start_script 不存在。\033[0m"
return 1
fi
# 启动容器,并指定固定的MAC地址
if ! docker run -itd \
--mac-address="02:42:ac:12:00:05" \
-p 18082:8082 -p 11883:1883 -p 8000:8000 -p 9001:9001 -p 8002:8002\
-v /var/www/player:/var/www/html \
-v /etc/localtime:/etc/localtime:ro \
-v /usr/share/zoneinfo:/usr/share/zoneinfo:ro \
--restart=always \
--privileged \
--name=uplayer \
139.9.60.86:5000/upython:v11 \
/root/start.sh; then
echo -e "\033[31m* 启动Python容器失败。\033[0m"
log "ERROR" "⛔ 镜像文件不存在: $image_tar"
return 1
else
echo -e "\033[32m* Python服务安装完成并启动成功!\033[0m"
fi
fi
# ------------------- 启动新容器 -------------------
log "INFO" "🚀 正在创建并启动容器 '$container_name'..."
$sudoset docker run -d \
--name "$container_name" \
--restart=always \
--mac-address="02:42:ac:12:00:05" \
--privileged \
-p 18082:8082 \
-p 11883:1883 \
-p 8005:8000 \
-p 9002:9001 \
-p 8006:8002 \
-v "$host_player_dir:/var/www/html" \
-v /etc/localtime:/etc/localtime:ro \
-v /usr/share/zoneinfo:/usr/share/zoneinfo:ro \
"$image_tag" \
"$container_start_cmd"
if [[ $? -ne 0 ]]; then
log "ERROR" "⛔ 容器创建失败,请检查参数或资源"
return 1
fi
log "INFO" "✅ 容器 '$container_name' 创建并启动成功"
# ------------------- 状态验证 -------------------
sleep 3
if $sudoset docker ps --format '{{.Names}}' | grep -wq "^$container_name$"; then
log "INFO" "🟢 Python Player 服务运行中 → 访问端口: 18082, 11883, 8005, 9002, 8006"
else
log "ERROR" "⛔ 容器启动后退出,请查看日志: docker logs $container_name"
return 1
fi
return 0
}
# ========================================
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论