@twinsearth/w2m-dsh-plugin 0.5.8 → 0.6.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 +384 -7
- package/CONTRIBUTING.md +14 -0
- package/PROTOCOL-v0.4.0.md +11 -3
- package/PROTOCOL.md +69 -15
- package/README.md +180 -27
- package/SECURITY.md +69 -19
- package/TROUBLESHOOTING.md +100 -53
- package/bin/w2m-rabbit.mjs +32 -5
- package/deploy/networking/LESSONS.md +18 -1
- package/deploy/networking/README.md +32 -7
- package/deploy/windows/README.md +20 -1
- package/deploy/windows/install-service.ps1 +98 -19
- package/deploy/windows/verify-supervisor.mjs +794 -0
- package/deploy/windows/w2m-agent-supervisor.mjs +867 -40
- package/docs/AUDIT.md +10 -1
- package/docs/DEPLOY.md +16 -3
- package/docs/P2P-CRYPTO.md +105 -4
- package/docs/PIPELINE-DESIGN.md +35 -10
- package/docs/TROUBLESHOOTING.md +22 -3
- package/package.json +6 -6
- package/src/agent/agent.mjs +152 -5
- package/src/agent/allowed-commands.mjs +539 -15
- package/src/agent/crypto.mjs +238 -40
- package/src/agent/exec.mjs +60 -4
- package/src/agent/p2p-node.mjs +40 -0
- package/src/agent/p2p.mjs +323 -30
- package/src/agent/resolve.mjs +68 -3
- package/src/plugin/fleet-target.mjs +160 -15
- package/src/plugin/tools.mjs +319 -56
- package/src/relay/server.mjs +74 -11
package/src/relay/server.mjs
CHANGED
|
@@ -36,6 +36,32 @@ export const DEFAULT_PAIR_RATE_LIMIT_PER_MINUTE = 5; // v0.1.2 §6
|
|
|
36
36
|
export const RATE_LIMIT_WINDOW_MS = 60_000;
|
|
37
37
|
export const DEFAULT_STATE_DIR = path.join(os.homedir(), '.w2m');
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* The environment variable that opts in to printing the operator token on a start that did not
|
|
41
|
+
* generate it (MEDIUM-6 of the v0.5.9 review).
|
|
42
|
+
*
|
|
43
|
+
* The value is printed **once, on the start that generated it**, because that is the only moment at
|
|
44
|
+
* which it exists nowhere else yet; every other start has `<state>/operator-token.txt` to read it
|
|
45
|
+
* from, and a relay's stdout ends up in journald, a container log or a CI transcript — where a
|
|
46
|
+
* credential that authorises dispatching to the whole fleet does not belong. `W2M_PRINT_OPERATOR_TOKEN=1`
|
|
47
|
+
* (or `printOperatorToken: true`, which is the same switch for an embedder) restores the old
|
|
48
|
+
* behaviour for an operator who genuinely needs it in the log, and says so in the banner when it does.
|
|
49
|
+
*/
|
|
50
|
+
export const PRINT_OPERATOR_TOKEN_ENV = 'W2M_PRINT_OPERATOR_TOKEN';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve that opt-in, so the relay and the CLI agree on one answer.
|
|
54
|
+
*
|
|
55
|
+
* @param {object} [options] Relay options; `printOperatorToken: true` is the programmatic spelling.
|
|
56
|
+
* @returns {'option'|'env'|null} Why printing was asked for, or null.
|
|
57
|
+
*/
|
|
58
|
+
export function printOperatorTokenOptIn(options = {}) {
|
|
59
|
+
if (options.printOperatorToken === true) return 'option';
|
|
60
|
+
const raw = process.env[PRINT_OPERATOR_TOKEN_ENV];
|
|
61
|
+
if (raw === '1' || raw === 'true') return 'env';
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
/* ------------------------------------------------------------------ */
|
|
40
66
|
/* helpers */
|
|
41
67
|
/* ------------------------------------------------------------------ */
|
|
@@ -459,11 +485,19 @@ export class RelayServer {
|
|
|
459
485
|
const wantsRequired = options.operatorTokenRequired !== false;
|
|
460
486
|
const explicit = options.operatorToken;
|
|
461
487
|
const persisted = this.persistence.enabled ? this.persistence.readOperatorToken() : null;
|
|
488
|
+
/**
|
|
489
|
+
* Why the token may be printed, when it may be: `'generated'` is the one start whose token exists
|
|
490
|
+
* nowhere else until this process writes it, and `'option'`/`'env'` are the documented opt-ins.
|
|
491
|
+
* @type {'generated'|'option'|'env'|null}
|
|
492
|
+
*/
|
|
493
|
+
this.operatorTokenPrintReason = printOperatorTokenOptIn(options);
|
|
462
494
|
|
|
463
495
|
if (!wantsRequired || explicit === '') {
|
|
464
496
|
this.operatorToken = null;
|
|
465
497
|
this.operatorTokenRequired = false;
|
|
466
498
|
this.operatorTokenSource = 'disabled';
|
|
499
|
+
this.operatorTokenPrintable = false;
|
|
500
|
+
this.operatorTokenPrintReason = null;
|
|
467
501
|
return;
|
|
468
502
|
}
|
|
469
503
|
this.operatorTokenRequired = true;
|
|
@@ -471,18 +505,22 @@ export class RelayServer {
|
|
|
471
505
|
this.operatorToken = explicit;
|
|
472
506
|
this.operatorTokenSource = 'option';
|
|
473
507
|
this.persistence.writeOperatorToken(explicit);
|
|
474
|
-
|
|
475
|
-
}
|
|
476
|
-
if (persisted) {
|
|
508
|
+
} else if (persisted) {
|
|
477
509
|
// §5.1 + §7: keep the token stable across restarts, otherwise every restart
|
|
478
510
|
// would silently invalidate the plugin's configured operatorToken.
|
|
479
511
|
this.operatorToken = persisted;
|
|
480
512
|
this.operatorTokenSource = 'restored';
|
|
481
|
-
|
|
513
|
+
} else {
|
|
514
|
+
this.operatorToken = randomBytes(32).toString('hex');
|
|
515
|
+
this.operatorTokenSource = 'generated';
|
|
516
|
+
this.persistence.writeOperatorToken(this.operatorToken);
|
|
482
517
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
518
|
+
// MEDIUM-6: the material is printed only by the start that generated it, or when the operator has
|
|
519
|
+
// explicitly asked for it. "The posture, never the material" still holds everywhere else.
|
|
520
|
+
if (this.operatorTokenSource === 'generated') {
|
|
521
|
+
this.operatorTokenPrintReason = this.operatorTokenPrintReason ?? 'generated';
|
|
522
|
+
}
|
|
523
|
+
this.operatorTokenPrintable = this.operatorTokenPrintReason !== null;
|
|
486
524
|
}
|
|
487
525
|
|
|
488
526
|
_buildStartupMessages() {
|
|
@@ -517,10 +555,30 @@ export class RelayServer {
|
|
|
517
555
|
m.push(`[w2m-rabbit] !! ${w.file}: ${w.message}`);
|
|
518
556
|
}
|
|
519
557
|
if (this.operatorTokenRequired) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
558
|
+
// MEDIUM-6: the token is material, not posture, so it is printed as rarely as it can be — on the
|
|
559
|
+
// start that generated it (`operatorTokenSource === 'generated'`) or when `printOperatorToken` /
|
|
560
|
+
// `W2M_PRINT_OPERATOR_TOKEN=1` asked for it — and once, never twice. Where it is stored is said in
|
|
561
|
+
// every case, including the case where there is nowhere to store it.
|
|
562
|
+
//
|
|
563
|
+
// DELIBERATE, do not "fix": on that one start the CLI prints the value a second time, in its own
|
|
564
|
+
// stdout banner. Two sinks for one start is not a leak — the defect was printing it on *every*
|
|
565
|
+
// start, restored ones included — and the box is the documented first-start handoff a human reads
|
|
566
|
+
// (docs/DEPLOY.md §2.4, test/crossnetwork.test.mjs). Collapsing the pair would trade a documented
|
|
567
|
+
// behaviour for one saved line on one start.
|
|
568
|
+
const tokenFile = this.persistence.operatorTokenFile ?? null;
|
|
569
|
+
const stored = tokenFile === null
|
|
570
|
+
? 'persistence is off, so this token exists only in this process and cannot be read back later'
|
|
571
|
+
: `stored at ${tokenFile} (0600)`;
|
|
572
|
+
if (this.operatorTokenPrintable) {
|
|
573
|
+
m.push(`[w2m-rabbit] operator token (${this.operatorTokenSource}) - required for POST /v1/task:`);
|
|
574
|
+
m.push(`[w2m-rabbit] ${this.operatorToken}`);
|
|
575
|
+
m.push(`[w2m-rabbit] printed once because ${this.operatorTokenPrintReason === 'generated'
|
|
576
|
+
? 'this start generated it' : `${this.operatorTokenPrintReason} asked for it (${PRINT_OPERATOR_TOKEN_ENV})`}`
|
|
577
|
+
+ ` - and ${stored}`);
|
|
578
|
+
} else {
|
|
579
|
+
m.push(`[w2m-rabbit] operator token (${this.operatorTokenSource}) - required for POST /v1/task, NOT printed`);
|
|
580
|
+
m.push(`[w2m-rabbit] read it from ${tokenFile ?? '<state>/operator-token.txt'} - ${stored}`);
|
|
581
|
+
m.push(`[w2m-rabbit] set ${PRINT_OPERATOR_TOKEN_ENV}=1 to print it on this start as well`);
|
|
524
582
|
}
|
|
525
583
|
} else {
|
|
526
584
|
m.push('[w2m-rabbit] !! WARNING: OPERATOR TOKEN DISABLED (--operator-token "") - '
|
|
@@ -562,6 +620,11 @@ export class RelayServer {
|
|
|
562
620
|
basePath: this.basePath,
|
|
563
621
|
operatorToken: this.operatorToken,
|
|
564
622
|
operatorTokenRequired: this.operatorTokenRequired,
|
|
623
|
+
// The CLI prints its own banner, so it has to reach the same conclusion this class did rather
|
|
624
|
+
// than re-deriving one from the token's presence (MEDIUM-6).
|
|
625
|
+
operatorTokenSource: this.operatorTokenSource,
|
|
626
|
+
operatorTokenPrintable: this.operatorTokenPrintable === true,
|
|
627
|
+
operatorTokenPrintReason: this.operatorTokenPrintReason ?? null,
|
|
565
628
|
scheme: this.scheme,
|
|
566
629
|
});
|
|
567
630
|
});
|