@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.
- package/README.md +0 -0
- package/dist/core/ConfigHandler.d.ts +12 -0
- package/dist/core/ConfigHandler.d.ts.map +1 -0
- package/dist/core/ConfigHandler.js +118 -0
- package/dist/core/ConfigHandler.js.map +1 -0
- package/dist/core/JavaChecker.d.ts +8 -0
- package/dist/core/JavaChecker.d.ts.map +1 -0
- package/dist/core/JavaChecker.js +68 -0
- package/dist/core/JavaChecker.js.map +1 -0
- package/dist/core/MinecraftServer.d.ts +28 -0
- package/dist/core/MinecraftServer.d.ts.map +1 -0
- package/dist/core/MinecraftServer.js +312 -0
- package/dist/core/MinecraftServer.js.map +1 -0
- package/dist/core/ServerManager.d.ts +30 -0
- package/dist/core/ServerManager.d.ts.map +1 -0
- package/dist/core/ServerManager.js +203 -0
- package/dist/core/ServerManager.js.map +1 -0
- package/dist/engines/Downloader.d.ts +16 -0
- package/dist/engines/Downloader.d.ts.map +1 -0
- package/dist/engines/Downloader.js +159 -0
- package/dist/engines/Downloader.js.map +1 -0
- package/dist/engines/FabricEngine.d.ts +11 -0
- package/dist/engines/FabricEngine.d.ts.map +1 -0
- package/dist/engines/FabricEngine.js +70 -0
- package/dist/engines/FabricEngine.js.map +1 -0
- package/dist/engines/ForgeEngine.d.ts +11 -0
- package/dist/engines/ForgeEngine.d.ts.map +1 -0
- package/dist/engines/ForgeEngine.js +84 -0
- package/dist/engines/ForgeEngine.js.map +1 -0
- package/dist/engines/PaperEngine.d.ts +11 -0
- package/dist/engines/PaperEngine.d.ts.map +1 -0
- package/dist/engines/PaperEngine.js +88 -0
- package/dist/engines/PaperEngine.js.map +1 -0
- package/dist/engines/ServerEngine.d.ts +10 -0
- package/dist/engines/ServerEngine.d.ts.map +1 -0
- package/dist/engines/ServerEngine.js +3 -0
- package/dist/engines/ServerEngine.js.map +1 -0
- package/dist/engines/VanillaEngine.d.ts +11 -0
- package/dist/engines/VanillaEngine.d.ts.map +1 -0
- package/dist/engines/VanillaEngine.js +70 -0
- package/dist/engines/VanillaEngine.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/platforms/BedrockServer.d.ts +30 -0
- package/dist/platforms/BedrockServer.d.ts.map +1 -0
- package/dist/platforms/BedrockServer.js +301 -0
- package/dist/platforms/BedrockServer.js.map +1 -0
- package/dist/platforms/GeyserBridge.d.ts +9 -0
- package/dist/platforms/GeyserBridge.d.ts.map +1 -0
- package/dist/platforms/GeyserBridge.js +98 -0
- package/dist/platforms/GeyserBridge.js.map +1 -0
- package/dist/platforms/JavaServer.d.ts +27 -0
- package/dist/platforms/JavaServer.d.ts.map +1 -0
- package/dist/platforms/JavaServer.js +237 -0
- package/dist/platforms/JavaServer.js.map +1 -0
- package/dist/types/index.d.ts +80 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/FileUtils.d.ts +20 -0
- package/dist/utils/FileUtils.d.ts.map +1 -0
- package/dist/utils/FileUtils.js +172 -0
- package/dist/utils/FileUtils.js.map +1 -0
- package/dist/utils/Logger.d.ts +26 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +91 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/PropertiesParser.d.ts +7 -0
- package/dist/utils/PropertiesParser.d.ts.map +1 -0
- package/dist/utils/PropertiesParser.js +124 -0
- package/dist/utils/PropertiesParser.js.map +1 -0
- package/dist/utils/SystemDetector.d.ts +14 -0
- package/dist/utils/SystemDetector.d.ts.map +1 -0
- package/dist/utils/SystemDetector.js +152 -0
- package/dist/utils/SystemDetector.js.map +1 -0
- package/package.json +51 -0
- package/src/core/ConfigHandler.ts +136 -0
- package/src/core/JavaChecker.ts +71 -0
- package/src/core/MinecraftServer.ts +326 -0
- package/src/core/ServerManager.ts +196 -0
- package/src/engines/Downloader.ts +144 -0
- package/src/engines/FabricEngine.ts +49 -0
- package/src/engines/ForgeEngine.ts +65 -0
- package/src/engines/PaperEngine.ts +68 -0
- package/src/engines/ServerEngine.ts +10 -0
- package/src/engines/VanillaEngine.ts +49 -0
- package/src/index.ts +83 -0
- package/src/platforms/BedrockServer.ts +311 -0
- package/src/platforms/GeyserBridge.ts +83 -0
- package/src/platforms/JavaServer.ts +241 -0
- package/src/scripts/detect-os.sh +56 -0
- package/src/scripts/install-java.sh +38 -0
- package/src/types/index.ts +89 -0
- package/src/utils/FileUtils.ts +162 -0
- package/src/utils/Logger.ts +97 -0
- package/src/utils/PropertiesParser.ts +126 -0
- package/src/utils/SystemDetector.ts +127 -0
- 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
|
+
}"
|