@dimzxzzx07/mc-headless 1.6.0 → 1.8.0
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/README.md +316 -390
- package/dist/core/JavaChecker.d.ts +2 -2
- package/dist/core/JavaChecker.d.ts.map +1 -1
- package/dist/core/JavaChecker.js +63 -21
- package/dist/core/JavaChecker.js.map +1 -1
- package/dist/core/MinecraftServer.d.ts +46 -0
- package/dist/core/MinecraftServer.d.ts.map +1 -1
- package/dist/core/MinecraftServer.js +599 -7
- package/dist/core/MinecraftServer.js.map +1 -1
- package/dist/platforms/ViaVersion.d.ts +7 -4
- package/dist/platforms/ViaVersion.d.ts.map +1 -1
- package/dist/platforms/ViaVersion.js +108 -124
- package/dist/platforms/ViaVersion.js.map +1 -1
- package/package.json +1 -1
- package/src/core/JavaChecker.ts +59 -24
- package/src/core/MinecraftServer.ts +685 -8
- package/src/platforms/ViaVersion.ts +119 -134
- package/src/scripts/install-java.sh +97 -32
|
@@ -53,6 +53,7 @@ const FabricEngine_1 = require("../engines/FabricEngine");
|
|
|
53
53
|
const GeyserBridge_1 = require("../platforms/GeyserBridge");
|
|
54
54
|
const ViaVersion_1 = require("../platforms/ViaVersion");
|
|
55
55
|
const path = __importStar(require("path"));
|
|
56
|
+
const fs = __importStar(require("fs-extra"));
|
|
56
57
|
class MinecraftServer extends events_1.EventEmitter {
|
|
57
58
|
config;
|
|
58
59
|
options;
|
|
@@ -65,11 +66,44 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
65
66
|
players = new Map();
|
|
66
67
|
backupCron = null;
|
|
67
68
|
startTime = null;
|
|
69
|
+
memoryMonitorInterval = null;
|
|
70
|
+
memoryUsageHistory = [];
|
|
71
|
+
worldSize = 0;
|
|
72
|
+
playerCount = 0;
|
|
73
|
+
javaCommand = 'java';
|
|
74
|
+
owners = new Set();
|
|
75
|
+
ownerCommandPrefix = '!';
|
|
68
76
|
constructor(userConfig = {}) {
|
|
69
77
|
super();
|
|
70
78
|
this.logger = Logger_1.Logger.getInstance();
|
|
71
79
|
this.logger.banner();
|
|
72
|
-
this.options =
|
|
80
|
+
this.options = {
|
|
81
|
+
javaVersion: 'auto',
|
|
82
|
+
memoryMonitor: {
|
|
83
|
+
enabled: true,
|
|
84
|
+
threshold: 90,
|
|
85
|
+
interval: 10000,
|
|
86
|
+
action: 'restart'
|
|
87
|
+
},
|
|
88
|
+
autoInstallJava: true,
|
|
89
|
+
networkOptimization: {
|
|
90
|
+
tcpFastOpen: true,
|
|
91
|
+
bungeeMode: false,
|
|
92
|
+
proxyProtocol: false
|
|
93
|
+
},
|
|
94
|
+
owners: [],
|
|
95
|
+
ownerCommands: {
|
|
96
|
+
prefix: '!',
|
|
97
|
+
enabled: true
|
|
98
|
+
},
|
|
99
|
+
...userConfig
|
|
100
|
+
};
|
|
101
|
+
if (this.options.owners) {
|
|
102
|
+
this.options.owners.forEach(owner => this.owners.add(owner.toLowerCase()));
|
|
103
|
+
}
|
|
104
|
+
if (this.options.ownerCommands?.prefix) {
|
|
105
|
+
this.ownerCommandPrefix = this.options.ownerCommands.prefix;
|
|
106
|
+
}
|
|
73
107
|
const handler = new ConfigHandler_1.ConfigHandler(userConfig);
|
|
74
108
|
this.config = handler.getConfig();
|
|
75
109
|
this.engine = this.createEngine();
|
|
@@ -106,13 +140,478 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
106
140
|
throw new Error(`Unsupported server type: ${this.config.type}`);
|
|
107
141
|
}
|
|
108
142
|
}
|
|
143
|
+
async detectJavaVersion() {
|
|
144
|
+
try {
|
|
145
|
+
const output = (0, child_process_1.execSync)('java -version 2>&1').toString();
|
|
146
|
+
if (output.includes('version "21')) {
|
|
147
|
+
return '21';
|
|
148
|
+
}
|
|
149
|
+
else if (output.includes('version "17')) {
|
|
150
|
+
return '17';
|
|
151
|
+
}
|
|
152
|
+
else if (output.includes('version "11')) {
|
|
153
|
+
return '11';
|
|
154
|
+
}
|
|
155
|
+
else if (output.includes('version "8')) {
|
|
156
|
+
return '8';
|
|
157
|
+
}
|
|
158
|
+
return 'unknown';
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return 'none';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async ensureJava() {
|
|
165
|
+
if (!this.options.autoInstallJava) {
|
|
166
|
+
await JavaChecker_1.JavaChecker.ensureJava();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const hasJava = await JavaChecker_1.JavaChecker.checkJava();
|
|
170
|
+
if (!hasJava) {
|
|
171
|
+
this.logger.info('Java not found, attempting to install...');
|
|
172
|
+
const osType = SystemDetector_1.SystemDetector.getOS();
|
|
173
|
+
const distro = SystemDetector_1.SystemDetector.getDistro();
|
|
174
|
+
const targetVersion = this.options.javaVersion === 'auto' ? '17' : this.options.javaVersion || '17';
|
|
175
|
+
if (osType === 'linux' || osType === 'android') {
|
|
176
|
+
await this.installJavaLinux(distro, targetVersion);
|
|
177
|
+
}
|
|
178
|
+
else if (osType === 'darwin') {
|
|
179
|
+
await this.installJavaMac(targetVersion);
|
|
180
|
+
}
|
|
181
|
+
else if (osType === 'windows') {
|
|
182
|
+
this.logger.error('Windows detected. Please install Java manually from https://adoptium.net');
|
|
183
|
+
throw new Error('Java not installed');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const detectedVersion = await this.detectJavaVersion();
|
|
188
|
+
this.logger.info(`Detected Java version: ${detectedVersion}`);
|
|
189
|
+
if (this.options.javaVersion !== 'auto' && detectedVersion !== this.options.javaVersion) {
|
|
190
|
+
this.logger.warning(`Server configured for Java ${this.options.javaVersion} but detected ${detectedVersion}. This may cause issues.`);
|
|
191
|
+
}
|
|
192
|
+
if (detectedVersion === '21' || detectedVersion === '17') {
|
|
193
|
+
this.javaCommand = 'java';
|
|
194
|
+
this.logger.success(`Using Java ${detectedVersion}`);
|
|
195
|
+
}
|
|
196
|
+
else if (detectedVersion === '11' || detectedVersion === '8') {
|
|
197
|
+
this.logger.warning(`Java ${detectedVersion} is too old. Minecraft 1.21+ requires Java 17 or 21.`);
|
|
198
|
+
this.logger.info('Attempting to install Java 17...');
|
|
199
|
+
const distro = SystemDetector_1.SystemDetector.getDistro();
|
|
200
|
+
await this.installJavaLinux(distro, '17');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const finalVersion = await this.detectJavaVersion();
|
|
204
|
+
this.logger.success(`Java version: ${finalVersion}`);
|
|
205
|
+
}
|
|
206
|
+
async installJavaLinux(distro, version) {
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
let command = '';
|
|
209
|
+
const javaPackage = version === '21' ? 'openjdk-21-jre-headless' : 'openjdk-17-jre-headless';
|
|
210
|
+
if (distro === 'ubuntu' || distro === 'debian') {
|
|
211
|
+
command = `apt update && apt install -y ${javaPackage}`;
|
|
212
|
+
}
|
|
213
|
+
else if (distro === 'centos' || distro === 'fedora') {
|
|
214
|
+
command = `yum install -y java-${version}-openjdk-headless`;
|
|
215
|
+
}
|
|
216
|
+
else if (distro === 'arch') {
|
|
217
|
+
const archPackage = version === '21' ? 'jre21-openjdk-headless' : 'jre17-openjdk-headless';
|
|
218
|
+
command = `pacman -S --noconfirm ${archPackage}`;
|
|
219
|
+
}
|
|
220
|
+
else if (distro === 'termux') {
|
|
221
|
+
command = 'pkg install -y openjdk-17';
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
this.logger.error('Unsupported Linux distribution. Please install Java manually.');
|
|
225
|
+
reject(new Error('Unsupported distribution'));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
this.logger.info(`Installing Java ${version} with: ${command}`);
|
|
229
|
+
const install = (0, child_process_1.exec)(command, (error) => {
|
|
230
|
+
if (error) {
|
|
231
|
+
reject(error);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
resolve();
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
if (install.stdout) {
|
|
238
|
+
install.stdout.pipe(process.stdout);
|
|
239
|
+
}
|
|
240
|
+
if (install.stderr) {
|
|
241
|
+
install.stderr.pipe(process.stderr);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async installJavaMac(version) {
|
|
246
|
+
return new Promise((resolve, reject) => {
|
|
247
|
+
const command = `brew install openjdk@${version}`;
|
|
248
|
+
this.logger.info(`Installing Java ${version} with: ${command}`);
|
|
249
|
+
const install = (0, child_process_1.exec)(command, (error) => {
|
|
250
|
+
if (error) {
|
|
251
|
+
reject(error);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
resolve();
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
if (install.stdout) {
|
|
258
|
+
install.stdout.pipe(process.stdout);
|
|
259
|
+
}
|
|
260
|
+
if (install.stderr) {
|
|
261
|
+
install.stderr.pipe(process.stderr);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
async calculateWorldSize() {
|
|
266
|
+
try {
|
|
267
|
+
const worldPath = path.join(process.cwd(), this.config.folders.world);
|
|
268
|
+
if (!await fs.pathExists(worldPath))
|
|
269
|
+
return 0;
|
|
270
|
+
const getSize = async (dir) => {
|
|
271
|
+
let total = 0;
|
|
272
|
+
const files = await fs.readdir(dir);
|
|
273
|
+
for (const file of files) {
|
|
274
|
+
const filePath = path.join(dir, file);
|
|
275
|
+
const stat = await fs.stat(filePath);
|
|
276
|
+
if (stat.isDirectory()) {
|
|
277
|
+
total += await getSize(filePath);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
total += stat.size;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return total;
|
|
284
|
+
};
|
|
285
|
+
return await getSize(worldPath);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return 0;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
buildJavaArgs() {
|
|
292
|
+
if (this.options.customJavaArgs && this.options.customJavaArgs.length > 0) {
|
|
293
|
+
return this.options.customJavaArgs;
|
|
294
|
+
}
|
|
295
|
+
const memMax = this.parseMemory(this.config.memory.max);
|
|
296
|
+
const javaVersion = this.options.javaVersion || 'auto';
|
|
297
|
+
let gcArgs = [];
|
|
298
|
+
if (memMax >= 16384) {
|
|
299
|
+
gcArgs = [
|
|
300
|
+
'-XX:+UseG1GC',
|
|
301
|
+
'-XX:+ParallelRefProcEnabled',
|
|
302
|
+
'-XX:MaxGCPauseMillis=100',
|
|
303
|
+
'-XX:+UnlockExperimentalVMOptions',
|
|
304
|
+
'-XX:+DisableExplicitGC',
|
|
305
|
+
'-XX:+AlwaysPreTouch',
|
|
306
|
+
'-XX:G1NewSizePercent=40',
|
|
307
|
+
'-XX:G1MaxNewSizePercent=50',
|
|
308
|
+
'-XX:G1HeapRegionSize=16M',
|
|
309
|
+
'-XX:G1ReservePercent=15',
|
|
310
|
+
'-XX:G1HeapWastePercent=5',
|
|
311
|
+
'-XX:G1MixedGCCountTarget=4',
|
|
312
|
+
'-XX:InitiatingHeapOccupancyPercent=20',
|
|
313
|
+
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
314
|
+
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
315
|
+
'-XX:SurvivorRatio=32',
|
|
316
|
+
'-XX:+PerfDisableSharedMem',
|
|
317
|
+
'-XX:MaxTenuringThreshold=1'
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
else if (memMax >= 8192) {
|
|
321
|
+
gcArgs = [
|
|
322
|
+
'-XX:+UseG1GC',
|
|
323
|
+
'-XX:+ParallelRefProcEnabled',
|
|
324
|
+
'-XX:MaxGCPauseMillis=150',
|
|
325
|
+
'-XX:+UnlockExperimentalVMOptions',
|
|
326
|
+
'-XX:+DisableExplicitGC',
|
|
327
|
+
'-XX:+AlwaysPreTouch',
|
|
328
|
+
'-XX:G1NewSizePercent=30',
|
|
329
|
+
'-XX:G1MaxNewSizePercent=40',
|
|
330
|
+
'-XX:G1HeapRegionSize=8M',
|
|
331
|
+
'-XX:G1ReservePercent=10',
|
|
332
|
+
'-XX:G1HeapWastePercent=5',
|
|
333
|
+
'-XX:G1MixedGCCountTarget=4',
|
|
334
|
+
'-XX:InitiatingHeapOccupancyPercent=15',
|
|
335
|
+
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
336
|
+
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
337
|
+
'-XX:SurvivorRatio=32',
|
|
338
|
+
'-XX:+PerfDisableSharedMem',
|
|
339
|
+
'-XX:MaxTenuringThreshold=1'
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
gcArgs = this.config.memory.useAikarsFlags ? [
|
|
344
|
+
'-XX:+UseG1GC',
|
|
345
|
+
'-XX:+ParallelRefProcEnabled',
|
|
346
|
+
'-XX:MaxGCPauseMillis=200',
|
|
347
|
+
'-XX:+UnlockExperimentalVMOptions',
|
|
348
|
+
'-XX:+DisableExplicitGC',
|
|
349
|
+
'-XX:+AlwaysPreTouch',
|
|
350
|
+
'-XX:G1HeapWastePercent=5',
|
|
351
|
+
'-XX:G1MixedGCCountTarget=4',
|
|
352
|
+
'-XX:InitiatingHeapOccupancyPercent=15',
|
|
353
|
+
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
354
|
+
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
355
|
+
'-XX:SurvivorRatio=32',
|
|
356
|
+
'-XX:+PerfDisableSharedMem',
|
|
357
|
+
'-XX:MaxTenuringThreshold=1',
|
|
358
|
+
'-Dusing.aikars.flags=https://mcflags.emc.gs',
|
|
359
|
+
'-Daikars.new.flags=true'
|
|
360
|
+
] : [];
|
|
361
|
+
}
|
|
362
|
+
if (javaVersion === '21') {
|
|
363
|
+
gcArgs.push('--enable-preview');
|
|
364
|
+
}
|
|
365
|
+
const baseArgs = [
|
|
366
|
+
`-Xms${this.config.memory.init}`,
|
|
367
|
+
`-Xmx${this.config.memory.max}`
|
|
368
|
+
];
|
|
369
|
+
return [...baseArgs, ...gcArgs];
|
|
370
|
+
}
|
|
371
|
+
processOwnerCommand(player, command) {
|
|
372
|
+
if (!this.options.ownerCommands?.enabled)
|
|
373
|
+
return;
|
|
374
|
+
if (!this.owners.has(player.toLowerCase()))
|
|
375
|
+
return;
|
|
376
|
+
const cmd = command.toLowerCase().trim();
|
|
377
|
+
const args = cmd.split(' ');
|
|
378
|
+
switch (args[0]) {
|
|
379
|
+
case 'gamemode':
|
|
380
|
+
case 'gm':
|
|
381
|
+
this.handleGamemodeCommand(player, args);
|
|
382
|
+
break;
|
|
383
|
+
case 'tp':
|
|
384
|
+
case 'teleport':
|
|
385
|
+
this.handleTeleportCommand(player, args);
|
|
386
|
+
break;
|
|
387
|
+
case 'give':
|
|
388
|
+
this.handleGiveCommand(player, args);
|
|
389
|
+
break;
|
|
390
|
+
case 'time':
|
|
391
|
+
this.handleTimeCommand(player, args);
|
|
392
|
+
break;
|
|
393
|
+
case 'weather':
|
|
394
|
+
this.handleWeatherCommand(player, args);
|
|
395
|
+
break;
|
|
396
|
+
case 'kill':
|
|
397
|
+
this.handleKillCommand(player, args);
|
|
398
|
+
break;
|
|
399
|
+
case 'ban':
|
|
400
|
+
this.handleBanCommand(player, args);
|
|
401
|
+
break;
|
|
402
|
+
case 'kick':
|
|
403
|
+
this.handleKickCommand(player, args);
|
|
404
|
+
break;
|
|
405
|
+
case 'op':
|
|
406
|
+
this.handleOpCommand(player, args);
|
|
407
|
+
break;
|
|
408
|
+
case 'deop':
|
|
409
|
+
this.handleDeopCommand(player, args);
|
|
410
|
+
break;
|
|
411
|
+
case 'reload':
|
|
412
|
+
this.sendCommand('reload');
|
|
413
|
+
this.logger.info(`${player} reloaded the server`);
|
|
414
|
+
break;
|
|
415
|
+
case 'save':
|
|
416
|
+
this.sendCommand('save-all');
|
|
417
|
+
this.logger.info(`${player} saved the world`);
|
|
418
|
+
break;
|
|
419
|
+
case 'list':
|
|
420
|
+
this.sendCommand('list');
|
|
421
|
+
break;
|
|
422
|
+
case 'help':
|
|
423
|
+
this.sendOwnerHelp(player);
|
|
424
|
+
break;
|
|
425
|
+
default:
|
|
426
|
+
this.sendCommand(command);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
handleGamemodeCommand(player, args) {
|
|
430
|
+
if (args.length < 2) {
|
|
431
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}gamemode <survival|creative|adventure|spectator> [player]","color":"red"}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const gamemode = args[1];
|
|
435
|
+
const target = args.length > 2 ? args[2] : player;
|
|
436
|
+
let gamemodeNum = 0;
|
|
437
|
+
switch (gamemode) {
|
|
438
|
+
case 'survival':
|
|
439
|
+
case '0':
|
|
440
|
+
gamemodeNum = 0;
|
|
441
|
+
break;
|
|
442
|
+
case 'creative':
|
|
443
|
+
case '1':
|
|
444
|
+
gamemodeNum = 1;
|
|
445
|
+
break;
|
|
446
|
+
case 'adventure':
|
|
447
|
+
case '2':
|
|
448
|
+
gamemodeNum = 2;
|
|
449
|
+
break;
|
|
450
|
+
case 'spectator':
|
|
451
|
+
case '3':
|
|
452
|
+
gamemodeNum = 3;
|
|
453
|
+
break;
|
|
454
|
+
default:
|
|
455
|
+
this.sendCommand(`tellraw ${player} {"text":"Invalid gamemode. Use: survival, creative, adventure, spectator","color":"red"}`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
this.sendCommand(`gamemode ${gamemodeNum} ${target}`);
|
|
459
|
+
this.logger.info(`${player} set gamemode of ${target} to ${gamemode}`);
|
|
460
|
+
}
|
|
461
|
+
handleTeleportCommand(player, args) {
|
|
462
|
+
if (args.length < 2) {
|
|
463
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}tp <player> [x y z]","color":"red"}`);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
const target = args[1];
|
|
467
|
+
if (args.length >= 4) {
|
|
468
|
+
const x = args[2];
|
|
469
|
+
const y = args[3];
|
|
470
|
+
const z = args[4] || '0';
|
|
471
|
+
this.sendCommand(`tp ${target} ${x} ${y} ${z}`);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
this.sendCommand(`tp ${player} ${target}`);
|
|
475
|
+
}
|
|
476
|
+
this.logger.info(`${player} teleported to ${target}`);
|
|
477
|
+
}
|
|
478
|
+
handleGiveCommand(player, args) {
|
|
479
|
+
if (args.length < 3) {
|
|
480
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}give <player> <item> [amount]","color":"red"}`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const target = args[1];
|
|
484
|
+
const item = args[2];
|
|
485
|
+
const amount = args.length > 3 ? args[3] : '1';
|
|
486
|
+
this.sendCommand(`give ${target} ${item} ${amount}`);
|
|
487
|
+
this.logger.info(`${player} gave ${amount} x ${item} to ${target}`);
|
|
488
|
+
}
|
|
489
|
+
handleTimeCommand(player, args) {
|
|
490
|
+
if (args.length < 2) {
|
|
491
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}time <set|add|query> <value>","color":"red"}`);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const subCmd = args[1];
|
|
495
|
+
const value = args.length > 2 ? args[2] : '';
|
|
496
|
+
if (subCmd === 'set') {
|
|
497
|
+
if (value === 'day') {
|
|
498
|
+
this.sendCommand('time set day');
|
|
499
|
+
}
|
|
500
|
+
else if (value === 'night') {
|
|
501
|
+
this.sendCommand('time set night');
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
this.sendCommand(`time set ${value}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
else if (subCmd === 'add') {
|
|
508
|
+
this.sendCommand(`time add ${value}`);
|
|
509
|
+
}
|
|
510
|
+
else if (subCmd === 'query') {
|
|
511
|
+
this.sendCommand('time query daytime');
|
|
512
|
+
}
|
|
513
|
+
this.logger.info(`${player} changed time: ${subCmd} ${value}`);
|
|
514
|
+
}
|
|
515
|
+
handleWeatherCommand(player, args) {
|
|
516
|
+
if (args.length < 2) {
|
|
517
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}weather <clear|rain|thunder> [duration]","color":"red"}`);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const weather = args[1];
|
|
521
|
+
const duration = args.length > 2 ? args[2] : '';
|
|
522
|
+
if (weather === 'clear') {
|
|
523
|
+
this.sendCommand('weather clear');
|
|
524
|
+
}
|
|
525
|
+
else if (weather === 'rain') {
|
|
526
|
+
this.sendCommand('weather rain');
|
|
527
|
+
}
|
|
528
|
+
else if (weather === 'thunder') {
|
|
529
|
+
this.sendCommand('weather thunder');
|
|
530
|
+
}
|
|
531
|
+
if (duration) {
|
|
532
|
+
this.sendCommand(`weather ${weather} ${duration}`);
|
|
533
|
+
}
|
|
534
|
+
this.logger.info(`${player} changed weather to ${weather}`);
|
|
535
|
+
}
|
|
536
|
+
handleKillCommand(player, args) {
|
|
537
|
+
const target = args.length > 1 ? args[1] : player;
|
|
538
|
+
this.sendCommand(`kill ${target}`);
|
|
539
|
+
this.logger.info(`${player} killed ${target}`);
|
|
540
|
+
}
|
|
541
|
+
handleBanCommand(player, args) {
|
|
542
|
+
if (args.length < 2) {
|
|
543
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}ban <player> [reason]","color":"red"}`);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const target = args[1];
|
|
547
|
+
const reason = args.slice(2).join(' ') || 'Banned by owner';
|
|
548
|
+
this.sendCommand(`ban ${target} ${reason}`);
|
|
549
|
+
this.logger.info(`${player} banned ${target}: ${reason}`);
|
|
550
|
+
}
|
|
551
|
+
handleKickCommand(player, args) {
|
|
552
|
+
if (args.length < 2) {
|
|
553
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}kick <player> [reason]","color":"red"}`);
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
const target = args[1];
|
|
557
|
+
const reason = args.slice(2).join(' ') || 'Kicked by owner';
|
|
558
|
+
this.sendCommand(`kick ${target} ${reason}`);
|
|
559
|
+
this.logger.info(`${player} kicked ${target}: ${reason}`);
|
|
560
|
+
}
|
|
561
|
+
handleOpCommand(player, args) {
|
|
562
|
+
if (args.length < 2) {
|
|
563
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}op <player>","color":"red"}`);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const target = args[1];
|
|
567
|
+
this.sendCommand(`op ${target}`);
|
|
568
|
+
this.logger.info(`${player} opped ${target}`);
|
|
569
|
+
}
|
|
570
|
+
handleDeopCommand(player, args) {
|
|
571
|
+
if (args.length < 2) {
|
|
572
|
+
this.sendCommand(`tellraw ${player} {"text":"Usage: ${this.ownerCommandPrefix}deop <player>","color":"red"}`);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const target = args[1];
|
|
576
|
+
this.sendCommand(`deop ${target}`);
|
|
577
|
+
this.logger.info(`${player} deopped ${target}`);
|
|
578
|
+
}
|
|
579
|
+
sendOwnerHelp(player) {
|
|
580
|
+
const commands = [
|
|
581
|
+
`{"text":"\\n=== Owner Commands ===\\n","color":"gold","bold":true}`,
|
|
582
|
+
`{"text":"${this.ownerCommandPrefix}gamemode <mode> [player] - Change gamemode\\n","color":"yellow"}`,
|
|
583
|
+
`{"text":"${this.ownerCommandPrefix}tp <player> [x y z] - Teleport\\n","color":"yellow"}`,
|
|
584
|
+
`{"text":"${this.ownerCommandPrefix}give <player> <item> [amount] - Give items\\n","color":"yellow"}`,
|
|
585
|
+
`{"text":"${this.ownerCommandPrefix}time <set|add> <value> - Change time\\n","color":"yellow"}`,
|
|
586
|
+
`{"text":"${this.ownerCommandPrefix}weather <clear|rain|thunder> - Change weather\\n","color":"yellow"}`,
|
|
587
|
+
`{"text":"${this.ownerCommandPrefix}kill [player] - Kill player\\n","color":"yellow"}`,
|
|
588
|
+
`{"text":"${this.ownerCommandPrefix}ban <player> [reason] - Ban player\\n","color":"yellow"}`,
|
|
589
|
+
`{"text":"${this.ownerCommandPrefix}kick <player> [reason] - Kick player\\n","color":"yellow"}`,
|
|
590
|
+
`{"text":"${this.ownerCommandPrefix}op <player> - Give operator\\n","color":"yellow"}`,
|
|
591
|
+
`{"text":"${this.ownerCommandPrefix}deop <player> - Remove operator\\n","color":"yellow"}`,
|
|
592
|
+
`{"text":"${this.ownerCommandPrefix}reload - Reload server\\n","color":"yellow"}`,
|
|
593
|
+
`{"text":"${this.ownerCommandPrefix}save - Save world\\n","color":"yellow"}`,
|
|
594
|
+
`{"text":"${this.ownerCommandPrefix}list - List players\\n","color":"yellow"}`,
|
|
595
|
+
`{"text":"${this.ownerCommandPrefix}help - Show this help\\n","color":"yellow"}`
|
|
596
|
+
];
|
|
597
|
+
commands.forEach(cmd => {
|
|
598
|
+
this.sendCommand(`tellraw ${player} ${cmd}`);
|
|
599
|
+
});
|
|
600
|
+
}
|
|
109
601
|
async start() {
|
|
110
602
|
this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
|
|
111
|
-
|
|
603
|
+
if (this.owners.size > 0) {
|
|
604
|
+
this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
|
|
605
|
+
}
|
|
606
|
+
await this.ensureJava();
|
|
112
607
|
const systemInfo = SystemDetector_1.SystemDetector.getSystemInfo();
|
|
113
608
|
this.logger.debug('System info:', systemInfo);
|
|
114
609
|
const serverDir = process.cwd();
|
|
115
610
|
await FileUtils_1.FileUtils.ensureServerStructure(this.config);
|
|
611
|
+
this.worldSize = await this.calculateWorldSize();
|
|
612
|
+
if (this.worldSize > 10 * 1024 * 1024 * 1024) {
|
|
613
|
+
this.logger.warning(`Large world detected (${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB). Consider increasing memory allocation.`);
|
|
614
|
+
}
|
|
116
615
|
const jarPath = await this.engine.download(this.config, serverDir);
|
|
117
616
|
if (this.config.type === 'forge') {
|
|
118
617
|
await this.engine.prepare(this.config, serverDir, jarPath);
|
|
@@ -136,7 +635,7 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
136
635
|
this.logger.info('ViaRewind will be installed');
|
|
137
636
|
}
|
|
138
637
|
}
|
|
139
|
-
const javaArgs = this.
|
|
638
|
+
const javaArgs = this.buildJavaArgs();
|
|
140
639
|
const serverJar = this.engine.getServerJar(jarPath);
|
|
141
640
|
const serverArgs = this.engine.getServerArgs();
|
|
142
641
|
const fullArgs = [
|
|
@@ -145,10 +644,16 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
145
644
|
serverJar,
|
|
146
645
|
...serverArgs
|
|
147
646
|
];
|
|
148
|
-
this.
|
|
149
|
-
|
|
647
|
+
if (this.options.networkOptimization?.tcpFastOpen) {
|
|
648
|
+
fullArgs.unshift('-Djava.net.preferIPv4Stack=true');
|
|
649
|
+
}
|
|
650
|
+
if (this.options.networkOptimization?.bungeeMode) {
|
|
651
|
+
fullArgs.unshift('-Dnet.kyori.adventure.text.serializer.legacy.AMPMSupport=true');
|
|
652
|
+
}
|
|
653
|
+
this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
|
|
654
|
+
this.process = (0, child_process_1.spawn)(this.javaCommand, fullArgs, {
|
|
150
655
|
cwd: serverDir,
|
|
151
|
-
stdio: 'pipe'
|
|
656
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
152
657
|
});
|
|
153
658
|
this.serverInfo.pid = this.process.pid;
|
|
154
659
|
this.serverInfo.status = 'starting';
|
|
@@ -162,20 +667,43 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
162
667
|
if (this.options.enableViaVersion !== false) {
|
|
163
668
|
this.logger.info('ViaVersion is active - players from older versions can connect');
|
|
164
669
|
}
|
|
670
|
+
if (this.worldSize > 0) {
|
|
671
|
+
this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
|
|
672
|
+
}
|
|
673
|
+
if (this.owners.size > 0) {
|
|
674
|
+
this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
|
|
675
|
+
}
|
|
165
676
|
this.emit('ready', this.serverInfo);
|
|
677
|
+
this.startMemoryMonitor();
|
|
166
678
|
}
|
|
167
679
|
if (output.includes('joined the game')) {
|
|
168
680
|
const match = output.match(/(\w+) joined the game/);
|
|
169
681
|
if (match) {
|
|
682
|
+
this.playerCount++;
|
|
170
683
|
this.handlePlayerJoin(match[1]);
|
|
684
|
+
if (this.owners.has(match[1].toLowerCase())) {
|
|
685
|
+
this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
|
|
686
|
+
}
|
|
171
687
|
}
|
|
172
688
|
}
|
|
173
689
|
if (output.includes('left the game')) {
|
|
174
690
|
const match = output.match(/(\w+) left the game/);
|
|
175
691
|
if (match) {
|
|
692
|
+
this.playerCount--;
|
|
176
693
|
this.handlePlayerLeave(match[1]);
|
|
177
694
|
}
|
|
178
695
|
}
|
|
696
|
+
if (output.includes('<') && output.includes('>')) {
|
|
697
|
+
const chatMatch = output.match(/<(\w+)>\s+(.+)/);
|
|
698
|
+
if (chatMatch) {
|
|
699
|
+
const player = chatMatch[1];
|
|
700
|
+
const message = chatMatch[2];
|
|
701
|
+
if (message.startsWith(this.ownerCommandPrefix)) {
|
|
702
|
+
const command = message.substring(this.ownerCommandPrefix.length);
|
|
703
|
+
this.processOwnerCommand(player, command);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
179
707
|
if (output.includes('[ViaVersion]')) {
|
|
180
708
|
this.logger.debug(`[ViaVersion] ${output.trim()}`);
|
|
181
709
|
}
|
|
@@ -198,6 +726,66 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
198
726
|
}
|
|
199
727
|
return this.serverInfo;
|
|
200
728
|
}
|
|
729
|
+
startMemoryMonitor() {
|
|
730
|
+
if (!this.options.memoryMonitor?.enabled)
|
|
731
|
+
return;
|
|
732
|
+
const threshold = this.options.memoryMonitor.threshold || 90;
|
|
733
|
+
const interval = this.options.memoryMonitor.interval || 10000;
|
|
734
|
+
const action = this.options.memoryMonitor.action || 'warn';
|
|
735
|
+
this.memoryMonitorInterval = setInterval(async () => {
|
|
736
|
+
if (this.serverInfo.status !== 'running' || !this.process)
|
|
737
|
+
return;
|
|
738
|
+
try {
|
|
739
|
+
const stats = await (0, pidusage_1.default)(this.process.pid);
|
|
740
|
+
const memMax = this.parseMemory(this.config.memory.max);
|
|
741
|
+
const memPercent = (stats.memory / (memMax * 1024 * 1024)) * 100;
|
|
742
|
+
this.memoryUsageHistory.push(memPercent);
|
|
743
|
+
if (this.memoryUsageHistory.length > 10) {
|
|
744
|
+
this.memoryUsageHistory.shift();
|
|
745
|
+
}
|
|
746
|
+
if (memPercent > threshold) {
|
|
747
|
+
this.logger.warning(`High memory usage: ${memPercent.toFixed(1)}%`);
|
|
748
|
+
const isIncreasing = this.memoryUsageHistory.length > 5 &&
|
|
749
|
+
this.memoryUsageHistory[this.memoryUsageHistory.length - 1] >
|
|
750
|
+
this.memoryUsageHistory[0] * 1.2;
|
|
751
|
+
if (isIncreasing) {
|
|
752
|
+
this.logger.warning('Memory leak detected!');
|
|
753
|
+
switch (action) {
|
|
754
|
+
case 'restart':
|
|
755
|
+
this.logger.info('Restarting server due to memory leak...');
|
|
756
|
+
await this.gracefulRestart();
|
|
757
|
+
break;
|
|
758
|
+
case 'stop':
|
|
759
|
+
this.logger.info('Stopping server due to memory leak...');
|
|
760
|
+
await this.stop();
|
|
761
|
+
break;
|
|
762
|
+
case 'warn':
|
|
763
|
+
default:
|
|
764
|
+
this.logger.warning('Please restart server to free memory');
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
catch (error) {
|
|
770
|
+
this.logger.error('Memory monitor error:', error);
|
|
771
|
+
}
|
|
772
|
+
}, interval);
|
|
773
|
+
}
|
|
774
|
+
async gracefulRestart() {
|
|
775
|
+
this.logger.info('Initiating graceful restart...');
|
|
776
|
+
this.sendCommand('say Server restarting in 30 seconds');
|
|
777
|
+
this.sendCommand('save-all');
|
|
778
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
779
|
+
this.sendCommand('say Server restarting in 20 seconds');
|
|
780
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
781
|
+
this.sendCommand('say Server restarting in 10 seconds');
|
|
782
|
+
this.sendCommand('save-all');
|
|
783
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
784
|
+
this.sendCommand('say Server restarting in 5 seconds');
|
|
785
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
786
|
+
await this.stop();
|
|
787
|
+
await this.start();
|
|
788
|
+
}
|
|
201
789
|
async stop() {
|
|
202
790
|
if (!this.process) {
|
|
203
791
|
this.logger.warning('Server not running');
|
|
@@ -205,8 +793,9 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
205
793
|
}
|
|
206
794
|
this.logger.info('Stopping server...');
|
|
207
795
|
this.serverInfo.status = 'stopping';
|
|
796
|
+
this.sendCommand('save-all');
|
|
208
797
|
this.sendCommand('stop');
|
|
209
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
798
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
210
799
|
if (this.process) {
|
|
211
800
|
this.process.kill();
|
|
212
801
|
this.process = null;
|
|
@@ -214,6 +803,9 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
214
803
|
if (this.backupCron) {
|
|
215
804
|
this.backupCron.stop();
|
|
216
805
|
}
|
|
806
|
+
if (this.memoryMonitorInterval) {
|
|
807
|
+
clearInterval(this.memoryMonitorInterval);
|
|
808
|
+
}
|
|
217
809
|
if (this.config.platform === 'all') {
|
|
218
810
|
this.geyser.stop();
|
|
219
811
|
}
|