@or3/cloud 0.1.90 → 0.1.92

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 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 20 or later.
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, tombstones its probe before
89
- requesting physical deletion, and revokes its temporary session even when a later
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
 
@@ -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
- "\uD83D\uDD10 encrypt with Dotenvx: https://dotenvx.com",
124
- "\uD83D\uDD10 prevent committing .env to code: https://dotenvx.com/precommit",
125
- "\uD83D\uDD10 prevent building .env in docker: https://dotenvx.com/prebuild",
126
- "\uD83D\uDCE1 add observability to secrets: https://dotenvx.com/ops",
127
- "\uD83D\uDC65 sync secrets across teammates & machines: https://dotenvx.com/ops",
128
- "\uD83D\uDDC2️ backup and recover secrets: https://dotenvx.com/ops",
129
- "✅ audit secrets and track compliance: https://dotenvx.com/ops",
130
- "\uD83D\uDD04 add secrets lifecycle management: https://dotenvx.com/ops",
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(`[dotenv@${version}][WARN] ${message}`);
129
+ console.error(`⚠ ${message}`);
206
130
  }
207
131
  function _debug(message) {
208
- console.log(`[dotenv@${version}][DEBUG] ${message}`);
132
+ console.log(`┆ ${message}`);
209
133
  }
210
134
  function _log(message) {
211
- console.log(`[dotenv@${version}] ${message}`);
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("Loading env from encrypted .env.vault");
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("No encoding is specified. UTF-8 is used by default");
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(`Failed to load ${path2} ${e.message}`);
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(`Failed to load ${filePath} ${e.message}`);
268
+ _debug(`failed to load ${filePath} ${e.message}`);
345
269
  }
346
270
  lastError = e;
347
271
  }
348
272
  }
349
- _log(`injecting env (${keysCount}) from ${shortPaths.join(",")} ${dim(`-- tip: ${_getRandomTip()}`)}`);
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(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
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: 5
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?.Labels?.["io.or3.cloud.deployment-id"] !== initial.state.deploymentId || !info.Mounts.some((mount) => mount.Destination === "/data" && mount.Type === "volume" && mount.Name === initial.state.volumeName))
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 join12, resolve as resolve7 } from "node:path";
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.90";
182
+ var PACKAGE_VERSION = "0.1.92";
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(join12(sourceDirectory, "or3-release.json")))
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(join12(sourceDirectory, "or3-release.json")));
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(join12(sourceDirectory, ".env")));
3068
- const providers = await readText(join12(sourceDirectory, "or3.providers.generated.ts"));
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", join12(sourceDirectory, "compose.yaml")];
3092
+ const sourceComposeFiles = ["-f", join13(sourceDirectory, "compose.yaml")];
3075
3093
  if (mode === "public")
3076
- sourceComposeFiles.push("-f", join12(sourceDirectory, "compose.public.yaml"));
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
- join12(sourceDirectory, ".env"),
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(join12(targetDirectory, ".or3-initial-credentials"), serializeInitialCredentials({
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(join12(sourceDirectory, ".env"), join12(sourceBackupDir, "config.env"));
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(join12(sourceBackupDir, "data.tgz")),
3216
- configSha256: await sha256File(join12(sourceBackupDir, "config.env")),
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(join12(sourceBackupDir, "manifest.json"), sourceManifestContents);
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 join15, resolve as resolve9 } from "node:path";
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 join13 } from "node:path";
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"], join13(backupPath, "data.tgz"), directory, secretValues(env));
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], join13(backupPath, "data.tgz"), directory, secretValues(env));
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 join14, resolve as resolve8 } from "node:path";
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(join14(deploymentPaths(directory).exports, `${backupId}.json`), { force: true });
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(join14(deploymentPaths(directory).exports, `${backup.backupId}.json`), { force: true });
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(join15(deploymentPaths(directory).exports, `${backupId}.json`))
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
- await copySecure(deploymentPaths(directory).env, join15(backupDir, "config.env"));
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(join15(backupDir, "data.tgz")),
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(join15(backupDir, "config.env")),
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(join15(backupDir, "manifest.json"), manifestContents);
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(join15(deploymentPaths(directory).exports, `${backupId}.json`))
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 join16, resolve as resolve10, sep } from "node:path";
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 = join16(await realpath(dirname5(dest)), basename5(dest));
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(join16(backupPath, file), join16(dest, file));
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(join16(backupPath, "managed-assets", name), join16(dest, "managed-assets", name));
3712
+ await copySecure(join17(backupPath, "managed-assets", name), join17(dest, "managed-assets", name));
3681
3713
  }
3682
3714
  }
3683
- const dataSha = await sha256File(join16(dest, "data.tgz"));
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(join16(dest, "config.env"));
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(join16(deploymentPaths(directory).exports, `${manifest2.backupId}.json`), `${JSON.stringify(receipt, null, 2)}
3737
+ await writeSecure(join17(deploymentPaths(directory).exports, `${manifest2.backupId}.json`), `${JSON.stringify(receipt, null, 2)}
3706
3738
  `);
3707
- let bytes = (await stat3(join16(dest, "data.tgz"))).size + (await stat3(join16(dest, "config.env"))).size + (await stat3(join16(dest, "manifest.json"))).size + (await stat3(join16(dest, "manifest.auth"))).size;
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(join16(dest, "managed-assets", name))).size;
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(join16(deploymentPaths(directory).exports, `${backupId}.json`)));
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 join17 } from "node:path";
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 = join17(directory, ".or3-initial-credentials");
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 join18, resolve as resolve11 } from "node:path";
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, join18(resolved, ".or3-initial-credentials")]) {
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(join18(paths.operatorIpc, "operator.sock"));
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 join19, relative, resolve as resolve12 } from "node:path";
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(join19(directory, ".or3-cloud", "state.json"))) {
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(join19(directory, ".or3-initial-credentials"), serializeInitialCredentials({
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 ${join19(folder, ".or3-initial-credentials")}; the same login opens ${url}/admin.`);
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 join23 } from "node:path";
4517
+ import { join as join24 } from "node:path";
4486
4518
 
4487
4519
  // src/backup/restore.ts
4488
- import { join as join20 } from "node:path";
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(join20(backupPath, "data.tgz"));
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(join20(backupPath, "config.env")));
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 join22 } from "node:path";
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 join21 } from "node:path";
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: 5
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 = join21(directory, "restarts.json");
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(join22(leasePath, "owner.json")));
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(join22(leasePath, "owner.json"), `${JSON.stringify(owner, null, 2)}
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(join22(leasePath, "owner.json"), `${JSON.stringify(owner, null, 2)}
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(join22(directory, ".or3-cloud", "restarts", "restarts.json"));
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(join23(pending.sourceDirectory, ".env")));
25148
- const sourceFiles = ["-f", join23(pending.sourceDirectory, "compose.yaml")];
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", join23(pending.sourceDirectory, "compose.public.yaml"));
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 join24 } from "node:path";
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(` ${join24(directory, file2)}`);
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(join24(directory, file2), { recursive: true, force: true });
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 join25 } from "node:path";
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(join25(backupPath, "config.env")));
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 join26 } from "node:path";
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(join26(backupPath, "config.env")));
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 join27, resolve as resolve14 } from "node:path";
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(join27(ASSET_ROOT, name));
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 = join27(directory, name);
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 join28, resolve as resolve15 } from "node:path";
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 = join28(directory, ".or3-initial-credentials");
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 join29 } from "node:path";
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(join29(directory, ".or3-cloud", "restarts", "restarts.json"));
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: join29(deploymentPaths(directory).operatorIpc, "operator.sock"),
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.90",
3
+ "version": "0.1.92",
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": ">=20"
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:d50b70d6522574798f1a3081894b5bf8fe9eca7aa5f183ad5a562de2e4f41c12",
23
- "operatorImageDigest": "sha256:7148fc08014a0a35c1bd211243c3cf7cecb4b25cce9ec0dac1ecd7e50a61121a",
24
- "sourceRevision": "8cc6e0d33cacbe304a0d3aec0d57563cdf4d7d9b"
22
+ "imageDigest": "sha256:a8364f719bc2ca4ab42c89e122a838b48819b20e02ba72b80882c07e56a6ffd4",
23
+ "operatorImageDigest": "sha256:7b38a8e3fa8296eb2f02ad9a0eb7e189f0c2209d66da1b72682ba8c2beb2c381",
24
+ "sourceRevision": "8d25b4b4eac5bba7ff210634f1add2265052f5f0"
25
25
  },
26
26
  "files": [
27
27
  "dist",