Monorepo project #508

Merged
Sirherobrine23 merged 3 commits from monorepo into main 2023-02-22 04:21:43 +00:00
50 changed files with 898 additions and 2555 deletions

@ -1,11 +0,0 @@
FROM ghcr.io/sirherobrine23/initjs:latest
# Install dependecies to Bedrock server
RUN (apt update && apt install -y libssl1.1 || (echo "deb http://security.ubuntu.com/ubuntu focal-security main" | tee /etc/apt/sources.list.d/focal-security.list && apt update && apt install -y libssl1.1)) || echo exit $?
# Add non root user and Install oh my zsh
ARG USERNAME="devcontainer"
ARG USER_UID="1000"
ARG USER_GID=$USER_UID
RUN initjs create-user --username "${USERNAME}" --uid "${USER_UID}" --gid "${USER_GID}" --groups sudo --groups docker
USER $USERNAME
WORKDIR /home/$USERNAME

@ -1,49 +0,0 @@
{
"name": "Bds Maneger Core",
"updateRemoteUserUID": false,
"containerUser": "develop",
"remoteUser": "develop",
"overrideCommand": false,
"postCreateCommand": "npm install",
"build": {
"dockerfile": "Dockerfile",
"args": {
"USERNAME": "develop",
"USER_UID": "1000"
}
},
"runArgs": [
"--init",
"--privileged"
],
"mounts": [
"target=/var/lib/docker,type=volume,source=bdsmanegercore"
],
"extensions": [
"benshabatnoam.google-translate-ext",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml",
"ms-vscode-remote.remote-containers",
"wix.vscode-import-cost",
"eg2.vscode-npm-script",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense",
"aaron-bond.better-comments",
"vscode-icons-team.vscode-icons",
"me-dutour-mathieu.vscode-github-actions",
"cschleiden.vscode-github-actions",
"oderwat.indent-rainbow",
"ms-azuretools.vscode-docker",
"formulahendry.code-runner",
"chrmarti.regex"
],
"settings": {
"editor.tabSize": 2,
"editor.minimap.enabled": false,
"files.eol": "\n",
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true
}
}

@ -1,29 +0,0 @@
#!/usr/bin/env node
import { createReadStream, promises as fs } from "node:fs";
import { Cloud, Extends } from "@sirherobrine23/coreutils";
import path from "path";
const { tenancy, fingerprint, privateKey, user, passphase } = process.env;
const oracleBucket = await Cloud.oracleBucket({
region: "sa-saopaulo-1",
name: "bdsFiles",
namespace: "grwodtg32n4d",
auth: {
type: "user",
tenancy,
fingerprint,
privateKey,
user,
passphase
}
})
const __dirname = path.resolve(process.cwd(), process.argv.slice(2)[0]||"");
await Extends.extendsFS.readdir({folderPath: __dirname}).then(files => files.filter(file => file.endsWith(".jar"))).then(async files => {
for (const file of files) {
const version = path.basename(file, ".jar").split("-")[1];
if (!version) continue;
console.log("Uploading %s, file: %O", version, "SpigotBuild/"+version+".jar");
await oracleBucket.uploadFile("SpigotBuild/"+version+".jar", createReadStream(file)).then(() => console.log("Uploaded %s", version));
await fs.unlink(file);
}
});

23
.github/uploadToBucket.mjs vendored Normal file

@ -0,0 +1,23 @@
#!/usr/bin/env node
import { createReadStream } from "fs";
import { oracleBucket } from "@sirherobrine23/cloud";
import extendsFS from "@sirherobrine23/extends";
import path from "node:path";
const [,, remote, local] = process.argv;
const bucket = await oracleBucket.oracleBucket({
region: "sa-saopaulo-1",
namespace: "grwodtg32n4d",
name: "bdsFiles",
auth: {
type: "preAuthentication",
// Public auth (No write enabled).
PreAuthenticatedKey: process.env.OCI_AUTHKEY
}
});
for await (const file of await extendsFS.readdir(path.resolve(process.cwd(), local))) {
console.log("Uploading %O", file);
await bucket.uploadFile(path.posix.resolve("/", remote ?? "", path.basename(file)), createReadStream(file));
console.log("Success %O", file);
}

@ -1,24 +0,0 @@
#!/usr/bin/env node
import { createReadStream } from "node:fs";
import coreutils, { extendFs } from "@sirherobrine23/coreutils";
import path from "node:path";
import fs from "node:fs/promises";
if (!process.env.OCI_AUTHKEY) throw new Error("No key auth");
const ociKeyAuth = (process.env.OCI_AUTHKEY||"").trim();
console.log("using key to upload '%s'", ociKeyAuth);
const files = (await extendFs.readdir({folderPath: path.join(process.cwd(), "phpOutput")})).filter(file => file.endsWith(".tar.gz")||file.endsWith(".zip")||file.endsWith(".tgz"));
await Promise.all(files.map(async file => {
const fileName = path.basename(file);
console.log("Uploading %s", fileName);
await coreutils.httpRequest.bufferFetch({
url: `https://objectstorage.sa-saopaulo-1.oraclecloud.com/p/${ociKeyAuth}/n/grwodtg32n4d/b/bdsFiles/o/php_bin/${encodeURIComponent(fileName.toLowerCase())}`,
method: "PUT",
body: createReadStream(file),
headers: {
"Content-Length": (await fs.lstat(file)).size.toString(),
"Content-Type": "application/octet-stream"
}
});
console.log("Upload success to %s", fileName);
}));

@ -1,43 +0,0 @@
name: Code Analyze
on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
- cron: "26 8 * * 1"
jobs:
snyk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --sarif-file-output=snyk.sarif
- name: Upload result to GitHub Code Scanning
continue-on-error: true
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: snyk.sarif
codeql:
name: CodeQL Analyze
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: javascript
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

@ -1,43 +0,0 @@
name: Deploy bds core wiki
on:
push:
branches:
- main
paths:
- "src/**/*"
- "tests/**/*"
- "package*.json"
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy_doc:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3.6.0
name: Setup node.js
with:
node-version: latest
- run: npm ci
- name: Gen docs
run: npm run docs
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: "docs"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

@ -215,7 +215,7 @@ jobs:
path: ./phpOutput path: ./phpOutput
- name: Upload to bucket - name: Upload to bucket
run: node .github/uploadphp/index.mjs run: node .github/uploadphp/index.mjs php_bin phpOutput
timeout-minutes: 25 timeout-minutes: 25
env: env:
OCI_AUTHKEY: ${{ secrets.OCI_AUTHKEY }} OCI_AUTHKEY: ${{ secrets.OCI_AUTHKEY }}

@ -1,69 +0,0 @@
name: Publish package
on:
release:
types:
- prereleased
- released
permissions:
pull-requests: write
id-token: write
jobs:
publishpackage:
runs-on: ubuntu-latest
name: Publish
steps:
- uses: actions/checkout@v3
name: Code checkout
with:
persist-credentials: true
ref: main
fetch-depth: 2
submodules: true
# Install basic tools
- uses: actions/setup-node@v3.6.0
name: Setup node.js
with:
registry-url: https://registry.npmjs.org/
node-version: latest
- name: Edit version
shell: node {0}
run: |
const fs = require("fs");
const path = require("path");
const packagePath = path.join(process.cwd(), "package.json");
const package = JSON.parse(fs.readFileSync(packagePath, "utf8"));
package.version = "${{ github.ref }}";
package.version = package.version.replace(/[A-Za-z_\/]+/, "");
fs.writeFileSync(packagePath, JSON.stringify(package, null, 2));
# Install depencides and build
- run: npm ci && npm run build
# Publish
- run: npm publish --tag ${{ github.event.release.prerelease && 'next' || 'latest' }}
name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ORG_TOKEN }}
# Add version to environment variables
- name: Add version to environment variables
run: |
cat package.json | jq -r '.version' > /tmp/version.txt
echo "PACKAGE_VERSION=$(cat /tmp/version.txt)" >> $GITHUB_ENV
# Create pull request to update version in main branch
- uses: peter-evans/create-pull-request@v4
name: Create Pull Request
continue-on-error: true
with:
commit-message: Update version v${{ env.PACKAGE_VERSION }}
delete-branch: true
assignees: SirHerobrine23
reviewers: SirHerobrine23
branch: update-version
title: Update package version v${{ env.PACKAGE_VERSION }}
body: Auto update package version, created with GitHub Actions

@ -107,10 +107,6 @@ jobs:
path: artifacts path: artifacts
- name: Upload to actifial - name: Upload to actifial
run: node .github/spigotBuilld/index.mjs artifacts run: node .github/uploadToBucket.ts SpigotBuild artifacts
env: env:
tenancy: ${{ secrets.OCI_TENANCY }} OCI_AUTHKEY: ${{ secrets.OCI_AUTHKEY }}
fingerprint: ${{ secrets.OCI_FING }}
privateKey: ${{ secrets.OCI_PRIV }}
user: ${{ secrets.OCI_USER }}
passphase: ${{ secrets.OCI_PASSPHASE || '' }}

@ -1,23 +1,13 @@
name: Test name: Test
on: on:
push: push:
branches:
- main
paths:
- "src/**/*"
- "tests/**/*"
- "package*.json"
pull_request:
branches:
- main
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: "Test" name: "Test"
env: env:
BDS_HOME: "~/.bdsCore" bdscoreroot: "~/.bdsCore"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
name: Code checkout name: Code checkout
@ -36,8 +26,8 @@ jobs:
# Install dependecies # Install dependecies
- name: Install nodejs dependencies - name: Install nodejs dependencies
run: npm ci run: npm install --no-save
# Run test # Build Core
- name: Test - name: Core Build
run: npm run test run: cd package/core && npm run build

20
.gitignore vendored

@ -1,17 +1,9 @@
/*.log # Node
node_modules/ node_modules/
dist/
src/**/*.d.ts
src/**/*.d.js
src/**/*.js
docs/
# PHP Bins # Typescript
muslCrossMake/ packages/**/*.js
packages/**/*.d.ts
# PHP Pre builds
phpOutput/ phpOutput/
*.tar.gz
*.tgz
*.zip
# Spigot
*.jar

@ -1,19 +0,0 @@
image:
file: .devcontainer/Dockerfile
tasks:
- init: npm install
vscode:
extensions:
- formulahendry.code-runner
- github.vscode-pull-request-github
- redhat.vscode-yaml
- eamodio.gitlens
- wix.vscode-import-cost
- eg2.vscode-npm-script
- christian-kohler.npm-intellisense
- christian-kohler.path-intellisense
- aaron-bond.better-comments
- vscode-icons-team.vscode-icons
- cschleiden.vscode-github-actions
- oderwat.indent-rainbow
- ms-azuretools.vscode-docker

@ -1,8 +0,0 @@
{
"extends": [
"development"
],
"hints": {
"typescript-config/strict": "off"
}
}

@ -1,11 +0,0 @@
exit: true
colors: true
full-trace: true
recursive: true
parallel: true
timeout: 0
node-option:
- "experimental-specifier-resolution=node"
- "loader=ts-node/esm"
extension:
- "test.ts"

@ -1,2 +0,0 @@
!dist/
backup_*.zip

@ -1,22 +0,0 @@
{
"recommendations": [
"formulahendry.code-runner",
"chrmarti.regex",
"benshabatnoam.google-translate-ext",
"eamodio.gitlens",
"github.vscode-pull-request-github",
"visualstudioexptteam.vscodeintellicode",
"redhat.vscode-yaml",
"ms-vscode-remote.remote-containers",
"wix.vscode-import-cost",
"eg2.vscode-npm-script",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense",
"aaron-bond.better-comments",
"vscode-icons-team.vscode-icons",
"me-dutour-mathieu.vscode-github-actions",
"cschleiden.vscode-github-actions",
"oderwat.indent-rainbow",
"ms-azuretools.vscode-docker"
]
}

34
.vscode/launch.json vendored

@ -1,34 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"name": "Current file",
"internalConsoleOptions": "openOnSessionStart",
"request": "launch",
"env": {
"BDSD_IGNORE_KEY": "1",
"PORT": "3000"
},
"args": [
"-r", "ts-node/register",
"${file}"
],
},
{
"type": "node",
"name": "Mocha Tests",
"program": "${workspaceFolder}/node_modules/mocha/bin/mocha.js",
"internalConsoleOptions": "openOnSessionStart",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"args": [
"-r", "ts-node/register",
"--colors",
"${workspaceFolder}/tests/**/*.ts"
],
},
]
}

@ -8,9 +8,7 @@
"files.trimTrailingWhitespace": true, "files.trimTrailingWhitespace": true,
"files.exclude": { "files.exclude": {
"**/node_modules/": true, "**/node_modules/": true,
// Ignore generate tsc files "**/src/**/*.js": true,
"**/dist/": true, "**/src/**/*.d.ts": true,
"**/src/**/*.js": false,
"**/src/**/*.d.ts": false,
} }
} }

@ -1,128 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
Email or Github issues.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

@ -1,8 +0,0 @@
# Contributing to the core
We hope to have multiple platforms supported by the project, as I want to bring together multiple servers to be managed in one place.
Any new platform will have to fork the repository or open an issue to request the new platform.
1. not all servers can be added because of project licensing but we can talk to the maintainers if needed.
2. This project is designed to run on various operating systems such as Android, Linux, Windows and MacOS (other systems can be added to be supported).

@ -632,7 +632,7 @@ state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found. the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.> <one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author> Copyright (C) 2023 Matheus Sampaio Queiroga
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode: notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author> <program> Copyright (C) 2023 Matheus Sampaio Queiroga
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details. under certain conditions; type `show c' for details.
@ -672,4 +672,3 @@ may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>. <https://www.gnu.org/licenses/why-not-lgpl.html>.

@ -1,12 +0,0 @@
# Bds Maneger Core
A quick way to install, update and start your Minecraft Java and Bedrock server.
## Current Bds core support servers
1. `Bedrock` (Mojang)
2. `Java` (Mojang)
3. `Pocketmine` (PMMP - Minecraft bedrock server writed in PHP)
4. `Spigot` (Spigot-MC is Minecraft java server to add plugins and more to server)
5. `Powernukkit` (Minecraft Bedrock Server in java)
6. `Paper` (PaperMC is Minecraft java server with plugins and mods)

@ -1,135 +0,0 @@
server-name=Dedicated Server
# Used as the server name
# Allowed values: Any string without semicolon symbol.
gamemode=survival
# Sets the game mode for new players.
# Allowed values: "survival", "creative", or "adventure"
force-gamemode=false
# force-gamemode=false (or force-gamemode is not defined in the server.properties)
# prevents the server from sending to the client gamemode values other
# than the gamemode value saved by the server during world creation
# even if those values are set in server.properties after world creation.
#
# force-gamemode=true forces the server to send to the client gamemode values
# other than the gamemode value saved by the server during world creation
# if those values are set in server.properties after world creation.
difficulty=easy
# Sets the difficulty of the world.
# Allowed values: "peaceful", "easy", "normal", or "hard"
allow-cheats=false
# If true then cheats like commands can be used.
# Allowed values: "true" or "false"
max-players=10
# The maximum number of players that can play on the server.
# Allowed values: Any positive integer
online-mode=true
# If true then all connected players must be authenticated to Xbox Live.
# Clients connecting to remote (non-LAN) servers will always require Xbox Live authentication regardless of this setting.
# If the server accepts connections from the Internet, then it's highly recommended to enable online-mode.
# Allowed values: "true" or "false"
allow-list=false
# If true then all connected players must be listed in the separate allowlist.json file.
# Allowed values: "true" or "false"
server-port=34215
# Which IPv4 port the server should listen to.
# Allowed values: Integers in the range [1, 65535]
server-portv6=33657
# Which IPv6 port the server should listen to.
# Allowed values: Integers in the range [1, 65535]
view-distance=32
# The maximum allowed view distance in number of chunks.
# Allowed values: Positive integer equal to 5 or greater.
tick-distance=4
# The world will be ticked this many chunks away from any player.
# Allowed values: Integers in the range [4, 12]
player-idle-timeout=30
# After a player has idled for this many minutes they will be kicked. If set to 0 then players can idle indefinitely.
# Allowed values: Any non-negative integer.
max-threads=8
# Maximum number of threads the server will try to use. If set to 0 or removed then it will use as many as possible.
# Allowed values: Any positive integer.
level-name=Bedrock level
# Allowed values: Any string without semicolon symbol or symbols illegal for file name: /\n\r\t\f`?*\\<>|\":
level-seed=
# Use to randomize the world
# Allowed values: Any string
default-player-permission-level=member
# Permission level for new players joining for the first time.
# Allowed values: "visitor", "member", "operator"
texturepack-required=false
# Force clients to use texture packs in the current world
# Allowed values: "true" or "false"
content-log-file-enabled=false
# Enables logging content errors to a file
# Allowed values: "true" or "false"
compression-threshold=1
# Determines the smallest size of raw network payload to compress
# Allowed values: 0-65535
compression-algorithm=zlib
# Determines the compression algorithm to use for networking
# Allowed values: "zlib", "snappy"
server-authoritative-movement=server-auth
# Allowed values: "client-auth", "server-auth", "server-auth-with-rewind"
# Enables server authoritative movement. If "server-auth", the server will replay local user input on
# the server and send down corrections when the client's position doesn't match the server's.
# If "server-auth-with-rewind" is enabled and the server sends a correction, the clients will be instructed
# to rewind time back to the correction time, apply the correction, then replay all the player's inputs since then. This results in smoother and more frequent corrections.
# Corrections will only happen if correct-player-movement is set to true.
player-movement-score-threshold=20
# The number of incongruent time intervals needed before abnormal behavior is reported.
# Disabled by server-authoritative-movement.
player-movement-action-direction-threshold=0.85
# The amount that the player's attack direction and look direction can differ.
# Allowed values: Any value in the range of [0, 1] where 1 means that the
# direction of the players view and the direction the player is attacking
# must match exactly and a value of 0 means that the two directions can
# differ by up to and including 90 degrees.
player-movement-distance-threshold=0.3
# The difference between server and client positions that needs to be exceeded before abnormal behavior is detected.
# Disabled by server-authoritative-movement.
player-movement-duration-threshold-in-ms=500
# The duration of time the server and client positions can be out of sync (as defined by player-movement-distance-threshold)
# before the abnormal movement score is incremented. This value is defined in milliseconds.
# Disabled by server-authoritative-movement.
correct-player-movement=false
# If true, the client position will get corrected to the server position if the movement score exceeds the threshold.
server-authoritative-block-breaking=false
# If true, the server will compute block mining operations in sync with the client so it can verify that the client should be able to break blocks when it thinks it can.
chat-restriction=None
# Allowed values: "None", "Dropped", "Disabled"
# This represents the level of restriction applied to the chat for each player that joins the server.
# "None" is the default and represents regular free chat.
# "Dropped" means the chat messages are dropped and never sent to any client. Players receive a message to let them know the feature is disabled.
# "Disabled" means that unless the player is an operator, the chat UI does not even appear. No information is displayed to the player.
disable-player-interaction=false
# If true, the server will inform clients that they should ignore other players when interacting with the world. This is not server authoritative.

@ -1,57 +0,0 @@
#Minecraft server properties
#Mon Oct 17 00:27:26 UTC 2022
enable-jmx-monitoring=false
rcon.port=25575
level-seed=
gamemode=survival
enable-command-block=false
enable-query=false
generator-settings={}
enforce-secure-profile=true
level-name=world
motd=A Minecraft Server
query.port=25565
pvp=true
generate-structures=true
max-chained-neighbor-updates=1000000
difficulty=easy
network-compression-threshold=256
max-tick-time=60000
require-resource-pack=false
use-native-transport=true
max-players=20
online-mode=true
enable-status=true
allow-flight=false
broadcast-rcon-to-ops=true
view-distance=10
server-ip=
resource-pack-prompt=
allow-nether=true
server-port=25565
enable-rcon=false
sync-chunk-writes=true
op-permission-level=4
prevent-proxy-connections=false
hide-online-players=false
resource-pack=
entity-broadcast-range-percentage=100
simulation-distance=10
rcon.password=
player-idle-timeout=0
force-gamemode=false
rate-limit=0
hardcore=false
white-list=false
broadcast-console-to-ops=true
spawn-npcs=true
previews-chat=false
spawn-animals=true
function-permission-level=2
level-type=minecraft\:normal
text-filtering-config=
spawn-monsters=true
enforce-whitelist=false
spawn-protection=16
resource-pack-sha1=
max-world-size=29999984

1338
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,52 +1,16 @@
{ {
"name": "@the-bds-maneger/core", "name": "@the-bds-maneger/monorepo",
"version": "5.4.0", "private": true,
"description": "A very simple way to manage Minecraft servers", "scripts": {},
"author": "Sirherobrine23", "author": "Matheus Sampaio Queiroga <srherobrine20@gmail.com>",
"license": "GPL-3.0", "license": "GPL-3.0",
"homepage": "https://sirherobrine23.org/BdsProject",
"type": "module",
"types": "./src/index.d.ts",
"main": "./src/index.js",
"private": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"docs": "typedoc --readme none --out docs src/index.ts",
"build": "tsc",
"test": "mocha src"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Sirherobrine23/Bds-Maneger-Core.git"
},
"keywords": [],
"bugs": {
"url": "https://github.com/Sirherobrine23/Bds-Maneger-Core/issues/new",
"email": "support_bds@sirherobrine23.org"
},
"engines": {
"node": ">=16.0.0"
},
"dependencies": {
"@sirherobrine23/coreutils": "^3.1.2",
"adm-zip": "^0.5.10",
"compare-versions": "^5.0.3",
"debug": "^4.3.4",
"prismarine-nbt": "^2.2.1",
"tar": "^6.1.13"
},
"devDependencies": { "devDependencies": {
"@types/adm-zip": "^0.5.0", "@types/node": "^18.14.0",
"@types/debug": "^4.1.7",
"@types/mocha": "^10.0.1",
"@types/node": "^18.13.0",
"@types/tar": "^6.1.3",
"mocha": "^10.2.0",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2",
"typedoc": "^0.23.24",
"typescript": "^4.9.5" "typescript": "^4.9.5"
} },
"workspaces": [
"package/core",
"package/cli",
"package/docker"
]
} }

18
package/cli/package.json Normal file

@ -0,0 +1,18 @@
{
"name": "@the-bds-maneger/cli",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"type": "module",
"scripts": {},
"keywords": [],
"author": "Matheus Sampaio Queiroga <srherobrine20@gmail.com>",
"license": "ISC",
"dependencies": {
"@the-bds-maneger/core": "*",
"yargs": "^17.7.0"
},
"devDependencies": {
"@types/yargs": "^17.0.22"
}
}

39
package/cli/src/index.ts Normal file

@ -0,0 +1,39 @@
#!/usr/bin/env node
import bdsCore from "@the-bds-maneger/core";
import yargs from "yargs";
// Init yargs
yargs(process.argv.slice(2)).version(false).help(true).strictCommands().demandCommand()
// bedrock
.command("bedrock", "Bedrock", yargs => yargs.command("install", "Install Server", async yargs => {
const options = yargs.option("altServer", {
string: true,
description: "Select a server other than Mojang",
demandOption: false,
choices: [
"pocketmine",
"powernukkit",
"cloudbust"
],
}).option("list", {
alias: "l",
boolean: true,
default: false,
description: "List versions instead of installing"
}).option("version", {
alias: "V",
string: true,
default: "latest",
description: "Server version to install",
}).parseSync();
if (options.list) return console.log(JSON.stringify(await bdsCore.Bedrock.listVersions({altServer: options.altServer as any}), null, 2));
const data = await bdsCore.Bedrock.installServer({
altServer: options.altServer as any,
version: options.version,
});
console.log("Server ID: %O", data.id);
}))
// run
.parseAsync();

31
package/cli/tsconfig.json Normal file

@ -0,0 +1,31 @@
{
"compilerOptions": {
"esModuleInterop": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ESNext",
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": false,
"noUnusedLocals": true,
"isolatedModules": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"allowJs": true,
"lib": [
"ESNext"
]
},
"exclude": [
"**/*.test.*"
],
"ts-node": {
"esm": true
},
"references": [
{
"path": "../core"
}
]
}

5
package/core/README.md Normal file

@ -0,0 +1,5 @@
# Bds Maneger Core
Este é um nucleo de utilização basica como: Fazer download do servidor, Gerenciar e outras coisas.
**Atualmente suportamos varias servidores, tanto para o Bedrock tanto o Java**

26
package/core/package.json Normal file

@ -0,0 +1,26 @@
{
"name": "@the-bds-maneger/core",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"types": "src/index.d.ts",
"type": "module",
"author": "Matheus Sampaio Queiroga <srherobrine20@gmail.com>",
"license": "GPL-3.0",
"scripts": {
"build": "tsc --build --clean && tsc"
},
"dependencies": {
"@sirherobrine23/cloud": "^3.2.1",
"@sirherobrine23/extends": "^3.2.1",
"@sirherobrine23/http": "^3.2.1",
"semver": "^7.3.8",
"tar": "^6.1.13",
"unzip-stream": "^0.3.1"
},
"devDependencies": {
"@types/semver": "^7.3.13",
"@types/tar": "^6.1.4",
"@types/unzip-stream": "^0.3.1"
}
}

12
package/core/src/index.ts Normal file

@ -0,0 +1,12 @@
export * from "./serverManeger.js";
export * as Bedrock from "./servers/bedrock.js";
export * as Java from "./servers/java.js";
import * as serverManeger from "./serverManeger.js";
import * as Bedrock from "./servers/bedrock.js";
import * as Java from "./servers/java.js";
export default {
...serverManeger,
Bedrock,
Java
};

@ -1,11 +1,12 @@
import { Cloud } from "@sirherobrine23/coreutils"; import { oracleBucket } from "@sirherobrine23/cloud";
export const oracleBucket = await Cloud.oracleBucket({ export const oracleStorage = await oracleBucket.oracleBucket({
region: "sa-saopaulo-1", region: "sa-saopaulo-1",
namespace: "grwodtg32n4d", namespace: "grwodtg32n4d",
name: "bdsFiles", name: "bdsFiles",
auth: { auth: {
type: "preAuthentication", type: "preAuthentication",
// Public auth (No write enabled).
PreAuthenticatedKey: "0IKM-5KFpAF8PuWoVe86QFsF4sipU2rXfojpaOMEdf4QgFQLcLlDWgMSPHWmjf5W" PreAuthenticatedKey: "0IKM-5KFpAF8PuWoVe86QFsF4sipU2rXfojpaOMEdf4QgFQLcLlDWgMSPHWmjf5W"
} }
}); });

@ -0,0 +1,121 @@
import { extendsFS } from "@sirherobrine23/extends";
import child_process from "node:child_process";
import crypto from "node:crypto";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
// Default bds maneger core
export const bdsManegerRoot = process.env.bdscoreroot ? path.resolve(process.cwd(), process.env.bdscoreroot) : path.join(os.homedir(), ".bdsmaneger");
if (!(await extendsFS.exists(bdsManegerRoot))) await fs.mkdir(bdsManegerRoot, {recursive: true});
export type runOptions = {
cwd: string,
env?: {[k: string]: string|number|boolean},
command: string,
args?: (string|number|boolean)[],
serverActions?: {
stop?(child: serverRun): void|Promise<void>,
}
};
export declare class serverRun extends child_process.ChildProcess {
on(event: string, listener: (...args: any[]) => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: "close", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
on(event: "disconnect", listener: () => void): this;
on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
on(event: "message", listener: (message: child_process.Serializable, sendHandle: child_process.SendHandle) => void): this;
on(event: "spawn", listener: () => void): this;
once(event: string, listener: (...args: any[]) => void): this;
once(event: "error", listener: (err: Error) => void): this;
once(event: "close", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
once(event: "disconnect", listener: () => void): this;
once(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): this;
once(event: "message", listener: (message: child_process.Serializable, sendHandle: child_process.SendHandle) => void): this;
once(event: "spawn", listener: () => void): this;
stopServer(): Promise<{code?: number, signal?: NodeJS.Signals}>;
sendCommand(...args: (string|number|boolean)[]): this;
}
/**
*
*/
export async function runServer(options: runOptions): Promise<serverRun> {
const child = child_process.execFile(options.command, [...((options.args ?? []).map(String))], {
maxBuffer: Infinity,
cwd: options.cwd || process.cwd(),
env: {
...process.env,
...Object.keys(options.env ?? {}).reduce((acc, a) => {
acc[a] = String(options.env[a]);
return acc;
}, {})
}
}) as serverRun;
child.sendCommand = function (...args) {
if (!child.stdin.writable) {
child.emit("error", new Error("cannot send command to server"));
return child;
};
child.stdin.write(args.map(String).join(" ")+"\n");
return child;
}
child.stopServer = async function () {
const stop = options.serverActions?.stop ?? function (child) {
};
Promise.resolve().then(() => stop(child)).catch(err => child.emit("error", err));
return new Promise((done, reject) => child.once("error", reject).once("exit", (code, signal) => done({code, signal})));
}
return child;
}
export type manegerOptions = {
ID?: string,
newID?: boolean,
};
/**
*
*/
export async function serverManeger(options: manegerOptions) {
if (!options) throw new TypeError("Por favor adicione as opções do serverManeger!");
if (!options.ID) options.newID = true;
if (options.newID) {
while(true) {
options.ID = crypto.randomBytes(16).toString("hex");
if (!(await fs.readdir(bdsManegerRoot)).includes(options.ID)) break;
}
}
/**
* Platform ID root path
*/
const rootPath = path.join(bdsManegerRoot, options.ID);
if (!(await extendsFS.exists(rootPath))) await fs.mkdir(rootPath, {recursive: true});
// sub-folders
const serverFolder = path.join(rootPath, "server");
const backup = path.join(rootPath, "backups");
for await (const p of [
serverFolder,
backup,
]) if (!(await extendsFS.exists(p))) await fs.mkdir(p, {recursive: true});
return {
id: options.ID,
rootPath,
serverFolder,
backup,
async runCommand(options: Omit<runOptions, "cwd">) {
return runServer({...options, cwd: serverFolder});
}
};
}
export type serverManegerV1 = Awaited<ReturnType<typeof serverManeger>>;

@ -0,0 +1,205 @@
import coreHttp, { Github, large } from "@sirherobrine23/http";
import { manegerOptions, runOptions, serverManeger } from "../serverManeger.js";
import { commandExists } from "../childPromisses.js";
import { oracleStorage } from "../internal.js";
import { pipeline } from "node:stream/promises";
import semver from "semver";
import unzip from "unzip-stream";
import utils from "node:util";
import path from "node:path";
import tar from "tar";
import extendsFS from "@sirherobrine23/extends";
export type bedrockOptions = manegerOptions & {
/**
* Servidor alternativo ao invés do servidor ofical da Mojang
*/
altServer?: "pocketmine"|"powernukkit"|"cloudbust",
};
const pocketmineGithub = await Github.GithubManeger("pmmp", "PocketMine-MP");
export async function listVersions(options: {altServer: "powernukkit"} & Omit<bedrockOptions, "altServer"|keyof manegerOptions>): Promise<{version: string, mcpeVersion: string, date: Date, variantType: "stable"|"snapshot", url: string}[]>;
export async function listVersions(options: {altServer: "pocketmine"} & Omit<bedrockOptions, "altServer"|keyof manegerOptions>): Promise<Github.githubRelease[]>;
export async function listVersions(): Promise<{version: string, date: Date, release?: "stable"|"preview", url: {[platform in NodeJS.Platform]?: {[arch in NodeJS.Architecture]?: string}}}[]>;
export async function listVersions(options?: Omit<bedrockOptions, keyof manegerOptions>) {
if (!options) options = {};
if (options.altServer === "pocketmine") return pocketmineGithub.getRelease();
else if (options.altServer === "powernukkit") {
const releases_version = (await coreHttp.jsonRequest<{[k: string]: {version: string, releaseTime: number, minecraftVersion: string, artefacts: string[], commitId: string, snapshotBuild?: number}[]}>("https://raw.githubusercontent.com/PowerNukkit/powernukkit-version-aggregator/master/powernukkit-versions.json")).body;
return Object.keys(releases_version).reduce((acc, key) => {
for (const data of releases_version[key]) {
const dt = new Date(data.releaseTime);
const getArtefactExtension = (artefactId: string) => (artefactId.includes("REDUCED_JAR")) ? ".jar" : (artefactId.includes("REDUCED_SOURCES_JAR")) ? "-sources.jar" : (artefactId.includes("SHADED_JAR")) ? "-shaded.jar" : (artefactId.includes("SHADED_SOURCES_JAR")) ? "-shaded-sources.jar" : (artefactId.includes("JAVADOC_JAR")) ? "-javadoc.jar" : ".unknown";
function buildArtefactUrl(data: any, artefactId?: string) {
const buildReleaseArtefactUrl = (data: any, artefactId?: string) => !data.artefacts.includes(artefactId) ? null : utils.format("https://search.maven.org/remotecontent?filepath=org/powernukkit/powernukkit/%s/powernukkit-%s%s", data.version, data.version, getArtefactExtension(artefactId));
const buildSnapshotArtefactUrl = (data: any, artefactId?: string) => !data.artefacts.includes(artefactId) ? null : utils.format("https://oss.sonatype.org/content/repositories/snapshots/org/powernukkit/powernukkit/%s-SNAPSHOT/powernukkit-%s-%s%s", data.version.substring(0, data.version.indexOf("-SNAPSHOT")), data.version.substring(0, data.version.indexOf("-SNAPSHOT")), dt.getUTCFullYear().toString().padStart(4, "0") + (dt.getUTCMonth() + 1).toString().padStart(2, "0") + dt.getUTCDate().toString().padStart(2, "0") + "." + dt.getUTCHours().toString().padStart(2, "0") + dt.getUTCMinutes().toString().padStart(2, "0") + dt.getUTCSeconds().toString().padStart(2, "0") + "-" + data.snapshotBuild, getArtefactExtension(artefactId));
if (artefactId == "GIT_SOURCE") {
if (data.commitId) return utils.format("https://github.com/PowerNukkit/PowerNukkit/tree/%s", data.commitId);
else if (data.snapshotBuild && data.artefacts.includes("SHADED_SOURCES_JAR")) return buildSnapshotArtefactUrl(data, "SHADED_SOURCES_JAR");
else if (data.snapshotBuild && data.artefacts.includes("REDUCED_SOURCES_JAR")) return buildSnapshotArtefactUrl(data, "REDUCED_SOURCES_JAR");
else if (data.artefacts.includes("SHADED_SOURCES_JAR")) return buildReleaseArtefactUrl(data, "SHADED_SOURCES_JAR");
else if (data.artefacts.includes("REDUCED_SOURCES_JAR")) return buildReleaseArtefactUrl(data, "REDUCED_SOURCES_JAR");
} else if (data.snapshotBuild) return buildSnapshotArtefactUrl(data, artefactId);
else return buildReleaseArtefactUrl(data, artefactId);
return null;
}
const artefacts = data.artefacts.reduce((acc, artefactId) => {acc[artefactId] = buildArtefactUrl(data, artefactId); return acc;}, {} as {[key: string]: string});
const verRel = {
version: data.version,
mcpeVersion: data.minecraftVersion,
date: dt,
variantType: (!data.snapshotBuild?"snapshot":"stable") as "stable"|"snapshot",
url: artefacts.SHADED_JAR || artefacts.REDUCED_JAR
};
if (!!verRel.url) acc.push(verRel);
}
return acc;
}, [] as {version: string, mcpeVersion: string, date: Date, variantType: "stable"|"snapshot", url: string}[]).filter(a => !!a.url).sort((b, a) => (b.date.getTime() - a.date.getTime()) - semver.compare(semver.valid(semver.coerce(a.version)), semver.valid(semver.coerce(b.version))));
} else if (options.altServer === "cloudbust") throw new TypeError("O Cloudbust não tem listagem de versöes");
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;
}
export async function installServer(options: bedrockOptions & {version?: string, allowBeta?: boolean}): Promise<{id: string, version: string, mcpeVersion?: string, releaseDate: Date}> {
const serverPath = await serverManeger(options);
if (options.altServer === "pocketmine") {
const version = (options.version ?? "latest").trim();
const rel = (await pocketmineGithub.getRelease(version));
if (!rel) throw new Error("Não foi possivel encontrar a versão informada do Pocketmine!");
const phpFile = (await oracleStorage.listFiles("php_bin")).find(file => file.name.includes(process.platform) && file.name.includes(process.arch));
if (!phpFile) throw new Error(`Não foi possivel encontra os arquivos do php para o ${process.platform} com a arquitetura ${process.arch}`);
if (phpFile.name.endsWith(".tar.gz")) await pipeline(await oracleStorage.getFileStream(phpFile.name), tar.extract({cwd: serverPath.serverFolder}));
else if (phpFile.name.endsWith(".zip")) await pipeline(await oracleStorage.getFileStream(phpFile.name), unzip.Extract({path: serverPath.serverFolder}));
else throw new Error("Arquivo encontrado não é suportado!");
// save phar
await large.saveFile({
url: rel.assets.find(a => a.name.endsWith(".phar"))?.browser_download_url,
path: path.join(serverPath.serverFolder, "server.phar")
});
return {
id: serverPath.id,
version: rel.tag_name,
releaseDate: new Date(rel.published_at)
};
} else if (options.altServer === "powernukkit") {
const version = (options.version ?? "latest").trim();
const releases = await listVersions({altServer: "powernukkit"});
const relVersion = releases.find(rel => {
if (rel.variantType === "snapshot") if (!options.allowBeta) return false;
if (version.toLowerCase() === "latest") return true;
return (rel.version === version || rel.mcpeVersion === version);
});
if (!relVersion) throw new Error("A versão não foi encontrada, por favor verique a versão informada!");
await large.saveFile({
path: path.join(serverPath.serverFolder, "server.jar"),
url: relVersion.url
});
return {
id: serverPath.id,
version: relVersion.version,
mcpeVersion: relVersion.mcpeVersion,
releaseDate: relVersion.date,
};
} else if (options.altServer === "cloudbust") {
await large.saveFile({
url: "https://ci.opencollab.dev/job/NukkitX/job/Server/job/bleeding/lastSuccessfulBuild/artifact/target/Cloudburst.jar",
path: path.join(serverPath.serverFolder, "server.jar")
});
return {
id: serverPath.id,
version: "bleeding",
releaseDate: new Date()
};
}
const bedrockVersion = (await listVersions()).find(rel => {
if (rel.release === "preview" && !!options.allowBeta) return false;
const version = (options.version ?? "latest").trim();
if (version.toLowerCase() === "latest") return true;
return rel.version === version;
});
if (!bedrockVersion) throw new Error("Não existe essa versão");
let downloadUrl = bedrockVersion.url[process.platform]?.[process.arch];
if ((["android", "linux"] as NodeJS.Process["platform"][]).includes(process.platform) && process.arch !== "x64") {
if (!downloadUrl) {
for (const emu of ["qemu-x86_64-static", "qemu-x86_64", "box64"]) {
if (downloadUrl) break;
if (await commandExists(emu)) downloadUrl = bedrockVersion.url.linux?.x64;
}
}
}
if (!downloadUrl) throw new Error(`Não existe o URL de download para ${process.platform} na arquitetura ${process.arch}`);
await pipeline(await coreHttp.streamRequest(downloadUrl), unzip.Extract({path: serverPath.serverFolder}));
return {
id: serverPath.id,
version: bedrockVersion.version,
releaseDate: bedrockVersion.date,
};
}
export async function startServer(options: bedrockOptions) {
const serverPath = await serverManeger(options);
if (options.altServer === "powernukkit"||options.altServer === "cloudbust") {
return serverPath.runCommand({
command: "java",
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",
"-jar", "server.jar",
],
serverActions: {
stop(child) {
child.sendCommand("stop");
},
}
})
} else if (options.altServer === "pocketmine") {
return serverPath.runCommand({
command: (await extendsFS.readdir(serverPath.serverFolder)).find(file => file.endsWith("php")||file.endsWith("php.exe")),
args: [
"server.jar",
"--no-wizard"
]
});
}
if (process.platform === "darwin") throw new Error("Run in docker or podman!");
const run: Omit<runOptions, "cwd"> = {
command: path.join(serverPath.serverFolder, "bedrock_server"),
serverActions: {
stop(child) {
child.sendCommand("stop");
},
}
};
if ((["android", "linux"] as NodeJS.Process["platform"][]).includes(process.platform) && process.arch !== "x64") {
for (const emu of ["qemu-x86_64-static", "qemu-x86_64", "box64"]) {
if (await commandExists(emu)) {
run.args = [emu, run.command];
run.command = emu;
break;
}
}
}
return serverPath.runCommand(run);
}

@ -0,0 +1,88 @@
import { manegerOptions, serverManeger } from "../serverManeger.js";
import coreHttp, { large } from "@sirherobrine23/http";
import utils from "node:util";
import path from "node:path";
export type javaOptions = manegerOptions & {
/**
* Servidor alternativo ao invés do servidor ofical da Mojang
*/
altServer?: "spigot"|"paper"|"purpur"
};
export async function listVersions(options: Omit<javaOptions, keyof manegerOptions>) {
if (options.altServer === "purpur") {
return Promise.all((await coreHttp.jsonRequest<{versions: string[]}>("https://api.purpurmc.org/v2/purpur")).body.versions.map(async version => ({
version,
downloadUrl: utils.format("https://api.purpurmc.org/v2/purpur/%s/latest/download", version),
date: new Date((await coreHttp.jsonRequest<{timestamp: number}>(utils.format("https://api.purpurmc.org/v2/purpur/%s/latest", version))).body.timestamp)
})));
} else if (options.altServer === "paper") {
return Promise.all((await coreHttp.jsonRequest<{versions: string[]}>("https://api.papermc.io/v2/projects/paper")).body.versions.map(async version => {
const build = (await coreHttp.jsonRequest<{builds: number[]}>(utils.format("https://api.papermc.io/v2/projects/paper/versions/%s", version))).body.builds.at(-1);
const data = (await coreHttp.jsonRequest<{time: string, downloads: {[k: string]: {name: string, sha256: string}}}>(utils.format("https://api.papermc.io/v2/projects/paper/versions/%s/builds/%s", version, build))).body;
return {
version,
date: new Date(data.time),
downloadUrl: utils.format("https://api.papermc.io/v2/projects/paper/versions/%s/builds/%s/downloads/%s", version, build, data.downloads["application"].name)
}
}));
} else if (options.altServer === "spigot") {
throw new Error("Não foi implementado!");
}
return (await Promise.all((await coreHttp.jsonRequest<{versions: {id: string, releaseTime: string, url: string}[]}>("https://launchermeta.mojang.com/mc/game/version_manifest_v2.json")).body.versions.map(async data => ({
version: data.id,
date: new Date(data.releaseTime),
downloadUrl: (await coreHttp.jsonRequest<{downloads: {[k: string]: {size: number, url: string}}}>(data.url)).body.downloads?.["server"]?.url
})))).filter(a => !!a.downloadUrl);
}
export async function installServer(options: javaOptions & {version?: string}) {
const serverPath = await serverManeger(options);
const version = (await listVersions(options)).find(rel => (!options.version || options.version === "latest" || rel.version === options.version));
if (!version) throw new Error("Não existe a versão informada!");
await large.saveFile({
path: path.join(serverPath.serverFolder, "server.jar"),
url: version.downloadUrl
});
return {
...version,
id: serverPath.id,
};
}
export async function startServer(options: javaOptions) {
const serverPath = await serverManeger(options);
return serverPath.runCommand({
command: "java",
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",
"-jar", "server.jar",
],
serverActions: {
stop(child) {
child.sendCommand("stop");
},
}
});
}

@ -0,0 +1,27 @@
{
"compilerOptions": {
"esModuleInterop": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ESNext",
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": false,
"noUnusedLocals": true,
"isolatedModules": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"allowJs": true,
"lib": [
"ESNext"
],
"composite": true
},
"exclude": [
"**/*.test.*"
],
"ts-node": {
"esm": true
}
}

@ -0,0 +1,17 @@
{
"name": "@the-bds-maneger/docker",
"private": true,
"version": "1.0.0",
"description": "",
"type": "module",
"bin": {
"bdsdocker": "./src/index.js"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Matheus Sampaio Queiroga <srherobrine20@gmail.com>",
"license": "ISC"
}

@ -0,0 +1,31 @@
{
"compilerOptions": {
"esModuleInterop": true,
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ESNext",
"forceConsistentCasingInFileNames": true,
"declaration": true,
"strict": false,
"noUnusedLocals": true,
"isolatedModules": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"allowJs": true,
"lib": [
"ESNext"
]
},
"exclude": [
"**/*.test.*"
],
"ts-node": {
"esm": true
},
"references": [
{
"path": "../core"
}
]
}

@ -1,3 +0,0 @@
export * as serverManeger from "./serverManeger.js";
export * as Bedrock from "./platform/Bedrock.js";
export * as Java from "./platform/Java.js";

@ -1,43 +0,0 @@
export default {parse, stringify};
export type properitiesBase = {[key: string]: string|number|true|false};
/**
* Parse Proprieties files and return a map of properties.
*
* @param Proper - String with the properties or similar files
* @returns
*/
export function parse<PropertiesObject extends properitiesBase>(Proper: string): PropertiesObject {
const ProPri = {};
const ProperSplit = Proper.replace(/\\\s+?\n/gi, "").split(/\r?\n/).map(Line => Line.trim()).filter(line => /.*(\s+)?\=(\s+)?.*/.test(line) && !/^#/.test(line));
for (const Line of ProperSplit) {
const LineMatch = Line.match(/^([^\s\=]+)\s*\=(.*)$/);
const key = LineMatch[1].trim(), value = LineMatch[2].trim();
ProPri[key] = value;
if (ProPri[key] === "true") ProPri[key] = true;
else if (ProPri[key] === "false") ProPri[key] = false;
else if (/^[0-9]+\.[0-9]+/.test(ProPri[key]) && !/^[0-9]+\.[0-9]+\.[0-9]+/.test(ProPri[key])) ProPri[key] = parseFloat(ProPri[key]);
else if (/^[0-9]+/.test(ProPri[key])) ProPri[key] = parseInt(ProPri[key]);
}
return ProPri as PropertiesObject;
}
/**
* Convert json to properities files.
*
* @param ProPri - String with properties file
* @returns
*/
export function stringify(ProPri: properitiesBase): string {
const Proper = [];
for (const key of Object.keys(ProPri)) {
if (ProPri[key] === null||ProPri[key] === undefined) Proper.push(`${key}=`);
else if (ProPri[key] === true) Proper.push(`${key}=true`);
else if (ProPri[key] === false) Proper.push(`${key}=false`);
else if (typeof ProPri[key] === "number") Proper.push(`${key}=${ProPri[key]}`);
else if (typeof ProPri[key] === "string") Proper.push(`${key}=${ProPri[key]}`);
else if (typeof ProPri[key] === "object") Proper.push(`${key}=${JSON.stringify(ProPri[key])}`);
else console.error(`[Proprieties.stringify] ${key} is not a valid type.`);
}
return Proper.join("\n");
}

@ -1,86 +0,0 @@
import dgram from "node:dgram";
import net from "node:net";
export type proxyUdpToTcpOptions = {
udpType?: dgram.SocketType,
listen?: number,
portListen?: (port: number) => void
};
export type proxyTcpToUdpClient = {
udpType?: dgram.SocketType,
listen?: number,
remote: {
host: string,
port: number
},
};
/**
* Transfer packets from UDP to TCP to send through some tunnel that only accepts TCP
*
* This also means that it will also have error transporting the data, so it is not guaranteed to work properly even more when dealing with UDP packets.
*/
export function proxyUdpToTcp(udpPort: number, options?: proxyUdpToTcpOptions) {
const tcpServer = net.createServer();
tcpServer.on("error", err => console.error(err));
tcpServer.on("connection", socket => {
const udpClient = dgram.createSocket(options?.udpType||"udp4");
// Close Sockets
udpClient.once("close", () => socket.end());
socket.once("close", () => udpClient.close());
// Print error
udpClient.on("error", console.error);
socket.on("error", console.error);
// Pipe Datas
udpClient.on("message", data => socket.write(data));
socket.on("data", data => udpClient.send(data));
// Connect
udpClient.connect(udpPort);
});
// Listen
tcpServer.listen(options?.listen||0, function() {
const addr = this.address();
if (options?.portListen) options.portListen(addr.port);
console.debug("bds proxy port listen, %s, (udp -> tcp)", addr.port);
tcpServer.once("close", () => console.debug("bds proxy close, %s", addr.port));
});
return tcpServer;
}
export function proxyTcpToUdp(options: proxyTcpToUdpClient) {
const sessions: {[keyIP: string]: net.Socket} = {};
const udp = dgram.createSocket(options?.udpType||"udp4");
udp.on("error", console.error);
udp.on("message", (msg, ipInfo) => {
const keyInfo = `${ipInfo.address}:${ipInfo.port}`;
// Client TCP
if (!sessions[keyInfo]) {
sessions[keyInfo] = net.createConnection(options.remote);
sessions[keyInfo].on("data", data => udp.send(data, ipInfo.port, ipInfo.address));
sessions[keyInfo].on("error", console.error);
sessions[keyInfo].once("close", () => {
delete sessions[keyInfo];
console.log("Client %s:%f close", ipInfo.address, ipInfo.port);
});
console.log("Client %s:%f connected", ipInfo.address, ipInfo.port);
}
// Send message
sessions[keyInfo].write(msg);
});
// Listen port
udp.bind(options.listen||0, function(){
const addr = this.address();
console.log("Port listen, %s (tcp -> udp)", addr.port);
});
}

@ -1,11 +0,0 @@
import net from "net"
export async function randomPort(): Promise<number> {
return new Promise((res, rej) => {
const srv = net.createServer();
srv.listen(0, () => {
const address = srv.address();
if (typeof address === "string") return rej(new Error("Invalid listen port"));
srv.close((_err) => res(address.port));
});
});
}

@ -1,252 +0,0 @@
import { createServerManeger, platformPathID, pathOptions, serverConfig } from "../serverManeger.js";
import { promises as fs, createWriteStream } from "node:fs";
import { oracleBucket } from "../lib/remote.js";
import { promisify } from "node:util";
import { pipeline } from "node:stream/promises";
import * as childPromisses from "../lib/childPromisses.js";
import coreUtils from "@sirherobrine23/coreutils";
import AdmZip from "adm-zip";
import path from "node:path";
import tar from "tar";
export type bedrockRootOption = pathOptions & {
variant?: "oficial"|"Pocketmine-PMMP"|"Powernukkit"|"Cloudbust"
};
export const hostArchEmulate = Object.freeze([
"qemu-x86_64-static",
"qemu-x86_64",
"box64"
]);
type bedrockVersionJSON = {
version: string,
date: Date,
release?: "stable"|"preview",
url: {
[platform in NodeJS.Platform]?: {
[arch in NodeJS.Architecture]?: string
}
}
};
async function getPHPBin(options?: bedrockRootOption) {
options = {variant: "oficial", ...options};
const serverPath = await platformPathID("bedrock", options);
const binFolder = path.join(serverPath.serverPath, "bin");
const files = await coreUtils.Extends.readdir({folderPath: binFolder});
const file = files.find((v) => v.endsWith("php.exe")||v.endsWith("php"));
if (!file) throw new Error("PHP Bin not found");
return file;
}
export async function installServer(version?: string, options?: bedrockRootOption) {
options = {variant: "oficial", ...options};
const serverPath = await platformPathID("bedrock", options);
if (options?.variant === "Pocketmine-PMMP") {
if (!version) version = "latest";
const phpBin = ((await oracleBucket.listFiles()) as any[]).filter(({name}) => name.includes("php_bin/")).filter(({name}) => name.includes(process.platform) && name.includes(process.arch)).at(0);
if (!phpBin) throw new Error("PHP Bin not found");
const binFolder = path.join(serverPath.serverPath, "bin");
if (await coreUtils.Extends.exists(binFolder)) await fs.rm(binFolder, {recursive: true});
await fs.mkdir(binFolder);
await pipeline(await oracleBucket.getFileStream(phpBin.name), createWriteStream(path.join(binFolder, "phpTmp")));
if (phpBin.name.endsWith(".tar.gz")) {
await tar.extract({
file: path.join(binFolder, "phpTmp"),
cwd: binFolder
});
} else if (phpBin.name.endsWith(".zip")) {
await promisify((new AdmZip(path.join(binFolder, "phpTmp"))).extractAllToAsync)(binFolder, true, true);
}
await fs.rm(path.join(binFolder, "phpTmp"));
const rel = await (await coreUtils.http.Github.GithubManeger("pmmp", "PocketMine-MP")).getRelease();
const relData = version.trim().toLowerCase() === "latest" ? rel.at(0) : rel.find((v) => v.tag_name === version.trim());
if (!relData) throw new Error("Version not found");
const phpAsset = relData.assets.find((a) => a.name.endsWith(".phar"))?.browser_download_url;
if (!phpAsset) throw new Error("PHP asset not found");
await coreUtils.http.large.saveFile({url: phpAsset, path: path.join(serverPath.serverPath, "PocketMine-MP.phar")});
return {
version: relData.tag_name,
releaseDate: new Date(relData.published_at),
release: (relData.prerelease ? "preview" : "stable") as "preview"|"stable",
url: phpAsset,
phpBin: phpBin.name,
};
} else if (options?.variant === "Powernukkit") {
if (!version) version = "latest";
const versions = await coreUtils.http.jsonRequest<{version: string, mcpeVersion: string, date: string, url: string, variantType: "snapshot"|"stable"}[]>("https://mcpeversion-static.sirherobrine23.org/powernukkit/all.json").then(data => data.body);
const versionData = version.trim().toLowerCase() === "latest" ? versions.at(-1) : versions.find((v) => v.version === version.trim() || v.mcpeVersion === version.trim());
if (!versionData) throw new Error("Version not found");
const url = versionData.url;
if (!url) throw new Error("Platform not supported");
await coreUtils.http.large.saveFile({url, path: path.join(serverPath.serverPath, "server.jar")});
return {
version: versionData.version,
mcpeVersion: versionData.mcpeVersion,
variantType: versionData.variantType,
releaseDate: new Date(versionData.date),
url,
};
} else if (options?.variant === "Cloudbust") {
await coreUtils.http.large.saveFile({
url: "https://ci.opencollab.dev/job/NukkitX/job/Server/job/bleeding/lastSuccessfulBuild/artifact/target/Cloudburst.jar",
path: path.join(serverPath.serverPath, "server.jar")
});
return {
version: "bleeding",
releaseDate: new Date(),
release: "preview",
url: "https://ci.opencollab.dev/job/NukkitX/job/Server/job/bleeding/lastSuccessfulBuild/artifact/target/Cloudburst.jar",
};
} else {
if (!version) version = "latest";
const versions = await coreUtils.http.jsonRequest<bedrockVersionJSON[]>("https://sirherobrine23.github.io/BedrockFetch/all.json").then(data => data.body);
const versionData = version.trim().toLowerCase() === "latest" ? versions.at(-1) : versions.find((v) => v.version === version.trim());
if (!versionData) throw new Error("Version not found");
let currentPlatform = process.platform;
if (currentPlatform === "android") currentPlatform = "linux";
const url = versionData.url[currentPlatform]?.[process.arch];
if (!url) throw new Error("Platform not supported");
(await coreUtils.http.large.admZip(url)).zip.extractAllTo(serverPath.serverPath, true, true);
return {
version: versionData.version,
releaseDate: new Date(versionData.date),
release: versionData.release ?? "stable",
url: url
}
}
}
export async function startServer(options?: bedrockRootOption) {
// Bad fix options
options = {variant: "oficial", ...options};
const serverPath = await platformPathID("bedrock", options);
// Server Object
const serverExec: serverConfig = {
exec: {
cwd: serverPath.serverPath,
},
actions: {}
};
if (options?.variant === "Pocketmine-PMMP") {
serverExec.exec.exec = await getPHPBin();
serverExec.exec.args = ["PocketMine-MP.phar", "--no-wizard"];
serverExec.actions = {
stopServer(child_process) {
child_process.stdin.write("stop\n");
},
onStart(lineData, fnRegister) {
if (!(lineData.includes("INFO") && lineData.includes("Done") && lineData.includes("help"))) return;
const doneStart = new Date();
fnRegister({
serverAvaible: doneStart,
bootUp: runStart.getTime() - doneStart.getTime()
});
},
};
} else if (options?.variant === "Powernukkit" || options?.variant === "Cloudbust") {
serverExec.exec.exec = "java";
serverExec.exec.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",
"-jar", "server.jar"
];
serverExec.actions = {
stopServer(child_process) {
child_process.stdin.write("stop\n");
},
onStart(lineData, fnRegister) {
if (!(lineData.includes("INFO") && lineData.includes("Done") && lineData.includes("help"))) return;
const doneStart = new Date();
fnRegister({
serverAvaible: doneStart,
bootUp: runStart.getTime() - doneStart.getTime()
});
},
};
} else {
if (process.platform === "win32") serverExec.exec.exec = "bedrock_server.exe";
else if (process.platform === "darwin") throw new Error("MacOS is not supported, run in Docker or Virtual Machine");
else {
serverExec.exec.exec = path.join(serverPath.serverPath, "bedrock_server");
serverExec.exec.env = {
LD_LIBRARY_PATH: serverPath.serverPath
};
}
if ((["android", "linux"]).includes(process.platform) && process.arch !== "x64") {
const exec = serverExec.exec.exec;
serverExec.exec.exec = undefined;
for (const command of hostArchEmulate) {
if (await childPromisses.commandExists(command, true)) {
serverExec.exec.args = [exec];
serverExec.exec.exec = command;
break;
}
if (!serverExec.exec.exec) throw new Error("No emulator found for this platform");
}
}
const startTest = /\[.*\]\s+Server\s+started\./;
// Server actions
serverExec.actions = {
stopServer(child_process) {
child_process.stdin.write("stop\n");
},
onStart(lineData, fnRegister) {if (startTest.test(lineData)) fnRegister({serverAvaible: new Date()});},
playerActions(lineData, fnRegister) {
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 connectTime = new Date();
if (!(newPlayerActions.test(lineData)||playerActionsV1.test(lineData))) return;
let playerName: string, action: string, xuid: string;
if (newPlayerActions.test(lineData)) {
const [, actionV2,, playerNameV2,, xuidV2] = lineData.match(newPlayerActions);
playerName = playerNameV2;
action = actionV2;
xuid = xuidV2;
} else {
const [, actionV1,, playerNameV1, xuidV1] = lineData.match(newPlayerActions);
playerName = playerNameV1;
action = actionV1;
xuid = xuidV1;
}
fnRegister({
player: playerName,
action: action === "Spawned" ? "spawned" : action === "connected" ? "join" : "leave",
actionDate: connectTime,
sessionID: serverPath.id,
more: {
xuid
}
});
},
};
}
const runStart = new Date();
return createServerManeger(serverExec);
}

@ -1,16 +0,0 @@
import { pathOptions } from "../serverManeger.js";
export type javaRootOption = pathOptions & {
variant?: "oficial"|"Spigot"|"Paper"|"Purpur",
};
export async function installServer(options?: javaRootOption) {
options = {variant: "oficial", ...options};
if (options?.variant === "Spigot") {
} else if (options?.variant === "Paper") {
} else if (options?.variant === "Purpur") {
} else {}
}
export default startServer;
export async function startServer(options?: javaRootOption) {}

@ -1,216 +0,0 @@
import { createInterface as readline } from "node:readline";
import { promises as fs } from "node:fs";
import child_process from "node:child_process";
import { Cloud, Extends as extendFs } from "@sirherobrine23/coreutils";
import crypto from "node:crypto";
import path from "node:path";
import os from "node:os";
import EventEmitter from "node:events";
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 playerAction = ({action: "join"|"spawned"|"leave"}|{
action: "kick"|"ban",
reason?: string,
by?: string
}) & {
player: string,
actionDate: Date,
sessionID: string
more?: any,
latestAction?: playerAction
}
export type serverConfig = {
exec: {
exec?: string,
args?: string[],
cwd?: string,
env?: NodeJS.ProcessEnv & {[key: string]: string},
},
actions?: {
stopServer?: (child_process: child_process.ChildProcess) => void,
onStart?: (lineData: string, fnRegister: (data?: {serverAvaible?: Date, bootUp?: number}) => void) => void,
playerActions?: (lineData: string, fnRegister: (data: playerAction) => void) => void,
},
maneger?: {
backup?: {
folderWatch: {local: string, remoteParent?: string}[],
} & ({
cloud: "google",
config: Cloud.googleOptions
}|{
cloud: "oracle_bucket",
config: Cloud.oracleOptions
})
}
};
declare class serverManeger extends EventEmitter {
on(event: "error", fn: (lineLog: any) => void): this;
once(event: "error", fn: (lineLog: any) => void): this;
emit(event: "error", data: any): boolean;
on(event: "log", fn: (lineLog: string) => void): this;
once(event: "log", fn: (lineLog: string) => void): this;
emit(event: "log", data: string): boolean;
on(event: "rawLog", fn: (raw: any) => void): this;
once(event: "rawLog", fn: (raw: any) => void): this;
emit(event: "rawLog", data: any): boolean;
// Player actions
on(event: "playerAction", fn: (playerAction: playerAction) => void): this;
once(event: "playerAction", fn: (playerAction: playerAction) => void): this;
emit(event: "playerAction", data: playerAction): boolean;
// Server started
on(event: "serverStarted", fn: (data: {serverAvaible: Date, bootUp: number}) => void): this;
once(event: "serverStarted", fn: (data: {serverAvaible: Date, bootUp: number}) => void): this;
emit(event: "serverStarted", data: {serverAvaible: Date, bootUp: number}): boolean;
}
export async function createServerManeger(serverOptions: serverConfig): Promise<serverManeger> {
const internalStops: (() => any|void)[] = [];
if (serverOptions?.maneger?.backup) {
const { folderWatch, cloud } = serverOptions?.maneger?.backup;
if (cloud === "oracle_bucket") {
const { config } = serverOptions?.maneger?.backup;
const ociClient = await Cloud.oracleBucket(config);
for await (const folder of folderWatch) {
ociClient;
folder;
}
}
}
const serverExec = child_process.execFile(serverOptions.exec.exec, serverOptions.exec.args ?? [], {
cwd: serverOptions.exec.cwd,
windowsHide: true,
maxBuffer: Infinity,
env: {
...process.env,
...serverOptions.exec.env
},
});
const playerActions: playerAction[] = [];
const internalEvent = new class serverManeger extends EventEmitter {
async stopServer() {
const stopServer = serverOptions.actions?.stopServer ?? ((child_process) => child_process.kill("SIGKILL"));
await Promise.resolve(stopServer(serverExec)).catch(err => internalEvent.emit("error", err));
internalStops.forEach((fn) => Promise.resolve().then(() => fn()).catch(err => internalEvent.emit("error", err)));
}
getPlayers() {
return playerActions ?? [];
}
};
serverExec.on("error", internalEvent.emit.bind(internalEvent, "error"));
const stdoutReadline = readline({input: serverExec.stdout});
stdoutReadline.on("line", (line) => internalEvent.emit("log", line));
stdoutReadline.on("error", internalEvent.emit.bind(internalEvent, "error"));
serverExec.stdout.on("data", (data) => internalEvent.emit("rawLog", data));
const stderrReadline = readline({input: serverExec.stderr});
stderrReadline.on("line", (line) => internalEvent.emit("log", line));
stderrReadline.on("error", internalEvent.emit.bind(internalEvent, "error"));
serverExec.stderr.on("data", (data) => internalEvent.emit("rawLog", data));
// Server start
if (serverOptions.actions?.onStart) {
const serverStartFN = serverOptions.actions.onStart;
let lock = false;
const started = new Date();
async function register(data?: {serverAvaible?: Date, bootUp?: number}) {
if (lock) return;
const eventData = {
serverAvaible: data?.serverAvaible ?? new Date(),
bootUp: data?.bootUp ?? new Date().getTime() - started.getTime()
};
internalEvent.emit("serverStarted", eventData);
lock = true;
stderrReadline.removeListener("line", register);
stdoutReadline.removeListener("line", register);
// emit and remove new listener for serverStarted
internalEvent.removeAllListeners("serverStarted");
internalEvent.prependListener("serverStarted", () => {
internalEvent.emit("serverStarted", eventData);
internalEvent.removeAllListeners("serverStarted");
});
}
stdoutReadline.on("line", (line) => serverStartFN(line, register));
stderrReadline.on("line", (line) => serverStartFN(line, register));
}
// Player actions
if (serverOptions.actions?.playerActions) {
const playerFn = serverOptions.actions.playerActions;
const registerData = (data: playerAction) => {
const player = playerActions.find((player) => player.player === data.player);
if (!player) playerActions.push(data);
else {
data.latestAction = player;
playerActions[playerActions.indexOf(player)] = data;
}
internalEvent.emit("playerAction", data);
}
stdoutReadline.on("line", (line) => playerFn(line, registerData));
stderrReadline.on("line", (line) => playerFn(line, registerData));
}
return internalEvent;
}

@ -4,22 +4,21 @@
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"target": "ESNext", "target": "ESNext",
"forceConsistentCasingInFileNames": true,
"declaration": true, "declaration": true,
"strict": false, "strict": false,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"isolatedModules": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"skipLibCheck": true, "skipLibCheck": true,
"allowJs": true, "allowJs": true,
"lib": ["ES6"] "lib": [
"ESNext"
]
}, },
"include": [
"src/**/*"
],
"exclude": [ "exclude": [
"src/**/*.test.ts", "**/*.test.ts"
"node_modules/**/*"
], ],
"ts-node": { "ts-node": {
"esm": true "esm": true