fix(backup): exclude autopass dir from backup, remove after restore
CI / Build & Test (push) Has been cancelled

- Backup: do not include autopass/ (may be in use); always include
  autopass-identity.json so sync identity is never lost
- Restore: remove autopass/ after extract so autopass can recreate from
  restored identity
- Docs: BACKUP.md, SYNC.md, ARCHITECTURE.md updated
This commit is contained in:
Raven Scott
2026-03-15 03:25:07 -04:00
parent e00028ca8e
commit cce71920e9
4 changed files with 28 additions and 12 deletions
+22 -5
View File
@@ -18,6 +18,7 @@ try {
} catch (_) {}
const BACKUP_DIR_NAME = 'backups';
const AUTOPASS_DIR_NAME = 'autopass';
const DEFAULT_RETENTION = 5;
let storageDir = null;
@@ -33,8 +34,8 @@ function setCertsPath(dir) {
}
/**
* Set the sync manager so backup can quiesce sync (close corestore) before copying.
* Backup must not run while sync is writing to autopass/ or the copy may be inconsistent.
* Set the sync manager so backup/restore can quiesce sync before copying or extracting.
* Quiescing ensures state and autopass-identity.json are not written during the operation.
*/
function setSyncManager(sm) {
syncManagerRef = sm;
@@ -45,6 +46,20 @@ function getBackupDir() {
return path.join(storageDir, BACKUP_DIR_NAME);
}
function removeAutopassDirAfterRestore() {
if (!storageDir) return;
const autopassPath = path.join(storageDir, AUTOPASS_DIR_NAME);
try {
if (fs.existsSync(autopassPath)) {
fs.rmSync(autopassPath, { recursive: true });
}
} catch (e) {
if (e.code !== 'ENOENT' && process.stderr) {
process.stderr.write('[backup-manager] removeAutopassDirAfterRestore: ' + e.message + '\n');
}
}
}
function ensureBackupDir() {
const dir = getBackupDir();
if (!fs.existsSync(dir)) {
@@ -80,7 +95,7 @@ async function createBackup() {
if (!storageDir) return { ok: false, error: 'Storage path not set' };
if (!spawn) return { ok: false, error: 'child_process.spawn not available — cannot run tar' };
// Quiesce sync so corestore/autopass is not being written to; ensures a consistent copy of autopass/ and autopass-identity.json
// Quiesce sync so state and identity are not being written during copy (autopass/ is not backed up)
if (syncManagerRef && typeof syncManagerRef.closeSyncForBackup === 'function') {
try {
await syncManagerRef.closeSyncForBackup();
@@ -112,10 +127,10 @@ async function createBackup() {
return { ok: false, error: 'Failed to create staging dir: ' + e.message };
}
// Copy storage entries (excluding backups/)
// Copy storage entries (excluding backups/ and autopass/ — autopass dir is never backed up)
let storageEntries;
try {
storageEntries = fs.readdirSync(storageDir).filter(e => e !== BACKUP_DIR_NAME);
storageEntries = fs.readdirSync(storageDir).filter(e => e !== BACKUP_DIR_NAME && e !== AUTOPASS_DIR_NAME);
} catch (e) {
cleanupStaging(stagingDir);
return { ok: false, error: 'Failed to read storage directory: ' + e.message };
@@ -303,6 +318,7 @@ async function restoreBackup(filename) {
} catch (e) {
return { ok: false, error: 'tar extract failed: ' + e.message };
}
removeAutopassDirAfterRestore();
return { ok: true, restoredStorage: true, restoredCerts: false };
}
@@ -336,6 +352,7 @@ async function restoreBackup(filename) {
await runCommand('cp', ['-R', path.join(extractedStorage, entry), dest]);
}
restoredStorage = true;
removeAutopassDirAfterRestore();
} catch (e) {
cleanupStaging(stagingDir);
return { ok: false, error: 'Failed to restore storage files: ' + e.message };