@serverkgg/bridge 0.0.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.
Files changed (73) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +52 -0
  3. package/package.json +26 -0
  4. package/src/cli/compile.ts +75 -0
  5. package/src/cli/directorySource.ts +48 -0
  6. package/src/cli/main.ts +44 -0
  7. package/src/cli/validate.ts +94 -0
  8. package/src/codecs/index.ts +3 -0
  9. package/src/codecs/json.ts +42 -0
  10. package/src/codecs/properties.ts +75 -0
  11. package/src/codecs/ueIni.ts +235 -0
  12. package/src/context/bridgeContext.ts +120 -0
  13. package/src/context/index.ts +1 -0
  14. package/src/core/bridgeAction.ts +13 -0
  15. package/src/core/bridgeId.ts +3 -0
  16. package/src/core/bridgeKind.ts +11 -0
  17. package/src/core/bridgePort.ts +4 -0
  18. package/src/core/bridgeRuntime.ts +13 -0
  19. package/src/core/bridgeSecretError.ts +10 -0
  20. package/src/core/bridgeStream.ts +4 -0
  21. package/src/core/bridgeValue.ts +19 -0
  22. package/src/core/index.ts +8 -0
  23. package/src/main.ts +5 -0
  24. package/src/manifest/assets.ts +102 -0
  25. package/src/manifest/category.ts +10 -0
  26. package/src/manifest/checks.ts +50 -0
  27. package/src/manifest/dimensions.ts +106 -0
  28. package/src/manifest/guide.ts +36 -0
  29. package/src/manifest/image.ts +1 -0
  30. package/src/manifest/index.ts +9 -0
  31. package/src/manifest/localized.ts +8 -0
  32. package/src/manifest/manifest.ts +170 -0
  33. package/src/manifest/source.ts +89 -0
  34. package/src/modules/bridge.ts +53 -0
  35. package/src/modules/bridgeDriver.ts +19 -0
  36. package/src/modules/bridgeModule.ts +114 -0
  37. package/src/modules/index.ts +3 -0
  38. package/src/panel/bridgeConfirm.ts +5 -0
  39. package/src/panel/bridgeControl.ts +7 -0
  40. package/src/panel/bridgeFormTarget.ts +4 -0
  41. package/src/panel/bridgeIcon.ts +17 -0
  42. package/src/panel/bridgeLayout.ts +6 -0
  43. package/src/panel/bridgePanel.ts +107 -0
  44. package/src/panel/index.ts +6 -0
  45. package/src/probes/a2s.ts +170 -0
  46. package/src/probes/index.ts +1 -0
  47. package/src/protocol/bridgeConfig.ts +13 -0
  48. package/src/protocol/bridgeFrame.ts +91 -0
  49. package/src/protocol/bridgeLine.ts +40 -0
  50. package/src/protocol/bridgeManifest.ts +19 -0
  51. package/src/protocol/bridgeVersion.ts +11 -0
  52. package/src/protocol/index.ts +7 -0
  53. package/src/runtime/bridgeCodec.ts +78 -0
  54. package/src/runtime/bridgeConfigReader.ts +119 -0
  55. package/src/runtime/bridgeContext.ts +96 -0
  56. package/src/runtime/bridgeDriver.ts +179 -0
  57. package/src/runtime/bridgeEvents.ts +71 -0
  58. package/src/runtime/bridgeExec.ts +143 -0
  59. package/src/runtime/bridgeFiles.ts +412 -0
  60. package/src/runtime/bridgeLog.ts +15 -0
  61. package/src/runtime/bridgeNet.ts +59 -0
  62. package/src/runtime/bridgePath.ts +132 -0
  63. package/src/runtime/bridgePrivilege.ts +99 -0
  64. package/src/runtime/bridgeProbe.ts +14 -0
  65. package/src/runtime/bridgeRegistry.ts +311 -0
  66. package/src/runtime/bridgeServer.ts +233 -0
  67. package/src/runtime/bridgeState.ts +40 -0
  68. package/src/runtime/bridgeSupervisor.ts +498 -0
  69. package/src/runtime/bridgeZip.ts +122 -0
  70. package/src/runtime/index.ts +18 -0
  71. package/src/runtime/serve.ts +294 -0
  72. package/src/utils/glob.ts +62 -0
  73. package/src/utils/index.ts +1 -0
@@ -0,0 +1,294 @@
1
+ import { chmod, chown, mkdir, rm } from "node:fs/promises";
2
+ import { BridgeDesired, BridgeRuntime } from "../core";
3
+ import type { BridgeDriver } from "../modules";
4
+ import {
5
+ BRIDGE_INSTALL_ERROR_LIMIT,
6
+ BRIDGE_VERSION,
7
+ type BridgeConfig,
8
+ type BridgeSampleResult,
9
+ type BridgeStatus,
10
+ } from "../protocol";
11
+ import { configPath, readConfig, socketPath, statePath } from "./bridgeConfigReader";
12
+ import { createContext } from "./bridgeContext";
13
+ import { validateDriver } from "./bridgeDriver";
14
+ import { createEventRing } from "./bridgeEvents";
15
+ import { errorText, log } from "./bridgeLog";
16
+ import { createRegistry } from "./bridgeRegistry";
17
+ import { createServer } from "./bridgeServer";
18
+ import { readDesired, writeDesired } from "./bridgeState";
19
+ import { createSupervisor } from "./bridgeSupervisor";
20
+
21
+ const VOLUME_MODE = 0o2775;
22
+
23
+ const RUN_MODE = 0o700;
24
+
25
+ const SOCKET_MODE = 0o600;
26
+
27
+ const DEFAULT_STOP_TIMEOUT_SECONDS = 30;
28
+
29
+ const DEFAULT_SAMPLE_REFRESH_SECONDS = 30;
30
+
31
+ const prepare = async (config: BridgeConfig) => {
32
+ await mkdir(config.cwd, {
33
+ recursive: true,
34
+ });
35
+
36
+ await mkdir(config.run, {
37
+ recursive: true,
38
+ });
39
+
40
+ await chmod(config.run, RUN_MODE);
41
+
42
+ await rm(socketPath(config), {
43
+ force: true,
44
+ });
45
+
46
+ if (typeof process.getuid === "function" && process.getuid() !== 0) {
47
+ return;
48
+ }
49
+
50
+ await chown(config.cwd, config.gameUid, config.gameGid);
51
+ await chmod(config.cwd, VOLUME_MODE);
52
+ };
53
+
54
+ export const serve = async (driver: BridgeDriver) => {
55
+ process.umask(0o002);
56
+
57
+ validateDriver(driver);
58
+
59
+ const config = await readConfig(configPath());
60
+
61
+ await prepare(config);
62
+
63
+ const desired = await readDesired(statePath(config));
64
+
65
+ let installError: string | null = null;
66
+
67
+ const supervisor = createSupervisor({
68
+ cwd: config.cwd,
69
+ uid: config.gameUid,
70
+ gid: config.gameGid,
71
+ stopTimeoutSeconds: driver.lifecycle.stopTimeoutSeconds ?? DEFAULT_STOP_TIMEOUT_SECONDS,
72
+ ready: driver.lifecycle.ready,
73
+ onLog: log,
74
+ async resolveCommand() {
75
+ return await driver.lifecycle.command(context);
76
+ },
77
+ async gracefulStop() {
78
+ await driver.lifecycle.stop?.(context);
79
+ },
80
+ onReady() {
81
+ void Promise.resolve(driver.lifecycle.onReady?.(context)).catch((error: unknown) => {
82
+ log("lifecycle ready hook failed", {
83
+ error: errorText(error),
84
+ });
85
+ });
86
+ },
87
+ });
88
+
89
+ const ring = createEventRing(driver.events, supervisor);
90
+
91
+ const context = createContext(config, supervisor, {
92
+ onLog: log,
93
+ onEmit(event, payload) {
94
+ ring.emit(event, payload);
95
+ },
96
+ });
97
+
98
+ const registry = createRegistry(driver, context, supervisor, ring);
99
+
100
+ let current = desired;
101
+
102
+ const status = (): BridgeStatus => {
103
+ return {
104
+ version: BRIDGE_VERSION,
105
+ runtime: supervisor.runtime,
106
+ desired: current,
107
+ exitCode: supervisor.exitCode,
108
+ restarts: supervisor.restarts,
109
+ installError,
110
+ };
111
+ };
112
+
113
+ supervisor.setRuntime(BridgeRuntime.Installing);
114
+
115
+ let sampled: {
116
+ value: BridgeSampleResult;
117
+ expiresAt: number;
118
+ } | null = null;
119
+
120
+ const sample = async (): Promise<BridgeSampleResult> => {
121
+ const query = driver.query;
122
+
123
+ if (!query) {
124
+ throw new Error("this game declares no query");
125
+ }
126
+
127
+ if (!supervisor.running) {
128
+ return {
129
+ online: null,
130
+ max: null,
131
+ };
132
+ }
133
+
134
+ const stamp = performance.now();
135
+
136
+ if (sampled && sampled.expiresAt > stamp) {
137
+ return sampled.value;
138
+ }
139
+
140
+ const result = await query.sample(context);
141
+
142
+ const value: BridgeSampleResult = {
143
+ online: result.online,
144
+ max: result.max,
145
+ };
146
+
147
+ sampled = {
148
+ value,
149
+ expiresAt: stamp + (query.refreshSeconds ?? DEFAULT_SAMPLE_REFRESH_SECONDS) * 1000,
150
+ };
151
+
152
+ return value;
153
+ };
154
+
155
+ const server = createServer({
156
+ socket: socketPath(config),
157
+ token: config.token,
158
+ onLog: log,
159
+ onStatus: status,
160
+ onManifest() {
161
+ return registry.manifest();
162
+ },
163
+ async onStart() {
164
+ current = BridgeDesired.Running;
165
+
166
+ await writeDesired(statePath(config), current);
167
+ await supervisor.start();
168
+ },
169
+ async onStop() {
170
+ current = BridgeDesired.Stopped;
171
+
172
+ await writeDesired(statePath(config), current);
173
+ await supervisor.stop();
174
+ },
175
+ async onCommand(input, expect, timeoutMs) {
176
+ return await supervisor.command(input, {
177
+ expect: expect === undefined ? undefined : new RegExp(expect),
178
+ timeoutMs,
179
+ });
180
+ },
181
+ async onInvoke(module, action, args) {
182
+ return await registry.invoke(module, action, args);
183
+ },
184
+ onEvents(cursor) {
185
+ return ring.drain(cursor);
186
+ },
187
+ async onSample() {
188
+ return await sample();
189
+ },
190
+ async onQuiesce() {
191
+ const backup = driver.backup;
192
+
193
+ if (!backup?.quiesce) {
194
+ return {
195
+ supported: false,
196
+ quiesced: false,
197
+ settleSeconds: 0,
198
+ };
199
+ }
200
+
201
+ if (!supervisor.running) {
202
+ return {
203
+ supported: true,
204
+ quiesced: false,
205
+ settleSeconds: 0,
206
+ };
207
+ }
208
+
209
+ await backup.quiesce(context);
210
+
211
+ return {
212
+ supported: true,
213
+ quiesced: true,
214
+ settleSeconds: backup.settleSeconds ?? 0,
215
+ };
216
+ },
217
+ async onRelease() {
218
+ if (supervisor.running) {
219
+ await driver.backup?.release?.(context);
220
+ }
221
+ },
222
+ });
223
+
224
+ await chmod(socketPath(config), SOCKET_MODE);
225
+
226
+ log("bridge ready", {
227
+ code: config.code,
228
+ desired: current,
229
+ });
230
+
231
+ let stopping = false;
232
+
233
+ const shutdown = async (signal: string) => {
234
+ if (stopping) {
235
+ return;
236
+ }
237
+
238
+ stopping = true;
239
+
240
+ log("shutting down", {
241
+ signal,
242
+ });
243
+
244
+ await supervisor.stop();
245
+
246
+ server.stop(true);
247
+
248
+ await rm(socketPath(config), {
249
+ force: true,
250
+ });
251
+
252
+ process.exit(0);
253
+ };
254
+
255
+ process.on("SIGTERM", () => {
256
+ void shutdown("SIGTERM");
257
+ });
258
+
259
+ process.on("SIGINT", () => {
260
+ void shutdown("SIGINT");
261
+ });
262
+
263
+ log("installing");
264
+
265
+ try {
266
+ await driver.install.run(context);
267
+
268
+ log("install complete");
269
+ } catch (error) {
270
+ installError = errorText(error).slice(0, BRIDGE_INSTALL_ERROR_LIMIT);
271
+
272
+ supervisor.setRuntime(BridgeRuntime.Crashed);
273
+
274
+ log("install failed", {
275
+ error: installError,
276
+ });
277
+
278
+ return;
279
+ }
280
+
281
+ supervisor.setRuntime(current === BridgeDesired.Running ? BridgeRuntime.Starting : BridgeRuntime.Stopped);
282
+
283
+ if (current === BridgeDesired.Running) {
284
+ try {
285
+ await supervisor.start();
286
+ } catch (error) {
287
+ supervisor.setRuntime(BridgeRuntime.Crashed);
288
+
289
+ log("start failed", {
290
+ error: errorText(error),
291
+ });
292
+ }
293
+ }
294
+ };
@@ -0,0 +1,62 @@
1
+ const segmentToRegex = (segment: string) => {
2
+ let out = "";
3
+
4
+ for (const char of segment) {
5
+ if (char === "*") {
6
+ out += "[^/]*";
7
+ } else if (char === "?") {
8
+ out += "[^/]";
9
+ } else {
10
+ out += char.replace(/[.+^${}()|[\]\\]/g, "\\$&");
11
+ }
12
+ }
13
+
14
+ return out;
15
+ };
16
+
17
+ export const compileGlob = (glob: string) => {
18
+ const segments = glob.split("/");
19
+
20
+ let pattern = "";
21
+
22
+ for (let index = 0; index < segments.length; index += 1) {
23
+ const segment = segments[index];
24
+
25
+ if (segment === undefined) {
26
+ continue;
27
+ }
28
+
29
+ const first = index === 0;
30
+ const last = index === segments.length - 1;
31
+
32
+ if (segment === "**") {
33
+ if (first && last) {
34
+ pattern += ".*";
35
+ } else if (first) {
36
+ pattern += "(?:.*/)?";
37
+ } else if (last) {
38
+ pattern += "(?:/.*)?";
39
+ } else {
40
+ pattern += "/(?:.*/)?";
41
+ }
42
+
43
+ continue;
44
+ }
45
+
46
+ if (!first && segments[index - 1] !== "**") {
47
+ pattern += "/";
48
+ }
49
+
50
+ pattern += segmentToRegex(segment);
51
+ }
52
+
53
+ return new RegExp(`^${pattern}$`);
54
+ };
55
+
56
+ export const compileGlobs = (globs: string[]) => {
57
+ return globs.map(compileGlob);
58
+ };
59
+
60
+ export const matchesAny = (path: string, patterns: RegExp[]) => {
61
+ return patterns.some((pattern) => pattern.test(path));
62
+ };
@@ -0,0 +1 @@
1
+ export * from "./glob";