diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..45a4534 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +FROM debian:bookworm-slim + +LABEL author="Makaren0" maintainer="Makaffe@hotmail.com" + +ENV DEBIAN_FRONTEND=noninteractive +ENV DEBCONF_NOWARNINGS="yes" + +RUN apt update; apt upgrade -y\ + && apt install -y libgcc1 lib32stdc++6 unzip curl iproute2 tzdata libgdiplus + +RUN apt-get update \ + && apt-get clean \ + && apt-get autoremove + +RUN apt --fix-broken install \ + && apt-get update \ + && apt-get upgrade \ + && dpkg --configure -a \ + && apt-get install -f + +RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \ + && apt install -y nodejs \ + && mkdir /node_modules + +RUN npm install --prefix / ws + +RUN apt update; apt upgrade -y\ + && dpkg --add-architecture i386; apt install -y wget file tar bzip2 gzip bsdmainutils python3 util-linux ca-certificates binutils bc jq tmux netcat lib32gcc-s1 lib32z1 + +RUN useradd -d /home/container -m container +USER container +ENV USER=container HOME=/home/container + +WORKDIR /home/container + +COPY ./entrypoint.sh /entrypoint.sh +COPY ./wrapper.js /wrapper.js + +CMD ["/bin/bash", "/entrypoint.sh"] diff --git a/README.md b/README.md index 9925542..1d3a6db 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -## Docker images \ No newline at end of file +## Game Rust Facepunch - Docker images \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..60e68fc --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/bash +echo -e "╔═════════════════════╗" +echo -e "║ EntryPoint.sh ║" +echo -e "╚═════════════════════╝\n" +cd /home/container + +# Fix for Rust not starting +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(pwd) + +# Make internal Docker IP address available to processes. +export INTERNAL_IP=`ip route get 1 | awk '{print $NF;exit}'` + +# Replace Startup Variables +MODIFIED_STARTUP=`eval echo $(echo ${STARTUP} | sed -e 's/{{/${/g' -e 's/}}/}/g')` +echo ":/home/container$ ${MODIFIED_STARTUP}" + +./steamcmd/steamcmd.sh +force_install_dir /home/container +login anonymous +app_update 258550 +quit + +if [ -f OXIDE_FLAG ] || [ "${OXIDE}" = 1 ] || [ "${UMOD}" = 1 ]; then + echo "Updating uMod..." + curl -sSL "https://github.com/OxideMod/Oxide.Rust/releases/latest/download/Oxide.Rust-linux.zip" > umod.zip + unzip -o -q umod.zip + rm umod.zip + echo "Done updating uMod!" +fi + +# Run the Server +echo -e "INVOKE WRAPPER..." +node /wrapper.js "${MODIFIED_STARTUP}" diff --git a/pterodactyl.install_script.sh b/pterodactyl.install_script.sh new file mode 100644 index 0000000..6d67f8c --- /dev/null +++ b/pterodactyl.install_script.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# steamcmd Base Installation Script +# +# Server Files: /mnt/server +echo -e "╔═════════════════════╗" +echo -e "║ InstallScript ║" +echo -e "╚═════════════════════╝\n" + +SRCDS_APPID=258550 + +## just in case someone removed the defaults. +if [ "${STEAM_USER}" == "" ]; then + echo -e "steam user is not set.\n" + echo -e "Using anonymous user.\n" + STEAM_USER=anonymous + STEAM_PASS="" + STEAM_AUTH="" +else + echo -e "user set to ${STEAM_USER}" +fi + +## download and install steamcmd +cd /tmp +mkdir -p /mnt/server/steamcmd +curl -sSL -o steamcmd.tar.gz https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz +tar -xzvf steamcmd.tar.gz -C /mnt/server/steamcmd +mkdir -p /mnt/server/steamapps # Fix steamcmd disk write error when this folder is missing +cd /mnt/server/steamcmd + +# SteamCMD fails otherwise for some reason, even running as root. +# This is changed at the end of the install process anyways. +chown -R root:root /mnt +export HOME=/mnt/server + +## install game using steamcmd +./steamcmd.sh +force_install_dir /mnt/server +login ${STEAM_USER} ${STEAM_PASS} ${STEAM_AUTH} +app_update ${SRCDS_APPID} ${EXTRA_FLAGS} validate +quit \ No newline at end of file diff --git a/wrapper.js b/wrapper.js new file mode 100644 index 0000000..ecb5d15 --- /dev/null +++ b/wrapper.js @@ -0,0 +1,139 @@ +#!/usr/bin/env node + +var startupCmd = ""; +const fs = require("fs"); +fs.writeFile("latest.log", "", (err) => { + if (err) console.log("Callback error in appendFile:" + err); +}); + +var args = process.argv.splice(process.execArgv.length + 2); +for (var i = 0; i < args.length; i++) { + if (i === args.length - 1) { + startupCmd += args[i]; + } else { + startupCmd += args[i] + " "; + } +} + +if (startupCmd.length < 1) { + console.log("Error: Please specify a startup command."); + process.exit(); +} + +const seenPercentage = {}; +function filter(data) { + const str = data.toString(); + if (str.startsWith("Loading Prefab Bundle ")) { // Rust seems to spam the same percentage, so filter out any duplicates. + const percentage = str.substr("Loading Prefab Bundle ".length); + if (seenPercentage[percentage]) return; + + seenPercentage[percentage] = true; + } + + console.log(str); +} + +var exec = require("child_process").exec; +console.log("Starting Rust in wrapper.js..."); + +var exited = false; +const gameProcess = exec(startupCmd); +gameProcess.stdout.on('data', filter); +gameProcess.stderr.on('data', filter); +gameProcess.on('exit', function (code, signal) { + exited = true; + + if (code) { + console.log("Main game process exited with code " + code); + // process.exit(code); + } +}); + +function initialListener(data) { + const command = data.toString().trim(); + if (command === 'quit') { + gameProcess.kill('SIGTERM'); + } else { + console.log('Unable to run "' + command + '" due to RCON not being connected yet.'); + } +} +process.stdin.resume(); +process.stdin.setEncoding("utf8"); +process.stdin.on('data', initialListener); + +process.on('exit', function(code) { + if (exited) return; + + console.log("Received request to stop the process, stopping the game..."); + gameProcess.kill('SIGTERM'); +}); + +var waiting = true; +var poll = function( ) { + function createPacket(command) { + var packet = { + Identifier: -1, + Message: command, + Name: "WebRcon" + }; + return JSON.stringify(packet); + } + + var serverHostname = process.env.RCON_IP ? process.env.RCON_IP : "localhost"; + var serverPort = process.env.RCON_PORT; + var serverPassword = process.env.RCON_PASS; + var WebSocket = require("ws"); + var ws = new WebSocket("ws://" + serverHostname + ":" + serverPort + "/" + serverPassword); + + ws.on("open", function open() { + console.log("Connected to RCON. Please wait until the server status switches to \"Running\"."); + waiting = false; + + // Hack to fix broken console output + ws.send(createPacket('status')); + + process.stdin.removeListener('data', initialListener); + gameProcess.stdout.removeListener('data', filter); + gameProcess.stderr.removeListener('data', filter); + process.stdin.on('data', function (text) { + ws.send(createPacket(text)); + }); + }); + + ws.on("message", function(data, flags) { + try { + var json = JSON.parse(data); + if (json !== undefined) { + if (json.Message !== undefined && json.Message.length > 0) { + console.log(json.Message); + const fs = require("fs"); + fs.appendFile("latest.log", "\n" + json.Message, (err) => { + if (err) console.log("Callback error in appendFile:"+err); + }); + } + } else { + console.log("Error: Invalid JSON received"); + } + } catch (e) { + if (e) { + console.log(e); + } + } + }); + + ws.on("error", function(err) { + waiting = true; + console.log("Waiting for RCON to come up..."); + setTimeout(poll, 5000); + }); + + ws.on("close", function() { + if (!waiting) { + console.log("Connection to server closed."); + + exited = true; + process.exit(); + } + }); +} +poll();