@dimzxzzx07/mc-headless 2.2.1 → 2.2.3
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 +658 -765
- package/dist/core/JavaChecker.d.ts +8 -2
- package/dist/core/JavaChecker.d.ts.map +1 -1
- package/dist/core/JavaChecker.js +219 -104
- package/dist/core/JavaChecker.js.map +1 -1
- package/dist/core/MinecraftServer.d.ts +17 -32
- package/dist/core/MinecraftServer.d.ts.map +1 -1
- package/dist/core/MinecraftServer.js +449 -186
- package/dist/core/MinecraftServer.js.map +1 -1
- package/dist/index.d.ts +19 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -18
- package/dist/index.js.map +1 -1
- package/dist/platforms/GeyserBridge.d.ts +21 -3
- package/dist/platforms/GeyserBridge.d.ts.map +1 -1
- package/dist/platforms/GeyserBridge.js +160 -60
- package/dist/platforms/GeyserBridge.js.map +1 -1
- package/dist/platforms/PluginManager.d.ts +16 -0
- package/dist/platforms/PluginManager.d.ts.map +1 -0
- package/dist/platforms/PluginManager.js +208 -0
- package/dist/platforms/PluginManager.js.map +1 -0
- package/dist/platforms/ViaVersion.d.ts +1 -7
- package/dist/platforms/ViaVersion.d.ts.map +1 -1
- package/dist/platforms/ViaVersion.js +23 -87
- package/dist/platforms/ViaVersion.js.map +1 -1
- package/dist/types/index.d.ts +189 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/core/JavaChecker.ts +224 -108
- package/src/core/MinecraftServer.ts +540 -254
- package/src/index.ts +19 -19
- package/src/platforms/GeyserBridge.ts +196 -80
- package/src/platforms/PluginManager.ts +206 -0
- package/src/platforms/ViaVersion.ts +26 -107
- package/src/types/index.ts +206 -0
|
@@ -37,6 +37,8 @@ exports.MinecraftServer = void 0;
|
|
|
37
37
|
const events_1 = require("events");
|
|
38
38
|
const child_process_1 = require("child_process");
|
|
39
39
|
const cron = __importStar(require("node-cron"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const fs = __importStar(require("fs-extra"));
|
|
40
42
|
const ConfigHandler_1 = require("./ConfigHandler");
|
|
41
43
|
const JavaChecker_1 = require("./JavaChecker");
|
|
42
44
|
const FileUtils_1 = require("../utils/FileUtils");
|
|
@@ -49,8 +51,7 @@ const FabricEngine_1 = require("../engines/FabricEngine");
|
|
|
49
51
|
const GeyserBridge_1 = require("../platforms/GeyserBridge");
|
|
50
52
|
const ViaVersion_1 = require("../platforms/ViaVersion");
|
|
51
53
|
const SkinRestorer_1 = require("../platforms/SkinRestorer");
|
|
52
|
-
const
|
|
53
|
-
const fs = __importStar(require("fs-extra"));
|
|
54
|
+
const PluginManager_1 = require("../platforms/PluginManager");
|
|
54
55
|
class MinecraftServer extends events_1.EventEmitter {
|
|
55
56
|
config;
|
|
56
57
|
options;
|
|
@@ -59,10 +60,13 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
59
60
|
geyser;
|
|
60
61
|
viaVersion;
|
|
61
62
|
skinRestorer;
|
|
63
|
+
pluginManager;
|
|
62
64
|
process = null;
|
|
63
65
|
serverInfo;
|
|
64
66
|
players = new Map();
|
|
65
67
|
backupCron = null;
|
|
68
|
+
cleanLogsCron = null;
|
|
69
|
+
ramdiskBackupCron = null;
|
|
66
70
|
startTime = null;
|
|
67
71
|
memoryMonitorInterval = null;
|
|
68
72
|
statsInterval = null;
|
|
@@ -78,46 +82,90 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
78
82
|
cpuUsage = 0;
|
|
79
83
|
cgroupMemory = 0;
|
|
80
84
|
cgroupCpu = 0;
|
|
85
|
+
isPterodactyl = false;
|
|
86
|
+
ramdiskPaths = new Map();
|
|
87
|
+
serverReady = false;
|
|
81
88
|
constructor(userConfig = {}) {
|
|
82
89
|
super();
|
|
83
90
|
this.logger = Logger_1.Logger.getInstance();
|
|
84
91
|
this.logger.banner();
|
|
92
|
+
this.detectEnvironment();
|
|
93
|
+
const defaultMemoryMonitor = {
|
|
94
|
+
enabled: true,
|
|
95
|
+
threshold: 90,
|
|
96
|
+
interval: 30000,
|
|
97
|
+
action: 'warn',
|
|
98
|
+
maxMemoryLeakRestarts: 3,
|
|
99
|
+
gcOnWarning: true
|
|
100
|
+
};
|
|
101
|
+
const defaultNetworkOptimization = {
|
|
102
|
+
tcpFastOpen: true,
|
|
103
|
+
tcpNoDelay: true,
|
|
104
|
+
preferIPv4: true,
|
|
105
|
+
compressionThreshold: 64,
|
|
106
|
+
bungeeMode: false,
|
|
107
|
+
proxyProtocol: false,
|
|
108
|
+
velocity: {
|
|
109
|
+
enabled: false,
|
|
110
|
+
secret: '',
|
|
111
|
+
forwardingMode: 'modern'
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const defaultOwnerCommands = {
|
|
115
|
+
prefix: '!',
|
|
116
|
+
enabled: true,
|
|
117
|
+
usernames: [],
|
|
118
|
+
permissions: []
|
|
119
|
+
};
|
|
120
|
+
const defaultRamdisk = {
|
|
121
|
+
enabled: false,
|
|
122
|
+
world: false,
|
|
123
|
+
plugins: false,
|
|
124
|
+
addons: false,
|
|
125
|
+
backupInterval: 300000,
|
|
126
|
+
masterStorage: '/home/minecraft-master',
|
|
127
|
+
size: '1G',
|
|
128
|
+
mountPoint: '/dev/shm/minecraft'
|
|
129
|
+
};
|
|
85
130
|
this.options = {
|
|
86
131
|
javaVersion: 'auto',
|
|
87
|
-
usePortableJava:
|
|
88
|
-
memoryMonitor:
|
|
89
|
-
enabled: true,
|
|
90
|
-
threshold: 90,
|
|
91
|
-
interval: 30000,
|
|
92
|
-
action: 'warn'
|
|
93
|
-
},
|
|
132
|
+
usePortableJava: !this.isPterodactyl,
|
|
133
|
+
memoryMonitor: defaultMemoryMonitor,
|
|
94
134
|
autoInstallJava: true,
|
|
95
|
-
networkOptimization:
|
|
96
|
-
tcpFastOpen: true,
|
|
97
|
-
bungeeMode: false,
|
|
98
|
-
proxyProtocol: false
|
|
99
|
-
},
|
|
135
|
+
networkOptimization: defaultNetworkOptimization,
|
|
100
136
|
owners: [],
|
|
101
|
-
ownerCommands:
|
|
102
|
-
prefix: '!',
|
|
103
|
-
enabled: true
|
|
104
|
-
},
|
|
137
|
+
ownerCommands: defaultOwnerCommands,
|
|
105
138
|
silentMode: true,
|
|
106
139
|
statsInterval: 30000,
|
|
140
|
+
cleanLogsInterval: 3 * 60 * 60 * 1000,
|
|
141
|
+
optimizeForPterodactyl: true,
|
|
142
|
+
ramdisk: defaultRamdisk,
|
|
143
|
+
symlinkMaster: '/home/minecraft-master',
|
|
144
|
+
enableProtocolLib: false,
|
|
145
|
+
enableTCPShield: false,
|
|
146
|
+
enableSpigotOptimizers: false,
|
|
147
|
+
enableSpark: false,
|
|
148
|
+
enableViewDistanceTweaks: false,
|
|
149
|
+
enableFarmControl: false,
|
|
150
|
+
enableEntityDetection: false,
|
|
107
151
|
...userConfig
|
|
108
152
|
};
|
|
109
153
|
if (this.options.owners) {
|
|
110
|
-
this.options.owners.forEach(owner => this.owners.add(owner.toLowerCase()));
|
|
154
|
+
this.options.owners.forEach((owner) => this.owners.add(owner.toLowerCase()));
|
|
111
155
|
}
|
|
112
156
|
if (this.options.ownerCommands?.prefix) {
|
|
113
157
|
this.ownerCommandPrefix = this.options.ownerCommands.prefix;
|
|
114
158
|
}
|
|
115
159
|
const handler = new ConfigHandler_1.ConfigHandler(userConfig);
|
|
116
160
|
this.config = handler.getConfig();
|
|
161
|
+
if (this.isPterodactyl && this.options.optimizeForPterodactyl) {
|
|
162
|
+
this.optimizeForPterodactylPanel();
|
|
163
|
+
}
|
|
117
164
|
this.engine = this.createEngine();
|
|
118
165
|
this.geyser = new GeyserBridge_1.GeyserBridge();
|
|
119
166
|
this.viaVersion = new ViaVersion_1.ViaVersionManager();
|
|
120
167
|
this.skinRestorer = new SkinRestorer_1.SkinRestorerManager();
|
|
168
|
+
this.pluginManager = new PluginManager_1.PluginManager();
|
|
121
169
|
this.serverInfo = {
|
|
122
170
|
pid: 0,
|
|
123
171
|
ip: this.config.network.ip,
|
|
@@ -135,6 +183,28 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
135
183
|
};
|
|
136
184
|
this.detectCgroupLimits();
|
|
137
185
|
}
|
|
186
|
+
detectEnvironment() {
|
|
187
|
+
this.isPterodactyl = !!(process.env.PTERODACTYL ||
|
|
188
|
+
process.env.SERVER_PORT ||
|
|
189
|
+
fs.existsSync('/home/container'));
|
|
190
|
+
if (this.isPterodactyl) {
|
|
191
|
+
this.logger.info('Pterodactyl environment detected');
|
|
192
|
+
const serverPort = process.env.SERVER_PORT;
|
|
193
|
+
if (serverPort) {
|
|
194
|
+
this.config.network.port = parseInt(serverPort);
|
|
195
|
+
this.logger.info(`Using Pterodactyl Java port: ${serverPort}`);
|
|
196
|
+
}
|
|
197
|
+
const bedrockPort = process.env.BEDROCK_PORT;
|
|
198
|
+
if (bedrockPort) {
|
|
199
|
+
this.config.network.bedrockPort = parseInt(bedrockPort);
|
|
200
|
+
this.logger.info(`Using Pterodactyl Bedrock port: ${bedrockPort}`);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
this.logger.warning('No BEDROCK_PORT environment variable found. Geyser will share Java port.');
|
|
204
|
+
this.logger.warning('Make sure UDP is enabled for this port in Pterodactyl panel');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
138
208
|
detectCgroupLimits() {
|
|
139
209
|
try {
|
|
140
210
|
if (fs.existsSync('/sys/fs/cgroup/memory/memory.limit_in_bytes')) {
|
|
@@ -155,6 +225,25 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
155
225
|
this.logger.debug('Not running in cgroup environment');
|
|
156
226
|
}
|
|
157
227
|
}
|
|
228
|
+
optimizeForPterodactylPanel() {
|
|
229
|
+
const pterodactylPort = process.env.SERVER_PORT;
|
|
230
|
+
if (pterodactylPort) {
|
|
231
|
+
this.config.network.port = parseInt(pterodactylPort);
|
|
232
|
+
}
|
|
233
|
+
const pterodactylMemory = process.env.SERVER_MEMORY_MAX;
|
|
234
|
+
if (pterodactylMemory) {
|
|
235
|
+
const memValue = parseInt(pterodactylMemory);
|
|
236
|
+
const safeMemory = Math.floor(memValue * 0.9);
|
|
237
|
+
this.config.memory.max = `${safeMemory}M`;
|
|
238
|
+
this.logger.info(`Pterodactyl memory limit detected, using 90% safe value: ${safeMemory}MB`);
|
|
239
|
+
}
|
|
240
|
+
const pterodactylInitMemory = process.env.SERVER_MEMORY_INIT;
|
|
241
|
+
if (pterodactylInitMemory) {
|
|
242
|
+
this.config.memory.init = pterodactylInitMemory;
|
|
243
|
+
}
|
|
244
|
+
this.config.world.viewDistance = 6;
|
|
245
|
+
this.config.world.simulationDistance = 4;
|
|
246
|
+
}
|
|
158
247
|
createEngine() {
|
|
159
248
|
switch (this.config.type) {
|
|
160
249
|
case 'paper':
|
|
@@ -171,18 +260,194 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
171
260
|
throw new Error(`Unsupported server type: ${this.config.type}`);
|
|
172
261
|
}
|
|
173
262
|
}
|
|
263
|
+
async setupSymlinkMaster() {
|
|
264
|
+
if (!this.options.symlinkMaster)
|
|
265
|
+
return;
|
|
266
|
+
const masterDir = this.options.symlinkMaster;
|
|
267
|
+
await fs.ensureDir(masterDir);
|
|
268
|
+
const subDirs = ['worlds', 'plugins', 'addons', 'mods'];
|
|
269
|
+
for (const dir of subDirs) {
|
|
270
|
+
const masterSubDir = path.join(masterDir, dir);
|
|
271
|
+
await fs.ensureDir(masterSubDir);
|
|
272
|
+
}
|
|
273
|
+
this.logger.info(`Symlink master directory created at ${masterDir}`);
|
|
274
|
+
}
|
|
275
|
+
async restoreFromMaster() {
|
|
276
|
+
if (!this.options.ramdisk?.enabled)
|
|
277
|
+
return;
|
|
278
|
+
const ramdiskBase = '/dev/shm/minecraft';
|
|
279
|
+
const items = [
|
|
280
|
+
{ name: 'world', target: this.config.folders.world },
|
|
281
|
+
{ name: 'plugins', target: this.config.folders.plugins },
|
|
282
|
+
{ name: 'addons', target: this.config.folders.addons }
|
|
283
|
+
];
|
|
284
|
+
for (const item of items) {
|
|
285
|
+
const ramdiskPath = path.join(ramdiskBase, item.name);
|
|
286
|
+
const masterPath = path.join(this.options.ramdisk.masterStorage, item.name);
|
|
287
|
+
if (fs.existsSync(ramdiskPath)) {
|
|
288
|
+
const ramdiskFiles = fs.readdirSync(ramdiskPath);
|
|
289
|
+
if (ramdiskFiles.length > 0) {
|
|
290
|
+
this.logger.debug(`RAM disk ${item.name} already has data, skipping restore`);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (fs.existsSync(masterPath)) {
|
|
295
|
+
const masterFiles = fs.readdirSync(masterPath);
|
|
296
|
+
if (masterFiles.length > 0) {
|
|
297
|
+
this.logger.info(`Restoring ${item.name} from master storage...`);
|
|
298
|
+
await fs.ensureDir(ramdiskPath);
|
|
299
|
+
await fs.copy(masterPath, ramdiskPath, { overwrite: true });
|
|
300
|
+
if (!fs.existsSync(item.target)) {
|
|
301
|
+
await fs.symlink(ramdiskPath, item.target, 'dir');
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async setupRamdisk() {
|
|
308
|
+
if (!this.options.ramdisk?.enabled)
|
|
309
|
+
return;
|
|
310
|
+
const ramdiskBase = '/dev/shm/minecraft';
|
|
311
|
+
await fs.ensureDir(ramdiskBase);
|
|
312
|
+
await this.restoreFromMaster();
|
|
313
|
+
const items = [
|
|
314
|
+
{ name: 'world', enabled: this.options.ramdisk.world, target: this.config.folders.world },
|
|
315
|
+
{ name: 'plugins', enabled: this.options.ramdisk.plugins, target: this.config.folders.plugins },
|
|
316
|
+
{ name: 'addons', enabled: this.options.ramdisk.addons, target: this.config.folders.addons }
|
|
317
|
+
];
|
|
318
|
+
for (const item of items) {
|
|
319
|
+
if (!item.enabled)
|
|
320
|
+
continue;
|
|
321
|
+
const ramdiskPath = path.join(ramdiskBase, item.name);
|
|
322
|
+
const masterPath = path.join(this.options.ramdisk.masterStorage, item.name);
|
|
323
|
+
await fs.ensureDir(masterPath);
|
|
324
|
+
if (fs.existsSync(item.target)) {
|
|
325
|
+
const stat = await fs.lstat(item.target);
|
|
326
|
+
if (stat.isSymbolicLink()) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
await fs.copy(item.target, masterPath, { overwrite: true });
|
|
330
|
+
await fs.remove(item.target);
|
|
331
|
+
}
|
|
332
|
+
await fs.ensureDir(ramdiskPath);
|
|
333
|
+
await fs.symlink(ramdiskPath, item.target, 'dir');
|
|
334
|
+
this.ramdiskPaths.set(item.name, ramdiskPath);
|
|
335
|
+
this.logger.info(`RAM disk created for ${item.name} at ${ramdiskPath}`);
|
|
336
|
+
}
|
|
337
|
+
if (this.options.ramdisk.backupInterval > 0) {
|
|
338
|
+
const minutes = Math.floor(this.options.ramdisk.backupInterval / 60000);
|
|
339
|
+
this.ramdiskBackupCron = cron.schedule(`*/${minutes} * * * *`, () => {
|
|
340
|
+
this.backupRamdiskToMaster();
|
|
341
|
+
});
|
|
342
|
+
this.logger.info(`RAM disk backup scheduled every ${minutes} minutes`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async backupRamdiskToMaster() {
|
|
346
|
+
if (!this.options.ramdisk?.enabled)
|
|
347
|
+
return;
|
|
348
|
+
for (const [name, ramdiskPath] of this.ramdiskPaths) {
|
|
349
|
+
const masterPath = path.join(this.options.ramdisk.masterStorage, name);
|
|
350
|
+
try {
|
|
351
|
+
await fs.copy(ramdiskPath, masterPath, { overwrite: true });
|
|
352
|
+
this.logger.debug(`RAM disk ${name} backed up to master storage`);
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
356
|
+
this.logger.error(`Failed to backup ${name} RAM disk: ${errorMessage}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
cleanOldLogs() {
|
|
361
|
+
const logsDir = path.join(process.cwd(), 'logs');
|
|
362
|
+
if (!fs.existsSync(logsDir))
|
|
363
|
+
return;
|
|
364
|
+
try {
|
|
365
|
+
const files = fs.readdirSync(logsDir);
|
|
366
|
+
const now = Date.now();
|
|
367
|
+
const threeHours = 3 * 60 * 60 * 1000;
|
|
368
|
+
files.forEach(file => {
|
|
369
|
+
const filePath = path.join(logsDir, file);
|
|
370
|
+
const stats = fs.statSync(filePath);
|
|
371
|
+
if (now - stats.mtimeMs > threeHours) {
|
|
372
|
+
fs.unlinkSync(filePath);
|
|
373
|
+
this.logger.debug(`Cleaned old log: ${file}`);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
this.logger.info('Old logs cleaned');
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
380
|
+
this.logger.warning(`Failed to clean logs: ${errorMessage}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async calculateWorldSize() {
|
|
384
|
+
try {
|
|
385
|
+
const worldPath = path.join(process.cwd(), this.config.folders.world);
|
|
386
|
+
if (!await fs.pathExists(worldPath))
|
|
387
|
+
return 0;
|
|
388
|
+
const getSize = async (dir) => {
|
|
389
|
+
let total = 0;
|
|
390
|
+
const files = await fs.readdir(dir);
|
|
391
|
+
for (const file of files) {
|
|
392
|
+
const filePath = path.join(dir, file);
|
|
393
|
+
const stat = await fs.stat(filePath);
|
|
394
|
+
if (stat.isDirectory()) {
|
|
395
|
+
total += await getSize(filePath);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
total += stat.size;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return total;
|
|
402
|
+
};
|
|
403
|
+
return await getSize(worldPath);
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
return 0;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
174
409
|
buildJavaArgs() {
|
|
175
410
|
if (this.options.customJavaArgs && this.options.customJavaArgs.length > 0) {
|
|
176
411
|
return this.options.customJavaArgs;
|
|
177
412
|
}
|
|
178
|
-
|
|
413
|
+
let memMax = this.parseMemory(this.config.memory.max);
|
|
414
|
+
if (this.isPterodactyl && this.cgroupMemory > 0) {
|
|
415
|
+
const safeMem = Math.floor(this.cgroupMemory * 0.9);
|
|
416
|
+
memMax = Math.min(memMax, safeMem);
|
|
417
|
+
this.logger.info(`Adjusted memory to ${memMax}MB (90% of cgroup limit)`);
|
|
418
|
+
}
|
|
179
419
|
const javaVersion = this.options.javaVersion || 'auto';
|
|
420
|
+
const baseArgs = [
|
|
421
|
+
`-Xms${this.config.memory.init}`,
|
|
422
|
+
`-Xmx${memMax}M`,
|
|
423
|
+
'-XX:+UseG1GC',
|
|
424
|
+
'-XX:MaxGCPauseMillis=50',
|
|
425
|
+
'-XX:+UseStringDeduplication',
|
|
426
|
+
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
427
|
+
'-Dio.netty.recycler.maxCapacity.default=0',
|
|
428
|
+
'-Dio.netty.leakDetectionLevel=disabled',
|
|
429
|
+
'-Dterminal.jline=false',
|
|
430
|
+
'-Dterminal.ansi=true'
|
|
431
|
+
];
|
|
432
|
+
if (this.options.networkOptimization?.preferIPv4) {
|
|
433
|
+
baseArgs.unshift('-Djava.net.preferIPv4Stack=true');
|
|
434
|
+
}
|
|
435
|
+
if (this.options.networkOptimization?.tcpFastOpen) {
|
|
436
|
+
baseArgs.unshift('-Djava.net.tcpFastOpen=true');
|
|
437
|
+
}
|
|
438
|
+
if (this.options.networkOptimization?.tcpNoDelay) {
|
|
439
|
+
baseArgs.unshift('-Djava.net.tcp.nodelay=true');
|
|
440
|
+
}
|
|
441
|
+
if (this.options.networkOptimization?.compressionThreshold) {
|
|
442
|
+
baseArgs.push(`-Dnetwork-compression-threshold=${this.options.networkOptimization.compressionThreshold}`);
|
|
443
|
+
}
|
|
444
|
+
if (this.options.networkOptimization?.velocity?.enabled) {
|
|
445
|
+
baseArgs.push('-Dvelocity.secret=' + this.options.networkOptimization.velocity.secret);
|
|
446
|
+
}
|
|
180
447
|
let gcArgs = [];
|
|
181
448
|
if (memMax >= 16384) {
|
|
182
449
|
gcArgs = [
|
|
183
|
-
'-XX:+UseG1GC',
|
|
184
450
|
'-XX:+ParallelRefProcEnabled',
|
|
185
|
-
'-XX:MaxGCPauseMillis=100',
|
|
186
451
|
'-XX:+UnlockExperimentalVMOptions',
|
|
187
452
|
'-XX:+DisableExplicitGC',
|
|
188
453
|
'-XX:+AlwaysPreTouch',
|
|
@@ -193,7 +458,6 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
193
458
|
'-XX:G1HeapWastePercent=5',
|
|
194
459
|
'-XX:G1MixedGCCountTarget=4',
|
|
195
460
|
'-XX:InitiatingHeapOccupancyPercent=20',
|
|
196
|
-
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
197
461
|
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
198
462
|
'-XX:SurvivorRatio=32',
|
|
199
463
|
'-XX:+PerfDisableSharedMem',
|
|
@@ -202,9 +466,7 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
202
466
|
}
|
|
203
467
|
else if (memMax >= 8192) {
|
|
204
468
|
gcArgs = [
|
|
205
|
-
'-XX:+UseG1GC',
|
|
206
469
|
'-XX:+ParallelRefProcEnabled',
|
|
207
|
-
'-XX:MaxGCPauseMillis=150',
|
|
208
470
|
'-XX:+UnlockExperimentalVMOptions',
|
|
209
471
|
'-XX:+DisableExplicitGC',
|
|
210
472
|
'-XX:+AlwaysPreTouch',
|
|
@@ -215,7 +477,6 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
215
477
|
'-XX:G1HeapWastePercent=5',
|
|
216
478
|
'-XX:G1MixedGCCountTarget=4',
|
|
217
479
|
'-XX:InitiatingHeapOccupancyPercent=15',
|
|
218
|
-
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
219
480
|
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
220
481
|
'-XX:SurvivorRatio=32',
|
|
221
482
|
'-XX:+PerfDisableSharedMem',
|
|
@@ -223,32 +484,27 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
223
484
|
];
|
|
224
485
|
}
|
|
225
486
|
else {
|
|
226
|
-
gcArgs =
|
|
227
|
-
'-XX:+UseG1GC',
|
|
487
|
+
gcArgs = [
|
|
228
488
|
'-XX:+ParallelRefProcEnabled',
|
|
229
|
-
'-XX:MaxGCPauseMillis=200',
|
|
230
489
|
'-XX:+UnlockExperimentalVMOptions',
|
|
231
490
|
'-XX:+DisableExplicitGC',
|
|
232
491
|
'-XX:+AlwaysPreTouch',
|
|
233
492
|
'-XX:G1HeapWastePercent=5',
|
|
234
493
|
'-XX:G1MixedGCCountTarget=4',
|
|
235
494
|
'-XX:InitiatingHeapOccupancyPercent=15',
|
|
236
|
-
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
237
495
|
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
238
496
|
'-XX:SurvivorRatio=32',
|
|
239
497
|
'-XX:+PerfDisableSharedMem',
|
|
240
|
-
'-XX:MaxTenuringThreshold=1'
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
498
|
+
'-XX:MaxTenuringThreshold=1'
|
|
499
|
+
];
|
|
500
|
+
if (this.config.memory.useAikarsFlags) {
|
|
501
|
+
gcArgs.push('-Dusing.aikars.flags=https://mcflags.emc.gs');
|
|
502
|
+
gcArgs.push('-Daikars.new.flags=true');
|
|
503
|
+
}
|
|
244
504
|
}
|
|
245
505
|
if (javaVersion === '21') {
|
|
246
506
|
gcArgs.push('--enable-preview');
|
|
247
507
|
}
|
|
248
|
-
const baseArgs = [
|
|
249
|
-
`-Xms${this.config.memory.init}`,
|
|
250
|
-
`-Xmx${this.config.memory.max}`
|
|
251
|
-
];
|
|
252
508
|
return [...baseArgs, ...gcArgs];
|
|
253
509
|
}
|
|
254
510
|
buildEnvironment() {
|
|
@@ -268,6 +524,67 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
268
524
|
}
|
|
269
525
|
return env;
|
|
270
526
|
}
|
|
527
|
+
setupLogHandlers() {
|
|
528
|
+
this.process.stdout.on('data', (data) => {
|
|
529
|
+
const output = data.toString();
|
|
530
|
+
process.stdout.write(output);
|
|
531
|
+
if (!this.serverReady) {
|
|
532
|
+
if (output.includes('Done') || output.includes('For help, type "help"')) {
|
|
533
|
+
this.serverReady = true;
|
|
534
|
+
this.serverInfo.status = 'running';
|
|
535
|
+
this.logger.success('Server started successfully!');
|
|
536
|
+
if (this.options.enableViaVersion !== false) {
|
|
537
|
+
this.logger.info('ViaVersion is active - players from older versions can connect');
|
|
538
|
+
}
|
|
539
|
+
if (this.options.enableSkinRestorer !== false) {
|
|
540
|
+
this.logger.info('SkinRestorer is active - player skins will be restored');
|
|
541
|
+
}
|
|
542
|
+
if (this.worldSize > 0) {
|
|
543
|
+
this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
|
|
544
|
+
}
|
|
545
|
+
if (this.owners.size > 0) {
|
|
546
|
+
this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
|
|
547
|
+
}
|
|
548
|
+
if (this.options.ramdisk?.enabled) {
|
|
549
|
+
this.logger.info('RAM disk active - worlds/plugins running in memory');
|
|
550
|
+
}
|
|
551
|
+
this.emit('ready', this.serverInfo);
|
|
552
|
+
this.startMemoryMonitor();
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (output.includes('joined the game')) {
|
|
556
|
+
const match = output.match(/(\w+) joined the game/);
|
|
557
|
+
if (match) {
|
|
558
|
+
this.playerCount++;
|
|
559
|
+
this.handlePlayerJoin(match[1]);
|
|
560
|
+
if (this.owners.has(match[1].toLowerCase())) {
|
|
561
|
+
this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (output.includes('left the game')) {
|
|
566
|
+
const match = output.match(/(\w+) left the game/);
|
|
567
|
+
if (match) {
|
|
568
|
+
this.playerCount--;
|
|
569
|
+
this.handlePlayerLeave(match[1]);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (output.includes('<') && output.includes('>')) {
|
|
573
|
+
const chatMatch = output.match(/<(\w+)>\s+(.+)/);
|
|
574
|
+
if (chatMatch) {
|
|
575
|
+
const player = chatMatch[1];
|
|
576
|
+
const message = chatMatch[2];
|
|
577
|
+
if (message.startsWith(this.ownerCommandPrefix)) {
|
|
578
|
+
const command = message.substring(this.ownerCommandPrefix.length);
|
|
579
|
+
this.processOwnerCommand(player, command);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
this.process.stderr.on('data', (data) => {
|
|
585
|
+
process.stderr.write(data.toString());
|
|
586
|
+
});
|
|
587
|
+
}
|
|
271
588
|
processOwnerCommand(player, command) {
|
|
272
589
|
if (!this.options.ownerCommands?.enabled)
|
|
273
590
|
return;
|
|
@@ -533,11 +850,79 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
533
850
|
this.emit('resource', this.serverInfo);
|
|
534
851
|
}
|
|
535
852
|
catch (error) {
|
|
536
|
-
|
|
853
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
854
|
+
this.logger.error(`Stats update error: ${errorMessage}`);
|
|
537
855
|
}
|
|
538
856
|
}
|
|
857
|
+
startMemoryMonitor() {
|
|
858
|
+
if (!this.options.memoryMonitor?.enabled)
|
|
859
|
+
return;
|
|
860
|
+
const threshold = this.options.memoryMonitor.threshold || 90;
|
|
861
|
+
const interval = this.options.memoryMonitor.interval || 30000;
|
|
862
|
+
const action = this.options.memoryMonitor.action || 'warn';
|
|
863
|
+
this.memoryMonitorInterval = setInterval(async () => {
|
|
864
|
+
if (this.serverInfo.status !== 'running' || !this.process)
|
|
865
|
+
return;
|
|
866
|
+
try {
|
|
867
|
+
await this.updateStats();
|
|
868
|
+
const memPercent = (this.serverInfo.memory.used / this.serverInfo.memory.max) * 100;
|
|
869
|
+
this.memoryUsageHistory.push(memPercent);
|
|
870
|
+
if (this.memoryUsageHistory.length > 10) {
|
|
871
|
+
this.memoryUsageHistory.shift();
|
|
872
|
+
}
|
|
873
|
+
if (memPercent > threshold) {
|
|
874
|
+
this.logger.warning(`High memory usage: ${memPercent.toFixed(1)}%`);
|
|
875
|
+
const isIncreasing = this.memoryUsageHistory.length > 5 &&
|
|
876
|
+
this.memoryUsageHistory[this.memoryUsageHistory.length - 1] >
|
|
877
|
+
this.memoryUsageHistory[0] * 1.2;
|
|
878
|
+
if (isIncreasing) {
|
|
879
|
+
this.logger.warning('Memory leak detected!');
|
|
880
|
+
if (action === 'restart' && this.players.size === 0) {
|
|
881
|
+
this.logger.info('No players online, restarting server due to memory leak...');
|
|
882
|
+
await this.gracefulRestart();
|
|
883
|
+
}
|
|
884
|
+
else if (action === 'stop' && this.players.size === 0) {
|
|
885
|
+
this.logger.info('No players online, stopping server due to memory leak...');
|
|
886
|
+
await this.stop();
|
|
887
|
+
}
|
|
888
|
+
else if (action === 'warn') {
|
|
889
|
+
this.logger.warning('Please restart server to free memory');
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
this.logger.warning('Cannot restart: players online');
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
899
|
+
this.logger.error(`Memory monitor error: ${errorMessage}`);
|
|
900
|
+
}
|
|
901
|
+
}, interval);
|
|
902
|
+
}
|
|
903
|
+
async gracefulRestart() {
|
|
904
|
+
this.logger.info('Initiating graceful restart...');
|
|
905
|
+
this.sendCommand('say Server restarting in 30 seconds');
|
|
906
|
+
this.sendCommand('save-all');
|
|
907
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
908
|
+
this.sendCommand('say Server restarting in 20 seconds');
|
|
909
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
910
|
+
this.sendCommand('say Server restarting in 10 seconds');
|
|
911
|
+
this.sendCommand('save-all');
|
|
912
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
913
|
+
this.sendCommand('say Server restarting in 5 seconds');
|
|
914
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
915
|
+
await this.stop();
|
|
916
|
+
await this.start();
|
|
917
|
+
}
|
|
539
918
|
async start() {
|
|
540
919
|
this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
|
|
920
|
+
await this.setupSymlinkMaster();
|
|
921
|
+
await this.setupRamdisk();
|
|
922
|
+
if (this.options.cleanLogsInterval && this.options.cleanLogsInterval > 0) {
|
|
923
|
+
this.cleanLogsCron = cron.schedule('0 */3 * * *', () => this.cleanOldLogs());
|
|
924
|
+
this.logger.info('Log cleanup scheduled every 3 hours');
|
|
925
|
+
}
|
|
541
926
|
if (this.owners.size > 0) {
|
|
542
927
|
this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
|
|
543
928
|
}
|
|
@@ -561,7 +946,21 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
561
946
|
}
|
|
562
947
|
if (this.config.platform === 'all') {
|
|
563
948
|
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
564
|
-
await this.geyser.setup(this.config);
|
|
949
|
+
await this.geyser.setup(this.config, this.isPterodactyl);
|
|
950
|
+
}
|
|
951
|
+
const pluginsToInstall = [
|
|
952
|
+
{ name: 'ProtocolLib', enabled: this.options.enableProtocolLib, url: 'https://github.com/dmulloy2/ProtocolLib/releases/latest/download/ProtocolLib.jar' },
|
|
953
|
+
{ name: 'TCPShield', enabled: this.options.enableTCPShield, url: 'https://tcpshield.com/api/download/plugin' },
|
|
954
|
+
{ name: 'SpigotOptimizers', enabled: this.options.enableSpigotOptimizers, url: 'https://github.com/SpigotOptimizers/SpigotOptimizers/releases/latest/download/SpigotOptimizers.jar' },
|
|
955
|
+
{ name: 'Spark', enabled: this.options.enableSpark, url: 'https://spark.lucko.me/download' },
|
|
956
|
+
{ name: 'ViewDistanceTweaks', enabled: this.options.enableViewDistanceTweaks, url: 'https://github.com/ViewDistanceTweaks/ViewDistanceTweaks/releases/latest/download/ViewDistanceTweaks.jar' },
|
|
957
|
+
{ name: 'FarmControl', enabled: this.options.enableFarmControl, url: 'https://github.com/FarmControl/FarmControl/releases/latest/download/FarmControl.jar' },
|
|
958
|
+
{ name: 'EntityDetection', enabled: this.options.enableEntityDetection, url: 'https://github.com/EntityDetection/EntityDetection/releases/latest/download/EntityDetection.jar' }
|
|
959
|
+
];
|
|
960
|
+
for (const plugin of pluginsToInstall) {
|
|
961
|
+
if (plugin.enabled) {
|
|
962
|
+
await this.pluginManager.installPlugin(plugin.name, plugin.url, this.config.folders.plugins);
|
|
963
|
+
}
|
|
565
964
|
}
|
|
566
965
|
if (this.options.enableViaVersion !== false) {
|
|
567
966
|
this.logger.info('Enabling ViaVersion for client version compatibility...');
|
|
@@ -589,12 +988,6 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
589
988
|
serverJar,
|
|
590
989
|
...serverArgs
|
|
591
990
|
];
|
|
592
|
-
if (this.options.networkOptimization?.tcpFastOpen) {
|
|
593
|
-
fullArgs.unshift('-Djava.net.preferIPv4Stack=true');
|
|
594
|
-
}
|
|
595
|
-
if (this.options.networkOptimization?.bungeeMode) {
|
|
596
|
-
fullArgs.unshift('-Dnet.kyori.adventure.text.serializer.legacy.AMPMSupport=true');
|
|
597
|
-
}
|
|
598
991
|
const env = this.buildEnvironment();
|
|
599
992
|
this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
|
|
600
993
|
this.process = (0, child_process_1.spawn)(this.javaCommand, fullArgs, {
|
|
@@ -610,41 +1003,7 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
610
1003
|
this.process.stderr.pipe(process.stderr);
|
|
611
1004
|
}
|
|
612
1005
|
else {
|
|
613
|
-
this.
|
|
614
|
-
const output = data.toString();
|
|
615
|
-
process.stdout.write(output);
|
|
616
|
-
if (output.includes('joined the game')) {
|
|
617
|
-
const match = output.match(/(\w+) joined the game/);
|
|
618
|
-
if (match) {
|
|
619
|
-
this.playerCount++;
|
|
620
|
-
this.handlePlayerJoin(match[1]);
|
|
621
|
-
if (this.owners.has(match[1].toLowerCase())) {
|
|
622
|
-
this.sendCommand(`tellraw ${match[1]} {"text":"Welcome Owner! Use ${this.ownerCommandPrefix}help for commands","color":"gold"}`);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
if (output.includes('left the game')) {
|
|
627
|
-
const match = output.match(/(\w+) left the game/);
|
|
628
|
-
if (match) {
|
|
629
|
-
this.playerCount--;
|
|
630
|
-
this.handlePlayerLeave(match[1]);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
if (output.includes('<') && output.includes('>')) {
|
|
634
|
-
const chatMatch = output.match(/<(\w+)>\s+(.+)/);
|
|
635
|
-
if (chatMatch) {
|
|
636
|
-
const player = chatMatch[1];
|
|
637
|
-
const message = chatMatch[2];
|
|
638
|
-
if (message.startsWith(this.ownerCommandPrefix)) {
|
|
639
|
-
const command = message.substring(this.ownerCommandPrefix.length);
|
|
640
|
-
this.processOwnerCommand(player, command);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
this.process.stderr.on('data', (data) => {
|
|
646
|
-
process.stderr.write(data.toString());
|
|
647
|
-
});
|
|
1006
|
+
this.setupLogHandlers();
|
|
648
1007
|
}
|
|
649
1008
|
this.process.on('exit', (code) => {
|
|
650
1009
|
this.serverInfo.status = 'stopped';
|
|
@@ -662,87 +1021,8 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
662
1021
|
if (this.config.backup.enabled) {
|
|
663
1022
|
this.setupBackups();
|
|
664
1023
|
}
|
|
665
|
-
setTimeout(() => {
|
|
666
|
-
if (this.serverInfo.status === 'starting') {
|
|
667
|
-
this.serverInfo.status = 'running';
|
|
668
|
-
this.logger.success('Server started successfully!');
|
|
669
|
-
if (this.options.enableViaVersion !== false) {
|
|
670
|
-
this.logger.info('ViaVersion is active - players from older versions can connect');
|
|
671
|
-
}
|
|
672
|
-
if (this.options.enableSkinRestorer !== false) {
|
|
673
|
-
this.logger.info('SkinRestorer is active - player skins will be restored');
|
|
674
|
-
}
|
|
675
|
-
if (this.worldSize > 0) {
|
|
676
|
-
this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
|
|
677
|
-
}
|
|
678
|
-
if (this.owners.size > 0) {
|
|
679
|
-
this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
|
|
680
|
-
}
|
|
681
|
-
this.emit('ready', this.serverInfo);
|
|
682
|
-
this.startMemoryMonitor();
|
|
683
|
-
}
|
|
684
|
-
}, 10000);
|
|
685
1024
|
return this.serverInfo;
|
|
686
1025
|
}
|
|
687
|
-
startMemoryMonitor() {
|
|
688
|
-
if (!this.options.memoryMonitor?.enabled)
|
|
689
|
-
return;
|
|
690
|
-
const threshold = this.options.memoryMonitor.threshold || 90;
|
|
691
|
-
const interval = this.options.memoryMonitor.interval || 30000;
|
|
692
|
-
const action = this.options.memoryMonitor.action || 'warn';
|
|
693
|
-
this.memoryMonitorInterval = setInterval(async () => {
|
|
694
|
-
if (this.serverInfo.status !== 'running' || !this.process)
|
|
695
|
-
return;
|
|
696
|
-
try {
|
|
697
|
-
await this.updateStats();
|
|
698
|
-
const memPercent = (this.serverInfo.memory.used / this.serverInfo.memory.max) * 100;
|
|
699
|
-
this.memoryUsageHistory.push(memPercent);
|
|
700
|
-
if (this.memoryUsageHistory.length > 10) {
|
|
701
|
-
this.memoryUsageHistory.shift();
|
|
702
|
-
}
|
|
703
|
-
if (memPercent > threshold) {
|
|
704
|
-
this.logger.warning(`High memory usage: ${memPercent.toFixed(1)}%`);
|
|
705
|
-
const isIncreasing = this.memoryUsageHistory.length > 5 &&
|
|
706
|
-
this.memoryUsageHistory[this.memoryUsageHistory.length - 1] >
|
|
707
|
-
this.memoryUsageHistory[0] * 1.2;
|
|
708
|
-
if (isIncreasing) {
|
|
709
|
-
this.logger.warning('Memory leak detected!');
|
|
710
|
-
switch (action) {
|
|
711
|
-
case 'restart':
|
|
712
|
-
this.logger.info('Restarting server due to memory leak...');
|
|
713
|
-
await this.gracefulRestart();
|
|
714
|
-
break;
|
|
715
|
-
case 'stop':
|
|
716
|
-
this.logger.info('Stopping server due to memory leak...');
|
|
717
|
-
await this.stop();
|
|
718
|
-
break;
|
|
719
|
-
case 'warn':
|
|
720
|
-
default:
|
|
721
|
-
this.logger.warning('Please restart server to free memory');
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
catch (error) {
|
|
727
|
-
this.logger.error('Memory monitor error:', error);
|
|
728
|
-
}
|
|
729
|
-
}, interval);
|
|
730
|
-
}
|
|
731
|
-
async gracefulRestart() {
|
|
732
|
-
this.logger.info('Initiating graceful restart...');
|
|
733
|
-
this.sendCommand('say Server restarting in 30 seconds');
|
|
734
|
-
this.sendCommand('save-all');
|
|
735
|
-
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
736
|
-
this.sendCommand('say Server restarting in 20 seconds');
|
|
737
|
-
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
738
|
-
this.sendCommand('say Server restarting in 10 seconds');
|
|
739
|
-
this.sendCommand('save-all');
|
|
740
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
741
|
-
this.sendCommand('say Server restarting in 5 seconds');
|
|
742
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
743
|
-
await this.stop();
|
|
744
|
-
await this.start();
|
|
745
|
-
}
|
|
746
1026
|
async stop() {
|
|
747
1027
|
if (!this.process) {
|
|
748
1028
|
this.logger.warning('Server not running');
|
|
@@ -760,6 +1040,12 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
760
1040
|
if (this.backupCron) {
|
|
761
1041
|
this.backupCron.stop();
|
|
762
1042
|
}
|
|
1043
|
+
if (this.cleanLogsCron) {
|
|
1044
|
+
this.cleanLogsCron.stop();
|
|
1045
|
+
}
|
|
1046
|
+
if (this.ramdiskBackupCron) {
|
|
1047
|
+
this.ramdiskBackupCron.stop();
|
|
1048
|
+
}
|
|
763
1049
|
if (this.memoryMonitorInterval) {
|
|
764
1050
|
clearInterval(this.memoryMonitorInterval);
|
|
765
1051
|
}
|
|
@@ -769,6 +1055,9 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
769
1055
|
if (this.config.platform === 'all') {
|
|
770
1056
|
this.geyser.stop();
|
|
771
1057
|
}
|
|
1058
|
+
if (this.options.ramdisk?.enabled) {
|
|
1059
|
+
await this.backupRamdiskToMaster();
|
|
1060
|
+
}
|
|
772
1061
|
this.logger.success('Server stopped');
|
|
773
1062
|
}
|
|
774
1063
|
sendCommand(command) {
|
|
@@ -864,32 +1153,6 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
864
1153
|
}
|
|
865
1154
|
return value;
|
|
866
1155
|
}
|
|
867
|
-
async calculateWorldSize() {
|
|
868
|
-
try {
|
|
869
|
-
const worldPath = path.join(process.cwd(), this.config.folders.world);
|
|
870
|
-
if (!await fs.pathExists(worldPath))
|
|
871
|
-
return 0;
|
|
872
|
-
const getSize = async (dir) => {
|
|
873
|
-
let total = 0;
|
|
874
|
-
const files = await fs.readdir(dir);
|
|
875
|
-
for (const file of files) {
|
|
876
|
-
const filePath = path.join(dir, file);
|
|
877
|
-
const stat = await fs.stat(filePath);
|
|
878
|
-
if (stat.isDirectory()) {
|
|
879
|
-
total += await getSize(filePath);
|
|
880
|
-
}
|
|
881
|
-
else {
|
|
882
|
-
total += stat.size;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
return total;
|
|
886
|
-
};
|
|
887
|
-
return await getSize(worldPath);
|
|
888
|
-
}
|
|
889
|
-
catch {
|
|
890
|
-
return 0;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
1156
|
}
|
|
894
1157
|
exports.MinecraftServer = MinecraftServer;
|
|
895
1158
|
//# sourceMappingURL=MinecraftServer.js.map
|