diff --git a/README.md b/README.md index f4dbea4..fbc6d1c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ mvn spring-boot:run 完整部署手册: - [docs/deployment/tms-deployment.md](/Users/waner/Work/CISD/文档/tms-framework/docs/deployment/tms-deployment.md) - [docs/deployment/tms-init-admin-permission-checklist.md](/Users/waner/Work/CISD/文档/tms-framework/docs/deployment/tms-init-admin-permission-checklist.md) +- [docs/deployment/tms-root-admin-actions.md](/Users/waner/Work/CISD/文档/tms-framework/docs/deployment/tms-root-admin-actions.md) 1. 构建 jar 包: @@ -39,6 +40,7 @@ cp scripts/tms.sh /home/tms/scripts/tms.sh cp scripts/standard-init/*.sh /home/tms/bin/ sudo cp scripts/standard-init/root-sbin/tms-run-standard-vendor-step /usr/local/sbin/tms-run-standard-vendor-step sudo cp scripts/standard-init/root-sbin/tms-prepare-standard-permissions /usr/local/sbin/tms-prepare-standard-permissions +sudo cp scripts/standard-init/root-sbin/tms-rabbitmqctl /usr/local/sbin/tms-rabbitmqctl cp scripts/resource-restore/*.sh /home/tms/bin/resource-restore/ cp config/application.yml.example /home/tms/config/application.yml chmod +x /home/tms/bin/*.sh @@ -48,6 +50,8 @@ sudo chown root:root /usr/local/sbin/tms-run-standard-vendor-step sudo chmod 755 /usr/local/sbin/tms-run-standard-vendor-step sudo chown root:root /usr/local/sbin/tms-prepare-standard-permissions sudo chmod 755 /usr/local/sbin/tms-prepare-standard-permissions +sudo chown root:root /usr/local/sbin/tms-rabbitmqctl +sudo chmod 755 /usr/local/sbin/tms-rabbitmqctl ``` 3. 启动 / 停止 / 状态: @@ -67,6 +71,7 @@ sudo chmod 755 /usr/local/sbin/tms-prepare-standard-permissions - 标准 CISD 初始化辅助脚本位于 `scripts/standard-init/`,部署时需复制到 `/home/tms/bin/`。 - `scripts/standard-init/root-sbin/tms-run-standard-vendor-step` 是标准版 vendor 客户化脚本的 root 受控入口,部署到 `/usr/local/sbin/` 后通过 sudoers 授权给 `tms` 使用。 - `scripts/standard-init/root-sbin/tms-prepare-standard-permissions` 是标准版介质权限预处理脚本,由 `root` 在初始化前手工执行,用于修正 `/home/cmep4i/cpackage`、license、SQL load、CMSP/CMTP 目录权限。 +- `scripts/standard-init/root-sbin/tms-rabbitmqctl` 是 RabbitMQ CLI 的 root 受控入口,初始化和资源恢复默认通过 `sudo -n /usr/local/sbin/tms-rabbitmqctl` 执行,避免 `tms` 用户 Erlang cookie 与 RabbitMQ 服务端不一致。 - 资源恢复辅助脚本位于 `scripts/resource-restore/`,部署时需复制到 `/home/tms/bin/resource-restore/`。 - `apply_standard_db.sh` 依赖预置环境变量,例如 `DB_USER`、`DB_PASSWORD`,不要直接写入 `application.yml`。 - 运行目录结构、配置项说明、文件上传 `fileId` 流程和故障排查,请查看上面的完整部署手册。 @@ -99,8 +104,8 @@ java -jar tms-framework.jar --tms.crypto.decrypt - 本地构建和测试统一使用 JDK 17,与项目和 CI 运行时保持一致。 Open: -- Swagger UI: http://localhost:8080/swagger-ui.html -- Internal API docs JSON: http://localhost:8080/v3/api-docs/internal-api +- Swagger UI: http://localhost:8088/swagger-ui.html +- Internal API docs JSON: http://localhost:8088/v3/api-docs/internal-api - Runtime-generated Swagger/OpenAPI docs are the source of truth for `/api/**`. - Internal health API: `GET /api/v1/device/status` - Internal sign preview API: `POST /api/v1/sign/preview` @@ -208,8 +213,8 @@ Open: - `/home/tms/bin/stop_standard_rabbitmq.sh` - `/home/tms/bin/apply_standard_db.sh` - `tms.init.executor.standard-db-username/password` 已正确配置 -- `tms.init.executor.rabbitmq-ctl-command` 指向可执行的 `rabbitmqctl` -- 运行时 Swagger 文档以 `http://:8080/swagger-ui.html` 和 `http://:8080/v3/api-docs/internal-api` 为准 +- `tms.init.executor.rabbitmq-ctl-command` 默认 `sudo -n /usr/local/sbin/tms-rabbitmqctl` +- 运行时 Swagger 文档以 `http://:8088/swagger-ui.html` 和 `http://:8088/v3/api-docs/internal-api` 为准 ## Offline Upgrade @@ -563,7 +568,7 @@ mvn -q test Example: ```bash -export BASE_URL=http://127.0.0.1:8080 +export BASE_URL=http://127.0.0.1:8088 export INTERNAL_TOKEN=change-me-internal-token export ALG_SM3=1 export ALG_SM4_ECB=1025 diff --git a/config/application.yml.example b/config/application.yml.example index e0a38ec..f27791b 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -68,6 +68,7 @@ tms: standard-rabbit-stop-command: /home/tms/bin/stop_standard_rabbitmq.sh standard-post-check-command: /home/tms/bin/check_standard_runtime.sh standard-vendor-step-runner-command: /usr/local/sbin/tms-run-standard-vendor-step + rabbitmq-ctl-command: sudo -n /usr/local/sbin/tms-rabbitmqctl upload-base-dir: /home/tms/uploads staging-root-dir: /tmp/tms-init-staging direct-receiver-license-target-path: /home/cmep4d/cmep/license/cmep.license diff --git a/docs/deployment/tms-deployment.md b/docs/deployment/tms-deployment.md index 1d39b51..6b7c6e6 100644 --- a/docs/deployment/tms-deployment.md +++ b/docs/deployment/tms-deployment.md @@ -364,12 +364,12 @@ java -version ### 3.3 网络与端口 TMS 默认监听端口: -- `8080` +- `8088` 检查命令: ```bash -ss -lntp | grep 8080 +ss -lntp | grep 8088 ``` ### 3.4 建议具备的系统命令 @@ -398,12 +398,33 @@ TMS 统一部署到 `/home/tms`: │ ├── apply_standard_db.sh │ ├── start_standard_apps.sh │ ├── start_standard_nginx.sh -│ └── check_standard_runtime.sh +│ ├── stop_standard_apps.sh +│ ├── stop_standard_nginx.sh +│ ├── stop_standard_rabbitmq.sh +│ ├── check_standard_runtime.sh +│ └── resource-restore +│ ├── apply-resource-backup.sh +│ ├── restore-db.sh +│ └── replay-mq.sh ├── config │ └── application.yml +├── gateway +│ ├── gm +│ │ ├── start.sh +│ │ ├── nginx.conf +│ │ └── config/http/tms.conf +│ ├── tls13 +│ │ ├── start.sh +│ │ ├── nginx.conf +│ │ └── conf.d/tms.conf +│ └── logs ├── logs │ ├── tms-framework.out.log │ └── tms-framework.err.log +├── root-sbin +│ ├── tms-prepare-standard-permissions +│ ├── tms-rabbitmqctl +│ └── tms-run-standard-vendor-step ├── run │ └── tms-framework.pid ├── scripts @@ -418,7 +439,9 @@ TMS 统一部署到 `/home/tms`: 目录用途: - `/home/tms/bin`:初始化后置辅助脚本目录,由 `tms.init.executor.*command` 引用 - `/home/tms/config`:Spring Boot 外部配置目录 +- `/home/tms/gateway`:TMS 独立网关配置、启动脚本和日志目录 - `/home/tms/logs`:TMS 运行日志 +- `/home/tms/root-sbin`:root 受控脚本的部署包暂存目录,实际执行位置为 `/usr/local/sbin` - `/home/tms/run`:PID 文件目录 - `/home/tms/scripts`:TMS 启停控制脚本目录 - `/home/tms/tmp`:初始化执行日志和临时目录 @@ -432,10 +455,30 @@ TMS 统一部署到 `/home/tms`: | `/home/tms/scripts/tms.sh` | `scripts/tms.sh` | | `/home/tms/bin/apply_standard_db.sh` | `scripts/standard-init/apply_standard_db.sh` | | `/home/tms/bin/start_standard_apps.sh` | `scripts/standard-init/start_standard_apps.sh` | +| `/home/tms/bin/stop_standard_apps.sh` | `scripts/standard-init/stop_standard_apps.sh` | | `/home/tms/bin/start_standard_nginx.sh` | `scripts/standard-init/start_standard_nginx.sh` | +| `/home/tms/bin/stop_standard_nginx.sh` | `scripts/standard-init/stop_standard_nginx.sh` | +| `/home/tms/bin/stop_standard_rabbitmq.sh` | `scripts/standard-init/stop_standard_rabbitmq.sh` | | `/home/tms/bin/check_standard_runtime.sh` | `scripts/standard-init/check_standard_runtime.sh` | +| `/home/tms/bin/resource-restore/apply-resource-backup.sh` | `scripts/resource-restore/apply-resource-backup.sh` | +| `/home/tms/bin/resource-restore/restore-db.sh` | `scripts/resource-restore/restore-db.sh` | +| `/home/tms/bin/resource-restore/replay-mq.sh` | `scripts/resource-restore/replay-mq.sh` | +| `/home/tms/root-sbin/tms-run-standard-vendor-step` | `scripts/standard-init/root-sbin/tms-run-standard-vendor-step` | +| `/home/tms/root-sbin/tms-prepare-standard-permissions` | `scripts/standard-init/root-sbin/tms-prepare-standard-permissions` | +| `/home/tms/root-sbin/tms-rabbitmqctl` | `scripts/standard-init/root-sbin/tms-rabbitmqctl` | +| `/usr/local/sbin/tms-run-standard-vendor-step` | 由 `/home/tms/root-sbin/tms-run-standard-vendor-step` 复制安装 | +| `/usr/local/sbin/tms-prepare-standard-permissions` | 由 `/home/tms/root-sbin/tms-prepare-standard-permissions` 复制安装 | +| `/usr/local/sbin/tms-rabbitmqctl` | 由 `/home/tms/root-sbin/tms-rabbitmqctl` 复制安装 | +| `/home/tms/gateway/tls13/start.sh` | `deployment/tms-gateway/tls13/start.sh` | +| `/home/tms/gateway/tls13/nginx.conf` | `deployment/tms-gateway/tls13/nginx.conf` | +| `/home/tms/gateway/tls13/conf.d/tms.conf` | `deployment/tms-gateway/tls13/conf.d/tms.conf` | +| `/home/tms/gateway/gm/start.sh` | `deployment/tms-gateway/gm/start.sh` | +| `/home/tms/gateway/gm/nginx.conf` | `deployment/tms-gateway/gm/nginx.conf` | +| `/home/tms/gateway/gm/config/http/tms.conf` | `deployment/tms-gateway/gm/config/http/tms.conf` | | `/home/tms/config/application.yml` | 由 `config/application.yml.example` 复制生成 | +`scripts/cisd_init_precheck.sh` 和 `scripts/pcie_realcard_smoke.sh` 属于诊断脚本,不作为 TMS 常驻运行脚本默认部署。现场需要时可复制到 `/home/tms/tools/` 临时执行。 + ## 6. 部署步骤 ### 6.1 构建 jar 包 @@ -448,7 +491,9 @@ mvn -q -DskipTests package ### 6.2 创建运行目录 ```bash -sudo mkdir -p /home/tms/{bin,config,scripts,logs,run,tmp,uploads,libs} +sudo mkdir -p /home/tms/{bin,config,scripts,logs,run,tmp,uploads,libs,root-sbin,tools} +sudo mkdir -p /home/tms/bin/resource-restore +sudo mkdir -p /home/tms/gateway/{tls13,gm,logs/tls13,logs/gm} sudo chown -R "$(whoami)":"$(whoami)" /home/tms ``` @@ -458,9 +503,16 @@ sudo chown -R "$(whoami)":"$(whoami)" /home/tms cp target/tms-framework-*.jar /home/tms/tms-framework.jar cp scripts/tms.sh /home/tms/scripts/tms.sh cp scripts/standard-init/*.sh /home/tms/bin/ +cp scripts/resource-restore/*.sh /home/tms/bin/resource-restore/ +cp scripts/standard-init/root-sbin/* /home/tms/root-sbin/ +cp -R deployment/tms-gateway/tls13/* /home/tms/gateway/tls13/ +cp -R deployment/tms-gateway/gm/* /home/tms/gateway/gm/ cp config/application.yml.example /home/tms/config/application.yml chmod +x /home/tms/scripts/tms.sh chmod +x /home/tms/bin/*.sh +chmod +x /home/tms/bin/resource-restore/*.sh +chmod +x /home/tms/gateway/tls13/start.sh +chmod +x /home/tms/gateway/gm/start.sh ``` ### 6.4 检查部署结果 @@ -516,7 +568,7 @@ tms: ### 7.3 标准版初始化关键配置 标准版初始化重点配置项: -- `standard-home-dir`:默认 `/home/cemp4i` +- `standard-home-dir`:默认 `/home/cmep4i` - `standard-cpconfig-path`:标准版 `cpconfig.cfg` 路径 - `standard-db-script-base-dir`:默认 `${standard-home-dir}/cpackage` - `standard-db-load-dir`:默认 `${standard-home-dir}/mysql/loadfilepath` @@ -524,6 +576,8 @@ tms: - `standard-web-front-zip-path`:默认 `${standard-home-dir}/cpackage/front/front.zip` - `standard-web-target-dir`:默认 `/usr/local/nginx/html` - `standard-web-organization-json-path`:默认 `/usr/local/nginx/html/organization.json` +- `standard-vendor-step-runner-command`:默认 `/usr/local/sbin/tms-run-standard-vendor-step`,用于 `SET_RABBITMQ`、`SET_TLQ`、`TLQ_CONF_APPLY`、`DEPLOY_RECEIVER` +- `rabbitmq-ctl-command`:默认 `sudo -n /usr/local/sbin/tms-rabbitmqctl`,初始化和资源恢复统一通过 root 受控 wrapper 调用 RabbitMQ CLI ### 7.4 不建议直接写入 application.yml 的凭据 @@ -559,7 +613,7 @@ tms: - `DB_HOST`,默认 `127.0.0.1` - `DB_PORT`,默认 `4000` - `DB_NAME`,默认 `CMEP` -- `LOAD_DIR`,默认 `/home/cemp4i/mysql/loadfilepath` +- `LOAD_DIR`,默认 `/home/cmep4i/mysql/loadfilepath` 示例: @@ -579,8 +633,8 @@ export DB_PASSWORD='secret' - 启动 CMTP 依赖: -- `/home/cemp4i/cmsp/start_cmsp.sh` -- `/home/cemp4i/cmtp/start_cmtp.sh` +- `/home/cmep4i/cmsp/start_cmsp.sh` +- `/home/cmep4i/cmtp/start_cmtp.sh` - 用户 `cmep4i` ### 8.3 `start_standard_nginx.sh` @@ -608,6 +662,50 @@ export DB_PASSWORD='secret' 该脚本仅用于初始化后的结果校验,不负责修复。 +### 8.5 `tms-run-standard-vendor-step` + +来源: +- [tms-run-standard-vendor-step](/Users/waner/Work/CISD/文档/tms-framework/scripts/standard-init/root-sbin/tms-run-standard-vendor-step) + +部署位置: +- `/usr/local/sbin/tms-run-standard-vendor-step` + +作用: +- 作为 root 拥有的受控入口,按固定参数执行标准 vendor 脚本 +- 内部切换到 `cmep4i` 用户后执行 `/home/cmep4i/cpackage/toolsh/*.sh` +- 当前只允许 `setfraq`、`setftq`、`settlq`、`setsptp` + +部署命令: + +```bash +sudo cp scripts/standard-init/root-sbin/tms-run-standard-vendor-step /usr/local/sbin/tms-run-standard-vendor-step +sudo chown root:root /usr/local/sbin/tms-run-standard-vendor-step +sudo chmod 755 /usr/local/sbin/tms-run-standard-vendor-step +``` + +sudoers 建议: + +```sudoers +Defaults:tms !requiretty + +tms ALL=(root) NOPASSWD: \ + /usr/local/sbin/tms-rabbitmqctl *, \ + /usr/local/sbin/tms-run-standard-vendor-step setfraq, \ + /usr/local/sbin/tms-run-standard-vendor-step setftq, \ + /usr/local/sbin/tms-run-standard-vendor-step settlq, \ + /usr/local/sbin/tms-run-standard-vendor-step setsptp +``` + +验证: + +```bash +su - tms +sudo -n /usr/local/sbin/tms-run-standard-vendor-step setsptp +ls -l /home/cmep4i/cmsp /home/cmep4i/cmtp +``` + +执行后 `/home/cmep4i` 下由 vendor 脚本生成或替换的文件应保持为 `cmep4i` 可访问,不应再由 `tms` 直接写成 `tms:tms`。 + ## 9. 文件上传与 `fileId` 模型 ### 9.1 核心原则 @@ -683,6 +781,207 @@ tail -n 100 /home/tms/logs/tms-framework.out.log tail -n 100 /home/tms/logs/tms-framework.err.log ``` +## 10.6 TMS 独立网关(可选) + +当现场需要为 `tms-framework` 单独提供 HTTPS 入口时,建议新增独立 TMS 网关,而不是接管标准收发器现有 `/usr/local/nginx`。 + +这套独立网关的定位是: + +- 只服务 TMS 前端和 TMS API +- 独立于标准收发器现有 Nginx +- 不参与 `START_NGINX`、`STOP_NGINX`、`POST_CHECK` + +标准收发器现有 Nginx 仍然保持: + +- 配置路径:`/usr/local/nginx/conf/nginx.conf` +- Web 发布目录:`/usr/local/nginx/html` +- 默认标准 Web 验收入口:`8000` + +### 10.6.1 推荐端口 + +推荐保留如下边界: + +- 标准收发器现有 Web:`8000` +- TMS TLS 1.3 网关:`443` +- TMS 国密网关:`8443` +- TMS 后端:`127.0.0.1:8088` + +因此在启用独立网关前,建议先检查: + +```bash +ss -lntp | grep -E ':443|:8443|:8000|:8088' +``` + +### 10.6.2 推荐目录 + +建议按如下结构部署: + +```text +/home/tms +├── web +│ └── dist +└── gateway + ├── tls13 + ├── gm + └── logs + ├── tls13 + └── gm +``` + +说明: + +- `/home/tms/web/dist`:TMS 前端构建产物 +- `/home/tms/gateway/tls13`:TLS 1.3 网关配置 +- `/home/tms/gateway/gm`:国密网关配置 +- `/home/tms/gateway/logs/*`:对应网关日志 + +### 10.6.3 仓库模板位置 + +仓库已提供两套模板: + +- TLS 1.3: + - `deployment/tms-gateway/tls13/nginx.conf` + - `deployment/tms-gateway/tls13/conf.d/tms.conf` + - `deployment/tms-gateway/tls13/start.sh` + - `deployment/tms-gateway/tls13/README.md` +- 国密: + - `deployment/tms-gateway/gm/nginx.conf` + - `deployment/tms-gateway/gm/config/http/tms.conf` + - `deployment/tms-gateway/gm/start.sh` + - `deployment/tms-gateway/gm/README.md` + +部署时建议将模板复制到: + +- `/home/tms/gateway/tls13` +- `/home/tms/gateway/gm` + +### 10.6.4 前端发布目录 + +TMS 前端建议统一发布到: + +```text +/home/tms/web/dist +``` + +不建议继续沿用其他项目使用过的 `/app/hsm/vue` 命名。 + +网关容器建议统一将: + +```text +/home/tms/web +``` + +挂载到容器: + +```text +/app +``` + +这样容器内静态资源根目录仍保持: + +```text +/app/dist +``` + +### 10.6.5 后端代理约定 + +独立网关统一代理到: + +```text +http://127.0.0.1:8088 +``` + +需要放行的核心路由: + +- `/` +- `/api/v1/` +- `/swagger-ui.html` +- `/swagger-ui/` +- `/v3/api-docs/` + +特别注意: + +- TMS 接口本身挂在 `/api/v1/**` +- 网关 `proxy_pass` 不应把 `/api/v1/` 前缀错误改写掉 +- 若网关误把 `/api/v1/device/status` 转成 `/device/status`,后端会返回 404 + +因此后续修改网关配置时,应保持“原始请求路径透传到 TMS 后端”这一约束。 + +### 10.6.6 证书与镜像 + +TLS 1.3 网关: + +- 默认使用 `nginx:latest` +- 证书目录:`/home/tms/gateway/tls13/certs` +- 默认文件名: + - `rsa.dev.pem` + - `rsa.dev.key` + +国密网关: + +- 默认使用 `gateway.lib:latest` +- 证书目录:`/home/tms/gateway/gm/certs` +- 默认文件名: + - `sm2.sig.pem` + - `sm2.sig.key` + - `sm2.enc.pem` + - `sm2.enc.key` + - `rsa.dev.pem` + - `rsa.dev.key` + +补充说明: + +- TLS 1.3 网关默认读取自己的 `/home/tms/gateway/tls13/certs` +- 国密网关默认读取自己的 `/home/tms/gateway/gm/certs` +- 如果现场希望 TLS 1.3 直接复用国密目录中的 RSA 证书,可在启动 TLS 1.3 网关时额外指定 `CERT_DIR=/home/tms/gateway/gm/certs` + +如果现场为离线部署,可参考: + +```bash +docker load -i /Users/waner/Work/CISD/文档/manager/tls13/tls13.tar +docker load -i /Users/waner/Work/CISD/文档/manager/gateway.tar +``` + +### 10.6.7 启动与验收 + +TLS 1.3 网关: + +```bash +cd /home/tms/gateway/tls13 +bash start.sh +``` + +国密网关: + +```bash +cd /home/tms/gateway/gm +bash start.sh +``` + +TLS 1.3 验收可先检查: + +```bash +curl -k https://127.0.0.1/ +curl -k https://127.0.0.1/swagger-ui.html +curl -k https://127.0.0.1/api/v1/device/status +``` + +国密入口建议使用现场指定的国密兼容浏览器或客户端进行验证。 + +### 10.6.8 与标准收发器 Nginx 的边界 + +本独立网关方案不改变以下既有边界: + +- 不写入 `/usr/local/nginx/conf/nginx.conf` +- 不使用 `/usr/local/nginx/html` +- 不替代标准收发器 Web 发布流程 +- 不改变标准收发器初始化和 reset 当前逻辑 + +换句话说: + +- 标准收发器 Nginx 继续服务标准产品自身的 Web +- TMS 独立网关只负责 TMS 自己的前端和 API + ## 11. 初始化前置检查 预检查脚本: @@ -859,8 +1158,8 @@ pgrep -f '/usr/local/nginx/sbin/nginx' ```bash /home/tms/scripts/tms.sh status -ss -lntp | grep 8080 -curl -s http://127.0.0.1:8080/api/v1/device/status +ss -lntp | grep 8088 +curl -s http://127.0.0.1:8088/api/v1/device/status find /home/tms/uploads -maxdepth 2 -type f | sort ls -l /home/tms/bin ls -l /home/tms/config/application.yml @@ -871,7 +1170,10 @@ ls -l /home/tms/config/application.yml ```bash bash -n /home/tms/bin/apply_standard_db.sh bash -n /home/tms/bin/start_standard_apps.sh +bash -n /home/tms/bin/stop_standard_apps.sh bash -n /home/tms/bin/start_standard_nginx.sh +bash -n /home/tms/bin/stop_standard_nginx.sh +bash -n /home/tms/bin/stop_standard_rabbitmq.sh bash -n /home/tms/bin/check_standard_runtime.sh ``` diff --git a/scripts/cisd_init_flow.sh b/scripts/cisd_init_flow.sh index 46408a8..57fd8a2 100755 --- a/scripts/cisd_init_flow.sh +++ b/scripts/cisd_init_flow.sh @@ -9,12 +9,12 @@ set -euo pipefail # REQUEST_JSON=/tmp/cisd-init-standard-rabbit.json ./scripts/cisd_init_flow.sh # # Optional env: -# BASE_URL=http://127.0.0.1:8080 +# BASE_URL=http://127.0.0.1:8088 # INTERNAL_TOKEN=change-me-internal-token # OUT_DIR=/tmp/cisd-init-run # FETCH_FAILED_STEP_LOG=true -BASE_URL="${BASE_URL:-http://127.0.0.1:8080}" +BASE_URL="${BASE_URL:-http://127.0.0.1:8088}" INTERNAL_TOKEN="${INTERNAL_TOKEN:-change-me-internal-token}" REQUEST_JSON="${REQUEST_JSON:-}" OUT_DIR="${OUT_DIR:-/tmp/cisd-init-run}" diff --git a/scripts/pcie_realcard_smoke.sh b/scripts/pcie_realcard_smoke.sh index ac02e08..1c0cf19 100755 --- a/scripts/pcie_realcard_smoke.sh +++ b/scripts/pcie_realcard_smoke.sh @@ -6,7 +6,7 @@ set -euo pipefail # chmod +x scripts/pcie_realcard_smoke.sh # ./scripts/pcie_realcard_smoke.sh -BASE_URL="${BASE_URL:-http://127.0.0.1:8080}" +BASE_URL="${BASE_URL:-http://127.0.0.1:8088}" INTERNAL_TOKEN="${INTERNAL_TOKEN:-change-me-internal-token}" TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-15}" diff --git a/scripts/resource-restore/replay-mq.sh b/scripts/resource-restore/replay-mq.sh index 083c7a3..c4dd8c1 100755 --- a/scripts/resource-restore/replay-mq.sh +++ b/scripts/resource-restore/replay-mq.sh @@ -40,25 +40,44 @@ json_text() { sed -nE 's/.*"'"${key}"'"[[:space:]]*:[[:space:]]*"([^"]*)".*/\1/p' "${CONTEXT_FILE}" | head -n 1 } +split_command() { + local value="$1" + # shellcheck disable=SC2206 + CTL_COMMAND=(${value}) +} + find_ctl() { local configured="${RABBITMQ_CTL_COMMAND:-}" if [[ -n "${configured}" ]]; then - printf '%s' "${configured}" + split_command "${configured}" + return 0 + fi + if [[ -x "/usr/local/sbin/tms-rabbitmqctl" ]] && command -v sudo >/dev/null 2>&1; then + CTL_COMMAND=(sudo -n /usr/local/sbin/tms-rabbitmqctl) return 0 fi for candidate in rabbitmqctl /usr/local/rabbitmq/sbin/rabbitmqctl /usr/local/rabbitmq-server/sbin/rabbitmqctl /usr/sbin/rabbitmqctl /usr/lib/rabbitmq/bin/rabbitmqctl; do if command -v "${candidate}" >/dev/null 2>&1; then - command -v "${candidate}" + CTL_COMMAND=("$(command -v "${candidate}")") return 0 fi if [[ -x "${candidate}" ]]; then - printf '%s' "${candidate}" + CTL_COMMAND=("${candidate}") return 0 fi done return 1 } +run_ctl() { + local timeout_seconds="${RABBITMQ_CTL_TIMEOUT_SECONDS:-30}" + if command -v timeout >/dev/null 2>&1; then + timeout "${timeout_seconds}" "${CTL_COMMAND[@]}" "$@" + else + "${CTL_COMMAND[@]}" "$@" + fi +} + if [[ -n "${MQ_REPLAY_COMMAND:-}" ]]; then log "delegating mq replay to configured command" "${MQ_REPLAY_COMMAND}" --context-file "${CONTEXT_FILE}" --work-dir "${WORK_DIR}" >> "${LOG_FILE}" 2>&1 @@ -85,15 +104,16 @@ if [[ -z "${USER_NAME}" || -z "${USER_PASS}" ]]; then exit 1 fi -CTL="$(find_ctl)" || { +CTL_COMMAND=() +find_ctl || { echo "[ERROR] rabbitmqctl not found" >&2 exit 1 } -"${CTL}" status >> "${LOG_FILE}" 2>&1 || log "rabbitmqctl status failed, continue replay" -"${CTL}" add_vhost "${VHOST}" >> "${LOG_FILE}" 2>&1 || true -"${CTL}" add_user "${USER_NAME}" "${USER_PASS}" >> "${LOG_FILE}" 2>&1 || "${CTL}" change_password "${USER_NAME}" "${USER_PASS}" >> "${LOG_FILE}" 2>&1 -"${CTL}" set_permissions -p "${VHOST}" "${USER_NAME}" ".*" ".*" ".*" >> "${LOG_FILE}" 2>&1 +run_ctl status >> "${LOG_FILE}" 2>&1 || log "rabbitmqctl status failed, continue replay" +run_ctl add_vhost "${VHOST}" >> "${LOG_FILE}" 2>&1 || true +run_ctl add_user "${USER_NAME}" "${USER_PASS}" >> "${LOG_FILE}" 2>&1 || run_ctl change_password "${USER_NAME}" "${USER_PASS}" >> "${LOG_FILE}" 2>&1 +run_ctl set_permissions -p "${VHOST}" "${USER_NAME}" ".*" ".*" ".*" >> "${LOG_FILE}" 2>&1 log "rabbitmq user and permissions replayed" if [[ -n "${RABBIT_QUEUE_REPLAY_SCRIPT_PATH:-}" && -x "${RABBIT_QUEUE_REPLAY_SCRIPT_PATH}" ]]; then diff --git a/scripts/resource-restore/restore-db.sh b/scripts/resource-restore/restore-db.sh index 4c7a9ed..8ed8450 100755 --- a/scripts/resource-restore/restore-db.sh +++ b/scripts/resource-restore/restore-db.sh @@ -60,5 +60,4 @@ fi --host="${HOST}" \ --port="${PORT}" \ --user="${USERNAME}" \ - --password="${PASSWORD}" \ - "${DB_NAME}" < "${SQL_FILE}" + --password="${PASSWORD}" < "${SQL_FILE}" diff --git a/scripts/standard-init/root-sbin/tms-rabbitmqctl b/scripts/standard-init/root-sbin/tms-rabbitmqctl new file mode 100755 index 0000000..29bfad5 --- /dev/null +++ b/scripts/standard-init/root-sbin/tms-rabbitmqctl @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "$(id -u)" -ne 0 ]]; then + echo "tms-rabbitmqctl must be executed as root through sudo" >&2 + exit 77 +fi + +if [[ $# -lt 1 ]]; then + echo "usage: tms-rabbitmqctl [args...]" >&2 + exit 64 +fi + +require_arg_count() { + local expected="$1" + shift + if [[ $# -ne "${expected}" ]]; then + echo "unsupported rabbitmqctl arguments" >&2 + exit 64 + fi +} + +case "$1" in + status|list_users) + require_arg_count 1 "$@" + ;; + add_vhost|delete_user) + require_arg_count 2 "$@" + ;; + add_user|change_password|authenticate_user) + require_arg_count 3 "$@" + ;; + set_permissions) + if [[ $# -ne 7 || "${2:-}" != "-p" ]]; then + echo "unsupported rabbitmqctl set_permissions arguments" >&2 + exit 64 + fi + ;; + clear_permissions) + if [[ $# -ne 4 || "${2:-}" != "-p" ]]; then + echo "unsupported rabbitmqctl clear_permissions arguments" >&2 + exit 64 + fi + ;; + list_queues) + if [[ ! ( $# -eq 2 || ( $# -eq 4 && "${2:-}" == "-p" ) ) ]]; then + echo "unsupported rabbitmqctl list_queues arguments" >&2 + exit 64 + fi + ;; + delete_queue) + if [[ ! ( $# -eq 2 || ( $# -eq 4 && "${2:-}" == "-p" ) ) ]]; then + echo "unsupported rabbitmqctl delete_queue arguments" >&2 + exit 64 + fi + ;; + *) + echo "unsupported rabbitmqctl command: $1" >&2 + exit 64 + ;; +esac + +export ERLANG_HOME="${ERLANG_HOME:-/usr/local/erlang}" +export RABBITMQ_HOME="${RABBITMQ_HOME:-/usr/local/rabbitmq}" +export PATH="${ERLANG_HOME}/bin:${RABBITMQ_HOME}/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin" + +if [[ -n "${RABBITMQ_CTL:-}" ]]; then + RABBITMQ_CTL_CANDIDATES=("${RABBITMQ_CTL}") +else + RABBITMQ_CTL_CANDIDATES=( + "${RABBITMQ_HOME}/sbin/rabbitmqctl" + "/usr/local/rabbitmq/sbin/rabbitmqctl" + "/usr/local/rabbitmq-server/sbin/rabbitmqctl" + "/usr/sbin/rabbitmqctl" + "/usr/lib/rabbitmq/bin/rabbitmqctl" + ) +fi + +RABBITMQ_CTL="" +for candidate in "${RABBITMQ_CTL_CANDIDATES[@]}"; do + if [[ -x "${candidate}" ]]; then + RABBITMQ_CTL="${candidate}" + break + fi +done + +if [[ ! -x "${RABBITMQ_CTL}" ]]; then + echo "rabbitmqctl not executable" >&2 + exit 69 +fi + +exec "${RABBITMQ_CTL}" "$@" diff --git a/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java b/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java index e5f736f..6b6c580 100644 --- a/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java +++ b/src/main/java/com/cisd/tms/modules/init/config/InitExecutorProperties.java @@ -15,6 +15,8 @@ public class InitExecutorProperties { private static final String DEFAULT_STANDARD_RUN_USER = "cmep4i"; private static final String DEFAULT_STANDARD_VENDOR_STEP_RUNNER_COMMAND = "/usr/local/sbin/tms-run-standard-vendor-step"; + private static final String DEFAULT_RABBITMQ_CTL_COMMAND = + "sudo -n /usr/local/sbin/tms-rabbitmqctl"; private Mode mode = Mode.SIMULATE; private int timeoutSec = 600; @@ -104,7 +106,7 @@ public class InitExecutorProperties { private String rabbitAdminUsername = "admin"; private String rabbitAdminPassword = "admin"; // RabbitMQ 管理命令路径 - private String rabbitmqCtlCommand = "rabbitmqctl"; + private String rabbitmqCtlCommand = DEFAULT_RABBITMQ_CTL_COMMAND; private String rabbitmqAdminCommand = "/usr/sbin/rabbitmqadmin"; // 标准版 RabbitMQ 初始化脚本(创建admin/通道账号、替换队列脚本机构号) private String standardRabbitSetupScriptPath = ""; diff --git a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java index f1e918e..a929acd 100644 --- a/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java +++ b/src/main/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutor.java @@ -684,6 +684,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { private String resolveRabbitmqCtlCommand() { List candidates = new ArrayList<>(); candidates.add(firstNonBlank(properties.getRabbitmqCtlCommand(), "rabbitmqctl")); + candidates.add("sudo -n /usr/local/sbin/tms-rabbitmqctl"); candidates.add("/usr/local/rabbitmq/sbin/rabbitmqctl"); candidates.add("/usr/local/rabbitmq-server/sbin/rabbitmqctl"); candidates.add("/usr/sbin/rabbitmqctl"); @@ -694,6 +695,12 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { if (isBlank(candidate)) { continue; } + if (candidateHasArguments(candidate)) { + if (isRunnableCommandPrefix(candidate)) { + return candidate; + } + continue; + } if (candidate.contains("/")) { try { Path path = Paths.get(candidate).normalize().toAbsolutePath(); @@ -716,6 +723,37 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { ); } + private boolean isRunnableCommandPrefix(String command) { + List parts = splitCommandPrefix(command); + if (parts.isEmpty()) { + return false; + } + String executable = parts.get(0); + if (executable.contains("/")) { + try { + Path path = Paths.get(executable).normalize().toAbsolutePath(); + return Files.exists(path) && Files.isRegularFile(path); + } catch (InvalidPathException ex) { + return false; + } + } + return !isBlank(findExecutableInPath(executable)); + } + + private boolean candidateHasArguments(String command) { + return splitCommandPrefix(command).size() > 1; + } + + private List splitCommandPrefix(String command) { + String value = normalize(command); + if (isBlank(value)) { + return List.of(); + } + return Arrays.stream(value.split("\\s+")) + .filter(part -> !isBlank(part)) + .toList(); + } + private String findExecutableInPath(String commandName) { String pathValue = System.getenv("PATH"); if (isBlank(pathValue)) { @@ -1920,7 +1958,19 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor { } private int runRabbitmqCommand(Path logFile, List command, boolean ignoreFailure) throws IOException, InterruptedException { - return runCommand(logFile, command, ignoreFailure, null, buildRabbitmqEnvironment()); + return runCommand(logFile, expandRabbitmqCommand(command), ignoreFailure, null, buildRabbitmqEnvironment()); + } + + private List expandRabbitmqCommand(List command) { + if (command == null || command.isEmpty()) { + return List.of(); + } + List expanded = new ArrayList<>(splitCommandPrefix(command.get(0))); + if (expanded.isEmpty()) { + expanded.add(command.get(0)); + } + expanded.addAll(command.subList(1, command.size())); + return expanded; } private int runCommand(Path logFile, List command, boolean ignoreFailure) throws IOException, InterruptedException { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index aec6974..377f673 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,7 +13,7 @@ spring: server: # TMS HTTP 服务监听端口。 - port: 8080 + port: 8088 management: endpoints: @@ -135,9 +135,8 @@ tms: rabbit-admin-username: ${TMS_INIT_EXECUTOR_RABBIT_ADMIN_USERNAME:admin} # RabbitMQ 管理员密码(预制)。 rabbit-admin-password: ${TMS_INIT_EXECUTOR_RABBIT_ADMIN_PASSWORD:admin} - # RabbitMQ 控制命令(用于新增用户、授权等)。 - # 建议线上配置为绝对路径,例如 /usr/local/rabbitmq/sbin/rabbitmqctl。 - rabbitmq-ctl-command: ${TMS_INIT_EXECUTOR_RABBITMQ_CTL_COMMAND:/usr/local/rabbitmq/sbin/rabbitmqctl} + # RabbitMQ 控制命令(用于新增用户、授权等);默认通过 root 受控 wrapper 执行,避免 tms 与 RabbitMQ 服务端 Erlang cookie 不一致。 + rabbitmq-ctl-command: ${TMS_INIT_EXECUTOR_RABBITMQ_CTL_COMMAND:sudo -n /usr/local/sbin/tms-rabbitmqctl} # RabbitMQ Admin CLI 命令路径(用于创建队列等)。 rabbitmq-admin-command: ${TMS_INIT_EXECUTOR_RABBITMQ_ADMIN_COMMAND:/usr/sbin/rabbitmqadmin} # 标准版 RabbitMQ 初始化脚本路径(保留兼容;MQ_USER_APPLY 当前优先直接调用 rabbitmqctl/rabbitmqadmin)。 @@ -322,7 +321,7 @@ tms: # 备份/恢复标准收发器业务库名;第一版默认使用 CMEP。 cmep-database-name: ${TMS_BACKUP_CMEP_DATABASE_NAME:CMEP} # 恢复完成后的最小健康检查地址。 - health-check-url: ${TMS_BACKUP_HEALTH_CHECK_URL:http://127.0.0.1:8080/actuator/health} + health-check-url: ${TMS_BACKUP_HEALTH_CHECK_URL:http://127.0.0.1:8088/actuator/health} # 健康检查请求超时(秒)。 health-check-timeout-seconds: ${TMS_BACKUP_HEALTH_CHECK_TIMEOUT_SECONDS:5} # 健康检查最大等待时间(秒);TMS 启动命令返回后,Spring Boot 仍可能需要一段时间才监听端口。 @@ -340,7 +339,7 @@ tms: internal-token: ${TMS_INTERNAL_TOKEN:change-me-internal-token} internal-auth: # 是否启用 /api/** 的内部登录鉴权;开发联调时可临时关闭。 - enabled: ${TMS_SECURITY_INTERNAL_AUTH_ENABLED:false} + enabled: ${TMS_SECURITY_INTERNAL_AUTH_ENABLED:true} # 关闭内部鉴权后,注入请求上下文的调试角色。 debug-role-code: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_ROLE_CODE:OPS_ADMIN} # 关闭内部鉴权后,注入请求上下文的调试认证等级。 @@ -349,7 +348,7 @@ tms: debug-session-token: ${TMS_SECURITY_INTERNAL_AUTH_DEBUG_SESSION_TOKEN:DEBUG-BYPASS} replay: # 是否启用防重放校验;本地 Postman/联调可临时设为 false,生产环境应保持 true。 - enabled: ${TMS_SECURITY_REPLAY_ENABLED:false} + enabled: ${TMS_SECURITY_REPLAY_ENABLED:true} openapi: # 外部签名服务接口允许的时间戳偏差(秒),防重放。 timestamp-skew-seconds: 300 diff --git a/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java b/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java index 6efb660..f1d2ec8 100644 --- a/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/restore/ApplyResourceBackupScriptContractTest.java @@ -332,6 +332,33 @@ class ApplyResourceBackupScriptContractTest { Assertions.assertTrue(status.contains("database restore failed")); } + @Test + void shouldExecuteDatabaseSqlWithoutConnectingToTargetDatabaseFirst() throws Exception { + Path commandLog = tempDir.resolve("mysql-restore.log"); + Path mysql = tempDir.resolve("mysql"); + Files.writeString(mysql, """ + #!/usr/bin/env bash + for arg in "$@"; do + printf 'ARG:%%s\\n' "$arg" >> "%s" + done + input="$(cat)" + if [ -n "$input" ]; then + printf 'STDIN:%%s\\n' "$input" >> "%s" + fi + """.formatted(commandLog, commandLog)); + mysql.toFile().setExecutable(true); + Path sqlFile = tempDir.resolve("CMEP.sql"); + Files.writeString(sqlFile, "CREATE DATABASE IF NOT EXISTS `CMEP`;\nUSE `CMEP`;\nselect 1;"); + + int exitCode = runDbRestoreScriptForExit(mysql, "CMEP", sqlFile); + + Assertions.assertEquals(0, exitCode); + String log = Files.readString(commandLog); + Assertions.assertTrue(log.contains("ARG:--host=127.0.0.1")); + Assertions.assertFalse(log.contains("ARG:CMEP" + System.lineSeparator())); + Assertions.assertTrue(log.contains("STDIN:CREATE DATABASE IF NOT EXISTS `CMEP`;")); + } + private static void runScript(Path workDir, Map environment) throws Exception { int exitCode = runScriptForExit(workDir, environment); Assertions.assertEquals(0, exitCode); @@ -350,6 +377,23 @@ class ApplyResourceBackupScriptContractTest { return process.waitFor(); } + private static int runDbRestoreScriptForExit(Path mysqlPath, String dbName, Path sqlFile) throws Exception { + ProcessBuilder builder = new ProcessBuilder( + "bash", + Path.of("scripts/resource-restore/restore-db.sh").toAbsolutePath().normalize().toString(), + "--mysql-path", mysqlPath.toString(), + "--host", "127.0.0.1", + "--port", "4000", + "--username", "root", + "--password", "secret", + "--db-name", dbName, + "--sql-file", sqlFile.toString() + ); + builder.directory(Path.of(".").toAbsolutePath().normalize().toFile()); + Process process = builder.start(); + return process.waitFor(); + } + private Path fakeScript(String name, Path commandLog) throws Exception { Path script = tempDir.resolve(name); Files.writeString(script, """ diff --git a/src/test/java/com/cisd/tms/modules/backup/restore/MqReplayScriptContractTest.java b/src/test/java/com/cisd/tms/modules/backup/restore/MqReplayScriptContractTest.java index 2726bca..c257264 100644 --- a/src/test/java/com/cisd/tms/modules/backup/restore/MqReplayScriptContractTest.java +++ b/src/test/java/com/cisd/tms/modules/backup/restore/MqReplayScriptContractTest.java @@ -39,6 +39,36 @@ class MqReplayScriptContractTest { Assertions.assertTrue(log.contains("set_permissions -p /RQ tms .* .* .*")); } + @Test + void shouldReplayRabbitMqThroughPrefixedRootWrapperCommand() throws Exception { + Path context = tempDir.resolve("mq-restore-context.json"); + Files.writeString(context, """ + {"schemaVersion":"1.0","productType":"ENTERPRISE","mqType":"RABBITMQ","orgCode":"BKCHCNBJXXX", + "mq":{"channelUsername":"bank_user","channelPassword":"Secret123!"}} + """); + Path commandLog = tempDir.resolve("commands.log"); + Path sudo = fakeSudo(commandLog); + Path wrapper = tempDir.resolve("tms-rabbitmqctl"); + + ProcessBuilder builder = new ProcessBuilder( + "bash", + Path.of("scripts/resource-restore/replay-mq.sh").toAbsolutePath().normalize().toString(), + "--context-file", + context.toString(), + "--work-dir", + tempDir.resolve("work").toString() + ); + builder.environment().put("RABBITMQ_CTL_COMMAND", sudo + " -n " + wrapper); + Process process = builder.start(); + + Assertions.assertEquals(0, process.waitFor(), new String(process.getErrorStream().readAllBytes())); + String log = Files.readString(commandLog); + Assertions.assertTrue(log.contains("-n " + wrapper + " status")); + Assertions.assertTrue(log.contains("-n " + wrapper + " add_vhost /RQ")); + Assertions.assertTrue(log.contains("-n " + wrapper + " add_user bank_user Secret123!")); + Assertions.assertTrue(log.contains("-n " + wrapper + " set_permissions -p /RQ bank_user .* .* .*")); + } + @Test void shouldDelegateToCustomReplayCommandWhenConfigured() throws Exception { Path context = tempDir.resolve("mq-restore-context.json"); @@ -71,4 +101,15 @@ class MqReplayScriptContractTest { script.toFile().setExecutable(true); return script; } + + private Path fakeSudo(Path commandLog) throws Exception { + Path script = tempDir.resolve("sudo"); + Files.writeString(script, """ + #!/usr/bin/env bash + printf '%%s\\n' "$*" >> "%s" + exit 0 + """.formatted(commandLog)); + script.toFile().setExecutable(true); + return script; + } } diff --git a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java index ffeb133..3050f34 100644 --- a/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java +++ b/src/test/java/com/cisd/tms/modules/init/executor/ConfigurableInitStepExecutorTest.java @@ -1112,13 +1112,15 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(exampleContent.contains("standard-nginx-start-command: /home/tms/bin/start_standard_nginx.sh")); Assertions.assertTrue(exampleContent.contains("standard-nginx-stop-command: /home/tms/bin/stop_standard_nginx.sh")); Assertions.assertTrue(exampleContent.contains("standard-post-check-command: /home/tms/bin/check_standard_runtime.sh")); + Assertions.assertTrue(exampleContent.contains("rabbitmq-ctl-command: sudo -n /usr/local/sbin/tms-rabbitmqctl")); String applicationContent = Files.readString(applicationConfig); Assertions.assertTrue(applicationContent.contains("standard-app-start-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_START_COMMAND:/home/tms/bin/start_standard_apps.sh}")); Assertions.assertTrue(applicationContent.contains("standard-app-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_APP_STOP_COMMAND:/home/tms/bin/stop_standard_apps.sh}")); - Assertions.assertTrue(applicationContent.contains("standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:/home/tms/bin/start_standard_nginx.sh}")); - Assertions.assertTrue(applicationContent.contains("standard-nginx-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_STOP_COMMAND:/home/tms/bin/stop_standard_nginx.sh}")); + Assertions.assertTrue(applicationContent.contains("standard-nginx-start-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_START_COMMAND:sudo -n /usr/local/sbin/tms-start-standard-nginx}")); + Assertions.assertTrue(applicationContent.contains("standard-nginx-stop-command: ${TMS_INIT_EXECUTOR_STANDARD_NGINX_STOP_COMMAND:sudo -n /usr/local/sbin/tms-stop-standard-nginx}")); Assertions.assertTrue(applicationContent.contains("standard-post-check-command: ${TMS_INIT_EXECUTOR_STANDARD_POST_CHECK_COMMAND:/home/tms/bin/check_standard_runtime.sh}")); + Assertions.assertTrue(applicationContent.contains("rabbitmq-ctl-command: ${TMS_INIT_EXECUTOR_RABBITMQ_CTL_COMMAND:sudo -n /usr/local/sbin/tms-rabbitmqctl}")); String readmeContent = Files.readString(readme); Assertions.assertTrue(readmeContent.contains("scripts/standard-init/")); @@ -1247,6 +1249,61 @@ class ConfigurableInitStepExecutorTest { Assertions.assertTrue(callsContent.contains("set_permissions -p /RQ bank_user .* .* .*")); } + @Test + void shouldApplyStandardRabbitUserThroughPrefixedRootWrapperCommand() throws IOException { + Path tempRoot = Files.createTempDirectory("init-executor-rabbit-wrapper-test"); + Path cpconfig = tempRoot.resolve("cpconfig.cfg"); + Path calls = tempRoot.resolve("calls.log"); + Path sudo = tempRoot.resolve("sudo"); + Path wrapper = tempRoot.resolve("tms-rabbitmqctl"); + Files.writeString(cpconfig, "MY_CIPSID_OR_BIC=AAAABBBBXXX\n"); + Files.writeString( + sudo, + "#!/bin/bash\n" + + "echo \"$*\" >> \"" + calls + "\"\n" + + "exit 0\n" + ); + Assertions.assertTrue(sudo.toFile().setExecutable(true)); + + InitExecutorProperties properties = new InitExecutorProperties(); + properties.setMode(InitExecutorProperties.Mode.LOCAL); + properties.setLogDir(tempRoot.resolve("logs").toString()); + properties.setStagingRootDir(tempRoot.resolve("staging").toString()); + properties.setStandardCpconfigPath(cpconfig.toString()); + properties.setRabbitmqCtlCommand(sudo + " -n " + wrapper); + ConfigurableInitStepExecutor executor = new ConfigurableInitStepExecutor(properties, new ObjectMapper()); + + InitTaskEntity task = new InitTaskEntity(); + task.setTaskId("TASK-RABBIT-WRAPPER-1"); + task.setProductType("ENTERPRISE"); + task.setInitPlanJson(buildInitPlanJson( + "ENTERPRISE", + Map.of( + "orgCodeType", "BIC", + "orgCode", "AAAABBBBXXX", + "orgNameCn", "测试机构", + "deployMode", "SINGLE", + "nodes", Map.of("node01Ip", "10.0.0.1"), + "mq", Map.of("mqType", "RABBITMQ", "channelUsername", "bank_user", "channelPassword", "bank_pass"), + "licenses", Map.of("receiverLicenseFileId", "receiver-001") + ) + )); + + InitTaskStepEntity step = new InitTaskStepEntity(); + step.setStepNo(1); + step.setStepCode("MQ_USER_APPLY"); + step.setCommandLine("internal::mq_user_apply"); + + InitStepExecutionResult result = executor.execute(task, step); + + Assertions.assertTrue(result.isSuccess()); + String callsContent = Files.readString(calls); + Assertions.assertTrue(callsContent.contains("-n " + wrapper + " status")); + Assertions.assertTrue(callsContent.contains("-n " + wrapper + " add_vhost /RQ")); + Assertions.assertTrue(callsContent.contains("-n " + wrapper + " add_user bank_user bank_pass")); + Assertions.assertTrue(callsContent.contains("-n " + wrapper + " set_permissions -p /RQ bank_user .* .* .*")); + } + @Test void shouldApplyStandardRabbitUserForIndirectRabbitmqTlq() throws IOException { Path tempRoot = Files.createTempDirectory("init-executor-rabbit-setup-indirect-tlq-test");