@h-ai/deploy 0.1.0-alpha.66 → 0.1.0-alpha.68

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.
package/README.md CHANGED
@@ -2,144 +2,190 @@
2
2
 
3
3
  [简体中文](README.md) | [English](README.en.md) | [日本語](README.ja.md)
4
4
 
5
- 部署 SDK,也是 `@h-ai/cli` 的 `hai deploy` 后端。**只部署已有产物,不编译源码、不构建镜像。**
5
+ ## 简介
6
6
 
7
- 完整的创建、开发、构建与部署流程见[使用指南](../../docs/usage/README.md);下文侧重部署准备、SDK 与二次开发。
7
+ 部署已有镜像与 Vercel prebuilt 产物。支持完整离线交付、选择服务更新、SSH 自动发布和跨平台人工部署脚本。SDK 不构建源码,CLI 的 --build 明确编排先构建后发布。
8
8
 
9
- ## 功能
9
+ ## 核心功能清单
10
10
 
11
- - `docker-ssh`:读取镜像清单,选择环境和中间件,生成一个 Compose tar;上传到 Linux 后解包、导入镜像、启动并检查健康状态。
12
- - `--bundle-only`:只生成同样的 tar 和操作说明,不连接 SSH;支持 U 盘或其他手工搬运方式。
13
- - fullstack 的构建清单含 app/web 两个镜像时一起部署。公开端口提供 Web 和同源 API;Web 不接收 API 的私有变量,支持 hash 页面刷新。
14
- - `vercel`:上传已有 `.vercel/output` 并设置运行变量;需要网络,按 [Build Output API](https://vercel.com/docs/build-output-api/primitives) 的 prebuilt 协议部署,不执行本地或平台源码构建。
15
- - 只从目标 importer 的 `pnpm-lock.yaml` 遍历运行依赖(含 workspace link),排除 dev/peer,识别数据库、cache、storage、vecdb、LDAP、邮件和短信等中间件。
16
- - 中间件逐项选择默认、自定义或保持不变;配置完整时用 `--skip-middleware` 跳过全部处理。Compose 默认支持 PostgreSQL、MySQL、Redis、RustFS/MinIO、Qdrant、pgvector、Chroma、OpenLDAP、Mailpit;云开通支持 Neon、Upstash、R2、Resend、阿里云短信。详细字段见 [环境与中间件](../cli/templates/skills/hai-deploy/middleware.md)。
11
+ - 一条命令完成构建、打包、SSH 上传、启动和访问检查。
12
+ - 完整包包含应用与自建中间件镜像;支持只更新选中的服务。
13
+ - 离线交付生成 tar 和 Windows/Linux/macOS 操作脚本,记录步骤结果并在失败时尝试恢复。
17
14
 
18
- ## 构建与部署
15
+ ## 使用入门
19
16
 
20
- 当前工程要求 Node `^22.22.2 || ^24.15.0 || >=26.0.0`、pnpm 12.11.2。Windows 用 `pnpm.cmd`;Node 20 / pnpm 9.5.0 不受支持。
17
+ ### 准备构建产物
21
18
 
22
- 生成工程的 `pnpm build` 调用 `hai build`,默认只编译源码。镜像和原生安装包按目标显式构建,**不选择 dev/test/prod,不读部署文件,也不注入运行期凭据**:
19
+ 创建项目默认安装部署工具,可用 `hai create my-app --no-deploy` 关闭。部署 SDK 属于开发依赖,不进入应用运行镜像。
23
20
 
24
- ```powershell
25
- pnpm.cmd build
26
- pnpm.cmd build --target docker
27
- # 自动选择可用的 Podman,失败后尝试 Docker;也可显式指定
28
- pnpm.cmd build --target docker --runtime docker
29
- # 启用 desktop 的 fullstack;需在对应系统上安装 Rust target/系统 SDK/打包工具
30
- pnpm.cmd build --target desktop --platform windows --arch x64
21
+ ```sh
22
+ pnpm build
23
+ pnpm build --target docker
24
+ pnpm build --target docker --images web
25
+ pnpm build --target desktop --platform windows --arch x64
26
+ ```
27
+
28
+ 源码编译、镜像构建、桌面打包分别对应 compile/docker/desktop。构建不选择部署环境、不注入运行期密钥。原生安装包在对应操作系统准备 SDK 与 Rust 工具链后构建。
29
+
30
+ #### build 的可选参数
31
+
32
+ | 参数 | 用途 |
33
+ | -------------------------- | --------------------------------------- |
34
+ | `--target` | compile(默认)、docker、desktop |
35
+ | `--images` | app、web 或 app,web;只构建导出所选镜像 |
36
+ | `--runtime` | auto(优先 Podman)、podman、docker |
37
+ | `--network` | 容器构建网络,默认引擎设置 |
38
+ | `--no-http-proxy` | Podman 构建不继承本机代理 |
39
+ | `--sqlite-prebuild-mirror` | SQLite 预编译包的可信 HTTP(S) 根地址 |
40
+ | `--platform` / `--arch` | 原生安装包的系统与架构 |
41
+
42
+ #### 用 hai.build.json 描述要打什么包
43
+
44
+ ```json
45
+ {
46
+ "images": [
47
+ { "name": "app", "context": ".", "dockerfile": "images/app.Dockerfile", "containerPort": 3000 },
48
+ { "name": "web", "context": ".", "dockerfile": "images/web.Dockerfile", "containerPort": 8080 }
49
+ ],
50
+ "desktop": ["apps/demo-desktop"]
51
+ }
31
52
  ```
32
53
 
33
- `hai.build.json` 指定 Dockerfile 阶段、context 和 Tauri 目录。compile 只编译源码,docker 导出镜像,desktop 调用 Tauri,具体默认值和示例见[构建参数](../../docs/usage/README.md#build-的可选参数)。版本取应用根 package.json.version;以 1.2.3 为例,输出 `.hai/build/docker/1.2.3-<构建编号>/`,含格式版本 2 的 manifest.json(appVersion 为应用版本)、app-1.2.3.tar、可选 web-1.2.3.tar 和 SHA-256。全部成功才更新 latest。Tauri 使用相同根版本,安装包在对应工程 src-tauri/target/<Rust target>/release/bundle/。各 OS 分别准备主机/runner 与 SDK,不承诺跨系统编译。
54
+ 每个镜像独立 Dockerfile;web 的 Nginx 配置与启动资源放 `images/web/`。Dockerfile 内保留编译/运行阶段,配置不再选择阶段。没有 Web 或 desktop 时删除相应项。
34
55
 
35
- fullstack 在工程根执行:
56
+ 部署生成的 `web.conf` 在 API location 设置 `client_max_body_size 2129920`(2 MiB + 32 KiB),并关闭 `proxy_request_buffering` / `proxy_buffering`。这允许 transport 的 1 MiB 明文经编码形成密文后通过,应用仍逐块执行更细的握手 JSON 16 KiB、公钥 4 KiB、明文 1 MiB 预算。自有反向代理须允许该密文预算并传递取消,不能用增大代理上限代替 serv/kit 的有界读取;大文件上传走明确的独立路径和体积策略。
36
57
 
37
- ```powershell
38
- pnpm.cmd run deploy --env test
39
- pnpm.cmd run deploy --env prod --skip-middleware
40
- # 精确复用一次构建,绝不重新构建
41
- pnpm.cmd run deploy --env prod --artifact-dir .hai/build/docker/<buildId>
42
- # 服务器网络不通,只交付包
43
- pnpm.cmd run deploy --env prod --bundle-only --output release-prod.tar
44
- ```
58
+ 无正文认证封装通过 X-Hai-Transport 头传输,生成配置使用 large_client_header_buffers 4 32k 与 proxy_buffer_size 32k 容纳头字段;自有代理也须允许并保留该头。
59
+
60
+ 构建器将锁文件、包配置、本地归档和补丁准备为 `images/dependencies/` 输入,结合 pnpm store 缓存与 Docker 分层复用下载。源码修改不应重新下载全部依赖;缓存为空仍能联网构建。Windows node_modules 不用于 Linux 镜像。完全离线构建还需预先准备基础镜像、工具链和匹配 ABI/架构/libc 的原生预编译包。
45
61
 
46
- 等价命令为 `pnpm exec hai deploy packages/my-app-serv --env prod --env-dir .`。API workspace 的服务目录为 `apps/my-app-service`。模板已安装项目 CLI;选择 deploy 功能时安装对应 SDK。旧工程需自行安装与框架版本匹配的 `@h-ai/cli`、`@h-ai/deploy`,并提供构建配置和 `_deploy.yml`。
62
+ 版本来自应用根 package.json.version。制品位于 `.hai/build/docker/<buildId>/`,包括格式版本 3 的 manifest.json、所选镜像 tar 和 SHA-256;partial 标识部分构建,hasWeb 保留工程拓扑。清单还会记住配置中填写的 app/web 容器端口(appContainerPort、webContainerPort),只打包一个镜像也不会丢掉另一个的端口;没填写则沿用部署默认值。相同应用版本允许重建,构建编号和镜像引用区分实际内容;全部导出成功才更新本机构建入口。基础镜像、中间件及工具使用明确版本,不使用 latest。
47
63
 
48
- 先检查 `.env.<mode>`;缺少则停止并提示创建。`.env` 公共基础、`.env.dev/test/prod` 环境覆盖和完整说明 `.env.example` 提交 Git;`.env.local` 保存私有值并忽略。优先级:进程 > `.env.local` > 环境文件 > `.env`,整体 JSON 节点优先于子字段。
64
+ ### 部署到环境
65
+
66
+ #### 先准备环境与部署目标
67
+
68
+ 准备 `.env.<环境>`,例如 `.env.prod`,以及服务目录的 `config/_deploy.yml`。fullstack 的根 deploy 脚本指向 `packages/<项目>-serv`,API workspace 指向 `apps/<项目>-service`;这是配置扫描入口,镜像清单仍在工程根。
49
69
 
50
70
  ```yaml
51
- # packages/my-app-serv/config/_deploy.yml
52
71
  provider:
53
72
  type: docker-ssh
54
73
  expose:
55
74
  hostPort: 18080
56
75
  container:
57
- s3Engine: ${HAI_DEPLOY_CONTAINER_S3ENGINE:rustfs}
76
+ s3Engine: rustfs
58
77
  ```
59
78
 
60
- container.s3Engine 可省略,默认 rustfs;仅自动创建 Compose S3 服务时区分 RustFS/MinIO 的镜像和桶初始化,既有自定义 S3 不受它影响。向导会保存选择,完整说明见[使用指南](../../docs/usage/README.md#先准备环境与部署目标)。跳过全部中间件处理使用 --skip-middleware,SDK 对应 skipMiddleware。
79
+ 直接部署时,向导按“服务器地址、SSH 登录用户、私钥文件、访问端口”等说明补齐缺少的配置,并复用已有完整配置。SSH 参数仅写本机 `.env.local`;私钥只保存路径。`--bundle-only` 不询问 SSH 连接信息,后续上传前在 `deploy-target.json` 中填写。
80
+
81
+ #### 按应用真正需要的中间件处理
82
+
83
+ 根据锁文件运行依赖和模块配置识别数据库、缓存、文件存储、向量库、LDAP、邮件等。逐项选择默认、自定义或保持。配置完备可用 `--skip-middleware` 跳过向导和开通。SQLite、Memory cache、本地存储不产生外部镜像;Compose 自建服务的镜像自动加入完整交付包。外部服务仍需目标网络可达,云开通不属于完全离线部署。
84
+
85
+ #### deploy 的可选参数
61
86
 
62
- 自动 SSH 部署首次向导填写目标,写入 `.env.local` 后复用;也可直接配置:
87
+ | 参数 | 用途 |
88
+ | --------------------- | --------------------------------------------------------- |
89
+ | `--env` / `--env-dir` | 环境和环境文件目录 |
90
+ | `--build` | 先构建选中应用镜像,再打包部署;可结合 bundle-only |
91
+ | `--services` | 只更新指定 Compose 服务,例如 web、app、app,web、storage |
92
+ | `--artifact-dir` | 精确选择已构建制品 |
93
+ | `--bundle-only` | 只生成 tar 和跨平台脚本,不连接服务器 |
94
+ | `--runtime` | 本地构建及中间件镜像导出引擎 |
95
+ | `--output` | 自定义部署 tar 路径,相对命令执行目录解析,自动补版本后缀 |
96
+ | `--skip-middleware` | 跳过中间件配置与开通 |
97
+ | `--yes` | 无交互使用有效配置;缺必要值明确报错 |
63
98
 
64
- ```dotenv
65
- HAI_DEPLOY_PROVIDER='{"type":"docker-ssh","ssh":{"host":"10.0.0.20","username":"deploy","port":22,"identityFile":"C:/Users/me/.ssh/hai_deploy"},"remote":{"baseDir":"/opt/hai/apps","runtime":"docker"},"expose":{"hostPort":18080,"containerPort":3000}}'
99
+ #### 两机连通:直接部署
100
+
101
+ ```sh
102
+ pnpm run deploy --env prod --build
103
+ pnpm run deploy --env prod --artifact-dir .hai/build/docker/<buildId>
104
+ pnpm run deploy --env prod --build --services web
66
105
  ```
67
106
 
68
- 仅打包不要求 SSH 参数。本地部署工具只需要已安装依赖、`tar` 和构建产物;**不需要本地 Docker/Podman**。自动部署另外需要 `ssh/scp`、服务器指纹与非交互登录权限、可写部署目录、SSH 及应用端口可达。
107
+ 部分更新需要服务器已有成功的完整安装,并且端口、应用布局相同。以服务器成功状态为基线,只替换选中的镜像和配置,保留其他服务、连接与卷。部署脚本先按项目和服务名移除选中的旧容器(保留数据卷),再执行 `compose up -d --no-deps --no-build --pull never --no-recreate web` 创建新容器;其他容器保持运行,不执行整个项目 down。首次安装使用完整包。storage-init 是创建 S3 桶的一次性任务,只在涉及存储初始化时执行,成功后再启动应用。
69
108
 
70
- ## tar 内容与手工启动
109
+ 命令中的 `-d` 表示后台启动,`--no-deps` 不连带启动或重启依赖,`--no-build` 不在服务器编译,`--pull never` 禁止拉取镜像,`--no-recreate` 保留仍存在的容器;选中的旧容器已由脚本移除,因此会创建新容器,末尾 `web` 是 Compose 服务名(不是镜像标签)。多个服务用空格分隔。
71
110
 
72
- 部署 tar 默认名为 bundle-<项目>-<部署ID>-<应用版本>.tar,自定义 --output release-prod.tar 也会自动得到 release-prod-1.2.3.tar(示例版本 1.2.3)。包含 docker-compose-1.2.3.yml、manifest.json、.env.runtime、可选 web.conf、镜像归档、SHA256SUMS、start-<mode>.sh 和 README.txt。它含私有凭据,权限 600,不提交 Git。packaged 不代表服务器已运行;自动部署上传一个 tar,在远程解包启动,不传源码或构建镜像。
111
+ `storage-init` 复用应用镜像内的 S3 SDK,等待存储服务就绪并检查桶;桶存在就跳过,不删除数据。脚本先启动 storage,再执行 `compose up --no-deps --no-build --pull never --force-recreate --exit-code-from storage-init storage-init`;初始化退出码为 0 后才启动选中应用。 部分更新时,脚本先移除旧的 storage-init 容器,并将该命令的 `--force-recreate` 改为 `--no-recreate`,避免连带重建应用。只更新 `storage` 时,初始化仍需包含 `app` 镜像的制品;若最近只构建了 Web,用 `--artifact-dir` 选择原完整制品,或先执行 `pnpm build --target docker --images app`。该镜像用于初始化,不会更新正在运行的 `app` 服务。
73
112
 
74
- 将包搬到 Linux 后,按包内 README 操作:
113
+ #### 两机不通:生成包,手工复制
75
114
 
76
115
  ```sh
77
- mkdir -p release && chmod 700 release
78
- tar -xf release-prod-1.2.3.tar -C release
79
- cd release
80
- HAI_DEPLOY_PROVIDER_REMOTE_RUNTIME=docker sh start-prod.sh
116
+ pnpm run deploy --env prod --build --bundle-only
81
117
  ```
82
118
 
83
- 启动脚本自动 -f 选择带版本的 Compose,检查归档和镜像、导入 app/web、导出环境标识后启动。缺中间件镜像时停止,不联网下载。卷按项目和环境隔离,跨版本重复部署保留数据。手工检查使用 `docker compose -f docker-compose-1.2.3.yml --env-file .env.runtime ps`(或 logs),并验证 Web、/health。生产 HTTPS、CORS、DNS 与证书须另行配置。
119
+ 默认将部署缓存、tar 和同名 `-deploy/` 操作目录放到工程根 `.hai/deploy/<环境>/`,与 `.hai/build/` 使用同一工程根。归档名包含项目名、部署 ID 和应用版本,例如 `bundle-demo-<部署ID>-1.2.3.tar`。显式传入 `--output release-prod.tar` 时,按命令执行目录输出 `release-prod-1.2.3.tar` 和相邻操作目录。包包含所选应用/中间件镜像、服务配置、私有环境、校验和、README 和 apply.sh。运行上传脚本前,先打开操作目录的 `deploy-target.json`,按其中逐项说明修改并核对服务器、SSH 用户和端口、私钥及指纹文件、远程目录等配置。Windows 运行 deploy.ps1,Linux/macOS 运行 deploy.sh;两个脚本均读取该 JSON,通过 SSH 上传后执行同一服务器流程。本机运行 deploy.sh 需 sh、awk、ssh、scp;可用 `sh deploy.sh 主机 [端口] [用户]` 覆盖连接地址,或给 deploy.ps1 传入 `-IdentityFile` 覆盖私钥。两个脚本均支持用 HAI_DEPLOY_IDENTITY_FILE、HAI_DEPLOY_KNOWN_HOSTS_FILE 覆盖私钥及指纹文件路径。换电脑或操作系统后,这些路径必须指向当前电脑上的实际文件。
84
120
 
85
- ## 完全离线准备(Windows → Linux)
121
+ 完全隔离的 Linux 服务器上,将 tar 解到独立目录,运行 `sh apply.sh`,也可传入部署根目录覆盖默认值。每步检查结果,任何失败返回非零退出码;镜像导入、服务启动和 HTTP 检查分别报告。结果写到服务器部署目录 `result-<部署ID>.json`,只有检查通过才更新 current 成功状态。重复应用同一部署包会明确停止;相同版本重新生成的新包允许发布。已有版本失败时尝试恢复选中的旧容器和配置,保留数据卷及诊断材料;不会回退已写入的业务数据。
86
122
 
87
- 已有 PostgreSQL、Redis、RustFS 镜像还不够,准备以下内容:
123
+ 服务器使用 util-linux `flock` 对部署根目录的 `.deploy.lock` 持有非阻塞内核锁,覆盖部署和失败恢复。正常前台部署及同步失败恢复收尾、发布终态后显式解锁,后台服务继续运行不阻塞后续部署。锁文件永久保留,禁止删除、替换或按经过时间抢锁;目录所在文件系统须支持 `flock`。HUP/INT/TERM 中断或父 shell 被强杀后,继承文件描述符的活跃子命令继续持锁,所有 holder 退出或服务器重启后才释放。旧 `.deploy-lock` 目录不再参与判锁,也不自动删除;升级前须结束旧版部署进程,勿混用旧包。强杀/断电不会运行恢复 trap,缺少结果文件时执行状态未知:先核对容器、镜像和 `current` 成功基线,再用新部署包恢复,不能把锁释放视为部署成功或副作用已回滚。
88
124
 
89
- 1. Windows 上受支持的 Node/pnpm、完整项目、锁文件和已安装 CLI/SDK 依赖;验证断网后命令可启动。本机无容器引擎时,从构建机复制完整 `.hai/build/docker/<buildId>`。
90
- 2. 在构建机预先执行 `pnpm build --target docker`,确认镜像架构与服务器一致。离线部署只消费这些 tar,不需要 Node 基础镜像、pnpm store、CUDA、编译器或 SQLite 下载。无 AI 运行依赖的 fullstack 不安装 AI/CUDA。
91
- 3. 若连构建也要离线,则构建机另需 Linux 容器引擎、所有基础镜像、pnpm/Corepack、完整 Linux 依赖缓存、SQLite 对应驱动版本/Node ABI/架构/libc 的预编译包。Windows node_modules 不能替代 Linux 原生依赖。镜像构建可用 `--sqlite-prebuild-mirror http://<可信地址>/better-sqlite3`;下载失败应修复缓存/网络,不在 deploy 安装编译环境。
92
- 4. Linux 上安装对应架构 Docker/Podman、可执行的 Compose 命令、`sh`、`tar`、`sha256sum`;运行用户有容器/目录/卷权限。中间件镜像**名称和标签**须与生成 README 一致,先用 `docker load` 导入;SDK 不自动捆绑中间件镜像。自定义连接则要准备已运行的服务、账号、数据库/桶、容器可达地址。
93
- 5. `.env.prod`、私有管理员密码/连接密钥、部署公开端口、持久化磁盘和必要证书。切勿把容器内 localhost 当服务器宿主机。
94
- 6. 无任何两机网络时,用 `--bundle-only`,手工复制包;有内网但无互联网时也可 SSH 自动部署。云 Provider/Provisioner 不适用于完全离线,应选 Compose 或自定义已有服务。
125
+ #### 完全离线前再核对一次
95
126
 
96
- 离线验收须实际断开外网后打包/搬运/启动、登录、页面刷新、重启和数据持久化;联网通过不等于断网通过。
127
+ - 完整 tar 已含自建中间件归档,镜像架构与服务器一致;服务器启动不访问 registry。
128
+ - Linux 已安装 Docker/Podman、可执行的 Compose、sh、tar、sha256sum、awk、curl、util-linux 的 flock;用户有目录与容器权限。
129
+ - 包含私有配置,安全搬运,不提交 Git;持久化卷身份跨版本稳定。
130
+ - 实际断外网验证启动、登录、页面刷新、重启与数据持久化。生产 HTTPS、证书、DNS 和外部服务需另行准备。
97
131
 
98
- ## Node API
132
+ ### SDK
99
133
 
100
134
  ```ts
101
135
  import { deploy } from '@h-ai/deploy'
102
136
 
103
- const initialized = await deploy.init({
104
- provider: { type: 'docker-ssh', expose: { hostPort: 18080 } },
105
- })
137
+ const initialized = await deploy.init({ provider: { type: 'docker-ssh', expose: { hostPort: 18080 } } })
106
138
  if (!initialized.success)
107
139
  throw new Error(initialized.error.message)
108
140
  try {
109
- const result = await deploy.deployApp('./packages/my-app-serv', {
141
+ const result = await deploy.deployApp('./packages/demo-serv', {
110
142
  environment: 'prod',
111
143
  envDir: '.',
112
144
  bundleOnly: true,
113
145
  artifactDir: './.hai/build/docker/<buildId>',
114
- skipMiddleware: true,
146
+ runtime: 'auto',
115
147
  })
116
148
  if (!result.success)
117
149
  throw new Error(result.error.message)
118
- // result.data.bundlePath / instructions;packaged 不代表服务器 ready。
150
+ // result.data.bundlePath / instructions; packaged does not mean server ready.
119
151
  }
120
152
  finally {
121
153
  await deploy.close()
122
154
  }
123
155
  ```
124
156
 
125
- `deploy.scan` 与 `deploy.credentials` 不依赖 init;业务 API 返回 HaiResult,先检查 success。资源开通失败保留恢复信息,不自动删除已创建资源。
157
+ `selectedServices: ['web']` 选择局部更新,`onProgress` 返回打包与服务器执行阶段。`deploy.scan` 和 `deploy.credentials.*` 可在初始化前使用。先判断 HaiResult.success 再读取数据。Vercel 使用 prebuilt API 上传已有 .vercel/output,需要网络,不提供离线交付或 Compose 局部发布。
158
+
159
+ 命令执行按正确解码的 UTF-8 逻辑流脱敏,已知秘密即使跨 stdout/stderr 数据块也不会原样释放;成功捕获结果、错误与进度回调遵循同一边界。调用内部命令工具时用 `secrets` 明确列出敏感字面量;编码、截取或转换后的值须单独列入。每条流默认仅捕获最后 1 Mi 字符,`maxCaptureChars` 可设 1~16 Mi 字符,结果用 `stdoutTruncated` / `stderrTruncated` 标记截断,解析结构化输出前必须检查。`onOutput` 可异步,按序等待并保留 pipe 背压,收到的是全部安全 stdout;部署进度仅解析每行前 512 字符。秘密总长度最多 1 Mi UTF-16 单元,超限或脱敏异常失败关闭;超时表示命令执行失败,远程进程终止仍需确认。
160
+
161
+ 命令 deadline 包含 helper 启动和输出处理;超时、输入/输出或回调异常会终止受管理进程。POSIX 使用独立进程组,Windows 通过内嵌 PowerShell/.NET Job Object 先挂起创建、成功分配后恢复,禁止 breakaway;helper、编译或分配失败不会降级为裸进程。Windows 需要系统 PowerShell 和 Add-Type,调用隐藏窗口且不加载用户 profile,参数、环境与 UTF-8 管道保持原样。POSIX 命令须让后代继承进程组;已脱离组的系统守护进程及远端服务不在本地终止确认范围。
162
+
163
+ `terminationTimeoutMs` 是异常后的独立核验等待,默认 10000、最大 60000 毫秒。发送 kill 不等于确认;结果在受管理 Job/进程组归零后记录 `localTermination: 'confirmed'`,核验超时或失败为 `unknown`,尚未启动为 `not-started`。在 `if (!result.success)` 分支调用公开 `getDeployCommandState(result.error)` 读取原因和状态;不含原始异常或命令参数。已经进入的异步输出回调不能撤销,异常后不再交付新输出。
126
164
 
127
- ## 二次开发与验证
165
+ SSH 中断始终先记 `remoteExecution: 'unknown'`。docker-ssh SDK 用新连接只读核对 `result-<deploymentId>.json`,仅完整、未截断且 ID 匹配的终态记录可确认 `finished`,同时给出真实阶段及退出码;脚本结束不表示容器停止。即使终态退出码为 0,原 SSH/输出失败仍保留为失败,避免丢失回执被伪装成成功。CLI 分别显示本地和远端状态及结果路径;缺结果、旧 ID、损坏内容或重连失败均保持未知,不自动重跑发布或回滚。手工上传脚本在 SSH 失败时保守提示未知和核对目录,部署 ID 见包内 manifest。先核对步骤文件、`current`、真实服务与数据,再决定恢复。
128
166
 
129
- ```powershell
130
- pnpm.cmd --filter @h-ai/deploy typecheck
131
- pnpm.cmd --filter @h-ai/deploy lint
132
- pnpm.cmd --filter @h-ai/deploy build
133
- pnpm.cmd --filter @h-ai/deploy test
134
- pnpm.cmd --filter @h-ai/cli test
135
- pnpm.cmd --filter @h-ai/cli test:scaffold-gates
136
- pnpm.cmd --filter @h-ai/cli test:deploy-gates
167
+ ## 开发测试
168
+
169
+ ```sh
170
+ pnpm --filter @h-ai/deploy typecheck
171
+ pnpm --filter @h-ai/deploy lint
172
+ pnpm --filter @h-ai/deploy build
173
+ pnpm --filter @h-ai/deploy test
174
+ pnpm --filter @h-ai/deploy test:e2e
175
+ pnpm --filter @h-ai/cli test:scaffold-gates
176
+ pnpm --filter @h-ai/cli test:deploy-gates
137
177
  ```
138
178
 
139
- 最后一项使用当前源码打包生成 fullstack,显式构建一次 API/Web,再验证 SSH 上传和手工 tar 启动、真实 Web 登录/刷新。需要运行的 Podman/Docker 和 OpenSSH。测试用 finally/teardown 清理临时工程、镜像、容器、报告、截图和辅助进程,失败同样清理;验收材料不提交 Git。原生安装包需在相应系统另做真实构建及安装验收。
179
+ 真实部署按 API、SSR、静态移动前端和 fullstack 选取代表:admin 覆盖 website/h5 共用的 SSR Dockerfile 与生产部署分支;各应用类型仍由八个 scaffold 场景分别安装、编译并执行 HTTP/浏览器验收。scaffold 与生成路由 runtime 按实测资源争用分批顺序执行,三个轻型 CLI 门禁可与 runtime 在最多两个 worker 内并行;生成场景复用 E2E 自带完整构建,保留生产构建隔离及真实产物检查。CLI 部署门禁保留认证/传输、增量更新、损坏制品和回滚,并用生产同版本 Nginx 对公开部署包执行 `nginx -t`。测试夹具通过 init 回收孤儿进程,并验证真实 PID 消失。
180
+
181
+ 验证真实生成项目与 SSH/人工部署,覆盖禁止访问镜像仓库、局部更新、归档损坏、同版本发布和健康失败。finally/teardown 只清理本次测试创建的项目、镜像、容器、日志和进程。
182
+
183
+ `test:e2e` 构建当前包,并在子进程中启用、串行执行锁与进程两组真实 Linux 门禁,根 `pnpm e2e` 也包含此入口。需要可连接的 Podman(优先)或 Docker,运行公开 API 生成的 `apply.sh`,验证并发、父进程强杀后的子进程持锁、全部进程退出和重启恢复。Compose/HTTP 使用确定性夹具,不能替代真实容器服务部署验收。
184
+
185
+ 进程门禁运行 `pnpm --filter @h-ai/deploy exec vitest run tests/deploy-command-process.test.ts tests/deploy-secret-masker.test.ts`。设置同一 `HAI_DEPLOY_E2E=1` 后运行 `tests/deploy-command-runtime.e2e.test.ts`,先构建 deploy;它使用真实 Linux、OpenSSH 和发布 dist,验证远端继续执行/完成回执以及本地进程组。该夹具需联网安装 sshd;Compose/HTTP 为确定性夹具,发布 deadline 缩短为 2500 毫秒,并注入暂停消费模拟丢失回执。
140
186
 
141
- 部署门禁默认不传递本机代理。需要代理下载构建依赖时,设置测试专用 `HAI_DEPLOY_E2E_CONTAINER_PROXY`(默认不设置),例如 Windows Podman 在 PowerShell 中设置 `$env:HAI_DEPLOY_E2E_CONTAINER_PROXY='http://host.containers.internal:10808'`;地址必须能从构建容器访问,不写入应用配置或交付镜像。
187
+ 部署夹具先检查固定公开基础镜像,仅明确缺失时拉取,可沿用 HAI_DEPLOY_E2E_MIRROR;公共缓存不进入清理账本。成功 create 后先登记容器 ID,再在其私有 PID namespace 内异步安装 APT/pip 并启动 sshd。准备失败、超时或合作式取消会等待本地客户端退出,按已登记 ID 回收并确认消失;仅重型嵌套 Podman 夹具使用特权。HAI_DEPLOY_E2E_APT_MIRROR 只接受镜像站主机名并保留 HTTPS。拥有权测试用固定公开镜像的本轮私有 tag,保留同内容外部 tag、重指向保护、动态端口和外部 sentinel 检查。create 回执丢失且未获得可信 ID、测试进程被强制终止等情况仍需记录未知状态,不能按名称或 label 认领清理。
142
188
 
143
- 测试专用 `HAI_DEPLOY_E2E_CONTAINER_NETWORK` 默认不设置,指定后传给 build 的 `--network`;例如 rootless Podman 可设置 `$env:HAI_DEPLOY_E2E_CONTAINER_NETWORK='pasta'`。构建与运行的网络可能不同,应同时校对代理在构建网络中的地址。
189
+ ## 许可证
144
190
 
145
- Windows runner 可设置测试开关 HAI_CLI_NATIVE_BUILD=1(默认不设置),再执行 `pnpm --filter @h-ai/cli test:scaffold-gates -t "fullstack scaffold"`,额外构建生成项目的 Tauri 安装包,检查根版本 2.4.6 进入 exe 文件名。需已安装 Rust/MSVC/Windows SDK;测试的原生输出也随临时工程自动清理。
191
+ Apache-2.0