2023-03-15 15:20:08 +00:00
|
|
|
import { v4 as uuidv4 } from "uuid";
|
|
|
|
import bcrypt from "bcrypt";
|
|
|
|
import k8s from "@kubernetes/client-node";
|
|
|
|
import yaml from "js-yaml";
|
|
|
|
import fs from "node:fs";
|
|
|
|
import path from "node:path";
|
2023-12-22 14:45:49 -07:00
|
|
|
|
2023-12-20 03:20:04 +00:00
|
|
|
import {
|
|
|
|
getFtpContainer,
|
|
|
|
getServerContainer,
|
|
|
|
getBackupContainer,
|
|
|
|
} from "./server-containers.js";
|
|
|
|
|
2023-03-15 15:20:08 +00:00
|
|
|
const kc = new k8s.KubeConfig();
|
|
|
|
kc.loadFromDefault();
|
|
|
|
const k8sDeps = kc.makeApiClient(k8s.AppsV1Api);
|
|
|
|
const k8sCore = kc.makeApiClient(k8s.CoreV1Api);
|
|
|
|
const namespace = process.env.MCL_SERVER_NAMESPACE;
|
|
|
|
|
2023-12-20 03:20:04 +00:00
|
|
|
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
2023-03-15 15:20:08 +00:00
|
|
|
|
2024-01-15 13:07:13 -07:00
|
|
|
function createBackupSecret(serverSpec) {
|
|
|
|
if (!serverSpec.backupEnabled) return; // If backup not defined, don't create RCLONE secret
|
|
|
|
const { mclName, id, backupId, backupKey, backupHost } = serverSpec;
|
|
|
|
const backupYaml = loadYaml("lib/k8s/configs/backup-secret.yml");
|
|
|
|
backupYaml.metadata.labels.app = `mcl-${mclName}-app`;
|
|
|
|
backupYaml.metadata.name = `mcl-${mclName}-backup-secret`;
|
|
|
|
backupYaml.metadata.namespace = namespace;
|
|
|
|
backupYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
|
|
|
const rcloneConfig = [
|
|
|
|
`[${mclName}-backup]`,
|
|
|
|
"type = s3",
|
|
|
|
"provider = Minio",
|
|
|
|
"env_auth = false",
|
|
|
|
`access_key_id = ${backupId}`,
|
|
|
|
`secret_access_key = ${backupKey}`,
|
|
|
|
`endpoint = ${backupHost}`,
|
|
|
|
`acl = private`,
|
|
|
|
].join("\n");
|
|
|
|
backupYaml.data["rclone.conf"] = Buffer.from(rcloneConfig).toString("base64");
|
|
|
|
return backupYaml;
|
|
|
|
}
|
|
|
|
|
2023-03-15 15:20:08 +00:00
|
|
|
function createRconSecret(serverSpec) {
|
2023-12-22 14:45:49 -07:00
|
|
|
const { mclName, id } = serverSpec;
|
2023-12-20 03:20:04 +00:00
|
|
|
const rconYaml = loadYaml("lib/k8s/configs/rcon-secret.yml");
|
2023-03-15 15:20:08 +00:00
|
|
|
// TODO: Dyamic rconPassword
|
|
|
|
const rconPassword = bcrypt.hashSync(uuidv4(), 10);
|
|
|
|
rconYaml.data["rcon-password"] = Buffer.from(rconPassword).toString("base64");
|
2023-12-22 14:45:49 -07:00
|
|
|
rconYaml.metadata.labels.app = `mcl-${mclName}-app`;
|
|
|
|
rconYaml.metadata.name = `mcl-${mclName}-rcon-secret`;
|
2023-03-15 15:20:08 +00:00
|
|
|
rconYaml.metadata.namespace = namespace;
|
2023-12-22 14:45:49 -07:00
|
|
|
rconYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
2023-03-15 15:20:08 +00:00
|
|
|
return rconYaml;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createServerVolume(serverSpec) {
|
2023-12-22 14:45:49 -07:00
|
|
|
const { mclName, id } = serverSpec;
|
2023-12-20 03:20:04 +00:00
|
|
|
const volumeYaml = loadYaml("lib/k8s/configs/server-pvc.yml");
|
2023-12-22 14:45:49 -07:00
|
|
|
volumeYaml.metadata.labels.service = `mcl-${mclName}-server`;
|
|
|
|
volumeYaml.metadata.name = `mcl-${mclName}-volume`;
|
2023-03-15 15:20:08 +00:00
|
|
|
volumeYaml.metadata.namespace = namespace;
|
2023-12-22 14:45:49 -07:00
|
|
|
volumeYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
2024-01-15 13:07:13 -07:00
|
|
|
volumeYaml.spec.resources.requests.storage = "5Gi"; // TODO: Changeme
|
2023-03-15 15:20:08 +00:00
|
|
|
return volumeYaml;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createServerDeploy(serverSpec) {
|
2024-01-15 13:07:13 -07:00
|
|
|
const { mclName, id, backupEnabled } = serverSpec;
|
2023-12-20 03:20:04 +00:00
|
|
|
const deployYaml = loadYaml("lib/k8s/configs/server-deployment.yml");
|
|
|
|
const { metadata } = deployYaml;
|
|
|
|
const serverContainer = getServerContainer(serverSpec);
|
|
|
|
const backupContainer = getBackupContainer(serverSpec);
|
|
|
|
const ftpContainer = getFtpContainer(serverSpec);
|
|
|
|
|
|
|
|
// Configure Metadata;
|
2023-12-22 14:45:49 -07:00
|
|
|
metadata.name = `mcl-${mclName}`;
|
2023-12-20 03:20:04 +00:00
|
|
|
metadata.namespace = namespace;
|
2023-12-22 14:45:49 -07:00
|
|
|
metadata.annotations["minecluster.dunemask.net/id"] = id;
|
2023-12-20 03:20:04 +00:00
|
|
|
deployYaml.metadata = metadata;
|
|
|
|
|
2024-01-15 13:07:13 -07:00
|
|
|
deployYaml.spec.template.spec.terminationGracePeriodSeconds = 1;
|
|
|
|
|
2023-12-20 03:20:04 +00:00
|
|
|
// Configure Lables & Selectors
|
2023-12-22 14:45:49 -07:00
|
|
|
deployYaml.spec.selector.matchLabels.app = `mcl-${mclName}-app`;
|
|
|
|
deployYaml.spec.template.metadata.labels.app = `mcl-${mclName}-app`;
|
|
|
|
deployYaml.spec.template.metadata.annotations["minecluster.dunemask.net/id"] =
|
|
|
|
id;
|
2023-03-15 15:20:08 +00:00
|
|
|
|
|
|
|
// Volumes
|
|
|
|
deployYaml.spec.template.spec.volumes.find(
|
2023-12-20 03:20:04 +00:00
|
|
|
({ name }) => name === "datadir",
|
2023-12-22 14:45:49 -07:00
|
|
|
).persistentVolumeClaim.claimName = `mcl-${mclName}-volume`;
|
2023-12-20 03:20:04 +00:00
|
|
|
|
2024-01-15 13:07:13 -07:00
|
|
|
// Backups
|
|
|
|
if (backupEnabled) {
|
|
|
|
deployYaml.spec.template.spec.volumes.push({
|
|
|
|
name: "rclone-config",
|
|
|
|
secret: {
|
|
|
|
defaultMode: 420,
|
|
|
|
items: [{ key: "rclone.conf", path: "rclone.conf" }],
|
|
|
|
secretName: `mcl-${mclName}-backup-secret`,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-12-20 03:20:04 +00:00
|
|
|
// Apply Containers TODO: User control for autostart
|
2023-12-22 14:58:03 -07:00
|
|
|
// deployYaml.spec.template.spec.containers.push(serverContainer);
|
2023-12-20 03:20:04 +00:00
|
|
|
deployYaml.spec.template.spec.containers.push(ftpContainer);
|
|
|
|
deployYaml.spec.replicas = 1;
|
2023-03-15 15:20:08 +00:00
|
|
|
return deployYaml;
|
|
|
|
}
|
|
|
|
|
|
|
|
function createServerService(serverSpec) {
|
2023-12-22 14:45:49 -07:00
|
|
|
const { mclName, host, id } = serverSpec;
|
2023-12-20 03:20:04 +00:00
|
|
|
const serviceYaml = loadYaml("lib/k8s/configs/server-svc.yml");
|
|
|
|
serviceYaml.metadata.annotations["ingress.qumine.io/hostname"] = host;
|
|
|
|
serviceYaml.metadata.annotations["mc-router.itzg.me/externalServerName"] =
|
|
|
|
host;
|
2023-12-22 14:45:49 -07:00
|
|
|
serviceYaml.metadata.labels.app = `mcl-${mclName}-app`;
|
|
|
|
serviceYaml.metadata.name = `mcl-${mclName}-server`;
|
2023-03-15 15:20:08 +00:00
|
|
|
serviceYaml.metadata.namespace = namespace;
|
2023-12-22 14:45:49 -07:00
|
|
|
serviceYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
|
|
|
serviceYaml.spec.selector.app = `mcl-${mclName}-app`;
|
2023-12-20 03:20:04 +00:00
|
|
|
// Port List:
|
|
|
|
const serverPortList = [{ p: 25565, n: "minecraft" }];
|
|
|
|
|
|
|
|
// Apply FTP Port List
|
|
|
|
const ftpPortList = [
|
|
|
|
{ p: 20, n: "ftp-data" },
|
|
|
|
{ p: 21, n: "ftp-commands" },
|
|
|
|
];
|
|
|
|
for (var p = 40000; p <= 40009; p++)
|
|
|
|
ftpPortList.push({ p, n: `ftp-passive-${p - 40000}` });
|
|
|
|
|
|
|
|
const portList = [...serverPortList, ...ftpPortList];
|
|
|
|
serviceYaml.spec.ports = portList.map(({ p: port, n: name }) => ({
|
|
|
|
port,
|
|
|
|
name,
|
|
|
|
protocol: "TCP",
|
|
|
|
targetPort: port,
|
|
|
|
}));
|
2023-03-15 15:20:08 +00:00
|
|
|
return serviceYaml;
|
|
|
|
}
|
|
|
|
|
2023-12-22 14:45:49 -07:00
|
|
|
function createRconService(createSpec) {
|
|
|
|
const { id, mclName } = createSpec;
|
2023-12-20 03:20:04 +00:00
|
|
|
const rconSvcYaml = loadYaml("lib/k8s/configs/rcon-svc.yml");
|
2023-12-22 14:45:49 -07:00
|
|
|
rconSvcYaml.metadata.labels.app = `mcl-${mclName}-app`;
|
|
|
|
rconSvcYaml.metadata.name = `mcl-${mclName}-rcon`;
|
2023-03-15 15:20:08 +00:00
|
|
|
rconSvcYaml.metadata.namespace = namespace;
|
2023-12-22 14:45:49 -07:00
|
|
|
rconSvcYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
|
|
|
rconSvcYaml.spec.selector.app = `mcl-${mclName}-app`;
|
2023-03-15 15:20:08 +00:00
|
|
|
return rconSvcYaml;
|
|
|
|
}
|
|
|
|
|
2023-12-22 14:45:49 -07:00
|
|
|
export default async function createServerResources(createSpec) {
|
2024-01-15 13:07:13 -07:00
|
|
|
const backupSecret = createBackupSecret(createSpec);
|
2023-12-22 14:45:49 -07:00
|
|
|
const rconSecret = createRconSecret(createSpec);
|
|
|
|
const serverVolume = createServerVolume(createSpec);
|
|
|
|
const serverDeploy = createServerDeploy(createSpec);
|
|
|
|
const serverService = createServerService(createSpec);
|
|
|
|
const rconService = createRconService(createSpec);
|
2023-03-15 15:20:08 +00:00
|
|
|
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume);
|
2024-01-15 13:07:13 -07:00
|
|
|
if (!!backupSecret) k8sCore.createNamespacedSecret(namespace, backupSecret);
|
2023-03-15 15:20:08 +00:00
|
|
|
k8sCore.createNamespacedSecret(namespace, rconSecret);
|
|
|
|
k8sCore.createNamespacedService(namespace, serverService);
|
|
|
|
k8sCore.createNamespacedService(namespace, rconService);
|
|
|
|
k8sDeps.createNamespacedDeployment(namespace, serverDeploy);
|
|
|
|
}
|