@or3/cloud 0.1.69 → 0.1.72

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.
Files changed (2) hide show
  1. package/dist/cli.mjs +36 -20
  2. package/package.json +4 -4
package/dist/cli.mjs CHANGED
@@ -31,7 +31,7 @@ import { fileURLToPath } from "node:url";
31
31
  import { createGunzip } from "node:zlib";
32
32
  var execFile = promisify(execFileCallback);
33
33
  var PACKAGE_ROOT = resolve(fileURLToPath(new URL("../", import.meta.url)));
34
- var PACKAGE_VERSION = "0.1.69";
34
+ var PACKAGE_VERSION = "0.1.72";
35
35
  var IMAGE_REPOSITORY = "ghcr.io/saluana/or3-chat";
36
36
  var ASSET_ROOT = resolve(fileURLToPath(new URL("../assets/", import.meta.url)));
37
37
  var LEGACY_STATE_SCHEMA_VERSION = 1;
@@ -368,8 +368,8 @@ async function run(command, args, cwd, environment) {
368
368
  });
369
369
  return {
370
370
  ok: true,
371
- stdout: result.stdout ?? "",
372
- stderr: result.stderr ?? "",
371
+ stdout: result.stdout,
372
+ stderr: result.stderr,
373
373
  command: printable,
374
374
  exitCode: 0
375
375
  };
@@ -494,7 +494,7 @@ function serializeCredentialValue(value) {
494
494
  return serializeEnvValue(value);
495
495
  }
496
496
  function serializeEnv(values) {
497
- return `${Object.entries(values).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${serializeEnvValue(value)}`).join(`
497
+ return `${Object.entries(values).filter((entry) => entry[1] !== undefined).map(([key, value]) => `${key}=${serializeEnvValue(value)}`).join(`
498
498
  `)}
499
499
  `;
500
500
  }
@@ -1036,7 +1036,7 @@ async function removeManagedDataVolumeForRecreation(directory, state) {
1036
1036
  } catch {
1037
1037
  throw new Error(`Docker returned unreadable metadata for managed volume ${state.volumeName}.`);
1038
1038
  }
1039
- if (volume.Name !== state.volumeName || volume.Labels?.["com.docker.compose.project"] !== state.composeProject || volume.Labels?.["com.docker.compose.volume"] !== "or3-data") {
1039
+ if (volume.Name !== state.volumeName || volume.Labels?.["com.docker.compose.project"] !== state.composeProject || volume.Labels["com.docker.compose.volume"] !== "or3-data") {
1040
1040
  throw new Error(`Volume ${state.volumeName} is not bound to this managed Compose deployment. Refusing to recreate it.`);
1041
1041
  }
1042
1042
  const removedVolume = await run("docker", ["volume", "rm", state.volumeName], directory);
@@ -1269,6 +1269,8 @@ async function assertImageReleaseIdentity(image, version) {
1269
1269
  } catch {
1270
1270
  throw new Error(`OR3 image ${image} has no readable release labels.`);
1271
1271
  }
1272
+ if (!labels || typeof labels !== "object")
1273
+ throw new Error(`OR3 image ${image} has no readable release labels.`);
1272
1274
  assertImageReleaseLabels(image, labels, version, packagedSourceRevision(version));
1273
1275
  }
1274
1276
  async function imageDigest(image) {
@@ -1636,6 +1638,7 @@ async function installManagedAssets(directory, assets) {
1636
1638
  const token = randomBytes(4).toString("hex");
1637
1639
  const staged = [];
1638
1640
  let retainRollbackCopies = false;
1641
+ const canCleanRollback = () => !retainRollbackCopies;
1639
1642
  try {
1640
1643
  for (const [name, contents] of assets) {
1641
1644
  const destination = join(directory, name);
@@ -1687,7 +1690,7 @@ async function installManagedAssets(directory, assets) {
1687
1690
  await rm(entry.replacement, { force: true }).catch(() => {
1688
1691
  return;
1689
1692
  });
1690
- if (entry.rollback && !retainRollbackCopies)
1693
+ if (entry.rollback && canCleanRollback())
1691
1694
  await rm(entry.rollback, { force: true }).catch(() => {
1692
1695
  return;
1693
1696
  });
@@ -2330,6 +2333,7 @@ async function streamCommandToFile(command, args, destination, cwd, secrets = []
2330
2333
  });
2331
2334
  let stderr = "";
2332
2335
  let timedOut = false;
2336
+ const hasTimedOut = () => timedOut;
2333
2337
  const timeout = setTimeout(() => {
2334
2338
  timedOut = true;
2335
2339
  terminateChildProcess(child);
@@ -2347,7 +2351,7 @@ async function streamCommandToFile(command, args, destination, cwd, secrets = []
2347
2351
  exit,
2348
2352
  pipeline(child.stdout, createWriteStream(temporary, { flags: "wx", mode: 384 }))
2349
2353
  ]);
2350
- if (timedOut)
2354
+ if (hasTimedOut())
2351
2355
  throw new Error(`${command} exceeded the ${STREAM_COMMAND_TIMEOUT_MS / 1000}-second archive deadline.`);
2352
2356
  if (exitCode !== 0) {
2353
2357
  throw new Error(`${command} ${args.join(" ")} exited with ${exitCode}. ${redact(stderr, secrets)}`.trim());
@@ -2375,6 +2379,7 @@ async function streamFileToCommand(command, args, source, cwd, secrets = []) {
2375
2379
  });
2376
2380
  let stderr = "";
2377
2381
  let timedOut = false;
2382
+ const hasTimedOut = () => timedOut;
2378
2383
  const timeout = setTimeout(() => {
2379
2384
  timedOut = true;
2380
2385
  terminateChildProcess(child);
@@ -2392,7 +2397,7 @@ async function streamFileToCommand(command, args, source, cwd, secrets = []) {
2392
2397
  exit,
2393
2398
  pipeline(createReadStream(source), child.stdin)
2394
2399
  ]);
2395
- if (timedOut)
2400
+ if (hasTimedOut())
2396
2401
  throw new Error(`${command} exceeded the ${STREAM_COMMAND_TIMEOUT_MS / 1000}-second archive deadline.`);
2397
2402
  if (exitCode !== 0) {
2398
2403
  throw new Error(`${command} ${args.join(" ")} exited with ${exitCode}. ${redact(stderr, secrets)}`.trim());
@@ -3124,12 +3129,19 @@ async function collectTargetReadyChecks(directory, mode, env) {
3124
3129
  }
3125
3130
  }
3126
3131
  checks.push(databasesOk ? { code: "database-integrity", status: "passed", detail: "auth.sqlite and sync.sqlite quick_check passed with managed ownership." } : { code: "database-integrity", status: "failed", detail: `SQLite integrity or ownership verification failed. ${redact(databaseCheck.stderr.trim(), secretValues(env))}` });
3127
- const baseUrl = new URL(mode === "public" ? `https://${env.OR3_PUBLIC_DOMAIN}` : `http://127.0.0.1:${env.OR3_PORT}`);
3128
- try {
3129
- validateVerificationHealth(await verificationJson(baseUrl, "/api/health?deep=true"));
3130
- checks.push({ code: "public-health", status: "passed", detail: `${baseUrl.origin} deep health reports the managed profile.` });
3131
- } catch (error) {
3132
- checks.push({ code: "public-health", status: "failed", detail: redact(error instanceof Error ? error.message : String(error)) });
3132
+ if (mode === "public") {
3133
+ const baseUrl = new URL(`https://${env.OR3_PUBLIC_DOMAIN}`);
3134
+ try {
3135
+ validateVerificationHealth(await verificationJson(baseUrl, "/api/health?deep=true"));
3136
+ checks.push({ code: "public-health", status: "passed", detail: `${baseUrl.origin} deep health reports the managed profile.` });
3137
+ } catch (error) {
3138
+ checks.push({ code: "public-health", status: "failed", detail: redact(error instanceof Error ? error.message : String(error)) });
3139
+ }
3140
+ } else {
3141
+ const internal = await run("docker", [
3142
+ ...composeArgs(directory, mode, ["exec", "-T", "or3", ...containerNodeCommand(HEALTH_SCRIPT)])
3143
+ ], directory);
3144
+ checks.push(internal.ok ? { code: "public-health", status: "passed", detail: "Container-internal deep health reports the managed profile." } : { code: "public-health", status: "failed", detail: `Container-internal deep health failed. ${redact(internal.stderr.trim(), secretValues(env))}` });
3133
3145
  }
3134
3146
  return { containerId, checks };
3135
3147
  }
@@ -3778,7 +3790,7 @@ async function assessUpdate(directory, targetVersion, options = {}) {
3778
3790
  }
3779
3791
  try {
3780
3792
  assertKnownStateSchema(state?.schemaVersion);
3781
- checks.push({ code: "state-schema", status: "passed", detail: `Managed state schema ${String(state?.schemaVersion)} is supported.` });
3793
+ checks.push({ code: "state-schema", status: "passed", detail: `Managed state schema ${String(state.schemaVersion)} is supported.` });
3782
3794
  } catch (error) {
3783
3795
  findings.push({ code: "state-schema-unsupported", severity: "blocker", message: error instanceof Error ? error.message : String(error) });
3784
3796
  }
@@ -4497,7 +4509,7 @@ async function doctorCommand(directory) {
4497
4509
  }
4498
4510
  function validateVerificationHealth(value) {
4499
4511
  const health = value;
4500
- if (!health || health.status !== "ok" || health.providers?.auth?.provider !== "basic-auth" || health.providers?.sync?.provider !== "sqlite" || health.providers?.storage?.provider !== "fs") {
4512
+ if (!health || health.status !== "ok" || health.providers?.auth?.provider !== "basic-auth" || health.providers.sync?.provider !== "sqlite" || health.providers.storage?.provider !== "fs") {
4501
4513
  throw new Error("Public deep health does not report the managed Basic Auth + SQLite + filesystem profile.");
4502
4514
  }
4503
4515
  return health;
@@ -4517,7 +4529,7 @@ async function verificationJson(baseUrl, path, options = {}) {
4517
4529
  accept: "application/json",
4518
4530
  ...options.body === undefined ? {} : { "content-type": "application/json" },
4519
4531
  ...options.cookie ? { cookie: options.cookie } : {},
4520
- ...method === "GET" ? {} : { origin: baseUrl.origin }
4532
+ ...method === "GET" ? {} : { origin: baseUrl.origin, "x-or3-cloud-intent": "mutation" }
4521
4533
  },
4522
4534
  ...options.body === undefined ? {} : { body: JSON.stringify(options.body) }
4523
4535
  });
@@ -4525,7 +4537,11 @@ async function verificationJson(baseUrl, path, options = {}) {
4525
4537
  const detail = (await response.text().catch(() => "")).slice(0, 300);
4526
4538
  throw new Error(`${response.url} returned HTTP ${response.status}${detail ? `: ${detail}` : ""}`);
4527
4539
  }
4528
- return response.json();
4540
+ const value = await response.json();
4541
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4542
+ throw new Error(`${response.url} returned an invalid JSON object.`);
4543
+ }
4544
+ return value;
4529
4545
  }
4530
4546
  function sameOriginVerificationUrl(baseUrl, value, label) {
4531
4547
  if (typeof value !== "string")
@@ -4592,7 +4608,7 @@ async function verifyPublicApplication(baseUrl, credentials) {
4592
4608
  throw new Error("Public Basic Auth sign-in did not set a session cookie.");
4593
4609
  try {
4594
4610
  const session = await verificationJson(baseUrl, "/api/auth/session", { cookie });
4595
- if (session.session?.user?.email !== email || !session.session?.workspace?.id) {
4611
+ if (session.session?.user?.email !== email || !session.session.workspace?.id) {
4596
4612
  throw new Error("Public session hydration did not return the verification user and workspace.");
4597
4613
  }
4598
4614
  const workspaceId = String(session.session.workspace.id);
@@ -4760,7 +4776,7 @@ async function provisionManagedCredentialsOnce(baseUrl, email, password, adminUs
4760
4776
  const appCookie = responseCookie(appSignIn, "Managed owner provisioning");
4761
4777
  try {
4762
4778
  const session = await verificationJson(baseUrl, "/api/auth/session", { cookie: appCookie });
4763
- if (session.session?.user?.email !== email || !session.session?.workspace?.id) {
4779
+ if (session.session?.user?.email !== email || !session.session.workspace?.id) {
4764
4780
  throw new Error("Managed owner provisioning did not create the expected account and workspace.");
4765
4781
  }
4766
4782
  } finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@or3/cloud",
3
- "version": "0.1.69",
3
+ "version": "0.1.72",
4
4
  "description": "Install and operate the OR3 Cloud container distribution",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0",
@@ -19,9 +19,9 @@
19
19
  "dashboardUpdateProtocol": 1,
20
20
  "dashboardUpdateMinimumSourceVersion": "0.1.40",
21
21
  "stateSchema": 1,
22
- "imageDigest": "sha256:996f32c9cdb811a7bed6ae4f061daf4b08f178ed1dd4afb937a29144413c643b",
23
- "operatorImageDigest": "sha256:10768bc90617001265537ba81f8fe0f18056c876195ae6738a630a10636e8baf",
24
- "sourceRevision": "5cf6ae829820ad956da9e6a27591ddf54a376b84"
22
+ "imageDigest": "sha256:c4b408ea3a5fbd647b26d6efe89ddad58f90be7528aa2b5b6ed20d6d41c928ac",
23
+ "operatorImageDigest": "sha256:05b03560594da28e436977b82a905aa9a7702668b5e4ac4daea8b54376a75813",
24
+ "sourceRevision": "90c5756fa313331df7be656fcbc1bce5a4164d6e"
25
25
  },
26
26
  "files": [
27
27
  "dist",