@dimzxzzx07/mc-headless 2.2.2 → 2.2.4
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 +652 -776
- package/dist/core/MinecraftServer.d.ts +13 -38
- package/dist/core/MinecraftServer.d.ts.map +1 -1
- package/dist/core/MinecraftServer.js +424 -336
- 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/MinecraftServer.ts +503 -414
- 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
|
@@ -51,6 +51,7 @@ const FabricEngine_1 = require("../engines/FabricEngine");
|
|
|
51
51
|
const GeyserBridge_1 = require("../platforms/GeyserBridge");
|
|
52
52
|
const ViaVersion_1 = require("../platforms/ViaVersion");
|
|
53
53
|
const SkinRestorer_1 = require("../platforms/SkinRestorer");
|
|
54
|
+
const PluginManager_1 = require("../platforms/PluginManager");
|
|
54
55
|
class MinecraftServer extends events_1.EventEmitter {
|
|
55
56
|
config;
|
|
56
57
|
options;
|
|
@@ -59,11 +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;
|
|
66
68
|
cleanLogsCron = null;
|
|
69
|
+
ramdiskBackupCron = null;
|
|
67
70
|
startTime = null;
|
|
68
71
|
memoryMonitorInterval = null;
|
|
69
72
|
statsInterval = null;
|
|
@@ -80,39 +83,75 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
80
83
|
cgroupMemory = 0;
|
|
81
84
|
cgroupCpu = 0;
|
|
82
85
|
isPterodactyl = false;
|
|
86
|
+
ramdiskPaths = new Map();
|
|
87
|
+
serverReady = false;
|
|
83
88
|
constructor(userConfig = {}) {
|
|
84
89
|
super();
|
|
85
90
|
this.logger = Logger_1.Logger.getInstance();
|
|
86
91
|
this.logger.banner();
|
|
87
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
|
+
};
|
|
88
130
|
this.options = {
|
|
89
131
|
javaVersion: 'auto',
|
|
90
132
|
usePortableJava: !this.isPterodactyl,
|
|
91
|
-
memoryMonitor:
|
|
92
|
-
enabled: true,
|
|
93
|
-
threshold: 90,
|
|
94
|
-
interval: 30000,
|
|
95
|
-
action: 'warn'
|
|
96
|
-
},
|
|
133
|
+
memoryMonitor: defaultMemoryMonitor,
|
|
97
134
|
autoInstallJava: true,
|
|
98
|
-
networkOptimization:
|
|
99
|
-
tcpFastOpen: true,
|
|
100
|
-
bungeeMode: false,
|
|
101
|
-
proxyProtocol: false
|
|
102
|
-
},
|
|
135
|
+
networkOptimization: defaultNetworkOptimization,
|
|
103
136
|
owners: [],
|
|
104
|
-
ownerCommands:
|
|
105
|
-
prefix: '!',
|
|
106
|
-
enabled: true
|
|
107
|
-
},
|
|
137
|
+
ownerCommands: defaultOwnerCommands,
|
|
108
138
|
silentMode: true,
|
|
109
139
|
statsInterval: 30000,
|
|
110
140
|
cleanLogsInterval: 3 * 60 * 60 * 1000,
|
|
111
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,
|
|
112
151
|
...userConfig
|
|
113
152
|
};
|
|
114
153
|
if (this.options.owners) {
|
|
115
|
-
this.options.owners.forEach(owner => this.owners.add(owner.toLowerCase()));
|
|
154
|
+
this.options.owners.forEach((owner) => this.owners.add(owner.toLowerCase()));
|
|
116
155
|
}
|
|
117
156
|
if (this.options.ownerCommands?.prefix) {
|
|
118
157
|
this.ownerCommandPrefix = this.options.ownerCommands.prefix;
|
|
@@ -126,6 +165,7 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
126
165
|
this.geyser = new GeyserBridge_1.GeyserBridge();
|
|
127
166
|
this.viaVersion = new ViaVersion_1.ViaVersionManager();
|
|
128
167
|
this.skinRestorer = new SkinRestorer_1.SkinRestorerManager();
|
|
168
|
+
this.pluginManager = new PluginManager_1.PluginManager();
|
|
129
169
|
this.serverInfo = {
|
|
130
170
|
pid: 0,
|
|
131
171
|
ip: this.config.network.ip,
|
|
@@ -142,27 +182,60 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
142
182
|
status: 'stopped'
|
|
143
183
|
};
|
|
144
184
|
this.detectCgroupLimits();
|
|
145
|
-
this.cleanPaperCache();
|
|
146
185
|
}
|
|
147
186
|
detectEnvironment() {
|
|
148
187
|
this.isPterodactyl = !!(process.env.PTERODACTYL ||
|
|
149
188
|
process.env.SERVER_PORT ||
|
|
150
|
-
process.env.SERVER_MEMORY_MAX ||
|
|
151
189
|
fs.existsSync('/home/container'));
|
|
152
190
|
if (this.isPterodactyl) {
|
|
153
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
|
+
}
|
|
208
|
+
detectCgroupLimits() {
|
|
209
|
+
try {
|
|
210
|
+
if (fs.existsSync('/sys/fs/cgroup/memory/memory.limit_in_bytes')) {
|
|
211
|
+
const limit = fs.readFileSync('/sys/fs/cgroup/memory/memory.limit_in_bytes', 'utf8');
|
|
212
|
+
this.cgroupMemory = parseInt(limit) / 1024 / 1024;
|
|
213
|
+
this.logger.debug(`Cgroup memory limit: ${this.cgroupMemory} MB`);
|
|
214
|
+
}
|
|
215
|
+
if (fs.existsSync('/sys/fs/cgroup/cpu/cpu.cfs_quota_us') && fs.existsSync('/sys/fs/cgroup/cpu/cpu.cfs_period_us')) {
|
|
216
|
+
const quota = parseInt(fs.readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_quota_us', 'utf8'));
|
|
217
|
+
const period = parseInt(fs.readFileSync('/sys/fs/cgroup/cpu/cpu.cfs_period_us', 'utf8'));
|
|
218
|
+
if (quota > 0 && period > 0) {
|
|
219
|
+
this.cgroupCpu = quota / period;
|
|
220
|
+
this.logger.debug(`Cgroup CPU limit: ${this.cgroupCpu} cores`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
this.logger.debug('Not running in cgroup environment');
|
|
154
226
|
}
|
|
155
227
|
}
|
|
156
228
|
optimizeForPterodactylPanel() {
|
|
157
229
|
const pterodactylPort = process.env.SERVER_PORT;
|
|
158
230
|
if (pterodactylPort) {
|
|
159
231
|
this.config.network.port = parseInt(pterodactylPort);
|
|
160
|
-
this.logger.info(`Using Pterodactyl port: ${pterodactylPort}`);
|
|
161
232
|
}
|
|
162
233
|
const pterodactylMemory = process.env.SERVER_MEMORY_MAX;
|
|
163
234
|
if (pterodactylMemory) {
|
|
164
|
-
|
|
165
|
-
|
|
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`);
|
|
166
239
|
}
|
|
167
240
|
const pterodactylInitMemory = process.env.SERVER_MEMORY_INIT;
|
|
168
241
|
if (pterodactylInitMemory) {
|
|
@@ -170,27 +243,119 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
170
243
|
}
|
|
171
244
|
this.config.world.viewDistance = 6;
|
|
172
245
|
this.config.world.simulationDistance = 4;
|
|
173
|
-
this.logger.info('Applied Pterodactyl optimizations');
|
|
174
246
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
247
|
+
createEngine() {
|
|
248
|
+
switch (this.config.type) {
|
|
249
|
+
case 'paper':
|
|
250
|
+
case 'purpur':
|
|
251
|
+
case 'spigot':
|
|
252
|
+
return new PaperEngine_1.PaperEngine();
|
|
253
|
+
case 'vanilla':
|
|
254
|
+
return new VanillaEngine_1.VanillaEngine();
|
|
255
|
+
case 'forge':
|
|
256
|
+
return new ForgeEngine_1.ForgeEngine();
|
|
257
|
+
case 'fabric':
|
|
258
|
+
return new FabricEngine_1.FabricEngine();
|
|
259
|
+
default:
|
|
260
|
+
throw new Error(`Unsupported server type: ${this.config.type}`);
|
|
261
|
+
}
|
|
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 }
|
|
181
283
|
];
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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;
|
|
188
292
|
}
|
|
189
|
-
|
|
190
|
-
|
|
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
|
+
}
|
|
191
303
|
}
|
|
192
304
|
}
|
|
193
|
-
}
|
|
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
|
+
}
|
|
194
359
|
}
|
|
195
360
|
cleanOldLogs() {
|
|
196
361
|
const logsDir = path.join(process.cwd(), 'logs');
|
|
@@ -211,151 +376,79 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
211
376
|
this.logger.info('Old logs cleaned');
|
|
212
377
|
}
|
|
213
378
|
catch (err) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
generateOptimizedConfigs() {
|
|
218
|
-
const serverDir = process.cwd();
|
|
219
|
-
const paperGlobalYml = path.join(serverDir, 'paper-global.yml');
|
|
220
|
-
if (!fs.existsSync(paperGlobalYml)) {
|
|
221
|
-
const paperGlobalConfig = `# Paper Global Configuration
|
|
222
|
-
# Optimized by MC-Headless
|
|
223
|
-
|
|
224
|
-
chunk-loading:
|
|
225
|
-
autoconfig-send-distance: false
|
|
226
|
-
enable-frustum-priority: false
|
|
227
|
-
global-max-chunk-load-rate: 100
|
|
228
|
-
global-max-chunk-send-rate: 50
|
|
229
|
-
|
|
230
|
-
entities:
|
|
231
|
-
despawn-ranges:
|
|
232
|
-
monster:
|
|
233
|
-
soft: 28
|
|
234
|
-
hard: 96
|
|
235
|
-
animal:
|
|
236
|
-
soft: 16
|
|
237
|
-
hard: 32
|
|
238
|
-
spawn-limits:
|
|
239
|
-
ambient: 5
|
|
240
|
-
axolotls: 5
|
|
241
|
-
creature: 10
|
|
242
|
-
monster: 15
|
|
243
|
-
water_ambient: 5
|
|
244
|
-
water_creature: 5
|
|
245
|
-
|
|
246
|
-
misc:
|
|
247
|
-
max-entity-speed: 100
|
|
248
|
-
redstone-implementation: ALTERNATIVE_CURRENT
|
|
249
|
-
|
|
250
|
-
player-auto-save: 6000
|
|
251
|
-
player-auto-save-rate: 1200
|
|
252
|
-
no-tick-view-distance: 12
|
|
253
|
-
`;
|
|
254
|
-
fs.writeFileSync(paperGlobalYml, paperGlobalConfig);
|
|
255
|
-
this.logger.debug('Generated paper-global.yml');
|
|
256
|
-
}
|
|
257
|
-
const paperWorldDefaultsYml = path.join(serverDir, 'paper-world-defaults.yml');
|
|
258
|
-
if (!fs.existsSync(paperWorldDefaultsYml)) {
|
|
259
|
-
const paperWorldConfig = `# Paper World Defaults
|
|
260
|
-
# Optimized by MC-Headless
|
|
261
|
-
|
|
262
|
-
view-distance: 6
|
|
263
|
-
no-tick-view-distance: 12
|
|
264
|
-
|
|
265
|
-
despawn-ranges:
|
|
266
|
-
monster:
|
|
267
|
-
soft: 28
|
|
268
|
-
hard: 96
|
|
269
|
-
animal:
|
|
270
|
-
soft: 16
|
|
271
|
-
hard: 32
|
|
272
|
-
ambient:
|
|
273
|
-
soft: 16
|
|
274
|
-
hard: 32
|
|
275
|
-
`;
|
|
276
|
-
fs.writeFileSync(paperWorldDefaultsYml, paperWorldConfig);
|
|
277
|
-
this.logger.debug('Generated paper-world-defaults.yml');
|
|
278
|
-
}
|
|
279
|
-
const spigotYml = path.join(serverDir, 'spigot.yml');
|
|
280
|
-
if (!fs.existsSync(spigotYml)) {
|
|
281
|
-
const spigotConfig = `# Spigot Configuration
|
|
282
|
-
# Optimized by MC-Headless
|
|
283
|
-
|
|
284
|
-
entity-activation-range:
|
|
285
|
-
animals: 16
|
|
286
|
-
monsters: 24
|
|
287
|
-
misc: 8
|
|
288
|
-
water: 8
|
|
289
|
-
|
|
290
|
-
mob-spawn-range: 4
|
|
291
|
-
|
|
292
|
-
world-settings:
|
|
293
|
-
default:
|
|
294
|
-
view-distance: 6
|
|
295
|
-
mob-spawn-range: 4
|
|
296
|
-
entity-activation-range:
|
|
297
|
-
animals: 16
|
|
298
|
-
monsters: 24
|
|
299
|
-
misc: 8
|
|
300
|
-
water: 8
|
|
301
|
-
`;
|
|
302
|
-
fs.writeFileSync(spigotYml, spigotConfig);
|
|
303
|
-
this.logger.debug('Generated spigot.yml');
|
|
379
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
380
|
+
this.logger.warning(`Failed to clean logs: ${errorMessage}`);
|
|
304
381
|
}
|
|
305
382
|
}
|
|
306
|
-
|
|
383
|
+
async calculateWorldSize() {
|
|
307
384
|
try {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
+
}
|
|
319
400
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
this.logger.debug('Not running in cgroup environment');
|
|
401
|
+
return total;
|
|
402
|
+
};
|
|
403
|
+
return await getSize(worldPath);
|
|
324
404
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
switch (this.config.type) {
|
|
328
|
-
case 'paper':
|
|
329
|
-
case 'purpur':
|
|
330
|
-
case 'spigot':
|
|
331
|
-
return new PaperEngine_1.PaperEngine();
|
|
332
|
-
case 'vanilla':
|
|
333
|
-
return new VanillaEngine_1.VanillaEngine();
|
|
334
|
-
case 'forge':
|
|
335
|
-
return new ForgeEngine_1.ForgeEngine();
|
|
336
|
-
case 'fabric':
|
|
337
|
-
return new FabricEngine_1.FabricEngine();
|
|
338
|
-
default:
|
|
339
|
-
throw new Error(`Unsupported server type: ${this.config.type}`);
|
|
405
|
+
catch {
|
|
406
|
+
return 0;
|
|
340
407
|
}
|
|
341
408
|
}
|
|
342
409
|
buildJavaArgs() {
|
|
343
410
|
if (this.options.customJavaArgs && this.options.customJavaArgs.length > 0) {
|
|
344
411
|
return this.options.customJavaArgs;
|
|
345
412
|
}
|
|
346
|
-
|
|
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
|
+
}
|
|
347
419
|
const javaVersion = this.options.javaVersion || 'auto';
|
|
348
420
|
const baseArgs = [
|
|
349
421
|
`-Xms${this.config.memory.init}`,
|
|
350
|
-
`-Xmx${
|
|
351
|
-
'-XX:+UseG1GC'
|
|
422
|
+
`-Xmx${memMax}M`,
|
|
423
|
+
'-XX:+UseG1GC',
|
|
424
|
+
'-XX:MaxGCPauseMillis=50',
|
|
425
|
+
'-Dio.netty.recycler.maxCapacity.default=0',
|
|
426
|
+
'-Dio.netty.leakDetectionLevel=disabled',
|
|
427
|
+
'-Dterminal.jline=false',
|
|
428
|
+
'-Dterminal.ansi=true'
|
|
352
429
|
];
|
|
430
|
+
if (this.options.networkOptimization?.preferIPv4) {
|
|
431
|
+
baseArgs.unshift('-Djava.net.preferIPv4Stack=true');
|
|
432
|
+
}
|
|
433
|
+
if (this.options.networkOptimization?.tcpFastOpen) {
|
|
434
|
+
baseArgs.unshift('-Djava.net.tcpFastOpen=true');
|
|
435
|
+
}
|
|
436
|
+
if (this.options.networkOptimization?.tcpNoDelay) {
|
|
437
|
+
baseArgs.unshift('-Djava.net.tcp.nodelay=true');
|
|
438
|
+
}
|
|
439
|
+
if (this.options.networkOptimization?.compressionThreshold) {
|
|
440
|
+
baseArgs.push(`-Dnetwork-compression-threshold=${this.options.networkOptimization.compressionThreshold}`);
|
|
441
|
+
}
|
|
442
|
+
if (this.options.networkOptimization?.velocity?.enabled) {
|
|
443
|
+
baseArgs.push('-Dvelocity.secret=' + this.options.networkOptimization.velocity.secret);
|
|
444
|
+
}
|
|
353
445
|
let gcArgs = [];
|
|
354
446
|
if (memMax >= 16384) {
|
|
355
447
|
gcArgs = [
|
|
356
|
-
'-XX:+ParallelRefProcEnabled',
|
|
357
|
-
'-XX:MaxGCPauseMillis=100',
|
|
358
448
|
'-XX:+UnlockExperimentalVMOptions',
|
|
449
|
+
'-XX:+UseStringDeduplication',
|
|
450
|
+
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
451
|
+
'-XX:+ParallelRefProcEnabled',
|
|
359
452
|
'-XX:+DisableExplicitGC',
|
|
360
453
|
'-XX:+AlwaysPreTouch',
|
|
361
454
|
'-XX:G1NewSizePercent=40',
|
|
@@ -365,7 +458,6 @@ world-settings:
|
|
|
365
458
|
'-XX:G1HeapWastePercent=5',
|
|
366
459
|
'-XX:G1MixedGCCountTarget=4',
|
|
367
460
|
'-XX:InitiatingHeapOccupancyPercent=20',
|
|
368
|
-
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
369
461
|
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
370
462
|
'-XX:SurvivorRatio=32',
|
|
371
463
|
'-XX:+PerfDisableSharedMem',
|
|
@@ -374,9 +466,10 @@ world-settings:
|
|
|
374
466
|
}
|
|
375
467
|
else if (memMax >= 8192) {
|
|
376
468
|
gcArgs = [
|
|
377
|
-
'-XX:+ParallelRefProcEnabled',
|
|
378
|
-
'-XX:MaxGCPauseMillis=150',
|
|
379
469
|
'-XX:+UnlockExperimentalVMOptions',
|
|
470
|
+
'-XX:+UseStringDeduplication',
|
|
471
|
+
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
472
|
+
'-XX:+ParallelRefProcEnabled',
|
|
380
473
|
'-XX:+DisableExplicitGC',
|
|
381
474
|
'-XX:+AlwaysPreTouch',
|
|
382
475
|
'-XX:G1NewSizePercent=30',
|
|
@@ -386,7 +479,6 @@ world-settings:
|
|
|
386
479
|
'-XX:G1HeapWastePercent=5',
|
|
387
480
|
'-XX:G1MixedGCCountTarget=4',
|
|
388
481
|
'-XX:InitiatingHeapOccupancyPercent=15',
|
|
389
|
-
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
390
482
|
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
391
483
|
'-XX:SurvivorRatio=32',
|
|
392
484
|
'-XX:+PerfDisableSharedMem',
|
|
@@ -394,23 +486,25 @@ world-settings:
|
|
|
394
486
|
];
|
|
395
487
|
}
|
|
396
488
|
else {
|
|
397
|
-
gcArgs =
|
|
398
|
-
'-XX:+ParallelRefProcEnabled',
|
|
399
|
-
'-XX:MaxGCPauseMillis=200',
|
|
489
|
+
gcArgs = [
|
|
400
490
|
'-XX:+UnlockExperimentalVMOptions',
|
|
491
|
+
'-XX:+UseStringDeduplication',
|
|
492
|
+
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
493
|
+
'-XX:+ParallelRefProcEnabled',
|
|
401
494
|
'-XX:+DisableExplicitGC',
|
|
402
495
|
'-XX:+AlwaysPreTouch',
|
|
403
496
|
'-XX:G1HeapWastePercent=5',
|
|
404
497
|
'-XX:G1MixedGCCountTarget=4',
|
|
405
498
|
'-XX:InitiatingHeapOccupancyPercent=15',
|
|
406
|
-
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
407
499
|
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
408
500
|
'-XX:SurvivorRatio=32',
|
|
409
501
|
'-XX:+PerfDisableSharedMem',
|
|
410
|
-
'-XX:MaxTenuringThreshold=1'
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
502
|
+
'-XX:MaxTenuringThreshold=1'
|
|
503
|
+
];
|
|
504
|
+
if (this.config.memory.useAikarsFlags) {
|
|
505
|
+
gcArgs.push('-Dusing.aikars.flags=https://mcflags.emc.gs');
|
|
506
|
+
gcArgs.push('-Daikars.new.flags=true');
|
|
507
|
+
}
|
|
414
508
|
}
|
|
415
509
|
if (javaVersion === '21') {
|
|
416
510
|
gcArgs.push('--enable-preview');
|
|
@@ -434,130 +528,34 @@ world-settings:
|
|
|
434
528
|
}
|
|
435
529
|
return env;
|
|
436
530
|
}
|
|
437
|
-
async start() {
|
|
438
|
-
this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
|
|
439
|
-
this.cleanPaperCache();
|
|
440
|
-
this.generateOptimizedConfigs();
|
|
441
|
-
if (this.options.cleanLogsInterval && this.options.cleanLogsInterval > 0) {
|
|
442
|
-
this.cleanLogsCron = cron.schedule('0 */3 * * *', () => this.cleanOldLogs());
|
|
443
|
-
this.logger.info('Log cleanup scheduled every 3 hours');
|
|
444
|
-
}
|
|
445
|
-
if (this.owners.size > 0) {
|
|
446
|
-
this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
|
|
447
|
-
}
|
|
448
|
-
this.javaInfo = await JavaChecker_1.JavaChecker.ensureJava(this.config.version, this.options.usePortableJava || false);
|
|
449
|
-
this.javaCommand = this.javaInfo.path;
|
|
450
|
-
this.logger.success(`Using Java ${this.javaInfo.version} (${this.javaInfo.type}) at ${this.javaInfo.path}`);
|
|
451
|
-
const systemInfo = SystemDetector_1.SystemDetector.getSystemInfo();
|
|
452
|
-
this.logger.debug('System info:', systemInfo);
|
|
453
|
-
const serverDir = process.cwd();
|
|
454
|
-
await FileUtils_1.FileUtils.ensureServerStructure(this.config);
|
|
455
|
-
this.worldSize = await this.calculateWorldSize();
|
|
456
|
-
if (this.worldSize > 10 * 1024 * 1024 * 1024) {
|
|
457
|
-
this.logger.warning(`Large world detected (${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB). Consider increasing memory allocation.`);
|
|
458
|
-
}
|
|
459
|
-
const jarPath = await this.engine.download(this.config, serverDir);
|
|
460
|
-
if (this.config.type === 'forge') {
|
|
461
|
-
await this.engine.prepare(this.config, serverDir, jarPath);
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
await this.engine.prepare(this.config, serverDir);
|
|
465
|
-
}
|
|
466
|
-
if (this.config.platform === 'all') {
|
|
467
|
-
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
468
|
-
await this.geyser.setup(this.config);
|
|
469
|
-
}
|
|
470
|
-
if (this.options.enableViaVersion !== false) {
|
|
471
|
-
this.logger.info('Enabling ViaVersion for client version compatibility...');
|
|
472
|
-
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
473
|
-
await this.viaVersion.setup(this.config);
|
|
474
|
-
await this.viaVersion.configureViaVersion(this.config);
|
|
475
|
-
if (this.options.enableViaBackwards !== false) {
|
|
476
|
-
this.logger.info('ViaBackwards will be installed');
|
|
477
|
-
}
|
|
478
|
-
if (this.options.enableViaRewind !== false) {
|
|
479
|
-
this.logger.info('ViaRewind will be installed');
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
if (this.options.enableSkinRestorer !== false) {
|
|
483
|
-
this.logger.info('Enabling SkinRestorer for player skins...');
|
|
484
|
-
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
485
|
-
await this.skinRestorer.setup(this.config);
|
|
486
|
-
}
|
|
487
|
-
const javaArgs = this.buildJavaArgs();
|
|
488
|
-
const serverJar = this.engine.getServerJar(jarPath);
|
|
489
|
-
const serverArgs = this.engine.getServerArgs();
|
|
490
|
-
const fullArgs = [
|
|
491
|
-
...javaArgs,
|
|
492
|
-
'-jar',
|
|
493
|
-
serverJar,
|
|
494
|
-
...serverArgs
|
|
495
|
-
];
|
|
496
|
-
if (this.options.networkOptimization?.tcpFastOpen) {
|
|
497
|
-
fullArgs.unshift('-Djava.net.preferIPv4Stack=true');
|
|
498
|
-
}
|
|
499
|
-
if (this.options.networkOptimization?.bungeeMode) {
|
|
500
|
-
fullArgs.unshift('-Dnet.kyori.adventure.text.serializer.legacy.AMPMSupport=true');
|
|
501
|
-
}
|
|
502
|
-
const env = this.buildEnvironment();
|
|
503
|
-
this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
|
|
504
|
-
this.process = (0, child_process_1.spawn)(this.javaCommand, fullArgs, {
|
|
505
|
-
cwd: serverDir,
|
|
506
|
-
env: env,
|
|
507
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
508
|
-
});
|
|
509
|
-
this.serverInfo.pid = this.process.pid;
|
|
510
|
-
this.serverInfo.status = 'starting';
|
|
511
|
-
this.startTime = new Date();
|
|
512
|
-
if (this.options.silentMode) {
|
|
513
|
-
this.process.stdout.pipe(process.stdout);
|
|
514
|
-
this.process.stderr.pipe(process.stderr);
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
this.setupLogHandlers();
|
|
518
|
-
}
|
|
519
|
-
this.process.on('exit', (code) => {
|
|
520
|
-
this.serverInfo.status = 'stopped';
|
|
521
|
-
this.logger.warning(`Server stopped with code ${code}`);
|
|
522
|
-
if (this.config.autoRestart && code !== 0) {
|
|
523
|
-
this.logger.info('Auto-restarting...');
|
|
524
|
-
this.start();
|
|
525
|
-
}
|
|
526
|
-
this.emit('stop', { code });
|
|
527
|
-
});
|
|
528
|
-
if (this.options.statsInterval && this.options.statsInterval > 0) {
|
|
529
|
-
this.statsInterval = setInterval(() => this.updateStats(), this.options.statsInterval);
|
|
530
|
-
}
|
|
531
|
-
this.monitorResources();
|
|
532
|
-
if (this.config.backup.enabled) {
|
|
533
|
-
this.setupBackups();
|
|
534
|
-
}
|
|
535
|
-
setTimeout(() => {
|
|
536
|
-
if (this.serverInfo.status === 'starting') {
|
|
537
|
-
this.serverInfo.status = 'running';
|
|
538
|
-
this.logger.success('Server started successfully!');
|
|
539
|
-
if (this.options.enableViaVersion !== false) {
|
|
540
|
-
this.logger.info('ViaVersion is active - players from older versions can connect');
|
|
541
|
-
}
|
|
542
|
-
if (this.options.enableSkinRestorer !== false) {
|
|
543
|
-
this.logger.info('SkinRestorer is active - player skins will be restored');
|
|
544
|
-
}
|
|
545
|
-
if (this.worldSize > 0) {
|
|
546
|
-
this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
|
|
547
|
-
}
|
|
548
|
-
if (this.owners.size > 0) {
|
|
549
|
-
this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
|
|
550
|
-
}
|
|
551
|
-
this.emit('ready', this.serverInfo);
|
|
552
|
-
this.startMemoryMonitor();
|
|
553
|
-
}
|
|
554
|
-
}, 10000);
|
|
555
|
-
return this.serverInfo;
|
|
556
|
-
}
|
|
557
531
|
setupLogHandlers() {
|
|
558
532
|
this.process.stdout.on('data', (data) => {
|
|
559
533
|
const output = data.toString();
|
|
560
534
|
process.stdout.write(output);
|
|
535
|
+
if (!this.serverReady) {
|
|
536
|
+
if (output.includes('Done') || output.includes('For help, type "help"')) {
|
|
537
|
+
this.serverReady = true;
|
|
538
|
+
this.serverInfo.status = 'running';
|
|
539
|
+
this.logger.success('Server started successfully!');
|
|
540
|
+
if (this.options.enableViaVersion !== false) {
|
|
541
|
+
this.logger.info('ViaVersion is active - players from older versions can connect');
|
|
542
|
+
}
|
|
543
|
+
if (this.options.enableSkinRestorer !== false) {
|
|
544
|
+
this.logger.info('SkinRestorer is active - player skins will be restored');
|
|
545
|
+
}
|
|
546
|
+
if (this.worldSize > 0) {
|
|
547
|
+
this.logger.info(`World size: ${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB`);
|
|
548
|
+
}
|
|
549
|
+
if (this.owners.size > 0) {
|
|
550
|
+
this.logger.info(`Owner commands enabled with prefix: ${this.ownerCommandPrefix}`);
|
|
551
|
+
}
|
|
552
|
+
if (this.options.ramdisk?.enabled) {
|
|
553
|
+
this.logger.info('RAM disk active - worlds/plugins running in memory');
|
|
554
|
+
}
|
|
555
|
+
this.emit('ready', this.serverInfo);
|
|
556
|
+
this.startMemoryMonitor();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
561
559
|
if (output.includes('joined the game')) {
|
|
562
560
|
const match = output.match(/(\w+) joined the game/);
|
|
563
561
|
if (match) {
|
|
@@ -856,7 +854,8 @@ world-settings:
|
|
|
856
854
|
this.emit('resource', this.serverInfo);
|
|
857
855
|
}
|
|
858
856
|
catch (error) {
|
|
859
|
-
|
|
857
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
858
|
+
this.logger.error(`Stats update error: ${errorMessage}`);
|
|
860
859
|
}
|
|
861
860
|
}
|
|
862
861
|
startMemoryMonitor() {
|
|
@@ -882,24 +881,26 @@ world-settings:
|
|
|
882
881
|
this.memoryUsageHistory[0] * 1.2;
|
|
883
882
|
if (isIncreasing) {
|
|
884
883
|
this.logger.warning('Memory leak detected!');
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
884
|
+
if (action === 'restart' && this.players.size === 0) {
|
|
885
|
+
this.logger.info('No players online, restarting server due to memory leak...');
|
|
886
|
+
await this.gracefulRestart();
|
|
887
|
+
}
|
|
888
|
+
else if (action === 'stop' && this.players.size === 0) {
|
|
889
|
+
this.logger.info('No players online, stopping server due to memory leak...');
|
|
890
|
+
await this.stop();
|
|
891
|
+
}
|
|
892
|
+
else if (action === 'warn') {
|
|
893
|
+
this.logger.warning('Please restart server to free memory');
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
this.logger.warning('Cannot restart: players online');
|
|
897
897
|
}
|
|
898
898
|
}
|
|
899
899
|
}
|
|
900
900
|
}
|
|
901
901
|
catch (error) {
|
|
902
|
-
|
|
902
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
903
|
+
this.logger.error(`Memory monitor error: ${errorMessage}`);
|
|
903
904
|
}
|
|
904
905
|
}, interval);
|
|
905
906
|
}
|
|
@@ -918,6 +919,114 @@ world-settings:
|
|
|
918
919
|
await this.stop();
|
|
919
920
|
await this.start();
|
|
920
921
|
}
|
|
922
|
+
async start() {
|
|
923
|
+
this.logger.info(`Starting ${this.config.type} server v${this.config.version}...`);
|
|
924
|
+
await this.setupSymlinkMaster();
|
|
925
|
+
await this.setupRamdisk();
|
|
926
|
+
if (this.options.cleanLogsInterval && this.options.cleanLogsInterval > 0) {
|
|
927
|
+
this.cleanLogsCron = cron.schedule('0 */3 * * *', () => this.cleanOldLogs());
|
|
928
|
+
this.logger.info('Log cleanup scheduled every 3 hours');
|
|
929
|
+
}
|
|
930
|
+
if (this.owners.size > 0) {
|
|
931
|
+
this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
|
|
932
|
+
}
|
|
933
|
+
this.javaInfo = await JavaChecker_1.JavaChecker.ensureJava(this.config.version, this.options.usePortableJava || false);
|
|
934
|
+
this.javaCommand = this.javaInfo.path;
|
|
935
|
+
this.logger.success(`Using Java ${this.javaInfo.version} (${this.javaInfo.type}) at ${this.javaInfo.path}`);
|
|
936
|
+
const systemInfo = SystemDetector_1.SystemDetector.getSystemInfo();
|
|
937
|
+
this.logger.debug('System info:', systemInfo);
|
|
938
|
+
const serverDir = process.cwd();
|
|
939
|
+
await FileUtils_1.FileUtils.ensureServerStructure(this.config);
|
|
940
|
+
this.worldSize = await this.calculateWorldSize();
|
|
941
|
+
if (this.worldSize > 10 * 1024 * 1024 * 1024) {
|
|
942
|
+
this.logger.warning(`Large world detected (${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB). Consider increasing memory allocation.`);
|
|
943
|
+
}
|
|
944
|
+
const jarPath = await this.engine.download(this.config, serverDir);
|
|
945
|
+
if (this.config.type === 'forge') {
|
|
946
|
+
await this.engine.prepare(this.config, serverDir, jarPath);
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
await this.engine.prepare(this.config, serverDir);
|
|
950
|
+
}
|
|
951
|
+
if (this.config.platform === 'all') {
|
|
952
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
953
|
+
await this.geyser.setup(this.config, this.isPterodactyl);
|
|
954
|
+
}
|
|
955
|
+
const pluginsToInstall = [
|
|
956
|
+
{ name: 'ProtocolLib', enabled: this.options.enableProtocolLib, url: 'https://github.com/dmulloy2/ProtocolLib/releases/latest/download/ProtocolLib.jar' },
|
|
957
|
+
{ name: 'TCPShield', enabled: this.options.enableTCPShield, url: 'https://tcpshield.com/api/download/plugin' },
|
|
958
|
+
{ name: 'SpigotOptimizers', enabled: this.options.enableSpigotOptimizers, url: 'https://github.com/SpigotOptimizers/SpigotOptimizers/releases/latest/download/SpigotOptimizers.jar' },
|
|
959
|
+
{ name: 'Spark', enabled: this.options.enableSpark, url: 'https://spark.lucko.me/download' },
|
|
960
|
+
{ name: 'ViewDistanceTweaks', enabled: this.options.enableViewDistanceTweaks, url: 'https://github.com/ViewDistanceTweaks/ViewDistanceTweaks/releases/latest/download/ViewDistanceTweaks.jar' },
|
|
961
|
+
{ name: 'FarmControl', enabled: this.options.enableFarmControl, url: 'https://github.com/FarmControl/FarmControl/releases/latest/download/FarmControl.jar' },
|
|
962
|
+
{ name: 'EntityDetection', enabled: this.options.enableEntityDetection, url: 'https://github.com/EntityDetection/EntityDetection/releases/latest/download/EntityDetection.jar' }
|
|
963
|
+
];
|
|
964
|
+
for (const plugin of pluginsToInstall) {
|
|
965
|
+
if (plugin.enabled) {
|
|
966
|
+
await this.pluginManager.installPlugin(plugin.name, plugin.url, this.config.folders.plugins);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (this.options.enableViaVersion !== false) {
|
|
970
|
+
this.logger.info('Enabling ViaVersion for client version compatibility...');
|
|
971
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
972
|
+
await this.viaVersion.setup(this.config);
|
|
973
|
+
await this.viaVersion.configureViaVersion(this.config);
|
|
974
|
+
if (this.options.enableViaBackwards !== false) {
|
|
975
|
+
this.logger.info('ViaBackwards will be installed');
|
|
976
|
+
}
|
|
977
|
+
if (this.options.enableViaRewind !== false) {
|
|
978
|
+
this.logger.info('ViaRewind will be installed');
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (this.options.enableSkinRestorer !== false) {
|
|
982
|
+
this.logger.info('Enabling SkinRestorer for player skins...');
|
|
983
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
984
|
+
await this.skinRestorer.setup(this.config);
|
|
985
|
+
}
|
|
986
|
+
const javaArgs = this.buildJavaArgs();
|
|
987
|
+
const serverJar = this.engine.getServerJar(jarPath);
|
|
988
|
+
const serverArgs = this.engine.getServerArgs();
|
|
989
|
+
const fullArgs = [
|
|
990
|
+
...javaArgs,
|
|
991
|
+
'-jar',
|
|
992
|
+
serverJar,
|
|
993
|
+
...serverArgs
|
|
994
|
+
];
|
|
995
|
+
const env = this.buildEnvironment();
|
|
996
|
+
this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
|
|
997
|
+
this.process = (0, child_process_1.spawn)(this.javaCommand, fullArgs, {
|
|
998
|
+
cwd: serverDir,
|
|
999
|
+
env: env,
|
|
1000
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
1001
|
+
});
|
|
1002
|
+
this.serverInfo.pid = this.process.pid;
|
|
1003
|
+
this.serverInfo.status = 'starting';
|
|
1004
|
+
this.startTime = new Date();
|
|
1005
|
+
if (this.options.silentMode) {
|
|
1006
|
+
this.process.stdout.pipe(process.stdout);
|
|
1007
|
+
this.process.stderr.pipe(process.stderr);
|
|
1008
|
+
}
|
|
1009
|
+
else {
|
|
1010
|
+
this.setupLogHandlers();
|
|
1011
|
+
}
|
|
1012
|
+
this.process.on('exit', (code) => {
|
|
1013
|
+
this.serverInfo.status = 'stopped';
|
|
1014
|
+
this.logger.warning(`Server stopped with code ${code}`);
|
|
1015
|
+
if (this.config.autoRestart && code !== 0) {
|
|
1016
|
+
this.logger.info('Auto-restarting...');
|
|
1017
|
+
this.start();
|
|
1018
|
+
}
|
|
1019
|
+
this.emit('stop', { code });
|
|
1020
|
+
});
|
|
1021
|
+
if (this.options.statsInterval && this.options.statsInterval > 0) {
|
|
1022
|
+
this.statsInterval = setInterval(() => this.updateStats(), this.options.statsInterval);
|
|
1023
|
+
}
|
|
1024
|
+
this.monitorResources();
|
|
1025
|
+
if (this.config.backup.enabled) {
|
|
1026
|
+
this.setupBackups();
|
|
1027
|
+
}
|
|
1028
|
+
return this.serverInfo;
|
|
1029
|
+
}
|
|
921
1030
|
async stop() {
|
|
922
1031
|
if (!this.process) {
|
|
923
1032
|
this.logger.warning('Server not running');
|
|
@@ -938,6 +1047,9 @@ world-settings:
|
|
|
938
1047
|
if (this.cleanLogsCron) {
|
|
939
1048
|
this.cleanLogsCron.stop();
|
|
940
1049
|
}
|
|
1050
|
+
if (this.ramdiskBackupCron) {
|
|
1051
|
+
this.ramdiskBackupCron.stop();
|
|
1052
|
+
}
|
|
941
1053
|
if (this.memoryMonitorInterval) {
|
|
942
1054
|
clearInterval(this.memoryMonitorInterval);
|
|
943
1055
|
}
|
|
@@ -947,7 +1059,9 @@ world-settings:
|
|
|
947
1059
|
if (this.config.platform === 'all') {
|
|
948
1060
|
this.geyser.stop();
|
|
949
1061
|
}
|
|
950
|
-
this.
|
|
1062
|
+
if (this.options.ramdisk?.enabled) {
|
|
1063
|
+
await this.backupRamdiskToMaster();
|
|
1064
|
+
}
|
|
951
1065
|
this.logger.success('Server stopped');
|
|
952
1066
|
}
|
|
953
1067
|
sendCommand(command) {
|
|
@@ -1043,32 +1157,6 @@ world-settings:
|
|
|
1043
1157
|
}
|
|
1044
1158
|
return value;
|
|
1045
1159
|
}
|
|
1046
|
-
async calculateWorldSize() {
|
|
1047
|
-
try {
|
|
1048
|
-
const worldPath = path.join(process.cwd(), this.config.folders.world);
|
|
1049
|
-
if (!await fs.pathExists(worldPath))
|
|
1050
|
-
return 0;
|
|
1051
|
-
const getSize = async (dir) => {
|
|
1052
|
-
let total = 0;
|
|
1053
|
-
const files = await fs.readdir(dir);
|
|
1054
|
-
for (const file of files) {
|
|
1055
|
-
const filePath = path.join(dir, file);
|
|
1056
|
-
const stat = await fs.stat(filePath);
|
|
1057
|
-
if (stat.isDirectory()) {
|
|
1058
|
-
total += await getSize(filePath);
|
|
1059
|
-
}
|
|
1060
|
-
else {
|
|
1061
|
-
total += stat.size;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
return total;
|
|
1065
|
-
};
|
|
1066
|
-
return await getSize(worldPath);
|
|
1067
|
-
}
|
|
1068
|
-
catch {
|
|
1069
|
-
return 0;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
1160
|
}
|
|
1073
1161
|
exports.MinecraftServer = MinecraftServer;
|
|
1074
1162
|
//# sourceMappingURL=MinecraftServer.js.map
|