@bli-cockpit/cli 0.2.13 → 0.2.14
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/dist/autostart.js +72 -5
- package/dist/commands/public-root.js +1 -1
- package/package.json +1 -1
package/dist/autostart.js
CHANGED
|
@@ -270,6 +270,13 @@ async function windowsTaskStatus(options) {
|
|
|
270
270
|
const queryMessage = `${query.stderr}\n${query.stdout}`;
|
|
271
271
|
const taskIsKnownAbsent = query.code === 1 &&
|
|
272
272
|
/cannot find|not found|does not exist/iu.test(queryMessage);
|
|
273
|
+
// Metadata only: schtasks stderr can carry the state directory, so the exit
|
|
274
|
+
// code and the classification travel, never the text.
|
|
275
|
+
console.error("[autostart] windows task query failed", JSON.stringify({
|
|
276
|
+
task_name: WINDOWS_AUTOSTART_TASK_NAME,
|
|
277
|
+
exit_code: query.code,
|
|
278
|
+
status: taskIsKnownAbsent ? "absent" : "not_loaded",
|
|
279
|
+
}));
|
|
273
280
|
return {
|
|
274
281
|
status: taskIsKnownAbsent ? "absent" : "not_loaded",
|
|
275
282
|
label: AUTOSTART_LABEL,
|
|
@@ -305,6 +312,23 @@ async function windowsTaskStatus(options) {
|
|
|
305
312
|
registrationProblems.push("sync script does not match the current roots or Cockpit runtime");
|
|
306
313
|
}
|
|
307
314
|
}
|
|
315
|
+
// Both branches log. A line that only fires on failure cannot answer "did
|
|
316
|
+
// background collection validate at all today?", which is the question that
|
|
317
|
+
// would have caught a validator rejecting every healthy Windows task.
|
|
318
|
+
// Problem labels carry no paths, so they are safe to emit verbatim.
|
|
319
|
+
if (registrationProblems.length > 0) {
|
|
320
|
+
console.error("[autostart] windows task needs repair", JSON.stringify({
|
|
321
|
+
task_name: WINDOWS_AUTOSTART_TASK_NAME,
|
|
322
|
+
problem_count: registrationProblems.length,
|
|
323
|
+
problems: registrationProblems,
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
console.error("[autostart] windows task validated", JSON.stringify({
|
|
328
|
+
task_name: WINDOWS_AUTOSTART_TASK_NAME,
|
|
329
|
+
interval_minutes: intervalMinutes,
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
308
332
|
return {
|
|
309
333
|
status: registrationProblems.length > 0 ? "not_loaded" : "loaded",
|
|
310
334
|
label: AUTOSTART_LABEL,
|
|
@@ -415,7 +439,7 @@ function windowsTaskRegistrationProblems(taskXml, expected) {
|
|
|
415
439
|
const actionArguments = exactExecBlock
|
|
416
440
|
.match(/<Arguments>\s*([^<]*?)\s*<\/Arguments>/iu)?.[1]
|
|
417
441
|
?.trim() ?? "";
|
|
418
|
-
const
|
|
442
|
+
const expectedArgumentFlags = "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File";
|
|
419
443
|
const requirements = [
|
|
420
444
|
[
|
|
421
445
|
/<DisallowStartIfOnBatteries>\s*false\s*<\/DisallowStartIfOnBatteries>/iu,
|
|
@@ -444,8 +468,18 @@ function windowsTaskRegistrationProblems(taskXml, expected) {
|
|
|
444
468
|
if (!/<LogonType>\s*InteractiveToken\s*<\/LogonType>/iu.test(exactPrincipalBlock)) {
|
|
445
469
|
problems.push("logon type is not InteractiveToken");
|
|
446
470
|
}
|
|
447
|
-
|
|
448
|
-
|
|
471
|
+
// Windows omits <RunLevel> entirely when a task runs at the default
|
|
472
|
+
// LeastPrivilege, so `schtasks /Create ... /RL LIMITED` stores a principal
|
|
473
|
+
// block with no RunLevel element at all. Demanding the element asks Windows
|
|
474
|
+
// to state a default it never states, which is why this check failed on
|
|
475
|
+
// every Windows machine in the fleet and passed on none (0 of 4
|
|
476
|
+
// autostart-alive events ok, 2026-08-12). Absent means LeastPrivilege; only
|
|
477
|
+
// an explicit elevated value is a real problem.
|
|
478
|
+
const runLevel = exactPrincipalBlock
|
|
479
|
+
.match(/<RunLevel>\s*([^<]*?)\s*<\/RunLevel>/iu)?.[1]
|
|
480
|
+
?.trim() || "LeastPrivilege";
|
|
481
|
+
if (!/^LeastPrivilege$/iu.test(runLevel)) {
|
|
482
|
+
problems.push(`task does not run with limited privileges (RunLevel=${runLevel})`);
|
|
449
483
|
}
|
|
450
484
|
}
|
|
451
485
|
if (actionContext && actionContext !== principalId) {
|
|
@@ -477,8 +511,9 @@ function windowsTaskRegistrationProblems(taskXml, expected) {
|
|
|
477
511
|
if (!sameWindowsPath(command, expected.powershellPath)) {
|
|
478
512
|
problems.push("task action does not run the expected Windows PowerShell");
|
|
479
513
|
}
|
|
480
|
-
|
|
481
|
-
|
|
514
|
+
const actionArgumentProblem = windowsActionArgumentProblem(actionArguments, expectedArgumentFlags, expected.scriptPath);
|
|
515
|
+
if (actionArgumentProblem) {
|
|
516
|
+
problems.push(actionArgumentProblem);
|
|
482
517
|
}
|
|
483
518
|
if (/<Duration>/iu.test(repetition)) {
|
|
484
519
|
problems.push("repetition has a finite duration");
|
|
@@ -489,6 +524,38 @@ function windowsPowerShellPath(env = process.env) {
|
|
|
489
524
|
const windowsRoot = env.SystemRoot ?? env.WINDIR ?? "C:\\Windows";
|
|
490
525
|
return path.win32.join(windowsRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
491
526
|
}
|
|
527
|
+
/**
|
|
528
|
+
* Task Scheduler stores a normalized form of the action it was given, not the
|
|
529
|
+
* string we passed: `schtasks /Create /TR "<powershell> <args>"` is split into
|
|
530
|
+
* <Command> plus <Arguments>, and the quotes around a space-free script path
|
|
531
|
+
* are dropped. Comparing <Arguments> against the exact string we built
|
|
532
|
+
* therefore fails on a task that is registered perfectly — observed on
|
|
533
|
+
* DESKTOP-G2UO1GK (CLI 0.2.13, 2026-08-12), where the stored value differed
|
|
534
|
+
* from the expected one only by those quotes.
|
|
535
|
+
*
|
|
536
|
+
* Compare the flags exactly, because those are ours, and compare the script as
|
|
537
|
+
* a path, because that is what Windows normalizes. Each failure returns its own
|
|
538
|
+
* reason so a repair message says which half is wrong rather than "arguments
|
|
539
|
+
* differ".
|
|
540
|
+
*/
|
|
541
|
+
function windowsActionArgumentProblem(actionArguments, expectedFlags, expectedScriptPath) {
|
|
542
|
+
if (!actionArguments.startsWith(expectedFlags)) {
|
|
543
|
+
return "task action does not run PowerShell with the expected Cockpit sync flags";
|
|
544
|
+
}
|
|
545
|
+
const scriptArgument = unquoteWindowsArgument(actionArguments.slice(expectedFlags.length));
|
|
546
|
+
if (!scriptArgument) {
|
|
547
|
+
return "task action names no sync script to run";
|
|
548
|
+
}
|
|
549
|
+
if (!sameWindowsPath(scriptArgument, expectedScriptPath)) {
|
|
550
|
+
return "task action runs a script other than the Cockpit sync script";
|
|
551
|
+
}
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
function unquoteWindowsArgument(value) {
|
|
555
|
+
const trimmed = value.trim();
|
|
556
|
+
const quoted = trimmed.match(/^"([\s\S]*)"$/u) ?? trimmed.match(/^'([\s\S]*)'$/u);
|
|
557
|
+
return (quoted?.[1] ?? trimmed).trim();
|
|
558
|
+
}
|
|
492
559
|
function sameWindowsPath(left, right) {
|
|
493
560
|
if (!left || !right)
|
|
494
561
|
return false;
|
|
@@ -15,7 +15,7 @@ export async function runCockpitCli(argv, io) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
if (command === "--version" || command === "-V" || command === "version") {
|
|
18
|
-
writeLine(io?.stdout ?? process.stdout, "0.2.
|
|
18
|
+
writeLine(io?.stdout ?? process.stdout, "0.2.14");
|
|
19
19
|
return 0;
|
|
20
20
|
}
|
|
21
21
|
|