@serverkgg/bridge 0.0.12 → 0.0.13
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 +9 -1
- package/src/catalogs/curseforge.test.ts +580 -0
- package/src/catalogs/curseforge.ts +461 -0
- package/src/catalogs/index.ts +2 -0
- package/src/catalogs/thunderstore.test.ts +94 -0
- package/src/catalogs/thunderstore.ts +239 -0
- package/src/codecs/index.ts +3 -0
- package/src/codecs/ini.test.ts +152 -0
- package/src/codecs/ini.ts +158 -0
- package/src/codecs/sourceCfg.test.ts +100 -0
- package/src/codecs/sourceCfg.ts +111 -0
- package/src/codecs/xmlProperties.test.ts +104 -0
- package/src/codecs/xmlProperties.ts +133 -0
- package/src/context/bridgeContext.ts +20 -0
- package/src/core/bridgeRconError.ts +19 -0
- package/src/core/index.ts +1 -0
- package/src/install/index.ts +1 -0
- package/src/install/installStamp.test.ts +125 -0
- package/src/install/installStamp.ts +40 -0
- package/src/main.ts +1 -0
- package/src/manifest/index.ts +1 -0
- package/src/manifest/manifest.test.ts +427 -2
- package/src/manifest/manifest.ts +141 -13
- package/src/manifest/primitives.ts +6 -0
- package/src/manifest/runtime.ts +89 -0
- package/src/net/index.ts +2 -0
- package/src/net/loopback.test.ts +152 -0
- package/src/net/loopback.ts +109 -0
- package/src/net/loopbackMethod.ts +7 -0
- package/src/presence/index.ts +1 -0
- package/src/presence/rosterSync.test.ts +179 -0
- package/src/presence/rosterSync.ts +57 -0
- package/src/protocol/bridgeEvents.ts +4 -0
- package/src/rcon/index.ts +3 -0
- package/src/rcon/sourceRcon.test.ts +308 -0
- package/src/rcon/sourceRcon.ts +276 -0
- package/src/rcon/sourceRconFrame.test.ts +152 -0
- package/src/rcon/sourceRconFrame.ts +99 -0
- package/src/runtime/bridgeCodec.ts +45 -2
- package/src/runtime/bridgeContext.ts +13 -6
- package/src/runtime/bridgeDriver.test.ts +1 -0
- package/src/runtime/bridgeLiveConfig.test.ts +149 -0
- package/src/runtime/bridgeLiveConfig.ts +63 -0
- package/src/runtime/bridgeRcon.ts +15 -0
- package/src/runtime/bridgeRegistry.ts +1 -0
- package/src/steam/index.ts +7 -0
- package/src/steam/steamApp.test.ts +78 -0
- package/src/steam/steamApp.ts +35 -0
- package/src/steam/steamcmd.test.ts +157 -0
- package/src/steam/steamcmd.ts +273 -0
- package/src/steam/steamcmdLogin.ts +34 -0
- package/src/steam/steamcmdNarrator.test.ts +90 -0
- package/src/steam/steamcmdNarrator.ts +43 -0
- package/src/steam/steamcmdPlatform.ts +4 -0
- package/src/steam/webApi.ts +70 -0
- package/src/steam/workshop.test.ts +45 -0
- package/src/steam/workshop.ts +148 -0
- package/src/utils/execDetail.ts +16 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/token.test.ts +50 -0
- package/src/utils/token.ts +35 -0
- package/src/wine/index.ts +3 -0
- package/src/wine/protonCommand.test.ts +67 -0
- package/src/wine/protonCommand.ts +49 -0
- package/src/wine/wineCommand.test.ts +92 -0
- package/src/wine/wineCommand.ts +54 -0
- package/src/wine/winePrefix.test.ts +111 -0
- package/src/wine/winePrefix.ts +88 -0
- package/src/runtime/bridgeSecrets.test.ts +0 -121
- package/src/runtime/bridgeSecrets.ts +0 -43
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { BridgeContext } from "../context";
|
|
2
|
+
import { readStamp, writeStamp } from "../install";
|
|
3
|
+
import { execDetail } from "../utils";
|
|
4
|
+
import { protonCommand } from "./protonCommand";
|
|
5
|
+
import { wineCommand } from "./wineCommand";
|
|
6
|
+
|
|
7
|
+
export const WINE_PREFIX_STAMP = ".serverk-wine.json";
|
|
8
|
+
|
|
9
|
+
const PREFIX_TIMEOUT_MS = 600_000;
|
|
10
|
+
|
|
11
|
+
const BOOT_EXE = "wineboot";
|
|
12
|
+
|
|
13
|
+
const BOOT_ARGS = [
|
|
14
|
+
"-i",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export interface WinePrefixStamp {
|
|
18
|
+
ready: boolean;
|
|
19
|
+
proton: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface PrepareWinePrefixOptions {
|
|
23
|
+
proton?: boolean;
|
|
24
|
+
appId?: string;
|
|
25
|
+
protonDir?: string;
|
|
26
|
+
compatDir?: string;
|
|
27
|
+
steamDir?: string;
|
|
28
|
+
prefix?: string;
|
|
29
|
+
home?: string;
|
|
30
|
+
xvfb?: boolean;
|
|
31
|
+
environment?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const prefixArgv = async (context: BridgeContext, options: PrepareWinePrefixOptions) => {
|
|
35
|
+
if (options.proton !== true) {
|
|
36
|
+
return await wineCommand(context, {
|
|
37
|
+
exe: BOOT_EXE,
|
|
38
|
+
args: BOOT_ARGS,
|
|
39
|
+
xvfb: options.xvfb,
|
|
40
|
+
prefix: options.prefix,
|
|
41
|
+
home: options.home,
|
|
42
|
+
environment: options.environment,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!options.appId) {
|
|
47
|
+
throw new Error("a proton prefix needs the steam app id it belongs to");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return await protonCommand(context, {
|
|
51
|
+
appId: options.appId,
|
|
52
|
+
exe: BOOT_EXE,
|
|
53
|
+
args: BOOT_ARGS,
|
|
54
|
+
protonDir: options.protonDir,
|
|
55
|
+
compatDir: options.compatDir,
|
|
56
|
+
steamDir: options.steamDir,
|
|
57
|
+
home: options.home,
|
|
58
|
+
environment: options.environment,
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const prepareWinePrefix = async (context: BridgeContext, options: PrepareWinePrefixOptions = {}) => {
|
|
63
|
+
const proton = options.proton === true;
|
|
64
|
+
const stamp = await readStamp<WinePrefixStamp>(context, WINE_PREFIX_STAMP);
|
|
65
|
+
|
|
66
|
+
if (stamp?.ready === true && stamp.proton === proton) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
context.log(proton ? "creating the proton prefix" : "creating the wine prefix");
|
|
71
|
+
|
|
72
|
+
const result = await context.exec(await prefixArgv(context, options), {
|
|
73
|
+
timeoutMs: PREFIX_TIMEOUT_MS,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (result.code !== 0) {
|
|
77
|
+
throw new Error(`the wine prefix could not be created — ${execDetail(result)}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await writeStamp<WinePrefixStamp>(
|
|
81
|
+
context,
|
|
82
|
+
{
|
|
83
|
+
ready: true,
|
|
84
|
+
proton,
|
|
85
|
+
},
|
|
86
|
+
WINE_PREFIX_STAMP,
|
|
87
|
+
);
|
|
88
|
+
};
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import type { BridgeConfig } from "../protocol";
|
|
6
|
-
import { createBridgeSecrets } from "./bridgeSecrets";
|
|
7
|
-
|
|
8
|
-
const configWith = (secrets: Record<string, string>): BridgeConfig => ({
|
|
9
|
-
code: "wsvv8fakxm",
|
|
10
|
-
token: "token",
|
|
11
|
-
cwd: "/data",
|
|
12
|
-
run: "/run/bridge",
|
|
13
|
-
ports: {
|
|
14
|
-
game: 25_565,
|
|
15
|
-
},
|
|
16
|
-
memoryMb: 2048,
|
|
17
|
-
cpuPercent: 100,
|
|
18
|
-
gameUid: 1000,
|
|
19
|
-
gameGid: 1000,
|
|
20
|
-
variables: {
|
|
21
|
-
VERSION: "1.21",
|
|
22
|
-
},
|
|
23
|
-
secrets,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
let directory: string;
|
|
27
|
-
|
|
28
|
-
let path: string;
|
|
29
|
-
|
|
30
|
-
const write = async (config: BridgeConfig) => {
|
|
31
|
-
await writeFile(path, `${JSON.stringify(config, null, 2)}\n`);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
beforeEach(async () => {
|
|
35
|
-
directory = await mkdtemp(join(tmpdir(), "bridge-secrets-"));
|
|
36
|
-
path = join(directory, "config.json");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
afterEach(async () => {
|
|
40
|
-
await rm(directory, {
|
|
41
|
-
force: true,
|
|
42
|
-
recursive: true,
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
describe("the bridge secrets reader", () => {
|
|
47
|
-
test("answers from the boot snapshot", async () => {
|
|
48
|
-
const config = configWith({
|
|
49
|
-
CURSEFORGE_API_KEY: "boot",
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
await write(config);
|
|
53
|
-
|
|
54
|
-
const secrets = createBridgeSecrets(path, config, 0);
|
|
55
|
-
|
|
56
|
-
expect(secrets.read("CURSEFORGE_API_KEY")).toBe("boot");
|
|
57
|
-
expect(secrets.read("MODRINTH_API_KEY")).toBeNull();
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("sees a value the agent rewrote into the config", async () => {
|
|
61
|
-
const config = configWith({
|
|
62
|
-
CURSEFORGE_API_KEY: "boot",
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
await write(config);
|
|
66
|
-
|
|
67
|
-
const secrets = createBridgeSecrets(path, config, 0);
|
|
68
|
-
|
|
69
|
-
expect(secrets.read("CURSEFORGE_API_KEY")).toBe("boot");
|
|
70
|
-
|
|
71
|
-
await write(
|
|
72
|
-
configWith({
|
|
73
|
-
CURSEFORGE_API_KEY: "rotated",
|
|
74
|
-
MODRINTH_API_KEY: "added",
|
|
75
|
-
}),
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
expect(secrets.read("CURSEFORGE_API_KEY")).toBe("rotated");
|
|
79
|
-
expect(secrets.read("MODRINTH_API_KEY")).toBe("added");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("keeps the last good values while the config is mid-write", async () => {
|
|
83
|
-
const config = configWith({
|
|
84
|
-
CURSEFORGE_API_KEY: "boot",
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
await write(config);
|
|
88
|
-
|
|
89
|
-
const secrets = createBridgeSecrets(path, config, 0);
|
|
90
|
-
|
|
91
|
-
expect(secrets.read("CURSEFORGE_API_KEY")).toBe("boot");
|
|
92
|
-
|
|
93
|
-
await writeFile(path, '{"code": "wsvv8fak');
|
|
94
|
-
|
|
95
|
-
expect(secrets.read("CURSEFORGE_API_KEY")).toBe("boot");
|
|
96
|
-
|
|
97
|
-
await rm(path);
|
|
98
|
-
|
|
99
|
-
expect(secrets.read("CURSEFORGE_API_KEY")).toBe("boot");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("re-reads the file at most once per refresh window", async () => {
|
|
103
|
-
const config = configWith({
|
|
104
|
-
CURSEFORGE_API_KEY: "boot",
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
await write(config);
|
|
108
|
-
|
|
109
|
-
const secrets = createBridgeSecrets(path, config, 60_000);
|
|
110
|
-
|
|
111
|
-
expect(secrets.read("CURSEFORGE_API_KEY")).toBe("boot");
|
|
112
|
-
|
|
113
|
-
await write(
|
|
114
|
-
configWith({
|
|
115
|
-
CURSEFORGE_API_KEY: "rotated",
|
|
116
|
-
}),
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
expect(secrets.read("CURSEFORGE_API_KEY")).toBe("boot");
|
|
120
|
-
});
|
|
121
|
-
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import type { BridgeConfig } from "../protocol";
|
|
3
|
-
import { parseConfig } from "./bridgeConfigReader";
|
|
4
|
-
|
|
5
|
-
export const BRIDGE_SECRET_REFRESH_MS = 2000;
|
|
6
|
-
|
|
7
|
-
export interface BridgeSecrets {
|
|
8
|
-
read: (key: string) => string | null;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const createBridgeSecrets = (
|
|
12
|
-
path: string,
|
|
13
|
-
config: BridgeConfig,
|
|
14
|
-
refreshMs: number = BRIDGE_SECRET_REFRESH_MS,
|
|
15
|
-
): BridgeSecrets => {
|
|
16
|
-
let values = config.secrets;
|
|
17
|
-
|
|
18
|
-
let checkedAt: number | null = null;
|
|
19
|
-
|
|
20
|
-
const refresh = () => {
|
|
21
|
-
const stamp = performance.now();
|
|
22
|
-
|
|
23
|
-
if (checkedAt !== null && stamp - checkedAt < refreshMs) {
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
checkedAt = stamp;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
values = parseConfig(readFileSync(path, "utf8")).secrets;
|
|
31
|
-
} catch {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
read(key) {
|
|
38
|
-
refresh();
|
|
39
|
-
|
|
40
|
-
return values[key] ?? null;
|
|
41
|
-
},
|
|
42
|
-
};
|
|
43
|
-
};
|