[FEATURE] Storage adjustments and minor tweaks (#9)
Co-authored-by: Dunemask <dunemask@gmail.com> Reviewed-on: https://gitea.dunemask.dev/elysium/minecluster/pulls/9
This commit is contained in:
parent
3d73f69678
commit
43c4409498
13 changed files with 121 additions and 39 deletions
|
@ -15,8 +15,14 @@ const dnsRegex = new RegExp(
|
|||
|
||||
function backupPayloadFilter(req, res) {
|
||||
const serverSpec = req.body;
|
||||
const { backupHost, backupBucket, backupId, backupKey, backupInterval } =
|
||||
serverSpec;
|
||||
const {
|
||||
storage,
|
||||
backupHost,
|
||||
backupBucket,
|
||||
backupId,
|
||||
backupKey,
|
||||
backupInterval,
|
||||
} = serverSpec;
|
||||
// TODO: Impliment non creation time backups
|
||||
if (
|
||||
!!backupHost ||
|
||||
|
@ -25,6 +31,8 @@ function backupPayloadFilter(req, res) {
|
|||
!!backupKey ||
|
||||
!!backupInterval
|
||||
) {
|
||||
if (storage === 0)
|
||||
return res.status(400).send("Backups cannot be used if storage is zero!");
|
||||
// If any keys are required, all are required
|
||||
if (
|
||||
!(
|
||||
|
|
|
@ -7,6 +7,7 @@ CREATE TABLE servers (
|
|||
server_type varchar(63) DEFAULT 'VANILLA',
|
||||
cpu varchar(63) DEFAULT '500',
|
||||
memory varchar(63) DEFAULT '512',
|
||||
storage varchar(63) DEFAULT NULL,
|
||||
backup_enabled BOOLEAN DEFAULT FALSE,
|
||||
backup_host varchar(255) DEFAULT NULL,
|
||||
backup_bucket_path varchar(255) DEFAULT NULL,
|
||||
|
|
|
@ -20,7 +20,9 @@ export async function createServerEntry(serverSpec) {
|
|||
host,
|
||||
version,
|
||||
serverType: server_type,
|
||||
cpu, // TODO: Ignored for now by the K8S manifests
|
||||
memory,
|
||||
storage: storage_val,
|
||||
extraPorts: extra_ports,
|
||||
backupHost: backup_host,
|
||||
backupBucket: backup_bucket_path,
|
||||
|
@ -28,12 +30,15 @@ export async function createServerEntry(serverSpec) {
|
|||
backupKey: backup_key,
|
||||
backupInterval: backup_interval,
|
||||
} = serverSpec;
|
||||
|
||||
var q = insertQuery(table, {
|
||||
name,
|
||||
host,
|
||||
version,
|
||||
server_type,
|
||||
cpu, // TODO: Ignored for now by the K8S manifests
|
||||
memory,
|
||||
storage: !storage_val || storage_val === "0" ? null : storage_val, // 0, undefined, null, or "0" becomes null
|
||||
extra_ports,
|
||||
backup_enabled: !!backup_interval ? true : null, // We already verified the payload, so any backup key will work
|
||||
backup_host,
|
||||
|
@ -51,7 +56,9 @@ export async function createServerEntry(serverSpec) {
|
|||
host,
|
||||
version,
|
||||
server_type: serverType,
|
||||
cpu, // TODO: Ignored for now by the K8S manifests
|
||||
memory,
|
||||
storage,
|
||||
extra_ports: extraPorts,
|
||||
backup_enabled: backupEnabled,
|
||||
backup_host: backupHost,
|
||||
|
@ -68,7 +75,9 @@ export async function createServerEntry(serverSpec) {
|
|||
host,
|
||||
version,
|
||||
serverType,
|
||||
cpu, // TODO: Ignored for now by the K8S manifests
|
||||
memory,
|
||||
storage,
|
||||
extraPorts,
|
||||
backupEnabled,
|
||||
backupHost,
|
||||
|
@ -102,7 +111,9 @@ export async function getServerEntry(serverId) {
|
|||
host,
|
||||
version,
|
||||
server_type: serverType,
|
||||
cpu, // TODO: Ignored for now by the K8S manifests
|
||||
memory,
|
||||
storage,
|
||||
extra_ports: extraPorts,
|
||||
backup_enabled: backupEnabled,
|
||||
backup_host: backupHost,
|
||||
|
@ -119,7 +130,9 @@ export async function getServerEntry(serverId) {
|
|||
host,
|
||||
version,
|
||||
serverType,
|
||||
cpu, // TODO: Ignored for now by the K8S manifests
|
||||
memory,
|
||||
storage,
|
||||
extraPorts,
|
||||
backupEnabled,
|
||||
backupHost,
|
||||
|
@ -140,7 +153,9 @@ export async function modifyServerEntry(serverSpec) {
|
|||
host,
|
||||
version,
|
||||
serverType: server_type,
|
||||
cpu, // TODO: Ignored for now by the K8S manifests
|
||||
memory,
|
||||
// storage, // DO NOT INCLUDE THIS KEY, Not all storage providers in kubernetes allow for dynamically resizable PVCs
|
||||
extraPorts: extra_ports,
|
||||
backupEnabled: backup_enabled,
|
||||
backupHost: backup_host,
|
||||
|
@ -157,7 +172,9 @@ export async function modifyServerEntry(serverSpec) {
|
|||
host,
|
||||
version,
|
||||
server_type,
|
||||
cpu, // TODO: Ignored for now by the K8S manifests
|
||||
memory,
|
||||
// storage, // DO NOT INCLUDE THIS KEY, Not all storage providers in kubernetes allow for dynamically resizable PVCs
|
||||
extra_ports,
|
||||
backup_enabled,
|
||||
backup_host,
|
||||
|
|
|
@ -30,11 +30,13 @@ spec:
|
|||
# runAsUser: 1000
|
||||
terminationGracePeriodSeconds: 30
|
||||
volumes:
|
||||
- name: datadir
|
||||
persistentVolumeClaim:
|
||||
claimName: changeme-pvc-name
|
||||
- emptyDir: {}
|
||||
name: datadir
|
||||
- emptyDir: {}
|
||||
name: backupdir
|
||||
# - name: datadir
|
||||
# persistentVolumeClaim:
|
||||
# claimName: changeme-pvc-name
|
||||
# - name: rclone-config
|
||||
# secret:
|
||||
# defaultMode: 420
|
||||
|
|
|
@ -4,7 +4,7 @@ import yaml from "js-yaml";
|
|||
const loadYaml = (f) => yaml.load(fs.readFileSync(path.resolve(f), "utf8"));
|
||||
|
||||
export function getFtpContainer(serverSpec) {
|
||||
const { mclName } = serverSpec;
|
||||
const { mclName, storage } = serverSpec;
|
||||
const ftpContainer = loadYaml("lib/k8s/configs/containers/ftp-server.yml");
|
||||
ftpContainer.name = `mcl-${mclName}-ftp`;
|
||||
const ftpPortList = [
|
||||
|
@ -18,11 +18,12 @@ export function getFtpContainer(serverSpec) {
|
|||
name,
|
||||
protocol: "TCP",
|
||||
}));
|
||||
if (!storage) delete ftpContainer.volumeMounts;
|
||||
return ftpContainer;
|
||||
}
|
||||
|
||||
export function getCoreServerContainer(serverSpec) {
|
||||
const { mclName, version, serverType, memory } = serverSpec;
|
||||
const { mclName, version, serverType, memory, storage } = serverSpec;
|
||||
const container = loadYaml("lib/k8s/configs/containers/minecraft-server.yml");
|
||||
// Container Updates
|
||||
container.name = `mcl-${mclName}-server`;
|
||||
|
@ -38,12 +39,21 @@ export function getCoreServerContainer(serverSpec) {
|
|||
// RCON
|
||||
const rs = `mcl-${mclName}-rcon-secret`;
|
||||
findEnv("RCON_PASSWORD").valueFrom.secretKeyRef.name = rs;
|
||||
if (!storage) delete container.volumeMounts;
|
||||
return container;
|
||||
}
|
||||
|
||||
export function getServerContainer(serverSpec) {
|
||||
const { difficulty, gamemode, motd, maxPlayers, seed, ops, whitelist } =
|
||||
serverSpec;
|
||||
const {
|
||||
difficulty,
|
||||
gamemode,
|
||||
motd,
|
||||
maxPlayers,
|
||||
seed,
|
||||
ops,
|
||||
whitelist,
|
||||
storage,
|
||||
} = serverSpec;
|
||||
const container = getCoreServerContainer(serverSpec);
|
||||
|
||||
const findEnv = (k) => container.env.find(({ name: n }) => n === k);
|
||||
|
@ -57,12 +67,13 @@ export function getServerContainer(serverSpec) {
|
|||
updateEnv("SEED", seed);
|
||||
updateEnv("OPS", ops);
|
||||
updateEnv("WHITELIST", whitelist); */
|
||||
if (!storage) delete container.volumeMounts;
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
export function getBackupContainer(serverSpec) {
|
||||
const { mclName, backupEnabled, backupPath } = serverSpec;
|
||||
const { mclName, backupEnabled, backupPath, storage } = serverSpec;
|
||||
const container = loadYaml("lib/k8s/configs/containers/minecraft-backup.yml");
|
||||
if (!backupEnabled) return;
|
||||
const findEnv = (k) => container.env.find(({ name: n }) => n === k);
|
||||
|
@ -73,6 +84,7 @@ export function getBackupContainer(serverSpec) {
|
|||
// RCON
|
||||
const rs = `mcl-${mclName}-rcon-secret`;
|
||||
findEnv("RCON_PASSWORD").valueFrom.secretKeyRef.name = rs;
|
||||
if (!storage) delete container.volumeMounts;
|
||||
|
||||
return container;
|
||||
}
|
||||
|
|
|
@ -86,18 +86,19 @@ function createRconSecret(serverSpec) {
|
|||
}
|
||||
|
||||
function createServerVolume(serverSpec) {
|
||||
const { mclName, id } = serverSpec;
|
||||
const { mclName, id, storage } = serverSpec;
|
||||
if (!storage) return;
|
||||
const volumeYaml = loadYaml("lib/k8s/configs/server-pvc.yml");
|
||||
volumeYaml.metadata.labels.service = `mcl-${mclName}-server`;
|
||||
volumeYaml.metadata.name = `mcl-${mclName}-volume`;
|
||||
volumeYaml.metadata.namespace = namespace;
|
||||
volumeYaml.metadata.annotations["minecluster.dunemask.net/id"] = id;
|
||||
volumeYaml.spec.resources.requests.storage = "5Gi"; // TODO: Changeme
|
||||
volumeYaml.spec.resources.requests.storage = `${storage}Gi`;
|
||||
return volumeYaml;
|
||||
}
|
||||
|
||||
function createServerDeploy(serverSpec) {
|
||||
const { mclName, id, backupEnabled } = serverSpec;
|
||||
const { mclName, id, backupEnabled, storage } = serverSpec;
|
||||
const deployYaml = loadYaml("lib/k8s/configs/server-deployment.yml");
|
||||
const { metadata } = deployYaml;
|
||||
const serverContainer = getServerContainer(serverSpec);
|
||||
|
@ -119,9 +120,18 @@ function createServerDeploy(serverSpec) {
|
|||
id;
|
||||
|
||||
// Volumes
|
||||
deployYaml.spec.template.spec.volumes.find(
|
||||
({ name }) => name === "datadir",
|
||||
).persistentVolumeClaim.claimName = `mcl-${mclName}-volume`;
|
||||
if (!!storage) {
|
||||
const dvi = deployYaml.spec.template.spec.volumes.findIndex(
|
||||
({ name }) => name === "datadir",
|
||||
);
|
||||
delete deployYaml.spec.template.spec.volumes[dvi].emptyDir;
|
||||
deployYaml.spec.template.spec.volumes[dvi] = {
|
||||
...deployYaml.spec.template.spec.volumes[dvi],
|
||||
persistentVolumeClaim: {
|
||||
claimName: `mcl-${mclName}-volume`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Backups
|
||||
if (backupEnabled) {
|
||||
|
@ -194,9 +204,10 @@ export default async function createServerResources(createSpec) {
|
|||
const rconService = createRconService(createSpec);
|
||||
const extraService = createExtraService(createSpec);
|
||||
const serverResources = [];
|
||||
serverResources.push(
|
||||
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume),
|
||||
);
|
||||
if (!!serverVolume)
|
||||
serverResources.push(
|
||||
k8sCore.createNamespacedPersistentVolumeClaim(namespace, serverVolume),
|
||||
);
|
||||
if (!!extraService)
|
||||
serverResources.push(
|
||||
k8sCore.createNamespacedService(namespace, extraService),
|
||||
|
|
|
@ -6,7 +6,7 @@ export default function BackupHostOption(props) {
|
|||
<TextField
|
||||
label="Backup Host"
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
value={value ?? ""}
|
||||
helperText="Example: s3.mydomain.com"
|
||||
FormHelperTextProps={{ sx: { ml: 0 } }}
|
||||
required
|
||||
|
|
|
@ -5,7 +5,7 @@ export default function BackupIdOption(props) {
|
|||
return (
|
||||
<TextField
|
||||
label="S3 Access Key ID"
|
||||
value={value}
|
||||
value={value ?? ""}
|
||||
onChange={onChange}
|
||||
helperText="Example: s3-access-key-id"
|
||||
FormHelperTextProps={{ sx: { ml: 0 } }}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import TextField from "@mui/material/TextField";
|
||||
export default function BackupKeyOption(props) {
|
||||
const { onChange } = props;
|
||||
const { value, onChange } = props;
|
||||
|
||||
return (
|
||||
<TextField
|
||||
label="S3 Access Key"
|
||||
value={value ?? ""}
|
||||
onChange={onChange}
|
||||
helperText="Example: s3-access-key"
|
||||
FormHelperTextProps={{ sx: { ml: 0 } }}
|
||||
|
|
26
src/components/server-options/StorageOption.jsx
Normal file
26
src/components/server-options/StorageOption.jsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import TextField from "@mui/material/TextField";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
|
||||
const maxStorageSupported = 80;
|
||||
export const storageOptions = new Array(2 * maxStorageSupported)
|
||||
.fill(0)
|
||||
.map((v, i) => (i + 1) * 0.5);
|
||||
|
||||
export default function StorageOption(props) {
|
||||
const { value, onChange } = props;
|
||||
return (
|
||||
<TextField
|
||||
label="Storage"
|
||||
onChange={onChange}
|
||||
value={value ?? null}
|
||||
select
|
||||
required
|
||||
SelectProps={{ MenuProps: { sx: { maxHeight: "20rem" } } }}
|
||||
>
|
||||
<MenuItem value={0}>No Storage</MenuItem>
|
||||
{storageOptions.map((o, i) => (
|
||||
<MenuItem value={o} key={i}>{`${o} GB`}</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
);
|
||||
}
|
|
@ -22,7 +22,7 @@ export default class RconSocket {
|
|||
|
||||
onRconError(v) {
|
||||
this.rconLive = false;
|
||||
console.log("Server sent" + v);
|
||||
console.log("Server sent: ", v);
|
||||
}
|
||||
|
||||
onConnect() {
|
||||
|
|
|
@ -22,6 +22,7 @@ import MemoryOption, {
|
|||
memoryOptions,
|
||||
} from "@mcl/components/server-options/MemoryOption.jsx";
|
||||
import ExtraPortsOption from "@mcl/components/server-options/ExtraPortsOption.jsx";
|
||||
import StorageOption from "@mcl/components/server-options/StorageOption.jsx";
|
||||
|
||||
import BackupHostOption from "@mcl/components/server-options/BackupHostOption.jsx";
|
||||
import BackupBucketOption from "@mcl/components/server-options/BackupBucketOption.jsx";
|
||||
|
@ -36,6 +37,7 @@ const defaultServer = {
|
|||
serverType: serverTypeOptions[0],
|
||||
cpu: cpuOptions[0],
|
||||
memory: memoryOptions[2], // 1.5GB
|
||||
storage: 0,
|
||||
extraPorts: [],
|
||||
};
|
||||
|
||||
|
@ -99,20 +101,23 @@ export default function CreateCoreOptions() {
|
|||
/>
|
||||
<CpuOption value={spec.cpu} onChange={coreUpdate("cpu")} />
|
||||
<MemoryOption value={spec.memory} onChange={coreUpdate("memory")} />
|
||||
<StorageOption value={spec.storage} onChange={coreUpdate("storage")} />
|
||||
<ExtraPortsOption onChange={updateSpec} />
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={backupEnabled}
|
||||
onChange={toggleBackupEnabled}
|
||||
inputProps={{ "aria-label": "controlled" }}
|
||||
/>
|
||||
}
|
||||
label="Enable Backups?"
|
||||
labelPlacement="start"
|
||||
sx={{ mr: "auto" }}
|
||||
/>
|
||||
{backupEnabled && (
|
||||
{spec.storage !== 0 && (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={backupEnabled}
|
||||
onChange={toggleBackupEnabled}
|
||||
inputProps={{ "aria-label": "controlled" }}
|
||||
/>
|
||||
}
|
||||
label="Enable Backups?"
|
||||
labelPlacement="start"
|
||||
sx={{ mr: "auto" }}
|
||||
/>
|
||||
)}
|
||||
{backupEnabled && spec.storage !== 0 && (
|
||||
<FormControl
|
||||
fullWidth
|
||||
sx={{ mt: "2rem", display: "flex", gap: ".5rem" }}
|
||||
|
|
|
@ -35,6 +35,7 @@ export default function EditCoreOptions(props) {
|
|||
const { serverId } = props;
|
||||
const [spec, setSpec] = useState();
|
||||
const modifyServer = useModifyServer(spec);
|
||||
const nav = useNavigate();
|
||||
const { isLoading, data: serverBlueprint } = useGetServer(serverId);
|
||||
|
||||
useEffect(() => setSpec(serverBlueprint), [serverBlueprint]);
|
||||
|
@ -47,9 +48,7 @@ export default function EditCoreOptions(props) {
|
|||
|
||||
const coreUpdate = (attr) => (e) => updateSpec(attr, e.target.value);
|
||||
|
||||
const upsertSpec = () => {
|
||||
modifyServer(spec);
|
||||
};
|
||||
const upsertSpec = () => modifyServer().then(() => nav("/"));
|
||||
|
||||
const toggleBackupEnabled = () =>
|
||||
updateSpec("backupEnabled", !spec.backupEnabled);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue