fix(sync): autopass device-file on restart and new invite
CI / Build & Test (push) Successful in 4m46s

- Remove autopass dir before createSyncInvite/pairWithInvite so Corestore
  gets a fresh device-file (device-file throws when inode/mtime changes)
- Await sync/rdp/holesail cleanup on host shutdown so corestore closes
  before process exit
- Reset initPromise on init failure so Create invite can retry after
  removing corrupt autopass dir
This commit is contained in:
Raven Scott
2026-03-15 02:50:53 -04:00
parent 7d88cdf841
commit 1eaa6e21ba
3 changed files with 41 additions and 11 deletions
+9 -6
View File
@@ -126,12 +126,15 @@ async function handleMessageAsync(send, msg) {
function cleanup() {
if (_scheduledBackupTimer) { clearTimeout(_scheduledBackupTimer); _scheduledBackupTimer = null; }
syncManager.cleanup().catch(() => {});
httpsProxy.stop(() => {});
connectProxy.stop(() => {});
sshManager.cleanup();
rdpManager.cleanup().catch(() => {});
holesailManager.cleanup().catch(() => {});
return Promise.all([
syncManager.cleanup().catch(() => {}),
rdpManager.cleanup().catch(() => {}),
holesailManager.cleanup().catch(() => {})
]).then(() => {
httpsProxy.stop(() => {});
connectProxy.stop(() => {});
sshManager.cleanup();
});
}
module.exports = { handleMessage: handleMessageAsync, cleanup };
+9 -5
View File
@@ -71,19 +71,23 @@ const messenger = createMessenger({
},
});
function shutdown() {
async function shutdown() {
removePidFile();
cleanup();
try {
const c = cleanup();
if (c && typeof c.then === 'function') await c;
} catch (_) {}
messenger.destroy();
process.exit(0);
}
process.on('exit', () => {
removePidFile();
cleanup();
const c = cleanup();
if (c && typeof c.then === 'function') c.catch(() => {});
});
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('SIGTERM', () => { shutdown().catch(() => process.exit(1)); });
process.on('SIGINT', () => { shutdown().catch(() => process.exit(1)); });
process.on('unhandledRejection', (reason) => {
logErr('unhandledRejection: ' + (reason && (reason.stack || reason.message || reason)));
+23
View File
@@ -127,6 +127,26 @@ async function closeSyncForBackup() {
await closePass();
}
/**
* Remove the autopass directory so a new Corestore can be created with a fresh device-file.
* The device-file package throws "Invalid device file, was modified" if inode/mtime changes
* (e.g. after restart or when reusing the same path for a new identity). Used before
* createSyncInvite and pairWithInvite so we start with a clean store.
*/
function removeAutopassDir() {
const autopassPath = getAutopassPath();
if (!autopassPath) return;
try {
if (fs.existsSync(autopassPath)) {
fs.rmSync(autopassPath, { recursive: true });
}
} catch (e) {
if (e.code !== 'ENOENT' && process.stderr) {
process.stderr.write('[sync-manager] removeAutopassDir: ' + e.message + '\n');
}
}
}
async function ensureInitialized() {
if (initPromise) return initPromise;
initPromise = (async () => {
@@ -148,6 +168,7 @@ async function ensureInitialized() {
if (process.stderr) process.stderr.write('[sync-manager] init failed: ' + e.message + '\n');
store = null;
pass = null;
initPromise = null; // allow retry on next getSyncStatus (e.g. after Create invite clears autopass dir)
}
})();
await initPromise;
@@ -352,6 +373,7 @@ async function getSyncStatus() {
async function createSyncInvite() {
if (!storageDir) return { ok: false, error: 'Storage path not set' };
await closePass();
removeAutopassDir();
clearIdentity();
try {
const Corestore = require('corestore');
@@ -379,6 +401,7 @@ async function createSyncInvite() {
async function pairWithInvite(invite) {
if (!storageDir || !invite || typeof invite !== 'string') return { ok: false, error: 'Storage path and invite required' };
await closePass();
removeAutopassDir();
clearIdentity();
try {
const Corestore = require('corestore');