@h-ai/cli 1.0.0-beta.1 → 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-M7HN2OE3.js → chunk-K6RWTTHD.js} +573 -106
- 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-3JCZO34U.js → chunk-YVNNMC4H.js} +365 -137
- package/dist/chunk-YVNNMC4H.js.map +1 -0
- package/dist/cli-config-templates-LUJGDQNW.js +5 -0
- package/dist/{cli-config-templates-RCWQMHZN.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 +118 -209
- package/dist/index.js.map +1 -1
- package/dist/metafile-esm.json +1 -1
- package/dist/{src-JRKSWJ2Q.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/AGENTS.md +2 -0
- package/templates/skills/bridges/admin/AGENTS.md +2 -0
- package/templates/skills/bridges/api/AGENTS.md +2 -0
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +2 -0
- package/templates/skills/bridges/h5/AGENTS.md +2 -0
- package/templates/skills/bridges/mobile-app/AGENTS.md +2 -0
- package/templates/skills/bridges/website/AGENTS.md +2 -0
- package/templates/skills/hai-ai/guide.md +5 -3
- package/templates/skills/hai-app-create/SKILL.md +1 -1
- package/templates/skills/hai-app-create/collaboration.md +6 -1
- package/templates/skills/hai-app-create/development.md +10 -4
- package/templates/skills/hai-app-create/reference.md +4 -2
- package/templates/skills/hai-app-review/SKILL.md +2 -0
- package/templates/skills/hai-app-tests/SKILL.md +2 -0
- 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-change-plan/SKILL.md +46 -0
- package/templates/skills/hai-deploy/SKILL.md +16 -4
- package/templates/skills/hai-deploy/middleware.md +4 -2
- package/templates/skills/hai-feedback/SKILL.md +1 -1
- package/templates/skills/hai-pr-review/SKILL.md +2 -0
- package/templates/skills/hai-serv/reference.md +5 -8
- package/templates/skills/hai-spec-implement/SKILL.md +2 -0
- package/templates/skills/hai-storage/SKILL.md +2 -2
- package/templates/skills/hai-storage/reference.md +1 -1
- package/dist/chunk-3JCZO34U.js.map +0 -1
- package/dist/chunk-AKV2CQZV.js.map +0 -1
- package/dist/chunk-M7HN2OE3.js.map +0 -1
- package/dist/cli-config-templates-RCWQMHZN.js +0 -5
- package/dist/src-JRKSWJ2Q.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
|
}
|
|
@@ -22612,9 +22617,9 @@ var en_US_default = {
|
|
|
22612
22617
|
deploy_webProxyInvalid: "The API prefix must be an absolute path containing only letters, numbers, underscores, hyphens and slashes.",
|
|
22613
22618
|
deploy_webBaseConflict: "The website mount path conflicts with an API or health endpoint. Choose a different PUBLIC_BASE_PATH.",
|
|
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
|
-
deploy_storagePublicEndpointInvalid: "Invalid storage.publicEndpoint. Use a browser-accessible HTTP(S)
|
|
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
|
|
@@ -22720,9 +22727,9 @@ var ja_JP_default = {
|
|
|
22720
22727
|
deploy_webProxyInvalid: "API\u30D7\u30EC\u30D5\u30A3\u30C3\u30AF\u30B9\u306F\u3001\u82F1\u6570\u5B57\u3001\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u3001\u30CF\u30A4\u30D5\u30F3\u3001\u30B9\u30E9\u30C3\u30B7\u30E5\u306E\u307F\u3092\u542B\u3080\u7D76\u5BFE\u30D1\u30B9\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
22721
22728
|
deploy_webBaseConflict: "\u30B5\u30A4\u30C8\u306E\u30DE\u30A6\u30F3\u30C8\u30D1\u30B9\u304CAPI\u307E\u305F\u306F\u30D8\u30EB\u30B9\u30C1\u30A7\u30C3\u30AF\u3068\u7AF6\u5408\u3057\u3066\u3044\u307E\u3059\u3002\u5225\u306EPUBLIC_BASE_PATH\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
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
|
-
deploy_storagePublicEndpointInvalid: "storage.publicEndpoint \u304C\u7121\u52B9\u3067\u3059\u3002\
|
|
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
|
|
@@ -22828,9 +22837,9 @@ var zh_CN_default2 = {
|
|
|
22828
22837
|
deploy_webProxyInvalid: "API \u524D\u7F00\u5FC5\u987B\u662F\u4EC5\u542B\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u3001\u8FDE\u5B57\u7B26\u548C\u659C\u6760\u7684\u7EDD\u5BF9\u8DEF\u5F84\u3002",
|
|
22829
22838
|
deploy_webBaseConflict: "\u7F51\u7AD9\u6302\u8F7D\u76EE\u5F55\u4E0E API \u6216\u5065\u5EB7\u68C0\u67E5\u5165\u53E3\u51B2\u7A81\uFF0C\u8BF7\u4F7F\u7528\u4E0D\u540C\u7684 PUBLIC_BASE_PATH\u3002",
|
|
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
|
-
deploy_storagePublicEndpointInvalid: "storage.publicEndpoint \u65E0\u6548\uFF1A\u8BF7\u586B\u5199\u6D4F\u89C8\u5668\u53EF\u8BBF\u95EE\u7684 HTTP(S) \
|
|
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,12 +27552,22 @@ 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, webBasePath]) : "";
|
|
27199
|
-
|
|
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("");
|
|
27570
|
+
return `${options.storageBucket ? renderStorageUpstream(options.storageBucket, options.storageBasePath) : ""}server {
|
|
27200
27571
|
listen ${webPort};
|
|
27201
27572
|
server_name _;
|
|
27202
27573
|
${renderTrustedEdgeProxies(options.trustedEdgeProxies)} root /usr/share/nginx/html;
|
|
@@ -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;
|
|
@@ -27232,9 +27597,11 @@ ${storageLocation}${webLocation} location = ${webBasePath}/runtime-config.js {
|
|
|
27232
27597
|
}
|
|
27233
27598
|
`;
|
|
27234
27599
|
}
|
|
27235
|
-
function renderStorageLocation(bucket, reservedPaths) {
|
|
27236
|
-
|
|
27237
|
-
|
|
27600
|
+
function renderStorageLocation(bucket, reservedPaths, storageBasePath = "") {
|
|
27601
|
+
renderWebLocation(storageBasePath);
|
|
27602
|
+
const path = `${storageBasePath}/${bucket}`;
|
|
27603
|
+
const reserved = ["/health", "/_hai", ...reservedPaths].filter(Boolean);
|
|
27604
|
+
if (!/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bucket) || bucket.includes("..") || ["health", "_hai"].includes(bucket) || reserved.some((item) => item === path || item.startsWith(`${path}/`) || path.startsWith(`${item}/`) || storageBasePath && (item === storageBasePath || item.startsWith(`${storageBasePath}/`) || storageBasePath.startsWith(`${item}/`)))) {
|
|
27238
27605
|
throw new Error("Storage bucket conflicts with application routes or is not a valid DNS bucket name");
|
|
27239
27606
|
}
|
|
27240
27607
|
return ` location ^~ ${path}/ {
|
|
@@ -27251,7 +27618,8 @@ function renderStorageLocation(bucket, reservedPaths) {
|
|
|
27251
27618
|
add_header Cache-Control "private, no-store" always;
|
|
27252
27619
|
limit_except GET PUT OPTIONS { deny all; }
|
|
27253
27620
|
client_max_body_size 0;
|
|
27254
|
-
|
|
27621
|
+
${storageBasePath ? ` # map \u4FDD\u7559\u539F\u59CB URI \u4E0E\u67E5\u8BE2\uFF1B\u4E0D\u5728 location \u5185\u521B\u5EFA\u6539\u53D8\u4EE3\u7406\u7EE7\u627F\u7684 if \u914D\u7F6E\u3002
|
|
27622
|
+
proxy_pass http://hai_storage_proxy$hai_storage_uri;` : " proxy_pass http://storage:9000;"}
|
|
27255
27623
|
proxy_http_version 1.1;
|
|
27256
27624
|
proxy_set_header Host $http_host;
|
|
27257
27625
|
proxy_set_header Connection "";
|
|
@@ -27263,16 +27631,27 @@ function renderStorageLocation(bucket, reservedPaths) {
|
|
|
27263
27631
|
}
|
|
27264
27632
|
`;
|
|
27265
27633
|
}
|
|
27634
|
+
function renderStorageUpstream(bucket, storageBasePath = "") {
|
|
27635
|
+
if (!storageBasePath)
|
|
27636
|
+
return "";
|
|
27637
|
+
const path = `${storageBasePath}/${bucket}`.replaceAll(".", "\\\\.");
|
|
27638
|
+
return `upstream hai_storage_proxy { server storage:9000; }
|
|
27639
|
+
map $request_uri $hai_storage_uri {
|
|
27640
|
+
default $request_uri;
|
|
27641
|
+
"~^${path}(/.*)$" /${bucket}$1;
|
|
27642
|
+
}
|
|
27643
|
+
`;
|
|
27644
|
+
}
|
|
27266
27645
|
function generateApplicationProxyConfig(options) {
|
|
27267
27646
|
const { primaryService, port, bucket, reservedPaths, maxPlaintextBytes } = options;
|
|
27268
27647
|
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
27269
27648
|
throw new Error("Invalid application proxy target");
|
|
27270
27649
|
const bodyLimits = resolveTransportBodyLimits(maxPlaintextBytes);
|
|
27271
|
-
return
|
|
27650
|
+
return `${bucket ? renderStorageUpstream(bucket, options.storageBasePath) : ""}server {
|
|
27272
27651
|
listen 8080;
|
|
27273
27652
|
server_name _;
|
|
27274
27653
|
${renderTrustedEdgeProxies(options.trustedEdgeProxies)} large_client_header_buffers 4 32k;
|
|
27275
|
-
${renderStorageLocation(bucket, reservedPaths)} location = /_hai/gateway-health {
|
|
27654
|
+
${bucket ? renderStorageLocation(bucket, reservedPaths, options.storageBasePath) : ""} location = /_hai/gateway-health {
|
|
27276
27655
|
access_log off;
|
|
27277
27656
|
return 200 'ok';
|
|
27278
27657
|
}
|
|
@@ -27380,9 +27759,22 @@ function prepareRequestAccess(options) {
|
|
|
27380
27759
|
}
|
|
27381
27760
|
}
|
|
27382
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
|
+
}
|
|
27383
27775
|
var ServerResultSchema = external_exports.object({
|
|
27384
27776
|
deploymentId: external_exports.string(),
|
|
27385
|
-
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"]),
|
|
27386
27778
|
exitCode: external_exports.number().int().min(0).max(255),
|
|
27387
27779
|
durationSeconds: external_exports.number().int().nonnegative(),
|
|
27388
27780
|
recoveryStatus: external_exports.enum(["not-needed", "passed", "failed", "stopped"]).optional()
|
|
@@ -27394,7 +27786,7 @@ function makeDeploymentId() {
|
|
|
27394
27786
|
return `${stamp}-${randomBytes(3).toString("hex")}`;
|
|
27395
27787
|
}
|
|
27396
27788
|
function sleep(ms) {
|
|
27397
|
-
return new Promise((
|
|
27789
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
27398
27790
|
}
|
|
27399
27791
|
function createDockerSshProvider(config2, container) {
|
|
27400
27792
|
const ssh = config2.ssh ? createSshExecutor(config2.ssh) : void 0;
|
|
@@ -27418,13 +27810,28 @@ function createDockerSshProvider(config2, container) {
|
|
|
27418
27810
|
deployM("deploy_remoteRuntimeMissing", { params: { runtime: candidates.join(" / ") } })
|
|
27419
27811
|
);
|
|
27420
27812
|
}
|
|
27421
|
-
async function healthCheck(url2) {
|
|
27813
|
+
async function healthCheck(url2, certificate) {
|
|
27422
27814
|
for (let attempt = 0; attempt < HEALTH_MAX_ATTEMPTS; attempt++) {
|
|
27423
27815
|
try {
|
|
27424
|
-
|
|
27425
|
-
|
|
27426
|
-
|
|
27427
|
-
|
|
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
|
+
}
|
|
27428
27835
|
} catch {
|
|
27429
27836
|
}
|
|
27430
27837
|
await sleep(HEALTH_POLL_INTERVAL);
|
|
@@ -27435,6 +27842,8 @@ function createDockerSshProvider(config2, container) {
|
|
|
27435
27842
|
const { appDir, projectName, scan, provisionEnv, appEnv, options } = context;
|
|
27436
27843
|
const deploymentId = makeDeploymentId();
|
|
27437
27844
|
const hostPort = config2.expose.hostPort;
|
|
27845
|
+
const https = config2.expose.https;
|
|
27846
|
+
const httpsAssets = https ? createHttpsAssets(https, resolveProjectDirectory(appDir)) : void 0;
|
|
27438
27847
|
const deploymentRoot = join(resolveProjectDirectory(appDir), ".hai", "deploy", options?.environment ?? "prod");
|
|
27439
27848
|
const artifact = context.artifact;
|
|
27440
27849
|
const resolvedBuildDir = context.artifactDir;
|
|
@@ -27453,26 +27862,32 @@ function createDockerSshProvider(config2, container) {
|
|
|
27453
27862
|
const containerPort = app?.containerPort ?? (primaryService === "serv" ? artifact.servContainerPort : artifact.webContainerPort) ?? config2.expose.containerPort;
|
|
27454
27863
|
const webContainerPort = web?.containerPort ?? artifact.webContainerPort ?? 8080;
|
|
27455
27864
|
const host = config2.ssh?.host;
|
|
27456
|
-
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}`;
|
|
27457
27866
|
const webBasePath = artifact.webBasePath ?? "";
|
|
27458
27867
|
const applicationUrl = `${url2}${webBasePath ? `${webBasePath}/` : ""}`;
|
|
27459
|
-
const standaloneWebConfig = primaryService === "web" && !scan.isSvelteKit && !!webBasePath;
|
|
27460
|
-
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"];
|
|
27461
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;
|
|
27462
27873
|
const managedStorage = sidecar.services.includes("storage") ? JSON.parse(sidecar.env.HAI_STORAGE) : void 0;
|
|
27874
|
+
let storageBasePath = "";
|
|
27463
27875
|
if (managedStorage?.publicEndpoint !== void 0) {
|
|
27464
27876
|
try {
|
|
27465
27877
|
if (typeof managedStorage.publicEndpoint !== "string")
|
|
27466
27878
|
throw new Error("Invalid public endpoint type");
|
|
27879
|
+
const unsafeCharacters = /[\u0000-\u0020\u007F\\%]/.test(managedStorage.publicEndpoint);
|
|
27467
27880
|
const endpoint = new URL(managedStorage.publicEndpoint);
|
|
27468
|
-
if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.
|
|
27881
|
+
if (!["http:", "https:"].includes(endpoint.protocol) || endpoint.username || endpoint.password || endpoint.search || endpoint.hash || !/^\/(?:[\w.-]+(?:\/[\w.-]+)*\/?)?$/.test(endpoint.pathname) || unsafeCharacters || /(?:^|\/)\.{1,2}(?:\/|$)/.test(managedStorage.publicEndpoint)) {
|
|
27469
27882
|
throw new Error("Invalid public endpoint origin");
|
|
27883
|
+
}
|
|
27884
|
+
storageBasePath = endpoint.pathname.replace(/\/+$/, "");
|
|
27470
27885
|
} catch {
|
|
27471
27886
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storagePublicEndpointInvalid"));
|
|
27472
27887
|
}
|
|
27473
27888
|
}
|
|
27474
27889
|
const storageRouting = managedStorage ? createHash("sha256").update(JSON.stringify({ ...managedStorage, secretAccessKey: void 0 })).digest("hex") : "";
|
|
27475
|
-
const gateway = !!managedStorage && !
|
|
27890
|
+
const gateway = !hasWebProxy && (!!managedStorage || !!https && !standaloneWebConfig);
|
|
27476
27891
|
if (gateway && primaryService === "web" && !scan.isSvelteKit)
|
|
27477
27892
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storageBackendRequired"));
|
|
27478
27893
|
const proxyService = gateway ? "gateway" : "web";
|
|
@@ -27483,6 +27898,35 @@ function createDockerSshProvider(config2, container) {
|
|
|
27483
27898
|
const server = existsSync(serverPath) || hasWebProxy ? loadDeploymentConfig("serv", serverPath, env) : ok({});
|
|
27484
27899
|
if (!server.success)
|
|
27485
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
|
+
}
|
|
27486
27930
|
let requestAccessSnapshot;
|
|
27487
27931
|
if (updatesApp && primaryService === "serv") {
|
|
27488
27932
|
const prepared2 = prepareRequestAccess({ settings: server.data, module: "serv", directory: appDir });
|
|
@@ -27544,25 +27988,25 @@ function createDockerSshProvider(config2, container) {
|
|
|
27544
27988
|
if (typeof apiPrefix !== "string" || !/^\/[\w/-]+$/.test(apiPrefix)) {
|
|
27545
27989
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webProxyInvalid"));
|
|
27546
27990
|
}
|
|
27547
|
-
if (webBasePath && ([
|
|
27991
|
+
if (webBasePath && ([...backendPaths, "/_hai/web-health"].includes(webBasePath) || webBasePath === apiPrefix.replace(/\/+$/, "") || webBasePath.startsWith(`${apiPrefix.replace(/\/+$/, "")}/`)))
|
|
27548
27992
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webBaseConflict"));
|
|
27549
27993
|
const transport = server.data.transport;
|
|
27550
27994
|
try {
|
|
27551
27995
|
const configured = transport ? transport.maxPlaintextBytes : void 0;
|
|
27552
27996
|
const limits = resolveTransportBodyLimits(Math.max(createTransportBodyPolicy(configured === "auto" ? void 0 : configured, transport ? transport.routePlaintextBytes : void 0).maximum.plaintextBytes, feedbackTransportBytes ?? 0));
|
|
27553
|
-
webProxy = generateWebProxyConfig(containerPort, apiPrefix, { webPort: webContainerPort, maxPlaintextBytes: limits.plaintextBytes, webBasePath, storageBucket: managedStorage?.bucket, trustedEdgeProxies: config2.trustedEdgeProxies });
|
|
27997
|
+
webProxy = generateWebProxyConfig(containerPort, apiPrefix, { webPort: webContainerPort, maxPlaintextBytes: limits.plaintextBytes, webBasePath, backendPaths, storageBucket: managedStorage?.bucket, storageBasePath, trustedEdgeProxies: config2.trustedEdgeProxies });
|
|
27554
27998
|
} catch (error62) {
|
|
27555
27999
|
return err(HaiDeployError.CONFIG_ERROR, error62 instanceof RangeError ? error62.message : deployM(managedStorage ? "deploy_storageProxyInvalid" : "deploy_transportBodyLimitInvalid"));
|
|
27556
28000
|
}
|
|
27557
|
-
env.HAI_SERV = JSON.stringify({ ...server.data, cors: { ...cors, origin: (!cors?.origin || cors.origin === "*") && config2.ssh ? url2 : cors?.origin ?? "*" } });
|
|
27558
|
-
} 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) {
|
|
27559
28003
|
try {
|
|
27560
28004
|
const http = server.data.http;
|
|
27561
28005
|
if (primaryService === "serv") {
|
|
27562
28006
|
const transport = server.data.transport;
|
|
27563
28007
|
applicationPlaintextBytes = Math.max(createTransportBodyPolicy(transport?.maxPlaintextBytes === "auto" ? void 0 : transport?.maxPlaintextBytes, transport?.routePlaintextBytes).maximum.plaintextBytes, feedbackTransportBytes ?? 0);
|
|
27564
28008
|
}
|
|
27565
|
-
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 });
|
|
27566
28010
|
} catch {
|
|
27567
28011
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storageProxyInvalid"));
|
|
27568
28012
|
}
|
|
@@ -27571,8 +28015,15 @@ function createDockerSshProvider(config2, container) {
|
|
|
27571
28015
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_webBaseConflict"));
|
|
27572
28016
|
webProxy = generateStaticWebConfig(containerPort, webBasePath);
|
|
27573
28017
|
}
|
|
27574
|
-
|
|
27575
|
-
|
|
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, "");
|
|
27576
28027
|
const suffix = `-${artifact.appVersion}`;
|
|
27577
28028
|
const name = stem.endsWith(suffix) ? stem : `${stem}${suffix}`;
|
|
27578
28029
|
if (!/^[\w.+-]+$/.test(name))
|
|
@@ -27605,10 +28056,13 @@ function createDockerSshProvider(config2, container) {
|
|
|
27605
28056
|
containerPort,
|
|
27606
28057
|
primaryService,
|
|
27607
28058
|
requestAccessPolicy: requestAccessSnapshot !== void 0,
|
|
28059
|
+
nodeRuntime: primaryService === "serv" || scan.isSvelteKit,
|
|
28060
|
+
nodeOptions: env.NODE_OPTIONS,
|
|
27608
28061
|
sidecar,
|
|
27609
28062
|
web: hasWebProxy ? { imageRef: web?.imageRef ?? "localhost/hai-unselected:0", containerPort: webContainerPort } : void 0,
|
|
27610
28063
|
mountWebConfig: standaloneWebConfig,
|
|
27611
|
-
gateway
|
|
28064
|
+
gateway,
|
|
28065
|
+
https
|
|
27612
28066
|
});
|
|
27613
28067
|
const composePath = join(artifactDir, composeName);
|
|
27614
28068
|
writeFileSync(composePath, composeContent);
|
|
@@ -27617,6 +28071,8 @@ function createDockerSshProvider(config2, container) {
|
|
|
27617
28071
|
const selected = options?.selectedServices ? [...options.selectedServices] : artifact.partial ? images.map((image) => image.name) : [...images.map((image) => image.name), ...Object.keys(sidecar.composeServices)];
|
|
27618
28072
|
if (gateway && !partial2)
|
|
27619
28073
|
selected.push("gateway");
|
|
28074
|
+
if (https?.type === "letsencrypt" && !partial2)
|
|
28075
|
+
selected.push("certbot");
|
|
27620
28076
|
if (!selected.length || selected.some((name2) => !/^[a-z][a-z0-9-]*$/.test(name2)) || new Set(selected).size !== selected.length)
|
|
27621
28077
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_selectionInvalid", { params: { service: selected.join(", ") } }));
|
|
27622
28078
|
for (const name2 of selected) {
|
|
@@ -27641,7 +28097,9 @@ function createDockerSshProvider(config2, container) {
|
|
|
27641
28097
|
containerPort,
|
|
27642
28098
|
middlewareEnv: serializeEnvFile(sidecar.env),
|
|
27643
28099
|
healthPaths,
|
|
27644
|
-
|
|
28100
|
+
httpsAssets,
|
|
28101
|
+
output: join(artifactDir, `${name}.tar${compressed ? ".gz" : ""}`),
|
|
28102
|
+
cacheDirectory: join(resolveProjectDirectory(appDir), ".hai", "cache", "images")
|
|
27645
28103
|
});
|
|
27646
28104
|
if (options?.bundleOnly)
|
|
27647
28105
|
return ok({ url: "", deploymentId, status: "packaged", provider: "docker-ssh", healthCheck: "skipped", envVarsSet: Object.keys(env), ...bundle });
|
|
@@ -27663,7 +28121,7 @@ function createDockerSshProvider(config2, container) {
|
|
|
27663
28121
|
return uploaded;
|
|
27664
28122
|
let pendingOutput = "";
|
|
27665
28123
|
const started = await ssh.exec(
|
|
27666
|
-
`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; }`,
|
|
27667
28125
|
{ timeoutMs: 6e5, secrets: sidecar.secrets, onOutput: (output2) => {
|
|
27668
28126
|
const parts = output2.split("\n");
|
|
27669
28127
|
for (let index = 0; index < parts.length; index++) {
|
|
@@ -27672,7 +28130,7 @@ function createDockerSshProvider(config2, container) {
|
|
|
27672
28130
|
break;
|
|
27673
28131
|
const line = pendingOutput;
|
|
27674
28132
|
pendingOutput = "";
|
|
27675
|
-
const stage = /^\[(preflight|images|dependencies|start|health|complete)\]/.exec(line)?.[1];
|
|
28133
|
+
const stage = /^\[(preflight|images|dependencies|start|https|health|complete)\]/.exec(line)?.[1];
|
|
27676
28134
|
if (stage)
|
|
27677
28135
|
options?.onProgress?.(stage);
|
|
27678
28136
|
}
|
|
@@ -27705,7 +28163,7 @@ ${terminal ? deployM("deploy_remoteFinished", { params: { stage: terminal.stage,
|
|
|
27705
28163
|
logger6.info("Running health check", { url: applicationUrl });
|
|
27706
28164
|
let healthy = true;
|
|
27707
28165
|
for (const healthPath of healthPaths) {
|
|
27708
|
-
if (!await healthCheck(`${url2}${healthPath}
|
|
28166
|
+
if (!await healthCheck(`${url2}${healthPath}`, httpsAssets?.["tls/fullchain.pem"])) {
|
|
27709
28167
|
healthy = false;
|
|
27710
28168
|
break;
|
|
27711
28169
|
}
|
|
@@ -27992,7 +28450,7 @@ async function pollDeploymentStatus(token, deploymentId) {
|
|
|
27992
28450
|
if (deployment.readyState === "ERROR" || deployment.readyState === "CANCELED") {
|
|
27993
28451
|
return "error";
|
|
27994
28452
|
}
|
|
27995
|
-
await new Promise((
|
|
28453
|
+
await new Promise((resolve9) => setTimeout(resolve9, POLL_INTERVAL));
|
|
27996
28454
|
}
|
|
27997
28455
|
return "building";
|
|
27998
28456
|
}
|
|
@@ -28527,7 +28985,7 @@ var deployCredentials = {
|
|
|
28527
28985
|
saveAll: (entries) => saveCredentials(entries)
|
|
28528
28986
|
};
|
|
28529
28987
|
function mergeProvisionEnv(results, scan, options) {
|
|
28530
|
-
|
|
28988
|
+
const env = {};
|
|
28531
28989
|
const reachRequirements = scan?.middlewares?.filter((item) => item.configName === "reach") ?? [];
|
|
28532
28990
|
const reachProviders = reachRequirements.map((item) => item.config);
|
|
28533
28991
|
let hasReachOutput = false;
|
|
@@ -28542,13 +29000,16 @@ function mergeProvisionEnv(results, scan, options) {
|
|
|
28542
29000
|
});
|
|
28543
29001
|
if (targets.length) {
|
|
28544
29002
|
for (const target of targets)
|
|
28545
|
-
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 };
|
|
28546
29004
|
} else {
|
|
28547
29005
|
reachProviders.push(provider);
|
|
28548
29006
|
}
|
|
28549
29007
|
}
|
|
28550
29008
|
}
|
|
28551
|
-
|
|
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
|
+
}
|
|
28552
29013
|
}
|
|
28553
29014
|
if (hasReachOutput)
|
|
28554
29015
|
env.HAI_REACH_PROVIDERS = JSON.stringify(reachProviders);
|
|
@@ -28969,7 +29430,8 @@ async function createDeploymentPlan(appDir, input2, options = {}) {
|
|
|
28969
29430
|
}
|
|
28970
29431
|
}
|
|
28971
29432
|
const sidecar = provider.type === "docker-ssh" ? planSidecars(directory, projectName, app, provisionEnv, effective, config2.container?.s3Engine, { dbPassword: "", storagePassword: "", ldapPassword: "" }) : void 0;
|
|
28972
|
-
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));
|
|
28973
29435
|
if (gateway && artifact.primaryService === "web" && !app.isSvelteKit)
|
|
28974
29436
|
return err(HaiDeployError.CONFIG_ERROR, deployM("deploy_storageBackendRequired"));
|
|
28975
29437
|
if (gateway) {
|
|
@@ -28977,18 +29439,23 @@ async function createDeploymentPlan(appDir, input2, options = {}) {
|
|
|
28977
29439
|
if (!baseline.required)
|
|
28978
29440
|
selected.push("gateway");
|
|
28979
29441
|
}
|
|
29442
|
+
if (https?.type === "letsencrypt") {
|
|
29443
|
+
available.push("certbot");
|
|
29444
|
+
if (!baseline.required)
|
|
29445
|
+
selected.push("certbot");
|
|
29446
|
+
}
|
|
28980
29447
|
const services = Object.entries(sidecar?.composeServices ?? {}).filter(([name]) => selected.includes(name));
|
|
28981
29448
|
return ok({
|
|
28982
29449
|
projectName,
|
|
28983
29450
|
root,
|
|
28984
|
-
|
|
29451
|
+
serviceDir: directory,
|
|
28985
29452
|
environment: environment.data.environment,
|
|
28986
|
-
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 },
|
|
28987
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,
|
|
28988
29455
|
selected,
|
|
28989
29456
|
retained: available.filter((name) => !selected.includes(name)),
|
|
28990
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] : [])] : [],
|
|
28991
|
-
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"] : []],
|
|
28992
29459
|
logicalVolumes: Object.keys(sidecar?.volumes ?? {}),
|
|
28993
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) })),
|
|
28994
29461
|
configuration: { files: [join(root, "hai.json"), ...configFiles].filter(existsSync), environmentKeys: Object.keys(environment.data.values).sort() },
|
|
@@ -29250,5 +29717,5 @@ async function acquireImportLock(path, root) {
|
|
|
29250
29717
|
}
|
|
29251
29718
|
|
|
29252
29719
|
export { DEPLOY_MIDDLEWARE, DeployConfigSchema, DeploymentArtifactSchema, HaiDeployError, ProviderConfigSchema, artifactChecksum, createDeploymentPlan, deploy, describeMiddlewareBackend, external_exports, getDeployCommandState, getDeployRecovery, importDeploymentSecrets, loadDeploymentConfig, loadDeploymentEnvironment, mergeEnvironmentLayers, normalizeProjectName, readDeploymentArtifact, saveDeploymentEnvironment };
|
|
29253
|
-
//# sourceMappingURL=chunk-
|
|
29254
|
-
//# sourceMappingURL=chunk-
|
|
29720
|
+
//# sourceMappingURL=chunk-K6RWTTHD.js.map
|
|
29721
|
+
//# sourceMappingURL=chunk-K6RWTTHD.js.map
|