@or3/cloud 0.1.63 → 0.1.65

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 (3) hide show
  1. package/README.md +11 -0
  2. package/dist/cli.mjs +19 -8
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -58,6 +58,11 @@ npx @or3/cloud stop
58
58
  npx @or3/cloud restart
59
59
  ```
60
60
 
61
+ The canonical procedure for both development and managed deployments is
62
+ [Updating OR3](https://github.com/Saluana/or3-chat/blob/or3-cloud/docs/cloud-updates.md).
63
+ This package is the managed path: it accepts only published stable releases and
64
+ never a development image.
65
+
61
66
  Run `verify` after every update and before declaring a deployment healthy. It
62
67
  checks the managed image digest, deep provider health, Basic Auth sign-in,
63
68
  session hydration, SQLite sync, a disposable filesystem upload/download/delete
@@ -87,6 +92,12 @@ carry a deployment-local authentication tag, so an altered or foreign archive
87
92
  cannot supply configuration or executable Compose assets. Every mutating
88
93
  command holds one deployment-wide lease; do not delete `.or3-cloud` lock or
89
94
  recovery files by hand.
95
+ Update recovery reads the authenticated pre-update snapshot recorded in
96
+ `backupPath`/`backupId`; restore and rollback recovery use their separate
97
+ `previousBackupPath`/`previousBackupId` snapshot. Recovery restores that snapshot,
98
+ including its data and release assets, before clearing the pending operation.
99
+ Legacy `adopt-source-*` directories are preserved outside managed backup
100
+ retention and are not treated as authenticated restore points.
90
101
  Backup artifacts are allocated in that journal before archiving, fully
91
102
  revalidated before success is reported, and safely removed by `recover` if a
92
103
  hard interruption leaves an incomplete artifact. A standalone backup and a
package/dist/cli.mjs CHANGED
@@ -11,6 +11,7 @@ import { createReadStream, createWriteStream, existsSync, readFileSync } from "n
11
11
  import {
12
12
  chmod,
13
13
  copyFile,
14
+ lstat,
14
15
  mkdir,
15
16
  open,
16
17
  readFile,
@@ -30,7 +31,7 @@ import { fileURLToPath } from "node:url";
30
31
  import { createGunzip } from "node:zlib";
31
32
  var execFile = promisify(execFileCallback);
32
33
  var PACKAGE_ROOT = resolve(fileURLToPath(new URL("../", import.meta.url)));
33
- var PACKAGE_VERSION = "0.1.63";
34
+ var PACKAGE_VERSION = "0.1.65";
34
35
  var IMAGE_REPOSITORY = "ghcr.io/saluana/or3-chat";
35
36
  var ASSET_ROOT = resolve(fileURLToPath(new URL("../assets/", import.meta.url)));
36
37
  var STATE_SCHEMA_VERSION = 1;
@@ -1835,6 +1836,9 @@ async function enumerateBackups(directory) {
1835
1836
  }
1836
1837
  const result = [];
1837
1838
  for (const entry of entries) {
1839
+ if (/^adopt-source-[0-9A-Za-z-]+$/.test(entry) && (await lstat(join(backupsRoot, entry))).isDirectory()) {
1840
+ continue;
1841
+ }
1838
1842
  if (!BACKUP_ID_PATTERN.test(entry)) {
1839
1843
  throw new Error(`Backup store contains an unexpected artifact ${join(backupsRoot, entry)}. Refusing to use it for retention; inspect or remove it explicitly.`);
1840
1844
  }
@@ -2996,7 +3000,15 @@ async function resolveBackup(directory, value) {
2996
3000
  throw new Error(`Backup ${value} was not found in ${deploymentPaths(directory).backups}.`);
2997
3001
  return resolve(candidate);
2998
3002
  }
2999
- async function recordedBackupPath(directory, pending, previous = false) {
3003
+ function assertDashboardOperatorMounts(mounts, directory) {
3004
+ const destinations = new Set(mounts.map((mount) => mount.Destination));
3005
+ for (const destination of ["/var/run/docker.sock", resolve(directory), "/run/or3-operator"]) {
3006
+ if (!destinations.has(destination))
3007
+ throw new Error(`Dashboard operator is missing its required ${destination} mount.`);
3008
+ }
3009
+ }
3010
+ async function recordedBackupPath(directory, pending) {
3011
+ const previous = pending.operation !== "update";
3000
3012
  const path = previous ? pending.previousBackupPath : pending.backupPath;
3001
3013
  const backupId = previous ? pending.previousBackupId : pending.backupId;
3002
3014
  const resolved = path ? resolve(path) : backupId ? await resolveBackup(directory, backupId) : undefined;
@@ -3021,7 +3033,7 @@ async function restorePreMutationSnapshot(directory, state, env) {
3021
3033
  const pending = state.incompleteOperation;
3022
3034
  if (!pending)
3023
3035
  throw new Error("No incomplete operation is available to restore.");
3024
- const previous = await recordedBackupPath(directory, pending, true);
3036
+ const previous = await recordedBackupPath(directory, pending);
3025
3037
  await updatePending(directory, state, { phase: "restoring-previous" });
3026
3038
  if (pending.recreateDataVolume) {
3027
3039
  await restoreBackupData(directory, state, env, previous.path, { recreateDataVolume: true });
@@ -3309,11 +3321,7 @@ async function doctorCommand(directory) {
3309
3321
  if (state.deploymentId && container.Config?.Labels?.["io.or3.cloud.deployment-id"] !== state.deploymentId) {
3310
3322
  throw new Error("Dashboard operator deployment identity does not match managed state.");
3311
3323
  }
3312
- const destinations = new Set(container.Mounts?.map((mount) => mount.Destination));
3313
- for (const destination of ["/var/run/docker.sock", "/deployment", "/run/or3-operator"]) {
3314
- if (!destinations.has(destination))
3315
- throw new Error(`Dashboard operator is missing its required ${destination} mount.`);
3316
- }
3324
+ assertDashboardOperatorMounts(container.Mounts ?? [], resolved);
3317
3325
  const ipcDirectory = await stat(paths.operatorIpc);
3318
3326
  const operatorSocket = await stat(join(paths.operatorIpc, "operator.sock"));
3319
3327
  const operatorUid = Number(env.OR3_OPERATOR_UID);
@@ -4634,11 +4642,13 @@ export {
4634
4642
  restoreManagedAssets,
4635
4643
  requiredArchiveSpace,
4636
4644
  redact,
4645
+ recordedBackupPath,
4637
4646
  purgeVolumesFromState,
4638
4647
  parseFlags,
4639
4648
  parseEnv,
4640
4649
  managedAssetNames,
4641
4650
  isVersion,
4651
+ enumerateBackups,
4642
4652
  dashboardOperatorHandoffArgs,
4643
4653
  copyAssets,
4644
4654
  checkResolvedLoopbackBinding,
@@ -4652,6 +4662,7 @@ export {
4652
4662
  assertPurgeBackupFreshness,
4653
4663
  assertImageReleaseLabels,
4654
4664
  assertEnoughFreeSpace,
4665
+ assertDashboardOperatorMounts,
4655
4666
  assertCommandPositionals,
4656
4667
  assertCommandFlags,
4657
4668
  assertBackupMatchesDeployment,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@or3/cloud",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "description": "Install and operate the OR3 Cloud container distribution",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0",
@@ -18,9 +18,9 @@
18
18
  "or3Cloud": {
19
19
  "dashboardUpdateProtocol": 1,
20
20
  "dashboardUpdateMinimumSourceVersion": "0.1.40",
21
- "imageDigest": "sha256:5e2dd31369dfc365a5774d67effc4eb7a876a40a45aedeb20982a585652bd206",
22
- "operatorImageDigest": "sha256:ab9d864b630b6e05db05e45041a25153b82b95e735f7a1579ff5edcfebbf84f8",
23
- "sourceRevision": "a14ddda64808ec91056d9f003589bd5b04986edf"
21
+ "imageDigest": "sha256:cce2c2331419dbb2e2d0b17e0a7da0c9f3106b3c6dc30f680a6d9f145f0c6c6e",
22
+ "operatorImageDigest": "sha256:b8732a186a6bcfbd47f93333b7ed90991b20f4dd569ec08aec509e9f21ab6561",
23
+ "sourceRevision": "19b8889981b2f5f958c23679887a9a4bebd0f58c"
24
24
  },
25
25
  "files": [
26
26
  "dist",