--------------------------------------------------------------------------------
/tools/prerelease-checks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CURDIR=$(dirname "$(readlink -f "$0")")
4 | BASEDIR=$(readlink -f "$CURDIR/..")
5 |
6 | . ${BASEDIR}/tools/variables.sh
7 | . ${BASEDIR}/tools/app_config.txt
8 |
9 | VERSION=""
10 |
11 | while [[ $# -gt 0 ]]; do
12 | key="$1"
13 |
14 | case $key in
15 | --version)
16 | VERSION="$2"
17 | shift # past value
18 | shift # past value
19 | ;;
20 |
21 | esac
22 | done
23 |
24 | release_dir="${BASEDIR}/release-builds"
25 |
26 | release_dir_linux="${release_dir}/linux"
27 | release_dir_win64="${release_dir}/win64"
28 |
29 | if [ ! -f "${release_dir_linux}/SatisfactoryServerManager" ]; then
30 | echo "Error: Linux Executable not created!"
31 | exit 1
32 | fi
33 |
34 | if [ ! -f "${release_dir_win64}/SatisfactoryServerManager.exe" ]; then
35 | echo "Error: Windows Executable not created!"
36 | exit 1
37 | fi
38 |
39 | ZipLinuxFileName="${release_dir}/SSM-Linux-x64-${VERSION}.tar.gz"
40 | ZipWin64FileName="${release_dir}/SSM-Win-x64-${VERSION}.zip"
41 |
42 | if [ ! -f "${ZipLinuxFileName}" ]; then
43 | echo "Error: Linux Tar not created!"
44 | exit 1
45 | fi
46 |
47 | if [ ! -f "${ZipWin64FileName}" ]; then
48 | echo "Error: Windows Zip not created!"
49 | exit 1
50 | fi
51 |
52 | exit 0
53 |
--------------------------------------------------------------------------------
/objects/obj_user_role.js:
--------------------------------------------------------------------------------
1 | class ObjUserRole {
2 | constructor() {
3 | this._id = -1;
4 | this._name = "";
5 | this._permissions = [];
6 | }
7 |
8 | /**
9 | *
10 | * @param {Object} data SQL data
11 | */
12 | parseDBData(data) {
13 | this._id = data.role_id;
14 | this._name = data.role_name;
15 | }
16 | /**
17 | *
18 | * @returns {Number} - Role ID
19 | */
20 | getId() {
21 | return this._id;
22 | }
23 |
24 | /**
25 | *
26 | * @returns {String} - Role Name
27 | */
28 | getName() {
29 | return this._name;
30 | }
31 |
32 | /**
33 | *
34 | * @param {Array} Permissions
35 | */
36 | SetPermissions(Permissions) {
37 | this._permissions = Permissions;
38 | }
39 |
40 | /**
41 | *
42 | * @param {String} RequiredPermission
43 | * @returns {Boolean} - Has Permissions Requested
44 | */
45 |
46 | HasPermission(RequiredPermission) {
47 | return this._permissions.includes(RequiredPermission);
48 | }
49 |
50 | getWebJson() {
51 | return {
52 | id: this.getId(),
53 | name: this.getName(),
54 | permissions: this._permissions,
55 | };
56 | }
57 | }
58 |
59 | module.exports = ObjUserRole;
60 |
--------------------------------------------------------------------------------
/server/server_cloud.js:
--------------------------------------------------------------------------------
1 | const axios = require("axios").default;
2 |
3 | class SSMCloud {
4 | constructor(opts) {
5 | const default_options = {
6 | github: {
7 | user: "mrhid6",
8 | repo: "satisfactoryservermanager",
9 | },
10 | };
11 |
12 | this._options = Object.assign({}, default_options, opts);
13 | this._GitHub_URL = "https://api.github.com";
14 | }
15 |
16 | getGithubLatestRelease() {
17 | return new Promise((resolve, reject) => {
18 | let url = this._GitHub_URL + "/repos/";
19 | url += this._options.github.user + "/";
20 | url += this._options.github.repo + "/";
21 | url += "releases/latest";
22 |
23 | axios
24 | .get(url, {
25 | timeout: 5000,
26 | })
27 | .then((res) => {
28 | if (res.data == null || typeof res.data == "undefined") {
29 | reject("Cant find latest release data!");
30 | return;
31 | }
32 | resolve(res.data);
33 | })
34 | .catch((err) => {
35 | reject(err);
36 | });
37 | });
38 | }
39 | }
40 |
41 | const ssmCloud = new SSMCloud();
42 | module.exports = ssmCloud;
43 |
--------------------------------------------------------------------------------
/tools/package/ci_deploy_release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | VERSION=$(cat package.json | grep version | awk '{print $2}' | sed -e 's/"//g' | sed -e 's/,//g')
4 |
5 | CURDIR=$(dirname "$(readlink -f "$0")")
6 | BASEDIR=$(readlink -f "$CURDIR/../..")
7 |
8 | release_dir="${BASEDIR}/release-builds"
9 | release_dir_win64="${release_dir}/win64"
10 | release_dir_linux="${release_dir}/linux"
11 |
12 | ZipWin64FileName="SSM-Win-x64-${VERSION}.zip"
13 | ZipWin64FilePath="${release_dir}/${ZipWin64FileName}"
14 |
15 | ZipLinuxFileName="SSM-Linux-x64-${VERSION}.tar.gz"
16 | ZipLinuxFilePath="${release_dir}/${ZipLinuxFileName}"
17 |
18 |
19 | PACKAGE_REGISTRY_URL="https://git.hostxtra.co.uk/api/v4/projects/${CI_PROJECT_ID}/packages/generic/ssm/${VERSION}"
20 |
21 | echo "$PACKAGE_REGISTRY_URL"
22 |
23 | curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${ZipWin64FilePath} ${PACKAGE_REGISTRY_URL}/${ZipWin64FileName}
24 | curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file ${ZipLinuxFilePath} ${PACKAGE_REGISTRY_URL}/${ZipLinuxFileName}
25 |
26 | release-cli --server-url "https://git.hostxtra.co.uk" --project-id ${CI_PROJECT_ID} --job-token ${CI_JOB_TOKEN} create --name "Release $VERSION" --tag-name $VERSION --assets-link "{\"name\":\"${ZipWin64FileName}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${ZipWin64FileName}\"}" --assets-link "{\"name\":\"${ZipLinuxFileName}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${ZipLinuxFileName}\"}"
27 |
--------------------------------------------------------------------------------
/public/modals/add-role-modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
20 |
21 |
22 |
23 |
24 |
25 | Add
26 | Role
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/objects/obj_webhook_notification_interface.js:
--------------------------------------------------------------------------------
1 | const NotificationInterface = require("./obj_notification_interface");
2 | const Logger = require("../server/server_logger");
3 |
4 | const axios = require("axios").default;
5 |
6 | class WebhookNotificationInterface extends NotificationInterface {
7 | constructor() {
8 | super();
9 | }
10 |
11 | init(data) {
12 | super.init(data);
13 | }
14 |
15 | PostData = async (data) => {
16 | const url = this.getURL();
17 |
18 | try {
19 | const res = await axios.post(url, data);
20 | return res;
21 | } catch (err) {
22 | throw new Error(err.message);
23 | }
24 | };
25 |
26 | TriggerSSMEvent = async (Notification) => {
27 | try {
28 | const res = await this.PostData(Notification.GetData());
29 | return res;
30 | } catch (err) {
31 | throw err;
32 | }
33 | };
34 |
35 | TriggerAgentEvent = async (Notification) => {
36 | try {
37 | const res = await this.PostData(Notification.GetData());
38 | return res;
39 | } catch (err) {
40 | throw err;
41 | }
42 | };
43 |
44 | TriggerServerEvent = async (Notification) => {
45 | try {
46 | const res = await this.PostData(Notification.GetData());
47 | return res;
48 | } catch (err) {
49 | throw err;
50 | }
51 | };
52 | }
53 |
54 | module.exports = WebhookNotificationInterface;
55 |
--------------------------------------------------------------------------------
/tools/update_SSM_exe.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CURDIR=$(dirname "$(readlink -f "$0")")
4 | BASEDIR=$(readlink -f "$CURDIR/..")
5 |
6 | . ${BASEDIR}/tools/variables.sh
7 |
8 | VERSION=""
9 |
10 | while [[ $# -gt 0 ]]; do
11 | key="$1"
12 |
13 | case $key in
14 | --version)
15 | VERSION="$2"
16 | shift # past value
17 | shift # past value
18 | ;;
19 |
20 | esac
21 | done
22 |
23 | if [ "${VERSION}" == "" ]; then
24 | echo "Error: version can't be null!"
25 | exit 1
26 | fi
27 |
28 | if [ $(which -a rcedit | wc -l) -eq 0 ]; then
29 | echo "Error: Can't find rcedit!"
30 | exit 1
31 | fi
32 |
33 | VERSION_NUM=$(echo $VERSION | sed -e 's/v//g')
34 |
35 | PKG_CACHE="${HOME}/.pkg-cache"
36 | PKG_CACHE_VER=$(ls ${PKG_CACHE} | sort | tail -1)
37 |
38 | PKG_CACHE_DIR="${PKG_CACHE}/${PKG_CACHE_VER}"
39 |
40 | PKG_EXE=$(find ${PKG_CACHE_DIR} -name "*win-x64" | sort | head -1)
41 |
42 | rcedit ${PKG_EXE} \
43 | --set-file-version "${VERSION_NUM}" \
44 | --set-product-version "${VERSION_NUM}" \
45 | --set-version-string "CompanyName" "SSM" \
46 | --set-version-string "ProductName" "SatisfactoryServerManager" \
47 | --set-version-string "FileDescription" "SatisfactoryServerManager (SSM)" \
48 | --set-version-string "OriginalFilename" "SatisfactoryServerManager.exe" \
49 | --set-version-string "InternalName" "SatisfactoryServerManager" \
50 | --set-version-string "LegalCopyright" "Copyright MrHid6. MIT License" \
51 | --set-icon "${BASEDIR}/public/images/ssm_logo256.ico"
52 |
53 | exit 0
54 |
--------------------------------------------------------------------------------
/views/users.hbs:
--------------------------------------------------------------------------------
1 |
2 |
Users
3 |
4 |
9 |
10 |
11 |
12 |
13 | User ID
14 | User Name
15 | Role
16 | Options
17 |
18 |
19 |
20 |
21 |
22 |
23 |
27 |
28 |
29 |
30 |
31 | Role ID
32 | Role Name
33 | Permissions
34 | Options
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/routes/api/ficsitinfo.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var router = express.Router();
3 |
4 | const ServerApp = require("../../server/server_app");
5 | const SSM_Mod_Handler = require("../../server/ms_agent/server_new_mod_handler");
6 |
7 | const middleWare = [
8 | ServerApp.checkLoggedInAPIMiddleWare,
9 | ServerApp.checkModsEnabledAPIMiddleWare,
10 | ];
11 |
12 | router.get("/smlversions", middleWare, function (req, res, next) {
13 | SSM_Mod_Handler.RetrieveSMLVersions()
14 | .then((result) => {
15 | res.json({
16 | result: "success",
17 | data: result,
18 | });
19 | })
20 | .catch((err) => {
21 | res.json({
22 | result: "error",
23 | error: err,
24 | });
25 | });
26 | });
27 |
28 | router.get("/modslist", middleWare, function (req, res, next) {
29 | SSM_Mod_Handler.RetrieveModListFromAPI()
30 | .then((result) => {
31 | res.json({
32 | result: "success",
33 | data: result,
34 | });
35 | })
36 | .catch((err) => {
37 | res.json({
38 | result: "error",
39 | error: err,
40 | });
41 | });
42 | });
43 |
44 | router.get("/modinfo/:modReference", middleWare, function (req, res, next) {
45 | const modReference = req.params.modReference;
46 |
47 | SSM_Mod_Handler.RetrieveModFromAPI(modReference).then((result) => {
48 | res.json({
49 | result: "success",
50 | data: result,
51 | });
52 | });
53 | });
54 |
55 | module.exports = router;
56 |
--------------------------------------------------------------------------------
/routes/api/logs.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var router = express.Router();
3 |
4 | const ServerApp = require("../../server/server_app");
5 | const SSM_Log_Handler = require("../../server/server_log_handler");
6 |
7 | const middleWare = [ServerApp.checkLoggedInAPIMiddleWare];
8 |
9 | router.get("/ssmlog", middleWare, function (req, res, next) {
10 | SSM_Log_Handler.getSSMLog()
11 | .then((result) => {
12 | res.json({
13 | result: "success",
14 | data: result,
15 | });
16 | })
17 | .catch((err) => {
18 | res.json({
19 | result: "error",
20 | error: err,
21 | });
22 | });
23 | });
24 |
25 | router.get("/smlauncherlog", middleWare, function (req, res, next) {
26 | SSM_Log_Handler.getSMLauncherLog()
27 | .then((result) => {
28 | res.json({
29 | result: "success",
30 | data: result,
31 | });
32 | })
33 | .catch((err) => {
34 | res.json({
35 | result: "error",
36 | error: err,
37 | });
38 | });
39 | });
40 |
41 | router.get("/sfserverlog", middleWare, function (req, res, next) {
42 | SSM_Log_Handler.getSFServerLog()
43 | .then((result) => {
44 | res.json({
45 | result: "success",
46 | data: result,
47 | });
48 | })
49 | .catch((err) => {
50 | res.json({
51 | result: "error",
52 | error: err,
53 | });
54 | });
55 | });
56 |
57 | module.exports = router;
58 |
--------------------------------------------------------------------------------
/public/modals/server-action-confirm.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Are you sure you want to do that?
23 |
27 | Cancel
28 |
29 |
33 | Confirm
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | const axios = require("axios").default;
2 | var ua = require("universal-analytics");
3 |
4 | class SSMCloud {
5 | constructor(opts) {
6 | const default_options = {
7 | github: {
8 | user: "",
9 | repo: "",
10 | },
11 | };
12 |
13 | this._options = Object.assign({}, default_options, opts);
14 | this.GitHub_URL = "https://api.github.com";
15 |
16 | this._GA_Property_ID = "UA-156450198-1";
17 | this._visitor = ua(this._GA_Property_ID);
18 | }
19 |
20 | getGithubLatestRelease() {
21 | return new Promise((resolve, reject) => {
22 | let url = this.GitHub_URL + "/repos/";
23 | url += this._options.github.user + "/";
24 | url += this._options.github.repo + "/";
25 | url += "releases/latest";
26 |
27 | axios
28 | .get(url)
29 | .then((res) => {
30 | if (res.data == null || typeof res.data == "undefined") {
31 | reject("Cant find latest release data!");
32 | return;
33 | }
34 | resolve(res.data);
35 | })
36 | .catch((err) => {
37 | reject(err);
38 | });
39 | });
40 | }
41 |
42 | sendGAEvent(category, action, label, value) {
43 | this._visitor.event(category, action, label, value, function (err) {
44 | console.log(err);
45 | });
46 | }
47 | }
48 |
49 | const cloudOptions = {
50 | github: {
51 | user: "mrhid6",
52 | repo: "satisfactoryservermanager",
53 | },
54 | };
55 | const test = new SSMCloud(cloudOptions);
56 |
57 | test.sendGAEvent("SSM Online", "Test", "Test", "1.0.12");
58 |
--------------------------------------------------------------------------------
/public/modals/add-user-modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
20 |
21 |
30 |
31 |
32 |
33 | Add
34 | User
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/views/layouts/login.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Satisfactory Server Manager
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {{{body}}}
35 |
36 |
--------------------------------------------------------------------------------
/public/modals/server-action-installsf.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Install in progress
21 |
31 |
32 | Satisfactory Server is being installed on the
33 | server, you can close this popup and monitor the
34 | Server state on the dashboard.
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/objects/configs/config_engine.js:
--------------------------------------------------------------------------------
1 | const iConfig = require("mrhid6utils").Config;
2 |
3 | class ObjEngineConfig extends iConfig {
4 | constructor(configDir) {
5 | super({
6 | useExactPath: true,
7 | configBaseDirectory: configDir,
8 | configName: "Engine",
9 | configType: "ini",
10 | createConfig: true,
11 | });
12 | }
13 |
14 | setDefaultValues = async () => {
15 | super.set("/Script/Engine.Player.ConfiguredInternetSpeed", 104857600);
16 | super.set("/Script/Engine.Player.ConfiguredLanSpeed", 104857600);
17 |
18 | super.set(
19 | "/Script/OnlineSubsystemUtils.IpNetDriver.NetServerMaxTickRate",
20 | 120
21 | );
22 | super.set(
23 | "/Script/OnlineSubsystemUtils.IpNetDriver.MaxNetTickRate",
24 | 400
25 | );
26 | super.set(
27 | "/Script/OnlineSubsystemUtils.IpNetDriver.MaxInternetClientRate",
28 | 104857600
29 | );
30 | super.set(
31 | "/Script/OnlineSubsystemUtils.IpNetDriver.MaxClientRate",
32 | 104857600
33 | );
34 | super.set(
35 | "/Script/OnlineSubsystemUtils.IpNetDriver.LanServerMaxTickRate",
36 | 120
37 | );
38 | super.set(
39 | "/Script/OnlineSubsystemUtils.IpNetDriver.InitialConnectTimeout",
40 | 300
41 | );
42 | super.set(
43 | "/Script/OnlineSubsystemUtils.IpNetDriver.ConnectionTimeout",
44 | 300
45 | );
46 |
47 | super.set(
48 | "/Script/SocketSubsystemEpic.EpicNetDriver.MaxClientRate",
49 | 104857600
50 | );
51 | super.set(
52 | "/Script/SocketSubsystemEpic.EpicNetDriver.MaxInternetClientRate",
53 | 104857600
54 | );
55 | };
56 | }
57 |
58 | module.exports = ObjEngineConfig;
59 |
--------------------------------------------------------------------------------
/public/modals/server-session-new.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 | Session Name:
15 |
16 | New name of the session.
17 |
18 |
19 |
20 |
26 |
27 |
28 |
29 |
30 |
34 | Cancel
35 |
36 |
40 | Confirm
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/tools/package/ci_compile_linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function printDots() {
4 | text=$1
5 | length=$2
6 | textlen=${#text}
7 |
8 | newlength=$((length - textlen - 1))
9 |
10 | v=$(printf "%-${newlength}s" ".")
11 | echo -en "${text} ${v// /.} "
12 | }
13 |
14 | CURDIR=$(dirname "$(readlink -f "$0")")
15 | BASEDIR=$(readlink -f "$CURDIR/../..")
16 |
17 | VERSION=$(cat package.json | grep version | awk '{print $2}' | sed -e 's/"//g' | sed -e 's/,//g')
18 |
19 | echo -en "\nPackaging Linux (Version: \e[34m${VERSION}\e[0m)\n"
20 |
21 | release_dir="${BASEDIR}/release-builds"
22 | release_dir_linux="${release_dir}/linux"
23 |
24 | rm -rf ${release_dir_linux}\* 2>&1 >/dev/null
25 |
26 | if [ ! -d "${release_dir_linux}" ]; then
27 | mkdir -p "${release_dir_linux}"
28 | fi
29 |
30 | cd ${BASEDIR}
31 |
32 | if [ ! -d "${BASEDIR}/assets" ]; then
33 | mkdir "${BASEDIR}/assets"
34 | fi
35 |
36 | echo -en "Version: ${VERSION}" >"${BASEDIR}/assets/version.txt"
37 |
38 | printDots "* Copying Linux Executables" 30
39 |
40 | find ${BASEDIR} -name "*.node" | grep -v "release-builds" | grep -v "obj.target" >${release_dir_linux}/exe.list
41 |
42 | while read -r line; do
43 | cp ${line} ${release_dir_linux}/.
44 | done <${release_dir_linux}/exe.list
45 | rm ${release_dir_linux}/exe.list
46 |
47 | echo -en "\e[32m✔\e[0m\n"
48 |
49 | printDots "* Compiling Linux" 30
50 |
51 | pkg app.js -c package.json -t node18-linux-x64 --out-path ${release_dir_linux} -d >${release_dir_linux}/build.log
52 |
53 | if [ $? -ne 0 ]; then
54 | echo -en "\e[31m✘\e[0m\n"
55 | exit 1
56 | else
57 | echo -en "\e[32m✔\e[0m\n"
58 | fi
59 |
60 | ZipLinuxFileName="${release_dir}/SSM-Linux-x64-${VERSION}.tar.gz"
61 |
62 | cd ${release_dir_linux}
63 | printDots "* Zipping Linux Binaries" 30
64 |
65 | tar cz --exclude='*.log' -f ${ZipLinuxFileName} ./* >/dev/null
66 | if [ $? -ne 0 ]; then
67 | echo -en "\e[31m✘\e[0m\n"
68 | exit 1
69 | else
70 | echo -en "\e[32m✔\e[0m\n"
71 | fi
72 |
73 |
74 |
--------------------------------------------------------------------------------
/public/css/login.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | height: 100%;
4 | }
5 |
6 | body {
7 | background: #333 url("/public/images/logonbg.png") no-repeat bottom center !important;
8 | color: #fff;
9 | font-size: 12px;
10 | }
11 |
12 | .main-container {
13 | padding: 15px 15px 15px 15px;
14 | min-height: 100%;
15 | overflow: hidden;
16 | }
17 |
18 |
19 | .form-control {
20 | font-size: 13px;
21 | }
22 |
23 | .login-card {
24 | position: absolute;
25 | top: 50%;
26 | left: 50%;
27 | transform: translateX(-50%) translateY(-50%);
28 | max-width: 500px;
29 | width: 100%;
30 | border: 1px solid rgba(0, 0, 0, 0.8) !important;
31 | box-sizing: content-box !important;
32 | margin-top: 15px;
33 | }
34 |
35 | .login-card .card-header {
36 | position: relative;
37 | height: 40px;
38 | padding: 0;
39 | }
40 |
41 | .login-card .card-header .logo {
42 | position: absolute;
43 | top: -64px;
44 | left: 50%;
45 | transform: translateX(-50%);
46 | }
47 |
48 | .login-card .card-header .logo img {
49 | width: 128px;
50 | }
51 |
52 | .login-card .card-title {
53 | padding-top: 10px;
54 | text-align: center;
55 | margin-bottom: 20px;
56 | }
57 |
58 | .login-card .signup-heading {
59 | font-weight: 500;
60 | padding-bottom: 3px;
61 | margin-bottom: 4px;
62 | border-bottom: 1px solid rgba(0, 0, 0, 0.05);
63 | }
64 |
65 | .login-card .form-group {
66 | margin-bottom: 5px;
67 | }
68 |
69 | .login-card label {
70 | line-height: 29px;
71 | margin: 0;
72 | font-size: 12px;
73 | }
74 |
75 | .login-card .error-content {
76 | font-size: 12px;
77 | font-weight: 700;
78 | text-align: center;
79 | color: darkred;
80 | background: pink;
81 | margin-bottom: 10px;
82 | }
83 |
84 | .login-card .error-content span {
85 | padding: 5px 0;
86 | }
87 |
88 | .login-card .btn {
89 | padding: 5px 8px;
90 | font-size: 12px;
91 | margin-top: 10px;
92 | }
93 |
94 | .login-card .pass-wrapper {
95 | width: 100%;
96 | height: 22px;
97 | }
--------------------------------------------------------------------------------
/views/login.hbs:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
Login to Dashboard
7 |
{{{ERROR_MSG}}}
8 |
42 |
43 |
--------------------------------------------------------------------------------
/public/images/favicons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/tools/interactive_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CURDIR=$(dirname "$(readlink -f "$0")")
4 | BASEDIR=$(readlink -f "$CURDIR/..")
5 |
6 | . ${BASEDIR}/tools/variables.sh
7 |
8 | if [ ! -f "${BASEDIR}/release-builds/linux/SatisfactoryServerManager" ]; then
9 | echo "Error: SSM Linux didn't compile correctly!"
10 | exit 1
11 | fi
12 |
13 | if [ ! -f "${BASEDIR}/release-builds/win64/SatisfactoryServerManager.exe" ]; then
14 | echo "Error: SSM Win64 didn't compile correctly!"
15 | exit 1
16 | fi
17 |
18 | chmod +x "${BASEDIR}/release-builds/win64/SatisfactoryServerManager.exe"
19 |
20 | ${BASEDIR}/release-builds/win64/SatisfactoryServerManager.exe >/dev/null 2>&1 &
21 | SSM_PID=$!
22 |
23 | while true; do
24 | read -p "Did SSM Start successfully? [Y/n]: " yn
25 | case $yn in
26 | [Yy]*)
27 | break
28 | ;;
29 | *)
30 | kill $SSM_PID
31 | echo "Error: SSM didn't start!"
32 | exit 1
33 | ;;
34 | esac
35 | done
36 |
37 | while true; do
38 | read -p "Login System Is working? [Y/n]: " yn
39 | case $yn in
40 | [Yy]*)
41 | break
42 | ;;
43 | *)
44 | kill $SSM_PID
45 | echo "Error: Cant login to SSM !"
46 | exit 1
47 | ;;
48 | esac
49 | done
50 |
51 | while true; do
52 | read -p "Is the SSM version correct? [Y/n]: " yn
53 | case $yn in
54 | [Yy]*)
55 | break
56 | ;;
57 | *)
58 | kill $SSM_PID
59 | echo "Error: SSM version is not set correctly in server_config.js!"
60 | exit 1
61 | ;;
62 | esac
63 | done
64 |
65 | while true; do
66 | read -p "Mods List populated? [Y/n]: " yn
67 | case $yn in
68 | [Yy]*)
69 | break
70 | ;;
71 | *)
72 | kill $SSM_PID
73 | echo "Error: Debug Mods List!"
74 | exit 1
75 | ;;
76 | esac
77 | done
78 |
79 | while true; do
80 | read -p "Is there any console errors? [Y/n]: " yn
81 | case $yn in
82 | [Yy]*)
83 | kill $SSM_PID
84 | echo "Error: There are console errors fix these first!"
85 | exit 1
86 | ;;
87 | *)
88 | break
89 | ;;
90 | esac
91 | done
92 |
93 | kill $SSM_PID
94 |
95 | exit 0
96 |
--------------------------------------------------------------------------------
/objects/obj_user.js:
--------------------------------------------------------------------------------
1 | const ObjUserRole = require("./obj_user_role");
2 |
3 | class ObjUser {
4 | constructor() {
5 | /** @type {Number} - User ID */
6 | this._id = -1;
7 | /** @type {String} - Username */
8 | this._username = "";
9 | /** @type {String} - Password */
10 | this._password = "";
11 | /** @type {Number} - Role ID */
12 | this._role_id = -1;
13 | /** @type {ObjUserRole} - Role */
14 | this._role = null;
15 | }
16 |
17 | /**
18 | *
19 | * @param {Object} data SQL data
20 | */
21 | parseDBData(data) {
22 | this._id = data.user_id;
23 | this._username = data.user_name;
24 | this._password = data.user_pass;
25 | this._role_id = data.user_role_id;
26 | }
27 |
28 | /**
29 | * Gets the User ID
30 | * @returns {Number} id
31 | */
32 | getId() {
33 | return this._id;
34 | }
35 |
36 | /**
37 | *
38 | * @returns {String} - Username
39 | */
40 | getUsername() {
41 | return this._username;
42 | }
43 |
44 | /**
45 | *
46 | * @returns {String} - Password
47 | */
48 | getPassword() {
49 | return this._password;
50 | }
51 |
52 | /**
53 | * Gets the User Role ID
54 | * @returns {Number} id
55 | */
56 | getRoleId() {
57 | return this._role_id;
58 | }
59 |
60 | /**
61 | *
62 | * @returns {ObjUserRole} User Role
63 | */
64 | getRole() {
65 | return this._role;
66 | }
67 |
68 | /**
69 | *
70 | * @param {ObjUserRole} Role
71 | */
72 | SetRole(Role) {
73 | this._role = Role;
74 | }
75 | /**
76 | *
77 | * @param {String} RequiredPermission
78 | * @returns {Boolean} - Has Requested Permission
79 | */
80 | HasPermission(RequiredPermission) {
81 | const Role = this.getRole();
82 | return Role.HasPermission(RequiredPermission);
83 | }
84 |
85 | getWebJson() {
86 | return {
87 | id: this.getId(),
88 | username: this.getUsername(),
89 | role: this.getRole().getWebJson(),
90 | };
91 | }
92 | }
93 |
94 | module.exports = ObjUser;
95 |
--------------------------------------------------------------------------------
/views/saves.hbs:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
16 |
17 |
40 |
41 |
42 |
43 | Session
44 | Name
45 | Last Updated
46 | Options
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/objects/obj_notification.js:
--------------------------------------------------------------------------------
1 | const hbs = require("handlebars");
2 | const path = require("path");
3 | const fs = require("fs-extra");
4 | const objectpath = require("object-path");
5 |
6 | class Notification {
7 | constructor(eventName) {
8 | const date = new Date();
9 |
10 | this._data = {
11 | event: eventName,
12 | timestamp: date.getTime(),
13 | webhook_id: -1,
14 | handled: false,
15 | attempts: 0,
16 | lastError: "",
17 | };
18 | this._TemplateHtmlPath = path.join(
19 | __basedir,
20 | "notifications",
21 | eventName + ".hbs"
22 | );
23 | this._html = "";
24 | }
25 |
26 | GetData() {
27 | return this._data;
28 | }
29 |
30 | GetEventName() {
31 | return this.get("event");
32 | }
33 |
34 | ParseSQLData(data) {
35 | this._data = JSON.parse(data.we_data);
36 | this._id = data.we_id;
37 | }
38 |
39 | get(key, defaultval) {
40 | let return_val = objectpath.get(this._data, key);
41 | if (return_val == null && defaultval != null) {
42 | this.set(key, defaultval);
43 | }
44 | return objectpath.get(this._data, key);
45 | }
46 |
47 | set(key, val) {
48 | objectpath.set(this._data, key, val);
49 | }
50 |
51 | build() {
52 | if (fs.existsSync(this._TemplateHtmlPath)) {
53 | const source = fs.readFileSync(this._TemplateHtmlPath, "utf8");
54 | const template = hbs.compile(source);
55 | this._html = template(this._data);
56 | }
57 | }
58 |
59 | applyJsonTemplate(template, backing) {
60 | for (var i in template) {
61 | var m = /^{(.+)}$/.exec(template[i]);
62 | if (m && backing[m[1]]) {
63 | // replace with a deep clone of the value from the backing model
64 | template[i] = JSON.parse(JSON.stringify(backing[m[1]]));
65 | } else if (template[i] && "object" == typeof template[i]) {
66 | // traverse down recursively
67 | applyTemplate(template[i], backing);
68 | }
69 | }
70 | return template;
71 | }
72 | }
73 |
74 | module.exports = Notification;
75 |
--------------------------------------------------------------------------------
/tools/package/ci_compile_windows.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function printDots() {
4 | text=$1
5 | length=$2
6 | textlen=${#text}
7 |
8 | newlength=$((length - textlen - 1))
9 |
10 | v=$(printf "%-${newlength}s" ".")
11 | echo -en "${text} ${v// /.} "
12 | }
13 |
14 | CURDIR=$(dirname "$(readlink -f "$0")")
15 | BASEDIR=$(readlink -f "$CURDIR/../..")
16 |
17 | VERSION=$(cat package.json | grep version | awk '{print $2}' | sed -e 's/"//g' | sed -e 's/,//g')
18 | FORCE=0
19 |
20 | while [[ $# -gt 0 ]]; do
21 | key="$1"
22 |
23 | case $key in
24 | --version)
25 | VERSION="$2"
26 | shift # past value
27 | shift # past value
28 | ;;
29 | --force)
30 | FORCE=1
31 | shift # past value
32 | ;;
33 |
34 | esac
35 | done
36 |
37 | echo -en "\nPackaging Application (Version: \e[34m${VERSION}\e[0m)\n"
38 |
39 | release_dir="${BASEDIR}/release-builds"
40 | release_dir_win64="${release_dir}/win64"
41 |
42 | rm -rf ${release_dir_win64}\* 2>&1 >/dev/null
43 |
44 | if [ ! -d "${release_dir_win64}" ]; then
45 | mkdir -p "${release_dir_win64}"
46 | fi
47 |
48 | cd ${BASEDIR}
49 |
50 | if [ ! -d "${BASEDIR}/assets" ]; then
51 | mkdir "${BASEDIR}/assets"
52 | fi
53 |
54 | echo -en "Version: ${VERSION}" >"${BASEDIR}/assets/version.txt"
55 |
56 | yarn
57 |
58 | printDots "* Copying Win64 Executables" 30
59 |
60 | find ${BASEDIR} -name "*.node" | grep -v "release-builds" >${release_dir_win64}/exe.list
61 |
62 | while read -r line; do
63 | cp ${line} ${release_dir_win64}/.
64 | done <${release_dir_win64}/exe.list
65 | rm ${release_dir_win64}/exe.list
66 |
67 | echo -en "\e[32m✔\e[0m\n"
68 |
69 | printDots "* Compiling Win64" 30
70 | pkg app.js -c package.json -t node18-win-x64 --out-path ${release_dir_win64} -d >${release_dir_win64}/build.log
71 | if [ $? -ne 0 ]; then
72 | echo -en "\e[31m✘\e[0m\n"
73 | exit 1
74 | else
75 | echo -en "\e[32m✔\e[0m\n"
76 | fi
77 |
78 | ZipWin64FileName="${release_dir}/SSM-Win-x64-${VERSION}.zip"
79 |
80 | cd ${release_dir_win64}
81 | printDots "* Zipping Windows Binaries" 30
82 | /c/BuildScripts/7z.exe a -tzip ${ZipWin64FileName} ./* -xr!build.log >/dev/null
83 |
84 | if [ $? -ne 0 ]; then
85 | echo -en "\e[31m✘\e[0m\n"
86 | exit 1
87 | else
88 | echo -en "\e[32m✔\e[0m\n"
89 | fi
90 |
--------------------------------------------------------------------------------
/server/ms_agent/server_gameconfig.js:
--------------------------------------------------------------------------------
1 | const SFS_Handler = require("./server_sfs_handler");
2 |
3 | const platform = process.platform;
4 | const fs = require("fs-extra");
5 | const path = require("path");
6 |
7 | const Config = require("../server_config");
8 |
9 | const IEngineConfig = require("../../objects/configs/config_engine.js");
10 | const IGameConfig = require("../../objects/configs/config_game.js");
11 |
12 | class GameConfig {
13 | constructor() {}
14 |
15 | load() {
16 | return new Promise((resolve, reject) => {
17 | SFS_Handler.isGameInstalled()
18 | .then((installed) => {
19 | if (installed === true) {
20 | let PlatformFolder = "";
21 | if (platform == "win32") {
22 | PlatformFolder = "WindowsServer";
23 | } else {
24 | PlatformFolder = "LinuxServer";
25 | }
26 |
27 | const configDir = path.join(
28 | Config.get("satisfactory.server_location"),
29 | "FactoryGame",
30 | "Saved",
31 | "Config",
32 | PlatformFolder
33 | );
34 | fs.ensureDirSync(configDir);
35 |
36 | this._EngineConfig = new IEngineConfig(configDir);
37 | this._GameConfig = new IGameConfig(configDir);
38 |
39 | const promises = [];
40 | promises.push(this._EngineConfig.load());
41 | promises.push(this._GameConfig.load());
42 |
43 | return Promise.all(promises);
44 | } else {
45 | return;
46 | }
47 | })
48 | .then(() => {
49 | resolve();
50 | })
51 | .catch((err) => {
52 | console.log(err);
53 | });
54 | });
55 | }
56 |
57 | getEngineConfig() {
58 | return this._EngineConfig;
59 | }
60 |
61 | getGameConfig() {
62 | return this._GameConfig;
63 | }
64 | }
65 |
66 | const gameConfig = new GameConfig();
67 | module.exports = gameConfig;
68 |
--------------------------------------------------------------------------------
/routes/api/info.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var router = express.Router();
3 |
4 | const ServerApp = require("../../server/server_app");
5 | const Config = require("../../server/server_config");
6 |
7 | const UserManager = require("../../server/server_user_manager");
8 | const NotificationHandler = require("../../server/server_notifcation_handler");
9 |
10 | const middleWare = [ServerApp.checkLoggedInAPIMiddleWare];
11 |
12 | router.get("/ssmversion", middleWare, function (req, res, next) {
13 | Config.getSSMVersion()
14 | .then((data) => {
15 | res.json({
16 | result: "success",
17 | data: data,
18 | });
19 | })
20 | .catch((err) => {
21 | res.json({
22 | result: "error",
23 | error: err.message,
24 | });
25 | });
26 | });
27 |
28 | router.get("/loggedin", middleWare, function (req, res, next) {
29 | res.json({
30 | result: "success",
31 | data: "",
32 | });
33 | });
34 |
35 | router.get("/users", middleWare, function (req, res, next) {
36 | UserManager.API_GetAllUsers().then((users) => {
37 | res.json({
38 | result: "success",
39 | data: users,
40 | });
41 | });
42 | });
43 |
44 | router.get("/roles", middleWare, function (req, res, next) {
45 | UserManager.API_GetAllRoles().then((roles) => {
46 | res.json({
47 | result: "success",
48 | data: roles,
49 | });
50 | });
51 | });
52 |
53 | router.get("/apikeys", middleWare, function (req, res, next) {
54 | UserManager.API_GetAllAPIKeys().then((keys) => {
55 | res.json({
56 | result: "success",
57 | data: keys,
58 | });
59 | });
60 | });
61 |
62 | router.get("/permissions", middleWare, function (req, res, next) {
63 | UserManager.API_GetAllPermissions().then((perms) => {
64 | res.json({
65 | result: "success",
66 | data: perms,
67 | });
68 | });
69 | });
70 |
71 | router.get("/webhooks", middleWare, function (req, res, next) {
72 | NotificationHandler.API_GetAllWebhooks().then((webhooks) => {
73 | res.json({
74 | result: "success",
75 | data: webhooks,
76 | });
77 | });
78 | });
79 |
80 | router.get("/serverrunning", middleWare, function (req, res, next) {
81 | res.json({
82 | result: "success",
83 | running: ServerApp._init,
84 | });
85 | });
86 |
87 | module.exports = router;
88 |
--------------------------------------------------------------------------------
/objects/obj_discord_notification_interface.js:
--------------------------------------------------------------------------------
1 | const NotificationInterface = require("./obj_notification_interface");
2 | const Logger = require("../server/server_logger");
3 |
4 | const { Webhook, MessageBuilder } = require("discord-webhook-node");
5 |
6 | class DiscordNotificationInterface extends NotificationInterface {
7 | constructor() {
8 | super();
9 |
10 | this._SSMLogo =
11 | "https://raw.githubusercontent.com/mrhid6/SatisfactoryServerManager/master/public/images/ssm_logo128.png";
12 | }
13 |
14 | init(data) {
15 | super.init(data);
16 |
17 | this._hook = new Webhook(this.getOptions().url);
18 | this._hook.setUsername("SSM Notifier");
19 | this._hook.setAvatar(this._SSMLogo);
20 | }
21 |
22 | TriggerSSMEvent(Notification) {
23 | return new Promise((resolve, reject) => {
24 | const embed = new MessageBuilder()
25 | .setTitle(Notification.get("title"))
26 | .setColor("#00b0f4")
27 | .setDescription(Notification.get("description"))
28 | .setTimestamp();
29 | this._hook.send(embed).then(() => {
30 | resolve();
31 | });
32 | });
33 | }
34 |
35 | TriggerAgentEvent(Notification) {
36 | return new Promise((resolve, reject) => {
37 | const embed = new MessageBuilder()
38 | .setTitle(Notification.get("title"))
39 | .setColor("#00b0f4")
40 | .setDescription(Notification.get("description"))
41 | .addField(
42 | "**Agent:**",
43 | Notification.get("details.agent_name"),
44 | true
45 | )
46 | .setTimestamp();
47 |
48 | this._hook.send(embed).then(() => {
49 | resolve();
50 | });
51 | });
52 | }
53 |
54 | TriggerServerEvent(Notification) {
55 | return new Promise((resolve, reject) => {
56 | const embed = new MessageBuilder()
57 | .setTitle(Notification.get("title"))
58 | .setColor("#00b0f4")
59 | .setDescription(Notification.get("description"))
60 | .addField(
61 | "**Agent:**",
62 | Notification.get("details.agent_name"),
63 | true
64 | )
65 | .setTimestamp();
66 |
67 | this._hook.send(embed).then(() => {
68 | resolve();
69 | });
70 | });
71 | }
72 | }
73 |
74 | module.exports = DiscordNotificationInterface;
75 |
--------------------------------------------------------------------------------
/uninstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export DEBIAN_FRONTEND=noninteractive
4 |
5 | echo "#-----------------------------#"
6 | echo "# _____ _____ __ __ #"
7 | echo "# / ____/ ____| \/ | #"
8 | echo "# | (___| (___ | \ / | #"
9 | echo "# \___ \\\\___ \| |\/| | #"
10 | echo "# ____) |___) | | | | #"
11 | echo "# |_____/_____/|_| |_| #"
12 | echo "#-----------------------------#"
13 | echo "# Satisfactory Server Manager #"
14 | echo "#-----------------------------#"
15 | echo ""
16 | echo "Uninstalling SSM will remove all SSM files and Server instances!"
17 | echo ""
18 | read -p "Are you sure you want to uninstall SSM (y/n)?" choice
19 | case "$choice" in
20 | y | Y) echo "yes" ;;
21 | *) exit 0 ;;
22 | esac
23 |
24 | TEMP_DIR=$(mktemp -d /tmp/XXXXX)
25 | INSTALL_DIR="/opt/SSM"
26 | FORCE=0
27 |
28 | while [[ $# -gt 0 ]]; do
29 | key="$1"
30 |
31 | case $key in
32 | --force | -f)
33 | FORCE=1
34 | shift # past value
35 | ;;
36 | --installdir)
37 | INSTALL_DIR=$2
38 | shift
39 | shift
40 | ;;
41 | *)
42 | echo "Invalid option must be: [--force, --update, --noservice, --dev, --installdir=
"
43 | exit 1
44 | ;;
45 | esac
46 | done
47 |
48 | ENV_SYSTEMD=$(pidof systemd | wc -l)
49 | ENV_SYSTEMCTL=$(which systemctl | wc -l)
50 |
51 | if [[ ${ENV_SYSTEMD} -eq 0 ]] && [[ ${ENV_SYSTEMCTL} -eq 0 ]]; then
52 | echo "Error: Cant install service on this system!"
53 | exit 3
54 | fi
55 |
56 | SSM_SERVICENAME="SSM.service"
57 | SSM_SERVICEFILE="/etc/systemd/system/SSM.service"
58 | SSM_SERVICE=$(
59 | systemctl list-units --full -all | grep -Fq "${SSM_SERVICENAME}"
60 | echo $?
61 | )
62 |
63 | if [ ${SSM_SERVICE} -eq 0 ]; then
64 | echo "* Stopping SSM Service"
65 | systemctl stop ${SSM_SERVICENAME}
66 | fi
67 |
68 | if [ ${SSM_SERVICE} -eq 0 ]; then
69 | echo "* Removing SSM Service"
70 | systemctl disable ${SSM_SERVICENAME} >/dev/null 2>&1
71 | rm -r "${SSM_SERVICEFILE}" >/dev/null 2>&1
72 | systemctl daemon-reload >/dev/null 2>&1
73 | fi
74 |
75 | echo "* Removing Install Directory ${INSTALL_DIR}"
76 |
77 | rm -r ${INSTALL_DIR} >/dev/null 2>&1
78 |
79 | echo "* Removing SSM user"
80 | deluser --remove-home ssm >/dev/null 2>&1
81 | delgroup ssm >/dev/null 2>&1
82 | rm -r "/home/ssm" >/dev/null 2>&1
83 |
84 | echo "* Removing Docker Containers"
85 | docker rm -f $(docker ps -a --filter="name=SSMAgent" -q) >/dev/null 2>&1
86 |
87 | echo "* Removing Docker Containers Directory /SSMAgents"
88 | rm -r /SSMAgents >/dev/null 2>&1
89 |
90 | echo "Successfully Uninstalled SSM!"
91 | exit 0
92 |
--------------------------------------------------------------------------------
/objects/obj_notification_interface.js:
--------------------------------------------------------------------------------
1 | const Logger = require("../server/server_logger");
2 |
3 | class NotificationInterface {
4 | constructor() {}
5 |
6 | init(opts) {
7 | this._options = opts;
8 | }
9 |
10 | getOptions() {
11 | return this._options;
12 | }
13 |
14 | getId() {
15 | return this.getOptions().id;
16 | }
17 |
18 | getURL() {
19 | return this.getOptions().url;
20 | }
21 |
22 | getName() {
23 | return this.getOptions().name;
24 | }
25 |
26 | isEnabled() {
27 | return this.getOptions().enabled;
28 | }
29 |
30 | getEvents() {
31 | return this.getOptions().events;
32 | }
33 |
34 | ListeningForEvent(Notification) {
35 | const listeningevent = this.getEvents().find(
36 | (e) => e == Notification.GetEventName()
37 | );
38 | return listeningevent != null;
39 | }
40 |
41 | CanTriggerEvent(Notification) {
42 | if (this.isEnabled() == false) {
43 | return false;
44 | }
45 |
46 | if (!this.ListeningForEvent(Notification)) {
47 | return false;
48 | }
49 |
50 | return true;
51 | }
52 |
53 | TriggerEvent = async (Notification) => {
54 | if (this.CanTriggerEvent(Notification)) {
55 | Logger.debug(
56 | "[NOTIFCATION] - Triggered Event " + Notification.GetEventName()
57 | );
58 |
59 | try {
60 | switch (Notification.GetEventName()) {
61 | case "ssm.startup":
62 | case "ssm.shutdown":
63 | await this.TriggerSSMEvent(Notification);
64 | break;
65 | case "agent.created":
66 | case "agent.started":
67 | case "agent.shutdown":
68 | await this.TriggerAgentEvent(Notification);
69 | break;
70 | case "server.starting":
71 | case "server.running":
72 | case "server.stopping":
73 | case "server.offline":
74 | await this.TriggerServerEvent(Notification);
75 | break;
76 | }
77 | } catch (err) {
78 | throw err;
79 | }
80 | }
81 | };
82 |
83 | TriggerSSMEvent = async (Notification) => {
84 | return;
85 | };
86 |
87 | TriggerAgentEvent = async (Notification) => {
88 | return;
89 | };
90 |
91 | TriggerServerEvent = async (Notification) => {
92 | return;
93 | };
94 | }
95 |
96 | module.exports = NotificationInterface;
97 |
--------------------------------------------------------------------------------
/public/modals/metrics-opt-in.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 | Welcome to Satisfactory Server Manager (SSM), This is most likely the first time you have
15 | opened SSM, and we want to tell you about SSM Metrics!
16 |
17 |
18 | Application metrics are used to better enhance this application (SSM) and give the user
19 | (You) a reliable experence. Information is sent
20 | to
21 | a secure Metrics and Analytics server.
22 | The following information would be sent:
23 |
24 |
25 | IP Address
26 | IP GeoLocation
27 | Application Starts
28 | Application Stops
29 | Error Messages
30 |
31 |
32 | You may choose to accept or reject the capture of Satisfactory Server Manager Metrics using
33 | the
34 | buttons below.
35 |
36 |
37 | If you accept and later decide to reject the metrics and request to remove all information
38 | about you, please contact metrics@hostxtra.co.uk
39 |
40 |
41 |
42 |
43 |
44 | Reject
46 | Accept
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/views/changedefaultpass.hbs:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
Change Default Password
7 |
{{{ERROR_MSG}}}
8 |
55 |
56 |
--------------------------------------------------------------------------------
/test/test8.js:
--------------------------------------------------------------------------------
1 | var fs = require("fs"),
2 | es = require("event-stream");
3 | var fsR = require("fs-reverse");
4 |
5 | function processLog() {
6 | var lineNr = 0;
7 |
8 | let fileCounter = 0;
9 | let tempFileContents = [];
10 |
11 | fsR("/tmp/log.log", {
12 | matcher: "\r",
13 | })
14 | .pipe(es.split())
15 | .pipe(
16 | es.mapSync(function (line) {
17 | console.log(line);
18 | })
19 | );
20 |
21 | var s = fsR("/tmp/log.log", {
22 | matcher: "\r",
23 | })
24 | .pipe(es.split())
25 | .pipe(
26 | es
27 | .mapSync(function (line) {
28 | if (line != "") {
29 | // pause the readstream
30 | s.pause();
31 |
32 | tempFileContents.push(line);
33 | lineNr++;
34 | if (lineNr % 1000 == 0) {
35 | fileCounter += 1;
36 | console.log(lineNr, fileCounter);
37 | fs.writeFileSync(
38 | `/tmp/logsplit_${fileCounter}.log`,
39 | JSON.stringify(tempFileContents)
40 | );
41 | tempFileContents = [];
42 | }
43 |
44 | // resume the readstream, possibly from a callback
45 | s.resume();
46 | }
47 | })
48 | .on("error", function (err) {
49 | console.log("Error while reading file.", err);
50 | })
51 | .on("end", function () {
52 | console.log("Read entire file.");
53 |
54 | if (tempFileContents.length > 0) {
55 | fileCounter += 1;
56 | console.log(lineNr, fileCounter);
57 | fs.writeFileSync(
58 | `/tmp/logsplit_${fileCounter}.log`,
59 | JSON.stringify(tempFileContents)
60 | );
61 | tempFileContents = [];
62 | }
63 | })
64 | );
65 | }
66 |
67 | function getLogLines(offset) {
68 | const fileNumber = Math.floor(offset / 1000) + 1;
69 | const logFile = `/tmp/logsplit_${fileNumber}.log`;
70 |
71 | if (fs.existsSync(logFile) == false) {
72 | return [];
73 | }
74 |
75 | const fileData = fs.readFileSync(logFile);
76 | try {
77 | const JsonData = JSON.parse(fileData);
78 | return JsonData;
79 | } catch (err) {}
80 |
81 | return [];
82 | }
83 | processLog();
84 |
85 | /*const data = getLogLines(-1)
86 |
87 | console.log(data);
88 | */
89 |
--------------------------------------------------------------------------------
/public/modals/add-apikey-modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
33 |
34 |
53 |
54 |
55 |
56 |
61 | Add API Key
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/tools/package/package_app.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #!/bin/bash
4 |
5 | CURDIR=$(dirname "$(readlink -f "$0")")
6 | BASEDIR=$(readlink -f "$CURDIR/../..")
7 |
8 | VERSION=""
9 | LINUX=0
10 | WINDOWS=0
11 | FORCE=0
12 |
13 | while [[ $# -gt 0 ]]; do
14 | key="$1"
15 |
16 | case $key in
17 | --version)
18 | VERSION="$2"
19 | shift # past value
20 | shift # past value
21 | ;;
22 | --linux)
23 | LINUX=1
24 | shift # past value
25 | ;;
26 | --windows)
27 | WINDOWS=1
28 | shift # past value
29 | ;;
30 | --force)
31 | FORCE=1
32 | shift # past value
33 | ;;
34 |
35 | esac
36 | done
37 |
38 | if [[ ! -f "${BASEDIR}/tools/app_config.txt" ]] && [[ $FORCE -eq 0 ]]; then
39 | echo "No config file was found please run configure_app.sh first"
40 | exit 1
41 | fi
42 |
43 | . ${BASEDIR}/tools/variables.sh
44 | . ${BASEDIR}/tools/app_config.txt
45 |
46 | echo -en "\nPackaging Application (Version: \e[34m${VERSION}\e[0m)\n"
47 |
48 | release_dir="${BASEDIR}/release-builds"
49 | release_dir_linux="${release_dir}/linux"
50 | release_dir_win64="${release_dir}/win64"
51 |
52 | rm -rf ${release_dir} 2>&1 >/dev/null
53 |
54 | if [ ! -d "${release_dir}" ]; then
55 | mkdir -p "${release_dir_linux}"
56 | mkdir -p "${release_dir_win64}"
57 | fi
58 |
59 | cd ${BASEDIR}
60 |
61 | #bash ${BASEDIR}/tools/update_SSM_exe.sh --version "${VERSION}"
62 |
63 | if [ $? -ne 0 ]; then
64 | echo "Error: failed to update SSM.exe version numbers"
65 | exit 1
66 | fi
67 |
68 | if [ ! -d "${BASEDIR}/assets" ]; then
69 | mkdir "${BASEDIR}/assets"
70 | fi
71 |
72 | echo -en "Version: ${VERSION}" >"${BASEDIR}/assets/version.txt"
73 |
74 | if [ ${LINUX} -eq 1 ]; then
75 | bash ${CURDIR}/package_linux.sh --version ${VERSION}
76 | fi
77 |
78 | if [ ${WINDOWS} -eq 1 ]; then
79 | printDots "* Copying Win64 Executables" 30
80 |
81 | find ${BASEDIR} -name "*.node" | grep -v "release-builds" >${release_dir_win64}/exe.list
82 |
83 | while read -r line; do
84 | cp ${line} ${release_dir_win64}/.
85 | done <${release_dir_win64}/exe.list
86 | rm ${release_dir_win64}/exe.list
87 |
88 | echo -en "\e[32m✔\e[0m\n"
89 |
90 | printDots "* Compiling Win64" 30
91 | pkg app.js -c package.json -t node16-win-x64 --out-path ${release_dir_win64} -d >${release_dir_win64}/build.log
92 | echo -en "\e[32m✔\e[0m\n"
93 | fi
94 |
95 | ZipLinuxFileName="${release_dir}/SSM-Linux-x64-${VERSION}.tar.gz"
96 | ZipWin64FileName="${release_dir}/SSM-Win-x64-${VERSION}.zip"
97 |
98 | printDots "* Zipping Binaries" 30
99 |
100 | cd ${release_dir_linux}
101 | tar cz --exclude='*.log' -f ${ZipLinuxFileName} ./* >/dev/null
102 | cd ${release_dir_win64}
103 | 7z a -tzip ${ZipWin64FileName} ./* -xr!build.log >/dev/null
104 | echo -en "\e[32m✔\e[0m\n"
105 |
106 | exit 0
107 |
--------------------------------------------------------------------------------
/src/logger.js:
--------------------------------------------------------------------------------
1 | const Logger = {};
2 |
3 | Logger.TYPES = {
4 | LOG: 0,
5 | INFO: 1,
6 | SUCCESS: 2,
7 | WARNING: 3,
8 | ERROR: 4,
9 | DEBUG: 5,
10 | RESET: 6,
11 | };
12 |
13 | Logger.LEVEL = Logger.TYPES.DEBUG;
14 |
15 | Logger.STYLES = [
16 | "padding: 2px 8px; margin-right:8px; background:#cccccc; color:#000; font-weight:bold; border:1px solid #000;",
17 | "padding: 2px 8px; margin-right:8px; background:#008cba; color:#fff; font-weight:bold; border:1px solid #000;",
18 | "padding: 2px 8px; margin-right:8px; background:#43ac6a; color:#fff; font-weight:bold; border:1px solid #3c9a5f;",
19 | "padding: 2px 8px; margin-right:8px; background:#E99002; color:#fff; font-weight:bold; border:1px solid #d08002;",
20 | "padding: 2px 8px; margin-right:8px; background:#F04124; color:#fff; font-weight:bold; border:1px solid #ea2f10;",
21 | "padding: 2px 8px; margin-right:8px; background:#003aba; color:#fff; font-weight:bold; border:1px solid #000;",
22 | "",
23 | ];
24 |
25 | Logger.init = () => {
26 | Logger.displayBanner();
27 | };
28 |
29 | Logger.displayBanner = () => {
30 | Logger.BannerMessage = `
31 | %c #-----------------------------#
32 | # _____ _____ __ __ #
33 | # / ____/ ____| \\/ | #
34 | # | (___| (___ | \\ / | #
35 | # \\___ \\\\___ \\| |\\/| | #
36 | # ____) |___) | | | | #
37 | # |_____/_____/|_| |_| #
38 | #-----------------------------#
39 | # Satisfactory Server Manager #
40 | #-----------------------------#
41 | `;
42 |
43 | console.log(
44 | Logger.BannerMessage,
45 | "background:#008cba;color:#fff;font-weight:bold"
46 | );
47 | };
48 |
49 | Logger.getLoggerTypeString = (LoggerType) => {
50 | switch (LoggerType) {
51 | case 0:
52 | return "LOG";
53 | case 1:
54 | return "INFO";
55 | case 2:
56 | return "SUCCESS";
57 | case 3:
58 | return "WARN";
59 | case 4:
60 | return "ERROR";
61 | case 5:
62 | return "DEBUG";
63 | }
64 | };
65 |
66 | Logger.toLog = (LoggerType, Message) => {
67 | if (LoggerType == null) return;
68 |
69 | if (LoggerType > Logger.LEVEL) return;
70 |
71 | const style = Logger.STYLES[LoggerType];
72 | const resetStyle = Logger.STYLES[Logger.TYPES.RESET];
73 | const typeString = Logger.getLoggerTypeString(LoggerType);
74 |
75 | console.log("%c" + typeString + "%c" + Message, style, resetStyle);
76 | };
77 |
78 | Logger.log = (Message) => {
79 | Logger.toLog(Logger.TYPES.LOG, Message);
80 | };
81 |
82 | Logger.info = (Message) => {
83 | Logger.toLog(Logger.TYPES.INFO, Message);
84 | };
85 |
86 | Logger.success = (Message) => {
87 | Logger.toLog(Logger.TYPES.SUCCESS, Message);
88 | };
89 |
90 | Logger.warning = (Message) => {
91 | Logger.toLog(Logger.TYPES.WARNING, Message);
92 | };
93 |
94 | Logger.error = (Message) => {
95 | Logger.toLog(Logger.TYPES.ERROR, Message);
96 | };
97 |
98 | Logger.debug = (Message) => {
99 | Logger.toLog(Logger.TYPES.DEBUG, Message);
100 | };
101 |
102 | module.exports = Logger;
103 |
--------------------------------------------------------------------------------
/public/modals/add-webhook-modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
33 |
34 |
48 |
49 |
53 |
54 |
55 |
56 |
60 | Add Webhook
61 |
62 |
63 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/tools/build_app.sh:
--------------------------------------------------------------------------------
1 | CURDIR=$(dirname "$(readlink -f "$0")")
2 | BASEDIR=$(readlink -f "$CURDIR/..")
3 |
4 | INSTALL=0
5 | UPDATE=0
6 | while [[ $# -gt 0 ]]; do
7 | key="$1"
8 | case $key in
9 | -i | --install)
10 | INSTALL=1
11 | shift # past value
12 | ;;
13 | -u | --update)
14 | UPDATE=1
15 | shift # past value
16 | ;;
17 |
18 | esac
19 | done
20 |
21 | if [ -f /etc/os-release ]; then
22 | # freedesktop.org and systemd
23 | . /etc/os-release
24 | OS=$NAME
25 | VER=$VERSION_ID
26 | elif type lsb_release >/dev/null 2>&1; then
27 | # linuxbase.org
28 | OS=$(lsb_release -si)
29 | VER=$(lsb_release -sr)
30 | elif [ -f /etc/lsb-release ]; then
31 | # For some versions of Debian/Ubuntu without lsb_release command
32 | . /etc/lsb-release
33 | OS=$DISTRIB_ID
34 | VER=$DISTRIB_RELEASE
35 | elif [ -f /etc/debian_version ]; then
36 | # Older Debian/Ubuntu/etc.
37 | OS=Debian
38 | VER=$(cat /etc/debian_version)
39 | fi
40 |
41 | cd ${BASEDIR}
42 | error=0
43 | if [[ "${OS}" == "Debian" ]] || [[ "${OS}" == "Ubuntu" ]]; then
44 | if [ $(which -a n | wc -l) -eq 0 ]; then
45 | if [ $INSTALL -eq 1 ]; then
46 | curl -L https://git.io/n-install | bash -s -- -y -q
47 | else
48 | echo "Error: N needs to be installed!"
49 | error=1
50 | fi
51 | fi
52 | fi
53 |
54 | if [ $(which -a npm | wc -l) -eq 0 ]; then
55 | echo "Error: NPM needs to be installed!"
56 | error=1
57 | fi
58 |
59 | if [ $(which -a pkg | wc -l) -eq 0 ]; then
60 | if [ $INSTALL -eq 1 ]; then
61 | npm i -g pkg
62 | else
63 | echo "Error: PKG needs to be installed!"
64 | error=1
65 | fi
66 | fi
67 |
68 | if [ $(which -a release-it | wc -l) -eq 0 ]; then
69 | if [ $INSTALL -eq 1 ]; then
70 | npm i -g release-it
71 | else
72 | echo "Error: RELEASE-IT needs to be installed!"
73 | error=1
74 | fi
75 | fi
76 |
77 | if [ $(which -a yarn | wc -l) -eq 0 ]; then
78 | if [ $INSTALL -eq 1 ]; then
79 | npm i -g yarn
80 | else
81 | echo "Error: YARN needs to be installed!"
82 | error=1
83 | fi
84 | fi
85 |
86 | if [ $(which -a browserify | wc -l) -eq 0 ]; then
87 | if [ $INSTALL -eq 1 ]; then
88 | npm i -g browserify uglifyify
89 | else
90 | echo "Error: browserify needs to be installed!"
91 | error=1
92 | fi
93 | fi
94 |
95 | if [ $error -eq 1 ]; then
96 | exit 1
97 | fi
98 |
99 | if [ $UPDATE -eq 1 ]; then
100 | npm i -g pkg release-it yarn browserify uglifyify
101 | fi
102 |
103 | if [[ "${OS}" == "Ubuntu" ]] && [[ "${VER}" != "19.10" ]]; then
104 | check_lib=$(strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX_3.4.26 | wc -l)
105 |
106 | if [ $check_lib -eq 0 ]; then
107 | add-apt-repository ppa:ubuntu-toolchain-r/test -y >/dev/null 2>&1
108 | apt-get -qq update -y
109 | apt-get -qq upgrade -y
110 | fi
111 |
112 | check_lib=$(strings /usr/lib/x86_64-linux-gnu/libstdc++.so.6 | grep GLIBCXX_3.4.26 | wc -l)
113 |
114 | if [ $check_lib -eq 0 ]; then
115 | echo "Error: Couldn't install required libraries"
116 | exit 1
117 | fi
118 | fi
119 |
120 | yarn
121 | resCode = $?
122 |
123 | yarn clean-css
124 | yarn bundle
125 |
126 | exit $resCode
127 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | workflow:
2 | rules:
3 | - if: $CI_COMMIT_TAG
4 | when: never
5 | - when: always
6 |
7 | stages:
8 | - build
9 | - deploy
10 |
11 | build-windows:
12 | stage: build
13 | tags:
14 | - windows
15 | variables:
16 | GIT_STRATEGY: fetch
17 | GIT_CHECKOUT: "true"
18 | GIT_SSH_COMMAND: "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
19 | script:
20 | - '& "C:\\Program Files\\Git\\bin\\bash.exe" --login -c "./tools/package/ci_compile_windows.sh --force"'
21 | artifacts:
22 | paths:
23 | - release-builds
24 | expire_in: 1 week
25 |
26 | build-linux:
27 | stage: build
28 | tags:
29 | - docker
30 | image: node:18.5
31 | before_script:
32 | - "which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )"
33 | - mkdir -p ~/.ssh
34 | - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
35 | - chmod 700 ~/.ssh/id_rsa
36 | - eval "$(ssh-agent -s)"
37 | - ssh-add ~/.ssh/id_rsa
38 | - ssh-keyscan -t rsa 10.10.10.6 > ~/.ssh/known_hosts
39 | - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
40 | - chmod 644 ~/.ssh/known_hosts
41 | - git remote set-url origin git@10.10.10.6:publicrefinedrd/satisfactoryservermanager.git
42 | - git fetch origin "${CI_COMMIT_SHA}"
43 | - git reset --hard FETCH_HEAD
44 | - "apt-get update -y && apt-get upgrade -y"
45 | script:
46 | - "git submodule update --init"
47 | - npm i -g yarn --force
48 | - npm i -g pkg --force
49 | - npm i -g node-abi --force
50 | - node --version
51 | - "yarn install"
52 | - yarn add node-abi
53 | - "bash tools/package/ci_compile_linux.sh"
54 | artifacts:
55 | paths:
56 | - release-builds
57 | expire_in: 1 week
58 |
59 | deploy-docker:
60 | stage: deploy
61 | image: docker:20-dind
62 | tags:
63 | - docker
64 | services:
65 | - name: docker:20-dind
66 | command: ["--tls=false"]
67 | alias: docker
68 | variables:
69 | GIT_STRATEGY: fetch
70 | GIT_CHECKOUT: "true"
71 | GIT_SSH_COMMAND: "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
72 | DOCKERIMG: "mrhid6/ssmagent"
73 | DOCKER_HOST: tcp://docker:2375
74 | DOCKER_DRIVER: overlay2
75 | DOCKER_TLS_CERTDIR: ""
76 | script:
77 | - sleep 30
78 | - VERSION=$(cat package.json | grep version | awk '{print $2}' | sed -e 's/"//g' | sed -e 's/,//g')
79 | - echo "${DOCKERPASS}" | docker login -u mrhid6 --password-stdin
80 | - docker build . --tag ${DOCKERIMG}:latest --tag ${DOCKERIMG}:${VERSION}
81 | - docker push ${DOCKERIMG}:latest
82 | - docker push ${DOCKERIMG}:${VERSION}
83 |
84 | deploy-release:
85 | stage: deploy
86 | tags:
87 | - windows
88 | variables:
89 | GIT_STRATEGY: fetch
90 | GIT_CHECKOUT: "true"
91 | GIT_SSH_COMMAND: "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
92 | before_script:
93 | - git remote set-url origin git@10.10.10.6:publicrefinedrd/satisfactoryservermanager.git
94 | - git fetch origin "${CI_COMMIT_SHA}"
95 | - git reset --hard FETCH_HEAD
96 | script:
97 | - '& "C:\\Program Files\\Git\\bin\\bash.exe" --login -c "bash ./tools/package/ci_deploy_release.sh"'
98 |
--------------------------------------------------------------------------------
/objects/obj_agent.js:
--------------------------------------------------------------------------------
1 | class ObjectAgent {
2 | constructor(container) {
3 | this._id = -1;
4 | this._name = "";
5 | this._displayname = "";
6 | this._docker_id = -1;
7 | this._ssmport = -1;
8 | this._serverport = -1;
9 | this._beaconport = -1;
10 | this._port = -1;
11 |
12 | this._container = container;
13 |
14 | this._active = false;
15 | this._info = {};
16 | this._memory = 0;
17 | }
18 |
19 | parseDBData(data) {
20 | this._id = data.agent_id;
21 | this._name = data.agent_name;
22 | this._displayname = data.agent_displayname;
23 | this._docker_id = data.agent_docker_id;
24 |
25 | this._ssmport = data.agent_ssm_port;
26 | this._serverport = data.agent_serverport;
27 | this._beaconport = data.agent_beaconport;
28 | this._port = data.agent_port;
29 |
30 | this._running = data.agent_running == 1;
31 | this._active = data.agent_active == 1;
32 | this._info = JSON.parse(data.agent_info || "{}");
33 | this._memory = data.agent_memory;
34 | }
35 |
36 | isActive() {
37 | if (!this.isRunning()) {
38 | return false;
39 | }
40 |
41 | return this._active;
42 | }
43 |
44 | setActive(active) {
45 | this._active = active;
46 | }
47 |
48 | isValid() {
49 | if (this.getDockerId() == null) {
50 | return false;
51 | }
52 |
53 | return true;
54 | }
55 |
56 | isRunning() {
57 | if (!this.isValid()) {
58 | return false;
59 | }
60 |
61 | return this._running;
62 | }
63 |
64 | getName() {
65 | return this._name;
66 | }
67 |
68 | getDisplayName() {
69 | return this._displayname;
70 | }
71 |
72 | getId() {
73 | return this._id;
74 | }
75 |
76 | getDockerId() {
77 | return this._docker_id;
78 | }
79 |
80 | /** Start Ports */
81 |
82 | getSSMPort() {
83 | return this._ssmport;
84 | }
85 |
86 | getServerPort() {
87 | return this._serverport;
88 | }
89 |
90 | getBeaconPort() {
91 | return this._beaconport;
92 | }
93 |
94 | getPort() {
95 | return this._port;
96 | }
97 |
98 | getMemory() {
99 | return this._memory;
100 | }
101 |
102 | /** End Ports */
103 |
104 | getURL() {
105 | const port = this.getSSMPort();
106 | this._url = "http://localhost:" + port + "/agent/";
107 |
108 | return this._url;
109 | }
110 |
111 | setInfo(info) {
112 | this._info = info;
113 | }
114 |
115 | getInfo() {
116 | return this._info;
117 | }
118 |
119 | getWebJson() {
120 | return {
121 | running: this.isRunning(),
122 | active: this.isActive(),
123 | id: this.getId(),
124 | name: this.getName(),
125 | displayname: this.getDisplayName(),
126 | memorylimit: this.getMemory(),
127 | ports: {
128 | AgentPort: this.getSSMPort(),
129 | ServerQueryPort: this.getServerPort(),
130 | BeaconPort: this.getBeaconPort(),
131 | ServerPort: this.getPort(),
132 | },
133 | info: this.getInfo(),
134 | };
135 | }
136 | }
137 |
138 | module.exports = ObjectAgent;
139 |
--------------------------------------------------------------------------------
/test/test5.js:
--------------------------------------------------------------------------------
1 | const Config = require("../server/server_config");
2 | const DB = require("../server/server_db");
3 | const logger = require("../server/server_logger");
4 | const ObjUser = require("../objects/obj_user");
5 | const ObjUserRole = require("../objects/obj_user_role");
6 |
7 | Config.load()
8 | .then(() => {
9 | return DB.init();
10 | })
11 | .then(() => {
12 | GetAllUsersFromDB();
13 | })
14 | .catch((err) => {
15 | logger.error(err.message);
16 | });
17 |
18 | function GetAllUsersFromDB() {
19 | return new Promise((resolve, reject) => {
20 | DB.query("SELECT * FROM users")
21 | .then((rows) => {
22 | const USERS = [];
23 | const UserRolePromises = [];
24 |
25 | rows.forEach((row) => {
26 | const User = new ObjUser();
27 | User.parseDBData(row);
28 | UserRolePromises.push(GetRoleForUser(User));
29 | USERS.push(User);
30 | });
31 |
32 | Promise.all(UserRolePromises).then((values) => {
33 | for (let i = 0; i < values.length; i++) {
34 | const role = values[i];
35 | USERS[i].SetRole(role);
36 | }
37 |
38 | console.log(JSON.stringify(USERS, null, 4));
39 | });
40 | })
41 | .catch((err) => {
42 | console.log(err);
43 | });
44 | });
45 | }
46 |
47 | /**
48 | * @param {ObjUser} User
49 | */
50 | function GetRoleForUser(User) {
51 | return new Promise((resolve, reject) => {
52 | DB.querySingle("SELECT * FROM roles WHERE role_id=?", [
53 | User.getRoleId(),
54 | ]).then((roleRow) => {
55 | const Role = new ObjUserRole();
56 | Role.parseDBData(roleRow);
57 |
58 | const perms = JSON.parse(roleRow.role_permissions);
59 | const permPromises = [];
60 |
61 | perms.forEach((perm) => {
62 | perm = perm.replace("*", "%");
63 | permPromises.push(GetPermissions(perm));
64 | });
65 |
66 | Promise.all(permPromises).then((values) => {
67 | const Permissions = [];
68 | for (let i = 0; i < values.length; i++) {
69 | const perms = values[i];
70 |
71 | for (let j = 0; j < perms.length; j++) {
72 | const perm = perms[j];
73 | if (Permissions.includes(perm) == false) {
74 | Permissions.push(perm);
75 | }
76 | }
77 | }
78 |
79 | Role.SetPermissions(Permissions);
80 | resolve(Role);
81 | });
82 | });
83 | });
84 | }
85 |
86 | function GetPermissions(PermissionShort) {
87 | return new Promise((resolve, reject) => {
88 | const Permissions = [];
89 | DB.query("SELECT * FROM permissions WHERE perm_name LIKE ?", [
90 | PermissionShort,
91 | ]).then((permRows) => {
92 | permRows.forEach((permRow) => {
93 | if (Permissions.includes(permRow.perm_name) == false) {
94 | Permissions.push(permRow.perm_name);
95 | }
96 | });
97 |
98 | resolve(Permissions);
99 | });
100 | });
101 | }
102 |
--------------------------------------------------------------------------------
/objects/errors/error_steamcmdrun.js:
--------------------------------------------------------------------------------
1 | class SteamCMDError extends Error {
2 | constructor(exitCode) {
3 | // Auto-generate the error message and send it to the super class.
4 | // noinspection JSCheckFunctionSignatures
5 | super(getErrorMessage(exitCode));
6 |
7 | this.name = "SteamCmdError";
8 | this.exitCode = exitCode;
9 | }
10 | }
11 |
12 | function getErrorMessage(exitCode) {
13 | switch (exitCode) {
14 | case EXIT_CODES.NO_ERROR:
15 | return "No error";
16 | case EXIT_CODES.UNKNOWN_ERROR:
17 | return "An unknown error occurred";
18 | case EXIT_CODES.ALREADY_LOGGED_IN:
19 | return "A user was already logged into SteamCMD";
20 | case EXIT_CODES.NO_CONNECTION:
21 | return "SteamCMD cannot connect to the internet";
22 | case EXIT_CODES.INVALID_PASSWORD:
23 | return "Invalid password";
24 | case EXIT_CODES.FAILED_TO_INSTALL:
25 | return (
26 | "The application failed to install for some reason. Reasons " +
27 | "include: you do not own the application, you do not have enough " +
28 | "hard drive space, a network error occurred, or the application " +
29 | "is not available for your selected platform."
30 | );
31 | case EXIT_CODES.MISSING_PARAMETERS_OR_NOT_LOGGED_IN:
32 | return (
33 | "One of your commands has missing parameters or you are not " +
34 | "logged in"
35 | );
36 | case EXIT_CODES.STEAM_GUARD_CODE_REQUIRED:
37 | return "A Steam Guard code was required to log in";
38 | // It is still unknown what exit code 7 means. That's why the error
39 | // message is still the default one.
40 | case EXIT_CODES.INITIALIZED:
41 | default:
42 | return `An unknown error occurred. Exit code: ${exitCode}`;
43 | }
44 | }
45 |
46 | const EXIT_CODES = {
47 | /**
48 | * Indicates that SteamCMD exited normally.
49 | */
50 | NO_ERROR: 0,
51 | /**
52 | * Indicates that Steam CMD had to quit due to some unknown error. This can
53 | * also indicate that the process was forcefully terminated.
54 | */
55 | UNKNOWN_ERROR: 1,
56 | /**
57 | * Indicates that the user attempted to login while another user was already
58 | * logged in.
59 | */
60 | ALREADY_LOGGED_IN: 2,
61 | /**
62 | * Indicates that SteamCMD has no connection to the internet.
63 | */
64 | NO_CONNECTION: 3,
65 | /**
66 | * Indicates that an incorrect password was provided.
67 | */
68 | INVALID_PASSWORD: 5,
69 | /**
70 | * On Windows this is returned only on the first run after the Steam CMD
71 | * executable has been downloaded. It is unknown what this error code
72 | * actually means.
73 | */
74 | INITIALIZED: 7,
75 | /**
76 | * Indicates that the application that you tried to update failed to
77 | * install. This can happen if you don't own it, if you don't have enough
78 | * hard drive space, if a network error occurred, or if the application is
79 | * not available for your selected platform.
80 | */
81 | FAILED_TO_INSTALL: 8,
82 | /**
83 | * Indicates that a command had missing parameters or that the user is not
84 | * logged in.
85 | */
86 | MISSING_PARAMETERS_OR_NOT_LOGGED_IN: 10,
87 | /**
88 | * Indicated that a Steam guard code is required before the login can
89 | * finish.
90 | */
91 | STEAM_GUARD_CODE_REQUIRED: 63,
92 | };
93 |
94 | module.exports.EXIT_CODES = EXIT_CODES;
95 | module.exports.SteamCMDError = SteamCMDError;
96 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SatisfactoryServerManager",
3 | "version": "1.1.34",
4 | "main": "app.js",
5 | "repository": "git@github.com:mrhid6/SatisfactoryServerManager.git",
6 | "author": "Mrhid6
",
7 | "license": "MIT",
8 | "dependencies": {
9 | "@electerm/strip-ansi": "^1.0.0",
10 | "@fortawesome/fontawesome-free": "^6.1.1",
11 | "archiver": "^5.3.1",
12 | "axios": "^1.1.3",
13 | "better-sqlite3": "^8.0.0",
14 | "binary-reader": "^0.1.2",
15 | "blueimp-file-upload": "^10.32.0",
16 | "body-parser": "^1.20.0",
17 | "bootstrap": "^5.1.3",
18 | "bootstrap4-toggle": "^3.6.1",
19 | "chmodr": "^1.2.0",
20 | "connect-fs2": "^0.1.8",
21 | "cookie-parser": "^1.4.6",
22 | "cors": "^2.8.5",
23 | "crypto-js": "^4.1.1",
24 | "datatables.net-bs5": "^1.12.1",
25 | "discord-webhook-node": "^1.1.8",
26 | "event-stream": "^4.0.1",
27 | "express": "^4.18.1",
28 | "express-handlebars": "^6.0.6",
29 | "express-session": "^1.17.3",
30 | "extract-zip": "^2.0.1",
31 | "form-data": "^4.0.0",
32 | "fs-extra": "^11.1.0",
33 | "fs-reverse": "^0.0.3",
34 | "github-api": "3.4.0",
35 | "graphql": "^16.5.0",
36 | "graphql-request": "^5.0.0",
37 | "isomorphic-fetch": "^3.0.0",
38 | "jquery": "^3.6.0",
39 | "jquery-circle-progress": "^1.2.2",
40 | "jquery-steps": "^1.1.0",
41 | "method-override": "^3.0.0",
42 | "mime-type": "^4.0.0",
43 | "moment": "^2.29.3",
44 | "mrhid6utils": "^1.0.6",
45 | "multer": "^1.4.5-lts.1",
46 | "node-abi": "^3.28.0",
47 | "node-docker-api": "^1.1.22",
48 | "node-pty": "^0.10.1",
49 | "node-schedule": "^2.1.0",
50 | "node-stream-zip": "^1.15.0",
51 | "platform-folders": "0.6.0",
52 | "popper.js": "^1.16.1",
53 | "recursive-readdir": "^2.2.2",
54 | "recursive-readdir-async": "^1.2.1",
55 | "rimraf": "^3.0.2",
56 | "satisfactory-json": "^0.0.63",
57 | "semver": "^7.3.7",
58 | "simple-node-logger": "^21.8.12",
59 | "systeminformation": "^5.12.3",
60 | "tar": "^6.1.11",
61 | "tmp-promise": "^3.0.3",
62 | "toastr": "^2.1.4",
63 | "vdf": "^0.0.2"
64 | },
65 | "scripts": {
66 | "start": "node app.js",
67 | "watch": "watchify src/app.js -o public/js/bundle.js",
68 | "bundle": "browserify -g uglifyify src/app.js -o public/js/bundle.js",
69 | "clean-css": "bash tools/clean-css.sh"
70 | },
71 | "resolutions": {
72 | "graceful-fs": "^4.2.10"
73 | },
74 | "pkg": {
75 | "assets": [
76 | "views/**/*",
77 | "public/**/*",
78 | "scripts/**/*.sh",
79 | "assets/**/*",
80 | "node_modules/jquery/dist/**/*",
81 | "node_modules/jquery-steps/build/**/*",
82 | "node_modules/@fortawesome/fontawesome-free/css/**/*",
83 | "node_modules/@fortawesome/fontawesome-free/webfonts/**/*",
84 | "node_modules/moment/min/**/*",
85 | "node_modules/toastr/build/**/*",
86 | "node_modules/ts-invariant/**/*",
87 | "node_modules/exiftool-vendored.exe/**/*",
88 | "node_modules/exiftool-vendored/**/*",
89 | "node_modules/exiftool-vendored.pl/**/*",
90 | "node_modules/jquery-circle-progress/**/*",
91 | "node_modules/axios/**/*",
92 | "db_updates/**/*"
93 | ],
94 | "targets": [
95 | "node16"
96 | ]
97 | },
98 | "devDependencies": {
99 | "uglifyify": "^5.0.2"
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/tools/package/package_linux.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CURDIR=$(dirname "$(readlink -f "$0")")
4 | BASEDIR=$(readlink -f "$CURDIR/../..")
5 |
6 | VERSION=""
7 | COMPILEONLY=0
8 |
9 | while [[ $# -gt 0 ]]; do
10 | key="$1"
11 |
12 | case $key in
13 | --version)
14 | VERSION="$2"
15 | shift # past value
16 | shift # past value
17 | ;;
18 | --compile)
19 | COMPILEONLY=1
20 | shift # past value
21 | ;;
22 | esac
23 | done
24 |
25 | . ${BASEDIR}/tools/variables.sh
26 | . ${BASEDIR}/tools/app_config.txt
27 |
28 | echo -en "\nPackaging Linux (Version: \e[34m${VERSION}\e[0m)\n"
29 |
30 | release_dir="${BASEDIR}/release-builds"
31 | release_dir_linux="${release_dir}/linux"
32 |
33 | rm -rf ${release_dir_linux} 2>&1 >/dev/null
34 |
35 | if [ ! -d "${release_dir_linux}" ]; then
36 | mkdir -p "${release_dir_linux}"
37 | fi
38 |
39 | if [ ${COMPILEONLY} -eq 0 ]; then
40 |
41 | echo "* Building Linux Executables: "
42 |
43 | printDots "* Cleaning Build Folder" 30
44 | sshargs="mkdir -p /nodejs/build >/dev/null 2>&1; \
45 | cd /nodejs/build; \
46 | rm -r SSM; >/dev/null 2>&1\
47 | "
48 |
49 | ${SSH_CMD} root@${LINUX_SERVER} "${sshargs}" >/dev/null 2>&1
50 | echo -en "\e[32m✔\e[0m\n"
51 |
52 | printDots "* Cloning SSM Repo" 30
53 | sshargs="cd /nodejs/build; \
54 | git clone https://github.com/mrhid6/SatisfactoryServerManager.git SSM; \
55 | exit \$?
56 | "
57 | ${SSH_CMD} root@${LINUX_SERVER} "${sshargs}" >/dev/null 2>&1
58 | if [ $? -ne 0 ]; then
59 | echo -en "\e[31m✘\e[0m\n"
60 | exit 1
61 | else
62 | echo -en "\e[32m✔\e[0m\n"
63 | fi
64 |
65 | printDots "* Building App" 30
66 | sshargs="PATH+=:/root/n/bin; \
67 | cd /nodejs/build/SSM; \
68 | bash ./tools/build_app.sh -i -u; \
69 | exit \$?
70 | "
71 | ${SSH_CMD} root@${LINUX_SERVER} "${sshargs}" >/dev/null 2>&1
72 |
73 | if [ $? -ne 0 ]; then
74 | echo -en "\e[31m✘\e[0m\n"
75 | exit 1
76 | else
77 | echo -en "\e[32m✔\e[0m\n"
78 | fi
79 |
80 | sshargs="PATH+=:/root/n/bin; \
81 | cd /nodejs/build/SSM; \
82 | bash ./tools/package/package_linux.sh --version ${VERSION} --compile; \
83 | exit \$?
84 | "
85 | ${SSH_CMD} root@${LINUX_SERVER} "${sshargs}"
86 |
87 | ${SCP_CMD} root@${LINUX_SERVER}:/nodejs/build/SSM/release-builds/linux/* ${release_dir_linux}/.
88 |
89 | DOCKERIMG="mrhid6/ssmagent"
90 |
91 | echo "${DOCKER_PASS}" | docker login -u mrhid6 --password-stdin
92 | docker build -t ${DOCKERIMG}:latest ${BASEDIR}/.
93 |
94 | docker tag ${DOCKERIMG}:latest ${DOCKERIMG}:${VERSION}
95 |
96 | docker push ${DOCKERIMG}:latest
97 | docker push ${DOCKERIMG}:${VERSION}
98 |
99 | else
100 | release_dir="${BASEDIR}/release-builds"
101 | release_dir_linux="${release_dir}/linux"
102 |
103 | rm -rf ${release_dir} 2>&1 >/dev/null
104 |
105 | if [ ! -d "${release_dir}" ]; then
106 | mkdir -p "${release_dir_linux}"
107 | fi
108 |
109 | printDots "* Copying Linux Executables" 30
110 |
111 | find ${BASEDIR} -name "*.node" | grep -v "release-builds" | grep -v "obj.target" >${release_dir_linux}/exe.list
112 |
113 | while read -r line; do
114 | cp ${line} ${release_dir_linux}/.
115 | done <${release_dir_linux}/exe.list
116 | rm ${release_dir_linux}/exe.list
117 |
118 | printDots "* Compiling Linux" 30
119 |
120 | pkg app.js -c package.json -t node16-linux-x64 --out-path ${release_dir_linux} -d >${release_dir_linux}/build.log
121 |
122 | if [ $? -ne 0 ]; then
123 | echo -en "\e[31m✘\e[0m\n"
124 | exit 1
125 | else
126 | echo -en "\e[32m✔\e[0m\n"
127 | fi
128 |
129 | fi
130 |
--------------------------------------------------------------------------------
/public/modals/create-server-modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
19 |
35 |
36 |
53 |
54 |
55 |
56 | Server Memory:
57 |
58 |
59 |
62 |
71 | 1.0G
76 |
77 |
78 |
79 |
80 |
81 |
85 | Add Server
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/routes/api/admin.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var router = express.Router();
3 |
4 | const ServerApp = require("../../server/server_app");
5 | const Config = require("../../server/server_config");
6 |
7 | const middleWare = [ServerApp.checkLoggedInAPIMiddleWare];
8 |
9 | router.post("/adduser", middleWare, (req, res) => {
10 | const post = req.body;
11 |
12 | ServerApp.API_CreateUser(post)
13 | .then(() => {
14 | res.json({
15 | result: "success",
16 | });
17 | })
18 | .catch((err) => {
19 | res.json({
20 | result: "error",
21 | error: err.message,
22 | });
23 | });
24 | });
25 |
26 | router.post("/generatedebugreport", middleWare, (req, res) => {
27 | const post = req.body;
28 |
29 | ServerApp.API_GenerateDebugReport(post)
30 | .then(() => {
31 | res.json({
32 | result: "success",
33 | });
34 | })
35 | .catch((err) => {
36 | res.json({
37 | result: "error",
38 | error: err.message,
39 | });
40 | });
41 | });
42 |
43 | router.get("/debugreports", middleWare, (req, res) => {
44 | ServerApp.API_GetDebugReports()
45 | .then((rows) => {
46 | res.json({
47 | result: "success",
48 | data: rows || [],
49 | });
50 | })
51 | .catch((err) => {
52 | res.json({
53 | result: "error",
54 | error: err.message,
55 | });
56 | });
57 | });
58 |
59 | router.post("/debugreport/download", middleWare, (req, res) => {
60 | const UserID = req.session.userid;
61 |
62 | ServerApp.API_DownloadDebugReportFile(req.body, UserID)
63 | .then((saveFile) => {
64 | res.download(saveFile.dr_path);
65 | })
66 | .catch((err) => {
67 | res.json({
68 | result: "error",
69 | error: err.message,
70 | });
71 | });
72 | });
73 |
74 | router.post("/debugreport/remove", middleWare, (req, res) => {
75 | ServerApp.API_RemoveDebugReportFile(req.body)
76 | .then(() => {
77 | res.json({
78 | result: "success",
79 | });
80 | })
81 | .catch((err) => {
82 | res.json({
83 | result: "error",
84 | error: err.message,
85 | });
86 | });
87 | });
88 |
89 | router.post("/addwebhook", middleWare, (req, res) => {
90 | ServerApp.API_AddWebhook(req.body)
91 | .then(() => {
92 | res.json({
93 | result: "success",
94 | });
95 | })
96 | .catch((err) => {
97 | res.json({
98 | result: "error",
99 | error: err.message,
100 | });
101 | });
102 | });
103 |
104 | router.post("/addapikey", middleWare, (req, res) => {
105 | ServerApp.API_AddAPIKey(req.body)
106 | .then(() => {
107 | res.json({
108 | result: "success",
109 | });
110 | })
111 | .catch((err) => {
112 | res.json({
113 | result: "error",
114 | error: err.message,
115 | });
116 | });
117 | });
118 |
119 | router.post("/revokeapikey", middleWare, (req, res) => {
120 | ServerApp.API_RevokeAPIKey(req.body)
121 | .then(() => {
122 | res.json({
123 | result: "success",
124 | });
125 | })
126 | .catch((err) => {
127 | res.json({
128 | result: "error",
129 | error: err.message,
130 | });
131 | });
132 | });
133 |
134 | module.exports = router;
135 |
--------------------------------------------------------------------------------
/server/server_docker_api.js:
--------------------------------------------------------------------------------
1 | const iDockerHelper = require("mrhid6utils").DockerHelper;
2 | const platform = process.platform;
3 |
4 | const { Docker } = require("node-docker-api");
5 |
6 | const DockerHelper = new iDockerHelper();
7 |
8 | class DockerAPI {
9 | constructor() {}
10 |
11 | ConnectDocker = async () => {
12 | let dockerSettings = {
13 | host: "http://127.0.0.1",
14 | port: 2375,
15 | };
16 |
17 | if (platform != "win32") {
18 | dockerSettings = {
19 | socketPath: "/var/run/docker.sock",
20 | };
21 | }
22 |
23 | return new Docker(dockerSettings);
24 | };
25 |
26 | PullDockerImage = async () => {
27 | const DockerConnection = await this.ConnectDocker();
28 | await DockerHelper.PullDockerImage(
29 | DockerConnection,
30 | "mrhid6/ssmagent",
31 | "latest",
32 | {}
33 | );
34 | };
35 |
36 | CheckDockerContainerExists = async (ContainerID) => {
37 | const DockerConnection = await this.ConnectDocker();
38 | return await DockerHelper.CheckDockerContainerExists(
39 | DockerConnection,
40 | ContainerID
41 | );
42 | };
43 |
44 | CheckDockerContainerExistsWithName = async (ContainerName) => {
45 | const DockerConnection = await this.ConnectDocker();
46 | return await DockerHelper.CheckDockerContainerExistsWithName(
47 | DockerConnection,
48 | ContainerName
49 | );
50 | };
51 |
52 | GetDockerContainersWithName = async (ContainerName) => {
53 | const DockerConnection = await this.ConnectDocker();
54 | return await DockerHelper.GetDockerContainersWithName(
55 | DockerConnection,
56 | ContainerName
57 | );
58 | };
59 |
60 | GetDockerContainerByID = async (ContainerID) => {
61 | const DockerConnection = await this.ConnectDocker();
62 | return await DockerHelper.GetDockerContainerByID(
63 | DockerConnection,
64 | ContainerID
65 | );
66 | };
67 |
68 | CreateDockerContainer = async (ContainerSettings) => {
69 | const DockerConnection = await this.ConnectDocker();
70 | await this.PullDockerImage();
71 | return await DockerHelper.CreateDockerContainer(
72 | DockerConnection,
73 | ContainerSettings,
74 | true
75 | );
76 | };
77 |
78 | StartDockerContainer = async (ContainerID) => {
79 | const DockerConnection = await this.ConnectDocker();
80 | return await DockerHelper.StartDockerContainer(
81 | DockerConnection,
82 | ContainerID
83 | );
84 | };
85 |
86 | StopDockerContainer = async (ContainerID) => {
87 | const DockerConnection = await this.ConnectDocker();
88 | return await DockerHelper.StopDockerContainer(
89 | DockerConnection,
90 | ContainerID
91 | );
92 | };
93 |
94 | WaitForContainerToStart = async (ContainerID) => {
95 | const DockerConnection = await this.ConnectDocker();
96 | await DockerHelper.WaitForContainerToStart(
97 | DockerConnection,
98 | ContainerID
99 | );
100 | };
101 |
102 | WaitForContainerToStop = async (ContainerID) => {
103 | const DockerConnection = await this.ConnectDocker();
104 | await DockerHelper.WaitForContainerToStop(
105 | DockerConnection,
106 | ContainerID
107 | );
108 | };
109 |
110 | DeleteDockerContainerById = async (ContainerID) => {
111 | const DockerConnection = await this.ConnectDocker();
112 | await DockerHelper.DeleteDockerContainerById(
113 | DockerConnection,
114 | ContainerID
115 | );
116 | };
117 | }
118 |
119 | const dockerAPI = new DockerAPI();
120 | module.exports = dockerAPI;
121 |
--------------------------------------------------------------------------------
/server/server_agent_app.js:
--------------------------------------------------------------------------------
1 | const Config = require("./server_config");
2 | const logger = require("./server_logger");
3 |
4 | const SFS_Handler = require("./ms_agent/server_sfs_handler");
5 | const SSM_Mod_Handler = require("./ms_agent/server_new_mod_handler");
6 | const SSM_Log_Handler = require("./server_log_handler");
7 | const GameConfig = require("./ms_agent/server_gameconfig");
8 | const SSM_BackupManager = require("./ms_agent/server_backup_manager");
9 |
10 | const AgentDB = require("./ms_agent/server_agent_db");
11 |
12 | const StatsManager = require("./ms_agent/server_stats_manager");
13 |
14 | const path = require("path");
15 | const fs = require("fs-extra");
16 |
17 | const rimraf = require("rimraf");
18 |
19 | class AgentApp {
20 | constructor() {}
21 |
22 | init = async () => {
23 | try {
24 | await AgentDB.init();
25 |
26 | await SFS_Handler.init();
27 | await SSM_Mod_Handler.init();
28 |
29 | SSM_BackupManager.init();
30 |
31 | await StatsManager.init();
32 | } catch (err) {
33 | logger.error(
34 | "[SFS_HANDLER] - Failed To Initialize - " + err.message
35 | );
36 | }
37 |
38 | this.SetupEventHandlers();
39 | };
40 |
41 | SetupEventHandlers() {}
42 |
43 | AgentConfig() {
44 | const ssmConfig = Config.get("ssm");
45 | const sfConfig = Config.get("satisfactory");
46 | const modsConfig = Config.get("mods");
47 | let gameConfig = {};
48 |
49 | if (GameConfig.getEngineConfig() != null) {
50 | gameConfig = {
51 | Engine: GameConfig.getEngineConfig().get(),
52 | Game: GameConfig.getGameConfig().get(),
53 | };
54 | }
55 |
56 | const ssmConfig_clone = JSON.parse(JSON.stringify(ssmConfig));
57 | const sfConfig_clone = Object.assign(
58 | Object.create(Object.getPrototypeOf(sfConfig)),
59 | sfConfig
60 | );
61 |
62 | delete ssmConfig_clone.users;
63 | delete ssmConfig_clone.agent;
64 | delete ssmConfig_clone.steamcmd;
65 | delete ssmConfig_clone.tempdir;
66 | delete ssmConfig_clone.github_version;
67 |
68 | delete sfConfig_clone.server_exe;
69 | delete sfConfig_clone.server_sub_exe;
70 |
71 | return {
72 | satisfactory: sfConfig_clone,
73 | ssm: ssmConfig_clone,
74 | mods: modsConfig,
75 | game: gameConfig,
76 | };
77 | }
78 |
79 | API_GetInfo = async () => {
80 | try {
81 | const resData = {
82 | version: Config.get("ssm.version"),
83 | config: this.AgentConfig(),
84 | };
85 |
86 | const serverstate = await SFS_Handler.getServerStatus();
87 |
88 | resData.serverstate = serverstate;
89 | delete resData.serverstate.pid1;
90 | delete resData.serverstate.pid2;
91 |
92 | const usercount = await this.GetUserCount();
93 | resData.usercount = usercount;
94 |
95 | const mods = await SSM_Mod_Handler.API_GetInstalledMods();
96 | resData.mods = mods;
97 |
98 | const stats = await StatsManager.API_ToJson();
99 | resData.stats = stats;
100 |
101 | return resData;
102 | } catch (err) {
103 | throw err;
104 | }
105 | };
106 |
107 | GetUserCount = async () => {
108 | return SSM_Log_Handler._UserCount;
109 | };
110 |
111 | API_DeleteSave(data) {
112 | return new Promise((resolve, reject) => {
113 | SFS_Handler.deleteSaveFile(data.savefile)
114 | .then(() => {
115 | resolve();
116 | })
117 | .catch((err) => {
118 | reject(err);
119 | });
120 | });
121 | }
122 | }
123 |
124 | const agentApp = new AgentApp();
125 |
126 | module.exports = agentApp;
127 |
--------------------------------------------------------------------------------
/src/cache.js:
--------------------------------------------------------------------------------
1 | const logger = require("./logger");
2 |
3 | const EventEmitter = require("events");
4 | const { stringify } = require("querystring");
5 |
6 | class PageCache extends EventEmitter {
7 | constructor() {
8 | super();
9 |
10 | this.AgentList = [];
11 | this.ActiveAgent = null;
12 | this.SMLVersions = [];
13 | this.FicsitMods = GetLocalStorage("FicsitMods", []);
14 | this.InstalledMods = [];
15 | }
16 |
17 | setAgentsList(agentList) {
18 | this.AgentList = agentList;
19 | this.setActiveAgent(getCookie("currentAgentId"));
20 | this.emit("setagentslist");
21 | }
22 |
23 | getAgentsList() {
24 | return this.AgentList;
25 | }
26 |
27 | setActiveAgent(id) {
28 | if (id == null) {
29 | return;
30 | }
31 |
32 | const Agent = this.getAgentsList().find((agent) => agent.id == id);
33 |
34 | if (Agent == null && this.getAgentsList().length > 0) {
35 | this.ActiveAgent = this.getAgentsList()[0];
36 | setCookie("currentAgentId", this.ActiveAgent.id, 10);
37 | this.emit("setactiveagent");
38 | return;
39 | } else if (Agent == null) {
40 | return;
41 | }
42 |
43 | if (this.ActiveAgent != null && this.ActiveAgent.id == Agent.id) {
44 | return;
45 | }
46 |
47 | setCookie("currentAgentId", id, 10);
48 |
49 | this.ActiveAgent = Agent;
50 | this.emit("setactiveagent");
51 | }
52 |
53 | getActiveAgent() {
54 | return this.ActiveAgent;
55 | }
56 |
57 | setSMLVersions(versions) {
58 | this.SMLVersions = versions;
59 | this.emit("setsmlversions");
60 | }
61 |
62 | getSMLVersions() {
63 | return this.SMLVersions;
64 | }
65 |
66 | setFicsitMods(mods) {
67 | this.FicsitMods = mods;
68 |
69 | const StorageData = {
70 | data: this.FicsitMods,
71 | };
72 |
73 | StoreInLocalStorage("FicsitMods", StorageData, 1);
74 | this.emit("setficsitmods");
75 | }
76 |
77 | getFicsitMods() {
78 | return this.FicsitMods;
79 | }
80 |
81 | getAgentInstalledMods() {
82 | return this.InstalledMods;
83 | }
84 |
85 | SetAgentInstalledMods(mods) {
86 | this.InstalledMods = mods;
87 | this.emit("setinstalledmods");
88 | }
89 | }
90 |
91 | function StoreInLocalStorage(Key, Data, ExpiryHrs) {
92 | var date = new Date();
93 | date.setTime(date.getTime() + ExpiryHrs * 60 * 60 * 1000);
94 | Data.expiry = date.getTime();
95 |
96 | const DataStr = JSON.stringify(Data);
97 |
98 | localStorage.setItem(Key, DataStr);
99 | }
100 |
101 | function RemoveLocalStorage(Key) {
102 | localStorage.removeItem(Key);
103 | }
104 |
105 | function GetLocalStorage(Key, defaultReturn) {
106 | const LSdata = localStorage.getItem(Key);
107 | const data = JSON.parse(LSdata);
108 |
109 | if (data == null) {
110 | return defaultReturn;
111 | }
112 |
113 | var date = new Date();
114 | if (date.getTime() > data.expiry) {
115 | RemoveLocalStorage(Key);
116 | return defaultReturn;
117 | }
118 |
119 | return data.data;
120 | }
121 |
122 | function setCookie(name, value, days) {
123 | var expires = "";
124 | if (days) {
125 | var date = new Date();
126 | date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
127 | expires = "; expires=" + date.toUTCString();
128 | }
129 | document.cookie = name + "=" + (value || "") + expires + "; path=/";
130 | }
131 |
132 | function getCookie(name) {
133 | var nameEQ = name + "=";
134 | var ca = document.cookie.split(";");
135 | for (var i = 0; i < ca.length; i++) {
136 | var c = ca[i];
137 | while (c.charAt(0) == " ") c = c.substring(1, c.length);
138 | if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
139 | }
140 | return null;
141 | }
142 |
143 | const pageCache = new PageCache();
144 |
145 | module.exports = pageCache;
146 |
--------------------------------------------------------------------------------
/views/logs.hbs:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
22 | This message should never show, There is an error
23 | somewhere!
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 | This message should never show, There is an error
40 | somewhere!
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
51 |
52 |
53 |
54 |
55 |
56 | This message should never show, There is an error
57 | somewhere!
58 |
59 |
60 |
61 |
62 |
90 |
91 |
92 |
93 |
96 |
97 |
98 |
99 |
100 |
101 | This message should never show, There is an error
102 | somewhere!
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/routes/api/public.js:
--------------------------------------------------------------------------------
1 | var express = require("express");
2 | var router = express.Router();
3 |
4 | const AgentHandler = require("../../server/server_agent_handler");
5 | const Config = require("../../server/server_config");
6 |
7 | const UserManager = require("../../server/server_user_manager");
8 |
9 | router.get("/servers", checkHeaderKey, async (req, res, next) => {
10 | try {
11 | await checkAPIPermissions("api.servers", req.apikey);
12 | } catch (err) {
13 | res.json({
14 | result: "error",
15 | error: err.message,
16 | });
17 | return;
18 | }
19 |
20 | try {
21 | const agents = await AgentHandler.API_GetAllAgents();
22 | const resData = [];
23 |
24 | for (let i = 0; i < agents.length; i++) {
25 | const agent = agents[i];
26 | const agent_clone = JSON.parse(JSON.stringify(agent));
27 |
28 | if (agent_clone.running == true && agent_clone.active == true) {
29 | agent_clone.info.MaxPlayers = 0;
30 |
31 | if (agent_clone.info.config.game.Game != null) {
32 | agent_clone.info.MaxPlayers =
33 | agent_clone.info.config.game.Game[
34 | "/Script/Engine"
35 | ].GameSession.MaxPlayers;
36 | }
37 | delete agent_clone.info.config.game;
38 |
39 | delete agent_clone.info.config.satisfactory;
40 | delete agent_clone.info.config.ssm;
41 | delete agent_clone.info.config.smm;
42 | //delete agent_clone.info.config.mods
43 |
44 | delete agent_clone.ports.AgentPort;
45 | delete agent_clone.ports.BeaconPort;
46 | delete agent_clone.ports.ServerPort;
47 | delete agent_clone.info.serverstate.pid1;
48 | delete agent_clone.info.serverstate.pid2;
49 | delete agent_clone.info.serverstate.pcpu;
50 | delete agent_clone.info.serverstate.pmem;
51 | }
52 |
53 | resData.push(agent_clone);
54 | }
55 |
56 | res.json({
57 | result: "success",
58 | data: resData,
59 | });
60 | } catch (err) {
61 | res.json({
62 | result: "error",
63 | error: err.message,
64 | });
65 | return;
66 | }
67 | });
68 |
69 | router.post("/serveraction", checkHeaderKey, async (req, res, next) => {
70 | try {
71 | await checkAPIPermissions("api.serveractions", req.apikey);
72 | } catch (err) {
73 | res.json({
74 | result: "error",
75 | error: err.message,
76 | });
77 | return;
78 | }
79 |
80 | const body = req.body;
81 |
82 | if (body.agentid == null || body.agentid == "") {
83 | res.json({
84 | result: "error",
85 | error: "Body doesn't include 'agentid' property!",
86 | });
87 | return;
88 | }
89 |
90 | if (body.action == null || body.action == "") {
91 | res.json({
92 | result: "error",
93 | error: "Body doesn't include 'action' property!",
94 | });
95 | return;
96 | }
97 |
98 | try {
99 | const UserAccount = await UserManager.GetUserFromAPIKey(req.apikey);
100 | await AgentHandler.API_ExecuteServerAction(body, UserAccount.getId());
101 |
102 | res.json({
103 | result: "success",
104 | });
105 | } catch (err) {
106 | res.json({
107 | result: "error",
108 | error: err.message,
109 | });
110 | return;
111 | }
112 | });
113 |
114 | const checkAPIPermissions = async (permission, key) => {
115 | try {
116 | await UserManager.CheckAPIUserHasPermission(permission, key);
117 | } catch (err) {
118 | throw err;
119 | }
120 | };
121 |
122 | function checkHeaderKey(req, res, next) {
123 | const headers = req.headers;
124 | const headerKey = headers["x-ssm-key"];
125 | if (headerKey == null) {
126 | console.log("Key is null : ", req.originalUrl);
127 | res.json({
128 | success: false,
129 | error: "Key is null!",
130 | });
131 | return;
132 | }
133 |
134 | UserManager.CheckAPIKeyIsValid(headerKey).then((valid) => {
135 | if (valid) {
136 | req.apikey = headerKey;
137 | next();
138 | return;
139 | } else {
140 | res.json({
141 | success: false,
142 | error: "Key mismatch!",
143 | });
144 | return;
145 | }
146 | });
147 | }
148 |
149 | module.exports = router;
150 |
--------------------------------------------------------------------------------
/src/page_logs.js:
--------------------------------------------------------------------------------
1 | const API_Proxy = require("./api_proxy");
2 | const PageCache = require("./cache");
3 |
4 | class Page_Logs {
5 | constructor() {
6 | this.ServerState = {};
7 |
8 | this._TotalSFLogLines = 0;
9 | this._SFLogOffset = 0;
10 | }
11 |
12 | init() {
13 | this.setupJqueryListeners();
14 | this.SetupEventHandlers();
15 | }
16 |
17 | setupJqueryListeners() {
18 | $("body").on("click", ".sf-log-page-link", (e) => {
19 | e.preventDefault();
20 | const $pageBtn = $(e.currentTarget);
21 | console.log(parseInt($pageBtn.text()) - 1);
22 | this._SFLogOffset = (parseInt($pageBtn.text()) - 1) * 500;
23 |
24 | this.getSFServerLog(true);
25 | });
26 | }
27 |
28 | SetupEventHandlers() {
29 | PageCache.on("setactiveagent", () => {
30 | this.MainDisplayFunction();
31 | });
32 | }
33 |
34 | MainDisplayFunction() {
35 | const Agent = PageCache.getActiveAgent();
36 |
37 | if (Agent == null) {
38 | this.getSSMLog();
39 | return;
40 | }
41 |
42 | this.getSSMLog();
43 | this.getSFServerLog();
44 | this.getSteamLog();
45 | }
46 |
47 | getSSMLog() {
48 | const Agent = PageCache.getActiveAgent();
49 | const postData = {};
50 |
51 | if (Agent == null) {
52 | postData.agentid = -1;
53 | } else {
54 | postData.agentid = Agent.id;
55 | }
56 |
57 | API_Proxy.postData("agent/logs/ssmlog", postData).then((res) => {
58 | const el = $("#ssm-log-viewer samp");
59 | el.empty();
60 | if (res.result == "success") {
61 | res.data.forEach((logline) => {
62 | el.append("" + logline + "
");
63 | });
64 | } else {
65 | el.text(res.error.message);
66 | }
67 | });
68 | }
69 |
70 | getSteamLog() {
71 | const Agent = PageCache.getActiveAgent();
72 | const postData = {};
73 |
74 | if (Agent == null) {
75 | postData.agentid = -1;
76 | } else {
77 | postData.agentid = Agent.id;
78 | }
79 |
80 | API_Proxy.postData("agent/logs/steamlog", postData).then((res) => {
81 | const el = $("#steam-log-viewer samp");
82 | el.empty();
83 | if (res.result == "success") {
84 | res.data.forEach((logline) => {
85 | el.append("" + logline + "
");
86 | });
87 | } else {
88 | el.text(res.error.message);
89 | }
90 | });
91 | }
92 |
93 | getSFServerLog(force = false) {
94 | const Agent = PageCache.getActiveAgent();
95 | const postData = {
96 | offset: this._SFLogOffset,
97 | };
98 |
99 | if (Agent == null) {
100 | postData.agentid = -1;
101 | } else {
102 | postData.agentid = Agent.id;
103 | }
104 |
105 | API_Proxy.postData("agent/logs/sfserverlog", postData).then((res) => {
106 | const el = $("#sf-log-viewer samp");
107 | const el2 = $("#sf-logins-viewer samp");
108 | el.empty();
109 | el2.empty();
110 | if (res.result == "success") {
111 | if (
112 | res.data.lineCount != this._TotalSFLogLines ||
113 | force == true
114 | ) {
115 | this._TotalSFLogLines = res.data.lineCount;
116 | this.buildSFLogPagination();
117 | res.data.logArray.forEach((logline) => {
118 | el.append("" + logline + "
");
119 | });
120 |
121 | res.data.playerJoins.forEach((logline) => {
122 | el2.append("" + logline + "
");
123 | });
124 | }
125 | } else {
126 | el.text(res.error);
127 | el2.text(res.error);
128 | }
129 | });
130 | }
131 |
132 | buildSFLogPagination() {
133 | const $el = $("#SFLogPagination .pagination");
134 | $el.empty();
135 |
136 | const pageCount = Math.ceil(this._TotalSFLogLines / 500) + 1;
137 | for (let i = 1; i < pageCount; i++) {
138 | const pageOffset = (i - 1) * 500;
139 |
140 | $el.append(
141 | `${i} `
144 | );
145 | }
146 | }
147 | }
148 |
149 | const page = new Page_Logs();
150 |
151 | module.exports = page;
152 |
--------------------------------------------------------------------------------
/src/api_proxy.js:
--------------------------------------------------------------------------------
1 | const Logger = require("./logger");
2 |
3 | class API_Proxy {
4 | constructor() {}
5 |
6 | get(...args) {
7 | const url = "/api/" + args.join("/");
8 | Logger.debug("API Proxy [GET] " + url);
9 | return new Promise((resolve, reject) => {
10 | $.get(url, (result) => {
11 | resolve(result);
12 | });
13 | });
14 | }
15 |
16 | post(...args) {
17 | const url = "/api/" + args.join("/");
18 | Logger.debug("API Proxy [POST] " + url);
19 | return new Promise((resolve, reject) => {
20 | $.post(url, (result) => {
21 | resolve(result);
22 | });
23 | });
24 | }
25 |
26 | postData(posturl, data) {
27 | const url = "/api/" + posturl;
28 | Logger.debug("API Proxy [POST] " + url);
29 | return new Promise((resolve, reject) => {
30 | $.post(url, data, (result) => {
31 | resolve(result);
32 | });
33 | });
34 | }
35 |
36 | upload(uploadurl, formdata) {
37 | const url = "/api/" + uploadurl;
38 | return new Promise((resolve, reject) => {
39 | $.ajax({
40 | url: url,
41 | type: "POST",
42 | data: formdata, // The form with the file inputs.
43 | processData: false,
44 | contentType: false, // Using FormData, no need to process data.
45 | })
46 | .done((data) => {
47 | resolve(data);
48 | })
49 | .fail((err) => {
50 | reject(err);
51 | });
52 | });
53 | }
54 |
55 | download(posturl, data) {
56 | const url = "/api/" + posturl;
57 | return new Promise((resolve, reject) => {
58 | $.ajax({
59 | type: "POST",
60 | url: url,
61 | data: data,
62 | xhrFields: {
63 | responseType: "blob", // to avoid binary data being mangled on charset conversion
64 | },
65 | success: function (blob, status, xhr) {
66 | // check for a filename
67 | var filename = "";
68 | var disposition = xhr.getResponseHeader(
69 | "Content-Disposition"
70 | );
71 | if (
72 | disposition &&
73 | disposition.indexOf("attachment") !== -1
74 | ) {
75 | var filenameRegex =
76 | /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
77 | var matches = filenameRegex.exec(disposition);
78 | if (matches != null && matches[1])
79 | filename = matches[1].replace(/['"]/g, "");
80 | }
81 |
82 | if (typeof window.navigator.msSaveBlob !== "undefined") {
83 | // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
84 | window.navigator.msSaveBlob(blob, filename);
85 | resolve();
86 | } else {
87 | var URL = window.URL || window.webkitURL;
88 | var downloadUrl = URL.createObjectURL(blob);
89 |
90 | if (filename) {
91 | // use HTML5 a[download] attribute to specify filename
92 | var a = document.createElement("a");
93 | // safari doesn't support this yet
94 | if (typeof a.download === "undefined") {
95 | window.location.href = downloadUrl;
96 | resolve();
97 | } else {
98 | a.href = downloadUrl;
99 | a.download = filename;
100 | document.body.appendChild(a);
101 | a.click();
102 | resolve();
103 | }
104 | } else {
105 | window.location.href = downloadUrl;
106 | resolve();
107 | }
108 |
109 | setTimeout(function () {
110 | URL.revokeObjectURL(downloadUrl);
111 | }, 100); // cleanup
112 | }
113 | },
114 | });
115 | });
116 | }
117 | }
118 |
119 | const api_proxy = new API_Proxy();
120 | module.exports = api_proxy;
121 |
--------------------------------------------------------------------------------
/tools/compile.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | CURDIR=$(dirname "$(readlink -f "$0")")
4 | BASEDIR=$(readlink -f "$CURDIR/..")
5 |
6 | VERSION=""
7 | LINUX=0
8 | WINDOWS=0
9 | FORCE=0
10 |
11 | while [[ $# -gt 0 ]]; do
12 | key="$1"
13 |
14 | case $key in
15 | --version)
16 | VERSION="$2"
17 | shift # past value
18 | shift # past value
19 | ;;
20 | --linux)
21 | LINUX=1
22 | shift # past value
23 | ;;
24 | --windows)
25 | WINDOWS=1
26 | shift # past value
27 | ;;
28 | --force)
29 | FORCE=1
30 | shift # past value
31 | ;;
32 |
33 | esac
34 | done
35 |
36 | if [[ ! -f "${BASEDIR}/tools/app_config.txt" ]] && [[ $FORCE -eq 0 ]]; then
37 | echo "No config file was found please run configure_app.sh first"
38 | exit 1
39 | fi
40 |
41 | . ${BASEDIR}/tools/variables.sh
42 | . ${BASEDIR}/tools/app_config.txt
43 |
44 | echo -en "\nCompiling Application (Version: \e[34m${VERSION}\e[0m)\n"
45 |
46 | if [ ! -d "${BASEDIR}/build" ]; then
47 | mkdir -p "${BASEDIR}/build"
48 | fi
49 |
50 | rm -rf ${BASEDIR}/release-builds 2>&1 >/dev/null
51 |
52 | if [ ! -d "${BASEDIR}/release-builds" ]; then
53 | mkdir -p "${BASEDIR}/release-builds/linux"
54 | mkdir -p "${BASEDIR}/release-builds/win64"
55 | fi
56 |
57 | release_dir="${BASEDIR}/release-builds"
58 |
59 | release_dir_linux="${release_dir}/linux"
60 | release_dir_win64="${release_dir}/win64"
61 |
62 | cd ${BASEDIR}
63 |
64 | #bash ${BASEDIR}/tools/update_SSM_exe.sh --version "${VERSION}"
65 |
66 | if [ $? -ne 0 ]; then
67 | echo "Error: failed to update SSM.exe version numbers"
68 | exit 1
69 | fi
70 |
71 | if [ ! -d "${BASEDIR}/assets" ]; then
72 | mkdir "${BASEDIR}/assets"
73 | fi
74 |
75 | echo -en "Version: ${VERSION}" >"${BASEDIR}/assets/version.txt"
76 |
77 | if [ "${USE_LINUX_SERVER}" == "1" ]; then
78 | echo "* Building Linux Executables: "
79 |
80 | printDots "* Cleaning Build Folder" 30
81 | sshargs="mkdir -p /nodejs/build >/dev/null 2>&1; \
82 | cd /nodejs/build; \
83 | rm -r SSM; >/dev/null 2>&1\
84 | "
85 |
86 | ${SSH_CMD} root@${LINUX_SERVER} "${sshargs}" >/dev/null 2>&1
87 | echo -en "\e[32m✔\e[0m\n"
88 |
89 | printDots "* Cloning SSM Repo" 30
90 | sshargs="cd /nodejs/build; \
91 | git clone https://github.com/mrhid6/SatisfactoryServerManager.git SSM; \
92 | exit \$?
93 | "
94 | ${SSH_CMD} root@${LINUX_SERVER} "${sshargs}" >/dev/null 2>&1
95 | if [ $? -ne 0 ]; then
96 | echo -en "\e[31m✘\e[0m\n"
97 | exit 1
98 | else
99 | echo -en "\e[32m✔\e[0m\n"
100 | fi
101 |
102 | printDots "* Building App" 30
103 | sshargs="PATH+=:/root/n/bin; \
104 | cd /nodejs/build/SSM; \
105 | bash ./tools/build_app.sh -i -u; \
106 | exit \$?
107 | "
108 | ${SSH_CMD} root@${LINUX_SERVER} "${sshargs}" >/dev/null 2>&1
109 |
110 | if [ $? -ne 0 ]; then
111 | echo -en "\e[31m✘\e[0m\n"
112 | exit 1
113 | else
114 | echo -en "\e[32m✔\e[0m\n"
115 | fi
116 |
117 | sshargs="cd /nodejs/build/SSM; \
118 | find /nodejs/build/SSM -name \"*.node\" | grep -v \"obj\"
119 | "
120 |
121 | printDots "* Copying Executables" 30
122 | ${SSH_CMD} root@${LINUX_SERVER} "${sshargs}" >${release_dir_linux}/exe.list
123 | while read -r line; do
124 | ${SCP_CMD} root@${LINUX_SERVER}:${line} ${release_dir_linux}/.
125 | done <${release_dir_linux}/exe.list
126 |
127 | rm ${release_dir_linux}/exe.list
128 | echo -en "\e[32m✔\e[0m\n"
129 |
130 | printDots "* Compiling Linux" 30
131 | sshargs="cd /nodejs/build/SSM; \
132 | find /nodejs/build/SSM -name \"*.node\" | grep -v \"obj\"
133 | "
134 | pkg app.js -c package.json -t node16-linux-x64 --out-path ${release_dir_linux} -d >${release_dir_linux}/build.log
135 | echo -en "\e[32m✔\e[0m\n"
136 |
137 | ZipLinuxFileName="${release_dir}/SSM-Linux-x64-${VERSION}.tar.gz"
138 |
139 | printDots "* Zipping Binaries" 30
140 | cd ${release_dir_linux}
141 | tar cz --exclude='*.log' -f ${ZipLinuxFileName} ./* >/dev/null
142 |
143 | echo -en "\e[32m✔\e[0m\n"
144 |
145 |
146 | fi
147 |
148 | printDots "* Copying Win64 Executables" 30
149 |
150 | find ${BASEDIR} -name "*.node" | grep -v "release-builds" >${release_dir_win64}/exe.list
151 |
152 | while read -r line; do
153 | cp ${line} ${release_dir_win64}/.
154 | done <${release_dir_win64}/exe.list
155 | rm ${release_dir_win64}/exe.list
156 |
157 | echo -en "\e[32m✔\e[0m\n"
158 |
159 | printDots "* Compiling Win64" 30
160 | pkg app.js -c package.json -t node16-win-x64 --out-path ${release_dir_win64} -d >${release_dir_win64}/build.log
161 | echo -en "\e[32m✔\e[0m\n"
162 |
163 | printDots "* Zipping Binaries" 30
164 |
165 | ZipWin64FileName="${release_dir}/SSM-Win-x64-${VERSION}.zip"
166 |
167 | cd ${release_dir_win64}
168 | 7z a -tzip ${ZipWin64FileName} ./* -xr!build.log >/dev/null
169 | echo -en "\e[32m✔\e[0m\n"
170 |
171 | exit 0
172 |
--------------------------------------------------------------------------------
/src/page_backups.js:
--------------------------------------------------------------------------------
1 | const API_Proxy = require("./api_proxy");
2 |
3 | const PageCache = require("./cache");
4 |
5 | class Page_Backups {
6 | constructor() {
7 | this._ROLES = [];
8 | }
9 |
10 | init() {
11 | this.setupJqueryListeners();
12 | this.SetupEventHandlers();
13 | }
14 |
15 | SetupEventHandlers() {
16 | PageCache.on("setactiveagent", () => {
17 | this.MainDisplayFunction();
18 | });
19 | }
20 |
21 | setupJqueryListeners() {
22 | $("body").on("click", ".download-backup-btn", (e) => {
23 | this.DownloadBackup($(e.currentTarget));
24 | });
25 | }
26 |
27 | MainDisplayFunction() {
28 | this.DisplayBackupsTable();
29 | }
30 |
31 | DisplayBackupsTable() {
32 | const Agent = PageCache.getActiveAgent();
33 |
34 | const postData = {
35 | agentid: Agent.id,
36 | };
37 |
38 | API_Proxy.postData("agent/backups", postData).then((res) => {
39 | const isDataTable = $.fn.dataTable.isDataTable("#backups-table");
40 | const tableData = [];
41 |
42 | const backups = res.data;
43 |
44 | if (backups != null && backups.length > 0) {
45 | let index = 0;
46 | backups.forEach((backup) => {
47 | let deleteBackupEl = $(" ")
48 | .addClass("btn btn-danger float-end remove-backup-btn")
49 | .html(" ")
50 | .attr("data-backup-name", backup.filename);
51 |
52 | let downloadBackupEl = $(" ")
53 | .addClass(
54 | "btn btn-primary float-start download-backup-btn"
55 | )
56 | .html(" ")
57 | .attr("data-backup-name", backup.filename);
58 |
59 | const downloadSaveStr = deleteBackupEl.prop("outerHTML");
60 | const deleteSaveStr = downloadBackupEl.prop("outerHTML");
61 |
62 | tableData.push([
63 | backup.filename,
64 | BackupDate(backup.created),
65 | humanFileSize(backup.size),
66 | downloadSaveStr + deleteSaveStr,
67 | ]);
68 | index++;
69 | });
70 | }
71 |
72 | if (isDataTable == false) {
73 | $("#backups-table").DataTable({
74 | paging: true,
75 | searching: false,
76 | info: false,
77 | order: [[0, "desc"]],
78 | columnDefs: [],
79 | data: tableData,
80 | });
81 | } else {
82 | const datatable = $("#backups-table").DataTable();
83 | datatable.clear();
84 | datatable.rows.add(tableData);
85 | datatable.draw();
86 | }
87 | });
88 | }
89 |
90 | DownloadBackup($btn) {
91 | const BackupFile = $btn.attr("data-backup-name");
92 |
93 | const Agent = PageCache.getActiveAgent();
94 |
95 | const postData = {
96 | agentid: Agent.id,
97 | backupfile: BackupFile,
98 | };
99 |
100 | console.log(postData);
101 |
102 | API_Proxy.download("agent/backups/download", postData)
103 | .then((res) => {
104 | console.log(res);
105 | })
106 | .catch((err) => {
107 | console.log(err);
108 | });
109 | }
110 | }
111 |
112 | function BackupDate(dateStr) {
113 | const date = new Date(dateStr);
114 | const day = date.getDate().pad(2);
115 | const month = (date.getMonth() + 1).pad(2);
116 | const year = date.getFullYear();
117 |
118 | const hour = date.getHours().pad(2);
119 | const min = date.getMinutes().pad(2);
120 | const sec = date.getSeconds().pad(2);
121 |
122 | return day + "/" + month + "/" + year + " " + hour + ":" + min + ":" + sec;
123 | }
124 |
125 | /**
126 | * Format bytes as human-readable text.
127 | *
128 | * @param bytes Number of bytes.
129 | * @param si True to use metric (SI) units, aka powers of 1000. False to use
130 | * binary (IEC), aka powers of 1024.
131 | * @param dp Number of decimal places to display.
132 | *
133 | * @return Formatted string.
134 | */
135 | function humanFileSize(bytes, si = false, dp = 1) {
136 | const thresh = si ? 1000 : 1024;
137 |
138 | if (Math.abs(bytes) < thresh) {
139 | return bytes + " B";
140 | }
141 |
142 | const units = si
143 | ? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
144 | : ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
145 | let u = -1;
146 | const r = 10 ** dp;
147 |
148 | do {
149 | bytes /= thresh;
150 | ++u;
151 | } while (
152 | Math.round(Math.abs(bytes) * r) / r >= thresh &&
153 | u < units.length - 1
154 | );
155 |
156 | return bytes.toFixed(dp) + " " + units[u];
157 | }
158 |
159 | const page = new Page_Backups();
160 |
161 | module.exports = page;
162 |
--------------------------------------------------------------------------------
/views/admin.hbs:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | Heads up!
10 | This page has not yet been updated!
11 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
27 | ID
28 | Name
29 | Enabled
30 | Type
31 | Options
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
47 |
48 |
49 |
50 |
51 | ID
52 | User Name
53 | API Key
54 | Options
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
73 |
74 |
75 |
76 |
77 | User ID
78 | User Name
79 | Role
80 | Options
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
96 |
97 |
98 |
99 |
100 | Role ID
101 | Role Name
102 | Permissions
103 | Options
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
123 |
124 |
125 |
126 |
127 | Debug ID
128 | Date Generated
129 | Options
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/server/server_notifcation_handler.js:
--------------------------------------------------------------------------------
1 | const DB = require("./server_db");
2 |
3 | const DiscordNotificationInterface = require("../objects/obj_discord_notification_interface");
4 | const WebhookNotificationInterface = require("../objects/obj_webhook_notification_interface");
5 |
6 | const iNotification = require("../objects/obj_notification");
7 |
8 | class NotificationHandler {
9 | constructor() {
10 | this._notifyinterfaces = [];
11 | this._WEBHOOKS = [];
12 | }
13 |
14 | init = async () => {
15 | await this.LoadWebHooksFromDB();
16 | await this.ProcessPendingEvents();
17 |
18 | setInterval(async () => {
19 | await this.ProcessPendingEvents();
20 | }, 10000);
21 | };
22 |
23 | RegisterInterface(NotifyInterface) {
24 | this._notifyinterfaces.push(NotifyInterface);
25 | }
26 |
27 | TriggerNotification = async (Notification) => {
28 | const NotifyInterface = this._notifyinterfaces.find(
29 | (int) => int.getId() == Notification.get("webhook_id")
30 | );
31 |
32 | if (NotifyInterface == null) {
33 | Notification.set("lastError", "webhook_id is null!");
34 | Notification.set("attempts", Notification.get("attempts") + 1);
35 | } else {
36 | try {
37 | await NotifyInterface.TriggerEvent(Notification);
38 | Notification.set("handled", true);
39 | } catch (err) {
40 | Notification.set("lastError", err.message);
41 | Notification.set("attempts", Notification.get("attempts") + 1);
42 | }
43 | }
44 |
45 | try {
46 | await this.SaveEvent(Notification);
47 | } catch (err) {
48 | console.log(err);
49 | }
50 | };
51 |
52 | StoreNotification = async (Notification) => {
53 | for (let i = 0; i < this._notifyinterfaces.length; i++) {
54 | const NotifyInterface = this._notifyinterfaces[i];
55 |
56 | if (NotifyInterface.CanTriggerEvent(Notification) == false)
57 | continue;
58 |
59 | Notification.set("webhook_id", NotifyInterface.getId());
60 |
61 | const sqlData = [JSON.stringify(Notification.GetData())];
62 |
63 | const sql = "INSERT INTO webhook_events(we_data) VALUES (?)";
64 | await DB.queryRun(sql, sqlData);
65 | }
66 | };
67 |
68 | GetPendingEvents = async () => {
69 | const rows = await DB.query("SELECT * FROM webhook_events");
70 |
71 | const resArray = [];
72 |
73 | for (let i = 0; i < rows.length; i++) {
74 | const row = rows[i];
75 | const Notification = new iNotification();
76 | Notification.ParseSQLData(row);
77 |
78 | if (
79 | Notification.get("handled") == false &&
80 | Notification.get("attempts") < 10
81 | ) {
82 | resArray.push(Notification);
83 | } else if (Notification.get("attempts") >= 10) {
84 | Notification.set("handled", true);
85 | await this.SaveEvent(Notification);
86 | }
87 | }
88 |
89 | return resArray;
90 | };
91 |
92 | ProcessPendingEvents = async () => {
93 | const Events = await this.GetPendingEvents();
94 |
95 | for (let i = 0; i < Events.length; i++) {
96 | const WebhookEvent = Events[i];
97 | try {
98 | await this.TriggerNotification(WebhookEvent);
99 | } catch (err) {
100 | console.log(err);
101 | }
102 | }
103 |
104 | await this.PurgeEvents();
105 | };
106 |
107 | SaveEvent = async (Notification) => {
108 | const sql = "UPDATE webhook_events SET we_data=? WHERE we_id=?";
109 | const sqlData = [
110 | JSON.stringify(Notification.GetData()),
111 | Notification._id,
112 | ];
113 |
114 | await DB.queryRun(sql, sqlData);
115 | };
116 |
117 | PurgeEvents = async () => {
118 | const rows = await DB.query("SELECT * FROM webhook_events");
119 |
120 | const resArray = [];
121 |
122 | const limitDate = new Date();
123 | limitDate.setDate(limitDate.getDate() - 10);
124 |
125 | for (let i = 0; i < rows.length; i++) {
126 | const row = rows[i];
127 | const Notification = new iNotification();
128 | Notification.ParseSQLData(row);
129 |
130 | if (
131 | Notification.get("handled") == true &&
132 | Notification.get("timestamp") < limitDate.getTime()
133 | ) {
134 | await DB.queryRun("DELETE FROM webhook_events WHERE we_id=?", [
135 | Notification._id,
136 | ]);
137 | }
138 | }
139 | };
140 |
141 | LoadWebHooksFromDB() {
142 | return new Promise((resolve, reject) => {
143 | DB.query("SELECT * FROM webhooks").then((rows) => {
144 | this._WEBHOOKS = [];
145 | for (let i = 0; i < rows.length; i++) {
146 | const row = rows[i];
147 |
148 | let iInterface = null;
149 |
150 | if (row.webhook_discord == 1) {
151 | iInterface = new DiscordNotificationInterface();
152 | } else {
153 | iInterface = new WebhookNotificationInterface();
154 | }
155 |
156 | iInterface.init({
157 | id: row.webhook_id,
158 | name: row.webhook_name,
159 | url: row.webhook_url,
160 | enabled: row.webhook_enabled == 1,
161 | events: JSON.parse(row.webhook_events || []),
162 | type: row.webhook_discord,
163 | });
164 |
165 | this._WEBHOOKS.push(iInterface);
166 | this.RegisterInterface(iInterface);
167 | }
168 |
169 | resolve();
170 | });
171 | });
172 | }
173 |
174 | API_GetAllWebhooks() {
175 | return new Promise((resolve, reject) => {
176 | const resArr = [];
177 |
178 | for (let i = 0; i < this._WEBHOOKS.length; i++) {
179 | const webhook = this._WEBHOOKS[i];
180 | resArr.push(webhook.getOptions());
181 | }
182 |
183 | resolve(resArr);
184 | });
185 | }
186 | }
187 |
188 | const notificationHandler = new NotificationHandler();
189 | module.exports = notificationHandler;
190 |
--------------------------------------------------------------------------------
/public/modals/inital-setup.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------