@h-ai/cli 1.0.0-beta.2 → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +20 -8
- package/README.ja.md +20 -8
- package/README.md +20 -8
- package/dist/{chunk-MUHRBJSW.js → chunk-K6RWTTHD.js} +546 -97
- package/dist/chunk-K6RWTTHD.js.map +1 -0
- package/dist/{chunk-AKV2CQZV.js → chunk-SWEU7KUE.js} +71 -59
- package/dist/chunk-SWEU7KUE.js.map +1 -0
- package/dist/{chunk-EF7WOFWZ.js → chunk-YVNNMC4H.js} +364 -136
- package/dist/chunk-YVNNMC4H.js.map +1 -0
- package/dist/cli-config-templates-LUJGDQNW.js +5 -0
- package/dist/{cli-config-templates-HHFHNXBN.js.map → cli-config-templates-LUJGDQNW.js.map} +1 -1
- package/dist/{cli-skill-templates-PN4FLGBC.js → cli-skill-templates-VV2XWYON.js} +3 -3
- package/dist/{cli-skill-templates-PN4FLGBC.js.map → cli-skill-templates-VV2XWYON.js.map} +1 -1
- package/dist/index.js +117 -208
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/{src-TEBHR5I5.js → src-IR5AU43K.js} +3 -3
- package/dist/src-IR5AU43K.js.map +1 -0
- package/package.json +11 -11
- package/templates/apps/api/.dockerignore.hbs +10 -0
- package/templates/apps/api/apps/{{projectName}}-serv/config/_serv.yml.hbs +20 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/app.ts.hbs +6 -1
- package/templates/apps/api/apps/{{projectName}}-serv/src/lib/server/init.ts.hbs +1 -1
- package/templates/apps/api/images/serv.Dockerfile.hbs +3 -2
- package/templates/apps/fullstack/.dockerignore.hbs +10 -0
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +16 -2
- package/templates/apps/fullstack/images/serv.Dockerfile.hbs +6 -2
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +6 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +10 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +14 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +1 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +7 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/security.test.ts.hbs +1 -1
- package/templates/apps/fullstack/pnpm-workspace.yaml.hbs +1 -0
- package/templates/apps/website/scripts/static-website.mjs.hbs +3 -3
- package/templates/base/.dockerignore.hbs +10 -0
- package/templates/base/images/web.Dockerfile.hbs +4 -2
- package/templates/base/package.json.hbs +1 -1
- package/templates/base/src/hooks.server.ts.hbs +9 -7
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +3 -1
- package/templates/skills/hai-ai/guide.md +5 -3
- package/templates/skills/hai-app-create/development.md +7 -3
- package/templates/skills/hai-app-create/reference.md +1 -1
- package/templates/skills/hai-build/SKILL.md +5 -5
- package/templates/skills/hai-build/paths.md +1 -1
- package/templates/skills/hai-build/reference.md +1 -1
- package/templates/skills/hai-deploy/SKILL.md +15 -3
- package/templates/skills/hai-deploy/middleware.md +4 -2
- package/templates/skills/hai-serv/reference.md +5 -8
- package/dist/chunk-AKV2CQZV.js.map +0 -1
- package/dist/chunk-EF7WOFWZ.js.map +0 -1
- package/dist/chunk-MUHRBJSW.js.map +0 -1
- package/dist/cli-config-templates-HHFHNXBN.js +0 -5
- package/dist/src-TEBHR5I5.js.map +0 -1
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { __commonJS, __export, __toESM } from './chunk-ZSDEVXZ7.js';
|
|
3
|
-
import { createHash, randomUUID, randomBytes } from 'crypto';
|
|
4
|
-
import { createReadStream, readFileSync, existsSync, lstatSync, writeFileSync, readdirSync, realpathSync, chmodSync, mkdirSync, mkdtempSync, copyFileSync, renameSync, rmSync,
|
|
3
|
+
import { createHash, randomUUID, randomBytes, X509Certificate, createPublicKey, createPrivateKey } from 'crypto';
|
|
4
|
+
import { createReadStream, readFileSync, existsSync, lstatSync, writeFileSync, readdirSync, realpathSync, chmodSync, mkdirSync, statSync, mkdtempSync, copyFileSync, renameSync, rmSync, createWriteStream } from 'fs';
|
|
5
5
|
import { resolve, extname, join, dirname, relative, basename, sep } from 'path';
|
|
6
|
-
import { core, err, ok, RequestAccessConfigSchema, TrustedProxiesSchema } from '@h-ai/core';
|
|
6
|
+
import { core, err, ok, CoreConfigSchema, RequestAccessConfigSchema, TrustedProxiesSchema } from '@h-ai/core';
|
|
7
7
|
import process6 from 'process';
|
|
8
8
|
import { parseEnv } from 'util';
|
|
9
9
|
import { LineCounter, parseDocument, parse as parse$1, stringify, parseAllDocuments } from 'yaml';
|
|
10
10
|
import { homedir, tmpdir } from 'os';
|
|
11
|
+
import { request } from 'https';
|
|
12
|
+
import { rootCertificates } from 'tls';
|
|
13
|
+
import { Transform } from 'stream';
|
|
14
|
+
import { pipeline } from 'stream/promises';
|
|
11
15
|
import { spawn } from 'child_process';
|
|
12
16
|
import { readdir, readFile } from 'fs/promises';
|
|
13
17
|
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
14
18
|
import { Buffer } from 'buffer';
|
|
19
|
+
import { isIP } from 'net';
|
|
15
20
|
|
|
16
21
|
// ../../node_modules/jsbn/index.js
|
|
17
22
|
var require_jsbn = __commonJS({
|
|
@@ -7878,7 +7883,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
7878
7883
|
var NONE = 0;
|
|
7879
7884
|
var ASSUMED = 1;
|
|
7880
7885
|
var PROVEN = 2;
|
|
7881
|
-
function isRecursive(inst, stack,
|
|
7886
|
+
function isRecursive(inst, stack, resolve9) {
|
|
7882
7887
|
const cached2 = recursive.get(inst);
|
|
7883
7888
|
if (cached2 !== void 0)
|
|
7884
7889
|
return cached2 ? PROVEN : NONE;
|
|
@@ -7888,7 +7893,7 @@ function isRecursive(inst, stack, resolve8) {
|
|
|
7888
7893
|
let result = NONE;
|
|
7889
7894
|
const check2 = (child) => {
|
|
7890
7895
|
if (result !== PROVEN && child?._zod) {
|
|
7891
|
-
const answer = isRecursive(child, stack,
|
|
7896
|
+
const answer = isRecursive(child, stack, resolve9);
|
|
7892
7897
|
if (answer > result)
|
|
7893
7898
|
result = answer;
|
|
7894
7899
|
}
|
|
@@ -7899,7 +7904,7 @@ function isRecursive(inst, stack, resolve8) {
|
|
|
7899
7904
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
7900
7905
|
if (!desc.enumerable)
|
|
7901
7906
|
continue;
|
|
7902
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
7907
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve9) : NONE;
|
|
7903
7908
|
if (child > answer)
|
|
7904
7909
|
answer = child;
|
|
7905
7910
|
}
|
|
@@ -7963,7 +7968,7 @@ function isRecursive(inst, stack, resolve8) {
|
|
|
7963
7968
|
break;
|
|
7964
7969
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
7965
7970
|
case "lazy": {
|
|
7966
|
-
const inner = def._cachedInner ?? (
|
|
7971
|
+
const inner = def._cachedInner ?? (resolve9 ? inst._zod.innerType : void 0);
|
|
7967
7972
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
7968
7973
|
break;
|
|
7969
7974
|
}
|
|
@@ -22614,7 +22619,7 @@ var en_US_default = {
|
|
|
22614
22619
|
deploy_storageProxyInvalid: "Cannot generate the storage proxy. Use a valid DNS bucket name whose path does not overlap the website, API, or health endpoints.",
|
|
22615
22620
|
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.",
|
|
22616
22621
|
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.",
|
|
22617
|
-
deploy_artifactInvalid: "Image artifacts are missing or invalid. Run pnpm build --target
|
|
22622
|
+
deploy_artifactInvalid: "Image artifacts are missing or invalid. Run pnpm build --target image, select valid --artifact-dir, or use pnpm run deploy --build.",
|
|
22618
22623
|
deploy_sshRequired: "Automatic deployment requires SSH settings. Use --bundle-only for manual transfer when the server is unreachable.",
|
|
22619
22624
|
deploy_sshPasswordInvalid: "SSH passwords must be nonempty, contain no CR/LF/NUL, and be at most 1023 UTF-8 bytes.",
|
|
22620
22625
|
deploy_sshAskpassFailed: "Could not prepare the managed SSH password helper. Check OpenSSH and Windows PowerShell 5.",
|
|
@@ -22664,7 +22669,9 @@ var en_US_default = {
|
|
|
22664
22669
|
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",
|
|
22665
22670
|
deploy_projectNameRequired: "The root package.json.name is missing or cannot be normalized into an image and deployment resource identity.",
|
|
22666
22671
|
deploy_projectNameOverrideRemoved: "Deployment identity comes from the root package.json.name; remove the obsolete projectName override.",
|
|
22667
|
-
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."
|
|
22672
|
+
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.",
|
|
22673
|
+
deploy_httpsPortsInvalid: "HTTP and HTTPS ports must differ; Let\u2019s Encrypt HTTP-01 requires host port 80.",
|
|
22674
|
+
deploy_httpsCertificateInvalid: "Invalid HTTPS certificate. Check readable PEM files, matching unencrypted private key, validity dates and domain, then repackage."
|
|
22668
22675
|
};
|
|
22669
22676
|
|
|
22670
22677
|
// ../deploy/messages/ja-JP.json
|
|
@@ -22722,7 +22729,7 @@ var ja_JP_default = {
|
|
|
22722
22729
|
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",
|
|
22723
22730
|
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",
|
|
22724
22731
|
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",
|
|
22725
|
-
deploy_artifactInvalid: "\u30A4\u30E1\u30FC\u30B8\u6210\u679C\u7269\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u3001\u7121\u52B9\u3067\u3059\u3002pnpm build --target
|
|
22732
|
+
deploy_artifactInvalid: "\u30A4\u30E1\u30FC\u30B8\u6210\u679C\u7269\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u3001\u7121\u52B9\u3067\u3059\u3002pnpm build --target image \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",
|
|
22726
22733
|
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",
|
|
22727
22734
|
deploy_sshPasswordInvalid: "SSH\u30D1\u30B9\u30EF\u30FC\u30C9\u306F\u7A7A\u306B\u3067\u304D\u305A\u3001CR/LF/NUL\u3092\u542B\u3081\u3089\u308C\u307E\u305B\u3093\u3002UTF-8\u30671023\u30D0\u30A4\u30C8\u4EE5\u4E0B\u306B\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
22728
22735
|
deploy_sshAskpassFailed: "\u7BA1\u7406\u5BFE\u8C61\u306ESSH\u30D1\u30B9\u30EF\u30FC\u30C9helper\u3092\u6E96\u5099\u3067\u304D\u307E\u305B\u3093\u3002OpenSSH\u3068Windows PowerShell 5\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
@@ -22772,7 +22779,9 @@ var ja_JP_default = {
|
|
|
22772
22779
|
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",
|
|
22773
22780
|
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",
|
|
22774
22781
|
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",
|
|
22775
|
-
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"
|
|
22782
|
+
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",
|
|
22783
|
+
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",
|
|
22784
|
+
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"
|
|
22776
22785
|
};
|
|
22777
22786
|
|
|
22778
22787
|
// ../deploy/messages/zh-CN.json
|
|
@@ -22830,7 +22839,7 @@ var zh_CN_default2 = {
|
|
|
22830
22839
|
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",
|
|
22831
22840
|
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",
|
|
22832
22841
|
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",
|
|
22833
|
-
deploy_artifactInvalid: "\u955C\u50CF\u4EA7\u7269\u7F3A\u5931\u6216\u6821\u9A8C\u5931\u8D25\u3002\u8BF7\u5148\u6267\u884C pnpm build --target
|
|
22842
|
+
deploy_artifactInvalid: "\u955C\u50CF\u4EA7\u7269\u7F3A\u5931\u6216\u6821\u9A8C\u5931\u8D25\u3002\u8BF7\u5148\u6267\u884C pnpm build --target image\uFF0C\u6216\u7528 --artifact-dir \u6307\u5B9A\u6709\u6548\u4EA7\u7269\uFF1B\u4E5F\u53EF\u7528 pnpm run deploy --build \u4E00\u6B21\u5B8C\u6210\u3002",
|
|
22834
22843
|
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",
|
|
22835
22844
|
deploy_sshPasswordInvalid: "SSH \u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A\u6216\u5305\u542B CR/LF/NUL\uFF0CUTF-8 \u957F\u5EA6\u4E0D\u80FD\u8D85\u8FC7 1023 \u5B57\u8282\u3002",
|
|
22836
22845
|
deploy_sshAskpassFailed: "\u65E0\u6CD5\u51C6\u5907\u53D7\u63A7 SSH \u5BC6\u7801 helper\uFF1B\u8BF7\u68C0\u67E5\u7CFB\u7EDF OpenSSH \u548C Windows PowerShell 5\u3002",
|
|
@@ -22880,7 +22889,9 @@ var zh_CN_default2 = {
|
|
|
22880
22889
|
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",
|
|
22881
22890
|
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",
|
|
22882
22891
|
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",
|
|
22883
|
-
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"
|
|
22892
|
+
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",
|
|
22893
|
+
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",
|
|
22894
|
+
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"
|
|
22884
22895
|
};
|
|
22885
22896
|
|
|
22886
22897
|
// ../deploy/src/deploy-i18n.ts
|
|
@@ -22920,12 +22931,43 @@ var SshConfigSchema = external_exports.object({
|
|
|
22920
22931
|
if (!ssh.password || /[\r\n\0]/.test(ssh.password) || new TextEncoder().encode(ssh.password).length > 1023)
|
|
22921
22932
|
context.addIssue({ code: "custom", path: ["password"], message: deployM("deploy_sshPasswordInvalid") });
|
|
22922
22933
|
});
|
|
22934
|
+
var HttpsDomainSchema = external_exports.string().max(253).regex(/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i).refine((value) => !value.includes(".."));
|
|
22935
|
+
var httpsOptions = {
|
|
22936
|
+
/** 公开访问域名;同时用于证书身份校验和 Nginx 入口。 */
|
|
22937
|
+
domain: HttpsDomainSchema.describe("HTTPS \u516C\u5F00\u57DF\u540D\uFF1B\u8BC1\u4E66\u5FC5\u987B\u5339\u914D\u8BE5\u57DF\u540D"),
|
|
22938
|
+
/** HTTPS 宿主端口,与 HTTP 端口不同。 */
|
|
22939
|
+
port: external_exports.number().int().min(1).max(65535).default(443).describe("HTTPS \u5BBF\u4E3B\u7AEF\u53E3\uFF0C\u9ED8\u8BA4 443\uFF0C\u987B\u4E0E HTTP \u7AEF\u53E3\u4E0D\u540C"),
|
|
22940
|
+
/** 是否将普通 HTTP 请求跳转到 HTTPS。 */
|
|
22941
|
+
redirectHttp: external_exports.boolean().default(true).describe("\u666E\u901A HTTP \u8BF7\u6C42\u662F\u5426\u4EE5 308 \u8DF3\u8F6C\u81F3 HTTPS\uFF0C\u5065\u5EB7\u68C0\u67E5\u548C ACME \u6311\u6218\u9664\u5916")
|
|
22942
|
+
};
|
|
22943
|
+
var HttpsConfigSchema = external_exports.discriminatedUnion("type", [
|
|
22944
|
+
external_exports.object({
|
|
22945
|
+
type: external_exports.literal("files"),
|
|
22946
|
+
...httpsOptions,
|
|
22947
|
+
/** 相对于工程根或绝对路径的 PEM 完整证书链及未加密私钥。 */
|
|
22948
|
+
certificateFile: external_exports.string().min(1).describe("PEM \u5B8C\u6574\u8BC1\u4E66\u94FE\u8DEF\u5F84\uFF0C\u76F8\u5BF9\u5DE5\u7A0B\u6839\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF1B\u6253\u5305\u65F6\u590D\u5236\u5230\u79C1\u6709\u53D1\u5E03\u76EE\u5F55"),
|
|
22949
|
+
privateKeyFile: external_exports.string().min(1).describe("\u672A\u52A0\u5BC6 PEM \u79C1\u94A5\u8DEF\u5F84\uFF0C\u76F8\u5BF9\u5DE5\u7A0B\u6839\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF1B\u5FC5\u987B\u4E0E\u8BC1\u4E66\u5339\u914D")
|
|
22950
|
+
}),
|
|
22951
|
+
external_exports.object({
|
|
22952
|
+
type: external_exports.literal("letsencrypt"),
|
|
22953
|
+
...httpsOptions,
|
|
22954
|
+
domain: HttpsDomainSchema.refine((value) => value.includes(".") && !/^\d+(?:\.\d+){3}$/.test(value)).describe("Let\u2019s Encrypt \u5355\u4E2A\u516C\u7F51\u57DF\u540D\uFF0C\u4F7F\u7528 HTTP-01\uFF0C\u4E0D\u652F\u6301 IP \u6216\u901A\u914D\u7B26"),
|
|
22955
|
+
email: external_exports.email().describe("Let\u2019s Encrypt \u8D26\u53F7\u8054\u7CFB\u90AE\u7BB1"),
|
|
22956
|
+
/** 使用者须明确同意 Let's Encrypt 的服务条款。 */
|
|
22957
|
+
acceptTerms: external_exports.literal(true).describe("\u660E\u786E\u540C\u610F Let\u2019s Encrypt \u670D\u52A1\u6761\u6B3E\u540E\u624D\u53EF\u8BBE\u7F6E\u4E3A true")
|
|
22958
|
+
})
|
|
22959
|
+
]);
|
|
22923
22960
|
var PortExposeSchema = external_exports.object({
|
|
22924
22961
|
type: external_exports.literal("port").default("port"),
|
|
22925
22962
|
/** 容器内监听端口(adapter-node 默认 3000) */
|
|
22926
22963
|
containerPort: external_exports.number().int().min(1).max(65535).default(3e3),
|
|
22927
22964
|
/** 宿主机映射端口 */
|
|
22928
|
-
hostPort: external_exports.number().int().min(1).max(65535)
|
|
22965
|
+
hostPort: external_exports.number().int().min(1).max(65535),
|
|
22966
|
+
/** 可选 HTTPS:已有 PEM 文件或官方 Certbot 自动申请续期;省略保留 HTTP。 */
|
|
22967
|
+
https: HttpsConfigSchema.optional().describe("\u53EF\u9009 HTTPS \u5165\u53E3\uFF1Bfiles \u4F7F\u7528\u5DF2\u6709\u8BC1\u4E66\uFF0Cletsencrypt \u4F7F\u7528\u5B98\u65B9 Certbot \u7533\u8BF7\u7EED\u671F\uFF0C\u7701\u7565\u4FDD\u6301 HTTP")
|
|
22968
|
+
}).superRefine((expose, context) => {
|
|
22969
|
+
if (expose.https && (expose.https.port === expose.hostPort || expose.https.type === "letsencrypt" && expose.hostPort !== 80))
|
|
22970
|
+
context.addIssue({ code: "custom", path: ["https"], message: deployM("deploy_httpsPortsInvalid") });
|
|
22929
22971
|
});
|
|
22930
22972
|
var ExposeSchema = PortExposeSchema;
|
|
22931
22973
|
var DockerSshProviderConfigSchema = external_exports.object({
|
|
@@ -23424,6 +23466,32 @@ function saveCredentials(entries) {
|
|
|
23424
23466
|
);
|
|
23425
23467
|
}
|
|
23426
23468
|
}
|
|
23469
|
+
|
|
23470
|
+
// ../deploy/src/deploy-middleware-config.ts
|
|
23471
|
+
var connectionFields = {
|
|
23472
|
+
db: ["url", "host", "port", "database", "user", "password", "ssl"],
|
|
23473
|
+
cache: ["url", "host", "port", "password", "cluster", "sentinel", "tls"],
|
|
23474
|
+
storage: ["endpoint", "bucket", "region", "accessKeyId", "secretAccessKey", "forcePathStyle", "createBucket"],
|
|
23475
|
+
vecdb: ["url", "host", "port", "database", "user", "password", "apiKey", "path", "serverCommand", "startupTimeout"],
|
|
23476
|
+
ldap: ["url", "bindDn", "bindPassword", "searchBase", "useTls"],
|
|
23477
|
+
email: ["host", "port", "secure", "user", "pass"]
|
|
23478
|
+
};
|
|
23479
|
+
function mergeManagedMiddlewareConfig(requirement, supplied) {
|
|
23480
|
+
if (!requirement || requirement.service !== "ldap" && requirement.backend !== supplied.type)
|
|
23481
|
+
return supplied;
|
|
23482
|
+
const configured = { ...requirement.config };
|
|
23483
|
+
for (const field2 of connectionFields[requirement.service] ?? [])
|
|
23484
|
+
delete configured[field2];
|
|
23485
|
+
const merged = { ...configured, ...supplied };
|
|
23486
|
+
if (requirement.service === "cache" && typeof configured.db === "number" && typeof supplied.url === "string") {
|
|
23487
|
+
const url2 = new URL(supplied.url);
|
|
23488
|
+
if (configured.db !== 0 || url2.pathname !== "" && url2.pathname !== "/") {
|
|
23489
|
+
url2.pathname = String(configured.db);
|
|
23490
|
+
merged.url = url2.toString();
|
|
23491
|
+
}
|
|
23492
|
+
}
|
|
23493
|
+
return merged;
|
|
23494
|
+
}
|
|
23427
23495
|
var RecoverySchema = external_exports.object({
|
|
23428
23496
|
projectName: external_exports.string(),
|
|
23429
23497
|
stage: external_exports.string(),
|
|
@@ -25200,7 +25268,7 @@ async function groupExited(pid) {
|
|
|
25200
25268
|
async function helper(command, args, timeoutMs) {
|
|
25201
25269
|
if (timeoutMs <= 0)
|
|
25202
25270
|
return false;
|
|
25203
|
-
return new Promise((
|
|
25271
|
+
return new Promise((resolve9) => {
|
|
25204
25272
|
const child = spawn(command, args, { windowsHide: true, shell: false, stdio: "ignore" });
|
|
25205
25273
|
let settled = false;
|
|
25206
25274
|
let timer;
|
|
@@ -25210,7 +25278,7 @@ async function helper(command, args, timeoutMs) {
|
|
|
25210
25278
|
settled = true;
|
|
25211
25279
|
if (timer)
|
|
25212
25280
|
clearTimeout(timer);
|
|
25213
|
-
|
|
25281
|
+
resolve9(success2);
|
|
25214
25282
|
};
|
|
25215
25283
|
timer = globalThis.setTimeout(() => {
|
|
25216
25284
|
child.kill("SIGKILL");
|
|
@@ -25353,7 +25421,7 @@ ${deployM(state.localTermination === "confirmed" ? "deploy_localStopped" : state
|
|
|
25353
25421
|
ext: { commandExecution: state }
|
|
25354
25422
|
});
|
|
25355
25423
|
try {
|
|
25356
|
-
return await new Promise((
|
|
25424
|
+
return await new Promise((resolve9) => {
|
|
25357
25425
|
if (typeof command !== "string" || !command || command.includes("\0") || args.some((argument) => typeof argument !== "string" || argument.includes("\0")))
|
|
25358
25426
|
throw new TypeError("Invalid command arguments");
|
|
25359
25427
|
if (options.input !== void 0 && typeof options.input !== "string")
|
|
@@ -25388,7 +25456,7 @@ ${deployM(state.localTermination === "confirmed" ? "deploy_localStopped" : state
|
|
|
25388
25456
|
settled = true;
|
|
25389
25457
|
if (timer)
|
|
25390
25458
|
clearTimeout(timer);
|
|
25391
|
-
|
|
25459
|
+
resolve9(result);
|
|
25392
25460
|
};
|
|
25393
25461
|
const interrupt = (reason, message) => {
|
|
25394
25462
|
interrupted = true;
|
|
@@ -25837,7 +25905,7 @@ start_services() {
|
|
|
25837
25905
|
application_services=''
|
|
25838
25906
|
application_orphan_option=$orphan_option
|
|
25839
25907
|
for service in "$@"; do
|
|
25840
|
-
case "$service" in serv|web|gateway) ;; *) middleware_services="$middleware_services $service";; esac
|
|
25908
|
+
case "$service" in serv|web|gateway|certbot) ;; *) middleware_services="$middleware_services $service";; esac
|
|
25841
25909
|
done
|
|
25842
25910
|
if [ -n "$middleware_services" ]; then
|
|
25843
25911
|
compose up -d --no-deps --no-build --pull never "$recreate_option" $orphan_option $middleware_services || return 1
|
|
@@ -25853,6 +25921,9 @@ start_services() {
|
|
|
25853
25921
|
fi
|
|
25854
25922
|
case " $* " in *' gateway '*) compose up -d --no-deps --no-build --pull never "$recreate_option" gateway || return 1;; esac
|
|
25855
25923
|
}
|
|
25924
|
+
start_renewal() {
|
|
25925
|
+
case " $* " in *' certbot '*) compose up -d --no-deps --no-build --pull never --no-recreate certbot || return 1;; esac
|
|
25926
|
+
}
|
|
25856
25927
|
read_proxy_service() {
|
|
25857
25928
|
proxy_directory=$1
|
|
25858
25929
|
if [ -e "$proxy_directory/proxy-service" ] || [ -L "$proxy_directory/proxy-service" ]; then
|
|
@@ -25876,13 +25947,22 @@ reload_retained_proxy() {
|
|
|
25876
25947
|
esac
|
|
25877
25948
|
}
|
|
25878
25949
|
wait_http() {
|
|
25879
|
-
|
|
25880
|
-
|
|
25950
|
+
http_release=$1
|
|
25951
|
+
http_port=$2
|
|
25952
|
+
shift 2
|
|
25953
|
+
https_domain=''
|
|
25954
|
+
if [ -f "$http_release/https" ]; then
|
|
25955
|
+
read -r https_domain https_port https_type < "$http_release/https"
|
|
25956
|
+
https_certificates="$http_release/tls"
|
|
25957
|
+
if [ "$https_type" = letsencrypt ]; then https_certificates="$root/https/certificates/$https_domain"; fi
|
|
25958
|
+
fi
|
|
25881
25959
|
for http_path in "$@"; do
|
|
25882
25960
|
http_ready=false
|
|
25883
25961
|
http_attempt=0
|
|
25884
25962
|
while [ "$http_attempt" -lt 40 ]; do
|
|
25885
|
-
if
|
|
25963
|
+
if [ -n "$https_domain" ]; then
|
|
25964
|
+
if curl --noproxy '*' -fsS --max-time 5 --cacert "$https_certificates/fullchain.pem" --resolve "$https_domain:$https_port:127.0.0.1" "https://$https_domain:$https_port$http_path" >/dev/null 2>&1; then http_ready=true; break; fi
|
|
25965
|
+
elif curl --noproxy '*' -fsS --max-time 5 "http://127.0.0.1:$http_port$http_path" >/dev/null 2>&1; then http_ready=true; break; fi
|
|
25886
25966
|
http_attempt=$((http_attempt + 1))
|
|
25887
25967
|
sleep 3
|
|
25888
25968
|
done
|
|
@@ -25895,7 +25975,6 @@ verify_recovery() {
|
|
|
25895
25975
|
recovery_port=$(awk -F: 'NR == 1 { print $1 }' "$recovery_release/layout") || return 1
|
|
25896
25976
|
recovery_paths=''
|
|
25897
25977
|
if [ -f "$recovery_release/health-paths" ]; then
|
|
25898
|
-
recovery_port=$(cat "$recovery_release/health-port") || return 1
|
|
25899
25978
|
recovery_paths=$(cat "$recovery_release/health-paths") || return 1
|
|
25900
25979
|
else
|
|
25901
25980
|
# 旧格式发布没有独立健康文件;按已存拓扑恢复最小 HTTP 检查。
|
|
@@ -25905,10 +25984,10 @@ verify_recovery() {
|
|
|
25905
25984
|
elif [ "$recovery_primary" = web ]; then recovery_paths='/'; else recovery_paths='/health'; fi
|
|
25906
25985
|
fi
|
|
25907
25986
|
case "$recovery_port" in ''|*[!0-9]*) return 1;; esac
|
|
25908
|
-
test -n "$recovery_paths" || return 1
|
|
25909
25987
|
wait_services $recover_selected || return 1
|
|
25988
|
+
start_renewal $recover_selected || return 1
|
|
25910
25989
|
reload_retained_proxy "$recovery_release" || return 1
|
|
25911
|
-
wait_http "$recovery_port" $recovery_paths
|
|
25990
|
+
wait_http "$recovery_release" "$recovery_port" $recovery_paths
|
|
25912
25991
|
}
|
|
25913
25992
|
finish() {
|
|
25914
25993
|
code=$?
|
|
@@ -26036,7 +26115,7 @@ if [ "$partial" = true ]; then
|
|
|
26036
26115
|
test "$current_storage_routing" = "$previous_storage_routing" || { printf 'Managed storage configuration changed. Create a full deployment to switch its public endpoint, bucket, or access policy consistently.\n' >&2; exit 1; }
|
|
26037
26116
|
if ! cmp -s layout "$root/releases/$previous/layout"; then
|
|
26038
26117
|
# 容量调整允许复用已部署 Web 镜像;端口、路由及其余代理策略仍要求全量部署。
|
|
26039
|
-
test "$(awk -F: '{print $1 ":" $2 ":" $3 ":" $4}' layout)" = "$(awk -F: '{print $1 ":" $2 ":" $3 ":" $4}' "$root/releases/$previous/layout")" || { printf 'Ports or application layout changed. Create a full deployment bundle.\n' >&2; exit 1; }
|
|
26118
|
+
test "$(awk -F: '{print $1 ":" $2 ":" $3 ":" $4 ":" $6}' layout)" = "$(awk -F: '{print $1 ":" $2 ":" $3 ":" $4 ":" $6}' "$root/releases/$previous/layout")" || { printf 'Ports, HTTPS or application layout changed. Create a full deployment bundle.\n' >&2; exit 1; }
|
|
26040
26119
|
test -f web.conf && test -f proxy-entry.json || { printf 'Proxy configuration missing. Create a full deployment bundle.\n' >&2; exit 1; }
|
|
26041
26120
|
previous_proxy=
|
|
26042
26121
|
while IFS= read -r compose_file; do
|
|
@@ -26054,9 +26133,17 @@ if [ "$partial" = true ]; then
|
|
|
26054
26133
|
fi
|
|
26055
26134
|
fi
|
|
26056
26135
|
mkdir -p "$release"
|
|
26057
|
-
|
|
26136
|
+
# 镜像直接从本次解包目录导入;发布记录只保存运行配置与恢复证据。
|
|
26137
|
+
for entry in "$source_dir"/* "$source_dir"/.[!.]*; do
|
|
26138
|
+
test -e "$entry" || continue
|
|
26139
|
+
test "$entry" != "$source_dir/images" || continue
|
|
26140
|
+
cp -R "$entry" "$release/"
|
|
26141
|
+
done
|
|
26058
26142
|
# Nginx 以非 root 用户运行;只开放不含凭据的代理配置读取权限。
|
|
26059
26143
|
if [ -f "$release/web.conf" ]; then chmod 644 "$release/web.conf"; fi
|
|
26144
|
+
for https_script in https-start.sh renew-https.sh; do
|
|
26145
|
+
if [ -f "$release/$https_script" ]; then chmod 444 "$release/$https_script"; fi
|
|
26146
|
+
done
|
|
26060
26147
|
# 策略不是凭据,发布目录保持700;只读挂载允许镜像中的非root应用读取。
|
|
26061
26148
|
if [ -f "$release/request-access.yml" ]; then chmod 444 "$release/request-access.yml"; fi
|
|
26062
26149
|
: > "$release/compose-files"
|
|
@@ -26070,6 +26157,12 @@ if [ "$partial" = true ]; then
|
|
|
26070
26157
|
fi
|
|
26071
26158
|
step credentials 'Resolving server-managed middleware credentials'
|
|
26072
26159
|
sh "$release/resolve-managed-secrets.sh" "$root" "$release" "$runtime"
|
|
26160
|
+
if [ -f "$release/runtime-env.mjs" ]; then
|
|
26161
|
+
# 上级目录保护宿主秘密;只读文件挂载允许非 root 应用读取,宿主所有者仍可修改配置。
|
|
26162
|
+
chmod 700 "$root" "$release"
|
|
26163
|
+
chmod 644 "$release/.env.runtime" "$release/middleware.env"
|
|
26164
|
+
chmod 444 "$release/runtime-env.mjs"
|
|
26165
|
+
fi
|
|
26073
26166
|
# 在任何可能启动服务(包括增量恢复旧拓扑)之前定稿,失败回滚不删除卷的引擎身份。
|
|
26074
26167
|
sh "$release/resolve-backend-state.sh" "$root" "$release" "$runtime" "$previous" commit
|
|
26075
26168
|
printf '%s\n' "$release/compose-entry.json" >> "$release/compose-files"
|
|
@@ -26107,12 +26200,15 @@ step images 'Loading packaged images (no registry access)'
|
|
|
26107
26200
|
while IFS=' ' read -r archive reference; do
|
|
26108
26201
|
test -n "$archive" || continue
|
|
26109
26202
|
images_changed=true
|
|
26110
|
-
"$runtime" load -i "$
|
|
26203
|
+
"$runtime" load -i "$source_dir/images/$archive"
|
|
26111
26204
|
"$runtime" image inspect "$reference" >/dev/null
|
|
26112
26205
|
architecture=$("$runtime" image inspect --format '{{.Architecture}}' "$reference")
|
|
26113
26206
|
host_arch=$(uname -m)
|
|
26114
26207
|
case "$host_arch:$architecture" in x86_64:amd64|aarch64:arm64) ;; *) printf 'Image architecture %s does not match server %s\n' "$architecture" "$host_arch" >&2; exit 1;; esac
|
|
26115
26208
|
done < "$release/image-list"
|
|
26209
|
+
if [ -f "$release/prepare-https.sh" ]; then
|
|
26210
|
+
sh "$release/prepare-https.sh" "$root" "$release" "$runtime" prepare "$partial"
|
|
26211
|
+
fi
|
|
26116
26212
|
step dependencies 'Checking dependencies of selected services'
|
|
26117
26213
|
if [ "$partial" = true ]; then cat "$release/services" >> "$release/dependencies"; fi
|
|
26118
26214
|
while IFS= read -r dependency; do
|
|
@@ -26124,9 +26220,18 @@ step start 'Updating selected services'
|
|
|
26124
26220
|
changed=true
|
|
26125
26221
|
remove_selected $selected
|
|
26126
26222
|
start_services $selected
|
|
26223
|
+
if [ -f "$release/prepare-https.sh" ] && [ "$partial" = false ]; then
|
|
26224
|
+
step https 'Issuing or installing HTTPS certificate'
|
|
26225
|
+
tls_proxy=$(read_proxy_service "$release")
|
|
26226
|
+
wait_services "$tls_proxy"
|
|
26227
|
+
sh "$release/prepare-https.sh" "$root" "$release" "$runtime" issue "$partial"
|
|
26228
|
+
compose exec -T "$tls_proxy" nginx -t
|
|
26229
|
+
compose exec -T "$tls_proxy" nginx -s reload
|
|
26230
|
+
fi
|
|
26231
|
+
start_renewal $selected
|
|
26127
26232
|
reload_retained_proxy "$release"
|
|
26128
26233
|
step health 'Checking HTTP access'
|
|
26129
|
-
wait_http @@PORT@@ @@HEALTH@@
|
|
26234
|
+
wait_http "$release" @@PORT@@ @@HEALTH@@
|
|
26130
26235
|
step complete 'Deployment passed all checks'
|
|
26131
26236
|
printf '%s\n' "$id" > "$root/current.next"
|
|
26132
26237
|
mv "$root/current.next" "$root/current"
|
|
@@ -26180,7 +26285,7 @@ case "$timeout" in ''|*[!0-9]*|???????????*) invalid_target;; esac
|
|
|
26180
26285
|
test "$timeout" -ge 1 && test "$timeout" -le 2147483647 || invalid_target
|
|
26181
26286
|
case "$remote" in /*) ;; *) invalid_target;; esac
|
|
26182
26287
|
case "$remote" in *[!a-zA-Z0-9/_.\ -]*|*/../*|*/..|/..) invalid_target;; esac
|
|
26183
|
-
case "$bundle" in *.tar) ;; *) invalid_target;; esac
|
|
26288
|
+
case "$bundle" in *.tar|*.tar.gz) ;; *) invalid_target;; esac
|
|
26184
26289
|
case "$bundle" in ''|*[!a-zA-Z0-9_.+-]*) invalid_target;; esac
|
|
26185
26290
|
case "$strict" in true) strict=accept-new;; false) strict=no;; *) invalid_target;; esac
|
|
26186
26291
|
test -f "./$bundle" || { printf 'Bundle missing: %s\n' "$bundle" >&2; exit 1; }
|
|
@@ -26304,7 +26409,7 @@ monitor=''
|
|
|
26304
26409
|
if [ "$upload_code" -ne 0 ]; then printf '[FAIL] Upload failed (exit %s). Check SSH output above.\n' "$upload_code" >&2; exit "$upload_code"; fi
|
|
26305
26410
|
printf '[UPLOAD] 100%% (%s/%s bytes)\n' "$total" "$total"
|
|
26306
26411
|
printf '[3/3] Applying package and checking HTTP access\n'
|
|
26307
|
-
if run_native ssh "$@" -p "$port" "$user@$host" "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote'"; then
|
|
26412
|
+
if run_native ssh "$@" -p "$port" "$user@$host" "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote' && { rm -rf -- '$bundle.contents' && rm -f -- '$bundle' || printf '[WARN] Deployment passed; incoming archive cleanup failed\\n' >&2; }"; then
|
|
26308
26413
|
:
|
|
26309
26414
|
else
|
|
26310
26415
|
code=$?
|
|
@@ -26339,7 +26444,7 @@ if ($windowsRuntime) {
|
|
|
26339
26444
|
Set-Location -LiteralPath $PSScriptRoot
|
|
26340
26445
|
$target = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'deploy-target.json') -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
26341
26446
|
if ($target.host -eq 'SERVER_HOST') { throw 'Set the server host in deploy-target.json.' }
|
|
26342
|
-
if ($target.host -isnot [string] -or $target.host -notmatch '^[a-zA-Z0-9][a-zA-Z0-9.:-]*$' -or $target.username -isnot [string] -or $target.username -notmatch '^[a-zA-Z_][a-zA-Z0-9_-]*$' -or $target.directory -isnot [string] -or $target.directory -notmatch '^/[a-zA-Z0-9/_. -]*$' -or $target.directory -match '(^|/)\.\.(/|$)' -or $target.bundle -isnot [string] -or $target.bundle -notmatch '^[a-zA-Z0-9_.+-]+\.tar
|
|
26447
|
+
if ($target.host -isnot [string] -or $target.host -notmatch '^[a-zA-Z0-9][a-zA-Z0-9.:-]*$' -or $target.username -isnot [string] -or $target.username -notmatch '^[a-zA-Z_][a-zA-Z0-9_-]*$' -or $target.directory -isnot [string] -or $target.directory -notmatch '^/[a-zA-Z0-9/_. -]*$' -or $target.directory -match '(^|/)\.\.(/|$)' -or $target.bundle -isnot [string] -or $target.bundle -notmatch '^[a-zA-Z0-9_.+-]+\.tar(\.gz)?$') { throw 'Invalid deployment target.' }
|
|
26343
26448
|
$port = if ($null -eq $target.port) { 22 } else { $target.port }
|
|
26344
26449
|
$timeout = if ($null -eq $target.connectTimeoutSeconds) { 10 } else { $target.connectTimeoutSeconds }
|
|
26345
26450
|
if ($port -is [string] -or [string]$port -notmatch '^[0-9]+$' -or $port -lt 1 -or $port -gt 65535 -or $timeout -is [string] -or [string]$timeout -notmatch '^[0-9]+$' -or $timeout -lt 1 -or $timeout -gt 2147483647) { throw 'Invalid deployment port or timeout.' }
|
|
@@ -26498,7 +26603,7 @@ try {
|
|
|
26498
26603
|
Invoke-Step '2/3 Upload' 'scp' ($options + @('-P', [string]$port, "./$bundle", "$($scpDestination):$remote/incoming/$bundle"))
|
|
26499
26604
|
Write-Host "[UPLOAD] 100% ($uploadBytes/$uploadBytes bytes)"
|
|
26500
26605
|
$applying = $true
|
|
26501
|
-
Invoke-Step '3/3 Apply and verify HTTP' 'ssh' ($options + @('-p', [string]$port, $destination, "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote'"))
|
|
26606
|
+
Invoke-Step '3/3 Apply and verify HTTP' 'ssh' ($options + @('-p', [string]$port, $destination, "cd '$remote/incoming' && mkdir -p '$bundle.contents' && tar -xf '$bundle' -C '$bundle.contents' && sh '$bundle.contents/apply.sh' '$remote' && { rm -rf -- '$bundle.contents' && rm -f -- '$bundle' || printf '[WARN] Deployment passed; incoming archive cleanup failed\\n' >&2; }"))
|
|
26502
26607
|
} catch {
|
|
26503
26608
|
if ($applying) {
|
|
26504
26609
|
if ($LASTEXITCODE -gt 0 -and $LASTEXITCODE -lt 128) { [Console]::Error.WriteLine("[FAIL] Remote deployment failed (exit $LASTEXITCODE). Inspect $remote/result-<deploymentId>.json (ID in README.txt) and startup diagnostics.") }
|
|
@@ -26667,13 +26772,14 @@ ${removed.error.message}` });
|
|
|
26667
26772
|
async function createDeploymentBundle(options) {
|
|
26668
26773
|
const { directory, artifactDir, artifact, projectName, environment, config: config2, selected } = options;
|
|
26669
26774
|
const output2 = resolve(options.output);
|
|
26670
|
-
if (!/^[\w.+-]+\.tar
|
|
26775
|
+
if (!/^[\w.+-]+\.tar(?:\.gz)?$/.test(basename(output2)))
|
|
26671
26776
|
throw new Error(deployM("deploy_bundleNameInvalid"));
|
|
26672
26777
|
const staging = mkdtempSync(join(directory, ".bundle-"));
|
|
26673
26778
|
const temporaryTar = `${output2}.partial`;
|
|
26674
26779
|
mkdirSync(dirname(output2), { recursive: true, mode: 448 });
|
|
26675
26780
|
chmodSync(staging, 448);
|
|
26676
26781
|
const files = [];
|
|
26782
|
+
const fileHashes = /* @__PURE__ */ new Map();
|
|
26677
26783
|
const put = (file2, content) => {
|
|
26678
26784
|
mkdirSync(dirname(join(staging, file2)), { recursive: true });
|
|
26679
26785
|
writeFileSync(join(staging, file2), content, { mode: 384 });
|
|
@@ -26698,7 +26804,7 @@ async function createDeploymentBundle(options) {
|
|
|
26698
26804
|
service.env_file = name === artifact.primaryService ? [`../${options.deploymentId}/.env.runtime`, `../${options.deploymentId}/middleware.env`] : [`../${options.deploymentId}/middleware-runtime.env`];
|
|
26699
26805
|
}
|
|
26700
26806
|
if (Array.isArray(service.volumes))
|
|
26701
|
-
service.volumes = service.volumes.map((mount) => typeof mount === "string" ? mount.replace(
|
|
26807
|
+
service.volumes = service.volumes.map((mount) => typeof mount === "string" ? mount.replace(/\.\/(web\.conf|request-access\.yml|tls|https-start\.sh|renew-https\.sh|\.env\.runtime|middleware\.env|runtime-env\.mjs):/g, `../${options.deploymentId}/$1:`) : mount);
|
|
26702
26808
|
}
|
|
26703
26809
|
put("compose.json", JSON.stringify({ ...compose, services: Object.fromEntries(selected.map((name) => [name, compose.services[name]])) }));
|
|
26704
26810
|
put("compose-entry.json", JSON.stringify({ ...compose, services: Object.fromEntries(selected.map((name) => [name, { extends: { file: `../${options.deploymentId}/compose.json`, service: name } }])) }));
|
|
@@ -26709,8 +26815,12 @@ async function createDeploymentBundle(options) {
|
|
|
26709
26815
|
throw new Error(deployM("deploy_imageVersionRequired", { params: { image: reference } }));
|
|
26710
26816
|
const built = artifact.images.find((image) => image.imageRef === reference);
|
|
26711
26817
|
const file2 = built?.file ?? `middleware-${imageList.length + 1}.tar`;
|
|
26818
|
+
const destination = join(staging, "images", file2);
|
|
26712
26819
|
if (built) {
|
|
26713
|
-
|
|
26820
|
+
const checksum = await copyArchive(join(artifactDir, file2), destination);
|
|
26821
|
+
if (checksum !== built.sha256)
|
|
26822
|
+
throw new Error(deployM("deploy_artifactInvalid"));
|
|
26823
|
+
fileHashes.set(`images/${file2}`, checksum);
|
|
26714
26824
|
} else {
|
|
26715
26825
|
if (!runtime) {
|
|
26716
26826
|
const candidates = options.runtime === "auto" ? ["podman", "docker"] : [options.runtime];
|
|
@@ -26723,14 +26833,41 @@ async function createDeploymentBundle(options) {
|
|
|
26723
26833
|
if (!runtime)
|
|
26724
26834
|
throw new Error(deployM("deploy_bundleRuntimeRequired"));
|
|
26725
26835
|
}
|
|
26726
|
-
|
|
26836
|
+
let inspected = await runCommand(runtime, ["image", "inspect", "--format", "{{.Id}}", reference]);
|
|
26837
|
+
if (!inspected.success) {
|
|
26727
26838
|
const pulled = await runCommand(runtime, ["pull", reference], { timeoutMs: 6e5 });
|
|
26728
26839
|
if (!pulled.success)
|
|
26729
26840
|
throw new Error(pulled.error.message);
|
|
26841
|
+
inspected = await runCommand(runtime, ["image", "inspect", "--format", "{{.Id}}", reference]);
|
|
26842
|
+
}
|
|
26843
|
+
const identity = inspected.success && !inspected.data.stdoutTruncated ? inspected.data.stdout.trim().replace(/^sha256:/, "") : "";
|
|
26844
|
+
const cacheIdentity = createHash("sha256").update(identity).update("\0").update(reference).digest("hex");
|
|
26845
|
+
const cache = options.cacheDirectory && /^[a-f0-9]{64}$/.test(identity) ? join(options.cacheDirectory, `${runtime}-${cacheIdentity}.tar`) : void 0;
|
|
26846
|
+
let checksum;
|
|
26847
|
+
if (cache && existsSync(cache) && existsSync(`${cache}.sha256`) && lstatSync(cache).isFile() && !lstatSync(cache).isSymbolicLink()) {
|
|
26848
|
+
const copied = await copyArchive(cache, destination);
|
|
26849
|
+
if (copied === readFileSync(`${cache}.sha256`, "utf8").trim())
|
|
26850
|
+
checksum = copied;
|
|
26851
|
+
}
|
|
26852
|
+
if (!checksum) {
|
|
26853
|
+
const saved = await runCommand(runtime, ["save", ...runtime === "podman" ? ["--format", "docker-archive"] : [], "-o", destination, reference], { timeoutMs: 6e5 });
|
|
26854
|
+
if (!saved.success)
|
|
26855
|
+
throw new Error(saved.error.message);
|
|
26856
|
+
checksum = await artifactChecksum(destination);
|
|
26857
|
+
if (cache) {
|
|
26858
|
+
mkdirSync(dirname(cache), { recursive: true, mode: 448 });
|
|
26859
|
+
const temporary = `${cache}.${basename(staging)}`;
|
|
26860
|
+
try {
|
|
26861
|
+
copyFileSync(destination, temporary);
|
|
26862
|
+
renameSync(temporary, cache);
|
|
26863
|
+
writeFileSync(`${cache}.sha256`, `${checksum}
|
|
26864
|
+
`, { mode: 384 });
|
|
26865
|
+
} finally {
|
|
26866
|
+
rmSync(temporary, { force: true });
|
|
26867
|
+
}
|
|
26868
|
+
}
|
|
26730
26869
|
}
|
|
26731
|
-
|
|
26732
|
-
if (!saved.success)
|
|
26733
|
-
throw new Error(saved.error.message);
|
|
26870
|
+
fileHashes.set(`images/${file2}`, checksum);
|
|
26734
26871
|
}
|
|
26735
26872
|
files.push(`images/${file2}`);
|
|
26736
26873
|
imageList.push(`${file2} ${reference}`);
|
|
@@ -26739,6 +26876,8 @@ async function createDeploymentBundle(options) {
|
|
|
26739
26876
|
if (selected.includes(artifact.primaryService) && existsSync(join(directory, "request-access.yml")))
|
|
26740
26877
|
put("request-access.yml", readFileSync(join(directory, "request-access.yml"), "utf8"));
|
|
26741
26878
|
put("middleware.env", options.middlewareEnv);
|
|
26879
|
+
if (selected.includes(artifact.primaryService) && !compose.services[artifact.primaryService]?.env_file)
|
|
26880
|
+
put("runtime-env.mjs", readFileSync(new URL("./deploy-runtime-env.mjs", import.meta.url), "utf8"));
|
|
26742
26881
|
const middlewareRuntime = join(directory, "middleware-runtime.env");
|
|
26743
26882
|
const nativeEnvironment = existsSync(middlewareRuntime) ? readFileSync(middlewareRuntime, "utf8") : "";
|
|
26744
26883
|
put("middleware-runtime.env", nativeEnvironment);
|
|
@@ -26746,18 +26885,17 @@ async function createDeploymentBundle(options) {
|
|
|
26746
26885
|
put(file2, content);
|
|
26747
26886
|
if (options.hasWebConfig)
|
|
26748
26887
|
put("web.conf", readFileSync(join(directory, "web.conf"), "utf8"));
|
|
26888
|
+
for (const [file2, content] of Object.entries(options.httpsAssets ?? {}))
|
|
26889
|
+
put(file2, content);
|
|
26749
26890
|
put("manifest.json", `${JSON.stringify({ ...artifact, images: artifact.images.filter((image) => references.has(image.imageRef)), deploymentId: options.deploymentId, selectedServices: selected, partial: options.partial }, null, 2)}
|
|
26750
26891
|
`);
|
|
26751
26892
|
if (options.hasWebConfig) {
|
|
26752
26893
|
put("proxy-entry.json", JSON.stringify({ services: { [options.proxyService ?? "web"]: { volumes: [`../${options.deploymentId}/web.conf:/etc/nginx/conf.d/default.conf:ro`] } } }));
|
|
26753
26894
|
}
|
|
26754
|
-
put("proxy-service", `${options.hasWebConfig ? options.proxyService ?? "web" : ""}
|
|
26755
|
-
`);
|
|
26756
26895
|
put("storage-routing", `${options.storageRouting ?? ""}
|
|
26757
26896
|
`);
|
|
26758
|
-
|
|
26759
|
-
`
|
|
26760
|
-
put("health-port", `${config2.expose.hostPort}
|
|
26897
|
+
const httpsFingerprint = config2.expose.https ? createHash("sha256").update(JSON.stringify({ ...config2.expose.https, certificateFile: void 0, privateKeyFile: void 0, certificate: options.httpsAssets?.["tls/fullchain.pem"] })).digest("hex") : "";
|
|
26898
|
+
put("layout", `${config2.expose.hostPort}:${options.containerPort}:${options.hasWebConfig ? options.proxyService === "gateway" ? "gateway" : "true" : "false"}:${artifact.primaryService}:${options.hasWebConfig ? await artifactChecksum(join(directory, "web.conf")) : ""}${httpsFingerprint ? `:${httpsFingerprint}` : ""}
|
|
26761
26899
|
`);
|
|
26762
26900
|
put("web-base-path", `${artifact.webBasePath ?? ""}
|
|
26763
26901
|
`);
|
|
@@ -26775,15 +26913,15 @@ async function createDeploymentBundle(options) {
|
|
|
26775
26913
|
const selection = deployM("deploy_bundleSelection", { params: { mode, services: selected.join(", ") } });
|
|
26776
26914
|
const instructions = `${selection}
|
|
26777
26915
|
${deployM("deploy_bundleInstructions", { params: instructionParams })}`;
|
|
26778
|
-
|
|
26916
|
+
const readme = `${deployM("deploy_bundleMetadata", { params: { version: artifact.appVersion, buildId: artifact.buildId, deploymentId: options.deploymentId, compose: "compose.json" } })}
|
|
26779
26917
|
${instructions}
|
|
26780
|
-
|
|
26918
|
+
`;
|
|
26781
26919
|
const checksums = [];
|
|
26782
26920
|
for (const file2 of files)
|
|
26783
|
-
checksums.push(`${await artifactChecksum(join(staging, file2))} ${file2}`);
|
|
26921
|
+
checksums.push(`${fileHashes.get(file2) ?? await artifactChecksum(join(staging, file2))} ${file2}`);
|
|
26784
26922
|
writeFileSync(join(staging, "SHA256SUMS"), `${checksums.join("\n")}
|
|
26785
26923
|
`);
|
|
26786
|
-
const tar = await runCommand("tar", ["-cf", temporaryTar, "-C", staging, "."]);
|
|
26924
|
+
const tar = await runCommand("tar", [output2.endsWith(".gz") ? "-czf" : "-cf", temporaryTar, "-C", staging, "."]);
|
|
26787
26925
|
if (!tar.success)
|
|
26788
26926
|
throw new Error(tar.error.message);
|
|
26789
26927
|
chmodSync(temporaryTar, 384);
|
|
@@ -26822,7 +26960,7 @@ ${instructions}
|
|
|
26822
26960
|
};
|
|
26823
26961
|
writeFileSync(join(scripts, "deploy-target.json"), `${JSON.stringify({ ...target, _comments: comments }, null, 2)}
|
|
26824
26962
|
`);
|
|
26825
|
-
writeFileSync(join(scripts, "README.txt"),
|
|
26963
|
+
writeFileSync(join(scripts, "README.txt"), readme);
|
|
26826
26964
|
for (const file2 of [`docker-compose-${artifact.appVersion}.yml`, ".env.runtime", "middleware-runtime.env", "request-access.yml", ...options.hasWebConfig ? ["web.conf"] : []])
|
|
26827
26965
|
rmSync(join(directory, file2), { force: true });
|
|
26828
26966
|
return { bundlePath: output2, instructions: `${selection}
|
|
@@ -26833,6 +26971,178 @@ ${scripts}`, startName: "apply.sh" };
|
|
|
26833
26971
|
rmSync(temporaryTar, { force: true });
|
|
26834
26972
|
}
|
|
26835
26973
|
}
|
|
26974
|
+
async function copyArchive(source, destination) {
|
|
26975
|
+
const hash2 = createHash("sha256");
|
|
26976
|
+
const digest = new Transform({
|
|
26977
|
+
transform(chunk, _encoding, callback) {
|
|
26978
|
+
hash2.update(chunk);
|
|
26979
|
+
callback(null, chunk);
|
|
26980
|
+
}
|
|
26981
|
+
});
|
|
26982
|
+
await pipeline(createReadStream(source), digest, createWriteStream(destination, { mode: 384 }));
|
|
26983
|
+
return hash2.digest("hex");
|
|
26984
|
+
}
|
|
26985
|
+
var CERTBOT_IMAGE = "docker.io/certbot/certbot:v5.9.0";
|
|
26986
|
+
function createHttpsAssets(config2, projectDirectory) {
|
|
26987
|
+
const assets = {
|
|
26988
|
+
https: `${config2.domain} ${config2.port} ${config2.type}
|
|
26989
|
+
`
|
|
26990
|
+
};
|
|
26991
|
+
if (config2.type === "files") {
|
|
26992
|
+
try {
|
|
26993
|
+
const readPem = (file2) => {
|
|
26994
|
+
const path = resolve(projectDirectory, file2);
|
|
26995
|
+
if (!statSync(path).isFile() || statSync(path).size > 1048576)
|
|
26996
|
+
throw new Error("Invalid PEM file");
|
|
26997
|
+
return readFileSync(path, "utf8");
|
|
26998
|
+
};
|
|
26999
|
+
const certificate = readPem(config2.certificateFile);
|
|
27000
|
+
const key = readPem(config2.privateKeyFile);
|
|
27001
|
+
const parsed = new X509Certificate(certificate);
|
|
27002
|
+
const publicKey = createPublicKey(createPrivateKey(key)).export({ type: "spki", format: "der" });
|
|
27003
|
+
const now = Date.now();
|
|
27004
|
+
if (!publicKey.equals(parsed.publicKey.export({ type: "spki", format: "der" })) || now < Date.parse(parsed.validFrom) || now >= Date.parse(parsed.validTo) || !(isIP(config2.domain) ? parsed.checkIP(config2.domain) : parsed.checkHost(config2.domain))) {
|
|
27005
|
+
throw new Error("Certificate validation failed");
|
|
27006
|
+
}
|
|
27007
|
+
assets["tls/fullchain.pem"] = certificate;
|
|
27008
|
+
assets["tls/privkey.pem"] = key;
|
|
27009
|
+
} catch {
|
|
27010
|
+
throw new Error(deployM("deploy_httpsCertificateInvalid"));
|
|
27011
|
+
}
|
|
27012
|
+
} else {
|
|
27013
|
+
assets["renew-https.sh"] = createRenewScript(config2);
|
|
27014
|
+
}
|
|
27015
|
+
assets["prepare-https.sh"] = createPrepareScript(config2);
|
|
27016
|
+
assets["https-start.sh"] = String.raw`#!/bin/sh
|
|
27017
|
+
set -eu
|
|
27018
|
+
# 目录挂载允许原子替换证书;只有新证书通过 nginx -t 才重载。
|
|
27019
|
+
watch_certificate() {
|
|
27020
|
+
previous=$(sha256sum /etc/nginx/tls/fullchain.pem)
|
|
27021
|
+
while sleep 60; do
|
|
27022
|
+
current=$(sha256sum /etc/nginx/tls/fullchain.pem) || continue
|
|
27023
|
+
if [ "$previous" != "$current" ]; then
|
|
27024
|
+
if nginx -t && nginx -s reload; then previous=$current; else printf '[ERROR] HTTPS certificate reload failed\n' >&2; fi
|
|
27025
|
+
fi
|
|
27026
|
+
done
|
|
27027
|
+
}
|
|
27028
|
+
watch_certificate &
|
|
27029
|
+
exec /docker-entrypoint.sh nginx -g 'daemon off;'
|
|
27030
|
+
`;
|
|
27031
|
+
return assets;
|
|
27032
|
+
}
|
|
27033
|
+
function renderHttpsProxy(source, config2, httpPort) {
|
|
27034
|
+
const publicPort = config2.port === 443 ? "" : `:${config2.port}`;
|
|
27035
|
+
const challenge = " location ^~ /.well-known/acme-challenge/ { root /var/www/certbot; }\n";
|
|
27036
|
+
const tls = source.replace(`listen ${httpPort};`, `listen ${httpPort === 8443 ? 8444 : 8443} ssl;
|
|
27037
|
+
ssl_certificate /etc/nginx/tls/fullchain.pem;
|
|
27038
|
+
ssl_certificate_key /etc/nginx/tls/privkey.pem;`).replace("server_name _;", `server_name ${config2.domain};`).replaceAll("proxy_set_header X-Forwarded-Proto $scheme;", "proxy_set_header X-Forwarded-Proto https;");
|
|
27039
|
+
const http = config2.redirectHttp ? `server {
|
|
27040
|
+
listen ${httpPort};
|
|
27041
|
+
server_name ${config2.domain};
|
|
27042
|
+
${challenge} location ~ ^/_hai/(web-health|gateway-health)$ { access_log off; return 200 'ok'; }
|
|
27043
|
+
location / { return 308 https://${config2.domain}${publicPort}$request_uri; }
|
|
27044
|
+
}
|
|
27045
|
+
` : source.slice(source.indexOf("server {")).replace("server_name _;", `server_name ${config2.domain};
|
|
27046
|
+
${challenge}`);
|
|
27047
|
+
return `${tls}
|
|
27048
|
+
${http}`;
|
|
27049
|
+
}
|
|
27050
|
+
var publishCertificate = String.raw`publish_certificate() {
|
|
27051
|
+
certificate="/etc/letsencrypt/live/$domain"
|
|
27052
|
+
test -s "$certificate/fullchain.pem" && test -s "$certificate/privkey.pem"
|
|
27053
|
+
cp "$certificate/fullchain.pem" /output/fullchain.pem.next
|
|
27054
|
+
cp "$certificate/privkey.pem" /output/privkey.pem.next
|
|
27055
|
+
chmod 444 /output/fullchain.pem.next /output/privkey.pem.next
|
|
27056
|
+
mv /output/privkey.pem.next /output/privkey.pem
|
|
27057
|
+
mv /output/fullchain.pem.next /output/fullchain.pem
|
|
27058
|
+
}
|
|
27059
|
+
`;
|
|
27060
|
+
function createRenewScript(config2) {
|
|
27061
|
+
return `#!/bin/sh
|
|
27062
|
+
set -eu
|
|
27063
|
+
domain=${shellArgument(config2.domain)}
|
|
27064
|
+
${publishCertificate}${String.raw`
|
|
27065
|
+
trap 'exit 0' TERM INT
|
|
27066
|
+
# Certbot 自己判断到期时间;同一官方客户端同时负责申请与续期,不实现 ACME 协议。
|
|
27067
|
+
while :; do
|
|
27068
|
+
if certbot renew --cert-name "$domain" --non-interactive; then
|
|
27069
|
+
if [ -s "/etc/letsencrypt/live/$domain/fullchain.pem" ]; then
|
|
27070
|
+
current=$(sha256sum "/etc/letsencrypt/live/$domain/fullchain.pem" | awk '{print $1}')
|
|
27071
|
+
installed=$(sha256sum /output/fullchain.pem | awk '{print $1}')
|
|
27072
|
+
if [ "$current" != "$installed" ]; then publish_certificate; fi
|
|
27073
|
+
fi
|
|
27074
|
+
else
|
|
27075
|
+
printf '[ERROR] Certbot renewal failed; the existing certificate remains installed\n' >&2
|
|
27076
|
+
fi
|
|
27077
|
+
sleep 43200 & wait $! || exit 0
|
|
27078
|
+
done
|
|
27079
|
+
`}`;
|
|
27080
|
+
}
|
|
27081
|
+
function createPrepareScript(config2) {
|
|
27082
|
+
const header = `#!/bin/sh
|
|
27083
|
+
set -eu
|
|
27084
|
+
umask 077
|
|
27085
|
+
root=$1
|
|
27086
|
+
release=$2
|
|
27087
|
+
runtime=$3
|
|
27088
|
+
mode=$4
|
|
27089
|
+
domain=${shellArgument(config2.domain)}
|
|
27090
|
+
`;
|
|
27091
|
+
if (config2.type === "files") {
|
|
27092
|
+
return header + String.raw`
|
|
27093
|
+
test "$mode" != prepare || {
|
|
27094
|
+
# 宿主 root/release 保持私密;容器仅可读取独立挂载的证书目录。
|
|
27095
|
+
chmod 700 "$root" "$release"
|
|
27096
|
+
chmod 755 "$release/tls"
|
|
27097
|
+
chmod 444 "$release/tls/fullchain.pem" "$release/tls/privkey.pem"
|
|
27098
|
+
}
|
|
27099
|
+
`;
|
|
27100
|
+
}
|
|
27101
|
+
const python = [
|
|
27102
|
+
"from cryptography import x509",
|
|
27103
|
+
"from cryptography.x509.oid import NameOID",
|
|
27104
|
+
"from cryptography.hazmat.primitives import hashes, serialization",
|
|
27105
|
+
"from cryptography.hazmat.primitives.asymmetric import ec",
|
|
27106
|
+
"from datetime import datetime, timedelta, timezone",
|
|
27107
|
+
"from pathlib import Path",
|
|
27108
|
+
"import sys",
|
|
27109
|
+
"name=sys.argv[1]",
|
|
27110
|
+
"key=ec.generate_private_key(ec.SECP256R1())",
|
|
27111
|
+
"subject=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,name)])",
|
|
27112
|
+
"now=datetime.now(timezone.utc)",
|
|
27113
|
+
"cert=x509.CertificateBuilder().subject_name(subject).issuer_name(subject).public_key(key.public_key()).serial_number(x509.random_serial_number()).not_valid_before(now-timedelta(minutes=1)).not_valid_after(now+timedelta(days=1)).add_extension(x509.SubjectAlternativeName([x509.DNSName(name)]),False).sign(key,hashes.SHA256())",
|
|
27114
|
+
'Path("/output/privkey.pem").write_bytes(key.private_bytes(serialization.Encoding.PEM,serialization.PrivateFormat.PKCS8,serialization.NoEncryption()))',
|
|
27115
|
+
'Path("/output/fullchain.pem").write_bytes(cert.public_bytes(serialization.Encoding.PEM))'
|
|
27116
|
+
].join("\n");
|
|
27117
|
+
const publish = `set -eu
|
|
27118
|
+
domain=${shellArgument(config2.domain)}
|
|
27119
|
+
${publishCertificate}
|
|
27120
|
+
publish_certificate
|
|
27121
|
+
`;
|
|
27122
|
+
return `${header}image=${shellArgument(CERTBOT_IMAGE)}
|
|
27123
|
+
email=${shellArgument(config2.email)}
|
|
27124
|
+
bootstrap=${shellArgument(python)}
|
|
27125
|
+
${String.raw`
|
|
27126
|
+
state="$root/https"
|
|
27127
|
+
certificates="$state/certificates/$domain"
|
|
27128
|
+
mkdir -p "$state/acme" "$state/webroot" "$certificates"
|
|
27129
|
+
chmod 700 "$root" "$state" "$state/acme"
|
|
27130
|
+
chmod 755 "$state/webroot" "$certificates"
|
|
27131
|
+
if [ "$mode" = prepare ]; then
|
|
27132
|
+
if [ ! -s "$certificates/fullchain.pem" ] || [ ! -s "$certificates/privkey.pem" ]; then
|
|
27133
|
+
if [ "$5" = true ]; then printf 'HTTPS certificate state missing; create a full deployment.\n' >&2; exit 1; fi
|
|
27134
|
+
# 临时证书只用于启动挑战入口;签发失败不得提交 current。
|
|
27135
|
+
"$runtime" run --rm --pull never --entrypoint python -v "$certificates:/output" "$image" -c "$bootstrap" "$domain"
|
|
27136
|
+
chmod 444 "$certificates/fullchain.pem" "$certificates/privkey.pem"
|
|
27137
|
+
fi
|
|
27138
|
+
elif [ "$mode" = issue ]; then
|
|
27139
|
+
"$runtime" run --rm --pull never -v "$state/acme:/etc/letsencrypt" -v "$state/webroot:/var/www/certbot" "$image" certonly --webroot --webroot-path /var/www/certbot --cert-name "$domain" --domain "$domain" --email "$email" --agree-tos --non-interactive --keep-until-expiring
|
|
27140
|
+
"$runtime" run --rm --pull never --entrypoint sh -v "$state/acme:/etc/letsencrypt:ro" -v "$certificates:/output" "$image" -c `}${shellArgument(publish)}
|
|
27141
|
+
fi
|
|
27142
|
+
`;
|
|
27143
|
+
}
|
|
27144
|
+
|
|
27145
|
+
// ../deploy/src/container/deploy-container-compose.ts
|
|
26836
27146
|
var IMAGES = {
|
|
26837
27147
|
postgres: "docker.io/library/postgres:16.15-alpine",
|
|
26838
27148
|
mysql: "docker.io/library/mysql:8.4.11",
|
|
@@ -27097,12 +27407,17 @@ function planSidecars(_appDir, projectName, scan, provisionEnv, options, s3Engin
|
|
|
27097
27407
|
const requirements = scan.middlewares?.filter((item) => item.service === "email" && item.backend === "smtp" && ["default", void 0].includes(options?.serviceModes?.[item.envPrefix] ?? options?.serviceModes?.email));
|
|
27098
27408
|
if (requirements?.length) {
|
|
27099
27409
|
for (const requirement of requirements)
|
|
27100
|
-
plan.env[requirement.envPrefix] = JSON.stringify({ name: requirement.config.name ?? "email", type: "smtp", host: "email", port: 1025, secure: false, from: "noreply@example.com" });
|
|
27410
|
+
plan.env[requirement.envPrefix] = JSON.stringify({ name: requirement.config.name ?? "email", type: "smtp", host: "email", port: 1025, secure: false, from: requirement.config.from || "noreply@example.com" });
|
|
27101
27411
|
} else if (!scan.middlewares) {
|
|
27102
27412
|
plan.env.HAI_REACH_PROVIDERS_0 = JSON.stringify({ name: "email", type: "smtp", host: "email", port: 1025, secure: false, from: "noreply@example.com" });
|
|
27103
27413
|
}
|
|
27104
27414
|
}
|
|
27105
27415
|
}
|
|
27416
|
+
for (const requirement of scan.middlewares ?? []) {
|
|
27417
|
+
const supplied = plan.env[requirement.envPrefix];
|
|
27418
|
+
if (supplied)
|
|
27419
|
+
plan.env[requirement.envPrefix] = JSON.stringify(mergeManagedMiddlewareConfig(requirement, JSON.parse(supplied)));
|
|
27420
|
+
}
|
|
27106
27421
|
return plan;
|
|
27107
27422
|
}
|
|
27108
27423
|
function buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort, options, appEnv = {}, s3Engine = "rustfs") {
|
|
@@ -27121,6 +27436,7 @@ function buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort,
|
|
|
27121
27436
|
function generateComposeFile(options) {
|
|
27122
27437
|
const { projectName, imageRef, hostPort, containerPort, sidecar } = options;
|
|
27123
27438
|
const primaryService = options.primaryService ?? "serv";
|
|
27439
|
+
const nodeRuntime = options.nodeRuntime ?? primaryService === "serv";
|
|
27124
27440
|
const app = {
|
|
27125
27441
|
image: imageRef,
|
|
27126
27442
|
labels: { "org.opencontainers.image.version": options.appVersion },
|
|
@@ -27132,11 +27448,28 @@ function generateComposeFile(options) {
|
|
|
27132
27448
|
ports: [`${hostPort}:${containerPort}`],
|
|
27133
27449
|
networks: ["hai-app"]
|
|
27134
27450
|
};
|
|
27451
|
+
if (primaryService === "serv") {
|
|
27452
|
+
app.healthcheck = {
|
|
27453
|
+
test: ["CMD", "node", "-e", "(async () => { try { const { core } = await import('@h-ai/core'); const { ServConfigSchema } = await import('@h-ai/serv'); core.init({ configDir: './config' }); const { server, http } = ServConfigSchema.parse(core.config.getOrThrow('serv')); if (http.health === false) process.exit(0); const response = await fetch('http://127.0.0.1:' + server.port + http.health.path, { signal: AbortSignal.timeout(4000) }); process.exit(response.ok ? 0 : 1); } catch { process.exit(1); } })()"],
|
|
27454
|
+
interval: "30s",
|
|
27455
|
+
timeout: "5s",
|
|
27456
|
+
start_period: "15s",
|
|
27457
|
+
retries: 3
|
|
27458
|
+
};
|
|
27459
|
+
}
|
|
27135
27460
|
const appVolumes = [...sidecar.appVolumes];
|
|
27461
|
+
const appEnvironment = {};
|
|
27462
|
+
if (nodeRuntime) {
|
|
27463
|
+
delete app.env_file;
|
|
27464
|
+
appVolumes.push("./.env.runtime:/run/hai/.env.runtime:ro", "./middleware.env:/run/hai/middleware.env:ro", "./runtime-env.mjs:/run/hai/runtime-env.mjs:ro");
|
|
27465
|
+
appEnvironment.NODE_OPTIONS = `--import /run/hai/runtime-env.mjs${options.nodeOptions ? ` ${options.nodeOptions}` : ""}`.replaceAll("$", () => "$$");
|
|
27466
|
+
}
|
|
27136
27467
|
if (options.requestAccessPolicy)
|
|
27137
27468
|
appVolumes.push("./request-access.yml:/run/hai/request-access.yml:ro");
|
|
27138
27469
|
if (options.web || options.gateway)
|
|
27139
|
-
|
|
27470
|
+
appEnvironment.HAI_DEPLOY_PRIVATE_PROXY = "1";
|
|
27471
|
+
if (Object.keys(appEnvironment).length)
|
|
27472
|
+
app.environment = appEnvironment;
|
|
27140
27473
|
if (options.mountWebConfig)
|
|
27141
27474
|
appVolumes.push("./web.conf:/etc/nginx/conf.d/default.conf:ro");
|
|
27142
27475
|
if (appVolumes.length)
|
|
@@ -27169,10 +27502,38 @@ function generateComposeFile(options) {
|
|
|
27169
27502
|
ports: [`${hostPort}:8080`],
|
|
27170
27503
|
healthcheck: { test: ["CMD", "wget", "-Y", "off", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/_hai/gateway-health"], interval: "30s", timeout: "5s", retries: 3 },
|
|
27171
27504
|
volumes: ["./web.conf:/etc/nginx/conf.d/default.conf:ro"],
|
|
27172
|
-
depends_on: [primaryService, "storage"],
|
|
27505
|
+
depends_on: [primaryService, ...sidecar.services.includes("storage") ? ["storage"] : []],
|
|
27173
27506
|
networks: ["hai-app"]
|
|
27174
27507
|
};
|
|
27175
27508
|
}
|
|
27509
|
+
if (options.https) {
|
|
27510
|
+
const proxy = services[options.gateway ? "gateway" : options.web ? "web" : primaryService];
|
|
27511
|
+
const https = options.https;
|
|
27512
|
+
const httpPort = options.gateway ? 8080 : options.web?.containerPort ?? containerPort;
|
|
27513
|
+
proxy.ports = [...proxy.ports, `${https.port}:${httpPort === 8443 ? 8444 : 8443}`];
|
|
27514
|
+
const certificates = https.type === "files" ? "./tls" : `../../https/certificates/${https.domain}`;
|
|
27515
|
+
proxy.volumes = [
|
|
27516
|
+
...proxy.volumes,
|
|
27517
|
+
`${certificates}:/etc/nginx/tls:ro`,
|
|
27518
|
+
"./https-start.sh:/run/hai/https-start.sh:ro",
|
|
27519
|
+
...https.type === "letsencrypt" ? ["../../https/webroot:/var/www/certbot:ro"] : []
|
|
27520
|
+
];
|
|
27521
|
+
proxy.entrypoint = ["sh", "/run/hai/https-start.sh"];
|
|
27522
|
+
if (https.type === "letsencrypt") {
|
|
27523
|
+
services.certbot = {
|
|
27524
|
+
image: CERTBOT_IMAGE,
|
|
27525
|
+
restart: "unless-stopped",
|
|
27526
|
+
entrypoint: ["sh", "/run/hai/renew-https.sh"],
|
|
27527
|
+
volumes: [
|
|
27528
|
+
"../../https/acme:/etc/letsencrypt",
|
|
27529
|
+
"../../https/webroot:/var/www/certbot",
|
|
27530
|
+
`../../https/certificates/${https.domain}:/output`,
|
|
27531
|
+
"./renew-https.sh:/run/hai/renew-https.sh:ro"
|
|
27532
|
+
],
|
|
27533
|
+
networks: ["hai-app"]
|
|
27534
|
+
};
|
|
27535
|
+
}
|
|
27536
|
+
}
|
|
27176
27537
|
const compose = {
|
|
27177
27538
|
"x-hai-version": options.appVersion,
|
|
27178
27539
|
services,
|
|
@@ -27191,11 +27552,21 @@ function generateWebProxyConfig(port, apiPrefix, options = {}) {
|
|
|
27191
27552
|
const webPort = options.webPort ?? 8080;
|
|
27192
27553
|
const webBasePath = options.webBasePath ?? "";
|
|
27193
27554
|
const apiPath = apiPrefix.replace(/\/+$/, "");
|
|
27194
|
-
|
|
27555
|
+
const backendPaths = [...new Set(options.backendPaths ?? ["/health"])];
|
|
27556
|
+
if (!/^\/[\w/-]+$/.test(apiPrefix) || ![port, webPort].every((value) => Number.isInteger(value) && value >= 1 && value <= 65535) || backendPaths.some((path) => !/^\/[\w./-]*$/.test(path) || path.startsWith("//") || path.split("/").some((part) => part === "." || part === "..") || path === `${webBasePath}/runtime-config.js` || path === "/_hai/web-health") || webBasePath && (backendPaths.includes(webBasePath) || webBasePath === apiPath || webBasePath.startsWith(`${apiPath}/`))) {
|
|
27195
27557
|
throw new Error("Invalid web proxy target");
|
|
27196
27558
|
}
|
|
27197
27559
|
const webLocation = renderWebLocation(webBasePath);
|
|
27198
|
-
const storageLocation = options.storageBucket ? renderStorageLocation(options.storageBucket, [apiPath, ...options.storageBasePath ? [`${webBasePath}/_app`, `${webBasePath}/assets`, `${webBasePath}/runtime-config.js`] : [webBasePath]], options.storageBasePath) : "";
|
|
27560
|
+
const storageLocation = options.storageBucket ? renderStorageLocation(options.storageBucket, [apiPath, ...backendPaths, ...options.storageBasePath ? [`${webBasePath}/_app`, `${webBasePath}/assets`, `${webBasePath}/runtime-config.js`] : [webBasePath]], options.storageBasePath) : "";
|
|
27561
|
+
const backendLocations = backendPaths.map((path) => ` location = ${path} {
|
|
27562
|
+
proxy_pass http://serv:${port};
|
|
27563
|
+
proxy_http_version 1.1;
|
|
27564
|
+
proxy_set_header Host $http_host;
|
|
27565
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
27566
|
+
proxy_set_header X-Forwarded-For $remote_addr;
|
|
27567
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
27568
|
+
}
|
|
27569
|
+
`).join("");
|
|
27199
27570
|
return `${options.storageBucket ? renderStorageUpstream(options.storageBucket, options.storageBasePath) : ""}server {
|
|
27200
27571
|
listen ${webPort};
|
|
27201
27572
|
server_name _;
|
|
@@ -27210,13 +27581,7 @@ ${storageLocation}${webLocation} location = ${webBasePath}/runtime-config.js {
|
|
|
27210
27581
|
access_log off;
|
|
27211
27582
|
return 200 'ok';
|
|
27212
27583
|
}
|
|
27213
|
-
location
|
|
27214
|
-
proxy_pass http://serv:${port};
|
|
27215
|
-
proxy_set_header Host $http_host;
|
|
27216
|
-
proxy_set_header X-Forwarded-For $remote_addr;
|
|
27217
|
-
proxy_set_header X-Real-IP $remote_addr;
|
|
27218
|
-
}
|
|
27219
|
-
location ${apiPrefix.replace(/\/$/, "")}/ {
|
|
27584
|
+
${backendLocations} location ${apiPrefix.replace(/\/$/, "")}/ {
|
|
27220
27585
|
client_max_body_size ${bodyLimits.encryptedPayloadBytes};
|
|
27221
27586
|
proxy_pass http://serv:${port};
|
|
27222
27587
|
proxy_http_version 1.1;
|
|
@@ -27282,11 +27647,11 @@ function generateApplicationProxyConfig(options) {
|
|
|
27282
27647
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
27283
27648
|
throw new Error("Invalid application proxy target");
|
|
27284
27649
|
const bodyLimits = resolveTransportBodyLimits(maxPlaintextBytes);
|
|
27285
|
-
return `${renderStorageUpstream(bucket, options.storageBasePath)}server {
|
|
27650
|
+
return `${bucket ? renderStorageUpstream(bucket, options.storageBasePath) : ""}server {
|
|
27286
27651
|
listen 8080;
|
|
27287
27652
|
server_name _;
|
|
27288
27653
|
${renderTrustedEdgeProxies(options.trustedEdgeProxies)} large_client_header_buffers 4 32k;
|
|
27289
|
-
${renderStorageLocation(bucket, reservedPaths, options.storageBasePath)} location = /_hai/gateway-health {
|
|
27654
|
+
${bucket ? renderStorageLocation(bucket, reservedPaths, options.storageBasePath) : ""} location = /_hai/gateway-health {
|
|
27290
27655
|
access_log off;
|
|
27291
27656
|
return 200 'ok';
|
|
27292
27657
|
}
|
|
@@ -27394,9 +27759,22 @@ function prepareRequestAccess(options) {
|
|
|
27394
27759
|
}
|
|
27395
27760
|
}
|
|
27396
27761
|
var HEALTH_MAX_ATTEMPTS = 40;
|
|
27762
|
+
function servicePath(value, fallback) {
|
|
27763
|
+
const path = value ?? fallback;
|
|
27764
|
+
if (typeof path !== "string" || !/^\/[\w./-]*$/.test(path) || path.startsWith("//") || path.split("/").some((part) => part === "." || part === ".."))
|
|
27765
|
+
throw new Error("Invalid service endpoint path");
|
|
27766
|
+
return path;
|
|
27767
|
+
}
|
|
27768
|
+
function endpointSettings(value) {
|
|
27769
|
+
if (value === void 0)
|
|
27770
|
+
return {};
|
|
27771
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
27772
|
+
throw new Error("Invalid service endpoint configuration");
|
|
27773
|
+
return value;
|
|
27774
|
+
}
|
|
27397
27775
|
var ServerResultSchema = external_exports.object({
|
|
27398
27776
|
deploymentId: external_exports.string(),
|
|
27399
|
-
stage: external_exports.enum(["preflight", "credentials", "recover", "images", "dependencies", "start", "health", "complete"]),
|
|
27777
|
+
stage: external_exports.enum(["preflight", "credentials", "recover", "images", "dependencies", "start", "https", "health", "complete"]),
|
|
27400
27778
|
exitCode: external_exports.number().int().min(0).max(255),
|
|
27401
27779
|
durationSeconds: external_exports.number().int().nonnegative(),
|
|
27402
27780
|
recoveryStatus: external_exports.enum(["not-needed", "passed", "failed", "stopped"]).optional()
|
|
@@ -27408,7 +27786,7 @@ function makeDeploymentId() {
|
|
|
27408
27786
|
return `${stamp}-${randomBytes(3).toString("hex")}`;
|
|
27409
27787
|
}
|
|
27410
27788
|
function sleep(ms) {
|
|
27411
|
-
return new Promise((
|
|
27789
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
27412
27790
|
}
|
|
27413
27791
|
function createDockerSshProvider(config2, container) {
|
|
27414
27792
|
const ssh = config2.ssh ? createSshExecutor(config2.ssh) : void 0;
|
|
@@ -27432,13 +27810,28 @@ function createDockerSshProvider(config2, container) {
|
|
|
27432
27810
|
deployM("deploy_remoteRuntimeMissing", { params: { runtime: candidates.join(" / ") } })
|
|
27433
27811
|
);
|
|
27434
27812
|
}
|
|
27435
|
-
async function healthCheck(url2) {
|
|
27813
|
+
async function healthCheck(url2, certificate) {
|
|
27436
27814
|
for (let attempt = 0; attempt < HEALTH_MAX_ATTEMPTS; attempt++) {
|
|
27437
27815
|
try {
|
|
27438
|
-
|
|
27439
|
-
|
|
27440
|
-
|
|
27441
|
-
|
|
27816
|
+
if (certificate) {
|
|
27817
|
+
const healthy = await new Promise((resolve9) => {
|
|
27818
|
+
const request$1 = request(url2, { ca: [...rootCertificates, certificate], timeout: 5e3 }, (response) => {
|
|
27819
|
+
response.resume();
|
|
27820
|
+
resolve9(response.statusCode !== void 0 && response.statusCode >= 200 && response.statusCode < 300);
|
|
27821
|
+
});
|
|
27822
|
+
request$1.on("error", () => resolve9(false));
|
|
27823
|
+
request$1.on("close", () => resolve9(false));
|
|
27824
|
+
request$1.on("timeout", () => request$1.destroy());
|
|
27825
|
+
request$1.end();
|
|
27826
|
+
});
|
|
27827
|
+
if (healthy)
|
|
27828
|
+
return true;
|
|
27829
|
+
} else {
|
|
27830
|
+
const response = await fetch(url2, { signal: AbortSignal.timeout(5e3) });
|
|
27831
|
+
await response.body?.cancel();
|
|
27832
|
+
if (response.ok)
|
|
27833
|
+
return true;
|
|
27834
|
+
}
|
|
27442
27835
|
} catch {
|
|
27443
27836
|
}
|
|
27444
27837
|
await sleep(HEALTH_POLL_INTERVAL);
|
|
@@ -27449,6 +27842,8 @@ function createDockerSshProvider(config2, container) {
|
|
|
27449
27842
|
const { appDir, projectName, scan, provisionEnv, appEnv, options } = context;
|
|
27450
27843
|
const deploymentId = makeDeploymentId();
|
|
27451
27844
|
const hostPort = config2.expose.hostPort;
|
|
27845
|
+
const https = config2.expose.https;
|
|
27846
|
+
const httpsAssets = https ? createHttpsAssets(https, resolveProjectDirectory(appDir)) : void 0;
|
|
27452
27847
|
const deploymentRoot = join(resolveProjectDirectory(appDir), ".hai", "deploy", options?.environment ?? "prod");
|
|
27453
27848
|
const artifact = context.artifact;
|
|
27454
27849
|
const resolvedBuildDir = context.artifactDir;
|
|
@@ -27467,12 +27862,14 @@ function createDockerSshProvider(config2, container) {
|
|
|
27467
27862
|
const containerPort = app?.containerPort ?? (primaryService === "serv" ? artifact.servContainerPort : artifact.webContainerPort) ?? config2.expose.containerPort;
|
|
27468
27863
|
const webContainerPort = web?.containerPort ?? artifact.webContainerPort ?? 8080;
|
|
27469
27864
|
const host = config2.ssh?.host;
|
|
27470
|
-
const url2 = host ? `http://${host.includes(":") ? `[${host}]` : host}:${hostPort}` : `http://SERVER_HOST:${hostPort}`;
|
|
27865
|
+
const url2 = https ? `https://${https.domain}${https.port === 443 ? "" : `:${https.port}`}` : host ? `http://${host.includes(":") ? `[${host}]` : host}:${hostPort}` : `http://SERVER_HOST:${hostPort}`;
|
|
27471
27866
|
const webBasePath = artifact.webBasePath ?? "";
|
|
27472
27867
|
const applicationUrl = `${url2}${webBasePath ? `${webBasePath}/` : ""}`;
|
|
27473
|
-
const standaloneWebConfig = primaryService === "web" && !scan.isSvelteKit && !!webBasePath;
|
|
27474
|
-
const healthPaths = hasWebProxy ? ["/
|
|
27868
|
+
const standaloneWebConfig = primaryService === "web" && !scan.isSvelteKit && (!!webBasePath || !!https);
|
|
27869
|
+
const healthPaths = hasWebProxy ? ["/_hai/web-health", `${webBasePath}/`] : scan.isSvelteKit ? [`${webBasePath}/`] : primaryService === "serv" ? [] : standaloneWebConfig ? ["/health", `${webBasePath}/`] : ["/health"];
|
|
27475
27870
|
const { env, sidecar } = buildRuntimeEnv(appDir, projectName, scan, provisionEnv, containerPort, options, appEnv, container?.s3Engine);
|
|
27871
|
+
if (https && scan.isSvelteKit && !env.ORIGIN)
|
|
27872
|
+
env.ORIGIN = url2;
|
|
27476
27873
|
const managedStorage = sidecar.services.includes("storage") ? JSON.parse(sidecar.env.HAI_STORAGE) : void 0;
|
|
27477
27874
|
let storageBasePath = "";
|
|
27478
27875
|
if (managedStorage?.publicEndpoint !== void 0) {
|
|
@@ -27490,7 +27887,7 @@ function createDockerSshProvider(config2, container) {
|
|
|
27490
27887
|
}
|
|
27491
27888
|
}
|
|
27492
27889
|
const storageRouting = managedStorage ? createHash("sha256").update(JSON.stringify({ ...managedStorage, secretAccessKey: void 0 })).digest("hex") : "";
|
|
27493
|
-
const gateway = !!managedStorage && !
|
|
27890
|
+
const gateway = !hasWebProxy && (!!managedStorage || !!https && !standaloneWebConfig);
|
|
27494
27891
|
if (gateway && primaryService === "web" && !scan.isSvelteKit)
|
|
27495
27892
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storageBackendRequired"));
|
|
27496
27893
|
const proxyService = gateway ? "gateway" : "web";
|
|
@@ -27501,6 +27898,35 @@ function createDockerSshProvider(config2, container) {
|
|
|
27501
27898
|
const server = existsSync(serverPath) || hasWebProxy ? loadDeploymentConfig("serv", serverPath, env) : ok({});
|
|
27502
27899
|
if (!server.success)
|
|
27503
27900
|
return server;
|
|
27901
|
+
const backendPaths = [];
|
|
27902
|
+
try {
|
|
27903
|
+
const http = endpointSettings(server.data.http);
|
|
27904
|
+
if (primaryService === "serv" && http.health !== false) {
|
|
27905
|
+
const health = endpointSettings(http.health);
|
|
27906
|
+
const path = servicePath(health.path, "/health");
|
|
27907
|
+
healthPaths.unshift(path);
|
|
27908
|
+
backendPaths.push(path, servicePath(health.readyPath, "/ready"));
|
|
27909
|
+
}
|
|
27910
|
+
if (hasWebProxy) {
|
|
27911
|
+
const corePath = join(appDir, "config", "_core.yml");
|
|
27912
|
+
const settings = existsSync(corePath) ? loadDeploymentConfig("core", corePath, env) : ok(env.HAI_CORE ? JSON.parse(env.HAI_CORE) : { env: env.HAI_CORE_ENV });
|
|
27913
|
+
if (!settings.success)
|
|
27914
|
+
return settings;
|
|
27915
|
+
const application = CoreConfigSchema.safeParse(settings.data);
|
|
27916
|
+
if (!application.success)
|
|
27917
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webProxyInvalid"));
|
|
27918
|
+
if (application.data.env !== "production") {
|
|
27919
|
+
if (http.openapi !== void 0 && http.openapi !== false)
|
|
27920
|
+
backendPaths.push(servicePath(endpointSettings(http.openapi).path, "/openapi.json"));
|
|
27921
|
+
if (http.docs !== void 0 && http.docs !== false) {
|
|
27922
|
+
const docsPath = servicePath(endpointSettings(http.docs).path, "/docs");
|
|
27923
|
+
backendPaths.push(docsPath, `${docsPath.slice(0, docsPath.lastIndexOf("/") + 1)}_hai/scalar.js`);
|
|
27924
|
+
}
|
|
27925
|
+
}
|
|
27926
|
+
}
|
|
27927
|
+
} catch {
|
|
27928
|
+
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webProxyInvalid"));
|
|
27929
|
+
}
|
|
27504
27930
|
let requestAccessSnapshot;
|
|
27505
27931
|
if (updatesApp && primaryService === "serv") {
|
|
27506
27932
|
const prepared2 = prepareRequestAccess({ settings: server.data, module: "serv", directory: appDir });
|
|
@@ -27562,25 +27988,25 @@ function createDockerSshProvider(config2, container) {
|
|
|
27562
27988
|
if (typeof apiPrefix !== "string" || !/^\/[\w/-]+$/.test(apiPrefix)) {
|
|
27563
27989
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webProxyInvalid"));
|
|
27564
27990
|
}
|
|
27565
|
-
if (webBasePath && ([
|
|
27991
|
+
if (webBasePath && ([...backendPaths, "/_hai/web-health"].includes(webBasePath) || webBasePath === apiPrefix.replace(/\/+$/, "") || webBasePath.startsWith(`${apiPrefix.replace(/\/+$/, "")}/`)))
|
|
27566
27992
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webBaseConflict"));
|
|
27567
27993
|
const transport = server.data.transport;
|
|
27568
27994
|
try {
|
|
27569
27995
|
const configured = transport ? transport.maxPlaintextBytes : void 0;
|
|
27570
27996
|
const limits = resolveTransportBodyLimits(Math.max(createTransportBodyPolicy(configured === "auto" ? void 0 : configured, transport ? transport.routePlaintextBytes : void 0).maximum.plaintextBytes, feedbackTransportBytes ?? 0));
|
|
27571
|
-
webProxy = generateWebProxyConfig(containerPort, apiPrefix, { webPort: webContainerPort, maxPlaintextBytes: limits.plaintextBytes, webBasePath, storageBucket: managedStorage?.bucket, storageBasePath, trustedEdgeProxies: config2.trustedEdgeProxies });
|
|
27997
|
+
webProxy = generateWebProxyConfig(containerPort, apiPrefix, { webPort: webContainerPort, maxPlaintextBytes: limits.plaintextBytes, webBasePath, backendPaths, storageBucket: managedStorage?.bucket, storageBasePath, trustedEdgeProxies: config2.trustedEdgeProxies });
|
|
27572
27998
|
} catch (error62) {
|
|
27573
27999
|
return err(HaiDeployError.CONFIG_ERROR, error62 instanceof RangeError ? error62.message : deployM(managedStorage ? "deploy_storageProxyInvalid" : "deploy_transportBodyLimitInvalid"));
|
|
27574
28000
|
}
|
|
27575
|
-
env.HAI_SERV = JSON.stringify({ ...server.data, cors: { ...cors, origin: (!cors?.origin || cors.origin === "*") && config2.ssh ? url2 : cors?.origin ?? "*" } });
|
|
27576
|
-
} else if (gateway
|
|
28001
|
+
env.HAI_SERV = JSON.stringify({ ...server.data, cors: { ...cors, origin: (!cors?.origin || cors.origin === "*") && (config2.ssh || https) ? url2 : cors?.origin ?? "*" } });
|
|
28002
|
+
} else if (gateway) {
|
|
27577
28003
|
try {
|
|
27578
28004
|
const http = server.data.http;
|
|
27579
28005
|
if (primaryService === "serv") {
|
|
27580
28006
|
const transport = server.data.transport;
|
|
27581
28007
|
applicationPlaintextBytes = Math.max(createTransportBodyPolicy(transport?.maxPlaintextBytes === "auto" ? void 0 : transport?.maxPlaintextBytes, transport?.routePlaintextBytes).maximum.plaintextBytes, feedbackTransportBytes ?? 0);
|
|
27582
28008
|
}
|
|
27583
|
-
webProxy = generateApplicationProxyConfig({ primaryService, port: containerPort, bucket: managedStorage
|
|
28009
|
+
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: config2.trustedEdgeProxies });
|
|
27584
28010
|
} catch {
|
|
27585
28011
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storageProxyInvalid"));
|
|
27586
28012
|
}
|
|
@@ -27589,8 +28015,15 @@ function createDockerSshProvider(config2, container) {
|
|
|
27589
28015
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webBaseConflict"));
|
|
27590
28016
|
webProxy = generateStaticWebConfig(containerPort, webBasePath);
|
|
27591
28017
|
}
|
|
27592
|
-
|
|
27593
|
-
|
|
28018
|
+
if (https && webProxy)
|
|
28019
|
+
webProxy = renderHttpsProxy(webProxy, https, hasWebProxy ? webContainerPort : gateway ? 8080 : containerPort);
|
|
28020
|
+
if (https && primaryService === "serv" && !hasWebProxy) {
|
|
28021
|
+
const cors = server.data.cors;
|
|
28022
|
+
env.HAI_SERV = JSON.stringify({ ...server.data, cors: { ...cors, origin: !cors?.origin || cors.origin === "*" ? url2 : cors.origin } });
|
|
28023
|
+
}
|
|
28024
|
+
const requestedOutput = options?.output ?? join(deploymentRoot, `bundle-${projectName}-${deploymentId}-${artifact.appVersion}.tar.gz`);
|
|
28025
|
+
const compressed = requestedOutput.endsWith(".tar.gz");
|
|
28026
|
+
const stem = basename(requestedOutput).replace(/\.tar(?:\.gz)?$/i, "");
|
|
27594
28027
|
const suffix = `-${artifact.appVersion}`;
|
|
27595
28028
|
const name = stem.endsWith(suffix) ? stem : `${stem}${suffix}`;
|
|
27596
28029
|
if (!/^[\w.+-]+$/.test(name))
|
|
@@ -27623,10 +28056,13 @@ function createDockerSshProvider(config2, container) {
|
|
|
27623
28056
|
containerPort,
|
|
27624
28057
|
primaryService,
|
|
27625
28058
|
requestAccessPolicy: requestAccessSnapshot !== void 0,
|
|
28059
|
+
nodeRuntime: primaryService === "serv" || scan.isSvelteKit,
|
|
28060
|
+
nodeOptions: env.NODE_OPTIONS,
|
|
27626
28061
|
sidecar,
|
|
27627
28062
|
web: hasWebProxy ? { imageRef: web?.imageRef ?? "localhost/hai-unselected:0", containerPort: webContainerPort } : void 0,
|
|
27628
28063
|
mountWebConfig: standaloneWebConfig,
|
|
27629
|
-
gateway
|
|
28064
|
+
gateway,
|
|
28065
|
+
https
|
|
27630
28066
|
});
|
|
27631
28067
|
const composePath = join(artifactDir, composeName);
|
|
27632
28068
|
writeFileSync(composePath, composeContent);
|
|
@@ -27635,6 +28071,8 @@ function createDockerSshProvider(config2, container) {
|
|
|
27635
28071
|
const selected = options?.selectedServices ? [...options.selectedServices] : artifact.partial ? images.map((image) => image.name) : [...images.map((image) => image.name), ...Object.keys(sidecar.composeServices)];
|
|
27636
28072
|
if (gateway && !partial2)
|
|
27637
28073
|
selected.push("gateway");
|
|
28074
|
+
if (https?.type === "letsencrypt" && !partial2)
|
|
28075
|
+
selected.push("certbot");
|
|
27638
28076
|
if (!selected.length || selected.some((name2) => !/^[a-z][a-z0-9-]*$/.test(name2)) || new Set(selected).size !== selected.length)
|
|
27639
28077
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_selectionInvalid", { params: { service: selected.join(", ") } }));
|
|
27640
28078
|
for (const name2 of selected) {
|
|
@@ -27659,7 +28097,9 @@ function createDockerSshProvider(config2, container) {
|
|
|
27659
28097
|
containerPort,
|
|
27660
28098
|
middlewareEnv: serializeEnvFile(sidecar.env),
|
|
27661
28099
|
healthPaths,
|
|
27662
|
-
|
|
28100
|
+
httpsAssets,
|
|
28101
|
+
output: join(artifactDir, `${name}.tar${compressed ? ".gz" : ""}`),
|
|
28102
|
+
cacheDirectory: join(resolveProjectDirectory(appDir), ".hai", "cache", "images")
|
|
27663
28103
|
});
|
|
27664
28104
|
if (options?.bundleOnly)
|
|
27665
28105
|
return ok({ url: "", deploymentId, status: "packaged", provider: "docker-ssh", healthCheck: "skipped", envVarsSet: Object.keys(env), ...bundle });
|
|
@@ -27681,7 +28121,7 @@ function createDockerSshProvider(config2, container) {
|
|
|
27681
28121
|
return uploaded;
|
|
27682
28122
|
let pendingOutput = "";
|
|
27683
28123
|
const started = await ssh.exec(
|
|
27684
|
-
`mkdir -p ${shellArgument(`${remoteTar}.contents`)} && chmod 600 ${shellArgument(remoteTar)} && tar -xf ${shellArgument(remoteTar)} -C ${shellArgument(`${remoteTar}.contents`)} && sh ${shellArgument(`${remoteTar}.contents/${bundle.startName}`)} ${shellArgument(remoteDir)}`,
|
|
28124
|
+
`mkdir -p ${shellArgument(`${remoteTar}.contents`)} && chmod 600 ${shellArgument(remoteTar)} && tar -xf ${shellArgument(remoteTar)} -C ${shellArgument(`${remoteTar}.contents`)} && sh ${shellArgument(`${remoteTar}.contents/${bundle.startName}`)} ${shellArgument(remoteDir)} && { rm -rf -- ${shellArgument(`${remoteTar}.contents`)} && rm -f -- ${shellArgument(remoteTar)} || printf '[WARN] Deployment passed; incoming archive cleanup failed\\n' >&2; }`,
|
|
27685
28125
|
{ timeoutMs: 6e5, secrets: sidecar.secrets, onOutput: (output2) => {
|
|
27686
28126
|
const parts = output2.split("\n");
|
|
27687
28127
|
for (let index = 0; index < parts.length; index++) {
|
|
@@ -27690,7 +28130,7 @@ function createDockerSshProvider(config2, container) {
|
|
|
27690
28130
|
break;
|
|
27691
28131
|
const line = pendingOutput;
|
|
27692
28132
|
pendingOutput = "";
|
|
27693
|
-
const stage = /^\[(preflight|images|dependencies|start|health|complete)\]/.exec(line)?.[1];
|
|
28133
|
+
const stage = /^\[(preflight|images|dependencies|start|https|health|complete)\]/.exec(line)?.[1];
|
|
27694
28134
|
if (stage)
|
|
27695
28135
|
options?.onProgress?.(stage);
|
|
27696
28136
|
}
|
|
@@ -27723,7 +28163,7 @@ ${terminal ? deployM("deploy_remoteFinished", { params: { stage: terminal.stage,
|
|
|
27723
28163
|
logger6.info("Running health check", { url: applicationUrl });
|
|
27724
28164
|
let healthy = true;
|
|
27725
28165
|
for (const healthPath of healthPaths) {
|
|
27726
|
-
if (!await healthCheck(`${url2}${healthPath}
|
|
28166
|
+
if (!await healthCheck(`${url2}${healthPath}`, httpsAssets?.["tls/fullchain.pem"])) {
|
|
27727
28167
|
healthy = false;
|
|
27728
28168
|
break;
|
|
27729
28169
|
}
|
|
@@ -28010,7 +28450,7 @@ async function pollDeploymentStatus(token, deploymentId) {
|
|
|
28010
28450
|
if (deployment.readyState === "ERROR" || deployment.readyState === "CANCELED") {
|
|
28011
28451
|
return "error";
|
|
28012
28452
|
}
|
|
28013
|
-
await new Promise((
|
|
28453
|
+
await new Promise((resolve9) => setTimeout(resolve9, POLL_INTERVAL));
|
|
28014
28454
|
}
|
|
28015
28455
|
return "building";
|
|
28016
28456
|
}
|
|
@@ -28545,7 +28985,7 @@ var deployCredentials = {
|
|
|
28545
28985
|
saveAll: (entries) => saveCredentials(entries)
|
|
28546
28986
|
};
|
|
28547
28987
|
function mergeProvisionEnv(results, scan, options) {
|
|
28548
|
-
|
|
28988
|
+
const env = {};
|
|
28549
28989
|
const reachRequirements = scan?.middlewares?.filter((item) => item.configName === "reach") ?? [];
|
|
28550
28990
|
const reachProviders = reachRequirements.map((item) => item.config);
|
|
28551
28991
|
let hasReachOutput = false;
|
|
@@ -28560,13 +29000,16 @@ function mergeProvisionEnv(results, scan, options) {
|
|
|
28560
29000
|
});
|
|
28561
29001
|
if (targets.length) {
|
|
28562
29002
|
for (const target of targets)
|
|
28563
|
-
reachProviders[reachRequirements.indexOf(target)] = { ...provider, name: target.config.name ?? provider.name };
|
|
29003
|
+
reachProviders[reachRequirements.indexOf(target)] = { ...mergeManagedMiddlewareConfig(target, provider), name: target.config.name ?? provider.name };
|
|
28564
29004
|
} else {
|
|
28565
29005
|
reachProviders.push(provider);
|
|
28566
29006
|
}
|
|
28567
29007
|
}
|
|
28568
29008
|
}
|
|
28569
|
-
|
|
29009
|
+
for (const [name, value] of Object.entries(prov.envVars)) {
|
|
29010
|
+
const requirement = scan?.middlewares?.find((item) => item.envPrefix === name);
|
|
29011
|
+
env[name] = requirement ? JSON.stringify(mergeManagedMiddlewareConfig(requirement, JSON.parse(value))) : value;
|
|
29012
|
+
}
|
|
28570
29013
|
}
|
|
28571
29014
|
if (hasReachOutput)
|
|
28572
29015
|
env.HAI_REACH_PROVIDERS = JSON.stringify(reachProviders);
|
|
@@ -28987,7 +29430,8 @@ async function createDeploymentPlan(appDir, input2, options = {}) {
|
|
|
28987
29430
|
}
|
|
28988
29431
|
}
|
|
28989
29432
|
const sidecar = provider.type === "docker-ssh" ? planSidecars(directory, projectName, app, provisionEnv, effective, config2.container?.s3Engine, { dbPassword: "", storagePassword: "", ldapPassword: "" }) : void 0;
|
|
28990
|
-
const
|
|
29433
|
+
const https = provider.type === "docker-ssh" ? provider.expose.https : void 0;
|
|
29434
|
+
const gateway = !!artifact && !(artifact.primaryService === "serv" && artifact.hasWeb) && (sidecar?.services.includes("storage") || !!https && (artifact.primaryService === "serv" || app.isSvelteKit));
|
|
28991
29435
|
if (gateway && artifact.primaryService === "web" && !app.isSvelteKit)
|
|
28992
29436
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storageBackendRequired"));
|
|
28993
29437
|
if (gateway) {
|
|
@@ -28995,18 +29439,23 @@ async function createDeploymentPlan(appDir, input2, options = {}) {
|
|
|
28995
29439
|
if (!baseline.required)
|
|
28996
29440
|
selected.push("gateway");
|
|
28997
29441
|
}
|
|
29442
|
+
if (https?.type === "letsencrypt") {
|
|
29443
|
+
available.push("certbot");
|
|
29444
|
+
if (!baseline.required)
|
|
29445
|
+
selected.push("certbot");
|
|
29446
|
+
}
|
|
28998
29447
|
const services = Object.entries(sidecar?.composeServices ?? {}).filter(([name]) => selected.includes(name));
|
|
28999
29448
|
return ok({
|
|
29000
29449
|
projectName,
|
|
29001
29450
|
root,
|
|
29002
|
-
|
|
29451
|
+
serviceDir: directory,
|
|
29003
29452
|
environment: environment.data.environment,
|
|
29004
|
-
target: provider.type === "docker-ssh" ? { provider: provider.type, host: provider.ssh?.host, port: provider.ssh?.port, directory: remoteDirectory, runtime: provider.remote.runtime, hostPort: provider.expose.hostPort } : { provider: provider.type },
|
|
29453
|
+
target: provider.type === "docker-ssh" ? { provider: provider.type, host: provider.ssh?.host, port: provider.ssh?.port, directory: remoteDirectory, runtime: provider.remote.runtime, hostPort: provider.expose.hostPort, https: https ? { type: https.type, domain: https.domain, port: https.port } : void 0 } : { provider: provider.type },
|
|
29005
29454
|
artifact: artifact ? { directory: artifactDir, version: artifact.appVersion, buildId: artifact.buildId, verified: true, images: artifact.images.map((image) => ({ name: image.name, reference: image.imageRef, sha256: image.sha256 })) } : void 0,
|
|
29006
29455
|
selected,
|
|
29007
29456
|
retained: available.filter((name) => !selected.includes(name)),
|
|
29008
29457
|
containers: provider.type === "docker-ssh" ? [...selected.filter((name) => images.includes(name) || name === "gateway").map((name) => `hai-${projectName}-${name}`), ...services.flatMap(([, service]) => typeof service.container_name === "string" ? [service.container_name] : [])] : [],
|
|
29009
|
-
composeManagedServices: services.filter(([, service]) => typeof service.container_name !== "string").map(([name]) => name),
|
|
29458
|
+
composeManagedServices: [...services.filter(([, service]) => typeof service.container_name !== "string").map(([name]) => name), ...selected.includes("certbot") ? ["certbot"] : []],
|
|
29010
29459
|
logicalVolumes: Object.keys(sidecar?.volumes ?? {}),
|
|
29011
29460
|
middleware: middlewares.map((item) => ({ service: item.service, backend: item.backend, configuration: item.envPrefix, action: selected.includes(item.service) ? effective.serviceModes?.[item.envPrefix] ?? effective.serviceModes?.[item.service] ?? "default" : "keep", provisioner: provisioners.get(item.service) })),
|
|
29012
29461
|
configuration: { files: [join(root, "hai.json"), ...configFiles].filter(existsSync), environmentKeys: Object.keys(environment.data.values).sort() },
|
|
@@ -29268,5 +29717,5 @@ async function acquireImportLock(path, root) {
|
|
|
29268
29717
|
}
|
|
29269
29718
|
|
|
29270
29719
|
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, createDeploymentPlan, deploy, describeMiddlewareBackend, external_exports, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, readDeploymentArtifact, saveDeploymentEnvironment };
|
|
29271
|
-
//# sourceMappingURL=chunk-
|
|
29272
|
-
//# sourceMappingURL=chunk-
|
|
29720
|
+
//# sourceMappingURL=chunk-K6RWTTHD.js.map
|
|
29721
|
+
//# sourceMappingURL=chunk-K6RWTTHD.js.map
|