@h-ai/deploy 1.0.0-beta.5 → 1.0.0-beta.7
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 +26 -11
- package/README.ja.md +26 -11
- package/README.md +26 -11
- package/dist/deploy-runtime-env.mjs +153 -19
- package/dist/index.d.ts +260 -98
- package/dist/index.js +6715 -767
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/README.en.md
CHANGED
|
@@ -29,7 +29,17 @@ pnpm build --target desktop --platform windows --arch x64
|
|
|
29
29
|
|
|
30
30
|
#### Build options
|
|
31
31
|
|
|
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
|
|
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 this build proxy (same as --proxy-mode none), 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
|
+
|
|
34
|
+
#### Download sources and proxies
|
|
35
|
+
|
|
36
|
+
`--image-source official|cn|custom` covers application base images and missing middleware images during packaging. `--npm-source` requires `--build` and controls Corepack/pnpm. The China preset uses 1ms for Docker Hub/GHCR and npmmirror for npm. First interactive choices are saved in `hai.json.build.sources`; flags override one invocation. Set `--proxy-mode inherit|system|custom|none`, `--proxy-url`, and `--proxy-bypass`; `--no-http-proxy` means none. Docker/remote Podman image pulls require engine-side proxy settings; build containers check connectivity separately. See [sources and proxies](../../docs/usage/configuration/delivery.md#下载源和代理) for custom sources, supported platforms and old-template updates.
|
|
37
|
+
|
|
38
|
+
SDK `DeployAppOptions.imageSource` accepts `{ type: 'cn' }` or `{ type: 'custom', registries: { 'docker.io': 'mirror.example.com/docker.io', 'ghcr.io': 'mirror.example.com/ghcr.io' } }`; `proxy` accepts `{ mode: 'inherit' }` or `{ mode: 'custom', url: 'http://127.0.0.1:10808', bypass: 'localhost' }`. Defaults use official sources and inherited process proxies. `resolveImageReference()` / `resolveDownloadProxy()` need no init and do not change engine settings. Middleware archives preserve canonical references for offline server imports.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
pnpm run deploy --env prod --build --bundle-only --image-source cn --npm-source cn
|
|
42
|
+
```
|
|
33
43
|
|
|
34
44
|
#### Describe packaging in hai.json
|
|
35
45
|
|
|
@@ -101,10 +111,9 @@ Prepare `.env.<environment>` and the project root `hai.json`, with image/native
|
|
|
101
111
|
|
|
102
112
|
The deployment SDK uses these priorities, highest first:
|
|
103
113
|
|
|
104
|
-
|
|
105
|
-
- Other modules: process > `.env.<environment>.local` > legacy `.env.local` > `.env.<environment>` > `.env`, preserving existing private credentials.
|
|
114
|
+
All `HAI_*` settings use process > `.env.<mode>.local` > `.env.local` > `.env.<mode>` > `.env` > YAML. Within one source, fields override base JSON. Replacing a whole node from a higher source clears lower-source fields. Node/Vite and deployment share Core resolution.
|
|
106
115
|
|
|
107
|
-
|
|
116
|
+
Public settings live in `.env` and `.env.<mode>`; SSH settings and credentials go to Git-ignored `.env.<mode>.local`. `.env.local` contains shared machine-private settings. Builds do not read deployment secrets.
|
|
108
117
|
|
|
109
118
|
For existing projects, move the previous build object into `hai.json.build`, convert deployment YAML into `hai.json.deploy`, remove the old files and retain `HAI_DEPLOY_*` variables. Existing self-hosted S3 installations must use a full deployment for this upgrade: their old baseline contains an exited initialization service, which fails the dependency checks of new incremental packages. The full deployment also refreshes the `createBucket` middleware setting; subsequent incremental updates work normally.
|
|
110
119
|
|
|
@@ -116,9 +125,9 @@ SSH settings are saved in `.env.<environment>.local`. `--bundle-only` offers to
|
|
|
116
125
|
|
|
117
126
|
#### Configure only the middleware the application needs
|
|
118
127
|
|
|
119
|
-
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.
|
|
128
|
+
Runtime lockfile dependencies of the target application and configuration determine middleware; commented configuration does not enable modules. LDAP is included only when effective `iam.login.ldap=true`, so disabled LDAP produces no prompt. 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.
|
|
120
129
|
|
|
121
|
-
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.
|
|
130
|
+
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. Generators prefer field variables and retain JSON arrays. Handwritten JSON remains a base and appears before its module fields; fields in the same source win. Dynamic maps, JSON text and explicit empty objects preserve their meaning. Expanding a whole-object replacement retains its highest `{}` reset marker so old YAML fields cannot reappear. Container paths stay in delivery output without changing source settings.
|
|
122
131
|
|
|
123
132
|
#### Direct private object storage access
|
|
124
133
|
|
|
@@ -241,11 +250,17 @@ SSH interruptions initially report `remoteExecution: 'unknown'`. The docker-ssh
|
|
|
241
250
|
|
|
242
251
|
Environment flags, initial account passwords and the transport key ring are also read directly by startup code. They remain available even when a similarly named configuration parent exists.
|
|
243
252
|
|
|
244
|
-
|
|
253
|
+
Generators prefer field variables and retain JSON arrays. Handwritten JSON remains a base and appears before its module fields; fields in the same source win. Dynamic maps, JSON text and explicit empty objects preserve their meaning. Expanding a whole-object replacement retains its highest `{}` reset marker so old YAML fields cannot reappear. Container paths stay in delivery output without changing source settings.
|
|
254
|
+
|
|
255
|
+
The deployment archive has five top-level entries: `apply.sh`, `manifest.json`, `config/`, `images/`, and `.hai/`. `config/.env.runtime` is the single application environment file. `manifest.json.metadata` holds nonsecret layout, dependencies, health paths, image lists and managed bindings. `.hai/compose.json` is a complete snapshot; partial updates replace selected service definitions and retain other services without historical Compose chains. `.hai/` also contains native environment, the loader, Nginx/TLS, policy and checksums. Secrets stay out of the manifest; helper scripts are embedded in `apply.sh`. Failure recovery restores application configuration before services while protecting credentials, backend ledgers and volumes. Bundle format 2 has no old-layout compatibility or migration. Runtime preparation reuses the already imported Node application image in a temporary container with networking disabled. It downloads no extra image and requires no host Node or jq.
|
|
256
|
+
|
|
257
|
+
Node backends (Serv / SvelteKit SSR) read `<deployment-root>/config/.env.runtime` on every start. This single application file includes managed connections. Edit a field such as `HAI_STORAGE_PUBLICENDPOINT`, then restart the backend; fields override parent JSON within the same source. The configuration directory is mounted read-only at `/run/hai/config`, so atomic file replacement is visible on restart. Private proxy identity and Node startup options remain deployment settings. Static Web, native middleware environment, ports/mounts, and Nginx routes or limits require deployment. Web/middleware-only updates retain the file; partial backend updates preserve managed connections, while full updates use the new package with existing managed credentials. Back up edits and copy permanent settings into the source environment.
|
|
258
|
+
|
|
259
|
+
Container packaging sets the listener to `0.0.0.0` and the port declared by the artifact, falling back to deployment settings when absent. It generates `HOST`, `PORT`, and the matching Serv fields. Loopback addresses and development ports in local `.env` files remain local settings. Update the artifact and deployment layout together when changing container ports.
|
|
245
260
|
|
|
246
|
-
|
|
261
|
+
Nginx dynamically tracks DNS changes for application and storage containers. The shared `.hai/proxy-start.sh` runs as an image entrypoint hook, preserves the image startup command, and reads resolvers from the container's `/etc/resolv.conf` and also watches certificate changes for HTTPS. Docker and Podman need no fixed DNS address. After editing runtime configuration and restarting the backend, the proxy reconnects automatically even if its IP changes.
|
|
247
262
|
|
|
248
|
-
|
|
263
|
+
Image-native `POSTGRES_*`, `MYSQL_*`, `RUSTFS_*`, `MINIO_*`, and `LDAP_*` keep the names required by their images, only in private `.hai/middleware-runtime.env` loaded by middleware. All application connections are merged into `config/.env.runtime`.
|
|
249
264
|
|
|
250
265
|
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.
|
|
251
266
|
|
|
@@ -253,9 +268,9 @@ On Podman, deployment actively runs each configured container healthcheck and re
|
|
|
253
268
|
|
|
254
269
|
Only the first startup batch removes orphan containers; later serv/Web startup preserves the middleware that is already ready.
|
|
255
270
|
|
|
256
|
-
|
|
271
|
+
Project creation generates independent initial passwords and transport key rings in `.env.dev.local`, `.env.test.local`, and `.env.prod.local`. These files are Git-ignored. Distribute environment secrets through protected files or a Secret Manager after cloning. Nodes in one environment share a valid ring and registry; never generate a ring at startup. Missing or invalid `HAI_CRYPTO_TRANSPORT_SERVERKEYRING` fails before upload. CLI and offline instructions identify password variables and sources without showing values. Initial passwords create missing accounts and do not reset existing accounts.
|
|
257
272
|
|
|
258
|
-
Each delivery still generates
|
|
273
|
+
Each delivery still generates `config/.env.runtime` inside the archive for Compose. This is generated output, not the saved configuration source. The default `NODE_ENV` follows the target: `test` → `test`, `dev/development` → `development`, and `prod/production` or other modes → `production`. Explicit `NODE_ENV` values in environment configuration override this default; build optimization does not select the runtime environment. Rebuild the bundle to update old deliveries.
|
|
259
274
|
|
|
260
275
|
Project-root `hai.json.deploy` stores deployment structure, `.env.<environment>` stores public settings, and `.env.<environment>.local` stores SSH settings and middleware secrets. Environment files default to the project/workspace root; override with `--env-dir`. After configuration, `pnpm run deploy --env test --build --bundle-only --yes` reuses complete settings without prompts; add `--skip-middleware` to retain middleware configuration. Interactive bundle-only mode offers to save or update SSH settings at the final configuration step; editing a delivery's `deploy-target.json` does not update the project.
|
|
261
276
|
|
package/README.ja.md
CHANGED
|
@@ -29,7 +29,17 @@ pnpm build --target desktop --platform windows --arch x64
|
|
|
29
29
|
|
|
30
30
|
#### build のオプション
|
|
31
31
|
|
|
32
|
-
`--target` は compile(既定)、image、app、desktop を選択します。`--images serv|web|serv,web` は対象イメージを、`--runtime auto|podman|docker` はローカルエンジンを選択します。auto は接続可能な Podman を優先します。`--network` は構築ネットワーク、`--no-http-proxy` は
|
|
32
|
+
`--target` は compile(既定)、image、app、desktop を選択します。`--images serv|web|serv,web` は対象イメージを、`--runtime auto|podman|docker` はローカルエンジンを選択します。auto は接続可能な Podman を優先します。`--network` は構築ネットワーク、`--no-http-proxy` は 今回のビルドのプロキシ無効(none と同じ)、`--sqlite-prebuild-mirror` は信頼できる HTTP(S) バイナリ配布元です。ネイティブ配布物には対象 OS の SDK と Rust ツールチェーンが必要です。構築時にデプロイ環境や秘密情報を読み込みません。
|
|
33
|
+
|
|
34
|
+
#### 取得元とプロキシ
|
|
35
|
+
|
|
36
|
+
`--image-source official|cn|custom` はアプリのベースと梱包時に不足したミドルウェアイメージに適用します。`--npm-source` は `--build` と併用し Corepack/pnpm を制御します。中国本土は毫秒镜像の Docker Hub/GHCR 通道と npmmirror を使用します。初回対話で `hai.json.build.sources` に保存し、引数は今回のみ上書きします。プロキシは `--proxy-mode inherit|system|custom|none`、`--proxy-url`、`--proxy-bypass` で指定し、`--no-http-proxy` は none と同じです。Docker/リモート Podman の取得はエンジン側の設定が必要で、ビルドコンテナの接続は別途確認します。カスタム設定、対応 OS、旧テンプレートの更新は[取得元とプロキシ](../../docs/usage/configuration/delivery.md#下载源和代理)を参照してください。
|
|
37
|
+
|
|
38
|
+
SDK の `DeployAppOptions.imageSource` は `{ type: 'cn' }` または `{ type: 'custom', registries: { 'docker.io': 'mirror.example.com/docker.io', 'ghcr.io': 'mirror.example.com/ghcr.io' } }`、`proxy` は `{ mode: 'inherit' }` または `{ mode: 'custom', url: 'http://127.0.0.1:10808', bypass: 'localhost' }` です。既定は公式とプロセスの継承です。`resolveImageReference()`/`resolveDownloadProxy()` は init 不要でエンジン設定を変更しません。ミドルウェアの元参照をアーカイブに保ち、サーバーではオフラインで読み込みます。
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
pnpm run deploy --env prod --build --bundle-only --image-source cn --npm-source cn
|
|
42
|
+
```
|
|
33
43
|
|
|
34
44
|
#### hai.json でパッケージの構成を指定する
|
|
35
45
|
|
|
@@ -101,10 +111,9 @@ CLI の --project-name と SDK の DeployAppOptions.projectName 上書きは廃
|
|
|
101
111
|
|
|
102
112
|
デプロイ SDK の優先順は次のとおりです(高い順)。
|
|
103
113
|
|
|
104
|
-
|
|
105
|
-
- その他のモジュール:プロセス > `.env.<環境>.local` > 旧 `.env.local` > `.env.<環境>` > `.env`。既存の非公開認証情報の優先順を維持します。
|
|
114
|
+
すべての `HAI_*` はプロセス > `.env.<mode>.local` > `.env.local` > `.env.<mode>` > `.env` > YAML の順です。同一ソースは JSON を基礎として個別フィールドで上書きし、高優先度ソースの全体置換は低優先度の細項を解除します。Node/Vite と配備は同じ Core 処理を使います。
|
|
106
115
|
|
|
107
|
-
|
|
116
|
+
公開設定は `.env` と `.env.<mode>`、SSH と資格情報は Git 対象外の `.env.<mode>.local`、マシン共通の秘密値は `.env.local` に保存します。構築時には配備の秘密値を読みません。
|
|
108
117
|
|
|
109
118
|
既存プロジェクトは旧構築オブジェクトを `hai.json.build`、旧配置 YAML を JSON に変換して `hai.json.deploy` に移し、元のファイルを削除します。`HAI_DEPLOY_*` は継続利用できます。既存の自前 S3 は今回の初回更新で全量配置が必要です。旧基準には終了済みの初期化サービスが残っており、新しい増分パッケージの依存稼働チェックで拒否されます。全量配置で `createBucket` 設定も更新した後は増分更新を継続できます。
|
|
110
119
|
|
|
@@ -114,9 +123,9 @@ SSH 設定は選択環境の `.env.<環境>.local` に保存します。`--bundl
|
|
|
114
123
|
|
|
115
124
|
#### 本当に必要なミドルウェアだけを設定する
|
|
116
125
|
|
|
117
|
-
|
|
126
|
+
対象アプリのロックファイルにある実行依存と設定に基づき、既定、自分の接続、設定維持を選びます。コメントの設定はモジュールを有効にしません。LDAP は有効な設定で `iam.login.ldap=true` の場合だけ配置対象となり、無効時には質問しません。対象環境の設定が完全なら、`--yes` は値を保持し、再入力やクラウド資源の再作成を行いません。対話モードでは今回の処理範囲を選べます。--skip-middleware は設定と開通を省略します。SQLite、メモリーキャッシュ、ローカル保存には外部イメージがありません。完全配布物には自前ミドルウェアのイメージも自動で含めます。外部サービスは接続先への到達性が必要で、クラウド開通はオフライン処理ではありません。
|
|
118
127
|
|
|
119
|
-
既定とカスタムは同じバックエンドの有効な設定を保持します。フォームにない接続プール、操作ログ、ストレージ公開 URL/接頭辞、Redis 再試行、ベクトル検索設定も対象です。自前構成とクラウド開通は接続先・認証だけを更新し、古い URL、TLS/クラスター設定、認証情報を引き継ぎません。SMTP 送信元と LDAP
|
|
128
|
+
既定とカスタムは同じバックエンドの有効な設定を保持します。フォームにない接続プール、操作ログ、ストレージ公開 URL/接頭辞、Redis 再試行、ベクトル検索設定も対象です。自前構成とクラウド開通は接続先・認証だけを更新し、古い URL、TLS/クラスター設定、認証情報を引き継ぎません。SMTP 送信元と LDAP 属性対応は保持します。バックエンド変更時は非互換項目を除去します。 生成処理は個別変数を優先し、配列は JSON を維持します。手書き JSON はモジュール細項の前に置く基礎値で、同一ソースの細項が優先されます。動的辞書、JSON テキスト、明示的な空オブジェクトの意味を維持します。全体置換を展開する際は最上位の `{}` リセットを残し、古い YAML フィールドの復活を防ぎます。コンテナー内パスは配布物のみ変更します。
|
|
120
129
|
|
|
121
130
|
#### 非公開オブジェクトストレージへの直接アクセス
|
|
122
131
|
|
|
@@ -239,11 +248,17 @@ SSH 中断は最初に `remoteExecution: 'unknown'` とし、docker-ssh SDK は
|
|
|
239
248
|
|
|
240
249
|
環境フラグ・初期アカウントパスワード・転送キーリングは起動コードも直接読み取るため、同じ接頭辞の親設定があっても保持します。
|
|
241
250
|
|
|
242
|
-
|
|
251
|
+
生成処理は個別変数を優先し、配列は JSON を維持します。手書き JSON はモジュール細項の前に置く基礎値で、同一ソースの細項が優先されます。動的辞書、JSON テキスト、明示的な空オブジェクトの意味を維持します。全体置換を展開する際は最上位の `{}` リセットを残し、古い YAML フィールドの復活を防ぎます。コンテナー内パスは配布物のみ変更します。
|
|
252
|
+
|
|
253
|
+
配布アーカイブのトップレベルは `apply.sh`、`manifest.json`、`config/`、`images/`、`.hai/` の 5 項目です。アプリ環境は `config/.env.runtime` に統一し、レイアウト、依存、健康パス、イメージ一覧、管理接続などの非機密メタデータは `manifest.json.metadata` に保存します。`.hai/compose.json` は全サービスのスナップショットで、部分更新は選択サービスの定義を置換し、他サービスを保持します。`.hai/` にネイティブ環境、ローダー、Nginx/TLS、ポリシー、チェックサムも保存し、秘密値は manifest に含めません。補助スクリプトは `apply.sh` に統合します。失敗時は先にアプリ設定、次にサービスを復元し、資格情報、台帳、データボリュームを保護します。形式 2 の旧ディレクトリー互換や移行は実装しません。 設定の確定は導入済みの Node アプリイメージを一時コンテナーで再利用し、ネットワークを無効化します。追加イメージの取得やホストの Node・jq は不要です。
|
|
254
|
+
|
|
255
|
+
Node バックエンド(Serv / SvelteKit SSR)は起動ごとに `<配備ディレクトリー>/config/.env.runtime` を読みます。管理接続も同じアプリ設定に統合します。`HAI_STORAGE_PUBLICENDPOINT` などを編集して再起動すると反映され、同じソースでは個別フィールドが親 JSON を上書きします。設定ディレクトリーを `/run/hai/config` に読み取り専用でマウントするため、ファイルのアトミック置換も次の起動で反映されます。非公開プロキシ識別と Node 起動引数は配備設定です。静的 Web、ネイティブ環境、ポート、マウント、Nginx の変更は再配備します。Web/ミドルウェアのみの更新は設定を維持し、バックエンドの部分更新は管理接続を維持、全量更新は新しい設定と既存の管理資格情報を使います。編集前にバックアップし、恒久的な値は元の環境にも反映してください。
|
|
256
|
+
|
|
257
|
+
コンテナーのパッケージ化では、待受アドレスを `0.0.0.0`、ポートを成果物の宣言値(未指定なら配置設定)として、`HOST`、`PORT` と Serv の対応フィールドを生成します。ローカル `.env` のループバックアドレスと開発ポートはローカル実行に使います。コンテナーポートの変更時は成果物と配置構成を更新して再配備してください。
|
|
243
258
|
|
|
244
|
-
|
|
259
|
+
Nginx はアプリとストレージのコンテナーの DNS 変更に動的に追従します。共通の `.hai/proxy-start.sh` はイメージの元の起動コマンドを維持する入口フックとして動作し、コンテナー内の `/etc/resolv.conf` からリゾルバーを読み、HTTPS では証明書更新も監視します。Docker と Podman の DNS アドレスを固定する必要はありません。実行時設定を編集してバックエンドを再起動した後、IP が変わってもプロキシは自動的に再接続します。
|
|
245
260
|
|
|
246
|
-
|
|
261
|
+
イメージ固有の `POSTGRES_*`、`MYSQL_*`、`RUSTFS_*`、`MINIO_*`、`LDAP_*` は必要な名前を維持し、ミドルウェア専用の非公開 `.hai/middleware-runtime.env` に保存します。アプリ接続はすべて `config/.env.runtime` に統合します。
|
|
247
262
|
|
|
248
263
|
サイドカーやクラウドの `HAI_*` オブジェクトで置換した古い子変数は除去し、Redis 選択後に `HAI_CACHE_TYPE=memory` を残しません。グローバル `~/.hai/credentials.yml` は不足値のみ補い、環境・プロセスの同名キーや親設定を上書きしません。選択したミドルウェアを起動し PostgreSQL/MySQL/Redis/S3 の正常性を確認後、serv/Web を起動します。増分と復旧も同じ順序で、未選択のコンテナーは維持します。
|
|
249
264
|
|
|
@@ -251,9 +266,9 @@ Podman では設定済みの healthcheck を明示的に実行して状態を再
|
|
|
251
266
|
|
|
252
267
|
孤立コンテナーの削除は最初の起動バッチのみで行い、後続の serv/Web 起動では準備済みのミドルウェアを維持します。
|
|
253
268
|
|
|
254
|
-
作成時に `.env.dev
|
|
269
|
+
作成時に `.env.dev.local`、`.env.test.local`、`.env.prod.local` に個別の初期パスワードと転送鍵リングを生成し、Git には登録しません。クローン後は保護ファイルまたは Secret Manager で環境の秘密値を配布します。同じ環境のノードは有効な ring と共有 registry を使い、起動時に生成しません。`HAI_CRYPTO_TRANSPORT_SERVERKEYRING` の不足・不正はアップロード前に失敗します。CLI とオフライン説明はパスワード変数と保存元のみ示し、値は表示しません。初期値は不足アカウント作成時のみ使用し、既存アカウントを変更しません。
|
|
255
270
|
|
|
256
|
-
配布ごとに起動時読み込み用の
|
|
271
|
+
配布ごとに起動時読み込み用の アーカイブ内の `config/.env.runtime` を生成します。これは生成物であり、設定の保存元ではありません。`NODE_ENV` の既定値は対象環境に従い、`test` は `test`、`dev/development` は `development`、`prod/production` とその他は `production` です。環境設定の明示的な `NODE_ENV` が優先され、構築の最適化モードとは別です。旧配布物は再度パッケージ化してください。
|
|
257
272
|
|
|
258
273
|
プロジェクトルートの `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` の編集はプロジェクトに反映されません。
|
|
259
274
|
|
package/README.md
CHANGED
|
@@ -37,10 +37,20 @@ pnpm build --target desktop --platform windows --arch x64
|
|
|
37
37
|
| `--images` | serv、web 或 serv,web;只构建导出所选镜像 |
|
|
38
38
|
| `--runtime` | auto(优先 Podman)、podman、docker |
|
|
39
39
|
| `--network` | 容器构建网络,默认引擎设置 |
|
|
40
|
-
| `--no-http-proxy` |
|
|
40
|
+
| `--no-http-proxy` | 禁用本次构建代理(等价 none) |
|
|
41
41
|
| `--sqlite-prebuild-mirror` | SQLite 预编译包的可信 HTTP(S) 根地址 |
|
|
42
42
|
| `--platform` / `--arch` | 原生安装包的系统与架构 |
|
|
43
43
|
|
|
44
|
+
#### 下载源和代理
|
|
45
|
+
|
|
46
|
+
`--image-source official|cn|custom` 同时作用于应用基础镜像和打包时缺少的中间件镜像。`--npm-source` 随 `--build` 控制 Corepack/pnpm;cn 使用毫秒镜像的 Docker Hub/GHCR 通道和 npmmirror。首次交互保存 `hai.json.build.sources`,命令覆盖只作用本次。代理使用 `--proxy-mode inherit|system|custom|none`、`--proxy-url` 与 `--proxy-bypass`;`--no-http-proxy` 等价 none。Docker/远程 Podman 的镜像拉取代理须在引擎侧设置,构建容器另做可达性检查。自定义源、平台范围及旧模板迁移见[下载源和代理](../../docs/usage/configuration/delivery.md#下载源和代理)。
|
|
47
|
+
|
|
48
|
+
SDK 的 `DeployAppOptions.imageSource` 为 `{ type: 'cn' }` 或 `{ type: 'custom', registries: { 'docker.io': 'mirror.example.com/docker.io', 'ghcr.io': 'mirror.example.com/ghcr.io' } }`,`proxy` 为 `{ mode: 'inherit' }` 或 `{ mode: 'custom', url: 'http://127.0.0.1:10808', bypass: 'localhost' }`。缺省为官方源、继承进程代理。`resolveImageReference()` / `resolveDownloadProxy()` 是无需 init 的解析工具,不修改引擎设置。中间件归档保留原始引用,服务器继续离线导入。
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
pnpm run deploy --env prod --build --bundle-only --image-source cn --npm-source cn
|
|
52
|
+
```
|
|
53
|
+
|
|
44
54
|
#### 用 hai.json 描述要打什么包
|
|
45
55
|
|
|
46
56
|
```json
|
|
@@ -114,10 +124,9 @@ CLI 的 --project-name 与 SDK 的 DeployAppOptions.projectName 覆盖入口已
|
|
|
114
124
|
|
|
115
125
|
部署 SDK 的环境优先级从高到低为:
|
|
116
126
|
|
|
117
|
-
|
|
118
|
-
- 其他模块:进程 > `.env.<环境>.local` > 旧 `.env.local` > `.env.<环境>` > `.env`,保留原有私有凭据优先级。
|
|
127
|
+
所有 `HAI_*` 统一使用:进程 > `.env.<mode>.local` > `.env.local` > `.env.<mode>` > `.env` > YAML。同来源 JSON 为基础,细项继续覆盖;高来源整段替换先清掉低来源细项。Node/Vite 与部署共用 Core 合并,不维护模块专用顺序。
|
|
119
128
|
|
|
120
|
-
|
|
129
|
+
公共配置保存在 `.env` 和 `.env.<mode>`,连接凭据与 SSH 配置保存在忽略 Git 的 `.env.<mode>.local`,`.env.local` 用于本机共享私有值。构建不读取部署秘密。
|
|
121
130
|
|
|
122
131
|
已有项目需把原构建对象移入 `hai.json.build`,把原部署 YAML 转成 JSON 放入 `hai.json.deploy`,删除原文件并保留现有 `HAI_DEPLOY_*` 变量。已有自建 S3 的项目首次升级必须全量部署:旧成功基线仍含已退出的一次性初始化服务,直接使用新增量包会被依赖存活检查拒绝;全量部署同时刷新中间件环境中的 `createBucket`,之后即可继续增量更新。
|
|
123
132
|
|
|
@@ -127,9 +136,9 @@ SSH 参数保存到选中环境的 `.env.<环境>.local`。`--bundle-only` 在
|
|
|
127
136
|
|
|
128
137
|
#### 按应用真正需要的中间件处理
|
|
129
138
|
|
|
130
|
-
|
|
139
|
+
根据目标应用锁文件的运行依赖和模块配置识别数据库、缓存、文件存储、向量库、LDAP、邮件等;仅保留注释配置不会启用模块。LDAP 只有在有效配置 `iam.login.ldap=true` 时才列入部署需求,未启用时不询问。逐项选择默认、自定义或保持。目标环境已有完整配置时,`--yes` 保持原值,不重新输入或重复云开通;交互模式仍可选择本次处理范围。配置完备可用 `--skip-middleware` 跳过向导和开通。SQLite、Memory cache、本地存储不产生外部镜像;Compose 自建服务的镜像自动加入完整交付包。外部服务仍需目标网络可达,云开通不属于完全离线部署。
|
|
131
140
|
|
|
132
|
-
默认和自定义模式均保留同后端的有效配置,包括表单未展示的连接池、操作日志、存储公开端点/前缀、Redis 重试和向量检索选项。自建与云开通仅重新定稿实际连接和认证;旧 URL、TLS/集群模式与凭据不能覆盖新服务,SMTP 发件人和 LDAP
|
|
141
|
+
默认和自定义模式均保留同后端的有效配置,包括表单未展示的连接池、操作日志、存储公开端点/前缀、Redis 重试和向量检索选项。自建与云开通仅重新定稿实际连接和认证;旧 URL、TLS/集群模式与凭据不能覆盖新服务,SMTP 发件人和 LDAP 属性映射继续保留。切换后端时清除不兼容字段。 生成端优先写细项,数组保留 JSON;手写 JSON 允许兜底,放在对应模块细项之前。同源细项覆盖 JSON,动态字典、JSON 文本和显式空对象保留原义;整体替换展开时以最上层 `{}` 标记阻止旧 YAML 字段恢复。容器路径只写交付配置,不反写工程源环境。
|
|
133
142
|
|
|
134
143
|
#### 私有对象存储直连
|
|
135
144
|
|
|
@@ -263,13 +272,19 @@ SSH 中断始终先记 `remoteExecution: 'unknown'`。docker-ssh SDK 用新连
|
|
|
263
272
|
|
|
264
273
|
### 运行环境文件与重复部署
|
|
265
274
|
|
|
266
|
-
Node 后端(Serv / SvelteKit SSR
|
|
275
|
+
Node 后端(Serv / SvelteKit SSR)每次启动读取服务器 `<部署目录>/config/.env.runtime`。应用配置与托管连接在此合并,修改 `HAI_STORAGE_PUBLICENDPOINT` 等细项后重启对应后端即可;同一来源的细项覆盖父 JSON。配置目录只读挂载到 `/run/hai/config`,编辑器原子替换文件也会被下一次启动读取。私有代理标记和 Node 启动参数由部署装配管理;静态 Web、中间件原生环境、端口/挂载、Nginx 路由与限额变更仍需部署。仅更新 Web/中间件保留当前应用配置;后端局部更新保留服务器托管连接,其余值来自新包,全量更新采用新包并按已有凭据状态定稿。手工修改先备份,长期配置同步回源环境。
|
|
276
|
+
|
|
277
|
+
容器打包时,监听地址采用 `0.0.0.0`,端口采用制品声明值(未声明时采用部署配置),并生成 `HOST`、`PORT` 和 Serv 的对应细项。本机 `.env` 中的回环地址与开发端口继续用于本地运行。修改容器端口时更新制品与部署布局后重新部署。
|
|
278
|
+
|
|
279
|
+
Nginx 通过动态上游跟随应用和存储容器的 DNS 地址变化。共用的 `.hai/proxy-start.sh` 作为镜像入口钩子执行,保留镜像原有启动命令,并从容器 `/etc/resolv.conf` 读取解析器,HTTPS 时同时监视证书更新;Docker 与 Podman 均无需固定 DNS 地址。修改运行配置后重启后端,即使容器 IP 变化,代理也会自动恢复连接。
|
|
267
280
|
|
|
268
281
|
同前缀但由进程直接读取的环境标记、初始账号密码和传输密钥环仍保留;它们不属于已被父对象替代的配置。
|
|
269
282
|
|
|
270
|
-
|
|
283
|
+
生成端优先写细项,数组保留 JSON;手写 JSON 允许兜底,放在对应模块细项之前。同源细项覆盖 JSON,动态字典、JSON 文本和显式空对象保留原义;整体替换展开时以最上层 `{}` 标记阻止旧 YAML 字段恢复。容器路径只写交付配置,不反写工程源环境。
|
|
284
|
+
|
|
285
|
+
部署归档只保留五个顶层入口:`apply.sh`、`manifest.json`、`config/`、`images/`、`.hai/`。`config/.env.runtime` 是唯一应用环境文件;`manifest.json.metadata` 集中保存依赖、布局、健康路径、镜像列表、受管绑定等非敏感元数据。`.hai/compose.json` 是完整服务快照;局部更新在服务边界替换并保留未选服务,不串联历史 Compose 文件。`.hai/` 还存原生环境、加载器、Nginx/TLS、策略和校验和,秘密不写入 manifest。辅助脚本合入 `apply.sh`。失败时先恢复原应用配置再恢复服务,凭据、后端账本和数据卷继续保护。新包格式为 2,不实现旧目录兼容或迁移。 运行配置定稿复用本包已导入的 Node 应用镜像,临时容器禁网,不额外下载镜像;服务器无需安装 Node 或 jq。
|
|
271
286
|
|
|
272
|
-
|
|
287
|
+
中间件原生变量(`POSTGRES_*`、`MYSQL_*`、`RUSTFS_*`、`MINIO_*`、`LDAP_*`)按镜像契约保留原名,只写私有 `.hai/middleware-runtime.env`,仅由中间件读取。应用连接全部合并到 `config/.env.runtime`。
|
|
273
288
|
|
|
274
289
|
自建或云开通的整体 `HAI_*` 配置会清除被替换的旧叶字段,例如 Redis 覆盖后不再留下 `HAI_CACHE_TYPE=memory`。全局 `~/.hai/credentials.yml` 只补缺项,不能覆盖当前环境/进程的同名键或父配置。部署先启动选中中间件,等待 PostgreSQL/MySQL/Redis/S3 健康,再启动 serv/Web;增量和失败恢复沿用此顺序,未选容器保持不变。
|
|
275
290
|
|
|
@@ -277,9 +292,9 @@ Podman 部署会主动执行容器已有的 healthcheck 并重新核对状态,
|
|
|
277
292
|
|
|
278
293
|
分批启动仅在首批清理孤立容器,后续 serv/Web 启动保留已经就绪的中间件。
|
|
279
294
|
|
|
280
|
-
|
|
295
|
+
创建工程时为 `.env.dev.local`、`.env.test.local`、`.env.prod.local` 分别生成独立初始密码和传输密钥环,文件不提交 Git。克隆工程后通过受保护文件或 Secret Manager 分发对应环境秘密,同环境所有节点使用同一 ring 与共享 registry,不在节点启动时生成。缺少或无效的 `HAI_CRYPTO_TRANSPORT_SERVERKEYRING` 在上传前报错。CLI 和离线说明只提示初始密码变量及来源,不输出值;密码仅用于创建缺失账号,已有账号不被重置。
|
|
281
296
|
|
|
282
|
-
每次交付仍生成
|
|
297
|
+
每次交付仍生成 归档内 `config/.env.runtime`,供后端启动预加载(静态 Web 仍用 Compose env_file);它是生成产物,不是配置记忆源。`NODE_ENV` 默认随目标环境选择:`test` → `test`,`dev/development` → `development`,`prod/production` 及其他模式 → `production`。环境文件中显式设置的 `NODE_ENV` 仍优先于该默认值;`--build` 的构建优化模式不决定运行环境。旧交付包不会自动改写,需重新打包。
|
|
283
298
|
|
|
284
299
|
项目根 `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` 不会回写工程。
|
|
285
300
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
/** Node
|
|
1
|
+
/** 应用仅加载一份运行配置;部署阶段在隔离 Node 容器内定稿连接与托管密码。 */
|
|
2
2
|
import { Buffer } from 'node:buffer'
|
|
3
3
|
import { closeSync, constants, fstatSync, openSync, readSync } from 'node:fs'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
4
5
|
import process from 'node:process'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
5
7
|
import { parseEnv } from 'node:util'
|
|
6
8
|
|
|
7
9
|
const maximumBytes = 1024 * 1024
|
|
8
10
|
|
|
9
|
-
/**
|
|
10
|
-
function
|
|
11
|
+
/** 拒绝特殊文件、链接、超限与不可读文件,不输出秘密。 */
|
|
12
|
+
function readText(name) {
|
|
11
13
|
let descriptor
|
|
12
14
|
try {
|
|
13
15
|
descriptor = openSync(new URL(name, import.meta.url), constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0))
|
|
@@ -29,30 +31,162 @@ function readEnvironment(name) {
|
|
|
29
31
|
}
|
|
30
32
|
if (length > maximumBytes)
|
|
31
33
|
throw new Error('Runtime environment file exceeds 1 MiB')
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
return buffer.subarray(0, length).toString('utf8')
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
closeSync(descriptor)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** 使用与启动入口一致的 dotenv 字面规则,保留混合引号、UTF-8 和多行值。 */
|
|
42
|
+
function readEnvironment(name) {
|
|
43
|
+
if (name === '-')
|
|
44
|
+
return {}
|
|
45
|
+
try {
|
|
46
|
+
const text = readText(name)
|
|
47
|
+
const values = parseEnv(text)
|
|
48
|
+
const names = new Set()
|
|
49
|
+
for (const match of text.matchAll(/^(?:export\s+)?([A-Z_]\w*)\s*=\s*('[^']*'|"(?:[^"\\]|\\.)*"|`[^`]*`|[^\r\n]*)/gim)) {
|
|
50
|
+
if (names.has(match[1]))
|
|
51
|
+
throw new Error('Duplicate environment variable')
|
|
52
|
+
names.add(match[1])
|
|
53
|
+
if (match[2].startsWith('"') && (match[2].includes('\\"') || match[2].includes('\\u0027'))) {
|
|
37
54
|
try {
|
|
38
55
|
values[match[1]] = JSON.parse(match[2])
|
|
39
56
|
}
|
|
40
57
|
catch { /* 普通 dotenv 双引号仍按 Node 原生规则解析。 */ }
|
|
41
58
|
}
|
|
42
|
-
return values
|
|
43
59
|
}
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
return values
|
|
61
|
+
}
|
|
62
|
+
catch { throw new Error('Runtime environment file could not be parsed') }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 清单只保存非敏感元数据,不把环境值放入错误消息。 */
|
|
66
|
+
function readManifest(name) {
|
|
67
|
+
if (name === '-')
|
|
68
|
+
return { metadata: {} }
|
|
69
|
+
try {
|
|
70
|
+
const value = JSON.parse(readText(name))
|
|
71
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || !value.metadata || typeof value.metadata !== 'object' || Array.isArray(value.metadata)
|
|
72
|
+
|| Object.values(value.metadata).some(item => typeof item !== 'string')) {
|
|
73
|
+
throw new Error('Invalid manifest')
|
|
46
74
|
}
|
|
75
|
+
return value
|
|
47
76
|
}
|
|
48
|
-
|
|
49
|
-
|
|
77
|
+
catch { throw new Error('Deployment manifest could not be parsed') }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 服务器凭据只在部署时注入最终配置;运行启动器不再读取第二份覆盖文件。 */
|
|
81
|
+
function applyManagedPasswords(values, bindings, state) {
|
|
82
|
+
for (const line of bindings.split('\n').filter(Boolean)) {
|
|
83
|
+
const [group, prefix, scope] = line.split(' ')
|
|
84
|
+
if (scope !== 'prefix' || !Object.keys(values).some(key => key === prefix || key.startsWith(`${prefix}_`)))
|
|
85
|
+
continue
|
|
86
|
+
if (!['DB_PASSWORD', 'STORAGE_PASSWORD', 'LDAP_PASSWORD'].includes(group) || !state[group])
|
|
87
|
+
throw new Error('Managed middleware credential state is missing')
|
|
88
|
+
let password = state[group]
|
|
89
|
+
if (state.FORMAT_VERSION === '2') {
|
|
90
|
+
if (!/^(?:[a-f0-9]{2})+$/.test(password))
|
|
91
|
+
throw new Error('Managed middleware credential encoding is invalid')
|
|
92
|
+
password = Buffer.from(password, 'hex').toString('utf8')
|
|
93
|
+
}
|
|
94
|
+
let configuration = {}
|
|
95
|
+
const parent = Object.hasOwn(values, prefix)
|
|
96
|
+
if (parent) {
|
|
97
|
+
try {
|
|
98
|
+
configuration = JSON.parse(values[prefix])
|
|
99
|
+
if (!configuration || typeof configuration !== 'object' || Array.isArray(configuration))
|
|
100
|
+
throw new Error('Invalid object')
|
|
101
|
+
}
|
|
102
|
+
catch { throw new Error(`Invalid managed application configuration: ${prefix}`) }
|
|
103
|
+
}
|
|
104
|
+
const passwordField = prefix === 'HAI_STORAGE' ? 'secretAccessKey' : prefix === 'HAI_IAM_LDAP' ? 'bindPassword' : 'password'
|
|
105
|
+
if (!['HAI_STORAGE', 'HAI_IAM_LDAP', 'HAI_DB', 'HAI_VECDB'].includes(prefix))
|
|
106
|
+
throw new Error('Unsupported managed middleware binding')
|
|
107
|
+
const url = values[`${prefix}_URL`] ?? configuration.url
|
|
108
|
+
if (['HAI_DB', 'HAI_VECDB'].includes(prefix) && url) {
|
|
109
|
+
try {
|
|
110
|
+
const connection = new URL(url)
|
|
111
|
+
connection.password = encodeURIComponent(password)
|
|
112
|
+
values[`${prefix}_URL`] = connection.toString()
|
|
113
|
+
}
|
|
114
|
+
catch { throw new Error(`Invalid managed application connection: ${prefix}`) }
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
values[`${prefix}_${passwordField.toUpperCase()}`] = password
|
|
118
|
+
}
|
|
50
119
|
}
|
|
51
120
|
}
|
|
52
121
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
122
|
+
/** 保留父 JSON 与显式细项;粗细覆盖由应用通用配置解析器完成。 */
|
|
123
|
+
function serialize(values) {
|
|
124
|
+
return `${Object.entries(values).sort(([left], [right]) => left.localeCompare(right)).filter(([name]) => name !== 'NODE_OPTIONS' && name !== 'HAI_DEPLOY_PRIVATE_PROXY').map(([name, value]) => {
|
|
125
|
+
const literal = !value.includes('\'') ? `'${value}'` : JSON.stringify(value).replaceAll('\'', '\\u0027')
|
|
126
|
+
return `${name}=${literal}`
|
|
127
|
+
}).join('\n')}\n`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** 局部后端发布沿用当前连接,完整发布采用新配置;不改动托管凭据状态。 */
|
|
131
|
+
function prepare(directory, previousManifest, previousEnvironment, credentialState, mode) {
|
|
132
|
+
if (!['full', 'partial'].includes(mode))
|
|
133
|
+
throw new Error('Invalid runtime configuration preparation mode')
|
|
134
|
+
const base = new URL(directory, import.meta.url)
|
|
135
|
+
const incoming = readManifest(new URL('manifest.json', base))
|
|
136
|
+
const previous = mode === 'full' ? { metadata: {} } : readManifest(previousManifest)
|
|
137
|
+
const before = mode === 'full' ? {} : readEnvironment(previousEnvironment)
|
|
138
|
+
const next = readEnvironment(new URL('config/.env.runtime', base))
|
|
139
|
+
const entries = key => [...new Set(`${previous.metadata[key] ?? ''}\n${incoming.metadata[key] ?? ''}`.split('\n').filter(Boolean))]
|
|
140
|
+
const keys = entries('managed-app-keys')
|
|
141
|
+
if (keys.some(key => !/^HAI_[A-Z0-9_]+$/.test(key)))
|
|
142
|
+
throw new Error('Invalid managed application environment key')
|
|
143
|
+
if (mode === 'partial') {
|
|
144
|
+
for (const key of keys) {
|
|
145
|
+
const retained = Object.keys(before).filter(name => name === key || name.startsWith(`${key}_`))
|
|
146
|
+
if (retained.length) {
|
|
147
|
+
for (const name of Object.keys(next)) {
|
|
148
|
+
if (name === key || name.startsWith(`${key}_`))
|
|
149
|
+
delete next[name]
|
|
150
|
+
}
|
|
151
|
+
for (const name of retained)
|
|
152
|
+
next[name] = before[name]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const bindings = entries('managed-bindings').join('\n')
|
|
157
|
+
const state = readEnvironment(credentialState)
|
|
158
|
+
const project = incoming.metadata['managed-project'] ?? previous.metadata['managed-project']
|
|
159
|
+
if (state.PROJECT && project && state.PROJECT !== project.trim())
|
|
160
|
+
throw new Error('Managed middleware credential project mismatch')
|
|
161
|
+
applyManagedPasswords(next, bindings, state)
|
|
162
|
+
incoming.metadata['managed-app-keys'] = `${keys.join('\n')}${keys.length ? '\n' : ''}`
|
|
163
|
+
incoming.metadata['managed-bindings'] = `${bindings}${bindings ? '\n' : ''}`
|
|
164
|
+
if (project)
|
|
165
|
+
incoming.metadata['managed-project'] = project
|
|
166
|
+
const environment = serialize(next)
|
|
167
|
+
if (Buffer.byteLength(environment) > maximumBytes)
|
|
168
|
+
throw new Error('Prepared runtime environment exceeds 1 MiB')
|
|
169
|
+
return { environment, manifest: incoming }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
173
|
+
if (process.argv[2] !== 'prepare' || process.argv.length !== 8)
|
|
174
|
+
throw new Error('Runtime preparation requires a release directory, previous manifest, current environment, credential state and mode')
|
|
175
|
+
process.stdout.write(JSON.stringify(prepare(...process.argv.slice(3))))
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const values = readEnvironment('./config/.env.runtime')
|
|
179
|
+
// 启动参数与私有代理身份属于部署装配,不由业务运行文件覆盖。
|
|
180
|
+
delete values.NODE_OPTIONS
|
|
181
|
+
delete values.HAI_DEPLOY_PRIVATE_PROXY
|
|
182
|
+
// HAI 配置的显式进程来源最高;镜像内 NODE_ENV/HOST/PORT 默认值由运行文件定稿。
|
|
183
|
+
const inherited = Object.fromEntries(Object.entries(process.env).filter(([name, value]) => name.startsWith('HAI_') && value !== undefined))
|
|
184
|
+
for (const name of Object.keys(inherited).sort((left, right) => left.length - right.length)) {
|
|
185
|
+
for (const previous of Object.keys(values)) {
|
|
186
|
+
if (previous.startsWith(`${name}_`))
|
|
187
|
+
delete values[previous]
|
|
188
|
+
}
|
|
189
|
+
values[name] = inherited[name]
|
|
190
|
+
}
|
|
191
|
+
Object.assign(process.env, values)
|
|
192
|
+
}
|