@dimzxzzx07/mc-headless 2.2.2 → 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 +652 -776
- package/dist/core/MinecraftServer.d.ts +13 -38
- package/dist/core/MinecraftServer.d.ts.map +1 -1
- package/dist/core/MinecraftServer.js +417 -333
- 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 +496 -411
- 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,150 +376,78 @@ class MinecraftServer extends events_1.EventEmitter {
|
|
|
211
376
|
this.logger.info('Old logs cleaned');
|
|
212
377
|
}
|
|
213
378
|
catch (err) {
|
|
214
|
-
|
|
379
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
380
|
+
this.logger.warning(`Failed to clean logs: ${errorMessage}`);
|
|
215
381
|
}
|
|
216
382
|
}
|
|
217
|
-
|
|
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');
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
detectCgroupLimits() {
|
|
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
|
+
'-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'
|
|
352
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
|
+
}
|
|
353
447
|
let gcArgs = [];
|
|
354
448
|
if (memMax >= 16384) {
|
|
355
449
|
gcArgs = [
|
|
356
450
|
'-XX:+ParallelRefProcEnabled',
|
|
357
|
-
'-XX:MaxGCPauseMillis=100',
|
|
358
451
|
'-XX:+UnlockExperimentalVMOptions',
|
|
359
452
|
'-XX:+DisableExplicitGC',
|
|
360
453
|
'-XX:+AlwaysPreTouch',
|
|
@@ -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',
|
|
@@ -375,7 +467,6 @@ world-settings:
|
|
|
375
467
|
else if (memMax >= 8192) {
|
|
376
468
|
gcArgs = [
|
|
377
469
|
'-XX:+ParallelRefProcEnabled',
|
|
378
|
-
'-XX:MaxGCPauseMillis=150',
|
|
379
470
|
'-XX:+UnlockExperimentalVMOptions',
|
|
380
471
|
'-XX:+DisableExplicitGC',
|
|
381
472
|
'-XX:+AlwaysPreTouch',
|
|
@@ -386,7 +477,6 @@ world-settings:
|
|
|
386
477
|
'-XX:G1HeapWastePercent=5',
|
|
387
478
|
'-XX:G1MixedGCCountTarget=4',
|
|
388
479
|
'-XX:InitiatingHeapOccupancyPercent=15',
|
|
389
|
-
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
390
480
|
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
391
481
|
'-XX:SurvivorRatio=32',
|
|
392
482
|
'-XX:+PerfDisableSharedMem',
|
|
@@ -394,23 +484,23 @@ world-settings:
|
|
|
394
484
|
];
|
|
395
485
|
}
|
|
396
486
|
else {
|
|
397
|
-
gcArgs =
|
|
487
|
+
gcArgs = [
|
|
398
488
|
'-XX:+ParallelRefProcEnabled',
|
|
399
|
-
'-XX:MaxGCPauseMillis=200',
|
|
400
489
|
'-XX:+UnlockExperimentalVMOptions',
|
|
401
490
|
'-XX:+DisableExplicitGC',
|
|
402
491
|
'-XX:+AlwaysPreTouch',
|
|
403
492
|
'-XX:G1HeapWastePercent=5',
|
|
404
493
|
'-XX:G1MixedGCCountTarget=4',
|
|
405
494
|
'-XX:InitiatingHeapOccupancyPercent=15',
|
|
406
|
-
'-XX:G1MixedGCLiveThresholdPercent=90',
|
|
407
495
|
'-XX:G1RSetUpdatingPauseTimePercent=5',
|
|
408
496
|
'-XX:SurvivorRatio=32',
|
|
409
497
|
'-XX:+PerfDisableSharedMem',
|
|
410
|
-
'-XX:MaxTenuringThreshold=1'
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
+
}
|
|
414
504
|
}
|
|
415
505
|
if (javaVersion === '21') {
|
|
416
506
|
gcArgs.push('--enable-preview');
|
|
@@ -434,130 +524,34 @@ world-settings:
|
|
|
434
524
|
}
|
|
435
525
|
return env;
|
|
436
526
|
}
|
|
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
527
|
setupLogHandlers() {
|
|
558
528
|
this.process.stdout.on('data', (data) => {
|
|
559
529
|
const output = data.toString();
|
|
560
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
|
+
}
|
|
561
555
|
if (output.includes('joined the game')) {
|
|
562
556
|
const match = output.match(/(\w+) joined the game/);
|
|
563
557
|
if (match) {
|
|
@@ -856,7 +850,8 @@ world-settings:
|
|
|
856
850
|
this.emit('resource', this.serverInfo);
|
|
857
851
|
}
|
|
858
852
|
catch (error) {
|
|
859
|
-
|
|
853
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
854
|
+
this.logger.error(`Stats update error: ${errorMessage}`);
|
|
860
855
|
}
|
|
861
856
|
}
|
|
862
857
|
startMemoryMonitor() {
|
|
@@ -882,24 +877,26 @@ world-settings:
|
|
|
882
877
|
this.memoryUsageHistory[0] * 1.2;
|
|
883
878
|
if (isIncreasing) {
|
|
884
879
|
this.logger.warning('Memory leak detected!');
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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');
|
|
897
893
|
}
|
|
898
894
|
}
|
|
899
895
|
}
|
|
900
896
|
}
|
|
901
897
|
catch (error) {
|
|
902
|
-
|
|
898
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
899
|
+
this.logger.error(`Memory monitor error: ${errorMessage}`);
|
|
903
900
|
}
|
|
904
901
|
}, interval);
|
|
905
902
|
}
|
|
@@ -918,6 +915,114 @@ world-settings:
|
|
|
918
915
|
await this.stop();
|
|
919
916
|
await this.start();
|
|
920
917
|
}
|
|
918
|
+
async start() {
|
|
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
|
+
}
|
|
926
|
+
if (this.owners.size > 0) {
|
|
927
|
+
this.logger.info(`Owners configured: ${Array.from(this.owners).join(', ')}`);
|
|
928
|
+
}
|
|
929
|
+
this.javaInfo = await JavaChecker_1.JavaChecker.ensureJava(this.config.version, this.options.usePortableJava || false);
|
|
930
|
+
this.javaCommand = this.javaInfo.path;
|
|
931
|
+
this.logger.success(`Using Java ${this.javaInfo.version} (${this.javaInfo.type}) at ${this.javaInfo.path}`);
|
|
932
|
+
const systemInfo = SystemDetector_1.SystemDetector.getSystemInfo();
|
|
933
|
+
this.logger.debug('System info:', systemInfo);
|
|
934
|
+
const serverDir = process.cwd();
|
|
935
|
+
await FileUtils_1.FileUtils.ensureServerStructure(this.config);
|
|
936
|
+
this.worldSize = await this.calculateWorldSize();
|
|
937
|
+
if (this.worldSize > 10 * 1024 * 1024 * 1024) {
|
|
938
|
+
this.logger.warning(`Large world detected (${(this.worldSize / 1024 / 1024 / 1024).toFixed(2)} GB). Consider increasing memory allocation.`);
|
|
939
|
+
}
|
|
940
|
+
const jarPath = await this.engine.download(this.config, serverDir);
|
|
941
|
+
if (this.config.type === 'forge') {
|
|
942
|
+
await this.engine.prepare(this.config, serverDir, jarPath);
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
await this.engine.prepare(this.config, serverDir);
|
|
946
|
+
}
|
|
947
|
+
if (this.config.platform === 'all') {
|
|
948
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
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
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (this.options.enableViaVersion !== false) {
|
|
966
|
+
this.logger.info('Enabling ViaVersion for client version compatibility...');
|
|
967
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
968
|
+
await this.viaVersion.setup(this.config);
|
|
969
|
+
await this.viaVersion.configureViaVersion(this.config);
|
|
970
|
+
if (this.options.enableViaBackwards !== false) {
|
|
971
|
+
this.logger.info('ViaBackwards will be installed');
|
|
972
|
+
}
|
|
973
|
+
if (this.options.enableViaRewind !== false) {
|
|
974
|
+
this.logger.info('ViaRewind will be installed');
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
if (this.options.enableSkinRestorer !== false) {
|
|
978
|
+
this.logger.info('Enabling SkinRestorer for player skins...');
|
|
979
|
+
await FileUtils_1.FileUtils.ensureDir(this.config.folders.plugins);
|
|
980
|
+
await this.skinRestorer.setup(this.config);
|
|
981
|
+
}
|
|
982
|
+
const javaArgs = this.buildJavaArgs();
|
|
983
|
+
const serverJar = this.engine.getServerJar(jarPath);
|
|
984
|
+
const serverArgs = this.engine.getServerArgs();
|
|
985
|
+
const fullArgs = [
|
|
986
|
+
...javaArgs,
|
|
987
|
+
'-jar',
|
|
988
|
+
serverJar,
|
|
989
|
+
...serverArgs
|
|
990
|
+
];
|
|
991
|
+
const env = this.buildEnvironment();
|
|
992
|
+
this.logger.info(`Launching: ${this.javaCommand} ${fullArgs.join(' ')}`);
|
|
993
|
+
this.process = (0, child_process_1.spawn)(this.javaCommand, fullArgs, {
|
|
994
|
+
cwd: serverDir,
|
|
995
|
+
env: env,
|
|
996
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
997
|
+
});
|
|
998
|
+
this.serverInfo.pid = this.process.pid;
|
|
999
|
+
this.serverInfo.status = 'starting';
|
|
1000
|
+
this.startTime = new Date();
|
|
1001
|
+
if (this.options.silentMode) {
|
|
1002
|
+
this.process.stdout.pipe(process.stdout);
|
|
1003
|
+
this.process.stderr.pipe(process.stderr);
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
this.setupLogHandlers();
|
|
1007
|
+
}
|
|
1008
|
+
this.process.on('exit', (code) => {
|
|
1009
|
+
this.serverInfo.status = 'stopped';
|
|
1010
|
+
this.logger.warning(`Server stopped with code ${code}`);
|
|
1011
|
+
if (this.config.autoRestart && code !== 0) {
|
|
1012
|
+
this.logger.info('Auto-restarting...');
|
|
1013
|
+
this.start();
|
|
1014
|
+
}
|
|
1015
|
+
this.emit('stop', { code });
|
|
1016
|
+
});
|
|
1017
|
+
if (this.options.statsInterval && this.options.statsInterval > 0) {
|
|
1018
|
+
this.statsInterval = setInterval(() => this.updateStats(), this.options.statsInterval);
|
|
1019
|
+
}
|
|
1020
|
+
this.monitorResources();
|
|
1021
|
+
if (this.config.backup.enabled) {
|
|
1022
|
+
this.setupBackups();
|
|
1023
|
+
}
|
|
1024
|
+
return this.serverInfo;
|
|
1025
|
+
}
|
|
921
1026
|
async stop() {
|
|
922
1027
|
if (!this.process) {
|
|
923
1028
|
this.logger.warning('Server not running');
|
|
@@ -938,6 +1043,9 @@ world-settings:
|
|
|
938
1043
|
if (this.cleanLogsCron) {
|
|
939
1044
|
this.cleanLogsCron.stop();
|
|
940
1045
|
}
|
|
1046
|
+
if (this.ramdiskBackupCron) {
|
|
1047
|
+
this.ramdiskBackupCron.stop();
|
|
1048
|
+
}
|
|
941
1049
|
if (this.memoryMonitorInterval) {
|
|
942
1050
|
clearInterval(this.memoryMonitorInterval);
|
|
943
1051
|
}
|
|
@@ -947,7 +1055,9 @@ world-settings:
|
|
|
947
1055
|
if (this.config.platform === 'all') {
|
|
948
1056
|
this.geyser.stop();
|
|
949
1057
|
}
|
|
950
|
-
this.
|
|
1058
|
+
if (this.options.ramdisk?.enabled) {
|
|
1059
|
+
await this.backupRamdiskToMaster();
|
|
1060
|
+
}
|
|
951
1061
|
this.logger.success('Server stopped');
|
|
952
1062
|
}
|
|
953
1063
|
sendCommand(command) {
|
|
@@ -1043,32 +1153,6 @@ world-settings:
|
|
|
1043
1153
|
}
|
|
1044
1154
|
return value;
|
|
1045
1155
|
}
|
|
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
1156
|
}
|
|
1073
1157
|
exports.MinecraftServer = MinecraftServer;
|
|
1074
1158
|
//# sourceMappingURL=MinecraftServer.js.map
|