@or3/cloud 0.1.64 → 0.1.66

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 +28 -9
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -92,6 +92,17 @@ carry a deployment-local authentication tag, so an altered or foreign archive
92
92
  cannot supply configuration or executable Compose assets. Every mutating
93
93
  command holds one deployment-wide lease; do not delete `.or3-cloud` lock or
94
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.
101
+ Backups from releases before authentication tags existed are also preserved:
102
+ `backup list` and retention skip `backup-*` entries with no `manifest.auth` and
103
+ print a warning. They do not sign, migrate, or delete those archives. An existing
104
+ but invalid tag remains an error; restore, recovery, and export still require
105
+ valid deployment authentication.
95
106
  Backup artifacts are allocated in that journal before archiving, fully
96
107
  revalidated before success is reported, and safely removed by `recover` if a
97
108
  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.64";
34
+ var PACKAGE_VERSION = "0.1.66";
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,10 +1836,21 @@ 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
  }
1841
1845
  const path = join(backupsRoot, entry);
1846
+ try {
1847
+ await lstat(join(path, "manifest.auth"));
1848
+ } catch (error) {
1849
+ if (error.code !== "ENOENT")
1850
+ throw error;
1851
+ console.warn(`Preserving unauthenticated backup ${entry}: no manifest.auth; excluded from managed backup listing and retention.`);
1852
+ continue;
1853
+ }
1842
1854
  const manifest = await readManifest(path, directory);
1843
1855
  if (manifest.backupId !== entry) {
1844
1856
  throw new Error(`Backup directory ${path} does not match manifest ID ${manifest.backupId}. Refusing to use it for retention.`);
@@ -2742,7 +2754,7 @@ async function backupListCommand(directory) {
2742
2754
  const loaded = await loadManaged(directory);
2743
2755
  const backups = await enumerateBackups(loaded.directory);
2744
2756
  if (backups.length === 0) {
2745
- console.log(`No backups exist yet for ${loaded.directory}. Run "npx @or3/cloud backup" to create one.`);
2757
+ console.log(`No authenticated backups are available for ${loaded.directory}. Run "npx @or3/cloud backup" to create one.`);
2746
2758
  return;
2747
2759
  }
2748
2760
  console.log(`OR3 Cloud backups for ${loaded.directory} (${backups.length}):`);
@@ -2996,7 +3008,15 @@ async function resolveBackup(directory, value) {
2996
3008
  throw new Error(`Backup ${value} was not found in ${deploymentPaths(directory).backups}.`);
2997
3009
  return resolve(candidate);
2998
3010
  }
2999
- async function recordedBackupPath(directory, pending, previous = false) {
3011
+ function assertDashboardOperatorMounts(mounts, directory) {
3012
+ const destinations = new Set(mounts.map((mount) => mount.Destination));
3013
+ for (const destination of ["/var/run/docker.sock", resolve(directory), "/run/or3-operator"]) {
3014
+ if (!destinations.has(destination))
3015
+ throw new Error(`Dashboard operator is missing its required ${destination} mount.`);
3016
+ }
3017
+ }
3018
+ async function recordedBackupPath(directory, pending) {
3019
+ const previous = pending.operation !== "update";
3000
3020
  const path = previous ? pending.previousBackupPath : pending.backupPath;
3001
3021
  const backupId = previous ? pending.previousBackupId : pending.backupId;
3002
3022
  const resolved = path ? resolve(path) : backupId ? await resolveBackup(directory, backupId) : undefined;
@@ -3021,7 +3041,7 @@ async function restorePreMutationSnapshot(directory, state, env) {
3021
3041
  const pending = state.incompleteOperation;
3022
3042
  if (!pending)
3023
3043
  throw new Error("No incomplete operation is available to restore.");
3024
- const previous = await recordedBackupPath(directory, pending, true);
3044
+ const previous = await recordedBackupPath(directory, pending);
3025
3045
  await updatePending(directory, state, { phase: "restoring-previous" });
3026
3046
  if (pending.recreateDataVolume) {
3027
3047
  await restoreBackupData(directory, state, env, previous.path, { recreateDataVolume: true });
@@ -3309,11 +3329,7 @@ async function doctorCommand(directory) {
3309
3329
  if (state.deploymentId && container.Config?.Labels?.["io.or3.cloud.deployment-id"] !== state.deploymentId) {
3310
3330
  throw new Error("Dashboard operator deployment identity does not match managed state.");
3311
3331
  }
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
- }
3332
+ assertDashboardOperatorMounts(container.Mounts ?? [], resolved);
3317
3333
  const ipcDirectory = await stat(paths.operatorIpc);
3318
3334
  const operatorSocket = await stat(join(paths.operatorIpc, "operator.sock"));
3319
3335
  const operatorUid = Number(env.OR3_OPERATOR_UID);
@@ -4634,11 +4650,13 @@ export {
4634
4650
  restoreManagedAssets,
4635
4651
  requiredArchiveSpace,
4636
4652
  redact,
4653
+ recordedBackupPath,
4637
4654
  purgeVolumesFromState,
4638
4655
  parseFlags,
4639
4656
  parseEnv,
4640
4657
  managedAssetNames,
4641
4658
  isVersion,
4659
+ enumerateBackups,
4642
4660
  dashboardOperatorHandoffArgs,
4643
4661
  copyAssets,
4644
4662
  checkResolvedLoopbackBinding,
@@ -4652,6 +4670,7 @@ export {
4652
4670
  assertPurgeBackupFreshness,
4653
4671
  assertImageReleaseLabels,
4654
4672
  assertEnoughFreeSpace,
4673
+ assertDashboardOperatorMounts,
4655
4674
  assertCommandPositionals,
4656
4675
  assertCommandFlags,
4657
4676
  assertBackupMatchesDeployment,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@or3/cloud",
3
- "version": "0.1.64",
3
+ "version": "0.1.66",
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:c0899969a8a93c91b68933c898984d927e9af1db2f24d45c065d9a3d47336a28",
22
- "operatorImageDigest": "sha256:cbbb6f3cbc51c07a98cace1deb0cca9784b0eaf7cbf727f739b44068d85aac90",
23
- "sourceRevision": "e63d5aeae215de841d5e440730367918c2802575"
21
+ "imageDigest": "sha256:fbe393d371bcd2cfdcdd4e8295cbb61c6ec11bbedccf84b88042ff28b8155fb6",
22
+ "operatorImageDigest": "sha256:7eda3cab7508094bc51a43bb0c10924a806eb6127160cd6fccbd77a8fda99e7f",
23
+ "sourceRevision": "79691e3f6b14ac7555aba765aee794263583d503"
24
24
  },
25
25
  "files": [
26
26
  "dist",