[FEATURE] Quality of Life Improvements for Management (#7)

Co-authored-by: Dunemask <dunemask@gmail.com>
Reviewed-on: https://gitea.dunemask.dev/elysium/minecluster/pulls/7
This commit is contained in:
dunemask 2024-01-22 01:01:12 +00:00
parent 6eb4ed3e95
commit 23efaafe1d
18 changed files with 479 additions and 39 deletions

View file

@ -15,7 +15,7 @@ const dnsRegex = new RegExp(
function payloadFilter(req, res) {
const serverSpec = req.body;
if (!serverSpec) return res.sendStatus(400);
const { name, host, version, serverType, memory } = serverSpec;
const { name, host, version, serverType, memory, extraPorts } = serverSpec;
const { backupHost, backupBucket, backupId, backupKey, backupInterval } =
serverSpec;
if (!name) return res.status(400).send("Server name is required!");
@ -24,6 +24,14 @@ function payloadFilter(req, res) {
if (!version) return res.status(400).send("Server version is required!");
if (!serverType) return res.status(400).send("Server type is required!");
if (!memory) return res.status(400).send("Memory is required!");
if (
!!extraPorts &&
(!Array.isArray(extraPorts) ||
extraPorts.find((e) => typeof e !== "string" || e.length > 5))
)
return res
.status(400)
.send("Extra ports must be a list of strings with length of 5!");
// TODO: Impliment non creation time backups
if (
!!backupHost ||

View file

@ -13,6 +13,7 @@ CREATE TABLE servers (
backup_id varchar(255) DEFAULT NULL,
backup_key varchar(255) DEFAULT NULL,
backup_interval varchar(255) DEFAULT NULL,
extra_ports varchar(7)[] DEFAULT NULL,
CONSTRAINT unique_host UNIQUE(host)
);
ALTER SEQUENCE servers_id_seq OWNED BY servers.id;

View file

@ -16,6 +16,7 @@ export async function createServerEntry(serverSpec) {
version,
serverType: server_type,
memory,
extraPorts: extra_ports,
backupHost: backup_host,
backupBucket: backup_bucket_path,
backupId: backup_id,
@ -28,6 +29,7 @@ export async function createServerEntry(serverSpec) {
version,
server_type,
memory,
extra_ports,
backup_enabled: !!backup_interval, // We already verified the payload, so any backup key will work
backup_host,
backup_bucket_path,
@ -45,6 +47,7 @@ export async function createServerEntry(serverSpec) {
version,
server_type: serverType,
memory,
extra_ports: extraPorts,
backup_enabled: backupEnabled,
backup_host: backupHost,
backup_bucket_path: backupPath,
@ -61,6 +64,7 @@ export async function createServerEntry(serverSpec) {
version,
serverType,
memory,
extraPorts,
backupEnabled,
backupHost,
backupPath,
@ -94,6 +98,7 @@ export async function getServerEntry(serverId) {
version,
server_type: serverType,
memory,
extra_ports: extraPorts,
backup_enabled: backupEnabled,
backup_host: backupHost,
backup_bucket_path: backupPath,
@ -110,6 +115,7 @@ export async function getServerEntry(serverId) {
version,
serverType,
memory,
extraPorts,
backupEnabled,
backupHost,
backupPath,

View file

@ -0,0 +1,23 @@
apiVersion: v1
kind: Service
metadata:
annotations:
minecluster.dunemask.net/id: changeme-server-id
labels:
app: changeme-app
name: changeme-extra
namespace: changeme-namespace
spec:
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
# ports: Programatically generated
# - name: port-name
# port: 1234
# protocol: TCP
# targetPort: port-name
selector:
app: changeme-app
sessionAffinity: None
type: ClusterIP

View file

@ -78,6 +78,7 @@ export function getServerAssets(serverId) {
backupSecret: secrets.find((s) =>
s.metadata.name.endsWith("-backup-secret"),
),
extraService: services.find((s) => s.metadata.name.endsWith("-extra")),
};
for (var k in serverAssets) if (serverAssets[k]) return serverAssets;
// If no assets exist, return nothing

View file

@ -19,6 +19,37 @@ const namespace = process.env.MCL_SERVER_NAMESPACE;
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
function createExtraService(serverSpec) {
const { mclName, id, extraPorts } = serverSpec;
if (!extraPorts) return;
const serviceYaml = loadYaml("lib/k8s/configs/extra-svc.yml");
serviceYaml.metadata.labels.app = `mcl-${mclName}-app`;
serviceYaml.metadata.name = `mcl-${mclName}-extra`;
serviceYaml.metadata.namespace = namespace;
serviceYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
serviceYaml.spec.selector.app = `mcl-${mclName}-app`;
// Port List:
const portList = extraPorts.map((p) => ({
port: parseInt(p),
name: `mcl-extra-${p}`,
}));
const tcpPorts = portList.map(({ port, name }) => ({
port,
name: `${name}-tcp`,
protocol: "TCP",
targetPort: port,
}));
const udpPorts = portList.map(({ port, name }) => ({
port,
name: `${name}-udp`,
protocol: "UDP",
targetPort: port,
}));
serviceYaml.spec.ports = [...tcpPorts, ...udpPorts];
return serviceYaml;
}
function createBackupSecret(serverSpec) {
if (!serverSpec.backupEnabled) return; // If backup not defined, don't create RCLONE secret
const { mclName, id, backupId, backupKey, backupHost } = serverSpec;
@ -161,10 +192,26 @@ export default async function createServerResources(createSpec) {
const serverDeploy = createServerDeploy(createSpec);
const serverService = createServerService(createSpec);
const rconService = createRconService(createSpec);
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume);
if (!!backupSecret) k8sCore.createNamespacedSecret(namespace, backupSecret);
k8sCore.createNamespacedSecret(namespace, rconSecret);
k8sCore.createNamespacedService(namespace, serverService);
k8sCore.createNamespacedService(namespace, rconService);
k8sDeps.createNamespacedDeployment(namespace, serverDeploy);
const extraService = createExtraService(createSpec);
const serverResources = [];
serverResources.push(
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume),
);
if (!!extraService)
serverResources.push(
k8sCore.createNamespacedService(namespace, extraService),
);
if (!!backupSecret)
serverResources.push(
k8sCore.createNamespacedSecret(namespace, backupSecret),
);
serverResources.push(k8sCore.createNamespacedSecret(namespace, rconSecret));
serverResources.push(
k8sCore.createNamespacedService(namespace, serverService),
);
serverResources.push(k8sCore.createNamespacedService(namespace, rconService));
serverResources.push(
k8sDeps.createNamespacedDeployment(namespace, serverDeploy),
);
return await Promise.all(serverResources);
}

View file

@ -51,6 +51,10 @@ export default async function deleteServerResources(serverSpec) {
const deleteBackupSecret = deleteOnExist(server.backupSecret, (name) =>
k8sCore.deleteNamespacedSecret(name, namespace),
);
const deleteExtraService = deleteOnExist(server.extraService, (name) =>
k8sCore.deleteNamespacedService(name, namespace),
);
const deleteVolume = deleteOnExist(server.volume, (name) =>
k8sCore.deleteNamespacedPersistentVolumeClaim(name, namespace),
);
@ -59,6 +63,7 @@ export default async function deleteServerResources(serverSpec) {
deleteService,
deleteRconService,
deleteRconSecret,
deleteExtraService,
deleteBackupSecret,
deleteVolume,
]).catch(deleteError);

View file

@ -82,7 +82,7 @@ export async function uploadServerItem(serverSpec, file) {
const { path } = serverSpec;
pathSecurityCheck(path);
await useServerFtp(serverSpec, async (c) => {
await c.uploadFrom(fileStream, `${path}/${file.originalname}`);
await c.uploadFrom(fileStream, path);
}).catch(handleError);
}