Files
openclaw/scripts/backup-ledger.js
T
Tabula Myriad a9ae1a6778 feat: Triad development iteration complete
Matrix Protocol:
- docker-compose.matrix.yml: Dendrite homeserver + PostgreSQL + Nginx TLS
- src/channels/plugins/matrix-channel.ts: OpenClaw plugin implementation
- docs/matrix-triad-setup.md: Setup guide with auth scheme (@tm1-4:triad.local)

MCP Server Integration:
- docs/mcp-triad-integration.md: SearXNG, Playwright, GitHub MCP configs
- docs/mcp-curiosity-mapping.md: Gap-to-capability mapping

Node Sync Architecture:
- src/services/node-sync-service.ts: WebSocket peer sync + presence detection
- src/services/node-sync-service.test.ts: Unit tests
- docs/node-sync-architecture.md: Architecture docs

Triad Resilience:
- scripts/triad-corruption-check.mjs: SQLite + log + config + git integrity
- docs/triad-resilience.md: Recovery procedures
- .secure/deployment-logs/README.md: Schema v2
- skills/triad-heartbeat/SKILL.md: Corruption check integration

NPM Publish Workflow:
- scripts/npm-publish.mjs: version, changelog, validate, publish, rollback
- .github/workflows/npm-publish.yml: GitHub Actions with provenance
- docs/npm-publish-guide.md: Complete documentation

All deliverables tested in Docker before production.
2026-03-24 00:44:50 -04:00

123 lines
4.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Triad Ledger Backup — Exports SQLite to JSON, commits to Tabula_Myriad
// Usage: node scripts/backup-ledger.js [--remote]
import { execSync } from "child_process";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const workspaceRoot = path.join(__dirname, "..");
const auraPath = path.join(workspaceRoot, ".aura");
const dbPath = path.join(auraPath, "consensus.db");
const backupDir = path.join(workspaceRoot, ".ledger-backups");
const privateRepoPath =
process.env.TABULA_MYRIAD_PATH ||
path.join(process.env.HOME || "/home/openclaw", "tabula-myriad-private");
// Ensure backup dir exists
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true });
}
console.log("🦞 Triad Ledger Backup — Starting...\n");
// Check if better-sqlite3 is available
let Database;
try {
Database = (await import("better-sqlite3")).default;
} catch {
console.log("❌ better-sqlite3 not installed. Run: npm install better-sqlite3");
process.exit(1);
}
// Open DB
if (!fs.existsSync(dbPath)) {
console.log(`❌ Ledger DB not found: ${dbPath}`);
console.log("Run: node scripts/init-triad-ledger.js first");
process.exit(1);
}
const db = new Database(dbPath);
console.log(`✅ Opened: ${dbPath}`);
// Export tables (handle missing tables gracefully)
const tables = {};
const tableQueries = {
consensus_votes: "SELECT * FROM consensus_votes ORDER BY timestamp DESC",
triad_state: "SELECT * FROM triad_state",
triad_tasks: "SELECT * FROM triad_tasks ORDER BY created_at DESC",
knowledge_entries:
"SELECT id, source, title, tags, ingested_at FROM knowledge_entries ORDER BY ingested_at DESC LIMIT 100",
curiosity_metrics: "SELECT * FROM curiosity_metrics ORDER BY timestamp DESC LIMIT 30",
};
for (const [table, query] of Object.entries(tableQueries)) {
try {
tables[table] = db.prepare(query).all();
console.log(`✅ Exported ${tables[table].length} rows from ${table}`);
} catch (e) {
if (e.code === "SQLITE_ERROR" && e.message.includes("no such table")) {
tables[table] = [];
console.log(`⚠️ Table ${table} not yet created (skipped)`);
} else {
throw e;
}
}
}
// Count records
const total = Object.entries(tables).reduce((sum, [, rows]) => sum + rows.length, 0);
console.log(`✅ Exported ${total} records across ${Object.keys(tables).length} tables`);
// Write JSON backup
const timestamp = new Date().toISOString().replace(/:/g, "-").slice(0, 19);
const backupFile = path.join(backupDir, `ledger-backup-${timestamp}.json`);
fs.writeFileSync(backupFile, JSON.stringify({ timestamp, tables }, null, 2));
console.log(`✅ Backup written: ${backupFile}`);
// Also write latest.json for easy restore
const latestFile = path.join(backupDir, "latest.json");
fs.writeFileSync(latestFile, JSON.stringify({ timestamp, tables }, null, 2));
console.log(`✅ Latest symlink: ${latestFile}`);
// Close DB
db.close();
// Copy to private repo if --remote flag
if (process.argv.includes("--remote")) {
if (!fs.existsSync(privateRepoPath)) {
console.log(`⚠️ Private repo not found: ${privateRepoPath}`);
console.log("Clone Tabula_Myriad first, or set TABULA_MYRIAD_PATH env var");
} else {
const ledgerBackupsPath = path.join(privateRepoPath, "ledger-backups");
if (!fs.existsSync(ledgerBackupsPath)) {
fs.mkdirSync(ledgerBackupsPath, { recursive: true });
}
// Copy backup
const remoteBackup = path.join(ledgerBackupsPath, `ledger-backup-${timestamp}.json`);
fs.copyFileSync(backupFile, remoteBackup);
fs.copyFileSync(latestFile, path.join(ledgerBackupsPath, "latest.json"));
console.log(`✅ Copied to private repo: ${ledgerBackupsPath}`);
// Commit
try {
execSync("git add -A", { cwd: privateRepoPath, stdio: "pipe" });
execSync(`git commit -m "Ledger backup ${timestamp}"`, {
cwd: privateRepoPath,
stdio: "pipe",
});
execSync("git push", { cwd: privateRepoPath, stdio: "pipe" });
console.log(`✅ Committed + pushed to Tabula_Myriad`);
} catch {
console.log("⚠️ Git commit failed (may be unconfigured). Manual commit needed.");
}
}
}
console.log("\n🦞 Backup complete.\n");
console.log("Restore command:");
console.log(` node scripts/restore-ledger.js ${latestFile}`);