@serverkgg/bridge 0.0.21 → 0.1.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/package.json +3 -1
- package/src/bot/botDeploy.test.ts +363 -0
- package/src/bot/botDeploy.ts +136 -0
- package/src/bot/botEnvironment.test.ts +189 -0
- package/src/bot/botEnvironment.ts +141 -0
- package/src/bot/botIdentity.test.ts +251 -0
- package/src/bot/botIdentity.ts +140 -0
- package/src/bot/botPanel.test.ts +216 -0
- package/src/bot/botPanel.ts +121 -0
- package/src/bot/botState.test.ts +45 -0
- package/src/bot/botState.ts +54 -0
- package/src/bot/botTerminal.test.ts +79 -0
- package/src/bot/botTerminal.ts +62 -0
- package/src/bot/botVariables.test.ts +193 -0
- package/src/bot/botVariables.ts +73 -0
- package/src/bot/detect/botFramework.ts +23 -0
- package/src/bot/detect/botFrameworks.ts +198 -0
- package/src/bot/detect/botInspectWarning.ts +15 -0
- package/src/bot/detect/botPackageManager.ts +6 -0
- package/src/bot/detect/botPlatform.ts +10 -0
- package/src/bot/detect/botRuntime.ts +15 -0
- package/src/bot/detect/botToken.test.ts +71 -0
- package/src/bot/detect/botToken.ts +126 -0
- package/src/bot/detect/detect.test.ts +543 -0
- package/src/bot/detect/detect.ts +842 -0
- package/src/bot/detect/detectResult.ts +40 -0
- package/src/bot/detect/detectTable.test.ts +72 -0
- package/src/bot/detect/detectTable.ts +166 -0
- package/src/bot/detect/envExample.test.ts +29 -0
- package/src/bot/detect/envExample.ts +48 -0
- package/src/bot/detect/envKey.ts +7 -0
- package/src/bot/detect/envScan.test.ts +39 -0
- package/src/bot/detect/envScan.ts +87 -0
- package/src/bot/detect/index.ts +17 -0
- package/src/bot/detect/inferBotPlatform.test.ts +60 -0
- package/src/bot/detect/inferBotPlatform.ts +28 -0
- package/src/bot/detect/keys.test.ts +28 -0
- package/src/bot/detect/keys.ts +64 -0
- package/src/bot/detect/sensitiveKey.test.ts +91 -0
- package/src/bot/detect/sensitiveKey.ts +44 -0
- package/src/bot/detect/serverkJson.test.ts +88 -0
- package/src/bot/detect/serverkJson.ts +174 -0
- package/src/bot/index.ts +8 -0
- package/src/cli/detectTable.test.ts +66 -0
- package/src/cli/detectTable.ts +43 -0
- package/src/cli/main.ts +12 -4
- package/src/cli/renderedFile.ts +35 -0
- package/src/cli/schema.ts +7 -26
- package/src/cli/validate.test.ts +210 -2
- package/src/cli/validate.ts +71 -7
- package/src/context/bridgeContext.ts +11 -0
- package/src/core/bridgePort.test.ts +45 -0
- package/src/core/bridgePort.ts +26 -0
- package/src/guides/compile.ts +2 -2
- package/src/manifest/index.ts +1 -0
- package/src/manifest/kind.ts +5 -0
- package/src/manifest/manifest.test.ts +630 -3
- package/src/manifest/manifest.ts +391 -87
- package/src/manifest/runtime.ts +2 -2
- package/src/manifest/source.ts +4 -4
- package/src/modules/bridgeLifecycleSubject.ts +8 -0
- package/src/modules/bridgeModule.ts +6 -0
- package/src/modules/bridgeStopSignal.ts +13 -0
- package/src/modules/index.ts +2 -0
- package/src/panel/bridgeDetail.ts +2 -0
- package/src/protocol/bridgeConfig.ts +2 -0
- package/src/runtime/bridgeConfigReader.test.ts +80 -0
- package/src/runtime/bridgeConfigReader.ts +20 -0
- package/src/runtime/bridgeContext.test.ts +241 -0
- package/src/runtime/bridgeContext.ts +35 -9
- package/src/runtime/bridgeDriver.test.ts +132 -1
- package/src/runtime/bridgeDriver.ts +54 -1
- package/src/runtime/bridgeFiles.test.ts +62 -0
- package/src/runtime/bridgeFiles.ts +12 -2
- package/src/runtime/bridgeLiveConfig.ts +18 -0
- package/src/runtime/bridgeRedaction.test.ts +410 -0
- package/src/runtime/bridgeRedaction.ts +214 -0
- package/src/runtime/bridgeRegistry.test.ts +3 -0
- package/src/runtime/bridgeRegistry.ts +15 -0
- package/src/runtime/bridgeServer.test.ts +130 -0
- package/src/runtime/bridgeServer.ts +24 -7
- package/src/runtime/bridgeSetupHandlers.test.ts +88 -0
- package/src/runtime/bridgeSetupHandlers.ts +16 -6
- package/src/runtime/bridgeSupervisor.test.ts +670 -2
- package/src/runtime/bridgeSupervisor.ts +219 -67
- package/src/runtime/serve.ts +47 -13
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { BridgeCommandOptions, BridgeReply } from "../context";
|
|
2
2
|
import { BridgeRuntime, BridgeStream } from "../core";
|
|
3
|
+
import { BridgeLifecycleSubject } from "../modules/bridgeLifecycleSubject";
|
|
4
|
+
import { BRIDGE_STOP_SIGNALS, type BridgeStopSignal } from "../modules/bridgeStopSignal";
|
|
3
5
|
import { BRIDGE_CRASH_LINE_LIMIT, BridgeCrashPhase, type BridgeCrashRecord } from "../protocol";
|
|
4
6
|
import { stripAnsi } from "../utils";
|
|
5
7
|
import type { BridgeLeveledLogger } from "./bridgeLog";
|
|
@@ -21,10 +23,14 @@ const RESTART_LIMIT = 3;
|
|
|
21
23
|
|
|
22
24
|
const STABLE_UPTIME_MS = 180_000;
|
|
23
25
|
|
|
26
|
+
const READY_TIMEOUT_STOP_FACTOR = 10;
|
|
27
|
+
|
|
24
28
|
const GAME_OOM_SCORE_ADJ = 500;
|
|
25
29
|
|
|
26
30
|
const SIGKILL_EXIT_CODE = 128 + 9;
|
|
27
31
|
|
|
32
|
+
const ENVIRONMENT_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
33
|
+
|
|
28
34
|
export interface SupervisorLine {
|
|
29
35
|
stream: BridgeStream;
|
|
30
36
|
line: string;
|
|
@@ -35,9 +41,14 @@ export interface SupervisorOptions {
|
|
|
35
41
|
uid: number;
|
|
36
42
|
gid: number;
|
|
37
43
|
stopTimeoutSeconds: number;
|
|
44
|
+
readyTimeoutSeconds?: number;
|
|
45
|
+
stopSignal?: BridgeStopSignal;
|
|
46
|
+
subject?: BridgeLifecycleSubject;
|
|
38
47
|
ready?: RegExp;
|
|
39
48
|
failed?: RegExp;
|
|
40
49
|
resolveCommand: () => Promise<string[]>;
|
|
50
|
+
resolveEnvironment?: () => Promise<Record<string, string>>;
|
|
51
|
+
redact?: (text: string) => string;
|
|
41
52
|
gracefulStop?: () => Promise<void>;
|
|
42
53
|
stableAfterMs?: number;
|
|
43
54
|
onReady?: () => void;
|
|
@@ -79,6 +90,27 @@ const SIGNAL_NUMBERS: Record<string, number> = {
|
|
|
79
90
|
SIGTERM: 15,
|
|
80
91
|
};
|
|
81
92
|
|
|
93
|
+
const spawnEnvironment = (subject: BridgeLifecycleSubject, resolved: Record<string, string>) => {
|
|
94
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
95
|
+
if (!ENVIRONMENT_KEY.test(key)) {
|
|
96
|
+
throw new Error(`the ${subject} environment names "${key}", which is not a valid variable name`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof value !== "string") {
|
|
100
|
+
throw new Error(`the ${subject} environment value for "${key}" is not a string`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (value.includes("\0")) {
|
|
104
|
+
throw new Error(`the ${subject} environment value for "${key}" contains a NUL byte`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
...process.env,
|
|
110
|
+
...resolved,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
82
114
|
const EARLY_OUTPUT_LIMIT = 2000;
|
|
83
115
|
|
|
84
116
|
const earlyOutput = async (spawned: { stdout: unknown; stderr: unknown }) => {
|
|
@@ -152,6 +184,7 @@ const chain = () => {
|
|
|
152
184
|
};
|
|
153
185
|
|
|
154
186
|
export const createSupervisor = (options: SupervisorOptions) => {
|
|
187
|
+
const subject = options.subject ?? BridgeLifecycleSubject.Game;
|
|
155
188
|
const tail: SupervisorLine[] = [];
|
|
156
189
|
const watchers = new Set<LineWatcher>();
|
|
157
190
|
const restartStamps: number[] = [];
|
|
@@ -168,10 +201,36 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
168
201
|
let stable = false;
|
|
169
202
|
let stableTimer: ReturnType<typeof setTimeout> | null = null;
|
|
170
203
|
let bootFailure: string | null = null;
|
|
204
|
+
let replacing: SpawnedGame | null = null;
|
|
171
205
|
|
|
172
206
|
const serialize = chain();
|
|
173
207
|
const transition = chain();
|
|
174
208
|
|
|
209
|
+
const readyTimeoutMs = (options.readyTimeoutSeconds ?? options.stopTimeoutSeconds * READY_TIMEOUT_STOP_FACTOR) * 1000;
|
|
210
|
+
|
|
211
|
+
const awaitingReady = () => {
|
|
212
|
+
return options.readyTimeoutSeconds !== undefined && runtime === BridgeRuntime.Starting;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const failBoot = (line: string) => {
|
|
216
|
+
const failing = child;
|
|
217
|
+
const failingGroup = childGroup;
|
|
218
|
+
|
|
219
|
+
if (failing === null) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
bootFailure = line.trim().slice(0, BRIDGE_CRASH_LINE_LIMIT);
|
|
224
|
+
|
|
225
|
+
terminate(failing, failingGroup, "SIGTERM");
|
|
226
|
+
|
|
227
|
+
void Bun.sleep(KILL_GRACE_MS).then(() => {
|
|
228
|
+
if (child === failing) {
|
|
229
|
+
terminate(failing, failingGroup, "SIGKILL");
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
|
|
175
234
|
const markStable = () => {
|
|
176
235
|
if (stable) {
|
|
177
236
|
return;
|
|
@@ -182,7 +241,14 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
182
241
|
options.onStable?.();
|
|
183
242
|
};
|
|
184
243
|
|
|
185
|
-
const emit = (
|
|
244
|
+
const emit = (raw: SupervisorLine) => {
|
|
245
|
+
const entry = options.redact
|
|
246
|
+
? {
|
|
247
|
+
stream: raw.stream,
|
|
248
|
+
line: options.redact(raw.line),
|
|
249
|
+
}
|
|
250
|
+
: raw;
|
|
251
|
+
|
|
186
252
|
if (entry.stream === BridgeStream.Stderr) {
|
|
187
253
|
process.stderr.write(`${entry.line}\n`);
|
|
188
254
|
} else {
|
|
@@ -288,7 +354,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
288
354
|
const [executable, ...rest] = command;
|
|
289
355
|
|
|
290
356
|
if (!executable) {
|
|
291
|
-
throw new Error(
|
|
357
|
+
throw new Error(`the ${subject} start command is empty`);
|
|
292
358
|
}
|
|
293
359
|
|
|
294
360
|
const argv = [
|
|
@@ -296,6 +362,10 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
296
362
|
...rest,
|
|
297
363
|
];
|
|
298
364
|
|
|
365
|
+
const environment = options.resolveEnvironment
|
|
366
|
+
? spawnEnvironment(subject, await options.resolveEnvironment())
|
|
367
|
+
: null;
|
|
368
|
+
|
|
299
369
|
const { argv: spawnArgv, credentials } = await unprivilegedArgv(argv, options.uid, options.gid);
|
|
300
370
|
|
|
301
371
|
launchedAt = performance.now();
|
|
@@ -320,7 +390,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
320
390
|
reported = true;
|
|
321
391
|
|
|
322
392
|
void handleExit(spawned, code, pumps).catch((error: unknown) => {
|
|
323
|
-
options.onLog.error(
|
|
393
|
+
options.onLog.error(`the ${subject} exit was not reported`, {
|
|
324
394
|
error: errorText(error),
|
|
325
395
|
});
|
|
326
396
|
});
|
|
@@ -328,6 +398,9 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
328
398
|
|
|
329
399
|
const spawned = Bun.spawn(spawnArgv, {
|
|
330
400
|
...credentials,
|
|
401
|
+
...(environment && {
|
|
402
|
+
env: environment,
|
|
403
|
+
}),
|
|
331
404
|
cwd: options.cwd,
|
|
332
405
|
detached: true,
|
|
333
406
|
stderr: "pipe",
|
|
@@ -349,7 +422,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
349
422
|
|
|
350
423
|
if (observed === null && finished) {
|
|
351
424
|
throw new Error(
|
|
352
|
-
`the
|
|
425
|
+
`the ${subject} exited immediately with code ${exitCodeOf(spawned.exitCode, spawned.signalCode)}${await earlyOutput(spawned)}`,
|
|
353
426
|
);
|
|
354
427
|
}
|
|
355
428
|
|
|
@@ -360,7 +433,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
360
433
|
sweep(spawned.pid, group);
|
|
361
434
|
|
|
362
435
|
throw new Error(
|
|
363
|
-
`the
|
|
436
|
+
`the ${subject} process runs as uid ${String(observed)}, expected ${options.uid} — refusing to run it as the bridge`,
|
|
364
437
|
);
|
|
365
438
|
}
|
|
366
439
|
|
|
@@ -373,7 +446,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
373
446
|
const launchStamp = launchedAt;
|
|
374
447
|
|
|
375
448
|
stableTimer = setTimeout(() => {
|
|
376
|
-
if (launchStamp !== launchedAt || child === null) {
|
|
449
|
+
if (launchStamp !== launchedAt || child === null || awaitingReady()) {
|
|
377
450
|
return;
|
|
378
451
|
}
|
|
379
452
|
|
|
@@ -387,7 +460,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
387
460
|
|
|
388
461
|
accepted = true;
|
|
389
462
|
|
|
390
|
-
options.onLog(
|
|
463
|
+
options.onLog(`${subject} started`, {
|
|
391
464
|
pid: spawned.pid,
|
|
392
465
|
group,
|
|
393
466
|
uid: options.uid,
|
|
@@ -400,27 +473,18 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
400
473
|
}
|
|
401
474
|
|
|
402
475
|
if (options.failed) {
|
|
403
|
-
void watch(options.failed,
|
|
476
|
+
void watch(options.failed, readyTimeoutMs).then((match) => {
|
|
404
477
|
if (!match || launchStamp !== launchedAt || child === null || stable) {
|
|
405
478
|
return;
|
|
406
479
|
}
|
|
407
480
|
|
|
408
|
-
|
|
481
|
+
const line = match.input ?? match[0];
|
|
409
482
|
|
|
410
|
-
options.onLog.error(
|
|
411
|
-
line:
|
|
483
|
+
options.onLog.error(`the ${subject} reported a failed boot, stopping it`, {
|
|
484
|
+
line: line.trim().slice(0, BRIDGE_CRASH_LINE_LIMIT),
|
|
412
485
|
});
|
|
413
486
|
|
|
414
|
-
|
|
415
|
-
const failingGroup = childGroup;
|
|
416
|
-
|
|
417
|
-
terminate(failing, failingGroup, "SIGTERM");
|
|
418
|
-
|
|
419
|
-
void Bun.sleep(KILL_GRACE_MS).then(() => {
|
|
420
|
-
if (child === failing) {
|
|
421
|
-
terminate(failing, failingGroup, "SIGKILL");
|
|
422
|
-
}
|
|
423
|
-
});
|
|
487
|
+
failBoot(line);
|
|
424
488
|
});
|
|
425
489
|
}
|
|
426
490
|
|
|
@@ -432,7 +496,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
432
496
|
|
|
433
497
|
runtime = BridgeRuntime.Starting;
|
|
434
498
|
|
|
435
|
-
void watch(options.ready,
|
|
499
|
+
void watch(options.ready, readyTimeoutMs).then((match) => {
|
|
436
500
|
if (launchStamp !== launchedAt || child === null) {
|
|
437
501
|
return;
|
|
438
502
|
}
|
|
@@ -441,7 +505,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
441
505
|
if (runtime === BridgeRuntime.Starting) {
|
|
442
506
|
runtime = BridgeRuntime.Running;
|
|
443
507
|
|
|
444
|
-
options.onLog(
|
|
508
|
+
options.onLog(`${subject} ready`);
|
|
445
509
|
options.onReady?.();
|
|
446
510
|
}
|
|
447
511
|
|
|
@@ -450,17 +514,27 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
450
514
|
return;
|
|
451
515
|
}
|
|
452
516
|
|
|
517
|
+
if (runtime === BridgeRuntime.Starting && options.readyTimeoutSeconds !== undefined) {
|
|
518
|
+
options.onLog.error(`the ${subject} did not report ready in time, stopping it`, {
|
|
519
|
+
readyTimeoutSeconds: options.readyTimeoutSeconds,
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
failBoot(`the ${subject} did not report ready within ${options.readyTimeoutSeconds} seconds`);
|
|
523
|
+
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
453
527
|
if (runtime === BridgeRuntime.Starting) {
|
|
454
528
|
runtime = BridgeRuntime.Running;
|
|
455
529
|
|
|
456
|
-
options.onLog.warn(
|
|
530
|
+
options.onLog.warn(`${subject} ready line not seen, treating it as running`);
|
|
457
531
|
}
|
|
458
532
|
|
|
459
533
|
markStable();
|
|
460
534
|
});
|
|
461
535
|
};
|
|
462
536
|
|
|
463
|
-
const
|
|
537
|
+
const restartAfterCrash = async () => {
|
|
464
538
|
await Bun.sleep(RESTART_DELAY_MS);
|
|
465
539
|
|
|
466
540
|
await transition(async () => {
|
|
@@ -473,7 +547,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
473
547
|
} catch (error) {
|
|
474
548
|
runtime = BridgeRuntime.Crashed;
|
|
475
549
|
|
|
476
|
-
options.onLog.error(
|
|
550
|
+
options.onLog.error(`${subject} failed to restart`, {
|
|
477
551
|
error: errorText(error),
|
|
478
552
|
});
|
|
479
553
|
}
|
|
@@ -502,12 +576,22 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
502
576
|
|
|
503
577
|
reap(finished, code);
|
|
504
578
|
|
|
579
|
+
if (finished === replacing) {
|
|
580
|
+
outOfMemory = false;
|
|
581
|
+
|
|
582
|
+
options.onLog(`${subject} stopped for a restart`, {
|
|
583
|
+
code,
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
|
|
505
589
|
if (!shouldRun) {
|
|
506
590
|
outOfMemory = false;
|
|
507
591
|
|
|
508
592
|
runtime = BridgeRuntime.Stopped;
|
|
509
593
|
|
|
510
|
-
options.onLog(
|
|
594
|
+
options.onLog(`${subject} stopped`, {
|
|
511
595
|
code,
|
|
512
596
|
});
|
|
513
597
|
|
|
@@ -520,7 +604,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
520
604
|
outOfMemory = code === SIGKILL_EXIT_CODE && bootFailure === null;
|
|
521
605
|
|
|
522
606
|
if (outOfMemory) {
|
|
523
|
-
options.onLog.error(
|
|
607
|
+
options.onLog.error(`the ${subject} was killed for running out of memory`, {
|
|
524
608
|
code,
|
|
525
609
|
});
|
|
526
610
|
}
|
|
@@ -538,16 +622,18 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
538
622
|
|
|
539
623
|
await drainPumps(pumps);
|
|
540
624
|
|
|
625
|
+
const line = bootFailure ?? lastLine();
|
|
626
|
+
|
|
541
627
|
options.onExit?.({
|
|
542
628
|
...settled,
|
|
543
|
-
lastLine:
|
|
629
|
+
lastLine: line !== null && options.redact ? options.redact(line) : line,
|
|
544
630
|
});
|
|
545
631
|
};
|
|
546
632
|
|
|
547
633
|
if (phase === BridgeCrashPhase.Booting) {
|
|
548
634
|
runtime = BridgeRuntime.Crashed;
|
|
549
635
|
|
|
550
|
-
options.onLog.error(
|
|
636
|
+
options.onLog.error(`${subject} exited while booting, not restarting`, {
|
|
551
637
|
code,
|
|
552
638
|
phase,
|
|
553
639
|
});
|
|
@@ -564,7 +650,7 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
564
650
|
breaker = true;
|
|
565
651
|
runtime = BridgeRuntime.Crashed;
|
|
566
652
|
|
|
567
|
-
options.onLog.error(
|
|
653
|
+
options.onLog.error(`${subject} crash loop detected, not restarting`, {
|
|
568
654
|
code,
|
|
569
655
|
restarts,
|
|
570
656
|
});
|
|
@@ -576,19 +662,80 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
576
662
|
|
|
577
663
|
runtime = BridgeRuntime.Starting;
|
|
578
664
|
|
|
579
|
-
options.onLog.warn(
|
|
665
|
+
options.onLog.warn(`${subject} exited, restarting`, {
|
|
580
666
|
code,
|
|
581
667
|
restarts,
|
|
582
668
|
});
|
|
583
669
|
|
|
584
670
|
await report(true, false);
|
|
585
671
|
|
|
586
|
-
void
|
|
672
|
+
void restartAfterCrash();
|
|
587
673
|
}
|
|
588
674
|
|
|
675
|
+
const escalate = async (active: SpawnedGame, group: number | null) => {
|
|
676
|
+
if (!options.stopSignal) {
|
|
677
|
+
terminate(active, group, "SIGTERM");
|
|
678
|
+
|
|
679
|
+
const afterTerm = await Promise.race([
|
|
680
|
+
active.exited,
|
|
681
|
+
Bun.sleep(KILL_GRACE_MS).then(() => null),
|
|
682
|
+
]);
|
|
683
|
+
|
|
684
|
+
if (afterTerm !== null) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
terminate(active, group, "SIGKILL");
|
|
690
|
+
|
|
691
|
+
await active.exited;
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
const halt = async (active: SpawnedGame, group: number | null) => {
|
|
695
|
+
if (options.gracefulStop) {
|
|
696
|
+
const graceful = options.gracefulStop().catch((error: unknown) => {
|
|
697
|
+
options.onLog.warn("graceful stop failed", {
|
|
698
|
+
error: errorText(error),
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
await Promise.race([
|
|
703
|
+
graceful,
|
|
704
|
+
active.exited,
|
|
705
|
+
]);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const exited = active.exitCode !== null || active.signalCode !== null;
|
|
709
|
+
|
|
710
|
+
if (options.stopSignal && !exited) {
|
|
711
|
+
terminate(active, group, BRIDGE_STOP_SIGNALS[options.stopSignal]);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const settled = await Promise.race([
|
|
715
|
+
active.exited,
|
|
716
|
+
Bun.sleep(options.stopTimeoutSeconds * 1000).then(() => null),
|
|
717
|
+
]);
|
|
718
|
+
|
|
719
|
+
if (settled === null) {
|
|
720
|
+
await escalate(active, group);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (child !== active) {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const code = exitCodeOf(active.exitCode, active.signalCode);
|
|
728
|
+
|
|
729
|
+
reap(active, code);
|
|
730
|
+
|
|
731
|
+
outOfMemory = false;
|
|
732
|
+
|
|
733
|
+
return code;
|
|
734
|
+
};
|
|
735
|
+
|
|
589
736
|
const writeLine = async (input: string) => {
|
|
590
737
|
if (!child) {
|
|
591
|
-
throw new Error(
|
|
738
|
+
throw new Error(`the ${subject} is not running`);
|
|
592
739
|
}
|
|
593
740
|
|
|
594
741
|
child.stdin.write(`${input}\n`);
|
|
@@ -652,54 +799,59 @@ export const createSupervisor = (options: SupervisorOptions) => {
|
|
|
652
799
|
|
|
653
800
|
runtime = BridgeRuntime.Stopping;
|
|
654
801
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
802
|
+
const code = await halt(active, group);
|
|
803
|
+
|
|
804
|
+
if (code !== null) {
|
|
805
|
+
options.onLog(`${subject} stopped`, {
|
|
806
|
+
code,
|
|
660
807
|
});
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
runtime = BridgeRuntime.Stopped;
|
|
811
|
+
|
|
812
|
+
options.onStopped?.();
|
|
813
|
+
});
|
|
814
|
+
},
|
|
661
815
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
816
|
+
async restart() {
|
|
817
|
+
await transition(async () => {
|
|
818
|
+
if (!shouldRun) {
|
|
819
|
+
throw new Error(`the ${subject} is not meant to be running, so there is nothing to restart`);
|
|
666
820
|
}
|
|
667
821
|
|
|
668
|
-
const
|
|
669
|
-
active.exited,
|
|
670
|
-
Bun.sleep(options.stopTimeoutSeconds * 1000).then(() => null),
|
|
671
|
-
]);
|
|
822
|
+
const active = child;
|
|
672
823
|
|
|
673
|
-
|
|
674
|
-
terminate(active, group, "SIGTERM");
|
|
824
|
+
runtime = BridgeRuntime.Stopping;
|
|
675
825
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
Bun.sleep(KILL_GRACE_MS).then(() => null),
|
|
679
|
-
]);
|
|
826
|
+
if (active) {
|
|
827
|
+
replacing = active;
|
|
680
828
|
|
|
681
|
-
|
|
682
|
-
|
|
829
|
+
try {
|
|
830
|
+
const code = await halt(active, childGroup);
|
|
683
831
|
|
|
684
|
-
|
|
832
|
+
if (code !== null) {
|
|
833
|
+
options.onLog(`${subject} stopped for a restart`, {
|
|
834
|
+
code,
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
} finally {
|
|
838
|
+
replacing = null;
|
|
685
839
|
}
|
|
686
840
|
}
|
|
687
841
|
|
|
688
|
-
if (
|
|
689
|
-
|
|
842
|
+
if (!shouldRun) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
690
845
|
|
|
691
|
-
|
|
846
|
+
breaker = false;
|
|
692
847
|
|
|
693
|
-
|
|
848
|
+
try {
|
|
849
|
+
await launch();
|
|
850
|
+
} catch (error) {
|
|
851
|
+
runtime = BridgeRuntime.Crashed;
|
|
694
852
|
|
|
695
|
-
|
|
696
|
-
code,
|
|
697
|
-
});
|
|
853
|
+
throw error;
|
|
698
854
|
}
|
|
699
|
-
|
|
700
|
-
runtime = BridgeRuntime.Stopped;
|
|
701
|
-
|
|
702
|
-
options.onStopped?.();
|
|
703
855
|
});
|
|
704
856
|
},
|
|
705
857
|
|
package/src/runtime/serve.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { chmod, chown, mkdir, rm } from "node:fs/promises";
|
|
2
2
|
import { BridgeDesired, type BridgeInstallHold, BridgeRuntime, BridgeUserError, type LocalizedText } from "../core";
|
|
3
|
-
import type
|
|
3
|
+
import { type BridgeDriver, BridgeLifecycleSubject } from "../modules";
|
|
4
4
|
import {
|
|
5
5
|
BRIDGE_INSTALL_ERROR_LIMIT,
|
|
6
6
|
BRIDGE_VERSION,
|
|
@@ -16,8 +16,10 @@ import { createContext } from "./bridgeContext";
|
|
|
16
16
|
import { validateDriver } from "./bridgeDriver";
|
|
17
17
|
import { createEventRing } from "./bridgeEvents";
|
|
18
18
|
import { decideInstall, recountHold } from "./bridgeInstall";
|
|
19
|
-
import {
|
|
19
|
+
import { createBridgeLiveConfig } from "./bridgeLiveConfig";
|
|
20
|
+
import { errorText, log as stdoutLog } from "./bridgeLog";
|
|
20
21
|
import { pendingResult, rejectedPending, unsupportedPending } from "./bridgePending";
|
|
22
|
+
import { createBridgeRedaction, redactLocalized, redactLogger, secretFieldKeys } from "./bridgeRedaction";
|
|
21
23
|
import { createRegistry } from "./bridgeRegistry";
|
|
22
24
|
import { createServer } from "./bridgeServer";
|
|
23
25
|
import { setupSummaryOf } from "./bridgeSetup";
|
|
@@ -69,25 +71,42 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
69
71
|
|
|
70
72
|
const desired = await readDesired(statePath(config));
|
|
71
73
|
|
|
74
|
+
const live = createBridgeLiveConfig(configPath(), config);
|
|
75
|
+
|
|
76
|
+
const redact = createBridgeRedaction(secretFieldKeys(driver), live);
|
|
77
|
+
|
|
78
|
+
const log = redactLogger(stdoutLog, redact);
|
|
79
|
+
|
|
72
80
|
let installError: string | null = null;
|
|
73
81
|
|
|
74
82
|
let installErrorText: LocalizedText | null = null;
|
|
75
83
|
|
|
76
84
|
let installHold: BridgeInstallHold | null = null;
|
|
77
85
|
|
|
78
|
-
const setup = createSetupHandlers(driver, log);
|
|
86
|
+
const setup = createSetupHandlers(driver, log, redact);
|
|
79
87
|
|
|
80
88
|
const supervisor = createSupervisor({
|
|
81
89
|
cwd: config.cwd,
|
|
82
90
|
uid: config.gameUid,
|
|
83
91
|
gid: config.gameGid,
|
|
84
92
|
stopTimeoutSeconds: driver.lifecycle.stopTimeoutSeconds ?? DEFAULT_STOP_TIMEOUT_SECONDS,
|
|
93
|
+
readyTimeoutSeconds: driver.lifecycle.readyTimeoutSeconds,
|
|
94
|
+
stopSignal: driver.lifecycle.stopSignal,
|
|
95
|
+
subject: driver.lifecycle.subject,
|
|
85
96
|
ready: driver.lifecycle.ready,
|
|
86
97
|
failed: driver.lifecycle.failed,
|
|
98
|
+
redact,
|
|
87
99
|
onLog: log,
|
|
88
100
|
async resolveCommand() {
|
|
101
|
+
live.reload();
|
|
102
|
+
|
|
89
103
|
return await driver.lifecycle.command(context);
|
|
90
104
|
},
|
|
105
|
+
resolveEnvironment: driver.lifecycle.environment
|
|
106
|
+
? async () => {
|
|
107
|
+
return (await driver.lifecycle.environment?.(context)) ?? {};
|
|
108
|
+
}
|
|
109
|
+
: undefined,
|
|
91
110
|
async gracefulStop() {
|
|
92
111
|
await driver.lifecycle.stop?.(context);
|
|
93
112
|
},
|
|
@@ -111,15 +130,29 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
111
130
|
|
|
112
131
|
const ring = createEventRing(driver.events, supervisor);
|
|
113
132
|
|
|
114
|
-
const context = createContext(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
133
|
+
const context = createContext(
|
|
134
|
+
config,
|
|
135
|
+
supervisor,
|
|
136
|
+
{
|
|
137
|
+
onLog: log,
|
|
138
|
+
onEmit(event, payload) {
|
|
139
|
+
ring.emit(event, payload);
|
|
140
|
+
},
|
|
141
|
+
onSetup(runtime) {
|
|
142
|
+
setup.onSetup(runtime);
|
|
143
|
+
},
|
|
144
|
+
async onRestart() {
|
|
145
|
+
if (current !== BridgeDesired.Running) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`the ${driver.lifecycle.subject ?? BridgeLifecycleSubject.Game} is not meant to be running, so there is nothing to restart`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await supervisor.restart();
|
|
152
|
+
},
|
|
121
153
|
},
|
|
122
|
-
|
|
154
|
+
live,
|
|
155
|
+
);
|
|
123
156
|
|
|
124
157
|
const registry = createRegistry(driver, context, supervisor, ring);
|
|
125
158
|
const backupLease = createBackupLease(async () => {
|
|
@@ -220,6 +253,7 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
220
253
|
const server = createServer({
|
|
221
254
|
socket: socketPath(config),
|
|
222
255
|
token: config.token,
|
|
256
|
+
redact,
|
|
223
257
|
onLog: log,
|
|
224
258
|
onStatus: status,
|
|
225
259
|
onManifest() {
|
|
@@ -428,8 +462,8 @@ export const serve = async (driver: BridgeDriver) => {
|
|
|
428
462
|
|
|
429
463
|
log("install complete");
|
|
430
464
|
} catch (error) {
|
|
431
|
-
installError = errorText(error).slice(0, BRIDGE_INSTALL_ERROR_LIMIT);
|
|
432
|
-
installErrorText = error instanceof BridgeUserError ? error.text : null;
|
|
465
|
+
installError = redact(errorText(error)).slice(0, BRIDGE_INSTALL_ERROR_LIMIT);
|
|
466
|
+
installErrorText = error instanceof BridgeUserError ? redactLocalized(error.text, redact) : null;
|
|
433
467
|
|
|
434
468
|
supervisor.setRuntime(BridgeRuntime.Crashed);
|
|
435
469
|
|