Files
CLI/src/main/kotlin/commands/dev/DevCommand.kt
Chaoscaot 408184bade
All checks were successful
SteamWarCI Build successful
Add Dev Command
2025-10-25 23:32:54 +02:00

197 lines
7.3 KiB
Kotlin

package de.steamwar.commands.dev
import com.github.ajalt.clikt.command.SuspendingCliktCommand
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.CliktError
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.help
import com.github.ajalt.clikt.parameters.arguments.multiple
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.help
import com.github.ajalt.clikt.parameters.options.multiple
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.file
import com.github.ajalt.clikt.parameters.types.long
import com.sun.security.auth.module.UnixSystem
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.File
const val LOG4J_CONFIG = """<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" packages="com.mojang.util">
<Appenders>
<Console name="WINDOWS_COMPAT" target="SYSTEM_OUT"></Console>
<Queue name="TerminalConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
</Queue>
<RollingRandomAccessFile name="File" fileName="$\{'sys:logPath'}/latest.log" filePattern="$\{'sys:logPath'}/%d{yyyy.MM.dd}.log.gz">
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
<DefaultRolloverStrategy max="7"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="info">
<filters>
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL" />
</filters>
<AppenderRef ref="WINDOWS_COMPAT" level="info"/>
<AppenderRef ref="File"/>
<AppenderRef ref="TerminalConsole" level="info"/>
</Root>
</Loggers>
</Configuration>"""
class DevCommand : CliktCommand("dev") {
override fun help(context: Context): String = "Start a dev Server"
override val treatUnknownOptionsAsArgs = true
val server by argument().help("Server Template")
val port by option("--port").long().default(
if (System.getProperty("os.name").lowercase()
.let { os -> listOf("mac", "nix", "sunos").any { it in os } }
) UnixSystem().uid else 2050
).help("Port for Server")
val world by option("--world", "-w").help("User World")
val plugins by option("--plugins", "-p").help("Plugin Dir")
val profile by option().flag().help("Add Profiling Arguments")
val forceUpgrade by option().flag().help("Force Upgrade")
val jar by option().file().help("Jar File")
val jvmArgs by argument().multiple()
override val printHelpOnEmptyArgs = true
val workingDir = File(".").absoluteFile
val log4jConfig = File(workingDir, "log4j2.xml")
override fun run() {
val args = mutableListOf<String>()
val serverDirectory = File(workingDir, server)
val serverDir =
if (serverDirectory.exists() && serverDirectory.isDirectory) serverDirectory else File(workingDir, server)
if (isVelocity(server)) {
runServer(args, jvmArgs, listOf(jar?.absolutePath ?: File("/jar/Velocity.jar").absolutePath), serverDir)
} else {
setLogConfig(args)
val version = findVersion(server) ?: throw CliktError("Unknown Server Version")
val worldFile = world?.let { File(workingDir, it) } ?: File(serverDir, "devtempworld")
val jarFile = jar?.absolutePath ?: JARS[server]?.let { VERSIONS[it] } ?: VERSIONS[version]
?: throw CliktError("Unknown Server Version")
if (!worldFile.exists()) {
val templateFile = File(serverDir, "Bauwelt")
if (!templateFile.exists()) {
throw CliktError("World Template not found!")
}
templateFile.copyRecursively(worldFile)
}
val devFile = File("/configs/DevServer/${System.getProperty("user.name")}.$port.$version")
if (System.getProperty("user.name") != "minecraft") {
devFile.createNewFile()
}
runServer(
args, jvmArgs, listOf(
jarFile,
if (forceUpgrade) "-forceUpgrade" else "",
"--port", port.toString(),
"--level-name", worldFile.name,
"--world-dir", workingDir.absolutePath,
"--no-gui",
*(if (plugins != null) arrayOf("--plugins", plugins!!) else arrayOf())
), serverDir
)
try {
devFile.delete()
} catch (_: Exception) {
}
}
}
val OPENJ9_ARGS = arrayOf(
"-Xmx1G",
"-Xgc:excessiveGCratio=80",
"-Xsyslog:none",
"-Xtrace:none",
"-Xnoclassgc",
"-Xdisableexplicitgc",
"-XX:+AlwaysPreTouch",
"-XX:+CompactStrings",
"-XX:-HeapDumpOnOutOfMemory",
"-XX:+ExitOnOutOfMemoryError"
)
val JAVA17_ARGS = arrayOf("--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED")
val VERSIONS = mapOf(
8 to "/jars/paper-1.8.8.jar",
9 to "/jars/spigot-1.9.4.jar",
10 to "/jars/paper-1.10.2.jar",
12 to "/jars/spigot-1.12.2.jar",
14 to "/jars/spigot-1.14.4.jar",
15 to "/jars/spigot-1.15.2.jar",
18 to "/jars/paper-1.18.2.jar",
19 to "/jars/paper-1.19.3.jar",
20 to "/jars/paper-1.20.1.jar",
21 to "/jars/paper-1.21.6.jar"
)
val JARS = mapOf(
"Tutorial" to 15,
"Lobby" to 20
)
fun findVersion(server: String): Int? = server.dropWhile { !it.isDigit() }.toIntOrNull()
fun isJava8(server: String): Boolean = findVersion(server)?.let { it <= 10 } ?: false
fun isVelocity(server: String): Boolean = server.endsWith("Velocity")
fun setLogConfig(args: MutableList<String>) {
args += "-DlogPath=${workingDir.absolutePath}/logs"
args += "-Dlog4j.configurationFile=${log4jConfig.absolutePath}"
if (!log4jConfig.exists()) {
log4jConfig.writeText(LOG4J_CONFIG)
}
}
fun runServer(args: List<String>, jvmArgs: List<String>, cmd: List<String>, serverDir: File) = runBlocking {
val command = arrayOf(
if (isJava8(server)) "/usr/lib/jvm/openj9-8/bin/java" else "java",
*jvmArgs.toTypedArray(),
*args.toTypedArray(),
*OPENJ9_ARGS,
*(if (isJava8(server)) arrayOf() else JAVA17_ARGS),
if (profile) "-javaagent:/jars/LixfelsProfiler.jar=start" else "",
"-Xshareclasses:nonfatal,name=$server",
"-jar",
*cmd.toTypedArray()
)
echo(command.joinToString(" "))
val process = ProcessBuilder(
*command
).directory(serverDir).start()
launch {
process.inputStream.transferTo(System.out)
}
launch {
process.errorStream.transferTo(System.err)
}
launch {
System.`in`.transferTo(process.outputStream)
}
process.waitFor()
}
}