-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsecondfry-start.sh
More file actions
297 lines (259 loc) · 11.1 KB
/
Copy pathsecondfry-start.sh
File metadata and controls
297 lines (259 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
#!/usr/bin/env bash
# Minecraft server startup script with Aikar's optimized flags
# Automatically detects available RAM and configures JVM accordingly
# Reference: https://docs.papermc.io/paper/aikars-flags
set -e
echo "=========================================="
echo "Using secondfry-start.sh startup script"
echo "This is NOT part of the original server distribution"
echo "=========================================="
echo ""
# Check if server has its own start.sh and warn user
if [ -f /server/start.sh ] && [ "$(realpath /server/start.sh)" != "$(realpath "$0")" ]; then
echo "⚠️ WARNING: Your server directory contains its own start.sh file"
echo "⚠️ We are using secondfry-start.sh instead of your server's start.sh"
echo "⚠️ If you want to use the original start.sh, please modify docker-compose.yml"
echo ""
fi
# Accept EULA on first run if not already accepted
if [ ! -f eula.txt ]; then
echo "eula=true" > eula.txt
fi
# Function to handle graceful shutdown
shutdown() {
echo "Received shutdown signal, stopping server gracefully..."
exit 0
}
# Trap SIGTERM and SIGINT for graceful shutdown
trap shutdown SIGTERM SIGINT
# ===== Automatic RAM Detection =====
# Aikar recommends 6-10GB for most servers, rarely beneficial above 12GB
# Reference: https://docs.papermc.io/paper/aikars-flags
MAX_HEAP_GB=${MAX_MEMORY_GB:-12}
# Allow manual override via environment variable
if [ -n "$MEMORY_GB" ]; then
HEAP_GB=$MEMORY_GB
echo "Using manually configured memory: ${HEAP_GB}GB"
HEAP_SIZE="${HEAP_GB}G"
else
# Detect total available memory
TOTAL_MEM_KB=$(grep MemTotal /proc/meminfo | awk '{print $2}')
TOTAL_MEM_MB=$((TOTAL_MEM_KB / 1024))
TOTAL_MEM_GB=$((TOTAL_MEM_MB / 1024))
echo "Detected ${TOTAL_MEM_GB}GB total system memory (${TOTAL_MEM_MB}MB)"
# Calculate heap size (leave overhead for OS to prevent OOM errors)
if [ $TOTAL_MEM_GB -lt 6 ]; then
echo "WARNING: Only ${TOTAL_MEM_GB}GB RAM detected. Aikar recommends minimum 6GB."
echo "Allocating ${TOTAL_MEM_GB}GB to heap (risky - may cause OOM)"
HEAP_GB=$TOTAL_MEM_GB
elif [ $TOTAL_MEM_GB -lt 8 ]; then
# For 6-7GB, leave 1GB overhead
HEAP_GB=$((TOTAL_MEM_GB - 1))
else
# For 8GB+, leave 2GB overhead
HEAP_GB=$((TOTAL_MEM_GB - 2))
fi
# Apply upper limit to prevent GC performance issues
# Aikar: "more memory does not mean better performance above a certain point"
if [ $HEAP_GB -gt $MAX_HEAP_GB ]; then
echo "Capping heap at ${MAX_HEAP_GB}GB (Aikar recommends 6-10GB, rarely beneficial above 12GB)"
echo "Override with MAX_MEMORY_GB environment variable if needed for heavy modpacks"
HEAP_GB=$MAX_HEAP_GB
fi
HEAP_SIZE="${HEAP_GB}G"
echo "Allocating ${HEAP_SIZE} to Java heap (leaving overhead for OS)"
fi
# ===== GC Mode Selection =====
#
# GC_MODE=aikar (default) Aikar's G1GC flags. Safe for vanilla/Paper.
# GC_MODE=shenandoah Generational Shenandoah. Recommended for Folia on
# hosts with ≥16 logical cores AND ≥16GB heap; on
# smaller hosts it is marginal vs G1GC (barrier
# overhead can exceed the pause-time savings).
#
# ConcGCThreads auto-scales at ~1 per 8 logical cores (Folia's region
# scheduler reserves ~80% of cores for tick threads; GC counts against
# that budget). ParallelGCThreads is left at the JVM default — it only
# runs during STW and does not compete with ticking.
GC_MODE=${GC_MODE:-aikar}
# Detect logical CPU count for Shenandoah tuning
CPU_COUNT=$(nproc 2>/dev/null || echo 1)
# ===== G1GC Configuration Based on Heap Size =====
# Only relevant when GC_MODE=aikar; Shenandoah ignores these.
if [ "$GC_MODE" = "aikar" ]; then
# For >12GB, use adjusted G1 settings per Aikar's recommendations
if [ ${HEAP_GB:-0} -gt 12 ]; then
echo "Using G1GC settings optimized for >12GB heap"
G1_NEW_SIZE=40
G1_MAX_NEW_SIZE=50
G1_HEAP_REGION_SIZE=16M
G1_RESERVE_PERCENT=15
G1_INIT_HEAP_OCCUPANCY=20
else
echo "Using standard G1GC settings for ≤12GB heap"
G1_NEW_SIZE=30
G1_MAX_NEW_SIZE=40
G1_HEAP_REGION_SIZE=8M
G1_RESERVE_PERCENT=20
G1_INIT_HEAP_OCCUPANCY=15
fi
fi
# ===== Find Launch Target =====
#
# Two supported layouts:
#
# 1. Single-JAR (vanilla, Paper, Fabric, Folia): launched via `-jar foo.jar`.
#
# 2. NeoForge / Forge modpack installers (FTB, ATLauncher, CurseForge
# server packs for 1.17+): no single launchable JAR. The installer
# drops a `libraries/.../unix_args.txt` that carries the module path,
# --add-opens, -DlegacyClassPath, and the BootstrapLauncher main
# class. We launch with `java <our-flags> @<unix_args.txt> nogui`.
#
# Manual override: JAVA_ARGS_FILE=/server/path/to/args.txt forces mode 2
# regardless of detection.
LAUNCH_ARGS_FILE=""
LAUNCH_MODE=""
if [ -n "$JAVA_ARGS_FILE" ]; then
if [ ! -f "$JAVA_ARGS_FILE" ]; then
echo "ERROR: JAVA_ARGS_FILE=${JAVA_ARGS_FILE} does not exist."
exit 1
fi
LAUNCH_ARGS_FILE="$JAVA_ARGS_FILE"
LAUNCH_MODE="manual override (JAVA_ARGS_FILE)"
else
# NeoForge (1.20.2+): libraries/net/neoforged/neoforge/<ver>/unix_args.txt
for candidate in libraries/net/neoforged/neoforge/*/unix_args.txt; do
[ -f "$candidate" ] || continue
LAUNCH_ARGS_FILE="$candidate"
LAUNCH_MODE="NeoForge"
break
done
# Legacy Forge (1.17 - 1.20.1): libraries/net/minecraftforge/forge/<ver>/unix_args.txt
if [ -z "$LAUNCH_ARGS_FILE" ]; then
for candidate in libraries/net/minecraftforge/forge/*/unix_args.txt; do
[ -f "$candidate" ] || continue
LAUNCH_ARGS_FILE="$candidate"
LAUNCH_MODE="Forge"
break
done
fi
fi
if [ -n "$LAUNCH_ARGS_FILE" ]; then
echo "Detected ${LAUNCH_MODE} modpack layout."
echo "Using launch args file: ${LAUNCH_ARGS_FILE}"
# FTB / NeoForge installers ship a user_jvm_args.txt with a hardcoded
# -Xmx (commonly 4G or 8G). We deliberately ignore it — letting it win
# would defeat the auto-RAM detection that is the whole point of this
# repo. We pass our own -Xms/-Xmx and Aikar's flags ahead of @args.
if [ -f user_jvm_args.txt ]; then
echo "⚠️ Ignoring user_jvm_args.txt — auto-RAM + Aikar's flags take precedence."
echo " Set MEMORY_GB in .env to force a specific heap size."
fi
LAUNCH_ARGS=( "@${LAUNCH_ARGS_FILE}" )
else
# Auto-detect server JAR (prioritize fabric, then folia, then paper, then any jar)
if [ -f fabric-server-mc.*.jar ]; then
SERVER_JAR=$(ls -1 fabric-server-mc.*.jar | head -n1)
elif ls folia-*.jar >/dev/null 2>&1; then
SERVER_JAR=$(ls -1 folia-*.jar | head -n1)
elif [ -f paper-*.jar ]; then
SERVER_JAR=$(ls -1 paper-*.jar | head -n1)
elif [ -f server.jar ]; then
SERVER_JAR="server.jar"
else
SERVER_JAR=$(ls -1 *.jar 2>/dev/null | head -n1)
fi
if [ -z "$SERVER_JAR" ]; then
echo "ERROR: No server JAR file found!"
echo "Please place your Minecraft server JAR in this directory,"
echo "or use a NeoForge/Forge modpack installer that produces"
echo "libraries/.../unix_args.txt."
exit 1
fi
echo "Using server JAR: ${SERVER_JAR}"
LAUNCH_ARGS=( -jar "${SERVER_JAR}" )
fi
# ===== Start Server =====
mkdir -p logs
case "$GC_MODE" in
shenandoah)
# Generational Shenandoah for Folia. Pause-time GC that does not
# compete heavily with Folia's region tick threads.
#
# Viability threshold: ≥16 logical cores AND ≥16GB heap. Below
# that the read/write barriers cost more than the pause savings,
# so Shenandoah ends up marginal (or worse) vs G1GC. We surface
# this once at startup and let the operator decide.
if [ "$CPU_COUNT" -lt 16 ] || [ "${HEAP_GB:-0}" -lt 16 ]; then
echo "NOTE: Shenandoah on this host (${CPU_COUNT} cores, ${HEAP_GB}GB heap) is"
echo " marginal vs G1GC — barrier overhead may exceed pause savings."
echo " Only stay on shenandoah if /spark health shows p99 region MSPT"
echo " regressions on G1GC that you can't fix with chunk pregen."
fi
# ConcGCThreads ~ 1 per 8 logical cores, minimum 1.
# Counts against Folia's ~80% tick-thread budget.
CONC_GC_THREADS=$(( CPU_COUNT / 8 ))
[ "$CONC_GC_THREADS" -lt 1 ] && CONC_GC_THREADS=1
# Larger heaps get more GC log rotation headroom.
if [ "${HEAP_GB:-0}" -ge 16 ]; then
GC_LOG_FILECOUNT=10
else
GC_LOG_FILECOUNT=5
fi
echo "Starting Minecraft server with generational Shenandoah (GC_MODE=${GC_MODE})..."
echo "Heap: ${HEAP_SIZE} | CPUs: ${CPU_COUNT} | ConcGCThreads: ${CONC_GC_THREADS}"
echo "NOTE: Heuristic flags are intentionally omitted under ShenandoahGCMode=generational"
echo " (auto-tuned; manual values produce warnings)."
echo "NOTE: For Folia, leave paper-global.yml threaded-regions/chunk-system at"
echo " defaults unless /spark health justifies tuning. See AGENTS.md."
exec java \
-Xms${HEAP_SIZE} \
-Xmx${HEAP_SIZE} \
-XX:+UnlockExperimentalVMOptions \
-XX:+AlwaysPreTouch \
-XX:+DisableExplicitGC \
-XX:+ParallelRefProcEnabled \
-XX:+UseShenandoahGC \
-XX:ShenandoahGCMode=generational \
-XX:ConcGCThreads=${CONC_GC_THREADS} \
-Xlog:gc*:file=logs/gc.log:time,level,tags:filecount=${GC_LOG_FILECOUNT},filesize=4M \
"${LAUNCH_ARGS[@]}" \
nogui
;;
aikar|"")
echo "Starting Minecraft server with Aikar's optimized flags (GC_MODE=aikar)..."
echo "Heap: ${HEAP_SIZE} | G1NewSize: ${G1_NEW_SIZE}-${G1_MAX_NEW_SIZE}%"
exec java \
-Xms${HEAP_SIZE} \
-Xmx${HEAP_SIZE} \
-XX:+UseG1GC \
-XX:+ParallelRefProcEnabled \
-XX:MaxGCPauseMillis=200 \
-XX:+UnlockExperimentalVMOptions \
-XX:+DisableExplicitGC \
-XX:+AlwaysPreTouch \
-XX:G1NewSizePercent=${G1_NEW_SIZE} \
-XX:G1MaxNewSizePercent=${G1_MAX_NEW_SIZE} \
-XX:G1HeapRegionSize=${G1_HEAP_REGION_SIZE} \
-XX:G1ReservePercent=${G1_RESERVE_PERCENT} \
-XX:G1HeapWastePercent=5 \
-XX:G1MixedGCCountTarget=4 \
-XX:InitiatingHeapOccupancyPercent=${G1_INIT_HEAP_OCCUPANCY} \
-XX:G1MixedGCLiveThresholdPercent=90 \
-XX:G1RSetUpdatingPauseTimePercent=5 \
-XX:SurvivorRatio=32 \
-XX:+PerfDisableSharedMem \
-XX:MaxTenuringThreshold=1 \
-Dusing.aikars.flags=https://mcflags.emc.gs \
-Daikars.new.flags=true \
-Xlog:gc*:logs/gc.log:time,uptime:filecount=5,filesize=1M \
"${LAUNCH_ARGS[@]}" \
nogui
;;
*)
echo "ERROR: Unknown GC_MODE='${GC_MODE}'."
echo "Valid values: aikar (default), shenandoah"
exit 1
;;
esac