mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 04:31:10 +00:00
151 lines
4.8 KiB
TypeScript
151 lines
4.8 KiB
TypeScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
|
import { writeJsonFileAtomically } from "openclaw/plugin-sdk/json-store";
|
|
import { resolveRequiredHomeDir } from "openclaw/plugin-sdk/provider-auth";
|
|
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
import { detectLegacyMatrixCrypto } from "./legacy-crypto.js";
|
|
import { detectLegacyMatrixState } from "./legacy-state.js";
|
|
|
|
const MATRIX_MIGRATION_SNAPSHOT_DIRNAME = "openclaw-migrations";
|
|
|
|
function isMatrixLegacyCryptoInspectorAvailable(): boolean {
|
|
return true;
|
|
}
|
|
|
|
type MatrixMigrationSnapshotMarker = {
|
|
version: 1;
|
|
createdAt: string;
|
|
archivePath: string;
|
|
trigger: string;
|
|
includeWorkspace: boolean;
|
|
};
|
|
|
|
export type MatrixMigrationSnapshotResult = {
|
|
created: boolean;
|
|
archivePath: string;
|
|
markerPath: string;
|
|
};
|
|
|
|
function loadSnapshotMarker(filePath: string): MatrixMigrationSnapshotMarker | null {
|
|
try {
|
|
if (!fs.existsSync(filePath)) {
|
|
return null;
|
|
}
|
|
const parsed = JSON.parse(
|
|
fs.readFileSync(filePath, "utf8"),
|
|
) as Partial<MatrixMigrationSnapshotMarker>;
|
|
if (
|
|
parsed.version !== 1 ||
|
|
typeof parsed.createdAt !== "string" ||
|
|
typeof parsed.archivePath !== "string" ||
|
|
typeof parsed.trigger !== "string"
|
|
) {
|
|
return null;
|
|
}
|
|
return {
|
|
version: 1,
|
|
createdAt: parsed.createdAt,
|
|
archivePath: parsed.archivePath,
|
|
trigger: parsed.trigger,
|
|
includeWorkspace: parsed.includeWorkspace === true,
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function resolveMatrixMigrationSnapshotMarkerPath(
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
): string {
|
|
const stateDir = resolveStateDir(env, os.homedir);
|
|
return path.join(stateDir, "matrix", "migration-snapshot.json");
|
|
}
|
|
|
|
export function resolveMatrixMigrationSnapshotOutputDir(
|
|
env: NodeJS.ProcessEnv = process.env,
|
|
): string {
|
|
const homeDir = resolveRequiredHomeDir(env, os.homedir);
|
|
return path.join(homeDir, "Backups", MATRIX_MIGRATION_SNAPSHOT_DIRNAME);
|
|
}
|
|
|
|
export function hasPendingMatrixMigration(params: {
|
|
cfg: OpenClawConfig;
|
|
env?: NodeJS.ProcessEnv;
|
|
}): boolean {
|
|
const env = params.env ?? process.env;
|
|
const legacyState = detectLegacyMatrixState({ cfg: params.cfg, env });
|
|
if (legacyState) {
|
|
return true;
|
|
}
|
|
const legacyCrypto = detectLegacyMatrixCrypto({ cfg: params.cfg, env });
|
|
return legacyCrypto.plans.length > 0 || legacyCrypto.warnings.length > 0;
|
|
}
|
|
|
|
export function hasActionableMatrixMigration(params: {
|
|
cfg: OpenClawConfig;
|
|
env?: NodeJS.ProcessEnv;
|
|
}): boolean {
|
|
const env = params.env ?? process.env;
|
|
const legacyState = detectLegacyMatrixState({ cfg: params.cfg, env });
|
|
if (legacyState && !("warning" in legacyState)) {
|
|
return true;
|
|
}
|
|
const legacyCrypto = detectLegacyMatrixCrypto({ cfg: params.cfg, env });
|
|
return legacyCrypto.plans.length > 0 && isMatrixLegacyCryptoInspectorAvailable();
|
|
}
|
|
|
|
export async function maybeCreateMatrixMigrationSnapshot(params: {
|
|
trigger: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
outputDir?: string;
|
|
createBackupArchive?: typeof import("openclaw/plugin-sdk/runtime").createBackupArchive;
|
|
log?: { info?: (message: string) => void; warn?: (message: string) => void };
|
|
}): Promise<MatrixMigrationSnapshotResult> {
|
|
const env = params.env ?? process.env;
|
|
const createBackupArchive =
|
|
params.createBackupArchive ?? (await import("openclaw/plugin-sdk/runtime")).createBackupArchive;
|
|
const markerPath = resolveMatrixMigrationSnapshotMarkerPath(env);
|
|
const existingMarker = loadSnapshotMarker(markerPath);
|
|
if (existingMarker?.archivePath && fs.existsSync(existingMarker.archivePath)) {
|
|
params.log?.info?.(
|
|
`matrix: reusing existing pre-migration backup snapshot: ${existingMarker.archivePath}`,
|
|
);
|
|
return {
|
|
created: false,
|
|
archivePath: existingMarker.archivePath,
|
|
markerPath,
|
|
};
|
|
}
|
|
if (existingMarker?.archivePath && !fs.existsSync(existingMarker.archivePath)) {
|
|
params.log?.warn?.(
|
|
`matrix: previous migration snapshot is missing (${existingMarker.archivePath}); creating a replacement backup before continuing`,
|
|
);
|
|
}
|
|
|
|
const snapshot = await createBackupArchive({
|
|
output: (() => {
|
|
const outputDir = params.outputDir ?? resolveMatrixMigrationSnapshotOutputDir(env);
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
return outputDir;
|
|
})(),
|
|
includeWorkspace: false,
|
|
});
|
|
|
|
const marker: MatrixMigrationSnapshotMarker = {
|
|
version: 1,
|
|
createdAt: snapshot.createdAt,
|
|
archivePath: snapshot.archivePath,
|
|
trigger: params.trigger,
|
|
includeWorkspace: snapshot.includeWorkspace,
|
|
};
|
|
await writeJsonFileAtomically(markerPath, marker);
|
|
params.log?.info?.(`matrix: created pre-migration backup snapshot: ${snapshot.archivePath}`);
|
|
return {
|
|
created: true,
|
|
archivePath: snapshot.archivePath,
|
|
markerPath,
|
|
};
|
|
}
|