@h-ai/deploy 1.0.0-beta.1 → 1.0.0-beta.2
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 +3 -1
- package/README.ja.md +3 -1
- package/README.md +3 -1
- package/dist/index.js +32 -14
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.en.md
CHANGED
|
@@ -118,7 +118,7 @@ Runtime lockfile dependencies and configuration determine middleware. Choose def
|
|
|
118
118
|
|
|
119
119
|
#### Direct private object storage access
|
|
120
120
|
|
|
121
|
-
Self-hosted S3 uses an internal endpoint
|
|
121
|
+
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.
|
|
122
122
|
|
|
123
123
|
Managed deployments retain bucket/region/prefix/publicEndpoint/publicUrl/operationLog while replacing internal connections and managed credentials. Adding the gateway or changing access settings requires a full baseline; partial preflight rejects mismatching configuration fingerprints. App/storage partial updates refresh retained proxy DNS, and recovery restores the previous topology. External/kept storage gets no local sidecar route; Local does not expose private directories. See the [storage handbook](../../docs/usage/configuration/data.md).
|
|
124
124
|
|
|
@@ -261,6 +261,8 @@ Serv `http.access` and SvelteKit `access` can reference separate yml/yaml files.
|
|
|
261
261
|
|
|
262
262
|
Full deployments and backend increments carry their own snapshot; frontend-only increments retain the backend mount. Failed deployment recovery uses the previous successful release's Compose and policy file. Non-root containers can read the policy inside a private release directory. Image and policy rollback does not undo database migrations or completed business effects.
|
|
263
263
|
|
|
264
|
+
For operator-managed rules that reload on restart, manage a read-only file outside the bundle and use a Compose override to replace the same `/run/hai/request-access.yml` mount. SDK rollback does not restore this external file. Recreate the container once to apply the mount; later file changes require a restart. Changed `env_file` variables still require container recreation, usually without rebuilding the image. See [mounted policy setup and verification](../../docs/usage/configuration/request-access.md#44-运维独立管理文件修改后重启生效).
|
|
265
|
+
|
|
264
266
|
Managed Compose sets `HAI_DEPLOY_PRIVATE_PROXY=1` only when a Web/gateway proxy fronts a backend without public ports. Serv/Kit default `trustedProxies: auto` uses container networks there and ignores forwarded headers on other hosts. Generated Nginx overwrites client-supplied forwarding headers with the actual peer. For an upstream load balancer, explicitly set Docker SSH Provider `trustedEdgeProxies` to its IPs/CIDRs; omitting it trusts no upstream, and `[]` explicitly selects the same behavior. Source addresses lost before Nginx cannot be reconstructed, including some rootless forwarding environments. See the [request access handbook](../../docs/usage/configuration/request-access.md) for examples and verification.
|
|
265
267
|
|
|
266
268
|
## Development and testing
|
package/README.ja.md
CHANGED
|
@@ -116,7 +116,7 @@ SSH 設定は選択環境の `.env.<環境>.local` に保存します。`--bundl
|
|
|
116
116
|
|
|
117
117
|
#### 非公開オブジェクトストレージへの直接アクセス
|
|
118
118
|
|
|
119
|
-
自前 S3 の endpoint
|
|
119
|
+
自前 S3 の endpoint は内部接続先です。publicEndpoint に `https://app.example.com/delivery/files` などの外部アドレスを設定できます。Nginx は `/delivery/files/<bucket>/...` を受け、入口パスだけを除去し、本来のバケット URI、署名クエリ、Host を保持して storage:9000 に転送します。Serv を経由せず、パス省略時は従来のルートバケット形式です。サイト配下に置けますが API、資源、ヘルスパスの競合は拒否します。ポートと管理コンソールは非公開、バッファリングと署名ログは無効です。認可と CORS は S3 に設定し、外部プロキシは入口パスと Host を保持してください。
|
|
120
120
|
|
|
121
121
|
自前構成は bucket/region/prefix/publicEndpoint/publicUrl/operationLog を保持し、内部接続と受管認証情報だけを置き換えます。gateway 追加や公開設定変更には完全デプロイが必要で、部分更新の事前検証は設定摘要の不一致を拒否します。アプリ/ストレージ部分更新では保持プロキシの DNS を更新し、失敗時は以前の構成に復元します。外部/保持接続にローカルルートは生成せず、Local の非公開ディレクトリも公開しません。[ストレージ手引き](../../docs/usage/configuration/data.md)を参照してください。
|
|
122
122
|
|
|
@@ -259,6 +259,8 @@ Serv の `http.access` と SvelteKit の `access` は独立した yml/yaml を
|
|
|
259
259
|
|
|
260
260
|
全量とバックエンドを含む増分は独自のスナップショットを持ち、フロントエンドだけの増分は旧バックエンドのマウントを維持します。失敗時は前回成功した Compose とポリシーへ復旧します。非 root コンテナでも私有リリースディレクトリ内のファイルを読み取れます。イメージと設定の復旧は DB 移行や実施済み業務を取り消しません。
|
|
261
261
|
|
|
262
|
+
運用側でルールを変更して再起動で反映する場合は、配布物の外で読み取り専用ファイルを管理し、Compose override で同じ `/run/hai/request-access.yml` のマウントを置き換えます。この外部ファイルは SDK の自動復旧対象外です。初回マウント適用はコンテナ再作成、以後のファイル変更は再起動で反映できます。`env_file` の環境変数変更は引き続きコンテナ再作成が必要で、通常イメージ再構築は不要です。[設定と実測の境界](../../docs/usage/configuration/request-access.md#44-运维独立管理文件修改后重启生效)を参照してください。
|
|
263
|
+
|
|
262
264
|
Web/gateway 入口がありバックエンドのポートが非公開の場合だけ、管理 Compose は `HAI_DEPLOY_PRIVATE_PROXY=1` を設定します。この構成の `trustedProxies: auto` はコンテナネットワークを採用し、他のホストでは転送ヘッダーを無視します。生成 Nginx は実接続元で転送ヘッダーを上書きします。外側の負荷分散器は Docker SSH Provider の `trustedEdgeProxies` に実 IP/CIDR を明記してください。省略時は上流を信頼せず、`[]` も同じ動作です。Nginx 到達前に失われた源 IP は復元できず、一部の rootless 構成も実測が必要です。[リクエスト制御手引き](../../docs/usage/configuration/request-access.md)に例と確認手順を記載しています。
|
|
263
265
|
|
|
264
266
|
## 開発とテスト
|
package/README.md
CHANGED
|
@@ -129,7 +129,7 @@ SSH 参数保存到选中环境的 `.env.<环境>.local`。`--bundle-only` 在
|
|
|
129
129
|
|
|
130
130
|
#### 私有对象存储直连
|
|
131
131
|
|
|
132
|
-
自建 S3 的 `endpoint` 使用 Compose
|
|
132
|
+
自建 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 网关去掉前缀。
|
|
133
133
|
|
|
134
134
|
自建配置保留 bucket/region/prefix/publicEndpoint/publicUrl/operationLog,只替换内部连接与托管凭据。首次增加网关或改变这些访问设置须全量部署,增量预检按配置摘要拒绝不一致;普通应用/存储增量刷新保留代理的服务 DNS,失败恢复使用旧拓扑。外部/保持连接不生成本地存储路由,Local 不公开私有目录。详见[存储配置手册](../../docs/usage/configuration/data.md#配置人员区分三个访问地址)。
|
|
135
135
|
|
|
@@ -271,6 +271,8 @@ Serv 的 `http.access`、SvelteKit 的 `access` 可引用独立 yml/yaml。部
|
|
|
271
271
|
|
|
272
272
|
全量与包含后端的增量都交付自己的快照;仅更新前端时保留后端原挂载。失败恢复使用旧成功发布的 Compose 和策略文件。运行账号无需 root:发布目录私有,策略文件在受限目录内以只读权限供容器读取。回滚镜像和策略不撤销数据库迁移或已经产生的业务效果。
|
|
273
273
|
|
|
274
|
+
如需由运维独立改规则、重启后生效,可在交付之外管理只读文件,并用 Compose override 替换同一 `/run/hai/request-access.yml` 挂载。该外部文件不参与 SDK 自动回滚,首次附加挂载需要重新创建容器;后续文件修改后重启即可。`env_file` 的环境变量改变仍需重新创建容器,通常无需重建镜像。步骤与验证边界见[独立文件重启配置](../../docs/usage/configuration/request-access.md#44-运维独立管理文件修改后重启生效)。
|
|
275
|
+
|
|
274
276
|
受管 Compose 只有存在 Web/gateway 代理且后端没有公开端口时,才设置 `HAI_DEPLOY_PRIVATE_PROXY=1`。Serv/Kit 默认 `trustedProxies: auto` 在此采用容器网段,其他宿主默认忽略转发头。生成 Nginx 用实际对端覆盖客户端提交的转发头;外层负载均衡需在 Docker SSH Provider 的 `trustedEdgeProxies` 显式配置真实 IP/CIDR,省略时不信任上游,`[]` 同样关闭信任。根本未传到 Nginx 的源 IP 无法恢复,Podman rootless 等环境应实际核验入口日志。完整示例和验证步骤见[请求访问控制手册](../../docs/usage/configuration/request-access.md)。
|
|
275
277
|
|
|
276
278
|
## 只读部署计划
|
package/dist/index.js
CHANGED
|
@@ -117,7 +117,7 @@ var en_US_default = {
|
|
|
117
117
|
deploy_webProxyInvalid: "The API prefix must be an absolute path containing only letters, numbers, underscores, hyphens and slashes.",
|
|
118
118
|
deploy_webBaseConflict: "The website mount path conflicts with an API or health endpoint. Choose a different PUBLIC_BASE_PATH.",
|
|
119
119
|
deploy_storageProxyInvalid: "Cannot generate the storage proxy. Use a valid DNS bucket name whose path does not overlap the website, API, or health endpoints.",
|
|
120
|
-
deploy_storagePublicEndpointInvalid: "Invalid storage.publicEndpoint. Use a browser-accessible HTTP(S)
|
|
120
|
+
deploy_storagePublicEndpointInvalid: "Invalid storage.publicEndpoint. Use a browser-accessible HTTP(S) address with an optional valid proxy path, without a bucket, credentials, query or fragment.",
|
|
121
121
|
deploy_storageBackendRequired: "A static browser app cannot deploy server-side storage credentials. Configure Storage in Serv or SvelteKit and obtain presigned links through its API.",
|
|
122
122
|
deploy_artifactInvalid: "Image artifacts are missing or invalid. Run pnpm build --target docker, select valid --artifact-dir, or use pnpm run deploy --build.",
|
|
123
123
|
deploy_sshRequired: "Automatic deployment requires SSH settings. Use --bundle-only for manual transfer when the server is unreachable.",
|
|
@@ -225,7 +225,7 @@ var ja_JP_default = {
|
|
|
225
225
|
deploy_webProxyInvalid: "API\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u306F\u3001\u82F1\u6570\u5B57\u3001\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u3001\u30CF\u30A4\u30D5\u30F3\u3001\u30B9\u30E9\u30C3\u30B7\u30E5\u306E\u307F\u3092\u542B\u3080\u7D76\u5BFE\u30D1\u30B9\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
226
226
|
deploy_webBaseConflict: "\u30B5\u30A4\u30C8\u306E\u30DE\u30A6\u30F3\u30C8\u30D1\u30B9\u304CAPI\u307E\u305F\u306F\u30D8\u30EB\u30B9\u30C1\u30A7\u30C3\u30AF\u3068\u7AF6\u5408\u3057\u3066\u3044\u307E\u3059\u3002\u5225\u306EPUBLIC_BASE_PATH\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
227
227
|
deploy_storageProxyInvalid: "\u30B9\u30C8\u30EC\u30FC\u30B8\u30D7\u30ED\u30AD\u30B7\u3092\u751F\u6210\u3067\u304D\u307E\u305B\u3093\u3002\u6709\u52B9\u306A DNS \u30D0\u30B1\u30C3\u30C8\u540D\u3092\u6307\u5B9A\u3057\u3001\u30B5\u30A4\u30C8\u3001API\u3001\u30D8\u30EB\u30B9\u30C1\u30A7\u30C3\u30AF\u306E\u30D1\u30B9\u3068\u306E\u91CD\u8907\u3092\u907F\u3051\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
228
|
-
deploy_storagePublicEndpointInvalid: "storage.publicEndpoint \u304C\u7121\u52B9\u3067\u3059\u3002\
|
|
228
|
+
deploy_storagePublicEndpointInvalid: "storage.publicEndpoint \u304C\u7121\u52B9\u3067\u3059\u3002\u30D6\u30E9\u30A6\u30B6\u30FC\u304B\u3089\u5230\u9054\u53EF\u80FD\u306A HTTP(S) \u30A2\u30C9\u30EC\u30B9\u306B\u6709\u52B9\u306A\u30D7\u30ED\u30AD\u30B7\u30D1\u30B9\u3092\u6307\u5B9A\u3067\u304D\u307E\u3059\u3002\u30D0\u30B1\u30C3\u30C8\u3001\u8A8D\u8A3C\u60C5\u5831\u3001\u30AF\u30A8\u30EA\u3001\u30D5\u30E9\u30B0\u30E1\u30F3\u30C8\u306F\u542B\u3081\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002",
|
|
229
229
|
deploy_storageBackendRequired: "\u9759\u7684\u30D6\u30E9\u30A6\u30B6\u30A2\u30D7\u30EA\u306B\u306F\u30B5\u30FC\u30D0\u30FC\u7528\u30B9\u30C8\u30EC\u30FC\u30B8\u8A8D\u8A3C\u60C5\u5831\u3092\u914D\u7F6E\u3067\u304D\u307E\u305B\u3093\u3002Serv \u307E\u305F\u306F SvelteKit \u3067 Storage \u3092\u8A2D\u5B9A\u3057\u3001API \u7D4C\u7531\u3067\u7F72\u540D\u4ED8\u304D\u30EA\u30F3\u30AF\u3092\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
230
230
|
deploy_artifactInvalid: "\u30A4\u30E1\u30FC\u30B8\u6210\u679C\u7269\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u3001\u7121\u52B9\u3067\u3059\u3002pnpm build --target docker \u3092\u5B9F\u884C\u3059\u308B\u304B\u3001--artifact-dir \u3067\u6709\u52B9\u306A\u6210\u679C\u7269\u3092\u9078\u629E\u3059\u308B\u304B\u3001pnpm run deploy --build \u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
231
231
|
deploy_sshRequired: "\u81EA\u52D5\u30C7\u30D7\u30ED\u30A4\u306B\u306FSSH\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059\u3002\u30B5\u30FC\u30D0\u30FC\u306B\u63A5\u7D9A\u3067\u304D\u306A\u3044\u5834\u5408\u306F--bundle-only\u3067\u624B\u52D5\u8EE2\u9001\u7528\u306E\u30D0\u30F3\u30C9\u30EB\u3092\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
@@ -333,7 +333,7 @@ var zh_CN_default = {
|
|
|
333
333
|
deploy_webProxyInvalid: "API \u524D\u7F00\u5FC5\u987B\u662F\u4EC5\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u3001\u8FDE\u5B57\u7B26\u548C\u659C\u6760\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002",
|
|
334
334
|
deploy_webBaseConflict: "\u7F51\u7AD9\u6302\u8F7D\u76EE\u5F55\u4E0E API \u6216\u5065\u5EB7\u68C0\u67E5\u5165\u53E3\u51B2\u7A81\uFF0C\u8BF7\u4F7F\u7528\u4E0D\u540C\u7684 PUBLIC_BASE_PATH\u3002",
|
|
335
335
|
deploy_storageProxyInvalid: "\u65E0\u6CD5\u751F\u6210\u5BF9\u8C61\u5B58\u50A8\u4EE3\u7406\uFF1A\u8BF7\u4F7F\u7528\u6709\u6548\u7684 DNS \u6876\u540D\uFF0C\u5E76\u786E\u4FDD\u6876\u8DEF\u5F84\u4E0D\u4E0E\u7F51\u7AD9\u76EE\u5F55\u3001API \u6216\u5065\u5EB7\u68C0\u67E5\u5165\u53E3\u91CD\u53E0\u3002",
|
|
336
|
-
deploy_storagePublicEndpointInvalid: "storage.publicEndpoint \u65E0\u6548\uFF1A\u8BF7\u586B\u5199\u6D4F\u89C8\u5668\u53EF\u8BBF\u95EE\u7684 HTTP(S) \
|
|
336
|
+
deploy_storagePublicEndpointInvalid: "storage.publicEndpoint \u65E0\u6548\uFF1A\u8BF7\u586B\u5199\u6D4F\u89C8\u5668\u53EF\u8BBF\u95EE\u7684 HTTP(S) \u5730\u5740\uFF0C\u53EF\u5305\u542B\u89C4\u8303\u7684\u4EE3\u7406\u76EE\u5F55\uFF0C\u4E0D\u80FD\u5305\u542B\u6876\u540D\u3001\u51ED\u636E\u3001\u67E5\u8BE2\u6216\u7247\u6BB5\u3002",
|
|
337
337
|
deploy_storageBackendRequired: "\u9759\u6001\u6D4F\u89C8\u5668\u5E94\u7528\u4E0D\u80FD\u90E8\u7F72\u5B58\u50A8\u670D\u52A1\u7AEF\u914D\u7F6E\u3002\u8BF7\u5728 Serv \u6216 SvelteKit \u670D\u52A1\u4E2D\u914D\u7F6E Storage\uFF0C\u518D\u901A\u8FC7 API \u83B7\u53D6\u9884\u7B7E\u540D\u94FE\u63A5\u3002",
|
|
338
338
|
deploy_artifactInvalid: "\u955C\u50CF\u4EA7\u7269\u7F3A\u5931\u6216\u6821\u9A8C\u5931\u8D25\u3002\u8BF7\u5148\u6267\u884C pnpm build --target docker\uFF0C\u6216\u7528 --artifact-dir \u6307\u5B9A\u6709\u6548\u4EA7\u7269\uFF1B\u4E5F\u53EF\u7528 pnpm run deploy --build \u4E00\u6B21\u5B8C\u6210\u3002",
|
|
339
339
|
deploy_sshRequired: "\u81EA\u52A8\u90E8\u7F72\u9700\u8981 SSH \u914D\u7F6E\uFF1B\u65E0\u7F51\u7EDC\u8FDE\u63A5\u65F6\u4F7F\u7528 --bundle-only \u751F\u6210\u90E8\u7F72\u5305\u5E76\u624B\u5DE5\u642C\u8FD0\u3002",
|
|
@@ -3791,8 +3791,8 @@ function generateWebProxyConfig(port, apiPrefix, options = {}) {
|
|
|
3791
3791
|
throw new Error("Invalid web proxy target");
|
|
3792
3792
|
}
|
|
3793
3793
|
const webLocation = renderWebLocation(webBasePath);
|
|
3794
|
-
const storageLocation = options.storageBucket ? renderStorageLocation(options.storageBucket, [apiPath, webBasePath]) : "";
|
|
3795
|
-
return
|
|
3794
|
+
const storageLocation = options.storageBucket ? renderStorageLocation(options.storageBucket, [apiPath, ...options.storageBasePath ? [`${webBasePath}/_app`, `${webBasePath}/assets`, `${webBasePath}/runtime-config.js`] : [webBasePath]], options.storageBasePath) : "";
|
|
3795
|
+
return `${options.storageBucket ? renderStorageUpstream(options.storageBucket, options.storageBasePath) : ""}server {
|
|
3796
3796
|
listen ${webPort};
|
|
3797
3797
|
server_name _;
|
|
3798
3798
|
${renderTrustedEdgeProxies(options.trustedEdgeProxies)} root /usr/share/nginx/html;
|
|
@@ -3828,9 +3828,11 @@ ${storageLocation}${webLocation} location = ${webBasePath}/runtime-config.js {
|
|
|
3828
3828
|
}
|
|
3829
3829
|
`;
|
|
3830
3830
|
}
|
|
3831
|
-
function renderStorageLocation(bucket, reservedPaths) {
|
|
3832
|
-
|
|
3833
|
-
|
|
3831
|
+
function renderStorageLocation(bucket, reservedPaths, storageBasePath = "") {
|
|
3832
|
+
renderWebLocation(storageBasePath);
|
|
3833
|
+
const path = `${storageBasePath}/${bucket}`;
|
|
3834
|
+
const reserved = ["/health", "/_hai", ...reservedPaths].filter(Boolean);
|
|
3835
|
+
if (!/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bucket) || bucket.includes("..") || ["health", "_hai"].includes(bucket) || reserved.some((item) => item === path || item.startsWith(`${path}/`) || path.startsWith(`${item}/`) || storageBasePath && (item === storageBasePath || item.startsWith(`${storageBasePath}/`) || storageBasePath.startsWith(`${item}/`)))) {
|
|
3834
3836
|
throw new Error("Storage bucket conflicts with application routes or is not a valid DNS bucket name");
|
|
3835
3837
|
}
|
|
3836
3838
|
return ` location ^~ ${path}/ {
|
|
@@ -3847,7 +3849,8 @@ function renderStorageLocation(bucket, reservedPaths) {
|
|
|
3847
3849
|
add_header Cache-Control "private, no-store" always;
|
|
3848
3850
|
limit_except GET PUT OPTIONS { deny all; }
|
|
3849
3851
|
client_max_body_size 0;
|
|
3850
|
-
|
|
3852
|
+
${storageBasePath ? ` # map \u4FDD\u7559\u539F\u59CB URI \u4E0E\u67E5\u8BE2\uFF1B\u4E0D\u5728 location \u5185\u521B\u5EFA\u6539\u53D8\u4EE3\u7406\u7EE7\u627F\u7684 if \u914D\u7F6E\u3002
|
|
3853
|
+
proxy_pass http://hai_storage_proxy$hai_storage_uri;` : " proxy_pass http://storage:9000;"}
|
|
3851
3854
|
proxy_http_version 1.1;
|
|
3852
3855
|
proxy_set_header Host $http_host;
|
|
3853
3856
|
proxy_set_header Connection "";
|
|
@@ -3859,16 +3862,27 @@ function renderStorageLocation(bucket, reservedPaths) {
|
|
|
3859
3862
|
}
|
|
3860
3863
|
`;
|
|
3861
3864
|
}
|
|
3865
|
+
function renderStorageUpstream(bucket, storageBasePath = "") {
|
|
3866
|
+
if (!storageBasePath)
|
|
3867
|
+
return "";
|
|
3868
|
+
const path = `${storageBasePath}/${bucket}`.replaceAll(".", "\\\\.");
|
|
3869
|
+
return `upstream hai_storage_proxy { server storage:9000; }
|
|
3870
|
+
map $request_uri $hai_storage_uri {
|
|
3871
|
+
default $request_uri;
|
|
3872
|
+
"~^${path}(/.*)$" /${bucket}$1;
|
|
3873
|
+
}
|
|
3874
|
+
`;
|
|
3875
|
+
}
|
|
3862
3876
|
function generateApplicationProxyConfig(options) {
|
|
3863
3877
|
const { primaryService, port, bucket, reservedPaths, maxPlaintextBytes } = options;
|
|
3864
3878
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
3865
3879
|
throw new Error("Invalid application proxy target");
|
|
3866
3880
|
const bodyLimits = resolveTransportBodyLimits(maxPlaintextBytes);
|
|
3867
|
-
return
|
|
3881
|
+
return `${renderStorageUpstream(bucket, options.storageBasePath)}server {
|
|
3868
3882
|
listen 8080;
|
|
3869
3883
|
server_name _;
|
|
3870
3884
|
${renderTrustedEdgeProxies(options.trustedEdgeProxies)} large_client_header_buffers 4 32k;
|
|
3871
|
-
${renderStorageLocation(bucket, reservedPaths)} location = /_hai/gateway-health {
|
|
3885
|
+
${renderStorageLocation(bucket, reservedPaths, options.storageBasePath)} location = /_hai/gateway-health {
|
|
3872
3886
|
access_log off;
|
|
3873
3887
|
return 200 'ok';
|
|
3874
3888
|
}
|
|
@@ -4056,13 +4070,17 @@ function createDockerSshProvider(config, container) {
|
|
|
4056
4070
|
const healthPaths = hasWebProxy ? ["/health", "/_hai/web-health", `${webBasePath}/`] : scan.isSvelteKit ? [`${webBasePath}/`] : standaloneWebConfig ? ["/health", `${webBasePath}/`] : ["/health"];
|
|
4057
4071
|
const { env, sidecar } = buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort, options, appEnv, container?.s3Engine);
|
|
4058
4072
|
const managedStorage = sidecar.services.includes("storage") ? JSON.parse(sidecar.env.HAI_STORAGE) : void 0;
|
|
4073
|
+
let storageBasePath = "";
|
|
4059
4074
|
if (managedStorage?.publicEndpoint !== void 0) {
|
|
4060
4075
|
try {
|
|
4061
4076
|
if (typeof managedStorage.publicEndpoint !== "string")
|
|
4062
4077
|
throw new Error("Invalid public endpoint type");
|
|
4078
|
+
const unsafeCharacters = /[\u0000-\u0020\u007F\\%]/.test(managedStorage.publicEndpoint);
|
|
4063
4079
|
const endpoint = new URL(managedStorage.publicEndpoint);
|
|
4064
|
-
if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.
|
|
4080
|
+
if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || !/^\/(?:[\w.-]+(?:\/[\w.-]+)*\/?)?$/.test(endpoint.pathname) || unsafeCharacters || /(?:^|\/)\.{1,2}(?:\/|$)/.test(managedStorage.publicEndpoint)) {
|
|
4065
4081
|
throw new Error("Invalid public endpoint origin");
|
|
4082
|
+
}
|
|
4083
|
+
storageBasePath = endpoint.pathname.replace(/\/+$/, "");
|
|
4066
4084
|
} catch {
|
|
4067
4085
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storagePublicEndpointInvalid"));
|
|
4068
4086
|
}
|
|
@@ -4146,7 +4164,7 @@ function createDockerSshProvider(config, container) {
|
|
|
4146
4164
|
try {
|
|
4147
4165
|
const configured = transport ? transport.maxPlaintextBytes : void 0;
|
|
4148
4166
|
const limits = resolveTransportBodyLimits(Math.max(createTransportBodyPolicy(configured === "auto" ? void 0 : configured, transport ? transport.routePlaintextBytes : void 0).maximum.plaintextBytes, feedbackTransportBytes ?? 0));
|
|
4149
|
-
webProxy = generateWebProxyConfig(containerPort, apiPrefix, { webPort: webContainerPort, maxPlaintextBytes: limits.plaintextBytes, webBasePath, storageBucket: managedStorage?.bucket, trustedEdgeProxies: config.trustedEdgeProxies });
|
|
4167
|
+
webProxy = generateWebProxyConfig(containerPort, apiPrefix, { webPort: webContainerPort, maxPlaintextBytes: limits.plaintextBytes, webBasePath, storageBucket: managedStorage?.bucket, storageBasePath, trustedEdgeProxies: config.trustedEdgeProxies });
|
|
4150
4168
|
} catch (error) {
|
|
4151
4169
|
return err(HaiDeployError.CONFIG_ERROR, error instanceof RangeError ? error.message : deployM(managedStorage ? "deploy_storageProxyInvalid" : "deploy_transportBodyLimitInvalid"));
|
|
4152
4170
|
}
|
|
@@ -4158,7 +4176,7 @@ function createDockerSshProvider(config, container) {
|
|
|
4158
4176
|
const transport = server.data.transport;
|
|
4159
4177
|
applicationPlaintextBytes = Math.max(createTransportBodyPolicy(transport?.maxPlaintextBytes === "auto" ? void 0 : transport?.maxPlaintextBytes, transport?.routePlaintextBytes).maximum.plaintextBytes, feedbackTransportBytes ?? 0);
|
|
4160
4178
|
}
|
|
4161
|
-
webProxy = generateApplicationProxyConfig({ primaryService, port: containerPort, bucket: managedStorage.bucket, reservedPaths: [
|
|
4179
|
+
webProxy = generateApplicationProxyConfig({ primaryService, port: containerPort, bucket: managedStorage.bucket, storageBasePath, reservedPaths: [http?.apiPrefix ?? "/api/v1", ...storageBasePath ? [`${webBasePath}/_app`, `${webBasePath}/assets`, `${webBasePath}/runtime-config.js`] : [webBasePath]], maxPlaintextBytes: applicationPlaintextBytes, trustedEdgeProxies: config.trustedEdgeProxies });
|
|
4162
4180
|
} catch {
|
|
4163
4181
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storageProxyInvalid"));
|
|
4164
4182
|
}
|