[FEATURE] Initial FTP data

This commit is contained in:
Dunemask 2023-12-16 09:28:06 -07:00
parent 8ef46ac02f
commit 7348b07352
13 changed files with 330 additions and 204 deletions

View file

@ -0,0 +1,36 @@
env:
- name: FTP_USER
value: "minecluster"
- name: FTP_PASS
value: "minecluster"
image: garethflowers/ftp-server
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command: ["echo"]
failureThreshold: 20
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
name: changeme-name-ftp
ports: [] # Programatically add all the ports for easier readability, Ports include: 20,21,40000-400009
readinessProbe:
exec:
command: ["echo"]
failureThreshold: 20
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
resources:
requests:
cpu: 500m
memory: 512Mi
stdin: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
tty: true
volumeMounts:
- mountPath: /home/minecluster
name: datadir

View file

@ -0,0 +1,63 @@
env:
- name: SRC_DIR
value: /data
- name: BACKUP_NAME
value: world
- name: INITIAL_DELAY
value: 2m
- name: BACKUP_INTERVAL
value: 24h
- name: PRUNE_BACKUPS_DAYS
value: "2"
- name: PAUSE_IF_NO_PLAYERS
value: "true"
- name: SERVER_PORT
value: "25565"
- name: RCON_HOST
value: localhost
- name: RCON_PORT
value: "25575"
- name: RCON_PASSWORD
valueFrom:
secretKeyRef:
key: rcon-password
name: changeme-rcon-secret
- name: RCON_RETRIES
value: "5"
- name: RCON_RETRY_INTERVAL
value: 10s
- name: EXCLUDES
value: "*.jar,cache,logs"
- name: BACKUP_METHOD
value: rclone
- name: DEST_DIR
value: /backups
- name: LINK_LATEST
value: "false"
- name: TAR_COMPRESS_METHOD
value: gzip
- name: ZSTD_PARAMETERS
value: -3 --long=25 --single-thread
- name: RCLONE_REMOTE
value: mc-dunemask-net
- name: RCLONE_DEST_DIR
value: /minecraft-backups/deltasmp-backups
- name: RCLONE_COMPRESS_METHOD
value: gzip
image: itzg/mc-backup:latest
imagePullPolicy: IfNotPresent
name: mcs-deltasmp-minecraft-mc-backup
resources:
requests:
cpu: 500m
memory: 512Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /data
name: datadir
readOnly: true
- mountPath: /backups
name: backupdir
- mountPath: /config/rclone
name: rclone-config

View file

@ -0,0 +1,112 @@
env:
- name: EULA
value: "TRUE"
- name: TYPE
value: VANILLA
- name: VERSION
value: "latest"
- name: DIFFICULTY
value: easy
- name: WHITELIST
- name: OPS
- name: ICON
- name: MAX_PLAYERS
value: "20"
- name: MAX_WORLD_SIZE
value: "10000"
- name: ALLOW_NETHER
value: "true"
- name: ANNOUNCE_PLAYER_ACHIEVEMENTS
value: "true"
- name: ENABLE_COMMAND_BLOCK
value: "true"
- name: FORCE_GAMEMODE
value: "false"
- name: GENERATE_STRUCTURES
value: "true"
- name: HARDCORE
value: "false"
- name: MAX_BUILD_HEIGHT
value: "256"
- name: MAX_TICK_TIME
value: "60000"
- name: SPAWN_ANIMALS
value: "true"
- name: SPAWN_MONSTERS
value: "true"
- name: SPAWN_NPCS
value: "true"
- name: SPAWN_PROTECTION
value: "16"
- name: VIEW_DISTANCE
value: "10"
- name: SEED
- name: MODE
value: survival
- name: MOTD
value: §6Minecluster Hosting
- name: PVP
value: "true"
- name: LEVEL_TYPE
value: DEFAULT
- name: GENERATOR_SETTINGS
- name: LEVEL
value: world
- name: MODPACK
- name: ONLINE_MODE
value: "true"
- name: MEMORY
value: 1024M
- name: JVM_OPTS
- name: JVM_XX_OPTS
- name: OVERRIDE_SERVER_PROPERTIES
value: "true"
- name: ENABLE_RCON
value: "true"
- name: RCON_PASSWORD
valueFrom:
secretKeyRef:
key: rcon-password
name: changeme-rcon-secret
image: itzg/minecraft-server:latest
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command:
- mc-health
failureThreshold: 20
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
name: changeme-name
ports:
- containerPort: 25565
name: minecraft
protocol: TCP
- containerPort: 25575
name: rcon
protocol: TCP
readinessProbe:
exec:
command:
- mc-health
failureThreshold: 20
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
resources:
requests:
cpu: 500m
memory: 512Mi
stdin: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
tty: true
volumeMounts:
- mountPath: /data
name: datadir
- mountPath: /backups
name: backupdir
readOnly: true

View file

@ -19,188 +19,13 @@ spec:
labels:
app: changeme-app
spec:
containers:
- env:
- name: SRC_DIR
value: /data
- name: BACKUP_NAME
value: world
- name: INITIAL_DELAY
value: 2m
- name: BACKUP_INTERVAL
value: 24h
- name: PRUNE_BACKUPS_DAYS
value: "2"
- name: PAUSE_IF_NO_PLAYERS
value: "true"
- name: SERVER_PORT
value: "25565"
- name: RCON_HOST
value: localhost
- name: RCON_PORT
value: "25575"
- name: RCON_PASSWORD
valueFrom:
secretKeyRef:
key: rcon-password
name: changeme-rcon-secret
- name: RCON_RETRIES
value: "5"
- name: RCON_RETRY_INTERVAL
value: 10s
- name: EXCLUDES
value: "*.jar,cache,logs"
- name: BACKUP_METHOD
value: rclone
- name: DEST_DIR
value: /backups
- name: LINK_LATEST
value: "false"
- name: TAR_COMPRESS_METHOD
value: gzip
- name: ZSTD_PARAMETERS
value: -3 --long=25 --single-thread
- name: RCLONE_REMOTE
value: mc-dunemask-net
- name: RCLONE_DEST_DIR
value: /minecraft-backups/deltasmp-backups
- name: RCLONE_COMPRESS_METHOD
value: gzip
image: itzg/mc-backup:latest
imagePullPolicy: IfNotPresent
name: mcs-deltasmp-minecraft-mc-backup
resources:
requests:
cpu: 500m
memory: 512Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /data
name: datadir
readOnly: true
- mountPath: /backups
name: backupdir
- mountPath: /config/rclone
name: rclone-config
- env:
- name: EULA
value: "TRUE"
- name: TYPE
value: VANILLA
- name: VERSION
value: "latest"
- name: DIFFICULTY
value: easy
- name: WHITELIST
- name: OPS
- name: ICON
- name: MAX_PLAYERS
value: "20"
- name: MAX_WORLD_SIZE
value: "10000"
- name: ALLOW_NETHER
value: "true"
- name: ANNOUNCE_PLAYER_ACHIEVEMENTS
value: "true"
- name: ENABLE_COMMAND_BLOCK
value: "true"
- name: FORCE_GAMEMODE
value: "false"
- name: GENERATE_STRUCTURES
value: "true"
- name: HARDCORE
value: "false"
- name: MAX_BUILD_HEIGHT
value: "256"
- name: MAX_TICK_TIME
value: "60000"
- name: SPAWN_ANIMALS
value: "true"
- name: SPAWN_MONSTERS
value: "true"
- name: SPAWN_NPCS
value: "true"
- name: SPAWN_PROTECTION
value: "16"
- name: VIEW_DISTANCE
value: "10"
- name: SEED
- name: MODE
value: survival
- name: MOTD
value: §6Minecluster Hosting
- name: PVP
value: "true"
- name: LEVEL_TYPE
value: DEFAULT
- name: GENERATOR_SETTINGS
- name: LEVEL
value: world
- name: MODPACK
- name: ONLINE_MODE
value: "true"
- name: MEMORY
value: 1024M
- name: JVM_OPTS
- name: JVM_XX_OPTS
- name: OVERRIDE_SERVER_PROPERTIES
value: "true"
- name: ENABLE_RCON
value: "true"
- name: RCON_PASSWORD
valueFrom:
secretKeyRef:
key: rcon-password
name: changeme-rcon-secret
image: itzg/minecraft-server:latest
imagePullPolicy: IfNotPresent
livenessProbe:
exec:
command:
- mc-health
failureThreshold: 20
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
name: changeme-name
ports:
- containerPort: 25565
name: minecraft
protocol: TCP
- containerPort: 25575
name: rcon
protocol: TCP
readinessProbe:
exec:
command:
- mc-health
failureThreshold: 20
initialDelaySeconds: 30
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 1
resources:
requests:
cpu: 500m
memory: 512Mi
stdin: true
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
tty: true
volumeMounts:
- mountPath: /data
name: datadir
- mountPath: /backups
name: backupdir
readOnly: true
containers: []
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext:
fsGroup: 2000
runAsUser: 1000
# securityContext:
# fsGroup: 2000
# runAsUser: 1000
terminationGracePeriodSeconds: 30
volumes:
- name: datadir

View file

@ -14,11 +14,15 @@ spec:
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
ports:
ports: # Programatically add all FTP ports. Port range includes 20, 21, 40000-40001
- name: minecraft
port: 25565
protocol: TCP
targetPort: minecraft
# - name: ftp-data
# port: 20
# protocol: TCP
# targetPort: ftp-data
selector:
app: changeme-app
sessionAffinity: None

View file

@ -61,11 +61,11 @@ export function getServerAssets(serverName) {
const serverAssets = {
deployment: deployments[0],
service: services.find(
(s) => s.metadata.name === `mcl-${serverName}-rcon`,
(s) => s.metadata.name === `mcl-${serverName}-server`,
),
volume: volumes[0],
rconService: services.find(
(s) => s.metadata.name === `mcl-${serverName}-server`,
(s) => s.metadata.name === `mcl-${serverName}-rcon`,
),
rconSecret: secrets[0],
};

View file

@ -55,6 +55,22 @@ function createServerVolume(serverSpec) {
return volumeYaml;
}
function getFtpContainer() {
const ftpContainer = loadYaml("lib/k8s/configs/containers/ftp-server.yml");
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}` });
ftpContainer.ports = ftpPortList.map(({ p: containerPort, n: name }) => ({
containerPort,
name,
protocol: "TCP",
}));
return ftpContainer;
}
function createServerDeploy(serverSpec) {
const {
name,
@ -71,16 +87,21 @@ function createServerDeploy(serverSpec) {
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}`;
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`;
deployYaml.spec.template.spec.containers.splice(0, 1); //TODO: Currently removing backup container
const serverContainer = deployYaml.spec.template.spec.containers[0];
const findEnv = (k) => serverContainer.env.find(({ name: n }) => n === k);
const updateEnv = (k, v) => (findEnv.value = v);
// Enviornment variables
@ -109,7 +130,8 @@ function createServerDeploy(serverSpec) {
deployYaml.spec.template.spec.volumes.find(
({ name }) => name === "datadir",
).persistentVolumeClaim.claimName = `mcl-${name}-volume`;
deployYaml.spec.template.spec.containers[0] = serverContainer;
deployYaml.spec.template.spec.containers.push(serverContainer);
deployYaml.spec.template.spec.containers.push(ftpContainer);
return deployYaml;
}
@ -123,6 +145,20 @@ function createServerService(serverSpec) {
serviceYaml.metadata.annotations["minecluster.dunemask.net/server-name"] =
name;
serviceYaml.spec.selector.app = `mcl-${name}-app`;
// 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}` });
serviceYaml.spec.ports = ftpPortList.map(({ p: port, n: name }) => ({
port,
name,
protocol: "TCP",
targetPort: port,
}));
return serviceYaml;
}

View file

@ -24,31 +24,27 @@ export default async function deleteServer(req, res) {
const { name } = serverSpec;
// Ensure deployment exists
const server = await getServerAssets(name);
if (!server) return res.status(404).send("No Resources for that server were found!");
if (!server)
return res.status(404).send("No Resources for that server were found!");
// Delete in reverse order
const deleteDeploy = deleteOnExist(
server.deployment,
(name) => k8sDeps.deleteNamespacedDeployment(name, namespace),
const deleteDeploy = deleteOnExist(server.deployment, (name) =>
k8sDeps.deleteNamespacedDeployment(name, namespace),
);
const deleteService = deleteOnExist(
server.service,
(name) => k8sCore.deleteNamespacedService(name, namespace),
const deleteService = deleteOnExist(server.service, (name) =>
k8sCore.deleteNamespacedService(name, namespace),
);
const deleteRconService = deleteOnExist(
server.rconService,
(name) => k8sCore.deleteNamespacedService(name, namespace),
const deleteRconService = deleteOnExist(server.rconService, (name) =>
k8sCore.deleteNamespacedService(name, namespace),
);
if(deleteDeploy) await deleteDeploy.catch(deleteError(res))
if (deleteDeploy) await deleteDeploy.catch(deleteError(res));
const deleteRconSecret = deleteOnExist(
server.rconSecret,
(name) => k8sCore.deleteNamespacedSecret(name, namespace),
const deleteRconSecret = deleteOnExist(server.rconSecret, (name) =>
k8sCore.deleteNamespacedSecret(name, namespace),
);
const deleteVolume = deleteOnExist(
server.volume,
(name) => k8sCore.deleteNamespacedPersistentVolumeClaim(name, namespace),
const deleteVolume = deleteOnExist(server.volume, (name) =>
k8sCore.deleteNamespacedPersistentVolumeClaim(name, namespace),
);
Promise.all([

35
lib/k8s/server-files.js Normal file
View file

@ -0,0 +1,35 @@
import ftp from "basic-ftp";
import { ERR } from "../util/logging.js";
import { getServerAssets } from "./k8s-server-control.js";
const namespace = process.env.MCL_SERVER_NAMESPACE;
export async function listFiles(req, res) {
const serverSpec = req.body;
if (!serverSpec) return res.sendStatus(400);
if (!serverSpec.name) return res.status(400).send("Server name required!");
const { name } = serverSpec;
const server = await getServerAssets(name);
if (!server)
return res.status(404).send("No Resources for that server were found!");
if (!server.service)
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.close();
}

View file

@ -0,0 +1,7 @@
import { Router, json as jsonMiddleware } from "express";
import { listFiles } from "../k8s/server-files.js";
const router = Router();
router.use(jsonMiddleware());
router.post("/list", listFiles);
export default router;

View file

@ -5,6 +5,7 @@ import express from "express";
import vitals from "../routes/vitals-route.js";
import systemRoute from "../routes/system-route.js";
import serverRoute from "../routes/server-route.js";
import filesRoute from "../routes/files-route.js";
import reactRoute from "../routes/react-route.js";
import {
logErrors,
@ -23,6 +24,7 @@ export default function buildRoutes(pg, skio) {
// Routes
router.use("/api/system", systemRoute);
router.use("/api/server", serverRoute);
router.use("/api/files", filesRoute);
router.use(["/mcl", "/mcl/*"], reactRoute); // Static Build Route
/*router.use(logErrors);
router.use(clientErrorHandler);

9
package-lock.json generated
View file

@ -11,6 +11,7 @@
"dependencies": {
"@kubernetes/client-node": "^0.20.0",
"aws-sdk": "^2.1514.0",
"basic-ftp": "^5.0.4",
"bcrypt": "^5.1.1",
"chalk": "^5.3.0",
"express": "^4.18.2",
@ -3491,6 +3492,14 @@
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/basic-ftp": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.4.tgz",
"integrity": "sha512-8PzkB0arJFV4jJWSGOYR+OEic6aeKMu/osRhBULN6RY0ykby6LKhbmuQ5ublvaas5BOwboah5D87nrHyuh8PPA==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/bcrypt": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",

View file

@ -40,6 +40,7 @@
"dependencies": {
"@kubernetes/client-node": "^0.20.0",
"aws-sdk": "^2.1514.0",
"basic-ftp": "^5.0.4",
"bcrypt": "^5.1.1",
"chalk": "^5.3.0",
"express": "^4.18.2",