@dimzxzzx07/mc-headless 1.8.0 → 1.9.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.
Files changed (37) hide show
  1. package/README.md +317 -703
  2. package/dist/core/JavaChecker.d.ts +16 -3
  3. package/dist/core/JavaChecker.d.ts.map +1 -1
  4. package/dist/core/JavaChecker.js +171 -65
  5. package/dist/core/JavaChecker.js.map +1 -1
  6. package/dist/core/MinecraftServer.d.ts +15 -0
  7. package/dist/core/MinecraftServer.d.ts.map +1 -1
  8. package/dist/core/MinecraftServer.js +200 -110
  9. package/dist/core/MinecraftServer.js.map +1 -1
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +31 -16
  13. package/dist/index.js.map +1 -1
  14. package/dist/platforms/BedrockServer.d.ts.map +1 -1
  15. package/dist/platforms/BedrockServer.js +2 -0
  16. package/dist/platforms/BedrockServer.js.map +1 -1
  17. package/dist/platforms/JavaServer.d.ts.map +1 -1
  18. package/dist/platforms/JavaServer.js +2 -0
  19. package/dist/platforms/JavaServer.js.map +1 -1
  20. package/dist/platforms/SkinRestorer.d.ts +14 -0
  21. package/dist/platforms/SkinRestorer.d.ts.map +1 -0
  22. package/dist/platforms/SkinRestorer.js +145 -0
  23. package/dist/platforms/SkinRestorer.js.map +1 -0
  24. package/dist/platforms/SkinsRestorer.d.ts +14 -0
  25. package/dist/platforms/SkinsRestorer.d.ts.map +1 -0
  26. package/dist/platforms/SkinsRestorer.js +145 -0
  27. package/dist/platforms/SkinsRestorer.js.map +1 -0
  28. package/dist/types/index.d.ts +2 -0
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/package.json +1 -1
  31. package/src/core/JavaChecker.ts +162 -61
  32. package/src/core/MinecraftServer.ts +233 -120
  33. package/src/index.ts +33 -17
  34. package/src/platforms/BedrockServer.ts +2 -0
  35. package/src/platforms/JavaServer.ts +2 -0
  36. package/src/platforms/SkinRestorer.ts +127 -0
  37. package/src/types/index.ts +2 -0
@@ -1,10 +1,9 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import { spawn, exec, execSync } from 'child_process';
3
- import pidusage from 'pidusage';
4
3
  import * as cron from 'node-cron';
5
4
  import { MinecraftConfig, ServerInfo, Player } from '../types';
6
5
  import { ConfigHandler } from './ConfigHandler';
7
- import { JavaChecker } from './JavaChecker';
6
+ import { JavaChecker, JavaInfo } from './JavaChecker';
8
7
  import { FileUtils } from '../utils/FileUtils';
9
8
  import { Logger } from '../utils/Logger';
10
9
  import { SystemDetector } from '../utils/SystemDetector';
@@ -15,6 +14,7 @@ import { FabricEngine } from '../engines/FabricEngine';
15
14
  import { ServerEngine } from '../engines/ServerEngine';
16
15
  import { GeyserBridge } from '../platforms/GeyserBridge';
17
16
  import { ViaVersionManager } from '../platforms/ViaVersion';
17
+ import { SkinRestorerManager } from '../platforms/SkinRestorer';
18
18
  import * as path from 'path';
19
19
  import * as fs from 'fs-extra';
20
20
 
@@ -22,9 +22,11 @@ export interface MinecraftServerOptions extends Partial<MinecraftConfig> {
22
22
  enableViaVersion?: boolean;
23
23
  enableViaBackwards?: boolean;
24
24
  enableViaRewind?: boolean;
25
+ enableSkinRestorer?: boolean;
25
26
  enableProtocolSupport?: boolean;
26
27
  customJavaArgs?: string[];
27
28
  javaVersion?: '17' | '21' | 'auto';
29
+ usePortableJava?: boolean;
28
30
  memoryMonitor?: {
29
31
  enabled: boolean;
30
32
  threshold: number;
@@ -42,6 +44,8 @@ export interface MinecraftServerOptions extends Partial<MinecraftConfig> {
42
44
  prefix: string;
43
45
  enabled: boolean;
44
46
  };
47
+ silentMode?: boolean;
48
+ statsInterval?: number;
45
49
  }
46
50
 
47
51
  export class MinecraftServer extends EventEmitter {
@@ -51,18 +55,26 @@ export class MinecraftServer extends EventEmitter {
51
55
  private engine: ServerEngine;
52
56
  private geyser: GeyserBridge;
53
57
  private viaVersion: ViaVersionManager;
58
+ private skinRestorer: SkinRestorerManager;
54
59
  private process: any = null;
55
60
  private serverInfo: ServerInfo;
56
61
  private players: Map<string, Player> = new Map();
57
62
  private backupCron: cron.ScheduledTask | null = null;
58
63
  private startTime: Date | null = null;
59
64
  private memoryMonitorInterval: NodeJS.Timeout | null = null;
65
+ private statsInterval: NodeJS.Timeout | null = null;
60
66
  private memoryUsageHistory: number[] = [];
61
67
  private worldSize: number = 0;
62
68
  private playerCount: number = 0;
63
69
  private javaCommand: string = 'java';
70
+ private javaInfo: JavaInfo | null = null;
64
71
  private owners: Set<string> = new Set();
65
72
  private ownerCommandPrefix: string = '!';
73
+ private lastCpuTotal: number = 0;
74
+ private lastCpuTime: number = 0;
75
+ private cpuUsage: number = 0;
76
+ private cgroupMemory: number = 0;
77
+ private cgroupCpu: number = 0;
66
78
 
67
79
  constructor(userConfig: MinecraftServerOptions = {}) {
68
80
  super();
@@ -71,11 +83,12 @@ export class MinecraftServer extends EventEmitter {
71
83
 
72
84
  this.options = {
73
85
  javaVersion: 'auto',
86
+ usePortableJava: true,
74
87
  memoryMonitor: {
75
88
  enabled: true,
76
89
  threshold: 90,
77
- interval: 10000,
78
- action: 'restart'
90
+ interval: 30000,
91
+ action: 'warn'
79
92
  },
80
93
  autoInstallJava: true,
81
94
  networkOptimization: {
@@ -88,6 +101,8 @@ export class MinecraftServer extends EventEmitter {
88
101
  prefix: '!',
89
102
  enabled: true
90
103
  },
104
+ silentMode: true,
105
+ statsInterval: 30000,
91
106
  ...userConfig
92
107
  };
93
108
 
@@ -105,6 +120,7 @@ export class MinecraftServer extends EventEmitter {
105
120
  this.engine = this.createEngine();
106
121
  this.geyser = new GeyserBridge();
107
122
  this.viaVersion = new ViaVersionManager();
123
+ this.skinRestorer = new SkinRestorerManager();
108
124
 
109
125
  this.serverInfo = {
110
126
  pid: 0,
@@ -118,8 +134,32 @@ export class MinecraftServer extends EventEmitter {
118
134
  maxPlayers: this.config.world.maxPlayers,
119
135
  uptime: 0,
120
136
  memory: { used: 0, max: 0 },
137
+ cpu: 0,
121
138
  status: 'stopped'
122
139
  };
140
+
141
+ this.detectCgroupLimits();
142
+ }
143
+
144
+ private detectCgroupLimits(): void {
145
+ try {
146
+ if (fs.existsSync('/sys/fs/cgroup/memory/memory.limit_in_bytes')) {
147
+ const limit = fs.readFileSync('/sys/fs/cgroup/memory/memory.limit_in_bytes', 'utf8');
148
+ this.cgroupMemory = parseInt(limit) / 1024 / 1024;
149
+ this.logger.debug(`Cgroup memory limit: ${this.cgroupMemory} MB`);
150
+ }
151
+
152
+ if (fs.existsSync('/sys/fs/cgroup/cpu/cpu.cfs_quota_us') && fs.existsSync('/sys/fs/cgroup/cpu/cpu.cfs_period_us')) {
153
+ const quota = parseInt(fs.readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_quota_us', 'utf8'));
154
+ const period = parseInt(fs.readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_period_us', 'utf8'));
155
+ if (quota > 0 && period > 0) {
156
+ this.cgroupCpu = quota / period;
157
+ this.logger.debug(`Cgroup CPU limit: ${this.cgroupCpu} cores`);
158
+ }
159
+ }
160
+ } catch (error) {
161
+ this.logger.debug('Not running in cgroup environment');
162
+ }
123
163
  }
124
164
 
125
165
  private createEngine(): ServerEngine {
@@ -163,42 +203,43 @@ export class MinecraftServer extends EventEmitter {
163
203
  return;
164
204
  }
165
205
 
166
- const hasJava = await JavaChecker.checkJava();
167
- if (!hasJava) {
168
- this.logger.info('Java not found, attempting to install...');
169
-
170
- const osType = SystemDetector.getOS();
171
- const distro = SystemDetector.getDistro();
172
- const targetVersion = this.options.javaVersion === 'auto' ? '17' : this.options.javaVersion || '17';
173
-
174
- if (osType === 'linux' || osType === 'android') {
175
- await this.installJavaLinux(distro, targetVersion);
176
- } else if (osType === 'darwin') {
177
- await this.installJavaMac(targetVersion);
178
- } else if (osType === 'windows') {
179
- this.logger.error('Windows detected. Please install Java manually from https://adoptium.net');
180
- throw new Error('Java not installed');
181
- }
206
+ const targetVersion = this.options.javaVersion === 'auto' ? '17' : this.options.javaVersion || '17';
207
+
208
+ if (this.options.usePortableJava) {
209
+ this.javaInfo = await JavaChecker.getOrDownloadPortableJava(targetVersion);
210
+ this.javaCommand = this.javaInfo.path;
211
+ this.logger.success(`Using portable Java ${this.javaInfo.version} from ${this.javaInfo.path}`);
182
212
  } else {
183
- const detectedVersion = await this.detectJavaVersion();
184
- this.logger.info(`Detected Java version: ${detectedVersion}`);
185
-
186
- if (this.options.javaVersion !== 'auto' && detectedVersion !== this.options.javaVersion) {
187
- this.logger.warning(`Server configured for Java ${this.options.javaVersion} but detected ${detectedVersion}. This may cause issues.`);
188
- }
189
-
190
- if (detectedVersion === '21' || detectedVersion === '17') {
191
- this.javaCommand = 'java';
192
- this.logger.success(`Using Java ${detectedVersion}`);
193
- } else if (detectedVersion === '11' || detectedVersion === '8') {
194
- this.logger.warning(`Java ${detectedVersion} is too old. Minecraft 1.21+ requires Java 17 or 21.`);
195
- this.logger.info('Attempting to install Java 17...');
213
+ const hasJava = await JavaChecker.checkJava();
214
+ if (!hasJava) {
215
+ this.logger.info('Java not found, attempting to install system Java...');
216
+ const osType = SystemDetector.getOS();
196
217
  const distro = SystemDetector.getDistro();
197
- await this.installJavaLinux(distro, '17');
218
+
219
+ if (osType === 'linux' || osType === 'android') {
220
+ await this.installJavaLinux(distro, targetVersion);
221
+ } else if (osType === 'darwin') {
222
+ await this.installJavaMac(targetVersion);
223
+ } else if (osType === 'windows') {
224
+ this.logger.error('Windows detected. Please install Java manually from https://adoptium.net');
225
+ throw new Error('Java not installed');
226
+ }
227
+ } else {
228
+ const detectedVersion = await this.detectJavaVersion();
229
+ this.logger.info(`Detected system Java version: ${detectedVersion}`);
230
+
231
+ if (detectedVersion === '21' || detectedVersion === '17') {
232
+ this.javaCommand = 'java';
233
+ this.logger.success(`Using system Java ${detectedVersion}`);
234
+ } else {
235
+ this.logger.warning(`System Java ${detectedVersion} is not optimal. Switching to portable Java...`);
236
+ this.javaInfo = await JavaChecker.getOrDownloadPortableJava(targetVersion);
237
+ this.javaCommand = this.javaInfo.path;
238
+ }
198
239
  }
199
240
  }
200
241
 
201
- const finalVersion = await this.detectJavaVersion();
242
+ const finalVersion = this.javaInfo ? this.javaInfo.version : await this.detectJavaVersion();
202
243
  this.logger.success(`Java version: ${finalVersion}`);
203
244
  }
204
245
 
@@ -378,6 +419,30 @@ export class MinecraftServer extends EventEmitter {
378
419
  return [...baseArgs, ...gcArgs];
379
420
  }
380
421
 
422
+ private buildEnvironment(): NodeJS.ProcessEnv {
423
+ const env: NodeJS.ProcessEnv = { ...process.env };
424
+
425
+ env.MALLOC_ARENA_MAX = '2';
426
+
427
+ env._JAVA_OPTIONS = `-Xmx${this.config.memory.max}`;
428
+
429
+ if (this.javaInfo && this.javaInfo.type === 'portable') {
430
+ env.JAVA_HOME = path.dirname(path.dirname(this.javaInfo.path));
431
+ env.PATH = `${path.dirname(this.javaInfo.path)}:${env.PATH}`;
432
+ }
433
+
434
+ if (this.cgroupMemory > 0) {
435
+ const memLimit = Math.min(this.parseMemory(this.config.memory.max), this.cgroupMemory);
436
+ env._JAVA_OPTIONS += ` -XX:MaxRAM=${memLimit}M`;
437
+ }
438
+
439
+ if (this.cgroupCpu > 0) {
440
+ env._JAVA_OPTIONS += ` -XX:ActiveProcessorCount=${Math.floor(this.cgroupCpu)}`;
441
+ }
442
+
443
+ return env;
444
+ }
445
+
381
446
  private processOwnerCommand(player: string, command: string): void {
382
447
  if (!this.options.ownerCommands?.enabled) return;
383
448
  if (!this.owners.has(player.toLowerCase())) return;
@@ -633,6 +698,51 @@ export class MinecraftServer extends EventEmitter {
633
698
  });
634
699
  }
635
700
 
701
+ private async updateStats(): Promise<void> {
702
+ if (!this.process || this.serverInfo.status !== 'running') return;
703
+
704
+ try {
705
+ const memMax = this.parseMemory(this.config.memory.max);
706
+
707
+ if (fs.existsSync('/sys/fs/cgroup/memory/memory.usage_in_bytes')) {
708
+ const usage = parseInt(fs.readFileSync('/sys/fs/cgroup/memory/memory.usage_in_bytes', 'utf8'));
709
+ this.serverInfo.memory.used = Math.round(usage / 1024 / 1024);
710
+ } else {
711
+ const stats = await import('pidusage');
712
+ const usage = await stats.default(this.process.pid);
713
+ this.serverInfo.memory.used = Math.round(usage.memory / 1024 / 1024);
714
+ }
715
+
716
+ this.serverInfo.memory.max = memMax;
717
+
718
+ if (fs.existsSync('/sys/fs/cgroup/cpuacct/cpuacct.usage')) {
719
+ const cpuTotal = parseInt(fs.readFileSync('/sys/fs/cgroup/cpuacct/cpuacct.usage', 'utf8'));
720
+ const now = Date.now();
721
+
722
+ if (this.lastCpuTotal > 0) {
723
+ const cpuDiff = cpuTotal - this.lastCpuTotal;
724
+ const timeDiff = now - this.lastCpuTime;
725
+ this.cpuUsage = (cpuDiff / timeDiff / 1e6) * 100;
726
+ if (this.cgroupCpu > 0) {
727
+ this.cpuUsage = this.cpuUsage / this.cgroupCpu;
728
+ }
729
+ this.serverInfo.cpu = Math.min(100, Math.max(0, Math.round(this.cpuUsage)));
730
+ }
731
+
732
+ this.lastCpuTotal = cpuTotal;
733
+ this.lastCpuTime = now;
734
+ }
735
+
736
+ this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
737
+ this.serverInfo.players = this.players.size;
738
+
739
+ this.emit('resource', this.serverInfo);
740
+
741
+ } catch (error) {
742
+ this.logger.error('Stats update error:', error);
743
+ }
744
+ }
745
+
636
746
  public async start(): Promise<ServerInfo> {
637
747
  this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
638
748
 
@@ -680,6 +790,12 @@ export class MinecraftServer extends EventEmitter {
680
790
  }
681
791
  }
682
792
 
793
+ if (this.options.enableSkinRestorer !== false) {
794
+ this.logger.info('Enabling SkinRestorer for player skins...');
795
+ await FileUtils.ensureDir(this.config.folders.plugins);
796
+ await this.skinRestorer.setup(this.config);
797
+ }
798
+
683
799
  const javaArgs = this.buildJavaArgs();
684
800
  const serverJar = this.engine.getServerJar(jarPath);
685
801
  const serverArgs = this.engine.getServerArgs();
@@ -699,10 +815,13 @@ export class MinecraftServer extends EventEmitter {
699
815
  fullArgs.unshift('-Dnet.kyori.adventure.text.serializer.legacy.AMPMSupport=true');
700
816
  }
701
817
 
818
+ const env = this.buildEnvironment();
819
+
702
820
  this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
703
821
 
704
822
  this.process = spawn(this.javaCommand, fullArgs, {
705
823
  cwd: serverDir,
824
+ env: env,
706
825
  stdio: ['pipe', 'pipe', 'pipe']
707
826
  });
708
827
 
@@ -710,71 +829,52 @@ export class MinecraftServer extends EventEmitter {
710
829
  this.serverInfo.status = 'starting';
711
830
  this.startTime = new Date();
712
831
 
713
- this.process.stdout.on('data', (data: Buffer) => {
714
- const output = data.toString();
715
- process.stdout.write(output);
716
-
717
- if (output.includes('Done') || output.includes('For help, type "help"')) {
718
- this.serverInfo.status = 'running';
719
- this.logger.success('Server started successfully!');
720
-
721
- if (this.options.enableViaVersion !== false) {
722
- this.logger.info('ViaVersion is active - players from older versions can connect');
723
- }
724
-
725
- if (this.worldSize > 0) {
726
- this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
727
- }
728
-
729
- if (this.owners.size > 0) {
730
- this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
731
- }
732
-
733
- this.emit('ready', this.serverInfo);
734
- this.startMemoryMonitor();
735
- }
736
-
737
- if (output.includes('joined the game')) {
738
- const match = output.match(/(\w+) joined the game/);
739
- if (match) {
740
- this.playerCount++;
741
- this.handlePlayerJoin(match[1]);
742
-
743
- if (this.owners.has(match[1].toLowerCase())) {
744
- this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
832
+ if (this.options.silentMode) {
833
+ this.process.stdout.pipe(process.stdout);
834
+ this.process.stderr.pipe(process.stderr);
835
+ } else {
836
+ this.process.stdout.on('data', (data: Buffer) => {
837
+ const output = data.toString();
838
+ process.stdout.write(output);
839
+
840
+ if (output.includes('joined the game')) {
841
+ const match = output.match(/(\w+) joined the game/);
842
+ if (match) {
843
+ this.playerCount++;
844
+ this.handlePlayerJoin(match[1]);
845
+
846
+ if (this.owners.has(match[1].toLowerCase())) {
847
+ this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
848
+ }
745
849
  }
746
850
  }
747
- }
748
851
 
749
- if (output.includes('left the game')) {
750
- const match = output.match(/(\w+) left the game/);
751
- if (match) {
752
- this.playerCount--;
753
- this.handlePlayerLeave(match[1]);
852
+ if (output.includes('left the game')) {
853
+ const match = output.match(/(\w+) left the game/);
854
+ if (match) {
855
+ this.playerCount--;
856
+ this.handlePlayerLeave(match[1]);
857
+ }
754
858
  }
755
- }
756
859
 
757
- if (output.includes('<') && output.includes('>')) {
758
- const chatMatch = output.match(/<(\w+)>\s+(.+)/);
759
- if (chatMatch) {
760
- const player = chatMatch[1];
761
- const message = chatMatch[2];
762
-
763
- if (message.startsWith(this.ownerCommandPrefix)) {
764
- const command = message.substring(this.ownerCommandPrefix.length);
765
- this.processOwnerCommand(player, command);
860
+ if (output.includes('<') && output.includes('>')) {
861
+ const chatMatch = output.match(/<(\w+)>\s+(.+)/);
862
+ if (chatMatch) {
863
+ const player = chatMatch[1];
864
+ const message = chatMatch[2];
865
+
866
+ if (message.startsWith(this.ownerCommandPrefix)) {
867
+ const command = message.substring(this.ownerCommandPrefix.length);
868
+ this.processOwnerCommand(player, command);
869
+ }
766
870
  }
767
871
  }
768
- }
769
-
770
- if (output.includes('[ViaVersion]')) {
771
- this.logger.debug(`[ViaVersion] ${output.trim()}`);
772
- }
773
- });
872
+ });
774
873
 
775
- this.process.stderr.on('data', (data: Buffer) => {
776
- process.stderr.write(data.toString());
777
- });
874
+ this.process.stderr.on('data', (data: Buffer) => {
875
+ process.stderr.write(data.toString());
876
+ });
877
+ }
778
878
 
779
879
  this.process.on('exit', (code: number) => {
780
880
  this.serverInfo.status = 'stopped';
@@ -788,12 +888,42 @@ export class MinecraftServer extends EventEmitter {
788
888
  this.emit('stop', { code });
789
889
  });
790
890
 
891
+ if (this.options.statsInterval && this.options.statsInterval > 0) {
892
+ this.statsInterval = setInterval(() => this.updateStats(), this.options.statsInterval);
893
+ }
894
+
791
895
  this.monitorResources();
792
896
 
793
897
  if (this.config.backup.enabled) {
794
898
  this.setupBackups();
795
899
  }
796
900
 
901
+ setTimeout(() => {
902
+ if (this.serverInfo.status === 'starting') {
903
+ this.serverInfo.status = 'running';
904
+ this.logger.success('Server started successfully!');
905
+
906
+ if (this.options.enableViaVersion !== false) {
907
+ this.logger.info('ViaVersion is active - players from older versions can connect');
908
+ }
909
+
910
+ if (this.options.enableSkinRestorer !== false) {
911
+ this.logger.info('SkinRestorer is active - player skins will be restored');
912
+ }
913
+
914
+ if (this.worldSize > 0) {
915
+ this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
916
+ }
917
+
918
+ if (this.owners.size > 0) {
919
+ this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
920
+ }
921
+
922
+ this.emit('ready', this.serverInfo);
923
+ this.startMemoryMonitor();
924
+ }
925
+ }, 10000);
926
+
797
927
  return this.serverInfo;
798
928
  }
799
929
 
@@ -801,16 +931,16 @@ export class MinecraftServer extends EventEmitter {
801
931
  if (!this.options.memoryMonitor?.enabled) return;
802
932
 
803
933
  const threshold = this.options.memoryMonitor.threshold || 90;
804
- const interval = this.options.memoryMonitor.interval || 10000;
934
+ const interval = this.options.memoryMonitor.interval || 30000;
805
935
  const action = this.options.memoryMonitor.action || 'warn';
806
936
 
807
937
  this.memoryMonitorInterval = setInterval(async () => {
808
938
  if (this.serverInfo.status !== 'running' || !this.process) return;
809
939
 
810
940
  try {
811
- const stats = await pidusage(this.process.pid);
812
- const memMax = this.parseMemory(this.config.memory.max);
813
- const memPercent = (stats.memory / (memMax * 1024 * 1024)) * 100;
941
+ await this.updateStats();
942
+
943
+ const memPercent = (this.serverInfo.memory.used / this.serverInfo.memory.max) * 100;
814
944
 
815
945
  this.memoryUsageHistory.push(memPercent);
816
946
  if (this.memoryUsageHistory.length > 10) {
@@ -901,6 +1031,10 @@ export class MinecraftServer extends EventEmitter {
901
1031
  clearInterval(this.memoryMonitorInterval);
902
1032
  }
903
1033
 
1034
+ if (this.statsInterval) {
1035
+ clearInterval(this.statsInterval);
1036
+ }
1037
+
904
1038
  if (this.config.platform === 'all') {
905
1039
  this.geyser.stop();
906
1040
  }
@@ -918,17 +1052,7 @@ export class MinecraftServer extends EventEmitter {
918
1052
  }
919
1053
 
920
1054
  public async getInfo(): Promise<ServerInfo> {
921
- if (this.serverInfo.status === 'running' && this.process) {
922
- try {
923
- const stats = await pidusage(this.process.pid);
924
- this.serverInfo.memory = {
925
- used: Math.round(stats.memory / 1024 / 1024),
926
- max: this.parseMemory(this.config.memory.max)
927
- };
928
- this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
929
- } catch {}
930
- }
931
-
1055
+ await this.updateStats();
932
1056
  return this.serverInfo;
933
1057
  }
934
1058
 
@@ -963,21 +1087,10 @@ export class MinecraftServer extends EventEmitter {
963
1087
  setInterval(async () => {
964
1088
  if (this.serverInfo.status === 'running' && this.process) {
965
1089
  try {
966
- const stats = await pidusage(this.process.pid);
967
- this.serverInfo.memory = {
968
- used: Math.round(stats.memory / 1024 / 1024),
969
- max: this.parseMemory(this.config.memory.max)
970
- };
971
- this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
972
-
973
- if (stats.cpu > 80) {
974
- this.logger.warning(`High CPU usage: ${stats.cpu}%`);
975
- }
976
-
977
- this.emit('resource', this.serverInfo);
1090
+ await this.updateStats();
978
1091
  } catch {}
979
1092
  }
980
- }, 5000);
1093
+ }, 30000);
981
1094
  }
982
1095
 
983
1096
  private setupBackups(): void {
package/src/index.ts CHANGED
@@ -1,6 +1,9 @@
1
+ #!/usr/bin/env node
2
+
1
3
  import { MinecraftServer } from './core/MinecraftServer';
2
4
  import { Logger, LogLevel } from './utils/Logger';
3
5
  import { SystemDetector } from './utils/SystemDetector';
6
+ import { JavaChecker } from './core/JavaChecker';
4
7
 
5
8
  export * from './core/MinecraftServer';
6
9
  export * from './core/ConfigHandler';
@@ -9,6 +12,8 @@ export * from './core/ServerManager';
9
12
  export * from './platforms/JavaServer';
10
13
  export * from './platforms/BedrockServer';
11
14
  export * from './platforms/GeyserBridge';
15
+ export * from './platforms/ViaVersion';
16
+ export * from './platforms/SkinRestorer';
12
17
  export * from './engines/Downloader';
13
18
  export * from './engines/PaperEngine';
14
19
  export * from './engines/VanillaEngine';
@@ -29,40 +34,49 @@ if (require.main === module) {
29
34
  const systemInfo = SystemDetector.getSystemInfo();
30
35
  logger.info('System Information:', systemInfo);
31
36
 
37
+ JavaChecker.cleanupOldJava();
38
+
32
39
  const server = new MinecraftServer({
33
40
  platform: 'java',
34
- version: '1.20.1',
41
+ version: '1.21.11',
35
42
  type: 'paper',
43
+ usePortableJava: true,
36
44
  memory: {
37
- init: '1G',
38
- max: '2G',
45
+ init: '2G',
46
+ max: '4G',
39
47
  useAikarsFlags: true
40
48
  },
41
49
  world: {
42
50
  difficulty: 'normal',
43
51
  hardcore: false,
44
52
  gamemode: 'survival',
45
- maxPlayers: 10,
46
- viewDistance: 10,
53
+ maxPlayers: 20,
54
+ viewDistance: 6,
47
55
  levelName: 'world'
48
- }
56
+ },
57
+ enableViaVersion: true,
58
+ enableViaBackwards: true,
59
+ enableViaRewind: true,
60
+ enableSkinRestorer: true,
61
+ silentMode: true,
62
+ statsInterval: 30000
49
63
  });
50
64
 
51
65
  server.on('ready', (info) => {
52
- logger.success(`
53
- Mc Headless - Powered By Dimzxzzx07
54
- Address: ${info.ip}:${info.port}
55
- Version: ${info.version} ${info.type}
56
- Players: 0/${info.maxPlayers}
66
+ console.log(`
67
+ Minecraft Headless - Powered By Dimzxzzx07
68
+ IP: ${info.ip}:${info.port}
69
+ Version: ${info.version}
70
+ Players: ${info.players}/${info.maxPlayers}
71
+ Memory: ${info.memory.used}/${info.memory.max} MB
72
+ CPU: ${info.cpu}%
57
73
  `);
58
74
  });
59
75
 
60
- server.on('player-join', (player) => {
61
- logger.info(`Player joined: ${player.name}`);
62
- });
63
-
64
- server.on('player-leave', (name) => {
65
- logger.info(`Player left: ${name}`);
76
+ server.on('resource', (info) => {
77
+ if (info.memory.used > info.memory.max * 0.8) {
78
+ logger.warning(`High memory usage: ${info.memory.used}/${info.memory.max} MB (${info.cpu}% CPU)`);
79
+ }
66
80
  });
67
81
 
68
82
  server.start().catch(error => {
@@ -72,12 +86,14 @@ Mc Headless - Powered By Dimzxzzx07
72
86
  process.on('SIGINT', async () => {
73
87
  logger.info('Received SIGINT, stopping server...');
74
88
  await server.stop();
89
+ JavaChecker.cleanupOldJava();
75
90
  process.exit(0);
76
91
  });
77
92
 
78
93
  process.on('SIGTERM', async () => {
79
94
  logger.info('Received SIGTERM, stopping server...');
80
95
  await server.stop();
96
+ JavaChecker.cleanupOldJava();
81
97
  process.exit(0);
82
98
  });
83
99
  }
@@ -39,6 +39,7 @@ export class BedrockServer extends EventEmitter {
39
39
  maxPlayers: this.config.world.maxPlayers,
40
40
  uptime: 0,
41
41
  memory: { used: 0, max: 0 },
42
+ cpu: 0,
42
43
  status: 'stopped'
43
44
  };
44
45
  }
@@ -258,6 +259,7 @@ export class BedrockServer extends EventEmitter {
258
259
  used: Math.round(stats.memory / 1024 / 1024),
259
260
  max: 0
260
261
  };
262
+ this.serverInfo.cpu = Math.round(stats.cpu);
261
263
  this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
262
264
 
263
265
  this.emit('resource', this.serverInfo);
@@ -41,6 +41,7 @@ export class JavaServer extends EventEmitter {
41
41
  maxPlayers: this.config.world.maxPlayers,
42
42
  uptime: 0,
43
43
  memory: { used: 0, max: 0 },
44
+ cpu: 0,
44
45
  status: 'stopped'
45
46
  };
46
47
  }
@@ -176,6 +177,7 @@ export class JavaServer extends EventEmitter {
176
177
  used: Math.round(stats.memory / 1024 / 1024),
177
178
  max: this.parseMemory(this.config.memory.max)
178
179
  };
180
+ this.serverInfo.cpu = Math.round(stats.cpu);
179
181
  this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
180
182
 
181
183
  if (stats.cpu > 80) {