# Docker 部署方案(寄卖商城 + 积分商城前后端) > 本方案覆盖: > - **integral-resell(积分商城)**:Webman PHP 后端二进制 + H5 静态站 > - **single-shop-22(寄卖商城)**:CRMEB Spring Boot 双 jar 后端 + Vue 管理后台 + uni-app H5 > - **Redis**:每个子栈独立容器(两套互不干扰) > > 不包含:`MER-2.2_2601` 多商户、Kafka 同步、MySQL(使用 **阿里云 RDS for MySQL**)。 --- ## 两步独立部署(推荐) 从 `czleilei240` 分支开始,两个商城**各自独立**部署,互不依赖。 | | 步骤一 寄卖商城 | 步骤二 积分商城 | |---|---|---| | 项目 | `integral-resell` | `single-shop-22` | | 目录 | `deploy/docker/step1-integral/` | `deploy/docker/step2-single-shop/` | | 服务 | redis · integral-houtai · integral-h5 | redis · single-admin-api · single-front-api · single-admin-web · single-h5 | | 宿主机端口 | `18080` | `18081`(管理后台) `18082`(H5) | | compose name | `resell-czleilei240` | `jifenmall-czleilei240` | | 详细说明 | [README](deploy/docker/step1-integral/README.md) | [README](deploy/docker/step2-single-shop/README.md) | ### 步骤一 — 寄卖商城(integral-resell) ```bash cd deploy/docker/step1-integral cp .env.example .env && cp houtai.env.example houtai.env # 填写 .env 中的 REDIS_PASSWORD # 填写 houtai.env 中的 DB_PASSWORD 和 REDIS_PASSWORD(两者须一致) docker compose --env-file .env up -d --build ``` ### 步骤二 — 积分商城(single-shop-22) ```bash cd deploy/docker/step2-single-shop cp .env.example .env # 填写 RDS_PASSWORD、REDIS_PASSWORD docker compose --env-file .env up -d --build # Java 首次构建约 10-20 分钟,可用 logs -f 观察进度 ``` > 下方章节描述的是历史上合并部署时的 `docker-compose.yml`(`deploy/docker/docker-compose.yml`),供参考。两步拆分方案已取代合并方案。 --- ## 一、架构总览 ```mermaid flowchart LR user((用户)) -->|H5/管理后台| EdgeLB[(阿里云 SLB / Nginx)] subgraph Host [Docker 主机] direction TB subgraph integralStack [积分商城] integral_h5[integral-h5\nNginx :80] integral_houtai[integral-houtai\nwebman.bin :8787] end subgraph singleStack [寄卖商城] single_admin_web[single-admin-web\nNginx :80] single_h5[single-h5\nNginx :80] single_admin_api[single-admin-api\nminiao-admin :30032] single_front_api[single-front-api\nminiao-front :30031] end redis[(redis :6379)] end EdgeLB -- jf-h5.* --> single_h5 EdgeLB -- jfadmin.* --> single_admin_web EdgeLB -- admin.* --> integral_h5 integral_h5 -- /api/ --> integral_houtai single_admin_web -- /api/ --> single_admin_api single_h5 -- /api/ --> single_front_api integral_houtai --> redis single_admin_api --> redis single_front_api --> redis integral_houtai --> RDS[(阿里云 RDS MySQL)] single_admin_api --> RDS single_front_api --> RDS ``` ### 服务清单 | 服务 | 镜像构建来源 | 容器内端口 | 默认宿主机端口 | 说明 | |------|--------------|------------|----------------|------| | `redis` | 官方 `redis:6.2-alpine` | 6379 | 6379 (可不暴露) | 持久化 AOF,挂卷 `redis-data` | | `integral-houtai` | `integral-resell/houtai/` | 8787 | (不暴露) | Webman 静态 ELF;同容器写 `runtime/` 与 `public/upload/` | | `integral-h5` | `integral-resell/h5/` | 80 | 18080 | 服务静态站;`/api/` 反代到 `integral-houtai`;启动时根据 env 重写 `static/configs.js` | | `single-admin-api` | `single-shop-22/backend/crmeb-admin` | 30032 | (不暴露) | 多阶段 Maven 构建出 `miao-admin-2.2.jar` | | `single-front-api` | `single-shop-22/backend/crmeb-front` | 30031 | (不暴露) | 多阶段 Maven 构建出 `miao-front-2.2.jar` | | `single-admin-web` | `single-shop-22/backend-adminend` | 80 | 18081 | 多阶段 Node 构建 `dist`;`/api/` 反代到 `single-admin-api` | | `single-h5` | `single-shop-22/single_uniapp22miao` | 80 | 18082 | 多阶段 Node 构建 `unpackage/dist/build/h5`;`/api/` 反代到 `single-front-api` | > **域名/端口策略**:建议在 docker 主机前再放一层 Nginx 或阿里云 SLB,按域名分发到 18080/18081/18082;下面的 compose 默认把这三个静态站暴露在宿主机不同端口。 ### 共享资源 / 卷 | 卷 / 目录 | 容器内挂载点 | 用途 | |-----------|-------------|------| | `redis-data` | `/data` | Redis AOF 持久化 | | `integral-runtime` | `/app/runtime` | Webman 运行时(session、views 缓存等) | | `integral-upload` | `/app/public/upload` | 积分商城上传图片 | | `single-images` | `/usr/local/crmeb/crmebimage` | 寄卖商城 `imagePath`,admin/front 两个 jar **共享** | | `single-logs` | `/app/log` | Spring Boot 日志(按需保留) | ### 与外部资源的关系 | 资源 | 地址来源 | 说明 | |------|---------|------| | 阿里云 RDS MySQL | `.env` 中 `RDS_*` | RDS 白名单需放通宿主机出口 IP;建议同地域 VPC | | OSS(可选) | `.env` 中 `OSS_*` | 不配置时积分商城走本地 `public/upload`;寄卖商城用 admin 后台界面里配置 | | 短信(可选) | `.env` 中 `SMS_*` | 仅积分商城 `.env` 用到 | --- ## 二、目录布局 ``` integral-shop/ ├── DOCKER_DEPLOY.md ← 本文档 ├── deploy/ │ └── docker/ │ ├── docker-compose.yml │ ├── .env.example │ ├── README.md ← 一键启动说明(简版) │ ├── redis/ │ │ └── redis.conf │ ├── integral-resell/ │ │ ├── houtai.Dockerfile │ │ ├── h5.Dockerfile │ │ ├── nginx-h5.conf │ │ ├── .env.template ← Webman 后端运行时 .env 模板 │ │ └── docker-entrypoint-h5.sh ← 启动时根据 env 改写 configs.js │ └── single-shop/ │ ├── admin-api.Dockerfile │ ├── front-api.Dockerfile │ ├── admin-web.Dockerfile │ ├── h5.Dockerfile │ ├── application-docker.yml ← Spring Boot docker profile │ ├── nginx-admin-web.conf │ └── nginx-h5.conf ├── integral-resell/ ← 源码(不改动) └── single-shop-22/ ← 源码(不改动) ``` > 所有 Dockerfile / compose 都**只读访问**源码目录,不会修改它们。 --- ## 三、配置与环境变量 复制一份 `deploy/docker/.env.example` 为 `deploy/docker/.env` 并按下面表格修改。 | 变量 | 用途 | 示例 | |------|------|------| | `TZ` | 容器时区 | `Asia/Shanghai` | | `RDS_HOST` | 阿里云 RDS 外网/内网地址 | `rm-bp1a178eq62lxba9xbo.mysql.rds.aliyuncs.com` | | `RDS_PORT` | RDS 端口 | `3306` | | `RDS_INTEGRAL_DB` | 积分商城数据库 | `yangtangyoupin` | | `RDS_INTEGRAL_USER` / `RDS_INTEGRAL_PASS` | 积分商城账号 | 默认 `yangtangyoupin` / 来自原 sxsy80 | | `RDS_SINGLE_DB` | 寄卖商城数据库(与积分共库) | `yangtangyoupin` | | `RDS_SINGLE_USER` / `RDS_SINGLE_PASS` | 寄卖商城账号 | 同上 | | `REDIS_PASSWORD` | 容器 Redis 密码 | 123456 | | `REDIS_INTEGRAL_DB` | 积分商城使用的 Redis db | `0` | | `REDIS_SINGLE_ADMIN_DB` | 寄卖 admin Redis db | `25` | | `REDIS_SINGLE_FRONT_DB` | 寄卖 front Redis db | `26` | | `INTEGRAL_API_PUBLIC_URL` | H5 调用后端的对外 URL | `https://admin.example.com` | | `INTEGRAL_IMG_PUBLIC_URL` | H5 引用图片的对外 URL | `https://admin.example.com` | | `INTEGRAL_H5_PUBLIC_URL` | H5 自身对外 URL | `https://h5.example.com/` | | `INTEGRAL_SN_ID` | `configs.js` 中 `sn_id` | `17533260260405` | | `INTEGRAL_APP_STR` | `configs.js` 中 `appStr`(**与寄卖 APP_SECRET 必须一致**) | `ZFyTNQTWEkCBczKzyUDJWE9Ecx260405` | | `SINGLE_ADMIN_BASE_API` | 寄卖管理后台调用 API 的域名/路径 | 留空走同域 `/api/` 即可 | | `SINGLE_H5_DOMAIN` | uni-app H5 调用 API 的域名 | 留空走同域 `/api/` 即可 | | `SYNC_SOURCE_ID` / `SYNC_TARGET_MER_ID` | 多商户同步配置(无 MER 时留空) | `""` / `0` | > **关键一致性**:积分商城 `configs.js#appStr` ↔ 积分商城 Webman `.env#APP_SECRET` ↔ 寄卖商城 admin 后台中的 `appStr` 必须一致;否则双向调用会鉴权失败。 --- ## 四、镜像构建策略 ### 0. 基础镜像选型(与主机对齐) | 服务 | 基础镜像 | libc | 选择理由 | |------|----------|------|----------| | `redis` | `redis:6.2-alpine` | musl | Redis 官方推荐,无 native 依赖 | | `integral-houtai` | `php:8.0-cli-bullseye` (Debian 11) | **glibc** | 与宝塔 PHP 8.0.26 版本对齐;已装 pdo_mysql / redis / gd / imagick 等 webman 常用扩展 | | `integral-h5` | `nginx:1.25-alpine` | musl | 纯静态文件 + nginx,无字体/native 依赖 | | `single-admin-api` / `single-front-api` 构建 | `maven:3.8.8-eclipse-temurin-17` (Debian) | **glibc** | 与部署环境 OpenJDK 17.0.x 对齐;pom.xml source/target=1.8,Java 17 编译器向下兼容 | | `single-admin-api` / `single-front-api` 运行 | `eclipse-temurin:17-jre-jammy` (Ubuntu 22.04) | **glibc** | 匹配主机 openjdk 17.0.18;Spring Boot 2.2.6 + Java 17 需额外 `--add-opens` JVM 参数(已写入 JAVA_OPTS);预装 `fonts-dejavu` + `fonts-wqy-zenhei` | | `single-admin-web` / `single-h5` 构建 | `node:16-alpine` | musl | 仅打包 JS/CSS,无 native 依赖 | | `single-admin-web` / `single-h5` 运行 | `nginx:1.25-alpine` | musl | 同上 | > **主机环境**:Debian-class(Linux kernel 6.1.164)/ x86_64 / Docker + BuildKit。 > 所有 `--platform=linux/amd64` 在主机上都是原生执行,**无 QEMU 模拟**;只有在 Apple Silicon 上本地 build 时才会走 QEMU。 > Java 后端最终镜像在 glibc 上运行,与主机 OS 完全同源;这样可避免 Alpine + musl 时验证码 / 中文字体 / POI 个别场景的兼容性问题。 ### 1. integral-houtai(PHP Webman 二进制) - 基础镜像:`debian:bookworm-slim`(webman.bin 为 ELF x86_64,静态链接,glibc 兼容)。 - 直接拷贝 `webman.bin`、`public/`、`runtime/views` 模板进镜像。 - `.env` **不打进镜像**,通过 `docker-compose` 把 `deploy/docker/integral-resell/.env.template` 渲染后挂进 `/app/.env`。 - 启动命令:`./webman.bin start -d`(业主指定的守护模式),entrypoint 在后台 `tail -F runtime/logs/*.log` 把日志接到容器 stdout,并监听主进程退出。收到 `SIGTERM` 时执行 `./webman.bin stop` 优雅退出。 ### 2. integral-h5(静态站) - 基础镜像:`nginx:1.25-alpine`。 - 直接拷贝 `integral-resell/h5/` 到 `/usr/share/nginx/html/`。 - `docker-entrypoint-h5.sh` 在启动时根据环境变量重写 `static/configs.js`:把 `BASE_URL/IMG_URL/H5_URL/sn_id/appStr` 替换为部署值,**无需重新构建前端**。 ### 3. single-admin-api / single-front-api(Spring Boot) - **多阶段构建**: - 构建阶段:`maven:3.8.8-eclipse-temurin-17` → `mvn package -pl crmeb-admin -am -DskipTests`(front 同理)。pom.xml 已配置 `source/target=1.8`,Java 17 编译器向下兼容,产物字节码级别不变。 - 运行阶段:`eclipse-temurin:17-jre-jammy`(Ubuntu 22.04 / glibc),与部署环境 `openjdk 17.0.18` 对齐;预装 `fontconfig + fonts-dejavu + fonts-wqy-zenhei`。 - **Java 17 兼容性**:Spring Boot 2.2.6 使用 cglib、Druid、Quartz 等大量反射,Java 17 强模块封装会报 `InaccessibleObjectException`。已在 `JAVA_OPTS` 中加入 7 条 `--add-opens`,覆盖已知触发点(`java.lang / reflect / util / io / math / sun.net.util / net`)。若运行时出现新的 `InaccessibleObjectException`,按报错包名继续追加 `--add-opens`。 - 通过 `--spring.profiles.active=docker` + `--spring.config.additional-location=file:/config/` 加载 `application-docker.yml`,所有 DB/Redis/端口参数从 `.env` 注入。 - `imagePath` 指向 `/usr/local/crmeb/crmebimage/`(挂卷 `single-images`,两个 jar 共享)。 ### 4. single-admin-web(Vue 管理后台) - **多阶段构建**: - 构建阶段:`node:16-alpine` → `npm ci && npm run build:prod`(`VUE_APP_BASE_API` 设为空,走同域 `/api/`)。 - 运行阶段:`nginx:1.25-alpine`,`/api/` 反代到 `single-admin-api:30032`。 - 若想**直接复用源码里现成的 `dist/`**,可改用「fast」分支(见 Dockerfile 注释)。 ### 5. single-h5(uni-app H5) - 同样多阶段:`node:16-alpine` → `npm install && npm run build:h5`,再 nginx 服务。 - `config/app.js` 里 `domain` 硬编码,需在构建前注入:通过 ARG `H5_API_DOMAIN` 做字符串替换,或留空让同域生效(推荐)。 --- ## 五、Spring Boot 接 RDS / Redis 的细节 寄卖商城原本通过 `spring.profiles.active=byjyw149` / `miao80` 等加载不同 yml。**Docker 部署用全新 profile `docker`**: ```yaml # deploy/docker/single-shop/application-docker.yml spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://${MYSQL_HOST}:${MYSQL_PORT:3306}/${MYSQL_DATABASE}?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false username: ${MYSQL_USERNAME} password: ${MYSQL_PASSWORD} redis: host: ${REDIS_HOST} port: ${REDIS_PORT:6379} password: ${REDIS_PASSWORD:} database: ${REDIS_DATABASE:0} ``` 通过 docker-compose 注入: ``` MYSQL_HOST=${RDS_HOST} MYSQL_DATABASE=${RDS_SINGLE_DB} MYSQL_USERNAME=${RDS_SINGLE_USER} MYSQL_PASSWORD=${RDS_SINGLE_PASS} REDIS_HOST=redis REDIS_PASSWORD=${REDIS_PASSWORD} REDIS_DATABASE=${REDIS_SINGLE_ADMIN_DB} ``` > **数据库初始化**:首次部署前请用 `db/` 下的 SQL(推荐 `db/yangtangyoupin.sql`、`db/shop22-v2.sql` 等)在 RDS 中初始化对应库;本方案不在容器中建库,**避免误覆盖现网数据**。 --- ## 六、首次部署步骤 ```bash # 1. 准备环境变量 cd integral-shop/deploy/docker cp .env.example .env vim .env # 填入 RDS / Redis / 域名等 # 2. 准备积分商城 Webman .env(容器运行时挂载) cp integral-resell/.env.template integral-resell/.env vim integral-resell/.env # 至少配置 DB_*、APP_SECRET、SMS_* # 3. 在阿里云 RDS 上提前导入数据 # - 积分商城:db/yangtangyoupin.sql # - 寄卖商城:db/shop22-v2.sql / db/jjy153-mysql.sql (按实际版本) # 4. 构建并启动 docker compose --env-file .env build docker compose --env-file .env up -d # 5. 健康检查 docker compose ps docker compose logs -f single-admin-api # 看到 "Started CrmebAdminApplication" 即成功 docker compose logs -f integral-houtai # 看到 "Webman start success" 即成功 curl -I http://localhost:18080 # 积分商城 H5 curl -I http://localhost:18081 # 寄卖管理后台 curl -I http://localhost:18082 # 寄卖用户 H5 ``` --- ## 七、日常运维 | 场景 | 操作 | |------|------| | 拉取最新源码后重建 | `docker compose build --no-cache ` 然后 `docker compose up -d ` | | 只重启某服务 | `docker compose restart single-admin-api` | | 看日志 | `docker compose logs -f --tail 200 single-front-api` | | 进入容器 | `docker compose exec single-admin-api sh` | | 备份用户上传 | `docker run --rm -v integral-upload:/d -v $(pwd):/b alpine tar czf /b/upload.tgz -C /d .` | | 备份寄卖商城图片 | 同上,卷名 `single-images` | | 切换到外部 Redis | 在 `.env` 把 `REDIS_HOST` 改成外部地址,并在 compose 里把 `redis` 服务注释 | | 切换到 Java 17 运行寄卖商城 | 把 `eclipse-temurin:8-jre-alpine` 改成 `17-jre`;本工程经验依旧建议 8 | --- ## 八、性能与资源建议 | 容器 | CPU | 内存(建议) | |------|----:|------------:| | `redis` | 0.2 | 256 MB | | `integral-houtai` | 0.5–1 | 256–512 MB(webman 多 worker 时 ↑) | | `integral-h5` | 0.1 | 64 MB | | `single-admin-api` | 1 | **JVM `-Xmx512m`,容器 limit 768 MB** | | `single-front-api` | 1 | JVM `-Xmx768m`,容器 limit 1 GB | | `single-admin-web` | 0.1 | 64 MB | | `single-h5` | 0.1 | 64 MB | 最低配宿主机:**2 vCPU / 4 GB**(建议 4 vCPU / 8 GB 起)。 --- ## 九、上线检查清单 - [ ] 阿里云 RDS 白名单已放通 Docker 主机出口 IP - [ ] RDS 中已导入对应库的 SQL,账号/密码权限正确 - [ ] `.env` 中无明文敏感信息提交到 git(已 `.gitignore`) - [ ] `INTEGRAL_APP_STR` 与寄卖商城 admin 后台 + Webman `.env#APP_SECRET` 三处一致 - [ ] 域名 / SSL 在外层 Nginx 或阿里云 SLB 完成 443 卸载 - [ ] OSS 与 SMS 等第三方密钥若启用,已写入对应位置 - [ ] 备份策略:RDS 自动备份 + `integral-upload` / `single-images` 卷定期 tar 备份 --- ## 十、远端 116.62.83.240 一键部署 服务器已具备的环境(来自宝塔软件商店截图): - Docker(节点管理 → Docker 已部署) - Nginx 1.28.1(**未使用**,本方案的 Nginx 在容器内) - MySQL 5.7.44(**未使用**,本方案走阿里云 RDS) - Redis 8.0.5(**默认未使用**,容器内自带 Redis;想复用宿主 Redis 见下方"复用宿主 Redis") - PHP 8.0.26(**未使用**,webman 走静态二进制) - rsync 已安装、SSH `root / A@123456` ### 1. 本机一次性配置 ```bash cd deploy/docker/scripts cp server.env.example server.env $EDITOR server.env # 默认已写好 116.62.83.240 / root / A@123456 ``` > **强烈建议先用 SSH key 替代密码**:`ssh-copy-id root@116.62.83.240` 后把 `server.env` 里的 `SSHPASS=` 注释掉即可。脚本会自动切回 SSH key 通道。 ### 2. 全量同步并启动 ```bash ./sync-to-server.sh up ``` 脚本流程: 1. SSH 登录 116.62.83.240,确保 `/root/integral-shop` 目录存在; 2. rsync 增量同步整个工程(自动排除 `node_modules / target / .git / runtime/logs / MER-2.2_2601` 等大目录); 3. 在远端 `cd /root/integral-shop/deploy/docker && docker compose build && docker compose up -d`; 4. 输出 `docker compose ps` 结果。 首次会要求你先在远端把 `.env / integral-resell/.env` 改成真实值(如未做,运行下面这一步): ```bash ./bootstrap-remote-env.sh # 在远端基于模板创建 .env,再 ssh 上去填写 ``` ### 3. 日常运维(无需登录服务器) ```bash ./remote-up.sh ps # 服务状态 ./remote-up.sh logs single-admin-api # 跟随日志 ./remote-up.sh restart single-front-api # 重启某服务 ./remote-up.sh build single-admin-web # 只重建某镜像 ./remote-up.sh exec single-admin-api sh # 进容器 ./remote-up.sh ssh # 直接登录服务器 ./sync-to-server.sh up # 改了代码后再同步+重启 ``` ### 4. 端口规划与宝塔放行 宿主机暴露端口(来自 `.env.example`,可改): | 端口 | 服务 | 用途 | |------|------|------| | 18080 | `integral-h5` | 积分商城 H5 | | 18081 | `single-admin-web` | 寄卖管理后台 | | 18082 | `single-h5` | 寄卖用户 H5 | | 6379 | `redis` | (生产建议关闭)`REDIS_HOST_PORT=` 留空即不暴露 | **安全组 / 防火墙**:阿里云安全组放通 18080-18082;宝塔面板 → 安全 → 放行同样端口。**强烈建议在前面再挂一层域名 + SSL**(宝塔自带 Nginx 反代 / 阿里云 SLB / Cloudflare 都行)。 ### 5. 复用宿主已有 Redis 8.0.5(可选) 若想节省一份 Redis,直接用宿主机的 Redis: ```bash # 1) 远端编辑 deploy/docker/.env REDIS_PASSWORD=宿主-Redis-的密码 # 2) 远端编辑 deploy/docker/docker-compose.yml # a. 注释掉 redis: 整个服务块(包括 healthcheck) # b. single-admin-api / single-front-api 把 REDIS_HOST: redis 改成 172.17.0.1 # (docker bridge 网关;宝塔默认端口 6379;先确保 Redis 监听 0.0.0.0 且防火墙放行 6379 to docker subnet) # c. integral-resell/.env 把 REDIS_HOST=redis 同步改为 172.17.0.1 # 3) 同步重启 ./sync-to-server.sh up ``` ### 6. 阿里云 RDS 白名单 到 RDS 控制台 → 白名单 → 加入 `116.62.83.240`(如果 RDS 与 ECS 同 VPC 则用内网地址 + 内网白名单更优)。 ### 7. 数据库初始化 ```bash # 本地把 SQL 上传到服务器(默认 db/ 已被 rsync 同步过去) ./remote-up.sh ssh # 远端 mysql -h -u -p < /root/integral-shop/db/yangtangyoupin.sql mysql -h -u -p < /root/integral-shop/db/shop22-v2.sql ``` 也可以用宝塔自带的 phpMyAdmin 直接连 RDS 导入。 --- ## 十一、各文件清单一览 参见 `deploy/docker/` 下: - `docker-compose.yml` ← 编排入口 - `.env.example` ← 环境变量模板 - `redis/redis.conf` - `integral-resell/houtai.Dockerfile` - `integral-resell/h5.Dockerfile` - `integral-resell/nginx-h5.conf` - `integral-resell/.env.template` - `integral-resell/docker-entrypoint-h5.sh` - `single-shop/admin-api.Dockerfile` - `single-shop/front-api.Dockerfile` - `single-shop/admin-web.Dockerfile` - `single-shop/h5.Dockerfile` - `single-shop/application-docker.yml` - `single-shop/nginx-admin-web.conf` - `single-shop/nginx-h5.conf` - `scripts/server.env.example` ← 远端 SSH 配置模板 - `scripts/sync-to-server.sh` ← rsync 增量同步 + 触发远端部署 - `scripts/remote-up.sh` ← 远端 `docker compose` 操作(up/build/restart/logs/ps/exec/ssh) - `scripts/bootstrap-remote-env.sh` ← 首次在远端生成 .env 模板 每个文件都已生成可直接使用的版本。