[FEATURE] Adjust server control
This commit is contained in:
parent
7348b07352
commit
62c966a6bd
8 changed files with 109 additions and 57 deletions
|
@ -52,7 +52,8 @@ env:
|
|||
- name: GENERATOR_SETTINGS
|
||||
- name: LEVEL
|
||||
value: world
|
||||
- name: MODPACK
|
||||
# - name: MODPACK
|
||||
# value: https://somemodpack.com
|
||||
- name: ONLINE_MODE
|
||||
value: "true"
|
||||
- name: MEMORY
|
||||
|
|
|
@ -16,6 +16,8 @@ spec:
|
|||
type: Recreate
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
minecluster.dunemask.net/server-name: changeme-server-name
|
||||
labels:
|
||||
app: changeme-app
|
||||
spec:
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import k8s from "@kubernetes/client-node";
|
||||
import { VERB } from "../util/logging.js";
|
||||
import { VERB, ERR } from "../util/logging.js";
|
||||
const kc = new k8s.KubeConfig();
|
||||
kc.loadFromDefault();
|
||||
|
||||
|
@ -13,7 +13,7 @@ const mineclusterManaged = (o) =>
|
|||
o.metadata.annotations &&
|
||||
o.metadata.annotations["minecluster.dunemask.net/server-name"] !== undefined;
|
||||
|
||||
const serverMatch = (serverName) => (o) =>
|
||||
export const serverMatch = (serverName) => (o) =>
|
||||
o.metadata.annotations["minecluster.dunemask.net/server-name"] === serverName;
|
||||
|
||||
export async function getDeployments() {
|
||||
|
@ -72,7 +72,7 @@ export function getServerAssets(serverName) {
|
|||
for (var k in serverAssets) if (serverAssets[k]) return serverAssets;
|
||||
// If no assets exist, return nothing
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
.catch((e) => ERR("SERVER ASSETS", e));
|
||||
}
|
||||
|
||||
export async function getDeployment(serverName) {
|
||||
|
@ -82,13 +82,26 @@ export async function getDeployment(serverName) {
|
|||
s.metadata.annotations["minecluster.dunemask.net/server-name"] ===
|
||||
serverName,
|
||||
);
|
||||
if (!serverDeployment) {
|
||||
console.log(servers.map((s) => s.metadata.annotations));
|
||||
if (!serverDeployment)
|
||||
throw Error(`MCL Deployment '${serverName}' could not be found!`);
|
||||
}
|
||||
|
||||
return serverDeployment;
|
||||
}
|
||||
|
||||
export async function toggleServer(serverName, scaleUp = false) {
|
||||
const deployment = await getDeployment(serverName);
|
||||
const { containers } = deployment.spec.template.spec;
|
||||
const ftpContainer = containers.find((c) => c.name.endsWith("-ftp"));
|
||||
|
||||
res.sendStatus(200);
|
||||
deployment.spec.template.spec.containers = containers;
|
||||
return k8sDeps.replaceNamespacedDeployment(
|
||||
deployment.metadata.name,
|
||||
namespace,
|
||||
deployment,
|
||||
);
|
||||
}
|
||||
|
||||
export async function scaleDeployment(serverName, scaleUp = false) {
|
||||
const deployment = await getDeployment(serverName);
|
||||
if (deployment.spec.replicas === 1 && scaleUp)
|
||||
|
@ -97,6 +110,7 @@ export async function scaleDeployment(serverName, scaleUp = false) {
|
|||
`MCL Deployment '${serverName}' is already scaled! Ignoring scale adjustment.`,
|
||||
);
|
||||
deployment.spec.replicas = scaleUp ? 1 : 0;
|
||||
|
||||
return k8sDeps.replaceNamespacedDeployment(
|
||||
deployment.metadata.name,
|
||||
namespace,
|
||||
|
|
|
@ -5,15 +5,18 @@ import { ERR } from "../util/logging.js";
|
|||
const kc = new k8s.KubeConfig();
|
||||
kc.loadFromDefault();
|
||||
const k8sApi = kc.makeApiClient(k8s.CoreV1Api);
|
||||
|
||||
export default async function liveLogging(socket, serverNamespace) {
|
||||
const containerName = `mcl-${socket.mcs.serverName}`;
|
||||
const { serverName } = socket.mcs;
|
||||
const podName = `mcl-${serverName}`;
|
||||
const containerName = `${podName}-server`;
|
||||
const podResponse = await k8sApi.listNamespacedPod(serverNamespace);
|
||||
const pods = podResponse.body.items.map((vp1) => vp1.metadata.name);
|
||||
const mcsPods = pods.filter((p) => p.startsWith(containerName));
|
||||
const mcsPods = pods.filter((p) => p.startsWith(podName));
|
||||
if (mcsPods.length === 0)
|
||||
throw Error(`Could not find a pod that starts with ${containerName}`);
|
||||
throw Error(`Could not find a pod that starts with ${podName}`);
|
||||
if (mcsPods.length > 1)
|
||||
throw Error(`Multiple pods match the name ${containerName}`);
|
||||
throw Error(`Multiple pods match the name ${podName}`);
|
||||
|
||||
const log = new k8s.Log(kc);
|
||||
const logStream = new stream.PassThrough();
|
||||
|
|
|
@ -10,6 +10,7 @@ const kc = new k8s.KubeConfig();
|
|||
kc.loadFromDefault();
|
||||
|
||||
const k8sMetrics = new k8s.Metrics(kc);
|
||||
const k8sDeps = kc.makeApiClient(k8s.AppsV1Api);
|
||||
const namespace = process.env.MCL_SERVER_NAMESPACE;
|
||||
|
||||
// Gets the all assets for the server
|
||||
|
|
|
@ -55,8 +55,10 @@ function createServerVolume(serverSpec) {
|
|||
return volumeYaml;
|
||||
}
|
||||
|
||||
function getFtpContainer() {
|
||||
function getFtpContainer(serverSpec) {
|
||||
const { name } = serverSpec;
|
||||
const ftpContainer = loadYaml("lib/k8s/configs/containers/ftp-server.yml");
|
||||
ftpContainer.name = `mcl-${name}-ftp`;
|
||||
const ftpPortList = [
|
||||
{ p: 20, n: "ftp-data" },
|
||||
{ p: 21, n: "ftp-commands" },
|
||||
|
@ -71,7 +73,7 @@ function getFtpContainer() {
|
|||
return ftpContainer;
|
||||
}
|
||||
|
||||
function createServerDeploy(serverSpec) {
|
||||
function getServerContainer(serverSpec) {
|
||||
const {
|
||||
name,
|
||||
version,
|
||||
|
@ -86,24 +88,16 @@ function createServerDeploy(serverSpec) {
|
|||
ops,
|
||||
whitelist,
|
||||
} = serverSpec;
|
||||
const deployYaml = loadYaml("lib/k8s/configs/server-deployment.yml");
|
||||
const serverContainer = loadYaml(
|
||||
"lib/k8s/configs/containers/minecraft-server.yml",
|
||||
);
|
||||
const backupContainer = loadYaml(
|
||||
"lib/k8s/configs/containers/minecraft-backup.yml",
|
||||
);
|
||||
const ftpContainer = getFtpContainer();
|
||||
deployYaml.metadata.name = `mcl-${name}`;
|
||||
const container = loadYaml("lib/k8s/configs/containers/minecraft-server.yml");
|
||||
|
||||
// Container Updates
|
||||
container.name = `mcl-${name}-server`;
|
||||
container.resources.requests.memory = `${memory}Mi`;
|
||||
// container.resources.limits.memory = `${memory}Mi`; // TODO Allow for limits beyond initial startup
|
||||
|
||||
const findEnv = (k) => container.env.find(({ name: n }) => n === k);
|
||||
const updateEnv = (k, v) => (findEnv(k).value = v);
|
||||
|
||||
deployYaml.metadata.namespace = namespace;
|
||||
deployYaml.metadata.annotations["minecluster.dunemask.net/server-name"] =
|
||||
name;
|
||||
deployYaml.spec.replicas = 0; // TODO: User control for autostart
|
||||
deployYaml.spec.selector.matchLabels.app = `mcl-${name}-app`;
|
||||
deployYaml.spec.template.metadata.labels.app = `mcl-${name}-app`;
|
||||
const findEnv = (k) => serverContainer.env.find(({ name: n }) => n === k);
|
||||
const updateEnv = (k, v) => (findEnv.value = v);
|
||||
// Enviornment variables
|
||||
updateEnv("TYPE", serverType);
|
||||
updateEnv("VERSION", version);
|
||||
|
@ -116,22 +110,49 @@ function createServerDeploy(serverSpec) {
|
|||
updateEnv("WHITELIST", whitelist);
|
||||
updateEnv("MEMORY", `${memory}M`);
|
||||
|
||||
if (version !== "VANILLA") delete findEnv("MODPACK").value;
|
||||
else updateEnv("MODPACK", modpack);
|
||||
findEnv("RCON_PASSWORD").valueFrom.secretKeyRef.name =
|
||||
`mcl-${name}-rcon-secret`;
|
||||
// RCON
|
||||
const rs = `mcl-${name}-rcon-secret`;
|
||||
findEnv("RCON_PASSWORD").valueFrom.secretKeyRef.name = rs;
|
||||
// Mods // TODO: remove these once files are managable
|
||||
/*if (version !== "VANILLA") delete findEnv("MODPACK").value;
|
||||
else updateEnv("MODPACK", modpack);*/
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
function getBackupContainer(serverSpec) {
|
||||
const container = loadYaml("lib/k8s/configs/containers/minecraft-backup.yml");
|
||||
return container;
|
||||
}
|
||||
|
||||
function createServerDeploy(serverSpec) {
|
||||
const { name } = serverSpec;
|
||||
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;
|
||||
metadata.name = `mcl-${name}`;
|
||||
metadata.namespace = namespace;
|
||||
metadata.annotations["minecluster.dunemask.net/server-name"] = name;
|
||||
deployYaml.metadata = metadata;
|
||||
|
||||
// Configure Lables & Selectors
|
||||
deployYaml.spec.selector.matchLabels.app = `mcl-${name}-app`;
|
||||
deployYaml.spec.template.metadata.labels.app = `mcl-${name}-app`;
|
||||
|
||||
// Server Container Name
|
||||
serverContainer.name = `mcl-${name}`;
|
||||
// Resources
|
||||
serverContainer.resources.requests.memory = `${memory}Mi`;
|
||||
// serverContainer.resources.limits.memory = `${memory}Mi`; // TODO Allow for limits beyond initial startup
|
||||
// Volumes
|
||||
deployYaml.spec.template.spec.volumes.find(
|
||||
({ name }) => name === "datadir",
|
||||
).persistentVolumeClaim.claimName = `mcl-${name}-volume`;
|
||||
|
||||
// Apply Containers
|
||||
deployYaml.spec.template.spec.containers.push(serverContainer);
|
||||
deployYaml.spec.template.spec.containers.push(ftpContainer);
|
||||
// TODO: User control for autostart
|
||||
deployYaml.spec.replicas = 0;
|
||||
return deployYaml;
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,22 @@ import { getServerAssets } from "./k8s-server-control.js";
|
|||
|
||||
const namespace = process.env.MCL_SERVER_NAMESPACE;
|
||||
|
||||
export async function useFtp(serverService) {
|
||||
const { name } = serverService.metadata;
|
||||
const client = new ftp.Client();
|
||||
await client.access({
|
||||
host: `${name}.${namespace}.svc.cluster.local`,
|
||||
user: "minecluster",
|
||||
password: "minecluster",
|
||||
});
|
||||
return client;
|
||||
}
|
||||
|
||||
const handleError = (res) => (e) => {
|
||||
ERR("SERVER FILES", "Error occurred while preforming FTP operation!", e);
|
||||
res.status(500).send("Error occurred while performing FTP operation!");
|
||||
};
|
||||
|
||||
export async function listFiles(req, res) {
|
||||
const serverSpec = req.body;
|
||||
if (!serverSpec) return res.sendStatus(400);
|
||||
|
@ -16,20 +32,12 @@ export async function listFiles(req, res) {
|
|||
return res
|
||||
.status(409)
|
||||
.send("Service doesn't exist, please contact your hosting provider!");
|
||||
const client = new ftp.Client(0);
|
||||
client.ftp.verbose = true;
|
||||
try {
|
||||
await client.access({
|
||||
host: `${server.service.metadata.name}.${namespace}.svc.cluster.local`,
|
||||
user: "minecluster",
|
||||
password: "minecluster",
|
||||
});
|
||||
const files = await client.list();
|
||||
res.json(files);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
ERR("SERVER FILES", "Error loading client files:");
|
||||
res.status(500).send(err);
|
||||
}
|
||||
// client.ftp.verbose = true;
|
||||
const client = await useFtp(server.service).catch(handleError(res));
|
||||
if (!client) return;
|
||||
await client
|
||||
.list()
|
||||
.then((f) => res.json(f))
|
||||
.catch(handleError(res));
|
||||
client.close();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import k8s from "@kubernetes/client-node";
|
||||
import { Rcon as RconClient } from "rcon-client";
|
||||
import { ERR } from "../util/logging.js";
|
||||
import { ERR, WARN } from "../util/logging.js";
|
||||
const kc = new k8s.KubeConfig();
|
||||
kc.loadFromDefault();
|
||||
const k8sCore = kc.makeApiClient(k8s.CoreV1Api);
|
||||
|
@ -15,7 +15,8 @@ export default async function rconInterface(socket) {
|
|||
rconRes.body.data["rcon-password"],
|
||||
"base64",
|
||||
).toString("utf8");
|
||||
const rconHost = `mcl-${socket.mcs.serverName}-rcon`;
|
||||
const { serverName } = socket.mcs;
|
||||
const rconHost = `mcl-${serverName}-rcon.${namespace}.svc.cluster.local`;
|
||||
const rcon = new RconClient({
|
||||
host: rconHost,
|
||||
port: 25575,
|
||||
|
@ -25,7 +26,8 @@ export default async function rconInterface(socket) {
|
|||
try {
|
||||
await rcon.connect();
|
||||
} catch (error) {
|
||||
ERR("RCON", `Could not connect to 'mcl-${socket.mcs.serverName}-rcon'`);
|
||||
socket.emit("push", "Could not connect RCON Input to server!");
|
||||
WARN("RCON", `Could not connect to '${rconHost}'`);
|
||||
}
|
||||
socket.rconClient = rcon;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue