@h-ai/deploy 1.0.0-beta.2 → 1.0.0-beta.3

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.en.md CHANGED
@@ -22,14 +22,14 @@ Delivery roles are `serv` for a separate API backend, `web` for a static or SSR
22
22
 
23
23
  ```sh
24
24
  pnpm build
25
- pnpm build --target docker
26
- pnpm build --target docker --images web
25
+ pnpm build --target image
26
+ pnpm build --target image --images web
27
27
  pnpm build --target desktop --platform windows --arch x64
28
28
  ```
29
29
 
30
30
  #### Build options
31
31
 
32
- `--target` selects compile (default), docker, app or desktop. `--images serv|web|serv,web` selects images. `--runtime auto|podman|docker` selects the local engine; auto prefers reachable Podman. `--network` overrides build networking, `--no-http-proxy` disables inherited Podman proxies, and `--sqlite-prebuild-mirror` selects a trusted HTTP(S) binary mirror. Native `--platform` and `--arch` require the matching host SDK and Rust toolchain. Builds do not load deployment environments or runtime secrets.
32
+ `--target` selects compile (default), image, app or desktop. `--images serv|web|serv,web` selects images. `--runtime auto|podman|docker` selects the local engine; auto prefers reachable Podman. `--network` overrides build networking, `--no-http-proxy` disables inherited Podman proxies, and `--sqlite-prebuild-mirror` selects a trusted HTTP(S) binary mirror. Native `--platform` and `--arch` require the matching host SDK and Rust toolchain. Builds do not load deployment environments or runtime secrets.
33
33
 
34
34
  #### Describe packaging in hai.json
35
35
 
@@ -85,7 +85,7 @@ The SDK generates matching Nginx paths, runtime configuration URLs, SSR health c
85
85
 
86
86
  #### Prepare the environment and destination
87
87
 
88
- The default deployment name comes from `package.json.name` at the project root (the nearest `hai.json` or lockfile directory), rejecting missing or invalid names, with an explicit environment appended. A fullstack `hai-delivery` project scanned at `packages/hai-delivery-serv` with `--env test` generates `/opt/hai/apps/hai-delivery-test` and `bundle-hai-delivery-test-<deploymentId>-<version>.tar`. Remote directories, bundles, the Compose project, API/Web/middleware containers, data volumes, server credential state and incremental baselines share this name. Actual project names ending in `-serv` are preserved.
88
+ The default deployment name comes from `package.json.name` at the project root (the nearest `hai.json` or lockfile directory), rejecting missing or invalid names, with an explicit environment appended. A fullstack `hai-delivery` project scanned at `packages/hai-delivery-serv` with `--env test` generates `/opt/hai/apps/hai-delivery-test` and `bundle-hai-delivery-test-<deploymentId>-<version>.tar.gz`. Remote directories, bundles, the Compose project, API/Web/middleware containers, data volumes, server credential state and incremental baselines share this name. Actual project names ending in `-serv` are preserved.
89
89
 
90
90
  Artifact formats 4 and 5 record primaryService (serv/web); hasWeb describes the complete browser topology. Format 5 also records a non-root webBasePath. Only serv + web generates a same-origin API proxy. Rebuild artifacts using formats 1–3. Back up the existing deployment and verify project identity, volumes and managed credentials before establishing a new full deployment baseline. Tools do not migrate data across projects. Full deployments still remove obsolete service containers under the same Compose project; editing deploy-target.json.directory alone does not migrate resources.
91
91
 
@@ -116,6 +116,8 @@ SSH settings are saved in `.env.<environment>.local`. `--bundle-only` offers to
116
116
 
117
117
  Runtime lockfile dependencies and configuration determine middleware. Choose defaults, custom connections or keep settings. With complete target-environment settings, `--yes` preserves values without asking again or provisioning cloud resources again; interactive mode still lets you select the scope. --skip-middleware skips configuration/provisioning. SQLite, memory cache and local storage need no external images. Complete bundles automatically include self-hosted middleware images. External services still require reachable destinations; cloud provisioning is not an offline operation.
118
118
 
119
+ Default and custom modes preserve effective settings for the same backend, including pool, operation log, public storage URLs/prefixes, Redis retry and vector search options absent from the form. Managed services replace connection selectors and credentials; old URLs, TLS/cluster modes and credentials cannot override the new service. SMTP senders and LDAP attribute mappings remain. Switching backends clears incompatible fields. Missing fields already saved in parent JSON need recovery from the original configuration or backup: restore `publicEndpoint` in the highest-priority `HAI_STORAGE`, then rebuild and deploy the bundle. Editing YAML or leaf variables hidden by that parent JSON cannot repair an old bundle.
120
+
119
121
  #### Direct private object storage access
120
122
 
121
123
  Self-hosted S3 uses an internal endpoint. Set `_storage.yml` publicEndpoint to an external address such as `https://app.example.com/delivery/files`. Nginx exposes `/delivery/files/<bucket>/...`, removes the proxy directory and forwards the original bucket URI, signature query and Host directly to storage:9000. Files bypass Serv; omitting the directory retains root bucket routes. A route may sit under the website directory; API, asset and health conflicts fail packaging. Storage ports/consoles remain private, buffering/signature logs are disabled, and S3 handles authorization and cross-origin CORS. External ingress must retain the full entry path and Host so only the SDK gateway removes the prefix.
@@ -148,7 +150,7 @@ S3 `createBucket` defaults to `false`. Self-hosted S3 deployments set `HAI_STORA
148
150
  pnpm run deploy --env prod --build --bundle-only
149
151
  ```
150
152
 
151
- Each delivery creates one `.hai/deploy/<environment>/bundle-<project>-<deploymentId>-<version>/` directory containing only the tar, deploy.sh, deploy.ps1, deploy-target.json, read-target.awk, deploy-local.sh and README.txt. Compose, runtime environments, web.conf and manifest.json remain inside the tar; the external README also records the deployment ID. Copy that directory as a unit. Explicit `--output release-prod.tar` produces `release-prod-<version>/release-prod-<version>.tar` relative to the command directory, with all companion files alongside it. The tar includes selected application/middleware images, configurations, private environment files, checksums, README and apply.sh. Before running either upload script, open `deploy-target.json` in the operation directory and use its per-field descriptions to edit and check the server, SSH user and port, private key and known-hosts files, remote directory and other settings. Run deploy.ps1 on Windows or deploy.sh on Linux/macOS; both scripts read this JSON to upload over SSH and execute the same server workflow. Running deploy.sh locally requires sh, awk, ssh and scp. Use `sh deploy.sh HOST [PORT] [USER]` to override the connection address, or pass `-IdentityFile` to deploy.ps1 to override its private key. Both scripts support HAI_DEPLOY_IDENTITY_FILE and HAI_DEPLOY_KNOWN_HOSTS_FILE to override key and known-hosts paths. After moving to another computer or operating system, update these paths to files on that computer.
153
+ Each delivery creates one `.hai/deploy/<environment>/bundle-<project>-<deploymentId>-<version>/` directory containing only the tar, deploy.sh, deploy.ps1, deploy-target.json, read-target.awk, deploy-local.sh and README.txt. Compose, runtime environments, web.conf and manifest.json remain inside the tar; the external README also records the deployment ID. Copy that directory as a unit. Explicit `--output release-prod.tar` produces `release-prod-<version>/release-prod-<version>.tar.gz` relative to the command directory, with all companion files alongside it. The tar includes selected application/middleware images, configurations, private environment files, checksums, README and apply.sh. Before running either upload script, open `deploy-target.json` in the operation directory and use its per-field descriptions to edit and check the server, SSH user and port, private key and known-hosts files, remote directory and other settings. Run deploy.ps1 on Windows or deploy.sh on Linux/macOS; both scripts read this JSON to upload over SSH and execute the same server workflow. Running deploy.sh locally requires sh, awk, ssh and scp. Use `sh deploy.sh HOST [PORT] [USER]` to override the connection address, or pass `-IdentityFile` to deploy.ps1 to override its private key. Both scripts support HAI_DEPLOY_IDENTITY_FILE and HAI_DEPLOY_KNOWN_HOSTS_FILE to override key and known-hosts paths. After moving to another computer or operating system, update these paths to files on that computer.
152
154
 
153
155
  Offline upload supports key and password authentication. `deploy-target.json` stores only the authentication choice and `passwordEnv` name (default `HAI_DEPLOY_PROVIDER_SSH_PASSWORD`), never the password value. Supply the password in the operator machine environment; it reaches only the current SSH/SCP child processes, not application environments, the tar or manifest. Windows can also read the persisted user environment. Windows needs PowerShell 5+ and OpenSSH; Linux/macOS uses sh, awk, ssh and scp. Neither uploader needs Node/pnpm. Target edits apply only to that delivery directory and never write back to the project; new deliveries use the saved project environment.
154
156
 
@@ -188,6 +190,16 @@ After the server passes local health checks and commits `current`, the SDK separ
188
190
 
189
191
  Complete bundles include self-hosted middleware archives and matching architecture; server startup never pulls images. Linux needs Docker/Podman, Docker Compose v2 or podman-compose 1.6.0+ (supports --pull never), sh, tar, sha256sum, awk, curl, util-linux flock and filesystem/container permissions. Protect private bundle contents. Keep volume identities stable across versions. Verify actual offline startup, login, refresh, restart and persistence. HTTPS, certificates, DNS and external services need separate preparation.
190
192
 
193
+ ### Build and packaging improvements
194
+
195
+ Image builds run at most two images concurrently by default; use `--concurrency 1` to reduce resource use. Dependency installation precedes source copies, and each side copies only its required source. Runtime images omit application declaration files, source maps and README files. Packaging validates and reuses middleware archives by image content ID and hashes application archives while copying. Delivery defaults to `.tar.gz`; explicit `--output release.tar` keeps an uncompressed archive. README is only outside the archive. Successful deployment removes its incoming upload/extraction, retains recovery configuration in releases, and keeps images in the container engine.
196
+
197
+ ### Optional HTTPS certificates
198
+
199
+ `pnpm run deploy` offers no HTTPS, existing PEM certificate/key paths, or Let’s Encrypt for every container template. Relative paths resolve from the project root. Certificates are checked for domain, validity and key matching, then copied into private releases. A pinned official Certbot image handles issuance and renewal checks every 12 hours, with automatic Nginx reload. It requires terms acceptance, a real email, public DNS, port 80 and CA connectivity. Isolated servers use existing certificates. Certificate state stays on the server; keys stay out of application images. `--yes` reuses saved settings.
200
+
201
+ Configure `hai.json.deploy.provider.expose.https` or private environment overrides. Defaults are HTTPS 443 and HTTP redirection; omission retains HTTP. Enabling/disabling HTTPS or changing mode, domain, ports or supplied certificates requires a full deployment. See the [HTTPS guide](../../docs/usage/configuration/delivery.md#可选https证书). Plans use `root` for the project and `serviceDir` for the service source/configuration directory, exposing only public certificate metadata.
202
+
191
203
  ### SDK
192
204
 
193
205
  ```ts
@@ -225,6 +237,8 @@ SSH interruptions initially report `remoteExecution: 'unknown'`. The docker-ssh
225
237
 
226
238
  ### Runtime environment files and repeated deployments
227
239
 
240
+ Node backends (Serv / SvelteKit SSR) reread the release `.env.runtime` on every start by default. Edit it, then run `docker restart <container>` or `podman restart <container>`; no image rebuild or extra override is needed. Existing deployments need one deployment with the new CLI. Removed settings do not survive through stale Compose injection. Managed connections in `middleware.env` retain precedence; private proxy identity and Node startup options remain deployment settings. Static Web, middleware, ports/mounts and Nginx limits keep their existing update flow. Back up manual edits and copy permanent changes into the source environment; the next backend deployment regenerates the file.
241
+
228
242
  Persisted framework configuration uses `HAI_*` names. Native `POSTGRES_*`, `MYSQL_*`, `RUSTFS_*`, `MINIO_*`, and `LDAP_*` variables keep the names required by middleware images, but are generated only in private `middleware-runtime.env`, loaded by middleware containers. They are no longer saved to project environment files or the application's `.env.runtime`. The `middleware.env` retained for incremental application updates contains framework `HAI_*` settings only. Previously saved native entries are not deleted automatically and are excluded from new application environments. Existing services keep their original release files when a new bundle is deployed incrementally.
229
243
 
230
244
  Whole `HAI_*` configuration supplied by sidecars or provisioners clears superseded leaf variables: Redis overrides no longer leave `HAI_CACHE_TYPE=memory`. Global `~/.hai/credentials.yml` fills missing settings only, respecting existing environment/process keys and parent objects. Deployment starts selected middleware and waits for PostgreSQL/MySQL/Redis/S3 health before serv/Web; incremental deployment and recovery use the same order and preserve unselected containers.
@@ -243,7 +257,7 @@ The server persists managed middleware passwords in `<remoteDir>/managed-secrets
243
257
 
244
258
  ## Read-only deployment plan
245
259
 
246
- `createDeploymentPlan(appDir, config, options)` initializes no provider, generates no credentials/output and connects to no target. It shares real scanning, environment resolution, resource naming and artifact checksums, reporting versions, build ID, selected/retained services, source files/key names, baseline requirements and recovery paths without values or SSH credentials. Unread remote current is not-inspected; execution still strictly requires a successful baseline for partial updates. Compose recovery does not undo database changes/migrations. CLI: `hai deploy --env prod --plan`.
260
+ `createDeploymentPlan(appDir, config, options)` initializes no provider, generates no credentials/output and connects to no target. It shares real scanning, environment resolution, resource naming and artifact checksums, reporting project `root`, service source/configuration `serviceDir`, versions, build ID, selected/retained services, source files/key names, baseline requirements and recovery paths without values or SSH credentials. Unread remote current is not-inspected; execution still strictly requires a successful baseline for partial updates. Compose recovery does not undo database changes/migrations. CLI: `hai deploy --env prod --plan`.
247
261
 
248
262
  Use `--plan --inspect-remote` (SDK inspectRemote:true) to read current and the successful release directory over SSH. It reports verified/missing/unavailable and the actual recovery release ID without target writes.
249
263
 
package/README.ja.md CHANGED
@@ -22,14 +22,14 @@
22
22
 
23
23
  ```sh
24
24
  pnpm build
25
- pnpm build --target docker
26
- pnpm build --target docker --images web
25
+ pnpm build --target image
26
+ pnpm build --target image --images web
27
27
  pnpm build --target desktop --platform windows --arch x64
28
28
  ```
29
29
 
30
30
  #### build のオプション
31
31
 
32
- `--target` は compile(既定)、docker、app、desktop を選択します。`--images serv|web|serv,web` は対象イメージを、`--runtime auto|podman|docker` はローカルエンジンを選択します。auto は接続可能な Podman を優先します。`--network` は構築ネットワーク、`--no-http-proxy` は Podman のプロキシ継承無効、`--sqlite-prebuild-mirror` は信頼できる HTTP(S) バイナリ配布元です。ネイティブ配布物には対象 OS の SDK と Rust ツールチェーンが必要です。構築時にデプロイ環境や秘密情報を読み込みません。
32
+ `--target` は compile(既定)、image、app、desktop を選択します。`--images serv|web|serv,web` は対象イメージを、`--runtime auto|podman|docker` はローカルエンジンを選択します。auto は接続可能な Podman を優先します。`--network` は構築ネットワーク、`--no-http-proxy` は Podman のプロキシ継承無効、`--sqlite-prebuild-mirror` は信頼できる HTTP(S) バイナリ配布元です。ネイティブ配布物には対象 OS の SDK と Rust ツールチェーンが必要です。構築時にデプロイ環境や秘密情報を読み込みません。
33
33
 
34
34
  #### hai.json でパッケージの構成を指定する
35
35
 
@@ -85,7 +85,7 @@ SDKは対応するNginx、実行時設定URL、SSRヘルスチェック、公開
85
85
 
86
86
  #### 環境と接続先を用意する
87
87
 
88
- 既定の配置名はプロジェクトルート(最も近い `hai.json` またはロックファイルのディレクトリ)の `package.json.name` を使い、未指定または無効な名前はエラーになります。明示した環境名を末尾に追加します。fullstack の `hai-delivery` で `packages/hai-delivery-serv` を走査しても、`--env test` は `/opt/hai/apps/hai-delivery-test` と `bundle-hai-delivery-test-<配置ID>-<版>.tar` を生成します。接続先ディレクトリ、配布物、Compose プロジェクト、API/Web/ミドルウェアのコンテナー、ボリューム、サーバー認証状態のファイル名、増分基準で同じ名前を使います。実際のプロジェクト名に含まれる `-serv` は削除しません。
88
+ 既定の配置名はプロジェクトルート(最も近い `hai.json` またはロックファイルのディレクトリ)の `package.json.name` を使い、未指定または無効な名前はエラーになります。明示した環境名を末尾に追加します。fullstack の `hai-delivery` で `packages/hai-delivery-serv` を走査しても、`--env test` は `/opt/hai/apps/hai-delivery-test` と `bundle-hai-delivery-test-<配置ID>-<版>.tar.gz` を生成します。接続先ディレクトリ、配布物、Compose プロジェクト、API/Web/ミドルウェアのコンテナー、ボリューム、サーバー認証状態のファイル名、増分基準で同じ名前を使います。実際のプロジェクト名に含まれる `-serv` は削除しません。
89
89
 
90
90
  成果物形式4・5はprimaryService(serv/web)と、工程全体のブラウザー構成を示すhasWebを記録します。形式5には非ルートのwebBasePathも含まれます。同一オリジンAPIプロキシはserv + webの場合だけ生成します。形式1~3は再構築が必要です。既存デプロイをバックアップし、プロジェクト名・ボリューム・管理対象の認証情報を確認してから、新しい完全デプロイの基準を作成してください。プロジェクト間のデータ移行は自動化しません。完全デプロイでは同じComposeプロジェクトの旧サービスコンテナーを削除するため、deploy-target.json.directoryの変更だけでは移行できません。
91
91
 
@@ -114,6 +114,8 @@ SSH 設定は選択環境の `.env.<環境>.local` に保存します。`--bundl
114
114
 
115
115
  実行依存と設定に基づき、既定、自分の接続、設定維持を選びます。対象環境の設定が完全なら、`--yes` は値を保持し、再入力やクラウド資源の再作成を行いません。対話モードでは今回の処理範囲を選べます。--skip-middleware は設定と開通を省略します。SQLite、メモリーキャッシュ、ローカル保存には外部イメージがありません。完全配布物には自前ミドルウェアのイメージも自動で含めます。外部サービスは接続先への到達性が必要で、クラウド開通はオフライン処理ではありません。
116
116
 
117
+ 既定とカスタムは同じバックエンドの有効な設定を保持します。フォームにない接続プール、操作ログ、ストレージ公開 URL/接頭辞、Redis 再試行、ベクトル検索設定も対象です。自前構成とクラウド開通は接続先・認証だけを更新し、古い URL、TLS/クラスター設定、認証情報を引き継ぎません。SMTP 送信元と LDAP 属性対応は保持します。バックエンド変更時は非互換項目を除去します。旧版が親 JSON から削除した項目は自動復元できません。元の設定やバックアップから最優先の `HAI_STORAGE` に `publicEndpoint` などを戻して配布物を再作成してください。親 JSON に隠された YAML や末端変数だけの変更では直りません。
118
+
117
119
  #### 非公開オブジェクトストレージへの直接アクセス
118
120
 
119
121
  自前 S3 の endpoint は内部接続先です。publicEndpoint に `https://app.example.com/delivery/files` などの外部アドレスを設定できます。Nginx は `/delivery/files/<bucket>/...` を受け、入口パスだけを除去し、本来のバケット URI、署名クエリ、Host を保持して storage:9000 に転送します。Serv を経由せず、パス省略時は従来のルートバケット形式です。サイト配下に置けますが API、資源、ヘルスパスの競合は拒否します。ポートと管理コンソールは非公開、バッファリングと署名ログは無効です。認可と CORS は S3 に設定し、外部プロキシは入口パスと Host を保持してください。
@@ -146,7 +148,7 @@ S3 の `createBucket` は既定で `false` です。自前 S3 には `HAI_STORAG
146
148
  pnpm run deploy --env prod --build --bundle-only
147
149
  ```
148
150
 
149
- 毎回 `.hai/deploy/<環境>/bundle-<プロジェクト>-<配置ID>-<版>/` を一つ生成し、外側には tar、deploy.sh、deploy.ps1、deploy-target.json、read-target.awk、deploy-local.sh、README.txt だけを保存します。Compose、実行環境、web.conf、manifest.json は tar 内に置き、外側の README にも配置 ID を記録します。ディレクトリ全体をコピーしてください。`--output release-prod.tar` は作業ディレクトリを基準に `release-prod-<版>/release-prod-<版>.tar` を生成し、付属ファイルも同じ場所に置きます。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 による鍵・指紋ファイルのパスの上書きも可能です。別の PC・OS に移す場合、これらのパスを移動先の実ファイルに合わせます。
151
+ 毎回 `.hai/deploy/<環境>/bundle-<プロジェクト>-<配置ID>-<版>/` を一つ生成し、外側には tar、deploy.sh、deploy.ps1、deploy-target.json、read-target.awk、deploy-local.sh、README.txt だけを保存します。Compose、実行環境、web.conf、manifest.json は tar 内に置き、外側の README にも配置 ID を記録します。ディレクトリ全体をコピーしてください。`--output release-prod.tar` は作業ディレクトリを基準に `release-prod-<版>/release-prod-<版>.tar.gz` を生成し、付属ファイルも同じ場所に置きます。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 による鍵・指紋ファイルのパスの上書きも可能です。別の PC・OS に移す場合、これらのパスを移動先の実ファイルに合わせます。
150
152
 
151
153
  オフラインアップロードは鍵とパスワードの両方に対応します。`deploy-target.json` に保存するのは認証方式と `passwordEnv` 名(既定 `HAI_DEPLOY_PROVIDER_SSH_PASSWORD`)だけで、パスワード値は含めません。操作する PC の環境から与え、今回の SSH/SCP 子プロセスだけに渡します。アプリ環境、tar、manifest には入りません。Windows では永続化したユーザー環境も参照できます。Windows は PowerShell 5+ と OpenSSH、Linux/macOS は sh、awk、ssh、scp が必要で、どちらも Node/pnpm は不要です。target の変更はその配布ディレクトリだけに適用し、プロジェクトには書き戻しません。新しい配布物は保存済みの環境設定を使います。
152
154
 
@@ -186,6 +188,16 @@ SDK はサーバー内の正常性確認と `current` 更新後、操作端末
186
188
 
187
189
  完全 tar は自前ミドルウェアのアーカイブと一致するアーキテクチャを含み、起動時にレジストリーへ接続しません。Linux に Docker/Podman、Docker Compose v2 または podman-compose 1.6.0 以降、sh、tar、sha256sum、awk、curl、util-linux の flock、ディレクトリ・コンテナー権限が必要です。秘密設定を安全に転送し、版間でボリューム識別を保持します。外部ネットワークを切って起動、ログイン、再読込、再起動と永続化を検証します。HTTPS、証明書、DNS、外部サービスは別途用意します。
188
190
 
191
+ ### 構築と配布の最適化
192
+
193
+ イメージは既定で最大 2 件を並列構築し、`--concurrency 1` で資源使用量を抑えます。ソースの前に依存をインストールし、各端で必要なソースだけをコピーします。実行イメージからアプリの型宣言・source map・README を除去します。ミドルウェアのアーカイブはイメージ内容 ID で検証・再利用し、コピーと同時にハッシュを計算します。配布の既定は `.tar.gz`、明示的な `--output release.tar` は非圧縮です。README は外側だけに保持します。成功後に今回の incoming アップロードと展開先を削除し、release に復旧設定、エンジンにイメージを保持します。
194
+
195
+ ### 任意の HTTPS 証明書
196
+
197
+ `pnpm run deploy` は全コンテナーテンプレートで HTTPS なし、既存 PEM 証明書・秘密鍵、Let’s Encrypt を選択できます。相対パスは工程ルート基準です。ドメイン・期限・鍵一致を検証して配布物にコピーします。版を固定した公式 Certbot イメージで申請し、12 時間ごとに更新を確認して Nginx を再読み込みします。利用規約の承認、実際のメール、公的 DNS、80 番ポート、CA 接続が必要です。隔離環境では既存証明書を使います。状態はサーバーに保持し、秘密鍵をアプリイメージに含めません。`--yes` は保存設定を使います。
198
+
199
+ `hai.json.deploy.provider.expose.https` または環境別秘密設定で指定します。既定は HTTPS 443 と HTTP リダイレクト、省略時は HTTP です。有効/無効・モード・ドメイン・ポート・持込証明書の変更は完全デプロイが必要です。[HTTPS 設定](../../docs/usage/configuration/delivery.md#可选https证书)を参照してください。計画では `root` が工程ルート、`serviceDir` がサービスのソース・設定ディレクトリです。
200
+
189
201
  ### SDK
190
202
 
191
203
  ```ts
@@ -223,6 +235,8 @@ SSH 中断は最初に `remoteExecution: 'unknown'` とし、docker-ssh SDK は
223
235
 
224
236
  ### 実行環境ファイルと設定の再利用
225
237
 
238
+ Node バックエンド(Serv / SvelteKit SSR)は起動ごとに配布先の `.env.runtime` を既定で読み直します。編集後に `docker restart <コンテナー>` または `podman restart <コンテナー>` を実行し、イメージ再構築や追加 override は不要です。既存配置は新 CLI で一度デプロイしてください。削除した設定を旧 Compose 環境から復活させず、管理接続の `middleware.env` を優先します。私有プロキシ識別と Node 起動引数は配置設定のままです。静的 Web・ミドルウェア・ポート/マウント・Nginx 制限は従来手順で更新します。手動変更をバックアップし、継続する値は元の環境にも反映してください。次のバックエンド配置時に再生成されます。
239
+
226
240
  フレームワークの永続設定は `HAI_*` を使います。`POSTGRES_*`、`MYSQL_*`、`RUSTFS_*`、`MINIO_*`、`LDAP_*` はミドルウェアイメージ必須の名前を維持し、非公開の `middleware-runtime.env` にだけ生成してミドルウェアに渡します。プロジェクト環境ファイルやアプリの `.env.runtime` には新規保存しません。増分アプリ更新用の `middleware.env` は `HAI_*` 設定のみです。既存の原生変数は自動削除せず、新しいアプリ環境から除外します。未更新サービスは以前の配布ファイルを使い続けます。
227
241
 
228
242
  サイドカーやクラウドの `HAI_*` オブジェクトで置換した古い子変数は除去し、Redis 選択後に `HAI_CACHE_TYPE=memory` を残しません。グローバル `~/.hai/credentials.yml` は不足値のみ補い、環境・プロセスの同名キーや親設定を上書きしません。選択したミドルウェアを起動し PostgreSQL/MySQL/Redis/S3 の正常性を確認後、serv/Web を起動します。増分と復旧も同じ順序で、未選択のコンテナーは維持します。
@@ -233,7 +247,7 @@ Podman では設定済みの healthcheck を明示的に実行して状態を再
233
247
 
234
248
  作成時に `.env.dev/.env.test/.env.prod` ごとに別の初期管理者パスワードと転送鍵リングを生成し、Git に保存します。クローン後も環境ごとに共有し、ローカル秘密ファイルやプロセス環境で上書きできます。transport 有効アプリの `HAI_CRYPTO_TRANSPORT_SERVERKEYRING` が不足・不正ならアップロード前に設定元を案内します。同じ環境の全ノードは環境ファイルまたは Secret Manager の上書きから同じ有効リングを読み込み、ノードごとに生成しないでください。CLI 完了時はログレベルによらず `HAI_ADMIN_DEFAULT_PASSWORD` の実際の保存元を案内し、値は表示しません。初回管理者作成時だけ有効です。 オフライン入口でも、サーバーの非公開 `.env.runtime` 内のパスワード変数の場所を案内します。
235
249
 
236
- 配布ごとに Compose 用の `.hai/deploy/<環境>/bundle-.../.env.runtime` を生成します。これは生成物であり、設定の保存元ではありません。`NODE_ENV` の既定値は対象環境に従い、`test` は `test`、`dev/development` は `development`、`prod/production` とその他は `production` です。環境設定の明示的な `NODE_ENV` が優先され、構築の最適化モードとは別です。旧配布物は再度パッケージ化してください。
250
+ 配布ごとに起動時読み込み用の `.hai/deploy/<環境>/bundle-.../.env.runtime` を生成します。これは生成物であり、設定の保存元ではありません。`NODE_ENV` の既定値は対象環境に従い、`test` は `test`、`dev/development` は `development`、`prod/production` とその他は `production` です。環境設定の明示的な `NODE_ENV` が優先され、構築の最適化モードとは別です。旧配布物は再度パッケージ化してください。
237
251
 
238
252
  プロジェクトルートの `hai.json.deploy` に配置構造、`.env.<環境>` に公開設定、`.env.<環境>.local` に SSH とミドルウェアの認証情報を保存します。環境ファイルは既定でプロジェクト/workspace ルート、変更時は `--env-dir` を指定します。設定後は `pnpm run deploy --env test --build --bundle-only --yes` で再入力を省略でき、ミドルウェア設定を保持する場合は `--skip-middleware` を追加します。対話式 bundle-only は最後に SSH 設定の保存・変更を確認します。配布物の `deploy-target.json` の編集はプロジェクトに反映されません。
239
253
 
@@ -241,7 +255,7 @@ Podman では設定済みの healthcheck を明示的に実行して状態を再
241
255
 
242
256
  ## 読み取り専用デプロイ計画
243
257
 
244
- `createDeploymentPlan(appDir, config, options)` は Provider 初期化、認証情報・出力作成、対象への接続を行いません。実際の走査、環境解決、命名と checksum 検証を共用し、版、ビルド ID、更新・保持範囲、設定元とキー名、基準要件と復旧先を返します。値と SSH 認証情報を除外します。未読の current は not-inspected とし、部分更新は実行時に成功基準を厳密に要求します。Compose 復旧は DB データや移行を戻しません。CLI は `hai deploy --env prod --plan`。
258
+ `createDeploymentPlan(appDir, config, options)` は Provider 初期化、認証情報・出力作成、対象への接続を行いません。実際の走査、環境解決、命名と checksum 検証を共用し、工程 `root`、サービスのソース・設定 `serviceDir`、版、ビルド ID、更新・保持範囲、設定元とキー名、基準要件と復旧先を返します。値と SSH 認証情報を除外します。未読の current は not-inspected とし、部分更新は実行時に成功基準を厳密に要求します。Compose 復旧は DB データや移行を戻しません。CLI は `hai deploy --env prod --plan`。
245
259
 
246
260
  `--plan --inspect-remote`(SDK inspectRemote:true)で SSH 経由の current と成功ディレクトリーを読み取り、verified/missing/unavailable と復旧リリース ID を示します。対象への書込は行いません。
247
261
 
package/README.md CHANGED
@@ -22,18 +22,18 @@
22
22
 
23
23
  ```sh
24
24
  pnpm build
25
- pnpm build --target docker
26
- pnpm build --target docker --images web
25
+ pnpm build --target image
26
+ pnpm build --target image --images web
27
27
  pnpm build --target desktop --platform windows --arch x64
28
28
  ```
29
29
 
30
- 源码编译、镜像构建、桌面打包分别对应 compile/docker/desktop。构建不选择部署环境、不注入运行期密钥。原生安装包在对应操作系统准备 SDK 与 Rust 工具链后构建。
30
+ 源码编译、镜像构建、桌面打包分别对应 compile/image/desktop。构建不选择部署环境、不注入运行期密钥。原生安装包在对应操作系统准备 SDK 与 Rust 工具链后构建。
31
31
 
32
32
  #### build 的可选参数
33
33
 
34
34
  | 参数 | 用途 |
35
35
  | -------------------------- | ----------------------------------------- |
36
- | `--target` | compile(默认)、docker、desktop |
36
+ | `--target` | compile(默认)、image、app、desktop |
37
37
  | `--images` | serv、web 或 serv,web;只构建导出所选镜像 |
38
38
  | `--runtime` | auto(优先 Podman)、podman、docker |
39
39
  | `--network` | 容器构建网络,默认引擎设置 |
@@ -98,7 +98,7 @@ SDK据制品生成Nginx目录、运行配置入口、SSR健康路径和返回URL
98
98
 
99
99
  #### 先准备环境与部署目标
100
100
 
101
- 默认部署名取工程根(最近的 `hai.json` 或锁文件所在目录)的 `package.json.name`,缺失时报错,再附加显式环境后缀。例如 fullstack 工程 `hai-delivery` 扫描 `packages/hai-delivery-serv`,`--env test` 生成 `/opt/hai/apps/hai-delivery-test` 和 `bundle-hai-delivery-test-<部署ID>-<版本>.tar`。远端目录、交付包、Compose 项目、API/Web/中间件容器、数据卷、服务器凭据状态及增量基线统一使用该名称;不会按字符串删除真实项目名中的 `-serv`。
101
+ 默认部署名取工程根(最近的 `hai.json` 或锁文件所在目录)的 `package.json.name`,缺失时报错,再附加显式环境后缀。例如 fullstack 工程 `hai-delivery` 扫描 `packages/hai-delivery-serv`,`--env test` 生成 `/opt/hai/apps/hai-delivery-test` 和 `bundle-hai-delivery-test-<部署ID>-<版本>.tar.gz`。远端目录、交付包、Compose 项目、API/Web/中间件容器、数据卷、服务器凭据状态及增量基线统一使用该名称;不会按字符串删除真实项目名中的 `-serv`。
102
102
 
103
103
  制品格式4和5都记录primaryService(serv/web),hasWeb表示完整工程是否包含浏览器端;格式5另记录非根目录webBasePath。只有serv + web才生成同源API代理。格式1至3制品须重新构建。旧部署先备份并核对项目名、数据卷和受管凭据,再切换到新的完整部署基线;工具不自动跨项目迁移数据。完整部署仍会按Compose规则清理同项目的旧服务容器,不能仅修改deploy-target.json.directory完成迁移。
104
104
 
@@ -127,6 +127,8 @@ SSH 参数保存到选中环境的 `.env.<环境>.local`。`--bundle-only` 在
127
127
 
128
128
  根据锁文件运行依赖和模块配置识别数据库、缓存、文件存储、向量库、LDAP、邮件等。逐项选择默认、自定义或保持。目标环境已有完整配置时,`--yes` 保持原值,不重新输入或重复云开通;交互模式仍可选择本次处理范围。配置完备可用 `--skip-middleware` 跳过向导和开通。SQLite、Memory cache、本地存储不产生外部镜像;Compose 自建服务的镜像自动加入完整交付包。外部服务仍需目标网络可达,云开通不属于完全离线部署。
129
129
 
130
+ 默认和自定义模式均保留同后端的有效配置,包括表单未展示的连接池、操作日志、存储公开端点/前缀、Redis 重试和向量检索选项。自建与云开通仅重新定稿实际连接和认证;旧 URL、TLS/集群模式与凭据不能覆盖新服务,SMTP 发件人和 LDAP 属性映射继续保留。切换后端时清除不兼容字段。旧向导已保存的缺字段父 JSON 不会自动恢复:须从原配置或备份补回最高优先级 `HAI_STORAGE` 中的 `publicEndpoint` 等字段,再重新打包部署;只改被父 JSON 覆盖的 YAML/叶变量无效。
131
+
130
132
  #### 私有对象存储直连
131
133
 
132
134
  自建 S3 的 `endpoint` 使用 Compose 内部地址,浏览器地址用 `_storage.yml` 的 `publicEndpoint` 配置,例如 `https://app.example.com/delivery/files`。Nginx 对外开放 `/delivery/files/<bucket>/...`,去掉 `/delivery/files` 后将原始桶 URI、签名查询与 Host 直接转发 storage:9000,文件不经过 Serv;省略目录时继续使用根桶路径。入口可位于网站目录内,API/资源/健康路径冲突会在打包前失败。存储端口与控制台不公开,对象缓冲和签名日志关闭;S3 负责私有桶授权和跨源 CORS。外层代理须保留完整入口路径与 Host,只由 SDK 网关去掉前缀。
@@ -169,7 +171,7 @@ S3 的 `createBucket` 默认为 `false`;部署器为自建 S3 注入 `HAI_STOR
169
171
  pnpm run deploy --env prod --build --bundle-only
170
172
  ```
171
173
 
172
- 每次交付只生成一个 `.hai/deploy/<环境>/bundle-<项目>-<部署ID>-<版本>/` 目录,外层仅保留 tar、deploy.sh、deploy.ps1、deploy-target.json、read-target.awk、deploy-local.sh 和 README.txt;Compose、运行环境、web.conf 和 manifest.json 仅保存在 tar 内,README 同时记录部署 ID,直接整体复制即可。显式 `--output release-prod.tar` 按命令执行目录生成 `release-prod-<版本>/release-prod-<版本>.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 覆盖私钥及指纹文件路径。换电脑或操作系统后,这些路径必须指向当前电脑上的实际文件。
174
+ 每次交付只生成一个 `.hai/deploy/<环境>/bundle-<项目>-<部署ID>-<版本>/` 目录,外层仅保留 tar、deploy.sh、deploy.ps1、deploy-target.json、read-target.awk、deploy-local.sh 和 README.txt;Compose、运行环境、web.conf 和 manifest.json 仅保存在 tar 内,README 同时记录部署 ID,直接整体复制即可。显式 `--output release-prod.tar` 按命令执行目录生成 `release-prod-<版本>/release-prod-<版本>.tar.gz`,其他文件仍在同一交付目录。包包含所选应用/中间件镜像、服务配置、私有环境、校验和、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 覆盖私钥及指纹文件路径。换电脑或操作系统后,这些路径必须指向当前电脑上的实际文件。
173
175
 
174
176
  离线上传支持密钥和密码认证。`deploy-target.json` 只保存认证方式及 `passwordEnv` 名称(默认 `HAI_DEPLOY_PROVIDER_SSH_PASSWORD`),不保存密码值。密码由操作机环境提供,仅传给本次 SSH/SCP 子进程,不进入应用环境、tar 或 manifest;Windows 也可读取用户持久环境。Windows 入口要求 PowerShell 5+ 和 OpenSSH,Linux/macOS 使用 sh、awk、ssh、scp,两者均不需要 Node/pnpm。修改 target 只影响当前交付目录,不回写工程;新交付仍使用工程保存的环境配置。
175
177
 
@@ -212,6 +214,16 @@ SDK 在服务器本地健康通过并提交 `current` 后,还从操作机检
212
214
  - 包含私有配置,安全搬运,不提交 Git;持久化卷身份跨版本稳定。
213
215
  - 实际断外网验证启动、登录、页面刷新、重启与数据持久化。生产 HTTPS、证书、DNS 和外部服务需另行准备。
214
216
 
217
+ ### 构建与交付优化
218
+
219
+ 镜像默认最多并行两项,`--concurrency 1` 可降低资源占用。Dockerfile 在复制源码前安装依赖;前后端仅复制各自必要源码,复用安装层和驱动缓存。运行镜像移除应用自身的声明文件、源码映射和说明文件。打包按镜像内容 ID 复用并校验中间件归档,复制应用镜像时同时计算摘要。默认交付压缩为 `.tar.gz`,显式 `--output release.tar` 可保留未压缩格式;README 仅保留外层一份。成功部署后清理本次 incoming 上传和解包目录,release 保留恢复配置和状态,镜像由容器引擎保存。
220
+
221
+ ### 可选 HTTPS 证书
222
+
223
+ `pnpm run deploy` 为所有容器模板统一提供“不启用 / 已有 PEM 证书和私钥路径 / Let’s Encrypt”。相对路径以工程根为基准,已有证书校验域名、有效期和密钥匹配后复制到私有发布包。Let’s Encrypt 使用固定版本官方 Certbot 镜像申请,每 12 小时检查续期,Nginx 自动重载。需要确认服务条款、真实邮箱、公网 DNS、80 端口和 CA 网络;隔离环境使用已有证书。证书状态保留在服务器,私钥不进入应用镜像。`--yes` 复用保存配置。
224
+
225
+ 配置为 `hai.json.deploy.provider.expose.https`,也可用环境私有文件覆盖。默认 HTTPS 443、HTTP 跳转开启,未配置保持 HTTP;启停或更改模式、域名、端口、本地证书需全量部署。配置和续期边界见[HTTPS 手册](../../docs/usage/configuration/delivery.md#可选https证书)。`--plan` 的 `root` 表示工程根,`serviceDir` 表示部署服务源码与配置目录,仅显示公开证书元数据。
226
+
215
227
  ### SDK
216
228
 
217
229
  ```ts
@@ -249,6 +261,8 @@ SSH 中断始终先记 `remoteExecution: 'unknown'`。docker-ssh SDK 用新连
249
261
 
250
262
  ### 运行环境文件与重复部署
251
263
 
264
+ Node 后端(Serv / SvelteKit SSR)默认在每次启动时重读发布目录的 `.env.runtime`,修改后 `docker restart <容器名>` 或 `podman restart <容器名>` 即可,无需重建镜像或额外 override。旧部署先用新版 CLI 发布一次。删除的字段不会从旧 Compose 注入中复活;服务器托管连接的 `middleware.env` 保持原有更高优先级,私有代理标记和 Node 启动参数仍由部署装配管理。静态 Web、中间件、端口/挂载和 Nginx 限额不在该重载范围。手工修改需备份;下一次后端发布会从工程环境重新生成,需要长期保留的配置同步回源环境。
265
+
252
266
  框架持久配置使用 `HAI_*` 命名。`POSTGRES_*`、`MYSQL_*`、`RUSTFS_*`、`MINIO_*`、`LDAP_*` 是中间件镜像的原生协议,必须保留原名,但只生成到私有的 `middleware-runtime.env` 并由中间件容器加载;不再写入工程环境文件或应用的 `.env.runtime`。应用增量部署沿用的 `middleware.env` 仅保存框架 `HAI_*` 配置。旧工程已保存的这些原生字段不会被自动删除,新应用环境会忽略它们;新包与旧成功基线可共存,旧服务仍读取其原交付文件。
253
267
 
254
268
  自建或云开通的整体 `HAI_*` 配置会清除被替换的旧叶字段,例如 Redis 覆盖后不再留下 `HAI_CACHE_TYPE=memory`。全局 `~/.hai/credentials.yml` 只补缺项,不能覆盖当前环境/进程的同名键或父配置。部署先启动选中中间件,等待 PostgreSQL/MySQL/Redis/S3 健康,再启动 serv/Web;增量和失败恢复沿用此顺序,未选容器保持不变。
@@ -259,7 +273,7 @@ Podman 部署会主动执行容器已有的 healthcheck 并重新核对状态,
259
273
 
260
274
  生成时为 `.env.dev/.env.test/.env.prod` 分别生成独立的初始管理员密码和传输密钥环;环境文件随项目提交,克隆后可共享,并可通过本机私有文件或进程环境覆盖。启用 transport 的应用缺少/错误配置 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING` 时,部署在上传前指出配置来源;同一环境的所有节点复用同一有效密钥环,可直接加载环境文件,也可通过 Secret Manager 覆盖;不为每个节点临时生成。CLI 完成部署后提示 `HAI_ADMIN_DEFAULT_PASSWORD` 的实际来源,不打印密码,不依赖日志级别;该密码仅在首次创建管理员时生效。 离线入口也会提示服务器私有 `.env.runtime` 中的密码变量位置。
261
275
 
262
- 每次交付仍生成 `.hai/deploy/<环境>/bundle-.../.env.runtime`,供 Compose 加载运行配置;它是生成产物,不是配置记忆源。`NODE_ENV` 默认随目标环境选择:`test` → `test`,`dev/development` → `development`,`prod/production` 及其他模式 → `production`。环境文件中显式设置的 `NODE_ENV` 仍优先于该默认值;`--build` 的构建优化模式不决定运行环境。旧交付包不会自动改写,需重新打包。
276
+ 每次交付仍生成 `.hai/deploy/<环境>/bundle-.../.env.runtime`,供后端启动预加载(静态 Web 仍用 Compose env_file);它是生成产物,不是配置记忆源。`NODE_ENV` 默认随目标环境选择:`test` → `test`,`dev/development` → `development`,`prod/production` 及其他模式 → `production`。环境文件中显式设置的 `NODE_ENV` 仍优先于该默认值;`--build` 的构建优化模式不决定运行环境。旧交付包不会自动改写,需重新打包。
263
277
 
264
278
  项目根 `hai.json.deploy` 保存部署结构,`.env.<环境>` 保存公共配置,`.env.<环境>.local` 保存 SSH 及中间件秘密值。环境文件默认来自项目/workspace 根,`--env-dir` 可指定目录。完整配置后可使用 `pnpm run deploy --env test --build --bundle-only --yes` 无交互复用;只想保留现有中间件配置时追加 `--skip-middleware`。交互式仅打包会在最后询问是否保存或修改 SSH 信息;修改交付目录的 `deploy-target.json` 不会回写工程。
265
279
 
@@ -277,7 +291,7 @@ Serv 的 `http.access`、SvelteKit 的 `access` 可引用独立 yml/yaml。部
277
291
 
278
292
  ## 只读部署计划
279
293
 
280
- `createDeploymentPlan(appDir, config, options)` 不初始化 Provider、不生成中间件凭据、不创建输出目录、不连接目标。复用实际扫描、环境解析、资源名称与制品 checksum 校验,返回版本、构建 ID、范围、保留服务、配置文件与变量名、基线要求和恢复目录,省略所有变量值与 SSH 凭据。远端 current 未读取时明确 not-inspected;局部更新仍由执行端严格要求成功基线。Compose 恢复不会回退数据库数据或迁移。CLI 对应 `hai deploy --env prod --plan`。
294
+ `createDeploymentPlan(appDir, config, options)` 不初始化 Provider、不生成中间件凭据、不创建输出目录、不连接目标。复用实际扫描、环境解析、资源名称与制品 checksum 校验,返回工程根 `root`、部署服务目录 `serviceDir`、版本、构建 ID、范围、保留服务、配置文件与变量名、基线要求和恢复目录,省略所有变量值与 SSH 凭据。远端 current 未读取时明确 not-inspected;局部更新仍由执行端严格要求成功基线。Compose 恢复不会回退数据库数据或迁移。CLI 对应 `hai deploy --env prod --plan`。
281
295
 
282
296
  需要目标基线事实时使用 `--plan --inspect-remote`(SDK inspectRemote:true),仅通过 SSH 读取 current 及成功发布目录,报告 verified/missing/unavailable 和实际恢复发布 ID,不写目标。
283
297
 
@@ -0,0 +1,58 @@
1
+ /** Node 后端默认启动预加载:每次启动读取发布环境,再加载服务器托管连接配置。 */
2
+ import { Buffer } from 'node:buffer'
3
+ import { closeSync, constants, fstatSync, openSync, readSync } from 'node:fs'
4
+ import process from 'node:process'
5
+ import { parseEnv } from 'node:util'
6
+
7
+ const maximumBytes = 1024 * 1024
8
+
9
+ /** 复用 Node dotenv 解析;拒绝特殊文件、链接、超限与不可读文件,不输出秘密。 */
10
+ function readEnvironment(name) {
11
+ let descriptor
12
+ try {
13
+ descriptor = openSync(new URL(name, import.meta.url), constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0))
14
+ }
15
+ catch {
16
+ throw new Error('Runtime environment file is missing or not readable')
17
+ }
18
+ try {
19
+ const stat = fstatSync(descriptor)
20
+ if (!stat.isFile() || stat.size > maximumBytes)
21
+ throw new Error('Runtime environment file must be a regular file no larger than 1 MiB')
22
+ const buffer = Buffer.alloc(maximumBytes + 1)
23
+ let length = 0
24
+ while (length < buffer.length) {
25
+ const count = readSync(descriptor, buffer, length, buffer.length - length, null)
26
+ if (!count)
27
+ break
28
+ length += count
29
+ }
30
+ if (length > maximumBytes)
31
+ throw new Error('Runtime environment file exceeds 1 MiB')
32
+ try {
33
+ const text = buffer.subarray(0, length).toString('utf8')
34
+ const values = parseEnv(text)
35
+ // 交付序列化对同时含单/双引号的字符串使用 JSON 转义;避免 Node 截断转义引号。
36
+ for (const match of text.matchAll(/^(?:export\s+)?([A-Za-z_]\w*)\s*=\s*("(?:[^"\\\r\n]|\\.)*")\s*(?:#[^\r\n]*)?$/gm)) {
37
+ try {
38
+ values[match[1]] = JSON.parse(match[2])
39
+ }
40
+ catch { /* 普通 dotenv 双引号仍按 Node 原生规则解析。 */ }
41
+ }
42
+ return values
43
+ }
44
+ catch {
45
+ throw new Error('Runtime environment file could not be parsed')
46
+ }
47
+ }
48
+ finally {
49
+ closeSync(descriptor)
50
+ }
51
+ }
52
+
53
+ // 托管连接优先级沿用原 env_file 顺序;删除应用字段不会从旧容器环境复活。
54
+ const values = { ...readEnvironment('./.env.runtime'), ...readEnvironment('./middleware.env') }
55
+ // 启动参数与私有代理身份属于部署装配,不由可编辑的业务环境覆盖。
56
+ delete values.NODE_OPTIONS
57
+ delete values.HAI_DEPLOY_PRIVATE_PROXY
58
+ Object.assign(process.env, values)
package/dist/index.d.ts CHANGED
@@ -102,11 +102,26 @@ declare const SshConfigSchema: z.ZodObject<{
102
102
  strictHostKeyChecking: z.ZodDefault<z.ZodBoolean>;
103
103
  connectTimeoutSeconds: z.ZodDefault<z.ZodNumber>;
104
104
  }, z.core.$strip>;
105
- /** 端口直出暴露配置 */
105
+ /** HTTP 端口及可选 HTTPS 入口;Let's Encrypt 使用 HTTP-01,HTTP 必须映射 80。 */
106
106
  declare const PortExposeSchema: z.ZodObject<{
107
107
  type: z.ZodDefault<z.ZodLiteral<"port">>;
108
108
  containerPort: z.ZodDefault<z.ZodNumber>;
109
109
  hostPort: z.ZodNumber;
110
+ https: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
111
+ certificateFile: z.ZodString;
112
+ privateKeyFile: z.ZodString;
113
+ domain: z.ZodString;
114
+ port: z.ZodDefault<z.ZodNumber>;
115
+ redirectHttp: z.ZodDefault<z.ZodBoolean>;
116
+ type: z.ZodLiteral<"files">;
117
+ }, z.core.$strip>, z.ZodObject<{
118
+ domain: z.ZodString;
119
+ email: z.ZodEmail;
120
+ acceptTerms: z.ZodLiteral<true>;
121
+ port: z.ZodDefault<z.ZodNumber>;
122
+ redirectHttp: z.ZodDefault<z.ZodBoolean>;
123
+ type: z.ZodLiteral<"letsencrypt">;
124
+ }, z.core.$strip>], "type">>;
110
125
  }, z.core.$strip>;
111
126
  /** docker-ssh 部署平台配置 */
112
127
  declare const DockerSshProviderConfigSchema: z.ZodObject<{
@@ -137,6 +152,21 @@ declare const DockerSshProviderConfigSchema: z.ZodObject<{
137
152
  type: z.ZodDefault<z.ZodLiteral<"port">>;
138
153
  containerPort: z.ZodDefault<z.ZodNumber>;
139
154
  hostPort: z.ZodNumber;
155
+ https: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
156
+ certificateFile: z.ZodString;
157
+ privateKeyFile: z.ZodString;
158
+ domain: z.ZodString;
159
+ port: z.ZodDefault<z.ZodNumber>;
160
+ redirectHttp: z.ZodDefault<z.ZodBoolean>;
161
+ type: z.ZodLiteral<"files">;
162
+ }, z.core.$strip>, z.ZodObject<{
163
+ domain: z.ZodString;
164
+ email: z.ZodEmail;
165
+ acceptTerms: z.ZodLiteral<true>;
166
+ port: z.ZodDefault<z.ZodNumber>;
167
+ redirectHttp: z.ZodDefault<z.ZodBoolean>;
168
+ type: z.ZodLiteral<"letsencrypt">;
169
+ }, z.core.$strip>], "type">>;
140
170
  }, z.core.$strip>;
141
171
  trustedEdgeProxies: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodIPv4, z.ZodIPv6, z.ZodCIDRv4, z.ZodCIDRv6]>>>;
142
172
  }, z.core.$strip>;
@@ -182,6 +212,21 @@ declare const ProviderConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
182
212
  type: z.ZodDefault<z.ZodLiteral<"port">>;
183
213
  containerPort: z.ZodDefault<z.ZodNumber>;
184
214
  hostPort: z.ZodNumber;
215
+ https: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
216
+ certificateFile: z.ZodString;
217
+ privateKeyFile: z.ZodString;
218
+ domain: z.ZodString;
219
+ port: z.ZodDefault<z.ZodNumber>;
220
+ redirectHttp: z.ZodDefault<z.ZodBoolean>;
221
+ type: z.ZodLiteral<"files">;
222
+ }, z.core.$strip>, z.ZodObject<{
223
+ domain: z.ZodString;
224
+ email: z.ZodEmail;
225
+ acceptTerms: z.ZodLiteral<true>;
226
+ port: z.ZodDefault<z.ZodNumber>;
227
+ redirectHttp: z.ZodDefault<z.ZodBoolean>;
228
+ type: z.ZodLiteral<"letsencrypt">;
229
+ }, z.core.$strip>], "type">>;
185
230
  }, z.core.$strip>;
186
231
  trustedEdgeProxies: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodIPv4, z.ZodIPv6, z.ZodCIDRv4, z.ZodCIDRv6]>>>;
187
232
  }, z.core.$strip>], "type">;
@@ -257,6 +302,21 @@ declare const DeployConfigSchema: z.ZodObject<{
257
302
  type: z.ZodDefault<z.ZodLiteral<"port">>;
258
303
  containerPort: z.ZodDefault<z.ZodNumber>;
259
304
  hostPort: z.ZodNumber;
305
+ https: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
306
+ certificateFile: z.ZodString;
307
+ privateKeyFile: z.ZodString;
308
+ domain: z.ZodString;
309
+ port: z.ZodDefault<z.ZodNumber>;
310
+ redirectHttp: z.ZodDefault<z.ZodBoolean>;
311
+ type: z.ZodLiteral<"files">;
312
+ }, z.core.$strip>, z.ZodObject<{
313
+ domain: z.ZodString;
314
+ email: z.ZodEmail;
315
+ acceptTerms: z.ZodLiteral<true>;
316
+ port: z.ZodDefault<z.ZodNumber>;
317
+ redirectHttp: z.ZodDefault<z.ZodBoolean>;
318
+ type: z.ZodLiteral<"letsencrypt">;
319
+ }, z.core.$strip>], "type">>;
260
320
  }, z.core.$strip>;
261
321
  trustedEdgeProxies: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodIPv4, z.ZodIPv6, z.ZodCIDRv4, z.ZodCIDRv6]>>>;
262
322
  }, z.core.$strip>], "type">;
@@ -663,7 +723,7 @@ interface DeployCommandState {
663
723
  */
664
724
  interface DeployAppOptions {
665
725
  /** 接收实际执行阶段,用于终端进度显示;不包含凭据或配置原文。 */
666
- onProgress?: (stage: 'packaging' | 'preflight' | 'images' | 'dependencies' | 'start' | 'health' | 'complete') => void;
726
+ onProgress?: (stage: 'packaging' | 'preflight' | 'images' | 'dependencies' | 'start' | 'https' | 'health' | 'complete') => void;
667
727
  /** 只更新指定的 Compose 服务;省略时完整制品部署全部,部分制品只部署其镜像。 */
668
728
  selectedServices?: string[];
669
729
  /** 打包中间件镜像所用的本地引擎,auto 优先 Podman。 */
@@ -942,7 +1002,7 @@ declare const deploy: DeployFunctions;
942
1002
  * const message = deployM('deploy_sshRequired')
943
1003
  * ```
944
1004
  */
945
- declare const deployM: (key: "deploy_notInitialized" | "deploy_initFailed" | "deploy_unsupportedProviderType" | "deploy_unsupportedProvisionerType" | "deploy_configTokenRequired" | "deploy_authRequired" | "deploy_authFailed" | "deploy_projectCreateFailed" | "deploy_uploadFailed" | "deploy_deployFailed" | "deploy_provisionFailed" | "deploy_adapterMissing" | "deploy_scanFailed" | "deploy_envVarFailed" | "deploy_credentialError" | "deploy_credentialInvalidKey" | "deploy_credentialSaved" | "deploy_credentialLoaded" | "deploy_configError" | "deploy_credentialMissing" | "deploy_apiError" | "deploy_fileUploadFailed" | "deploy_provisionNoResult" | "deploy_recoveryFailure" | "deploy_recoverySuggestion" | "deploy_commandTimeout" | "deploy_commandFailed" | "deploy_remoteConnectFailed" | "deploy_remoteRuntimeMissing" | "deploy_imageTransferFailed" | "deploy_healthCheckFailed" | "deploy_invalidEnvironment" | "deploy_environmentMissing" | "deploy_environmentReadFailed" | "deploy_environmentWriteFailed" | "deploy_multipleApplications" | "deploy_purposeSqlite" | "deploy_purposeDatabase" | "deploy_purposeMemory" | "deploy_purposeLocal" | "deploy_purposeCache" | "deploy_purposeStorage" | "deploy_purposeVector" | "deploy_purposeDisabled" | "deploy_purposeLdap" | "deploy_purposeConsole" | "deploy_purposeEmail" | "deploy_purposeSms" | "deploy_webProxyInvalid" | "deploy_webBaseConflict" | "deploy_storageProxyInvalid" | "deploy_storagePublicEndpointInvalid" | "deploy_storageBackendRequired" | "deploy_artifactInvalid" | "deploy_sshRequired" | "deploy_sshPasswordInvalid" | "deploy_sshAskpassFailed" | "deploy_sshAskpassCleanupFailed" | "deploy_targetAuth" | "deploy_targetPasswordEnv" | "deploy_bundleInstructions" | "deploy_bundleMetadata" | "deploy_targetHost" | "deploy_targetPort" | "deploy_targetUsername" | "deploy_targetIdentityFile" | "deploy_targetKnownHostsFile" | "deploy_targetStrictHostKeyChecking" | "deploy_targetConnectTimeoutSeconds" | "deploy_targetDirectory" | "deploy_targetBundle" | "deploy_bundleOnlyContainer" | "deploy_selectedContainerOnly" | "deploy_selectionInvalid" | "deploy_imageVersionRequired" | "deploy_bundleRuntimeRequired" | "deploy_bundleFailed" | "deploy_configFileInvalid" | "deploy_configFileUnreadable" | "deploy_bundleNameInvalid" | "deploy_localStopped" | "deploy_localUnknown" | "deploy_localNotStarted" | "deploy_commandOutputFailed" | "deploy_commandInputFailed" | "deploy_commandTreeActive" | "deploy_commandExecutionFailed" | "deploy_remoteUnknown" | "deploy_remoteFinished" | "deploy_bundleDirectoryExists" | "deploy_bundleFull" | "deploy_bundlePartial" | "deploy_bundleSelection" | "deploy_transportBodyLimitInvalid" | "deploy_transportKeyRingInvalid" | "deploy_uploadPasswordPrompt" | "deploy_uploadPasswordUnavailable" | "deploy_uploadPasswordCancelled" | "deploy_uploadPasswordInvalid" | "deploy_managedSecretsInvalid" | "deploy_secretImportFailed" | "deploy_projectNameRequired" | "deploy_projectNameOverrideRemoved" | "deploy_requestAccessInvalid", options?: _h_ai_core.MessageOptions) => string;
1005
+ declare const deployM: (key: "deploy_notInitialized" | "deploy_initFailed" | "deploy_unsupportedProviderType" | "deploy_unsupportedProvisionerType" | "deploy_configTokenRequired" | "deploy_authRequired" | "deploy_authFailed" | "deploy_projectCreateFailed" | "deploy_uploadFailed" | "deploy_deployFailed" | "deploy_provisionFailed" | "deploy_adapterMissing" | "deploy_scanFailed" | "deploy_envVarFailed" | "deploy_credentialError" | "deploy_credentialInvalidKey" | "deploy_credentialSaved" | "deploy_credentialLoaded" | "deploy_configError" | "deploy_credentialMissing" | "deploy_apiError" | "deploy_fileUploadFailed" | "deploy_provisionNoResult" | "deploy_recoveryFailure" | "deploy_recoverySuggestion" | "deploy_commandTimeout" | "deploy_commandFailed" | "deploy_remoteConnectFailed" | "deploy_remoteRuntimeMissing" | "deploy_imageTransferFailed" | "deploy_healthCheckFailed" | "deploy_invalidEnvironment" | "deploy_environmentMissing" | "deploy_environmentReadFailed" | "deploy_environmentWriteFailed" | "deploy_multipleApplications" | "deploy_purposeSqlite" | "deploy_purposeDatabase" | "deploy_purposeMemory" | "deploy_purposeLocal" | "deploy_purposeCache" | "deploy_purposeStorage" | "deploy_purposeVector" | "deploy_purposeDisabled" | "deploy_purposeLdap" | "deploy_purposeConsole" | "deploy_purposeEmail" | "deploy_purposeSms" | "deploy_webProxyInvalid" | "deploy_webBaseConflict" | "deploy_storageProxyInvalid" | "deploy_storagePublicEndpointInvalid" | "deploy_storageBackendRequired" | "deploy_artifactInvalid" | "deploy_sshRequired" | "deploy_sshPasswordInvalid" | "deploy_sshAskpassFailed" | "deploy_sshAskpassCleanupFailed" | "deploy_targetAuth" | "deploy_targetPasswordEnv" | "deploy_bundleInstructions" | "deploy_bundleMetadata" | "deploy_targetHost" | "deploy_targetPort" | "deploy_targetUsername" | "deploy_targetIdentityFile" | "deploy_targetKnownHostsFile" | "deploy_targetStrictHostKeyChecking" | "deploy_targetConnectTimeoutSeconds" | "deploy_targetDirectory" | "deploy_targetBundle" | "deploy_bundleOnlyContainer" | "deploy_selectedContainerOnly" | "deploy_selectionInvalid" | "deploy_imageVersionRequired" | "deploy_bundleRuntimeRequired" | "deploy_bundleFailed" | "deploy_configFileInvalid" | "deploy_configFileUnreadable" | "deploy_bundleNameInvalid" | "deploy_localStopped" | "deploy_localUnknown" | "deploy_localNotStarted" | "deploy_commandOutputFailed" | "deploy_commandInputFailed" | "deploy_commandTreeActive" | "deploy_commandExecutionFailed" | "deploy_remoteUnknown" | "deploy_remoteFinished" | "deploy_bundleDirectoryExists" | "deploy_bundleFull" | "deploy_bundlePartial" | "deploy_bundleSelection" | "deploy_transportBodyLimitInvalid" | "deploy_transportKeyRingInvalid" | "deploy_uploadPasswordPrompt" | "deploy_uploadPasswordUnavailable" | "deploy_uploadPasswordCancelled" | "deploy_uploadPasswordInvalid" | "deploy_managedSecretsInvalid" | "deploy_secretImportFailed" | "deploy_projectNameRequired" | "deploy_projectNameOverrideRemoved" | "deploy_requestAccessInvalid" | "deploy_httpsPortsInvalid" | "deploy_httpsCertificateInvalid", options?: _h_ai_core.MessageOptions) => string;
946
1006
 
947
1007
  /**
948
1008
  * 部署中间件的后端与连接字段目录,供扫描、CLI 和容器规划共同使用。
@@ -1022,6 +1082,7 @@ declare function describeMiddlewareBackend(backend: MiddlewareBackend): string;
1022
1082
 
1023
1083
  /**
1024
1084
  * 查看与执行入口相同的扫描、环境、项目身份和制品校验结果。远端 current 只报告为待执行时核验。
1085
+ * 输出 root 是工程根,serviceDir 是读取运行配置、扫描依赖的部署服务目录。
1025
1086
  * @example
1026
1087
  * ```ts
1027
1088
  * import { createDeploymentPlan } from '@h-ai/deploy'
@@ -1038,7 +1099,7 @@ declare function createDeploymentPlan(appDir: string, input: DeployConfigInput,
1038
1099
  data: {
1039
1100
  projectName: string;
1040
1101
  root: string;
1041
- appDir: string;
1102
+ serviceDir: string;
1042
1103
  environment: string;
1043
1104
  target: {
1044
1105
  provider: "docker-ssh";
@@ -1047,6 +1108,11 @@ declare function createDeploymentPlan(appDir: string, input: DeployConfigInput,
1047
1108
  directory: string | undefined;
1048
1109
  runtime: "auto" | "docker" | "podman";
1049
1110
  hostPort: number;
1111
+ https: {
1112
+ type: "files" | "letsencrypt";
1113
+ domain: string;
1114
+ port: number;
1115
+ } | undefined;
1050
1116
  } | {
1051
1117
  provider: "vercel";
1052
1118
  host?: undefined;
@@ -1054,6 +1120,7 @@ declare function createDeploymentPlan(appDir: string, input: DeployConfigInput,
1054
1120
  directory?: undefined;
1055
1121
  runtime?: undefined;
1056
1122
  hostPort?: undefined;
1123
+ https?: undefined;
1057
1124
  };
1058
1125
  artifact: {
1059
1126
  directory: string;