dsh-autorestart 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +313 -0
  2. package/README.md +465 -40
  3. package/assets/icon-9c245e5b.ico +0 -0
  4. package/assets/icon-rescue-1902bb15.ico +0 -0
  5. package/assets/icon-rescue-1902bb15.png +0 -0
  6. package/client.js +3002 -170
  7. package/cordis.patch.yml +12 -0
  8. package/docs/README.zh.md +231 -23
  9. package/docs/platform-matrix.md +171 -55
  10. package/docs/real-machine-verification-0.8.6.md +202 -0
  11. package/index.js +13272 -8513
  12. package/lib/autostart-advice.js +31 -2
  13. package/lib/backup.js +2768 -21
  14. package/lib/checkpoint.js +214 -7
  15. package/lib/cookie-clear.js +260 -0
  16. package/lib/lifecycle.js +23 -40
  17. package/lib/plugin-compare.js +499 -0
  18. package/lib/plugin-failure.js +382 -0
  19. package/lib/plugin-recover.js +1557 -0
  20. package/lib/plugin-watch.js +213 -0
  21. package/lib/rescue-paths.js +982 -56
  22. package/lib/restart-phase.js +5 -0
  23. package/lib/restart-verdict.js +379 -0
  24. package/lib/restart.js +244 -7
  25. package/lib/scheduler-capability.js +19 -3
  26. package/lib/sleep.js +190 -29
  27. package/lib/watchdog-disarm.js +46 -2
  28. package/lib/watchdog-schedule.js +834 -17
  29. package/lib/watchdog-tier.js +8 -1
  30. package/lib/watchdog.js +1222 -47
  31. package/locale/en.json +175 -68
  32. package/locale/zh.json +175 -68
  33. package/package.json +3 -2
  34. package/screenshots.json +6 -6
  35. package/scripts/README.md +6 -3
  36. package/scripts/build-icon.mjs +998 -0
  37. package/scripts/clear-dsh-auth-cookies.mjs +257 -0
  38. package/scripts/emergency-disable.sh +69 -2
  39. package/scripts/emergency-toggle.ps1 +85 -69
  40. package/scripts/rescue-strings.zh.json +36 -18
  41. package/scripts/restore-profile.ps1 +182 -1
  42. package/scripts/watchdog.mjs +432 -79
package/locale/en.json CHANGED
@@ -175,8 +175,8 @@
175
175
  "restart": "Restart settings",
176
176
  "shutdown": "Shutdown settings",
177
177
  "supervisor": "Start at login",
178
- "report": "Report a problem",
179
- "rescue": "If DSH will not start"
178
+ "report": "Something is wrong",
179
+ "rescue": "Desktop rescue"
180
180
  },
181
181
  "seconds": {
182
182
  "label": "Shutdown countdown seconds",
@@ -192,26 +192,77 @@
192
192
  },
193
193
  "backup": {
194
194
  "label": "Backup before a restart",
195
- "hint": "Take a snapshot of the current configuration before every restart, so a host that cannot boot again is rolled back automatically.",
195
+ "details": "Details",
196
+ "section": "What is backed up, and the manual backup steps",
197
+ "hint": "A copy is taken before every restart, so a host that cannot boot again is rolled back. What it holds: your settings (always); plugins you wrote and linked from a source directory (copied file by file); plugins downloaded from the internet (only which ones and which version are recorded — never copied; they are reinstalled from that list).",
196
198
  "list": "What is backed up right now",
197
- "count": "{n} copies right now",
199
+ "count": "{n} copies right now, using {size}.",
200
+ "multiProfile": "More than one profile was found, so backups and rollbacks are paused: it cannot tell which one to back up. Fix it by starting with DSH_PROFILE set, or by keeping a single profile.",
198
201
  "keep": "At most {n} copies are kept; the oldest one is deleted when there are more.",
202
+ "kindsLocal": "Both linked plugins (written by hand) and localized ones (their directory has a .git) are in the copy in full, so they can be rolled back.",
203
+ "kindsThirdParty": "Third-party plugins are maintained by their authors; if something is wrong, reinstall them.",
204
+ "limitLabel": "Copies kept",
205
+ "limitHint": "Backups take disk space, so it is best to keep as few copies as possible.",
206
+ "limitAsk": "This deletes the {remove} oldest copies and cannot be undone. {from} copies now → {to} kept.",
207
+ "limitConfirm": "Delete and keep {to}",
208
+ "limitCancel": "Cancel",
209
+ "limitDeleted": "Deleted {removed} copies; {kept} are kept now.",
210
+ "limitMore": "You picked {requested}; {kept} are kept.",
211
+ "limitFailed": "The change did not go through; the copy limit is unchanged.",
199
212
  "empty": "No backup yet; one is written before every restart.",
200
213
  "path": "Where they live",
201
214
  "copyPath": "Copy the path",
202
215
  "copied": "The path is on the clipboard",
203
216
  "copyFailed": "The copy failed — please select the path above by hand",
204
217
  "time": "Backup time",
205
- "size": "Backup size",
206
218
  "withPlugins": "(plugins included)",
207
- "withoutPlugins": "(plugins excluded)"
219
+ "withoutPlugins": "(plugins excluded)",
220
+ "fallback": {
221
+ "seed": "There is also a first-install seed copy (settings only, not verified, used only when the copy above is unavailable), {time}, {size}",
222
+ "other": "There is also an unverified copy (never restored automatically, used only when the copy above is unavailable), {time}, {size}"
223
+ },
224
+ "othersSummary": "{n} more pre-restart snapshots (unverified, never restored automatically)",
225
+ "scopeLabel": "What it holds",
226
+ "scope": {
227
+ "config-only": "configuration only",
228
+ "config+plugins": "configuration and installed plugins",
229
+ "config+links": "configuration and linked plugins",
230
+ "slim": "slim copy (configuration and by-hand plugins)"
231
+ },
232
+ "scopeMissing": "(not recorded)",
233
+ "interventionLabel": "Was this boot rescued by the system",
234
+ "intervention": {
235
+ "true": "yes, the watchdog restored a copy",
236
+ "false": "no, nothing had to step in",
237
+ "unknown": "not recorded"
238
+ },
239
+ "rollback": "Roll back to this copy",
240
+ "startupHint": "This is the state at the most recent startup. DSH starting does not mean every plugin is healthy — a broken plugin is excluded and the host still boots. Which copy to roll back to is your call.",
241
+ "rollbackConfirmTitle": "Roll back to this copy?",
242
+ "rollbackConfirmBody": "The backup from {time} will be restored over the current state — **what you have now is replaced** (a snapshot of it is taken first, so this rollback can be undone).",
243
+ "rollbackConfirmRestart": "A restart follows automatically: a restorable copy of what was here before the rollback was already saved; this restart takes no further backup. This page disconnects during the restart and works again afterwards.",
244
+ "rollbackConfirmYes": "Roll back",
245
+ "rollbackConfirmNo": "Cancel",
246
+ "rollbackBusy": "Rolling back…",
247
+ "rollbackDone": "Rolled back — restarting…",
248
+ "rollbackFailed": "The rollback did not work: {message}"
208
249
  },
209
250
  "backupPlugins": {
210
251
  "label": "Include the installed plugins in the backup",
211
252
  "hint": "Off: only the state of DSH itself is backed up — the plugin directory of the profile is left out."
212
253
  },
254
+ "restartBanner": {
255
+ "title": "This machine has no restart capability right now: this plugin will not restart, and will not take a backup before a restart. Press \"Set it up for me\" and I derive and write it — and that can be undone.",
256
+ "fix": "Set it up for me"
257
+ },
213
258
  "supervisor": {
214
259
  "status": "Started by",
260
+ "status.external": "How I remember you were started: by your own script (the restart is its job — I stay out of it)",
261
+ "status.bareWritten": "How I remember you were started: restarted with the command you confirmed",
262
+ "status.bareUnknown": "I don't know yet how to start you again",
263
+ "status.unknown": "I cannot tell how you were started (I don't guess, and I won't start you on my own)",
264
+ "status.selfSupervised": "How I remember you were started: my own supervisor is in charge (the restart is its job)",
265
+ "status.container": "There is no 'at login' here: the orchestrator is in charge",
215
266
  "installed": "the supervisor of this plugin",
216
267
  "external": "your own way",
217
268
  "unknown": "nobody knows",
@@ -229,12 +280,6 @@
229
280
  "accessHint": "The token changes with every restart, and so does the link. This one is always the current one.",
230
281
  "pending": "The uninstall did not finish. Please run it by hand: {commands}",
231
282
  "reason": "Why",
232
- "command": "The restart command",
233
- "derive": "Derive the restart command",
234
- "apply": "Use this command",
235
- "revert": "Undo",
236
- "noCommand": "No replayable restart command could be derived",
237
- "plan": "Dry run",
238
283
  "reason.ok": "This command starts this DSH again.",
239
284
  "reason.desktop-app": "The desktop app restarts itself; there is no command to write.",
240
285
  "reason.not-a-web-host": "This DSH is not a web host: it has no port and no listener.",
@@ -243,12 +288,12 @@
243
288
  "reason.container-no-init": "This machine has no \"at login\" (a container), so no automatic start is promised.",
244
289
  "reason.port-not-fixable": "The port changes every time, so the browser would be logged out; the port has to be fixed first.",
245
290
  "reason.supervised": "The supervisor of this plugin is in charge: the restart is handed to it.",
246
- "status.external": "How I remember you were started: by your own script (the restart is its job — I stay out of it)",
247
- "status.bareWritten": "How I remember you were started: restarted with the command you confirmed",
248
- "status.bareUnknown": "I don't know yet how to start you again",
249
- "status.unknown": "I cannot tell how you were started (I don't guess, and I won't start you on my own)",
250
- "status.selfSupervised": "How I remember you were started: my own supervisor is in charge (the restart is its job)",
251
- "status.container": "There is no 'at login' here: the orchestrator is in charge",
291
+ "command": "The restart command",
292
+ "derive": "Derive the restart command",
293
+ "apply": "Use this command",
294
+ "revert": "Undo",
295
+ "noCommand": "No replayable restart command could be derived",
296
+ "plan": "Dry run",
252
297
  "redetect": "Detect again",
253
298
  "deriveHelp": "Help me find out how to restart",
254
299
  "technical": "Technical details",
@@ -264,15 +309,27 @@
264
309
  "startup.unknown": "it could not be established who started it",
265
310
  "autostart": "How \"start at login\" works here"
266
311
  },
267
- "report": {
268
- "label": "Something wrong?",
269
- "github": "Copy the diagnostics and open a GitHub issue",
270
- "hint": "The diagnostics carry no access token and no secrets — paste them into the issue and we can see what went wrong.",
271
- "email": "No GitHub account? Write to {email}",
272
- "copy": "Copy the diagnostics",
273
- "copyFailed": "The copy failed — please copy this by hand",
274
- "issueTitle": "A problem with the restart / start at login",
275
- "issueBody": "Please paste the diagnostics you just copied below.\n\n## What happened\n\n\n## What you expected\n\n\n## How to reproduce\n"
312
+ "autostartKind": {
313
+ "other": "something else",
314
+ "windows-task": "Windows scheduled task",
315
+ "windows-startup": "Windows startup folder",
316
+ "systemd-user": "systemd user service",
317
+ "systemd-system": "systemd system service",
318
+ "wsl-systemd": "systemd inside WSL",
319
+ "wsl-no-systemd": "WSL without systemd",
320
+ "linux-no-systemd": "Linux without systemd",
321
+ "container": "a container",
322
+ "desktop": "the desktop app itself",
323
+ "unknown": "cannot tell",
324
+ "pm2": "pm2 (a process manager)"
325
+ },
326
+ "startupWhy": {
327
+ "desktop": "this process is the desktop client itself — it relaunches itself",
328
+ "bare": "it was started by hand in a terminal; nobody manages it",
329
+ "external": "another supervisor started it",
330
+ "container": "it runs in a container; the orchestrator is in charge",
331
+ "unknown": "it could not be established how it was started",
332
+ "self-supervised": "the supervisor of this plugin started it"
276
333
  },
277
334
  "rescue": {
278
335
  "hint": "When DSH does not come up, these two scripts rescue it for you (neither of them needs DSH running).",
@@ -284,21 +341,41 @@
284
341
  "copyFailed": "The copy failed — please select the path above by hand",
285
342
  "reveal": "Open the folder",
286
343
  "which": "Which one: the whole environment is broken (a changed configuration, another plugin, a bad upgrade) → \"Back to the last good state\"; only this plugin is suspect → \"Take this plugin out only\".",
287
- "warning": "Important: these two scripts are for when DSH does not come up. Using \"Back to the last good state\" while DSH is running overwrites the configuration you are working with right now.",
288
- "shortcut": "Put a shortcut on the desktop",
289
- "shortcutDone": "It is on the desktop",
290
- "noDesktop": "This machine has no desktop directory, so no shortcut can be placed (the paths are above — copy them by hand).",
291
- "shortcutFailed": "The shortcut was not placed: {message}",
292
- "revealFailed": "The folder did not open: {message}",
344
+ "warning": "Important: this desktop entry and the two scripts above are for when DSH does not come up. Do not click it while DSH is running — \"Back to the last good state\" overwrites the configuration you are working with right now.",
293
345
  "rollbackOk": "Automatic rollback: available (a host that does not come up after a restart goes back to the last good state)",
294
346
  "rollbackBlocked": "Automatic rollback: **not available** — this computer does not allow creating scheduled tasks. **What you lose: DSH will NOT recover by itself when it does not come up after a restart**; when it breaks, double-click the restore script in \"If DSH will not start\".",
295
347
  "rollbackUnknown": "Automatic rollback: not known yet (this machine has not been checked)",
296
348
  "watchdogTier": "How the watchdog is triggered: {tier}",
297
349
  "watchdogLast": "The last watchdog run: {result}",
350
+ "watchdogStrict": "The last watchdog run did NOT act: no backup satisfies both conditions — it records a startup, and that startup had no system intervention. Please handle it by hand.",
351
+ "watchdogRestoredFrom": "The copy the last watchdog run restored: {dir}",
298
352
  "tierScheduledTask": "a Windows scheduled task",
299
353
  "tierSystemdRun": "a systemd timer",
300
354
  "tierDetachedProcess": "a detached background process",
301
- "tierNone": "(this machine has no way to run it)"
355
+ "tierNone": "(this machine has no way to run it)",
356
+ "shortcut": "Put a shortcut on the desktop",
357
+ "shortcutDone": "It is on the desktop",
358
+ "noDesktop": "This machine has no desktop directory, so no shortcut can be placed (the paths are above — copy them by hand).",
359
+ "noDesktopPlatform": "The web / server version has no desktop, so there is no one-click entry here; use the two .sh scripts above.",
360
+ "commandLabel": "Rescue entry (command)",
361
+ "copyCommand": "Copy the command",
362
+ "commandHint": "Copy this command and run it — the same effect as double-clicking the desktop entry.",
363
+ "commandUnavailable": "No usable rescue command was found on this machine.",
364
+ "shortcutScope": "It can only put the WHOLE environment back to the last good state — it cannot take one plugin out.",
365
+ "shortcutBlocked": "A system policy does not allow creating the shortcut (a group policy or a permission limit). Copy the script path below and put it on the desktop by hand:",
366
+ "shortcutWriteFailed": "The shortcut was not written: {message}. Copy the script path below and put it on the desktop by hand:",
367
+ "shortcutFailed": "The shortcut was not placed: {message}",
368
+ "revealFailed": "The folder did not open: {message}"
369
+ },
370
+ "report": {
371
+ "label": "Something wrong?",
372
+ "github": "Copy the diagnostics and open a GitHub issue",
373
+ "hint": "The diagnostics carry no access token and no secrets — paste them into the issue and we can see what went wrong.",
374
+ "email": "No GitHub account? Write to {email}",
375
+ "copy": "Copy the diagnostics",
376
+ "copyFailed": "The copy failed — please copy this by hand",
377
+ "issueTitle": "A problem with the restart / start at login",
378
+ "issueBody": "Please paste the diagnostics you just copied below.\n\n## What happened\n\n\n## What you expected\n\n\n## How to reproduce\n"
302
379
  },
303
380
  "error": "Could not save the settings: {message}",
304
381
  "saveNotice": {
@@ -306,40 +383,12 @@
306
383
  "failed": "Not saved",
307
384
  "dismiss": "Dismiss"
308
385
  },
309
- "autostartKind": {
310
- "other": "something else",
311
- "windows-task": "Windows scheduled task",
312
- "windows-startup": "Windows startup folder",
313
- "systemd-user": "systemd user service",
314
- "systemd-system": "systemd system service",
315
- "wsl-systemd": "systemd inside WSL",
316
- "wsl-no-systemd": "WSL without systemd",
317
- "container": "a container",
318
- "desktop": "the desktop app itself",
319
- "unknown": "cannot tell",
320
- "pm2": "pm2 (a process manager)"
321
- },
322
- "startupWhy": {
323
- "desktop": "this process is the desktop client itself — it relaunches itself",
324
- "bare": "it was started by hand in a terminal; nobody manages it",
325
- "external": "another supervisor started it",
326
- "container": "it runs in a container; the orchestrator is in charge",
327
- "unknown": "it could not be established how it was started",
328
- "self-supervised": "the supervisor of this plugin started it"
329
- }
330
- },
331
- "restart": {
332
- "stage": {
333
- "snapshotting": "Backing up…",
334
- "restarting": "Restarting…",
335
- "back": "Back"
336
- },
337
- "overlay": {
338
- "snapshotting": "Backing up before DSH restart",
339
- "size": "{done} / {total}",
340
- "sizeDone": "{done}",
341
- "restarting": "Restarting…",
342
- "back": "Back again"
386
+ "description": "Restarts DSH, watches your plugins, and puts back the ones it can.",
387
+ "restore": "Restore",
388
+ "thirdParty": "You did not write this plugin yourself, so it cannot be restored from the backup - it has to be reinstalled.",
389
+ "version": "version {version}",
390
+ "restart": {
391
+ "cancelledNote": "The restart you asked for did not happen: you requested it at {at}, waited {waited}, and it was never carried out; after the host restarted, that request was cancelled. If you still want it, just ask once more."
343
392
  }
344
393
  },
345
394
  "meta": {
@@ -358,5 +407,63 @@
358
407
  "disabled": "The button is currently switched off; you can turn it on in the settings.",
359
408
  "button": "Got it",
360
409
  "error": "Could not record it: {message}"
410
+ },
411
+ "pluginFailure": {
412
+ "title": "Two things worth a look",
413
+ "hint": "These are on record - you can restore them later from the Plugins page.",
414
+ "dismiss": "Got it",
415
+ "dismissLabel": "Hide this for now (it comes back on the next start)",
416
+ "certainTitle": "These features did not come up this time",
417
+ "certainIntro": "They were there on the previous start and are not here now. That is something that happened, not a guess.",
418
+ "clueTitle": "These plugins differ from the backup",
419
+ "clueIntro": "The files of these plugins are not what the latest backup holds. They may simply have been upgraded - this block lists a difference, not a conclusion.",
420
+ "uiBlindSpot": "Also {count} interface module(s) did not appear this time (they do not map to a plugin you installed, so they are not listed here).",
421
+ "restoreAll": "Restore all ({count})",
422
+ "restoreAllDone": "Done: {done} restored, {failed} not restored.",
423
+ "close": "Dismiss this notice",
424
+ "linkedHint": "You wrote this plugin yourself and installed it as a link, so it can be put back from the backup directly.",
425
+ "thirdPartyHint": "This plugin was installed from elsewhere and the backup does not hold its files. To put it back, use the command below: it reinstalls the same version (not the latest one):",
426
+ "commandLabel": "Copy this command",
427
+ "noVersion": "The backup does not record its exact version, so it can only be reinstalled by name.",
428
+ "restore": "Restore {plugin} only",
429
+ "restoring": "Restoring {plugin}…",
430
+ "restored": "{plugin} was put back to the backup version (backup taken {at}).",
431
+ "failed": "{plugin} was not restored: {reason}",
432
+ "noBackup": "There is no usable backup on this machine; it has to be handled by hand."
433
+ },
434
+ "watchdog": {
435
+ "hint": "Changed your mind later? Look for the dsh-autorestart settings on the Plugins page.",
436
+ "title": "If a restart fails, it can rescue you automatically",
437
+ "explain": "This plugin ships with an automatic rescue: when a newly restarted version never comes up, it restores a backup it considers usable (the criterion is that the content fingerprint matched and that boot came up — it does not mean the plugins inside are all fine), then starts the program again. It is on by default.",
438
+ "whereOff": "If you do not want it, turn it off on the settings page — with it off, a new version that does not start has to be handled by hand.",
439
+ "gotIt": "Got it",
440
+ "noticeSaved": "That is the only notice; it will not appear again.",
441
+ "warnOff": "With it off, a new version that does not start has to be handled by hand.",
442
+ "keepOn": "Keep it on (recommended)",
443
+ "turnOff": "No, I will handle it",
444
+ "savedOn": "The watchdog is on: the next restart is protected.",
445
+ "savedOff": "The watchdog is off: the next restart will not roll back on its own.",
446
+ "saveFailed": "Your choice was not saved, so the watchdog setting is unchanged. Please try again, or change it on the settings page."
447
+ },
448
+ "restart": {
449
+ "stage": {
450
+ "snapshotting": "Backing up…",
451
+ "restarting": "Restarting…",
452
+ "back": "Back"
453
+ },
454
+ "overlay": {
455
+ "snapshotting": "Backing up before DSH restart",
456
+ "size": "{done} / {total}",
457
+ "sizeDone": "{done}",
458
+ "restarting": "Restarting…",
459
+ "deferred": "A conversation just started — restart postponed",
460
+ "deferredNote": "It will retry automatically when idle · {count} waiting",
461
+ "deferredNoteZero": "It will retry automatically when idle",
462
+ "back": "Back again"
463
+ },
464
+ "waitedSeconds": "a few seconds",
465
+ "waitedMinutes": "about {n} min",
466
+ "waitedHours": "about {n}h {m}m",
467
+ "waitedHoursExact": "about {n}h"
361
468
  }
362
469
  }