@or3/cloud 0.1.90 → 0.1.91
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.md +26 -4
- package/assets/compose.yaml +54 -0
- package/assets/dashboard-lifecycle.mjs +100 -96
- package/dist/cli.mjs +162 -81
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ application repository or installing its dependencies on the host.
|
|
|
5
5
|
|
|
6
6
|
## Quick start: local machine
|
|
7
7
|
|
|
8
|
-
You need Docker Engine with Docker Compose v2 and Node.js
|
|
8
|
+
You need Docker Engine with Docker Compose v2 and Node.js 24.22.1 or later in the 24.x line.
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
11
|
npx @or3/cloud init --local
|
|
@@ -26,6 +26,28 @@ Basic Auth + SQLite + filesystem storage. The CLI checks the image manifest,
|
|
|
26
26
|
source/version labels, and then writes a digest-qualified image reference; it
|
|
27
27
|
also verifies the image of the running container before committing state.
|
|
28
28
|
|
|
29
|
+
## Default credential vault
|
|
30
|
+
|
|
31
|
+
Authenticated managed deployments provision a separate `or3-vault` container
|
|
32
|
+
from the same image, using private persistent data/master-key volumes and a
|
|
33
|
+
shared local socket. The application receives only an expiring scoped token;
|
|
34
|
+
it never mounts vault data, master keys or operator credentials. Setup refuses
|
|
35
|
+
partial or lost custody instead of reinitializing. This supports one persistent
|
|
36
|
+
Docker host; it does not provide serverless storage or multi-host replication.
|
|
37
|
+
|
|
38
|
+
`NUXT_SECRETS_PROVIDER=` explicitly disables the vault for instance-billed-only
|
|
39
|
+
operation; stop before changing this setting, then start to apply it. A restart
|
|
40
|
+
alone retains the container environment. Cloud never falls back to browser credentials. Keep master-key
|
|
41
|
+
recovery copies separate from application backups. Managed snapshots/exports
|
|
42
|
+
include a checksummed encrypted `vault.backup`; application rollback preserves
|
|
43
|
+
the live vault. Disaster recovery requires an explicit fresh-key vault restore
|
|
44
|
+
and fresh service tokens. Preserve custody volumes during cleanup.
|
|
45
|
+
|
|
46
|
+
See [Cloud credentials and recovery](../../public/_documentation/cloud/secret-store.md)
|
|
47
|
+
for configuration, migration, plugin compatibility and outstanding production
|
|
48
|
+
qualification requirements. Run `bun scripts/test/run-cloud-vault-e2e.ts` from
|
|
49
|
+
the repository root for the disposable container custody/recovery drill.
|
|
50
|
+
|
|
29
51
|
## Run it publicly on a VPS
|
|
30
52
|
|
|
31
53
|
Before starting, point a domain at the VPS and allow inbound TCP ports `80`
|
|
@@ -85,9 +107,9 @@ Until the one-time handoff file is deleted, `verify` may read its initial owner
|
|
|
85
107
|
credential from that protected file; it is never restored to `.env` or Docker
|
|
86
108
|
container metadata.
|
|
87
109
|
Verification rejects cross-origin filesystem grants and unexpected methods or
|
|
88
|
-
headers, validates the storage ID before uploading,
|
|
89
|
-
requesting physical deletion, and revokes
|
|
90
|
-
check fails. When the provider explicitly requires deletion coordination,
|
|
110
|
+
headers, validates the storage ID before uploading, commits the presigned upload
|
|
111
|
+
intent, tombstones its probe before requesting physical deletion, and revokes
|
|
112
|
+
its temporary session even when a later check fails. When the provider explicitly requires deletion coordination,
|
|
91
113
|
verification reports physical cleanup as deferred; retained probe bytes remain
|
|
92
114
|
subject to the provider retention policy. Other deletion failures still fail verification.
|
|
93
115
|
|
package/assets/compose.yaml
CHANGED
|
@@ -19,10 +19,21 @@ services:
|
|
|
19
19
|
OR3_STORAGE_FS_ROOT: /data/storage
|
|
20
20
|
OR3_EXTENSIONS_ROOT: /data/extensions
|
|
21
21
|
OR3_ADMIN_DATA_DIR: /data/admin
|
|
22
|
+
NUXT_SECRETS_PROVIDER: "${NUXT_SECRETS_PROVIDER-or3-secrets}"
|
|
23
|
+
NUXT_SECRETS_SOCKET_PATH: /run/or3-secrets/api.sock
|
|
24
|
+
NUXT_SECRETS_TOKEN_FILE: /run/or3-secrets-client/service.token
|
|
25
|
+
NUXT_SECRETS_SERVICE_UID: "1000"
|
|
26
|
+
OR3_MANAGED_VAULT_CONFIG: /run/or3-secrets-client/config.json
|
|
22
27
|
ports:
|
|
23
28
|
- "127.0.0.1:${OR3_PORT}:3000"
|
|
24
29
|
volumes:
|
|
25
30
|
- or3-data:/data
|
|
31
|
+
- or3-vault-run:/run/or3-secrets:ro
|
|
32
|
+
- or3-vault-client:/run/or3-secrets-client:ro
|
|
33
|
+
depends_on:
|
|
34
|
+
or3-vault:
|
|
35
|
+
condition: service_healthy
|
|
36
|
+
required: false
|
|
26
37
|
restart: unless-stopped
|
|
27
38
|
# Leave enough time for Nuxt to close SSE responses and upstream provider
|
|
28
39
|
# connections before Docker sends SIGKILL.
|
|
@@ -69,8 +80,51 @@ services:
|
|
|
69
80
|
retries: 12
|
|
70
81
|
start_period: 30s
|
|
71
82
|
|
|
83
|
+
or3-vault:
|
|
84
|
+
image: ${OR3_IMAGE}
|
|
85
|
+
profiles: [or3-vault]
|
|
86
|
+
labels:
|
|
87
|
+
io.or3.cloud.deployment-id: ${OR3_DEPLOYMENT_ID}
|
|
88
|
+
user: "0:0"
|
|
89
|
+
entrypoint: [/nodejs/bin/node, /app/vault/entrypoint.mjs]
|
|
90
|
+
environment:
|
|
91
|
+
OR3_VAULT_NAMESPACE: "${NUXT_SECRETS_NAMESPACE:-or3}"
|
|
92
|
+
network_mode: none
|
|
93
|
+
read_only: true
|
|
94
|
+
security_opt: [no-new-privileges:true]
|
|
95
|
+
cap_drop: [ALL]
|
|
96
|
+
# The confined supervisor provisions identities and atomically renews the
|
|
97
|
+
# app-owned token. Actual vault CLI/service processes run as UID 1000.
|
|
98
|
+
cap_add: [CHOWN, FOWNER, DAC_OVERRIDE, SETUID, SETGID]
|
|
99
|
+
tmpfs: [/tmp:size=16m]
|
|
100
|
+
volumes:
|
|
101
|
+
- or3-vault-data:/vault-data
|
|
102
|
+
- or3-vault-key:/vault-key
|
|
103
|
+
- or3-vault-run:/run/or3-secrets
|
|
104
|
+
- or3-vault-client:/run/or3-secrets-client
|
|
105
|
+
restart: unless-stopped
|
|
106
|
+
stop_grace_period: 25s
|
|
107
|
+
logging:
|
|
108
|
+
driver: json-file
|
|
109
|
+
options: { max-size: "10m", max-file: "3" }
|
|
110
|
+
healthcheck:
|
|
111
|
+
test: [CMD, /nodejs/bin/node, /app/vault/entrypoint.mjs, health]
|
|
112
|
+
interval: 10s
|
|
113
|
+
timeout: 5s
|
|
114
|
+
retries: 6
|
|
115
|
+
start_period: 20s
|
|
116
|
+
|
|
72
117
|
volumes:
|
|
73
118
|
or3-data:
|
|
74
119
|
name: ${OR3_VOLUME_NAME}
|
|
75
120
|
labels:
|
|
76
121
|
io.or3.cloud.deployment-id: ${OR3_DEPLOYMENT_ID}
|
|
122
|
+
|
|
123
|
+
or3-vault-data:
|
|
124
|
+
name: ${OR3_VOLUME_NAME}-vault-data
|
|
125
|
+
or3-vault-key:
|
|
126
|
+
name: ${OR3_VOLUME_NAME}-vault-key
|
|
127
|
+
or3-vault-run:
|
|
128
|
+
name: ${OR3_VOLUME_NAME}-vault-run
|
|
129
|
+
or3-vault-client:
|
|
130
|
+
name: ${OR3_VOLUME_NAME}-vault-client
|
|
@@ -45,97 +45,21 @@ var __export = (target, all) => {
|
|
|
45
45
|
};
|
|
46
46
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
47
47
|
|
|
48
|
-
// ../../node_modules/dotenv/package.json
|
|
49
|
-
var require_package = __commonJS((exports, module) => {
|
|
50
|
-
module.exports = {
|
|
51
|
-
name: "dotenv",
|
|
52
|
-
version: "17.2.3",
|
|
53
|
-
description: "Loads environment variables from .env file",
|
|
54
|
-
main: "lib/main.js",
|
|
55
|
-
types: "lib/main.d.ts",
|
|
56
|
-
exports: {
|
|
57
|
-
".": {
|
|
58
|
-
types: "./lib/main.d.ts",
|
|
59
|
-
require: "./lib/main.js",
|
|
60
|
-
default: "./lib/main.js"
|
|
61
|
-
},
|
|
62
|
-
"./config": "./config.js",
|
|
63
|
-
"./config.js": "./config.js",
|
|
64
|
-
"./lib/env-options": "./lib/env-options.js",
|
|
65
|
-
"./lib/env-options.js": "./lib/env-options.js",
|
|
66
|
-
"./lib/cli-options": "./lib/cli-options.js",
|
|
67
|
-
"./lib/cli-options.js": "./lib/cli-options.js",
|
|
68
|
-
"./package.json": "./package.json"
|
|
69
|
-
},
|
|
70
|
-
scripts: {
|
|
71
|
-
"dts-check": "tsc --project tests/types/tsconfig.json",
|
|
72
|
-
lint: "standard",
|
|
73
|
-
pretest: "npm run lint && npm run dts-check",
|
|
74
|
-
test: "tap run tests/**/*.js --allow-empty-coverage --disable-coverage --timeout=60000",
|
|
75
|
-
"test:coverage": "tap run tests/**/*.js --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov",
|
|
76
|
-
prerelease: "npm test",
|
|
77
|
-
release: "standard-version"
|
|
78
|
-
},
|
|
79
|
-
repository: {
|
|
80
|
-
type: "git",
|
|
81
|
-
url: "git://github.com/motdotla/dotenv.git"
|
|
82
|
-
},
|
|
83
|
-
homepage: "https://github.com/motdotla/dotenv#readme",
|
|
84
|
-
funding: "https://dotenvx.com",
|
|
85
|
-
keywords: [
|
|
86
|
-
"dotenv",
|
|
87
|
-
"env",
|
|
88
|
-
".env",
|
|
89
|
-
"environment",
|
|
90
|
-
"variables",
|
|
91
|
-
"config",
|
|
92
|
-
"settings"
|
|
93
|
-
],
|
|
94
|
-
readmeFilename: "README.md",
|
|
95
|
-
license: "BSD-2-Clause",
|
|
96
|
-
devDependencies: {
|
|
97
|
-
"@types/node": "^18.11.3",
|
|
98
|
-
decache: "^4.6.2",
|
|
99
|
-
sinon: "^14.0.1",
|
|
100
|
-
standard: "^17.0.0",
|
|
101
|
-
"standard-version": "^9.5.0",
|
|
102
|
-
tap: "^19.2.0",
|
|
103
|
-
typescript: "^4.8.4"
|
|
104
|
-
},
|
|
105
|
-
engines: {
|
|
106
|
-
node: ">=12"
|
|
107
|
-
},
|
|
108
|
-
browser: {
|
|
109
|
-
fs: false
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
});
|
|
113
|
-
|
|
114
48
|
// ../../node_modules/dotenv/lib/main.js
|
|
115
49
|
var require_main = __commonJS((exports, module) => {
|
|
116
50
|
var fs = __require("fs");
|
|
117
51
|
var path = __require("path");
|
|
118
52
|
var os = __require("os");
|
|
119
53
|
var crypto = __require("crypto");
|
|
120
|
-
var packageJson = require_package();
|
|
121
|
-
var version = packageJson.version;
|
|
122
54
|
var TIPS = [
|
|
123
|
-
"
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"\uD83D\uDD11 add access controls to secrets: https://dotenvx.com/ops",
|
|
132
|
-
"\uD83D\uDEE0️ run anywhere with `dotenvx run -- yourcommand`",
|
|
133
|
-
"⚙️ specify custom .env file path with { path: '/custom/path/.env' }",
|
|
134
|
-
"⚙️ enable debug logging with { debug: true }",
|
|
135
|
-
"⚙️ override existing env vars with { override: true }",
|
|
136
|
-
"⚙️ suppress all logs with { quiet: true }",
|
|
137
|
-
"⚙️ write to custom object with { processEnv: myObject }",
|
|
138
|
-
"⚙️ load multiple .env files with { path: ['.env.local', '.env'] }"
|
|
55
|
+
"◈ encrypted .env [www.dotenvx.com]",
|
|
56
|
+
"◈ secrets for agents [www.dotenvx.com]",
|
|
57
|
+
"⌁ auth for agents [www.vestauth.com]",
|
|
58
|
+
"⌘ custom filepath { path: '/custom/path/.env' }",
|
|
59
|
+
"⌘ enable debugging { debug: true }",
|
|
60
|
+
"⌘ override existing { override: true }",
|
|
61
|
+
"⌘ suppress logs { quiet: true }",
|
|
62
|
+
"⌘ multiple files { path: ['.env.local', '.env'] }"
|
|
139
63
|
];
|
|
140
64
|
function _getRandomTip() {
|
|
141
65
|
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
@@ -202,13 +126,13 @@ var require_main = __commonJS((exports, module) => {
|
|
|
202
126
|
return DotenvModule.parse(decrypted);
|
|
203
127
|
}
|
|
204
128
|
function _warn(message) {
|
|
205
|
-
console.error(
|
|
129
|
+
console.error(`⚠ ${message}`);
|
|
206
130
|
}
|
|
207
131
|
function _debug(message) {
|
|
208
|
-
console.log(
|
|
132
|
+
console.log(`┆ ${message}`);
|
|
209
133
|
}
|
|
210
134
|
function _log(message) {
|
|
211
|
-
console.log(
|
|
135
|
+
console.log(`◇ ${message}`);
|
|
212
136
|
}
|
|
213
137
|
function _dotenvKey(options) {
|
|
214
138
|
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
|
|
@@ -279,7 +203,7 @@ var require_main = __commonJS((exports, module) => {
|
|
|
279
203
|
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
280
204
|
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
281
205
|
if (debug || !quiet) {
|
|
282
|
-
_log("
|
|
206
|
+
_log("loading env from encrypted .env.vault");
|
|
283
207
|
}
|
|
284
208
|
const parsed = DotenvModule._parseVault(options);
|
|
285
209
|
let processEnv = process.env;
|
|
@@ -302,7 +226,7 @@ var require_main = __commonJS((exports, module) => {
|
|
|
302
226
|
encoding = options.encoding;
|
|
303
227
|
} else {
|
|
304
228
|
if (debug) {
|
|
305
|
-
_debug("
|
|
229
|
+
_debug("no encoding is specified (UTF-8 is used by default)");
|
|
306
230
|
}
|
|
307
231
|
}
|
|
308
232
|
let optionPaths = [dotenvPath];
|
|
@@ -324,7 +248,7 @@ var require_main = __commonJS((exports, module) => {
|
|
|
324
248
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
325
249
|
} catch (e) {
|
|
326
250
|
if (debug) {
|
|
327
|
-
_debug(`
|
|
251
|
+
_debug(`failed to load ${path2} ${e.message}`);
|
|
328
252
|
}
|
|
329
253
|
lastError = e;
|
|
330
254
|
}
|
|
@@ -341,12 +265,12 @@ var require_main = __commonJS((exports, module) => {
|
|
|
341
265
|
shortPaths.push(relative);
|
|
342
266
|
} catch (e) {
|
|
343
267
|
if (debug) {
|
|
344
|
-
_debug(`
|
|
268
|
+
_debug(`failed to load ${filePath} ${e.message}`);
|
|
345
269
|
}
|
|
346
270
|
lastError = e;
|
|
347
271
|
}
|
|
348
272
|
}
|
|
349
|
-
_log(`
|
|
273
|
+
_log(`injected env (${keysCount}) from ${shortPaths.join(",")} ${dim(`// tip: ${_getRandomTip()}`)}`);
|
|
350
274
|
}
|
|
351
275
|
if (lastError) {
|
|
352
276
|
return { parsed: parsedAll, error: lastError };
|
|
@@ -360,7 +284,7 @@ var require_main = __commonJS((exports, module) => {
|
|
|
360
284
|
}
|
|
361
285
|
const vaultPath = _vaultPath(options);
|
|
362
286
|
if (!vaultPath) {
|
|
363
|
-
_warn(`
|
|
287
|
+
_warn(`you set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}`);
|
|
364
288
|
return DotenvModule.configDotenv(options);
|
|
365
289
|
}
|
|
366
290
|
return DotenvModule._configVault(options);
|
|
@@ -1035,6 +959,7 @@ __export(exports_util, {
|
|
|
1035
959
|
toZod: () => toZod,
|
|
1036
960
|
stringifyPrimitive: () => stringifyPrimitive,
|
|
1037
961
|
slugify: () => slugify,
|
|
962
|
+
sizeCaps: () => sizeCaps,
|
|
1038
963
|
shallowClone: () => shallowClone,
|
|
1039
964
|
safeExtend: () => safeExtend,
|
|
1040
965
|
required: () => required,
|
|
@@ -1609,6 +1534,18 @@ function prefixIssues(path, issues) {
|
|
|
1609
1534
|
function unwrapMessage(message) {
|
|
1610
1535
|
return typeof message === "string" ? message : message?.message;
|
|
1611
1536
|
}
|
|
1537
|
+
function sizeCaps(defChecks) {
|
|
1538
|
+
const caps = [];
|
|
1539
|
+
for (const ch of defChecks ?? []) {
|
|
1540
|
+
const d = ch._zod.def;
|
|
1541
|
+
if (!/_(length|size)$|_equals$/.test(d.check))
|
|
1542
|
+
break;
|
|
1543
|
+
const max = d.maximum ?? d.length ?? d.size;
|
|
1544
|
+
if (max >= 0)
|
|
1545
|
+
caps.push([max, ch]);
|
|
1546
|
+
}
|
|
1547
|
+
return caps;
|
|
1548
|
+
}
|
|
1612
1549
|
function attachSchema(issues, start, inst) {
|
|
1613
1550
|
var _a;
|
|
1614
1551
|
for (let i = start;i < issues.length; i++) {
|
|
@@ -3028,7 +2965,7 @@ ${content.join(`
|
|
|
3028
2965
|
var version = {
|
|
3029
2966
|
major: 4,
|
|
3030
2967
|
minor: 6,
|
|
3031
|
-
patch:
|
|
2968
|
+
patch: 7
|
|
3032
2969
|
};
|
|
3033
2970
|
|
|
3034
2971
|
// ../../node_modules/zod/v4/core/schemas.js
|
|
@@ -3777,6 +3714,31 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
|
|
|
3777
3714
|
return payload;
|
|
3778
3715
|
};
|
|
3779
3716
|
});
|
|
3717
|
+
function capSize(inst, size) {
|
|
3718
|
+
const caps = sizeCaps(inst._zod.def.checks);
|
|
3719
|
+
if (!caps.length)
|
|
3720
|
+
return;
|
|
3721
|
+
const cap = Math.min(...caps.map((c) => c[0]));
|
|
3722
|
+
const parse2 = inst._zod.parse;
|
|
3723
|
+
inst._zod.parse = (payload, ctx) => {
|
|
3724
|
+
const n = size(payload.value);
|
|
3725
|
+
if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
|
|
3726
|
+
for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
|
|
3727
|
+
if (n > max && ch._zod.def.when(payload)) {
|
|
3728
|
+
const start = payload.issues.length;
|
|
3729
|
+
ch._zod.check(payload);
|
|
3730
|
+
payload.issues[start].continue = false;
|
|
3731
|
+
attachSchema(payload.issues, start, inst);
|
|
3732
|
+
return payload;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
return parse2(payload, ctx);
|
|
3737
|
+
};
|
|
3738
|
+
}
|
|
3739
|
+
var arraySize = (input) => Array.isArray(input) ? input.length : -1;
|
|
3740
|
+
var mapSize = (input) => input instanceof Map ? input.size : -1;
|
|
3741
|
+
var setSize = (input) => input instanceof Set ? input.size : -1;
|
|
3780
3742
|
function handleArrayResult(result, final, index) {
|
|
3781
3743
|
if (result.issues.length) {
|
|
3782
3744
|
final.issues.push(...prefixIssues(index, result.issues));
|
|
@@ -3820,6 +3782,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
|
|
|
3820
3782
|
}
|
|
3821
3783
|
return payload;
|
|
3822
3784
|
};
|
|
3785
|
+
capSize(inst, arraySize);
|
|
3823
3786
|
});
|
|
3824
3787
|
function handlePropertyResult(result, final, key, input, optin, optout) {
|
|
3825
3788
|
const isPresent = key in input;
|
|
@@ -4774,6 +4737,7 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
|
|
|
4774
4737
|
return Promise.all(proms).then(() => payload);
|
|
4775
4738
|
return payload;
|
|
4776
4739
|
};
|
|
4740
|
+
capSize(inst, mapSize);
|
|
4777
4741
|
});
|
|
4778
4742
|
function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
|
|
4779
4743
|
if (keyResult.issues.length) {
|
|
@@ -4840,6 +4804,7 @@ var $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
|
|
|
4840
4804
|
return Promise.all(proms).then(() => payload);
|
|
4841
4805
|
return payload;
|
|
4842
4806
|
};
|
|
4807
|
+
capSize(inst, setSize);
|
|
4843
4808
|
});
|
|
4844
4809
|
function handleSetResult(result, final) {
|
|
4845
4810
|
if (result.issues.length) {
|
|
@@ -14289,9 +14254,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
14289
14254
|
doc.write(`}`);
|
|
14290
14255
|
return outputVar;
|
|
14291
14256
|
}
|
|
14257
|
+
function generateSizeGate(doc, schema, size) {
|
|
14258
|
+
for (const [max, check] of sizeCaps(schema._zod.def.checks)) {
|
|
14259
|
+
doc.write(`if (${size} > ${numericOperand(max, check._zod.def.check)}) return INVALID;`);
|
|
14260
|
+
}
|
|
14261
|
+
}
|
|
14292
14262
|
function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
14293
14263
|
const def = schema._zod.def;
|
|
14294
14264
|
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
14265
|
+
generateSizeGate(doc, schema, `${accessor}.length`);
|
|
14295
14266
|
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
14296
14267
|
const iVar = newVar(ctx);
|
|
14297
14268
|
const elemVar = newVar(ctx);
|
|
@@ -14691,6 +14662,7 @@ function literalPropertyKey(ctx, key) {
|
|
|
14691
14662
|
function generateMapCheck(doc, ctx, schema, accessor) {
|
|
14692
14663
|
const def = schema._zod.def;
|
|
14693
14664
|
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
14665
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
14694
14666
|
const outputVar = newVar(ctx);
|
|
14695
14667
|
const kVar = newVar(ctx);
|
|
14696
14668
|
const valVar = newVar(ctx);
|
|
@@ -14707,6 +14679,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
|
|
|
14707
14679
|
function generateSetCheck(doc, ctx, schema, accessor) {
|
|
14708
14680
|
const def = schema._zod.def;
|
|
14709
14681
|
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
14682
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
14710
14683
|
const outputVar = newVar(ctx);
|
|
14711
14684
|
const valVar = newVar(ctx);
|
|
14712
14685
|
doc.write(`const ${outputVar} = new Set();`);
|
|
@@ -20053,7 +20026,7 @@ class RestartController {
|
|
|
20053
20026
|
const timer = setTimeout(() => {
|
|
20054
20027
|
this.cancel(request.requestId).catch(() => {});
|
|
20055
20028
|
}, 330000);
|
|
20056
|
-
timer.unref
|
|
20029
|
+
timer.unref();
|
|
20057
20030
|
this.preparationTimers.set(request.requestId, timer);
|
|
20058
20031
|
return publicOperation(o);
|
|
20059
20032
|
});
|
|
@@ -20380,6 +20353,30 @@ async function writeEnvFileDetailed(updates, options = {}) {
|
|
|
20380
20353
|
}
|
|
20381
20354
|
|
|
20382
20355
|
// ../../shared/admin/configuration.ts
|
|
20356
|
+
var siteSettingPaths = {
|
|
20357
|
+
OR3_SITE_NAME: ["or3", "site", "name"],
|
|
20358
|
+
OR3_SITE_DESCRIPTION: ["or3", "site", "description"],
|
|
20359
|
+
OR3_LOGO_URL: ["or3", "site", "logoUrl"],
|
|
20360
|
+
OR3_FAVICON_URL: ["or3", "site", "faviconUrl"],
|
|
20361
|
+
OR3_DEFAULT_THEME: ["or3", "site", "defaultTheme"],
|
|
20362
|
+
OR3_DISABLED_THEMES: ["or3", "site", "disabledThemes"],
|
|
20363
|
+
OR3_DEFAULT_PANE_COUNT: ["or3", "ui", "defaultPaneCount"],
|
|
20364
|
+
OR3_MAX_PANES: ["or3", "ui", "maxPanes"],
|
|
20365
|
+
OR3_SIDEBAR_COLLAPSED: ["or3", "ui", "sidebarCollapsedByDefault"],
|
|
20366
|
+
OR3_TERMS_URL: ["or3", "legal", "termsUrl"],
|
|
20367
|
+
OR3_PRIVACY_URL: ["or3", "legal", "privacyUrl"],
|
|
20368
|
+
OR3_WORKFLOWS_ENABLED: ["features", "workflows", "enabled"],
|
|
20369
|
+
OR3_WORKFLOWS_EDITOR: ["features", "workflows", "editor"],
|
|
20370
|
+
OR3_WORKFLOWS_SLASH_COMMANDS: ["features", "workflows", "slashCommands"],
|
|
20371
|
+
OR3_WORKFLOWS_EXECUTION: ["features", "workflows", "execution"],
|
|
20372
|
+
OR3_DOCUMENTS_ENABLED: ["features", "documents", "enabled"],
|
|
20373
|
+
OR3_BACKUP_ENABLED: ["features", "backup", "enabled"],
|
|
20374
|
+
OR3_MENTIONS_ENABLED: ["features", "mentions", "enabled"],
|
|
20375
|
+
OR3_MENTIONS_DOCUMENTS: ["features", "mentions", "documents"],
|
|
20376
|
+
OR3_MENTIONS_CONVERSATIONS: ["features", "mentions", "conversations"],
|
|
20377
|
+
OR3_DASHBOARD_ENABLED: ["features", "dashboard", "enabled"]
|
|
20378
|
+
};
|
|
20379
|
+
var siteSettingKeys = new Set(Object.keys(siteSettingPaths));
|
|
20383
20380
|
var restartSettingKeys = new Set(["OR3_AUTH_REGISTRATION_MODE", "OR3_ADMIN_ALLOW_RESTART"]);
|
|
20384
20381
|
|
|
20385
20382
|
// ../../shared/server/restarts/configuration.ts
|
|
@@ -20823,6 +20820,12 @@ var SECRET_KEYS = [
|
|
|
20823
20820
|
];
|
|
20824
20821
|
var ALLOWED_ENV_KEYS = new Set([
|
|
20825
20822
|
"SSR_AUTH_ENABLED",
|
|
20823
|
+
"NUXT_SECRETS_PROVIDER",
|
|
20824
|
+
"NUXT_SECRETS_NAMESPACE",
|
|
20825
|
+
"NUXT_SECRETS_SOCKET_PATH",
|
|
20826
|
+
"NUXT_SECRETS_TOKEN_FILE",
|
|
20827
|
+
"NUXT_SECRETS_VAULT_ID",
|
|
20828
|
+
"NUXT_SECRETS_SERVICE_UID",
|
|
20826
20829
|
"AUTH_PROVIDER",
|
|
20827
20830
|
"OR3_AUTH_PROVIDER",
|
|
20828
20831
|
"OR3_AUTH_REGISTRATION_MODE",
|
|
@@ -21054,6 +21057,7 @@ function composeArgs(directory, mode, command = []) {
|
|
|
21054
21057
|
}
|
|
21055
21058
|
return [
|
|
21056
21059
|
"compose",
|
|
21060
|
+
...env.SSR_AUTH_ENABLED === "true" && (env.NUXT_SECRETS_PROVIDER ?? "or3-secrets") === "or3-secrets" ? ["--profile", "or3-vault"] : [],
|
|
21057
21061
|
"--project-name",
|
|
21058
21062
|
project,
|
|
21059
21063
|
"--project-directory",
|
|
@@ -21329,7 +21333,7 @@ async function createManagedLifecycle(directory) {
|
|
|
21329
21333
|
Config: exports_external.object({ Labels: exports_external.record(exports_external.string(), exports_external.string()).nullish() }).passthrough(),
|
|
21330
21334
|
Mounts: exports_external.array(exports_external.object({ Destination: exports_external.string(), Name: exports_external.string().optional(), Type: exports_external.string() }).passthrough())
|
|
21331
21335
|
}).passthrough()).nonempty().parse(JSON.parse(result.stdout))[0];
|
|
21332
|
-
if (info.Config
|
|
21336
|
+
if (info.Config.Labels?.["io.or3.cloud.deployment-id"] !== initial.state.deploymentId || !info.Mounts.some((mount) => mount.Destination === "/data" && mount.Type === "volume" && mount.Name === initial.state.volumeName))
|
|
21333
21337
|
throw new RestartError("deployment_identity_changed");
|
|
21334
21338
|
return { loaded, imageId: String(info.Image) };
|
|
21335
21339
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -132,7 +132,7 @@ import { basename as basename7, resolve as resolve16 } from "node:path";
|
|
|
132
132
|
|
|
133
133
|
// src/commands/adopt.ts
|
|
134
134
|
import { chmod as chmod5, mkdir as mkdir5 } from "node:fs/promises";
|
|
135
|
-
import { basename as basename3, join as
|
|
135
|
+
import { basename as basename3, join as join13, resolve as resolve7 } from "node:path";
|
|
136
136
|
|
|
137
137
|
// src/backup/archive.ts
|
|
138
138
|
import { createReadStream as createReadStream3 } from "node:fs";
|
|
@@ -179,7 +179,7 @@ import { readFileSync } from "node:fs";
|
|
|
179
179
|
import { join, resolve } from "node:path";
|
|
180
180
|
import { fileURLToPath } from "node:url";
|
|
181
181
|
var PACKAGE_ROOT = resolve(fileURLToPath(new URL("../", import.meta.url)));
|
|
182
|
-
var PACKAGE_VERSION = "0.1.
|
|
182
|
+
var PACKAGE_VERSION = "0.1.91";
|
|
183
183
|
var IMAGE_REPOSITORY = "ghcr.io/saluana/or3-chat";
|
|
184
184
|
var ASSET_ROOT = resolve(fileURLToPath(new URL("../assets/", import.meta.url)));
|
|
185
185
|
var SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -371,6 +371,12 @@ var SECRET_KEYS = [
|
|
|
371
371
|
];
|
|
372
372
|
var ALLOWED_ENV_KEYS = new Set([
|
|
373
373
|
"SSR_AUTH_ENABLED",
|
|
374
|
+
"NUXT_SECRETS_PROVIDER",
|
|
375
|
+
"NUXT_SECRETS_NAMESPACE",
|
|
376
|
+
"NUXT_SECRETS_SOCKET_PATH",
|
|
377
|
+
"NUXT_SECRETS_TOKEN_FILE",
|
|
378
|
+
"NUXT_SECRETS_VAULT_ID",
|
|
379
|
+
"NUXT_SECRETS_SERVICE_UID",
|
|
374
380
|
"AUTH_PROVIDER",
|
|
375
381
|
"OR3_AUTH_PROVIDER",
|
|
376
382
|
"OR3_AUTH_REGISTRATION_MODE",
|
|
@@ -1052,6 +1058,7 @@ function composeArgs(directory, mode, command = []) {
|
|
|
1052
1058
|
}
|
|
1053
1059
|
return [
|
|
1054
1060
|
"compose",
|
|
1061
|
+
...env.SSR_AUTH_ENABLED === "true" && (env.NUXT_SECRETS_PROVIDER ?? "or3-secrets") === "or3-secrets" ? ["--profile", "or3-vault"] : [],
|
|
1055
1062
|
"--project-name",
|
|
1056
1063
|
project,
|
|
1057
1064
|
"--project-directory",
|
|
@@ -1923,7 +1930,7 @@ async function inspectBackupEntry(backupsRoot, entry, directory) {
|
|
|
1923
1930
|
};
|
|
1924
1931
|
}
|
|
1925
1932
|
let bytes = 0;
|
|
1926
|
-
for (const file of ["data.tgz", "config.env", "manifest.json", "manifest.auth"]) {
|
|
1933
|
+
for (const file of ["data.tgz", "config.env", "manifest.json", "manifest.auth", ...manifest2.vaultSha256 ? ["vault.backup"] : []]) {
|
|
1927
1934
|
bytes += (await lstat(join9(path, file))).size;
|
|
1928
1935
|
}
|
|
1929
1936
|
return {
|
|
@@ -2035,7 +2042,7 @@ async function readManifest(backupPath, authenticatedForDirectory) {
|
|
|
2035
2042
|
if (authenticatedForDirectory)
|
|
2036
2043
|
await assertBackupAuthentication(authenticatedForDirectory, backupPath, manifestContents);
|
|
2037
2044
|
const manifest2 = JSON.parse(manifestContents);
|
|
2038
|
-
if (manifest2.schemaVersion !== 1 || !BACKUP_ID_PATTERN.test(manifest2.backupId) || !Number.isFinite(Date.parse(manifest2.createdAt)) || !isVersion(manifest2.appVersion) || !manifest2.image || !/^sha256:[0-9a-f]{64}$/i.test(manifest2.imageDigest) || !/^[0-9a-f]{64}$/i.test(manifest2.dataSha256) || !/^[0-9a-f]{64}$/i.test(manifest2.configSha256 ?? "") || manifest2.dataBytes !== undefined && (!Number.isSafeInteger(manifest2.dataBytes) || manifest2.dataBytes < 0)) {
|
|
2045
|
+
if (manifest2.schemaVersion !== 1 || !BACKUP_ID_PATTERN.test(manifest2.backupId) || !Number.isFinite(Date.parse(manifest2.createdAt)) || !isVersion(manifest2.appVersion) || !manifest2.image || !/^sha256:[0-9a-f]{64}$/i.test(manifest2.imageDigest) || !/^[0-9a-f]{64}$/i.test(manifest2.dataSha256) || !/^[0-9a-f]{64}$/i.test(manifest2.configSha256 ?? "") || manifest2.vaultSha256 !== undefined && !/^[0-9a-f]{64}$/i.test(manifest2.vaultSha256) || manifest2.dataBytes !== undefined && (!Number.isSafeInteger(manifest2.dataBytes) || manifest2.dataBytes < 0)) {
|
|
2039
2046
|
throw new Error(`Invalid backup manifest at ${backupPath}.`);
|
|
2040
2047
|
}
|
|
2041
2048
|
const actual = await sha256File(join9(backupPath, "data.tgz"));
|
|
@@ -2046,6 +2053,8 @@ async function readManifest(backupPath, authenticatedForDirectory) {
|
|
|
2046
2053
|
if (configActual !== manifest2.configSha256)
|
|
2047
2054
|
throw new Error(`Backup configuration checksum mismatch for ${manifest2.backupId}.`);
|
|
2048
2055
|
}
|
|
2056
|
+
if (manifest2.vaultSha256 && await sha256File(join9(backupPath, "vault.backup")) !== manifest2.vaultSha256)
|
|
2057
|
+
throw new Error(`Vault backup checksum mismatch for ${manifest2.backupId}.`);
|
|
2049
2058
|
await verifiedManagedAssetContents(backupPath, manifest2);
|
|
2050
2059
|
return manifest2;
|
|
2051
2060
|
}
|
|
@@ -2748,8 +2757,16 @@ async function checkPublicPrerequisites(domain) {
|
|
|
2748
2757
|
}
|
|
2749
2758
|
|
|
2750
2759
|
// src/runtime/project.ts
|
|
2760
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
2761
|
+
import { join as join12 } from "node:path";
|
|
2762
|
+
function managedVaultEnabled(directory, env) {
|
|
2763
|
+
const selected = env ?? parseEnv(readFileSync5(join12(directory, ".env"), "utf8"));
|
|
2764
|
+
return selected.SSR_AUTH_ENABLED === "true" && (selected.NUXT_SECRETS_PROVIDER ?? "or3-secrets") === "or3-secrets" && readFileSync5(join12(directory, "compose.yaml"), "utf8").includes(`
|
|
2765
|
+
or3-vault:
|
|
2766
|
+
`);
|
|
2767
|
+
}
|
|
2751
2768
|
async function stopProject(directory, mode) {
|
|
2752
|
-
await compose(directory, mode, ["stop", "or3"]);
|
|
2769
|
+
await compose(directory, mode, ["stop", "or3", ...managedVaultEnabled(directory) ? ["or3-vault"] : []]);
|
|
2753
2770
|
}
|
|
2754
2771
|
async function projectServiceRunning(directory, mode) {
|
|
2755
2772
|
const result = await run("docker", composeArgs(directory, mode, ["ps", "--status", "running", "-q", "or3"]), directory);
|
|
@@ -2898,6 +2915,7 @@ async function verifyPublicApplication(baseUrl, credentials) {
|
|
|
2898
2915
|
workspace_id: workspaceId,
|
|
2899
2916
|
hash,
|
|
2900
2917
|
storage_id: storageId,
|
|
2918
|
+
intent_id: presign.intentId,
|
|
2901
2919
|
storage_provider_id: "fs",
|
|
2902
2920
|
mime_type: "image/png",
|
|
2903
2921
|
size_bytes: probeBytes.length,
|
|
@@ -3056,31 +3074,31 @@ async function adoptCommand(positionals, flags) {
|
|
|
3056
3074
|
if (targetDirectory === sourceDirectory)
|
|
3057
3075
|
throw new Error("Choose a separate target directory for adoption.");
|
|
3058
3076
|
await readDirectoryEmpty(targetDirectory);
|
|
3059
|
-
if (!await fileExists(
|
|
3077
|
+
if (!await fileExists(join13(sourceDirectory, "or3-release.json")))
|
|
3060
3078
|
throw new Error(`V1 release metadata is missing at ${sourceDirectory}/or3-release.json.`);
|
|
3061
|
-
const release = JSON.parse(await readText(
|
|
3079
|
+
const release = JSON.parse(await readText(join13(sourceDirectory, "or3-release.json")));
|
|
3062
3080
|
if (!release.or3Version || !isVersion(release.or3Version))
|
|
3063
3081
|
throw new Error("V1 release metadata does not contain a usable OR3 version.");
|
|
3064
3082
|
if (release.or3Version !== PACKAGE_VERSION) {
|
|
3065
3083
|
throw new Error(`V1 is on OR3 ${release.or3Version}, but this CLI ships managed assets for ${PACKAGE_VERSION}. Run the matching @or3/cloud version or perform a qualified host CLI update before adoption.`);
|
|
3066
3084
|
}
|
|
3067
|
-
const sourceEnv = parseEnv(await readText(
|
|
3068
|
-
const providers = await readText(
|
|
3085
|
+
const sourceEnv = parseEnv(await readText(join13(sourceDirectory, ".env")));
|
|
3086
|
+
const providers = await readText(join13(sourceDirectory, "or3.providers.generated.ts"));
|
|
3069
3087
|
assertSupportedSource(sourceDirectory, sourceEnv, providers);
|
|
3070
3088
|
const mode = sourceMode(sourceDirectory, sourceEnv);
|
|
3071
3089
|
const domain = mode === "public" ? sourceEnv.OR3_PUBLIC_DOMAIN : undefined;
|
|
3072
3090
|
if (domain)
|
|
3073
3091
|
await checkPublicPrerequisites(domain);
|
|
3074
|
-
const sourceComposeFiles = ["-f",
|
|
3092
|
+
const sourceComposeFiles = ["-f", join13(sourceDirectory, "compose.yaml")];
|
|
3075
3093
|
if (mode === "public")
|
|
3076
|
-
sourceComposeFiles.push("-f",
|
|
3094
|
+
sourceComposeFiles.push("-f", join13(sourceDirectory, "compose.public.yaml"));
|
|
3077
3095
|
await ensureDocker();
|
|
3078
3096
|
const sourceConfig = await run("docker", [
|
|
3079
3097
|
"compose",
|
|
3080
3098
|
"--project-directory",
|
|
3081
3099
|
sourceDirectory,
|
|
3082
3100
|
"--env-file",
|
|
3083
|
-
|
|
3101
|
+
join13(sourceDirectory, ".env"),
|
|
3084
3102
|
...sourceComposeFiles,
|
|
3085
3103
|
"config",
|
|
3086
3104
|
"--format",
|
|
@@ -3167,7 +3185,7 @@ ${existing.stderr}`)) {
|
|
|
3167
3185
|
OR3_GUEST_ACCESS_ENABLED: "false"
|
|
3168
3186
|
});
|
|
3169
3187
|
await writeSecure(deploymentPaths(targetDirectory).env, serializeEnv(targetEnv));
|
|
3170
|
-
await writeSecure(
|
|
3188
|
+
await writeSecure(join13(targetDirectory, ".or3-initial-credentials"), serializeInitialCredentials({
|
|
3171
3189
|
bootstrapEmail: email,
|
|
3172
3190
|
bootstrapPassword: password,
|
|
3173
3191
|
adminUsername: targetEnv.OR3_ADMIN_USERNAME,
|
|
@@ -3203,7 +3221,7 @@ ${existing.stderr}`)) {
|
|
|
3203
3221
|
}
|
|
3204
3222
|
}
|
|
3205
3223
|
await mkdir5(sourceBackupDir, { recursive: true, mode: 448 });
|
|
3206
|
-
await copySecure(
|
|
3224
|
+
await copySecure(join13(sourceDirectory, ".env"), join13(sourceBackupDir, "config.env"));
|
|
3207
3225
|
await archiveExternalVolume(sourceImage, sourceVolume, sourceBackupDir);
|
|
3208
3226
|
const sourceManifest = {
|
|
3209
3227
|
schemaVersion: 1,
|
|
@@ -3212,8 +3230,8 @@ ${existing.stderr}`)) {
|
|
|
3212
3230
|
appVersion: release.or3Version,
|
|
3213
3231
|
image,
|
|
3214
3232
|
imageDigest: digest,
|
|
3215
|
-
dataSha256: await sha256File(
|
|
3216
|
-
configSha256: await sha256File(
|
|
3233
|
+
dataSha256: await sha256File(join13(sourceBackupDir, "data.tgz")),
|
|
3234
|
+
configSha256: await sha256File(join13(sourceBackupDir, "config.env")),
|
|
3217
3235
|
mode,
|
|
3218
3236
|
domain,
|
|
3219
3237
|
composeProject: sourceEnv.OR3_COMPOSE_PROJECT ?? sanitizeName(basename3(sourceDirectory)),
|
|
@@ -3224,7 +3242,7 @@ ${existing.stderr}`)) {
|
|
|
3224
3242
|
};
|
|
3225
3243
|
const sourceManifestContents = `${JSON.stringify(sourceManifest, null, 2)}
|
|
3226
3244
|
`;
|
|
3227
|
-
await writeSecure(
|
|
3245
|
+
await writeSecure(join13(sourceBackupDir, "manifest.json"), sourceManifestContents);
|
|
3228
3246
|
await writeBackupAuthentication(targetDirectory, sourceBackupDir, sourceManifestContents);
|
|
3229
3247
|
await readManifest(sourceBackupDir, targetDirectory);
|
|
3230
3248
|
await updatePending(targetDirectory, state, { phase: "snapshot-created" });
|
|
@@ -3260,11 +3278,11 @@ ${sourceRecoveryError ? "The original V1 deployment needs manual recovery. " : "
|
|
|
3260
3278
|
|
|
3261
3279
|
// src/backup/create.ts
|
|
3262
3280
|
import { chmod as chmod6, lstat as lstat2, mkdir as mkdir6 } from "node:fs/promises";
|
|
3263
|
-
import { join as
|
|
3281
|
+
import { join as join16, resolve as resolve9 } from "node:path";
|
|
3264
3282
|
|
|
3265
3283
|
// src/backup/references.ts
|
|
3266
3284
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3267
|
-
import { join as
|
|
3285
|
+
import { join as join14 } from "node:path";
|
|
3268
3286
|
var INSPECTOR = "/app/scripts/docker/backup-inspect.mjs";
|
|
3269
3287
|
var NODE = 'if [ -x /nodejs/bin/node ]; then exec /nodejs/bin/node "$@"; else exec node "$@"; fi';
|
|
3270
3288
|
async function validateCanonicalBackupReferences(directory, env, backupPath) {
|
|
@@ -3309,7 +3327,7 @@ async function validateCanonicalBackupReferences(directory, env, backupPath) {
|
|
|
3309
3327
|
"-c",
|
|
3310
3328
|
"sleep 240"
|
|
3311
3329
|
]);
|
|
3312
|
-
await streamFileToCommand("docker", ["exec", "-i", helper, "sh", "-c", "ulimit -f 16384; tar tvzf - > /scratch/inventory"],
|
|
3330
|
+
await streamFileToCommand("docker", ["exec", "-i", helper, "sh", "-c", "ulimit -f 16384; tar tvzf - > /scratch/inventory"], join14(backupPath, "data.tgz"), directory, secretValues(env));
|
|
3313
3331
|
const inventory = JSON.parse(await checked(["exec", helper, "sh", "-c", NODE, "or3-backup-inspection", INSPECTOR, "inventory"]));
|
|
3314
3332
|
if (!Array.isArray(inventory.selected) || !inventory.selected.includes("./sync.sqlite") && !inventory.selected.includes("sync.sqlite") || inventory.selected.some((name) => !/^(?:\.\/)?sync[.]sqlite(?:-wal)?$/.test(name)) || inventory.selected.length > 2 || !Number.isSafeInteger(inventory.bytes) || inventory.bytes < 0)
|
|
3315
3333
|
throw new Error("Invalid offline backup inspection inventory response.");
|
|
@@ -3317,7 +3335,7 @@ async function validateCanonicalBackupReferences(directory, env, backupPath) {
|
|
|
3317
3335
|
if (!Number.isFinite(freeKiB) || freeKiB < 0)
|
|
3318
3336
|
throw new Error("Could not verify offline backup scratch capacity.");
|
|
3319
3337
|
assertEnoughFreeSpace(freeKiB * 1024, inventory.bytes, "Offline backup inspection scratch");
|
|
3320
|
-
await streamFileToCommand("docker", ["exec", "-i", helper, "tar", "-xz", "-f", "-", "-C", "/scratch", ...inventory.selected],
|
|
3338
|
+
await streamFileToCommand("docker", ["exec", "-i", helper, "tar", "-xz", "-f", "-", "-C", "/scratch", ...inventory.selected], join14(backupPath, "data.tgz"), directory, secretValues(env));
|
|
3321
3339
|
const result = JSON.parse(await checked(["exec", helper, "sh", "-c", NODE, "or3-backup-inspection", INSPECTOR, "references", env.OR3_STORAGE_FS_ROOT ?? "/data/storage"]));
|
|
3322
3340
|
if (result.contract !== 1 || result.complete !== true || !Number.isSafeInteger(result.liveAttachments) || result.liveAttachments < 0)
|
|
3323
3341
|
throw new Error("Offline canonical backup reference inspection did not complete.");
|
|
@@ -3334,7 +3352,7 @@ async function validateCanonicalBackupReferences(directory, env, backupPath) {
|
|
|
3334
3352
|
|
|
3335
3353
|
// src/backup/retention.ts
|
|
3336
3354
|
import { rm as rm5 } from "node:fs/promises";
|
|
3337
|
-
import { basename as basename4, dirname as dirname4, join as
|
|
3355
|
+
import { basename as basename4, dirname as dirname4, join as join15, resolve as resolve8 } from "node:path";
|
|
3338
3356
|
var BACKUP_RETENTION_KEEP = 5;
|
|
3339
3357
|
function assertRemovableArtifactName(name) {
|
|
3340
3358
|
if (!BACKUP_ID_PATTERN.test(name)) {
|
|
@@ -3350,7 +3368,7 @@ async function removeNamedBackupArtifact(directory, backupId) {
|
|
|
3350
3368
|
throw new Error(`Refusing to remove a path outside the backups directory: ${target}.`);
|
|
3351
3369
|
}
|
|
3352
3370
|
await rm5(target, { recursive: true, force: true });
|
|
3353
|
-
await rm5(
|
|
3371
|
+
await rm5(join15(deploymentPaths(directory).exports, `${backupId}.json`), { force: true });
|
|
3354
3372
|
}
|
|
3355
3373
|
async function removeEnumeratedBackupArtifact(directory, backup) {
|
|
3356
3374
|
await lifecycleFaults.beforeArtifactDelete?.();
|
|
@@ -3360,7 +3378,7 @@ async function removeEnumeratedBackupArtifact(directory, backup) {
|
|
|
3360
3378
|
throw new Error(`Refusing to prune an untrusted backup path ${backup.path}.`);
|
|
3361
3379
|
}
|
|
3362
3380
|
await rm5(target, { recursive: true, force: true });
|
|
3363
|
-
await rm5(
|
|
3381
|
+
await rm5(join15(deploymentPaths(directory).exports, `${backup.backupId}.json`), { force: true });
|
|
3364
3382
|
}
|
|
3365
3383
|
function planRetention(entries, keep, protectedIds, options = {}) {
|
|
3366
3384
|
if (!Number.isInteger(keep) || keep < 1)
|
|
@@ -3467,7 +3485,7 @@ async function cleanupPartialBackup(directory, backupId) {
|
|
|
3467
3485
|
}
|
|
3468
3486
|
const statuses = await Promise.all([
|
|
3469
3487
|
artifactStatus(backupDirectory(directory, backupId)),
|
|
3470
|
-
artifactStatus(
|
|
3488
|
+
artifactStatus(join16(deploymentPaths(directory).exports, `${backupId}.json`))
|
|
3471
3489
|
]);
|
|
3472
3490
|
const removed = statuses.every((status) => status.absent);
|
|
3473
3491
|
const detail = statuses.map((status) => status.detail).join(" ");
|
|
@@ -3515,7 +3533,20 @@ async function createBackup(directory, state, env, options = {}) {
|
|
|
3515
3533
|
capturedAt = now();
|
|
3516
3534
|
await report("capturing", `Backup capture: OR3 is stopped; writing ${backupDir}.`);
|
|
3517
3535
|
await volumeArchive(directory, state.mode, env, backupDir);
|
|
3518
|
-
|
|
3536
|
+
if (managedVaultEnabled(directory, env)) {
|
|
3537
|
+
await streamCommandToFile("docker", composeArgs(directory, state.mode, [
|
|
3538
|
+
"run",
|
|
3539
|
+
"--rm",
|
|
3540
|
+
"-T",
|
|
3541
|
+
"--no-deps",
|
|
3542
|
+
"--entrypoint",
|
|
3543
|
+
"/nodejs/bin/node",
|
|
3544
|
+
"or3-vault",
|
|
3545
|
+
"/app/vault/entrypoint.mjs",
|
|
3546
|
+
"backup"
|
|
3547
|
+
]), join16(backupDir, "vault.backup"), directory, secretValues(env));
|
|
3548
|
+
}
|
|
3549
|
+
await copySecure(deploymentPaths(directory).env, join16(backupDir, "config.env"));
|
|
3519
3550
|
managedAssetSha256 = await snapshotManagedAssets(directory, state.mode, backupDir);
|
|
3520
3551
|
artifact = "captured";
|
|
3521
3552
|
} catch (error) {
|
|
@@ -3548,9 +3579,10 @@ async function createBackup(directory, state, env, options = {}) {
|
|
|
3548
3579
|
appVersion: state.appVersion,
|
|
3549
3580
|
image: state.image,
|
|
3550
3581
|
imageDigest: state.imageDigest,
|
|
3551
|
-
dataSha256: await sha256File(
|
|
3582
|
+
dataSha256: await sha256File(join16(backupDir, "data.tgz")),
|
|
3583
|
+
...managedVaultEnabled(directory, env) ? { vaultSha256: await sha256File(join16(backupDir, "vault.backup")) } : {},
|
|
3552
3584
|
dataBytes: volumeSize,
|
|
3553
|
-
configSha256: await sha256File(
|
|
3585
|
+
configSha256: await sha256File(join16(backupDir, "config.env")),
|
|
3554
3586
|
managedAssetSha256,
|
|
3555
3587
|
managedAssetInventoryVersion: managedAssetSha256?.["dashboard-lifecycle.mjs"] ? MANAGED_ASSET_INVENTORY_VERSION : managedAssetSha256?.["compose.operator.yaml"] ? 3 : managedAssetSha256?.["dashboard-operator.mjs"] ? 2 : undefined,
|
|
3556
3588
|
mode: state.mode,
|
|
@@ -3564,7 +3596,7 @@ async function createBackup(directory, state, env, options = {}) {
|
|
|
3564
3596
|
};
|
|
3565
3597
|
const manifestContents = `${JSON.stringify(manifest2, null, 2)}
|
|
3566
3598
|
`;
|
|
3567
|
-
await writeSecure(
|
|
3599
|
+
await writeSecure(join16(backupDir, "manifest.json"), manifestContents);
|
|
3568
3600
|
await writeBackupAuthentication(directory, backupDir, manifestContents);
|
|
3569
3601
|
if (!options.safetySnapshot)
|
|
3570
3602
|
await validateCanonicalBackupReferences(directory, { ...env, OR3_IMAGE: imageAtDigest(state.image, state.imageDigest) }, backupDir);
|
|
@@ -3621,7 +3653,7 @@ async function inspectUnclaimedBackupArtifacts(directory, backupId, backupPath)
|
|
|
3621
3653
|
return;
|
|
3622
3654
|
const statuses = await Promise.all([
|
|
3623
3655
|
artifactStatus(expectedPath),
|
|
3624
|
-
artifactStatus(
|
|
3656
|
+
artifactStatus(join16(deploymentPaths(directory).exports, `${backupId}.json`))
|
|
3625
3657
|
]);
|
|
3626
3658
|
const detail = statuses.map((status) => status.detail).join(" ");
|
|
3627
3659
|
return statuses.every((status) => status.absent) ? detail : `Maintenance warning: backup artifact ownership was not recorded. ${detail} This recovery did not verify or remove these paths. Inspect them with "npx @or3/cloud backup list".`;
|
|
@@ -3648,7 +3680,7 @@ async function createPreMutationSnapshot(directory, state, env, backupId) {
|
|
|
3648
3680
|
|
|
3649
3681
|
// src/backup/export.ts
|
|
3650
3682
|
import { chmod as chmod7, mkdir as mkdir7, realpath, rm as rm6, stat as stat3 } from "node:fs/promises";
|
|
3651
|
-
import { basename as basename5, dirname as dirname5, isAbsolute as isAbsolute3, join as
|
|
3683
|
+
import { basename as basename5, dirname as dirname5, isAbsolute as isAbsolute3, join as join17, resolve as resolve10, sep } from "node:path";
|
|
3652
3684
|
var PURGE_REQUIRES_BACKUP_WITHIN_MS = 24 * 60 * 60 * 1000;
|
|
3653
3685
|
async function exportBackup(directory, backupId, destination) {
|
|
3654
3686
|
const backupPath = await resolveBackup(directory, backupId);
|
|
@@ -3662,7 +3694,7 @@ async function exportBackup(directory, backupId, destination) {
|
|
|
3662
3694
|
if (await fileExists(dest))
|
|
3663
3695
|
throw new Error(`Destination ${dest} already exists. Choose a new empty destination so an export can never merge with unrelated files.`);
|
|
3664
3696
|
await mkdir7(dirname5(dest), { recursive: true, mode: 448 });
|
|
3665
|
-
const canonicalDestination =
|
|
3697
|
+
const canonicalDestination = join17(await realpath(dirname5(dest)), basename5(dest));
|
|
3666
3698
|
if (canonicalDestination === deploymentRoot || canonicalDestination.startsWith(`${deploymentRoot}${sep}`)) {
|
|
3667
3699
|
throw new Error("Backup exports must live outside the managed deployment directory so `remove --purge-data` can never delete the remaining copy.");
|
|
3668
3700
|
}
|
|
@@ -3672,20 +3704,20 @@ async function exportBackup(directory, backupId, destination) {
|
|
|
3672
3704
|
throw new Error("Backup export destination changed while it was being created. Refusing to write an export through an unexpected path.");
|
|
3673
3705
|
}
|
|
3674
3706
|
await chmod7(dest, 448);
|
|
3675
|
-
for (const file of ["data.tgz", "config.env", "manifest.json", "manifest.auth"]) {
|
|
3676
|
-
await copySecure(
|
|
3707
|
+
for (const file of ["data.tgz", "config.env", "manifest.json", "manifest.auth", ...manifest2.vaultSha256 ? ["vault.backup"] : []]) {
|
|
3708
|
+
await copySecure(join17(backupPath, file), join17(dest, file));
|
|
3677
3709
|
}
|
|
3678
3710
|
if (manifest2.managedAssetSha256) {
|
|
3679
3711
|
for (const name of managedAssetNamesForInventory(manifest2.mode, manifest2.managedAssetInventoryVersion)) {
|
|
3680
|
-
await copySecure(
|
|
3712
|
+
await copySecure(join17(backupPath, "managed-assets", name), join17(dest, "managed-assets", name));
|
|
3681
3713
|
}
|
|
3682
3714
|
}
|
|
3683
|
-
const dataSha = await sha256File(
|
|
3715
|
+
const dataSha = await sha256File(join17(dest, "data.tgz"));
|
|
3684
3716
|
if (dataSha !== manifest2.dataSha256) {
|
|
3685
3717
|
throw new Error(`Exported data.tgz checksum mismatch for ${manifest2.backupId}. Expected ${manifest2.dataSha256}, got ${dataSha}.`);
|
|
3686
3718
|
}
|
|
3687
3719
|
if (manifest2.configSha256) {
|
|
3688
|
-
const configSha = await sha256File(
|
|
3720
|
+
const configSha = await sha256File(join17(dest, "config.env"));
|
|
3689
3721
|
if (configSha !== manifest2.configSha256) {
|
|
3690
3722
|
throw new Error(`Exported config.env checksum mismatch for ${manifest2.backupId}.`);
|
|
3691
3723
|
}
|
|
@@ -3702,12 +3734,12 @@ async function exportBackup(directory, backupId, destination) {
|
|
|
3702
3734
|
dataSha256: manifest2.dataSha256,
|
|
3703
3735
|
configSha256: manifest2.configSha256
|
|
3704
3736
|
};
|
|
3705
|
-
await writeSecure(
|
|
3737
|
+
await writeSecure(join17(deploymentPaths(directory).exports, `${manifest2.backupId}.json`), `${JSON.stringify(receipt, null, 2)}
|
|
3706
3738
|
`);
|
|
3707
|
-
let bytes = (await stat3(
|
|
3739
|
+
let bytes = (manifest2.vaultSha256 ? (await stat3(join17(dest, "vault.backup"))).size : 0) + (await stat3(join17(dest, "data.tgz"))).size + (await stat3(join17(dest, "config.env"))).size + (await stat3(join17(dest, "manifest.json"))).size + (await stat3(join17(dest, "manifest.auth"))).size;
|
|
3708
3740
|
if (manifest2.managedAssetSha256) {
|
|
3709
3741
|
for (const name of managedAssetNamesForInventory(manifest2.mode, manifest2.managedAssetInventoryVersion)) {
|
|
3710
|
-
bytes += (await stat3(
|
|
3742
|
+
bytes += (await stat3(join17(dest, "managed-assets", name))).size;
|
|
3711
3743
|
}
|
|
3712
3744
|
}
|
|
3713
3745
|
console.log(`Exported backup ${manifest2.backupId} (${bytes} bytes) to ${dest}`);
|
|
@@ -3730,7 +3762,7 @@ function assertPurgeBackupFreshness(backups, nowMs) {
|
|
|
3730
3762
|
}
|
|
3731
3763
|
async function readBackupExportReceipt(directory, backupId) {
|
|
3732
3764
|
try {
|
|
3733
|
-
const receipt = JSON.parse(await readText(
|
|
3765
|
+
const receipt = JSON.parse(await readText(join17(deploymentPaths(directory).exports, `${backupId}.json`)));
|
|
3734
3766
|
if (receipt.schemaVersion !== 1 || receipt.backupId !== backupId || typeof receipt.destination !== "string" || !isAbsolute3(receipt.destination) || typeof receipt.destinationDevice !== "number" || !receipt.dataSha256)
|
|
3735
3767
|
return;
|
|
3736
3768
|
return receipt;
|
|
@@ -3934,7 +3966,7 @@ async function backupCommand(directory, positionals, flags) {
|
|
|
3934
3966
|
|
|
3935
3967
|
// src/credentials/reset.ts
|
|
3936
3968
|
import { rm as rm7 } from "node:fs/promises";
|
|
3937
|
-
import { join as
|
|
3969
|
+
import { join as join18 } from "node:path";
|
|
3938
3970
|
|
|
3939
3971
|
// src/credentials/reset-script.ts
|
|
3940
3972
|
function buildCredentialsResetScript(input2) {
|
|
@@ -4127,7 +4159,7 @@ async function applyCredentialReset(directory, state, nextEnv) {
|
|
|
4127
4159
|
await runCredentialsResetScript(directory, state.mode, values);
|
|
4128
4160
|
const runtimeEnv = withoutProvisioningCredentials(nextEnv);
|
|
4129
4161
|
await writeSecure(deploymentPaths(directory).env, serializeEnv(runtimeEnv));
|
|
4130
|
-
const initialCredentials =
|
|
4162
|
+
const initialCredentials = join18(directory, ".or3-initial-credentials");
|
|
4131
4163
|
await rm7(initialCredentials, { force: true });
|
|
4132
4164
|
await stopProject(directory, state.mode);
|
|
4133
4165
|
await startProject(directory, state.mode, runtimeEnv);
|
|
@@ -4189,7 +4221,7 @@ async function credentialsCommand(directory, positionals, flags) {
|
|
|
4189
4221
|
// src/commands/doctor.ts
|
|
4190
4222
|
import { existsSync as existsSync3 } from "node:fs";
|
|
4191
4223
|
import { stat as stat4 } from "node:fs/promises";
|
|
4192
|
-
import { basename as basename6, join as
|
|
4224
|
+
import { basename as basename6, join as join19, resolve as resolve11 } from "node:path";
|
|
4193
4225
|
async function doctorCommand(directory) {
|
|
4194
4226
|
const resolved = resolve11(directory);
|
|
4195
4227
|
const paths = deploymentPaths(resolved);
|
|
@@ -4214,7 +4246,7 @@ async function doctorCommand(directory) {
|
|
|
4214
4246
|
failures.push(error instanceof Error ? error.message : String(error));
|
|
4215
4247
|
console.log("✗ Managed state or .env is missing/invalid");
|
|
4216
4248
|
}
|
|
4217
|
-
for (const file of [paths.env, paths.state,
|
|
4249
|
+
for (const file of [paths.env, paths.state, join19(resolved, ".or3-initial-credentials")]) {
|
|
4218
4250
|
if (!existsSync3(file))
|
|
4219
4251
|
continue;
|
|
4220
4252
|
const mode = (await stat4(file)).mode & 511;
|
|
@@ -4286,7 +4318,7 @@ async function doctorCommand(directory) {
|
|
|
4286
4318
|
}
|
|
4287
4319
|
assertDashboardOperatorMounts(container.Mounts ?? [], resolved);
|
|
4288
4320
|
const ipcDirectory = await stat4(paths.operatorIpc);
|
|
4289
|
-
const operatorSocket = await stat4(
|
|
4321
|
+
const operatorSocket = await stat4(join19(paths.operatorIpc, "operator.sock"));
|
|
4290
4322
|
const operatorUid = Number(env.OR3_OPERATOR_UID);
|
|
4291
4323
|
const operatorGid = Number(env.OR3_OPERATOR_GID);
|
|
4292
4324
|
if ((ipcDirectory.mode & 511) !== 456 || ipcDirectory.uid !== operatorUid || ipcDirectory.gid !== operatorGid || !operatorSocket.isSocket() || (operatorSocket.mode & 511) !== 432 || operatorSocket.uid !== operatorUid || operatorSocket.gid !== operatorGid) {
|
|
@@ -4345,7 +4377,7 @@ async function doctorCommand(directory) {
|
|
|
4345
4377
|
|
|
4346
4378
|
// src/commands/init.ts
|
|
4347
4379
|
import { chmod as chmod8, mkdir as mkdir8 } from "node:fs/promises";
|
|
4348
|
-
import { join as
|
|
4380
|
+
import { join as join20, relative, resolve as resolve12 } from "node:path";
|
|
4349
4381
|
async function initCommand(positionals, flags) {
|
|
4350
4382
|
const hasLocal = boolFlag(flags, "local");
|
|
4351
4383
|
const hasPublic = boolFlag(flags, "public");
|
|
@@ -4353,7 +4385,7 @@ async function initCommand(positionals, flags) {
|
|
|
4353
4385
|
throw new Error("Choose exactly one of --local or --public.");
|
|
4354
4386
|
const mode = hasPublic ? "public" : "local";
|
|
4355
4387
|
const directory = resolve12(process.cwd(), positionals[0] ?? "or3-cloud");
|
|
4356
|
-
if (await fileExists(
|
|
4388
|
+
if (await fileExists(join20(directory, ".or3-cloud", "state.json"))) {
|
|
4357
4389
|
throw new Error(`${directory} is already a managed deployment. Use update, doctor, or adopt.`);
|
|
4358
4390
|
}
|
|
4359
4391
|
await readDirectoryEmpty(directory);
|
|
@@ -4404,7 +4436,7 @@ ${existing.stderr}`)) {
|
|
|
4404
4436
|
await copyAssets(directory, mode);
|
|
4405
4437
|
const env = buildEnv({ mode, version, directory, image, email, password, domain, port, dashboardOperator: operator });
|
|
4406
4438
|
await writeSecure(deploymentPaths(directory).env, serializeEnv(env));
|
|
4407
|
-
await writeSecure(
|
|
4439
|
+
await writeSecure(join20(directory, ".or3-initial-credentials"), serializeInitialCredentials({
|
|
4408
4440
|
bootstrapEmail: email,
|
|
4409
4441
|
bootstrapPassword: password,
|
|
4410
4442
|
adminUsername: email,
|
|
@@ -4429,7 +4461,7 @@ ${existing.stderr}`)) {
|
|
|
4429
4461
|
const folder = relative(process.cwd(), directory) || ".";
|
|
4430
4462
|
console.log(`
|
|
4431
4463
|
OR3 Cloud ${version} is running at ${url}`);
|
|
4432
|
-
console.log(`Sign in as ${email} with the password in ${
|
|
4464
|
+
console.log(`Sign in as ${email} with the password in ${join20(folder, ".or3-initial-credentials")}; the same login opens ${url}/admin.`);
|
|
4433
4465
|
console.log("Save it in a password manager, then delete that file.");
|
|
4434
4466
|
console.log(`
|
|
4435
4467
|
Run later commands from the deployment folder: cd ${quote(folder)} && npx @or3/cloud doctor`);
|
|
@@ -4482,10 +4514,10 @@ async function restartCommand(directory, lifecycle) {
|
|
|
4482
4514
|
}
|
|
4483
4515
|
|
|
4484
4516
|
// src/commands/recover.ts
|
|
4485
|
-
import { join as
|
|
4517
|
+
import { join as join24 } from "node:path";
|
|
4486
4518
|
|
|
4487
4519
|
// src/backup/restore.ts
|
|
4488
|
-
import { join as
|
|
4520
|
+
import { join as join21 } from "node:path";
|
|
4489
4521
|
function assertFilesystemPayloadHash(key, readChunk, createHash3) {
|
|
4490
4522
|
const match = /^(sha256|md5)_([a-fA-F0-9]+)$/.exec(key);
|
|
4491
4523
|
const algorithm = match?.[1];
|
|
@@ -4547,7 +4579,7 @@ if ((storageRoot === '/data' || storageRoot.startsWith('/data/')) && fs.existsSy
|
|
|
4547
4579
|
}
|
|
4548
4580
|
`;
|
|
4549
4581
|
async function assertRestoreFreeSpace(directory, state, env, manifest2, backupPath) {
|
|
4550
|
-
const archiveBytes = await gzipUncompressedBytes(
|
|
4582
|
+
const archiveBytes = await gzipUncompressedBytes(join21(backupPath, "data.tgz"));
|
|
4551
4583
|
const requiredBytes = Math.max(manifest2.dataBytes ?? 0, archiveBytes);
|
|
4552
4584
|
const freeBytes = await dataVolumeFreeBytes(directory, state.mode, env);
|
|
4553
4585
|
const reclaimableBytes = await dataVolumeSize(directory, state.mode, env, "allocated");
|
|
@@ -4556,7 +4588,7 @@ async function assertRestoreFreeSpace(directory, state, env, manifest2, backupPa
|
|
|
4556
4588
|
async function restoreBackupData(directory, state, env, backupPath, options = {}) {
|
|
4557
4589
|
const manifest2 = await readManifest(backupPath, directory);
|
|
4558
4590
|
assertRestorableManagedAssets(manifest2);
|
|
4559
|
-
const backupEnv = parseEnv(await readText(
|
|
4591
|
+
const backupEnv = parseEnv(await readText(join21(backupPath, "config.env")));
|
|
4560
4592
|
if (backupEnv.OR3_VERSION !== manifest2.appVersion || backupEnv.OR3_IMAGE !== manifest2.image) {
|
|
4561
4593
|
throw new Error(`Backup ${manifest2.backupId} configuration does not match its manifest.`);
|
|
4562
4594
|
}
|
|
@@ -4644,12 +4676,12 @@ import { resolve as resolve13 } from "node:path";
|
|
|
4644
4676
|
// src/deployment/lease.ts
|
|
4645
4677
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
4646
4678
|
import { mkdir as mkdir10, rename as rename3, rm as rm9 } from "node:fs/promises";
|
|
4647
|
-
import { dirname as dirname7, join as
|
|
4679
|
+
import { dirname as dirname7, join as join23 } from "node:path";
|
|
4648
4680
|
|
|
4649
4681
|
// ../../shared/server/restarts/controller.ts
|
|
4650
4682
|
import { randomUUID as randomUUID3, createHash as createHash3 } from "node:crypto";
|
|
4651
4683
|
import { mkdir as mkdir9, open as open3, readFile as readFile3, rename as rename2, rm as rm8 } from "node:fs/promises";
|
|
4652
|
-
import { dirname as dirname6, join as
|
|
4684
|
+
import { dirname as dirname6, join as join22 } from "node:path";
|
|
4653
4685
|
|
|
4654
4686
|
// ../../node_modules/zod/v4/classic/external.js
|
|
4655
4687
|
var exports_external = {};
|
|
@@ -5241,6 +5273,7 @@ __export(exports_util, {
|
|
|
5241
5273
|
toZod: () => toZod,
|
|
5242
5274
|
stringifyPrimitive: () => stringifyPrimitive,
|
|
5243
5275
|
slugify: () => slugify,
|
|
5276
|
+
sizeCaps: () => sizeCaps,
|
|
5244
5277
|
shallowClone: () => shallowClone,
|
|
5245
5278
|
safeExtend: () => safeExtend,
|
|
5246
5279
|
required: () => required,
|
|
@@ -5815,6 +5848,18 @@ function prefixIssues(path, issues) {
|
|
|
5815
5848
|
function unwrapMessage(message) {
|
|
5816
5849
|
return typeof message === "string" ? message : message?.message;
|
|
5817
5850
|
}
|
|
5851
|
+
function sizeCaps(defChecks) {
|
|
5852
|
+
const caps = [];
|
|
5853
|
+
for (const ch of defChecks ?? []) {
|
|
5854
|
+
const d = ch._zod.def;
|
|
5855
|
+
if (!/_(length|size)$|_equals$/.test(d.check))
|
|
5856
|
+
break;
|
|
5857
|
+
const max = d.maximum ?? d.length ?? d.size;
|
|
5858
|
+
if (max >= 0)
|
|
5859
|
+
caps.push([max, ch]);
|
|
5860
|
+
}
|
|
5861
|
+
return caps;
|
|
5862
|
+
}
|
|
5818
5863
|
function attachSchema(issues, start, inst) {
|
|
5819
5864
|
var _a;
|
|
5820
5865
|
for (let i = start;i < issues.length; i++) {
|
|
@@ -7234,7 +7279,7 @@ ${content.join(`
|
|
|
7234
7279
|
var version = {
|
|
7235
7280
|
major: 4,
|
|
7236
7281
|
minor: 6,
|
|
7237
|
-
patch:
|
|
7282
|
+
patch: 7
|
|
7238
7283
|
};
|
|
7239
7284
|
|
|
7240
7285
|
// ../../node_modules/zod/v4/core/schemas.js
|
|
@@ -7983,6 +8028,31 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
|
|
|
7983
8028
|
return payload;
|
|
7984
8029
|
};
|
|
7985
8030
|
});
|
|
8031
|
+
function capSize(inst, size) {
|
|
8032
|
+
const caps = sizeCaps(inst._zod.def.checks);
|
|
8033
|
+
if (!caps.length)
|
|
8034
|
+
return;
|
|
8035
|
+
const cap = Math.min(...caps.map((c) => c[0]));
|
|
8036
|
+
const parse2 = inst._zod.parse;
|
|
8037
|
+
inst._zod.parse = (payload, ctx) => {
|
|
8038
|
+
const n = size(payload.value);
|
|
8039
|
+
if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
|
|
8040
|
+
for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
|
|
8041
|
+
if (n > max && ch._zod.def.when(payload)) {
|
|
8042
|
+
const start = payload.issues.length;
|
|
8043
|
+
ch._zod.check(payload);
|
|
8044
|
+
payload.issues[start].continue = false;
|
|
8045
|
+
attachSchema(payload.issues, start, inst);
|
|
8046
|
+
return payload;
|
|
8047
|
+
}
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8050
|
+
return parse2(payload, ctx);
|
|
8051
|
+
};
|
|
8052
|
+
}
|
|
8053
|
+
var arraySize = (input2) => Array.isArray(input2) ? input2.length : -1;
|
|
8054
|
+
var mapSize = (input2) => input2 instanceof Map ? input2.size : -1;
|
|
8055
|
+
var setSize = (input2) => input2 instanceof Set ? input2.size : -1;
|
|
7986
8056
|
function handleArrayResult(result, final, index) {
|
|
7987
8057
|
if (result.issues.length) {
|
|
7988
8058
|
final.issues.push(...prefixIssues(index, result.issues));
|
|
@@ -8026,6 +8096,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
|
|
|
8026
8096
|
}
|
|
8027
8097
|
return payload;
|
|
8028
8098
|
};
|
|
8099
|
+
capSize(inst, arraySize);
|
|
8029
8100
|
});
|
|
8030
8101
|
function handlePropertyResult(result, final, key, input2, optin, optout) {
|
|
8031
8102
|
const isPresent = key in input2;
|
|
@@ -8980,6 +9051,7 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
|
|
|
8980
9051
|
return Promise.all(proms).then(() => payload);
|
|
8981
9052
|
return payload;
|
|
8982
9053
|
};
|
|
9054
|
+
capSize(inst, mapSize);
|
|
8983
9055
|
});
|
|
8984
9056
|
function handleMapResult(keyResult, valueResult, final, key, input2, inst, ctx) {
|
|
8985
9057
|
if (keyResult.issues.length) {
|
|
@@ -9046,6 +9118,7 @@ var $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
|
|
|
9046
9118
|
return Promise.all(proms).then(() => payload);
|
|
9047
9119
|
return payload;
|
|
9048
9120
|
};
|
|
9121
|
+
capSize(inst, setSize);
|
|
9049
9122
|
});
|
|
9050
9123
|
function handleSetResult(result, final) {
|
|
9051
9124
|
if (result.issues.length) {
|
|
@@ -18495,9 +18568,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
18495
18568
|
doc.write(`}`);
|
|
18496
18569
|
return outputVar;
|
|
18497
18570
|
}
|
|
18571
|
+
function generateSizeGate(doc, schema, size) {
|
|
18572
|
+
for (const [max, check] of sizeCaps(schema._zod.def.checks)) {
|
|
18573
|
+
doc.write(`if (${size} > ${numericOperand(max, check._zod.def.check)}) return INVALID;`);
|
|
18574
|
+
}
|
|
18575
|
+
}
|
|
18498
18576
|
function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
18499
18577
|
const def = schema._zod.def;
|
|
18500
18578
|
doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
|
|
18579
|
+
generateSizeGate(doc, schema, `${accessor}.length`);
|
|
18501
18580
|
const outputVar = buildsValue ? newVar(ctx) : null;
|
|
18502
18581
|
const iVar = newVar(ctx);
|
|
18503
18582
|
const elemVar = newVar(ctx);
|
|
@@ -18897,6 +18976,7 @@ function literalPropertyKey(ctx, key) {
|
|
|
18897
18976
|
function generateMapCheck(doc, ctx, schema, accessor) {
|
|
18898
18977
|
const def = schema._zod.def;
|
|
18899
18978
|
doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
|
|
18979
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
18900
18980
|
const outputVar = newVar(ctx);
|
|
18901
18981
|
const kVar = newVar(ctx);
|
|
18902
18982
|
const valVar = newVar(ctx);
|
|
@@ -18913,6 +18993,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
|
|
|
18913
18993
|
function generateSetCheck(doc, ctx, schema, accessor) {
|
|
18914
18994
|
const def = schema._zod.def;
|
|
18915
18995
|
doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
|
|
18996
|
+
generateSizeGate(doc, schema, `${accessor}.size`);
|
|
18916
18997
|
const outputVar = newVar(ctx);
|
|
18917
18998
|
const valVar = newVar(ctx);
|
|
18918
18999
|
doc.write(`const ${outputVar} = new Set();`);
|
|
@@ -24113,7 +24194,7 @@ class RestartController {
|
|
|
24113
24194
|
constructor(directory, driver) {
|
|
24114
24195
|
this.directory = directory;
|
|
24115
24196
|
this.driver = driver;
|
|
24116
|
-
this.path =
|
|
24197
|
+
this.path = join22(directory, "restarts.json");
|
|
24117
24198
|
}
|
|
24118
24199
|
lock(action) {
|
|
24119
24200
|
const next = this.serial.then(action, action);
|
|
@@ -24256,7 +24337,7 @@ class RestartController {
|
|
|
24256
24337
|
const timer = setTimeout(() => {
|
|
24257
24338
|
this.cancel(request.requestId).catch(() => {});
|
|
24258
24339
|
}, 330000);
|
|
24259
|
-
timer.unref
|
|
24340
|
+
timer.unref();
|
|
24260
24341
|
this.preparationTimers.set(request.requestId, timer);
|
|
24261
24342
|
return publicOperation(o);
|
|
24262
24343
|
});
|
|
@@ -24411,7 +24492,7 @@ class RestartController {
|
|
|
24411
24492
|
var DASHBOARD_LEASE_STALE_MS = 30000;
|
|
24412
24493
|
async function readLeaseOwner(leasePath) {
|
|
24413
24494
|
try {
|
|
24414
|
-
const owner = JSON.parse(await readText(
|
|
24495
|
+
const owner = JSON.parse(await readText(join23(leasePath, "owner.json")));
|
|
24415
24496
|
if (owner.schemaVersion !== 1 || typeof owner.nonce !== "string" || typeof owner.command !== "string" || owner.origin !== "cli" && owner.origin !== "dashboard" || !Number.isSafeInteger(owner.pid) || typeof owner.acquiredAt !== "string" || typeof owner.heartbeatAt !== "string")
|
|
24416
24497
|
return;
|
|
24417
24498
|
return owner;
|
|
@@ -24470,7 +24551,7 @@ async function acquireDeploymentLease(directory, command, dashboardJobId) {
|
|
|
24470
24551
|
}
|
|
24471
24552
|
}
|
|
24472
24553
|
try {
|
|
24473
|
-
await writeSecure(
|
|
24554
|
+
await writeSecure(join23(leasePath, "owner.json"), `${JSON.stringify(owner, null, 2)}
|
|
24474
24555
|
`);
|
|
24475
24556
|
} catch (error62) {
|
|
24476
24557
|
await rm9(leasePath, { recursive: true, force: true }).catch(() => {
|
|
@@ -24480,7 +24561,7 @@ async function acquireDeploymentLease(directory, command, dashboardJobId) {
|
|
|
24480
24561
|
}
|
|
24481
24562
|
const heartbeat = setInterval(() => {
|
|
24482
24563
|
owner.heartbeatAt = now();
|
|
24483
|
-
writeSecure(
|
|
24564
|
+
writeSecure(join23(leasePath, "owner.json"), `${JSON.stringify(owner, null, 2)}
|
|
24484
24565
|
`).catch(() => {
|
|
24485
24566
|
return;
|
|
24486
24567
|
});
|
|
@@ -24505,7 +24586,7 @@ async function withDeploymentLease(directory, command, action) {
|
|
|
24505
24586
|
}
|
|
24506
24587
|
async function assertRestartJournalSettled(directory) {
|
|
24507
24588
|
try {
|
|
24508
|
-
const journal = await readRestartJournal(
|
|
24589
|
+
const journal = await readRestartJournal(join23(directory, ".or3-cloud", "restarts", "restarts.json"));
|
|
24509
24590
|
if (journal.operations.some((operation) => !["succeeded", "cancelled", "failed"].includes(operation.phase))) {
|
|
24510
24591
|
throw new Error("A dashboard restart is unresolved. Run the compatible CLI recover --dry-run to inspect and recover --finish to reconcile it before another mutation.");
|
|
24511
24592
|
}
|
|
@@ -25144,11 +25225,11 @@ async function runRecovery(directory, context) {
|
|
|
25144
25225
|
await compose(loaded.directory, loaded.state.mode, ["down"]).catch(() => {
|
|
25145
25226
|
return;
|
|
25146
25227
|
});
|
|
25147
|
-
const sourceEnv = parseEnv(await readText(
|
|
25148
|
-
const sourceFiles = ["-f",
|
|
25228
|
+
const sourceEnv = parseEnv(await readText(join24(pending.sourceDirectory, ".env")));
|
|
25229
|
+
const sourceFiles = ["-f", join24(pending.sourceDirectory, "compose.yaml")];
|
|
25149
25230
|
const sourceModeValue = sourceMode(pending.sourceDirectory, sourceEnv);
|
|
25150
25231
|
if (sourceModeValue === "public")
|
|
25151
|
-
sourceFiles.push("-f",
|
|
25232
|
+
sourceFiles.push("-f", join24(pending.sourceDirectory, "compose.public.yaml"));
|
|
25152
25233
|
if (pending.sourceInitiallyRunning !== false) {
|
|
25153
25234
|
await restartSource(pending.sourceDirectory, sourceFiles, secretValues(sourceEnv));
|
|
25154
25235
|
}
|
|
@@ -25163,7 +25244,7 @@ async function runRecovery(directory, context) {
|
|
|
25163
25244
|
|
|
25164
25245
|
// src/commands/remove.ts
|
|
25165
25246
|
import { rm as rm10 } from "node:fs/promises";
|
|
25166
|
-
import { join as
|
|
25247
|
+
import { join as join25 } from "node:path";
|
|
25167
25248
|
function purgeVolumesFromState(state) {
|
|
25168
25249
|
const volumes = [state.volumeName];
|
|
25169
25250
|
if (state.mode === "public") {
|
|
@@ -25200,7 +25281,7 @@ async function removeCommand(directory, flags) {
|
|
|
25200
25281
|
for (const volume of volumes)
|
|
25201
25282
|
console.log(` docker volume ${volume}`);
|
|
25202
25283
|
for (const file2 of managedFiles)
|
|
25203
|
-
console.log(` ${
|
|
25284
|
+
console.log(` ${join25(directory, file2)}`);
|
|
25204
25285
|
await compose(directory, state.mode, ["down"]);
|
|
25205
25286
|
await assertPurgeHasVerifiedExport(directory, await enumerateBackups(directory), Date.now());
|
|
25206
25287
|
for (const volume of volumes) {
|
|
@@ -25210,13 +25291,13 @@ async function removeCommand(directory, flags) {
|
|
|
25210
25291
|
${result.stderr}`);
|
|
25211
25292
|
}
|
|
25212
25293
|
for (const file2 of managedFiles) {
|
|
25213
|
-
await rm10(
|
|
25294
|
+
await rm10(join25(directory, file2), { recursive: true, force: true });
|
|
25214
25295
|
}
|
|
25215
25296
|
console.log(`Purged: data volumes, backups, .env, managed state, and compose files were deleted. The verified export at ${exportReceipt.destination} is the remaining copy.`);
|
|
25216
25297
|
}
|
|
25217
25298
|
|
|
25218
25299
|
// src/commands/restore.ts
|
|
25219
|
-
import { join as
|
|
25300
|
+
import { join as join26 } from "node:path";
|
|
25220
25301
|
async function restoreCommand(directory, flags, positionals) {
|
|
25221
25302
|
if (!boolFlag(flags, "yes"))
|
|
25222
25303
|
throw new Error("Restore replaces live data. Re-run with --yes after confirming the backup and data-loss boundary.");
|
|
@@ -25229,7 +25310,7 @@ async function restoreCommand(directory, flags, positionals) {
|
|
|
25229
25310
|
const backupPath = await resolveBackup(loaded.directory, backupValue);
|
|
25230
25311
|
const manifest2 = await readManifest(backupPath, loaded.directory);
|
|
25231
25312
|
assertRestorableManagedAssets(manifest2);
|
|
25232
|
-
const backupEnv = parseEnv(await readText(
|
|
25313
|
+
const backupEnv = parseEnv(await readText(join26(backupPath, "config.env")));
|
|
25233
25314
|
if (backupEnv.OR3_VERSION !== manifest2.appVersion || backupEnv.OR3_IMAGE !== manifest2.image) {
|
|
25234
25315
|
throw new Error(`Backup ${manifest2.backupId} configuration does not match its manifest.`);
|
|
25235
25316
|
}
|
|
@@ -25319,7 +25400,7 @@ async function restoreCommand(directory, flags, positionals) {
|
|
|
25319
25400
|
}
|
|
25320
25401
|
|
|
25321
25402
|
// src/commands/rollback.ts
|
|
25322
|
-
import { join as
|
|
25403
|
+
import { join as join27 } from "node:path";
|
|
25323
25404
|
async function rollbackCommand(directory, flags) {
|
|
25324
25405
|
if (!boolFlag(flags, "yes"))
|
|
25325
25406
|
throw new Error("Rollback restores the previous image and data snapshot. Re-run with --yes after confirming post-update data loss.");
|
|
@@ -25335,7 +25416,7 @@ async function rollbackCommand(directory, flags) {
|
|
|
25335
25416
|
if (manifest2.appVersion !== point.appVersion || manifest2.image !== point.image || manifest2.imageDigest !== point.imageDigest) {
|
|
25336
25417
|
throw new Error(`Rollback point ${point.backupId} no longer matches its recorded image/version. Refusing to mutate the deployment.`);
|
|
25337
25418
|
}
|
|
25338
|
-
const backupEnv = parseEnv(await readText(
|
|
25419
|
+
const backupEnv = parseEnv(await readText(join27(backupPath, "config.env")));
|
|
25339
25420
|
if (backupEnv.OR3_VERSION !== manifest2.appVersion || backupEnv.OR3_IMAGE !== manifest2.image) {
|
|
25340
25421
|
throw new Error(`Backup ${manifest2.backupId} configuration does not match its manifest.`);
|
|
25341
25422
|
}
|
|
@@ -25546,7 +25627,7 @@ ${result.stderr}`);
|
|
|
25546
25627
|
|
|
25547
25628
|
// src/commands/update.ts
|
|
25548
25629
|
import { readFile as readFile4, statfs as statfs2 } from "node:fs/promises";
|
|
25549
|
-
import { join as
|
|
25630
|
+
import { join as join28, resolve as resolve14 } from "node:path";
|
|
25550
25631
|
async function assessUpdate(directory, targetVersion, options = {}) {
|
|
25551
25632
|
const checkDocker = options.checkDocker ?? false;
|
|
25552
25633
|
const observation = await observeDeployment(directory, { checkDocker, checkImage: false });
|
|
@@ -25611,7 +25692,7 @@ async function assessUpdate(directory, targetVersion, options = {}) {
|
|
|
25611
25692
|
for (const name of managedAssetNames(state?.mode ?? "local")) {
|
|
25612
25693
|
let desired;
|
|
25613
25694
|
try {
|
|
25614
|
-
desired = await readFile4(
|
|
25695
|
+
desired = await readFile4(join28(ASSET_ROOT, name));
|
|
25615
25696
|
} catch (error62) {
|
|
25616
25697
|
findings.push({
|
|
25617
25698
|
code: "managed-asset-unreadable",
|
|
@@ -25622,7 +25703,7 @@ async function assessUpdate(directory, targetVersion, options = {}) {
|
|
|
25622
25703
|
continue;
|
|
25623
25704
|
}
|
|
25624
25705
|
try {
|
|
25625
|
-
const installedPath =
|
|
25706
|
+
const installedPath = join28(directory, name);
|
|
25626
25707
|
if (!await fileExists(installedPath)) {
|
|
25627
25708
|
changed.push(`${name} (add)`);
|
|
25628
25709
|
continue;
|
|
@@ -25977,7 +26058,7 @@ async function runUpdateCommand(directory, flags, asJson) {
|
|
|
25977
26058
|
}
|
|
25978
26059
|
|
|
25979
26060
|
// src/commands/verify.ts
|
|
25980
|
-
import { join as
|
|
26061
|
+
import { join as join29, resolve as resolve15 } from "node:path";
|
|
25981
26062
|
async function verificationCredentials(directory, flags, env) {
|
|
25982
26063
|
const passwordFile = stringFlag(flags, "verification-password-file");
|
|
25983
26064
|
if (flags["verification-email"] !== undefined && !stringFlag(flags, "verification-email")) {
|
|
@@ -25989,7 +26070,7 @@ async function verificationCredentials(directory, flags, env) {
|
|
|
25989
26070
|
const configuredEmail = stringFlag(flags, "verification-email")?.trim() || env.OR3_MANAGED_OWNER_EMAIL || env.OR3_BASIC_AUTH_BOOTSTRAP_EMAIL;
|
|
25990
26071
|
let initialCredentials = {};
|
|
25991
26072
|
if (!configuredEmail || !passwordFile && !env.OR3_BASIC_AUTH_BOOTSTRAP_PASSWORD) {
|
|
25992
|
-
const initialCredentialsPath =
|
|
26073
|
+
const initialCredentialsPath = join29(directory, ".or3-initial-credentials");
|
|
25993
26074
|
if (await fileExists(initialCredentialsPath)) {
|
|
25994
26075
|
initialCredentials = parseEnv(await readOwnerOnlyText(initialCredentialsPath, "The initial credentials file"));
|
|
25995
26076
|
}
|
|
@@ -26230,11 +26311,11 @@ The installer never changes firewall, DNS, Cloudflare, or Tailscale settings.`);
|
|
|
26230
26311
|
|
|
26231
26312
|
// src/commands/recover-restart.ts
|
|
26232
26313
|
import { request } from "node:http";
|
|
26233
|
-
import { join as
|
|
26314
|
+
import { join as join30 } from "node:path";
|
|
26234
26315
|
async function recoverRestart(directory, flags) {
|
|
26235
26316
|
let journal;
|
|
26236
26317
|
try {
|
|
26237
|
-
journal = await readRestartJournal(
|
|
26318
|
+
journal = await readRestartJournal(join30(directory, ".or3-cloud", "restarts", "restarts.json"));
|
|
26238
26319
|
} catch (error62) {
|
|
26239
26320
|
if (error62.code === "ENOENT")
|
|
26240
26321
|
return false;
|
|
@@ -26259,7 +26340,7 @@ async function recoverRestart(directory, flags) {
|
|
|
26259
26340
|
const value = JSON.stringify({ requestId: operation.requestId });
|
|
26260
26341
|
await new Promise((resolve17, reject) => {
|
|
26261
26342
|
const req = request({
|
|
26262
|
-
socketPath:
|
|
26343
|
+
socketPath: join30(deploymentPaths(directory).operatorIpc, "operator.sock"),
|
|
26263
26344
|
path: "/lifecycle/v1/reconcile",
|
|
26264
26345
|
method: "POST",
|
|
26265
26346
|
headers: { "content-type": "application/json", "content-length": Buffer.byteLength(value) }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@or3/cloud",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.91",
|
|
4
4
|
"description": "Install and operate the OR3 Cloud container distribution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"directory": "packages/or3-cloud"
|
|
11
11
|
},
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": "
|
|
13
|
+
"node": "^24.22.1"
|
|
14
14
|
},
|
|
15
15
|
"bin": {
|
|
16
16
|
"or3": "./dist/cli.mjs"
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"dashboardUpdateProtocol": 1,
|
|
20
20
|
"dashboardUpdateMinimumSourceVersion": "0.1.40",
|
|
21
21
|
"stateSchema": 1,
|
|
22
|
-
"imageDigest": "sha256:
|
|
23
|
-
"operatorImageDigest": "sha256:
|
|
24
|
-
"sourceRevision": "
|
|
22
|
+
"imageDigest": "sha256:c8ddb0c55aa89f7eb768f29f25edfc782ff45ec6d3089972e5b9bc7d01781a27",
|
|
23
|
+
"operatorImageDigest": "sha256:84593cc55d1c01ce53a49438b5f47624182fe376e7a009511438b8077854c4cf",
|
|
24
|
+
"sourceRevision": "c299552915aa0f3977673bcfa702f539d225a6c8"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|