Update Platforms #390
2
.github/workflows/testProject.yml
vendored
2
.github/workflows/testProject.yml
vendored
@ -38,4 +38,4 @@ jobs:
|
||||
run: npm ci
|
||||
|
||||
- name: Test
|
||||
run: npm run test:host -- --exit-on-error --show-return
|
||||
run: npm run test -- --exit-on-error --show-return
|
||||
|
@ -16,8 +16,8 @@
|
||||
"scripts": {
|
||||
"dev": "nodemon",
|
||||
"build": "run-s build:*",
|
||||
"test:host": "node testProject.js",
|
||||
"test": "docker build . -f test.Dockerfile",
|
||||
"test": "node testProject.js",
|
||||
"test:docker": "docker build . -f test.Dockerfile",
|
||||
"build:cjs": "tsc --outDir dist/cjs --module commonjs",
|
||||
"build:esm": "tsc --outDir dist/esm --module es6 && node -e 'const fs = require(\"fs\");const path = require(\"path\");const read = (pathRe) => {for (const fileFolde of fs.readdirSync(pathRe)) {const filePath = path.join(pathRe, fileFolde);if (fs.statSync(filePath).isDirectory()) read(filePath);else {console.log(filePath, \"-->\", filePath.replace(/\\.js$/, \".mjs\"));fs.renameSync(filePath, filePath.replace(/\\.js$/, \".mjs\"));}}};read(\"dist/esm\");'"
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as bdsTypes from "./globalType";
|
||||
import * as platform from "./platform/index";
|
||||
import platform from "./platform/index";
|
||||
|
||||
export default DownloadServer;
|
||||
export async function DownloadServer(Platform: bdsTypes.Platform, Version: string|boolean): Promise<{Version: string, Date: Date, url: string}> {
|
||||
|
@ -72,6 +72,7 @@ export async function startServer(): Promise<BdsSession> {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Player
|
||||
onLog.on("all", data => {
|
||||
if (/r\s+.*\:\s+.*\,\s+xuid\:\s+.*/gi.test(data)) {
|
||||
|
@ -2,22 +2,29 @@ import * as bedrock from "./bedrock/index";
|
||||
import * as pocketmine from "./pocketmine/index";
|
||||
import * as java from "./java/index";
|
||||
import * as spigot from "./spigot/index";
|
||||
import { BdsSession } from "../globalType";
|
||||
|
||||
type globalPlatform = {
|
||||
[platform: string]: {
|
||||
DownloadServer: (version: string) => Promise<{version: string, publishDate: Date, url: string}>,
|
||||
/**
|
||||
* Download Server (and ¹auto install).
|
||||
*
|
||||
* 1: **In java server required java installation (if not installed, it will install it)**
|
||||
*/
|
||||
DownloadServer: (version: string|boolean) => Promise<{version: string, publishDate: Date, url: string}>,
|
||||
server: {
|
||||
/** get server session */
|
||||
startServer: () => Promise<BdsSession>,
|
||||
/** Get all Server Sessions */
|
||||
getSessions: () => {[sessionId: string]: BdsSession}
|
||||
},
|
||||
backup: {
|
||||
/** Create Platform Backup, and return Buffer zip File */
|
||||
CreateBackup: () => Promise<Buffer>,
|
||||
}
|
||||
/** Restore Zip Backup, this option replace local files */
|
||||
RestoreBackup: (buffer: Buffer) => Promise<void>,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
bedrock.backup.CreateBackup
|
||||
|
||||
const platforms: globalPlatform = {
|
||||
bedrock,
|
||||
pocketmine,
|
||||
java,
|
||||
spigot
|
||||
}
|
||||
export default platforms
|
||||
export default {bedrock, java, pocketmine, spigot} as globalPlatform;
|
40
src/platform/java/backup.ts
Normal file
40
src/platform/java/backup.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import * as fsOld from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import admZip from "adm-zip";
|
||||
import { serverRoot } from "../../pathControl";
|
||||
const javaPath = path.join(serverRoot, "java");
|
||||
|
||||
const filesFoldertoIgnore = ["Server.jar", "eula.txt", "libraries", "logs", "usercache.json", "versions"];
|
||||
|
||||
/**
|
||||
* Create backup for Worlds and Settings
|
||||
*/
|
||||
export async function CreateBackup(): Promise<Buffer> {
|
||||
if (!(fsOld.existsSync(javaPath))) throw new Error("Install server");
|
||||
const filesLint = (await fs.readdir(javaPath)).filter(file => !(filesFoldertoIgnore.some(folder => folder === file)));
|
||||
const zip = new admZip();
|
||||
for (const file of filesLint) {
|
||||
const filePath = path.join(javaPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
if (stats.isSymbolicLink()) {
|
||||
const realPath = await fs.realpath(filePath);
|
||||
const realStats = await fs.stat(realPath);
|
||||
if (realStats.isDirectory()) zip.addLocalFolder(realPath, file);
|
||||
else zip.addLocalFile(realPath, file);
|
||||
} else if (stats.isDirectory()) zip.addLocalFolder(filePath);
|
||||
else zip.addLocalFile(filePath);
|
||||
}
|
||||
return zip.toBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore backup for Worlds and Settings
|
||||
*
|
||||
* WARNING: This will overwrite existing files and World folder files
|
||||
*/
|
||||
export async function RestoreBackup(zipBuffer: Buffer): Promise<void> {
|
||||
const zip = new admZip(zipBuffer);
|
||||
await new Promise((resolve, reject) => zip.extractAllToAsync(javaPath, true, true, (err) => !!err ? reject(err) : resolve("")));
|
||||
return;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import * as server from "./server";
|
||||
import * as backup from "./backup";
|
||||
import DownloadServer from "./download";
|
||||
export {server, DownloadServer};
|
||||
export {server, DownloadServer, backup};
|
40
src/platform/pocketmine/backup.ts
Normal file
40
src/platform/pocketmine/backup.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import * as fsOld from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import admZip from "adm-zip";
|
||||
import { serverRoot } from '../../pathControl';
|
||||
const javaPath = path.join(serverRoot, "pocketmine");
|
||||
|
||||
const filesFoldertoIgnore = [];
|
||||
|
||||
/**
|
||||
* Create backup for Worlds and Settings
|
||||
*/
|
||||
export async function CreateBackup(): Promise<Buffer> {
|
||||
if (!(fsOld.existsSync(javaPath))) throw new Error("Install server");
|
||||
const filesLint = (await fs.readdir(javaPath)).filter(file => !(filesFoldertoIgnore.some(folder => folder === file)));
|
||||
const zip = new admZip();
|
||||
for (const file of filesLint) {
|
||||
const filePath = path.join(javaPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
if (stats.isSymbolicLink()) {
|
||||
const realPath = await fs.realpath(filePath);
|
||||
const realStats = await fs.stat(realPath);
|
||||
if (realStats.isDirectory()) zip.addLocalFolder(realPath, file);
|
||||
else zip.addLocalFile(realPath, file);
|
||||
} else if (stats.isDirectory()) zip.addLocalFolder(filePath);
|
||||
else zip.addLocalFile(filePath);
|
||||
}
|
||||
return zip.toBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore backup for Worlds and Settings
|
||||
*
|
||||
* WARNING: This will overwrite existing files and World folder files
|
||||
*/
|
||||
export async function RestoreBackup(zipBuffer: Buffer): Promise<void> {
|
||||
const zip = new admZip(zipBuffer);
|
||||
await new Promise((resolve, reject) => zip.extractAllToAsync(javaPath, true, true, (err) => !!err ? reject(err) : resolve("")));
|
||||
return;
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import * as addons from "./addons";
|
||||
import * as config from "./config";
|
||||
import * as server from "./server";
|
||||
import * as backup from "./backup";
|
||||
import DownloadServer from "./download";
|
||||
export {addons, config, server, DownloadServer};
|
||||
export {addons, config, server, DownloadServer, backup};
|
@ -12,25 +12,6 @@ import { createZipBackup } from "../../backup/zip";
|
||||
const pocketmineSesions: {[key: string]: BdsSession} = {};
|
||||
export function getSessions() {return pocketmineSesions;}
|
||||
|
||||
export function parseUserAction(data: string): {player: string; action: "connect"|"disconnect"|"unknown"; date: Date; xuid?: string;}|void {
|
||||
if (/\[.*\]:\s+(.*)\s+(.*)\s+the\s+game/gi.test(data)) {
|
||||
const actionDate = new Date();
|
||||
const [action, player] = (data.match(/[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi)||[]).slice(1, 3);
|
||||
const __PlayerAction: {player: string, action: "connect"|"disconnect"|"unknown"} = {
|
||||
player: player,
|
||||
action: "unknown"
|
||||
};
|
||||
if (action === "joined") __PlayerAction.action = "connect";
|
||||
else if (action === "left") __PlayerAction.action = "disconnect";
|
||||
return {
|
||||
player: __PlayerAction.player,
|
||||
action: __PlayerAction.action,
|
||||
date: actionDate
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const ServerPath = path.join(serverRoot, "pocketmine");
|
||||
export async function startServer(): Promise<BdsSession> {
|
||||
const SessionID = crypto.randomUUID();
|
||||
@ -71,16 +52,18 @@ export async function startServer(): Promise<BdsSession> {
|
||||
} = {};
|
||||
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
|
||||
|
||||
// Port
|
||||
// Port listen
|
||||
onLog.on("all", data => {
|
||||
// [16:49:31.284] [Server thread/INFO]: Minecraft network interface running on [::]:19133
|
||||
// [16:49:31.273] [Server thread/INFO]: Minecraft network interface running on 0.0.0.0:19132
|
||||
if (/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(.*)/gi.test(data)) {
|
||||
const portParse = data.match(/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(.*)/)[1];
|
||||
if (!!portParse) {
|
||||
if (/\[.*\]/.test(portParse)) {
|
||||
if (/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+.*/gi.test(data)) {
|
||||
const matchString = data.match(/\[.*\]:\s+Minecraft\s+network\s+interface\s+running\s+on\s+(.*)/);
|
||||
if (!!matchString) {
|
||||
const portParse = matchString[1];
|
||||
const isIpv6 = /\[.*\]/.test(portParse);
|
||||
if (isIpv6) {
|
||||
ports.push({
|
||||
port: parseInt(portParse.match(/\[.*\]:\s+(.*)/)[1]),
|
||||
port: parseInt(portParse.replace(/\[.*\]:/, "").trim()),
|
||||
version: "IPv6"
|
||||
});
|
||||
} else {
|
||||
@ -92,7 +75,8 @@ export async function startServer(): Promise<BdsSession> {
|
||||
}
|
||||
}
|
||||
});
|
||||
// Player
|
||||
|
||||
// Player Actions
|
||||
onLog.on("all", data => {
|
||||
if (/\[.*\]:\s+(.*)\s+(.*)\s+the\s+game/gi.test(data)) {
|
||||
const actionDate = new Date();
|
||||
|
40
src/platform/spigot/backup.ts
Normal file
40
src/platform/spigot/backup.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import * as fsOld from "node:fs";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import admZip from "adm-zip";
|
||||
import { serverRoot } from '../../pathControl';
|
||||
const javaPath = path.join(serverRoot, "java");
|
||||
|
||||
const filesFoldertoIgnore = [];
|
||||
|
||||
/**
|
||||
* Create backup for Worlds and Settings
|
||||
*/
|
||||
export async function CreateBackup(): Promise<Buffer> {
|
||||
if (!(fsOld.existsSync(javaPath))) throw new Error("Install server");
|
||||
const filesLint = (await fs.readdir(javaPath)).filter(file => !(filesFoldertoIgnore.some(folder => folder === file)));
|
||||
const zip = new admZip();
|
||||
for (const file of filesLint) {
|
||||
const filePath = path.join(javaPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
if (stats.isSymbolicLink()) {
|
||||
const realPath = await fs.realpath(filePath);
|
||||
const realStats = await fs.stat(realPath);
|
||||
if (realStats.isDirectory()) zip.addLocalFolder(realPath, file);
|
||||
else zip.addLocalFile(realPath, file);
|
||||
} else if (stats.isDirectory()) zip.addLocalFolder(filePath);
|
||||
else zip.addLocalFile(filePath);
|
||||
}
|
||||
return zip.toBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore backup for Worlds and Settings
|
||||
*
|
||||
* WARNING: This will overwrite existing files and World folder files
|
||||
*/
|
||||
export async function RestoreBackup(zipBuffer: Buffer): Promise<void> {
|
||||
const zip = new admZip(zipBuffer);
|
||||
await new Promise((resolve, reject) => zip.extractAllToAsync(javaPath, true, true, (err) => !!err ? reject(err) : resolve("")));
|
||||
return;
|
||||
}
|
@ -1,2 +1,4 @@
|
||||
import * as server from "./server";
|
||||
import * as backup from "./backup";
|
||||
import DownloadServer from "./download";
|
||||
export {DownloadServer};
|
||||
export {server, backup, DownloadServer};
|
154
src/platform/spigot/server.ts
Normal file
154
src/platform/spigot/server.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import events from "events";
|
||||
import crypto from "crypto";
|
||||
import node_cron from "cron";
|
||||
import * as child_process from "../../childProcess";
|
||||
import { backupRoot, serverRoot } from "../../pathControl";
|
||||
import { BdsSession, bdsSessionCommands } from "../../globalType";
|
||||
import { gitBackup, gitBackupOption } from "../../backup/git";
|
||||
import { createZipBackup } from "../../backup/zip";
|
||||
|
||||
const javaSesions: {[key: string]: BdsSession} = {};
|
||||
export function getSessions() {return javaSesions;}
|
||||
|
||||
const ServerPath = path.join(serverRoot, "spigot");
|
||||
export async function startServer(): Promise<BdsSession> {
|
||||
const SessionID = crypto.randomUUID();
|
||||
// Start Server
|
||||
const serverEvents = new events();
|
||||
const StartDate = new Date();
|
||||
const ServerProcess = await child_process.execServer({runOn: "host"}, "java", ["-jar", "Server.jar"], {cwd: ServerPath});
|
||||
const { onExit } = ServerProcess;
|
||||
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
|
||||
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
|
||||
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
|
||||
const uid = crypto.randomUUID();
|
||||
playerCallbacks[uid] = {callback: callback};
|
||||
return uid;
|
||||
};
|
||||
|
||||
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
|
||||
const playersConnections: {[player: string]: {action: "connect"|"disconnect"|"unknown", date: Date, history: Array<{action: "connect"|"disconnect"|"unknown", date: Date}>}} = {};
|
||||
|
||||
// Parse ports
|
||||
onLog.on("all", data => {
|
||||
const portParse = data.match(/Starting\s+Minecraft\s+server\s+on\s+(.*)\:(\d+)/);
|
||||
if (!!portParse) {
|
||||
ports.push({
|
||||
port: parseInt(portParse[2]),
|
||||
version: "IPv4/IPv6"
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Run Command
|
||||
const serverCommands: bdsSessionCommands = {
|
||||
/**
|
||||
* Run any commands in server.
|
||||
* @param command - Run any commands in server without parse commands
|
||||
* @returns - Server commands
|
||||
*/
|
||||
execCommand: (...command) => {
|
||||
ServerProcess.writelf(command.map(a => String(a)).join(" "));
|
||||
return serverCommands;
|
||||
},
|
||||
tpPlayer: (player: string, x: number, y: number, z: number) => {
|
||||
serverCommands.execCommand("tp", player, x, y, z);
|
||||
return serverCommands;
|
||||
},
|
||||
worldGamemode: (gamemode: "survival"|"creative"|"hardcore") => {
|
||||
serverCommands.execCommand("gamemode", gamemode);
|
||||
return serverCommands;
|
||||
},
|
||||
userGamemode: (player: string, gamemode: "survival"|"creative"|"hardcore") => {
|
||||
serverCommands.execCommand("gamemode", gamemode, player);
|
||||
return serverCommands;
|
||||
},
|
||||
stop: (): Promise<number|null> => {
|
||||
if (ServerProcess.Exec.exitCode !== null||ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
|
||||
if (ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
|
||||
ServerProcess.writelf("stop");
|
||||
return ServerProcess.onExit();
|
||||
}
|
||||
}
|
||||
|
||||
const backupCron = (crontime: string|Date, option?: {type: "git"; config: gitBackupOption}|{type: "zip", config?: {pathZip?: string}}): node_cron.CronJob => {
|
||||
// Validate Config
|
||||
if (option) {
|
||||
if (option.type === "git") {
|
||||
if (!option.config) throw new Error("Config is required");
|
||||
} else if (option.type === "zip") {}
|
||||
else option = {type: "zip"};
|
||||
}
|
||||
async function lockServerBackup() {
|
||||
serverCommands.execCommand("save hold");
|
||||
await new Promise(accept => setTimeout(accept, 1000));
|
||||
serverCommands.execCommand("save query");
|
||||
await new Promise(accept => setTimeout(accept, 1000));
|
||||
}
|
||||
async function unLockServerBackup() {
|
||||
serverCommands.execCommand("save resume");
|
||||
await new Promise(accept => setTimeout(accept, 1000));
|
||||
}
|
||||
if (!option) option = {type: "zip"};
|
||||
const CrontimeBackup = new node_cron.CronJob(crontime, async () => {
|
||||
if (option.type === "git") {
|
||||
await lockServerBackup();
|
||||
await gitBackup(option.config).catch(() => undefined).then(() => unLockServerBackup());
|
||||
} else if (option.type === "zip") {
|
||||
await lockServerBackup();
|
||||
if (!!option?.config?.pathZip) await createZipBackup({path: path.resolve(backupRoot, option?.config?.pathZip)}).catch(() => undefined);
|
||||
else await createZipBackup(true).catch(() => undefined);
|
||||
await unLockServerBackup();
|
||||
}
|
||||
});
|
||||
CrontimeBackup.start();
|
||||
onExit().catch(() => null).then(() => CrontimeBackup.stop());
|
||||
return CrontimeBackup;
|
||||
}
|
||||
|
||||
// Session log
|
||||
const logFile = path.resolve(process.env.LOG_PATH||path.resolve(ServerPath, "../log"), `bedrock_${SessionID}.log`);
|
||||
if(!(fs.existsSync(path.parse(logFile).dir))) fs.mkdirSync(path.parse(logFile).dir, {recursive: true});
|
||||
const logStream = fs.createWriteStream(logFile, {flags: "w+"});
|
||||
logStream.write(`[${StartDate.toString()}] Server started\n\n`);
|
||||
ServerProcess.Exec.stdout.pipe(logStream);
|
||||
ServerProcess.Exec.stderr.pipe(logStream);
|
||||
|
||||
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
|
||||
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
|
||||
|
||||
// Session Object
|
||||
const Seesion: BdsSession = {
|
||||
id: SessionID,
|
||||
startDate: StartDate,
|
||||
creteBackup: backupCron,
|
||||
onExit: onExit,
|
||||
onPlayer: onPlayer,
|
||||
ports: () => ports,
|
||||
getPlayer: () => playersConnections,
|
||||
server: {
|
||||
on: serverOn,
|
||||
once: serverOnce
|
||||
},
|
||||
seed: undefined,
|
||||
started: false,
|
||||
addonManeger: undefined,
|
||||
log: onLog,
|
||||
commands: serverCommands,
|
||||
};
|
||||
|
||||
onLog.on("all", lineData => {
|
||||
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
|
||||
if (/\[.*\].*\s+Done\s+\(.*\)\!.*/.test(lineData)) {
|
||||
Seesion.started = true;
|
||||
serverEvents.emit("started", new Date());
|
||||
}
|
||||
});
|
||||
|
||||
// Return Session
|
||||
javaSesions[SessionID] = Seesion;
|
||||
onExit().catch(() => null).then(() => delete javaSesions[SessionID]);
|
||||
return Seesion;
|
||||
}
|
164
src/server.ts
164
src/server.ts
@ -1,16 +1,5 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import events from "events";
|
||||
import crypto from "crypto";
|
||||
import node_cron from "cron";
|
||||
import * as platformManeger from "./platform";
|
||||
import * as child_process from "./childProcess";
|
||||
import { parseConfig as serverConfigParse } from "./serverConfig";
|
||||
import * as worldManeger from "./worldManeger";
|
||||
import platformManeger from "./platform";
|
||||
import * as bdsTypes from "./globalType";
|
||||
import { backupRoot, serverRoot } from "./pathControl";
|
||||
import { gitBackup, gitBackupOption } from "./backup/git";
|
||||
import { createZipBackup } from "./backup/zip";
|
||||
|
||||
// Server Sessions
|
||||
const Sessions: {[Session: string]: bdsTypes.BdsSession} = {};
|
||||
@ -23,156 +12,9 @@ export function getSessions(): {[SessionID: string]: bdsTypes.BdsSession} {retur
|
||||
// Start Server
|
||||
export default Start;
|
||||
export async function Start(Platform: bdsTypes.Platform, options?: bdsTypes.startServerOptions): Promise<bdsTypes.BdsSession> {
|
||||
const SessionID = crypto.randomUUID();
|
||||
const ServerPath = path.join(serverRoot, Platform);
|
||||
if (!(fs.existsSync(ServerPath))) fs.mkdirSync(ServerPath, {recursive: true});
|
||||
const Process: {command: string; args: Array<string>; env: {[env: string]: string};} = {
|
||||
command: "",
|
||||
args: [],
|
||||
env: {}
|
||||
};
|
||||
if (Platform === "bedrock") return platformManeger.bedrock.server.startServer();
|
||||
else if (Platform === "java") return platformManeger.java.server.startServer();
|
||||
else if (Platform === "pocketmine") return platformManeger.pocketmine.server.startServer();
|
||||
else if (Platform === "spigot") {
|
||||
Process.command = "java";
|
||||
Process.args.push("-jar");
|
||||
Process.args.push(path.resolve(ServerPath, "Spigot.jar"));
|
||||
}
|
||||
|
||||
if (options?.storageOnlyWorlds) {
|
||||
await worldManeger.storageWorld(Platform, ServerPath, (await serverConfigParse(Platform)).world);
|
||||
}
|
||||
|
||||
// Start Server
|
||||
const serverEvents = new events();
|
||||
const StartDate = new Date();
|
||||
const ServerProcess = await child_process.execServer({runOn: "host"}, Process.command, Process.args, {env: Process.env, cwd: ServerPath});
|
||||
const { onExit } = ServerProcess;
|
||||
const onLog = {on: ServerProcess.on, once: ServerProcess.once};
|
||||
const playerCallbacks: {[id: string]: {callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void}} = {};
|
||||
const onPlayer = (callback: (data: {player: string; action: "connect"|"disconnect"|"unknown"; date: Date;}) => void) => {
|
||||
const uid = crypto.randomUUID();
|
||||
playerCallbacks[uid] = {callback: callback};
|
||||
return uid;
|
||||
};
|
||||
|
||||
const playersConnections: {
|
||||
[player: string]: {
|
||||
action: "connect"|"disconnect"|"unknown";
|
||||
date: Date;
|
||||
history: Array<{
|
||||
action: "connect"|"disconnect"|"unknown";
|
||||
date: Date
|
||||
}>
|
||||
}
|
||||
} = {};
|
||||
const ports: Array<{port: number; protocol?: "TCP"|"UDP"; version?: "IPv4"|"IPv6"|"IPv4/IPv6";}> = [];
|
||||
|
||||
// Run Command
|
||||
const serverCommands: bdsTypes.bdsSessionCommands = {
|
||||
/**
|
||||
* Run any commands in server.
|
||||
* @param command - Run any commands in server without parse commands
|
||||
* @returns - Server commands
|
||||
*/
|
||||
execCommand: (...command) => {
|
||||
ServerProcess.Exec.stdin.write(command.map(a => String(a)).join(" ")+"\n");
|
||||
return serverCommands;
|
||||
},
|
||||
tpPlayer: (player: string, x: number, y: number, z: number) => {
|
||||
serverCommands.execCommand("tp", player, x, y, z);
|
||||
return serverCommands;
|
||||
},
|
||||
worldGamemode: (gamemode: "survival"|"creative"|"hardcore") => {
|
||||
if (Platform === "spigot"||Platform === "pocketmine") serverCommands.execCommand("gamemode", gamemode);
|
||||
return serverCommands;
|
||||
},
|
||||
userGamemode: (player: string, gamemode: "survival"|"creative"|"hardcore") => {
|
||||
if (Platform === "spigot"||Platform === "pocketmine") serverCommands.execCommand("gamemode", gamemode, player);
|
||||
return serverCommands;
|
||||
},
|
||||
stop: (): Promise<number|null> => {
|
||||
if (ServerProcess.Exec.exitCode !== null||ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
|
||||
if (Platform === "spigot"||Platform === "pocketmine") serverCommands.execCommand("stop");
|
||||
else ServerProcess.Exec.kill();
|
||||
if (ServerProcess.Exec.killed) return Promise.resolve(ServerProcess.Exec.exitCode);
|
||||
return ServerProcess.onExit();
|
||||
}
|
||||
}
|
||||
|
||||
const backupCron = (crontime: string|Date, option?: {type: "git"; config: gitBackupOption}|{type: "zip", config?: {pathZip?: string}}): node_cron.CronJob => {
|
||||
// Validate Config
|
||||
if (option) {
|
||||
if (option.type === "git") {
|
||||
if (!option.config) throw new Error("Config is required");
|
||||
} else if (option.type === "zip") {}
|
||||
else option = {type: "zip"};
|
||||
}
|
||||
async function lockServerBackup() {
|
||||
if (Platform === "bedrock") {
|
||||
serverCommands.execCommand("save hold");
|
||||
await new Promise(accept => setTimeout(accept, 1000));
|
||||
serverCommands.execCommand("save query");
|
||||
await new Promise(accept => setTimeout(accept, 1000));
|
||||
}
|
||||
}
|
||||
async function unLockServerBackup() {
|
||||
if (Platform === "bedrock") {
|
||||
serverCommands.execCommand("save resume");
|
||||
await new Promise(accept => setTimeout(accept, 1000));
|
||||
}
|
||||
}
|
||||
if (!option) option = {type: "zip"};
|
||||
const CrontimeBackup = new node_cron.CronJob(crontime, async () => {
|
||||
if (option.type === "git") {
|
||||
await lockServerBackup();
|
||||
await gitBackup(option.config).catch(() => undefined).then(() => unLockServerBackup());
|
||||
} else if (option.type === "zip") {
|
||||
await lockServerBackup();
|
||||
if (!!option?.config?.pathZip) await createZipBackup({path: path.resolve(backupRoot, option?.config?.pathZip)}).catch(() => undefined);
|
||||
else await createZipBackup(true).catch(() => undefined);
|
||||
await unLockServerBackup();
|
||||
}
|
||||
});
|
||||
CrontimeBackup.start();
|
||||
onExit().catch(() => null).then(() => CrontimeBackup.stop());
|
||||
return CrontimeBackup;
|
||||
}
|
||||
|
||||
// Session log
|
||||
const logFile = path.resolve(process.env.LOG_PATH||path.resolve(ServerPath, "../log"), `${Platform}_${SessionID}.log`);
|
||||
if(!(fs.existsSync(path.parse(logFile).dir))) fs.mkdirSync(path.parse(logFile).dir, {recursive: true});
|
||||
const logStream = fs.createWriteStream(logFile, {flags: "w+"});
|
||||
logStream.write(`[${StartDate.toString()}] Server started\n\n`);
|
||||
ServerProcess.Exec.stdout.pipe(logStream);
|
||||
ServerProcess.Exec.stderr.pipe(logStream);
|
||||
|
||||
const serverOn = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.on(act, call);
|
||||
const serverOnce = (act: "started" | "ban", call: (...any: any[]) => void) => serverEvents.once(act, call);
|
||||
|
||||
// Session Object
|
||||
const Seesion: bdsTypes.BdsSession = {
|
||||
id: SessionID,
|
||||
startDate: StartDate,
|
||||
creteBackup: backupCron,
|
||||
onExit: onExit,
|
||||
onPlayer: onPlayer,
|
||||
ports: () => ports,
|
||||
getPlayer: () => playersConnections,
|
||||
server: {
|
||||
on: serverOn,
|
||||
once: serverOnce
|
||||
},
|
||||
seed: undefined,
|
||||
started: false,
|
||||
addonManeger: undefined,
|
||||
log: onLog,
|
||||
commands: serverCommands,
|
||||
};
|
||||
|
||||
// Return Session
|
||||
Sessions[SessionID] = Seesion;
|
||||
onExit().catch(() => null).then(() => delete Sessions[SessionID]);
|
||||
return Seesion;
|
||||
else if (Platform === "spigot") return platformManeger.spigot.server.startServer();
|
||||
throw new Error(`Platform ${Platform} is not supported`);
|
||||
}
|
Reference in New Issue
Block a user