Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.gradle
**/build
.git
.idea
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This dockerfile is far from best practice and is a pure "Make it work" approach. Please do not use it as a reference of any kind.
FROM gradle:jdk21-alpine as build
FROM gradle:jdk25-alpine as build

COPY . .
RUN gradle clean :bot:build :plugin-paper:build --no-daemon
Expand Down
10 changes: 10 additions & 0 deletions bot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,26 @@ dependencies {
implementation("de.chojo", "cjda-util", "2.14.5+jda-6.3.0") {
exclude(module = "opus-java")
}

implementation("net.dv8tion", "JDA", "6.4.1")

implementation(libs.javalin.bundle)
implementation(libs.javalin.openapi)
implementation(libs.javalin.swagger)
annotationProcessor(libs.javalin.annotation)
implementation("net.lingala.zip4j", "zip4j", "2.11.6")

// config
implementation("dev.chojo", "ocular", "2.2.1")
implementation("tools.jackson.dataformat:jackson-dataformat-yaml:3.1.4")

// database
implementation(libs.bundles.sadu)
implementation("org.postgresql", "postgresql", "42.7.11")

// docker api
implementation(libs.bundles.docker)

// Logging
implementation(libs.bundles.logging)
implementation("club.minnced", "discord-webhooks", "0.8.4")
Expand Down
1 change: 1 addition & 0 deletions bot/src/main/java/de/chojo/gamejam/Bot.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ private void buildLocale() {
.addLanguage(DiscordLocale.GERMAN)
.withLanguageProvider(guild -> Optional.ofNullable(guilds.guild(guild).settings().locale())
.map(DiscordLocale::from))
.withGuildLocaleCodeProvider((guild, s) -> Optional.empty())
.build();
}

Expand Down
18 changes: 12 additions & 6 deletions bot/src/main/java/de/chojo/gamejam/configuration/ConfigFile.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,22 @@

package de.chojo.gamejam.configuration;

import de.chojo.gamejam.configuration.elements.Api;
import de.chojo.gamejam.configuration.elements.BaseSettings;
import de.chojo.gamejam.configuration.elements.Database;
import de.chojo.gamejam.configuration.elements.Plugins;
import de.chojo.gamejam.configuration.elements.ServerManagement;
import de.chojo.gamejam.configuration.elements.ServerTemplate;
import de.chojo.gamejam.configuration.elements.*;

@SuppressWarnings("FieldMayBeFinal")
public class ConfigFile {
private BaseSettings baseSettings = new BaseSettings();
private Database database = new Database();
private Api api = new Api();
private ServerManagement serverManagement = new ServerManagement();

private Docker docker = new Docker();
private Plugins plugins = new Plugins();

public void setServerTemplate(ServerTemplate serverTemplate) {
this.serverTemplate = serverTemplate;
}

private ServerTemplate serverTemplate = new ServerTemplate();

public BaseSettings baseSettings() {
Expand All @@ -38,6 +40,10 @@ public ServerManagement serverManagement(){
return serverManagement;
}

public Docker docker() {
return docker;
}

public Plugins plugins() {
return plugins;
}
Expand Down
38 changes: 22 additions & 16 deletions bot/src/main/java/de/chojo/gamejam/configuration/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@

package de.chojo.gamejam.configuration;

import de.chojo.gamejam.configuration.elements.Api;
import de.chojo.gamejam.configuration.elements.BaseSettings;
import de.chojo.gamejam.configuration.elements.Database;
import de.chojo.gamejam.configuration.elements.Plugins;
import de.chojo.gamejam.configuration.elements.ServerManagement;
import de.chojo.gamejam.configuration.elements.ServerTemplate;
import de.chojo.jdautil.configuration.BaseConfiguration;

public class Configuration extends BaseConfiguration<ConfigFile> {
import de.chojo.gamejam.configuration.elements.*;
import dev.chojo.ocular.Configurations;
import dev.chojo.ocular.dataformats.YamlDataFormat;
import dev.chojo.ocular.key.Key;

import java.nio.file.Path;
import java.util.List;

public class Configuration extends Configurations<ConfigFile> {
public static final Key<ConfigFile> MAIN = Key.builder(Path.of("config.yml"), ConfigFile::new).build();

private Configuration() {
super(new ConfigFile());
super(Path.of("config"), MAIN, List.of(new YamlDataFormat()), Configuration.class.getClassLoader(), null);
}

public static Configuration create() {
Expand All @@ -27,26 +29,30 @@ public static Configuration create() {


public Database database() {
return config().database();
return main().database();
}

public Docker docker() {
return main().docker();
}

public BaseSettings baseSettings() {
return config().baseSettings();
return main().baseSettings();
}

public Api api() {
return config().api();
return main().api();
}

public ServerManagement serverManagement() {
return config().serverManagement();
return main().serverManagement();
}

public Plugins plugins() {
return config().plugins();
return main().plugins();
}

public ServerTemplate serverTemplate() {
return config().serverTemplate();
return main().serverTemplate();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
*
* Copyright (C) 2022 DevCord Team and Contributor
*/

package de.chojo.gamejam.configuration.elements;


@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
public class Docker {
private String host = "unix:///var/run/docker.sock";
private String certPath = "/home/user/.docker";
private boolean tlsVerify = true;
private String registryUsername;
private String registryPassword;
private String registryEmail;
private String registryUrl;

public String getHost() {
return host;
}

public String getCertPath() {
return certPath;
}

public boolean isTlsVerify() {
return tlsVerify;
}

public String getRegistryUsername() {
return registryUsername;
}

public String getRegistryPassword() {
return registryPassword;
}

public String getRegistryEmail() {
return registryEmail;
}

public String getRegistryUrl() {
return registryUrl;
}
}
145 changes: 145 additions & 0 deletions bot/src/main/java/de/chojo/gamejam/server/DockerService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
*
* Copyright (C) 2022 DevCord Team and Contributor
*/

package de.chojo.gamejam.server;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DockerClientImpl;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import de.chojo.gamejam.configuration.elements.Docker;
import org.slf4j.Logger;

import java.io.IOException;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.Optional;

import static org.slf4j.LoggerFactory.getLogger;

public class DockerService {
private DockerClientConfig dockerClientConfig;
private DockerClient dockerClient;
private static final Logger log = getLogger(DockerService.class);
private static final String DOCKER_IMAGE = "itzg/minecraft-server:latest";
private static final String DOCKER_VOLUME_DATA_DIR = "/data";

public DockerService(Docker dockerConfig) {
this.dockerClientConfig = DefaultDockerClientConfig
.createDefaultConfigBuilder()
.withDockerHost(dockerConfig.getHost())
.withDockerCertPath(dockerConfig.getCertPath())
.withDockerTlsVerify(dockerConfig.isTlsVerify())
.withRegistryUsername(dockerConfig.getRegistryUsername())
.withRegistryPassword(dockerConfig.getRegistryPassword())
.withRegistryEmail(dockerConfig.getRegistryEmail())
.withRegistryUrl(dockerConfig.getRegistryUrl())
.build();
}

public void initDockerClient() {
DockerHttpClient httpClient = new ApacheDockerHttpClient.Builder()
.dockerHost(dockerClientConfig.getDockerHost())
.sslConfig(dockerClientConfig.getSSLConfig())
.maxConnections(100)
.connectionTimeout(Duration.ofSeconds(30))
.responseTimeout(Duration.ofSeconds(45))
.build();

dockerClient = DockerClientImpl.getInstance(dockerClientConfig, httpClient);
}

public void shutdown() throws IOException {
dockerClient.close();
}

public void provisionServer(int teamId) {
log.info("Provisioning server for team {}", teamId);
dockerClient.createVolumeCmd()
.withName(volumeName(teamId))
.exec();

HostConfig hostConfig = HostConfig.newHostConfig()
.withBinds(new Bind(volumeName(teamId), new Volume(DOCKER_VOLUME_DATA_DIR)));

dockerClient.createContainerCmd(DOCKER_IMAGE)
.withName(containerName(teamId))
.withHostConfig(hostConfig)
.exec();
log.info("Server provisioned for team with container name {} and volume name {}", containerName(teamId), volumeName(teamId));
}

public void destroyServer(int teamId) {
log.info("Destroying server for team {}", teamId);
dockerClient.removeContainerCmd(containerName(teamId)).exec();
dockerClient.removeVolumeCmd(volumeName(teamId)).exec();
}

public void startServer(int teamId) {
log.info("Starting server for team {}", teamId);
dockerClient.startContainerCmd(containerName(teamId)).exec();
}

public void stopServer(int teamId) {
log.info("Stopping server for team {}", teamId);
dockerClient.stopContainerCmd(containerName(teamId)).exec();
}

public void restartServer(int teamId) {
dockerClient.restartContainerCmd(containerName(teamId)).exec();
}

public boolean running(int teamId) {
return dockerClient.listContainersCmd()
.withShowAll(true)
.withNameFilter(List.of(containerName(teamId)))
.exec()
.stream()
.anyMatch(container -> container.getState().equals("running"));
}

public void sendCommand(int teamId, String command) {
dockerClient.execCreateCmd(containerName(teamId))
.withCmd(String.format("mc rcon-cli %s", command))
.exec();
}

public boolean exists(int teamId) {
return dockerClient.listContainersCmd()
.withShowAll(true)
.exec()
.stream()
.anyMatch(container -> container.getId().startsWith(containerName(teamId)));
}

public Optional<Container> container(int teamId) {
return dockerClient.listContainersCmd()
.withShowAll(true)
.withNameFilter(List.of(containerName(teamId)))
.exec()
.stream()
.findFirst();
}

public void copyArchiveToContainer(int teamId, Path source, Path destination) {
dockerClient.copyArchiveToContainerCmd(containerName(teamId))
.withHostResource(source.toString())
.withRemotePath(destination.toString())
.exec();
}

private String volumeName(int teamId) {
return "plugin-jam-team-" + teamId;
}

public String containerName(int teamId) {
return "plugin-jam-team-" + teamId;
}
}
7 changes: 5 additions & 2 deletions bot/src/main/java/de/chojo/gamejam/server/ServerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class ServerService implements Runnable {
private Teams teams;
private final Configuration configuration;
private final Stack<Integer> freePorts = new Stack<>();
private final DockerService dockerService;

public static ServerService create(ScheduledExecutorService executorService, Configuration configuration) {
var serverService = new ServerService(configuration);
Expand All @@ -47,6 +48,8 @@ private ServerService(Configuration configuration) {
this.configuration = configuration;
IntStream.rangeClosed(configuration.serverManagement().minPort(), configuration.serverManagement().maxPort())
.forEach(freePorts::add);
this.dockerService = new DockerService(configuration.docker());
this.dockerService.initDockerClient();
}

public void shutdown() {
Expand Down Expand Up @@ -123,7 +126,7 @@ public CompletableFuture<Boolean> syncVelocity() {
}
var team = optTeam.get();
log.info("Registered server for team {} with id {}", team.meta().name(), team.id());
var teamServer = new TeamServer(this, team, configuration, registration.port(), registration.apiPort());
var teamServer = new TeamServer(this, dockerService, team, configuration, registration.port(), registration.apiPort());
teamServer.running(true);
server.put(team, teamServer);
freePorts.removeElement(registration.apiPort());
Expand All @@ -134,7 +137,7 @@ public CompletableFuture<Boolean> syncVelocity() {
}

public TeamServer get(Team team) {
return server.computeIfAbsent(team, key -> new TeamServer(this, key, configuration, nextPort(), nextPort()));
return server.computeIfAbsent(team, key -> new TeamServer(this, dockerService, key, configuration, nextPort(), nextPort()));
}

private int nextPort() {
Expand Down
Loading