V6 initial #507
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -10,7 +10,7 @@
|
||||
"**/node_modules/": true,
|
||||
// Ignore generate tsc files
|
||||
"**/dist/": true,
|
||||
"**/src/**/*.js": true,
|
||||
"**/src/**/*.d.ts": true,
|
||||
"**/src/**/*.js": false,
|
||||
"**/src/**/*.d.ts": false,
|
||||
}
|
||||
}
|
@@ -1,15 +0,0 @@
|
||||
import * as Bedrock from "./bedrock.js";
|
||||
|
||||
if (!(process.platform === "win32"||process.platform === "linux")) console.log("Bedrock disabled to %s, avaible only to Windows and Linux");
|
||||
else {
|
||||
describe("Bedrock", async function() {
|
||||
this.timeout(Infinity);
|
||||
let id: string;
|
||||
it("Install and Start", async () => {
|
||||
id = (await Bedrock.installServer({version: "latest", platformOptions: {newId: true}})).id as string
|
||||
const serverManeger = await Bedrock.startServer({id});
|
||||
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
|
||||
return serverManeger.waitExit();
|
||||
});
|
||||
});
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
import * as java from "./java.js";
|
||||
|
||||
describe("Java", function() {
|
||||
this.timeout(Infinity);
|
||||
let id: string;
|
||||
it("Install and Start", async () => {
|
||||
id = (await java.installServer("latest", {newId: true})).id as string
|
||||
const serverManeger = await java.startServer({
|
||||
platformOptions: {id}
|
||||
});
|
||||
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
|
||||
return serverManeger.waitExit();
|
||||
});
|
||||
});
|
@@ -1,12 +0,0 @@
|
||||
import {installServer, startServer} from "./paper.js";
|
||||
|
||||
describe("PaperMC", function() {
|
||||
this.timeout(Infinity);
|
||||
let id: string;
|
||||
it("Install and Start", async () => {
|
||||
id = (await installServer("latest", {newId: true})).id as string
|
||||
const serverManeger = await startServer({platformOptions: {id}});
|
||||
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
|
||||
return serverManeger.waitExit();
|
||||
});
|
||||
});
|
@@ -1,12 +0,0 @@
|
||||
import { installServer, startServer } from "./pocketmine.js";
|
||||
|
||||
describe("Pocketmine", function() {
|
||||
this.timeout(Infinity);
|
||||
let id: string;
|
||||
it("Install and Start", async () => {
|
||||
id = (await installServer("latest", {newId: true})).id as string
|
||||
const serverManeger = await startServer({id});
|
||||
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
|
||||
return serverManeger.waitExit();
|
||||
});
|
||||
});
|
@@ -1,12 +0,0 @@
|
||||
import { installServer, startServer } from "./pwnuukit.js";
|
||||
|
||||
describe("Powernukkit", function() {
|
||||
this.timeout(Infinity);
|
||||
let id: string;
|
||||
it("Install and Start", async () => {
|
||||
id = (await installServer("latest", {newId: true})).id as string
|
||||
const serverManeger = await startServer({platformOptions: {id}});
|
||||
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
|
||||
return serverManeger.waitExit();
|
||||
});
|
||||
});
|
@@ -1,12 +0,0 @@
|
||||
import * as spigot from "./spigot.js";
|
||||
|
||||
describe("Spigot", function (){
|
||||
this.timeout(Infinity);
|
||||
let id: string;
|
||||
it("Install and Start", async () => {
|
||||
id = (await spigot.installServer("latest", {newId: true})).id as string
|
||||
const serverManeger = await spigot.startServer({platformOptions: {id}});
|
||||
serverManeger.events.once("serverStarted", () => serverManeger.stopServer());
|
||||
return serverManeger.waitExit();
|
||||
});
|
||||
});
|
@@ -1,348 +0,0 @@
|
||||
import { manegerConfigProprieties } from "../configManipulate.js";
|
||||
import { randomPort } from "../lib/randomPort.js";
|
||||
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
|
||||
import * as globalPlatfroms from "../globalPlatfroms.js";
|
||||
import coreUtils from "@sirherobrine23/coreutils";
|
||||
import path from "node:path";
|
||||
import fsOld from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// RegExp
|
||||
const portListen = /\[.*\]\s+(IPv[46])\s+supported,\s+port:\s+([0-9]+)/;
|
||||
const started = /\[.*\]\s+Server\s+started\./;
|
||||
const playerActionsV1 = /\[.*\]\s+Player\s+((dis|)connected):\s+(.*),\s+xuid:\s+([0-9]+)/;
|
||||
const newPlayerActions = /\[.*INFO\]\s+Player\s+(Spawned|connected|disconnected):\s+([\s\S\w]+)\s+(xuid:\s+([0-9]+))?/;
|
||||
const fileSave = /^(worlds|server\.properties|((permissions|allowlist)\.json))$/;
|
||||
|
||||
type bedrockVersionJSON = {
|
||||
version: string,
|
||||
date: Date,
|
||||
release?: "stable"|"preview",
|
||||
url: {
|
||||
[platform in NodeJS.Platform]?: {
|
||||
[arch in NodeJS.Architecture]?: string
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export type installOptions = {
|
||||
version: string|boolean,
|
||||
release?: bedrockVersionJSON["release"],
|
||||
platformOptions?: bdsPlatformOptions,
|
||||
generateRandomPorts?: boolean
|
||||
};
|
||||
|
||||
const emulaterSoftwares = [
|
||||
"qemu-x86_64-static",
|
||||
"qemu-x86_64",
|
||||
"box64"
|
||||
];
|
||||
|
||||
export async function installServer(installOptions: installOptions) {
|
||||
if ((["android", "linux"]).includes(process.platform) && process.arch !== "x64") {
|
||||
let emitThrow = true;
|
||||
for (const emu of emulaterSoftwares) if (await coreUtils.childPromisses.commandExists(emu)) {emitThrow = false; break;}
|
||||
if (emitThrow) throw new Error("Cannot emulate x64 architecture. Check the documentents in \"https://github.com/core/wiki/Server-Platforms#minecraft-bedrock-server-alpha\"");
|
||||
}
|
||||
const folderControl = await pathControl("bedrock", installOptions?.platformOptions||{id: "default"});
|
||||
const allVersions = await coreUtils.httpRequest.getJSON<bedrockVersionJSON[]>("https://the-bds-maneger.github.io/BedrockFetch/all.json");
|
||||
const bedrockData = ((typeof installOptions?.version === "boolean")||(installOptions?.version?.trim()?.toLowerCase() === "latest")) ? allVersions.at(-1) : allVersions.find(rel => ((rel.release||"stable") !== (installOptions?.release||"stable")) && (rel.version === installOptions.version));
|
||||
|
||||
let platform = process.platform;
|
||||
if (platform === "android") platform = "linux";
|
||||
let url = bedrockData?.url[platform]?.[process.arch];
|
||||
if (!url) throw new Error("No url to current os platform");
|
||||
|
||||
// Remover files
|
||||
const files = await fs.readdir(folderControl.serverPath);
|
||||
await Promise.all(files.filter(file => !fileSave.test(file)).map(file => fs.rm(path.join(folderControl.serverPath, file), {recursive: true, force: true})));
|
||||
const backups = await Promise.all(files.filter(file => fileSave.test(file)).map(async file => fs.lstat(path.join(folderControl.serverPath, file)).then(res => res.isFile()?fs.readFile(path.join(folderControl.serverPath, file)).then(data => ({data, file: path.join(folderControl.serverPath, file)})).catch(() => null):null)))
|
||||
|
||||
// Extract file
|
||||
await coreUtils.httpRequestLarge.extractZip({url, folderTarget: folderControl.serverPath});
|
||||
await fs.writeFile(path.join(folderControl.serverRoot, "version_installed.json"), JSON.stringify({version: bedrockData.version, date: bedrockData.date, installDate: new Date()}));
|
||||
|
||||
// Restore files
|
||||
if (backups.length > 0) await Promise.all(backups.filter(file => file !== null).map(({data, file}) => fs.writeFile(file, data).catch(() => null)));
|
||||
|
||||
if (installOptions?.generateRandomPorts||folderControl.platformIDs.length > 2) {
|
||||
let v4: number, v6: number;
|
||||
const platformPorts = (await Promise.all(folderControl.platformIDs.map(async id =>(await serverConfig({id})).getConfig()))).map(config => ({v4: config["server-port"], v6: config["server-portv6"]}));
|
||||
while (!v4||!v6) {
|
||||
const tmpNumber = await randomPort();
|
||||
if (platformPorts.some(ports => ports.v4 === tmpNumber||ports.v6 == tmpNumber)) continue;
|
||||
if (!v4) v4 = tmpNumber;
|
||||
else v6 = tmpNumber;
|
||||
};
|
||||
await (await serverConfig({id: folderControl.id})).editConfig({name: "serverPort", data: v4}).editConfig({name: "serverPortv6", data: v6}).save()
|
||||
}
|
||||
return {
|
||||
id: folderControl.id,
|
||||
url: url,
|
||||
version: bedrockData.version,
|
||||
date: bedrockData.date
|
||||
};
|
||||
}
|
||||
|
||||
export async function startServer(platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath, logsPath, id } = await pathControl("bedrock", platformOptions);
|
||||
if (!fsOld.existsSync(path.join(serverPath, "bedrock_server"+(process.platform==="win32"?".exe":"")))) throw new Error("Install server fist");
|
||||
const args: string[] = [];
|
||||
let command = path.join(serverPath, "bedrock_server");
|
||||
if ((["android", "linux"]).includes(process.platform) && process.arch !== "x64") {
|
||||
args.push(command);
|
||||
let emitThrow = true;
|
||||
for (const emu of emulaterSoftwares) if (await coreUtils.childPromisses.commandExists(emu)) {
|
||||
emitThrow = false;
|
||||
command = emu;
|
||||
break;
|
||||
}
|
||||
if (emitThrow) throw new Error("Cannot emulate x64 architecture. Check the documentents in \"https://github.com/core/wiki/Server-Platforms#minecraft-bedrock-server-alpha\"");
|
||||
}
|
||||
const backendStart = new Date(), logFileOut = path.join(logsPath, `${backendStart.getTime()}_${process.platform}_${process.arch}.log`);
|
||||
const serverConfig: globalPlatfroms.actionsV2 = {
|
||||
serverStarted(data, done) {
|
||||
if (started.test(data)) done({
|
||||
onAvaible: new Date(),
|
||||
timePassed: Date.now() - backendStart.getTime()
|
||||
});
|
||||
},
|
||||
portListening(data, done) {
|
||||
if (!portListen.test(data)) return;
|
||||
const [, protocol, port] = data.match(portListen);
|
||||
done({
|
||||
type: "UDP",
|
||||
port: parseInt(port),
|
||||
host: protocol?.trim() === "IPv4" ? "127.0.0.1" : protocol?.trim() === "IPv6" ? "[::]" : "Unknown",
|
||||
protocol: protocol?.trim() === "IPv4" ? "IPv4" : protocol?.trim() === "IPv6" ? "IPv6" : "Unknown"
|
||||
});
|
||||
},
|
||||
playerAction(data, Callbacks) {
|
||||
const connectTime = new Date();
|
||||
if (!(newPlayerActions.test(data)||playerActionsV1.test(data))) return;
|
||||
let playerName: string, action: string, xuid: string;
|
||||
if (newPlayerActions.test(data)) {
|
||||
const [, actionV2,, playerNameV2,, xuidV2] = data.match(newPlayerActions);
|
||||
playerName = playerNameV2;
|
||||
action = actionV2;
|
||||
xuid = xuidV2;
|
||||
} else {
|
||||
const [, actionV1,, playerNameV1, xuidV1] = data.match(newPlayerActions);
|
||||
playerName = playerNameV1;
|
||||
action = actionV1;
|
||||
xuid = xuidV1;
|
||||
}
|
||||
const playerData: globalPlatfroms.playerBase = {connectTime, playerName, xuid};
|
||||
if (action === "connect") Callbacks.connect(playerData);
|
||||
else if (action === "disconnect") Callbacks.disconnect(playerData);
|
||||
else if (action === "Spawned") Callbacks.spawn(playerData);
|
||||
else Callbacks.unknown({...playerData, lineString: data});
|
||||
},
|
||||
stopServer(components) {
|
||||
components.actions.runCommand("stop");
|
||||
return components.actions.waitExit();
|
||||
},
|
||||
playerTp(actions, playerName, x, y, z) {
|
||||
if (!/".*"/.test(playerName) && playerName.includes(" ")) playerName = `"${playerName}"`;
|
||||
actions.runCommand("tp", playerName, x, y, z);
|
||||
},
|
||||
};
|
||||
return globalPlatfroms.actionV2({
|
||||
id,
|
||||
platform: "bedrock",
|
||||
processConfig: {command, args, options: {cwd: serverPath, maxBuffer: Infinity, env: {LD_LIBRARY_PATH: process.platform === "win32"?undefined:serverPath}, logPath: {stdout: logFileOut}}},
|
||||
hooks: serverConfig
|
||||
});
|
||||
}
|
||||
|
||||
export type editConfig =
|
||||
{name: "serverName", data: string}|
|
||||
{name: "gamemode", data: "survival"|"creative"|"adventure"}|
|
||||
{name: "forceGamemode", data: boolean}|
|
||||
{name: "difficulty", data: "peaceful"|"easy"|"normal"|"hard"}|
|
||||
{name: "allowCheats", data: boolean}|
|
||||
{name: "maxPlayers", data: number}|
|
||||
{name: "onlineMode", data: boolean}|
|
||||
{name: "allowList", data: boolean}|
|
||||
{name: "serverPort", data: number}|
|
||||
{name: "serverPortv6", data: number}|
|
||||
{name: "viewDistance", data: number}|
|
||||
{name: "tickDistance", data: "4"|"6"|"8"|"10"|"12"}|
|
||||
{name: "playerIdleTimeout", data: number}|
|
||||
{name: "maxThreads", data: number}|
|
||||
{name: "levelName", data: string}|
|
||||
{name: "levelSeed", data?: string}|
|
||||
{name: "levelType", data?: "FLAT"|"LEGACY"|"DEFAULT"}|
|
||||
{name: "defaultPlayerPermissionLevel", data: "visitor"|"member"|"operator"}|
|
||||
{name: "texturepackRequired", data: boolean}|
|
||||
{name: "chatRestriction", data: "None"|"Dropped"|"Disabled"}|
|
||||
{name: "mojangTelemetry", data: boolean};
|
||||
|
||||
export type bedrockConfig = {
|
||||
"server-name": string,
|
||||
"gamemode": "survival"|"creative"|"adventure",
|
||||
"force-gamemode": boolean,
|
||||
"difficulty": "peaceful"|"easy"|"normal"|"hard",
|
||||
"allow-cheats": boolean,
|
||||
"max-players": number,
|
||||
"online-mode": true,
|
||||
"allow-list": boolean,
|
||||
"server-port": number,
|
||||
"server-portv6": number,
|
||||
"view-distance": number,
|
||||
"tick-distance": "4"|"6"|"8"|"10"|"12",
|
||||
"player-idle-timeout": number,
|
||||
"max-threads": number,
|
||||
"level-name": string,
|
||||
"level-seed": any,
|
||||
"default-player-permission-level": "visitor"|"member"|"operator",
|
||||
"texturepack-required": boolean,
|
||||
"content-log-file-enabled": boolean,
|
||||
"compression-threshold": number,
|
||||
"server-authoritative-movement": string,
|
||||
"player-movement-score-threshold": number,
|
||||
"player-movement-action-direction-threshold": number,
|
||||
"player-movement-distance-threshold": number,
|
||||
"player-movement-duration-threshold-in-ms": number,
|
||||
"correct-player-movement": boolean,
|
||||
"server-authoritative-block-breaking": boolean,
|
||||
"chat-restriction": "None"|"Dropped"|"Disabled",
|
||||
"disable-player-interaction": boolean,
|
||||
"emit-server-telemetry"?: boolean
|
||||
}
|
||||
|
||||
export async function serverConfig(platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath } = await pathControl("bedrock", platformOptions);
|
||||
const fileProperties = path.join(serverPath, "server.properties");
|
||||
if (!await coreUtils.extendFs.exists(fileProperties)) await fs.cp(path.join(__dirname, "../configs/java/server.properties"), fileProperties);
|
||||
return manegerConfigProprieties<editConfig, bedrockConfig>({
|
||||
configPath: fileProperties,
|
||||
configManipulate: {
|
||||
serverName: {
|
||||
regexReplace: /server-name=.*/,
|
||||
valueFormat: "server-name=%s"
|
||||
},
|
||||
gamemode: {
|
||||
regexReplace: /gamemode=(survival|creative|adventure)/,
|
||||
valueFormat: "gamemode=%s"
|
||||
},
|
||||
forceGamemode: {
|
||||
regexReplace: /force-gamemode=(true|false)/,
|
||||
valueFormat: "force-gamemode=%s"
|
||||
},
|
||||
difficulty: {
|
||||
regexReplace: /difficulty=(peaceful|easy|normal|hard)/,
|
||||
valueFormat: "difficulty=%s"
|
||||
},
|
||||
allowCheats: {
|
||||
regexReplace: /allow-cheats=(false|true)/,
|
||||
valueFormat: "allow-cheats=%s"
|
||||
},
|
||||
maxPlayers: {
|
||||
regexReplace: /max-players=[0-9]+/,
|
||||
valueFormat: "max-players=%s"
|
||||
},
|
||||
onlineMode: {
|
||||
regexReplace: /online-mode=(true|false)/,
|
||||
valueFormat: "online-mode=%s"
|
||||
},
|
||||
allowList: {
|
||||
regexReplace: /allow-list=(false|true)/,
|
||||
valueFormat: "allow-list=%s"
|
||||
},
|
||||
tickDistance: {
|
||||
regexReplace: /tick-distance=(4|6|8|10|12)/,
|
||||
valueFormat: "tick-distance=%f",
|
||||
validate(value: number) {return ([4,6,8,10,12]).includes(value);}
|
||||
},
|
||||
playerIdleTimeout: {
|
||||
regexReplace: /player-idle-timeout=[0-9]+/,
|
||||
valueFormat: "player-idle-timeout=%f"
|
||||
},
|
||||
maxThreads: {
|
||||
regexReplace: /max-threads=[0-9]+/,
|
||||
valueFormat: "max-threads=%f"
|
||||
},
|
||||
levelName: {
|
||||
regexReplace: /^level-name=[\s\w\S]+/,
|
||||
valueFormat: "level-name=%s"
|
||||
},
|
||||
levelSeed: {
|
||||
regexReplace: /level-seed=[0-9]+/,
|
||||
valueFormat: "level-seed=%f"
|
||||
},
|
||||
levelType: {
|
||||
regexReplace: /level-type=(FLAT|LEGACY|DEFAULT)/,
|
||||
valueFormat: "level-type=%s"
|
||||
},
|
||||
defaultPlayerPermissionLevel: {
|
||||
regexReplace: /default-player-permission-level=(visitor|member|operator)/,
|
||||
valueFormat: "default-player-permission-level=%s"
|
||||
},
|
||||
texturepackRequired: {
|
||||
regexReplace: /texturepack-required=(false|true)/,
|
||||
valueFormat: "texturepack-required=%s"
|
||||
},
|
||||
chatRestriction: {
|
||||
regexReplace: /chat-restriction=(None|Dropped|Disabled)/,
|
||||
valueFormat: "chat-restriction=%s"
|
||||
},
|
||||
viewDistance: {
|
||||
validate(value: number) {return value > 4},
|
||||
regexReplace: /view-distance=[0-9]+/,
|
||||
valueFormat: "view-distance=%f"
|
||||
},
|
||||
mojangTelemetry: {
|
||||
addIfNotExist: "chat-restriction=false",
|
||||
regexReplace: /chat-restriction=(true|false)/,
|
||||
valueFormat: "chat-restriction=%s"
|
||||
},
|
||||
serverPort: {
|
||||
regexReplace: /server-port=[0-9]+/,
|
||||
valueFormat: "server-port=%f"
|
||||
},
|
||||
serverPortv6: {
|
||||
regexReplace: /server-portv6=[0-9]+/,
|
||||
valueFormat: "server-portv6=%f"
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export type resourcePacks = {
|
||||
pack_id: string,
|
||||
version?: number[]
|
||||
};
|
||||
|
||||
export type resourceManifest = {
|
||||
format_version?: 2,
|
||||
header: {
|
||||
uuid: string,
|
||||
name?: string,
|
||||
description?: string,
|
||||
version?: number[],
|
||||
min_engine_version?: number[]
|
||||
},
|
||||
modules?: {
|
||||
uuid: string,
|
||||
type: "resources",
|
||||
description?: string,
|
||||
version: number[]
|
||||
}[]
|
||||
};
|
||||
|
||||
export async function addResourcePacksToWorld(resourceId: string, platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath } = await pathControl("bedrock", platformOptions);
|
||||
const serverConfigObject = (await serverConfig(platformOptions)).getConfig();
|
||||
if (!await coreUtils.extendFs.exists(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"))) await fs.writeFile(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"), "[]");
|
||||
const resourcesData: resourcePacks[] = JSON.parse(await fs.readFile(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"), "utf8"));
|
||||
const manifests: resourceManifest[] = await Promise.all((await coreUtils.extendFs.readdir({folderPath: [path.join(serverPath, "resource_packs"), path.join(serverPath, "worlds", serverConfigObject["level-name"], "resource_packs")]})).filter((file: string) => file.endsWith("manifest.json")).map(async (file: string) => JSON.parse(await fs.readFile(file, "utf8"))));
|
||||
const packInfo = manifests.find(pf => pf.header.uuid === resourceId);
|
||||
if (!packInfo) throw new Error("UUID to texture not installed in the server");
|
||||
if (resourcesData.includes({pack_id: resourceId})) throw new Error("Textura alredy installed in the World");
|
||||
resourcesData.push({pack_id: packInfo.header.uuid, version: packInfo.header.version});
|
||||
await fs.writeFile(path.join(serverPath, "worlds", serverConfigObject["level-name"], "world_resource_packs.json"), JSON.stringify(resourcesData, null, 2));
|
||||
return resourcesData;
|
||||
}
|
@@ -1,194 +0,0 @@
|
||||
import coreUtils from "@sirherobrine23/coreutils";
|
||||
import { actionV2, actionsV2 } from "../globalPlatfroms.js";
|
||||
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
|
||||
import { manegerConfigProprieties } from "../configManipulate.js";
|
||||
import { randomPort } from "../lib/randomPort.js";
|
||||
import fsOld from "node:fs";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import { compareVersions } from "compare-versions";
|
||||
import {fileURLToPath} from 'url';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
type javaRemoteVersion = {
|
||||
version: string,
|
||||
date: string,
|
||||
latest: boolean,
|
||||
url: string
|
||||
};
|
||||
|
||||
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath, serverRoot, platformIDs, id } = await pathControl("java", platformOptions);
|
||||
const allVersions = (await coreUtils.httpRequest.getJSON<javaRemoteVersion[]>("https://mcpeversion-static.sirherobrine23.org/java/all.json")).sort(({version: a}, {version: b}) => compareVersions(a, b));
|
||||
const javaDownload = ((typeof version === "boolean")||(version?.trim()?.toLowerCase() === "latest")) ? allVersions.at(-1) : allVersions.find(rel => (version === rel.version));
|
||||
if (!javaDownload) throw new Error("Cannot find version!");
|
||||
await coreUtils.httpRequestLarge.saveFile({url: javaDownload.url, filePath: path.join(serverPath, "server.jar")});
|
||||
await fs.writeFile(path.join(serverRoot, "version_installed.json"), JSON.stringify({version: javaDownload.version, date: javaDownload.date, installDate: new Date()}));
|
||||
|
||||
if (platformIDs.length > 1) {
|
||||
const platformPorts = (await Promise.all(platformIDs.map(id => serverConfig({id})))).map(config => config.getConfig()["server-port"]);
|
||||
let port: number;
|
||||
while (!port) {
|
||||
const tmpNumber = await randomPort();
|
||||
if (platformPorts.some(ports => ports === tmpNumber)) continue;
|
||||
port = tmpNumber;
|
||||
};
|
||||
await (await serverConfig({id})).editConfig({name: "serverPort", data: port}).save();
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
version: javaDownload.version,
|
||||
date: javaDownload.date,
|
||||
url: javaDownload.url
|
||||
};
|
||||
}
|
||||
|
||||
export const started = /\[.*\].*\s+Done\s+\([0-9\.]+s\)\!.*/;
|
||||
export const portListen = /\[.*\]:\s+Starting\s+Minecraft\s+server\s+on\s+(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[A-Za-z0-9]+|\*):([0-9]+))/;
|
||||
export const playerAction = /\[.*\]:\s+([\S\w]+)\s+(joined|left|lost)/;
|
||||
const javaHooks: actionsV2 = {
|
||||
serverStarted(data, done) {
|
||||
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
|
||||
if (started.test(data)) done(new Date());
|
||||
},
|
||||
portListening(data, done) {
|
||||
const portParse = data.match(portListen);
|
||||
if (!portParse) return;
|
||||
let [,, host, port] = portParse;
|
||||
if (host === "*"||!host) host = "127.0.0.1";
|
||||
done({
|
||||
port: parseInt(port),
|
||||
type: "TCP",
|
||||
host: host,
|
||||
protocol: "IPV4/IPv6"
|
||||
});
|
||||
},
|
||||
playerAction(data, Callbacks) {
|
||||
if (playerAction.test(data)) {
|
||||
const [, playerName, action] = data.match(data)||[];
|
||||
if (action === "joined") Callbacks.connect({playerName, connectTime: new Date()});
|
||||
else if (action === "left") Callbacks.disconnect({playerName, connectTime: new Date()});
|
||||
else if (action === "lost") Callbacks.unknown({playerName, connectTime: new Date(), action: "lost"});
|
||||
else Callbacks.unknown({playerName, connectTime: new Date()});
|
||||
}
|
||||
},
|
||||
stopServer(components) {
|
||||
components.actions.runCommand("stop");
|
||||
return components.actions.waitExit();
|
||||
},
|
||||
};
|
||||
|
||||
export async function startServer(Config?: {maxMemory?: number, minMemory?: number, maxFreeMemory?: boolean, platformOptions?: bdsPlatformOptions}) {
|
||||
const { serverPath, logsPath, id } = await pathControl("java", Config?.platformOptions||{id: "default"});
|
||||
const jarPath = path.join(serverPath, "server.jar");
|
||||
if (!fsOld.existsSync(jarPath)) throw new Error("Install server fist.");
|
||||
const command = "java";
|
||||
const args = [
|
||||
"-XX:+UseG1GC",
|
||||
"-XX:+ParallelRefProcEnabled",
|
||||
"-XX:MaxGCPauseMillis=200",
|
||||
"-XX:+UnlockExperimentalVMOptions",
|
||||
"-XX:+DisableExplicitGC",
|
||||
"-XX:+AlwaysPreTouch",
|
||||
"-XX:G1NewSizePercent=30",
|
||||
"-XX:G1MaxNewSizePercent=40",
|
||||
"-XX:G1HeapRegionSize=8M",
|
||||
"-XX:G1ReservePercent=20",
|
||||
"-XX:G1HeapWastePercent=5",
|
||||
"-XX:G1MixedGCCountTarget=4",
|
||||
"-XX:InitiatingHeapOccupancyPercent=15",
|
||||
"-XX:G1MixedGCLiveThresholdPercent=90",
|
||||
"-XX:G1RSetUpdatingPauseTimePercent=5",
|
||||
"-XX:SurvivorRatio=32",
|
||||
"-XX:+PerfDisableSharedMem",
|
||||
"-XX:MaxTenuringThreshold=1",
|
||||
"-Dusing.aikars.flags=https://mcflags.emc.gs",
|
||||
"-Daikars.new.flags=true",
|
||||
"-XX:+UnlockDiagnosticVMOptions",
|
||||
"-XX:-UseAESCTRIntrinsics"
|
||||
];
|
||||
if (Config) {
|
||||
if (Config.maxFreeMemory) {
|
||||
const safeFree = Math.floor(os.freemem()/1e6);
|
||||
if (safeFree > 1000) Config.maxMemory = safeFree;
|
||||
else console.warn("There is little ram available!")
|
||||
}
|
||||
if (Config.maxMemory) args.push(`-Xmx${Config.maxMemory}m`);
|
||||
if (Config.minMemory) args.push(`-Xms${Config.minMemory}m`);
|
||||
}
|
||||
args.push("-jar", jarPath, "nogui");
|
||||
const eula = path.join(serverPath, "eula.txt");
|
||||
await fs.writeFile(eula, (await fs.readFile(eula, "utf8").catch(() => "eula=false")).replace("eula=false", "eula=true"));
|
||||
const logFileOut = path.join(logsPath, `${Date.now()}_${process.platform}_${process.arch}.log`);
|
||||
return actionV2({
|
||||
id, platform: "java",
|
||||
processConfig: {command, args, options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
|
||||
hooks: javaHooks
|
||||
});
|
||||
}
|
||||
|
||||
export type Gamemode = {name: "Gamemode", data: "survival"|"creative"|"hardcore"};
|
||||
export type Difficulty = {name: "Difficulty", data: "peaceful"|"easy"|"normal"|"hard"};
|
||||
export type serverPort = {name: "serverPort", data: number};
|
||||
export type maxPlayers = {name: "maxPlayers", data: number};
|
||||
export type allowList = {name: "allowList", data: boolean};
|
||||
export type serverDescription = {name: "serverDescription", data: string};
|
||||
export type worldName = {name: "worldName", data: string};
|
||||
export type editConfig = Gamemode|Difficulty|serverPort|maxPlayers|allowList|serverDescription|worldName;
|
||||
|
||||
/**
|
||||
* Update java server config
|
||||
* @param platformOptions
|
||||
* @returns
|
||||
*/
|
||||
export async function serverConfig(platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath } = await pathControl("java", platformOptions);
|
||||
const fileProperties = path.join(serverPath, "server.properties");
|
||||
if (!await coreUtils.extendFs.exists(fileProperties)) await fs.cp(path.join(__dirname, "../configs/java/server.properties"), fileProperties);
|
||||
return manegerConfigProprieties<editConfig>({
|
||||
configPath: fileProperties,
|
||||
configManipulate: {
|
||||
Gamemode: (fileConfig, value: string) => {
|
||||
if (!(["survival", "creative", "hardcore"]).includes(value)) throw new Error("Invalid gameode");
|
||||
if (value === "hardcore") fileConfig = fileConfig.replace(/gamemode=(survival|creative)/, `gamemode=survial`).replace(/hardcore=(false|true)/, `hardcore=true`);
|
||||
else {
|
||||
if (!(value === "survival"||value === "creative")) throw new Error("Invalid gamemode");
|
||||
fileConfig = fileConfig.replace(/gamemode=(survival|creative)/, `gamemode=${value}`).replace(/hardcore=(false|true)/, `hardcore=false`);
|
||||
}
|
||||
return fileConfig;
|
||||
},
|
||||
Difficulty: {
|
||||
validate: (value: string) => (["peaceful", "easy", "normal", "hard"]).includes(value),
|
||||
regexReplace: /difficulty=(peaceful|easy|normal|hard)/,
|
||||
valueFormat: "difficulty=%s"
|
||||
},
|
||||
serverPort: {
|
||||
validate: (value: number) => value > 1000,
|
||||
regexReplace: /server-port=([0-9]+)/,
|
||||
valueFormat: "server-port=%f"
|
||||
},
|
||||
maxPlayers: {
|
||||
validate: (value: number) => value > 1,
|
||||
regexReplace: /max-players=([0-9]+)/,
|
||||
valueFormat: "max-players=%f"
|
||||
},
|
||||
serverDescription: {
|
||||
validate: (value: string) => value.length < 50,
|
||||
regexReplace: /motd=(.*)/,
|
||||
valueFormat: "motd=%s"
|
||||
},
|
||||
worldName: {
|
||||
validate: (value: string) => value.length < 50,
|
||||
regexReplace: /level-name=(.*)/,
|
||||
valueFormat: "level-name=%s"
|
||||
},
|
||||
allowList: {
|
||||
validate: (value: boolean) => value === true||value === false,
|
||||
regexReplace: /white-list=(true|false)/,
|
||||
valueFormat: "white-list=%o"
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@@ -1,119 +0,0 @@
|
||||
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
|
||||
import { spigotProprieties } from "./spigot.js";
|
||||
import * as globalPlatfroms from "../globalPlatfroms.js";
|
||||
import * as coreUtils from "@sirherobrine23/coreutils";
|
||||
import Proprieties from "../lib/Proprieties.js";
|
||||
import fsOld from "node:fs";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import { compareVersions } from "compare-versions";
|
||||
|
||||
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath, id } = await pathControl("paper", platformOptions);
|
||||
const allVersions = (await coreUtils.httpRequest.getJSON("https://mcpeversion-static.sirherobrine23.org/paper/all.json")).sort(({version: a}, {version: b}) => compareVersions(a, b));
|
||||
const release = ((typeof version === "boolean")||(version?.trim()?.toLowerCase() === "latest")) ? allVersions.at(-1) : allVersions.find(rel => (version === rel.version));
|
||||
if (!release) throw new Error("Cannot find version!");
|
||||
await coreUtils.httpRequestLarge.saveFile({url: release.url, filePath: path.join(serverPath, "paper.jar")});
|
||||
return {
|
||||
id,
|
||||
version: release.version,
|
||||
url: release.url,
|
||||
date: release.date
|
||||
};
|
||||
}
|
||||
|
||||
export const started = /\[.*\].*\s+Done\s+\(.*\)\!.*/;
|
||||
export const portListen = /\[.*\]:\s+Starting\s+Minecraft\s+server\s+on\s+(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[A-Za-z0-9]+|\*):([0-9]+))/;
|
||||
export const playerAction = /\[.*\]:\s+([\S\w]+)\s+(joined|left|lost)/;
|
||||
export const paperHook: globalPlatfroms.actionsV2 = {
|
||||
serverStarted(data, done) {
|
||||
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
|
||||
if (started.test(data)) done(new Date());
|
||||
},
|
||||
playerAction(data, Callbacks) {
|
||||
if (!playerAction.test(data)) return;
|
||||
const [, playerName, action] = data.match(data)||[];
|
||||
if (action === "joined") Callbacks.connect({playerName, connectTime: new Date()});
|
||||
else if (action === "left") Callbacks.disconnect({playerName, connectTime: new Date()});
|
||||
else if (action === "lost") Callbacks.unknown({playerName, connectTime: new Date(), action: "lost"});
|
||||
else Callbacks.unknown({playerName, connectTime: new Date()});
|
||||
},
|
||||
portListening(data, done) {
|
||||
const portParse = data.match(portListen);
|
||||
if (!portParse) return;
|
||||
let [,, host, port] = portParse;
|
||||
if (host === "*"||!host) host = "127.0.0.1";
|
||||
done({
|
||||
port: parseInt(port),
|
||||
type: "TCP",
|
||||
host: host,
|
||||
protocol: /::/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6"
|
||||
});
|
||||
},
|
||||
stopServer(components) {
|
||||
components.actions.runCommand("stop");
|
||||
return components.actions.waitExit();
|
||||
},
|
||||
};
|
||||
|
||||
export async function startServer(Config?: {maxMemory?: number, minMemory?: number, maxFreeMemory?: boolean, platformOptions?: bdsPlatformOptions}) {
|
||||
const { serverPath, logsPath, id } = await pathControl("paper", Config?.platformOptions||{id: "default"});
|
||||
if (!fsOld.existsSync(path.join(serverPath, "paper.jar"))) throw new Error("Install server fist.");
|
||||
const args = [
|
||||
"-XX:+UseG1GC",
|
||||
"-XX:+ParallelRefProcEnabled",
|
||||
"-XX:MaxGCPauseMillis=200",
|
||||
"-XX:+UnlockExperimentalVMOptions",
|
||||
"-XX:+DisableExplicitGC",
|
||||
"-XX:+AlwaysPreTouch",
|
||||
"-XX:G1NewSizePercent=30",
|
||||
"-XX:G1MaxNewSizePercent=40",
|
||||
"-XX:G1HeapRegionSize=8M",
|
||||
"-XX:G1ReservePercent=20",
|
||||
"-XX:G1HeapWastePercent=5",
|
||||
"-XX:G1MixedGCCountTarget=4",
|
||||
"-XX:InitiatingHeapOccupancyPercent=15",
|
||||
"-XX:G1MixedGCLiveThresholdPercent=90",
|
||||
"-XX:G1RSetUpdatingPauseTimePercent=5",
|
||||
"-XX:SurvivorRatio=32",
|
||||
"-XX:+PerfDisableSharedMem",
|
||||
"-XX:MaxTenuringThreshold=1",
|
||||
"-Dusing.aikars.flags=https://mcflags.emc.gs",
|
||||
"-Daikars.new.flags=true",
|
||||
"-XX:+UnlockDiagnosticVMOptions",
|
||||
"-XX:-UseAESCTRIntrinsics"
|
||||
];
|
||||
if (Config) {
|
||||
if (Config.maxFreeMemory) {
|
||||
const safeFree = Math.floor(os.freemem()/1e6);
|
||||
if (safeFree > 1000) Config.maxMemory = safeFree;
|
||||
else console.warn("There is little ram available!")
|
||||
}
|
||||
if (Config.maxMemory) args.push(`-Xmx${Config.maxMemory}m`);
|
||||
if (Config.minMemory) args.push(`-Xms${Config.minMemory}m`);
|
||||
}
|
||||
|
||||
args.push("-jar", path.join(serverPath, "paper.jar"), "nogui");
|
||||
const eula = path.join(serverPath, "eula.txt");
|
||||
await fs.writeFile(eula, (await fs.readFile(eula, "utf8").catch(() => "eula=false")).replace("eula=false", "eula=true"));
|
||||
const logFileOut = path.join(logsPath, `${Date.now()}_${process.platform}_${process.arch}.log`);
|
||||
return globalPlatfroms.actionV2({
|
||||
id, platform: "paper",
|
||||
processConfig: {command: "java", args, options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
|
||||
hooks: paperHook
|
||||
});
|
||||
}
|
||||
|
||||
export async function getConfig(platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath } = await pathControl("paper", platformOptions);
|
||||
return Proprieties.parse<spigotProprieties>(await fs.readFile(path.join(serverPath, "server.properties"), "utf8"));
|
||||
}
|
||||
|
||||
// This is a fast and dirty way to implement a new feature, but i'm too exhausted to implement the same type as bedrock
|
||||
export async function updateConfig(config: {key: string, value: any}, platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const currentConfig = await getConfig(platformOptions);
|
||||
const { serverPath } = await pathControl("paper", platformOptions);
|
||||
currentConfig[config.key] = config.value;
|
||||
return fs.writeFile(path.join(serverPath, "server.properties"), Proprieties.stringify(currentConfig));
|
||||
}
|
@@ -1,115 +0,0 @@
|
||||
import { childPromisses, extendFs, httpRequest, httpRequestGithub, httpRequestLarge } from "@sirherobrine23/coreutils";
|
||||
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
|
||||
import { existsSync as fsExistsSync, Stats } from "node:fs";
|
||||
import * as globalPlatfroms from "../globalPlatfroms.js";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import debug from "debug";
|
||||
const pocketmineDebug = debug("bdscore:platform:pocketmine");
|
||||
const phpStaticBucket = "https://objectstorage.sa-saopaulo-1.oraclecloud.com/p/0IKM-5KFpAF8PuWoVe86QFsF4sipU2rXfojpaOMEdf4QgFQLcLlDWgMSPHWmjf5W/n/grwodtg32n4d/b/bdsFiles/o/";
|
||||
|
||||
export async function listVersions() {
|
||||
return (await httpRequestGithub.getRelease({owner: "pmmp", repository: "PocketMine-MP", all: true})).map(data => {
|
||||
const pharFile = data.assets.find(data => data.name.endsWith((".phar")));
|
||||
return {
|
||||
version: data.tag_name,
|
||||
publish: new Date(data.published_at),
|
||||
pharFile: pharFile?.browser_download_url,
|
||||
size: pharFile?.size
|
||||
};
|
||||
}).filter(rel => !!rel.pharFile);
|
||||
}
|
||||
|
||||
async function findPhp(serverPath: string, extraPath?: string): Promise<string> {
|
||||
if (!extraPath) extraPath = path.join(serverPath, "bin");
|
||||
const files = await Promise.all((await fs.readdir(extraPath)).map(file => fs.lstat(path.join(extraPath, file)).then(stat => ({stat, file, fullPath: path.join(extraPath, file)})).catch(() => {})));
|
||||
let folderFF = "";
|
||||
for (const file of (files.filter(a=>!!a) as {file: string, fullPath: string, stat: Stats}[]).sort(a => a.stat.isDirectory() ? 1:-1)) {
|
||||
if (file.stat.isDirectory()) {
|
||||
folderFF = await findPhp(serverPath, file.fullPath).catch(() => "");
|
||||
if (folderFF) return folderFF;
|
||||
} else if (file.file === "php"||file.file === "php.exe") return file.fullPath;
|
||||
}
|
||||
if (folderFF) return folderFF;
|
||||
throw new Error("Cannot find php");
|
||||
}
|
||||
|
||||
async function installPhp(serverPath: string): Promise<void> {
|
||||
let filePath = (await httpRequest.getJSON<{objects: {name: string}[]}>(phpStaticBucket)).objects.find(({name}) => name.includes(process.platform) && name.includes(process.arch))?.name;
|
||||
if (!filePath) {
|
||||
pocketmineDebug("Current OS: %s", process.platform);
|
||||
pocketmineDebug("Current Arch: %s", process.arch);
|
||||
throw new Error("Cannot find php release!");
|
||||
}
|
||||
const binFolder = path.resolve(serverPath, "bin");
|
||||
if (await extendFs.exists(binFolder)) await fs.rm(path.resolve(serverPath, "bin"), {recursive: true});
|
||||
filePath = phpStaticBucket+filePath;
|
||||
if (filePath.endsWith(".zip")) await httpRequestLarge.extractZip({url: filePath, folderTarget: binFolder});
|
||||
else if (filePath.endsWith(".tar.gz")||filePath.endsWith(".tgz")||filePath.endsWith(".tar")) await httpRequestLarge.tarExtract({url: filePath, folderPath: binFolder});
|
||||
else throw new Error("Invalid file: "+filePath);
|
||||
|
||||
// test it's works php
|
||||
const phpExec = await findPhp(serverPath);
|
||||
if (!phpExec) throw new Error("Cannot find php exec file!");
|
||||
await childPromisses.execFile(phpExec, ["--version"]).catch(err => {
|
||||
pocketmineDebug("PHP bin error: %O", err);
|
||||
pocketmineDebug("Current OS: %s", process.platform);
|
||||
pocketmineDebug("Current Arch: %s", process.arch);
|
||||
throw new Error("Corrupt PHP in host!");
|
||||
});
|
||||
}
|
||||
|
||||
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
platformOptions.withBuildFolder = true;
|
||||
const serverFolders = await pathControl("pocketmine", platformOptions);
|
||||
await installPhp(serverFolders.serverPath);
|
||||
if (typeof version === "boolean") version = "latest";
|
||||
if (version?.trim()?.toLowerCase() === "latest") version = (await listVersions()).at(0)?.version
|
||||
const info = (await listVersions()).find(rel => rel.version === version);
|
||||
await httpRequestLarge.saveFile({url: info?.pharFile, filePath: path.join(serverFolders.serverPath, "pocketmine.phar")});
|
||||
return {
|
||||
id: serverFolders.id,
|
||||
version: info.version,
|
||||
url: info.pharFile,
|
||||
date: info.publish
|
||||
};
|
||||
}
|
||||
|
||||
const portListen = /([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|\[[a-zA-Z0-9:]+\]):([0-9]+)/;
|
||||
// const player = /[.*]:\s+(.*)\s+(.*)\s+the\s+game/gi;
|
||||
export async function startServer(platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath, logsPath, id } = await pathControl("pocketmine", platformOptions);
|
||||
const serverPhar = path.join(serverPath, "pocketmine.phar");
|
||||
if (!fsExistsSync(serverPhar)) throw new Error("Install server fist!");
|
||||
const runStart = new Date();
|
||||
const logFileOut = path.join(logsPath, `${runStart.getTime()}_${process.platform}_${process.arch}.stdout.log`);
|
||||
const pocketmineHooks: globalPlatfroms.actionsV2 = {
|
||||
serverStarted(data, done) {
|
||||
if (!(data.includes("INFO") && data.includes("Done") && data.includes("help"))) return;
|
||||
const doneStart = new Date();
|
||||
done({
|
||||
onAvaible: doneStart,
|
||||
timePassed: runStart.getTime() - doneStart.getTime()
|
||||
});
|
||||
},
|
||||
portListening(data, done) {
|
||||
if (!portListen.test(data)) return;
|
||||
const [,, host, port] = data.match(portListen);
|
||||
done({
|
||||
type: "UDP",
|
||||
protocol: /\[[a-zA-Z0-9:]+\]/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6",
|
||||
port: parseInt(port),
|
||||
host: host?.trim()
|
||||
});
|
||||
},
|
||||
stopServer(components) {
|
||||
components.actions.runCommand("stop");
|
||||
return components.actions.waitExit();
|
||||
},
|
||||
};
|
||||
return globalPlatfroms.actionV2({
|
||||
id, platform: "pocketmine",
|
||||
processConfig: {command: await findPhp(serverPath), args: [serverPhar, "--no-wizard"], options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
|
||||
hooks: pocketmineHooks
|
||||
});
|
||||
}
|
@@ -1,103 +0,0 @@
|
||||
import coreUtils, { httpRequestLarge } from "@sirherobrine23/coreutils";
|
||||
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
|
||||
import * as globalPlatfroms from "../globalPlatfroms.js";
|
||||
import path from "node:path";
|
||||
import fsOld from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import { compareVersions } from "compare-versions";
|
||||
|
||||
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath, id } = await pathControl("powernukkit", platformOptions);
|
||||
const jarPath = path.join(serverPath, "pwnukkit.jar");
|
||||
if (!fsOld.existsSync(serverPath)) await fs.mkdir(serverPath, {recursive: true});
|
||||
const allVersions = (await coreUtils.httpRequest.getJSON("https://mcpeversion-static.sirherobrine23.org/paper/all.json")).sort(({version: a}, {version: b}) => compareVersions(a, b));
|
||||
const pwNukktiData = ((typeof version === "boolean")||(version?.trim()?.toLowerCase() === "latest")) ? allVersions.at(-1) : allVersions.find(rel => (version === rel.version));
|
||||
if (!pwNukktiData) throw new Error("Cannot find version!");
|
||||
// const pwNukktiData = await platformManeger.powernukkit.find(version);
|
||||
await httpRequestLarge.saveFile({url: pwNukktiData.url, filePath: jarPath})
|
||||
return {
|
||||
id,
|
||||
version: pwNukktiData.version,
|
||||
url: pwNukktiData.url,
|
||||
date: pwNukktiData.date
|
||||
};
|
||||
}
|
||||
|
||||
export const playerAction = /^.*\[.*\]\s([\S\w]+|"[\S\w]+")\s+(left|joined)\s+the\s+game$/;
|
||||
export const portListen = /Opening\s+server\s+on\s+(([A-Za-z0-9:\.]+):([0-9]+))/;
|
||||
export const powernukkitHooks: globalPlatfroms.actionsV2 = {
|
||||
serverStarted(data, done) {
|
||||
// 16:57:15 [INFO ] Done (2.122s)! For help, type "help" or "?"
|
||||
if (/^.*\[.*\]\s+Done\s+\([0-9\.]+s\)!/.test(data)) done(new Date());
|
||||
},
|
||||
portListening(data, done) {
|
||||
if (portListen.test(data)) {
|
||||
const [,, host, port] = data.match(portListen);
|
||||
done({
|
||||
port: parseInt(port),
|
||||
type: "UDP",
|
||||
protocol: /::/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6",
|
||||
host: host
|
||||
})
|
||||
}
|
||||
},
|
||||
playerAction(data, Callbacks) {
|
||||
if (playerAction.test(data)) {
|
||||
const [, playerName, action] = data.match(playerAction)||[];
|
||||
if (action === "joined") Callbacks.connect({connectTime: new Date(), playerName});
|
||||
else if (action === "left") Callbacks.disconnect({connectTime: new Date(), playerName});
|
||||
else Callbacks.unknown({connectTime: new Date(), playerName});
|
||||
}
|
||||
},
|
||||
stopServer(components) {
|
||||
components.actions.runCommand("stop");
|
||||
return components.actions.waitExit();
|
||||
},
|
||||
};
|
||||
|
||||
export async function startServer(Config?: {maxMemory?: number, minMemory?: number, maxFreeMemory?: boolean, platformOptions?: bdsPlatformOptions}) {
|
||||
const { serverPath, logsPath, id } = await pathControl("powernukkit", Config?.platformOptions||{id: "default"});
|
||||
const jarPath = path.join(serverPath, "pwnukkit.jar");
|
||||
if (!fsOld.existsSync(jarPath)) throw new Error("Install server fist.");
|
||||
const args = [
|
||||
"-XX:+UseG1GC",
|
||||
"-XX:+ParallelRefProcEnabled",
|
||||
"-XX:MaxGCPauseMillis=200",
|
||||
"-XX:+UnlockExperimentalVMOptions",
|
||||
"-XX:+DisableExplicitGC",
|
||||
"-XX:+AlwaysPreTouch",
|
||||
"-XX:G1NewSizePercent=30",
|
||||
"-XX:G1MaxNewSizePercent=40",
|
||||
"-XX:G1HeapRegionSize=8M",
|
||||
"-XX:G1ReservePercent=20",
|
||||
"-XX:G1HeapWastePercent=5",
|
||||
"-XX:G1MixedGCCountTarget=4",
|
||||
"-XX:InitiatingHeapOccupancyPercent=15",
|
||||
"-XX:G1MixedGCLiveThresholdPercent=90",
|
||||
"-XX:G1RSetUpdatingPauseTimePercent=5",
|
||||
"-XX:SurvivorRatio=32",
|
||||
"-XX:+PerfDisableSharedMem",
|
||||
"-XX:MaxTenuringThreshold=1",
|
||||
"-Dusing.aikars.flags=https://mcflags.emc.gs",
|
||||
"-Daikars.new.flags=true",
|
||||
"-XX:+UnlockDiagnosticVMOptions",
|
||||
"-XX:-UseAESCTRIntrinsics"
|
||||
];
|
||||
if (Config) {
|
||||
if (Config.maxFreeMemory) {
|
||||
const safeFree = Math.floor(os.freemem()/1e6);
|
||||
if (safeFree > 1000) Config.maxMemory = safeFree;
|
||||
else console.warn("There is little ram available!")
|
||||
}
|
||||
if (Config.maxMemory) args.push(`-Xmx${Config.maxMemory}m`);
|
||||
if (Config.minMemory) args.push(`-Xms${Config.minMemory}m`);
|
||||
}
|
||||
args.push("-jar", jarPath, "--language", "eng");
|
||||
const logFileOut = path.join(logsPath, `${Date.now()}_${process.platform}_${process.arch}.log`);
|
||||
return globalPlatfroms.actionV2({
|
||||
id, platform: "powernukkit",
|
||||
processConfig: {command: "java", args, options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
|
||||
hooks: powernukkitHooks
|
||||
});
|
||||
}
|
@@ -1,198 +0,0 @@
|
||||
import { httpRequestLarge, httpRequestGithub } from "@sirherobrine23/coreutils";
|
||||
import { pathControl, bdsPlatformOptions } from "../platformPathManeger.js";
|
||||
import * as globalPlatfroms from "../globalPlatfroms.js";
|
||||
import Proprieties from "../lib/Proprieties.js"
|
||||
import fsOld from "node:fs";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import { compareVersions } from "compare-versions";
|
||||
|
||||
async function listVersions(): Promise<{version: string, date: Date, url: string}[]> {
|
||||
const releases = (await httpRequestGithub.getRelease({owner: "The-Bds-Maneger", repository: "SpigotBuilds", all: true})).filter(rel => rel.tag_name !== "latest" && rel.assets.some(file => file.name.endsWith(".jar")));
|
||||
return releases.map(rel => ({
|
||||
version: rel.tag_name,
|
||||
date: new Date(rel.published_at),
|
||||
url: rel.assets.find(file => file.name.endsWith(".jar"))?.browser_download_url
|
||||
})).sort((a, b) => compareVersions(a.version.replace(/\-[a-zA-Z\-]+/, ".0"), b.version.replace(/\-[a-zA-Z\-]+/, ".0")));
|
||||
}
|
||||
|
||||
export async function installServer(version: string|boolean, platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
if (!(typeof version === "boolean"||typeof version === "string" && !!version?.trim())) throw TypeError("Version only if boolean and string");
|
||||
const serverPath = await pathControl("spigot", platformOptions);
|
||||
const relData = (await listVersions()).find(rel => ((typeof version === "boolean"||version.trim().toLowerCase() === "latest")||version === rel.version));
|
||||
if (!relData) throw new Error("no version found!");
|
||||
await httpRequestLarge.saveFile({
|
||||
url: relData.url,
|
||||
filePath: path.join(serverPath.serverPath, "server.jar")
|
||||
});
|
||||
return {
|
||||
id: serverPath.id,
|
||||
url: relData.url,
|
||||
version: version,
|
||||
date: relData.date
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export const started = /\[.*\].*\s+Done\s+\(.*\)\!.*/;
|
||||
export const portListen = /\[.*\]:\s+Starting\s+Minecraft\s+server\s+on\s+(([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|[A-Za-z0-9]+|\*):([0-9]+))/;
|
||||
export const playerAction = /\[.*\]:\s+([\S\w]+)\s+(joined|left|lost)/;
|
||||
export const serverConfig: globalPlatfroms.actionsV2 = {
|
||||
serverStarted(data, done) {
|
||||
// [22:35:26] [Server thread/INFO]: Done (6.249s)! For help, type "help"
|
||||
if (started.test(data)) done(new Date());
|
||||
},
|
||||
portListening(data, done) {
|
||||
const serverPort = data.match(portListen);
|
||||
if (serverPort) {
|
||||
let [,, host, port] = serverPort;
|
||||
if (host === "*"||!host) host = "127.0.0.1";
|
||||
return done({
|
||||
port: parseInt(port),
|
||||
type: "TCP",
|
||||
host: host,
|
||||
protocol: /::/.test(host?.trim())?"IPv6":/[0-9]+\.[0-9]+/.test(host?.trim())?"IPv4":"IPV4/IPv6"
|
||||
});
|
||||
}
|
||||
},
|
||||
playerAction(data, Callbacks) {
|
||||
if (playerAction.test(data)) {
|
||||
const [, playerName, action] = data.match(data)||[];
|
||||
if (action === "joined") Callbacks.connect({playerName, connectTime: new Date()});
|
||||
else if (action === "left") Callbacks.disconnect({playerName, connectTime: new Date()});
|
||||
else if (action === "lost") Callbacks.unknown({playerName, connectTime: new Date(), action: "lost"});
|
||||
else Callbacks.unknown({playerName, connectTime: new Date()});
|
||||
}
|
||||
},
|
||||
stopServer(components) {
|
||||
components.actions.runCommand("stop");
|
||||
return components.actions.waitExit();
|
||||
},
|
||||
};
|
||||
|
||||
export async function startServer(Config?: {maxMemory?: number, minMemory?: number, maxFreeMemory?: boolean, platformOptions?: bdsPlatformOptions}) {
|
||||
const { serverPath, logsPath, id } = await pathControl("spigot", Config?.platformOptions||{id: "default"});
|
||||
const jarPath = path.join(serverPath, "server.jar");
|
||||
if (!fsOld.existsSync(jarPath)) throw new Error("Install server fist.");
|
||||
const args = [
|
||||
"-XX:+UseG1GC",
|
||||
"-XX:+ParallelRefProcEnabled",
|
||||
"-XX:MaxGCPauseMillis=200",
|
||||
"-XX:+UnlockExperimentalVMOptions",
|
||||
"-XX:+DisableExplicitGC",
|
||||
"-XX:+AlwaysPreTouch",
|
||||
"-XX:G1NewSizePercent=30",
|
||||
"-XX:G1MaxNewSizePercent=40",
|
||||
"-XX:G1HeapRegionSize=8M",
|
||||
"-XX:G1ReservePercent=20",
|
||||
"-XX:G1HeapWastePercent=5",
|
||||
"-XX:G1MixedGCCountTarget=4",
|
||||
"-XX:InitiatingHeapOccupancyPercent=15",
|
||||
"-XX:G1MixedGCLiveThresholdPercent=90",
|
||||
"-XX:G1RSetUpdatingPauseTimePercent=5",
|
||||
"-XX:SurvivorRatio=32",
|
||||
"-XX:+PerfDisableSharedMem",
|
||||
"-XX:MaxTenuringThreshold=1",
|
||||
"-Dusing.aikars.flags=https://mcflags.emc.gs",
|
||||
"-Daikars.new.flags=true",
|
||||
"-XX:+UnlockDiagnosticVMOptions",
|
||||
"-XX:-UseAESCTRIntrinsics"
|
||||
];
|
||||
if (Config) {
|
||||
if (Config.maxFreeMemory) {
|
||||
const safeFree = Math.floor(os.freemem()/1e6);
|
||||
if (safeFree > 1000) Config.maxMemory = safeFree;
|
||||
else console.warn("There is little ram available!")
|
||||
}
|
||||
if (Config.maxMemory) args.push(`-Xmx${Config.maxMemory}m`);
|
||||
if (Config.minMemory) args.push(`-Xms${Config.minMemory}m`);
|
||||
}
|
||||
|
||||
args.push("-jar", jarPath, "nogui");
|
||||
const eula = path.join(serverPath, "eula.txt");
|
||||
await fs.readFile(eula, "utf8").catch(() => "eula=false").then(eulaFile => fs.writeFile(eula, eulaFile.replace("eula=false", "eula=true")));
|
||||
const logFileOut = path.join(logsPath, `${Date.now()}_${process.platform}_${process.arch}.log`);
|
||||
return globalPlatfroms.actionV2({
|
||||
id, platform: "spigot",
|
||||
processConfig: {command: "java", args, options: {cwd: serverPath, maxBuffer: Infinity, logPath: {stdout: logFileOut}}},
|
||||
hooks: serverConfig
|
||||
});
|
||||
}
|
||||
|
||||
export type baseConfig = {
|
||||
"gamemode": string,
|
||||
"level-name": string,
|
||||
"level-seed": number,
|
||||
"pvp": boolean,
|
||||
"difficulty": "easy"|"normal"|"hard",
|
||||
"hardcore": boolean,
|
||||
"motd": string,
|
||||
"server-port": number,
|
||||
"enforce-secure-profile": boolean,
|
||||
"require-resource-pack": boolean,
|
||||
"max-players": number,
|
||||
"online-mode": boolean,
|
||||
"enable-status": boolean,
|
||||
"allow-flight": boolean,
|
||||
"view-distance": number,
|
||||
"simulation-distance": number,
|
||||
"player-idle-timeout": number,
|
||||
"force-gamemode": boolean,
|
||||
"white-list": boolean,
|
||||
"spawn-animals": boolean,
|
||||
"enforce-whitelist": boolean,
|
||||
"enable-command-block": boolean,
|
||||
};
|
||||
|
||||
export type ignoreBaseConfig = {
|
||||
"query.port": number,
|
||||
"enable-jmx-monitoring": boolean,
|
||||
"rcon.port": number,
|
||||
"enable-query": boolean,
|
||||
"generator-settings": "{}"|string,
|
||||
"generate-structures": boolean,
|
||||
"max-chained-neighbor-updates": number,
|
||||
"network-compression-threshold": number,
|
||||
"max-tick-time": number,
|
||||
"use-native-transport": boolean,
|
||||
"broadcast-rcon-to-ops": boolean,
|
||||
"server-ip": string,
|
||||
"resource-pack-prompt": any,
|
||||
"allow-nether": boolean,
|
||||
"enable-rcon": boolean,
|
||||
"sync-chunk-writes": boolean,
|
||||
"op-permission-level": number,
|
||||
"prevent-proxy-connections": boolean,
|
||||
"hide-online-players": boolean,
|
||||
"resource-pack": any,
|
||||
"entity-broadcast-range-percentage": number,
|
||||
"rcon.password": string,
|
||||
"debug": boolean,
|
||||
"rate-limit": number,
|
||||
"broadcast-console-to-ops": boolean,
|
||||
"spawn-npcs": boolean,
|
||||
"previews-chat": boolean,
|
||||
"function-permission-level": number,
|
||||
"level-type": "minecraft\\:normal"|string,
|
||||
"text-filtering-config": any,
|
||||
"spawn-monsters": boolean,
|
||||
"spawn-protection": number,
|
||||
"resource-pack-sha1": any|string,
|
||||
"max-world-size": number
|
||||
}
|
||||
|
||||
export type spigotProprieties = baseConfig & ignoreBaseConfig;
|
||||
|
||||
export async function getConfig(platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const { serverPath } = await pathControl("spigot", platformOptions);
|
||||
return Proprieties.parse<spigotProprieties>(await fs.readFile(path.join(serverPath, "server.properties"), "utf8"));
|
||||
}
|
||||
|
||||
// This is a fast and dirty way to implement a new feature, but i'm too exhausted to implement the same type as bedrock
|
||||
export async function updateConfig(config: {key: string, value: any}, platformOptions: bdsPlatformOptions = {id: "default"}) {
|
||||
const currentConfig = await getConfig(platformOptions);
|
||||
const { serverPath } = await pathControl("spigot", platformOptions);
|
||||
currentConfig[config.key] = config.value;
|
||||
return fs.writeFile(path.join(serverPath, "server.properties"), Proprieties.stringify(currentConfig));
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
import fs from "node:fs/promises";
|
||||
import utils from "node:util";
|
||||
import Proprieties, { properitiesBase } from "./lib/Proprieties.js";
|
||||
|
||||
export type configEdit = {name: string, data?: any};
|
||||
export async function manegerConfigProprieties<updateConfig extends configEdit, configJson extends properitiesBase = any>(config: {configPath: string, configManipulate: {[Properties in updateConfig["name"]]: ((config: string, value: updateConfig["data"]) => string)|{validate?: (value: updateConfig["data"]) => boolean, regexReplace: RegExp, valueFormat: string, addIfNotExist?: string}}}) {
|
||||
let configFile = await fs.readFile(config.configPath, "utf8");
|
||||
const mani = {save, editConfig, getConfig: () => Proprieties.parse<configJson>(configFile)};
|
||||
async function save() {
|
||||
await fs.writeFile(config.configPath, configFile);
|
||||
return configFile;
|
||||
}
|
||||
|
||||
function editConfig(serverConfig: updateConfig) {
|
||||
if (!config.configManipulate[serverConfig.name]) throw new Error("Key name not exist");
|
||||
const manipulation = config.configManipulate[serverConfig.name as updateConfig["name"]];
|
||||
if (typeof manipulation === "function") configFile = manipulation(configFile, serverConfig.data);
|
||||
else {
|
||||
if (typeof manipulation.validate === "function") if (!manipulation.validate(serverConfig.data)) throw new Error("Invaid value");
|
||||
if (!manipulation.regexReplace.test(configFile)) {
|
||||
if (manipulation.addIfNotExist) configFile += ("\n"+manipulation.addIfNotExist);
|
||||
else throw new Error("Not config exist!");
|
||||
}
|
||||
configFile = configFile.replace(manipulation.regexReplace, utils.format(manipulation.valueFormat, serverConfig.data));
|
||||
}
|
||||
return mani;
|
||||
}
|
||||
|
||||
return mani;
|
||||
}
|
@@ -1,155 +0,0 @@
|
||||
import * as coreUtils from "@sirherobrine23/coreutils";
|
||||
import net from "node:net";
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import tar from "tar";
|
||||
import { bdsRoot } from "./platformPathManeger.js";
|
||||
|
||||
export type payload = {
|
||||
httpVersion?: string,
|
||||
request?: {method: string, path: string},
|
||||
response?: {code: number, text: string},
|
||||
header?: {[key: string]: string|boolean|number},
|
||||
second?: payload,
|
||||
raw?: string,
|
||||
socket?: net.Socket
|
||||
body?: any,
|
||||
};
|
||||
|
||||
export class payloadError extends Error {
|
||||
public payload: payload;
|
||||
constructor(errMessage: string, payload: payload) {
|
||||
super(errMessage);
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
|
||||
const payloadRequest = /^(GET|POST|CONNECT|HEAD|PUT|DELETE)\s+(.*)\s+HTTP\/([0-9\.]+)/;
|
||||
const payloadResponse = /HTTP\/([0-9\.]+)\s+([0-9]+)\s+([\w\S\s]+)/;
|
||||
const parseHeard = /^([0-9A-Za-z\._-\s@]+):([\w\S\s]+)/;
|
||||
async function parsePayload(input: string|Buffer|net.Socket): Promise<payload> {
|
||||
const payloadBody: payload = {header: {}};
|
||||
let data = "";
|
||||
if (typeof input === "string") data = input; else {
|
||||
if (Buffer.isBuffer(input)) data = input.toString("utf8");
|
||||
else {
|
||||
payloadBody.socket = input;
|
||||
data = await new Promise<string>(done => input.once("data", dataInput => done(dataInput.toString("utf8"))));
|
||||
}
|
||||
}
|
||||
payloadBody.raw = data;
|
||||
if (/^{.*}$/.test(data.replace(/\r?\n/, ""))) {
|
||||
payloadBody.body = JSON.parse(data);
|
||||
return payloadBody;
|
||||
}
|
||||
for (const line of data.trim().split(/\r?\n/g)) {
|
||||
data = data.replace(line, "").trim();
|
||||
if (payloadResponse.test(line) && !payloadBody.request) {
|
||||
const [, httpVersion, CodeNumber, responseText] = line.match(payloadResponse);
|
||||
payloadBody.httpVersion = httpVersion;
|
||||
payloadBody.response = {
|
||||
code: parseInt(CodeNumber),
|
||||
text: responseText
|
||||
};
|
||||
} else if (payloadRequest.test(line) && !payloadBody.response) {
|
||||
const [, method, reqPath, httpVersion] = line.match(payloadRequest);
|
||||
if (!payloadBody.request) {
|
||||
payloadBody.httpVersion = httpVersion;
|
||||
payloadBody.request = {
|
||||
method: method,
|
||||
path: reqPath
|
||||
};
|
||||
continue;
|
||||
} else payloadBody.second = await parsePayload(`${line}\r\n${data}`);
|
||||
break;
|
||||
} else if (parseHeard.test(line.trim())) {
|
||||
const [, key, value] = line.trim().match(parseHeard);
|
||||
if (value.trim() === "false"||value.trim() === "true") payloadBody.header[key.trim()] = Boolean(value.trim());
|
||||
else if (/^[0-9]+$/.test(value.trim())) payloadBody.header[key.trim()] = parseFloat(value.trim());
|
||||
else payloadBody.header[key.trim()] = value.trim();
|
||||
continue;
|
||||
}
|
||||
payloadBody.body = data.trim();
|
||||
if (line.trim() === "") break;
|
||||
};
|
||||
if (payloadBody.body) {
|
||||
const backupBody = payloadBody.body;
|
||||
try {
|
||||
payloadBody.body = JSON.parse(backupBody);
|
||||
} catch (_) {
|
||||
payloadBody.body = backupBody;
|
||||
}
|
||||
}
|
||||
if (payloadBody.response) {
|
||||
if (!/[12][0-9][0-9]/.test(payloadBody.response.code.toFixed())) throw new payloadError(`Response code ${payloadBody.response.code}, text: ${payloadBody.response.text}`, payloadBody);
|
||||
}
|
||||
return payloadBody;
|
||||
}
|
||||
|
||||
function stringifyPayload(socket: net.Socket, response: payload) {
|
||||
let message = "";
|
||||
if (response.request) message += `${response.request.method.toUpperCase()} ${response.request.path} HTTP/${response.httpVersion||"1.0"}\r\n`;
|
||||
else message += `HTTP/${response.httpVersion||"1.0"} ${response.response.code} ${response.response.text}\r\n`;
|
||||
if (response.header) message += (Object.keys(response.header).map(key => `${key}: ${response.header[key]}`).join("\r\n"))+"\r\n";
|
||||
message += "\r\n";
|
||||
if (response.body !== undefined) {
|
||||
if (Array.isArray(response.body)||typeof response.body === "object") message += JSON.stringify(response.body);
|
||||
else if (typeof response.body === "string") message += response.body;
|
||||
else if (typeof response.body === "bigint") message += response.body.toString();
|
||||
else message += String(response.body);
|
||||
}
|
||||
socket.write(message);
|
||||
return socket;
|
||||
}
|
||||
|
||||
export class exportBds {
|
||||
public acceptConnection = true;
|
||||
public authToken = crypto.randomBytes(16).toString("base64");
|
||||
#server = net.createServer(async (socket): Promise<any> => {
|
||||
if (!this.acceptConnection) return stringifyPayload(socket, {response: {code: 400, text: "Server locked"}, body: {erro: "Server locked"}}).end();
|
||||
const payload = await parsePayload(await new Promise<string>(done => socket.once("data", res => done(res.toString("utf8")))));
|
||||
if (payload.header.Authorization !== this.authToken) return stringifyPayload(socket, {response: {code: 401, text: "Not allowed"}, body: {error: "Invalid token"}}).end();
|
||||
else stringifyPayload(socket, {response: {code: 200, text: "Success"}, header: {Date: (new Date()).toISOString(), "Content-Type": "bdsStream/tar"}});
|
||||
this.acceptConnection = false;
|
||||
console.log("Sending to %s", socket.localAddress+":"+socket.localPort);
|
||||
// Compact bds root
|
||||
const tarCompress = tar.create({gzip: true, cwd: bdsRoot}, await fs.readdir(bdsRoot));
|
||||
tarCompress.pipe(socket);
|
||||
tarCompress.on("end", () => this.#server.close());
|
||||
socket.on("close", tarCompress.destroy);
|
||||
});
|
||||
|
||||
public async listen(port = 0) {
|
||||
return new Promise<number>(done => {
|
||||
this.#server.listen(port, async () => {
|
||||
const externalIP = await coreUtils.httpRequestClient.getExternalIP();
|
||||
let address = this.#server.address()["address"], port = this.#server.address()["port"];
|
||||
if (/::/.test(address)) address = `[${address}]`;
|
||||
console.log("auth token '%s'", this.authToken);
|
||||
console.log("Port listen on http://%s:%s/", address, port);
|
||||
console.log("Port listen on http://%s:%s/", externalIP.ipv4, port);
|
||||
if (externalIP.ipv6) console.log("Port listen on http://[%s]:%s/", externalIP.ipv6, port);
|
||||
return done(port);
|
||||
});
|
||||
});
|
||||
}
|
||||
public async waitClose() {
|
||||
return new Promise<void>((done, reject) => {
|
||||
this.#server.on("error", reject);
|
||||
this.#server.once("close", done);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function importBds(option: {host: string, port: number, authToken: string}) {
|
||||
if (await coreUtils.extendFs.exists(bdsRoot)) await fs.rename(bdsRoot, bdsRoot+"_backup_"+Date.now());
|
||||
const {socket} = await parsePayload(stringifyPayload(net.createConnection({host: option.host, port: option.port}), {request: {method: "GET", path: "/"}, header: {Authorization: option.authToken}}));
|
||||
console.info("Connection sucess!");
|
||||
const tar_extract = tar.extract({cwd: bdsRoot, noChmod: false, noMtime: false, preserveOwner: true});
|
||||
socket.pipe(tar_extract);
|
||||
return new Promise<void>((done, reject) => {
|
||||
socket.on("error", reject);
|
||||
tar_extract.on("error", reject);
|
||||
socket.once("close", () => tar_extract.on("finish", done));
|
||||
});
|
||||
}
|
@@ -1,259 +0,0 @@
|
||||
import fs from "node:fs";
|
||||
import readline from "node:readline";
|
||||
import child_process from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { bdsPlatform } from "./platformPathManeger.js";
|
||||
import debug from "debug";
|
||||
export const internalSessions: {[sessionID: string]: serverActionV2} = {};
|
||||
process.once("exit", () => Object.keys(internalSessions).forEach(id => internalSessions[id].stopServer()));
|
||||
const actionsDebug = debug("bdscore:global_platform");
|
||||
|
||||
export type actionCommandOption = {command: string, args?: string[], options?: fs.ObjectEncodingOptions & child_process.ExecFileOptions & {logPath?: {stdout: string, stderr?: string}}};
|
||||
export type playerHistoric = {[player: string]: {action: "connect"|"disconnect"|"unknown"|"spawn"; date: Date; history: Array<{action: "connect"|"disconnect"|"unknown"|"spawn"; date: Date}>}};
|
||||
export type playerBase = {
|
||||
playerName: string,
|
||||
connectTime: Date,
|
||||
xuid?: string,
|
||||
action?: string,
|
||||
lineString?: string
|
||||
};
|
||||
export type portListen = {port: number, host?: string, type: "TCP"|"UDP"|"TCP/UDP", protocol: "IPv4"|"IPv6"|"IPV4/IPv6"|"Unknown", proxyOrigin?: number, plugin?: string};
|
||||
export type serverStarted = Date|{
|
||||
onAvaible: Date,
|
||||
timePassed: number
|
||||
};
|
||||
|
||||
export declare interface serverActionsEvent {
|
||||
on(act: "error", fn: (data: any) => void): this;
|
||||
once(act: "error", fn: (data: any) => void): this;
|
||||
emit(act: "error", data: any): boolean;
|
||||
|
||||
on(act: "playerConnect"|"playerDisconnect"|"playerUnknown"|"playerSpawn", fn: (data: playerBase) => void): this;
|
||||
once(act: "playerConnect"|"playerDisconnect"|"playerUnknown"|"playerSpawn", fn: (data: playerBase) => void): this;
|
||||
emit(act: "playerConnect"|"playerDisconnect"|"playerUnknown"|"playerSpawn", data: playerBase): boolean;
|
||||
|
||||
on(act: "portListening", fn: (data: portListen) => void): this;
|
||||
once(act: "portListening", fn: (data: portListen) => void): this;
|
||||
emit(act: "portListening", data: portListen): boolean;
|
||||
|
||||
on(act: "serverStarted", fn: (data: serverStarted) => void): this;
|
||||
once(act: "serverStarted", fn: (data: serverStarted) => void): this;
|
||||
emit(act: "serverStarted", data: serverStarted): boolean;
|
||||
|
||||
on(act: "log_stderr", fn: (data: string) => void): this;
|
||||
once(act: "log_stderr", fn: (data: string) => void): this;
|
||||
emit(act: "log_stderr", data: string): boolean;
|
||||
|
||||
on(act: "log_stdout", fn: (data: string) => void): this;
|
||||
once(act: "log_stdout", fn: (data: string) => void): this;
|
||||
emit(act: "log_stdout", data: string): boolean;
|
||||
|
||||
on(act: "log", fn: (data: string) => void): this;
|
||||
once(act: "log", fn: (data: string) => void): this;
|
||||
emit(act: "log", data: string): boolean;
|
||||
|
||||
on(act: "exit", fn: (data: {code: number, signal: NodeJS.Signals}) => void): this;
|
||||
once(act: "exit", fn: (data: {code: number, signal: NodeJS.Signals}) => void): this;
|
||||
emit(act: "exit", data: {code: number, signal: NodeJS.Signals}): boolean;
|
||||
}
|
||||
export class serverActionsEvent extends EventEmitter {};
|
||||
|
||||
export type playerCallback = {
|
||||
[T in playerHistoric[number]["action"]]: (player: playerBase) => void
|
||||
};
|
||||
|
||||
export type actionsV2 = {
|
||||
serverStarted?: (data: string, done: (startedDate: serverStarted) => void) => void,
|
||||
portListening?: (data: string, done: (portInfo: portListen) => void) => void,
|
||||
playerAction?: (data: string, Callbacks: playerCallback) => void,
|
||||
stopServer?: (components: {child: child_process.ChildProcess, actions: serverActionV2}) => void|ReturnType<serverActionV2["stopServer"]>,
|
||||
playerTp?: (actions: serverActionV2, playerName: string, x: number|string, y: number|string, z: number|string) => void,
|
||||
};
|
||||
|
||||
export type serverActionV2 = {
|
||||
version: 2,
|
||||
id: string,
|
||||
platform: bdsPlatform,
|
||||
events: serverActionsEvent,
|
||||
serverCommand?: actionCommandOption,
|
||||
serverStarted?: serverStarted,
|
||||
killProcess: (signal?: number|NodeJS.Signals) => boolean,
|
||||
waitExit: () => Promise<number|NodeJS.Signals>
|
||||
stopServer: () => ReturnType<serverActionV2["waitExit"]>,
|
||||
runCommand?: (...command: Array<string|number|boolean>) => serverActionV2,
|
||||
tp?: (playerName: string, x: number|string, y: number|string, z: number|string) => serverActionV2,
|
||||
portListening: {[port: number]: portListen},
|
||||
playerActions: playerHistoric,
|
||||
};
|
||||
|
||||
/**
|
||||
* A second version for actions, this version fixes several issues that occur with the old class-based version.
|
||||
*/
|
||||
export async function actionV2(options: {id: string, platform: bdsPlatform, processConfig: actionCommandOption, hooks: actionsV2}) {
|
||||
if (internalSessions[options.id]) throw new Error("The platform with that id is already running!");
|
||||
// Add exec options if not exists
|
||||
const {processConfig} = options;
|
||||
if (!processConfig.args) processConfig.args = [];
|
||||
if (!processConfig.options) processConfig.options = {};
|
||||
// Disable log buffer limit
|
||||
processConfig.options.maxBuffer = Infinity;
|
||||
processConfig.options.env = {...process.env, ...(processConfig.options.env||{})}
|
||||
|
||||
// Run commands
|
||||
actionsDebug("Stating %s", options.id);
|
||||
const childProcess = child_process.execFile(processConfig.command, processConfig.args, processConfig.options);
|
||||
const serverObject: serverActionV2 = {
|
||||
version: 2,
|
||||
id: options.id,
|
||||
platform: options.platform,
|
||||
events: new serverActionsEvent({captureRejections: false}),
|
||||
playerActions: {},
|
||||
portListening: {},
|
||||
serverCommand: processConfig,
|
||||
runCommand(...command: Array<string|number|boolean>) {
|
||||
const commandMaped = command.map(a => String(a)).join(" ")
|
||||
actionsDebug("%s run '%s'", options.id, commandMaped);
|
||||
childProcess.stdin.write(commandMaped+"\n");
|
||||
return serverObject;
|
||||
},
|
||||
killProcess(signal?: number|NodeJS.Signals) {
|
||||
actionsDebug("%s call kill with %s", options.id, signal);
|
||||
if(childProcess?.killed) return childProcess?.killed;
|
||||
return childProcess.kill(signal);
|
||||
},
|
||||
stopServer() {
|
||||
actionsDebug("%s call stop server", options.id);
|
||||
if (options.hooks.stopServer === undefined) childProcess.kill("SIGKILL");
|
||||
const data = options.hooks.stopServer({child: childProcess, actions: serverObject});
|
||||
if (!data) return serverObject.waitExit();
|
||||
return data;
|
||||
},
|
||||
async waitExit(): Promise<number|NodeJS.Signals> {
|
||||
if (childProcess.exitCode||childProcess.signalCode) return childProcess.exitCode||childProcess.signalCode;
|
||||
return new Promise<number>((done, reject) => {
|
||||
childProcess.once("error", err => reject(err));
|
||||
childProcess.once("close", code => done(code));
|
||||
});
|
||||
},
|
||||
tp(playerName: string, x: number|string = 0, y: number|string = 0, z: number|string = 0) {
|
||||
const tpfunction = options.hooks.playerTp;
|
||||
if (tpfunction === undefined) throw new Error("tp is disabled, tpfunction not defined to platform!");
|
||||
if (!(playerName?.startsWith("@")||!!this.playerActions[playerName])) throw new Error("Player or target not exist");
|
||||
tpfunction(serverObject, playerName, x, y, z);
|
||||
return serverObject;
|
||||
}
|
||||
};
|
||||
|
||||
// Add to internal sessions
|
||||
internalSessions[options.id] = serverObject;
|
||||
serverObject.events.on("exit", () => delete internalSessions[options.id]);
|
||||
|
||||
// Break lines with readline
|
||||
const readlineStdout = readline.createInterface(childProcess.stdout);
|
||||
readlineStdout.on("line", data => {
|
||||
serverObject.events.emit("log", data);
|
||||
serverObject.events.emit("log_stdout", data)
|
||||
});
|
||||
const readlineStderr = readline.createInterface(childProcess.stderr);
|
||||
readlineStderr.on("line", data => {
|
||||
serverObject.events.emit("log", data);
|
||||
serverObject.events.emit("log_stderr", data);
|
||||
});
|
||||
|
||||
// Register hooks
|
||||
// Server avaible to player
|
||||
if (options.hooks.serverStarted) {
|
||||
actionsDebug("Register server started function to %s", options.id);
|
||||
function started(data: string) {
|
||||
return options.hooks.serverStarted(data, onAvaible => {
|
||||
actionsDebug("Call server started function to %s", options.id);
|
||||
if (serverObject.serverStarted) return;
|
||||
serverObject.serverStarted = onAvaible;
|
||||
serverObject.events.emit("serverStarted", onAvaible);
|
||||
});
|
||||
}
|
||||
readlineStderr.on("line", started);
|
||||
readlineStdout.on("line", started);
|
||||
}
|
||||
|
||||
// Server Player actions
|
||||
if (options.hooks.playerAction) {
|
||||
function updateHistoric(action: playerHistoric[0]["action"], data: playerBase) {
|
||||
if (!serverObject.playerActions[data.playerName]) {
|
||||
serverObject.playerActions[data.playerName] = {
|
||||
action,
|
||||
date: data.connectTime,
|
||||
history: [
|
||||
{
|
||||
action,
|
||||
date: data.connectTime
|
||||
}
|
||||
]
|
||||
};
|
||||
return;
|
||||
}
|
||||
serverObject.playerActions[data.playerName].action = action;
|
||||
serverObject.playerActions[data.playerName].date = data.connectTime;
|
||||
serverObject.playerActions[data.playerName].history.push({
|
||||
action,
|
||||
date: data.connectTime
|
||||
});
|
||||
}
|
||||
actionsDebug("Register player actions to %s", options.id);
|
||||
const playerConnect = (data: playerBase) => {
|
||||
actionsDebug("Player actions to %s, call connect", options.id);
|
||||
serverObject.events.emit("playerConnect", data);
|
||||
updateHistoric("connect", data);
|
||||
}
|
||||
const playerSpawn = (data: playerBase) => {
|
||||
actionsDebug("Player actions to %s, call spawn", options.id);
|
||||
serverObject.events.emit("playerSpawn", data);
|
||||
updateHistoric("spawn", data);
|
||||
}
|
||||
const playerDisonnect = (data: playerBase) => {
|
||||
actionsDebug("Player actions to %s, call disconnect", options.id);
|
||||
serverObject.events.emit("playerDisconnect", data);
|
||||
updateHistoric("disconnect", data);
|
||||
}
|
||||
const playerUnknown = (data: playerBase) => {
|
||||
actionsDebug("Player actions to %s, call unknown", options.id);
|
||||
serverObject.events.emit("playerUnknown", data);
|
||||
updateHistoric("unknown", data);
|
||||
}
|
||||
const mainFunc = (data: string) => options.hooks.playerAction(data, {
|
||||
connect: playerConnect,
|
||||
disconnect: playerDisonnect,
|
||||
unknown: playerUnknown,
|
||||
spawn: playerSpawn
|
||||
});
|
||||
readlineStdout.on("line", mainFunc);
|
||||
readlineStderr.on("line", mainFunc);
|
||||
}
|
||||
|
||||
// Port listen
|
||||
if (options.hooks.portListening) {
|
||||
actionsDebug("Register port listening to %s", options.id);
|
||||
const mainFunc = (data: string) => options.hooks.portListening(data, (portData) => {
|
||||
actionsDebug("Port listen to %s", options.id);
|
||||
serverObject.events.emit("portListening", portData);
|
||||
serverObject.portListening[portData.port] = portData;
|
||||
})
|
||||
readlineStdout.on("line", mainFunc);
|
||||
readlineStderr.on("line", mainFunc);
|
||||
}
|
||||
|
||||
// Pipe logs
|
||||
if (processConfig.options.logPath) {
|
||||
actionsDebug("Pipe log to file in %s", options.id);
|
||||
childProcess.stdout.pipe(fs.createWriteStream(processConfig.options.logPath.stdout));
|
||||
if (processConfig.options.logPath.stderr) childProcess.stderr.pipe(fs.createWriteStream(processConfig.options.logPath.stderr));
|
||||
}
|
||||
|
||||
// Add listeners to actions events
|
||||
childProcess.on("error", data => serverObject.events.emit("error", data));
|
||||
childProcess.on("exit", (code, signal) => serverObject.events.emit("exit", {code, signal}));
|
||||
childProcess.on("exit", () => serverObject.events.removeAllListeners());
|
||||
|
||||
// Return session maneger
|
||||
return serverObject;
|
||||
}
|
33
src/index.ts
33
src/index.ts
@@ -1,33 +0,0 @@
|
||||
// Utils
|
||||
import * as platformPathManeger from "./platformPathManeger.js"
|
||||
import * as globalPlatfroms from "./globalPlatfroms.js";
|
||||
import * as pluginManeger from "./plugin/plugin.js";
|
||||
import * as export_import from "./export_import.js";
|
||||
import * as pluginHooks from "./plugin/hook.js";
|
||||
import * as proxy from "./lib/proxy.js";
|
||||
|
||||
// Platforms
|
||||
import * as Bedrock from "./Platforms/bedrock.js";
|
||||
import * as Java from "./Platforms/java.js";
|
||||
import * as PocketmineMP from "./Platforms/pocketmine.js";
|
||||
import * as Spigot from "./Platforms/spigot.js";
|
||||
import * as Powernukkit from "./Platforms/pwnuukit.js";
|
||||
import * as PaperMC from "./Platforms/paper.js";
|
||||
|
||||
export {platformPathManeger, globalPlatfroms, pluginManeger, export_import, PocketmineMP, pluginHooks, Powernukkit, PaperMC, Bedrock, Spigot, proxy, Java};
|
||||
export default {
|
||||
Bedrock,
|
||||
Java,
|
||||
PocketmineMP,
|
||||
Powernukkit,
|
||||
PaperMC,
|
||||
Spigot,
|
||||
utils: {
|
||||
platformPathManeger,
|
||||
globalPlatfroms,
|
||||
pluginManeger,
|
||||
pluginHooks,
|
||||
export_import,
|
||||
proxy
|
||||
}
|
||||
};
|
||||
|
13
src/platform/Bedrock.ts
Normal file
13
src/platform/Bedrock.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { serverManeger, pathOptions } from "../serverManeger.js";
|
||||
|
||||
export default async function Bedrock(options?: pathOptions & {variant?: string}) {
|
||||
options = {...options};
|
||||
|
||||
return serverManeger({
|
||||
exec: {
|
||||
exec: "java",
|
||||
args: []
|
||||
},
|
||||
actions: {}
|
||||
})
|
||||
}
|
@@ -1,142 +0,0 @@
|
||||
import { extendFs } from "@sirherobrine23/coreutils";
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
export let bdsRoot = process.env.BDS_HOME?(process.env.BDS_HOME.startsWith("~")?process.env.BDS_HOME.replace("~", os.homedir()):process.env.BDS_HOME):path.join(os.homedir(), ".bdsManeger");
|
||||
|
||||
export type bdsPlatform = "bedrock"|"java"|"pocketmine"|"spigot"|"powernukkit"|"paper";
|
||||
export type bdsPlatformOptions = {
|
||||
newId?: boolean,
|
||||
id?: "default"|string,
|
||||
withBuildFolder?: boolean
|
||||
};
|
||||
|
||||
// This array to valida platform imput!
|
||||
const platformArray: bdsPlatform[] = [
|
||||
"bedrock",
|
||||
"java",
|
||||
"pocketmine",
|
||||
"spigot",
|
||||
"powernukkit",
|
||||
"paper"
|
||||
];
|
||||
|
||||
// Test ID
|
||||
const idRegex = /^[A-Za-z0-9]*$/;
|
||||
|
||||
/**
|
||||
* Register or get folder to Servers, this is to create and maneger folders
|
||||
* @param platform - Select platform to maneger folders
|
||||
* @param options - ?
|
||||
*/
|
||||
export async function pathControl(platform: bdsPlatform, options?: bdsPlatformOptions) {
|
||||
if (!platformArray.includes(platform)) throw new Error("Invalid platform");
|
||||
const platformRoot = path.join(bdsRoot, platform);
|
||||
if (!await extendFs.exists(platformRoot)) await fs.mkdir(platformRoot, {recursive: true});
|
||||
if (!options) options = {};
|
||||
|
||||
// Create if not exists
|
||||
const foldersAndLink = await fs.readdir(platformRoot);
|
||||
if (foldersAndLink.length === 0) options.newId = true;
|
||||
if (options.newId) {
|
||||
options.id = crypto.randomBytes(16).toString("hex");
|
||||
fs.mkdir(path.join(platformRoot, options.id), {recursive: true});
|
||||
if (await extendFs.exists(path.join(platformRoot, "default"))) await fs.unlink(path.join(platformRoot, "default"));
|
||||
await fs.symlink(path.join(platformRoot, options.id), path.join(platformRoot, "default"));
|
||||
} else if (!await extendFs.exists(path.join(platformRoot, options.id))) throw new Error("Folder ID not created!");
|
||||
|
||||
// Get real id
|
||||
if (!idRegex.test(options.id)) throw new Error("Invalid Platform ID");
|
||||
if (options?.id === "default") options.id = path.basename(await fs.realpath(path.join(platformRoot, options.id)).catch(async () => (await fs.readdir(platformRoot)).sort().at(0)));
|
||||
|
||||
// Mount Paths
|
||||
const serverRoot = path.join(platformRoot, options.id);
|
||||
const serverPath = path.join(serverRoot, "server");
|
||||
const hooksPath = path.join(serverRoot, "hooks");
|
||||
const backupPath = path.join(serverRoot, "backup");
|
||||
const logsPath = path.join(serverRoot, "logs");
|
||||
let buildFolder: string;
|
||||
if (options?.withBuildFolder) buildFolder = path.join(serverRoot, "build");
|
||||
|
||||
// Create folder if not exists
|
||||
if (!(await extendFs.exists(serverRoot))) await fs.mkdir(serverRoot, {recursive: true});
|
||||
if (!(await extendFs.exists(serverPath))) await fs.mkdir(serverPath, {recursive: true});
|
||||
if (!(await extendFs.exists(hooksPath))) await fs.mkdir(hooksPath, {recursive: true});
|
||||
if (!(await extendFs.exists(backupPath))) await fs.mkdir(backupPath, {recursive: true});
|
||||
if (!(await extendFs.exists(logsPath))) await fs.mkdir(logsPath, {recursive: true});
|
||||
if (buildFolder && !(await extendFs.exists(buildFolder))) await fs.mkdir(buildFolder, {recursive: true});
|
||||
|
||||
return {
|
||||
id: options?.id,
|
||||
serverRoot,
|
||||
serverPath,
|
||||
hooksPath,
|
||||
backupPath,
|
||||
logsPath,
|
||||
buildFolder,
|
||||
platformIDs: foldersAndLink
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Change default folder to Platform
|
||||
* @param platform
|
||||
* @param id
|
||||
* @returns
|
||||
*/
|
||||
export async function changeDefault(platform: bdsPlatform, id: bdsPlatformOptions["id"]) {
|
||||
if (!platformArray.includes(platform)) throw new Error("Invalid platform");
|
||||
const serverPlatform = path.join(bdsRoot, platform);
|
||||
if (!await extendFs.exists(serverPlatform)) throw new Error("Install server fist!");
|
||||
const ids = (await fs.readdir(serverPlatform)).filter(folder => folder.toLowerCase() !== "default");
|
||||
if (!ids.includes(id)) throw new Error("Id not exists to Platform");
|
||||
const oldPath = fs.realpath(path.join(bdsRoot, platform, "default"));
|
||||
if (await extendFs.exists(path.join(bdsRoot, platform, "default"))) await fs.unlink(path.join(bdsRoot, platform, "default"));
|
||||
await fs.symlink(path.join(bdsRoot, platform, id), path.join(bdsRoot, platform, "default"));
|
||||
|
||||
return {
|
||||
oldPath,
|
||||
newPath: path.join(bdsRoot, platform, id)
|
||||
};
|
||||
}
|
||||
|
||||
export type platformIds = {
|
||||
[platform in bdsPlatform]?: {
|
||||
realID?: string,
|
||||
id: string,
|
||||
}[]
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all ids to all platforms installed
|
||||
* @returns
|
||||
*/
|
||||
export async function getIds(): Promise<platformIds>;
|
||||
/**
|
||||
* Get all ids to platform
|
||||
* @param platform - Set platform to get ids
|
||||
* @returns
|
||||
*/
|
||||
export async function getIds(platform: bdsPlatform): Promise<string[]>;
|
||||
export async function getIds(platform?: bdsPlatform): Promise<platformIds|string[]> {
|
||||
if (!platform) {
|
||||
const platformIds: platformIds = {};
|
||||
if (!await extendFs.exists(bdsRoot)) return platformIds;
|
||||
const Platforms = (await fs.readdir(bdsRoot)).filter(folder => platformArray.includes(folder as bdsPlatform));
|
||||
await Promise.all(Platforms.map(async Platform => {
|
||||
for (const id of await fs.readdir(path.join(bdsRoot, Platform))) {
|
||||
if (!platformIds[Platform]) platformIds[Platform] = []
|
||||
const idPlatform = path.join(bdsRoot, Platform, id);
|
||||
const realPath = await fs.realpath(idPlatform);
|
||||
if (idPlatform !== realPath) platformIds[Platform].push({id, realID: path.basename(realPath)});
|
||||
else platformIds[Platform].push({id});
|
||||
}
|
||||
}));
|
||||
return platformIds;
|
||||
}
|
||||
if (!platformArray.includes(platform)) throw new Error("Invalid platform");
|
||||
const serverPlatform = path.join(bdsRoot, platform);
|
||||
if (!await extendFs.exists(serverPlatform)) throw new Error("Install server fist!");
|
||||
return (await fs.readdir(serverPlatform)).filter(folder => folder.toLowerCase() !== "default");
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
import { httpRequestLarge, childPromisses } from "@sirherobrine23/coreutils";
|
||||
import { actionsV2 } from "../globalPlatfroms.js";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
|
||||
export type hooksPlatform = "bedrock"|"java"|"pocketmine"|"spigot"|"powernukkit"|"paper";
|
||||
export type hooksPlatformGeneric = hooksPlatform|"generic";
|
||||
export type hooksRegister = {
|
||||
scriptName: string,
|
||||
platforms: hooksPlatformGeneric[],
|
||||
register: (actions: actionsV2) => void,
|
||||
};
|
||||
|
||||
async function exists(path: string) {
|
||||
return fs.access(path).then(() => true).catch(() => false);
|
||||
}
|
||||
|
||||
const gitUrlDetect = /(^http:\/\/.*\.git$|^http:\/\/.*.git$|^git:\/\/)/;
|
||||
|
||||
export class script_hook {
|
||||
#serverActions: actionsV2;
|
||||
#currentPlatform: hooksPlatformGeneric;
|
||||
#localFolder: string;
|
||||
|
||||
async #registerScript(filePath: string) {
|
||||
const lo_script = (await(eval(`import("${filePath}")`))) as hooksRegister|{default: hooksRegister};
|
||||
let script: hooksRegister;
|
||||
if (lo_script["default"]) script = lo_script["default"];
|
||||
else script = lo_script as hooksRegister;
|
||||
console.log(script);
|
||||
if (!script.scriptName) throw new Error("Scriptname in null");
|
||||
if (!script.register) throw new Error("Register not is function");
|
||||
if (!(script.platforms.includes("generic")||script.platforms.includes(this.#currentPlatform))) throw new Error(`Cannot get valid platform (${this.#currentPlatform}) (${script.platforms?.join(", ")})`);
|
||||
const { scriptName, register } = script;
|
||||
console.log("Loading hook %s (External)", scriptName);
|
||||
return (Promise.resolve().then(() => register(this.#serverActions))).then(() => console.log("hook %s loaded (External)", scriptName));
|
||||
};
|
||||
|
||||
async #loadLocalScript() {
|
||||
if (!this.#serverActions) throw new Error("Server actions (globalPlatform) is undefined");
|
||||
if (!await exists(this.#localFolder)) await fs.mkdir(this.#localFolder, {recursive: true});
|
||||
const localFiles = (await fs.readdir(this.#localFolder)).map(file => path.join(this.#localFolder, file));
|
||||
for (const localFile of localFiles) {
|
||||
await fs.lstat(localFile).then(async stat => {
|
||||
if (stat.isFile()) return this.#registerScript(localFile);
|
||||
if (await exists(path.join(localFile, "package.json"))) {
|
||||
const externalHookPackage = JSON.parse(await fs.readFile(path.join(localFile, "package.json"), "utf8"));
|
||||
let indexFile: string;
|
||||
if (externalHookPackage.main) indexFile = path.join(localFile, externalHookPackage.main);
|
||||
else {
|
||||
const files = (await fs.readdir(localFile)).filter(file => /index\.(js|cjs|mjs)$/.test(file));
|
||||
if (files.length === 0) throw new Error("no index files");
|
||||
indexFile = path.join(localFile, files[0]);
|
||||
}
|
||||
if (!indexFile) throw new Error("No file to init");
|
||||
return this.#registerScript(indexFile);
|
||||
}
|
||||
throw new Error("Know know, boom!");
|
||||
}).catch(err => console.error(String(err)));
|
||||
}
|
||||
}
|
||||
|
||||
public async installHook(urlHost: string, fileName?: string) {
|
||||
if (!await exists(this.#localFolder)) await fs.mkdir(this.#localFolder, {recursive: true});
|
||||
if (!fileName) fileName = path.basename(urlHost);
|
||||
const onSave = path.join(this.#localFolder, fileName);
|
||||
// Git
|
||||
if (gitUrlDetect.test(urlHost)) {
|
||||
await childPromisses.execFile("git", ["clone", urlHost, "--depth", "1", onSave], {cwd: this.#localFolder});
|
||||
if (await exists(path.join(onSave, "package.json"))) await childPromisses.execFile("npm", ["install", "--no-save"], {cwd: onSave});
|
||||
} else await httpRequestLarge.saveFile({url: urlHost, filePath: onSave});
|
||||
if (!!this.#serverActions) await this.#registerScript(onSave);
|
||||
return;
|
||||
}
|
||||
|
||||
constructor(hookFolder: string, targetPlatform: hooksPlatform, platformActions?: actionsV2) {
|
||||
this.#serverActions = platformActions;
|
||||
this.#currentPlatform = targetPlatform;
|
||||
this.#localFolder = hookFolder;
|
||||
if (platformActions) this.#loadLocalScript();
|
||||
}
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
import path from "node:path";
|
||||
import type { bdsPlatform } from "../platformPathManeger.js";
|
||||
import { httpRequest, httpRequestLarge } from "@sirherobrine23/coreutils";
|
||||
|
||||
export type pluginConfig = {
|
||||
version?: 1,
|
||||
name: string,
|
||||
url: string,
|
||||
fileName?: string,
|
||||
platforms: bdsPlatform[],
|
||||
dependecies?: string[],
|
||||
/** @deprecated */
|
||||
scripts?: string[],
|
||||
};
|
||||
|
||||
export type pluginV2 = {
|
||||
version: 2,
|
||||
name: string,
|
||||
pluginVersion?: string,
|
||||
platform: {
|
||||
[platform: string]: {
|
||||
url: string,
|
||||
fileName?: string,
|
||||
dependencies?: string[]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class pluginManeger {
|
||||
#pluginFolder: string;
|
||||
#platform: bdsPlatform;
|
||||
constructor(pluginFolder: string, options?: {
|
||||
bdsPlatfrom: bdsPlatform
|
||||
}) {
|
||||
this.#pluginFolder = pluginFolder;
|
||||
this.#platform = options?.bdsPlatfrom;
|
||||
}
|
||||
|
||||
async installPlugin(plugin: string) {
|
||||
const urlandbds = /http[s]:\/\/|bdsplugin:\/\//;
|
||||
if (/bdsplugin:\/\//.test(plugin)) plugin = `https://raw.githubusercontent.com/The-Bds-Maneger/plugin_list/main/plugins/${plugin.replace(urlandbds, "").replace(".json", "")}.json`;
|
||||
else if (!/http[s]:\/\/\//.test(plugin)) plugin = "bdsplugin://"+plugin;
|
||||
const info = await httpRequest.getJSON<pluginConfig|pluginV2>(plugin);
|
||||
if (info.version === 2) {
|
||||
const platformData = info.platform[this.#platform];
|
||||
if (!platformData) throw new Error("Platform not supported to Plugin!");
|
||||
await httpRequestLarge.saveFile({
|
||||
url: platformData.url,
|
||||
filePath: path.join(this.#pluginFolder, platformData.fileName||path.basename(platformData.url))
|
||||
});
|
||||
if (platformData.dependencies) await Promise.all(platformData.dependencies.map(dep => this.installPlugin(dep)));
|
||||
return;
|
||||
}
|
||||
if (!info.platforms.includes(this.#platform)) throw new Error("Platform not supported to Plugin!");
|
||||
await httpRequestLarge.saveFile({
|
||||
url: info.url,
|
||||
filePath: path.join(this.#pluginFolder, info.fileName||path.basename(info.url))
|
||||
});
|
||||
if (info.dependecies) await Promise.all(info.dependecies.map(dep => this.installPlugin(dep)));
|
||||
}
|
||||
};
|
98
src/serverManeger.ts
Normal file
98
src/serverManeger.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { createInterface as readline } from "node:readline";
|
||||
import { promises as fs } from "node:fs";
|
||||
import child_process, { ChildProcess } from "node:child_process";
|
||||
import { extendFs } from "@sirherobrine23/coreutils";
|
||||
import crypto from "node:crypto";
|
||||
import path from "node:path";
|
||||
import os from "node:os";
|
||||
|
||||
export type pathOptions = {
|
||||
id?: "default"|string,
|
||||
newId?: boolean,
|
||||
withBuildFolder?: boolean,
|
||||
};
|
||||
|
||||
export let bdsRoot = process.env.BDS_HOME?(process.env.BDS_HOME.startsWith("~")?process.env.BDS_HOME.replace("~", os.homedir()):process.env.BDS_HOME):path.join(os.homedir(), ".bdsManeger");
|
||||
export async function platformPathID(platform: "bedrock"|"java", options?: pathOptions) {
|
||||
if (!(["bedrock", "java"].includes(platform))) throw new Error("Invalid platform target");
|
||||
options = {id: "default", ...options};
|
||||
const platformRoot = path.join(bdsRoot, platform);
|
||||
if (!await extendFs.exists(platformRoot)) await fs.mkdir(platformRoot, {recursive: true});
|
||||
if (!options) options = {};
|
||||
|
||||
// Create if not exists
|
||||
const foldersAndLink = await fs.readdir(platformRoot);
|
||||
if (foldersAndLink.length === 0) options.newId = true;
|
||||
if (options.newId) {
|
||||
options.id = crypto.randomBytes(16).toString("hex");
|
||||
fs.mkdir(path.join(platformRoot, options.id), {recursive: true});
|
||||
if (await extendFs.exists(path.join(platformRoot, "default"))) await fs.unlink(path.join(platformRoot, "default"));
|
||||
await fs.symlink(path.join(platformRoot, options.id), path.join(platformRoot, "default"));
|
||||
} else if (!await extendFs.exists(path.join(platformRoot, options.id))) throw new Error("Folder ID not created!");
|
||||
|
||||
// Get real id
|
||||
if (!(/^[A-Za-z0-9]*$/).test(options.id)) throw new Error("Invalid Platform ID");
|
||||
if (options?.id === "default") options.id = path.basename(await fs.realpath(path.join(platformRoot, options.id)).catch(async () => (await fs.readdir(platformRoot)).sort().at(0)));
|
||||
|
||||
// Mount Paths
|
||||
const serverRoot = path.join(platformRoot, options.id);
|
||||
const serverPath = path.join(serverRoot, "server");
|
||||
const hooksPath = path.join(serverRoot, "hooks");
|
||||
const backupPath = path.join(serverRoot, "backup");
|
||||
const logsPath = path.join(serverRoot, "logs");
|
||||
let buildFolder: string;
|
||||
if (options?.withBuildFolder) buildFolder = path.join(serverRoot, "build");
|
||||
|
||||
// Create folder if not exists
|
||||
if (!(await extendFs.exists(serverRoot))) await fs.mkdir(serverRoot, {recursive: true});
|
||||
if (!(await extendFs.exists(serverPath))) await fs.mkdir(serverPath, {recursive: true});
|
||||
if (!(await extendFs.exists(hooksPath))) await fs.mkdir(hooksPath, {recursive: true});
|
||||
if (!(await extendFs.exists(backupPath))) await fs.mkdir(backupPath, {recursive: true});
|
||||
if (!(await extendFs.exists(logsPath))) await fs.mkdir(logsPath, {recursive: true});
|
||||
if (buildFolder && !(await extendFs.exists(buildFolder))) await fs.mkdir(buildFolder, {recursive: true});
|
||||
|
||||
return {
|
||||
id: options?.id,
|
||||
serverRoot,
|
||||
serverPath,
|
||||
hooksPath,
|
||||
backupPath,
|
||||
logsPath,
|
||||
buildFolder,
|
||||
platformIDs: foldersAndLink
|
||||
};
|
||||
}
|
||||
|
||||
export type serverConfig = {
|
||||
exec: {
|
||||
exec: string,
|
||||
args?: string[],
|
||||
cwd?: string,
|
||||
env?: NodeJS.ProcessEnv & {[key: string]: string},
|
||||
},
|
||||
actions: {
|
||||
stopServer?: (child_process: ChildProcess) => void,
|
||||
onStart?: (lineData: string, fnRegister: (data: {serverAvaible: Date, bootUp?: number}) => void) => void,
|
||||
playerActions?: (lineData: string, fnRegister: (data: {player: string, action: "join" | "leave"}) => void) => void,
|
||||
}
|
||||
};
|
||||
|
||||
export async function serverManeger(serverOptions: serverConfig) {
|
||||
const serverExec = child_process.execFile(serverOptions.exec.exec, serverOptions.exec.args ?? [], {
|
||||
cwd: serverOptions.exec.cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
...serverOptions.exec.env
|
||||
},
|
||||
windowsHide: true,
|
||||
maxBuffer: Infinity
|
||||
});
|
||||
const stdoutReadline = readline({input: serverExec.stdout});
|
||||
const stderrReadline = readline({input: serverExec.stderr});
|
||||
|
||||
return {
|
||||
serverExec,
|
||||
stdoutReadline,
|
||||
stderrReadline
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user