Web interface #525
							
								
								
									
										18
									
								
								.github/workflows/test.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.github/workflows/test.yaml
									
									
									
									
										vendored
									
									
								
							@@ -9,11 +9,11 @@ jobs:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        package:
 | 
			
		||||
          - core
 | 
			
		||||
          - cli
 | 
			
		||||
          - docker
 | 
			
		||||
          - verapi
 | 
			
		||||
    name: "Testing ${{ matrix.package }}"
 | 
			
		||||
          - "@the-bds-maneger/core"
 | 
			
		||||
          - "@the-bds-maneger/web"
 | 
			
		||||
          - "bds-maneger"
 | 
			
		||||
          - "@the-bds-maneger/verapi"
 | 
			
		||||
    name: "Testing \"${{ matrix.package }}\""
 | 
			
		||||
    env:
 | 
			
		||||
      bdscoreroot: "~/.bdsCore"
 | 
			
		||||
    steps:
 | 
			
		||||
@@ -38,8 +38,8 @@ jobs:
 | 
			
		||||
 | 
			
		||||
    # Build Core
 | 
			
		||||
    - name: Core Build
 | 
			
		||||
      if: matrix.package != 'core'
 | 
			
		||||
      run: npm run -w package/core build
 | 
			
		||||
      if: matrix.package != '@the-bds-maneger/core'
 | 
			
		||||
      run: npm run -w "@the-bds-maneger/core" build
 | 
			
		||||
 | 
			
		||||
    - name: Build ${{ matrix.package }}
 | 
			
		||||
      run: npm run --if-present -w "package/${{ matrix.package }}" build
 | 
			
		||||
    - name: Build "${{ matrix.package }}"
 | 
			
		||||
      run: npm run --if-present -w "${{ matrix.package }}" prepack
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
# npm
 | 
			
		||||
/*.tgz
 | 
			
		||||
*.tgz
 | 
			
		||||
 | 
			
		||||
# Node
 | 
			
		||||
node_modules/
 | 
			
		||||
@@ -8,7 +8,7 @@ node_modules/
 | 
			
		||||
# Typescript
 | 
			
		||||
**/*.js
 | 
			
		||||
**/*.d.ts
 | 
			
		||||
**/tsconfig.tsbuildinfo
 | 
			
		||||
**/*.tsbuildinfo
 | 
			
		||||
 | 
			
		||||
# PHP and Spigot Pre builds
 | 
			
		||||
phpOutput/
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
# npm
 | 
			
		||||
/*.tgz
 | 
			
		||||
*.tgz
 | 
			
		||||
 | 
			
		||||
# Typescript
 | 
			
		||||
**/*.ts
 | 
			
		||||
!**/*.d.ts
 | 
			
		||||
**/tsconfig.tsbuildinfo
 | 
			
		||||
**/*.tsbuildinfo
 | 
			
		||||
 | 
			
		||||
# PHP and Spigot Pre builds
 | 
			
		||||
phpOutput/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@@ -6,9 +6,22 @@
 | 
			
		||||
  "editor.minimap.enabled": false,
 | 
			
		||||
  "files.trimFinalNewlines": true,
 | 
			
		||||
  "files.trimTrailingWhitespace": true,
 | 
			
		||||
  // "files.exclude": {
 | 
			
		||||
  //   "**/node_modules/": true,
 | 
			
		||||
  //   "**/src/**/*.js": true,
 | 
			
		||||
  //   "**/src/**/*.d.ts": true,
 | 
			
		||||
  // }
 | 
			
		||||
  "editor.insertSpaces": true,
 | 
			
		||||
  "editor.detectIndentation": false,
 | 
			
		||||
  "editor.codeActionsOnSave": {
 | 
			
		||||
    "source.organizeImports": true
 | 
			
		||||
  },
 | 
			
		||||
  "files.exclude": {
 | 
			
		||||
    "**/node_modules/": true,
 | 
			
		||||
    "packages/web/src/next/.next": true,
 | 
			
		||||
  },
 | 
			
		||||
  "terminal.integrated.env.windows": {
 | 
			
		||||
    "PATH": "${workspaceFolder}/node_modules/.bin;${env:PATH}"
 | 
			
		||||
  },
 | 
			
		||||
  "terminal.integrated.env.linux": {
 | 
			
		||||
    "PATH": "${workspaceFolder}/node_modules/.bin:${env:PATH}"
 | 
			
		||||
  },
 | 
			
		||||
  "terminal.integrated.env.osx": {
 | 
			
		||||
    "PATH": "${workspaceFolder}/node_modules/.bin:${env:PATH}"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
FROM node:lts
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY ./ ./
 | 
			
		||||
RUN npm install --no-save && npm run -w package/docker build
 | 
			
		||||
RUN npm install --no-save && npm run -w "@the-bds-maneger/web" build
 | 
			
		||||
 | 
			
		||||
FROM node:lts
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								package.json
									
									
									
									
									
								
							@@ -9,13 +9,13 @@
 | 
			
		||||
  "license": "GPL-3.0",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/express": "^4.17.17",
 | 
			
		||||
    "@types/node": "^20.1.3",
 | 
			
		||||
    "typescript": "^5.0.4"
 | 
			
		||||
    "@types/node": "^20.2.1",
 | 
			
		||||
    "typescript": "4.9.5"
 | 
			
		||||
  },
 | 
			
		||||
  "workspaces": [
 | 
			
		||||
    "package/core",
 | 
			
		||||
    "package/cli",
 | 
			
		||||
    "package/docker",
 | 
			
		||||
    "package/verapi"
 | 
			
		||||
    "packages/core",
 | 
			
		||||
    "packages/cli",
 | 
			
		||||
    "packages/web",
 | 
			
		||||
    "packages/verapi"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
export * from "./serverManeger.js";
 | 
			
		||||
 | 
			
		||||
import * as serverManeger from "./serverManeger.js";
 | 
			
		||||
import * as Bedrock from "./servers/bedrock.js";
 | 
			
		||||
import * as Java from "./servers/java.js";
 | 
			
		||||
 | 
			
		||||
export default {...serverManeger, serverManeger, Bedrock, Java };
 | 
			
		||||
export { serverManeger, Bedrock, Java };
 | 
			
		||||
@@ -1,37 +0,0 @@
 | 
			
		||||
# Bds Maneger Core
 | 
			
		||||
 | 
			
		||||
Basic core to install, update and manage several minecraft servers automatically, depending on a few dependencies, the basic being **Nodejs**.
 | 
			
		||||
 | 
			
		||||
## Servers supports and TODO
 | 
			
		||||
 | 
			
		||||
**Bedrock Mojang**:
 | 
			
		||||
  - [x] Install/Update.
 | 
			
		||||
  - [ ] Hot backup.
 | 
			
		||||
  - [x] Start.
 | 
			
		||||
  - [x] Port Listened.
 | 
			
		||||
  - [ ] Player connect/disconnect/spawn.
 | 
			
		||||
  - [ ] Player kick/ban.
 | 
			
		||||
 | 
			
		||||
**Pocketmine PMMP**:
 | 
			
		||||
  - [x] Install/Update.
 | 
			
		||||
  - [ ] Hot backup.
 | 
			
		||||
  - [x] Start.
 | 
			
		||||
  - [ ] Port listened.
 | 
			
		||||
  - [ ] Player connect/disconnect.
 | 
			
		||||
  - [ ] Player kick/ban.
 | 
			
		||||
 | 
			
		||||
**Powernukkit** and **Cloudbust**:
 | 
			
		||||
  - [x] Install/Update.
 | 
			
		||||
  - 🚫 Hot backup.
 | 
			
		||||
  - [x] Start.
 | 
			
		||||
  - [ ] Port listened.
 | 
			
		||||
  - [ ] Player connect/disconnect.
 | 
			
		||||
  - [ ] Player kick/ban.
 | 
			
		||||
 | 
			
		||||
**Java Mojang**, **Purpur**, **Paper** and **Spigot**:
 | 
			
		||||
  - [x] Install/Update.
 | 
			
		||||
  - 🚫 Hot Backup.
 | 
			
		||||
  - [x] Start.
 | 
			
		||||
  - [ ] Port listened.
 | 
			
		||||
  - [ ] Player connect/disconect action.
 | 
			
		||||
  - [ ] Player kick/ban.
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@the-bds-maneger/docker",
 | 
			
		||||
  "version": "6.0.1",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "author": "Matheus Sampaio Queiroga <srherobrine20@gmail.com>",
 | 
			
		||||
  "license": "GPL-3.0",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "tsc --build --clean && tsc --build"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@the-bds-maneger/core": "^6.0.3",
 | 
			
		||||
    "express": "^4.18.2",
 | 
			
		||||
    "mongodb": "^5.5.0",
 | 
			
		||||
    "neste": "^1.0.2",
 | 
			
		||||
    "yaml": "^2.2.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/express": "^4.17.17"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,137 +0,0 @@
 | 
			
		||||
#!/usr/bin/env node
 | 
			
		||||
import bdsCore, { serverManeger, serverRun } from "@the-bds-maneger/core";
 | 
			
		||||
import express from "express";
 | 
			
		||||
import neste from "neste";
 | 
			
		||||
import yaml from "yaml";
 | 
			
		||||
 | 
			
		||||
const sessions: {[id: string]: serverRun} = {};
 | 
			
		||||
process.on("exit", () => Object.keys(sessions).forEach(k => sessions[k].stopServer()));
 | 
			
		||||
 | 
			
		||||
// Catch error
 | 
			
		||||
for (const k of ["uncaughtException", "unhandledRejection"]) process.on(k, err => console.log(err));
 | 
			
		||||
const app = neste();
 | 
			
		||||
app.use(async (req, res, next) => {
 | 
			
		||||
  req.res.json = res.json = function(body: any) {return Object.assign(res, Promise.resolve(body).then(d => res.send(JSON.stringify(d, null, 2))).catch(next));}
 | 
			
		||||
  if (typeof req.headers["content-type"] === "string" && (["application/x-yaml", "text/yaml", "text/x-yaml"]).find(k => req.headers["content-type"].includes(k))) {
 | 
			
		||||
    const data: Buffer[] = [];
 | 
			
		||||
    req.on("data", d => data.push(d));
 | 
			
		||||
    await new Promise((done, reject) => req.on("error", reject).once("close", () => {
 | 
			
		||||
      try {
 | 
			
		||||
        req.body = yaml.parse(Buffer.concat(data).toString("utf8"));
 | 
			
		||||
        done(null);
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        reject(err);
 | 
			
		||||
      }
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
  next();
 | 
			
		||||
}, express.json(), express.urlencoded({extended: true}));
 | 
			
		||||
 | 
			
		||||
// Get current server running
 | 
			
		||||
app.route("/v1").get(({res}) => res.json(Object.keys(sessions).reduce((acc, key) => {
 | 
			
		||||
  acc[key] = {
 | 
			
		||||
    ports: sessions[key].portListening,
 | 
			
		||||
    player: sessions[key].playerActions.reduce((acc, player) => {
 | 
			
		||||
      if (!acc[player.playerName]) acc[player.playerName] = player;
 | 
			
		||||
      else acc[player.playerName] = {
 | 
			
		||||
        ...player,
 | 
			
		||||
        previous: acc[player.playerName]
 | 
			
		||||
      };
 | 
			
		||||
      return acc;
 | 
			
		||||
    }, {})
 | 
			
		||||
  };
 | 
			
		||||
  return acc;
 | 
			
		||||
}, {})));
 | 
			
		||||
 | 
			
		||||
app.route("/v1/id").get(async ({res}) => res.json(await serverManeger.listIDs())).delete(async (req, res) => {
 | 
			
		||||
  const IDs: string[] = [];
 | 
			
		||||
  if (typeof req.body === "string") IDs.push(...(String(req.body).split(/[;,]/).map(s => s.trim())))
 | 
			
		||||
  else if (Array.isArray(req.body)) IDs.push(...(req.body.map(k => typeof k === "string" ? k : k?.id).filter(s => !!s)))
 | 
			
		||||
 | 
			
		||||
  if (IDs.find(k => k === "*")) return Promise.all((await bdsCore.listIDs()).map(async (idManeger) => idManeger.delete().then(() => ({id: idManeger.id})).catch(err => ({err: String(err?.message || err)})))).then(res.json);
 | 
			
		||||
  else if (IDs.length > 0) {
 | 
			
		||||
    const folder = (await bdsCore.listIDs()).filter(k => IDs.includes(k.id));
 | 
			
		||||
    if (folder.length === 0) return res.status(400).json({error: "all id is invalid"});
 | 
			
		||||
    return Promise.all(folder.map(async (idManeger) => idManeger.delete().then(() => ({id: idManeger.id})).catch(err => ({err: String(err?.message || err)})))).then(res.json);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return res.status(400).json({
 | 
			
		||||
    error: "Body is String or Array"
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/v1/platform(s)?/:platform?", async (req, res) => {
 | 
			
		||||
  const { platform = "bedrock" } = req.params;
 | 
			
		||||
  if (!(platform === "bedrock"||platform === "java")) return res.status(400).json({error: "Invalid platform"});
 | 
			
		||||
  if (platform === "bedrock") {
 | 
			
		||||
    return res.json(await bdsCore.Bedrock.listVersions(req.query.alt as any));
 | 
			
		||||
  }
 | 
			
		||||
  return res.json(await bdsCore.Java.listVersions(req.query.alt as any));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.route("/v1/server").put(async (req, res) => {
 | 
			
		||||
  const { platform } = req.body as { platform: "bedrock"|"java" };
 | 
			
		||||
  if (!(platform === "bedrock" || platform === "java")) return res.status(400).json({error: "Platform is invalid"});
 | 
			
		||||
  const platformInstall = await (platform === "java" ? bdsCore.Java.installServer : bdsCore.Bedrock.installServer)({
 | 
			
		||||
    newID: true,
 | 
			
		||||
    version: req.body?.version ?? "latest",
 | 
			
		||||
    altServer: req.body?.altServer as never,
 | 
			
		||||
    allowBeta: req.body?.allowBeta ?? req.query.allowBeta === "true"
 | 
			
		||||
  });
 | 
			
		||||
  delete platformInstall["downloads"]?.server?.urls;
 | 
			
		||||
  return res.json(platformInstall);
 | 
			
		||||
}).patch(async (req, res) => {
 | 
			
		||||
  const { id } = req.body;
 | 
			
		||||
  const localID = (await bdsCore.listIDs()).find(ind => ind.id === id);
 | 
			
		||||
  if (!localID) return res.status(400).json({error: "server not installed to update"});
 | 
			
		||||
  if (sessions[id]) await sessions[id].stopServer();
 | 
			
		||||
  const platformInstall = await (localID.platform === "java" ? bdsCore.Java.installServer : bdsCore.Bedrock.installServer)({
 | 
			
		||||
    newID: true,
 | 
			
		||||
    version: req.body?.version ?? "latest",
 | 
			
		||||
    altServer: req.body?.altServer as never,
 | 
			
		||||
    allowBeta: req.body?.allowBeta ?? req.query.allowBeta === "true"
 | 
			
		||||
  });
 | 
			
		||||
  delete platformInstall["downloads"]?.server?.urls;
 | 
			
		||||
  return res.json(platformInstall);
 | 
			
		||||
}).post(async (req, res) => {
 | 
			
		||||
  const { id } = req.body;
 | 
			
		||||
  const idInfo = (await serverManeger.listIDs()).find(f => f.id === id);
 | 
			
		||||
  if (!idInfo) return res.status(400).json({error: "ID not exsists"});
 | 
			
		||||
  if (sessions[id]) return res.status(400).json({error: "Server are running"});
 | 
			
		||||
  sessions[id] = await (idInfo.platform === "java" ? bdsCore.Java.startServer : bdsCore.Bedrock.startServer)({
 | 
			
		||||
    newID: false,
 | 
			
		||||
    ID: id
 | 
			
		||||
  });
 | 
			
		||||
  sessions[id].once("close", () => delete sessions[id]).on("line", (line, from) => console.log("[%s from %s]: %s", id, from, line));
 | 
			
		||||
  return res.json({
 | 
			
		||||
    spawnargs: sessions[id].spawnargs,
 | 
			
		||||
    pid: sessions[id].pid,
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.route("/v1/server/:id").get((req, res) => {
 | 
			
		||||
  if (!sessions[req.params.id]) return res.status(400).json({error: "Session not running"});
 | 
			
		||||
  return res.json({
 | 
			
		||||
    // bedrockConnect: sessions[req.params.id].runOptions.paths.platform === "java" ? null : `minecraft:?addExternalServer=${sessions[req.params.id].runOptions.paths.id}|${}:${sessions[req.params.id].portListening.at(0).port}`,
 | 
			
		||||
    ports: sessions[req.params.id].portListening,
 | 
			
		||||
    player: sessions[req.params.id].playerActions.reduce((acc, player) => {
 | 
			
		||||
      if (!acc[player.playerName]) acc[player.playerName] = player;
 | 
			
		||||
      else acc[player.playerName] = {
 | 
			
		||||
        ...player,
 | 
			
		||||
        previous: acc[player.playerName]
 | 
			
		||||
      };
 | 
			
		||||
      return acc;
 | 
			
		||||
    }, {})
 | 
			
		||||
  });
 | 
			
		||||
}).post(async (req, res) => {
 | 
			
		||||
  if (!sessions[req.params.id]) return res.status(400).json({error: "Session not running"});
 | 
			
		||||
  if (Array.isArray(req.body)) sessions[req.params.id].sendCommand(...req.body);
 | 
			
		||||
  else sessions[req.params.id].sendCommand(req);
 | 
			
		||||
  return res.status(200).send("ok");
 | 
			
		||||
}).delete((req, res) => {
 | 
			
		||||
  if (!sessions[req.params.id]) return res.status(400).json({error: "Session not running"});
 | 
			
		||||
  return sessions[req.params.id].stopServer().then(res.json).catch(err => res.status(400).json({err: String(err?.message || err)}));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Listen
 | 
			
		||||
app.listen(process.env.PORT ?? 3000, function() {const a = this.address(); console.log("Bds API Listen on %O", a?.["port"] ?? a)});
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "../../tsconfig.json",
 | 
			
		||||
  "references": [{"path": "../core"}]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "bds-maneger",
 | 
			
		||||
  "version": "6.0.1",
 | 
			
		||||
  "version": "6.0.4",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "src/index.js",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
@@ -16,7 +16,7 @@
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [],
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@the-bds-maneger/core": "^6.0.3",
 | 
			
		||||
    "@the-bds-maneger/core": "^6.0.4",
 | 
			
		||||
    "yargs": "^17.7.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
@@ -39,12 +39,15 @@ yargs(process.argv.slice(2)).version(false).help(true).strictCommands().demandCo
 | 
			
		||||
  })
 | 
			
		||||
  .parseSync();
 | 
			
		||||
 | 
			
		||||
  const installData = await (options.platform === "java" ? bdsCore.Java.installServer : bdsCore.Bedrock.installServer)({
 | 
			
		||||
  const serverPath = await bdsCore.serverManeger.serverManeger(options.platform === "java" ? "java" : "bedrock", {
 | 
			
		||||
    ...(options.id ? {newID: false, ID: options.id} : {newID: true}),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const installData = await (options.platform === "java" ? bdsCore.Java.installServer : bdsCore.Bedrock.installServer)(Object.assign({}, serverPath, {
 | 
			
		||||
    version: options.version,
 | 
			
		||||
    altServer: options.altserver as never,
 | 
			
		||||
    allowBeta: Boolean(options.beta)
 | 
			
		||||
  });
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  console.log("ID: %O, Server Version: %O, Server Date: %O", installData.id, installData.version, installData.date);
 | 
			
		||||
})
 | 
			
		||||
@@ -73,7 +76,8 @@ yargs(process.argv.slice(2)).version(false).help(true).strictCommands().demandCo
 | 
			
		||||
  }).parseSync();
 | 
			
		||||
  const idInfo = (await bdsCore.listIDs()).find(local => local.id === option.id);
 | 
			
		||||
  if (!idInfo) throw new Error("Invalid ID");
 | 
			
		||||
  const session = await (idInfo.platform === "java" ? bdsCore.Java.startServer : bdsCore.Bedrock.startServer)({ID: idInfo.id});
 | 
			
		||||
  const sserverPaths = await bdsCore.serverManeger.serverManeger(option.platform === "java" ? "java" : "bedrock", {ID: option.id, newID: false});
 | 
			
		||||
  const session = await (idInfo.platform === "java" ? bdsCore.Java.startServer : bdsCore.Bedrock.startServer)(sserverPaths);
 | 
			
		||||
  process.on("error", console.log);
 | 
			
		||||
  session.once("backup", filePath => console.log("Backup file path: %O", filePath));
 | 
			
		||||
  process.stdin.pipe(session.stdin);
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@the-bds-maneger/core",
 | 
			
		||||
  "version": "6.0.1",
 | 
			
		||||
  "version": "6.0.4",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "src/index.js",
 | 
			
		||||
  "types": "src/index.d.ts",
 | 
			
		||||
@@ -20,13 +20,15 @@
 | 
			
		||||
    "@sirherobrine23/extends": "^3.6.11",
 | 
			
		||||
    "@sirherobrine23/http": "^3.6.11",
 | 
			
		||||
    "sanitize-filename": "^1.6.3",
 | 
			
		||||
    "semver": "^7.5.0",
 | 
			
		||||
    "tar": "^6.1.14",
 | 
			
		||||
    "unzip-stream": "^0.3.1"
 | 
			
		||||
    "semver": "^7.5.1",
 | 
			
		||||
    "tar": "^6.1.15",
 | 
			
		||||
    "unzip-stream": "^0.3.1",
 | 
			
		||||
    "unzipper": "^0.10.14"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/semver": "^7.5.0",
 | 
			
		||||
    "@types/tar": "^6.1.5",
 | 
			
		||||
    "@types/unzip-stream": "^0.3.1"
 | 
			
		||||
    "@types/unzip-stream": "^0.3.1",
 | 
			
		||||
    "@types/unzipper": "^0.10.6"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								packages/core/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								packages/core/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
export * from "./reindex.js";
 | 
			
		||||
export * as default from "./reindex.js";
 | 
			
		||||
							
								
								
									
										4
									
								
								packages/core/src/reindex.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								packages/core/src/reindex.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
export * from "./serverManeger.js";
 | 
			
		||||
export * as serverManeger from "./serverManeger.js";
 | 
			
		||||
export * as Bedrock from "./servers/bedrock.js";
 | 
			
		||||
export * as Java from "./servers/java.js";
 | 
			
		||||
@@ -19,15 +19,15 @@ export const bdsManegerRoot = ENVROOT ? path.resolve(process.cwd(), ENVROOT) : p
 | 
			
		||||
if (!(await extendsFS.exists(bdsManegerRoot))) await fs.mkdir(bdsManegerRoot, {recursive: true});
 | 
			
		||||
export type withPromise<T> = T|Promise<T>;
 | 
			
		||||
 | 
			
		||||
export type manegerOptions = {
 | 
			
		||||
export interface manegerOptions {
 | 
			
		||||
  ID?: string,
 | 
			
		||||
  newID?: boolean,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// only letters and numbers
 | 
			
		||||
const idReg = /^[a-zA-Z0-9]+$/;
 | 
			
		||||
const idReg = /^[a-zA-Z0-9_]+$/;
 | 
			
		||||
 | 
			
		||||
export type serverManegerV1 = {
 | 
			
		||||
export interface serverManegerV1 {
 | 
			
		||||
  id: string,
 | 
			
		||||
  rootPath: string,
 | 
			
		||||
  serverFolder: string,
 | 
			
		||||
@@ -49,7 +49,7 @@ export async function serverManeger(platform: serverManegerV1["platform"], optio
 | 
			
		||||
  // Create or check if exists
 | 
			
		||||
  if (options.newID === true) {
 | 
			
		||||
    while(true) {
 | 
			
		||||
      options.ID = crypto.randomBytes(crypto.randomInt(8, 14)).toString("hex");
 | 
			
		||||
      options.ID = typeof crypto.randomUUID === "function" ?  crypto.randomUUID().split("-").join("_") : crypto.randomBytes(crypto.randomInt(8, 14)).toString("hex");
 | 
			
		||||
      if (!(idReg.test(options.ID))) continue;
 | 
			
		||||
      if (!((await fs.readdir(platformFolder).catch(() => [])).includes(options.ID))) break;
 | 
			
		||||
    }
 | 
			
		||||
@@ -171,7 +171,7 @@ export declare class serverRun extends child_process.ChildProcess {
 | 
			
		||||
  avaibleDate?: Date;
 | 
			
		||||
  runOptions: runOptions;
 | 
			
		||||
  portListening: portListen[];
 | 
			
		||||
  logPath: {stderr: string, stdout: string};
 | 
			
		||||
  logPath: {stderr: string, stdout: string, merged: string};
 | 
			
		||||
  playerActions: playerAction[];
 | 
			
		||||
  stdoutInterface: readline.Interface;
 | 
			
		||||
  stderrInterface: readline.Interface;
 | 
			
		||||
@@ -186,6 +186,7 @@ export declare class serverRun extends child_process.ChildProcess {
 | 
			
		||||
 * Run servers globally and hormonally across servers
 | 
			
		||||
 */
 | 
			
		||||
export async function runServer(options: runOptions): Promise<serverRun> {
 | 
			
		||||
  if (!options.stdio) options.stdio = ["pipe", "pipe", "pipe"];
 | 
			
		||||
  const child = child_process.spawn(options.command, [...((options.args ?? []).map(String))], {
 | 
			
		||||
    // maxBuffer: Infinity,
 | 
			
		||||
    stdio: options.stdio,
 | 
			
		||||
@@ -210,8 +211,11 @@ export async function runServer(options: runOptions): Promise<serverRun> {
 | 
			
		||||
  const currentDate = new Date();
 | 
			
		||||
  const baseLog = path.join(options.paths.logs, format("%s_%s_%s_%s-%s-%s", currentDate.getDate(), currentDate.getMonth()+1, currentDate.getFullYear(), currentDate.getHours(), currentDate.getMinutes(), currentDate.getSeconds()));
 | 
			
		||||
  await fs.mkdir(baseLog, {recursive: true});
 | 
			
		||||
  child.logPath = {stdout: path.join(baseLog, "stdout.log"), stderr: path.join(baseLog, "stderr.log")};
 | 
			
		||||
  child.logPath = {stdout: path.join(baseLog, "stdout.log"), stderr: path.join(baseLog, "stderr.log"), merged: path.join(baseLog, "server.log")};
 | 
			
		||||
  const allLog = createWriteStream(child.logPath.merged);
 | 
			
		||||
  child.stdout.pipe(allLog);
 | 
			
		||||
  child.stdout.pipe(createWriteStream(child.logPath.stdout));
 | 
			
		||||
  child.stderr.pipe(allLog);
 | 
			
		||||
  child.stderr.pipe(createWriteStream(child.logPath.stderr));
 | 
			
		||||
 | 
			
		||||
  // Lines
 | 
			
		||||
@@ -265,7 +269,7 @@ export async function runServer(options: runOptions): Promise<serverRun> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  child.hotBackup = function hotBackup() {
 | 
			
		||||
    return Object.assign(Promise.resolve().then((async () => {
 | 
			
		||||
    return Object.assign({}, Promise.resolve().then((async () => {
 | 
			
		||||
      if (!options.serverActions?.hotBackup) throw new Error("Hot backup disabled to current platform!");
 | 
			
		||||
      child.emit("backup", "start");
 | 
			
		||||
      return Promise.resolve(options.serverActions.hotBackup.call(child) as ReturnType<typeof options.serverActions.hotBackup>).then(data => {
 | 
			
		||||
@@ -1,21 +1,21 @@
 | 
			
		||||
import fsOld, { promises as fs } from "node:fs";
 | 
			
		||||
import coreHttp, { Github } from "@sirherobrine23/http";
 | 
			
		||||
import { manegerOptions, runOptions, serverManeger, serverManegerV1 } from "../serverManeger.js";
 | 
			
		||||
import { runOptions, serverManegerV1 } from "../serverManeger.js";
 | 
			
		||||
import { oracleStorage } from "../internal.js";
 | 
			
		||||
import { pipeline } from "node:stream/promises";
 | 
			
		||||
import { Readable } from "node:stream";
 | 
			
		||||
import extendsFS, { promiseChildProcess } from "@sirherobrine23/extends";
 | 
			
		||||
import semver from "semver";
 | 
			
		||||
import unzip from "unzip-stream";
 | 
			
		||||
import unzip from "unzipper";
 | 
			
		||||
import utils from "node:util";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import tar from "tar";
 | 
			
		||||
 | 
			
		||||
export type bedrockOptions = manegerOptions & {
 | 
			
		||||
export interface bedrockOptions {
 | 
			
		||||
  /**
 | 
			
		||||
   * Alternative server instead of official Mojang server
 | 
			
		||||
   */
 | 
			
		||||
  altServer?: "pocketmine"|"powernukkit"|"nukkit"|"cloudbust",
 | 
			
		||||
  altServer?: "mojang"|"pocketmine"|"powernukkit"|"nukkit"|"cloudbust",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const pocketmineGithub = await Github.repositoryManeger("pmmp", "PocketMine-MP");
 | 
			
		||||
@@ -47,7 +47,8 @@ export type bedrockList = {
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export async function listVersions(altServer?: bedrockOptions["altServer"]): Promise<bedrockList[]> {
 | 
			
		||||
  if (altServer) if (!(["cloudbust", "cloudbust", "nukkit", "pocketmine", "powernukkit"]).includes(altServer)) throw new TypeError("Invalid alt server");
 | 
			
		||||
  if (!altServer) altServer = "mojang";
 | 
			
		||||
  if (altServer) if (!(["mojang", "cloudbust", "cloudbust", "nukkit", "pocketmine", "powernukkit"]).includes(altServer)) throw new TypeError("Invalid alt server");
 | 
			
		||||
  if (altServer === "pocketmine") {
 | 
			
		||||
    return (await pocketmineGithub.release.getRelease()).filter(rel => (rel.assets.find(assert => assert.name.endsWith(".phar")) ?? {}).browser_download_url).map(rel => ({
 | 
			
		||||
      date: new Date(rel.created_at),
 | 
			
		||||
@@ -137,7 +138,8 @@ export async function listVersions(altServer?: bedrockOptions["altServer"]): Pro
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
  } else if (altServer === "mojang") {
 | 
			
		||||
 | 
			
		||||
    return (await coreHttp.jsonRequest<{version: string, date: Date, release?: "stable"|"preview", url: {[platform in NodeJS.Platform]?: {[arch in NodeJS.Architecture]?: string}}}[]>("https://sirherobrine23.github.io/BedrockFetch/all.json")).body.sort((b, a) => semver.compare(semver.valid(semver.coerce(a.version)), semver.valid(semver.coerce(b.version)))).map(rel => ({
 | 
			
		||||
      version: rel.version,
 | 
			
		||||
      date: new Date(rel.date),
 | 
			
		||||
@@ -156,11 +158,12 @@ export async function listVersions(altServer?: bedrockOptions["altServer"]): Pro
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }));
 | 
			
		||||
  } else throw new Error("Invalid platform");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function installServer(options: bedrockOptions & {version?: string, allowBeta?: boolean}) {
 | 
			
		||||
  const serverPath = await serverManeger("bedrock", options);
 | 
			
		||||
export async function installServer(serverPath: serverManegerV1, options: bedrockOptions & {version?: string, allowBeta?: boolean}) {
 | 
			
		||||
  const versions = await listVersions(options?.altServer);
 | 
			
		||||
  if (!options.altServer) options.altServer = "mojang";
 | 
			
		||||
  if (options.altServer === "pocketmine") {
 | 
			
		||||
    const rel = options.version === "latest" ? versions.at(0) : versions.find(rel => rel.version === options.version);
 | 
			
		||||
    if (!rel) throw new Error("Version not exsists");
 | 
			
		||||
@@ -179,7 +182,7 @@ export async function installServer(options: bedrockOptions & {version?: string,
 | 
			
		||||
      ...rel,
 | 
			
		||||
      id: serverPath.id,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  } else if (options.altServer === "mojang") {
 | 
			
		||||
    const bedrockVersion = versions.find(rel => {
 | 
			
		||||
      if (rel.release === "preview") if (options.allowBeta !== true) return false;
 | 
			
		||||
      const version = (options.version ?? "latest").trim();
 | 
			
		||||
@@ -206,12 +209,13 @@ export async function installServer(options: bedrockOptions & {version?: string,
 | 
			
		||||
      ...bedrockVersion,
 | 
			
		||||
      id: serverPath.id,
 | 
			
		||||
    };
 | 
			
		||||
  } else throw new Error("Invalid platform");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function startServer(options: bedrockOptions) {
 | 
			
		||||
  const serverPath = await serverManeger("bedrock", options);
 | 
			
		||||
export async function startServer(maneger: serverManegerV1, options: bedrockOptions) {
 | 
			
		||||
  if (!options.altServer) options.altServer = "mojang";
 | 
			
		||||
  if (options.altServer === "powernukkit"||options.altServer === "cloudbust") {
 | 
			
		||||
    return serverPath.runCommand({
 | 
			
		||||
    return maneger.runCommand({
 | 
			
		||||
      command: "java",
 | 
			
		||||
      args: [
 | 
			
		||||
        "-XX:+UseG1GC",
 | 
			
		||||
@@ -236,7 +240,7 @@ export async function startServer(options: bedrockOptions) {
 | 
			
		||||
        "-Daikars.new.flags=true",
 | 
			
		||||
        "-jar", "server.jar",
 | 
			
		||||
      ],
 | 
			
		||||
      paths: serverPath,
 | 
			
		||||
      paths: maneger,
 | 
			
		||||
      serverActions: {
 | 
			
		||||
        stop() {
 | 
			
		||||
          this.sendCommand("stop");
 | 
			
		||||
@@ -244,13 +248,13 @@ export async function startServer(options: bedrockOptions) {
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  } else if (options.altServer === "pocketmine") {
 | 
			
		||||
    return serverPath.runCommand({
 | 
			
		||||
      command: (await extendsFS.readdir(serverPath.serverFolder)).find(file => file.endsWith("php")||file.endsWith("php.exe")),
 | 
			
		||||
    return maneger.runCommand({
 | 
			
		||||
      command: (await extendsFS.readdir(maneger.serverFolder)).find(file => file.endsWith("php")||file.endsWith("php.exe")),
 | 
			
		||||
      args: [
 | 
			
		||||
        "server.phar",
 | 
			
		||||
        "--no-wizard"
 | 
			
		||||
      ],
 | 
			
		||||
      paths: serverPath,
 | 
			
		||||
      paths: maneger,
 | 
			
		||||
      serverActions: {
 | 
			
		||||
        stop() {
 | 
			
		||||
          this.sendCommand("stop")
 | 
			
		||||
@@ -260,8 +264,8 @@ export async function startServer(options: bedrockOptions) {
 | 
			
		||||
  }
 | 
			
		||||
  if (process.platform === "darwin") throw new Error("Run in docker or podman!");
 | 
			
		||||
  const run: Omit<runOptions, "cwd"> = {
 | 
			
		||||
    command: path.join(serverPath.serverFolder, "bedrock_server"),
 | 
			
		||||
    paths: serverPath,
 | 
			
		||||
    command: path.join(maneger.serverFolder, "bedrock_server"+(process.platform === "win32" ? ".exe" : "")),
 | 
			
		||||
    paths: maneger,
 | 
			
		||||
    serverActions: {
 | 
			
		||||
      stop() {
 | 
			
		||||
        this.sendCommand("stop");
 | 
			
		||||
@@ -322,6 +326,19 @@ export async function startServer(options: bedrockOptions) {
 | 
			
		||||
        if (data.includes("started") && data.includes("Server")) return new Date();
 | 
			
		||||
        return null
 | 
			
		||||
      },
 | 
			
		||||
      async hotBackup() {
 | 
			
		||||
        const ff = (await fs.readdir(this.runOptions.paths.serverFolder)).filter(ff => {
 | 
			
		||||
          let ok = ff.endsWith(".json");
 | 
			
		||||
          if (!ok) ok = ff === "server.properties";
 | 
			
		||||
          if (!ok) ok = ff === "worlds";
 | 
			
		||||
          return ok;
 | 
			
		||||
        });
 | 
			
		||||
        return tar.create({
 | 
			
		||||
          gzip: true,
 | 
			
		||||
          cwd: this.runOptions.paths.serverFolder,
 | 
			
		||||
          prefix: ""
 | 
			
		||||
        }, ff);
 | 
			
		||||
      },
 | 
			
		||||
      postStart: [
 | 
			
		||||
        async function() {
 | 
			
		||||
          let breaked = false;
 | 
			
		||||
@@ -367,5 +384,5 @@ export async function startServer(options: bedrockOptions) {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return serverPath.runCommand(run);
 | 
			
		||||
  return maneger.runCommand(run);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { manegerOptions, serverManeger } from "../serverManeger.js";
 | 
			
		||||
import { serverManegerV1 } from "../serverManeger.js";
 | 
			
		||||
import { oracleStorage } from "../internal.js";
 | 
			
		||||
import { extendsFS } from "@sirherobrine23/extends";
 | 
			
		||||
import { pipeline } from "node:stream/promises";
 | 
			
		||||
@@ -9,11 +9,11 @@ import utils from "node:util";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import fs from "node:fs";
 | 
			
		||||
 | 
			
		||||
export type javaOptions = manegerOptions & {
 | 
			
		||||
export interface javaOptions {
 | 
			
		||||
  /**
 | 
			
		||||
   * Alternative server instead of official Mojang server
 | 
			
		||||
   */
 | 
			
		||||
  altServer?: "spigot"|"paper"|"purpur"|"glowstone"|"folia"|"cuberite"
 | 
			
		||||
  altServer?: "mojang"|"spigot"|"paper"|"purpur"|"glowstone"|"folia"|"cuberite"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type javaList = {
 | 
			
		||||
@@ -30,7 +30,8 @@ export type javaList = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export async function listVersions(altServer?: javaOptions["altServer"]): Promise<javaList[]> {
 | 
			
		||||
  if (altServer) if(!(["paper", "folia", "purpur", "spigot", "glowstone", "cuberite"]).includes(altServer)) throw new TypeError("Invalid alt server!");
 | 
			
		||||
  if (!altServer) altServer = "mojang";
 | 
			
		||||
  if (altServer) if(!(["mojang", "paper", "folia", "purpur", "spigot", "glowstone", "cuberite"]).includes(altServer)) throw new TypeError("Invalid alt server!");
 | 
			
		||||
  if (altServer === "purpur") {
 | 
			
		||||
    return (await Promise.all((await coreHttp.jsonRequest<{versions: string[]}>("https://api.purpurmc.org/v2/purpur")).body.versions.map(async (version): Promise<javaList> => ({
 | 
			
		||||
      version,
 | 
			
		||||
@@ -143,8 +144,7 @@ export async function listVersions(altServer?: javaOptions["altServer"]): Promis
 | 
			
		||||
        },
 | 
			
		||||
      }]
 | 
			
		||||
    }));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  } else if (altServer === "mojang") {
 | 
			
		||||
    return (await Promise.all((await coreHttp.jsonRequest<{versions: {id: string, releaseTime: string, url: string, type: "snapshot"|"release"}[]}>("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json")).body.versions.map(async (data): Promise<javaList> => {
 | 
			
		||||
      const fileURL = (await coreHttp.jsonRequest<{downloads: {[k: string]: {size: number, url: string}}}>(data.url)).body.downloads?.["server"]?.url;
 | 
			
		||||
      if (!fileURL) return null;
 | 
			
		||||
@@ -160,10 +160,11 @@ export async function listVersions(altServer?: javaOptions["altServer"]): Promis
 | 
			
		||||
        }],
 | 
			
		||||
      };
 | 
			
		||||
    }))).filter(a => !!a);
 | 
			
		||||
  } else throw new Error("Invalid platform");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function installServer(options: javaOptions & {version?: string, allowBeta?: boolean}) {
 | 
			
		||||
  const serverPath = await serverManeger("java", options);
 | 
			
		||||
export async function installServer(serverPath: serverManegerV1, options: javaOptions & {version?: string, allowBeta?: boolean}) {
 | 
			
		||||
  if (!options.altServer) options.altServer = "mojang";
 | 
			
		||||
  const version = (await listVersions(options.altServer)).filter(rel => rel.release === "stable" ? true : !!options.allowBeta).find(rel => (!options.version || options.version === "latest" || rel.version === options.version));
 | 
			
		||||
  if (!version) throw new Error("The specified version does not exist!");
 | 
			
		||||
  for (const file of version.getFile) await pipeline(await file.stream(), fs.createWriteStream(path.join(serverPath.serverFolder, file.fileName)));
 | 
			
		||||
@@ -176,8 +177,8 @@ export async function installServer(options: javaOptions & {version?: string, al
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function startServer(options: javaOptions) {
 | 
			
		||||
  const serverPath = await serverManeger("java", options);
 | 
			
		||||
export async function startServer(serverPath: serverManegerV1, options: javaOptions) {
 | 
			
		||||
  if (!options.altServer) options.altServer = "mojang";
 | 
			
		||||
  // Java server
 | 
			
		||||
  if (await extendsFS.exists(path.join(serverPath.serverFolder, "server.jar"))) {
 | 
			
		||||
    return serverPath.runCommand({
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@the-bds-maneger/verapi",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "version": "6.0.1",
 | 
			
		||||
  "version": "6.0.4",
 | 
			
		||||
  "description": "Public API to Minecraft Servers",
 | 
			
		||||
  "main": "src/index.js",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
@@ -16,10 +16,7 @@
 | 
			
		||||
    "directory": "package/verapi"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@the-bds-maneger/core": "^6.0.3",
 | 
			
		||||
    "@the-bds-maneger/core": "^6.0.4",
 | 
			
		||||
    "express": "^4.18.2"
 | 
			
		||||
  },
 | 
			
		||||
  "workspaces": [
 | 
			
		||||
    "../core"
 | 
			
		||||
  ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								packages/web/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								packages/web/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
/*.env.*
 | 
			
		||||
/*.env
 | 
			
		||||
							
								
								
									
										8
									
								
								packages/web/nodemon.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								packages/web/nodemon.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
{
 | 
			
		||||
  "ext": "ts,cts,mts,json",
 | 
			
		||||
  "watch": ["src"],
 | 
			
		||||
  "exec": "ts-node",
 | 
			
		||||
  "args": [
 | 
			
		||||
    "src/index.ts"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								packages/web/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								packages/web/package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@the-bds-maneger/web",
 | 
			
		||||
  "version": "6.0.4",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "author": "Matheus Sampaio Queiroga <srherobrine20@gmail.com>",
 | 
			
		||||
  "license": "GPL-3.0",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "prepack": "tsc --build --clean && tsc --build && next build ./src/next/",
 | 
			
		||||
    "postpack": "tsc --build --clean",
 | 
			
		||||
    "dev": "nodemon"
 | 
			
		||||
  },
 | 
			
		||||
  "bin": {
 | 
			
		||||
    "bds-web": "src/index.js"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@the-bds-maneger/core": "^6.0.4",
 | 
			
		||||
    "@types/express-session": "^1.17.7",
 | 
			
		||||
    "dotenv": "^16.0.3",
 | 
			
		||||
    "express": "^4.18.2",
 | 
			
		||||
    "express-rate-limit": "^6.7.0",
 | 
			
		||||
    "express-session": "^1.17.3",
 | 
			
		||||
    "mongodb": "^5.5.0",
 | 
			
		||||
    "next": "^13.4.4",
 | 
			
		||||
    "react": "^18.2.0",
 | 
			
		||||
    "react-dom": "^18.2.0",
 | 
			
		||||
    "ssh2": "^1.13.0",
 | 
			
		||||
    "xterm": "^5.1.0",
 | 
			
		||||
    "yaml": "^2.3.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/express": "^4.17.17",
 | 
			
		||||
    "@types/react": "18.2.7",
 | 
			
		||||
    "@types/ssh2": "^1.11.11",
 | 
			
		||||
    "nodemon": "^2.0.22"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								packages/web/src/cookie.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								packages/web/src/cookie.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,85 @@
 | 
			
		||||
import cookie, { Store } from "express-session";
 | 
			
		||||
import { database } from "./db.js";
 | 
			
		||||
 | 
			
		||||
declare module "express-session" {
 | 
			
		||||
  interface SessionData {
 | 
			
		||||
    userID: string;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type cookieSave = {
 | 
			
		||||
  sid: string;
 | 
			
		||||
  session: cookie.SessionData;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const cookieCollection = database.collection<cookieSave>("authCookie");
 | 
			
		||||
class bdsSession extends Store {
 | 
			
		||||
  nMap = new Map<string, cookie.SessionData>();
 | 
			
		||||
  destroy(sid: string, callback?: (err?: any) => void): void {
 | 
			
		||||
    if (this.nMap.has(sid)) this.nMap.delete(sid);
 | 
			
		||||
    cookieCollection.deleteOne({sid}).then(() => callback(), err => callback(err));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get(sid: string, callback: (err?: any, session?: cookie.SessionData) => void) {
 | 
			
		||||
    if (this.nMap.has(sid)) return callback(null, this.nMap.get(sid));
 | 
			
		||||
    (async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        const inDb = await cookieCollection.findOne({sid});
 | 
			
		||||
        if (inDb) return callback(null, inDb.session);
 | 
			
		||||
        return callback();
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        return callback(err);
 | 
			
		||||
      }
 | 
			
		||||
    })();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set(sid: string, session: cookie.SessionData, callback?: (err?: any) => void) {
 | 
			
		||||
    (async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        if (this.nMap.has(sid)) return callback();
 | 
			
		||||
        this.nMap.set(sid, session);
 | 
			
		||||
        const existsInDb = await cookieCollection.findOne({sid});
 | 
			
		||||
        if (existsInDb) await cookieCollection.deleteOne({sid});
 | 
			
		||||
        await cookieCollection.insertOne({
 | 
			
		||||
          sid,
 | 
			
		||||
          session: typeof session["toJSON"] === "function" ? session["toJSON"]() : session,
 | 
			
		||||
        });
 | 
			
		||||
        this.nMap.delete(sid);
 | 
			
		||||
        return callback();
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        callback(err);
 | 
			
		||||
      }
 | 
			
		||||
    })();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  all(callback: (err: any, obj?: cookie.SessionData[] | { [sid: string]: cookie.SessionData; }) => void) {
 | 
			
		||||
    (async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        const sessions = await cookieCollection.find().toArray();
 | 
			
		||||
        callback(null, sessions.reduce<Parameters<typeof callback>[1]>((acc, data) => {
 | 
			
		||||
          acc[data.sid] = data.session;
 | 
			
		||||
          return acc;
 | 
			
		||||
        }, {}));
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        callback(err);
 | 
			
		||||
      }
 | 
			
		||||
    })();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clear(callback?: (err?: any) => void) {
 | 
			
		||||
    cookieCollection.deleteMany({}).then(() => callback(), err => callback(err));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default cookie({
 | 
			
		||||
  name: "bdsLogin",
 | 
			
		||||
  secret: process.env.COOKIE_SECRET,
 | 
			
		||||
  resave: true,
 | 
			
		||||
  saveUninitialized: true,
 | 
			
		||||
  cookie: {
 | 
			
		||||
    maxAge: 1000 * 60 * 60 * 24 * 7 * 30,
 | 
			
		||||
    httpOnly: false,
 | 
			
		||||
    secure: "auto"
 | 
			
		||||
  },
 | 
			
		||||
  store: new bdsSession(),
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										135
									
								
								packages/web/src/db.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								packages/web/src/db.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
			
		||||
import { serverManegerV1, bdsManegerRoot, runServer, runOptions } from "@the-bds-maneger/core";
 | 
			
		||||
import { MongoClient } from "mongodb";
 | 
			
		||||
import { extendsFS } from "@sirherobrine23/extends";
 | 
			
		||||
import { promisify } from "node:util";
 | 
			
		||||
import crypto from "node:crypto";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
import fs from "node:fs/promises";
 | 
			
		||||
export const { MONGO_URI = "mongodb://127.0.0.1", DB_NAME = "bdsWeb" } = process.env;
 | 
			
		||||
 | 
			
		||||
export const client = await (new MongoClient(MONGO_URI)).connect();
 | 
			
		||||
export const database = client.db(DB_NAME);
 | 
			
		||||
 | 
			
		||||
export type userPermission = "root"|"admin"|"confirm";
 | 
			
		||||
export type userCollection = {
 | 
			
		||||
  ID: string;
 | 
			
		||||
  createAt: Date;
 | 
			
		||||
  email: string;
 | 
			
		||||
  password: {
 | 
			
		||||
    salt: string;
 | 
			
		||||
    hash: string;
 | 
			
		||||
  };
 | 
			
		||||
  username: string;
 | 
			
		||||
  permissions: userPermission[];
 | 
			
		||||
  tokens: string[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const userCollection = database.collection<userCollection>("user");
 | 
			
		||||
 | 
			
		||||
export async function createToken() {
 | 
			
		||||
  let token: string;
 | 
			
		||||
  function bufToChar(buf: Buffer) {
 | 
			
		||||
    let str: string = "";
 | 
			
		||||
    for (let i = 0; buf.length > i; i++) {
 | 
			
		||||
      if ((/[a-zA-Z0-9]/).test(String.fromCharCode(buf[i]))) str += String.fromCharCode(buf[i]);
 | 
			
		||||
      else str += randomInt(2, 20000);
 | 
			
		||||
    }
 | 
			
		||||
    return str;
 | 
			
		||||
  }
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (await userCollection.findOne({tokens: [(token = "tk_"+bufToChar(randomBytes(16)))]})) continue;
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
  return token;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function passworldSc(input: string): Promise<{hash: string, salt: string}> {
 | 
			
		||||
  const iv = crypto.randomBytes(16);
 | 
			
		||||
  const secret = crypto.randomBytes(24);
 | 
			
		||||
  return new Promise((done, reject) => {
 | 
			
		||||
    crypto.scrypt(secret, "salt", 24, (err, key) => {
 | 
			
		||||
      if (err) return reject(err);
 | 
			
		||||
      const cipher = crypto.createCipheriv("aes-192-cbc", key, iv);
 | 
			
		||||
      cipher.on("error", reject);
 | 
			
		||||
      return done({
 | 
			
		||||
        hash: Buffer.from(cipher.update(input, "utf8", "hex") + cipher.final("hex"), "utf8").toString("base64"),
 | 
			
		||||
        salt: Buffer.from(iv.toString("hex") + "::::" + secret.toString("hex"), "utf8").toString("base64")
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function passworldDc(hash: string, salt: string): Promise<string> {
 | 
			
		||||
  const hashSplit = Buffer.from(salt, "base64").toString("utf8").split("::::");
 | 
			
		||||
  return new Promise((done, reject) => {
 | 
			
		||||
    const iv = Buffer.from(hashSplit.at(0), "hex");
 | 
			
		||||
    const secret = Buffer.from(hashSplit.at(1), "hex");
 | 
			
		||||
    crypto.scrypt(secret, "salt", 24, (err, key) => {
 | 
			
		||||
      if (err) return reject(err);
 | 
			
		||||
      const decipher = crypto.createDecipheriv("aes-192-cbc", key, iv);
 | 
			
		||||
      decipher.on("error", reject);
 | 
			
		||||
      return done(decipher.update(Buffer.from(hash, "base64").toString(), "hex", "utf8") + decipher.final("utf8"));
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function passwordCheck(info: userCollection, password: string) {
 | 
			
		||||
  const { password: { hash, salt } } = info;
 | 
			
		||||
  return (await passworldDc(hash, salt)) === password;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type serverDB = {
 | 
			
		||||
  ID: string;
 | 
			
		||||
  platform: serverManegerV1["platform"];
 | 
			
		||||
  users: string[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const serversIDs = database.collection<serverDB>("server");
 | 
			
		||||
 | 
			
		||||
export async function getServerPaths(ID: string): Promise<serverManegerV1> {
 | 
			
		||||
  const info = await serversIDs.findOne({ID});
 | 
			
		||||
  if (!(info)) throw new Error("Server not exists!");
 | 
			
		||||
 | 
			
		||||
  const rootPath = path.join(bdsManegerRoot, info.platform, ID);
 | 
			
		||||
  const serverFolder = path.join(rootPath, "server");
 | 
			
		||||
  const backup = path.join(rootPath, "backups");
 | 
			
		||||
  const log = path.join(rootPath, "logs");
 | 
			
		||||
 | 
			
		||||
  // Create folders
 | 
			
		||||
  for (const p of [serverFolder, backup, log]) if (!(await extendsFS.exists(p))) await fs.mkdir(p, {recursive: true});
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    id: ID,
 | 
			
		||||
    platform: info.platform,
 | 
			
		||||
    rootPath,
 | 
			
		||||
    serverFolder,
 | 
			
		||||
    backup,
 | 
			
		||||
    logs: log,
 | 
			
		||||
    async runCommand(options: Omit<runOptions, "cwd">) {
 | 
			
		||||
      return runServer({
 | 
			
		||||
        ...options,
 | 
			
		||||
        cwd: serverFolder
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function createServerID(platform: serverManegerV1["platform"], usersIds: string[] = []): Promise<serverManegerV1> {
 | 
			
		||||
  if (!((["bedrock", "java"]).includes(platform))) throw new Error("Set valid platform name!");
 | 
			
		||||
 | 
			
		||||
  // Create Server ID
 | 
			
		||||
  let ID: string;
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (await userCollection.findOne({ID: (ID = randomUUID().split("-").join("_"))})) continue;
 | 
			
		||||
    else if (await extendsFS.exists(path.join(bdsManegerRoot, platform, ID))) continue;
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Insert
 | 
			
		||||
  await serversIDs.insertOne({ID, platform, users: []});
 | 
			
		||||
 | 
			
		||||
  // If seted user inject to DB
 | 
			
		||||
  if (usersIds && usersIds.length > 0) await serversIDs.findOneAndUpdate({ID}, {$set: {users: usersIds}});
 | 
			
		||||
 | 
			
		||||
  return getServerPaths(ID);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								packages/web/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								packages/web/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
#!/usr/bin/env node
 | 
			
		||||
import "dotenv/config.js";
 | 
			
		||||
import express from "express";
 | 
			
		||||
import cookie from "./cookie.js";
 | 
			
		||||
import expressLayer from "express/lib/router/layer.js";
 | 
			
		||||
import * as nextPage from "./reactServer.js";
 | 
			
		||||
import mcserverAPI from "./mcserver.js";
 | 
			
		||||
import loginRegisterRoute from "./login.js";
 | 
			
		||||
 | 
			
		||||
// Patch express promise catch's
 | 
			
		||||
expressLayer.prototype.handle_request = async function handle_request_promised(...args) {
 | 
			
		||||
  var fn = this.handle;
 | 
			
		||||
  if (fn.length > 3) return args.at(-1)();
 | 
			
		||||
  await Promise.resolve().then(() => fn.call(this, ...args)).catch(args.at(-1));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Express app
 | 
			
		||||
const app = express();
 | 
			
		||||
 | 
			
		||||
app.disable("etag").disable("x-powered-by");
 | 
			
		||||
app.use(cookie, express.json(), express.urlencoded({extended: true}));
 | 
			
		||||
 | 
			
		||||
// API
 | 
			
		||||
app.use("/api/mcserver", mcserverAPI);
 | 
			
		||||
app.use(loginRegisterRoute);
 | 
			
		||||
 | 
			
		||||
// Next request
 | 
			
		||||
app.all("*", (req, res) => nextPage.nextHandler(req, res));
 | 
			
		||||
 | 
			
		||||
// 500 error
 | 
			
		||||
app.use((err, _req, res, _next) => {
 | 
			
		||||
  console.error(err);
 | 
			
		||||
  res.status(500).json({error: err?.message||err})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.listen(Number(process.env.PORT || "3000"), function() {
 | 
			
		||||
  console.log("Server listen on %O", this.address());
 | 
			
		||||
  this.on("upgrade", nextPage.nextUpgarde);
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										90
									
								
								packages/web/src/login.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								packages/web/src/login.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
import express from "express";
 | 
			
		||||
import rateLimit from "express-rate-limit";
 | 
			
		||||
import crypto from "node:crypto";
 | 
			
		||||
import { createToken, passwordCheck, passworldSc, userCollection } from "./db.js";
 | 
			
		||||
import { pageRender } from "./reactServer.js";
 | 
			
		||||
import path from "node:path";
 | 
			
		||||
 | 
			
		||||
const app = express.Router();
 | 
			
		||||
export default app;
 | 
			
		||||
 | 
			
		||||
app.get("/login", (req, res) => {
 | 
			
		||||
  return pageRender(req, res, "/login");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/register", (req, res) => {
 | 
			
		||||
  return pageRender(req, res, "/register");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Rate limit
 | 
			
		||||
app.use(rateLimit({
 | 
			
		||||
  max: 500,
 | 
			
		||||
  windowMs: 1000 * 60 * 60 * 2,
 | 
			
		||||
  message: "Try again more later, you have many requests!"
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
app.post("/api/login", async (req, res) => {
 | 
			
		||||
  if (typeof req.body !== "object") return res.status(400).json({ error: "Require body to login" });
 | 
			
		||||
  const existsUser = await userCollection.findOne({ $or: [{ username: req.body.username }, { email: req.body.username }] });
 | 
			
		||||
  if (!existsUser) return res.status(400).json({ error: "User not exists" });
 | 
			
		||||
  else if (!(await passwordCheck(existsUser, req.body.password))) return res.status(401).json({ error: "Invalid password" });
 | 
			
		||||
  req.session.userID = existsUser.ID;
 | 
			
		||||
  await new Promise<void>((done, reject) => req.session.save(err => err ? reject(err) : done()));
 | 
			
		||||
  return res.json({
 | 
			
		||||
    ID: existsUser.ID,
 | 
			
		||||
    createAt: existsUser.createAt,
 | 
			
		||||
    username: existsUser.username,
 | 
			
		||||
    permissions: existsUser.permissions,
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.delete("/api/logout", async (req, res) => {
 | 
			
		||||
  if (typeof req.session.userID === "string") await new Promise<void>((done, reject) => req.session.destroy(err => err ? reject(err) : done()));
 | 
			
		||||
  return res.sendStatus(200);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.post("/api/register", async (req, res) => {
 | 
			
		||||
  if (typeof req.body !== "object") return res.status(400).json({ error: "Require body to register user!" });
 | 
			
		||||
  const { username, email, password } = req.body;
 | 
			
		||||
  if (!(typeof username === "string" && typeof email === "string")) return res.status(400).json({ error: "Invalid username and email body" });
 | 
			
		||||
  else if (!(typeof password === "string" && (password.length >= 8))) return res.status(400).json({ error: "Require password with 8 characters" });
 | 
			
		||||
  else if (await userCollection.findOne({ $or: [{ username }, { email }] })) return res.status(400).json({ error: "Username or Email in use!" });
 | 
			
		||||
  const passEncrypt = await passworldSc(password);
 | 
			
		||||
  let ID: string;
 | 
			
		||||
  while (true) if (!(await userCollection.findOne({ ID: (ID = crypto.randomUUID()) }))) break;
 | 
			
		||||
  const token = await createToken();
 | 
			
		||||
  await userCollection.insertOne({
 | 
			
		||||
    ID, createAt: new Date(),
 | 
			
		||||
    email, username, password: passEncrypt,
 | 
			
		||||
    tokens: [token],
 | 
			
		||||
    permissions: [
 | 
			
		||||
      "confirm"
 | 
			
		||||
    ]
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return res.json({
 | 
			
		||||
    ID,
 | 
			
		||||
    token
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const deleteIDs = new Map<string, string>();
 | 
			
		||||
app.delete("/api/register", async (req, res) => {
 | 
			
		||||
  if (typeof req.session.userID !== "string") return res.status(400).json({ error: "Require login fist to delete account" });
 | 
			
		||||
  let deleteID: string;
 | 
			
		||||
  while (true) if (!(deleteIDs.has((deleteID = crypto.randomUUID())))) break;
 | 
			
		||||
  deleteIDs.set(deleteID, req.session.userID);
 | 
			
		||||
  const location = path.posix.join((new URL(req.url, "localhost.com")).pathname, deleteID);
 | 
			
		||||
  res.setHeader("Location", location);
 | 
			
		||||
  return res.status(201).json({
 | 
			
		||||
    deleteID
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.delete("/api/register/:deleteID", async (req, res) => {
 | 
			
		||||
  if (!(deleteIDs.has(req.params.deleteID))) return res.status(400).json({ error: "Id not exists!" });
 | 
			
		||||
  else if (deleteIDs.get(req.params.deleteID) !== req.session.userID) return res.status(400).json({ error: "You do not have access to this ID" });
 | 
			
		||||
  const userInfo = await userCollection.findOneAndDelete({ ID: req.session.userID });
 | 
			
		||||
  deleteIDs.delete(req.params.deleteID);
 | 
			
		||||
  return res.json(userInfo.value);
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										131
									
								
								packages/web/src/mcserver.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								packages/web/src/mcserver.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
import { Bedrock, Java, serverRun } from "@the-bds-maneger/core";
 | 
			
		||||
import express from "express";
 | 
			
		||||
import fs from "node:fs/promises";
 | 
			
		||||
import { createServerID, getServerPaths, passwordCheck, serversIDs, userCollection } from "./db.js";
 | 
			
		||||
 | 
			
		||||
const app = express.Router();
 | 
			
		||||
export default app;
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  namespace Express {
 | 
			
		||||
    export interface Request {
 | 
			
		||||
      userInfo?: userCollection;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check token
 | 
			
		||||
app.use(async (req, res, next) => {
 | 
			
		||||
  if (typeof req.headers.authorization === "string") {
 | 
			
		||||
    const { authorization } = req.headers;
 | 
			
		||||
    if (authorization.startsWith("Basic ")) {
 | 
			
		||||
      const authDecrypt = Buffer.from(authorization.slice(5).trim(), "base64").toString("utf8");
 | 
			
		||||
      const username = authDecrypt.slice(0, authDecrypt.indexOf(":"));
 | 
			
		||||
      const password = authDecrypt.slice(authDecrypt.indexOf(":") + 1);
 | 
			
		||||
      if (!(username && password)) return res.status(401).json({ error: "Basic auth require username and password!" });
 | 
			
		||||
      const userInfo = await userCollection.findOne({ $or: [{ username }, { email: username }] });
 | 
			
		||||
      if (!userInfo) return res.status(401).json({ error: "User not exists" });
 | 
			
		||||
      else if (!(await passwordCheck(userInfo, password))) return res.status(401).json({ error: "Invalid password" });
 | 
			
		||||
      req.session.userID = userInfo.ID;
 | 
			
		||||
    } else if (authorization.startsWith("Token ") || authorization.startsWith("token ")) {
 | 
			
		||||
      const token = authorization.slice(5).trim();
 | 
			
		||||
      const userInfo = await userCollection.findOne({ tokens: [token] });
 | 
			
		||||
      if (!userInfo) return res.status(401).json({ error: "Token not exists" });
 | 
			
		||||
      req.session.userID = userInfo.ID;
 | 
			
		||||
    } else return res.status(401).json({ error: "Invalid authorization schema" });
 | 
			
		||||
    await new Promise<void>((done, reject) => req.session.save(err => err ? reject(err) : done()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (typeof req.session.userID !== "string") return res.status(401).json({ error: "Not authenticated" });
 | 
			
		||||
  const userInfo = req.userInfo = await userCollection.findOne({ ID: req.session.userID });
 | 
			
		||||
  if (!userInfo) {
 | 
			
		||||
    await new Promise<void>((done, reject) => req.session.destroy(err => err ? reject(err) : done()));
 | 
			
		||||
    return res.status(401).json({ error: "User not exists" });
 | 
			
		||||
  } else if (userInfo.permissions.includes("confirm")) return res.status(401).json({ error: "Unauthorized, ask the Site administrator for confirmation!" });
 | 
			
		||||
 | 
			
		||||
  return next();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const sessionMAP = new Map<string, serverRun>();
 | 
			
		||||
 | 
			
		||||
// List auth user server allow access
 | 
			
		||||
app.get("/", async (req, res) => {
 | 
			
		||||
  const servers = await serversIDs.find({ users: [req.session.userID] }).toArray();
 | 
			
		||||
  return res.json(servers.map(info => ({
 | 
			
		||||
    ID: info.ID,
 | 
			
		||||
    platform: info.platform,
 | 
			
		||||
    running: sessionMAP.has(info.ID),
 | 
			
		||||
  })));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Create new Server
 | 
			
		||||
app.post("/", async (req, res) => {
 | 
			
		||||
  if (!(req.userInfo.permissions.includes("admin"))) return res.status(401).json({ error: "You no have access to create server" });
 | 
			
		||||
  else if (typeof req.body !== "object") return res.status(400).json({ error: "Require body to setup server" });
 | 
			
		||||
  const { platform } = req.body;
 | 
			
		||||
  if (!(platform === "bedrock" || platform === "java")) res.status(400).json({ error: "Invalid platform" });
 | 
			
		||||
  const v1 = await createServerID(platform, [req.session.userID]);
 | 
			
		||||
  if (platform === "bedrock") {
 | 
			
		||||
    await Bedrock.installServer(v1, { version: req.body.version, altServer: req.body.altServer, allowBeta: !!req.body.allowBeta });
 | 
			
		||||
  } else {
 | 
			
		||||
    await Java.installServer(v1, { version: req.body.version, altServer: req.body.altServer, allowBeta: !!req.body.allowBeta });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return res.status(201).json({
 | 
			
		||||
    ID: v1.id
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.delete("/", async (req, res) => {
 | 
			
		||||
  if (!(req.userInfo.permissions.includes("admin"))) return res.status(401).json({ error: "You no have access to create server" });
 | 
			
		||||
  else if (typeof req.body !== "object") return res.status(400).json({ error: "Require body to setup server" });
 | 
			
		||||
  const serverInfo = await serversIDs.findOne({ID: String(req.body.id)});
 | 
			
		||||
  if (!(serverInfo)) return res.status(404).json({error: "Server not exists"});
 | 
			
		||||
  if (sessionMAP.has(serverInfo.ID)) await sessionMAP.get(serverInfo.ID).stopServer();
 | 
			
		||||
  const v1 = await getServerPaths(serverInfo.ID);
 | 
			
		||||
  await fs.rm(v1.rootPath, {recursive: true, force: true});
 | 
			
		||||
  return res.json((await serversIDs.findOneAndDelete({ID: serverInfo.ID})).value);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/server/:ID", async (req, res) => {
 | 
			
		||||
  const serverInfo = await serversIDs.findOne({ID: req.params.ID});
 | 
			
		||||
  if (!(serverInfo)) return res.status(404).json({error: "Server not exists"});
 | 
			
		||||
  else if (!(serverInfo.users.includes(req.session.userID))) return res.status(404).json({error: "You do not have permission for this server"});
 | 
			
		||||
  const Running = sessionMAP.get(serverInfo.ID);
 | 
			
		||||
  return res.json({
 | 
			
		||||
    running: !!Running,
 | 
			
		||||
    ports: Running?.portListening,
 | 
			
		||||
    players: Running?.playerActions,
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/server/:ID/hotbackup", async (req, res) => {
 | 
			
		||||
  const serverInfo = await serversIDs.findOne({ID: req.params.ID});
 | 
			
		||||
  if (!(serverInfo)) return res.status(404).json({error: "Server not exists"});
 | 
			
		||||
  else if (!(serverInfo.users.includes(req.session.userID))) return res.status(404).json({error: "You do not have permission for this server"});
 | 
			
		||||
  else if (!(sessionMAP.has(req.params.ID))) return res.status(400).json({error: "Server not running"});
 | 
			
		||||
  const run = sessionMAP.get(serverInfo.ID);
 | 
			
		||||
  const data = await run.hotBackup();
 | 
			
		||||
  if (!data) return res.status(503).json({error: "Server not support hot backup"});
 | 
			
		||||
  return data.pipe(res.writeHead(200, {}));
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.post("/server/:ID", async (req, res) => {
 | 
			
		||||
  const serverInfo = await serversIDs.findOne({ID: req.params.ID});
 | 
			
		||||
  if (!(serverInfo)) return res.status(404).json({error: "Server not exists"});
 | 
			
		||||
  else if (!(serverInfo.users.includes(req.session.userID))) return res.status(404).json({error: "You do not have permission for this server"});
 | 
			
		||||
  else if (sessionMAP.has(serverInfo.ID)) return res.status(400).json({error: "the server is already running"});
 | 
			
		||||
  const v1 = await getServerPaths(serverInfo.ID);
 | 
			
		||||
  const server = await (serverInfo.platform === "bedrock" ? Bedrock.startServer : Java.startServer)(v1, {});
 | 
			
		||||
  sessionMAP.set(v1.id, server);
 | 
			
		||||
  server.once("exit", () => sessionMAP.delete(v1.id));
 | 
			
		||||
  return res.status(201).json({ID: v1.id, pid: server.pid});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.delete("/server/:ID", async (req, res) => {
 | 
			
		||||
  const serverInfo = await serversIDs.findOne({ID: req.params.ID});
 | 
			
		||||
  if (!(serverInfo)) return res.status(404).json({error: "Server not exists"});
 | 
			
		||||
  else if (!(serverInfo.users.includes(req.session.userID))) return res.status(404).json({error: "You do not have permission for this server"});
 | 
			
		||||
  else if (!(sessionMAP.has(req.params.ID))) return res.status(400).json({error: "Server not running"});
 | 
			
		||||
  return res.json(await sessionMAP.get(req.params.ID).stopServer());
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										5
									
								
								packages/web/src/next/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/web/src/next/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
# Next
 | 
			
		||||
.next/
 | 
			
		||||
 | 
			
		||||
# Include typescript
 | 
			
		||||
!next-env.d.ts
 | 
			
		||||
							
								
								
									
										5
									
								
								packages/web/src/next/next-env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								packages/web/src/next/next-env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
/// <reference types="next" />
 | 
			
		||||
/// <reference types="next/image-types/global" />
 | 
			
		||||
 | 
			
		||||
// NOTE: This file should not be edited
 | 
			
		||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/web/src/next/next.config.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/web/src/next/next.config.cjs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
/** @type {import('next').NextConfig} */
 | 
			
		||||
const nextConfig = {
 | 
			
		||||
  experimental: {
 | 
			
		||||
    typedRoutes: true,
 | 
			
		||||
  },
 | 
			
		||||
  eslint: {
 | 
			
		||||
    ignoreDuringBuilds: true,
 | 
			
		||||
  },
 | 
			
		||||
  typescript: {
 | 
			
		||||
    ignoreBuildErrors: true,
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = nextConfig;
 | 
			
		||||
							
								
								
									
										3
									
								
								packages/web/src/next/pages/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/web/src/next/pages/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
export default function Home() {
 | 
			
		||||
  return <>Works from Express + Next.js</>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								packages/web/src/next/pages/login.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/web/src/next/pages/login.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
export default function LoginPage() {
 | 
			
		||||
  return <>
 | 
			
		||||
    <form action="/api/login" method="post">
 | 
			
		||||
      <div>
 | 
			
		||||
        <label>Username/Email: </label>
 | 
			
		||||
        <input type="text" name="username" />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <label>Password: </label>
 | 
			
		||||
        <input type="password" name="password" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
  </>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								packages/web/src/next/pages/register.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/web/src/next/pages/register.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
export default function RegisterPage() {
 | 
			
		||||
  return <>
 | 
			
		||||
    <form action="/api/register" method="post">
 | 
			
		||||
      <div>
 | 
			
		||||
        <input type="email" name="email" />
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
  </>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								packages/web/src/next/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								packages/web/src/next/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "../../tsconfig.json",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "jsx": "preserve",
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "allowSyntheticDefaultImports": true,
 | 
			
		||||
    "noEmit": true,
 | 
			
		||||
    "incremental": true,
 | 
			
		||||
    "resolveJsonModule": true,
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "dom",
 | 
			
		||||
      "DOM.Iterable"
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "**/next.config.*js",
 | 
			
		||||
    "**/.next/"
 | 
			
		||||
  ],
 | 
			
		||||
  "include": [
 | 
			
		||||
    "next-env.d.ts",
 | 
			
		||||
    "**/*.ts",
 | 
			
		||||
    "**/*.tsx"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								packages/web/src/reactServer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								packages/web/src/reactServer.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
import _next from "next";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import { fileURLToPath } from "url";
 | 
			
		||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
 | 
			
		||||
const next: typeof _next.default = _next as any;
 | 
			
		||||
let _require: typeof require;
 | 
			
		||||
if (typeof require === "function") _require = require; else _require = (await import("module")).default.createRequire(import.meta.url);
 | 
			
		||||
export const dev = import.meta.url.endsWith(".ts"), { PORT = "3000", HOSTNAME = "localhost" } = process.env;
 | 
			
		||||
 | 
			
		||||
const dir = path.join(__dirname, "next");
 | 
			
		||||
export const nextApp = next({
 | 
			
		||||
  customServer: true,
 | 
			
		||||
  hostname: HOSTNAME,
 | 
			
		||||
  quiet: true,
 | 
			
		||||
  port: Number(PORT),
 | 
			
		||||
  conf: _require(path.join(dir, "next.config.cjs")),
 | 
			
		||||
  dir,
 | 
			
		||||
  dev,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
await nextApp.prepare();
 | 
			
		||||
export const nextHandler = nextApp.getRequestHandler();
 | 
			
		||||
export const nextUpgarde = nextApp.getUpgradeHandler();
 | 
			
		||||
export const {
 | 
			
		||||
  render: pageRender,
 | 
			
		||||
  render404,
 | 
			
		||||
  renderError,
 | 
			
		||||
} = nextApp;
 | 
			
		||||
							
								
								
									
										11
									
								
								packages/web/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/web/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
{
 | 
			
		||||
  "extends": "../../tsconfig.json",
 | 
			
		||||
  "references": [
 | 
			
		||||
    {
 | 
			
		||||
      "path": "../core"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "src/next/next.config.cjs"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@@ -20,7 +20,8 @@
 | 
			
		||||
  },
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "**/*.test.ts",
 | 
			
		||||
    "node_modules/"
 | 
			
		||||
    "**/node_modules/**",
 | 
			
		||||
    "packages/verapi/**"
 | 
			
		||||
  ],
 | 
			
		||||
  "ts-node": {
 | 
			
		||||
    "files": true,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user