@dimzxzzx07/mc-headless 1.0.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 (100) hide show
  1. package/README.md +0 -0
  2. package/dist/core/ConfigHandler.d.ts +12 -0
  3. package/dist/core/ConfigHandler.d.ts.map +1 -0
  4. package/dist/core/ConfigHandler.js +118 -0
  5. package/dist/core/ConfigHandler.js.map +1 -0
  6. package/dist/core/JavaChecker.d.ts +8 -0
  7. package/dist/core/JavaChecker.d.ts.map +1 -0
  8. package/dist/core/JavaChecker.js +68 -0
  9. package/dist/core/JavaChecker.js.map +1 -0
  10. package/dist/core/MinecraftServer.d.ts +28 -0
  11. package/dist/core/MinecraftServer.d.ts.map +1 -0
  12. package/dist/core/MinecraftServer.js +312 -0
  13. package/dist/core/MinecraftServer.js.map +1 -0
  14. package/dist/core/ServerManager.d.ts +30 -0
  15. package/dist/core/ServerManager.d.ts.map +1 -0
  16. package/dist/core/ServerManager.js +203 -0
  17. package/dist/core/ServerManager.js.map +1 -0
  18. package/dist/engines/Downloader.d.ts +16 -0
  19. package/dist/engines/Downloader.d.ts.map +1 -0
  20. package/dist/engines/Downloader.js +159 -0
  21. package/dist/engines/Downloader.js.map +1 -0
  22. package/dist/engines/FabricEngine.d.ts +11 -0
  23. package/dist/engines/FabricEngine.d.ts.map +1 -0
  24. package/dist/engines/FabricEngine.js +70 -0
  25. package/dist/engines/FabricEngine.js.map +1 -0
  26. package/dist/engines/ForgeEngine.d.ts +11 -0
  27. package/dist/engines/ForgeEngine.d.ts.map +1 -0
  28. package/dist/engines/ForgeEngine.js +84 -0
  29. package/dist/engines/ForgeEngine.js.map +1 -0
  30. package/dist/engines/PaperEngine.d.ts +11 -0
  31. package/dist/engines/PaperEngine.d.ts.map +1 -0
  32. package/dist/engines/PaperEngine.js +88 -0
  33. package/dist/engines/PaperEngine.js.map +1 -0
  34. package/dist/engines/ServerEngine.d.ts +10 -0
  35. package/dist/engines/ServerEngine.d.ts.map +1 -0
  36. package/dist/engines/ServerEngine.js +3 -0
  37. package/dist/engines/ServerEngine.js.map +1 -0
  38. package/dist/engines/VanillaEngine.d.ts +11 -0
  39. package/dist/engines/VanillaEngine.d.ts.map +1 -0
  40. package/dist/engines/VanillaEngine.js +70 -0
  41. package/dist/engines/VanillaEngine.js.map +1 -0
  42. package/dist/index.d.ts +20 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +89 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/platforms/BedrockServer.d.ts +30 -0
  47. package/dist/platforms/BedrockServer.d.ts.map +1 -0
  48. package/dist/platforms/BedrockServer.js +301 -0
  49. package/dist/platforms/BedrockServer.js.map +1 -0
  50. package/dist/platforms/GeyserBridge.d.ts +9 -0
  51. package/dist/platforms/GeyserBridge.d.ts.map +1 -0
  52. package/dist/platforms/GeyserBridge.js +98 -0
  53. package/dist/platforms/GeyserBridge.js.map +1 -0
  54. package/dist/platforms/JavaServer.d.ts +27 -0
  55. package/dist/platforms/JavaServer.d.ts.map +1 -0
  56. package/dist/platforms/JavaServer.js +237 -0
  57. package/dist/platforms/JavaServer.js.map +1 -0
  58. package/dist/types/index.d.ts +80 -0
  59. package/dist/types/index.d.ts.map +1 -0
  60. package/dist/types/index.js +3 -0
  61. package/dist/types/index.js.map +1 -0
  62. package/dist/utils/FileUtils.d.ts +20 -0
  63. package/dist/utils/FileUtils.d.ts.map +1 -0
  64. package/dist/utils/FileUtils.js +172 -0
  65. package/dist/utils/FileUtils.js.map +1 -0
  66. package/dist/utils/Logger.d.ts +26 -0
  67. package/dist/utils/Logger.d.ts.map +1 -0
  68. package/dist/utils/Logger.js +91 -0
  69. package/dist/utils/Logger.js.map +1 -0
  70. package/dist/utils/PropertiesParser.d.ts +7 -0
  71. package/dist/utils/PropertiesParser.d.ts.map +1 -0
  72. package/dist/utils/PropertiesParser.js +124 -0
  73. package/dist/utils/PropertiesParser.js.map +1 -0
  74. package/dist/utils/SystemDetector.d.ts +14 -0
  75. package/dist/utils/SystemDetector.d.ts.map +1 -0
  76. package/dist/utils/SystemDetector.js +152 -0
  77. package/dist/utils/SystemDetector.js.map +1 -0
  78. package/package.json +51 -0
  79. package/src/core/ConfigHandler.ts +136 -0
  80. package/src/core/JavaChecker.ts +71 -0
  81. package/src/core/MinecraftServer.ts +326 -0
  82. package/src/core/ServerManager.ts +196 -0
  83. package/src/engines/Downloader.ts +144 -0
  84. package/src/engines/FabricEngine.ts +49 -0
  85. package/src/engines/ForgeEngine.ts +65 -0
  86. package/src/engines/PaperEngine.ts +68 -0
  87. package/src/engines/ServerEngine.ts +10 -0
  88. package/src/engines/VanillaEngine.ts +49 -0
  89. package/src/index.ts +83 -0
  90. package/src/platforms/BedrockServer.ts +311 -0
  91. package/src/platforms/GeyserBridge.ts +83 -0
  92. package/src/platforms/JavaServer.ts +241 -0
  93. package/src/scripts/detect-os.sh +56 -0
  94. package/src/scripts/install-java.sh +38 -0
  95. package/src/types/index.ts +89 -0
  96. package/src/utils/FileUtils.ts +162 -0
  97. package/src/utils/Logger.ts +97 -0
  98. package/src/utils/PropertiesParser.ts +126 -0
  99. package/src/utils/SystemDetector.ts +127 -0
  100. package/tsconfig.json +23 -0
@@ -0,0 +1,311 @@
1
+ import { spawn } from 'child_process';
2
+ import { EventEmitter } from 'events';
3
+ import * as fs from 'fs-extra';
4
+ import * as path from 'path';
5
+ import * as os from 'os';
6
+ import axios from 'axios';
7
+ import * as tar from 'tar';
8
+ import pidusage from 'pidusage';
9
+ const AdmZip = require('adm-zip');
10
+ import { MinecraftConfig, ServerInfo, Player } from '../types';
11
+ import { Logger } from '../utils/Logger';
12
+ import { FileUtils } from '../utils/FileUtils';
13
+ import { PropertiesParser } from '../utils/PropertiesParser';
14
+
15
+ export class BedrockServer extends EventEmitter {
16
+ private config: MinecraftConfig;
17
+ private logger: Logger;
18
+ private process: any = null;
19
+ private serverInfo: ServerInfo;
20
+ private players: Map<string, Player> = new Map();
21
+ private startTime: Date | null = null;
22
+ private serverDir: string;
23
+ private serverExecutable: string = '';
24
+
25
+ constructor(config: MinecraftConfig, serverDir: string) {
26
+ super();
27
+ this.config = config;
28
+ this.serverDir = serverDir;
29
+ this.logger = Logger.getInstance();
30
+
31
+ this.serverInfo = {
32
+ pid: 0,
33
+ ip: this.config.network.ip,
34
+ port: this.config.network.bedrockPort || 19132,
35
+ version: this.config.version,
36
+ type: 'bedrock' as any,
37
+ platform: 'bedrock',
38
+ players: 0,
39
+ maxPlayers: this.config.world.maxPlayers,
40
+ uptime: 0,
41
+ memory: { used: 0, max: 0 },
42
+ status: 'stopped'
43
+ };
44
+ }
45
+
46
+ public async download(): Promise<string> {
47
+ this.logger.info('Downloading Bedrock server...');
48
+
49
+ const platform = this.getPlatform();
50
+ const downloadUrl = this.getDownloadUrl(platform);
51
+ const fileName = `bedrock-server-${this.config.version}.zip`;
52
+ const downloadPath = path.join(this.serverDir, fileName);
53
+
54
+ const writer = fs.createWriteStream(downloadPath);
55
+ const response = await axios({
56
+ method: 'GET',
57
+ url: downloadUrl,
58
+ responseType: 'stream'
59
+ });
60
+
61
+ const totalLength = response.headers['content-length'];
62
+ let downloadedLength = 0;
63
+
64
+ response.data.on('data', (chunk: Buffer) => {
65
+ downloadedLength += chunk.length;
66
+ if (totalLength) {
67
+ const progress = (downloadedLength / parseInt(totalLength) * 100).toFixed(2);
68
+ process.stdout.write(`\rDownloading: ${progress}%`);
69
+ }
70
+ });
71
+
72
+ response.data.pipe(writer);
73
+
74
+ return new Promise((resolve, reject) => {
75
+ writer.on('finish', async () => {
76
+ process.stdout.write('\n');
77
+ this.logger.success('Download complete');
78
+ await this.extract(downloadPath);
79
+ resolve(downloadPath);
80
+ });
81
+
82
+ writer.on('error', reject);
83
+ });
84
+ }
85
+
86
+ private getPlatform(): string {
87
+ const arch = os.arch();
88
+ const platform = os.platform();
89
+
90
+ if (platform === 'linux') {
91
+ if (arch === 'x64') return 'linux-x86_64';
92
+ if (arch === 'arm64') return 'linux-arm64';
93
+ if (arch === 'arm') return 'linux-arm32';
94
+ } else if (platform === 'win32') {
95
+ return 'windows-x86_64';
96
+ }
97
+
98
+ throw new Error(`Unsupported platform: ${platform} ${arch}`);
99
+ }
100
+
101
+ private getDownloadUrl(platform: string): string {
102
+ const version = this.config.version;
103
+ const baseUrl = 'https://www.minecraft.net/bedrockdedicatedserver/bin-linux';
104
+
105
+ const urls: Record<string, string> = {
106
+ 'linux-x86_64': `${baseUrl}/bedrock-server-${version}.zip`,
107
+ 'linux-arm64': `${baseUrl}-arm64/bedrock-server-${version}.zip`,
108
+ 'linux-arm32': `${baseUrl}-arm32/bedrock-server-${version}.zip`,
109
+ 'windows-x86_64': `https://www.minecraft.net/bedrockdedicatedserver/bin-win64/bedrock-server-${version}.zip`
110
+ };
111
+
112
+ return urls[platform];
113
+ }
114
+
115
+ private async extract(zipPath: string): Promise<void> {
116
+ this.logger.info('Extracting Bedrock server...');
117
+
118
+ if (zipPath.endsWith('.zip')) {
119
+ const zip = new AdmZip(zipPath);
120
+ zip.extractAllTo(this.serverDir, true);
121
+ } else if (zipPath.endsWith('.tar.gz')) {
122
+ await tar.extract({
123
+ file: zipPath,
124
+ cwd: this.serverDir
125
+ });
126
+ }
127
+
128
+ await fs.remove(zipPath);
129
+ this.logger.success('Extraction complete');
130
+
131
+ this.findExecutable();
132
+ }
133
+
134
+ private findExecutable(): void {
135
+ const possibleNames = ['bedrock_server', 'bedrock_server.exe', 'bedrock_server-arm64', 'bedrock_server-arm32'];
136
+
137
+ for (const name of possibleNames) {
138
+ const exePath = path.join(this.serverDir, name);
139
+ if (fs.existsSync(exePath)) {
140
+ this.serverExecutable = exePath;
141
+ fs.chmodSync(exePath, 0o755);
142
+ this.logger.debug(`Found executable: ${name}`);
143
+ break;
144
+ }
145
+ }
146
+
147
+ if (!this.serverExecutable) {
148
+ throw new Error('Could not find Bedrock server executable');
149
+ }
150
+ }
151
+
152
+ public async prepare(): Promise<void> {
153
+ this.logger.info('Preparing Bedrock server...');
154
+
155
+ const properties = PropertiesParser.generateBedrockProperties(this.config);
156
+
157
+ const serverPropsPath = path.join(this.serverDir, 'server.properties');
158
+ await FileUtils.writeProperties(serverPropsPath, properties);
159
+
160
+ const permissionsPath = path.join(this.serverDir, 'permissions.json');
161
+ if (!await FileUtils.fileExists(permissionsPath)) {
162
+ await FileUtils.writeFile(permissionsPath, JSON.stringify([], null, 2));
163
+ }
164
+
165
+ const whitelistPath = path.join(this.serverDir, 'whitelist.json');
166
+ if (!await FileUtils.fileExists(whitelistPath)) {
167
+ await FileUtils.writeFile(whitelistPath, JSON.stringify([], null, 2));
168
+ }
169
+
170
+ this.logger.success('Bedrock server prepared');
171
+ }
172
+
173
+ public async start(): Promise<ServerInfo> {
174
+ if (!this.serverExecutable) {
175
+ this.findExecutable();
176
+ }
177
+
178
+ this.logger.info(`Launching Bedrock server: ${this.serverExecutable}`);
179
+
180
+ this.process = spawn(this.serverExecutable, [], {
181
+ cwd: this.serverDir,
182
+ stdio: 'pipe'
183
+ });
184
+
185
+ this.serverInfo.pid = this.process.pid!;
186
+ this.serverInfo.status = 'starting';
187
+ this.startTime = new Date();
188
+
189
+ this.setupEventHandlers();
190
+
191
+ this.monitorResources();
192
+
193
+ return this.serverInfo;
194
+ }
195
+
196
+ private setupEventHandlers(): void {
197
+ this.process.stdout.on('data', (data: Buffer) => {
198
+ const output = data.toString();
199
+ process.stdout.write(`[Bedrock] ${output}`);
200
+
201
+ if (output.includes('Server started')) {
202
+ this.serverInfo.status = 'running';
203
+ this.logger.success('Bedrock server started successfully!');
204
+ this.emit('ready', this.serverInfo);
205
+ }
206
+
207
+ if (output.includes('Player connected:')) {
208
+ const match = output.match(/Player connected: (\w+)/);
209
+ if (match) {
210
+ this.handlePlayerJoin(match[1]);
211
+ }
212
+ }
213
+
214
+ if (output.includes('Player disconnected:')) {
215
+ const match = output.match(/Player disconnected: (\w+)/);
216
+ if (match) {
217
+ this.handlePlayerLeave(match[1]);
218
+ }
219
+ }
220
+ });
221
+
222
+ this.process.stderr.on('data', (data: Buffer) => {
223
+ process.stderr.write(`[Bedrock Error] ${data.toString()}`);
224
+ });
225
+
226
+ this.process.on('exit', (code: number) => {
227
+ this.serverInfo.status = 'stopped';
228
+ this.logger.warning(`Bedrock server stopped with code ${code}`);
229
+ this.emit('stop', { code });
230
+ });
231
+ }
232
+
233
+ private handlePlayerJoin(name: string): void {
234
+ const player: Player = {
235
+ name,
236
+ uuid: '',
237
+ ip: '',
238
+ ping: 0,
239
+ connectedAt: new Date()
240
+ };
241
+ this.players.set(name, player);
242
+ this.serverInfo.players = this.players.size;
243
+ this.emit('player-join', player);
244
+ }
245
+
246
+ private handlePlayerLeave(name: string): void {
247
+ this.players.delete(name);
248
+ this.serverInfo.players = this.players.size;
249
+ this.emit('player-leave', name);
250
+ }
251
+
252
+ private monitorResources(): void {
253
+ setInterval(async () => {
254
+ if (this.serverInfo.status === 'running' && this.process) {
255
+ try {
256
+ const stats = await pidusage(this.process.pid);
257
+ this.serverInfo.memory = {
258
+ used: Math.round(stats.memory / 1024 / 1024),
259
+ max: 0
260
+ };
261
+ this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
262
+
263
+ this.emit('resource', this.serverInfo);
264
+ } catch {}
265
+ }
266
+ }, 5000);
267
+ }
268
+
269
+ public sendCommand(command: string): void {
270
+ if (!this.process || this.serverInfo.status !== 'running') {
271
+ throw new Error('Server not running');
272
+ }
273
+
274
+ this.process.stdin.write(command + '\n');
275
+ this.logger.debug(`Command sent: ${command}`);
276
+ }
277
+
278
+ public stop(): Promise<void> {
279
+ return new Promise((resolve) => {
280
+ if (!this.process) {
281
+ resolve();
282
+ return;
283
+ }
284
+
285
+ this.logger.info('Stopping Bedrock server...');
286
+ this.serverInfo.status = 'stopping';
287
+
288
+ this.sendCommand('stop');
289
+
290
+ setTimeout(() => {
291
+ if (this.process) {
292
+ this.process.kill();
293
+ this.process = null;
294
+ }
295
+ resolve();
296
+ }, 5000);
297
+ });
298
+ }
299
+
300
+ public getInfo(): ServerInfo {
301
+ return this.serverInfo;
302
+ }
303
+
304
+ public getPlayers(): Player[] {
305
+ return Array.from(this.players.values());
306
+ }
307
+
308
+ public isRunning(): boolean {
309
+ return this.serverInfo.status === 'running';
310
+ }
311
+ }
@@ -0,0 +1,83 @@
1
+ import { MinecraftConfig } from '../types';
2
+ import { Downloader } from '../engines/Downloader';
3
+ import { FileUtils } from '../utils/FileUtils';
4
+ import { Logger } from '../utils/Logger';
5
+ import * as path from 'path';
6
+ import { spawn } from 'child_process';
7
+
8
+ export class GeyserBridge {
9
+ private logger = Logger.getInstance();
10
+ private process: any = null;
11
+
12
+ public async setup(config: MinecraftConfig): Promise<void> {
13
+ const pluginsDir = config.folders.plugins;
14
+ await FileUtils.ensureDir(pluginsDir);
15
+
16
+ const geyserJar = path.join(pluginsDir, 'geyser.jar');
17
+ const floodgateJar = path.join(pluginsDir, 'floodgate.jar');
18
+
19
+ if (!await FileUtils.fileExists(geyserJar)) {
20
+ this.logger.info('Downloading Geyser...');
21
+ const geyserInfo = Downloader.getGeyserURL();
22
+ await Downloader.downloadFile(geyserInfo, pluginsDir);
23
+ await FileUtils.moveFile(
24
+ path.join(pluginsDir, geyserInfo.fileName),
25
+ geyserJar
26
+ );
27
+ }
28
+
29
+ if (!await FileUtils.fileExists(floodgateJar)) {
30
+ this.logger.info('Downloading Floodgate...');
31
+ const floodgateInfo = Downloader.getFloodgateURL();
32
+ await Downloader.downloadFile(floodgateInfo, pluginsDir);
33
+ await FileUtils.moveFile(
34
+ path.join(pluginsDir, floodgateInfo.fileName),
35
+ floodgateJar
36
+ );
37
+ }
38
+
39
+ this.logger.success('Geyser & Floodgate installed');
40
+ }
41
+
42
+ public async startStandalone(config: MinecraftConfig, serverDir: string): Promise<void> {
43
+ const geyserJar = path.join(serverDir, 'geyer-standalone.jar');
44
+
45
+ if (!await FileUtils.fileExists(geyserJar)) {
46
+ const geyserInfo = Downloader.getGeyserURL();
47
+ await Downloader.downloadFile(geyserInfo, serverDir);
48
+ await FileUtils.moveFile(
49
+ path.join(serverDir, geyserInfo.fileName),
50
+ geyserJar
51
+ );
52
+ }
53
+
54
+ const javaArgs = [
55
+ `-Xms${config.memory.init}`,
56
+ `-Xmx${config.memory.max}`,
57
+ '-jar',
58
+ geyserJar
59
+ ];
60
+
61
+ this.logger.info('Starting Geyser standalone...');
62
+ this.process = spawn('java', javaArgs, {
63
+ cwd: serverDir,
64
+ stdio: 'pipe'
65
+ });
66
+
67
+ this.process.stdout.on('data', (data: Buffer) => {
68
+ process.stdout.write(`[Geyser] ${data.toString()}`);
69
+ });
70
+
71
+ this.process.stderr.on('data', (data: Buffer) => {
72
+ process.stderr.write(`[Geyser Error] ${data.toString()}`);
73
+ });
74
+ }
75
+
76
+ public stop(): void {
77
+ if (this.process) {
78
+ this.process.kill();
79
+ this.process = null;
80
+ this.logger.info('Geyser stopped');
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,241 @@
1
+ import { spawn } from 'child_process';
2
+ import { EventEmitter } from 'events';
3
+ import pidusage from 'pidusage';
4
+ import { MinecraftConfig, ServerInfo, Player } from '../types';
5
+ import { Logger } from '../utils/Logger';
6
+ import { FileUtils } from '../utils/FileUtils';
7
+ import { PropertiesParser } from '../utils/PropertiesParser';
8
+ import { PaperEngine } from '../engines/PaperEngine';
9
+ import { VanillaEngine } from '../engines/VanillaEngine';
10
+ import { ForgeEngine } from '../engines/ForgeEngine';
11
+ import { FabricEngine } from '../engines/FabricEngine';
12
+ import { ServerEngine } from '../engines/ServerEngine';
13
+ import * as path from 'path';
14
+
15
+ export class JavaServer extends EventEmitter {
16
+ private config: MinecraftConfig;
17
+ private logger: Logger;
18
+ private engine: ServerEngine;
19
+ private process: any = null;
20
+ private serverInfo: ServerInfo;
21
+ private players: Map<string, Player> = new Map();
22
+ private startTime: Date | null = null;
23
+ private serverDir: string;
24
+
25
+ constructor(config: MinecraftConfig, serverDir: string) {
26
+ super();
27
+ this.config = config;
28
+ this.serverDir = serverDir;
29
+ this.logger = Logger.getInstance();
30
+
31
+ this.engine = this.createEngine();
32
+
33
+ this.serverInfo = {
34
+ pid: 0,
35
+ ip: this.config.network.ip,
36
+ port: this.config.network.port,
37
+ version: this.config.version,
38
+ type: this.config.type,
39
+ platform: 'java',
40
+ players: 0,
41
+ maxPlayers: this.config.world.maxPlayers,
42
+ uptime: 0,
43
+ memory: { used: 0, max: 0 },
44
+ status: 'stopped'
45
+ };
46
+ }
47
+
48
+ private createEngine(): ServerEngine {
49
+ switch (this.config.type) {
50
+ case 'paper':
51
+ case 'purpur':
52
+ case 'spigot':
53
+ return new PaperEngine();
54
+ case 'vanilla':
55
+ return new VanillaEngine();
56
+ case 'forge':
57
+ return new ForgeEngine();
58
+ case 'fabric':
59
+ return new FabricEngine();
60
+ default:
61
+ throw new Error(`Unsupported server type: ${this.config.type}`);
62
+ }
63
+ }
64
+
65
+ public async prepare(): Promise<void> {
66
+ this.logger.info(`Preparing Java server: ${this.config.type} v${this.config.version}`);
67
+
68
+ if (this.config.autoAcceptEula) {
69
+ await FileUtils.writeFile(
70
+ path.join(this.serverDir, 'eula.txt'),
71
+ 'eula=true'
72
+ );
73
+ }
74
+
75
+ const properties = PropertiesParser.generateServerProperties(this.config);
76
+ await FileUtils.writeProperties(
77
+ path.join(this.serverDir, 'server.properties'),
78
+ properties
79
+ );
80
+
81
+ this.logger.success('Java server prepared');
82
+ }
83
+
84
+ public async start(jarPath: string): Promise<ServerInfo> {
85
+ const javaArgs = this.engine.getJavaArgs(this.config);
86
+ const serverJar = this.engine.getServerJar(jarPath);
87
+ const serverArgs = this.engine.getServerArgs();
88
+
89
+ const fullArgs = [
90
+ ...javaArgs,
91
+ '-jar',
92
+ serverJar,
93
+ ...serverArgs
94
+ ];
95
+
96
+ this.logger.info(`Launching Java server: java ${fullArgs.join(' ')}`);
97
+
98
+ this.process = spawn('java', fullArgs, {
99
+ cwd: this.serverDir,
100
+ stdio: 'pipe'
101
+ });
102
+
103
+ this.serverInfo.pid = this.process.pid!;
104
+ this.serverInfo.status = 'starting';
105
+ this.startTime = new Date();
106
+
107
+ this.setupEventHandlers();
108
+
109
+ this.monitorResources();
110
+
111
+ return this.serverInfo;
112
+ }
113
+
114
+ private setupEventHandlers(): void {
115
+ this.process.stdout.on('data', (data: Buffer) => {
116
+ const output = data.toString();
117
+ process.stdout.write(`[Java] ${output}`);
118
+
119
+ if (output.includes('Done') || output.includes('For help, type "help"')) {
120
+ this.serverInfo.status = 'running';
121
+ this.logger.success('Java server started successfully!');
122
+ this.emit('ready', this.serverInfo);
123
+ }
124
+
125
+ if (output.includes('joined the game')) {
126
+ const match = output.match(/(\w+) joined the game/);
127
+ if (match) {
128
+ this.handlePlayerJoin(match[1]);
129
+ }
130
+ }
131
+
132
+ if (output.includes('left the game')) {
133
+ const match = output.match(/(\w+) left the game/);
134
+ if (match) {
135
+ this.handlePlayerLeave(match[1]);
136
+ }
137
+ }
138
+ });
139
+
140
+ this.process.stderr.on('data', (data: Buffer) => {
141
+ process.stderr.write(`[Java Error] ${data.toString()}`);
142
+ });
143
+
144
+ this.process.on('exit', (code: number) => {
145
+ this.serverInfo.status = 'stopped';
146
+ this.logger.warning(`Java server stopped with code ${code}`);
147
+ this.emit('stop', { code });
148
+ });
149
+ }
150
+
151
+ private handlePlayerJoin(name: string): void {
152
+ const player: Player = {
153
+ name,
154
+ uuid: '',
155
+ ip: '',
156
+ ping: 0,
157
+ connectedAt: new Date()
158
+ };
159
+ this.players.set(name, player);
160
+ this.serverInfo.players = this.players.size;
161
+ this.emit('player-join', player);
162
+ }
163
+
164
+ private handlePlayerLeave(name: string): void {
165
+ this.players.delete(name);
166
+ this.serverInfo.players = this.players.size;
167
+ this.emit('player-leave', name);
168
+ }
169
+
170
+ private monitorResources(): void {
171
+ setInterval(async () => {
172
+ if (this.serverInfo.status === 'running' && this.process) {
173
+ try {
174
+ const stats = await pidusage(this.process.pid);
175
+ this.serverInfo.memory = {
176
+ used: Math.round(stats.memory / 1024 / 1024),
177
+ max: this.parseMemory(this.config.memory.max)
178
+ };
179
+ this.serverInfo.uptime = Math.floor((Date.now() - (this.startTime?.getTime() || 0)) / 1000);
180
+
181
+ if (stats.cpu > 80) {
182
+ this.logger.warning(`High CPU usage: ${stats.cpu}%`);
183
+ }
184
+
185
+ this.emit('resource', this.serverInfo);
186
+ } catch {}
187
+ }
188
+ }, 5000);
189
+ }
190
+
191
+ public sendCommand(command: string): void {
192
+ if (!this.process || this.serverInfo.status !== 'running') {
193
+ throw new Error('Server not running');
194
+ }
195
+
196
+ this.process.stdin.write(command + '\n');
197
+ this.logger.debug(`Command sent: ${command}`);
198
+ }
199
+
200
+ public stop(): Promise<void> {
201
+ return new Promise((resolve) => {
202
+ if (!this.process) {
203
+ resolve();
204
+ return;
205
+ }
206
+
207
+ this.logger.info('Stopping Java server...');
208
+ this.serverInfo.status = 'stopping';
209
+
210
+ this.sendCommand('stop');
211
+
212
+ setTimeout(() => {
213
+ if (this.process) {
214
+ this.process.kill();
215
+ this.process = null;
216
+ }
217
+ resolve();
218
+ }, 5000);
219
+ });
220
+ }
221
+
222
+ public getInfo(): ServerInfo {
223
+ return this.serverInfo;
224
+ }
225
+
226
+ public getPlayers(): Player[] {
227
+ return Array.from(this.players.values());
228
+ }
229
+
230
+ private parseMemory(memStr: string): number {
231
+ const value = parseInt(memStr);
232
+ if (memStr.endsWith('G')) {
233
+ return value * 1024;
234
+ }
235
+ return value;
236
+ }
237
+
238
+ public isRunning(): boolean {
239
+ return this.serverInfo.status === 'running';
240
+ }
241
+ }
@@ -0,0 +1,56 @@
1
+ #!/bin/bash
2
+
3
+ detect_os() {
4
+ if [ -f /etc/os-release ]; then
5
+ . /etc/os-release
6
+ echo "$ID"
7
+ elif [ -n "$TERMUX_VERSION" ]; then
8
+ echo "termux"
9
+ elif [ "$(uname)" == "Darwin" ]; then
10
+ echo "macos"
11
+ elif [ "$(expr substr $(uname -s) 1 5)" == "MINGW" ]; then
12
+ echo "windows"
13
+ else
14
+ echo "unknown"
15
+ fi
16
+ }
17
+
18
+ detect_arch() {
19
+ uname -m
20
+ }
21
+
22
+ detect_java() {
23
+ if command -v java &> /dev/null; then
24
+ java -version 2>&1 | head -n 1
25
+ else
26
+ echo "not_installed"
27
+ fi
28
+ }
29
+
30
+ detect_memory() {
31
+ if [ "$(uname)" == "Linux" ]; then
32
+ free -g | awk '/^Mem:/{print $2}'
33
+ elif [ "$(uname)" == "Darwin" ]; then
34
+ echo "$(sysctl -n hw.memsize)/1024/1024/1024" | bc
35
+ else
36
+ echo "unknown"
37
+ fi
38
+ }
39
+
40
+ detect_cpu() {
41
+ if [ "$(uname)" == "Linux" ]; then
42
+ nproc
43
+ elif [ "$(uname)" == "Darwin" ]; then
44
+ sysctl -n hw.ncpu
45
+ else
46
+ echo "unknown"
47
+ fi
48
+ }
49
+
50
+ echo "{
51
+ \"os\": \"$(detect_os)\",
52
+ \"arch\": \"$(detect_arch)\",
53
+ \"java\": \"$(detect_java)\",
54
+ \"memory_gb\": $(detect_memory),
55
+ \"cpu_cores\": $(detect_cpu)
56
+ }"