@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.
- package/README.md +11 -0
- package/dist/cli.mjs +28 -9
- 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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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:
|
|
22
|
-
"operatorImageDigest": "sha256:
|
|
23
|
-
"sourceRevision": "
|
|
21
|
+
"imageDigest": "sha256:fbe393d371bcd2cfdcdd4e8295cbb61c6ec11bbedccf84b88042ff28b8155fb6",
|
|
22
|
+
"operatorImageDigest": "sha256:7eda3cab7508094bc51a43bb0c10924a806eb6127160cd6fccbd77a8fda99e7f",
|
|
23
|
+
"sourceRevision": "79691e3f6b14ac7555aba765aee794263583d503"
|
|
24
24
|
},
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|