OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli commits / 3082627

mirall-cli: single-daemon guard (SEV-4) + web download-folder shape fix (SEV-3)
SEV-4: runDaemon blindly unlinked the UDS sock on boot, which would rip the socket out from under an already-running daemon and leave two processes fighting the single-writer RocksDB lock. Now probe the sock first (Client.connectUds): if a LIVE daemon answers, refuse (unless --force-duplicate); only a genuinely stale sock is cleared. SEV-3: web/bridge-shim getDownloadFolder/setDownloadFolder used a top-level `downloadFolder` key that never matched DEFAULT_CONFIG's `downloads.folder`, so the download folder always read back empty. Use the real shape. (SEV-4 "web server never closed on shutdown" was already handled by the 0.1.4 gracefulShutdown, which closes webServer.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
30826271f074040cfa67aa7302a9d13e17b43282
by plankatron <fleeky@gmail.com> · 2026-07-24T18:17:36.000Z

Changed files (2) +16 -3

bin/mirall.js +12 -1
……@@ -108,7 +108,18 @@ async function runDaemon () {
108108 const storage = flags.storage || path.join(HOME, '.mirall', 'storage')
109109 const downloads = flags.downloads || path.join(HOME, '.mirall', 'downloads')
110110 fs.mkdirSync(path.dirname(sock), { recursive: true })
111 try { fs.unlinkSync(sock) } catch {} // clear a stale socket from a prior run
111 // Single-daemon guard: if a LIVE daemon already owns this sock, refuse rather than blindly
112 // unlinking it (which would rip the socket out from under the running daemon and leave two
113 // processes fighting the single-writer RocksDB lock). Only clear a genuinely stale sock.
114 let liveDaemon = false
115 try {
116 const { Client } = await import('hyper-svc')
117 const probe = await Client.connectUds(sock) // throws if nothing is listening (stale/absent)
118 liveDaemon = true
119 try { probe.close && probe.close() } catch {}
120 } catch {}
121 if (liveDaemon && !flags['force-duplicate']) return fail('a mirall daemon is already running on ' + sock + ' (stop it, or pass --force-duplicate with a different --sock to run another)')
122 if (!liveDaemon) { try { fs.unlinkSync(sock) } catch {} } // clear a stale socket from a prior run
112123
113124 // --bootstrap '[{"host":"127.0.0.1","port":N},…]' points the swarm at a local testnet
114125 // (deterministic multi-node testing); omit for the public DHT.
web/bridge-shim.js +4 -2
……@@ -115,8 +115,10 @@
115115 setTrayLabels: function () { return Promise.resolve() }, menuContextChanged: function () { return Promise.resolve() },
116116
117117 // ── downloads / folders (LOCAL machine — fs-browse picker replaces these, see notes) ──
118 getDownloadFolder: function () { return Promise.resolve(cfg().downloadFolder || '') },
119 setDownloadFolder: function (f) { return Promise.resolve(setCfg({ downloadFolder: f })) },
118 // Read/write config.downloads.folder — the real config shape (DEFAULT_CONFIG above). The
119 // earlier top-level `downloadFolder` key never matched, so the folder always read back empty.
120 getDownloadFolder: function () { return Promise.resolve((cfg().downloads || {}).folder || '') },
121 setDownloadFolder: function (f) { return Promise.resolve(setCfg({ downloads: { folder: f } })) },
120122 browseDownloadFolder: function () { return pickFolder() },
121123 browseShareFolder: function () { return pickFolder() },
122124 startOwnedFolderWatcher: function () { return Promise.resolve() }, stopOwnedFolderWatcher: function () { return Promise.resolve() },