package de.steamwar.commands.dev
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.defaultLazy
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.help
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.github.ajalt.clikt.parameters.types.path
import com.sun.security.auth.module.UnixSystem
import java.io.File
import kotlin.io.path.absolute
import kotlin.io.path.absolutePathString
const val LOG4J_CONFIG = """
"""
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().defaultLazy { UnixSystem().uid + 1010 }.help("Port for Server")
val world by option("--world", "-w").path(canBeFile = false).help("User World")
val plugins by option("--plugins", "-p").path(true, canBeFile = false).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(true, canBeDir = false).help("Jar File")
val jvm by option().file(true, canBeDir = false).help("Java Executable")
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()
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?.absolute()?.toFile() ?: File(serverDir, "devtempworld")
val jarFile = jar?.absolutePath ?: additionalVersions[server]?.let { supportedVersionJars[it] } ?: supportedVersionJars[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) arrayOf("-forceUpgrade") else arrayOf()),
"--port", port.toString(),
"--level-name", worldFile.name,
"--world-dir", workingDir.absolutePath,
"--nogui",
*(if (plugins != null) arrayOf("--plugins", plugins!!.absolutePathString()) else arrayOf())
), serverDir
)
try {
devFile.delete()
} catch (_: Exception) { /* ignored */ }
}
}
val jvmDefaultParams = arrayOf(
"-Xmx1G",
"-Xgc:excessiveGCratio=80",
"-Xsyslog:none",
"-Xtrace:none",
"-Xnoclassgc",
"-Xdisableexplicitgc",
"-XX:+AlwaysPreTouch",
"-XX:+CompactStrings",
"-XX:-HeapDumpOnOutOfMemory",
"-XX:+ExitOnOutOfMemoryError"
)
val jvmArgOverrides = arrayOf("--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED")
val supportedVersionJars = 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 additionalVersions = 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) {
args += "-DlogPath=${workingDir.absolutePath}/logs"
args += "-Dlog4j.configurationFile=${log4jConfig.absolutePath}"
if (!log4jConfig.exists()) {
log4jConfig.writeText(LOG4J_CONFIG)
}
}
fun runServer(args: List, jvmArgs: List, cmd: List, serverDir: File) {
val process = ProcessBuilder(
jvm?.absolutePath ?: if (isJava8(server)) "/usr/lib/jvm/openj9-8/bin/java" else "java",
*jvmArgs.toTypedArray(),
*args.toTypedArray(),
*jvmDefaultParams,
*(if (isJava8(server)) arrayOf() else jvmArgOverrides),
*(if (profile) arrayOf("-javaagent:/jars/LixfelsProfiler.jar=start") else arrayOf()),
"-Xshareclasses:nonfatal,name=$server",
"-jar",
*cmd.toTypedArray()
).directory(serverDir).inheritIO().start()
Runtime.getRuntime().addShutdownHook(Thread { if (process.isAlive) process.destroyForcibly() })
process.waitFor()
}
}