diff --git a/README.md b/README.md index 86569fe..31090b6 100644 --- a/README.md +++ b/README.md @@ -2,32 +2,54 @@ ## 快速开始 -**首次部署:** +### 首次部署 ```bash curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/install.sh \ -o install.sh && bash install.sh ``` -脚本自动完成:依赖检测 → 配置生成 → 镜像拉取 → 容器启动 → 租户初始化(新建或迁移)→ 全量验证。 +交互式向导依次完成:依赖检测 → 配置生成 → 镜像拉取 → 容器启动 → 租户初始化 → 全量验证。 -**升级已有部署:** +### 升级已有部署 ```bash curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/upgrade.sh \ -o upgrade.sh && bash upgrade.sh ``` -升级脚本自动完成:下载最新脚本 → 保留全部数据和配置 → 修复配置问题 → 可选拉取新镜像 → 重启容器 → 全量验证。 +保留全部数据和配置,自动修复已知配置问题,可选拉取新镜像,完成后运行全量验证。 -部署完成后根据输出的端口表配置宿主机 nginx,详见 [docs/runbook.md](docs/runbook.md)。 +### 容器异常重置 + +```bash +bash scripts/reset.sh +``` + +适用于:nginx 502、服务 crash-loop、密码注入错误、升级后镜像未生效。保留数据和配置,彻底重建所有容器。 + +--- + +## 脚本说明 + +| 脚本 | 适用场景 | +|------|---------| +| `install.sh` | 全新服务器首次部署;或彻底清除数据重装 | +| `upgrade.sh` | 保留数据,更新部署脚本和镜像(推荐日常升级方式) | +| `scripts/reset.sh` | 保留数据,容器状态异常时快速重建恢复 | +| `scripts/update.sh` | 在安装目录内执行的局部升级(由 upgrade.sh 调用) | +| `scripts/verify.sh` | 随时重新运行全量验证 | +| `scripts/backup.sh` | 备份数据 | +| `scripts/restore.sh` | 恢复备份 | + +--- ## 部署架构 ``` -上层 nginx(任意层级) +上层 nginx(任意层级,HTTPS / 域名) │ - └── 本机 IP:80 内置 nginx 容器(统一入口,无需宿主机 nginx) + └── 本机 IP:80 内置 nginx 容器(统一入口) │ ├── tenant-service /api/ /actuator/ ├── file-service /file/ @@ -38,7 +60,9 @@ curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/mai └── push-service (厂商回调,按需) ``` -内置 nginx 容器直接绑定宿主机 `0.0.0.0:80`,**宿主机无需任何 nginx 配置**,上层 nginx 直接 proxy_pass 到本机 IP 即可。各业务容器(11224–11231)绑定 `127.0.0.1`,仅用于本地调试。 +内置 nginx 容器直接绑定宿主机 `0.0.0.0:80`,**宿主机无需配置 nginx**,上层 nginx 直接 proxy_pass 到本机 IP 即可。各业务容器(11224–11231)绑定 `127.0.0.1`,仅用于本地调试。 + +--- ## 租户初始化方式 @@ -47,6 +71,8 @@ curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/mai - **新建租户**:填写邮箱、用户名、密码,首次启动自动创建 - **迁移租户**:在公有化平台安全中心生成迁移密钥(`pmk_` 开头),粘贴后自动完成导入 +--- + ## 服务说明 | Profile | 服务 | 说明 | @@ -59,21 +85,24 @@ curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/mai | update | update-service | 版本管理 + RN 热更新 | | license | license-service | License 校验 | -## 后期启用/禁用服务 - ```bash -./scripts/enable-service.sh im -./scripts/disable-service.sh im +# 后期启用 / 禁用可选服务 +bash scripts/enable-service.sh im +bash scripts/disable-service.sh im ``` +--- + ## 注意事项 -- `application.yml` 中数据库 URL 硬编码了生产地址,`docker-compose.yml` 的 `environment:` 节负责覆盖,**不能删除** -- 上层 nginx `proxy_pass` 写本机 IP(端口 80),须透传 `Upgrade` / `Connection` 头(WebSocket 必需),每层都要加,详见 [docs/runbook.md](docs/runbook.md) +- 上层 nginx 每一层都必须透传 `Upgrade` / `Connection` 头,否则 IM WebSocket 会断开,详见 [docs/runbook.md](docs/runbook.md) - 宿主机本身无需配置 nginx +--- + ## 文档 -- [运行手册](docs/runbook.md) — 完整部署流程、nginx 配置、常用运维命令 +- [运行手册](docs/runbook.md) — 完整部署流程、nginx 配置、故障排查、常用运维命令 - [配置说明](docs/configuration.md) — 各配置文件字段说明 - [验收清单](docs/acceptance-checklist.md) — 交付验收检查项 +- [部署信息记录模板](docs/deployment-defaults.md) — 填写后交付客户存档 diff --git a/docs/acceptance-checklist.md b/docs/acceptance-checklist.md index a7ccd62..be8c0e0 100644 --- a/docs/acceptance-checklist.md +++ b/docs/acceptance-checklist.md @@ -11,7 +11,6 @@ - [ ] tenant-service 健康:`curl http://127.0.0.1:11224/actuator/health` 返回 200 - [ ] 控制台前端可访问:`curl http://127.0.0.1:11226` 返回 HTML -- [ ] 运营后台可访问:`curl http://127.0.0.1:11227` 返回 HTML - [ ] 文件服务可访问:`curl http://127.0.0.1:11225/file/health` 返回正常 - [ ] 私有化模式确认:`/api/private/deployment/status` 返回 `"mode":"PRIVATE"` - [ ] 注册入口关闭:`"tenantRegisterEnabled":false` @@ -19,8 +18,8 @@ ## 可选服务 - [ ] im-service 健康:`curl http://127.0.0.1:11228/actuator/health` 返回 200 -- [ ] update-service 健康:`curl http://127.0.0.1:11229/actuator/health` 返回 200 -- [ ] license-service 健康:`curl http://127.0.0.1:11230/actuator/health` 返回 200 +- [ ] update-service 健康:`curl http://127.0.0.1:11230/actuator/health` 返回 200 +- [ ] license-service 健康:`curl http://127.0.0.1:11231/actuator/health` 返回 200 - [ ] push-service 已启动(厂商凭据按需填写) ## 中间件 @@ -38,9 +37,9 @@ - [ ] 宿主机 nginx 配置后,通过外部地址可访问控制台 - [ ] `/api/im/` 路由到 im-service(11228) -- [ ] `/ws/im` WebSocket 连接正常(需 `Upgrade` 头) -- [ ] `/api/v1/updates/` 路由到 update-service(11229),不被 `/api/` 拦截 -- [ ] `/api/license/` 路由到 license-service(11230),不被 `/api/` 拦截 +- [ ] `/ws/im` WebSocket 连接正常(需每层 nginx 透传 `Upgrade` / `Connection` 头) +- [ ] `/api/v1/updates/` 路由到 update-service(11230),不被通用 `/api/` 拦截 +- [ ] `/api/license/` 路由到 license-service(11231),不被通用 `/api/` 拦截 - [ ] `/file/` 支持大文件上传(500MB) ## 公有化隔离 diff --git a/docs/configuration.md b/docs/configuration.md index ff33155..de8ce4a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -14,16 +14,25 @@ | `MYSQL_MODE` | `managed`(容器托管)或 `external`(客户自备) | | `REDIS_MODE` | `managed` 或 `external` | | `CONSOLE_DOMAIN` | 对外访问地址,用于服务间跳转和 SDK 配置 | +| `NGINX_BIND` | 内置 nginx 绑定端口。`80` = 直接监听宿主机 80;`127.0.0.1:11223` = 有宿主机 nginx 场景 | + +--- ## `config/secrets.env` -敏感配置,权限 600,不提交 Git。 +敏感配置,权限 600,不提交 Git。由 `install.sh` 自动生成。 | 字段 | 说明 | |------|------| | `MYSQL_ROOT_PASSWORD` | MySQL root 密码 | | `MYSQL_PASSWORD` | 业务账号密码 | | `REDIS_PASSWORD` | Redis 密码 | +| `SPRING_DATASOURCE_PASSWORD` | Spring 读取的数据库密码(与 `MYSQL_PASSWORD` 值相同) | +| `SPRING_DATA_REDIS_PASSWORD` | Spring 读取的 Redis 密码(与 `REDIS_PASSWORD` 值相同) | + +> **设计说明**:Spring 通过 `env_file` 直接读取 `SPRING_DATASOURCE_PASSWORD`,而非通过 `docker-compose.yml` 的 `environment:` 节替换 `${MYSQL_PASSWORD}`。这是因为 compose 变量替换在 shell 中找不到该变量时会替换为空字符串,导致"Access denied (using password: NO)"。旧版部署如缺少这两个字段,`reset.sh` 和 `update.sh` 会自动补齐。 + +--- ## `config/xuqm.env` @@ -38,32 +47,19 @@ TENANT_BOOTSTRAP_ENABLED=true SDK 对外地址也在此文件配置: ```env -CONSOLE_DOMAIN=http://your-domain-or-ip -SDK_FILE_SERVICE_URL=http://your-domain-or-ip -SDK_IM_API_URL=http://your-domain-or-ip -SDK_IM_WS_URL=ws://your-domain-or-ip/ws/im +CONSOLE_DOMAIN=https://your-domain +SDK_FILE_SERVICE_URL=https://your-domain +SDK_IM_API_URL=https://your-domain +SDK_IM_WS_URL=wss://your-domain/ws/im ``` +--- + ## `config/tenant/bootstrap.env` 初始租户配置,权限 600。迁移模式下由迁移流程自动覆盖。 -## Spring Boot 数据库 URL 覆盖 - -`application.yml` 中数据库 URL 硬编码了生产地址。私有化部署**必须**通过 `docker-compose.yml` 的 `environment:` 节覆盖: - -```yaml -environment: - SPRING_DATASOURCE_URL: "jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-xuqm_private}?..." - SPRING_DATASOURCE_USERNAME: "${MYSQL_USERNAME:-xuqm}" - SPRING_DATASOURCE_PASSWORD: "${MYSQL_PASSWORD}" - SPRING_DATA_REDIS_HOST: "${REDIS_HOST}" - SPRING_DATA_REDIS_PORT: "${REDIS_PORT:-6379}" - SPRING_DATA_REDIS_PASSWORD: "${REDIS_PASSWORD}" - SPRING_DATA_REDIS_DATABASE: "${REDIS_DATABASE:-0}" -``` - -`docker-compose.yml` 已包含此配置,不需要手动修改。 +--- ## 服务端口 @@ -74,12 +70,30 @@ environment: | 11224 | tenant-service | 9001 | `/api/` `/actuator/` | | 11225 | file-service | 8086 | `/file/` | | 11226 | tenant-web | 80 | `/`(兜底路由) | -| 11227 | im-service | 8082 | `/api/im/` `/ws/im` | -| 11229 | update-service | 8084 | `/api/v1/updates/` `/api/v1/rn/` | -| 11230 | license-service | 8085 | `/api/license/` | -| 11231 | push-service | 8083 | 厂商回调(按需) | +| 11228 | im-service | 8082 | `/api/im/` `/ws/im` | +| 11229 | push-service | 8083 | 厂商回调(按需) | +| 11230 | update-service | 8084 | `/api/v1/updates/` `/api/v1/rn/` | +| 11231 | license-service | 8085 | `/api/license/` | -内置 nginx 绑定宿主机 `0.0.0.0:80`,上层直接 proxy_pass 到本机 IP 即可。11224–11231 绑定 `127.0.0.1`,仅用于直接调试。 +11224–11231 全部绑定 `127.0.0.1`,仅用于直接调试。正常流量走内置 nginx(80 / 11223)。 + +--- + +## Spring Boot 数据库 URL 覆盖 + +`application.yml` 中数据库 URL 硬编码了生产地址。私有化部署通过 `docker-compose.yml` 的 `environment:` 节覆盖连接信息(URL、用户名、Host、端口),密码通过 `config/secrets.env` 的 `env_file` 直接注入,两者不重叠: + +```yaml +environment: + SPRING_DATASOURCE_URL: "jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT:-3306}/..." + SPRING_DATASOURCE_USERNAME: "${MYSQL_USERNAME:-xuqm}" + SPRING_DATA_REDIS_HOST: "${REDIS_HOST}" + SPRING_DATA_REDIS_PORT: "${REDIS_PORT:-6379}" + SPRING_DATA_REDIS_DATABASE: "${REDIS_DATABASE:-0}" + # 密码不在此处设置,由 secrets.env env_file 注入,见上方说明 +``` + +--- ## 容器内部通信 @@ -94,11 +108,15 @@ PUSH_SERVICE_URL: "http://push-service:8083" SDK_TENANT_SERVICE_URL: "http://tenant-service:9001" ``` -`docker-compose.yml` 已包含上述配置,不需要手动修改。 +--- ## 内置 nginx -nginx 容器属于 `base` 必启服务,直接绑定宿主机 `0.0.0.0:80`,统一处理所有内部路由(按 Docker 服务名转发,无需关心各服务端口)。**宿主机无需额外 nginx 配置**,上层 nginx 直接 proxy_pass 指向本机 IP:80 即可,详见 [runbook.md](runbook.md#nginx-配置)。 +nginx 容器属于 `base` 必启服务,直接绑定宿主机 `0.0.0.0:80`,统一处理所有内部路由。配置文件为 `config/nginx/conf.d/xuqm.conf`,使用 `resolver 127.0.0.11 valid=10s` + 变量化 `proxy_pass`,**容器重建后 nginx 无需 reload**,自动通过 Docker DNS 获取新 IP。 + +详见 [runbook.md](runbook.md#nginx-配置)。 + +--- ## `config/vendors/` @@ -107,10 +125,14 @@ nginx 容器属于 `base` 必启服务,直接绑定宿主机 `0.0.0.0:80`, | `push.env` | 华为/小米/OPPO/vivo/荣耀/APNs/FCM 推送凭据,默认全部 `false` | | `store-submit.env` | 应用市场自动发布凭据,默认全部 `false` | +--- + ## `config/mail/smtp.env` 邮件服务配置,不填则邮件功能不可用,不影响其他服务启动。 +--- + ## `config/sdk/xuqm-private-sdk.json` -SDK 初始化配置,由 `deploy.sh` 自动生成,客户端应用使用此文件初始化 SDK,不再指向公有化地址。 +SDK 初始化配置,由 `deploy.sh` 自动生成,客户端应用使用此文件初始化 SDK,不再指向公有化地址。如地址错误可执行 `bash scripts/update.sh` 自动修正。 diff --git a/docs/deployment-defaults.md b/docs/deployment-defaults.md index f72101b..a94ba2e 100644 --- a/docs/deployment-defaults.md +++ b/docs/deployment-defaults.md @@ -43,10 +43,10 @@ | 11224 | tenant-service | 9001 | | 11225 | file-service | 8086 | | 11226 | tenant-web | 80 | -| 11227 | im-service | 8082 | -| 11229 | update-service | 8084 | -| 11230 | license-service | 8085 | -| 11231 | push-service | 8083 | +| 11228 | im-service | 8082 | +| 11229 | push-service | 8083 | +| 11230 | update-service | 8084 | +| 11231 | license-service | 8085 | 宿主机 nginx 配置参考见 [docs/runbook.md](runbook.md)。 @@ -79,6 +79,9 @@ bash scripts/verify.sh # 停止所有服务(保留数据) docker compose -f docker-compose.yml -f docker-compose.infra.yml down + +# 容器异常时重置(保留数据) +bash scripts/reset.sh ``` --- @@ -88,3 +91,4 @@ docker compose -f docker-compose.yml -f docker-compose.infra.yml down 1. **注册功能已禁用**:`TENANT_REGISTER_ENABLED=false`,只能用初始化的账号登录。 2. **数据持久化**:MySQL 和 Redis 数据存储在 `data/` 目录,容器重启不丢失。 3. **推送服务**:push-service 已启动,各厂商推送默认关闭,需填写 `config/vendors/push.env` 并重启。 +4. **WebSocket**:每一层 nginx 代理都必须透传 `Upgrade` / `Connection` 头,否则 IM 连接失败。 diff --git a/docs/runbook.md b/docs/runbook.md index 261bb2f..895d050 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -11,7 +11,7 @@ curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/mai 1. 选择租户初始化方式(新建 / 迁移) 2. 填写租户信息或迁移密钥 -3. 填写外部访问地址(宿主机 nginx 对外的地址,用于 SDK 配置) +3. 填写外部访问地址(上层 nginx 对外的域名/IP,用于 SDK 配置) 4. 自动完成容器启动、数据库初始化、全量验证 完成后按输出的端口表配置宿主机 nginx。 @@ -56,9 +56,9 @@ location / { } ``` -> **两种场景通用**:每一层 nginx 代理都必须加 `proxy_http_version 1.1` 和 `Upgrade`/`Connection` 头,缺少任意一层 IM WebSocket 就会断开。 +> **每一层 nginx 代理都必须加** `proxy_http_version 1.1` 和 `Upgrade`/`Connection` 头,缺少任意一层 IM WebSocket 就会断开。 -内置 nginx 路由配置在 `config/nginx/conf.d/xuqm.conf`,使用 Docker 服务名路由到各容器,无需关心具体端口。 +内置 nginx 路由配置在 `config/nginx/conf.d/xuqm.conf`,使用 Docker 服务名动态路由,容器重建后无需重载 nginx。 --- @@ -68,31 +68,44 @@ location / { |-----------|------| | **80** | 内置 nginx 入口(无宿主机 nginx 场景,`NGINX_BIND=80`) | | **11223** | 内置 nginx 入口(有宿主机 nginx 场景,`NGINX_BIND=127.0.0.1:11223`) | -| 11224–11231 | 各业务容器(绑定 127.0.0.1,调试用) | +| 11224–11231 | 各业务容器(绑定 127.0.0.1,仅调试用) | 各业务容器端口仅用于直接调试,正常流量全部走内置 nginx 入口。 --- -## 租户迁移 +## 日常运维 -迁移通过一键部署向导完成,选择「迁移租户」后: +### 升级 -1. 前往公有化平台控制台 → 安全中心 → 私有化部署迁移 → 生成迁移密钥 -2. 将 `pmk_` 开头的密钥粘贴进向导 -3. 脚本自动导出租户数据、导入私有库、写入 license 记录、重启服务 +```bash +curl -fsSL https://xuqinmin.com/xuqmGroup/XuqmGroup-PrivateDeploy/raw/branch/main/upgrade.sh \ + -o upgrade.sh && bash upgrade.sh +``` -迁移为单租户操作,会清空现有 bootstrap 数据后写入迁移数据。 +保留全部数据和配置,自动修复已知配置问题,可选拉取新镜像,完成后运行全量验证。 ---- +### 容器重置(保留数据) -## 常用运维命令 +容器状态异常时(502、crash-loop、密码错误)执行: + +```bash +bash scripts/reset.sh +``` + +彻底重建所有容器并拉取最新镜像,不删除数据卷,不修改配置文件。如只想重建容器而不拉取镜像: + +```bash +bash scripts/reset.sh --no-pull +``` + +### 常用命令 ```bash # 查看所有容器状态 docker compose -f docker-compose.yml -f docker-compose.infra.yml ps -# 查看服务日志 +# 查看服务日志(替换 tenant-service 为目标服务名) docker compose -f docker-compose.yml -f docker-compose.infra.yml logs --tail 100 tenant-service # 重新运行全量验证 @@ -116,6 +129,18 @@ bash scripts/restore.sh --- +## 租户迁移 + +迁移通过一键部署向导完成,选择「迁移租户」后: + +1. 前往公有化平台控制台 → 安全中心 → 私有化部署迁移 → 生成迁移密钥 +2. 将 `pmk_` 开头的密钥粘贴进向导 +3. 脚本自动导出租户数据、导入私有库、写入 license 记录、重启服务 + +迁移为单租户操作,会清空现有 bootstrap 数据后写入迁移数据。 + +--- + ## 数据目录 | 路径 | 说明 | @@ -129,12 +154,75 @@ bash scripts/restore.sh --- -## 重新部署(幂等) +## 故障排查 -脚本幂等,可重复执行: +### nginx 返回 502 + +**现象**:所有接口返回 502,容器状态正常。 +**原因**:nginx 缓存了旧容器 IP,容器重建后 IP 变更未同步。 +**处理**: ```bash -bash install.sh +bash scripts/reset.sh --no-pull ``` -已运行的容器不会被重建,数据目录不受影响。 +内置 nginx 配置已使用 Docker DNS 动态解析(`resolver 127.0.0.11 valid=10s`),重置后新容器 IP 会自动生效,无需手动 reload。 + +--- + +### 服务 crash-loop / 启动失败 + +**现象**:`docker compose ps` 显示服务 `Restarting`,日志反复出现启动异常。 +**处理**:先查日志确认原因: + +```bash +docker compose -f docker-compose.yml -f docker-compose.infra.yml logs --tail 50 <服务名> +``` + +常见原因及处理: + +| 日志关键字 | 原因 | 处理 | +|-----------|------|------| +| `Access denied ... (using password: NO)` | secrets.env 缺少 SPRING_DATASOURCE_PASSWORD | 执行 `bash scripts/reset.sh`,脚本会自动补齐 | +| `Communications link failure` | MySQL 尚未就绪 | 等待约 30s 后自动恢复,或执行 reset | +| `NOAUTH Authentication required` | secrets.env 缺少 SPRING_DATA_REDIS_PASSWORD | 执行 `bash scripts/reset.sh` | +| `host not found in upstream` | nginx 启动时 DNS 解析失败 | 确认其他容器正常启动后执行 reset | + +--- + +### IM WebSocket 连接失败(426 / 502) + +**现象**:浏览器控制台 WebSocket 握手返回 426 或 502。 +**排查顺序**: + +1. 确认每一层 nginx 都有 `proxy_http_version 1.1` + `Upgrade`/`Connection` 头 +2. 确认 im-service 容器正常运行:`docker compose ps im-service` +3. 确认镜像版本包含私有化 CORS 修复(`DEPLOYMENT_MODE=PRIVATE` 放开所有 Origin) + +```bash +# 检查 im-service 是否健康 +curl -s http://127.0.0.1:11228/actuator/health +``` + +--- + +### 升级后镜像未生效 + +**现象**:更新了镜像但服务行为未变化。 +**原因**:`docker compose restart` 不会应用新镜像,需 `up -d` 触发重建。 +**处理**: + +```bash +bash scripts/reset.sh # 拉取最新镜像并重建所有容器 +``` + +--- + +### SDK 地址仍指向公有化平台 + +**现象**:`config/sdk/xuqm-private-sdk.json` 或 `config/xuqm.env` 中仍有 `xuqinmin.com` 地址。 +**处理**: + +```bash +bash scripts/update.sh # 自动检测并修正 SDK URL +```