diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/FoundationCorePlugin.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/FoundationCorePlugin.kt index c314360..3e42990 100644 --- a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/FoundationCorePlugin.kt +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/FoundationCorePlugin.kt @@ -1,6 +1,7 @@ package cloud.kubelet.foundation.core import cloud.kubelet.foundation.core.command.* +import cloud.kubelet.foundation.core.devupdate.DevUpdateServer import cloud.kubelet.foundation.core.persist.PersistentStore import cloud.kubelet.foundation.core.persist.setAllProperties import io.papermc.paper.event.player.AsyncChatEvent @@ -19,6 +20,8 @@ import java.util.concurrent.ConcurrentHashMap class FoundationCorePlugin : JavaPlugin(), Listener { internal val persistentStores = ConcurrentHashMap() private lateinit var _pluginDataPath: Path + private lateinit var chatLogStore: PersistentStore + private lateinit var devUpdateServer: DevUpdateServer var pluginDataPath: Path /** @@ -40,8 +43,6 @@ class FoundationCorePlugin : JavaPlugin(), Listener { */ fun getPersistentStore(name: String) = persistentStores.getOrPut(name) { PersistentStore(this, name) } - private lateinit var chatLogStore: PersistentStore - override fun onEnable() { pluginDataPath = dataFolder.toPath() val backupPath = pluginDataPath.resolve(BACKUPS_DIRECTORY) @@ -67,11 +68,14 @@ class FoundationCorePlugin : JavaPlugin(), Listener { log.info("Features:") Util.printFeatureStatus(log, "Backup", BACKUP_ENABLED) chatLogStore = getPersistentStore("chat-logs") + devUpdateServer = DevUpdateServer(this) + devUpdateServer.enable() } override fun onDisable() { persistentStores.values.forEach { store -> store.close() } persistentStores.clear() + devUpdateServer.disable() } private fun registerCommandExecutor(name: String, executor: CommandExecutor) { diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/devupdate/DevUpdateConfig.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/devupdate/DevUpdateConfig.kt new file mode 100644 index 0000000..a64d3ac --- /dev/null +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/devupdate/DevUpdateConfig.kt @@ -0,0 +1,10 @@ +package cloud.kubelet.foundation.core.devupdate + +import kotlinx.serialization.Serializable + +@Serializable +class DevUpdateConfig( + val port: Int = 8484, + val token: String, + val ipAllowList: List = listOf("*") +) diff --git a/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/devupdate/DevUpdateServer.kt b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/devupdate/DevUpdateServer.kt new file mode 100644 index 0000000..42327e4 --- /dev/null +++ b/foundation-core/src/main/kotlin/cloud/kubelet/foundation/core/devupdate/DevUpdateServer.kt @@ -0,0 +1,89 @@ +package cloud.kubelet.foundation.core.devupdate + +import cloud.kubelet.foundation.core.FoundationCorePlugin +import cloud.kubelet.foundation.core.Util +import com.charleskorn.kaml.Yaml +import com.sun.net.httpserver.HttpExchange +import com.sun.net.httpserver.HttpServer +import java.net.InetSocketAddress +import kotlin.io.path.inputStream + +class DevUpdateServer(val plugin: FoundationCorePlugin) { + private lateinit var config: DevUpdateConfig + private var server: HttpServer? = null + + fun enable() { + val configPath = Util.copyDefaultConfig( + plugin.slF4JLogger, + plugin.pluginDataPath, + "devupdate.yaml" + ) + + config = Yaml.default.decodeFromStream(DevUpdateConfig.serializer(), configPath.inputStream()) + start() + } + + private fun start() { + if (config.token.isEmpty()) { + return + } + + if (config.token.length < 8) { + plugin.slF4JLogger.warn("DevUpdate Token was too short (must be 8 or more characters)") + return + } + + val server = HttpServer.create() + server.createContext("/").setHandler { exchange -> + val ip = exchange.remoteAddress.address.hostAddress + if (!config.ipAllowList.contains("*") && !config.ipAllowList.contains(ip)) { + plugin.slF4JLogger.warn("DevUpdate Server received request from IP $ip which is not allowed.") + exchange.close() + return@setHandler + } + + plugin.slF4JLogger.info("DevUpdate Server Request $ip ${exchange.requestMethod} ${exchange.requestURI.path}") + if (exchange.requestMethod != "POST") { + exchange.respond(405, "Method not allowed.") + return@setHandler + } + + if (exchange.requestURI.path != "/webhook/update") { + exchange.respond(404, "Not Found.") + return@setHandler + } + + if (exchange.requestURI.query != config.token) { + exchange.respond(401, "Unauthorized.") + return@setHandler + } + + exchange.respond(200, "Success.") + plugin.server.scheduler.runTask(plugin) { -> + plugin.slF4JLogger.info("DevUpdate Server Restart") + try { + plugin.server.dispatchCommand(plugin.server.consoleSender, "fupdate") + plugin.server.dispatchCommand(plugin.server.consoleSender, "stop") + } catch (e: Exception) { + plugin.slF4JLogger.error("DevUpdate Server failed to update server.", e) + } + } + } + server.bind(InetSocketAddress("0.0.0.0", config.port), 0) + server.start() + this.server = server + plugin.slF4JLogger.info("DevUpdate Server listening on port ${config.port}") + } + + fun disable() { + server?.stop(0) + } + + private fun HttpExchange.respond(code: Int, content: String) { + val encoded = content.encodeToByteArray() + sendResponseHeaders(code, encoded.size.toLong()) + responseBody.write(encoded) + responseBody.close() + close() + } +} diff --git a/foundation-core/src/main/resources/devupdate.yaml b/foundation-core/src/main/resources/devupdate.yaml new file mode 100644 index 0000000..cd46761 --- /dev/null +++ b/foundation-core/src/main/resources/devupdate.yaml @@ -0,0 +1,12 @@ +# Server port to listen on. +port: 8484 + +# An authentication token. Should be random and 8 or more characters. +# If empty, the DevUpdate server is not enabled. +token: "" + +# IP address allow list. +# If * is specified, all addresses are allowed. +# Specify IP addresses as a string that should be allowed to update the server. +ipAllowList: + - "*"