Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a6f0150
feat: implement core launcher with Redis integration and server state…
TheBjoRedCraft May 23, 2026
1e8e3ef
refactor: remove Redis integration and simplify server state management
TheBjoRedCraft May 24, 2026
5b62e34
refactor: remove Redis integration and simplify server state management
TheBjoRedCraft May 24, 2026
d2e1235
refactor: replace logger with console output and improve server state…
TheBjoRedCraft May 24, 2026
db5bba6
refactor: enhance server launch process and state management
TheBjoRedCraft May 24, 2026
16df0b5
feat: implement plugin updater with GitHub integration and auto-updat…
TheBjoRedCraft May 24, 2026
fb103bf
refactor: clean up CoreLauncher and GitHubClient for improved readabi…
TheBjoRedCraft May 24, 2026
818fdba
feat: enhance plugin updater with improved asset matching and special…
TheBjoRedCraft May 25, 2026
bdb5c51
feat: optimize plugin update checks using coroutines for improved per…
TheBjoRedCraft May 25, 2026
303298e
feat: update log prefix in CoreLauncher to include timestamp for bett…
TheBjoRedCraft May 25, 2026
6c10e6f
feat: update log prefix in CoreLauncher to use braces for better form…
TheBjoRedCraft May 25, 2026
11af783
feat: update log prefix in CoreLauncher to use a new date-time format…
TheBjoRedCraft May 25, 2026
637bc4d
feat: update log prefix in CoreLauncher to use square brackets for be…
TheBjoRedCraft May 25, 2026
77c6026
feat: refactor CoreLauncher to use lazy config initialization and imp…
TheBjoRedCraft May 25, 2026
3196f48
feat: add support for core launcher integration and update event hand…
TheBjoRedCraft May 26, 2026
67a3972
feat: add surfCoreApiCommon dependency to build configuration
TheBjoRedCraft May 26, 2026
5dff79a
feat: enhance VelocityRedisListener with service status handling and …
TheBjoRedCraft May 26, 2026
3682ce4
feat: add toggle command for service status message notifications
TheBjoRedCraft May 26, 2026
f698864
feat: integrate Redis for service status monitoring and update handling
TheBjoRedCraft May 26, 2026
f6f0dea
Merge remote-tracking branch 'origin/version/26.1' into feat/launcher
TheBjoRedCraft May 27, 2026
cb6dee9
Merge remote-tracking branch 'origin/version/26.1' into feat/launcher
TheBjoRedCraft May 27, 2026
372e0d5
feat: add publishing configuration for slneReleases repository
TheBjoRedCraft May 27, 2026
0bf67d8
feat: enhance server online status handling and support for velocity-…
TheBjoRedCraft May 28, 2026
889a474
Merge branch 'version/26.1' into feat/launcher
TheBjoRedCraft Jun 20, 2026
820b4cb
Potential fix for pull request finding
TheBjoRedCraft Jun 20, 2026
fe3bd78
Potential fix for pull request finding
TheBjoRedCraft Jun 21, 2026
9f1d839
Potential fix for pull request finding
TheBjoRedCraft Jun 21, 2026
5faed67
Potential fix for pull request finding
TheBjoRedCraft Jun 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
kotlin.code.style=official
kotlin.stdlib.default.dependency=false
org.gradle.parallel=true
version=2.3.2
version=2.4.0
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
7 changes: 5 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#Sun Apr 05 17:57:20 CEST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 10 additions & 21 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ include("surf-core-velocity")

// Microservice
include("surf-core-microservice")

include("surf-core-launcher")
include("surf-core-launcher:surf-core-launcher-api")
include("surf-core-launcher:surf-core-launcher-server")
4 changes: 4 additions & 0 deletions surf-core-api/surf-core-api-common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ plugins {
id("dev.slne.surf.api.gradle.core")
}

surfCoreApi {
withSurfRedis()
}

publishing {
repositories {
slneReleases()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.slne.surf.core.core.common.redis.event
package dev.slne.surf.core.api.common.event.redis

import dev.slne.surf.core.api.common.event.SurfEvent
import dev.slne.surf.redis.event.RedisEvent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dev.slne.surf.core.api.common.server.state

enum class SurfServiceStatus {
UNREACHABLE,
CRASHED
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package dev.slne.surf.core.core.common.event

import dev.slne.surf.core.core.common.redis.event.SurfEventFireRedisEvent
import dev.slne.surf.core.api.common.event.redis.SurfEventFireRedisEvent
import dev.slne.surf.redis.event.OnRedisEvent

object LocalSurfEventBusListener {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package dev.slne.surf.core.core.common.event

import dev.slne.surf.core.api.common.event.SurfEvent
import dev.slne.surf.core.api.common.event.SurfEventHandler
import dev.slne.surf.core.api.common.event.redis.SurfEventFireRedisEvent
import dev.slne.surf.core.core.CoreInstance
import dev.slne.surf.core.core.common.redis.event.SurfEventFireRedisEvent
import kotlin.reflect.KClass
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.findAnnotation
Expand Down
19 changes: 19 additions & 0 deletions surf-core-launcher/surf-core-launcher-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import dev.slne.surf.api.gradle.util.slneReleases

plugins {
id("dev.slne.surf.api.gradle.core")
}

surfCoreApi {
withSurfRedis()
}

dependencies {
api(projects.surfCoreApi.surfCoreApiCommon)
}

publishing {
repositories {
slneReleases()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package dev.slne.surf.core.launcher.api

object LauncherConstants {
const val PROPERTY_LAUNCHED_BY_CORE = "LAUNCHED_BY_CORE"
Comment thread
TheBjoRedCraft marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.slne.surf.core.launcher.api.redis

import dev.slne.surf.core.api.common.server.state.SurfServiceStatus
import dev.slne.surf.redis.event.RedisEvent
import kotlinx.serialization.Serializable

@Serializable
data class ServiceStatusRedisEvent(
val serviceName: String,
val status: SurfServiceStatus
) : RedisEvent()
21 changes: 21 additions & 0 deletions surf-core-launcher/surf-core-launcher-server/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
plugins {
id("dev.slne.surf.api.gradle.standalone")
}

dependencies {
api(projects.surfCoreLauncher.surfCoreLauncherApi)
implementation(projects.surfCoreApi.surfCoreApiCommon)
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.2")
implementation("dev.slne.surf.redis:surf-redis-standalone:1.6.1")
}

tasks.jar {
manifest {
attributes["Main-Class"] = "dev.slne.surf.core.launcher.server.CoreLauncherKt"
}
}

tasks.shadowJar {
exclude("okio/**")
exclude("io/netty/**")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package dev.slne.surf.core.launcher.server

import dev.slne.surf.api.standalone.SurfApiStandaloneBootstrap
import dev.slne.surf.core.api.common.event.SurfServerStartEvent
import dev.slne.surf.core.api.common.event.redis.SurfEventFireRedisEvent
import dev.slne.surf.core.launcher.api.LauncherConstants
import dev.slne.surf.core.launcher.server.config.CoreLauncherConfig
import dev.slne.surf.core.launcher.server.ping.MinecraftServerPinger
import dev.slne.surf.core.launcher.server.updater.process.PluginUpdater
import dev.slne.surf.redis.RedisApi
import dev.slne.surf.redis.StandaloneRedisInstance
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import java.nio.file.Path
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.io.path.Path
import kotlin.time.Duration.Companion.seconds

private val secondDateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss")

val LOG_PREFIX
get() =
"\u001B[0;91m[${
LocalDateTime.now().format(secondDateTimeFormatter)
} CoreLauncher]\u001B[0m"

object CoreLauncher {
private val shuttingDown = AtomicBoolean(false)
lateinit var serverProcess: Process

private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var monitorJob: Job? = null
private val redisInstance = StandaloneRedisInstance(
name = "surf-core-launcher",
configPath = findRedisPluginPath()
?: error("Could not find Redis plugin configuration path, cannot start Redis instance")
)
lateinit var redisApi: RedisApi

val serverOnline = MutableStateFlow(false)

val config by lazy {
CoreLauncherConfig.getConfig()
}

suspend fun launch() = withContext(Dispatchers.IO) {
SurfApiStandaloneBootstrap.bootstrap()
SurfApiStandaloneBootstrap.enable()

println("$LOG_PREFIX Initializing Redis instance...")

redisInstance.create()
redisApi = RedisApi.create()
redisApi.freezeAndConnect()

println("$LOG_PREFIX Redis instance initialized and connected")

if (config.autoUpdateSurfPlugins) {
println("$LOG_PREFIX Searching plugin updates...")

withTimeoutOrNull(20.seconds) {
if (config.personalAccessToken.isBlank()) {
println("$LOG_PREFIX No GitHub personal access token provided, skipping plugin update check")
return@withTimeoutOrNull
}

PluginUpdater.start()
}
?: println("$LOG_PREFIX Plugin update check timed out after 20 seconds, continuing with server startup")
}

println("$LOG_PREFIX Starting Minecraft Server...")

val command = buildStartupCommand()

serverProcess = ProcessBuilder(command)
.redirectInput(ProcessBuilder.Redirect.INHERIT)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start()

println("$LOG_PREFIX Server process started")

redisApi.publishEvent(
SurfEventFireRedisEvent(
SurfServerStartEvent(
serverName = config.serverName
)
)
)

monitorJob = scope.launch {
launch {
serverProcess.inputStream.bufferedReader().forEachLine { line ->
println(line)

if (line.contains(
config.startedMessage,
ignoreCase = true
)
) {
serverOnline.value = true
println("$LOG_PREFIX Server is now online.")
}
}
}

MinecraftServerPinger.monitor(serverProcess)
}
}

suspend fun shutdown() {
if (!shuttingDown.compareAndSet(false, true)) {
return
}

serverOnline.value = true

println("$LOG_PREFIX Shutting down launcher/server...")
println("$LOG_PREFIX Disconnecting Redis instance...")

redisApi.disconnect()
redisInstance.shutdown()
println("$LOG_PREFIX Redis instance disconnected and shutdown")

monitorJob?.cancelAndJoin()

if (::serverProcess.isInitialized && serverProcess.isAlive) {
serverProcess.destroy()

if (!serverProcess.waitFor(45, TimeUnit.SECONDS)) {
println("$LOG_PREFIX Server did not stop within 45 seconds")
}
}

scope.cancel()
}

fun isShuttingDown(): Boolean = shuttingDown.get()

private fun buildStartupCommand(): List<String> {
val base = config.serverStartupCommand

val parts = Regex("""[^\s"]+|"([^"]*)"""")
.findAll(base)
.map { it.value.replace("\"", "") }
.toMutableList()

val flag = "-D${LauncherConstants.PROPERTY_LAUNCHED_BY_CORE}"

if (parts.none { it == flag }) {
parts.add(1, flag)
}
Comment thread
TheBjoRedCraft marked this conversation as resolved.

return parts
}

private fun findRedisPluginPath(): Path? {
val possiblePaths = listOf(
Path("plugins", "surf-redis-paper"),
Path("plugins", "surf-redis-velocity")
)

return possiblePaths.firstOrNull { path ->
path.toFile().exists()
}
}
}

suspend fun main(args: Array<String>) {
Runtime.getRuntime().addShutdownHook(Thread {
runBlocking {
CoreLauncher.shutdown()
SurfApiStandaloneBootstrap.shutdown()
}
})

CoreLauncher.launch()
CoreLauncher.serverProcess.waitFor()

CoreLauncher.shutdown()
SurfApiStandaloneBootstrap.shutdown()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.slne.surf.core.launcher.server

object CoreLauncherEnvironment {
val MC_MEMORY_MAX =
System.getenv("SERVER_MEMORY") ?: error("SERVER_MEMORY environment variable is not set")
val SERVER_PORT = System.getenv("SERVER_PORT")?.toInt()
?: error("SERVER_PORT environment variable is not set or is not a valid integer")
}
Loading