diff --git a/scripts/deregister-room.ts b/scripts/deregister-room.ts index ab2c3b2..0729930 100644 --- a/scripts/deregister-room.ts +++ b/scripts/deregister-room.ts @@ -4,7 +4,7 @@ * channel record (chats) and its message history (messages). * * Usage: - * bun scripts/deregister-room.ts [--dry-run] [--force] [more...] + * bun scripts/deregister-room.ts [--dry-run] [--force] [--no-backup] [more...] * * What it removes for each target chat: * - room registration + role/skill overrides + channel owner lease @@ -24,6 +24,8 @@ * - a group folder shared by another remaining room is NOT deleted on disk * (only that chat's DB rows are removed) * - --dry-run reports what would change without writing anything + * - the DB is auto-backed up to /home/claude/ejclaw-db-backup-.db before + * the purge unless --no-backup is passed */ import { execFileSync } from 'child_process'; import fs from 'fs'; @@ -36,11 +38,12 @@ import { DATA_DIR, GROUPS_DIR, STORE_DIR } from '../src/config.js'; const args = process.argv.slice(2); const dryRun = args.includes('--dry-run'); const force = args.includes('--force'); +const noBackup = args.includes('--no-backup'); const ids = args.filter((a) => !a.startsWith('--')); if (ids.length === 0) { console.error( - 'Usage: bun scripts/deregister-room.ts [--dry-run] [--force] [more...]', + 'Usage: bun scripts/deregister-room.ts [--dry-run] [--force] [--no-backup] [more...]', ); process.exit(2); } @@ -126,6 +129,14 @@ const taskIds = ( }> ).map((r) => r.id); const qT = taskIds.map(() => '?').join(','); +// task_run_logs.task_id references scheduled_tasks.id (NOT paired_tasks.id), so +// its rows must be pruned by the chat's scheduled task ids. +const scheduledTaskIds = ( + db + .query(`SELECT id FROM scheduled_tasks WHERE chat_jid IN (${qJ})`) + .all(...jids) as Array<{ id: string }> +).map((r) => r.id); +const qS = scheduledTaskIds.map(() => '?').join(','); const safeFolderList = [...safeFolders]; const qF = safeFolderList.map(() => '?').join(','); @@ -158,7 +169,17 @@ const addTask = (t: string) => { addTask('paired_turn_attempts'); addTask('paired_turn_outputs'); addTask('paired_turns'); -addTask('task_run_logs'); +// Prune scheduled-task run logs before the scheduled_tasks rows they belong to. +if (scheduledTaskIds.length) { + plan.push({ + table: 'task_run_logs', + n: count('task_run_logs', `task_id IN (${qS})`, scheduledTaskIds), + run: () => + db + .query(`DELETE FROM task_run_logs WHERE task_id IN (${qS})`) + .run(...scheduledTaskIds), + }); +} for (const t of [ 'paired_task_execution_leases', 'paired_turn_reservations', @@ -222,6 +243,19 @@ if (dryRun) { } // --- execute --- +// Auto-backup the DB before the irreversible purge (skip with --no-backup). +if (!noBackup) { + const dbFile = path.join(STORE_DIR, 'messages.db'); + const stamp = new Date() + .toISOString() + .replace(/[-:]/g, '') + .replace(/\..+$/, '') + .replace('T', '-'); + const backup = path.join('/home/claude', `ejclaw-db-backup-${stamp}.db`); + fs.copyFileSync(dbFile, backup); + console.log(`DB backed up to ${backup}`); +} + db.exec('BEGIN'); try { for (const p of plan) p.run();