@wdyy/skills 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/.well-known/skills/index.json +2 -2
  2. package/.well-known/skills/wdyy-deployment-standard/SKILL.md +33 -44
  3. package/.well-known/skills/wdyy-deployment-standard/agents/openai.yaml +2 -2
  4. package/.well-known/skills/wdyy-deployment-standard/reference/linux-deployment-rules.md +27 -63
  5. package/.well-known/skills/wdyy-deployment-standard/scripts/generate-deployment-files.mjs +19 -9
  6. package/.well-known/skills/wdyy-deployment-standard/scripts/generate-deployment-files.test.mjs +16 -2
  7. package/.well-known/skills/wdyy-deployment-standard/scripts/validate-deployment-package.mjs +61 -123
  8. package/.well-known/skills/wdyy-deployment-standard/scripts/validate-deployment-package.test.mjs +145 -347
  9. package/.well-known/skills/wdyy-deployment-standard/templates/deploy.sh.template +184 -675
  10. package/.well-known/skills/wdyy-deployment-standard/templates/docker-compose.yml +35 -0
  11. package/.well-known/skills/wdyy-deployment-standard/templates/env.example.template +0 -7
  12. package/.well-known/skills/wdyy-deployment-standard/templates/frontend-container.conf.template +2 -2
  13. package/.well-known/skills/wdyy-logging-standard/SKILL.md +2 -2
  14. package/.well-known/skills/wdyy-logging-standard/agents/openai.yaml +1 -1
  15. package/.well-known/skills/wdyy-logging-standard/reference/logging-rules.md +2 -2
  16. package/.well-known/skills/wdyy-logging-standard/scripts/validate-log-entry.test.mjs +1 -1
  17. package/README.md +4 -2
  18. package/lib/wdyy-cli.js +3 -2
  19. package/package.json +1 -1
  20. package/.well-known/skills/wdyy-deployment-standard/templates/docker-compose.blue-green.yml +0 -70
  21. package/.well-known/skills/wdyy-deployment-standard/templates/nginx-upstream.template.conf +0 -26
@@ -7,8 +7,8 @@
7
7
  },
8
8
  {
9
9
  "name": "wdyy-deployment-standard",
10
- "description": "基于标准五段根 .env,为无 CI/CD 的多项目 Linux 服务器生成确定性的前后端双 Docker 发布配置、本地 deploy.sh build 完整打包、人工上传及按独立端口执行蓝绿启动、替换、重启、停止、删除和回滚。Use when 规划、实现、审查或执行 `/xxzx/home/项目目录` 下的手工生产部署时。",
11
- "files": ["SKILL.md", "agents/openai.yaml", "reference/linux-deployment-rules.md", "scripts/generate-deployment-files.mjs", "scripts/generate-deployment-files.test.mjs", "scripts/validate-deployment-package.mjs", "scripts/validate-deployment-package.test.mjs", "templates/Dockerfile.template", "templates/frontend.Dockerfile.template", "templates/frontend-container.conf.template", "templates/env.example.template", "templates/deploy.sh.template", "templates/docker-compose.blue-green.yml", "templates/dockerignore.template", "templates/nginx-upstream.template.conf"]
10
+ "description": "基于标准五段根 .env,为无 CI/CD 项目生成确定性的前后端带版本双 Docker 镜像、本地 deploy.sh build 导出包、人工上传,以及服务器直接宿主机端口的交互式运行、替换和停止;不使用蓝绿部署、宿主机 Nginx 或数据库迁移。Use when 规划、实现、审查或执行手工生产部署时。",
11
+ "files": ["SKILL.md", "agents/openai.yaml", "reference/linux-deployment-rules.md", "scripts/generate-deployment-files.mjs", "scripts/generate-deployment-files.test.mjs", "scripts/validate-deployment-package.mjs", "scripts/validate-deployment-package.test.mjs", "templates/Dockerfile.template", "templates/frontend.Dockerfile.template", "templates/frontend-container.conf.template", "templates/env.example.template", "templates/deploy.sh.template", "templates/docker-compose.yml", "templates/dockerignore.template"]
12
12
  },
13
13
  {
14
14
  "name": "wdyy-api-standard",
@@ -1,74 +1,63 @@
1
1
  ---
2
2
  name: wdyy-deployment-standard
3
- description: 基于标准五段根 .env,为无 CI/CD 的多项目 Linux 服务器生成确定性的前后端双 Docker 发布配置、本地 deploy.sh build 完整打包、人工上传及按独立端口执行蓝绿启动、替换、重启、停止、删除和回滚。Use when 规划、实现、审查或执行 `/xxzx/home/项目目录` 下的手工生产部署时。
3
+ description: 基于标准五段根 .env,为无 CI/CD 项目生成确定性的前后端带版本双 Docker 镜像、本地 deploy.sh build 导出包、人工上传,以及服务器直接宿主机端口的交互式运行、替换和停止;不使用蓝绿部署、宿主机 Nginx 或数据库迁移。Use when 规划、实现、审查或执行手工生产部署时。
4
4
  ---
5
5
 
6
6
  # 企业部署规范
7
7
 
8
8
  ## Overview
9
9
 
10
- 把开发、构建、上传和服务器操作分开:开发只用 `pnpm dev`;本地用 `./deploy.sh build` 生成前后端双镜像和完整 `deploy/`;工程师人工上传;服务器按根 `.env` 的项目端口执行成对蓝绿切流。
10
+ 把开发、构建、上传和服务器操作分开:开发只用 `pnpm dev`;本地项目根目录用 `./deploy.sh build` 生成带版本号的前后端镜像和完整 `deploy/`;工程师人工上传;服务器项目根目录无参数运行 `./deploy.sh`,按菜单直接运行或替换固定服务。
11
11
 
12
12
  ## 使用边界
13
13
 
14
- 1. 先读取目标项目真实的 pnpm scripts、前后端构建输出、健康/版本接口、Dockerfile、`.env`Nginx include 方式。
15
- 2. 完整读取 [Linux 部署规则](reference/linux-deployment-rules.md),运行 `node scripts/generate-deployment-files.mjs --target <目标项目根目录> --write` 确定性生成受版本控制文件,并向 `.env` 与 `.env.example` 的唯一“部署”段追加同名空配置;既有文件不一致时先审查差异,只有工程师确认后才使用 `--force`。
16
- 3. 应用 URL 与端口只使用 `FRONTEND_URL`、`FRONTEND_PORT`、`BACKEND_URL`、`BACKEND_PORT`;部署端点使用各自唯一变量。健康和版本 URL 在脚本中由主机、端口及固定路径构造,不在 `.env` 中重复配置。
17
- 4. 工程师人工上传 `deploy/` 全部内容;脚本不得上传、拉取代码、安装依赖、修改共享 Nginx 主配置或替代 GO/NO-GO
14
+ 1. 先读取目标项目真实的 pnpm scripts、前后端构建输出、健康/版本接口、Dockerfile 和 `.env`。
15
+ 2. 完整读取 [Linux 部署规则](reference/linux-deployment-rules.md),运行 `node scripts/generate-deployment-files.mjs --target <目标项目根目录> --write` 确定性生成受版本控制文件,并向 `.env` 与 `.env.example` 的唯一“部署”段追加同名配置;既有文件不一致时先审查差异,只有工程师确认后才使用 `--force`。
16
+ 3. 应用监听和宿主机直接暴露端口只使用 `FRONTEND_URL`、`FRONTEND_PORT`、`BACKEND_URL`、`BACKEND_PORT`;不得增加重复容器端口或健康 URL 配置。
17
+ 4. 工程师人工上传 `deploy/` 全部内容作为服务器项目根目录;脚本不得上传、拉取代码、安装依赖或修改宿主机 Nginx。
18
18
  5. 生成后先运行生成器的 `--check`,再运行 `scripts/validate-deployment-package.mjs` 和全部行为测试。
19
19
 
20
20
  ## 固定契约
21
21
 
22
- - 项目名只允许小写字母、数字和下划线,并与服务器目录 `/xxzx/home/<项目名称>` 对应。
23
- - 两个镜像必须精确命名为 `<项目名称>_frontend:<YYYYMMDD-NNN>` 和 `<项目名称>_backend:<YYYYMMDD-NNN>`。
24
- - Compose 必须包含 frontend-blue、backend-blue、frontend-green、backend-green;同一颜色的前后端作为一个切流单元。
25
- - 宿主机 Nginx 只监听根 `.env` 的 `PROJECT_HTTP_PORT`,`/api/` 代理活动后端,其他请求代理活动前端。
26
- - 所有容器只把项目根 `./logs` 挂载到 `/app/logs`;日志文件直接位于 `logs/`,不得创建服务、颜色或日期子目录。
22
+ - 项目名只允许小写字母、数字和下划线;服务器部署根由上传后的 `deploy.sh` 物理路径确定。
23
+ - 镜像必须精确命名为 `<项目名称>_frontend:<YYYYMMDD-NNN>` 和 `<项目名称>_backend:<YYYYMMDD-NNN>`。
24
+ - Compose 只允许 frontend、backend 两个服务,不设置 `container_name`,不包含颜色、切流或回滚状态。
25
+ - frontend 直接映射 `${DOCKER_BIND_IP}:${FRONTEND_PORT}:${FRONTEND_PORT}`;backend 直接映射 `${DOCKER_BIND_IP}:${BACKEND_PORT}:${BACKEND_PORT}`。
26
+ - 两个容器只把服务器项目根 `./logs` 挂载到 `/app/logs`;日志文件直接位于 `logs/`,不得创建服务、实例、颜色或日期子目录。
27
+ - 服务器部署操作追加写入 `./logs/deploy.log`;前端 Nginx 写入 `frontend-access.log` 和 `frontend-error.log`;后端遵循 `wdyy-logging-standard`。
27
28
  - 根 `.env` 原样复制到 `deploy/.env` 并人工上传,但必须被 `.dockerignore` 排除,且不得进入版本目录或任一镜像。
28
- - `FRONTEND_BASE_IMAGE` 与 `BACKEND_BASE_IMAGE` 使用非空 Docker 镜像引用;可直接使用本地带标签镜像。相同标签在不同时间重新构建时可能对应不同基础镜像内容,但已导出的项目成品镜像仍按版本确定部署。
29
- - 环境文件固定按“前端、后端、数据库、API、部署”排列;部署生成器保留前四段和已有值,只扩充部署段。
30
- - `DATABASE_MIGRATION_MODE=manual` 时只打包迁移与执行脚本;`none` 时不要求数据库文件。任何部署命令都不得自动访问数据库。
31
- - 相同输入生成的 deploy.sh、Compose、Nginx 配置必须字节一致;版本指针、镜像归档和 manifest 可随发布变化。
29
+ - `.env.example` 内置 `FRONTEND_BASE_IMAGE=nginx:stable` 与 `BACKEND_BASE_IMAGE=node:24-alpine3.24`;`.env` 可显式覆盖。
30
+ - 环境文件固定按“前端、后端、数据库、API、部署”排列;部署段只包含 `PROJECT_NAME`、`DOCKER_BIND_IP`、`FRONTEND_BASE_IMAGE`、`BACKEND_BASE_IMAGE`。
31
+ - 发布链路不接受数据库迁移参数,也不打包、校验或执行迁移文件。
32
32
 
33
33
  ## 工作流
34
34
 
35
35
  1. 使用 `pnpm dev` 完成开发测试。
36
- 2. 执行不带额外参数的 `./deploy.sh build`;从 `AGENTS.md` 唯一“发布记录”表生成 `YYYYMMDD-NNN`。
37
- 3. 依次通过测试、lint、类型检查、构建、双镜像语义检查、包校验和配置确定性检查;成功后才替换 `deploy/` 并写发布记录。
38
- 4. 人工把 `deploy/` 全部内容上传到 `.env` 声明的 `<SERVER_PROJECTS_ROOT>/<PROJECT_NAME>`;生产值不得通过命令行传入。
39
- 5. 数据库模式为 manual 时,由工程师单独执行版本包中的迁移脚本并确认结果。
40
- 6. 首次部署执行 `./deploy.sh start`;后续新版本执行 `./deploy.sh replace`。
41
- 7. 通过 `restart`、`stop`、`status`、`rollback <version>` 管理当前项目;`remove` 只删除本项目容器、网络及两个精确镜像仓库。
42
-
43
- ## Nginx 多项目隔离
44
-
45
- - 运维只在服务器初次接入时让共享 Nginx include `/xxzx/home/*/nginx/site.conf`;部署脚本不得编辑该全局配置。
46
- - 每个项目只写自身 `nginx/site.conf` 和 `nginx/active-upstreams.conf`。
47
- - upstream 名称由严格项目名确定,不接受自由文本配置。
48
- - 在共享文件锁内检查其他项目配置和运行中容器的实际发布端口、安装候选配置、运行进程级 `nginx -t` 和 reload;失败时恢复本项目旧文件。
49
- - 其他项目配置错误可以阻止本次 reload,但不得强制跳过检查或改写其他项目文件。
36
+ 2. 在本地项目根目录执行不带额外参数的 `./deploy.sh build`;从 `AGENTS.md` 唯一“发布记录”表生成 `YYYYMMDD-NNN`。
37
+ 3. 依次通过测试、lint、类型检查、构建、双镜像标签检查、包校验和配置确定性检查;成功后才替换 `deploy/` 并写发布记录。
38
+ 4. 人工把 `deploy/` 全部内容上传为服务器项目根目录;生产值不得通过命令行传入。
39
+ 5. 在服务器项目根目录无参数执行 `./deploy.sh`,选择:运行或替换前端、运行或替换后端、运行或替换前后端、停止。
40
+ 6. 运行或替换会载入所选服务的目标版本镜像、强制重建并健康检查;停止会同时停止 frontend backend。
50
41
 
51
42
  ## 禁止事项
52
43
 
53
- - 不得接受人工 build/start/replace 版本参数,不得扫描目录替代发布账本或 `release.env`。
54
- - 不得使用 Bash `source`/`eval` 解析 `.env` 或状态文件。
44
+ - 不得使用蓝绿、滚动切流、活动/历史颜色、宿主机 Nginx、自动回滚或旧版 start/replace/restart/remove/status/rollback 命令。
45
+ - 不得接受人工 build 版本参数,不得扫描版本目录替代发布账本或 `release.env`。
46
+ - 不得使用 Bash `source`/`eval` 解析 `.env` 或版本指针。
55
47
  - 不得使用镜像 fallback、固定 `/srv`、固定 URL/IP/端口、PM2、systemd 或自动安装。
56
- - 不得重新引入 `FRONTEND_CONTAINER_PORT`、`BACKEND_CONTAINER_PORT`、`BACKEND_LISTEN_HOST` 或任何可推导的 `*_HEALTH_URL`、`*_VERSION_URL`。
57
- - 不得先停止活动颜色再启动新颜色,不得分别切换前后端,不得在验证前 reload。
48
+ - 不得重新引入 `PROJECT_HTTP_PORT`、`FRONTEND_BLUE_PORT`、`FRONTEND_GREEN_PORT`、`BACKEND_BLUE_PORT`、`BACKEND_GREEN_PORT`、`FRONTEND_CONTAINER_PORT`、`BACKEND_CONTAINER_PORT`、`BACKEND_LISTEN_HOST` 或任何 `*_HEALTH_URL`、`*_VERSION_URL`。
58
49
  - 不得自动执行 psql、迁移脚本或数据库回滚。
59
- - 不得让 `remove` 删除其他项目资源、日志、发布文件、状态或 Nginx 配置。
60
- - 不得以字符串关键字存在代替命令行为、失败恢复和资源边界测试。
50
+ - 不得以字符串关键字存在代替命令行为、失败传播和资源边界测试。
61
51
 
62
52
  ## Verification
63
53
 
64
- - [ ] 两个镜像名称、标签和版本 label 精确匹配发布版本。
65
- - [ ] 四个服务均使用根 `.env`,且只挂载 `../../logs:/app/logs`。
66
- - [ ] `.env` 与 `.env.example` 的五段、变量顺序一致,部署段键相同且无重复 URL/端口配置。
67
- - [ ] 外部项目端口唯一;Nginx 一次同时切换前后端,失败恢复旧流量和状态。
68
- - [ ] start、replace、restart、stop、remove、status、rollback 行为与资源范围均通过测试。
69
- - [ ] manual/none 两种数据库模式均通过测试,部署脚本不存在数据库执行路径。
70
- - [ ] 连续两次构建的配置文件一致,失败不写发布记录、不暴露半成品。
54
+ - [ ] 两个镜像仓库名、版本标签和 OCI version label 精确匹配发布版本。
55
+ - [ ] Compose 只含 frontend、backend,使用根 `.env`、直接宿主机端口和 `../../logs:/app/logs`。
56
+ - [ ] 服务器无参数菜单只有四项;独立和同时替换不会操作未选择服务;停止同时停止两服务。
57
+ - [ ] `.env` 与 `.env.example` 五段、变量顺序一致,无蓝绿、Nginx、重复端口或日志目录配置。
58
+ - [ ] 部署、前端和后端日志直接位于项目根 `logs/`,容器替换或停止后仍保留。
59
+ - [ ] 不存在数据库迁移参数、迁移包内容、上传命令或数据库执行路径。
60
+ - [ ] 连续两次构建的 deploy.sh 和 Compose 一致,失败不写发布记录、不暴露半成品。
71
61
  - [ ] `deploy/` 含根 `.env`,但版本目录、Docker 上下文和镜像不含秘密文件。
72
- - [ ] 替换或删除容器后,项目根 `logs/` 内容仍保留。
73
62
 
74
- 任一步失败必须明确报告具体阶段并保持或恢复本项目原流量;不得以兜底、静默跳过、强制切流或删除失败证据规避问题。
63
+ 任一步失败必须明确报告具体阶段并返回非零;不得静默跳过、伪造成功或删除失败证据。
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "wdyy-deployment-standard"
3
- short_description: "Build isolated dual-image manual deployment packages"
4
- default_prompt: "Use $wdyy-deployment-standard to generate and validate an isolated dual-image manual deployment workflow."
3
+ short_description: "Build and operate direct dual-image deployments"
4
+ default_prompt: "Use $wdyy-deployment-standard to generate and validate a versioned dual-image manual deployment with direct host ports and an interactive server menu."
@@ -9,17 +9,16 @@ deploy.sh
9
9
  .dockerignore
10
10
  src/frontend/Dockerfile
11
11
  src/backend/Dockerfile
12
- scripts/deployment/docker-compose.blue-green.yml
12
+ scripts/deployment/docker-compose.yml
13
13
  scripts/deployment/frontend-container.conf.template
14
- scripts/deployment/nginx-site.conf
15
14
  .env.example
16
15
  ```
17
16
 
18
- 这些文件必须由 `scripts/generate-deployment-files.mjs --target <project-root> --write` 按固定映射原子生成,templates 是唯一源。既有差异默认失败,不得静默覆盖;工程师审查并确认后才允许 `--force`。生成后执行 `--check`。build 必须原样复制 deploy.sh、Compose Nginx 模板,不得根据运行顺序产生随机内容;相同输入连续构建时这些配置的 SHA-256 必须一致。
17
+ 这些文件必须由 `scripts/generate-deployment-files.mjs --target <project-root> --write` 按固定映射原子生成,templates 是唯一源。既有差异默认失败,工程师审查确认后才允许 `--force`。生成后执行 `--check`。相同输入连续构建时 deploy.sh Compose 必须字节一致。
19
18
 
20
19
  ## 根 .env
21
20
 
22
- 根 `.env` 是本地 build、上传包和服务器 Compose 的唯一配置文件。`.env` 与 `.env.example` 固定按“前端、后端、数据库、API、部署”排列;部署生成器保留前四段与已有值,只向两个文件的部署段追加同名空键。`.env.example` 必须直接采用 `templates/env.example.template` 的键、顺序与非敏感默认值。脚本必须按数据解析,不得 `source` 或 `eval`。部署前根 `.env` 至少要求:
21
+ 根 `.env` 是本地 build、上传包和服务器 Compose 的唯一配置文件。`.env` 与 `.env.example` 固定按“前端、后端、数据库、API、部署”排列。部署前至少要求:
23
22
 
24
23
  ```dotenv
25
24
  FRONTEND_URL=0.0.0.0
@@ -33,35 +32,25 @@ DB_PASSWORD=
33
32
  DB_NAME=
34
33
  DB_SCHEMA=
35
34
  PROJECT_NAME=example_project
36
- SERVER_PROJECTS_ROOT=/xxzx/home
37
- PROJECT_HTTP_PORT=9099
38
- DOCKER_BIND_IP=127.0.0.1
39
- FRONTEND_BLUE_PORT=19091
40
- FRONTEND_GREEN_PORT=19092
41
- BACKEND_BLUE_PORT=19093
42
- BACKEND_GREEN_PORT=19094
35
+ DOCKER_BIND_IP=0.0.0.0
43
36
  FRONTEND_BASE_IMAGE=nginx:stable
44
37
  BACKEND_BASE_IMAGE=node:24-alpine3.24
45
- DATABASE_MIGRATION_MODE=none
46
38
  ```
47
39
 
48
- `FRONTEND_URL`、`FRONTEND_PORT`、`BACKEND_URL`、`BACKEND_PORT` 是应用监听的唯一来源;容器、蓝绿和项目入口的健康/版本 URL 必须由对应主机、端口与固定路径确定性构造。不得增加 `FRONTEND_CONTAINER_PORT`、`BACKEND_CONTAINER_PORT`、`BACKEND_LISTEN_HOST`、`*_HEALTH_URL` `*_VERSION_URL`。通配监听地址 `0.0.0.0` 仅在主动本机探测时转换为 `127.0.0.1`。
40
+ `FRONTEND_PORT`、`BACKEND_PORT` 同时是容器监听端口与宿主机直接暴露端口,二者必须不同且位于 1–65535。`PROJECT_NAME` 只允许 `^[a-z][a-z0-9_]*$`。`.env` 权限必须为 `0600` 或只读的 `0400`。脚本按数据解析环境文件,不得 `source` `eval`。
49
41
 
50
- 部署示例值只说明字段,生成目标项目后必须由工程师确认 `.env` 实际值,不得保留占位符或示例部署端口。`PROJECT_NAME` 只允许 `^[a-z][a-z0-9_]*$`。所有宿主机端口必须互不相同且在 1–65535;两个基础镜像必须是非空 Docker 镜像引用,可使用本地带标签镜像。相同标签在不同时间重新构建时可能解析为不同基础镜像内容;成功构建后导出的项目成品镜像仍用于服务器端确定部署。数据库连接键允许为空,但项目使用数据库时必须填写真实值。
51
-
52
- `.env` 权限必须为 `0600` 或只读的 `0400`。部署脚本只把明确的部署键载入自身环境;数据库密码、JWT 等其他应用键保留在文件中供 Compose 注入容器,不得改变脚本的 `PATH`、Shell 选项或命令行为。
53
-
54
- 可选路径键为 `FRONTEND_DOCKERFILE`、`BACKEND_DOCKERFILE`、`MIGRATIONS_SOURCE`、`MIGRATION_RUNNER_SOURCE`、`DEPLOY_CONFIG_DIR`;未设置时只能使用与目标项目真实结构一致且已验证的标准路径。
42
+ 可选路径键为 `FRONTEND_DOCKERFILE`、`BACKEND_DOCKERFILE`、`DOCKERIGNORE_FILE`、`DEPLOY_CONFIG_DIR` `COMPOSE_SOURCE`;它们不属于标准 `.env.example`,只在目标项目确有非标准结构且已确认时使用。
55
43
 
56
44
  ## Docker 与日志
57
45
 
58
46
  - 前端镜像:`${PROJECT_NAME}_frontend:${RELEASE_VERSION}`。
59
47
  - 后端镜像:`${PROJECT_NAME}_backend:${RELEASE_VERSION}`。
60
- - 四个服务由同一个 `docker compose --project-name "$PROJECT_NAME"` 管理,不设置 `container_name`。
61
- - 四个服务都使用 `../../.env`,复用 `FRONTEND_PORT`、`BACKEND_PORT`、`FRONTEND_URL`、`BACKEND_URL`,并且只有 `../../logs:/app/logs` 一个日志挂载。
62
- - 前端 Nginx 容器的 access/error 文件直接写入 `/app/logs/frontend-<color>-*.log`;不得创建子目录。
63
- - 后端必须把自身日志直接写入 `/app/logs`,不得依赖容器可写层保存持久日志。
64
- - `.dockerignore` 必须排除 `.env`、`.env.*`、密钥、凭据、deploy、logs、Git、依赖缓存和虚拟环境。
48
+ - Compose project 使用 `$PROJECT_NAME`,只定义 frontend、backend,不设置 `container_name`。
49
+ - frontend 端口映射为 `${DOCKER_BIND_IP}:${FRONTEND_PORT}:${FRONTEND_PORT}`;backend 同理使用 `BACKEND_PORT`。
50
+ - 两个服务均使用 `../../.env`,且只挂载 `../../logs:/app/logs`。
51
+ - 前端 Nginx access/error 直接写 `/app/logs/frontend-access.log`、`/app/logs/frontend-error.log`。
52
+ - 后端日志直接写 `/app/logs`;服务器菜单操作追加到部署根 `logs/deploy.log`。
53
+ - `.dockerignore` 必须排除 `.env`、秘密、deploy、logs、Git、依赖缓存和虚拟环境。
65
54
 
66
55
  ## 发布账本与本地包
67
56
 
@@ -75,7 +64,7 @@ DATABASE_MIGRATION_MODE=none
75
64
  | 20260808-001 | 2026-08-08 09:30:00 +08:00 |
76
65
  ```
77
66
 
78
- build 使用项目级原子锁,严格验证账本后生成当天下一序号。全部门禁、双镜像、包结构和确定性校验通过后才写账本。失败必须保留原 deploy 和原账本。
67
+ build 使用项目级原子锁,严格验证账本后生成当天下一序号。全部门禁、双镜像、包结构和确定性校验通过后才写账本。
79
68
 
80
69
  ```text
81
70
  deploy/
@@ -86,50 +75,25 @@ deploy/
86
75
  ├── frontend-image.tar
87
76
  ├── backend-image.tar
88
77
  ├── manifest.sha256
89
- ├── docker/docker-compose.blue-green.yml
90
- ├── nginx/site.conf
91
- └── database/ # 仅 manual 模式
92
- ├── migrations/
93
- └── apply-migrations.sh
94
- ```
95
-
96
- `release.env` 只包含当前版本。manifest 覆盖版本目录内除自身外的全部普通文件,禁止符号链接、硬链接秘密、绝对路径和 `..`。根 `.env` 可上传,但版本目录与镜像不得包含它。
97
-
98
- ## 服务器部署和 Nginx
99
-
100
- 上传目标必须是 `${SERVER_PROJECTS_ROOT}/${PROJECT_NAME}`,生产值为 `/xxzx/home/<项目名称>`。脚本通过自身物理路径验证该关系,不依赖调用者当前目录。
101
-
102
- 共享 Nginx 主配置由运维预先 include `/xxzx/home/*/nginx/site.conf`。项目脚本不得写 `/etc/nginx` 或其他项目,只维护:
103
-
104
- ```text
105
- <project-root>/nginx/site.conf
106
- <project-root>/nginx/active-upstreams.conf
78
+ └── docker/docker-compose.yml
107
79
  ```
108
80
 
109
- site 监听 `PROJECT_HTTP_PORT`,并带项目名、外部端口及四个 Docker 宿主机端口标记。首次部署和端口变更必须在共享锁内检查展开的 Nginx 配置、其他标准项目 site 以及运行中容器的实际发布端口;任一外部端口或 blue/green 宿主机端口重复、其他项目缺少端口标记或不能证明唯一时失败。候选 site 与前后端 upstream 一起安装,`nginx -t` 或 reload 失败时恢复旧文件。
110
-
111
- ## 命令
81
+ `release.env` 只包含当前版本。manifest 覆盖版本目录内除自身外的全部普通文件。根 `.env` 可上传,但版本目录与镜像不得包含它。
112
82
 
113
- - `./deploy.sh start`:只允许无活动状态的首次 blue 部署。
114
- - `./deploy.sh replace`:只允许已有活动状态且 release.env 指向不同新版本。
115
- - `./deploy.sh restart`:重新创建并验证当前颜色的两个容器,不改变流量颜色和版本。
116
- - `./deploy.sh stop`:停止当前颜色的两个容器,保留所有文件和状态。
117
- - `./deploy.sh remove`:删除 Compose project 下四个容器及网络,并删除两个精确项目镜像仓库的全部标签;保留日志、包、状态和 Nginx 文件。
118
- - `./deploy.sh status`:报告 active/previous 和四服务状态。
119
- - `./deploy.sh rollback <version>`:只允许 previous 中已保留且健康的成对容器。
83
+ ## 服务器菜单
120
84
 
121
- rollback 的版本外,命令不得接受额外参数。
85
+ 工程师把 `deploy/` 内容完整上传为服务器项目根目录,随后无参数执行 `./deploy.sh`:
122
86
 
123
- ## 数据库
87
+ 1. 运行或替换前端:验证包和前端镜像标签,只载入并重建 frontend,验证 `/health` 与 `/version`。
88
+ 2. 运行或替换后端:验证包和后端镜像标签,只载入并重建 backend,验证 `/health` 与 `/version`。
89
+ 3. 运行或替换前后端:载入并重建两个服务,两个服务全部验证成功后报告成功。
90
+ 4. 停止:同时停止 frontend 和 backend,保留容器、镜像、上传文件和日志。
124
91
 
125
- - `DATABASE_MIGRATION_MODE=none`:版本包不要求数据库目录。
126
- - `DATABASE_MIGRATION_MODE=manual`:必须打包完整迁移目录和可执行脚本。
127
- - deploy.sh 的任何服务器命令都不得调用迁移脚本、psql 或数据库客户端。
128
- - build 完成和服务器 start/replace 必须明确提示 manual 操作尚需工程师单独执行,不得声称已迁移。
92
+ 菜单操作使用 `release.env` 指向的版本,不接受版本参数。非法输入必须在 Docker 状态变更前失败。部署根始终由脚本物理路径确定。
129
93
 
130
- ## 失败边界
94
+ ## 数据库和失败边界
131
95
 
132
- - 新颜色前后端全部健康且后端版本正确前,不得修改 Nginx 或活动状态。
133
- - Nginx candidate、全局校验、reload 或状态提交失败时,必须恢复本项目旧配置和旧状态。
134
- - 不得停止旧颜色掩盖新颜色失败;不得修改、停止或删除其他项目任何资源。
135
- - `remove` 不得删除 logs、上传版本目录、release.env、状态文件或 Nginx 配置。
96
+ - 部署 Skill 不接受数据库迁移参数,不打包、校验、提示或执行迁移文件。
97
+ - 固定服务替换允许短暂停机;失败时返回非零并记录阶段,不自动回滚或伪造成功。
98
+ - 脚本不得上传文件、拉取代码、安装软件、修改宿主机 Nginx 或操作其他项目资源。
99
+ - 旧蓝绿容器、状态和宿主机 Nginx 配置必须由工程师按旧部署实际状态单独迁移,新脚本不得猜测并自动删除。
@@ -10,9 +10,8 @@ const mappings = [
10
10
  ['dockerignore.template', '.dockerignore', 0o644],
11
11
  ['frontend.Dockerfile.template', 'src/frontend/Dockerfile', 0o644],
12
12
  ['Dockerfile.template', 'src/backend/Dockerfile', 0o644],
13
- ['docker-compose.blue-green.yml', 'scripts/deployment/docker-compose.blue-green.yml', 0o644],
13
+ ['docker-compose.yml', 'scripts/deployment/docker-compose.yml', 0o644],
14
14
  ['frontend-container.conf.template', 'scripts/deployment/frontend-container.conf.template', 0o644],
15
- ['nginx-upstream.template.conf', 'scripts/deployment/nginx-site.conf', 0o644],
16
15
  ];
17
16
 
18
17
  const sectionNames = ['前端', '后端', '数据库', 'API', '部署'];
@@ -22,11 +21,16 @@ const baseKeys = new Map([
22
21
  ['数据库', ['DB_URL', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME', 'DB_SCHEMA']],
23
22
  ]);
24
23
  const deploymentKeys = [
25
- 'PROJECT_NAME', 'SERVER_PROJECTS_ROOT', 'PROJECT_HTTP_PORT', 'DOCKER_BIND_IP',
26
- 'FRONTEND_BLUE_PORT', 'FRONTEND_GREEN_PORT', 'BACKEND_BLUE_PORT', 'BACKEND_GREEN_PORT',
27
- 'FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE', 'DATABASE_MIGRATION_MODE',
24
+ 'PROJECT_NAME', 'DOCKER_BIND_IP',
25
+ 'FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE',
28
26
  ];
29
27
 
28
+ const removedDeploymentKeys = new Set([
29
+ 'PROJECT_HTTP_PORT', 'FRONTEND_BLUE_PORT', 'FRONTEND_GREEN_PORT',
30
+ 'BACKEND_BLUE_PORT', 'BACKEND_GREEN_PORT', 'SERVER_PROJECTS_ROOT',
31
+ 'NGINX_SOURCE', 'LOG_DIR', 'HOST_LOG_DIR',
32
+ ]);
33
+
30
34
  function parseEnvironmentStructure(text, relativeTarget, requireExampleDefaults, exampleValues) {
31
35
  const normalized = text.replaceAll('\r\n', '\n');
32
36
  const sections = new Map(sectionNames.map((name) => [name, []]));
@@ -47,7 +51,7 @@ function parseEnvironmentStructure(text, relativeTarget, requireExampleDefaults,
47
51
  const entry = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
48
52
  if (!entry) throw new Error(`${relativeTarget}:${index + 1} must use KEY=VALUE syntax`);
49
53
  if (seenKeys.has(entry[1])) throw new Error(`${relativeTarget} contains duplicate key: ${entry[1]}`);
50
- if (['FRONTEND_CONTAINER_PORT', 'BACKEND_CONTAINER_PORT', 'BACKEND_LISTEN_HOST'].includes(entry[1]) || /_(?:HEALTH|VERSION)_URL$/.test(entry[1])) {
54
+ if (removedDeploymentKeys.has(entry[1]) || ['FRONTEND_CONTAINER_PORT', 'BACKEND_CONTAINER_PORT', 'BACKEND_LISTEN_HOST'].includes(entry[1]) || /_(?:HEALTH|VERSION)_URL$/.test(entry[1])) {
51
55
  throw new Error(`${relativeTarget} contains legacy duplicate configuration: ${entry[1]}`);
52
56
  }
53
57
  seenKeys.add(entry[1]);
@@ -73,8 +77,14 @@ function parseEnvironmentStructure(text, relativeTarget, requireExampleDefaults,
73
77
  if (actualDeploymentKeys.length !== 0 && actualDeploymentKeys.join('|') !== deploymentKeys.join('|')) {
74
78
  throw new Error(`${relativeTarget} # 部署 keys must be empty or exactly: ${deploymentKeys.join(', ')}`);
75
79
  }
76
- if (requireExampleDefaults && sections.get('部署').some(({ value }) => value !== '')) {
77
- throw new Error(`${relativeTarget} deployment example values must be empty`);
80
+ if (requireExampleDefaults && sections.get('部署').some(({ key, value }) => !['FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE'].includes(key) && value !== '')) {
81
+ throw new Error(`${relativeTarget} deployment example values must be empty except base-image defaults`);
82
+ }
83
+ if (requireExampleDefaults) {
84
+ for (const key of ['FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE']) {
85
+ const entry = sections.get('部署').find((item) => item.key === key);
86
+ if (entry && entry.value !== exampleValues.get(key)) throw new Error(`${relativeTarget} must keep the standard default for ${key}`);
87
+ }
78
88
  }
79
89
  return {
80
90
  extended: actualDeploymentKeys.length === deploymentKeys.length,
@@ -83,7 +93,7 @@ function parseEnvironmentStructure(text, relativeTarget, requireExampleDefaults,
83
93
  }
84
94
 
85
95
  function extendDeploymentSection(text) {
86
- return `${text.trimEnd()}\n${deploymentKeys.map((key) => `${key}=`).join('\n')}\n`;
96
+ return `${text.trimEnd()}\n${deploymentKeys.map((key) => `${key}=${['FRONTEND_BASE_IMAGE', 'BACKEND_BASE_IMAGE'].includes(key) ? exampleValues.get(key) : ''}`).join('\n')}\n`;
87
97
  }
88
98
 
89
99
  const args = process.argv.slice(2);
@@ -34,11 +34,16 @@ test('确定性生成全部标准文件并通过 check', async () => {
34
34
  const checked = run(root, '--check');
35
35
  assert.equal(checked.status, 0, checked.stderr);
36
36
  assert.match(checked.stdout, /verified deterministic deployment files/);
37
- assert.match(await readFile(join(root, 'deploy.sh'), 'utf8'), /build\|start\|replace/);
37
+ assert.match(await readFile(join(root, 'deploy.sh'), 'utf8'), /server_menu/);
38
+ assert.match(await readFile(join(root, 'scripts/deployment/docker-compose.yml'), 'utf8'), /^ frontend:[\s\S]*^ backend:/m);
39
+ await assert.rejects(stat(join(root, 'scripts/deployment/docker-compose.blue-green.yml')));
38
40
  const environment = await readFile(join(root, '.env'), 'utf8');
39
41
  const example = await readFile(join(root, '.env.example'), 'utf8');
40
42
  assert.match(environment, /^# 前端\nFRONTEND_URL=0\.0\.0\.0\nFRONTEND_PORT=5173$/m);
41
- assert.match(environment, /^# 部署\nPROJECT_NAME=\nSERVER_PROJECTS_ROOT=/m);
43
+ assert.match(environment, /^# 部署\nPROJECT_NAME=\nDOCKER_BIND_IP=/m);
44
+ assert.doesNotMatch(environment, /PROJECT_HTTP_PORT|BLUE_PORT|GREEN_PORT/);
45
+ assert.match(environment, /^FRONTEND_BASE_IMAGE=nginx:stable$/m);
46
+ assert.match(environment, /^BACKEND_BASE_IMAGE=node:24-alpine3\.24$/m);
42
47
  assert.equal(example, environment);
43
48
  if (process.platform !== 'win32') assert.equal((await stat(join(root, '.env'))).mode & 0o777, 0o600);
44
49
  });
@@ -104,3 +109,12 @@ test('重复或冲突参数被明确拒绝', async () => {
104
109
  assert.notEqual(duplicateTarget.status, 0);
105
110
  assert.match(duplicateTarget.stderr, /Duplicate or missing --target argument/);
106
111
  });
112
+
113
+ test('旧蓝绿和宿主机 Nginx 配置键被明确拒绝', async () => {
114
+ const root = await projectRoot();
115
+ const envPath = join(root, '.env');
116
+ await writeFile(envPath, (await readFile(envPath, 'utf8')).replace('# 部署\n', '# 部署\nPROJECT_HTTP_PORT=9099\n'), { mode: 0o600 });
117
+ const generated = run(root, '--write');
118
+ assert.notEqual(generated.status, 0);
119
+ assert.match(generated.stderr, /legacy duplicate configuration/);
120
+ });