Skip to content

Commit

Permalink
feat: Added auto update and restart command
Browse files Browse the repository at this point in the history
  • Loading branch information
phinner committed Jul 16, 2024
1 parent 1f9fd7d commit 9aca462
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.MoreExecutors
import com.google.common.util.concurrent.ThreadFactoryBuilder
import com.xpdustry.imperium.common.account.AccountManager
import com.xpdustry.imperium.common.account.SimpleAccountManager
import com.xpdustry.imperium.common.application.ImperiumApplication
import com.xpdustry.imperium.common.bridge.PlayerTracker
import com.xpdustry.imperium.common.bridge.RequestingPlayerTracker
import com.xpdustry.imperium.common.config.DatabaseConfig
Expand Down Expand Up @@ -149,3 +150,7 @@ fun MutableInstanceManager.registerCommonModule() {

provider<Executor>("main") { MoreExecutors.directExecutor() }
}

fun MutableInstanceManager.registerApplication(application: ImperiumApplication) {
provider<ImperiumApplication> { application }
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ sealed interface MessengerConfig {
) : MessengerConfig
}

data class ServerConfig(val name: String, val displayName: String = name.capitalize()) {
data class ServerConfig(
val name: String,
val displayName: String = name.capitalize(),
val autoUpdate: Boolean = true
) {
val identity: Identity.Server
get() = Identity.Server(name)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
*/
package com.xpdustry.imperium.common.content

enum class MindustryGamemode {
enum class MindustryGamemode(val pvp: Boolean = false) {
SURVIVAL,
ATTACK,
PVP,
PVP(pvp = true),
SANDBOX,
ROUTER,
SURVIVAL_EXPERT,
HEXED,
HEXED(pvp = true),
TOWER_DEFENSE,
HUB,
TESTING,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Imperium, the software collection powering the Chaotic Neutral network.
* Copyright (C) 2024 Xpdustry
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.xpdustry.imperium.common.control

import com.xpdustry.imperium.common.message.Message
import kotlinx.serialization.Serializable

@Serializable data class RestartMessage(val target: String, val immediate: Boolean) : Message
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.xpdustry.imperium.common.annotation.AnnotationScanner
import com.xpdustry.imperium.common.application.BaseImperiumApplication
import com.xpdustry.imperium.common.application.ExitStatus
import com.xpdustry.imperium.common.inject.get
import com.xpdustry.imperium.common.registerApplication
import com.xpdustry.imperium.common.registerCommonModule
import com.xpdustry.imperium.discord.account.RoleSyncListener
import com.xpdustry.imperium.discord.bridge.MindustryBridgeListener
Expand Down Expand Up @@ -51,9 +52,12 @@ class ImperiumDiscord : BaseImperiumApplication(LOGGER) {
fun main() {
val application = ImperiumDiscord()

application.instances.registerCommonModule()
application.instances.registerDiscordModule()
application.instances.createAll()
with(application.instances) {
registerApplication(application)
registerCommonModule()
registerDiscordModule()
createAll()
}

sequenceOf(
MindustryBridgeListener::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
*/
package com.xpdustry.imperium.discord.commands

import com.xpdustry.imperium.common.account.Rank
import com.xpdustry.imperium.common.application.ExitStatus
import com.xpdustry.imperium.common.application.ImperiumApplication
import com.xpdustry.imperium.common.bridge.PlayerTracker
import com.xpdustry.imperium.common.command.ImperiumCommand
import com.xpdustry.imperium.common.control.RestartMessage
import com.xpdustry.imperium.common.inject.InstanceManager
import com.xpdustry.imperium.common.inject.get
import com.xpdustry.imperium.common.message.Messenger
import com.xpdustry.imperium.common.network.Discovery
import com.xpdustry.imperium.discord.command.InteractionSender
import com.xpdustry.imperium.discord.command.annotation.NonEphemeral
Expand All @@ -35,6 +39,8 @@ import net.dv8tion.jda.api.entities.MessageEmbed
class ServerCommand(instances: InstanceManager) : ImperiumApplication.Listener {
private val discovery = instances.get<Discovery>()
private val tracker = instances.get<PlayerTracker>()
private val application = instances.get<ImperiumApplication>()
private val messenger = instances.get<Messenger>()

@ImperiumCommand(["server", "list"])
@NonEphemeral
Expand Down Expand Up @@ -82,6 +88,27 @@ class ServerCommand(instances: InstanceManager) : ImperiumApplication.Listener {
}
}

@ImperiumCommand(["server", "restart"], Rank.ADMIN)
@NonEphemeral
suspend fun onServerRestart(
actor: InteractionSender.Slash,
server: String,
immediate: Boolean = false
) {
if (server == "discord") {
actor.respond("Restarting discord bot.")
application.exit(ExitStatus.RESTART)
return
} else {
if (discovery.servers[server] == null) {
actor.respond("Server not found.")
return
}
messenger.publish(RestartMessage(server, immediate))
actor.respond("Sent restart request to server **$server**.")
}
}

private fun createPlayerListEmbed(
list: List<PlayerTracker.Entry>,
name: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.xpdustry.imperium.common.config.ImperiumConfig
import com.xpdustry.imperium.common.config.MindustryConfig
import com.xpdustry.imperium.common.content.MindustryGamemode
import com.xpdustry.imperium.common.inject.get
import com.xpdustry.imperium.common.registerApplication
import com.xpdustry.imperium.common.registerCommonModule
import com.xpdustry.imperium.common.webhook.WebhookMessage
import com.xpdustry.imperium.common.webhook.WebhookMessageSender
Expand All @@ -50,6 +51,7 @@ import com.xpdustry.imperium.mindustry.command.CommandAnnotationScanner
import com.xpdustry.imperium.mindustry.command.HelpCommand
import com.xpdustry.imperium.mindustry.component.ImperiumComponentRendererProvider
import com.xpdustry.imperium.mindustry.config.ConventionListener
import com.xpdustry.imperium.mindustry.control.RestartListener
import com.xpdustry.imperium.mindustry.game.AlertListener
import com.xpdustry.imperium.mindustry.game.GameListener
import com.xpdustry.imperium.mindustry.game.ImperiumLogicListener
Expand Down Expand Up @@ -107,9 +109,12 @@ class ImperiumPlugin : AbstractMindustryPlugin() {
override fun onLoad() {
SaveVersion.addCustomChunk("imperium", ImperiumMetadataChunkReader)

application.instances.registerCommonModule()
application.instances.registerMindustryModule(this)
application.instances.createAll()
with(application.instances) {
registerApplication(application)
registerCommonModule()
registerMindustryModule(this@ImperiumPlugin)
createAll()
}

registerService(
RankProvider::class,
Expand Down Expand Up @@ -172,7 +177,8 @@ class ImperiumPlugin : AbstractMindustryPlugin() {
ModerationCommand::class,
AlertListener::class,
TeamCommand::class,
FormationListener::class)
FormationListener::class,
RestartListener::class)
.forEach(application::register)

val gamemode = application.instances.get<MindustryConfig>().gamemode
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Imperium, the software collection powering the Chaotic Neutral network.
* Copyright (C) 2024 Xpdustry
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.xpdustry.imperium.mindustry.control

import com.xpdustry.distributor.api.DistributorProvider
import com.xpdustry.distributor.api.annotation.TaskHandler
import com.xpdustry.distributor.api.scheduler.MindustryTimeUnit
import com.xpdustry.imperium.common.application.ExitStatus
import com.xpdustry.imperium.common.application.ImperiumApplication
import com.xpdustry.imperium.common.async.ImperiumScope
import com.xpdustry.imperium.common.config.ImperiumConfig
import com.xpdustry.imperium.common.content.MindustryGamemode
import com.xpdustry.imperium.common.control.RestartMessage
import com.xpdustry.imperium.common.inject.InstanceManager
import com.xpdustry.imperium.common.inject.get
import com.xpdustry.imperium.common.message.Messenger
import com.xpdustry.imperium.common.message.consumer
import com.xpdustry.imperium.common.misc.LoggerDelegate
import com.xpdustry.imperium.common.network.await
import com.xpdustry.imperium.common.version.ImperiumVersion
import com.xpdustry.imperium.common.webhook.WebhookMessage
import com.xpdustry.imperium.common.webhook.WebhookMessageSender
import com.xpdustry.imperium.mindustry.misc.Entities
import com.xpdustry.imperium.mindustry.misc.onEvent
import com.xpdustry.imperium.mindustry.misc.runMindustryThread
import com.xpdustry.imperium.mindustry.translation.server_restart_delay
import com.xpdustry.imperium.mindustry.translation.server_restart_game_over
import java.io.IOException
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.concurrent.atomic.AtomicReference
import kotlin.io.path.Path
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import mindustry.Vars
import mindustry.game.EventType.GameOverEvent
import okhttp3.OkHttpClient
import okhttp3.Request

class RestartListener(instances: InstanceManager) : ImperiumApplication.Listener {

private val webhook: WebhookMessageSender = instances.get()
private val config: ImperiumConfig = instances.get()
private val http: OkHttpClient = instances.get()
private val version: ImperiumVersion = instances.get()
private val restating = AtomicReference(false)
private var download: AtomicReference<ImperiumVersion?> = AtomicReference()
private val messenger = instances.get<Messenger>()
private val application = instances.get<ImperiumApplication>()

override fun onImperiumInit() {
messenger.consumer<RestartMessage> {
if (it.target == config.server.name) {
prepareRestart("admin", it.immediate)
}
}
}

@TaskHandler(interval = 5L, unit = MindustryTimeUnit.MINUTES)
fun onUpdateCheck() {
if (!config.server.autoUpdate) {
return
}
LOGGER.debug("Checking for updates...")
ImperiumScope.IO.launch {
http
.newCall(
Request.Builder()
.url("https://api.github.com/repos/xpdustry/imperium/releases/latest")
.get()
.build())
.await()
.use { response ->
if (response.code != 200) {
LOGGER.error(
"Failed to check for updates (code={}): {}",
response.code,
response.message)
return@launch
}
val latest =
ImperiumVersion.parse(
Json.parseToJsonElement(response.body!!.string())
.jsonObject["tag_name"]!!
.jsonPrimitive
.content)
if (latest > version) {
download.set(latest)
prepareRestart("update", false)
}
}
}
}

private fun prepareRestart(reason: String, immediate: Boolean) {
if (restating.getAndSet(true)) {
return
}
val everyone = DistributorProvider.get().audienceProvider.everyone
val gamemode = config.mindustry!!.gamemode
if (immediate || Entities.getPlayers().isEmpty() || Vars.state.gameOver) {
everyone.sendMessage(server_restart_delay(reason, 3.seconds))
ImperiumScope.MAIN.launch { restart() }
} else if (gamemode.pvp) {
everyone.sendMessage(server_restart_game_over(reason))
onEvent<GameOverEvent> {
ImperiumScope.MAIN.launch {
delay(5.seconds)
restart()
}
}
} else if (gamemode == MindustryGamemode.HUB) {
everyone.sendMessage(server_restart_delay(reason, 10.seconds))
ImperiumScope.MAIN.launch {
delay(10.seconds)
restart()
}
} else {
everyone.sendMessage(server_restart_delay(reason, 5.minutes))
ImperiumScope.MAIN.launch {
delay(5.minutes)
restart()
}
}
}

private suspend fun restart() {
if (download.get() != null) {
val target =
Path(RestartListener::class.java.protectionDomain.codeSource.location.toURI().path)
http
.newCall(
Request.Builder()
.url(
"https://github.com/xpdustry/imperium/releases/download/v${download.get()!!}/imperium-mindustry.jar")
.get()
.build())
.await()
.use { response ->
try {
if (response.code != 200) {
throw IOException(
"Failed to download the latest version (code=${response.code}): ${response.message}")
}
val source = Files.createTempFile("imperium-mindustry", ".jar.tmp")
response.body!!.byteStream().use { input ->
Files.copy(input, source, StandardCopyOption.REPLACE_EXISTING)
}
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING)
LOGGER.info("Successfully downloaded version {}", download.get()!!)
} catch (e: Exception) {
LOGGER.error("Failed to download the latest version", e)
webhook.send(
WebhookMessage(
content = "@phinner, server failed to auto update: ${e.message}"))
runMindustryThread { application.exit(ExitStatus.EXIT) }
return
}
}
}
runMindustryThread { application.exit(ExitStatus.RESTART) }
}

companion object {
private val LOGGER by LoggerDelegate()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,20 @@ fun command_team_success(team: Team): Component =

fun gatekeeper_failure(kind: String, details: String = "none"): Component =
translatable("imperium.gatekeeper.failure.$kind", TranslationArguments.array(details), SCARLET)

fun server_restart_delay(reason: String, delay: Duration): Component =
components(
SCARLET,
translatable("imperium.restart.reason.$reason"),
space(),
translatable(
"imperium.restart.trigger.delay", TranslationArguments.array(duration(delay, ACCENT))),
)

fun server_restart_game_over(reason: String): Component =
components(
SCARLET,
translatable("imperium.restart.reason.$reason"),
space(),
translatable("imperium.restart.trigger.game-over"),
)
Loading

0 comments on commit 9aca462

Please sign in to comment.