@minecraft-skills/rcon 0.1.3
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/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/index.d.mts +96 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +491 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sya-ri
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @minecraft-skills/rcon
|
|
2
|
+
|
|
3
|
+
RCON configuration, permission, and execution utilities for minecraft-skills.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @minecraft-skills/rcon
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Node.js 22.12 or newer is required.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import {
|
|
17
|
+
createRconConfig,
|
|
18
|
+
evaluateRconPermission,
|
|
19
|
+
getRconConfigStatus,
|
|
20
|
+
runRconCommand,
|
|
21
|
+
} from "@minecraft-skills/rcon";
|
|
22
|
+
|
|
23
|
+
createRconConfig({
|
|
24
|
+
configPath: "./.minecraft-skills/rcon.json",
|
|
25
|
+
preset: "readonly",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const status = getRconConfigStatus({
|
|
29
|
+
configPath: "./.minecraft-skills/rcon.json",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const decision = evaluateRconPermission("list");
|
|
33
|
+
|
|
34
|
+
const result = await runRconCommand({
|
|
35
|
+
command: "list",
|
|
36
|
+
configPath: "./.minecraft-skills/rcon.json",
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`runRconCommand` sends the command only when the selected profile is configured and the regex
|
|
41
|
+
permissions allow it. Rejected commands return a permission decision without contacting the server.
|
|
42
|
+
|
|
43
|
+
## Configuration
|
|
44
|
+
|
|
45
|
+
Secrets should stay in environment variables. Config values can use `$env:NAME`:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"$schema": "https://raw.githubusercontent.com/sya-ri/minecraft-skills/main/schema/rcon-config.schema.json",
|
|
50
|
+
"defaultProfile": "local",
|
|
51
|
+
"profiles": {
|
|
52
|
+
"local": {
|
|
53
|
+
"host": "127.0.0.1",
|
|
54
|
+
"port": 25575,
|
|
55
|
+
"password": "$env:MINECRAFT_SKILLS_RCON_PASSWORD",
|
|
56
|
+
"permissions": {
|
|
57
|
+
"preset": "readonly"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
See the repository `docs/RCON.md` for config search order, permission presets, CLI usage, and MCP
|
|
65
|
+
behavior.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
type RconPermissionPreset = "readonly" | "guarded" | "full";
|
|
3
|
+
type RconPermissionMode = "allow" | "deny";
|
|
4
|
+
type RconPermissions = {
|
|
5
|
+
preset?: RconPermissionPreset;
|
|
6
|
+
defaultMode?: RconPermissionMode;
|
|
7
|
+
allow?: string[];
|
|
8
|
+
deny?: string[];
|
|
9
|
+
};
|
|
10
|
+
type RconProfileConfig = {
|
|
11
|
+
host: string;
|
|
12
|
+
port: number | string;
|
|
13
|
+
password: string;
|
|
14
|
+
timeoutMs?: number | string;
|
|
15
|
+
permissions?: RconPermissions;
|
|
16
|
+
};
|
|
17
|
+
type RconConfig = {
|
|
18
|
+
$schema?: string;
|
|
19
|
+
defaultProfile?: string;
|
|
20
|
+
profiles: Record<string, RconProfileConfig>;
|
|
21
|
+
};
|
|
22
|
+
type RconConfigStatusOptions = {
|
|
23
|
+
configPath?: string;
|
|
24
|
+
profile?: string;
|
|
25
|
+
};
|
|
26
|
+
type RconConfigStatus = {
|
|
27
|
+
configured: boolean;
|
|
28
|
+
configPath: string | null;
|
|
29
|
+
source: "explicit" | "env-config" | "local" | "user" | "env-profile" | "missing";
|
|
30
|
+
profile: string | null;
|
|
31
|
+
missing: string[];
|
|
32
|
+
warnings: string[];
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
35
|
+
type RconCreateConfigOptions = {
|
|
36
|
+
configPath?: string;
|
|
37
|
+
profile?: string;
|
|
38
|
+
host?: string;
|
|
39
|
+
port?: number | string;
|
|
40
|
+
passwordEnv?: string;
|
|
41
|
+
preset?: RconPermissionPreset;
|
|
42
|
+
force?: boolean;
|
|
43
|
+
};
|
|
44
|
+
type RconCreateConfigResult = {
|
|
45
|
+
path: string;
|
|
46
|
+
existed: boolean;
|
|
47
|
+
written: boolean;
|
|
48
|
+
warning: string | null;
|
|
49
|
+
config: RconConfig;
|
|
50
|
+
};
|
|
51
|
+
type RconCommandOptions = {
|
|
52
|
+
command: string;
|
|
53
|
+
configPath?: string;
|
|
54
|
+
profile?: string;
|
|
55
|
+
timeoutMs?: number;
|
|
56
|
+
};
|
|
57
|
+
type RconPermissionDecision = {
|
|
58
|
+
allowed: boolean;
|
|
59
|
+
defaultMode: RconPermissionMode;
|
|
60
|
+
matchedRule: {
|
|
61
|
+
source: "allow" | "deny" | "default";
|
|
62
|
+
pattern: string | null;
|
|
63
|
+
};
|
|
64
|
+
normalizedCommand: string;
|
|
65
|
+
};
|
|
66
|
+
type RconCommandResult = {
|
|
67
|
+
profile: string;
|
|
68
|
+
host: string;
|
|
69
|
+
port: number;
|
|
70
|
+
command: string;
|
|
71
|
+
response: string;
|
|
72
|
+
permissionDecision: RconPermissionDecision;
|
|
73
|
+
};
|
|
74
|
+
type ResolvedRconProfile = {
|
|
75
|
+
configPath: string | null;
|
|
76
|
+
source: RconConfigStatus["source"];
|
|
77
|
+
profile: string;
|
|
78
|
+
host: string;
|
|
79
|
+
port: number;
|
|
80
|
+
password: string;
|
|
81
|
+
timeoutMs: number;
|
|
82
|
+
permissions: Required<RconPermissions>;
|
|
83
|
+
warnings: string[];
|
|
84
|
+
};
|
|
85
|
+
type RconTransport = (profile: ResolvedRconProfile, command: string) => Promise<string>;
|
|
86
|
+
declare function resolveRconPermissions(permissions?: RconPermissions): Required<RconPermissions>;
|
|
87
|
+
declare function evaluateRconPermission(command: string, permissions?: RconPermissions): RconPermissionDecision;
|
|
88
|
+
declare function defaultRconConfigPath(): string;
|
|
89
|
+
declare function getRconConfigStatus(options?: RconConfigStatusOptions): RconConfigStatus;
|
|
90
|
+
declare function isRconConfigured(options?: RconConfigStatusOptions): boolean;
|
|
91
|
+
declare function createRconConfig(options?: RconCreateConfigOptions): RconCreateConfigResult;
|
|
92
|
+
declare function sendRconCommand(profile: ResolvedRconProfile, command: string): Promise<string>;
|
|
93
|
+
declare function runRconCommand(options: RconCommandOptions, transport?: RconTransport): Promise<RconCommandResult>;
|
|
94
|
+
//#endregion
|
|
95
|
+
export { RconCommandOptions, RconCommandResult, RconConfig, RconConfigStatus, RconConfigStatusOptions, RconCreateConfigOptions, RconCreateConfigResult, RconPermissionDecision, RconPermissionMode, RconPermissionPreset, RconPermissions, RconProfileConfig, RconTransport, ResolvedRconProfile, createRconConfig, defaultRconConfigPath, evaluateRconPermission, getRconConfigStatus, isRconConfigured, resolveRconPermissions, runRconCommand, sendRconCommand };
|
|
96
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";KAKY,oBAAA;AAAA,KACA,kBAAA;AAAA,KAEA,eAAA;EACV,MAAA,GAAS,oBAAA;EACT,WAAA,GAAc,kBAAkB;EAChC,KAAA;EACA,IAAA;AAAA;AAAA,KAGU,iBAAA;EACV,IAAA;EACA,IAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA,GAAc,eAAe;AAAA;AAAA,KAGnB,UAAA;EACV,OAAA;EACA,cAAA;EACA,QAAA,EAAU,MAAM,SAAS,iBAAA;AAAA;AAAA,KAGf,uBAAA;EACV,UAAA;EACA,OAAO;AAAA;AAAA,KAGG,gBAAA;EACV,UAAA;EACA,UAAA;EACA,MAAA;EACA,OAAA;EACA,OAAA;EACA,QAAA;EACA,OAAA;AAAA;AAAA,KAGU,uBAAA;EACV,UAAA;EACA,OAAA;EACA,IAAA;EACA,IAAA;EACA,WAAA;EACA,MAAA,GAAS,oBAAoB;EAC7B,KAAA;AAAA;AAAA,KAGU,sBAAA;EACV,IAAA;EACA,OAAA;EACA,OAAA;EACA,OAAA;EACA,MAAA,EAAQ,UAAU;AAAA;AAAA,KAGR,kBAAA;EACV,OAAA;EACA,UAAA;EACA,OAAA;EACA,SAAA;AAAA;AAAA,KAGU,sBAAA;EACV,OAAA;EACA,WAAA,EAAa,kBAAkB;EAC/B,WAAA;IACE,MAAA;IACA,OAAA;EAAA;EAEF,iBAAA;AAAA;AAAA,KAGU,iBAAA;EACV,OAAA;EACA,IAAA;EACA,IAAA;EACA,OAAA;EACA,QAAA;EACA,kBAAA,EAAoB,sBAAsB;AAAA;AAAA,KAGhC,mBAAA;EACV,UAAA;EACA,MAAA,EAAQ,gBAAA;EACR,OAAA;EACA,IAAA;EACA,IAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA,EAAa,QAAA,CAAS,eAAA;EACtB,QAAA;AAAA;AAAA,KAGU,aAAA,IAAiB,OAAA,EAAS,mBAAA,EAAqB,OAAA,aAAoB,OAAO;AAAA,iBA0GtE,sBAAA,CAAuB,WAAA,GAAc,eAAA,GAAkB,QAAA,CAAS,eAAA;AAAA,iBA0BhE,sBAAA,CACd,OAAA,UACA,WAAA,GAAc,eAAA,GACb,sBAAsB;AAAA,iBAyCT,qBAAA;AAAA,iBA8NA,mBAAA,CAAoB,OAAA,GAAS,uBAAA,GAA+B,gBAAgB;AAAA,iBAI5E,gBAAA,CAAiB,OAAqC,GAA5B,uBAA4B;AAAA,iBAItD,gBAAA,CAAiB,OAAA,GAAS,uBAAA,GAA+B,sBAAsB;AAAA,iBAiIzE,eAAA,CACpB,OAAA,EAAS,mBAAA,EACT,OAAA,WACC,OAAO;AAAA,iBAoDY,cAAA,CACpB,OAAA,EAAS,kBAAA,EACT,SAAA,GAAW,aAAA,GACV,OAAA,CAAQ,iBAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { Socket } from "node:net";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
//#region src/index.ts
|
|
6
|
+
const defaultRconPort = 25575;
|
|
7
|
+
const defaultRconTimeoutMs = 2e3;
|
|
8
|
+
const readonlyPermissions = {
|
|
9
|
+
preset: "readonly",
|
|
10
|
+
defaultMode: "deny",
|
|
11
|
+
allow: [
|
|
12
|
+
"^list$",
|
|
13
|
+
"^help(?: .+)?$",
|
|
14
|
+
"^minecraft:help(?: .+)?$",
|
|
15
|
+
"^version$",
|
|
16
|
+
"^plugins$",
|
|
17
|
+
"^pl$",
|
|
18
|
+
"^bukkit:version$",
|
|
19
|
+
"^bukkit:plugins$",
|
|
20
|
+
"^time query (?:daytime|gametime|day)$",
|
|
21
|
+
"^difficulty$",
|
|
22
|
+
"^worldborder get$",
|
|
23
|
+
"^gamerule [A-Za-z0-9_.:-]+$",
|
|
24
|
+
"^datapack list(?: .+)?$",
|
|
25
|
+
"^team list(?: .+)?$",
|
|
26
|
+
"^scoreboard objectives list$",
|
|
27
|
+
"^scoreboard players list(?: .+)?$",
|
|
28
|
+
"^scoreboard players get .+$",
|
|
29
|
+
"^bossbar list$",
|
|
30
|
+
"^bossbar get .+$",
|
|
31
|
+
"^attribute .+ get .+$",
|
|
32
|
+
"^(?:experience|xp) query .+$",
|
|
33
|
+
"^data get .+$",
|
|
34
|
+
"^tag .+ list$",
|
|
35
|
+
"^forceload query(?: .+)?$"
|
|
36
|
+
],
|
|
37
|
+
deny: []
|
|
38
|
+
};
|
|
39
|
+
const guardedPermissions = {
|
|
40
|
+
preset: "guarded",
|
|
41
|
+
defaultMode: "allow",
|
|
42
|
+
allow: [],
|
|
43
|
+
deny: [
|
|
44
|
+
"(?:^|\\s)(?:minecraft:)?(?:stop|restart)(?:$|\\s)",
|
|
45
|
+
"(?:^|\\s)(?:minecraft:)?save-[A-Za-z-]+(?:$|\\s)",
|
|
46
|
+
"(?:^|\\s)(?:minecraft:)?(?:op|deop|ban|ban-ip|pardon|pardon-ip|kick)(?:$|\\s)",
|
|
47
|
+
"(?:^|\\s)(?:minecraft:)?whitelist(?:$|\\s)"
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
const fullPermissions = {
|
|
51
|
+
preset: "full",
|
|
52
|
+
defaultMode: "allow",
|
|
53
|
+
allow: [],
|
|
54
|
+
deny: []
|
|
55
|
+
};
|
|
56
|
+
function presetPermissions(preset) {
|
|
57
|
+
if (preset === "readonly") return readonlyPermissions;
|
|
58
|
+
if (preset === "guarded") return guardedPermissions;
|
|
59
|
+
return fullPermissions;
|
|
60
|
+
}
|
|
61
|
+
function assertRconPermissionPreset(value) {
|
|
62
|
+
if (value === "readonly" || value === "guarded" || value === "full") return value;
|
|
63
|
+
throw new Error(`Invalid RCON permission preset: ${value}`);
|
|
64
|
+
}
|
|
65
|
+
function assertRconPermissionMode(value) {
|
|
66
|
+
if (value === "allow" || value === "deny") return value;
|
|
67
|
+
throw new Error(`Invalid RCON permission defaultMode: ${value}`);
|
|
68
|
+
}
|
|
69
|
+
function stringArray(value, field) {
|
|
70
|
+
if (value === void 0) return [];
|
|
71
|
+
if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) throw new Error(`RCON permissions ${field} must be a string array`);
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function normalizeRconCommand(command) {
|
|
75
|
+
return command.trim().replace(/^\/+/, "").replace(/\s+/g, " ");
|
|
76
|
+
}
|
|
77
|
+
function compileRconRegex(pattern) {
|
|
78
|
+
try {
|
|
79
|
+
return new RegExp(pattern, "i");
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw new Error(`Invalid RCON permission regex ${JSON.stringify(pattern)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function resolveRconPermissions(permissions) {
|
|
85
|
+
if (!permissions) return {
|
|
86
|
+
...readonlyPermissions,
|
|
87
|
+
allow: [...readonlyPermissions.allow],
|
|
88
|
+
deny: []
|
|
89
|
+
};
|
|
90
|
+
const preset = permissions.preset ? assertRconPermissionPreset(permissions.preset) : void 0;
|
|
91
|
+
const defaultMode = permissions.defaultMode ? assertRconPermissionMode(permissions.defaultMode) : void 0;
|
|
92
|
+
const allow = stringArray(permissions.allow, "allow");
|
|
93
|
+
const deny = stringArray(permissions.deny, "deny");
|
|
94
|
+
const base = permissions.preset ? presetPermissions(preset ?? "readonly") : {
|
|
95
|
+
preset: "readonly",
|
|
96
|
+
defaultMode: "deny",
|
|
97
|
+
allow: [],
|
|
98
|
+
deny: []
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
preset: preset ?? base.preset,
|
|
102
|
+
defaultMode: defaultMode ?? base.defaultMode,
|
|
103
|
+
allow: [...base.allow, ...allow],
|
|
104
|
+
deny: [...base.deny, ...deny]
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function evaluateRconPermission(command, permissions) {
|
|
108
|
+
const normalizedCommand = normalizeRconCommand(command);
|
|
109
|
+
const resolved = resolveRconPermissions(permissions);
|
|
110
|
+
for (const pattern of resolved.deny) if (compileRconRegex(pattern).test(normalizedCommand)) return {
|
|
111
|
+
allowed: false,
|
|
112
|
+
defaultMode: resolved.defaultMode,
|
|
113
|
+
matchedRule: {
|
|
114
|
+
source: "deny",
|
|
115
|
+
pattern
|
|
116
|
+
},
|
|
117
|
+
normalizedCommand
|
|
118
|
+
};
|
|
119
|
+
for (const pattern of resolved.allow) if (compileRconRegex(pattern).test(normalizedCommand)) return {
|
|
120
|
+
allowed: true,
|
|
121
|
+
defaultMode: resolved.defaultMode,
|
|
122
|
+
matchedRule: {
|
|
123
|
+
source: "allow",
|
|
124
|
+
pattern
|
|
125
|
+
},
|
|
126
|
+
normalizedCommand
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
allowed: resolved.defaultMode === "allow",
|
|
130
|
+
defaultMode: resolved.defaultMode,
|
|
131
|
+
matchedRule: {
|
|
132
|
+
source: "default",
|
|
133
|
+
pattern: null
|
|
134
|
+
},
|
|
135
|
+
normalizedCommand
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function configHome() {
|
|
139
|
+
if (process.platform === "darwin") return join(homedir(), "Library", "Application Support", "minecraft-skills");
|
|
140
|
+
if (process.platform === "win32") return join(process.env.APPDATA ?? join(homedir(), "AppData", "Roaming"), "minecraft-skills");
|
|
141
|
+
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "minecraft-skills");
|
|
142
|
+
}
|
|
143
|
+
function defaultRconConfigPath() {
|
|
144
|
+
return join(configHome(), "rcon.json");
|
|
145
|
+
}
|
|
146
|
+
function localRconConfigCandidates(cwd = process.cwd()) {
|
|
147
|
+
return [join(cwd, ".minecraft-skills", "rcon.json"), join(cwd, "minecraft-skills.rcon.json")];
|
|
148
|
+
}
|
|
149
|
+
function findRconConfigPath(explicitPath) {
|
|
150
|
+
if (explicitPath) return {
|
|
151
|
+
path: resolve(explicitPath),
|
|
152
|
+
source: "explicit"
|
|
153
|
+
};
|
|
154
|
+
if (process.env.MINECRAFT_SKILLS_RCON_CONFIG) return {
|
|
155
|
+
path: resolve(process.env.MINECRAFT_SKILLS_RCON_CONFIG),
|
|
156
|
+
source: "env-config"
|
|
157
|
+
};
|
|
158
|
+
for (const candidate of localRconConfigCandidates()) if (existsSync(candidate)) return {
|
|
159
|
+
path: candidate,
|
|
160
|
+
source: "local"
|
|
161
|
+
};
|
|
162
|
+
const userPath = defaultRconConfigPath();
|
|
163
|
+
if (existsSync(userPath)) return {
|
|
164
|
+
path: userPath,
|
|
165
|
+
source: "user"
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
path: null,
|
|
169
|
+
source: "missing"
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function readRconConfig(path) {
|
|
173
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
174
|
+
if (typeof parsed !== "object" || parsed === null || !("profiles" in parsed)) throw new Error(`Invalid RCON config: ${path}`);
|
|
175
|
+
const config = parsed;
|
|
176
|
+
if (typeof config.profiles !== "object" || config.profiles === null) throw new Error(`Invalid RCON config profiles: ${path}`);
|
|
177
|
+
return config;
|
|
178
|
+
}
|
|
179
|
+
function envValue(name) {
|
|
180
|
+
const value = process.env[name];
|
|
181
|
+
return value && value.length > 0 ? value : void 0;
|
|
182
|
+
}
|
|
183
|
+
function resolveConfigValue(value, field, missing) {
|
|
184
|
+
if (typeof value === "number") return String(value);
|
|
185
|
+
if (value.startsWith("$env:")) {
|
|
186
|
+
const name = value.slice(5);
|
|
187
|
+
const resolved = envValue(name);
|
|
188
|
+
if (!resolved) {
|
|
189
|
+
missing.push(name);
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
|
+
return resolved;
|
|
193
|
+
}
|
|
194
|
+
if (field === "password") return value;
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
function parsePort(value, missing) {
|
|
198
|
+
const resolved = resolveConfigValue(value, "port", missing);
|
|
199
|
+
if (!resolved) return defaultRconPort;
|
|
200
|
+
const port = Number(resolved);
|
|
201
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new Error(`Invalid RCON port: ${resolved}`);
|
|
202
|
+
return port;
|
|
203
|
+
}
|
|
204
|
+
function parseTimeoutMs(value, missing) {
|
|
205
|
+
const resolved = resolveConfigValue(value, "timeoutMs", missing);
|
|
206
|
+
if (!resolved) return defaultRconTimeoutMs;
|
|
207
|
+
const timeoutMs = Number(resolved);
|
|
208
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error(`Invalid RCON timeoutMs: ${resolved}`);
|
|
209
|
+
return timeoutMs;
|
|
210
|
+
}
|
|
211
|
+
function envProfile() {
|
|
212
|
+
const host = envValue("MINECRAFT_SKILLS_RCON_HOST");
|
|
213
|
+
const password = envValue("MINECRAFT_SKILLS_RCON_PASSWORD");
|
|
214
|
+
if (!host || !password) return null;
|
|
215
|
+
return {
|
|
216
|
+
host,
|
|
217
|
+
port: envValue("MINECRAFT_SKILLS_RCON_PORT") ?? defaultRconPort,
|
|
218
|
+
password,
|
|
219
|
+
timeoutMs: envValue("MINECRAFT_SKILLS_RCON_TIMEOUT_MS") ?? defaultRconTimeoutMs,
|
|
220
|
+
permissions: { preset: envValue("MINECRAFT_SKILLS_RCON_PRESET") ?? "readonly" }
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function profileNameFrom(config, requested) {
|
|
224
|
+
return requested ?? envValue("MINECRAFT_SKILLS_RCON_PROFILE") ?? config?.defaultProfile ?? null;
|
|
225
|
+
}
|
|
226
|
+
function resolveRconProfile(options = {}) {
|
|
227
|
+
const missing = [];
|
|
228
|
+
const warnings = [];
|
|
229
|
+
const found = findRconConfigPath(options.configPath);
|
|
230
|
+
let config = null;
|
|
231
|
+
if (found.path) {
|
|
232
|
+
if (!existsSync(found.path)) return { status: {
|
|
233
|
+
configured: false,
|
|
234
|
+
configPath: found.path,
|
|
235
|
+
source: found.source,
|
|
236
|
+
profile: null,
|
|
237
|
+
missing: [found.path],
|
|
238
|
+
warnings,
|
|
239
|
+
message: `RCON config file does not exist: ${found.path}`
|
|
240
|
+
} };
|
|
241
|
+
config = readRconConfig(found.path);
|
|
242
|
+
}
|
|
243
|
+
const profileName = profileNameFrom(config, options.profile) ?? "env";
|
|
244
|
+
const profileConfig = config?.profiles[profileName] ?? (!config ? envProfile() : null);
|
|
245
|
+
if (!profileConfig) return { status: {
|
|
246
|
+
configured: false,
|
|
247
|
+
configPath: found.path,
|
|
248
|
+
source: found.source,
|
|
249
|
+
profile: profileName,
|
|
250
|
+
missing: config ? [`profiles.${profileName}`] : ["MINECRAFT_SKILLS_RCON_HOST", "MINECRAFT_SKILLS_RCON_PASSWORD"],
|
|
251
|
+
warnings,
|
|
252
|
+
message: config ? `RCON profile is not defined: ${profileName}` : "Set an RCON config file or MINECRAFT_SKILLS_RCON_HOST and MINECRAFT_SKILLS_RCON_PASSWORD."
|
|
253
|
+
} };
|
|
254
|
+
const merged = {
|
|
255
|
+
...profileConfig,
|
|
256
|
+
...envValue("MINECRAFT_SKILLS_RCON_HOST") ? { host: "$env:MINECRAFT_SKILLS_RCON_HOST" } : {},
|
|
257
|
+
...envValue("MINECRAFT_SKILLS_RCON_PORT") ? { port: "$env:MINECRAFT_SKILLS_RCON_PORT" } : {},
|
|
258
|
+
...envValue("MINECRAFT_SKILLS_RCON_PASSWORD") ? { password: "$env:MINECRAFT_SKILLS_RCON_PASSWORD" } : {},
|
|
259
|
+
...envValue("MINECRAFT_SKILLS_RCON_TIMEOUT_MS") ? { timeoutMs: "$env:MINECRAFT_SKILLS_RCON_TIMEOUT_MS" } : {}
|
|
260
|
+
};
|
|
261
|
+
const host = resolveConfigValue(merged.host, "host", missing);
|
|
262
|
+
const password = resolveConfigValue(merged.password, "password", missing);
|
|
263
|
+
const port = parsePort(merged.port ?? defaultRconPort, missing);
|
|
264
|
+
const timeoutMs = parseTimeoutMs(merged.timeoutMs ?? defaultRconTimeoutMs, missing);
|
|
265
|
+
if (typeof merged.password === "string" && !merged.password.startsWith("$env:")) warnings.push("RCON password is stored as a literal value. Prefer password: \"$env:NAME\".");
|
|
266
|
+
if (missing.length > 0 || !host || !password) return { status: {
|
|
267
|
+
configured: false,
|
|
268
|
+
configPath: found.path,
|
|
269
|
+
source: found.path ? found.source : "missing",
|
|
270
|
+
profile: profileName,
|
|
271
|
+
missing,
|
|
272
|
+
warnings,
|
|
273
|
+
message: `RCON profile ${profileName} is missing required values.`
|
|
274
|
+
} };
|
|
275
|
+
const envPreset = envValue("MINECRAFT_SKILLS_RCON_PRESET");
|
|
276
|
+
const permissions = envPreset ? {
|
|
277
|
+
...merged.permissions ?? {},
|
|
278
|
+
preset: envPreset
|
|
279
|
+
} : merged.permissions;
|
|
280
|
+
const profile = {
|
|
281
|
+
configPath: found.path,
|
|
282
|
+
source: found.path ? found.source : "env-profile",
|
|
283
|
+
profile: profileName,
|
|
284
|
+
host,
|
|
285
|
+
port,
|
|
286
|
+
password,
|
|
287
|
+
timeoutMs,
|
|
288
|
+
permissions: resolveRconPermissions(permissions),
|
|
289
|
+
warnings
|
|
290
|
+
};
|
|
291
|
+
return {
|
|
292
|
+
profile,
|
|
293
|
+
status: {
|
|
294
|
+
configured: true,
|
|
295
|
+
configPath: found.path,
|
|
296
|
+
source: profile.source,
|
|
297
|
+
profile: profileName,
|
|
298
|
+
missing: [],
|
|
299
|
+
warnings,
|
|
300
|
+
message: `RCON profile ${profileName} is configured.`
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function getRconConfigStatus(options = {}) {
|
|
305
|
+
return resolveRconProfile(options).status;
|
|
306
|
+
}
|
|
307
|
+
function isRconConfigured(options = {}) {
|
|
308
|
+
return getRconConfigStatus(options).configured;
|
|
309
|
+
}
|
|
310
|
+
function createRconConfig(options = {}) {
|
|
311
|
+
const path = resolve(options.configPath ?? defaultRconConfigPath());
|
|
312
|
+
const existed = existsSync(path);
|
|
313
|
+
const profile = options.profile ?? "local";
|
|
314
|
+
const config = {
|
|
315
|
+
$schema: "https://raw.githubusercontent.com/sya-ri/minecraft-skills/main/schema/rcon-config.schema.json",
|
|
316
|
+
defaultProfile: profile,
|
|
317
|
+
profiles: { [profile]: {
|
|
318
|
+
host: options.host ?? "127.0.0.1",
|
|
319
|
+
port: options.port ?? defaultRconPort,
|
|
320
|
+
password: `$env:${options.passwordEnv ?? "MINECRAFT_SKILLS_RCON_PASSWORD"}`,
|
|
321
|
+
permissions: { preset: options.preset ?? "readonly" }
|
|
322
|
+
} }
|
|
323
|
+
};
|
|
324
|
+
if (existed && !options.force) return {
|
|
325
|
+
path,
|
|
326
|
+
existed,
|
|
327
|
+
written: false,
|
|
328
|
+
warning: `RCON config already exists and was not overwritten: ${path}`,
|
|
329
|
+
config
|
|
330
|
+
};
|
|
331
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
332
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
|
|
333
|
+
return {
|
|
334
|
+
path,
|
|
335
|
+
existed,
|
|
336
|
+
written: true,
|
|
337
|
+
warning: existed ? `Overwrote existing RCON config: ${path}` : null,
|
|
338
|
+
config
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function writePacket(socket, id, type, payload) {
|
|
342
|
+
const bodyLength = Buffer.byteLength(payload) + 10;
|
|
343
|
+
const buffer = Buffer.alloc(bodyLength + 4);
|
|
344
|
+
buffer.writeInt32LE(bodyLength, 0);
|
|
345
|
+
buffer.writeInt32LE(id, 4);
|
|
346
|
+
buffer.writeInt32LE(type, 8);
|
|
347
|
+
buffer.write(payload, 12, "utf8");
|
|
348
|
+
buffer.writeInt16LE(0, bodyLength + 2);
|
|
349
|
+
socket.write(buffer);
|
|
350
|
+
}
|
|
351
|
+
function parsePackets(buffer) {
|
|
352
|
+
const packets = [];
|
|
353
|
+
let offset = 0;
|
|
354
|
+
while (buffer.length - offset >= 4) {
|
|
355
|
+
const length = buffer.readInt32LE(offset);
|
|
356
|
+
if (buffer.length - offset < length + 4) break;
|
|
357
|
+
const start = offset + 4;
|
|
358
|
+
const id = buffer.readInt32LE(start);
|
|
359
|
+
const type = buffer.readInt32LE(start + 4);
|
|
360
|
+
const body = buffer.subarray(start + 8, start + length - 2).toString("utf8");
|
|
361
|
+
packets.push({
|
|
362
|
+
id,
|
|
363
|
+
type,
|
|
364
|
+
body
|
|
365
|
+
});
|
|
366
|
+
offset += length + 4;
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
packets,
|
|
370
|
+
rest: buffer.subarray(offset)
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function readPacketsUntil(socket, timeoutMs, stop) {
|
|
374
|
+
return new Promise((resolvePromise, reject) => {
|
|
375
|
+
let pending = Buffer.alloc(0);
|
|
376
|
+
const packets = [];
|
|
377
|
+
const timer = setTimeout(() => {
|
|
378
|
+
cleanup();
|
|
379
|
+
reject(/* @__PURE__ */ new Error("RCON response timed out"));
|
|
380
|
+
}, timeoutMs);
|
|
381
|
+
const cleanup = () => {
|
|
382
|
+
clearTimeout(timer);
|
|
383
|
+
socket.off("data", onData);
|
|
384
|
+
socket.off("error", onError);
|
|
385
|
+
socket.off("end", onEnd);
|
|
386
|
+
socket.off("close", onClose);
|
|
387
|
+
socket.off("timeout", onTimeout);
|
|
388
|
+
};
|
|
389
|
+
const onError = (error) => {
|
|
390
|
+
cleanup();
|
|
391
|
+
reject(error);
|
|
392
|
+
};
|
|
393
|
+
const onEnd = () => {
|
|
394
|
+
cleanup();
|
|
395
|
+
reject(/* @__PURE__ */ new Error("RCON connection ended before the expected response was received"));
|
|
396
|
+
};
|
|
397
|
+
const onClose = () => {
|
|
398
|
+
cleanup();
|
|
399
|
+
reject(/* @__PURE__ */ new Error("RCON connection closed before the expected response was received"));
|
|
400
|
+
};
|
|
401
|
+
const onTimeout = () => {
|
|
402
|
+
cleanup();
|
|
403
|
+
socket.destroy();
|
|
404
|
+
reject(/* @__PURE__ */ new Error("RCON response timed out"));
|
|
405
|
+
};
|
|
406
|
+
const onData = (chunk) => {
|
|
407
|
+
pending = Buffer.concat([pending, chunk]);
|
|
408
|
+
const parsed = parsePackets(pending);
|
|
409
|
+
pending = parsed.rest;
|
|
410
|
+
for (const packet of parsed.packets) {
|
|
411
|
+
packets.push(packet);
|
|
412
|
+
if (stop(packet)) {
|
|
413
|
+
cleanup();
|
|
414
|
+
resolvePromise(packets);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
socket.on("data", onData);
|
|
420
|
+
socket.on("error", onError);
|
|
421
|
+
socket.on("end", onEnd);
|
|
422
|
+
socket.on("close", onClose);
|
|
423
|
+
socket.on("timeout", onTimeout);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
async function sendRconCommand(profile, command) {
|
|
427
|
+
const socket = new Socket();
|
|
428
|
+
socket.setTimeout(profile.timeoutMs);
|
|
429
|
+
await new Promise((resolvePromise, reject) => {
|
|
430
|
+
const timer = setTimeout(() => {
|
|
431
|
+
cleanup();
|
|
432
|
+
socket.destroy();
|
|
433
|
+
reject(/* @__PURE__ */ new Error("RCON connection timed out"));
|
|
434
|
+
}, profile.timeoutMs);
|
|
435
|
+
const cleanup = () => {
|
|
436
|
+
clearTimeout(timer);
|
|
437
|
+
socket.off("error", onError);
|
|
438
|
+
};
|
|
439
|
+
const onError = (error) => {
|
|
440
|
+
cleanup();
|
|
441
|
+
reject(error);
|
|
442
|
+
};
|
|
443
|
+
socket.once("error", onError);
|
|
444
|
+
socket.connect(profile.port, profile.host, () => {
|
|
445
|
+
cleanup();
|
|
446
|
+
resolvePromise();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
try {
|
|
450
|
+
const authPromise = readPacketsUntil(socket, profile.timeoutMs, (packet) => packet.id === 1 || packet.id === -1);
|
|
451
|
+
writePacket(socket, 1, 3, profile.password);
|
|
452
|
+
if ((await authPromise).some((packet) => packet.id === -1)) throw new Error("RCON authentication failed");
|
|
453
|
+
const responsePromise = readPacketsUntil(socket, profile.timeoutMs, (packet) => packet.id === 3);
|
|
454
|
+
writePacket(socket, 2, 2, command);
|
|
455
|
+
writePacket(socket, 3, 2, "");
|
|
456
|
+
return (await responsePromise).filter((packet) => packet.id === 2).map((packet) => packet.body).join("");
|
|
457
|
+
} finally {
|
|
458
|
+
socket.end();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
async function runRconCommand(options, transport = sendRconCommand) {
|
|
462
|
+
const resolved = resolveRconProfile(options);
|
|
463
|
+
if (!resolved.profile) throw new Error(resolved.status.message);
|
|
464
|
+
const command = normalizeRconCommand(options.command);
|
|
465
|
+
if (!command) throw new Error("RCON command must not be empty");
|
|
466
|
+
const permissionDecision = evaluateRconPermission(command, resolved.profile.permissions);
|
|
467
|
+
if (!permissionDecision.allowed) return {
|
|
468
|
+
profile: resolved.profile.profile,
|
|
469
|
+
host: resolved.profile.host,
|
|
470
|
+
port: resolved.profile.port,
|
|
471
|
+
command,
|
|
472
|
+
response: "",
|
|
473
|
+
permissionDecision
|
|
474
|
+
};
|
|
475
|
+
const profile = options.timeoutMs ? {
|
|
476
|
+
...resolved.profile,
|
|
477
|
+
timeoutMs: options.timeoutMs
|
|
478
|
+
} : resolved.profile;
|
|
479
|
+
return {
|
|
480
|
+
profile: profile.profile,
|
|
481
|
+
host: profile.host,
|
|
482
|
+
port: profile.port,
|
|
483
|
+
command,
|
|
484
|
+
response: await transport(profile, command),
|
|
485
|
+
permissionDecision
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
//#endregion
|
|
489
|
+
export { createRconConfig, defaultRconConfigPath, evaluateRconPermission, getRconConfigStatus, isRconConfigured, resolveRconPermissions, runRconCommand, sendRconCommand };
|
|
490
|
+
|
|
491
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { Socket } from \"node:net\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\n\nexport type RconPermissionPreset = \"readonly\" | \"guarded\" | \"full\";\nexport type RconPermissionMode = \"allow\" | \"deny\";\n\nexport type RconPermissions = {\n preset?: RconPermissionPreset;\n defaultMode?: RconPermissionMode;\n allow?: string[];\n deny?: string[];\n};\n\nexport type RconProfileConfig = {\n host: string;\n port: number | string;\n password: string;\n timeoutMs?: number | string;\n permissions?: RconPermissions;\n};\n\nexport type RconConfig = {\n $schema?: string;\n defaultProfile?: string;\n profiles: Record<string, RconProfileConfig>;\n};\n\nexport type RconConfigStatusOptions = {\n configPath?: string;\n profile?: string;\n};\n\nexport type RconConfigStatus = {\n configured: boolean;\n configPath: string | null;\n source: \"explicit\" | \"env-config\" | \"local\" | \"user\" | \"env-profile\" | \"missing\";\n profile: string | null;\n missing: string[];\n warnings: string[];\n message: string;\n};\n\nexport type RconCreateConfigOptions = {\n configPath?: string;\n profile?: string;\n host?: string;\n port?: number | string;\n passwordEnv?: string;\n preset?: RconPermissionPreset;\n force?: boolean;\n};\n\nexport type RconCreateConfigResult = {\n path: string;\n existed: boolean;\n written: boolean;\n warning: string | null;\n config: RconConfig;\n};\n\nexport type RconCommandOptions = {\n command: string;\n configPath?: string;\n profile?: string;\n timeoutMs?: number;\n};\n\nexport type RconPermissionDecision = {\n allowed: boolean;\n defaultMode: RconPermissionMode;\n matchedRule: {\n source: \"allow\" | \"deny\" | \"default\";\n pattern: string | null;\n };\n normalizedCommand: string;\n};\n\nexport type RconCommandResult = {\n profile: string;\n host: string;\n port: number;\n command: string;\n response: string;\n permissionDecision: RconPermissionDecision;\n};\n\nexport type ResolvedRconProfile = {\n configPath: string | null;\n source: RconConfigStatus[\"source\"];\n profile: string;\n host: string;\n port: number;\n password: string;\n timeoutMs: number;\n permissions: Required<RconPermissions>;\n warnings: string[];\n};\n\nexport type RconTransport = (profile: ResolvedRconProfile, command: string) => Promise<string>;\n\nconst defaultRconPort = 25575;\nconst defaultRconTimeoutMs = 2000;\n\nconst readonlyPermissions: Required<RconPermissions> = {\n preset: \"readonly\",\n defaultMode: \"deny\",\n allow: [\n \"^list$\",\n \"^help(?: .+)?$\",\n \"^minecraft:help(?: .+)?$\",\n \"^version$\",\n \"^plugins$\",\n \"^pl$\",\n \"^bukkit:version$\",\n \"^bukkit:plugins$\",\n \"^time query (?:daytime|gametime|day)$\",\n \"^difficulty$\",\n \"^worldborder get$\",\n \"^gamerule [A-Za-z0-9_.:-]+$\",\n \"^datapack list(?: .+)?$\",\n \"^team list(?: .+)?$\",\n \"^scoreboard objectives list$\",\n \"^scoreboard players list(?: .+)?$\",\n \"^scoreboard players get .+$\",\n \"^bossbar list$\",\n \"^bossbar get .+$\",\n \"^attribute .+ get .+$\",\n \"^(?:experience|xp) query .+$\",\n \"^data get .+$\",\n \"^tag .+ list$\",\n \"^forceload query(?: .+)?$\",\n ],\n deny: [],\n};\n\nconst guardedPermissions: Required<RconPermissions> = {\n preset: \"guarded\",\n defaultMode: \"allow\",\n allow: [],\n deny: [\n \"(?:^|\\\\s)(?:minecraft:)?(?:stop|restart)(?:$|\\\\s)\",\n \"(?:^|\\\\s)(?:minecraft:)?save-[A-Za-z-]+(?:$|\\\\s)\",\n \"(?:^|\\\\s)(?:minecraft:)?(?:op|deop|ban|ban-ip|pardon|pardon-ip|kick)(?:$|\\\\s)\",\n \"(?:^|\\\\s)(?:minecraft:)?whitelist(?:$|\\\\s)\",\n ],\n};\n\nconst fullPermissions: Required<RconPermissions> = {\n preset: \"full\",\n defaultMode: \"allow\",\n allow: [],\n deny: [],\n};\n\nfunction presetPermissions(preset: RconPermissionPreset): Required<RconPermissions> {\n if (preset === \"readonly\") {\n return readonlyPermissions;\n }\n if (preset === \"guarded\") {\n return guardedPermissions;\n }\n return fullPermissions;\n}\n\nfunction assertRconPermissionPreset(value: string): RconPermissionPreset {\n if (value === \"readonly\" || value === \"guarded\" || value === \"full\") {\n return value;\n }\n throw new Error(`Invalid RCON permission preset: ${value}`);\n}\n\nfunction assertRconPermissionMode(value: string): RconPermissionMode {\n if (value === \"allow\" || value === \"deny\") {\n return value;\n }\n throw new Error(`Invalid RCON permission defaultMode: ${value}`);\n}\n\nfunction stringArray(value: unknown, field: string): string[] {\n if (value === undefined) {\n return [];\n }\n if (!Array.isArray(value) || !value.every((entry) => typeof entry === \"string\")) {\n throw new Error(`RCON permissions ${field} must be a string array`);\n }\n return value;\n}\n\nfunction normalizeRconCommand(command: string): string {\n return command.trim().replace(/^\\/+/, \"\").replace(/\\s+/g, \" \");\n}\n\nfunction compileRconRegex(pattern: string): RegExp {\n try {\n return new RegExp(pattern, \"i\");\n } catch (error) {\n throw new Error(\n `Invalid RCON permission regex ${JSON.stringify(pattern)}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n}\n\nexport function resolveRconPermissions(permissions?: RconPermissions): Required<RconPermissions> {\n if (!permissions) {\n return { ...readonlyPermissions, allow: [...readonlyPermissions.allow], deny: [] };\n }\n const preset = permissions.preset ? assertRconPermissionPreset(permissions.preset) : undefined;\n const defaultMode = permissions.defaultMode\n ? assertRconPermissionMode(permissions.defaultMode)\n : undefined;\n const allow = stringArray(permissions.allow, \"allow\");\n const deny = stringArray(permissions.deny, \"deny\");\n const base = permissions.preset\n ? presetPermissions(preset ?? \"readonly\")\n : {\n preset: \"readonly\" as const,\n defaultMode: \"deny\" as const,\n allow: [],\n deny: [],\n };\n return {\n preset: preset ?? base.preset,\n defaultMode: defaultMode ?? base.defaultMode,\n allow: [...base.allow, ...allow],\n deny: [...base.deny, ...deny],\n };\n}\n\nexport function evaluateRconPermission(\n command: string,\n permissions?: RconPermissions,\n): RconPermissionDecision {\n const normalizedCommand = normalizeRconCommand(command);\n const resolved = resolveRconPermissions(permissions);\n for (const pattern of resolved.deny) {\n if (compileRconRegex(pattern).test(normalizedCommand)) {\n return {\n allowed: false,\n defaultMode: resolved.defaultMode,\n matchedRule: { source: \"deny\", pattern },\n normalizedCommand,\n };\n }\n }\n for (const pattern of resolved.allow) {\n if (compileRconRegex(pattern).test(normalizedCommand)) {\n return {\n allowed: true,\n defaultMode: resolved.defaultMode,\n matchedRule: { source: \"allow\", pattern },\n normalizedCommand,\n };\n }\n }\n return {\n allowed: resolved.defaultMode === \"allow\",\n defaultMode: resolved.defaultMode,\n matchedRule: { source: \"default\", pattern: null },\n normalizedCommand,\n };\n}\n\nfunction configHome(): string {\n if (process.platform === \"darwin\") {\n return join(homedir(), \"Library\", \"Application Support\", \"minecraft-skills\");\n }\n if (process.platform === \"win32\") {\n return join(process.env.APPDATA ?? join(homedir(), \"AppData\", \"Roaming\"), \"minecraft-skills\");\n }\n return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), \".config\"), \"minecraft-skills\");\n}\n\nexport function defaultRconConfigPath(): string {\n return join(configHome(), \"rcon.json\");\n}\n\nfunction localRconConfigCandidates(cwd = process.cwd()): string[] {\n return [join(cwd, \".minecraft-skills\", \"rcon.json\"), join(cwd, \"minecraft-skills.rcon.json\")];\n}\n\nfunction findRconConfigPath(explicitPath?: string): {\n path: string | null;\n source: RconConfigStatus[\"source\"];\n} {\n if (explicitPath) {\n return { path: resolve(explicitPath), source: \"explicit\" };\n }\n if (process.env.MINECRAFT_SKILLS_RCON_CONFIG) {\n return { path: resolve(process.env.MINECRAFT_SKILLS_RCON_CONFIG), source: \"env-config\" };\n }\n for (const candidate of localRconConfigCandidates()) {\n if (existsSync(candidate)) {\n return { path: candidate, source: \"local\" };\n }\n }\n const userPath = defaultRconConfigPath();\n if (existsSync(userPath)) {\n return { path: userPath, source: \"user\" };\n }\n return { path: null, source: \"missing\" };\n}\n\nfunction readRconConfig(path: string): RconConfig {\n const parsed = JSON.parse(readFileSync(path, \"utf8\")) as unknown;\n if (typeof parsed !== \"object\" || parsed === null || !(\"profiles\" in parsed)) {\n throw new Error(`Invalid RCON config: ${path}`);\n }\n const config = parsed as RconConfig;\n if (typeof config.profiles !== \"object\" || config.profiles === null) {\n throw new Error(`Invalid RCON config profiles: ${path}`);\n }\n return config;\n}\n\nfunction envValue(name: string): string | undefined {\n const value = process.env[name];\n return value && value.length > 0 ? value : undefined;\n}\n\nfunction resolveConfigValue(value: string | number, field: string, missing: string[]): string {\n if (typeof value === \"number\") {\n return String(value);\n }\n if (value.startsWith(\"$env:\")) {\n const name = value.slice(\"$env:\".length);\n const resolved = envValue(name);\n if (!resolved) {\n missing.push(name);\n return \"\";\n }\n return resolved;\n }\n if (field === \"password\") {\n return value;\n }\n return value;\n}\n\nfunction parsePort(value: string | number, missing: string[]): number {\n const resolved = resolveConfigValue(value, \"port\", missing);\n if (!resolved) {\n return defaultRconPort;\n }\n const port = Number(resolved);\n if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n throw new Error(`Invalid RCON port: ${resolved}`);\n }\n return port;\n}\n\nfunction parseTimeoutMs(value: string | number, missing: string[]): number {\n const resolved = resolveConfigValue(value, \"timeoutMs\", missing);\n if (!resolved) {\n return defaultRconTimeoutMs;\n }\n const timeoutMs = Number(resolved);\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new Error(`Invalid RCON timeoutMs: ${resolved}`);\n }\n return timeoutMs;\n}\n\nfunction envProfile(): RconProfileConfig | null {\n const host = envValue(\"MINECRAFT_SKILLS_RCON_HOST\");\n const password = envValue(\"MINECRAFT_SKILLS_RCON_PASSWORD\");\n if (!host || !password) {\n return null;\n }\n return {\n host,\n port: envValue(\"MINECRAFT_SKILLS_RCON_PORT\") ?? defaultRconPort,\n password,\n timeoutMs: envValue(\"MINECRAFT_SKILLS_RCON_TIMEOUT_MS\") ?? defaultRconTimeoutMs,\n permissions: {\n preset:\n (envValue(\"MINECRAFT_SKILLS_RCON_PRESET\") as RconPermissionPreset | undefined) ??\n \"readonly\",\n },\n };\n}\n\nfunction profileNameFrom(config: RconConfig | null, requested?: string): string | null {\n return requested ?? envValue(\"MINECRAFT_SKILLS_RCON_PROFILE\") ?? config?.defaultProfile ?? null;\n}\n\nfunction resolveRconProfile(options: RconConfigStatusOptions = {}): {\n profile?: ResolvedRconProfile;\n status: RconConfigStatus;\n} {\n const missing: string[] = [];\n const warnings: string[] = [];\n const found = findRconConfigPath(options.configPath);\n let config: RconConfig | null = null;\n if (found.path) {\n if (!existsSync(found.path)) {\n return {\n status: {\n configured: false,\n configPath: found.path,\n source: found.source,\n profile: null,\n missing: [found.path],\n warnings,\n message: `RCON config file does not exist: ${found.path}`,\n },\n };\n }\n config = readRconConfig(found.path);\n }\n\n const profileName = profileNameFrom(config, options.profile) ?? \"env\";\n const configProfile = config?.profiles[profileName];\n const profileConfig = configProfile ?? (!config ? envProfile() : null);\n if (!profileConfig) {\n return {\n status: {\n configured: false,\n configPath: found.path,\n source: found.source,\n profile: profileName,\n missing: config\n ? [`profiles.${profileName}`]\n : [\"MINECRAFT_SKILLS_RCON_HOST\", \"MINECRAFT_SKILLS_RCON_PASSWORD\"],\n warnings,\n message: config\n ? `RCON profile is not defined: ${profileName}`\n : \"Set an RCON config file or MINECRAFT_SKILLS_RCON_HOST and MINECRAFT_SKILLS_RCON_PASSWORD.\",\n },\n };\n }\n\n const merged: RconProfileConfig = {\n ...profileConfig,\n ...(envValue(\"MINECRAFT_SKILLS_RCON_HOST\") ? { host: \"$env:MINECRAFT_SKILLS_RCON_HOST\" } : {}),\n ...(envValue(\"MINECRAFT_SKILLS_RCON_PORT\") ? { port: \"$env:MINECRAFT_SKILLS_RCON_PORT\" } : {}),\n ...(envValue(\"MINECRAFT_SKILLS_RCON_PASSWORD\")\n ? { password: \"$env:MINECRAFT_SKILLS_RCON_PASSWORD\" }\n : {}),\n ...(envValue(\"MINECRAFT_SKILLS_RCON_TIMEOUT_MS\")\n ? { timeoutMs: \"$env:MINECRAFT_SKILLS_RCON_TIMEOUT_MS\" }\n : {}),\n };\n const host = resolveConfigValue(merged.host, \"host\", missing);\n const password = resolveConfigValue(merged.password, \"password\", missing);\n const port = parsePort(merged.port ?? defaultRconPort, missing);\n const timeoutMs = parseTimeoutMs(merged.timeoutMs ?? defaultRconTimeoutMs, missing);\n if (typeof merged.password === \"string\" && !merged.password.startsWith(\"$env:\")) {\n warnings.push('RCON password is stored as a literal value. Prefer password: \"$env:NAME\".');\n }\n if (missing.length > 0 || !host || !password) {\n return {\n status: {\n configured: false,\n configPath: found.path,\n source: found.path ? found.source : \"missing\",\n profile: profileName,\n missing,\n warnings,\n message: `RCON profile ${profileName} is missing required values.`,\n },\n };\n }\n const envPreset = envValue(\"MINECRAFT_SKILLS_RCON_PRESET\");\n const permissions = envPreset\n ? {\n ...(merged.permissions ?? {}),\n preset: envPreset as RconPermissionPreset,\n }\n : merged.permissions;\n const profile: ResolvedRconProfile = {\n configPath: found.path,\n source: found.path ? found.source : \"env-profile\",\n profile: profileName,\n host,\n port,\n password,\n timeoutMs,\n permissions: resolveRconPermissions(permissions),\n warnings,\n };\n return {\n profile,\n status: {\n configured: true,\n configPath: found.path,\n source: profile.source,\n profile: profileName,\n missing: [],\n warnings,\n message: `RCON profile ${profileName} is configured.`,\n },\n };\n}\n\nexport function getRconConfigStatus(options: RconConfigStatusOptions = {}): RconConfigStatus {\n return resolveRconProfile(options).status;\n}\n\nexport function isRconConfigured(options: RconConfigStatusOptions = {}): boolean {\n return getRconConfigStatus(options).configured;\n}\n\nexport function createRconConfig(options: RconCreateConfigOptions = {}): RconCreateConfigResult {\n const path = resolve(options.configPath ?? defaultRconConfigPath());\n const existed = existsSync(path);\n const profile = options.profile ?? \"local\";\n const config: RconConfig = {\n $schema:\n \"https://raw.githubusercontent.com/sya-ri/minecraft-skills/main/schema/rcon-config.schema.json\",\n defaultProfile: profile,\n profiles: {\n [profile]: {\n host: options.host ?? \"127.0.0.1\",\n port: options.port ?? defaultRconPort,\n password: `$env:${options.passwordEnv ?? \"MINECRAFT_SKILLS_RCON_PASSWORD\"}`,\n permissions: {\n preset: options.preset ?? \"readonly\",\n },\n },\n },\n };\n if (existed && !options.force) {\n return {\n path,\n existed,\n written: false,\n warning: `RCON config already exists and was not overwritten: ${path}`,\n config,\n };\n }\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n return {\n path,\n existed,\n written: true,\n warning: existed ? `Overwrote existing RCON config: ${path}` : null,\n config,\n };\n}\n\nfunction writePacket(socket: Socket, id: number, type: number, payload: string): void {\n const bodyLength = Buffer.byteLength(payload) + 10;\n const buffer = Buffer.alloc(bodyLength + 4);\n buffer.writeInt32LE(bodyLength, 0);\n buffer.writeInt32LE(id, 4);\n buffer.writeInt32LE(type, 8);\n buffer.write(payload, 12, \"utf8\");\n buffer.writeInt16LE(0, bodyLength + 2);\n socket.write(buffer);\n}\n\nfunction parsePackets(buffer: Buffer<ArrayBufferLike>): {\n packets: Array<{ id: number; type: number; body: string }>;\n rest: Buffer<ArrayBufferLike>;\n} {\n const packets: Array<{ id: number; type: number; body: string }> = [];\n let offset = 0;\n while (buffer.length - offset >= 4) {\n const length = buffer.readInt32LE(offset);\n if (buffer.length - offset < length + 4) {\n break;\n }\n const start = offset + 4;\n const id = buffer.readInt32LE(start);\n const type = buffer.readInt32LE(start + 4);\n const body = buffer.subarray(start + 8, start + length - 2).toString(\"utf8\");\n packets.push({ id, type, body });\n offset += length + 4;\n }\n return { packets, rest: buffer.subarray(offset) };\n}\n\nasync function readPacketsUntil(\n socket: Socket,\n timeoutMs: number,\n stop: (packet: { id: number; type: number; body: string }) => boolean,\n): Promise<Array<{ id: number; type: number; body: string }>> {\n return new Promise((resolvePromise, reject) => {\n let pending: Buffer<ArrayBufferLike> = Buffer.alloc(0);\n const packets: Array<{ id: number; type: number; body: string }> = [];\n const timer = setTimeout(() => {\n cleanup();\n reject(new Error(\"RCON response timed out\"));\n }, timeoutMs);\n const cleanup = () => {\n clearTimeout(timer);\n socket.off(\"data\", onData);\n socket.off(\"error\", onError);\n socket.off(\"end\", onEnd);\n socket.off(\"close\", onClose);\n socket.off(\"timeout\", onTimeout);\n };\n const onError = (error: Error) => {\n cleanup();\n reject(error);\n };\n const onEnd = () => {\n cleanup();\n reject(new Error(\"RCON connection ended before the expected response was received\"));\n };\n const onClose = () => {\n cleanup();\n reject(new Error(\"RCON connection closed before the expected response was received\"));\n };\n const onTimeout = () => {\n cleanup();\n socket.destroy();\n reject(new Error(\"RCON response timed out\"));\n };\n const onData = (chunk: Buffer) => {\n pending = Buffer.concat([pending, chunk]);\n const parsed = parsePackets(pending);\n pending = parsed.rest;\n for (const packet of parsed.packets) {\n packets.push(packet);\n if (stop(packet)) {\n cleanup();\n resolvePromise(packets);\n return;\n }\n }\n };\n socket.on(\"data\", onData);\n socket.on(\"error\", onError);\n socket.on(\"end\", onEnd);\n socket.on(\"close\", onClose);\n socket.on(\"timeout\", onTimeout);\n });\n}\n\nexport async function sendRconCommand(\n profile: ResolvedRconProfile,\n command: string,\n): Promise<string> {\n const socket = new Socket();\n socket.setTimeout(profile.timeoutMs);\n await new Promise<void>((resolvePromise, reject) => {\n const timer = setTimeout(() => {\n cleanup();\n socket.destroy();\n reject(new Error(\"RCON connection timed out\"));\n }, profile.timeoutMs);\n const cleanup = () => {\n clearTimeout(timer);\n socket.off(\"error\", onError);\n };\n const onError = (error: Error) => {\n cleanup();\n reject(error);\n };\n socket.once(\"error\", onError);\n socket.connect(profile.port, profile.host, () => {\n cleanup();\n resolvePromise();\n });\n });\n try {\n const authPromise = readPacketsUntil(\n socket,\n profile.timeoutMs,\n (packet) => packet.id === 1 || packet.id === -1,\n );\n writePacket(socket, 1, 3, profile.password);\n const authPackets = await authPromise;\n if (authPackets.some((packet) => packet.id === -1)) {\n throw new Error(\"RCON authentication failed\");\n }\n\n const responsePromise = readPacketsUntil(\n socket,\n profile.timeoutMs,\n (packet) => packet.id === 3,\n );\n writePacket(socket, 2, 2, command);\n writePacket(socket, 3, 2, \"\");\n const responsePackets = await responsePromise;\n return responsePackets\n .filter((packet) => packet.id === 2)\n .map((packet) => packet.body)\n .join(\"\");\n } finally {\n socket.end();\n }\n}\n\nexport async function runRconCommand(\n options: RconCommandOptions,\n transport: RconTransport = sendRconCommand,\n): Promise<RconCommandResult> {\n const resolved = resolveRconProfile(options);\n if (!resolved.profile) {\n throw new Error(resolved.status.message);\n }\n const command = normalizeRconCommand(options.command);\n if (!command) {\n throw new Error(\"RCON command must not be empty\");\n }\n const permissionDecision = evaluateRconPermission(command, resolved.profile.permissions);\n if (!permissionDecision.allowed) {\n return {\n profile: resolved.profile.profile,\n host: resolved.profile.host,\n port: resolved.profile.port,\n command,\n response: \"\",\n permissionDecision,\n };\n }\n const profile = options.timeoutMs\n ? { ...resolved.profile, timeoutMs: options.timeoutMs }\n : resolved.profile;\n return {\n profile: profile.profile,\n host: profile.host,\n port: profile.port,\n command,\n response: await transport(profile, command),\n permissionDecision,\n };\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@minecraft-skills/rcon",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "RCON configuration, permissions, and execution utilities for Minecraft Skills.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"minecraft",
|
|
9
|
+
"rcon",
|
|
10
|
+
"ai"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/sya-ri/minecraft-skills.git",
|
|
15
|
+
"directory": "packages/rcon"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/sya-ri/minecraft-skills/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/sya-ri/minecraft-skills#readme",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22.12"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"default": "./dist/index.mjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"files": [
|
|
35
|
+
"dist"
|
|
36
|
+
],
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "26.0.0",
|
|
39
|
+
"@typescript/native-preview": "7.0.0-dev.20260621.1",
|
|
40
|
+
"tsdown": "0.22.3",
|
|
41
|
+
"typescript": "6.0.3",
|
|
42
|
+
"vitest": "4.1.9"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsdown",
|
|
46
|
+
"typecheck": "tsgo -p tsconfig.json --noEmit",
|
|
47
|
+
"typecheck:tsc": "tsc -p tsconfig.json --noEmit",
|
|
48
|
+
"test": "vitest run"
|
|
49
|
+
}
|
|
50
|
+
}
|