Skip to content

Commit aca1d34

Browse files
authored
Server Start Script Generator (#280)
1 parent d91d503 commit aca1d34

File tree

6 files changed

+524
-9
lines changed

6 files changed

+524
-9
lines changed

config/sidebar.misc.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ const misc: SidebarsConfig = {
1212
id: "README",
1313
},
1414
items: [
15+
{
16+
type: "category",
17+
label: "Tools",
18+
collapsed: false,
19+
collapsible: true,
20+
items: [
21+
"tools/start-script-gen",
22+
],
23+
},
1524
"java-install",
1625
"downloads-api",
1726
"hangar-publishing",
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
slug: /tools/start-script-gen
3+
description: A Start Script Generator for PaperMC Projects
4+
---
5+
6+
import StartScriptGenerator from '@site/src/components/StartScriptGenerator';
7+
8+
# Start Script Generator
9+
10+
<StartScriptGenerator />

docs/paper/admin/getting-started/getting-started.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ disables Vanilla's GUI, so you don't get double interfaces when using the comman
5454

5555
For more advanced Java tuning, see [Aikar's Flags](../how-to/aikars-flags.md).
5656

57+
Use our [Startup Script Generator](/misc/tools/start-script-gen) to generate a startup script for your server.
58+
5759
To configure your server, see the [Global Configuration](../reference/configuration/global-configuration.mdx) and
5860
[Per World Configuration](../reference/configuration/world-configuration.mdx) pages.
5961

docs/paper/admin/how-to/aikars-flags.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,6 @@ up much space (5MB)
189189
15. **+PerfDisableSharedMem:** Causes GC to write to file system which can cause major latency if
190190
disk IO is high -- See https://www.evanjones.ca/jvm-mmap-pause.html
191191

192-
## Using Large Pages
193-
194-
For Large Pages -- It's even more important to use -Xms = -Xmx! Large Pages needs to have all the
195-
memory specified for it, or you could end up without the gains. This memory will not be used by the
196-
OS anyway, so use it.
197-
198-
Additionally, use these flags (Metaspace is Java 8 Only, don't use it for Java7):
199-
`-XX:+UseLargePagesInMetaspace`
200-
201192
### Transparent Huge Pages
202193

203194
Controversial feature but may be usable if you can not configure your host for real HugeTLBFS. Try
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import React, { useEffect, useState, useRef } from 'react';
2+
import '@site/src/css/start-script-generator.css';
3+
4+
const markerPoints = [4, 8, 12, 16, 20];
5+
6+
type FlagType = {
7+
label: string;
8+
value: string;
9+
description: string;
10+
};
11+
12+
const WINDOWS_AUTO_RESTART = ':start\n%%CONTENT%%\n\necho Server restarting...\necho Press CTRL + C to stop.\ngoto :start';
13+
const LINUX_AUTO_RESTART = 'while [ true ]; do\n %%CONTENT%%\n echo Server restarting...\n echo Press CTRL + C to stop.\ndone';
14+
15+
const FLAGS: { [key: string]: FlagType } = {
16+
AIKARS: {
17+
label: 'Aikar\'s',
18+
value: [
19+
'-XX:+AlwaysPreTouch',
20+
'-XX:+DisableExplicitGC',
21+
'-XX:+ParallelRefProcEnabled',
22+
'-XX:+PerfDisableSharedMem',
23+
'-XX:+UnlockExperimentalVMOptions',
24+
'-XX:+UseG1GC',
25+
'-XX:G1HeapRegionSize=8M',
26+
'-XX:G1HeapWastePercent=5',
27+
'-XX:G1MaxNewSizePercent=40',
28+
'-XX:G1MixedGCCountTarget=4',
29+
'-XX:G1MixedGCLiveThresholdPercent=90',
30+
'-XX:G1NewSizePercent=30',
31+
'-XX:G1RSetUpdatingPauseTimePercent=5',
32+
'-XX:G1ReservePercent=20',
33+
'-XX:InitiatingHeapOccupancyPercent=15',
34+
'-XX:MaxGCPauseMillis=200',
35+
'-XX:MaxTenuringThreshold=1',
36+
'-XX:SurvivorRatio=32',
37+
'-Dusing.aikars.flags=https://mcflags.emc.gs',
38+
'-Daikars.new.flags=true'
39+
].join(' '),
40+
description: 'Optimized Minecraft flags by Aikar for better server performance.',
41+
},
42+
NONE: {
43+
label: 'None',
44+
value: '',
45+
description: 'No additional flags.'
46+
},
47+
VELOCITY: {
48+
label: 'Velocity',
49+
value: [
50+
'-XX:+AlwaysPreTouch',
51+
'-XX:+ParallelRefProcEnabled',
52+
'-XX:+UnlockExperimentalVMOptions',
53+
'-XX:+UseG1GC',
54+
'-XX:G1HeapRegionSize=4M',
55+
'-XX:MaxInlineLevel=15'
56+
].join(' '),
57+
description: 'Flags recommended for use with the Velocity proxy server.',
58+
},
59+
};
60+
61+
const isServerSide = typeof document === 'undefined';
62+
63+
const generateStartCommand = (memory: number, selectedFlag: FlagType, filename: string, guiEnabled: boolean, autoRestartEnabled: boolean, platform: string) => {
64+
setTimeout(resizeOutput, 0);
65+
let content = '';
66+
const command = `java -Xmx${memory * 1024}M -Xms${memory * 1024}M ${selectedFlag.value}${selectedFlag === FLAGS.NONE ? '' : ' '}-jar ${filename === '' ? 'server.jar' : filename} ${guiEnabled ? '' : '--nogui'}`;
67+
68+
if (autoRestartEnabled)
69+
content = (platform === 'windows' ? WINDOWS_AUTO_RESTART : LINUX_AUTO_RESTART).replace('%%CONTENT%%', command);
70+
else
71+
content = command;
72+
73+
if (platform === 'linux')
74+
content = '#!/bin/bash\n\n' + content;
75+
76+
return content;
77+
};
78+
79+
const resizeOutput = () => {
80+
if (isServerSide) return;
81+
const element = document.getElementById('output-command-text');
82+
if (!element) return;
83+
84+
element.style.height = 'auto';
85+
const scrollHeight = element.scrollHeight;
86+
87+
requestAnimationFrame(() => {
88+
element.style.height = `${scrollHeight}px`;
89+
});
90+
};
91+
92+
const StartScriptGenerator: React.FC = () => {
93+
const [memory, setMemory] = useState(4.0);
94+
const [filename, setFilename] = useState('server.jar');
95+
const [selectedFlag, setSelectedFlag] = useState(FLAGS.AIKARS);
96+
const [dropdownVisible, setDropdownVisible] = useState(false);
97+
const [guiEnabled, setGuiEnabled] = useState(false);
98+
const [autoRestart, setAutoRestart] = useState(false);
99+
const [platform, setPlatform] = useState('linux');
100+
const dropdownRef = useRef(null);
101+
102+
const handleClickOutside = (event: { target: EventTarget | null }) => {
103+
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
104+
setDropdownVisible(false);
105+
}
106+
};
107+
108+
const handleGreenButtonHighlight = (element: HTMLElement | null) => {
109+
if (!element) return;
110+
element.classList.add('success');
111+
setTimeout(function() {
112+
element.classList.remove('success');
113+
}, 500);
114+
}
115+
116+
const handleCopyToClipboard = () => {
117+
navigator.clipboard.writeText(generateStartCommand(memory, selectedFlag, filename, guiEnabled, autoRestart, platform));
118+
handleGreenButtonHighlight(document.getElementById('clipboard-copy-button'));
119+
};
120+
121+
const handleDownload = () => {
122+
handleGreenButtonHighlight(document.getElementById('contents-download-button'));
123+
const blob = new Blob([generateStartCommand(memory, selectedFlag, filename, guiEnabled, autoRestart, platform)], { type: 'text/plain' });
124+
const link = document.createElement('a');
125+
link.href = URL.createObjectURL(blob);
126+
link.download = 'start.' + (platform === 'windows' ? 'bat' : 'sh');
127+
link.click()
128+
link.remove();
129+
}
130+
131+
useEffect(() => {
132+
resizeOutput();
133+
if (isServerSide) return;
134+
document.addEventListener('mousedown', handleClickOutside);
135+
136+
return () => {
137+
document.removeEventListener('mousedown', handleClickOutside);
138+
};
139+
}, [memory, filename, selectedFlag, guiEnabled, autoRestart]);
140+
141+
return (
142+
<div className='server-config-container'>
143+
<h2>Server Configuration</h2>
144+
<div className='config-section'>
145+
<label id='memory-slider-label' className='sr-only'>Memory Usage: {memory}GB</label>
146+
<input
147+
id='memory-slider'
148+
type='range'
149+
min={0.5}
150+
max={24}
151+
step={0.5}
152+
value={memory}
153+
onChange={(e) => setMemory(parseFloat(e.target.value))}
154+
aria-labelledby='memory-slider-label'
155+
/>
156+
<div className='slider-markers'>
157+
{markerPoints.map((point) => (
158+
<div
159+
key={point}
160+
className='slider-marker'
161+
style={{ left: `${((point - 0.5) / 23.5) * 100}%` }}
162+
>
163+
{point}GB
164+
</div>
165+
))}
166+
</div>
167+
</div>
168+
<div className={'middle-flex-wrapper'}>
169+
<div className='config-section'>
170+
<label htmlFor='filename-input'>Filename:</label>
171+
<input
172+
id='filename-input'
173+
type='text'
174+
value={filename}
175+
placeholder={'server.jar'}
176+
onChange={(e) => setFilename(e.target.value)}
177+
className={'filename-input'}
178+
/>
179+
</div>
180+
<div className='config-section'>
181+
<label htmlFor='flags-dropdown'>Flags:</label>
182+
<div className='custom-dropdown' ref={dropdownRef}>
183+
<div
184+
className='selected-flag'
185+
onClick={() => setDropdownVisible(!dropdownVisible)}
186+
>
187+
{selectedFlag.label}
188+
</div>
189+
{dropdownVisible && (
190+
<div className='dropdown-content'>
191+
{Object.values(FLAGS).map((flag) => (
192+
<div
193+
key={flag.label}
194+
className={`dropdown-item ${flag === selectedFlag ? 'selected' : ''}`}
195+
onClick={() => {
196+
setSelectedFlag(flag);
197+
setDropdownVisible(false);
198+
}}
199+
>
200+
<div className='flag-label'>{flag.label}</div>
201+
<div className='flag-description'>{flag.description}</div>
202+
</div>
203+
))}
204+
</div>
205+
)}
206+
</div>
207+
</div>
208+
</div>
209+
<div className='config-section'>
210+
<div className={'gui-container'}>
211+
<label style={{marginRight: '10px'}}>GUI:</label>
212+
<input type='checkbox' id='gui-toggle' className='checkbox' onChange={() => setGuiEnabled(!guiEnabled)}/>
213+
<label htmlFor='gui-toggle' className='switch'></label>
214+
</div>
215+
<div className={'gui-container'}>
216+
<label style={{marginRight: '10px'}}>Auto-Restart:</label>
217+
<input type='checkbox' id='restart-toggle' className='checkbox' onChange={() => setAutoRestart(!autoRestart)}/>
218+
<label htmlFor='restart-toggle' className='switch'></label>
219+
</div>
220+
221+
<div className={'platform-selector'}>
222+
<label>Platform:</label>
223+
<select id={'platform-select'} onChange={event => setPlatform(event.target.value)}>
224+
<option value='linux'>Linux/Mac</option>
225+
<option value='windows'>Windows</option>
226+
</select>
227+
{platform === 'windows' &&
228+
<p className={'windows-warning'}>For optimal performance, we recommend running Linux</p>
229+
}
230+
</div>
231+
</div>
232+
<div className='config-section'>
233+
<label>Generated Command:</label>
234+
<textarea className={'output-command'}
235+
value={generateStartCommand(memory, selectedFlag, filename, guiEnabled, autoRestart, platform)}
236+
id={'output-command-text'} readOnly/>
237+
<div className={'copy-button'}>
238+
<button id={'clipboard-copy-button'} onClick={handleCopyToClipboard}>
239+
Copy to Clipboard
240+
</button>
241+
<button id={'contents-download-button'} onClick={handleDownload}>
242+
Download
243+
</button>
244+
</div>
245+
</div>
246+
</div>
247+
);
248+
};
249+
250+
export default StartScriptGenerator;

0 commit comments

Comments
 (0)