@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.
- package/README.md +317 -703
- package/dist/core/JavaChecker.d.ts +16 -3
- package/dist/core/JavaChecker.d.ts.map +1 -1
- package/dist/core/JavaChecker.js +171 -65
- package/dist/core/JavaChecker.js.map +1 -1
- package/dist/core/MinecraftServer.d.ts +15 -0
- package/dist/core/MinecraftServer.d.ts.map +1 -1
- package/dist/core/MinecraftServer.js +200 -110
- package/dist/core/MinecraftServer.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -16
- package/dist/index.js.map +1 -1
- package/dist/platforms/BedrockServer.d.ts.map +1 -1
- package/dist/platforms/BedrockServer.js +2 -0
- package/dist/platforms/BedrockServer.js.map +1 -1
- package/dist/platforms/JavaServer.d.ts.map +1 -1
- package/dist/platforms/JavaServer.js +2 -0
- package/dist/platforms/JavaServer.js.map +1 -1
- package/dist/platforms/SkinRestorer.d.ts +14 -0
- package/dist/platforms/SkinRestorer.d.ts.map +1 -0
- package/dist/platforms/SkinRestorer.js +145 -0
- package/dist/platforms/SkinRestorer.js.map +1 -0
- package/dist/platforms/SkinsRestorer.d.ts +14 -0
- package/dist/platforms/SkinsRestorer.d.ts.map +1 -0
- package/dist/platforms/SkinsRestorer.js +145 -0
- package/dist/platforms/SkinsRestorer.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/JavaChecker.ts +162 -61
- package/src/core/MinecraftServer.ts +233 -120
- package/src/index.ts +33 -17
- package/src/platforms/BedrockServer.ts +2 -0
- package/src/platforms/JavaServer.ts +2 -0
- package/src/platforms/SkinRestorer.ts +127 -0
- 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:
|
|
78
|
-
action: '
|
|
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
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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.
|
|
714
|
-
|
|
715
|
-
process.
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if (
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
776
|
-
|
|
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 ||
|
|
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
|
-
|
|
812
|
-
|
|
813
|
-
const memPercent = (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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.
|
|
41
|
+
version: '1.21.11',
|
|
35
42
|
type: 'paper',
|
|
43
|
+
usePortableJava: true,
|
|
36
44
|
memory: {
|
|
37
|
-
init: '
|
|
38
|
-
max: '
|
|
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:
|
|
46
|
-
viewDistance:
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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('
|
|
61
|
-
|
|
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) {
|