dsh-autorestart 0.7.0 → 0.8.1
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.
- package/CHANGELOG.md +47 -0
- package/README.md +137 -6
- package/client.js +387 -0
- package/cordis.patch.yml +7 -0
- package/docs/README.zh.md +83 -5
- package/docs/platform-matrix.md +92 -0
- package/docs/rescue-drill.md +606 -0
- package/index.js +598 -3
- package/lib/diagnostics.js +8 -0
- package/lib/rescue-paths.js +253 -0
- package/lib/rescue.js +596 -0
- package/lib/scheduler-capability.js +300 -0
- package/lib/watchdog-arbiter.js +117 -0
- package/lib/watchdog-schedule.js +310 -0
- package/lib/watchdog-tier.js +182 -0
- package/lib/watchdog.js +479 -0
- package/locale/en.json +28 -1
- package/locale/zh.json +28 -1
- package/package.json +6 -1
- package/scripts/README.md +65 -0
- package/scripts/drill-rescue.ps1 +508 -0
- package/scripts/drill-rescue.sh +427 -0
- package/scripts/drill-watchdog.mjs +341 -0
- package/scripts/emergency-disable.sh +314 -0
- package/scripts/emergency-toggle.ps1 +136 -65
- package/scripts/rescue-strings.zh.json +168 -0
- package/scripts/restore-profile.cmd +44 -0
- package/scripts/restore-profile.ps1 +608 -0
- package/scripts/restore-profile.sh +593 -0
- package/scripts/run-tests.ps1 +1 -1
- package/scripts/watchdog.mjs +203 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CAN THIS MACHINE CREATE A ONE-OFF SCHEDULED TASK?
|
|
3
|
+
*
|
|
4
|
+
* The automatic rollback of this plugin works through a scheduled task: the
|
|
5
|
+
* host is restarted, and a task that fires a minute later brings it back if
|
|
6
|
+
* it did not come up. On a machine where such a task cannot be created
|
|
7
|
+
* (a corporate policy, a machine without the rights, a locked-down service
|
|
8
|
+
* account) that safety net is silently missing — and the user has to be told
|
|
9
|
+
* what they lose.
|
|
10
|
+
*
|
|
11
|
+
* WHAT THIS DOES NOT DO (on purpose):
|
|
12
|
+
*
|
|
13
|
+
* - it does NOT design a fallback chain ("use the startup folder instead",
|
|
14
|
+
* "use a systemd timer instead", ...): the user decided against it;
|
|
15
|
+
* - it does NOT try to bypass the policy ("maybe another way works"): if
|
|
16
|
+
* the machine says no, the answer is no.
|
|
17
|
+
*
|
|
18
|
+
* It only DETECTS, once, and tells the truth.
|
|
19
|
+
*
|
|
20
|
+
* THE PROBE NEVER LEAVES A TASK BEHIND:
|
|
21
|
+
*
|
|
22
|
+
* 1. a task with a unique name is registered (it fires in two minutes and
|
|
23
|
+
* does nothing: `cmd /c exit 0`);
|
|
24
|
+
* 2. it is read back and verified (the trigger is the one we asked for);
|
|
25
|
+
* 3. it is deleted;
|
|
26
|
+
* 4. it is read back AGAIN — the task must be gone.
|
|
27
|
+
*
|
|
28
|
+
* The verification is the XML of the task, not its human readable listing:
|
|
29
|
+
* the XML has no translated words, so the check does not depend on the
|
|
30
|
+
* language of the machine ("Ready" in English, "就绪" in Chinese).
|
|
31
|
+
*
|
|
32
|
+
* Everything that touches the system is injected, so a test never has to
|
|
33
|
+
* create a real task.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { execFileSync } from 'node:child_process';
|
|
37
|
+
|
|
38
|
+
/** The scheduler works on this machine. */
|
|
39
|
+
export const SCHEDULER_OK = 'ok';
|
|
40
|
+
/** The scheduler is refused here — the automatic rollback is not available. */
|
|
41
|
+
export const SCHEDULER_BLOCKED = 'blocked';
|
|
42
|
+
/** Nobody checked yet. */
|
|
43
|
+
export const SCHEDULER_UNKNOWN = 'unknown';
|
|
44
|
+
|
|
45
|
+
/** The name of the probe task. It is unique per run, so two probes cannot collide. */
|
|
46
|
+
export const PROBE_TASK_PREFIX = 'dsh-autorestart-capability-probe';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The name of the probe task for this run.
|
|
50
|
+
*
|
|
51
|
+
* @param {number} now
|
|
52
|
+
* @param {number|string} pid
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
export function probeTaskName(now = Date.now(), pid = 0) {
|
|
56
|
+
const stamp = Math.trunc(Number(now) || 0).toString(36);
|
|
57
|
+
const who = String(pid === 0 ? '0' : pid).replace(/[^A-Za-z0-9]/g, '');
|
|
58
|
+
return `${PROBE_TASK_PREFIX}-${who}-${stamp}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The trigger of the probe: two minutes from now, in the formats schtasks
|
|
63
|
+
* wants and in the format the XML reports.
|
|
64
|
+
*
|
|
65
|
+
* @param {number} now
|
|
66
|
+
* @returns {{ sd: string, st: string, iso: string }}
|
|
67
|
+
*/
|
|
68
|
+
export function probeTrigger(now = Date.now()) {
|
|
69
|
+
const date = new Date(Math.trunc(Number(now) || 0) + 2 * 60 * 1000);
|
|
70
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
71
|
+
const yyyy = date.getFullYear();
|
|
72
|
+
const mm = pad(date.getMonth() + 1);
|
|
73
|
+
const dd = pad(date.getDate());
|
|
74
|
+
const hh = pad(date.getHours());
|
|
75
|
+
const mi = pad(date.getMinutes());
|
|
76
|
+
return { sd: `${yyyy}/${mm}/${dd}`, st: `${hh}:${mi}`, iso: `${yyyy}-${mm}-${dd}T${hh}:${mi}` };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The default executor: run a command and report what it said.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} file
|
|
83
|
+
* @param {string[]} args
|
|
84
|
+
* @returns {{ ok: boolean, stdout: string, error: string|null }}
|
|
85
|
+
*/
|
|
86
|
+
function defaultExec(file, args) {
|
|
87
|
+
try {
|
|
88
|
+
const stdout = execFileSync(file, args, { encoding: 'utf8', timeout: 20000, windowsHide: true });
|
|
89
|
+
return { ok: true, stdout: typeof stdout === 'string' ? stdout : '', error: null };
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
stdout: error && typeof error.stdout === 'string' ? error.stdout : '',
|
|
94
|
+
error: error && error.message ? error.message : String(error),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The trigger of the task, read out of its XML.
|
|
101
|
+
*
|
|
102
|
+
* @param {string} xml
|
|
103
|
+
* @returns {string} the ISO start boundary, or an empty string
|
|
104
|
+
*/
|
|
105
|
+
export function readStartBoundary(xml) {
|
|
106
|
+
const text = typeof xml === 'string' ? xml : '';
|
|
107
|
+
const match = /<StartBoundary>([^<]+)<\/StartBoundary>/i.exec(text);
|
|
108
|
+
return match ? match[1].trim() : '';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The trigger of the task, read out of its XML, compared with what we asked
|
|
113
|
+
* for (the XML writes seconds, we asked without them).
|
|
114
|
+
*
|
|
115
|
+
* @param {string} xml
|
|
116
|
+
* @param {string} expectedIso
|
|
117
|
+
* @returns {boolean}
|
|
118
|
+
*/
|
|
119
|
+
export function triggerMatches(xml, expectedIso) {
|
|
120
|
+
const boundary = readStartBoundary(xml);
|
|
121
|
+
if (boundary === '') return false;
|
|
122
|
+
return boundary.slice(0, 16) === String(expectedIso).slice(0, 16);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Would the task fire?
|
|
127
|
+
*
|
|
128
|
+
* The XML does NOT always carry an `<Enabled>` element: a task without one is
|
|
129
|
+
* enabled (that is the default), and only an explicit `false` disables it.
|
|
130
|
+
* (A real run on a Chinese Windows answered with a `<TimeTrigger>` that has
|
|
131
|
+
* no `<Enabled>` at all — a check that demanded `<Enabled>true</Enabled>` would
|
|
132
|
+
* have called a perfectly working machine "blocked".)
|
|
133
|
+
*
|
|
134
|
+
* @param {string} xml
|
|
135
|
+
* @returns {boolean}
|
|
136
|
+
*/
|
|
137
|
+
export function taskEnabled(xml) {
|
|
138
|
+
const text = typeof xml === 'string' ? xml : '';
|
|
139
|
+
return !/<Enabled>false<\/Enabled>/i.test(text);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Does the task carry an action (is it a real one-off task)?
|
|
144
|
+
*
|
|
145
|
+
* @param {string} xml
|
|
146
|
+
* @returns {boolean}
|
|
147
|
+
*/
|
|
148
|
+
export function taskHasAction(xml) {
|
|
149
|
+
const text = typeof xml === 'string' ? xml : '';
|
|
150
|
+
return /<Command>/i.test(text);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* THE PROBE.
|
|
155
|
+
*
|
|
156
|
+
* @param {{
|
|
157
|
+
* platform?: string,
|
|
158
|
+
* exec?: (file: string, args: string[]) => { ok: boolean, stdout: string, error: string|null },
|
|
159
|
+
* now?: number,
|
|
160
|
+
* taskName?: string,
|
|
161
|
+
* pid?: number|string,
|
|
162
|
+
* }} [options]
|
|
163
|
+
* @returns {{ scheduler: string, checkedAt: number, evidence: object }}
|
|
164
|
+
*/
|
|
165
|
+
export function probeSchedulerCapability({
|
|
166
|
+
platform = process.platform,
|
|
167
|
+
exec = defaultExec,
|
|
168
|
+
now = Date.now(),
|
|
169
|
+
taskName = probeTaskName(now, process.pid),
|
|
170
|
+
pid = process.pid,
|
|
171
|
+
} = {}) {
|
|
172
|
+
const checkedAt = Math.trunc(Number(now) || Date.now());
|
|
173
|
+
const evidence = { platform, taskName, steps: [] };
|
|
174
|
+
|
|
175
|
+
// Only Windows uses a one-off scheduled task. Everywhere else the question
|
|
176
|
+
// does not apply (the advice for those platforms is elsewhere).
|
|
177
|
+
if (platform !== 'win32') {
|
|
178
|
+
return {
|
|
179
|
+
scheduler: SCHEDULER_OK,
|
|
180
|
+
checkedAt,
|
|
181
|
+
evidence: {
|
|
182
|
+
...evidence,
|
|
183
|
+
applicable: false,
|
|
184
|
+
note: 'not applicable: this platform does not use a one-off scheduled task',
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const trigger = probeTrigger(checkedAt);
|
|
190
|
+
const step = (kind, ok, detail) => {
|
|
191
|
+
evidence.steps.push({ kind, ok, detail: detail === undefined ? null : detail });
|
|
192
|
+
return ok;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// (0) Idempotence: a task of an earlier run (or of another instance) must
|
|
196
|
+
// not be in the way.
|
|
197
|
+
const preClean = exec('schtasks', ['/Delete', '/TN', taskName, '/F']);
|
|
198
|
+
step('pre-clean', true, preClean.ok ? 'deleted a leftover probe task' : 'no leftover probe task');
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
// (1) Register the probe task: it fires in two minutes and does nothing.
|
|
202
|
+
const created = exec('schtasks', [
|
|
203
|
+
'/Create', '/TN', taskName, '/SC', 'ONCE', '/SD', trigger.sd, '/ST', trigger.st,
|
|
204
|
+
'/TR', 'cmd /c exit 0', '/F',
|
|
205
|
+
]);
|
|
206
|
+
if (!step('create', created.ok, created.ok ? 'the task was registered' : created.error)) {
|
|
207
|
+
return { scheduler: SCHEDULER_BLOCKED, checkedAt, evidence };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// (2) Read it back: the trigger must be the one we asked for, and the
|
|
211
|
+
// task must be enabled (that is what "Ready" means in the XML).
|
|
212
|
+
const query = exec('schtasks', ['/Query', '/TN', taskName, '/XML']);
|
|
213
|
+
if (!step('verify', query.ok, query.ok ? 'the task was read back' : query.error)) {
|
|
214
|
+
return { scheduler: SCHEDULER_BLOCKED, checkedAt, evidence };
|
|
215
|
+
}
|
|
216
|
+
if (!step('trigger', triggerMatches(query.stdout, trigger.iso), 'expected ' + trigger.iso + ', got ' + readStartBoundary(query.stdout))) {
|
|
217
|
+
return { scheduler: SCHEDULER_BLOCKED, checkedAt, evidence };
|
|
218
|
+
}
|
|
219
|
+
if (!step('enabled', taskEnabled(query.stdout), 'the task would fire')) {
|
|
220
|
+
return { scheduler: SCHEDULER_BLOCKED, checkedAt, evidence };
|
|
221
|
+
}
|
|
222
|
+
if (!step('action', taskHasAction(query.stdout), 'the task carries an action')) {
|
|
223
|
+
return { scheduler: SCHEDULER_BLOCKED, checkedAt, evidence };
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
step('error', false, error && error.message ? error.message : String(error));
|
|
227
|
+
return { scheduler: SCHEDULER_BLOCKED, checkedAt, evidence };
|
|
228
|
+
} finally {
|
|
229
|
+
// (3) Delete it — ALWAYS, also when a step above failed. A registration
|
|
230
|
+
// that was refused can still have left a task behind (the command may
|
|
231
|
+
// have created it and then failed), so the cleanup is not conditional.
|
|
232
|
+
const removed = exec('schtasks', ['/Delete', '/TN', taskName, '/F']);
|
|
233
|
+
step('delete', removed.ok, removed.ok ? 'the probe task was deleted' : removed.error);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// (4) Read it back once more: it must be GONE. A task that survives the
|
|
237
|
+
// probe is a task the user would find in his task scheduler.
|
|
238
|
+
const after = exec('schtasks', ['/Query', '/TN', taskName, '/XML']);
|
|
239
|
+
const gone = !after.ok;
|
|
240
|
+
step('gone', gone, gone ? 'the task is gone' : 'the task still exists: ' + after.error);
|
|
241
|
+
if (!gone) {
|
|
242
|
+
return { scheduler: SCHEDULER_BLOCKED, checkedAt, evidence };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { scheduler: SCHEDULER_OK, checkedAt, evidence };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Must the user be warned now?
|
|
250
|
+
*
|
|
251
|
+
* The warning is for ONE moment: the transition into "blocked". A machine
|
|
252
|
+
* that was blocked and stays blocked does not nag, and a user who has seen it
|
|
253
|
+
* is not asked again.
|
|
254
|
+
*
|
|
255
|
+
* @param {object|null} previous the capabilities before the check
|
|
256
|
+
* @param {object|null} current the capabilities after the check
|
|
257
|
+
* @returns {boolean}
|
|
258
|
+
*/
|
|
259
|
+
export function shouldWarnScheduler(previous, current) {
|
|
260
|
+
if (!current || typeof current !== 'object') return false;
|
|
261
|
+
if (current.scheduler !== SCHEDULER_BLOCKED) return false;
|
|
262
|
+
if (current.warnedAt) return false;
|
|
263
|
+
const before = previous && typeof previous === 'object' ? previous.scheduler : SCHEDULER_UNKNOWN;
|
|
264
|
+
// Only the TRANSITION warns: from ok or unknown into blocked.
|
|
265
|
+
return before !== SCHEDULER_BLOCKED;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* The capabilities with the warning marked as seen.
|
|
270
|
+
*
|
|
271
|
+
* @param {object} capabilities
|
|
272
|
+
* @param {number} [now]
|
|
273
|
+
* @returns {object}
|
|
274
|
+
*/
|
|
275
|
+
export function markSchedulerWarned(capabilities, now = Date.now()) {
|
|
276
|
+
const record = capabilities && typeof capabilities === 'object' ? capabilities : {};
|
|
277
|
+
if (record.warnedAt) return record;
|
|
278
|
+
return { ...record, warnedAt: Math.trunc(Number(now) || Date.now()) };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* A capabilities record that says nothing yet.
|
|
283
|
+
*
|
|
284
|
+
* @returns {{ scheduler: string, checkedAt: number|null, evidence: object|null, warnedAt: number|null }}
|
|
285
|
+
*/
|
|
286
|
+
export function emptyCapabilities() {
|
|
287
|
+
return { scheduler: SCHEDULER_UNKNOWN, checkedAt: null, evidence: null, warnedAt: null };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Is the capabilities record readable?
|
|
292
|
+
*
|
|
293
|
+
* @param {unknown} raw
|
|
294
|
+
* @returns {boolean}
|
|
295
|
+
*/
|
|
296
|
+
export function isCapabilities(raw) {
|
|
297
|
+
if (!raw || typeof raw !== 'object') return false;
|
|
298
|
+
const scheduler = raw.scheduler;
|
|
299
|
+
return scheduler === SCHEDULER_OK || scheduler === SCHEDULER_BLOCKED || scheduler === SCHEDULER_UNKNOWN;
|
|
300
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE watchdog at a time.
|
|
3
|
+
*
|
|
4
|
+
* This plugin knows two watchdogs:
|
|
5
|
+
*
|
|
6
|
+
* - **v1** (`lib/restart.js`): a detached PowerShell script. It puts the
|
|
7
|
+
* snapshot back when the host does not come up — and it does **not** start
|
|
8
|
+
* the host again.
|
|
9
|
+
* - **v2** (`lib/watchdog.js` + the three tiers): it puts the snapshot back
|
|
10
|
+
* **and** starts the host again, and it writes a report.
|
|
11
|
+
*
|
|
12
|
+
* Both take the SAME lock (`rollback.lock`). Two of them at once is worse than
|
|
13
|
+
* one: the one that loses the lock gives up, and if the loser is v2 the user
|
|
14
|
+
* gets a restored profile **and nothing running** — the rescue becomes the
|
|
15
|
+
* hole.
|
|
16
|
+
*
|
|
17
|
+
* So the rule is simple and it is decided in ONE place:
|
|
18
|
+
*
|
|
19
|
+
* - v2 is armed → v2 is the arbiter, the old watcher is **not** started;
|
|
20
|
+
* - v2 could not be armed (no tier, the watchdog is off, the registration
|
|
21
|
+
* failed) → v1 is the arbiter, so the old machines still have a safety net.
|
|
22
|
+
*
|
|
23
|
+
* The v1 code stays (a way back, and the old tests still use it); it is just
|
|
24
|
+
* no longer started next to v2.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** Who is watching this restart. */
|
|
28
|
+
export const ARBITERS = Object.freeze({
|
|
29
|
+
/** The new watchdog: restore + start the host again + a report. */
|
|
30
|
+
V2: 'v2',
|
|
31
|
+
/** The old watcher: restore only. */
|
|
32
|
+
V1: 'v1',
|
|
33
|
+
/** Nobody: the supervisor of the plugin restores the snapshot itself. */
|
|
34
|
+
NONE: 'none',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Decide who is the arbiter of this restart.
|
|
39
|
+
*
|
|
40
|
+
* @param {{
|
|
41
|
+
* v2Armed?: boolean,
|
|
42
|
+
* supervised?: boolean,
|
|
43
|
+
* }} [options]
|
|
44
|
+
* @returns {{ arbiter: string, startV1: boolean, reason: string }}
|
|
45
|
+
*/
|
|
46
|
+
export function planWatchdogArbiter({ v2Armed = false, supervised = false } = {}) {
|
|
47
|
+
if (supervised === true) {
|
|
48
|
+
return {
|
|
49
|
+
arbiter: ARBITERS.NONE,
|
|
50
|
+
startV1: false,
|
|
51
|
+
reason: 'the supervisor of the plugin restores the snapshot itself',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (v2Armed === true) {
|
|
55
|
+
return {
|
|
56
|
+
arbiter: ARBITERS.V2,
|
|
57
|
+
startV1: false,
|
|
58
|
+
reason: 'the watchdog v2 is armed: it restores the snapshot AND starts the host again',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
arbiter: ARBITERS.V1,
|
|
63
|
+
startV1: true,
|
|
64
|
+
reason: 'the watchdog v2 is not available: the old watcher takes over',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Arm the watchdog — and only ONE of them.
|
|
70
|
+
*
|
|
71
|
+
* Both arms are injected, so a test can count them: when v2 is armed, the v1
|
|
72
|
+
* arm is never called, and the other way round.
|
|
73
|
+
*
|
|
74
|
+
* @param {{
|
|
75
|
+
* v2Arm?: Function|null,
|
|
76
|
+
* v1Arm?: Function|null,
|
|
77
|
+
* supervised?: boolean,
|
|
78
|
+
* log?: { warn?: Function, info?: Function }|null,
|
|
79
|
+
* }} [options]
|
|
80
|
+
* @returns {{
|
|
81
|
+
* arbiter: string,
|
|
82
|
+
* reason: string,
|
|
83
|
+
* v2: object,
|
|
84
|
+
* v1: object|null,
|
|
85
|
+
* v1Started: boolean,
|
|
86
|
+
* }}
|
|
87
|
+
*/
|
|
88
|
+
export function armTheOneArbiter({
|
|
89
|
+
v2Arm = null,
|
|
90
|
+
v1Arm = null,
|
|
91
|
+
supervised = false,
|
|
92
|
+
log = null,
|
|
93
|
+
} = {}) {
|
|
94
|
+
const v2 = typeof v2Arm === 'function'
|
|
95
|
+
? v2Arm()
|
|
96
|
+
: { ok: false, tier: 'none', reason: 'the watchdog v2 was not armed' };
|
|
97
|
+
const plan = planWatchdogArbiter({ v2Armed: v2 !== null && v2.ok === true, supervised });
|
|
98
|
+
let v1 = null;
|
|
99
|
+
if (plan.startV1 === true && typeof v1Arm === 'function') {
|
|
100
|
+
v1 = v1Arm();
|
|
101
|
+
}
|
|
102
|
+
if (log !== null && typeof log === 'object' && typeof log.warn === 'function') {
|
|
103
|
+
log.warn('the watchdog arbiter is decided', {
|
|
104
|
+
arbiter: plan.arbiter,
|
|
105
|
+
reason: plan.reason,
|
|
106
|
+
v2Tier: v2 && typeof v2.tier === 'string' ? v2.tier : 'none',
|
|
107
|
+
v1Started: v1 !== null,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
arbiter: plan.arbiter,
|
|
112
|
+
reason: plan.reason,
|
|
113
|
+
v2: v2 === null ? { ok: false, tier: 'none', reason: 'the watchdog v2 was not armed' } : v2,
|
|
114
|
+
v1,
|
|
115
|
+
v1Started: v1 !== null,
|
|
116
|
+
};
|
|
117
|
+
}
|