@dimzxzzx07/mc-headless 2.2.1 → 2.2.2
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 +46 -29
- package/dist/core/JavaChecker.d.ts +8 -2
- package/dist/core/JavaChecker.d.ts.map +1 -1
- package/dist/core/JavaChecker.js +219 -104
- package/dist/core/JavaChecker.js.map +1 -1
- package/dist/core/MinecraftServer.d.ts +11 -1
- package/dist/core/MinecraftServer.d.ts.map +1 -1
- package/dist/core/MinecraftServer.js +337 -158
- package/dist/core/MinecraftServer.js.map +1 -1
- package/package.json +1 -1
- package/src/core/JavaChecker.ts +224 -108
- package/src/core/MinecraftServer.ts +402 -201
|
@@ -37,6 +37,8 @@ exports.MinecraftServer = void 0;
|
|
|
37
37
|
const events_1 = require("events");
|
|
38
38
|
const child_process_1 = require("child_process");
|
|
39
39
|
const cron = __importStar(require("node-cron"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const fs = __importStar(require("fs-extra"));
|
|
40
42
|
const ConfigHandler_1 = require("./ConfigHandler");
|
|
41
43
|
const JavaChecker_1 = require("./JavaChecker");
|
|
42
44
|
const FileUtils_1 = require("../utils/FileUtils");
|
|
@@ -49,8 +51,6 @@ const FabricEngine_1 = require("../engines/FabricEngine");
|
|
|
49
51
|
const GeyserBridge_1 = require("../platforms/GeyserBridge");
|
|
50
52
|
const ViaVersion_1 = require("../platforms/ViaVersion");
|
|
51
53
|
const SkinRestorer_1 = require("../platforms/SkinRestorer");
|
|
52
|
-
const path = __importStar(require("path"));
|
|
53
|
-
const fs = __importStar(require("fs-extra"));
|
|
54
54
|
class MinecraftServer extends events_1.EventEmitter {
|
|
55
55
|
config;
|
|
56
56
|
options;
|
|
@@ -63,6 +63,7 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
63
63
|
serverInfo;
|
|
64
64
|
players = new Map();
|
|
65
65
|
backupCron = null;
|
|
66
|
+
cleanLogsCron = null;
|
|
66
67
|
startTime = null;
|
|
67
68
|
memoryMonitorInterval = null;
|
|
68
69
|
statsInterval = null;
|
|
@@ -78,13 +79,15 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
78
79
|
cpuUsage = 0;
|
|
79
80
|
cgroupMemory = 0;
|
|
80
81
|
cgroupCpu = 0;
|
|
82
|
+
isPterodactyl = false;
|
|
81
83
|
constructor(userConfig = {}) {
|
|
82
84
|
super();
|
|
83
85
|
this.logger = Logger_1.Logger.getInstance();
|
|
84
86
|
this.logger.banner();
|
|
87
|
+
this.detectEnvironment();
|
|
85
88
|
this.options = {
|
|
86
89
|
javaVersion: 'auto',
|
|
87
|
-
usePortableJava:
|
|
90
|
+
usePortableJava: !this.isPterodactyl,
|
|
88
91
|
memoryMonitor: {
|
|
89
92
|
enabled: true,
|
|
90
93
|
threshold: 90,
|
|
@@ -104,6 +107,8 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
104
107
|
},
|
|
105
108
|
silentMode: true,
|
|
106
109
|
statsInterval: 30000,
|
|
110
|
+
cleanLogsInterval: 3 * 60 * 60 * 1000,
|
|
111
|
+
optimizeForPterodactyl: true,
|
|
107
112
|
...userConfig
|
|
108
113
|
};
|
|
109
114
|
if (this.options.owners) {
|
|
@@ -114,6 +119,9 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
114
119
|
}
|
|
115
120
|
const handler = new ConfigHandler_1.ConfigHandler(userConfig);
|
|
116
121
|
this.config = handler.getConfig();
|
|
122
|
+
if (this.isPterodactyl && this.options.optimizeForPterodactyl) {
|
|
123
|
+
this.optimizeForPterodactylPanel();
|
|
124
|
+
}
|
|
117
125
|
this.engine = this.createEngine();
|
|
118
126
|
this.geyser = new GeyserBridge_1.GeyserBridge();
|
|
119
127
|
this.viaVersion = new ViaVersion_1.ViaVersionManager();
|
|
@@ -134,6 +142,166 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
134
142
|
status: 'stopped'
|
|
135
143
|
};
|
|
136
144
|
this.detectCgroupLimits();
|
|
145
|
+
this.cleanPaperCache();
|
|
146
|
+
}
|
|
147
|
+
detectEnvironment() {
|
|
148
|
+
this.isPterodactyl = !!(process.env.PTERODACTYL ||
|
|
149
|
+
process.env.SERVER_PORT ||
|
|
150
|
+
process.env.SERVER_MEMORY_MAX ||
|
|
151
|
+
fs.existsSync('/home/container'));
|
|
152
|
+
if (this.isPterodactyl) {
|
|
153
|
+
this.logger.info('Pterodactyl environment detected');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
optimizeForPterodactylPanel() {
|
|
157
|
+
const pterodactylPort = process.env.SERVER_PORT;
|
|
158
|
+
if (pterodactylPort) {
|
|
159
|
+
this.config.network.port = parseInt(pterodactylPort);
|
|
160
|
+
this.logger.info(`Using Pterodactyl port: ${pterodactylPort}`);
|
|
161
|
+
}
|
|
162
|
+
const pterodactylMemory = process.env.SERVER_MEMORY_MAX;
|
|
163
|
+
if (pterodactylMemory) {
|
|
164
|
+
this.config.memory.max = pterodactylMemory;
|
|
165
|
+
this.logger.info(`Using Pterodactyl memory: ${pterodactylMemory}`);
|
|
166
|
+
}
|
|
167
|
+
const pterodactylInitMemory = process.env.SERVER_MEMORY_INIT;
|
|
168
|
+
if (pterodactylInitMemory) {
|
|
169
|
+
this.config.memory.init = pterodactylInitMemory;
|
|
170
|
+
}
|
|
171
|
+
this.config.world.viewDistance = 6;
|
|
172
|
+
this.config.world.simulationDistance = 4;
|
|
173
|
+
this.logger.info('Applied Pterodactyl optimizations');
|
|
174
|
+
}
|
|
175
|
+
cleanPaperCache() {
|
|
176
|
+
const cachePaths = [
|
|
177
|
+
path.join(process.cwd(), 'plugins', '.paper-remapped'),
|
|
178
|
+
path.join(process.cwd(), 'plugins', '.paper-remapped', 'geyser.jar'),
|
|
179
|
+
path.join(process.cwd(), 'plugins', '.paper-remapped', 'ViaVersion.jar'),
|
|
180
|
+
path.join(process.cwd(), 'cache', 'paper-remapped')
|
|
181
|
+
];
|
|
182
|
+
cachePaths.forEach(cachePath => {
|
|
183
|
+
if (fs.existsSync(cachePath)) {
|
|
184
|
+
this.logger.info(`Cleaning corrupt cache: ${cachePath}`);
|
|
185
|
+
try {
|
|
186
|
+
fs.rmSync(cachePath, { recursive: true, force: true });
|
|
187
|
+
this.logger.debug(`Cache cleaned: ${cachePath}`);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
this.logger.warning(`Failed to clean cache: ${err.message}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
cleanOldLogs() {
|
|
196
|
+
const logsDir = path.join(process.cwd(), 'logs');
|
|
197
|
+
if (!fs.existsSync(logsDir))
|
|
198
|
+
return;
|
|
199
|
+
try {
|
|
200
|
+
const files = fs.readdirSync(logsDir);
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const threeHours = 3 * 60 * 60 * 1000;
|
|
203
|
+
files.forEach(file => {
|
|
204
|
+
const filePath = path.join(logsDir, file);
|
|
205
|
+
const stats = fs.statSync(filePath);
|
|
206
|
+
if (now - stats.mtimeMs > threeHours) {
|
|
207
|
+
fs.unlinkSync(filePath);
|
|
208
|
+
this.logger.debug(`Cleaned old log: ${file}`);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
this.logger.info('Old logs cleaned');
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
this.logger.warning(`Failed to clean logs: ${err.message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
generateOptimizedConfigs() {
|
|
218
|
+
const serverDir = process.cwd();
|
|
219
|
+
const paperGlobalYml = path.join(serverDir, 'paper-global.yml');
|
|
220
|
+
if (!fs.existsSync(paperGlobalYml)) {
|
|
221
|
+
const paperGlobalConfig = `# Paper Global Configuration
|
|
222
|
+
# Optimized by MC-Headless
|
|
223
|
+
|
|
224
|
+
chunk-loading:
|
|
225
|
+
autoconfig-send-distance: false
|
|
226
|
+
enable-frustum-priority: false
|
|
227
|
+
global-max-chunk-load-rate: 100
|
|
228
|
+
global-max-chunk-send-rate: 50
|
|
229
|
+
|
|
230
|
+
entities:
|
|
231
|
+
despawn-ranges:
|
|
232
|
+
monster:
|
|
233
|
+
soft: 28
|
|
234
|
+
hard: 96
|
|
235
|
+
animal:
|
|
236
|
+
soft: 16
|
|
237
|
+
hard: 32
|
|
238
|
+
spawn-limits:
|
|
239
|
+
ambient: 5
|
|
240
|
+
axolotls: 5
|
|
241
|
+
creature: 10
|
|
242
|
+
monster: 15
|
|
243
|
+
water_ambient: 5
|
|
244
|
+
water_creature: 5
|
|
245
|
+
|
|
246
|
+
misc:
|
|
247
|
+
max-entity-speed: 100
|
|
248
|
+
redstone-implementation: ALTERNATIVE_CURRENT
|
|
249
|
+
|
|
250
|
+
player-auto-save: 6000
|
|
251
|
+
player-auto-save-rate: 1200
|
|
252
|
+
no-tick-view-distance: 12
|
|
253
|
+
`;
|
|
254
|
+
fs.writeFileSync(paperGlobalYml, paperGlobalConfig);
|
|
255
|
+
this.logger.debug('Generated paper-global.yml');
|
|
256
|
+
}
|
|
257
|
+
const paperWorldDefaultsYml = path.join(serverDir, 'paper-world-defaults.yml');
|
|
258
|
+
if (!fs.existsSync(paperWorldDefaultsYml)) {
|
|
259
|
+
const paperWorldConfig = `# Paper World Defaults
|
|
260
|
+
# Optimized by MC-Headless
|
|
261
|
+
|
|
262
|
+
view-distance: 6
|
|
263
|
+
no-tick-view-distance: 12
|
|
264
|
+
|
|
265
|
+
despawn-ranges:
|
|
266
|
+
monster:
|
|
267
|
+
soft: 28
|
|
268
|
+
hard: 96
|
|
269
|
+
animal:
|
|
270
|
+
soft: 16
|
|
271
|
+
hard: 32
|
|
272
|
+
ambient:
|
|
273
|
+
soft: 16
|
|
274
|
+
hard: 32
|
|
275
|
+
`;
|
|
276
|
+
fs.writeFileSync(paperWorldDefaultsYml, paperWorldConfig);
|
|
277
|
+
this.logger.debug('Generated paper-world-defaults.yml');
|
|
278
|
+
}
|
|
279
|
+
const spigotYml = path.join(serverDir, 'spigot.yml');
|
|
280
|
+
if (!fs.existsSync(spigotYml)) {
|
|
281
|
+
const spigotConfig = `# Spigot Configuration
|
|
282
|
+
# Optimized by MC-Headless
|
|
283
|
+
|
|
284
|
+
entity-activation-range:
|
|
285
|
+
animals: 16
|
|
286
|
+
monsters: 24
|
|
287
|
+
misc: 8
|
|
288
|
+
water: 8
|
|
289
|
+
|
|
290
|
+
mob-spawn-range: 4
|
|
291
|
+
|
|
292
|
+
world-settings:
|
|
293
|
+
default:
|
|
294
|
+
view-distance: 6
|
|
295
|
+
mob-spawn-range: 4
|
|
296
|
+
entity-activation-range:
|
|
297
|
+
animals: 16
|
|
298
|
+
monsters: 24
|
|
299
|
+
misc: 8
|
|
300
|
+
water: 8
|
|
301
|
+
`;
|
|
302
|
+
fs.writeFileSync(spigotYml, spigotConfig);
|
|
303
|
+
this.logger.debug('Generated spigot.yml');
|
|
304
|
+
}
|
|
137
305
|
}
|
|
138
306
|
detectCgroupLimits() {
|
|
139
307
|
try {
|
|
@@ -177,10 +345,14 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
177
345
|
}
|
|
178
346
|
const memMax = this.parseMemory(this.config.memory.max);
|
|
179
347
|
const javaVersion = this.options.javaVersion || 'auto';
|
|
348
|
+
const baseArgs = [
|
|
349
|
+
`-Xms${this.config.memory.init}`,
|
|
350
|
+
`-Xmx${this.config.memory.max}`,
|
|
351
|
+
'-XX:+UseG1GC'
|
|
352
|
+
];
|
|
180
353
|
let gcArgs = [];
|
|
181
354
|
if (memMax >= 16384) {
|
|
182
355
|
gcArgs = [
|
|
183
|
-
'-XX:+UseG1GC',
|
|
184
356
|
'-XX:+ParallelRefProcEnabled',
|
|
185
357
|
'-XX:MaxGCPauseMillis=100',
|
|
186
358
|
'-XX:+UnlockExperimentalVMOptions',
|
|
@@ -202,7 +374,6 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
202
374
|
}
|
|
203
375
|
else if (memMax >= 8192) {
|
|
204
376
|
gcArgs = [
|
|
205
|
-
'-XX:+UseG1GC',
|
|
206
377
|
'-XX:+ParallelRefProcEnabled',
|
|
207
378
|
'-XX:MaxGCPauseMillis=150',
|
|
208
379
|
'-XX:+UnlockExperimentalVMOptions',
|
|
@@ -224,7 +395,6 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
224
395
|
}
|
|
225
396
|
else {
|
|
226
397
|
gcArgs = this.config.memory.useAikarsFlags ? [
|
|
227
|
-
'-XX:+UseG1GC',
|
|
228
398
|
'-XX:+ParallelRefProcEnabled',
|
|
229
399
|
'-XX:MaxGCPauseMillis=200',
|
|
230
400
|
'-XX:+UnlockExperimentalVMOptions',
|
|
@@ -245,10 +415,6 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
245
415
|
if (javaVersion === '21') {
|
|
246
416
|
gcArgs.push('--enable-preview');
|
|
247
417
|
}
|
|
248
|
-
const baseArgs = [
|
|
249
|
-
`-Xms${this.config.memory.init}`,
|
|
250
|
-
`-Xmx${this.config.memory.max}`
|
|
251
|
-
];
|
|
252
418
|
return [...baseArgs, ...gcArgs];
|
|
253
419
|
}
|
|
254
420
|
buildEnvironment() {
|
|
@@ -268,6 +434,163 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
268
434
|
}
|
|
269
435
|
return env;
|
|
270
436
|
}
|
|
437
|
+
async start() {
|
|
438
|
+
this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
|
|
439
|
+
this.cleanPaperCache();
|
|
440
|
+
this.generateOptimizedConfigs();
|
|
441
|
+
if (this.options.cleanLogsInterval && this.options.cleanLogsInterval > 0) {
|
|
442
|
+
this.cleanLogsCron = cron.schedule('0 */3 * * *', () => this.cleanOldLogs());
|
|
443
|
+
this.logger.info('Log cleanup scheduled every 3 hours');
|
|
444
|
+
}
|
|
445
|
+
if (this.owners.size > 0) {
|
|
446
|
+
this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
|
|
447
|
+
}
|
|
448
|
+
this.javaInfo = await JavaChecker_1.JavaChecker.ensureJava(this.config.version, this.options.usePortableJava || false);
|
|
449
|
+
this.javaCommand = this.javaInfo.path;
|
|
450
|
+
this.logger.success(`Using Java ${this.javaInfo.version} (${this.javaInfo.type}) at ${this.javaInfo.path}`);
|
|
451
|
+
const systemInfo = SystemDetector_1.SystemDetector.getSystemInfo();
|
|
452
|
+
this.logger.debug('System info:', systemInfo);
|
|
453
|
+
const serverDir = process.cwd();
|
|
454
|
+
await FileUtils_1.FileUtils.ensureServerStructure(this.config);
|
|
455
|
+
this.worldSize = await this.calculateWorldSize();
|
|
456
|
+
if (this.worldSize > 10 * 1024 * 1024 * 1024) {
|
|
457
|
+
this.logger.warning(`Large world detected (${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB). Consider increasing memory allocation.`);
|
|
458
|
+
}
|
|
459
|
+
const jarPath = await this.engine.download(this.config, serverDir);
|
|
460
|
+
if (this.config.type === 'forge') {
|
|
461
|
+
await this.engine.prepare(this.config, serverDir, jarPath);
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
await this.engine.prepare(this.config, serverDir);
|
|
465
|
+
}
|
|
466
|
+
if (this.config.platform === 'all') {
|
|
467
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
468
|
+
await this.geyser.setup(this.config);
|
|
469
|
+
}
|
|
470
|
+
if (this.options.enableViaVersion !== false) {
|
|
471
|
+
this.logger.info('Enabling ViaVersion for client version compatibility...');
|
|
472
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
473
|
+
await this.viaVersion.setup(this.config);
|
|
474
|
+
await this.viaVersion.configureViaVersion(this.config);
|
|
475
|
+
if (this.options.enableViaBackwards !== false) {
|
|
476
|
+
this.logger.info('ViaBackwards will be installed');
|
|
477
|
+
}
|
|
478
|
+
if (this.options.enableViaRewind !== false) {
|
|
479
|
+
this.logger.info('ViaRewind will be installed');
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (this.options.enableSkinRestorer !== false) {
|
|
483
|
+
this.logger.info('Enabling SkinRestorer for player skins...');
|
|
484
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
485
|
+
await this.skinRestorer.setup(this.config);
|
|
486
|
+
}
|
|
487
|
+
const javaArgs = this.buildJavaArgs();
|
|
488
|
+
const serverJar = this.engine.getServerJar(jarPath);
|
|
489
|
+
const serverArgs = this.engine.getServerArgs();
|
|
490
|
+
const fullArgs = [
|
|
491
|
+
...javaArgs,
|
|
492
|
+
'-jar',
|
|
493
|
+
serverJar,
|
|
494
|
+
...serverArgs
|
|
495
|
+
];
|
|
496
|
+
if (this.options.networkOptimization?.tcpFastOpen) {
|
|
497
|
+
fullArgs.unshift('-Djava.net.preferIPv4Stack=true');
|
|
498
|
+
}
|
|
499
|
+
if (this.options.networkOptimization?.bungeeMode) {
|
|
500
|
+
fullArgs.unshift('-Dnet.kyori.adventure.text.serializer.legacy.AMPMSupport=true');
|
|
501
|
+
}
|
|
502
|
+
const env = this.buildEnvironment();
|
|
503
|
+
this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
|
|
504
|
+
this.process = (0, child_process_1.spawn)(this.javaCommand, fullArgs, {
|
|
505
|
+
cwd: serverDir,
|
|
506
|
+
env: env,
|
|
507
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
508
|
+
});
|
|
509
|
+
this.serverInfo.pid = this.process.pid;
|
|
510
|
+
this.serverInfo.status = 'starting';
|
|
511
|
+
this.startTime = new Date();
|
|
512
|
+
if (this.options.silentMode) {
|
|
513
|
+
this.process.stdout.pipe(process.stdout);
|
|
514
|
+
this.process.stderr.pipe(process.stderr);
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
this.setupLogHandlers();
|
|
518
|
+
}
|
|
519
|
+
this.process.on('exit', (code) => {
|
|
520
|
+
this.serverInfo.status = 'stopped';
|
|
521
|
+
this.logger.warning(`Server stopped with code ${code}`);
|
|
522
|
+
if (this.config.autoRestart && code !== 0) {
|
|
523
|
+
this.logger.info('Auto-restarting...');
|
|
524
|
+
this.start();
|
|
525
|
+
}
|
|
526
|
+
this.emit('stop', { code });
|
|
527
|
+
});
|
|
528
|
+
if (this.options.statsInterval && this.options.statsInterval > 0) {
|
|
529
|
+
this.statsInterval = setInterval(() => this.updateStats(), this.options.statsInterval);
|
|
530
|
+
}
|
|
531
|
+
this.monitorResources();
|
|
532
|
+
if (this.config.backup.enabled) {
|
|
533
|
+
this.setupBackups();
|
|
534
|
+
}
|
|
535
|
+
setTimeout(() => {
|
|
536
|
+
if (this.serverInfo.status === 'starting') {
|
|
537
|
+
this.serverInfo.status = 'running';
|
|
538
|
+
this.logger.success('Server started successfully!');
|
|
539
|
+
if (this.options.enableViaVersion !== false) {
|
|
540
|
+
this.logger.info('ViaVersion is active - players from older versions can connect');
|
|
541
|
+
}
|
|
542
|
+
if (this.options.enableSkinRestorer !== false) {
|
|
543
|
+
this.logger.info('SkinRestorer is active - player skins will be restored');
|
|
544
|
+
}
|
|
545
|
+
if (this.worldSize > 0) {
|
|
546
|
+
this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
|
|
547
|
+
}
|
|
548
|
+
if (this.owners.size > 0) {
|
|
549
|
+
this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
|
|
550
|
+
}
|
|
551
|
+
this.emit('ready', this.serverInfo);
|
|
552
|
+
this.startMemoryMonitor();
|
|
553
|
+
}
|
|
554
|
+
}, 10000);
|
|
555
|
+
return this.serverInfo;
|
|
556
|
+
}
|
|
557
|
+
setupLogHandlers() {
|
|
558
|
+
this.process.stdout.on('data', (data) => {
|
|
559
|
+
const output = data.toString();
|
|
560
|
+
process.stdout.write(output);
|
|
561
|
+
if (output.includes('joined the game')) {
|
|
562
|
+
const match = output.match(/(\w+) joined the game/);
|
|
563
|
+
if (match) {
|
|
564
|
+
this.playerCount++;
|
|
565
|
+
this.handlePlayerJoin(match[1]);
|
|
566
|
+
if (this.owners.has(match[1].toLowerCase())) {
|
|
567
|
+
this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (output.includes('left the game')) {
|
|
572
|
+
const match = output.match(/(\w+) left the game/);
|
|
573
|
+
if (match) {
|
|
574
|
+
this.playerCount--;
|
|
575
|
+
this.handlePlayerLeave(match[1]);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (output.includes('<') && output.includes('>')) {
|
|
579
|
+
const chatMatch = output.match(/<(\w+)>\s+(.+)/);
|
|
580
|
+
if (chatMatch) {
|
|
581
|
+
const player = chatMatch[1];
|
|
582
|
+
const message = chatMatch[2];
|
|
583
|
+
if (message.startsWith(this.ownerCommandPrefix)) {
|
|
584
|
+
const command = message.substring(this.ownerCommandPrefix.length);
|
|
585
|
+
this.processOwnerCommand(player, command);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
this.process.stderr.on('data', (data) => {
|
|
591
|
+
process.stderr.write(data.toString());
|
|
592
|
+
});
|
|
593
|
+
}
|
|
271
594
|
processOwnerCommand(player, command) {
|
|
272
595
|
if (!this.options.ownerCommands?.enabled)
|
|
273
596
|
return;
|
|
@@ -536,154 +859,6 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
536
859
|
this.logger.error('Stats update error:', error);
|
|
537
860
|
}
|
|
538
861
|
}
|
|
539
|
-
async start() {
|
|
540
|
-
this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
|
|
541
|
-
if (this.owners.size > 0) {
|
|
542
|
-
this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
|
|
543
|
-
}
|
|
544
|
-
this.javaInfo = await JavaChecker_1.JavaChecker.ensureJava(this.config.version, this.options.usePortableJava || false);
|
|
545
|
-
this.javaCommand = this.javaInfo.path;
|
|
546
|
-
this.logger.success(`Using Java ${this.javaInfo.version} (${this.javaInfo.type}) at ${this.javaInfo.path}`);
|
|
547
|
-
const systemInfo = SystemDetector_1.SystemDetector.getSystemInfo();
|
|
548
|
-
this.logger.debug('System info:', systemInfo);
|
|
549
|
-
const serverDir = process.cwd();
|
|
550
|
-
await FileUtils_1.FileUtils.ensureServerStructure(this.config);
|
|
551
|
-
this.worldSize = await this.calculateWorldSize();
|
|
552
|
-
if (this.worldSize > 10 * 1024 * 1024 * 1024) {
|
|
553
|
-
this.logger.warning(`Large world detected (${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB). Consider increasing memory allocation.`);
|
|
554
|
-
}
|
|
555
|
-
const jarPath = await this.engine.download(this.config, serverDir);
|
|
556
|
-
if (this.config.type === 'forge') {
|
|
557
|
-
await this.engine.prepare(this.config, serverDir, jarPath);
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
await this.engine.prepare(this.config, serverDir);
|
|
561
|
-
}
|
|
562
|
-
if (this.config.platform === 'all') {
|
|
563
|
-
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
564
|
-
await this.geyser.setup(this.config);
|
|
565
|
-
}
|
|
566
|
-
if (this.options.enableViaVersion !== false) {
|
|
567
|
-
this.logger.info('Enabling ViaVersion for client version compatibility...');
|
|
568
|
-
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
569
|
-
await this.viaVersion.setup(this.config);
|
|
570
|
-
await this.viaVersion.configureViaVersion(this.config);
|
|
571
|
-
if (this.options.enableViaBackwards !== false) {
|
|
572
|
-
this.logger.info('ViaBackwards will be installed');
|
|
573
|
-
}
|
|
574
|
-
if (this.options.enableViaRewind !== false) {
|
|
575
|
-
this.logger.info('ViaRewind will be installed');
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
if (this.options.enableSkinRestorer !== false) {
|
|
579
|
-
this.logger.info('Enabling SkinRestorer for player skins...');
|
|
580
|
-
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
581
|
-
await this.skinRestorer.setup(this.config);
|
|
582
|
-
}
|
|
583
|
-
const javaArgs = this.buildJavaArgs();
|
|
584
|
-
const serverJar = this.engine.getServerJar(jarPath);
|
|
585
|
-
const serverArgs = this.engine.getServerArgs();
|
|
586
|
-
const fullArgs = [
|
|
587
|
-
...javaArgs,
|
|
588
|
-
'-jar',
|
|
589
|
-
serverJar,
|
|
590
|
-
...serverArgs
|
|
591
|
-
];
|
|
592
|
-
if (this.options.networkOptimization?.tcpFastOpen) {
|
|
593
|
-
fullArgs.unshift('-Djava.net.preferIPv4Stack=true');
|
|
594
|
-
}
|
|
595
|
-
if (this.options.networkOptimization?.bungeeMode) {
|
|
596
|
-
fullArgs.unshift('-Dnet.kyori.adventure.text.serializer.legacy.AMPMSupport=true');
|
|
597
|
-
}
|
|
598
|
-
const env = this.buildEnvironment();
|
|
599
|
-
this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
|
|
600
|
-
this.process = (0, child_process_1.spawn)(this.javaCommand, fullArgs, {
|
|
601
|
-
cwd: serverDir,
|
|
602
|
-
env: env,
|
|
603
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
604
|
-
});
|
|
605
|
-
this.serverInfo.pid = this.process.pid;
|
|
606
|
-
this.serverInfo.status = 'starting';
|
|
607
|
-
this.startTime = new Date();
|
|
608
|
-
if (this.options.silentMode) {
|
|
609
|
-
this.process.stdout.pipe(process.stdout);
|
|
610
|
-
this.process.stderr.pipe(process.stderr);
|
|
611
|
-
}
|
|
612
|
-
else {
|
|
613
|
-
this.process.stdout.on('data', (data) => {
|
|
614
|
-
const output = data.toString();
|
|
615
|
-
process.stdout.write(output);
|
|
616
|
-
if (output.includes('joined the game')) {
|
|
617
|
-
const match = output.match(/(\w+) joined the game/);
|
|
618
|
-
if (match) {
|
|
619
|
-
this.playerCount++;
|
|
620
|
-
this.handlePlayerJoin(match[1]);
|
|
621
|
-
if (this.owners.has(match[1].toLowerCase())) {
|
|
622
|
-
this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
if (output.includes('left the game')) {
|
|
627
|
-
const match = output.match(/(\w+) left the game/);
|
|
628
|
-
if (match) {
|
|
629
|
-
this.playerCount--;
|
|
630
|
-
this.handlePlayerLeave(match[1]);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
if (output.includes('<') && output.includes('>')) {
|
|
634
|
-
const chatMatch = output.match(/<(\w+)>\s+(.+)/);
|
|
635
|
-
if (chatMatch) {
|
|
636
|
-
const player = chatMatch[1];
|
|
637
|
-
const message = chatMatch[2];
|
|
638
|
-
if (message.startsWith(this.ownerCommandPrefix)) {
|
|
639
|
-
const command = message.substring(this.ownerCommandPrefix.length);
|
|
640
|
-
this.processOwnerCommand(player, command);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
this.process.stderr.on('data', (data) => {
|
|
646
|
-
process.stderr.write(data.toString());
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
this.process.on('exit', (code) => {
|
|
650
|
-
this.serverInfo.status = 'stopped';
|
|
651
|
-
this.logger.warning(`Server stopped with code ${code}`);
|
|
652
|
-
if (this.config.autoRestart && code !== 0) {
|
|
653
|
-
this.logger.info('Auto-restarting...');
|
|
654
|
-
this.start();
|
|
655
|
-
}
|
|
656
|
-
this.emit('stop', { code });
|
|
657
|
-
});
|
|
658
|
-
if (this.options.statsInterval && this.options.statsInterval > 0) {
|
|
659
|
-
this.statsInterval = setInterval(() => this.updateStats(), this.options.statsInterval);
|
|
660
|
-
}
|
|
661
|
-
this.monitorResources();
|
|
662
|
-
if (this.config.backup.enabled) {
|
|
663
|
-
this.setupBackups();
|
|
664
|
-
}
|
|
665
|
-
setTimeout(() => {
|
|
666
|
-
if (this.serverInfo.status === 'starting') {
|
|
667
|
-
this.serverInfo.status = 'running';
|
|
668
|
-
this.logger.success('Server started successfully!');
|
|
669
|
-
if (this.options.enableViaVersion !== false) {
|
|
670
|
-
this.logger.info('ViaVersion is active - players from older versions can connect');
|
|
671
|
-
}
|
|
672
|
-
if (this.options.enableSkinRestorer !== false) {
|
|
673
|
-
this.logger.info('SkinRestorer is active - player skins will be restored');
|
|
674
|
-
}
|
|
675
|
-
if (this.worldSize > 0) {
|
|
676
|
-
this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
|
|
677
|
-
}
|
|
678
|
-
if (this.owners.size > 0) {
|
|
679
|
-
this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
|
|
680
|
-
}
|
|
681
|
-
this.emit('ready', this.serverInfo);
|
|
682
|
-
this.startMemoryMonitor();
|
|
683
|
-
}
|
|
684
|
-
}, 10000);
|
|
685
|
-
return this.serverInfo;
|
|
686
|
-
}
|
|
687
862
|
startMemoryMonitor() {
|
|
688
863
|
if (!this.options.memoryMonitor?.enabled)
|
|
689
864
|
return;
|
|
@@ -760,6 +935,9 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
760
935
|
if (this.backupCron) {
|
|
761
936
|
this.backupCron.stop();
|
|
762
937
|
}
|
|
938
|
+
if (this.cleanLogsCron) {
|
|
939
|
+
this.cleanLogsCron.stop();
|
|
940
|
+
}
|
|
763
941
|
if (this.memoryMonitorInterval) {
|
|
764
942
|
clearInterval(this.memoryMonitorInterval);
|
|
765
943
|
}
|
|
@@ -769,6 +947,7 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
769
947
|
if (this.config.platform === 'all') {
|
|
770
948
|
this.geyser.stop();
|
|
771
949
|
}
|
|
950
|
+
this.cleanPaperCache();
|
|
772
951
|
this.logger.success('Server stopped');
|
|
773
952
|
}
|
|
774
953
|
sendCommand(command) {
|