fix(backup): exclude autopass dir from backup, remove after restore
CI / Build & Test (push) Has been cancelled
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:
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user