fix:权限调整

This commit is contained in:
waner 2026-05-19 17:12:53 +08:00
parent a30c029fc4
commit 58dc015e01
14 changed files with 651 additions and 39 deletions

View File

@ -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://<ip>:8080/swagger-ui.html` 和 `http://<ip>:8080/v3/api-docs/internal-api` 为准
- `tms.init.executor.rabbitmq-ctl-command` 默认 `sudo -n /usr/local/sbin/tms-rabbitmqctl`
- 运行时 Swagger 文档以 `http://<ip>:8088/swagger-ui.html` 和 `http://<ip>: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

View File

@ -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

View File

@ -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
```

View File

@ -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}"

View File

@ -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}"

View File

@ -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

View File

@ -60,5 +60,4 @@ fi
--host="${HOST}" \
--port="${PORT}" \
--user="${USERNAME}" \
--password="${PASSWORD}" \
"${DB_NAME}" < "${SQL_FILE}"
--password="${PASSWORD}" < "${SQL_FILE}"

View File

@ -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 <allowed-rabbitmqctl-command> [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}" "$@"

View File

@ -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 = "";

View File

@ -684,6 +684,7 @@ public class ConfigurableInitStepExecutor implements InitStepExecutor {
private String resolveRabbitmqCtlCommand() {
List<String> 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<String> 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<String> 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<String> command, boolean ignoreFailure) throws IOException, InterruptedException {
return runCommand(logFile, command, ignoreFailure, null, buildRabbitmqEnvironment());
return runCommand(logFile, expandRabbitmqCommand(command), ignoreFailure, null, buildRabbitmqEnvironment());
}
private List<String> expandRabbitmqCommand(List<String> command) {
if (command == null || command.isEmpty()) {
return List.of();
}
List<String> 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<String> command, boolean ignoreFailure) throws IOException, InterruptedException {

View File

@ -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

View File

@ -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<String, String> 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, """

View File

@ -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;
}
}

View File

@ -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");