mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
37b4fc8621
* refactor(infra): extract shared git exec and verified snapshot-copy helpers Moves the worktrees git wrapper to src/infra/git-exec.ts (with optional maxOutputBytes for large buffered reads) and the online-backup/sanitize/ VACUUM/verify snapshot step into src/snapshot/openclaw-snapshot-copy.ts so snapshot backends share one hardened copy path. Behavior-identical moves; all importers updated. * feat(snapshot): git-backed versioned SQLite snapshot engine Deterministic per-table JSONL dumps (PK-ordered, lossless bigint/blob encoding), verbatim DDL preservation, virtual/shadow-table skipping with FTS rebuild on restore, secret-table redaction policy, manifest with per-table row counts and content hashes, and restore verification by re-serialization. Unchanged data produces no commit. * feat(backup): recorded runs, freshness surfacing, and scheduled git backups Every backup attempt is recorded in the previously writer-less backup_runs table (bounded to 200 rows). openclaw status gains a Backups overview row and JSON payload; doctor prints an informational hint when no successful backup is recorded or the newest is stale. New commands: backup git init/create/log/verify/restore and backup enable/disable, which provision one idempotent gateway cron job running scheduled git backups. * fix(state): stop bumping schema_meta.updated_at on unchanged opens updated_at now records when schema metadata actually changed instead of when the database was last opened; unconditional bumps dirtied the row on every open and defeated no-change backup detection. * docs: document versioned git backups, scheduling, and backup freshness * fix(backup): satisfy CI ownership checks * fix(backup): complete CI contract coverage * fix(backup): complete credential table redaction * fix(backup): isolate git repository ownership * fix(backup): persist push degradation * fix(backup): atomically converge schedules * fix(status): isolate backup freshness environment * fix(status): carry scan environment to freshness reads * fix(backup): harden Git repository ownership * docs(backup): document Git repository safety * fix(backup): non-creating outcome log and origin preflight for pushed schedules Recording a backup outcome never bootstraps an absent state database (a failed backup on a fresh host would otherwise create a blank DB that a retry treats as real input), and backup enable --push now requires the repository to have an origin remote, pointing at backup git init --remote instead of scheduling permanently degraded pushes. * refactor(worktrees): use shared git exec helpers * refactor(worktrees): remove unused git buffer wrapper * refactor(worktrees): consume buffered git helper * feat(backup): redact pushed schedules by default Unattended recurring pushes retain credential-bearing tables durably in remote Git history, so backup enable --push now defaults to --exclude-secrets; --include-secrets is the explicit full-fidelity override (still warned). Local non-push schedules keep full fidelity for complete restores. * fix(backup): redact audit HMAC and OAuth pending state; tolerate absent backup_runs Adds audit_identity_keys (audit HMAC key) and mcp_oauth_pending_authorizations (live OAuth callback state) to the redaction inventory, and makes read-only backup freshness treat a same-version database without the additive backup_runs table as no recorded backups instead of failing before a writable open converges the schema. * fix(backup): restrict schedules to local gateways * fix(snapshot): harden Git restore and redaction * fix(backup): block pushes of adopted history * fix(backup): contain commits and pairing secrets
94 lines
3.1 KiB
TypeScript
94 lines
3.1 KiB
TypeScript
// Runtime gateway RPC helper shared by CLI commands that call the Gateway.
|
|
import {
|
|
GATEWAY_CLIENT_MODES,
|
|
GATEWAY_CLIENT_NAMES,
|
|
} from "../../packages/gateway-protocol/src/client-info.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { callGateway, isImplicitLocalGatewayTarget } from "../gateway/call.js";
|
|
import type { GatewayRpcOpts } from "./gateway-rpc.types.js";
|
|
import { parseTimeoutMsWithFallback } from "./parse-timeout.js";
|
|
import { withProgress } from "./progress.js";
|
|
|
|
type CallGatewayFromCliRuntimeExtra = {
|
|
clientName?: Parameters<typeof callGateway>[0]["clientName"];
|
|
mode?: Parameters<typeof callGateway>[0]["mode"];
|
|
deviceIdentity?: Parameters<typeof callGateway>[0]["deviceIdentity"];
|
|
signal?: Parameters<typeof callGateway>[0]["signal"];
|
|
expectFinal?: boolean;
|
|
progress?: boolean;
|
|
scopes?: Parameters<typeof callGateway>[0]["scopes"];
|
|
defaultTimeoutMs?: number;
|
|
timeoutMs?: number | null;
|
|
label?: string;
|
|
useStoredDeviceAuth?: boolean;
|
|
requiredStoredDeviceAuthScopes?: Parameters<
|
|
typeof callGateway
|
|
>[0]["requiredStoredDeviceAuthScopes"];
|
|
requireLocalBackendSharedAuth?: boolean;
|
|
};
|
|
|
|
type GatewayCliTransportRpcOpts = Omit<GatewayRpcOpts, "timeout"> & {
|
|
config?: OpenClawConfig;
|
|
timeout?: string | null;
|
|
localPortOverride?: number;
|
|
};
|
|
|
|
const DEFAULT_GATEWAY_RPC_TIMEOUT_MS = 30_000;
|
|
|
|
export async function isImplicitLocalGatewayTargetFromCliRuntime(
|
|
opts: GatewayCliTransportRpcOpts,
|
|
): Promise<boolean> {
|
|
return await isImplicitLocalGatewayTarget({
|
|
config: opts.config,
|
|
url: opts.url,
|
|
localPortOverride: opts.localPortOverride,
|
|
});
|
|
}
|
|
|
|
export async function callGatewayFromCliRuntime(
|
|
method: string,
|
|
opts: GatewayCliTransportRpcOpts,
|
|
params?: unknown,
|
|
extra?: CallGatewayFromCliRuntimeExtra,
|
|
) {
|
|
// Progress is disabled for JSON output so stdout stays parseable.
|
|
const showProgress = extra?.progress ?? opts.json !== true;
|
|
const timeoutMs =
|
|
extra?.timeoutMs !== undefined
|
|
? extra.timeoutMs
|
|
: opts.timeout === null
|
|
? null
|
|
: parseTimeoutMsWithFallback(
|
|
opts.timeout,
|
|
extra?.defaultTimeoutMs ?? DEFAULT_GATEWAY_RPC_TIMEOUT_MS,
|
|
{ invalidType: "error" },
|
|
);
|
|
return await withProgress(
|
|
{
|
|
label: extra?.label ?? `Gateway ${method}`,
|
|
indeterminate: true,
|
|
enabled: showProgress,
|
|
},
|
|
async () =>
|
|
await callGateway({
|
|
config: opts.config,
|
|
url: opts.url,
|
|
token: opts.token,
|
|
password: opts.password,
|
|
method,
|
|
params,
|
|
deviceIdentity: extra?.deviceIdentity,
|
|
expectFinal: extra?.expectFinal ?? Boolean(opts.expectFinal),
|
|
scopes: extra?.scopes,
|
|
useStoredDeviceAuth: extra?.useStoredDeviceAuth,
|
|
requiredStoredDeviceAuthScopes: extra?.requiredStoredDeviceAuthScopes,
|
|
requireLocalBackendSharedAuth: extra?.requireLocalBackendSharedAuth,
|
|
signal: extra?.signal,
|
|
timeoutMs,
|
|
localPortOverride: opts.localPortOverride,
|
|
clientName: extra?.clientName ?? GATEWAY_CLIENT_NAMES.CLI,
|
|
mode: extra?.mode ?? GATEWAY_CLIENT_MODES.CLI,
|
|
}),
|
|
);
|
|
}
|