@h-ai/deploy 1.0.0-beta.21 → 1.0.0-beta.23
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 +8 -10
- package/README.ja.md +7 -9
- package/README.md +7 -9
- package/dist/deploy-runtime-env.mjs +3 -11
- package/dist/index.d.ts +6 -38
- package/dist/index.js +255 -667
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/docs/credential-import.en.md +0 -40
- package/docs/credential-import.ja.md +0 -40
- package/docs/credential-import.md +0 -70
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID, randomBytes, X509Certificate, createPublicKey, createPrivateKey } from 'crypto';
|
|
2
|
-
import { createReadStream, readFileSync, existsSync, lstatSync, mkdirSync, readdirSync,
|
|
2
|
+
import { createReadStream, readFileSync, existsSync, lstatSync, mkdirSync, readdirSync, openSync, constants, fstatSync, readSync, closeSync, fchmodSync, writeFileSync, fsyncSync, renameSync, unlinkSync, chmodSync, mkdtempSync, copyFileSync, rmSync, realpathSync, createWriteStream, statSync } from 'fs';
|
|
3
3
|
import { resolve, extname, join, dirname, basename, relative, sep } from 'path';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { core, err, ok, CoreConfigSchema, RequestAccessConfigSchema, TrustedProxiesSchema } from '@h-ai/core';
|
|
6
|
-
import {
|
|
6
|
+
import { execFileSync, spawn } from 'child_process';
|
|
7
7
|
import process from 'process';
|
|
8
8
|
import { LineCounter, parseDocument, parse, stringify, parseAllDocuments } from 'yaml';
|
|
9
9
|
import { Buffer, constants as constants$1 } from 'buffer';
|
|
@@ -22,17 +22,14 @@ import { isIP } from 'net';
|
|
|
22
22
|
// src/deploy-artifact.ts
|
|
23
23
|
var DeploymentArtifactSchema = z.object({
|
|
24
24
|
/** 清单格式版本,与应用版本和部署环境无关。 */
|
|
25
|
-
version: z.
|
|
26
|
-
z.literal(4),
|
|
27
|
-
z.literal(5)
|
|
28
|
-
]),
|
|
25
|
+
version: z.literal(5),
|
|
29
26
|
/** 主运行服务:独立后端为 serv,独立浏览器站点(静态或 SSR)为 web。 */
|
|
30
27
|
primaryService: z.enum(["serv", "web"]),
|
|
31
28
|
/** 应用根 package.json 的版本,用于归档、Compose 与桌面交付;最长 80 字符。 */
|
|
32
29
|
appVersion: z.string().max(80).regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9a-z-]+(?:\.[0-9a-z-]+)*)?(?:\+[0-9a-z-]+(?:\.[0-9a-z-]+)*)?$/i),
|
|
33
30
|
/** 本次构建标识;同一应用版本允许重复构建。 */
|
|
34
31
|
buildId: z.string().max(128).regex(/^[a-z0-9][\w.+-]*$/i),
|
|
35
|
-
/** 构建启动时观察到的 Git
|
|
32
|
+
/** 构建启动时观察到的 Git 来源。 */
|
|
36
33
|
source: z.object({
|
|
37
34
|
/** 工程 package.json 所在仓库的提交 SHA;无法确定时为 unknown。 */
|
|
38
35
|
commit: z.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64}|unknown)$/),
|
|
@@ -43,8 +40,8 @@ var DeploymentArtifactSchema = z.object({
|
|
|
43
40
|
partial: z.boolean().default(false),
|
|
44
41
|
/** 是否包含浏览器端;仅构建 serv 时也保留完整工程的 Web 拓扑。 */
|
|
45
42
|
hasWeb: z.boolean().default(false),
|
|
46
|
-
/** Web
|
|
47
|
-
webBasePath: z.string().refine((value) => value === "" || /^\/[\w.-]+(?:\/[\w.-]+)*$/.test(value) && !value.split("/").some((segment) => segment === "." || segment === ".."))
|
|
43
|
+
/** Web 构建的公开挂载目录,无末尾斜杠;空字符串表示域名根目录。增量制品须保留完整拓扑的同一值。 */
|
|
44
|
+
webBasePath: z.string().refine((value) => value === "" || /^\/[\w.-]+(?:\/[\w.-]+)*$/.test(value) && !value.split("/").some((segment) => segment === "." || segment === "..")),
|
|
48
45
|
/** 工程显式配置的后端端口,部分构建未包含 serv 时仍保留。 */
|
|
49
46
|
servContainerPort: z.number().int().min(1).max(65535).optional(),
|
|
50
47
|
/** 工程显式配置的 Web 端口,部分构建未包含 web 镜像时仍保留;缺省使用部署默认值。 */
|
|
@@ -59,12 +56,12 @@ var DeploymentArtifactSchema = z.object({
|
|
|
59
56
|
imageRef: z.string().regex(/^[a-z0-9][a-z0-9./:_-]*:[a-z0-9_][a-z0-9_.-]*$/).refine((value) => !value.endsWith(":latest")),
|
|
60
57
|
/** tar 内容的 SHA-256,用于部署前校验。 */
|
|
61
58
|
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
62
|
-
/** 引擎实际构建的镜像 OS
|
|
59
|
+
/** 引擎实际构建的镜像 OS/架构。 */
|
|
63
60
|
platform: z.string().regex(/^[a-z0-9][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*$/).optional(),
|
|
64
61
|
/** 容器内部监听端口;服务器公开端口由部署选择。 */
|
|
65
62
|
containerPort: z.number().int().min(1).max(65535).optional()
|
|
66
63
|
})).min(1).max(2).refine((images) => new Set(images.map((image) => image.name)).size === images.length)
|
|
67
|
-
}).refine((artifact) =>
|
|
64
|
+
}).refine((artifact) => !artifact.webBasePath || artifact.hasWeb).refine((artifact) => artifact.images.every((image) => image.file === `${image.name}-${artifact.appVersion}.tar`)).refine((artifact) => artifact.primaryService !== "web" || artifact.hasWeb).refine((artifact) => artifact.images.every((image) => image.name === artifact.primaryService || image.name === "web" && artifact.hasWeb)).refine((artifact) => artifact.partial || artifact.images.some((image) => image.name === artifact.primaryService)).refine((artifact) => artifact.partial || !artifact.hasWeb || artifact.images.some((image) => image.name === "web"));
|
|
68
65
|
async function artifactChecksum(file) {
|
|
69
66
|
const hash = createHash("sha256");
|
|
70
67
|
for await (const chunk of createReadStream(file))
|
|
@@ -189,9 +186,7 @@ var en_US_default = {
|
|
|
189
186
|
deploy_uploadPasswordCancelled: "Password input cancelled; no server connection was made.",
|
|
190
187
|
deploy_uploadPasswordInvalid: "SSH passwords must contain 1\u20131023 UTF-8 bytes without line breaks.",
|
|
191
188
|
deploy_managedSecretsInvalid: "Invalid generated middleware credential candidates. Rebuild the deployment bundle; server credentials will not be rotated automatically.",
|
|
192
|
-
deploy_secretImportFailed: "Legacy credential import was rejected during {phase}; check private input permissions, project/volume identity, live credentials and the deployment lock. No services were updated; inspect the protected state file if commit is unconfirmed",
|
|
193
189
|
deploy_projectNameRequired: "The root package.json.name is missing or cannot be normalized into an image and deployment resource identity.",
|
|
194
|
-
deploy_projectNameOverrideRemoved: "Deployment identity comes from the root package.json.name; remove the obsolete projectName override.",
|
|
195
190
|
deploy_requestAccessInvalid: "Invalid request access policy ({field}). Check fields, IP addresses, timezones and time windows. For a file reference, use readable YAML no larger than 1 MiB. Fix the configuration and create a new bundle.",
|
|
196
191
|
deploy_httpsPortsInvalid: "HTTP and HTTPS ports must differ; Let\u2019s Encrypt HTTP-01 requires host port 80.",
|
|
197
192
|
deploy_httpsCertificateInvalid: "Invalid HTTPS certificate. Check readable PEM files, matching unencrypted private key, validity dates and domain, then repackage.",
|
|
@@ -221,7 +216,9 @@ var en_US_default = {
|
|
|
221
216
|
deploy_middlewareMethod: "Enter POST or PUT.",
|
|
222
217
|
deploy_configResolutionFailed: "Cannot resolve {path}. Check the configuration structure and array indices for {fields}.",
|
|
223
218
|
deploy_reachEmptyProviderOverride: "While resolving {path}, {field} refers to a missing provider because HAI_REACH_PROVIDERS is []. Remove stale indexed variables and set HAI_REACH_PROVIDERS to the complete notification provider list in .env.<environment>.local.",
|
|
224
|
-
deploy_s3Engine: "Choose the self-hosted S3 implementation"
|
|
219
|
+
deploy_s3Engine: "Choose the self-hosted S3 implementation",
|
|
220
|
+
deploy_downloadUnavailableTitle: "Download link unavailable",
|
|
221
|
+
deploy_downloadUnavailableInstruction: "Return to the download page to get a new link. If the download still fails, contact your administrator."
|
|
225
222
|
};
|
|
226
223
|
|
|
227
224
|
// messages/ja-JP.json
|
|
@@ -332,9 +329,7 @@ var ja_JP_default = {
|
|
|
332
329
|
deploy_uploadPasswordCancelled: "\u30D1\u30B9\u30EF\u30FC\u30C9\u5165\u529B\u3092\u4E2D\u6B62\u3057\u307E\u3057\u305F\u3002\u30B5\u30FC\u30D0\u30FC\u306B\u306F\u672A\u63A5\u7D9A\u3067\u3059\u3002",
|
|
333
330
|
deploy_uploadPasswordInvalid: "SSH \u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u6539\u884C\u3092\u542B\u307E\u306A\u3044 1\u20131023 UTF-8 \u30D0\u30A4\u30C8\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
334
331
|
deploy_managedSecretsInvalid: "\u81EA\u5EFA\u30DF\u30C9\u30EB\u30A6\u30A7\u30A2\u306E\u5019\u88DC\u8A8D\u8A3C\u60C5\u5831\u304C\u7121\u52B9\u3067\u3059\u3002\u914D\u7F6E\u30D1\u30C3\u30B1\u30FC\u30B8\u3092\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30B5\u30FC\u30D0\u30FC\u306E\u8A8D\u8A3C\u60C5\u5831\u306F\u81EA\u52D5\u5909\u66F4\u3055\u308C\u307E\u305B\u3093\u3002",
|
|
335
|
-
deploy_secretImportFailed: "\u65E7\u8A8D\u8A3C\u60C5\u5831\u306E\u30A4\u30F3\u30DD\u30FC\u30C8\u306F {phase} \u6BB5\u968E\u3067\u62D2\u5426\u3055\u308C\u307E\u3057\u305F\u3002\u5165\u529B\u6A29\u9650\u3001\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u3068\u30DC\u30EA\u30E5\u30FC\u30E0\u3001\u5B9F\u969B\u306E\u8A8D\u8A3C\u60C5\u5831\u3001\u30C7\u30D7\u30ED\u30A4\u30ED\u30C3\u30AF\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30B5\u30FC\u30D3\u30B9\u306F\u66F4\u65B0\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u78BA\u5B9A\u304C\u4E0D\u660E\u306A\u5834\u5408\u306F\u4FDD\u8B77\u3055\u308C\u305F\u72B6\u614B\u30D5\u30A1\u30A4\u30EB\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
336
332
|
deploy_projectNameRequired: "\u5DE5\u7A0B\u30EB\u30FC\u30C8\u306E package.json.name \u304C\u306A\u3044\u304B\u6B63\u898F\u5316\u3067\u304D\u306A\u3044\u305F\u3081\u3001\u30A4\u30E1\u30FC\u30B8\u3068\u30C7\u30D7\u30ED\u30A4\u30EA\u30BD\u30FC\u30B9\u306E\u8B58\u5225\u540D\u3092\u751F\u6210\u3067\u304D\u307E\u305B\u3093\u3002",
|
|
337
|
-
deploy_projectNameOverrideRemoved: "\u30C7\u30D7\u30ED\u30A4\u8B58\u5225\u540D\u306F\u5DE5\u7A0B\u30EB\u30FC\u30C8\u306E package.json.name \u304B\u3089\u53D6\u5F97\u3057\u307E\u3059\u3002\u65E7 projectName \u4E0A\u66F8\u304D\u5F15\u6570\u3092\u524A\u9664\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
338
333
|
deploy_requestAccessInvalid: "\u30EA\u30AF\u30A8\u30B9\u30C8\u30A2\u30AF\u30BB\u30B9\u8A2D\u5B9A\uFF08{field}\uFF09\u304C\u7121\u52B9\u3067\u3059\u3002\u30D5\u30A3\u30FC\u30EB\u30C9\u3001IP\u3001\u30BF\u30A4\u30E0\u30BE\u30FC\u30F3\u3001\u6642\u9593\u5E2F\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30D5\u30A1\u30A4\u30EB\u306F\u8AAD\u307F\u53D6\u308A\u53EF\u80FD\u306A 1 MiB \u4EE5\u4E0B\u306E YAML \u3092\u6307\u5B9A\u3057\u3001\u4FEE\u6B63\u5F8C\u306B\u518D\u30D1\u30C3\u30B1\u30FC\u30B8\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
339
334
|
deploy_httpsPortsInvalid: "HTTP \u3068 HTTPS \u306E\u30DD\u30FC\u30C8\u306F\u7570\u306A\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002Let\u2019s Encrypt HTTP-01 \u306B\u306F\u30DB\u30B9\u30C8\u306E\u30DD\u30FC\u30C8 80 \u304C\u5FC5\u8981\u3067\u3059\u3002",
|
|
340
335
|
deploy_httpsCertificateInvalid: "HTTPS \u8A3C\u660E\u66F8\u304C\u7121\u52B9\u3067\u3059\u3002PEM \u30D5\u30A1\u30A4\u30EB\u3001\u6697\u53F7\u5316\u3055\u308C\u3066\u3044\u306A\u3044\u79D8\u5BC6\u9375\u3068\u306E\u4E00\u81F4\u3001\u6709\u52B9\u671F\u9593\u3001\u30C9\u30E1\u30A4\u30F3\u3092\u78BA\u8A8D\u3057\u3001\u518D\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
@@ -364,7 +359,9 @@ var ja_JP_default = {
|
|
|
364
359
|
deploy_middlewareMethod: "POST \u307E\u305F\u306F PUT \u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
365
360
|
deploy_configResolutionFailed: "{path} \u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002{fields} \u306B\u5BFE\u5FDC\u3059\u308B\u8A2D\u5B9A\u306E\u69CB\u9020\u3068\u914D\u5217\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
366
361
|
deploy_reachEmptyProviderOverride: "{path} \u306E\u6709\u52B9\u306A\u8A2D\u5B9A\u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002HAI_REACH_PROVIDERS \u304C [] \u306E\u305F\u3081\u3001{field} \u306B\u5BFE\u5FDC\u3059\u308B\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u304C\u3042\u308A\u307E\u305B\u3093\u3002\u53E4\u3044\u30A4\u30F3\u30C7\u30C3\u30AF\u30B9\u4ED8\u304D\u5909\u6570\u3092\u524A\u9664\u3057\u3001.env.<\u74B0\u5883>.local \u306E HAI_REACH_PROVIDERS \u306B\u901A\u77E5\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306E\u5B8C\u5168\u306A\u4E00\u89A7\u3092\u8A2D\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
367
|
-
deploy_s3Engine: "\u30BB\u30EB\u30D5\u30DB\u30B9\u30C8 S3 \u306E\u5B9F\u88C5\u3092\u9078\u629E"
|
|
362
|
+
deploy_s3Engine: "\u30BB\u30EB\u30D5\u30DB\u30B9\u30C8 S3 \u306E\u5B9F\u88C5\u3092\u9078\u629E",
|
|
363
|
+
deploy_downloadUnavailableTitle: "\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u30EA\u30F3\u30AF\u306B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u307E\u305B\u3093",
|
|
364
|
+
deploy_downloadUnavailableInstruction: "\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u30DA\u30FC\u30B8\u306B\u623B\u308A\u3001\u65B0\u3057\u3044\u30EA\u30F3\u30AF\u3092\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u89E3\u6C7A\u3057\u306A\u3044\u5834\u5408\u306F\u7BA1\u7406\u8005\u306B\u304A\u554F\u3044\u5408\u308F\u305B\u304F\u3060\u3055\u3044\u3002"
|
|
368
365
|
};
|
|
369
366
|
|
|
370
367
|
// messages/zh-CN.json
|
|
@@ -475,9 +472,7 @@ var zh_CN_default = {
|
|
|
475
472
|
deploy_uploadPasswordCancelled: "\u5DF2\u53D6\u6D88\u5BC6\u7801\u8F93\u5165\uFF0C\u5C1A\u672A\u8FDE\u63A5\u670D\u52A1\u5668\u3002",
|
|
476
473
|
deploy_uploadPasswordInvalid: "SSH \u5BC6\u7801\u987B\u4E3A 1\u20131023 \u4E2A UTF-8 \u5B57\u8282\uFF0C\u4E14\u4E0D\u80FD\u5305\u542B\u6362\u884C\u3002",
|
|
477
474
|
deploy_managedSecretsInvalid: "\u81EA\u5EFA\u4E2D\u95F4\u4EF6\u5019\u9009\u51ED\u636E\u65E0\u6548\uFF0C\u8BF7\u91CD\u65B0\u751F\u6210\u90E8\u7F72\u5305\uFF1B\u670D\u52A1\u5668\u51ED\u636E\u4E0D\u4F1A\u81EA\u52A8\u66F4\u6362\u3002",
|
|
478
|
-
deploy_secretImportFailed: "\u65E7\u51ED\u636E\u5BFC\u5165\u5728 {phase} \u9636\u6BB5\u88AB\u62D2\u7EDD\uFF1B\u8BF7\u6838\u5BF9\u79C1\u6709\u8F93\u5165\u6743\u9650\u3001\u9879\u76EE/\u5377\u8EAB\u4EFD\u3001\u5B9E\u9645\u670D\u52A1\u51ED\u636E\u53CA\u90E8\u7F72\u9501\uFF0C\u672A\u6267\u884C\u670D\u52A1\u66F4\u65B0\uFF1B\u63D0\u4EA4\u7ED3\u679C\u4E0D\u660E\u65F6\u8BF7\u6838\u5BF9\u53D7\u4FDD\u62A4\u7684\u72B6\u6001\u6587\u4EF6",
|
|
479
475
|
deploy_projectNameRequired: "\u5DE5\u7A0B\u6839 package.json.name \u7F3A\u5931\u6216\u65E0\u6CD5\u5F52\u4E00\u5316\uFF0C\u4E0D\u80FD\u751F\u6210\u955C\u50CF\u4E0E\u90E8\u7F72\u8D44\u6E90\u8EAB\u4EFD\u3002",
|
|
480
|
-
deploy_projectNameOverrideRemoved: "\u90E8\u7F72\u9879\u76EE\u540D\u7EDF\u4E00\u6765\u81EA\u5DE5\u7A0B\u6839 package.json.name\uFF1B\u8BF7\u79FB\u9664\u65E7 projectName \u8986\u76D6\u53C2\u6570\u3002",
|
|
481
476
|
deploy_requestAccessInvalid: "\u8BF7\u6C42\u8BBF\u95EE\u7B56\u7565\u914D\u7F6E\u65E0\u6548\uFF08{field}\uFF09\u3002\u8BF7\u68C0\u67E5\u5B57\u6BB5\u3001IP\u3001\u65F6\u533A\u53CA\u65F6\u95F4\u7A97\u53E3\uFF1B\u5982\u5F15\u7528\u6587\u4EF6\uFF0C\u8BF7\u786E\u8BA4\u6587\u4EF6\u53EF\u8BFB\u3001\u683C\u5F0F\u4E3A YAML \u4E14\u4E0D\u8D85\u8FC7 1 MiB\u3002\u4FEE\u6B63\u540E\u91CD\u65B0\u6253\u5305\u3002",
|
|
482
477
|
deploy_httpsPortsInvalid: "HTTP \u4E0E HTTPS \u7AEF\u53E3\u5FC5\u987B\u4E0D\u540C\uFF1BLet\u2019s Encrypt \u7684 HTTP-01 \u5165\u53E3\u5FC5\u987B\u4F7F\u7528\u5BBF\u4E3B\u673A 80 \u7AEF\u53E3\u3002",
|
|
483
478
|
deploy_httpsCertificateInvalid: "HTTPS \u8BC1\u4E66\u65E0\u6548\uFF1A\u8BF7\u6838\u5BF9 PEM \u6587\u4EF6\u53EF\u8BFB\u3001\u8BC1\u4E66\u4E0E\u672A\u52A0\u5BC6\u79C1\u94A5\u5339\u914D\u3001\u8BC1\u4E66\u6709\u6548\u671F\u53CA\u57DF\u540D\uFF0C\u7136\u540E\u91CD\u65B0\u6253\u5305\u3002",
|
|
@@ -507,7 +502,9 @@ var zh_CN_default = {
|
|
|
507
502
|
deploy_middlewareMethod: "\u8BF7\u8F93\u5165 POST \u6216 PUT\u3002",
|
|
508
503
|
deploy_configResolutionFailed: "\u65E0\u6CD5\u89E3\u6790 {path}\u3002\u8BF7\u68C0\u67E5 {fields} \u5BF9\u5E94\u7684\u914D\u7F6E\u7ED3\u6784\u4E0E\u6570\u7EC4\u7D22\u5F15\u3002",
|
|
509
504
|
deploy_reachEmptyProviderOverride: "\u89E3\u6790 {path} \u7684\u751F\u6548\u914D\u7F6E\u65F6\u53D1\u73B0 {field} \u6307\u5411\u4E0D\u5B58\u5728\u7684 Provider\u3002HAI_REACH_PROVIDERS \u5F53\u524D\u4E3A []\u3002\u8BF7\u5220\u9664\u65E7\u7684\u7D22\u5F15\u53D8\u91CF\uFF0C\u5E76\u5728 .env.<\u73AF\u5883>.local \u4E2D\u7528 HAI_REACH_PROVIDERS \u914D\u7F6E\u5B8C\u6574\u7684\u901A\u77E5 Provider \u5217\u8868\u3002",
|
|
510
|
-
deploy_s3Engine: "\u9009\u62E9\u81EA\u5EFA S3 \u670D\u52A1\u5B9E\u73B0"
|
|
505
|
+
deploy_s3Engine: "\u9009\u62E9\u81EA\u5EFA S3 \u670D\u52A1\u5B9E\u73B0",
|
|
506
|
+
deploy_downloadUnavailableTitle: "\u4E0B\u8F7D\u94FE\u63A5\u65E0\u6CD5\u8BBF\u95EE",
|
|
507
|
+
deploy_downloadUnavailableInstruction: "\u8BF7\u8FD4\u56DE\u4E0B\u8F7D\u9875\u9762\u91CD\u65B0\u83B7\u53D6\u94FE\u63A5\u3002\u82E5\u4ECD\u65E0\u6CD5\u4E0B\u8F7D\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002"
|
|
511
508
|
};
|
|
512
509
|
|
|
513
510
|
// src/deploy-i18n.ts
|
|
@@ -1457,7 +1454,26 @@ var deploy_config_shapes_default = {
|
|
|
1457
1454
|
type: "string",
|
|
1458
1455
|
format: "uri"
|
|
1459
1456
|
},
|
|
1457
|
+
multipart: {
|
|
1458
|
+
default: {},
|
|
1459
|
+
type: "object",
|
|
1460
|
+
properties: {
|
|
1461
|
+
partSizeBytes: {
|
|
1462
|
+
default: 5242880,
|
|
1463
|
+
type: "integer",
|
|
1464
|
+
minimum: 5242880,
|
|
1465
|
+
maximum: 5368709120
|
|
1466
|
+
},
|
|
1467
|
+
queueSize: {
|
|
1468
|
+
default: 2,
|
|
1469
|
+
type: "integer",
|
|
1470
|
+
minimum: 1,
|
|
1471
|
+
maximum: 9007199254740991
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1460
1475
|
operationLog: {
|
|
1476
|
+
default: {},
|
|
1461
1477
|
type: "object",
|
|
1462
1478
|
properties: {
|
|
1463
1479
|
read: {
|
|
@@ -1465,7 +1481,7 @@ var deploy_config_shapes_default = {
|
|
|
1465
1481
|
type: "boolean"
|
|
1466
1482
|
},
|
|
1467
1483
|
write: {
|
|
1468
|
-
default:
|
|
1484
|
+
default: true,
|
|
1469
1485
|
type: "boolean"
|
|
1470
1486
|
},
|
|
1471
1487
|
maxLength: {
|
|
@@ -1473,15 +1489,6 @@ var deploy_config_shapes_default = {
|
|
|
1473
1489
|
type: "integer",
|
|
1474
1490
|
minimum: 0,
|
|
1475
1491
|
maximum: 9007199254740991
|
|
1476
|
-
},
|
|
1477
|
-
level: {
|
|
1478
|
-
default: "debug",
|
|
1479
|
-
type: "string",
|
|
1480
|
-
enum: [
|
|
1481
|
-
"info",
|
|
1482
|
-
"debug",
|
|
1483
|
-
"trace"
|
|
1484
|
-
]
|
|
1485
1492
|
}
|
|
1486
1493
|
}
|
|
1487
1494
|
}
|
|
@@ -1514,6 +1521,7 @@ var deploy_config_shapes_default = {
|
|
|
1514
1521
|
type: "number"
|
|
1515
1522
|
},
|
|
1516
1523
|
operationLog: {
|
|
1524
|
+
default: {},
|
|
1517
1525
|
type: "object",
|
|
1518
1526
|
properties: {
|
|
1519
1527
|
read: {
|
|
@@ -1521,7 +1529,7 @@ var deploy_config_shapes_default = {
|
|
|
1521
1529
|
type: "boolean"
|
|
1522
1530
|
},
|
|
1523
1531
|
write: {
|
|
1524
|
-
default:
|
|
1532
|
+
default: true,
|
|
1525
1533
|
type: "boolean"
|
|
1526
1534
|
},
|
|
1527
1535
|
maxLength: {
|
|
@@ -1529,15 +1537,6 @@ var deploy_config_shapes_default = {
|
|
|
1529
1537
|
type: "integer",
|
|
1530
1538
|
minimum: 0,
|
|
1531
1539
|
maximum: 9007199254740991
|
|
1532
|
-
},
|
|
1533
|
-
level: {
|
|
1534
|
-
default: "debug",
|
|
1535
|
-
type: "string",
|
|
1536
|
-
enum: [
|
|
1537
|
-
"info",
|
|
1538
|
-
"debug",
|
|
1539
|
-
"trace"
|
|
1540
|
-
]
|
|
1541
1540
|
}
|
|
1542
1541
|
}
|
|
1543
1542
|
}
|
|
@@ -6253,9 +6252,9 @@ function existingFile(path) {
|
|
|
6253
6252
|
throw error;
|
|
6254
6253
|
}
|
|
6255
6254
|
}
|
|
6256
|
-
function writeLocalFile(path, content,
|
|
6255
|
+
function writeLocalFile(path, content, privateFile = false) {
|
|
6257
6256
|
const previous = existingFile(path);
|
|
6258
|
-
const mode =
|
|
6257
|
+
const mode = privateFile ? 384 : previous ? Number(previous.mode & 0o777n) : 420;
|
|
6259
6258
|
const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
|
|
6260
6259
|
let fd;
|
|
6261
6260
|
let owned;
|
|
@@ -6462,7 +6461,7 @@ function isSecret(name, value) {
|
|
|
6462
6461
|
}
|
|
6463
6462
|
function generatedVariables(values) {
|
|
6464
6463
|
const generated = {};
|
|
6465
|
-
const
|
|
6464
|
+
const groups2 = /* @__PURE__ */ new Map();
|
|
6466
6465
|
const structures = /* @__PURE__ */ new Set();
|
|
6467
6466
|
for (const [name, value] of Object.entries(values)) {
|
|
6468
6467
|
if (value === void 0)
|
|
@@ -6474,9 +6473,9 @@ function generatedVariables(values) {
|
|
|
6474
6473
|
const module = /^HAI_([A-Z0-9]+)(?:_|$)/.exec(name)?.[1]?.toLowerCase();
|
|
6475
6474
|
if (!module || !shapes[module])
|
|
6476
6475
|
continue;
|
|
6477
|
-
const group =
|
|
6476
|
+
const group = groups2.get(module) ?? {};
|
|
6478
6477
|
group[name] = text;
|
|
6479
|
-
|
|
6478
|
+
groups2.set(module, group);
|
|
6480
6479
|
try {
|
|
6481
6480
|
const parsed = JSON.parse(text);
|
|
6482
6481
|
if (parsed !== null && typeof parsed === "object")
|
|
@@ -6484,7 +6483,7 @@ function generatedVariables(values) {
|
|
|
6484
6483
|
} catch {
|
|
6485
6484
|
}
|
|
6486
6485
|
}
|
|
6487
|
-
for (const [module, group] of
|
|
6486
|
+
for (const [module, group] of groups2) {
|
|
6488
6487
|
const structuredNames = Object.keys(group).filter((name) => structures.has(name));
|
|
6489
6488
|
if (!structuredNames.length)
|
|
6490
6489
|
continue;
|
|
@@ -6531,7 +6530,7 @@ function generatedVariables(values) {
|
|
|
6531
6530
|
}
|
|
6532
6531
|
return generated;
|
|
6533
6532
|
}
|
|
6534
|
-
function updateFile(path, values,
|
|
6533
|
+
function updateFile(path, values, privateFile) {
|
|
6535
6534
|
let content = existsSync(path) ? readLocalFile(path).toString("utf8") : "# Local private deployment configuration; do not commit.\n";
|
|
6536
6535
|
for (const [name, value] of Object.entries(values)) {
|
|
6537
6536
|
if (!/^[A-Z][A-Z0-9_]*$/.test(name))
|
|
@@ -6555,15 +6554,15 @@ ${line}
|
|
|
6555
6554
|
content = withoutParent.slice(0, child.index) + parent[0].replace(/\r?\n?$/, "\n") + withoutParent.slice(child.index);
|
|
6556
6555
|
}
|
|
6557
6556
|
}
|
|
6558
|
-
writeLocalFile(path, content,
|
|
6557
|
+
writeLocalFile(path, content, privateFile);
|
|
6559
6558
|
}
|
|
6560
|
-
function removeOverrides(path, names,
|
|
6559
|
+
function removeOverrides(path, names, privateFile = true) {
|
|
6561
6560
|
if (!existsSync(path) || names.length === 0)
|
|
6562
6561
|
return;
|
|
6563
6562
|
let content = readLocalFile(path).toString("utf8");
|
|
6564
6563
|
for (const name of names)
|
|
6565
6564
|
content = content.replace(new RegExp(`^[\\t ]*(?:export[\\t ]+)?${name}[\\t ]*=[\\t ]*(?:'[^']*'|"(?:[^"\\\\]|\\\\.)*"|\`[^\`]*\`|[^\\r\\n]*)[^\\r\\n]*(?:\\r?\\n|$)`, "gm"), "");
|
|
6566
|
-
writeLocalFile(path, content,
|
|
6565
|
+
writeLocalFile(path, content, privateFile);
|
|
6567
6566
|
}
|
|
6568
6567
|
function saveDeploymentEnvironment(environment, values, options = {}) {
|
|
6569
6568
|
try {
|
|
@@ -7126,47 +7125,6 @@ async function scanApp(inputDir, options) {
|
|
|
7126
7125
|
}
|
|
7127
7126
|
}
|
|
7128
7127
|
|
|
7129
|
-
// src/container/deploy-compose-identity.ts
|
|
7130
|
-
function createComposeContainerLookupScript() {
|
|
7131
|
-
const labelFormat = [
|
|
7132
|
-
"com.docker.compose.project",
|
|
7133
|
-
"com.docker.compose.service",
|
|
7134
|
-
"io.podman.compose.project",
|
|
7135
|
-
"io.podman.compose.service"
|
|
7136
|
-
].map((key) => `{{index .Config.Labels \`${key}\`}}`).join("|");
|
|
7137
|
-
return String.raw`compose_container_ids() (
|
|
7138
|
-
if [ "$#" -ne 4 ]; then printf 'Compose container lookup requires runtime, project, service and selector.\n' >&2; exit 1; fi
|
|
7139
|
-
lookup_runtime=$1
|
|
7140
|
-
lookup_project=$2
|
|
7141
|
-
lookup_service=$3
|
|
7142
|
-
lookup_selector=$4
|
|
7143
|
-
case "$lookup_runtime" in docker|podman) ;; *) printf 'Invalid Compose lookup runtime.\n' >&2; exit 1;; esac
|
|
7144
|
-
case "$lookup_project" in ''|*[!A-Za-z0-9_.-]*) printf 'Invalid Compose project identity.\n' >&2; exit 1;; esac
|
|
7145
|
-
case "$lookup_service" in ''|*[!A-Za-z0-9_.-]*) printf 'Invalid Compose service identity.\n' >&2; exit 1;; esac
|
|
7146
|
-
case "$lookup_selector" in -q|-aq) ;; *) printf 'Invalid Compose container selector.\n' >&2; exit 1;; esac
|
|
7147
|
-
lookup_ids=''
|
|
7148
|
-
for lookup_namespace in com.docker.compose io.podman.compose; do
|
|
7149
|
-
lookup_found=$("$lookup_runtime" ps "$lookup_selector" --filter "label=$lookup_namespace.project=$lookup_project" --filter "label=$lookup_namespace.service=$lookup_service") || { printf 'Cannot query Compose containers.\n' >&2; exit 1; }
|
|
7150
|
-
printf '%s\n' "$lookup_found" | LC_ALL=C awk 'NF {if(NF!=1 || $0!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/) exit 1}' || { printf 'Invalid Compose container identifiers.\n' >&2; exit 1; }
|
|
7151
|
-
lookup_ids=$(printf '%s\n%s\n' "$lookup_ids" "$lookup_found" | LC_ALL=C awk 'NF && !seen[$0]++ {print}') || exit 1
|
|
7152
|
-
done
|
|
7153
|
-
for lookup_id in $lookup_ids; do
|
|
7154
|
-
lookup_labels=$("$lookup_runtime" inspect --format '${labelFormat}' "$lookup_id") || { printf 'Cannot inspect Compose container labels.\n' >&2; exit 1; }
|
|
7155
|
-
printf '%s\n' "$lookup_labels" | LC_ALL=C awk -F'|' -v project="$lookup_project" -v service="$lookup_service" '
|
|
7156
|
-
NR!=1 || NF!=4 {exit 1}
|
|
7157
|
-
{
|
|
7158
|
-
docker=($1!="" && $1!="<no value>") || ($2!="" && $2!="<no value>")
|
|
7159
|
-
podman=($3!="" && $3!="<no value>") || ($4!="" && $4!="<no value>")
|
|
7160
|
-
if((!docker && !podman) || (docker && ($1!=project || $2!=service)) || (podman && ($3!=project || $4!=service))) exit 1
|
|
7161
|
-
}
|
|
7162
|
-
END {if(NR!=1) exit 1}
|
|
7163
|
-
' || { printf 'Container labels do not confirm the requested Compose project and service.\n' >&2; exit 1; }
|
|
7164
|
-
done
|
|
7165
|
-
if [ -n "$lookup_ids" ]; then printf '%s\n' "$lookup_ids"; fi
|
|
7166
|
-
)
|
|
7167
|
-
`;
|
|
7168
|
-
}
|
|
7169
|
-
|
|
7170
7128
|
// src/container/deploy-json.ts
|
|
7171
7129
|
function createDeploymentJsonTool() {
|
|
7172
7130
|
return String.raw`function fail(message) {
|
|
@@ -7491,7 +7449,7 @@ deployment_metadata_init() {
|
|
|
7491
7449
|
deployment_metadata_file() {
|
|
7492
7450
|
metadata_directory=$1
|
|
7493
7451
|
metadata_name=$2
|
|
7494
|
-
case "$metadata_name" in layout|health-paths|image-list|dependencies|managed-
|
|
7452
|
+
case "$metadata_name" in layout|health-paths|image-list|dependencies|managed-bindings|managed-volumes|managed-app-keys|unmanaged-app-keys|state-backends|storage-routing|web-base-path|node-runtime|proxy-service|https) ;; *) printf 'Unknown deployment metadata field.\n' >&2; return 1;; esac
|
|
7495
7453
|
test -f "$metadata_directory/manifest.json" && test ! -L "$metadata_directory/manifest.json" || return 1
|
|
7496
7454
|
metadata_identity=$(printf '%s' "$metadata_directory" | sha256sum | awk '{print $1}')
|
|
7497
7455
|
metadata_cached="$metadata_work/$metadata_identity-$metadata_name"
|
|
@@ -7588,8 +7546,6 @@ function createManagedSecretAssets(projectName, nativeEnvironment, compose) {
|
|
|
7588
7546
|
}
|
|
7589
7547
|
}
|
|
7590
7548
|
return {
|
|
7591
|
-
"managed-project": `hai-${projectName}
|
|
7592
|
-
`,
|
|
7593
7549
|
"managed-candidates.env": candidates.length ? `${candidates.join("\n")}
|
|
7594
7550
|
` : "",
|
|
7595
7551
|
"managed-bindings": bindings.size ? `${[...bindings].join("\n")}
|
|
@@ -7599,29 +7555,6 @@ function createManagedSecretAssets(projectName, nativeEnvironment, compose) {
|
|
|
7599
7555
|
"resolve-managed-secrets.sh": createManagedSecretScript()
|
|
7600
7556
|
};
|
|
7601
7557
|
}
|
|
7602
|
-
function createManagedSecretValidationScript() {
|
|
7603
|
-
return String.raw`valid_values() {
|
|
7604
|
-
version=$(awk -F= '$1=="FORMAT_VERSION" {print $2}' "$1")
|
|
7605
|
-
if [ "$version" = 2 ]; then
|
|
7606
|
-
LC_ALL=C awk -F= '
|
|
7607
|
-
NF {
|
|
7608
|
-
if(NF!=2 || seen[$1]++) exit 1
|
|
7609
|
-
if($1=="FORMAT_VERSION") {if($2!="2") exit 1; next}
|
|
7610
|
-
if($1=="PROJECT") {if($2!~/^[A-Za-z0-9][A-Za-z0-9_.-]*$/) exit 1; project=1; next}
|
|
7611
|
-
if($1~/^VOLUME_[0-9]+$/) {if($2!~/^(DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD):[A-Za-z0-9][A-Za-z0-9_.-]*$/) exit 1; next}
|
|
7612
|
-
if($1!~/^(DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD)$/ || $2!~/^[0-9a-f]+$/ || length($2)%2 || length($2)>8192) exit 1
|
|
7613
|
-
for(i=1;i<=length($2);i+=2) {b=substr($2,i,2); if(b~/^[01]/ || b=="7f") exit 1}
|
|
7614
|
-
}
|
|
7615
|
-
END {if(!project) exit 1}
|
|
7616
|
-
' "$1"
|
|
7617
|
-
return
|
|
7618
|
-
fi
|
|
7619
|
-
LC_ALL=C awk -F= '
|
|
7620
|
-
NF {if(NF!=2 || $1!~/^(DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD)$/ || $2!~/^[A-Za-z0-9_-]+$/ || length($2)!=32 || seen[$1]++) exit 1}
|
|
7621
|
-
' "$1"
|
|
7622
|
-
}
|
|
7623
|
-
`;
|
|
7624
|
-
}
|
|
7625
7558
|
function createManagedSecretScript() {
|
|
7626
7559
|
return String.raw`#!/bin/sh
|
|
7627
7560
|
# 由 apply.sh 在项目部署锁内调用;长期凭据只保存在服务器,不依赖打包机。
|
|
@@ -7636,12 +7569,15 @@ test -d "$root" && test ! -L "$root" && test -d "$release/.hai" && test ! -L "$r
|
|
|
7636
7569
|
@@MANIFEST_FUNCTIONS@@
|
|
7637
7570
|
deployment_metadata_init
|
|
7638
7571
|
trap deployment_metadata_cleanup EXIT
|
|
7639
|
-
managed_project=$(deployment_metadata_file "$release" managed-project)
|
|
7640
7572
|
managed_bindings=$(deployment_metadata_file "$release" managed-bindings)
|
|
7641
7573
|
managed_volumes=$(deployment_metadata_file "$release" managed-volumes)
|
|
7642
7574
|
test -s "$candidate_file" || exit 0
|
|
7643
7575
|
fail() { printf '%s\n' "$*" >&2; exit 1; }
|
|
7644
|
-
|
|
7576
|
+
valid_values() {
|
|
7577
|
+
LC_ALL=C awk -F= '
|
|
7578
|
+
NF {if(NF!=2 || $1!~/^(DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD)$/ || $2!~/^[A-Za-z0-9_-]+$/ || length($2)!=32 || seen[$1]++) exit 1}
|
|
7579
|
+
' "$1"
|
|
7580
|
+
}
|
|
7645
7581
|
value_of() { awk -F= -v key="$2" '$1==key {print $2}' "$1"; }
|
|
7646
7582
|
test ! -L "$state" || fail "Invalid managed middleware credential state: $state"
|
|
7647
7583
|
valid_values "$candidate_file" || fail 'Invalid managed middleware credential candidates; rebuild the deployment bundle.'
|
|
@@ -7652,22 +7588,6 @@ if [ -f "$state" ]; then
|
|
|
7652
7588
|
else
|
|
7653
7589
|
: > "$next"
|
|
7654
7590
|
fi
|
|
7655
|
-
format=$(value_of "$next" FORMAT_VERSION)
|
|
7656
|
-
if [ "$format" = 2 ]; then
|
|
7657
|
-
test "$(value_of "$next" PROJECT)" = "$(cat "$managed_project")" || fail 'Managed middleware credential project mismatch.'
|
|
7658
|
-
known=$("$runtime" volume ls -q) || fail 'Cannot check persistent volumes; managed middleware credentials were not changed.'
|
|
7659
|
-
while IFS=' ' read -r volume_group volume; do
|
|
7660
|
-
test -n "$volume" || continue
|
|
7661
|
-
binding="$volume_group:$volume"
|
|
7662
|
-
if ! awk -F= -v binding="$binding" '$1~/^VOLUME_[0-9]+$/ && $2==binding {found=1} END {exit !found}' "$next"; then
|
|
7663
|
-
if printf '%s\n' "$known" | grep -Fxq "$volume"; then
|
|
7664
|
-
fail 'Existing persistent volume is not bound to the imported credential state. Import its verified identity first.'
|
|
7665
|
-
fi
|
|
7666
|
-
index=$(awk -F= '$1~/^VOLUME_[0-9]+$/ {n=substr($1,8)+0; if(n>max)max=n} END {print max+1}' "$next")
|
|
7667
|
-
printf 'VOLUME_%s=%s\n' "$index" "$binding" >> "$next"
|
|
7668
|
-
fi
|
|
7669
|
-
done < "$managed_volumes"
|
|
7670
|
-
fi
|
|
7671
7591
|
volumes_checked=false
|
|
7672
7592
|
known_volumes=''
|
|
7673
7593
|
while IFS='=' read -r name candidate; do
|
|
@@ -7684,64 +7604,24 @@ while IFS='=' read -r name candidate; do
|
|
|
7684
7604
|
fail "Missing $name in $state for existing persistent volume $volume. Restore the server credential state before retrying."
|
|
7685
7605
|
fi
|
|
7686
7606
|
done < "$managed_volumes"
|
|
7687
|
-
|
|
7688
|
-
if [ "$format" = 2 ]; then value=$(printf '%s' "$candidate" | od -An -tx1 | tr -d ' \n'); fi
|
|
7689
|
-
printf '%s=%s\n' "$name" "$value" >> "$next"
|
|
7607
|
+
printf '%s=%s\n' "$name" "$candidate" >> "$next"
|
|
7690
7608
|
fi
|
|
7691
7609
|
done < "$candidate_file"
|
|
7692
7610
|
while IFS='=' read -r name candidate; do
|
|
7693
7611
|
test -n "$name" || continue
|
|
7694
7612
|
value=$(value_of "$next" "$name")
|
|
7695
7613
|
for file in config/.env.runtime .hai/middleware-runtime.env; do
|
|
7696
|
-
|
|
7697
|
-
if [ "$file" = .hai/middleware-runtime.env ]; then compose_environment=true; fi
|
|
7698
|
-
HAI_DEPLOY_SECRET_CANDIDATE="$candidate" HAI_DEPLOY_SECRET_VALUE="$value" LC_ALL=C awk -v group="$name" -v format="$format" -v compose_environment="$compose_environment" '
|
|
7614
|
+
HAI_DEPLOY_SECRET_CANDIDATE="$candidate" HAI_DEPLOY_SECRET_VALUE="$value" LC_ALL=C awk -v group="$name" '
|
|
7699
7615
|
FILENAME==ARGV[1] {if($1==group) {keys[$2]=$3}; next}
|
|
7700
7616
|
function replace_literal(line, needle, value, result, at) {
|
|
7701
7617
|
result=""
|
|
7702
7618
|
while((at=index(line,needle))>0) {result=result substr(line,1,at-1) value; line=substr(line,at+length(needle))}
|
|
7703
7619
|
return result line
|
|
7704
7620
|
}
|
|
7705
|
-
function decode_hex(hex, result, i, high, low) {
|
|
7706
|
-
for(i=1;i<=length(hex);i+=2) {high=index("0123456789abcdef",substr(hex,i,1))-1; low=index("0123456789abcdef",substr(hex,i+1,1))-1; result=result sprintf("%c",high*16+low)}
|
|
7707
|
-
return result
|
|
7708
|
-
}
|
|
7709
|
-
function json_escape(value, apostrophe, result, i, ch) {
|
|
7710
|
-
for(i=1;i<=length(value);i++) {
|
|
7711
|
-
ch=substr(value,i,1)
|
|
7712
|
-
if(ch=="\\") result=result "\\\\"
|
|
7713
|
-
else if(ch=="\"") result=result "\\\""
|
|
7714
|
-
else if(ch==sprintf("%c",39) && apostrophe) result=result "\\u0027"
|
|
7715
|
-
else result=result ch
|
|
7716
|
-
}
|
|
7717
|
-
return result
|
|
7718
|
-
}
|
|
7719
|
-
function uri_escape(value, result, i, ch, j, ordinal) {
|
|
7720
|
-
for(i=1;i<=length(value);i++) {
|
|
7721
|
-
ch=substr(value,i,1)
|
|
7722
|
-
if(ch~/[A-Za-z0-9_.~-]/) result=result ch
|
|
7723
|
-
else {ordinal=0; for(j=1;j<=255;j++) if(sprintf("%c",j)==ch) {ordinal=j; break}; result=result sprintf("%%%02X",ordinal)}
|
|
7724
|
-
}
|
|
7725
|
-
return result
|
|
7726
|
-
}
|
|
7727
7621
|
{
|
|
7728
7622
|
equal=index($0,"="); key=substr($0,1,equal-1); scoped=0
|
|
7729
7623
|
for(prefix in keys) if(key==prefix || (keys[prefix]=="prefix" && index(key,prefix "_")==1)) scoped=1
|
|
7730
|
-
if(scoped
|
|
7731
|
-
candidate=ENVIRON["HAI_DEPLOY_SECRET_CANDIDATE"]; value=decode_hex(ENVIRON["HAI_DEPLOY_SECRET_VALUE"])
|
|
7732
|
-
literal=substr($0,equal+1); quote=sprintf("%c",39)
|
|
7733
|
-
json=substr(literal,1,1)==quote && (substr(literal,2,1)=="{" || substr(literal,2,1)=="[")
|
|
7734
|
-
if(json) literal=substr(literal,2,length(literal)-2)
|
|
7735
|
-
literal=replace_literal(literal,":" candidate "@",":" uri_escape(value) "@")
|
|
7736
|
-
if(json) {literal=replace_literal(literal,candidate,json_escape(value,1)); print key "=" quote literal quote}
|
|
7737
|
-
else {
|
|
7738
|
-
if(substr(literal,1,1)==quote || substr(literal,1,1)=="\"") literal=substr(literal,2,length(literal)-2)
|
|
7739
|
-
literal=replace_literal(literal,candidate,value)
|
|
7740
|
-
literal=json_escape(literal,0); if(compose_environment=="true") literal=replace_literal(literal,"$","$")
|
|
7741
|
-
print key "=\"" literal "\""
|
|
7742
|
-
}
|
|
7743
|
-
}
|
|
7744
|
-
else if(scoped) print replace_literal($0,ENVIRON["HAI_DEPLOY_SECRET_CANDIDATE"],ENVIRON["HAI_DEPLOY_SECRET_VALUE"])
|
|
7624
|
+
if(scoped) print replace_literal($0,ENVIRON["HAI_DEPLOY_SECRET_CANDIDATE"],ENVIRON["HAI_DEPLOY_SECRET_VALUE"])
|
|
7745
7625
|
else print
|
|
7746
7626
|
}
|
|
7747
7627
|
' "$managed_bindings" "$release/$file" > "$release/$file.next"
|
|
@@ -7751,7 +7631,7 @@ done < "$candidate_file"
|
|
|
7751
7631
|
chmod 600 "$next"
|
|
7752
7632
|
mv "$next" "$state"
|
|
7753
7633
|
printf '[INFO] Managed middleware credential source: %s (server state; values hidden).\n' "$state"
|
|
7754
|
-
`.replace("@@
|
|
7634
|
+
`.replace("@@MANIFEST_FUNCTIONS@@", () => createDeploymentManifestFunctions());
|
|
7755
7635
|
}
|
|
7756
7636
|
|
|
7757
7637
|
// src/container/deploy-backend-state.ts
|
|
@@ -7814,11 +7694,11 @@ backend_manifest=$(deployment_metadata_file "$release" state-backends)
|
|
|
7814
7694
|
regular() { test -f "$1" && test ! -L "$1" || fail "Backend state must be a regular file: $1"; }
|
|
7815
7695
|
absent_or_regular() { if [ -e "$1" ] || [ -L "$1" ]; then regular "$1"; fi; }
|
|
7816
7696
|
valid_tuples() {
|
|
7817
|
-
LC_ALL=C awk
|
|
7697
|
+
LC_ALL=C awk '
|
|
7818
7698
|
NF {
|
|
7819
|
-
if(
|
|
7699
|
+
if(NF!=3 || roles[$1]++) exit 1
|
|
7820
7700
|
if(!(($1=="db" && $2~/^(postgresql|mysql)$/) || ($1=="storage" && $2~/^(rustfs|minio)$/) || ($1=="vecdb" && $2~/^(qdrant|pgvector|chroma)$/))) exit 1
|
|
7821
|
-
if(
|
|
7701
|
+
if($3!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ || volumes[$3]++) exit 1
|
|
7822
7702
|
}
|
|
7823
7703
|
' "$1"
|
|
7824
7704
|
}
|
|
@@ -7869,45 +7749,6 @@ known=$("$runtime" volume ls -q) || fail 'Cannot list persistent volumes; no bac
|
|
|
7869
7749
|
printf '%s\n' "$known" | LC_ALL=C awk 'NF {if(NF!=1 || $1!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ || seen[$1]++) exit 1}' || fail 'Invalid persistent volume listing.'
|
|
7870
7750
|
exists_volume() { printf '%s\n' "$known" | grep -Fxq "$1"; }
|
|
7871
7751
|
ledger_tuple() { printf '%s\n' "$ledger" | awk -v volume="$1" '$3==volume {print}'; }
|
|
7872
|
-
@@MANAGED_VALUES_VALIDATOR@@
|
|
7873
|
-
@@COMPOSE_CONTAINER_LOOKUP@@
|
|
7874
|
-
# 状态补证:卷挂载和镜像均取自同项目、同角色的唯一实际容器。
|
|
7875
|
-
container_binding() {
|
|
7876
|
-
binding_service=$1
|
|
7877
|
-
binding_family=$2
|
|
7878
|
-
binding_expected=$3
|
|
7879
|
-
binding_running=$4
|
|
7880
|
-
binding_flags=-aq
|
|
7881
|
-
if [ "$binding_running" = true ]; then binding_flags=-q; fi
|
|
7882
|
-
binding_containers=$(compose_container_ids "$runtime" "$project" "$binding_service" "$binding_flags") || fail 'Cannot inspect successful deployment containers.'
|
|
7883
|
-
printf '%s\n' "$binding_containers" | LC_ALL=C awk 'NR!=1 || NF!=1 || $1!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ {exit 1} END {if(NR!=1) exit 1}' || fail "Cannot prove the previous volume binding for $binding_service. Restore verified backend state."
|
|
7884
|
-
if [ "$binding_running" = true ]; then
|
|
7885
|
-
binding_status=$("$runtime" inspect --format '{{.State.Running}}' "$binding_containers") || fail 'Cannot verify the imported service is running.'
|
|
7886
|
-
test "$binding_status" = true || fail "Imported backend requires its verified original container to be running: $binding_service."
|
|
7887
|
-
fi
|
|
7888
|
-
binding_mounted=$("$runtime" inspect --format '{{range .Mounts}}{{if eq .Type "volume"}}{{println .Name}}{{end}}{{end}}' "$binding_containers") || fail 'Cannot inspect previous named volume mounts.'
|
|
7889
|
-
printf '%s\n' "$binding_mounted" | LC_ALL=C awk 'NR!=1 || NF!=1 || $1!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ {exit 1} END {if(NR!=1) exit 1}' || fail "Ambiguous previous volume binding for $binding_service."
|
|
7890
|
-
binding_image=$("$runtime" inspect --format '{{.Config.Image}}' "$binding_containers") || fail 'Cannot identify the previous container image.'
|
|
7891
|
-
binding_actual_family=$(printf '%s\n' "$binding_image" | LC_ALL=C awk 'NR!=1 || NF!=1 {exit 1} {image=$0; sub(/^.*\//,"",image); sub(/[:@].*$/,"",image); if(image=="postgres") image="postgresql"; print image}') || fail 'Invalid previous container image identity.'
|
|
7892
|
-
test "$binding_actual_family" = "$binding_family" || fail "Actual previous container image does not confirm its backend family: $binding_service."
|
|
7893
|
-
if [ -n "$binding_expected" ] && [ "$binding_expected" != "$binding_mounted" ]; then fail "Actual previous volume differs from its backend manifest: $binding_service."; fi
|
|
7894
|
-
printf '%s\n' "$binding_mounted"
|
|
7895
|
-
}
|
|
7896
|
-
confirmed_import() {
|
|
7897
|
-
import_service=$1
|
|
7898
|
-
import_family=$2
|
|
7899
|
-
import_volume=$3
|
|
7900
|
-
import_state="$root/managed-secrets.env"
|
|
7901
|
-
test -f "$import_state" && test ! -L "$import_state" || return 1
|
|
7902
|
-
valid_values "$import_state" || return 1
|
|
7903
|
-
test "$(awk -F= '$1=="FORMAT_VERSION" {print $2}' "$import_state")" = 2 || return 1
|
|
7904
|
-
test "$(awk -F= '$1=="PROJECT" {print $2}' "$import_state")" = "$project" || return 1
|
|
7905
|
-
case "$import_service" in db|vecdb) import_group=DB_PASSWORD;; storage) import_group=STORAGE_PASSWORD;; *) return 1;; esac
|
|
7906
|
-
awk -F= -v group="$import_group" -v binding="$import_group:$import_volume" '$1==group && $2!="" {credential=1} $1~/^VOLUME_[0-9]+$/ && $2==binding {volume=1} END {exit !(credential && volume)}' "$import_state" || return 1
|
|
7907
|
-
# 密码不证明目录格式;受控导入还须与唯一运行原容器的实际卷及镜像家族共同吻合。
|
|
7908
|
-
import_actual=$(container_binding "$import_service" "$import_family" "$import_volume" true) || return 1
|
|
7909
|
-
test "$import_actual" = "$import_volume"
|
|
7910
|
-
}
|
|
7911
7752
|
snapshot_previous=$previous
|
|
7912
7753
|
if [ -z "$snapshot_previous" ]; then snapshot_previous=-; fi
|
|
7913
7754
|
if [ "$mode" = recover ]; then
|
|
@@ -7925,7 +7766,7 @@ RECOVERY_TUPLES
|
|
|
7925
7766
|
exit 0
|
|
7926
7767
|
fi
|
|
7927
7768
|
regular "$backend_manifest"
|
|
7928
|
-
valid_tuples "$backend_manifest"
|
|
7769
|
+
valid_tuples "$backend_manifest" || fail 'Invalid backend manifest; rebuild the deployment bundle.'
|
|
7929
7770
|
active=''
|
|
7930
7771
|
if [ -n "$previous" ]; then
|
|
7931
7772
|
baseline="$root/releases/$previous"
|
|
@@ -7934,7 +7775,7 @@ if [ -n "$previous" ]; then
|
|
|
7934
7775
|
baseline_services=$(deployment_services_file "$baseline")
|
|
7935
7776
|
previous_manifest=$(deployment_metadata_file "$baseline" state-backends)
|
|
7936
7777
|
regular "$previous_manifest"
|
|
7937
|
-
valid_tuples "$previous_manifest"
|
|
7778
|
+
valid_tuples "$previous_manifest" || fail 'Invalid successful backend manifest.'
|
|
7938
7779
|
LC_ALL=C awk 'NF {if(NF!=1 || $1!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/ || seen[$1]++) exit 1}' "$baseline_services" || fail 'Invalid successful deployment services.'
|
|
7939
7780
|
for active_service in db storage vecdb; do
|
|
7940
7781
|
if ! grep -Fxq "$active_service" "$baseline_services"; then continue; fi
|
|
@@ -7947,28 +7788,18 @@ if [ -n "$previous" ]; then
|
|
|
7947
7788
|
printf '%s %s %s\n' "$active_service" "$active_family" "$active_volume" | LC_ALL=C awk 'NF!=3 || !(($1=="db" && $2~/^(postgresql|mysql)$/) || ($1=="storage" && $2~/^(rustfs|minio)$/) || ($1=="vecdb" && $2~/^(qdrant|pgvector|chroma)$/)) {exit 1}' || fail "Unknown installed backend for $active_service."
|
|
7948
7789
|
exists_volume "$active_volume" || fail "Active persistent volume is missing: $active_volume. Restore its backup instead of creating an empty replacement."
|
|
7949
7790
|
confirmed=$(ledger_tuple "$active_volume")
|
|
7950
|
-
test
|
|
7951
|
-
if [ -z "$confirmed" ]; then
|
|
7952
|
-
proven_volume=$(container_binding "$active_service" "$active_family" "$active_volume" false)
|
|
7953
|
-
test "$proven_volume" = "$active_volume" || fail "Cannot confirm the active persistent volume: $active_service."
|
|
7954
|
-
fi
|
|
7791
|
+
test "$confirmed" = "$active_service $active_family $active_volume" || fail "Active persistent backend is missing from or conflicts with the protected ledger: $active_service. Restore verified backend state."
|
|
7955
7792
|
active=$(printf '%s\n%s %s %s\n' "$active" "$active_service" "$active_family" "$active_volume" | sed '/^$/d')
|
|
7956
7793
|
done
|
|
7957
7794
|
fi
|
|
7958
|
-
#
|
|
7959
|
-
printf '%s\n' "$active" | valid_tuples /dev/stdin
|
|
7795
|
+
# 同一实际卷不能绑定给多个有状态服务。
|
|
7796
|
+
printf '%s\n' "$active" | valid_tuples /dev/stdin || fail 'Conflicting successful persistent volume bindings.'
|
|
7960
7797
|
# 合并时只替换同一卷的无数据预认领,不能按本次拓扑裁剪历史绑定。
|
|
7961
7798
|
merged=$ledger
|
|
7962
7799
|
merge_tuple() {
|
|
7963
7800
|
merged=$(printf '%s\n' "$merged" | awk -v volume="$3" 'NF && $3!=volume {print}')
|
|
7964
7801
|
merged=$(printf '%s\n%s %s %s\n' "$merged" "$1" "$2" "$3" | sed '/^$/d')
|
|
7965
7802
|
}
|
|
7966
|
-
while IFS=' ' read -r active_service active_family active_volume; do
|
|
7967
|
-
test -n "$active_service" || continue
|
|
7968
|
-
merge_tuple "$active_service" "$active_family" "$active_volume"
|
|
7969
|
-
done <<ACTIVE_TUPLES
|
|
7970
|
-
$active
|
|
7971
|
-
ACTIVE_TUPLES
|
|
7972
7803
|
while IFS=' ' read -r candidate_service candidate_family candidate_volume; do
|
|
7973
7804
|
test -n "$candidate_service" || continue
|
|
7974
7805
|
prior_active=$(printf '%s\n' "$active" | awk -v service="$candidate_service" '$1==service {print}')
|
|
@@ -7976,8 +7807,6 @@ while IFS=' ' read -r candidate_service candidate_family candidate_volume; do
|
|
|
7976
7807
|
test -z "$prior_active" || test "$prior_active" = "$tuple" || fail "Persistent backend $candidate_service changed or its active volume binding changed. Use a separate deployment identity and migrate or restore data explicitly."
|
|
7977
7808
|
if exists_volume "$candidate_volume"; then
|
|
7978
7809
|
confirmed=$(ledger_tuple "$candidate_volume")
|
|
7979
|
-
if [ -z "$confirmed" ]; then confirmed=$(printf '%s\n' "$active" | awk -v volume="$candidate_volume" '$3==volume {print}'); fi
|
|
7980
|
-
if [ -z "$confirmed" ] && confirmed_import "$candidate_service" "$candidate_family" "$candidate_volume"; then confirmed=$tuple; fi
|
|
7981
7810
|
test "$confirmed" = "$tuple" || fail "Existing persistent volume has an unknown or incompatible backend: $candidate_service. Restore verified backend state; in-place reuse is refused."
|
|
7982
7811
|
else
|
|
7983
7812
|
users=$("$runtime" ps -aq --filter "volume=$candidate_volume") || fail 'Cannot check persistent volume users.'
|
|
@@ -7997,7 +7826,7 @@ mv -f -- "$snapshot.next" "$snapshot"
|
|
|
7997
7826
|
(set -C; printf '%s\n' "$state_content" > "$state.next") || fail 'Cannot stage the persistent backend ledger.'
|
|
7998
7827
|
chmod 600 "$state.next"
|
|
7999
7828
|
mv -f -- "$state.next" "$state"
|
|
8000
|
-
`.replace("@@PROJECT@@", () => projectName).replace("@@
|
|
7829
|
+
`.replace("@@PROJECT@@", () => projectName).replace("@@MANIFEST_FUNCTIONS@@", () => createDeploymentManifestFunctions());
|
|
8001
7830
|
}
|
|
8002
7831
|
var nativeJob = String.raw`
|
|
8003
7832
|
using System;
|
|
@@ -8196,7 +8025,7 @@ async function groupExited(pid) {
|
|
|
8196
8025
|
async function helper(command, args, timeoutMs) {
|
|
8197
8026
|
if (timeoutMs <= 0)
|
|
8198
8027
|
return false;
|
|
8199
|
-
return new Promise((
|
|
8028
|
+
return new Promise((resolve8) => {
|
|
8200
8029
|
const child = spawn(command, args, { windowsHide: true, shell: false, stdio: "ignore" });
|
|
8201
8030
|
let settled = false;
|
|
8202
8031
|
let timer;
|
|
@@ -8206,7 +8035,7 @@ async function helper(command, args, timeoutMs) {
|
|
|
8206
8035
|
settled = true;
|
|
8207
8036
|
if (timer)
|
|
8208
8037
|
clearTimeout(timer);
|
|
8209
|
-
|
|
8038
|
+
resolve8(success);
|
|
8210
8039
|
};
|
|
8211
8040
|
timer = globalThis.setTimeout(() => {
|
|
8212
8041
|
child.kill("SIGKILL");
|
|
@@ -8359,7 +8188,7 @@ ${deployM(state.localTermination === "confirmed" ? "deploy_localStopped" : state
|
|
|
8359
8188
|
ext: { commandExecution: state }
|
|
8360
8189
|
});
|
|
8361
8190
|
try {
|
|
8362
|
-
return await new Promise((
|
|
8191
|
+
return await new Promise((resolve8) => {
|
|
8363
8192
|
if (typeof command !== "string" || !command || command.includes("\0") || args.some((argument) => typeof argument !== "string" || argument.includes("\0")))
|
|
8364
8193
|
throw new TypeError("Invalid command arguments");
|
|
8365
8194
|
if (options.input !== void 0 && typeof options.input !== "string")
|
|
@@ -8402,7 +8231,7 @@ ${deployM(state.localTermination === "confirmed" ? "deploy_localStopped" : state
|
|
|
8402
8231
|
settled = true;
|
|
8403
8232
|
if (timer)
|
|
8404
8233
|
clearTimeout(timer);
|
|
8405
|
-
|
|
8234
|
+
resolve8(result);
|
|
8406
8235
|
};
|
|
8407
8236
|
const interrupt = (reason, message) => {
|
|
8408
8237
|
interrupted = true;
|
|
@@ -8589,6 +8418,47 @@ watch_certificate &
|
|
|
8589
8418
|
`;
|
|
8590
8419
|
}
|
|
8591
8420
|
|
|
8421
|
+
// src/container/deploy-compose-identity.ts
|
|
8422
|
+
function createComposeContainerLookupScript() {
|
|
8423
|
+
const labelFormat = [
|
|
8424
|
+
"com.docker.compose.project",
|
|
8425
|
+
"com.docker.compose.service",
|
|
8426
|
+
"io.podman.compose.project",
|
|
8427
|
+
"io.podman.compose.service"
|
|
8428
|
+
].map((key) => `{{index .Config.Labels \`${key}\`}}`).join("|");
|
|
8429
|
+
return String.raw`compose_container_ids() (
|
|
8430
|
+
if [ "$#" -ne 4 ]; then printf 'Compose container lookup requires runtime, project, service and selector.\n' >&2; exit 1; fi
|
|
8431
|
+
lookup_runtime=$1
|
|
8432
|
+
lookup_project=$2
|
|
8433
|
+
lookup_service=$3
|
|
8434
|
+
lookup_selector=$4
|
|
8435
|
+
case "$lookup_runtime" in docker|podman) ;; *) printf 'Invalid Compose lookup runtime.\n' >&2; exit 1;; esac
|
|
8436
|
+
case "$lookup_project" in ''|*[!A-Za-z0-9_.-]*) printf 'Invalid Compose project identity.\n' >&2; exit 1;; esac
|
|
8437
|
+
case "$lookup_service" in ''|*[!A-Za-z0-9_.-]*) printf 'Invalid Compose service identity.\n' >&2; exit 1;; esac
|
|
8438
|
+
case "$lookup_selector" in -q|-aq) ;; *) printf 'Invalid Compose container selector.\n' >&2; exit 1;; esac
|
|
8439
|
+
lookup_ids=''
|
|
8440
|
+
for lookup_namespace in com.docker.compose io.podman.compose; do
|
|
8441
|
+
lookup_found=$("$lookup_runtime" ps "$lookup_selector" --filter "label=$lookup_namespace.project=$lookup_project" --filter "label=$lookup_namespace.service=$lookup_service") || { printf 'Cannot query Compose containers.\n' >&2; exit 1; }
|
|
8442
|
+
printf '%s\n' "$lookup_found" | LC_ALL=C awk 'NF {if(NF!=1 || $0!~/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/) exit 1}' || { printf 'Invalid Compose container identifiers.\n' >&2; exit 1; }
|
|
8443
|
+
lookup_ids=$(printf '%s\n%s\n' "$lookup_ids" "$lookup_found" | LC_ALL=C awk 'NF && !seen[$0]++ {print}') || exit 1
|
|
8444
|
+
done
|
|
8445
|
+
for lookup_id in $lookup_ids; do
|
|
8446
|
+
lookup_labels=$("$lookup_runtime" inspect --format '${labelFormat}' "$lookup_id") || { printf 'Cannot inspect Compose container labels.\n' >&2; exit 1; }
|
|
8447
|
+
printf '%s\n' "$lookup_labels" | LC_ALL=C awk -F'|' -v project="$lookup_project" -v service="$lookup_service" '
|
|
8448
|
+
NR!=1 || NF!=4 {exit 1}
|
|
8449
|
+
{
|
|
8450
|
+
docker=($1!="" && $1!="<no value>") || ($2!="" && $2!="<no value>")
|
|
8451
|
+
podman=($3!="" && $3!="<no value>") || ($4!="" && $4!="<no value>")
|
|
8452
|
+
if((!docker && !podman) || (docker && ($1!=project || $2!=service)) || (podman && ($3!=project || $4!=service))) exit 1
|
|
8453
|
+
}
|
|
8454
|
+
END {if(NR!=1) exit 1}
|
|
8455
|
+
' || { printf 'Container labels do not confirm the requested Compose project and service.\n' >&2; exit 1; }
|
|
8456
|
+
done
|
|
8457
|
+
if [ -n "$lookup_ids" ]; then printf '%s\n' "$lookup_ids"; fi
|
|
8458
|
+
)
|
|
8459
|
+
`;
|
|
8460
|
+
}
|
|
8461
|
+
|
|
8592
8462
|
// src/container/deploy-runtime-script.ts
|
|
8593
8463
|
function createRuntimeConfigurationScript(primaryService) {
|
|
8594
8464
|
return String.raw`configuration_mount() {
|
|
@@ -9248,7 +9118,7 @@ if [ "$partial" = true ]; then
|
|
|
9248
9118
|
fi
|
|
9249
9119
|
;;
|
|
9250
9120
|
esac
|
|
9251
|
-
#
|
|
9121
|
+
# Web 挂载目录由完整拓扑确定,服务增量必须匹配已部署的目录。
|
|
9252
9122
|
current_web_base=$(deployment_json read metadata.web-base-path string "$source_dir/manifest.json")
|
|
9253
9123
|
previous_web_base=$(deployment_json read metadata.web-base-path string "$root/releases/$previous/manifest.json")
|
|
9254
9124
|
test "$current_web_base" = "$previous_web_base" || { printf 'Web base path changed. Match the installed prefix for partial updates, or create a full deployment.\n' >&2; exit 1; }
|
|
@@ -10144,7 +10014,7 @@ async function createDeploymentBundle(options) {
|
|
|
10144
10014
|
const metadata = {
|
|
10145
10015
|
"layout": `${config.expose.hostPort}:${options.containerPort}:${options.hasWebConfig ? options.proxyService === "gateway" ? "gateway" : "true" : "false"}:${artifact.primaryService}:${options.hasWebConfig ? await artifactChecksum(join(directory, "web.conf")) : ""}${httpsFingerprint ? `:${httpsFingerprint}` : ""}
|
|
10146
10016
|
`,
|
|
10147
|
-
"web-base-path": `${artifact.webBasePath
|
|
10017
|
+
"web-base-path": `${artifact.webBasePath}
|
|
10148
10018
|
`,
|
|
10149
10019
|
"health-paths": `${options.healthPaths.join("\n")}
|
|
10150
10020
|
`,
|
|
@@ -10157,7 +10027,6 @@ async function createDeploymentBundle(options) {
|
|
|
10157
10027
|
"node-runtime": String(nodeRuntime),
|
|
10158
10028
|
"proxy-service": options.hasWebConfig ? options.proxyService ?? "web" : "",
|
|
10159
10029
|
"https": options.httpsAssets?.https ?? "",
|
|
10160
|
-
"managed-project": managed["managed-project"],
|
|
10161
10030
|
// 未选应用时只带容器原生绑定,保持当前发布的应用连接归属。
|
|
10162
10031
|
"managed-bindings": updatesApp ? managed["managed-bindings"] : managed["managed-bindings"].replace(/^.* prefix\n/gm, ""),
|
|
10163
10032
|
"managed-volumes": managed["managed-volumes"],
|
|
@@ -10354,6 +10223,87 @@ async function copyArchive(source, destination) {
|
|
|
10354
10223
|
await pipeline(createReadStream(source), digest, createWriteStream(destination, { mode: 384 }));
|
|
10355
10224
|
return hash.digest("hex");
|
|
10356
10225
|
}
|
|
10226
|
+
|
|
10227
|
+
// src/container/deploy-download-error.ts
|
|
10228
|
+
var PAGE_STYLE = `
|
|
10229
|
+
* { box-sizing: border-box; }
|
|
10230
|
+
body {
|
|
10231
|
+
margin: 0;
|
|
10232
|
+
min-height: 100vh;
|
|
10233
|
+
display: grid;
|
|
10234
|
+
place-items: center;
|
|
10235
|
+
padding: 24px;
|
|
10236
|
+
background: #f1f5f9;
|
|
10237
|
+
color: #172033;
|
|
10238
|
+
font-family: Inter, system-ui, "Microsoft YaHei", sans-serif;
|
|
10239
|
+
line-height: 1.7;
|
|
10240
|
+
}
|
|
10241
|
+
main {
|
|
10242
|
+
width: min(100%, 640px);
|
|
10243
|
+
padding: clamp(24px, 6vw, 48px);
|
|
10244
|
+
border: 1px solid #dbe3ee;
|
|
10245
|
+
border-top: 4px solid #2563eb;
|
|
10246
|
+
border-radius: 12px;
|
|
10247
|
+
background: #fff;
|
|
10248
|
+
}
|
|
10249
|
+
h1 { margin: 0 0 16px; font-size: clamp(24px, 5vw, 32px); line-height: 1.4; }
|
|
10250
|
+
p { margin: 0; color: #475569; font-size: 16px; }
|
|
10251
|
+
`;
|
|
10252
|
+
function escapeHtml(value) {
|
|
10253
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
10254
|
+
}
|
|
10255
|
+
function renderPage() {
|
|
10256
|
+
return `<!doctype html>
|
|
10257
|
+
<html lang="$hai_download_language">
|
|
10258
|
+
<head>
|
|
10259
|
+
<meta charset="utf-8">
|
|
10260
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10261
|
+
<meta name="robots" content="noindex">
|
|
10262
|
+
<meta name="referrer" content="no-referrer">
|
|
10263
|
+
<link rel="icon" href="data:,">
|
|
10264
|
+
<title>$hai_download_title</title>
|
|
10265
|
+
<style>${PAGE_STYLE}</style>
|
|
10266
|
+
</head>
|
|
10267
|
+
<body><main><h1>$hai_download_title</h1><p>$hai_download_instruction</p></main></body>
|
|
10268
|
+
</html>`;
|
|
10269
|
+
}
|
|
10270
|
+
function renderDownloadErrorMaps() {
|
|
10271
|
+
return `map "$request_method:$args" $hai_signed_download {
|
|
10272
|
+
default 0;
|
|
10273
|
+
"~*^(GET|HEAD):(?:[^&]*&)*X-Amz-Signature=[^&]+(?:&|$)" 1;
|
|
10274
|
+
}
|
|
10275
|
+
map $http_accept_language $hai_download_language {
|
|
10276
|
+
default zh-CN;
|
|
10277
|
+
~*^en en-US;
|
|
10278
|
+
~*^ja ja-JP;
|
|
10279
|
+
}
|
|
10280
|
+
map $hai_download_language $hai_download_title {
|
|
10281
|
+
default ${JSON.stringify(escapeHtml(deployM("deploy_downloadUnavailableTitle", { locale: "zh-CN" })))};
|
|
10282
|
+
en-US ${JSON.stringify(escapeHtml(deployM("deploy_downloadUnavailableTitle", { locale: "en-US" })))};
|
|
10283
|
+
ja-JP ${JSON.stringify(escapeHtml(deployM("deploy_downloadUnavailableTitle", { locale: "ja-JP" })))};
|
|
10284
|
+
}
|
|
10285
|
+
map $hai_download_language $hai_download_instruction {
|
|
10286
|
+
default ${JSON.stringify(escapeHtml(deployM("deploy_downloadUnavailableInstruction", { locale: "zh-CN" })))};
|
|
10287
|
+
en-US ${JSON.stringify(escapeHtml(deployM("deploy_downloadUnavailableInstruction", { locale: "en-US" })))};
|
|
10288
|
+
ja-JP ${JSON.stringify(escapeHtml(deployM("deploy_downloadUnavailableInstruction", { locale: "ja-JP" })))};
|
|
10289
|
+
}
|
|
10290
|
+
`;
|
|
10291
|
+
}
|
|
10292
|
+
function renderDownloadErrorLocation() {
|
|
10293
|
+
return ` location @hai_download_unavailable {
|
|
10294
|
+
access_log off;
|
|
10295
|
+
error_log /dev/null;
|
|
10296
|
+
types { }
|
|
10297
|
+
default_type text/html;
|
|
10298
|
+
charset utf-8;
|
|
10299
|
+
add_header Cache-Control "private, no-store" always;
|
|
10300
|
+
add_header Referrer-Policy no-referrer always;
|
|
10301
|
+
add_header X-Content-Type-Options nosniff always;
|
|
10302
|
+
add_header Content-Security-Policy "default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'" always;
|
|
10303
|
+
return 404 ${JSON.stringify(renderPage())};
|
|
10304
|
+
}
|
|
10305
|
+
`;
|
|
10306
|
+
}
|
|
10357
10307
|
var CERTBOT_IMAGE = "docker.io/certbot/certbot:v5.9.0";
|
|
10358
10308
|
function createHttpsAssets(config, projectDirectory) {
|
|
10359
10309
|
const assets = {
|
|
@@ -11088,6 +11038,7 @@ function generateWebProxyConfig(port, apiPrefix, options = {}) {
|
|
|
11088
11038
|
}
|
|
11089
11039
|
`).join("");
|
|
11090
11040
|
return `include /tmp/hai-dns.conf;
|
|
11041
|
+
${renderDownloadErrorMaps()}
|
|
11091
11042
|
${renderResolvedUpstream("hai_serv_backend", "serv", port)}${options.storageBucket ? renderStorageUpstream(options.storageBucket, options.storageBasePath) : ""}server {
|
|
11092
11043
|
listen ${webPort};
|
|
11093
11044
|
server_name _;
|
|
@@ -11171,17 +11122,7 @@ function generateApplicationProxyConfig(options) {
|
|
|
11171
11122
|
const bodyLimits = resolveTransportBodyLimits(maxPlaintextBytes);
|
|
11172
11123
|
const trustedEdges = renderTrustedEdgeProxies(options.trustedEdgeProxies);
|
|
11173
11124
|
const forwardedProtocol = options.trustedEdgeProxies?.length ? "$hai_request_scheme" : "$scheme";
|
|
11174
|
-
|
|
11175
|
-
${renderResolvedUpstream("hai_application_backend", primaryService, port)}${renderTrustedEdgeProtocol(options.trustedEdgeProxies)}${bucket ? renderStorageUpstream(bucket, options.storageBasePath) : ""}server {
|
|
11176
|
-
listen 8080;
|
|
11177
|
-
server_name _;
|
|
11178
|
-
${trustedEdges} large_client_header_buffers 4 32k;
|
|
11179
|
-
${bucket ? renderStorageLocation(bucket, reservedPaths, options.storageBasePath) : ""} location = /_hai/gateway-health {
|
|
11180
|
-
access_log off;
|
|
11181
|
-
return 200 'ok';
|
|
11182
|
-
}
|
|
11183
|
-
location / {
|
|
11184
|
-
client_max_body_size ${bodyLimits.encryptedPayloadBytes};
|
|
11125
|
+
const proxySettings = ` client_max_body_size ${bodyLimits.encryptedPayloadBytes};
|
|
11185
11126
|
proxy_pass http://hai_application_backend;
|
|
11186
11127
|
proxy_http_version 1.1;
|
|
11187
11128
|
proxy_set_header Host $http_host;
|
|
@@ -11195,7 +11136,29 @@ ${bucket ? renderStorageLocation(bucket, reservedPaths, options.storageBasePath)
|
|
|
11195
11136
|
proxy_request_buffering off;
|
|
11196
11137
|
proxy_buffering off;
|
|
11197
11138
|
proxy_buffer_size 32k;
|
|
11198
|
-
proxy_buffers 8 32k
|
|
11139
|
+
proxy_buffers 8 32k;`;
|
|
11140
|
+
return `include /tmp/hai-dns.conf;
|
|
11141
|
+
${renderDownloadErrorMaps()}
|
|
11142
|
+
${renderResolvedUpstream("hai_application_backend", primaryService, port)}${renderTrustedEdgeProtocol(options.trustedEdgeProxies)}${bucket ? renderStorageUpstream(bucket, options.storageBasePath) : ""}server {
|
|
11143
|
+
listen 8080;
|
|
11144
|
+
server_name _;
|
|
11145
|
+
${trustedEdges} large_client_header_buffers 4 32k;
|
|
11146
|
+
${bucket ? renderStorageLocation(bucket, reservedPaths, options.storageBasePath) : ""} location = /_hai/gateway-health {
|
|
11147
|
+
access_log off;
|
|
11148
|
+
return 200 'ok';
|
|
11149
|
+
}
|
|
11150
|
+
${renderDownloadErrorLocation()} location / {
|
|
11151
|
+
recursive_error_pages on;
|
|
11152
|
+
error_page 418 = @hai_download_proxy;
|
|
11153
|
+
if ($hai_signed_download) { return 418; }
|
|
11154
|
+
${proxySettings}
|
|
11155
|
+
}
|
|
11156
|
+
location @hai_download_proxy {
|
|
11157
|
+
access_log off;
|
|
11158
|
+
error_log /dev/null;
|
|
11159
|
+
proxy_intercept_errors on;
|
|
11160
|
+
error_page 404 = @hai_download_unavailable;
|
|
11161
|
+
${proxySettings}
|
|
11199
11162
|
}
|
|
11200
11163
|
}
|
|
11201
11164
|
`;
|
|
@@ -11234,24 +11197,35 @@ map "$hai_trusted_edge:$http_x_forwarded_proto" $hai_request_scheme {
|
|
|
11234
11197
|
function renderWebLocation(webBasePath) {
|
|
11235
11198
|
if (webBasePath && (!/^\/[\w.-]+(?:\/[\w.-]+)*$/.test(webBasePath) || webBasePath.split("/").some((segment) => segment === "." || segment === "..") || ["/health", "/_hai/web-health"].includes(webBasePath)))
|
|
11236
11199
|
throw new Error("Invalid web base path");
|
|
11237
|
-
|
|
11200
|
+
const fallback = `${renderDownloadErrorLocation()} location @hai_spa_fallback {
|
|
11201
|
+
error_page 418 = @hai_download_unavailable;
|
|
11202
|
+
if ($hai_signed_download) { return 418; }
|
|
11203
|
+
try_files /index.html =404;
|
|
11204
|
+
}
|
|
11205
|
+
`;
|
|
11206
|
+
return fallback + (webBasePath ? ` absolute_redirect off;
|
|
11238
11207
|
location = ${webBasePath} {
|
|
11239
11208
|
return 308 ${webBasePath}/$is_args$args;
|
|
11240
11209
|
}
|
|
11241
11210
|
location ${webBasePath}/ {
|
|
11242
11211
|
rewrite ^${webBasePath.replaceAll(".", "\\.").replaceAll("-", "\\-")}/(.*)$ /$1 break;
|
|
11243
|
-
try_files $uri/index.html $uri
|
|
11212
|
+
try_files $uri/index.html $uri @hai_spa_fallback;
|
|
11213
|
+
}
|
|
11214
|
+
location / {
|
|
11215
|
+
error_page 418 = @hai_download_unavailable;
|
|
11216
|
+
if ($hai_signed_download) { return 418; }
|
|
11217
|
+
return 404;
|
|
11244
11218
|
}
|
|
11245
|
-
location / { return 404; }
|
|
11246
11219
|
` : ` location / {
|
|
11247
|
-
try_files $uri $uri/
|
|
11220
|
+
try_files $uri $uri/ @hai_spa_fallback;
|
|
11248
11221
|
}
|
|
11249
|
-
|
|
11222
|
+
`);
|
|
11250
11223
|
}
|
|
11251
11224
|
function generateStaticWebConfig(port, webBasePath) {
|
|
11252
11225
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
11253
11226
|
throw new Error("Invalid web proxy target");
|
|
11254
|
-
return
|
|
11227
|
+
return `${renderDownloadErrorMaps()}
|
|
11228
|
+
server {
|
|
11255
11229
|
listen ${port};
|
|
11256
11230
|
server_name _;
|
|
11257
11231
|
root /usr/share/nginx/html;
|
|
@@ -11357,7 +11331,7 @@ function makeDeploymentId() {
|
|
|
11357
11331
|
return `${core.time.formatDate(/* @__PURE__ */ new Date(), "YYYYMMDD-HHmmss")}-${randomBytes(3).toString("hex")}`;
|
|
11358
11332
|
}
|
|
11359
11333
|
function sleep(ms) {
|
|
11360
|
-
return new Promise((
|
|
11334
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
11361
11335
|
}
|
|
11362
11336
|
function createDockerSshProvider(config, container) {
|
|
11363
11337
|
const ssh = config.ssh ? createSshExecutor(config.ssh) : void 0;
|
|
@@ -11389,16 +11363,16 @@ function createDockerSshProvider(config, container) {
|
|
|
11389
11363
|
for (let attempt = 0; attempt < HEALTH_MAX_ATTEMPTS; attempt++) {
|
|
11390
11364
|
try {
|
|
11391
11365
|
if (certificate) {
|
|
11392
|
-
const healthy = await new Promise((
|
|
11366
|
+
const healthy = await new Promise((resolve8) => {
|
|
11393
11367
|
const request$1 = request(url, {
|
|
11394
11368
|
ca: [...rootCertificates, certificate],
|
|
11395
11369
|
timeout: 5e3
|
|
11396
11370
|
}, (response) => {
|
|
11397
11371
|
response.resume();
|
|
11398
|
-
|
|
11372
|
+
resolve8(response.statusCode !== void 0 && response.statusCode >= 200 && response.statusCode < 300);
|
|
11399
11373
|
});
|
|
11400
|
-
request$1.on("error", () =>
|
|
11401
|
-
request$1.on("close", () =>
|
|
11374
|
+
request$1.on("error", () => resolve8(false));
|
|
11375
|
+
request$1.on("close", () => resolve8(false));
|
|
11402
11376
|
request$1.on("timeout", () => request$1.destroy());
|
|
11403
11377
|
request$1.end();
|
|
11404
11378
|
});
|
|
@@ -11449,7 +11423,7 @@ function createDockerSshProvider(config, container) {
|
|
|
11449
11423
|
const webContainerPort = web?.containerPort ?? artifact.webContainerPort ?? 8080;
|
|
11450
11424
|
const host = config.ssh?.host;
|
|
11451
11425
|
const url = https ? `https://${https.domain}${https.port === 443 ? "" : `:${https.port}`}` : host ? `http://${host.includes(":") ? `[${host}]` : host}:${hostPort}` : `http://SERVER_HOST:${hostPort}`;
|
|
11452
|
-
const webBasePath = artifact.webBasePath
|
|
11426
|
+
const webBasePath = artifact.webBasePath;
|
|
11453
11427
|
const applicationUrl = `${url}${webBasePath ? `${webBasePath}/` : ""}`;
|
|
11454
11428
|
const standaloneWebConfig = primaryService === "web" && !scan.isSvelteKit && (!!webBasePath || !!https);
|
|
11455
11429
|
const healthPaths = hasWebProxy ? ["/_hai/web-health", `${webBasePath}/`] : scan.isSvelteKit ? [`${webBasePath}/`] : primaryService === "serv" ? [] : standaloneWebConfig ? ["/health", `${webBasePath}/`] : ["/health"];
|
|
@@ -12042,7 +12016,7 @@ function createVercelProvider() {
|
|
|
12042
12016
|
);
|
|
12043
12017
|
token = apiToken;
|
|
12044
12018
|
const username = user.user.username || user.user.email;
|
|
12045
|
-
logger6.info("Vercel authenticated"
|
|
12019
|
+
logger6.info("Vercel authenticated");
|
|
12046
12020
|
return ok(username);
|
|
12047
12021
|
} catch (error) {
|
|
12048
12022
|
logger6.error("Vercel authentication failed", { error });
|
|
@@ -12227,7 +12201,7 @@ async function pollDeploymentStatus(token, deploymentId) {
|
|
|
12227
12201
|
if (deployment.readyState === "ERROR" || deployment.readyState === "CANCELED") {
|
|
12228
12202
|
return "error";
|
|
12229
12203
|
}
|
|
12230
|
-
await new Promise((
|
|
12204
|
+
await new Promise((resolve8) => setTimeout(resolve8, POLL_INTERVAL));
|
|
12231
12205
|
}
|
|
12232
12206
|
return "building";
|
|
12233
12207
|
}
|
|
@@ -12252,7 +12226,7 @@ function createAliyunProvisioner() {
|
|
|
12252
12226
|
accessKey = akId;
|
|
12253
12227
|
secretKey = akSecret;
|
|
12254
12228
|
signName = credentials.signName;
|
|
12255
|
-
logger7.info("Aliyun authenticated"
|
|
12229
|
+
logger7.info("Aliyun authenticated");
|
|
12256
12230
|
return ok(akId);
|
|
12257
12231
|
} catch (error) {
|
|
12258
12232
|
logger7.error("Aliyun authentication failed", { error });
|
|
@@ -12493,7 +12467,7 @@ function createR2Provisioner() {
|
|
|
12493
12467
|
token = apiTok;
|
|
12494
12468
|
accessKey = credentials.accessKeyId;
|
|
12495
12469
|
secretKey = credentials.secretAccessKey;
|
|
12496
|
-
logger9.info("Cloudflare R2 authenticated"
|
|
12470
|
+
logger9.info("Cloudflare R2 authenticated");
|
|
12497
12471
|
return ok(acctId);
|
|
12498
12472
|
} catch (error) {
|
|
12499
12473
|
logger9.error("Cloudflare R2 authentication failed", { error });
|
|
@@ -12716,7 +12690,7 @@ function createUpstashProvisioner() {
|
|
|
12716
12690
|
await listUpstashDatabases(userEmail, userKey);
|
|
12717
12691
|
email = userEmail;
|
|
12718
12692
|
apiKey = userKey;
|
|
12719
|
-
logger11.info("Upstash authenticated"
|
|
12693
|
+
logger11.info("Upstash authenticated");
|
|
12720
12694
|
return ok(userEmail);
|
|
12721
12695
|
} catch (error) {
|
|
12722
12696
|
logger11.error("Upstash authentication failed", { error });
|
|
@@ -12925,10 +12899,8 @@ var deploy = {
|
|
|
12925
12899
|
} else {
|
|
12926
12900
|
provider = createProviderByType(parsed.provider.type);
|
|
12927
12901
|
const authResult = await provider.authenticate(parsed.provider.token);
|
|
12928
|
-
if (!authResult.success)
|
|
12902
|
+
if (!authResult.success)
|
|
12929
12903
|
return err(authResult.error);
|
|
12930
|
-
}
|
|
12931
|
-
logger12.info("Provider authenticated", { provider: parsed.provider.type, user: authResult.data });
|
|
12932
12904
|
}
|
|
12933
12905
|
currentProvisioners.clear();
|
|
12934
12906
|
if (parsed.services) {
|
|
@@ -12942,14 +12914,16 @@ var deploy = {
|
|
|
12942
12914
|
if (!provAuthResult.success) {
|
|
12943
12915
|
logger12.error("Provisioner authentication failed", {
|
|
12944
12916
|
service: serviceType,
|
|
12945
|
-
provisioner: serviceConfig.provisioner
|
|
12917
|
+
provisioner: serviceConfig.provisioner,
|
|
12918
|
+
errorCode: provAuthResult.error.code,
|
|
12919
|
+
traceId: provAuthResult.error.traceId
|
|
12946
12920
|
});
|
|
12947
12921
|
currentProvisioners.clear();
|
|
12948
12922
|
return err(provAuthResult.error);
|
|
12949
12923
|
}
|
|
12950
12924
|
logger12.info("Provisioner authenticated", {
|
|
12951
12925
|
service: serviceType,
|
|
12952
|
-
|
|
12926
|
+
provisioner: provisioner.name
|
|
12953
12927
|
});
|
|
12954
12928
|
currentProvisioners.set(serviceType, provisioner);
|
|
12955
12929
|
}
|
|
@@ -13048,8 +13022,6 @@ var deploy = {
|
|
|
13048
13022
|
if (!currentProvider && !currentContainerProvider) {
|
|
13049
13023
|
return notInitialized.result();
|
|
13050
13024
|
}
|
|
13051
|
-
if (options && "projectName" in options && options.projectName !== void 0)
|
|
13052
|
-
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_projectNameOverrideRemoved"));
|
|
13053
13025
|
logger12.info("Starting deployment", { appDir, environment: options?.environment });
|
|
13054
13026
|
const scanOptions = { ...options, environment: options?.environment ?? currentConfig?.environment };
|
|
13055
13027
|
const scanResult = await scanApp(appDir, scanOptions);
|
|
@@ -13783,8 +13755,6 @@ function describeMiddlewareBackend(backend) {
|
|
|
13783
13755
|
return deployM(backend.purpose);
|
|
13784
13756
|
}
|
|
13785
13757
|
async function createDeploymentPlan(appDir, input, options = {}) {
|
|
13786
|
-
if ("projectName" in options && options.projectName !== void 0)
|
|
13787
|
-
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_projectNameOverrideRemoved"));
|
|
13788
13758
|
const parsed = DeployConfigSchema.safeParse(input);
|
|
13789
13759
|
if (!parsed.success) {
|
|
13790
13760
|
return err({
|
|
@@ -13985,389 +13955,7 @@ async function createDeploymentPlan(appDir, input, options = {}) {
|
|
|
13985
13955
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_artifactInvalid"));
|
|
13986
13956
|
}
|
|
13987
13957
|
}
|
|
13988
|
-
var nameSchema = z.string().regex(/^[a-z0-9][\w.-]{0,127}$/i);
|
|
13989
|
-
var passwordSchema = z.string().min(1).max(1024).refine((value) => Buffer.from(value, "utf8").toString("utf8") === value && [...value].every((character) => character.codePointAt(0) >= 32 && character.codePointAt(0) !== 127));
|
|
13990
|
-
var importSchema = z.strictObject({
|
|
13991
|
-
version: z.literal(1),
|
|
13992
|
-
projectName: nameSchema,
|
|
13993
|
-
credentials: z.strictObject({
|
|
13994
|
-
DB_PASSWORD: passwordSchema.optional(),
|
|
13995
|
-
STORAGE_PASSWORD: passwordSchema.optional(),
|
|
13996
|
-
LDAP_PASSWORD: passwordSchema.optional()
|
|
13997
|
-
}),
|
|
13998
|
-
services: z.array(z.strictObject({
|
|
13999
|
-
service: z.enum(["db", "vecdb", "storage", "ldap"]),
|
|
14000
|
-
container: nameSchema,
|
|
14001
|
-
kind: z.enum(["postgresql", "mysql", "s3", "ldap"]),
|
|
14002
|
-
volumes: z.array(nameSchema).min(1).max(8),
|
|
14003
|
-
username: z.string().min(1).max(128).default("hai"),
|
|
14004
|
-
database: z.string().min(1).max(128).default("hai"),
|
|
14005
|
-
bucket: z.string().regex(/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/).default("hai"),
|
|
14006
|
-
accessKeyId: z.string().min(1).max(128).default("hai-storage"),
|
|
14007
|
-
region: z.string().regex(/^[a-z0-9-]+$/).default("us-east-1"),
|
|
14008
|
-
bindDn: z.string().min(1).max(512).default("cn=admin,dc=example,dc=org")
|
|
14009
|
-
})).min(1).max(4)
|
|
14010
|
-
});
|
|
14011
|
-
var groups2 = {
|
|
14012
|
-
db: "DB_PASSWORD",
|
|
14013
|
-
vecdb: "DB_PASSWORD",
|
|
14014
|
-
storage: "STORAGE_PASSWORD",
|
|
14015
|
-
ldap: "LDAP_PASSWORD"
|
|
14016
|
-
};
|
|
14017
|
-
var importCommitScript = String.raw`(async()=>{const fs=require('node:fs'),path=require('node:path'),crypto=require('node:crypto');const root=process.argv[1];let temporary;
|
|
14018
|
-
try{process.stdout.write('LOCKED\n');let input='';for await(const chunk of process.stdin){input+=chunk;if(input.length>65536)throw Error()}
|
|
14019
|
-
if(!input)return;const {content}=JSON.parse(input);if(typeof content!=='string'||!content.startsWith('FORMAT_VERSION=2\n'))throw Error();
|
|
14020
|
-
const state=path.join(root,'managed-secrets.env');try{fs.lstatSync(state);throw Error('exists')}catch(error){if(error.code!=='ENOENT')throw error}
|
|
14021
|
-
temporary=path.join(root,'.managed-secrets-'+crypto.randomUUID()+'.next');const fd=fs.openSync(temporary,fs.constants.O_WRONLY|fs.constants.O_CREAT|fs.constants.O_EXCL|fs.constants.O_NOFOLLOW,0o600);
|
|
14022
|
-
try{fs.writeFileSync(fd,content);fs.fsyncSync(fd)}finally{fs.closeSync(fd)}fs.renameSync(temporary,state);temporary=undefined;
|
|
14023
|
-
const directory=fs.openSync(root,fs.constants.O_RDONLY);try{fs.fsyncSync(directory)}finally{fs.closeSync(directory)}process.stdout.write('COMMITTED\n');
|
|
14024
|
-
}catch{process.exitCode=1}finally{if(temporary)try{fs.unlinkSync(temporary)}catch{}}})()`;
|
|
14025
|
-
var s3VerificationScript = String.raw`(async()=>{try{
|
|
14026
|
-
const {createHash,createHmac}=require('node:crypto');let input='';for await(const chunk of process.stdin)input+=chunk;
|
|
14027
|
-
const {endpoint,bucket,accessKeyId,secret,region}=JSON.parse(input);const url=new URL('/'+encodeURIComponent(bucket),endpoint);
|
|
14028
|
-
const hash=value=>createHash('sha256').update(value).digest('hex');const hmac=(key,value)=>createHmac('sha256',key).update(value).digest();
|
|
14029
|
-
const date=new Date().toISOString().replace(/[:-]|\.\d{3}/g,'');const day=date.slice(0,8);const payload=hash('');
|
|
14030
|
-
const headers='host:'+url.host+'\nx-amz-content-sha256:'+payload+'\nx-amz-date:'+date+'\n';const signed='host;x-amz-content-sha256;x-amz-date';
|
|
14031
|
-
const scope=day+'/'+region+'/s3/aws4_request';const canonical=['HEAD',url.pathname,'',headers,signed,payload].join('\n');
|
|
14032
|
-
const key=hmac(hmac(hmac(hmac('AWS4'+secret,day),region),'s3'),'aws4_request');const signature=hmac(key,['AWS4-HMAC-SHA256',date,scope,hash(canonical)].join('\n')).toString('hex');
|
|
14033
|
-
const response=await fetch(url,{method:'HEAD',redirect:'manual',signal:AbortSignal.timeout(10000),headers:{'x-amz-date':date,'x-amz-content-sha256':payload,Authorization:'AWS4-HMAC-SHA256 Credential='+accessKeyId+'/'+scope+', SignedHeaders='+signed+', Signature='+signature}});
|
|
14034
|
-
await response.body?.cancel();if(response.ok)process.stdout.write('VERIFIED');else process.exitCode=1;
|
|
14035
|
-
}catch{process.exitCode=1}})()`;
|
|
14036
|
-
async function importDeploymentSecrets(options) {
|
|
14037
|
-
let lock;
|
|
14038
|
-
let commitAttempted = false;
|
|
14039
|
-
let phase = "input";
|
|
14040
|
-
try {
|
|
14041
|
-
if (!["docker", "podman"].includes(options.runtime))
|
|
14042
|
-
throw new Error("input");
|
|
14043
|
-
const root = resolve(options.directory);
|
|
14044
|
-
if (realpathSync(root) !== root || !lstatSync(root).isDirectory() || process.platform !== "win32" && lstatSync(root).mode & 18)
|
|
14045
|
-
throw new Error("input");
|
|
14046
|
-
const input = privateFile(resolve(options.inputFile));
|
|
14047
|
-
const parsed = importSchema.safeParse(JSON.parse(input));
|
|
14048
|
-
if (!parsed.success) {
|
|
14049
|
-
return err({
|
|
14050
|
-
...HaiDeployError.CONFIG_ERROR,
|
|
14051
|
-
message: deployM("deploy_secretImportFailed", {
|
|
14052
|
-
params: { phase }
|
|
14053
|
-
}),
|
|
14054
|
-
ext: {
|
|
14055
|
-
phase,
|
|
14056
|
-
state: "unchanged",
|
|
14057
|
-
fields: parsed.error.issues.map((issue) => issue.path.join("."))
|
|
14058
|
-
}
|
|
14059
|
-
});
|
|
14060
|
-
}
|
|
14061
|
-
const config = parsed.data;
|
|
14062
|
-
const project = `hai-${config.projectName}`;
|
|
14063
|
-
const selectedGroups = [
|
|
14064
|
-
...new Set(config.services.map((service) => groups2[service.service]))
|
|
14065
|
-
].sort();
|
|
14066
|
-
if (new Set(config.services.map((service) => service.service)).size !== config.services.length || Object.keys(config.credentials).some((group) => !selectedGroups.includes(group)) || selectedGroups.some((group) => !config.credentials[group])) {
|
|
14067
|
-
throw new Error("input");
|
|
14068
|
-
}
|
|
14069
|
-
const secrets = Object.values(config.credentials).filter((value) => typeof value === "string");
|
|
14070
|
-
const stateFile = join(root, "managed-secrets.env");
|
|
14071
|
-
const lockFile = join(root, ".deploy.lock");
|
|
14072
|
-
phase = "lock";
|
|
14073
|
-
if (options.dryRun === false) {
|
|
14074
|
-
if (process.platform !== "linux")
|
|
14075
|
-
throw new Error("lock");
|
|
14076
|
-
if (existsSync(lockFile) && (lstatSync(lockFile).isSymbolicLink() || !lstatSync(lockFile).isFile() || lstatSync(lockFile).nlink !== 1))
|
|
14077
|
-
throw new Error("lock");
|
|
14078
|
-
lock = await acquireImportLock(lockFile, root);
|
|
14079
|
-
}
|
|
14080
|
-
phase = "identity";
|
|
14081
|
-
const volumes = [];
|
|
14082
|
-
for (const service of config.services) {
|
|
14083
|
-
const kindValid = service.service === "storage" ? service.kind === "s3" : service.service === "ldap" ? service.kind === "ldap" : ["postgresql", "mysql"].includes(service.kind);
|
|
14084
|
-
if (!kindValid)
|
|
14085
|
-
throw new Error("identity");
|
|
14086
|
-
if (["postgresql", "mysql"].includes(service.kind) && (service.username !== "hai" || service.database !== "hai") || service.kind === "s3" && (service.accessKeyId !== "hai-storage" || service.bucket !== "hai" || service.region !== "us-east-1") || service.kind === "ldap" && service.bindDn !== "cn=admin,dc=example,dc=org") {
|
|
14087
|
-
throw new Error("identity");
|
|
14088
|
-
}
|
|
14089
|
-
const identity = await inspectContainer(options.runtime, service.container, secrets);
|
|
14090
|
-
if (identity.project !== project || identity.service !== service.service || !identity.running)
|
|
14091
|
-
throw new Error("identity");
|
|
14092
|
-
for (const volume of service.volumes) {
|
|
14093
|
-
if (!identity.volumes.includes(volume))
|
|
14094
|
-
throw new Error("identity");
|
|
14095
|
-
volumes.push({ group: groups2[service.service], name: volume });
|
|
14096
|
-
}
|
|
14097
|
-
phase = "verification";
|
|
14098
|
-
const password = config.credentials[groups2[service.service]];
|
|
14099
|
-
let verified = false;
|
|
14100
|
-
if (service.kind === "s3") {
|
|
14101
|
-
const helper2 = `hai-${config.projectName}-app`;
|
|
14102
|
-
const application = await inspectContainer(options.runtime, helper2, secrets);
|
|
14103
|
-
if (application.project !== project || application.service !== "app" || !application.running || !application.networks.some((network) => identity.networks.includes(network)))
|
|
14104
|
-
throw new Error("identity");
|
|
14105
|
-
const result = await runCommand(options.runtime, [
|
|
14106
|
-
"exec",
|
|
14107
|
-
"-i",
|
|
14108
|
-
helper2,
|
|
14109
|
-
"node",
|
|
14110
|
-
"-e",
|
|
14111
|
-
s3VerificationScript
|
|
14112
|
-
], {
|
|
14113
|
-
input: JSON.stringify({
|
|
14114
|
-
endpoint: `http://${service.container}:9000`,
|
|
14115
|
-
bucket: service.bucket,
|
|
14116
|
-
accessKeyId: service.accessKeyId,
|
|
14117
|
-
secret: password,
|
|
14118
|
-
region: service.region
|
|
14119
|
-
}),
|
|
14120
|
-
secrets,
|
|
14121
|
-
timeoutMs: 15e3,
|
|
14122
|
-
maxCaptureChars: 1024
|
|
14123
|
-
});
|
|
14124
|
-
verified = result.success && result.data.stdout.trim() === "VERIFIED";
|
|
14125
|
-
} else {
|
|
14126
|
-
const args = service.kind === "ldap" ? [
|
|
14127
|
-
"exec",
|
|
14128
|
-
"-i",
|
|
14129
|
-
service.container,
|
|
14130
|
-
"ldapwhoami",
|
|
14131
|
-
"-x",
|
|
14132
|
-
"-H",
|
|
14133
|
-
"ldap://127.0.0.1:389",
|
|
14134
|
-
"-D",
|
|
14135
|
-
service.bindDn,
|
|
14136
|
-
"-y",
|
|
14137
|
-
"/dev/stdin"
|
|
14138
|
-
] : [
|
|
14139
|
-
"exec",
|
|
14140
|
-
"-i",
|
|
14141
|
-
service.container,
|
|
14142
|
-
"sh",
|
|
14143
|
-
"-c",
|
|
14144
|
-
service.kind === "postgresql" ? 'IFS= read -r PGPASSWORD; export PGPASSWORD; exec psql --no-psqlrc -h "$3" -U "$1" -d "dbname=$2 require_auth=scram-sha-256,md5,password" -Atc "SELECT 1"' : 'IFS= read -r MYSQL_PWD; export MYSQL_PWD; exec mysql -h 127.0.0.1 -u "$1" "$2" -Nse "SELECT 1"',
|
|
14145
|
-
"hai-secret-check",
|
|
14146
|
-
service.username,
|
|
14147
|
-
service.database,
|
|
14148
|
-
service.container
|
|
14149
|
-
];
|
|
14150
|
-
const result = await runCommand(options.runtime, args, {
|
|
14151
|
-
input: service.kind === "ldap" ? password : `${password}
|
|
14152
|
-
`,
|
|
14153
|
-
secrets,
|
|
14154
|
-
timeoutMs: 15e3,
|
|
14155
|
-
maxCaptureChars: 1024
|
|
14156
|
-
});
|
|
14157
|
-
verified = result.success && !result.data.stdoutTruncated && (service.kind === "ldap" ? result.data.stdout.trim().startsWith("dn:") : result.data.stdout.trim() === "1");
|
|
14158
|
-
if (verified && service.kind === "mysql") {
|
|
14159
|
-
const rootCheck = await runCommand(options.runtime, [
|
|
14160
|
-
...args.slice(0, -3),
|
|
14161
|
-
"root",
|
|
14162
|
-
service.database,
|
|
14163
|
-
service.container
|
|
14164
|
-
], {
|
|
14165
|
-
input: `${password}
|
|
14166
|
-
`,
|
|
14167
|
-
secrets,
|
|
14168
|
-
timeoutMs: 15e3,
|
|
14169
|
-
maxCaptureChars: 1024
|
|
14170
|
-
});
|
|
14171
|
-
verified = rootCheck.success && !rootCheck.data.stdoutTruncated && rootCheck.data.stdout.trim() === "1";
|
|
14172
|
-
const invalidPassword = `hai-import-invalid-${randomUUID()}`;
|
|
14173
|
-
for (const username of [service.username, "root"]) {
|
|
14174
|
-
if (!verified)
|
|
14175
|
-
break;
|
|
14176
|
-
const negative = await runCommand(options.runtime, [
|
|
14177
|
-
...args.slice(0, -3),
|
|
14178
|
-
username,
|
|
14179
|
-
service.database,
|
|
14180
|
-
service.container
|
|
14181
|
-
], {
|
|
14182
|
-
input: `${invalidPassword}
|
|
14183
|
-
`,
|
|
14184
|
-
secrets: [...secrets, invalidPassword],
|
|
14185
|
-
timeoutMs: 15e3,
|
|
14186
|
-
maxCaptureChars: 1024
|
|
14187
|
-
});
|
|
14188
|
-
verified = !negative.success && /ERROR 1045 \(28000\)/.test(negative.error.message);
|
|
14189
|
-
}
|
|
14190
|
-
}
|
|
14191
|
-
}
|
|
14192
|
-
if (!verified)
|
|
14193
|
-
throw new Error("verification");
|
|
14194
|
-
phase = "identity";
|
|
14195
|
-
}
|
|
14196
|
-
phase = "state";
|
|
14197
|
-
const uniqueVolumes = [
|
|
14198
|
-
...new Map(volumes.map((volume) => [`${volume.group}:${volume.name}`, volume])).values()
|
|
14199
|
-
].sort((a, b) => `${a.group}:${a.name}`.localeCompare(`${b.group}:${b.name}`));
|
|
14200
|
-
const lines = [
|
|
14201
|
-
"FORMAT_VERSION=2",
|
|
14202
|
-
`PROJECT=${project}`,
|
|
14203
|
-
...selectedGroups.map((group) => `${group}=${Buffer.from(config.credentials[group], "utf8").toString("hex")}`),
|
|
14204
|
-
...uniqueVolumes.map((volume, index) => `VOLUME_${index + 1}=${volume.group}:${volume.name}`)
|
|
14205
|
-
];
|
|
14206
|
-
let changed = false;
|
|
14207
|
-
if (existsSync(stateFile) || isSymbolicLink(stateFile)) {
|
|
14208
|
-
const existing = readImportedState(privateFile(stateFile));
|
|
14209
|
-
if (existing.FORMAT_VERSION !== "2" || existing.PROJECT !== project || selectedGroups.some((group) => existing[group] !== Buffer.from(config.credentials[group], "utf8").toString("hex")) || uniqueVolumes.some((volume) => !Object.entries(existing).some(([key, value]) => /^VOLUME_\d+$/.test(key) && value === `${volume.group}:${volume.name}`)))
|
|
14210
|
-
throw new Error("state");
|
|
14211
|
-
} else if (options.dryRun === false) {
|
|
14212
|
-
commitAttempted = true;
|
|
14213
|
-
if (!lock || !await lock.commit(`${lines.join("\n")}
|
|
14214
|
-
`))
|
|
14215
|
-
throw new Error("lock");
|
|
14216
|
-
changed = true;
|
|
14217
|
-
}
|
|
14218
|
-
return ok({
|
|
14219
|
-
projectName: config.projectName,
|
|
14220
|
-
stateVersion: 2,
|
|
14221
|
-
groups: selectedGroups,
|
|
14222
|
-
volumes: uniqueVolumes.map((volume) => volume.name),
|
|
14223
|
-
changed
|
|
14224
|
-
});
|
|
14225
|
-
} catch {
|
|
14226
|
-
return err({
|
|
14227
|
-
...HaiDeployError.CONFIG_ERROR,
|
|
14228
|
-
message: deployM("deploy_secretImportFailed", {
|
|
14229
|
-
params: { phase }
|
|
14230
|
-
}),
|
|
14231
|
-
ext: {
|
|
14232
|
-
phase,
|
|
14233
|
-
state: commitAttempted ? "unknown" : "unchanged"
|
|
14234
|
-
}
|
|
14235
|
-
});
|
|
14236
|
-
} finally {
|
|
14237
|
-
await lock?.release();
|
|
14238
|
-
}
|
|
14239
|
-
}
|
|
14240
|
-
function readImportedState(text) {
|
|
14241
|
-
const result = {};
|
|
14242
|
-
for (const line of text.trimEnd().split("\n")) {
|
|
14243
|
-
const match = /^(FORMAT_VERSION|PROJECT|DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD|VOLUME_[1-9]\d*)=([^=]+)$/.exec(line);
|
|
14244
|
-
if (!match || Object.hasOwn(result, match[1]))
|
|
14245
|
-
throw new Error("state");
|
|
14246
|
-
const key = match[1];
|
|
14247
|
-
const value = match[2];
|
|
14248
|
-
if (key.endsWith("_PASSWORD")) {
|
|
14249
|
-
if (!/^(?:[a-f0-9]{2})+$/.test(value))
|
|
14250
|
-
throw new Error("state");
|
|
14251
|
-
const decoded = Buffer.from(value, "hex").toString("utf8");
|
|
14252
|
-
if (!passwordSchema.safeParse(decoded).success || Buffer.from(decoded, "utf8").toString("hex") !== value)
|
|
14253
|
-
throw new Error("state");
|
|
14254
|
-
} else if (key.startsWith("VOLUME_") && !/^(?:DB_PASSWORD|STORAGE_PASSWORD|LDAP_PASSWORD):[a-zA-Z0-9][\w.-]*$/.test(value)) {
|
|
14255
|
-
throw new Error("state");
|
|
14256
|
-
}
|
|
14257
|
-
result[key] = value;
|
|
14258
|
-
}
|
|
14259
|
-
if (result.FORMAT_VERSION !== "2" || !nameSchema.safeParse(result.PROJECT).success)
|
|
14260
|
-
throw new Error("state");
|
|
14261
|
-
return result;
|
|
14262
|
-
}
|
|
14263
|
-
function isSymbolicLink(path) {
|
|
14264
|
-
try {
|
|
14265
|
-
return lstatSync(path).isSymbolicLink();
|
|
14266
|
-
} catch {
|
|
14267
|
-
return false;
|
|
14268
|
-
}
|
|
14269
|
-
}
|
|
14270
|
-
function privateFile(path) {
|
|
14271
|
-
return readLocalFile(path, { maxBytes: 65536, private: true }).toString("utf8");
|
|
14272
|
-
}
|
|
14273
|
-
async function inspectContainer(runtime, container, secrets) {
|
|
14274
|
-
const result = await runCommand(runtime, [
|
|
14275
|
-
"inspect",
|
|
14276
|
-
"--format",
|
|
14277
|
-
"{{json .Config.Labels}}\n{{json .Mounts}}\n{{json .State.Running}}\n{{json .NetworkSettings.Networks}}",
|
|
14278
|
-
container
|
|
14279
|
-
], { secrets, timeoutMs: 1e4, maxCaptureChars: 32768 });
|
|
14280
|
-
if (!result.success || result.data.stdoutTruncated)
|
|
14281
|
-
throw new Error("identity");
|
|
14282
|
-
const [labelsText, mountsText, runningText, networksText] = result.data.stdout.trim().split("\n");
|
|
14283
|
-
const labels = z.record(z.string(), z.string()).parse(JSON.parse(labelsText));
|
|
14284
|
-
const mounts = z.array(z.object({
|
|
14285
|
-
Type: z.string(),
|
|
14286
|
-
Name: z.string().optional()
|
|
14287
|
-
})).parse(JSON.parse(mountsText));
|
|
14288
|
-
const networks = z.record(z.string(), z.object({
|
|
14289
|
-
NetworkID: z.string()
|
|
14290
|
-
})).parse(JSON.parse(networksText) ?? {});
|
|
14291
|
-
const dockerProject = labels["com.docker.compose.project"] || void 0;
|
|
14292
|
-
const podmanProject = labels["io.podman.compose.project"] || void 0;
|
|
14293
|
-
const dockerService = labels["com.docker.compose.service"] || void 0;
|
|
14294
|
-
const podmanService = labels["io.podman.compose.service"] || void 0;
|
|
14295
|
-
if (Boolean(dockerProject) !== Boolean(dockerService) || Boolean(podmanProject) !== Boolean(podmanService) || dockerProject && podmanProject && dockerProject !== podmanProject || dockerService && podmanService && dockerService !== podmanService) {
|
|
14296
|
-
throw new Error("identity");
|
|
14297
|
-
}
|
|
14298
|
-
return {
|
|
14299
|
-
project: dockerProject ?? podmanProject,
|
|
14300
|
-
service: dockerService ?? podmanService,
|
|
14301
|
-
running: runningText === "true",
|
|
14302
|
-
volumes: mounts.flatMap((mount) => mount.Type === "volume" && mount.Name ? [mount.Name] : []),
|
|
14303
|
-
networks: Object.values(networks).map((network) => network.NetworkID)
|
|
14304
|
-
};
|
|
14305
|
-
}
|
|
14306
|
-
async function acquireImportLock(path, root) {
|
|
14307
|
-
return new Promise((resolveLock, reject) => {
|
|
14308
|
-
const child = spawn("flock", [
|
|
14309
|
-
"-n",
|
|
14310
|
-
path,
|
|
14311
|
-
process.execPath,
|
|
14312
|
-
"-e",
|
|
14313
|
-
importCommitScript,
|
|
14314
|
-
root
|
|
14315
|
-
], {
|
|
14316
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
14317
|
-
windowsHide: true
|
|
14318
|
-
});
|
|
14319
|
-
let acquired = false;
|
|
14320
|
-
let exited = false;
|
|
14321
|
-
let output = "";
|
|
14322
|
-
const finished = new Promise((done) => child.once("close", () => {
|
|
14323
|
-
exited = true;
|
|
14324
|
-
done();
|
|
14325
|
-
}));
|
|
14326
|
-
child.stdin.on("error", () => {
|
|
14327
|
-
});
|
|
14328
|
-
const timer = setTimeout(() => {
|
|
14329
|
-
child.stdin.end();
|
|
14330
|
-
child.kill();
|
|
14331
|
-
reject(new Error("lock"));
|
|
14332
|
-
}, 5e3);
|
|
14333
|
-
child.once("error", () => {
|
|
14334
|
-
clearTimeout(timer);
|
|
14335
|
-
reject(new Error("lock"));
|
|
14336
|
-
});
|
|
14337
|
-
child.once("exit", () => {
|
|
14338
|
-
if (!acquired) {
|
|
14339
|
-
clearTimeout(timer);
|
|
14340
|
-
reject(new Error("lock"));
|
|
14341
|
-
}
|
|
14342
|
-
});
|
|
14343
|
-
child.stdout.on("data", (data) => {
|
|
14344
|
-
output += data.toString();
|
|
14345
|
-
if (acquired || !output.includes("\n"))
|
|
14346
|
-
return;
|
|
14347
|
-
if (!output.startsWith("LOCKED\n") || exited) {
|
|
14348
|
-
child.stdin.end();
|
|
14349
|
-
reject(new Error("lock"));
|
|
14350
|
-
return;
|
|
14351
|
-
}
|
|
14352
|
-
acquired = true;
|
|
14353
|
-
clearTimeout(timer);
|
|
14354
|
-
resolveLock({
|
|
14355
|
-
commit: async (content) => {
|
|
14356
|
-
if (exited)
|
|
14357
|
-
return false;
|
|
14358
|
-
child.stdin.end(JSON.stringify({ content }));
|
|
14359
|
-
await finished;
|
|
14360
|
-
return child.exitCode === 0 && output === "LOCKED\nCOMMITTED\n";
|
|
14361
|
-
},
|
|
14362
|
-
release: async () => {
|
|
14363
|
-
child.stdin.end();
|
|
14364
|
-
await finished;
|
|
14365
|
-
}
|
|
14366
|
-
});
|
|
14367
|
-
});
|
|
14368
|
-
});
|
|
14369
|
-
}
|
|
14370
13958
|
|
|
14371
|
-
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, canKeepMiddleware, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery,
|
|
13959
|
+
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, canKeepMiddleware, createDeploymentPlan, deploy, describeMiddlewareBackend, getDeployCommandState, getDeployRecovery, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, parseMiddlewareField, readDeploymentArtifact, resolveDownloadProxy, resolveImageReference, saveDeploymentEnvironment, saveMiddlewareEnvironment, validateMiddlewareField };
|
|
14372
13960
|
//# sourceMappingURL=index.js.map
|
|
14373
13961
|
//# sourceMappingURL=index.js.map
|