Multi-Hoster-Upload/package.json
Administrator 7a40afbe7e fix(config): fsync config writes + guard against account-wipe on a corrupt read (v3.3.105)
A user's server crashed hard during an upload and lost all configured accounts. It
was NOT the v3.3.104 update (a second server updated fine and kept its accounts) —
it was a data-durability hole exposed by the crash:

- Config writes were atomic (tmp + rename) but never fsync'd, so a hard crash could
  leave electron-config.json truncated/unflushed on disk.
- On restart, load() reads the truncated file, falls back to .bak, and if that is
  also bad returns empty DEFAULTS. The next settings/queue save then persists EMPTY
  hosters — permanently wiping the accounts. Worse, the async _atomicWrite blindly
  copied the (now truncated) live file over .bak, so an empty live could clobber a
  good backup.

Hardening (lib/config-store.js + main.js; no behavior change in the happy path):
- fsync before rename in both write paths — _atomicWrite (openSync/writeSync/
  fsyncSync/closeSync) and the synchronous save-global-settings-sync on window close.
  A hard crash can no longer leave a truncated config.
- _atomicWrite only refreshes .bak when the current live file is non-trivial
  (trim length > 2), so an empty/truncated live can never overwrite a good backup
  (the sync-save path already did this).
- Wipe-guard (_guardHosters): save(), saveRotationCursors() and the sync close-save
  never intend to change hosters; if after a load() the hosters are all empty and
  the write did not explicitly provide hosters, recover them from disk
  (_recoverHostersFromDisk: live -> .bak -> .pre-history-split.bak) instead of
  persisting the wipe. An explicit save({hosters: {}}) (user deleted all accounts)
  is still allowed. Restored hosters are already-encrypted on disk and
  encryptCredentials skips already-encrypted fields, so re-serializing is safe.
- load() gained a third fallback tier — the permanent pre-history-split.bak snapshot
  (which still holds the accounts) — so load() itself recovers after corruption.

Recovery for the already-affected server: copy
%APPDATA%/multi-hoster-uploader/electron-config.json.pre-history-split.bak (or .bak)
over electron-config.json with the app closed.

2 new regression tests (post-wipe valid-empty live + .bak → guard restores accounts;
an explicit empty-hosters save is not blocked). 409 tests pass; clean boot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 03:53:01 +02:00

57 lines
1.4 KiB
JSON

{
"name": "multi-hoster-uploader",
"version": "3.3.105",
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
"main": "main.js",
"scripts": {
"start": "electron .",
"test": "node --test tests/*.test.js tests/ui-smoke.js",
"dist": "electron-builder --win",
"release:win": "electron-builder --publish never --win nsis portable",
"release:gitea": "node scripts/release_gitea.mjs"
},
"dependencies": {
"chokidar": "^3.6.0",
"undici": "^7.16.0",
"ws": "^8.19.0"
},
"devDependencies": {
"electron": "^41.3.0",
"electron-builder": "^26.8.1",
"eslint": "^10.1.0",
"eslint-plugin-security": "^4.0.0",
"rcedit": "^4.0.1"
},
"build": {
"appId": "com.multihoster.uploader",
"productName": "Multi-Hoster-Upload",
"directories": {
"buildResources": "assets",
"output": "release"
},
"files": [
"main.js",
"preload.js",
"lib/**/*",
"renderer/**/*",
"assets/app_icon.ico",
"assets/app_icon.png"
],
"win": {
"target": [
"nsis",
"portable"
],
"icon": "assets/app_icon.ico",
"signAndEditExecutable": false
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true
},
"afterPack": "scripts/afterPack.cjs"
}
}