From e0e4469d34204844b2843e01a98914bb0b024401 Mon Sep 17 00:00:00 2001 From: KHA Entertaiment Date: Sat, 21 Mar 2026 22:04:22 -0700 Subject: [PATCH 1/2] fix: add native-backup/native-verify CLI commands and fix code issues - Add native-backup and native-verify CLI commands to wrap openclaw backup - Fix duplicate imports in skill.py (removed non-existent generate_restore_url) - Remove duplicate/broken methods from core.py (add_checkpoint_serve_info and second get_checkpoint_serves definition were overriding correct versions) - All 43 tests pass Co-Authored-By: Claude Opus 4.6 --- src/ocbs/cli.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++- src/ocbs/core.py | 71 +---------------------------------------- src/ocbs/skill.py | 7 ++--- 3 files changed, 82 insertions(+), 76 deletions(-) diff --git a/src/ocbs/cli.py b/src/ocbs/cli.py index 43a62fc..ddde255 100644 --- a/src/ocbs/cli.py +++ b/src/ocbs/cli.py @@ -160,5 +160,83 @@ def checkpoint(ctx, reason, serve): sys.exit(1) +@main.command() +@click.option('--scope', type=click.Choice(['config', 'config+session', 'config+session+workspace']), + default='config', help='Backup scope') +@click.option('--verify', is_flag=True, help='Verify archive after creation') +@click.option('--output', '-o', type=click.Path(exists=False, file_okay=False, dir_okay=True), + help='Output directory for the archive') +def native_backup(scope, verify, output): + """Run OpenClaw native backup to create a tar.gz archive.""" + import subprocess + + args = ["openclaw", "backup", "create"] + + if scope == "config": + args.append("--only-config") + elif scope == "config+session": + args.append("--no-include-workspace") + # Full scope uses no flags + + if verify: + args.append("--verify") + + if output: + args.extend(["--output", output]) + + try: + result = subprocess.run( + args, + capture_output=True, + text=True, + timeout=600 + ) + + if result.returncode != 0: + click.echo(f"Native backup failed: {result.stderr}", err=True) + sys.exit(1) + + click.echo(f"Native backup created successfully:\n{result.stdout}") + except subprocess.TimeoutExpired: + click.echo("Error: Native backup timed out (10 minutes)", err=True) + sys.exit(1) + except FileNotFoundError: + click.echo("Error: openclaw command not found. Ensure OpenClaw is installed.", err=True) + sys.exit(1) + except Exception as e: + click.echo(f"Error running native backup: {e}", err=True) + sys.exit(1) + + +@main.command() +@click.argument('archive', type=click.Path(exists=True)) +def native_verify(archive): + """Verify a native backup archive.""" + import subprocess + + try: + result = subprocess.run( + ["openclaw", "backup", "verify", archive], + capture_output=True, + text=True, + timeout=60 + ) + + if result.returncode != 0: + click.echo(f"Verification failed: {result.stderr}", err=True) + sys.exit(1) + + click.echo(f"Archive verified successfully:\n{result.stdout}") + except subprocess.TimeoutExpired: + click.echo("Error: Verification timed out", err=True) + sys.exit(1) + except FileNotFoundError: + click.echo("Error: openclaw command not found. Ensure OpenClaw is installed.", err=True) + sys.exit(1) + except Exception as e: + click.echo(f"Error verifying archive: {e}", err=True) + sys.exit(1) + + if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/src/ocbs/core.py b/src/ocbs/core.py index bdc36cc..bb0c410 100644 --- a/src/ocbs/core.py +++ b/src/ocbs/core.py @@ -822,73 +822,4 @@ def get_checkpoint(self, checkpoint_id: str) -> Optional[dict]: 'timestamp': row[3], 'active': bool(row[4]) } - return None - - def add_checkpoint_serve_info(self, checkpoint_id: str, token: str, expires_at: str) -> bool: - """Add serve endpoint reference to a checkpoint record.""" - # Create serve_records table if not exists - with sqlite3.connect(self.db_path, timeout=30) as conn: - conn.execute("PRAGMA journal_mode=WAL") - conn.execute("PRAGMA busy_timeout=30000") - conn.execute(""" - CREATE TABLE IF NOT EXISTS serve_records ( - token TEXT PRIMARY KEY, - checkpoint_id TEXT, - created_at TEXT, - expires_at TEXT, - used INTEGER DEFAULT 0, - proceeded INTEGER DEFAULT 0, - restored INTEGER DEFAULT 0, - FOREIGN KEY (checkpoint_id) REFERENCES checkpoints(checkpoint_id) - ) - """) - - # Check if token already exists - cursor = conn.execute( - "SELECT token FROM serve_records WHERE token = ?", - (token,) - ) - if cursor.fetchone(): - return False - - conn.execute( - """INSERT INTO serve_records (token, checkpoint_id, created_at, expires_at) - VALUES (?, ?, ?, ?)""", - (token, checkpoint_id, datetime.now().isoformat(), expires_at) - ) - return True - - def get_checkpoint_serves(self, checkpoint_id: str) -> list[dict]: - """Get all serve records for a checkpoint.""" - with sqlite3.connect(self.db_path, timeout=30) as conn: - conn.execute("PRAGMA journal_mode=WAL") - conn.execute("PRAGMA busy_timeout=30000") - conn.execute(""" - CREATE TABLE IF NOT EXISTS serve_records ( - token TEXT PRIMARY KEY, - checkpoint_id TEXT, - created_at TEXT, - expires_at TEXT, - used INTEGER DEFAULT 0, - proceeded INTEGER DEFAULT 0, - restored INTEGER DEFAULT 0, - FOREIGN KEY (checkpoint_id) REFERENCES checkpoints(checkpoint_id) - ) - """) - cursor = conn.execute( - """SELECT token, checkpoint_id, created_at, expires_at, used, proceeded, restored - FROM serve_records WHERE checkpoint_id = ? ORDER BY created_at DESC""", - (checkpoint_id,) - ) - return [ - { - 'token': row[0], - 'checkpoint_id': row[1], - 'created_at': row[2], - 'expires_at': row[3], - 'used': bool(row[4]), - 'proceeded': bool(row[5]), - 'restored': bool(row[6]) - } - for row in cursor.fetchall() - ] + return None \ No newline at end of file diff --git a/src/ocbs/skill.py b/src/ocbs/skill.py index a0b5e47..6e29548 100644 --- a/src/ocbs/skill.py +++ b/src/ocbs/skill.py @@ -26,10 +26,7 @@ from typing import Optional from .core import BackupSource, BackupScope, OCBSCore -from .serve import generate_restore_url, format_restore_message, start_restore_server - -from .core import OCBSCore, BackupScope -from .serve import RestorePageServer +from .serve import format_restore_message, start_restore_server class OCBSBackupSkill: @@ -495,4 +492,4 @@ async def native_verify(self, archive: str) -> str: } } } -} +} \ No newline at end of file From ac6f00573953459bfd2f356d0676caa72ee11b24 Mon Sep 17 00:00:00 2001 From: OpenClaw Service User Date: Thu, 26 Mar 2026 16:58:16 -0700 Subject: [PATCH 2/2] chore: trigger CodeRabbit review