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