@dimzxzzx07/mc-headless 2.2.0 → 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.
@@ -1,6 +1,8 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import { spawn } from 'child_process';
3
3
  import * as cron from 'node-cron';
4
+ import * as path from 'path';
5
+ import * as fs from 'fs-extra';
4
6
  import { MinecraftConfig, ServerInfo, Player } from '../types';
5
7
  import { ConfigHandler } from './ConfigHandler';
6
8
  import { JavaChecker, JavaInfo } from './JavaChecker';
@@ -15,8 +17,6 @@ import { ServerEngine } from '../engines/ServerEngine';
15
17
  import { GeyserBridge } from '../platforms/GeyserBridge';
16
18
  import { ViaVersionManager } from '../platforms/ViaVersion';
17
19
  import { SkinRestorerManager } from '../platforms/SkinRestorer';
18
- import * as path from 'path';
19
- import * as fs from 'fs-extra';
20
20
 
21
21
  export interface MinecraftServerOptions extends Partial<MinecraftConfig> {
22
22
  enableViaVersion?: boolean;
@@ -46,6 +46,8 @@ export interface MinecraftServerOptions extends Partial<MinecraftConfig> {
46
46
  };
47
47
  silentMode?: boolean;
48
48
  statsInterval?: number;
49
+ cleanLogsInterval?: number;
50
+ optimizeForPterodactyl?: boolean;
49
51
  }
50
52
 
51
53
  export class MinecraftServer extends EventEmitter {
@@ -60,6 +62,7 @@ export class MinecraftServer extends EventEmitter {
60
62
  private serverInfo: ServerInfo;
61
63
  private players: Map<string, Player> = new Map();
62
64
  private backupCron: cron.ScheduledTask | null = null;
65
+ private cleanLogsCron: cron.ScheduledTask | null = null;
63
66
  private startTime: Date | null = null;
64
67
  private memoryMonitorInterval: NodeJS.Timeout | null = null;
65
68
  private statsInterval: NodeJS.Timeout | null = null;
@@ -75,15 +78,18 @@ export class MinecraftServer extends EventEmitter {
75
78
  private cpuUsage: number = 0;
76
79
  private cgroupMemory: number = 0;
77
80
  private cgroupCpu: number = 0;
81
+ private isPterodactyl: boolean = false;
78
82
 
79
83
  constructor(userConfig: MinecraftServerOptions = {}) {
80
84
  super();
81
85
  this.logger = Logger.getInstance();
82
86
  this.logger.banner();
83
87
 
88
+ this.detectEnvironment();
89
+
84
90
  this.options = {
85
91
  javaVersion: 'auto',
86
- usePortableJava: true,
92
+ usePortableJava: !this.isPterodactyl,
87
93
  memoryMonitor: {
88
94
  enabled: true,
89
95
  threshold: 90,
@@ -103,6 +109,8 @@ export class MinecraftServer extends EventEmitter {
103
109
  },
104
110
  silentMode: true,
105
111
  statsInterval: 30000,
112
+ cleanLogsInterval: 3 * 60 * 60 * 1000,
113
+ optimizeForPterodactyl: true,
106
114
  ...userConfig
107
115
  };
108
116
 
@@ -117,6 +125,10 @@ export class MinecraftServer extends EventEmitter {
117
125
  const handler = new ConfigHandler(userConfig);
118
126
  this.config = handler.getConfig();
119
127
 
128
+ if (this.isPterodactyl && this.options.optimizeForPterodactyl) {
129
+ this.optimizeForPterodactylPanel();
130
+ }
131
+
120
132
  this.engine = this.createEngine();
121
133
  this.geyser = new GeyserBridge();
122
134
  this.viaVersion = new ViaVersionManager();
@@ -139,6 +151,181 @@ export class MinecraftServer extends EventEmitter {
139
151
  };
140
152
 
141
153
  this.detectCgroupLimits();
154
+ this.cleanPaperCache();
155
+ }
156
+
157
+ private detectEnvironment(): void {
158
+ this.isPterodactyl = !!(
159
+ process.env.PTERODACTYL ||
160
+ process.env.SERVER_PORT ||
161
+ process.env.SERVER_MEMORY_MAX ||
162
+ fs.existsSync('/home/container')
163
+ );
164
+
165
+ if (this.isPterodactyl) {
166
+ this.logger.info('Pterodactyl environment detected');
167
+ }
168
+ }
169
+
170
+ private optimizeForPterodactylPanel(): void {
171
+ const pterodactylPort = process.env.SERVER_PORT;
172
+ if (pterodactylPort) {
173
+ this.config.network.port = parseInt(pterodactylPort);
174
+ this.logger.info(`Using Pterodactyl port: ${pterodactylPort}`);
175
+ }
176
+
177
+ const pterodactylMemory = process.env.SERVER_MEMORY_MAX;
178
+ if (pterodactylMemory) {
179
+ this.config.memory.max = pterodactylMemory;
180
+ this.logger.info(`Using Pterodactyl memory: ${pterodactylMemory}`);
181
+ }
182
+
183
+ const pterodactylInitMemory = process.env.SERVER_MEMORY_INIT;
184
+ if (pterodactylInitMemory) {
185
+ this.config.memory.init = pterodactylInitMemory;
186
+ }
187
+
188
+ this.config.world.viewDistance = 6;
189
+ this.config.world.simulationDistance = 4;
190
+
191
+ this.logger.info('Applied Pterodactyl optimizations');
192
+ }
193
+
194
+ private cleanPaperCache(): void {
195
+ const cachePaths = [
196
+ path.join(process.cwd(), 'plugins', '.paper-remapped'),
197
+ path.join(process.cwd(), 'plugins', '.paper-remapped', 'geyser.jar'),
198
+ path.join(process.cwd(), 'plugins', '.paper-remapped', 'ViaVersion.jar'),
199
+ path.join(process.cwd(), 'cache', 'paper-remapped')
200
+ ];
201
+
202
+ cachePaths.forEach(cachePath => {
203
+ if (fs.existsSync(cachePath)) {
204
+ this.logger.info(`Cleaning corrupt cache: ${cachePath}`);
205
+ try {
206
+ fs.rmSync(cachePath, { recursive: true, force: true });
207
+ this.logger.debug(`Cache cleaned: ${cachePath}`);
208
+ } catch (err: any) {
209
+ this.logger.warning(`Failed to clean cache: ${err.message}`);
210
+ }
211
+ }
212
+ });
213
+ }
214
+
215
+ private cleanOldLogs(): void {
216
+ const logsDir = path.join(process.cwd(), 'logs');
217
+ if (!fs.existsSync(logsDir)) return;
218
+
219
+ try {
220
+ const files = fs.readdirSync(logsDir);
221
+ const now = Date.now();
222
+ const threeHours = 3 * 60 * 60 * 1000;
223
+
224
+ files.forEach(file => {
225
+ const filePath = path.join(logsDir, file);
226
+ const stats = fs.statSync(filePath);
227
+ if (now - stats.mtimeMs > threeHours) {
228
+ fs.unlinkSync(filePath);
229
+ this.logger.debug(`Cleaned old log: ${file}`);
230
+ }
231
+ });
232
+ this.logger.info('Old logs cleaned');
233
+ } catch (err: any) {
234
+ this.logger.warning(`Failed to clean logs: ${err.message}`);
235
+ }
236
+ }
237
+
238
+ private generateOptimizedConfigs(): void {
239
+ const serverDir = process.cwd();
240
+
241
+ const paperGlobalYml = path.join(serverDir, 'paper-global.yml');
242
+ if (!fs.existsSync(paperGlobalYml)) {
243
+ const paperGlobalConfig = `# Paper Global Configuration
244
+ # Optimized by MC-Headless
245
+
246
+ chunk-loading:
247
+ autoconfig-send-distance: false
248
+ enable-frustum-priority: false
249
+ global-max-chunk-load-rate: 100
250
+ global-max-chunk-send-rate: 50
251
+
252
+ entities:
253
+ despawn-ranges:
254
+ monster:
255
+ soft: 28
256
+ hard: 96
257
+ animal:
258
+ soft: 16
259
+ hard: 32
260
+ spawn-limits:
261
+ ambient: 5
262
+ axolotls: 5
263
+ creature: 10
264
+ monster: 15
265
+ water_ambient: 5
266
+ water_creature: 5
267
+
268
+ misc:
269
+ max-entity-speed: 100
270
+ redstone-implementation: ALTERNATIVE_CURRENT
271
+
272
+ player-auto-save: 6000
273
+ player-auto-save-rate: 1200
274
+ no-tick-view-distance: 12
275
+ `;
276
+ fs.writeFileSync(paperGlobalYml, paperGlobalConfig);
277
+ this.logger.debug('Generated paper-global.yml');
278
+ }
279
+
280
+ const paperWorldDefaultsYml = path.join(serverDir, 'paper-world-defaults.yml');
281
+ if (!fs.existsSync(paperWorldDefaultsYml)) {
282
+ const paperWorldConfig = `# Paper World Defaults
283
+ # Optimized by MC-Headless
284
+
285
+ view-distance: 6
286
+ no-tick-view-distance: 12
287
+
288
+ despawn-ranges:
289
+ monster:
290
+ soft: 28
291
+ hard: 96
292
+ animal:
293
+ soft: 16
294
+ hard: 32
295
+ ambient:
296
+ soft: 16
297
+ hard: 32
298
+ `;
299
+ fs.writeFileSync(paperWorldDefaultsYml, paperWorldConfig);
300
+ this.logger.debug('Generated paper-world-defaults.yml');
301
+ }
302
+
303
+ const spigotYml = path.join(serverDir, 'spigot.yml');
304
+ if (!fs.existsSync(spigotYml)) {
305
+ const spigotConfig = `# Spigot Configuration
306
+ # Optimized by MC-Headless
307
+
308
+ entity-activation-range:
309
+ animals: 16
310
+ monsters: 24
311
+ misc: 8
312
+ water: 8
313
+
314
+ mob-spawn-range: 4
315
+
316
+ world-settings:
317
+ default:
318
+ view-distance: 6
319
+ mob-spawn-range: 4
320
+ entity-activation-range:
321
+ animals: 16
322
+ monsters: 24
323
+ misc: 8
324
+ water: 8
325
+ `;
326
+ fs.writeFileSync(spigotYml, spigotConfig);
327
+ this.logger.debug('Generated spigot.yml');
328
+ }
142
329
  }
143
330
 
144
331
  private detectCgroupLimits(): void {
@@ -187,11 +374,16 @@ export class MinecraftServer extends EventEmitter {
187
374
  const memMax = this.parseMemory(this.config.memory.max);
188
375
  const javaVersion = this.options.javaVersion || 'auto';
189
376
 
377
+ const baseArgs = [
378
+ `-Xms${this.config.memory.init}`,
379
+ `-Xmx${this.config.memory.max}`,
380
+ '-XX:+UseG1GC'
381
+ ];
382
+
190
383
  let gcArgs: string[] = [];
191
384
 
192
385
  if (memMax >= 16384) {
193
386
  gcArgs = [
194
- '-XX:+UseG1GC',
195
387
  '-XX:+ParallelRefProcEnabled',
196
388
  '-XX:MaxGCPauseMillis=100',
197
389
  '-XX:+UnlockExperimentalVMOptions',
@@ -212,7 +404,6 @@ export class MinecraftServer extends EventEmitter {
212
404
  ];
213
405
  } else if (memMax >= 8192) {
214
406
  gcArgs = [
215
- '-XX:+UseG1GC',
216
407
  '-XX:+ParallelRefProcEnabled',
217
408
  '-XX:MaxGCPauseMillis=150',
218
409
  '-XX:+UnlockExperimentalVMOptions',
@@ -233,7 +424,6 @@ export class MinecraftServer extends EventEmitter {
233
424
  ];
234
425
  } else {
235
426
  gcArgs = this.config.memory.useAikarsFlags ? [
236
- '-XX:+UseG1GC',
237
427
  '-XX:+ParallelRefProcEnabled',
238
428
  '-XX:MaxGCPauseMillis=200',
239
429
  '-XX:+UnlockExperimentalVMOptions',
@@ -256,11 +446,6 @@ export class MinecraftServer extends EventEmitter {
256
446
  gcArgs.push('--enable-preview');
257
447
  }
258
448
 
259
- const baseArgs = [
260
- `-Xms${this.config.memory.init}`,
261
- `-Xmx${this.config.memory.max}`
262
- ];
263
-
264
449
  return [...baseArgs, ...gcArgs];
265
450
  }
266
451
 
@@ -268,7 +453,6 @@ export class MinecraftServer extends EventEmitter {
268
453
  const env: NodeJS.ProcessEnv = { ...process.env };
269
454
 
270
455
  env.MALLOC_ARENA_MAX = '2';
271
-
272
456
  env._JAVA_OPTIONS = `-Xmx${this.config.memory.max}`;
273
457
 
274
458
  if (this.javaInfo && this.javaInfo.type === 'portable') {
@@ -288,6 +472,207 @@ export class MinecraftServer extends EventEmitter {
288
472
  return env;
289
473
  }
290
474
 
475
+ public async start(): Promise<ServerInfo> {
476
+ this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
477
+
478
+ this.cleanPaperCache();
479
+ this.generateOptimizedConfigs();
480
+
481
+ if (this.options.cleanLogsInterval && this.options.cleanLogsInterval > 0) {
482
+ this.cleanLogsCron = cron.schedule('0 */3 * * *', () => this.cleanOldLogs());
483
+ this.logger.info('Log cleanup scheduled every 3 hours');
484
+ }
485
+
486
+ if (this.owners.size > 0) {
487
+ this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
488
+ }
489
+
490
+ this.javaInfo = await JavaChecker.ensureJava(
491
+ this.config.version,
492
+ this.options.usePortableJava || false
493
+ );
494
+ this.javaCommand = this.javaInfo.path;
495
+ this.logger.success(`Using Java ${this.javaInfo.version} (${this.javaInfo.type}) at ${this.javaInfo.path}`);
496
+
497
+ const systemInfo = SystemDetector.getSystemInfo();
498
+ this.logger.debug('System info:', systemInfo);
499
+
500
+ const serverDir = process.cwd();
501
+ await FileUtils.ensureServerStructure(this.config);
502
+
503
+ this.worldSize = await this.calculateWorldSize();
504
+ if (this.worldSize > 10 * 1024 * 1024 * 1024) {
505
+ this.logger.warning(`Large world detected (${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB). Consider increasing memory allocation.`);
506
+ }
507
+
508
+ const jarPath = await this.engine.download(this.config, serverDir);
509
+
510
+ if (this.config.type === 'forge') {
511
+ await this.engine.prepare(this.config, serverDir, jarPath);
512
+ } else {
513
+ await this.engine.prepare(this.config, serverDir);
514
+ }
515
+
516
+ if (this.config.platform === 'all') {
517
+ await FileUtils.ensureDir(this.config.folders.plugins);
518
+ await this.geyser.setup(this.config);
519
+ }
520
+
521
+ if (this.options.enableViaVersion !== false) {
522
+ this.logger.info('Enabling ViaVersion for client version compatibility...');
523
+ await FileUtils.ensureDir(this.config.folders.plugins);
524
+ await this.viaVersion.setup(this.config);
525
+ await this.viaVersion.configureViaVersion(this.config);
526
+
527
+ if (this.options.enableViaBackwards !== false) {
528
+ this.logger.info('ViaBackwards will be installed');
529
+ }
530
+ if (this.options.enableViaRewind !== false) {
531
+ this.logger.info('ViaRewind will be installed');
532
+ }
533
+ }
534
+
535
+ if (this.options.enableSkinRestorer !== false) {
536
+ this.logger.info('Enabling SkinRestorer for player skins...');
537
+ await FileUtils.ensureDir(this.config.folders.plugins);
538
+ await this.skinRestorer.setup(this.config);
539
+ }
540
+
541
+ const javaArgs = this.buildJavaArgs();
542
+ const serverJar = this.engine.getServerJar(jarPath);
543
+ const serverArgs = this.engine.getServerArgs();
544
+
545
+ const fullArgs = [
546
+ ...javaArgs,
547
+ '-jar',
548
+ serverJar,
549
+ ...serverArgs
550
+ ];
551
+
552
+ if (this.options.networkOptimization?.tcpFastOpen) {
553
+ fullArgs.unshift('-Djava.net.preferIPv4Stack=true');
554
+ }
555
+
556
+ if (this.options.networkOptimization?.bungeeMode) {
557
+ fullArgs.unshift('-Dnet.kyori.adventure.text.serializer.legacy.AMPMSupport=true');
558
+ }
559
+
560
+ const env = this.buildEnvironment();
561
+
562
+ this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
563
+
564
+ this.process = spawn(this.javaCommand, fullArgs, {
565
+ cwd: serverDir,
566
+ env: env,
567
+ stdio: ['pipe', 'pipe', 'pipe']
568
+ });
569
+
570
+ this.serverInfo.pid = this.process.pid!;
571
+ this.serverInfo.status = 'starting';
572
+ this.startTime = new Date();
573
+
574
+ if (this.options.silentMode) {
575
+ this.process.stdout.pipe(process.stdout);
576
+ this.process.stderr.pipe(process.stderr);
577
+ } else {
578
+ this.setupLogHandlers();
579
+ }
580
+
581
+ this.process.on('exit', (code: number) => {
582
+ this.serverInfo.status = 'stopped';
583
+ this.logger.warning(`Server stopped with code ${code}`);
584
+
585
+ if (this.config.autoRestart && code !== 0) {
586
+ this.logger.info('Auto-restarting...');
587
+ this.start();
588
+ }
589
+
590
+ this.emit('stop', { code });
591
+ });
592
+
593
+ if (this.options.statsInterval && this.options.statsInterval > 0) {
594
+ this.statsInterval = setInterval(() => this.updateStats(), this.options.statsInterval);
595
+ }
596
+
597
+ this.monitorResources();
598
+
599
+ if (this.config.backup.enabled) {
600
+ this.setupBackups();
601
+ }
602
+
603
+ setTimeout(() => {
604
+ if (this.serverInfo.status === 'starting') {
605
+ this.serverInfo.status = 'running';
606
+ this.logger.success('Server started successfully!');
607
+
608
+ if (this.options.enableViaVersion !== false) {
609
+ this.logger.info('ViaVersion is active - players from older versions can connect');
610
+ }
611
+
612
+ if (this.options.enableSkinRestorer !== false) {
613
+ this.logger.info('SkinRestorer is active - player skins will be restored');
614
+ }
615
+
616
+ if (this.worldSize > 0) {
617
+ this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
618
+ }
619
+
620
+ if (this.owners.size > 0) {
621
+ this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
622
+ }
623
+
624
+ this.emit('ready', this.serverInfo);
625
+ this.startMemoryMonitor();
626
+ }
627
+ }, 10000);
628
+
629
+ return this.serverInfo;
630
+ }
631
+
632
+ private setupLogHandlers(): void {
633
+ this.process.stdout.on('data', (data: Buffer) => {
634
+ const output = data.toString();
635
+ process.stdout.write(output);
636
+
637
+ if (output.includes('joined the game')) {
638
+ const match = output.match(/(\w+) joined the game/);
639
+ if (match) {
640
+ this.playerCount++;
641
+ this.handlePlayerJoin(match[1]);
642
+
643
+ if (this.owners.has(match[1].toLowerCase())) {
644
+ this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
645
+ }
646
+ }
647
+ }
648
+
649
+ if (output.includes('left the game')) {
650
+ const match = output.match(/(\w+) left the game/);
651
+ if (match) {
652
+ this.playerCount--;
653
+ this.handlePlayerLeave(match[1]);
654
+ }
655
+ }
656
+
657
+ if (output.includes('<') && output.includes('>')) {
658
+ const chatMatch = output.match(/<(\w+)>\s+(.+)/);
659
+ if (chatMatch) {
660
+ const player = chatMatch[1];
661
+ const message = chatMatch[2];
662
+
663
+ if (message.startsWith(this.ownerCommandPrefix)) {
664
+ const command = message.substring(this.ownerCommandPrefix.length);
665
+ this.processOwnerCommand(player, command);
666
+ }
667
+ }
668
+ }
669
+ });
670
+
671
+ this.process.stderr.on('data', (data: Buffer) => {
672
+ process.stderr.write(data.toString());
673
+ });
674
+ }
675
+
291
676
  private processOwnerCommand(player: string, command: string): void {
292
677
  if (!this.options.ownerCommands?.enabled) return;
293
678
  if (!this.owners.has(player.toLowerCase())) return;
@@ -588,195 +973,6 @@ export class MinecraftServer extends EventEmitter {
588
973
  }
589
974
  }
590
975
 
591
- public async start(): Promise<ServerInfo> {
592
- this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
593
-
594
- if (this.owners.size > 0) {
595
- this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
596
- }
597
-
598
- this.javaInfo = await JavaChecker.ensureJava(
599
- this.config.version,
600
- this.options.usePortableJava || false
601
- );
602
- this.javaCommand = this.javaInfo.path;
603
- this.logger.success(`Using Java ${this.javaInfo.version} (${this.javaInfo.type}) at ${this.javaInfo.path}`);
604
-
605
- const systemInfo = SystemDetector.getSystemInfo();
606
- this.logger.debug('System info:', systemInfo);
607
-
608
- const serverDir = process.cwd();
609
- await FileUtils.ensureServerStructure(this.config);
610
-
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
- }
615
-
616
- const jarPath = await this.engine.download(this.config, serverDir);
617
-
618
- if (this.config.type === 'forge') {
619
- await this.engine.prepare(this.config, serverDir, jarPath);
620
- } else {
621
- await this.engine.prepare(this.config, serverDir);
622
- }
623
-
624
- if (this.config.platform === 'all') {
625
- await FileUtils.ensureDir(this.config.folders.plugins);
626
- await this.geyser.setup(this.config);
627
- }
628
-
629
- if (this.options.enableViaVersion !== false) {
630
- this.logger.info('Enabling ViaVersion for client version compatibility...');
631
- await FileUtils.ensureDir(this.config.folders.plugins);
632
- await this.viaVersion.setup(this.config);
633
- await this.viaVersion.configureViaVersion(this.config);
634
-
635
- if (this.options.enableViaBackwards !== false) {
636
- this.logger.info('ViaBackwards will be installed');
637
- }
638
- if (this.options.enableViaRewind !== false) {
639
- this.logger.info('ViaRewind will be installed');
640
- }
641
- }
642
-
643
- if (this.options.enableSkinRestorer !== false) {
644
- this.logger.info('Enabling SkinRestorer for player skins...');
645
- await FileUtils.ensureDir(this.config.folders.plugins);
646
- await this.skinRestorer.setup(this.config);
647
- }
648
-
649
- const javaArgs = this.buildJavaArgs();
650
- const serverJar = this.engine.getServerJar(jarPath);
651
- const serverArgs = this.engine.getServerArgs();
652
-
653
- const fullArgs = [
654
- ...javaArgs,
655
- '-jar',
656
- serverJar,
657
- ...serverArgs
658
- ];
659
-
660
- if (this.options.networkOptimization?.tcpFastOpen) {
661
- fullArgs.unshift('-Djava.net.preferIPv4Stack=true');
662
- }
663
-
664
- if (this.options.networkOptimization?.bungeeMode) {
665
- fullArgs.unshift('-Dnet.kyori.adventure.text.serializer.legacy.AMPMSupport=true');
666
- }
667
-
668
- const env = this.buildEnvironment();
669
-
670
- this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
671
-
672
- this.process = spawn(this.javaCommand, fullArgs, {
673
- cwd: serverDir,
674
- env: env,
675
- stdio: ['pipe', 'pipe', 'pipe']
676
- });
677
-
678
- this.serverInfo.pid = this.process.pid!;
679
- this.serverInfo.status = 'starting';
680
- this.startTime = new Date();
681
-
682
- if (this.options.silentMode) {
683
- this.process.stdout.pipe(process.stdout);
684
- this.process.stderr.pipe(process.stderr);
685
- } else {
686
- this.process.stdout.on('data', (data: Buffer) => {
687
- const output = data.toString();
688
- process.stdout.write(output);
689
-
690
- if (output.includes('joined the game')) {
691
- const match = output.match(/(\w+) joined the game/);
692
- if (match) {
693
- this.playerCount++;
694
- this.handlePlayerJoin(match[1]);
695
-
696
- if (this.owners.has(match[1].toLowerCase())) {
697
- this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
698
- }
699
- }
700
- }
701
-
702
- if (output.includes('left the game')) {
703
- const match = output.match(/(\w+) left the game/);
704
- if (match) {
705
- this.playerCount--;
706
- this.handlePlayerLeave(match[1]);
707
- }
708
- }
709
-
710
- if (output.includes('<') && output.includes('>')) {
711
- const chatMatch = output.match(/<(\w+)>\s+(.+)/);
712
- if (chatMatch) {
713
- const player = chatMatch[1];
714
- const message = chatMatch[2];
715
-
716
- if (message.startsWith(this.ownerCommandPrefix)) {
717
- const command = message.substring(this.ownerCommandPrefix.length);
718
- this.processOwnerCommand(player, command);
719
- }
720
- }
721
- }
722
- });
723
-
724
- this.process.stderr.on('data', (data: Buffer) => {
725
- process.stderr.write(data.toString());
726
- });
727
- }
728
-
729
- this.process.on('exit', (code: number) => {
730
- this.serverInfo.status = 'stopped';
731
- this.logger.warning(`Server stopped with code ${code}`);
732
-
733
- if (this.config.autoRestart && code !== 0) {
734
- this.logger.info('Auto-restarting...');
735
- this.start();
736
- }
737
-
738
- this.emit('stop', { code });
739
- });
740
-
741
- if (this.options.statsInterval && this.options.statsInterval > 0) {
742
- this.statsInterval = setInterval(() => this.updateStats(), this.options.statsInterval);
743
- }
744
-
745
- this.monitorResources();
746
-
747
- if (this.config.backup.enabled) {
748
- this.setupBackups();
749
- }
750
-
751
- setTimeout(() => {
752
- if (this.serverInfo.status === 'starting') {
753
- this.serverInfo.status = 'running';
754
- this.logger.success('Server started successfully!');
755
-
756
- if (this.options.enableViaVersion !== false) {
757
- this.logger.info('ViaVersion is active - players from older versions can connect');
758
- }
759
-
760
- if (this.options.enableSkinRestorer !== false) {
761
- this.logger.info('SkinRestorer is active - player skins will be restored');
762
- }
763
-
764
- if (this.worldSize > 0) {
765
- this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
766
- }
767
-
768
- if (this.owners.size > 0) {
769
- this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
770
- }
771
-
772
- this.emit('ready', this.serverInfo);
773
- this.startMemoryMonitor();
774
- }
775
- }, 10000);
776
-
777
- return this.serverInfo;
778
- }
779
-
780
976
  private startMemoryMonitor(): void {
781
977
  if (!this.options.memoryMonitor?.enabled) return;
782
978
 
@@ -877,6 +1073,10 @@ export class MinecraftServer extends EventEmitter {
877
1073
  this.backupCron.stop();
878
1074
  }
879
1075
 
1076
+ if (this.cleanLogsCron) {
1077
+ this.cleanLogsCron.stop();
1078
+ }
1079
+
880
1080
  if (this.memoryMonitorInterval) {
881
1081
  clearInterval(this.memoryMonitorInterval);
882
1082
  }
@@ -889,6 +1089,7 @@ export class MinecraftServer extends EventEmitter {
889
1089
  this.geyser.stop();
890
1090
  }
891
1091
 
1092
+ this.cleanPaperCache();
892
1093
  this.logger.success('Server stopped');
893
1094
  }
894
1095