diff --git a/.github/docker/entrypoint.sh b/.github/docker/entrypoint.sh index e6e1b0966..e2df6439c 100644 --- a/.github/docker/entrypoint.sh +++ b/.github/docker/entrypoint.sh @@ -30,7 +30,7 @@ else fi echo "Checking if https is required." -if [ -f /etc/nginx/conf.d/default.conf ]; then +if [ -f /etc/nginx/http.d/panel.conf ]; then echo "Using nginx config already in place." if [ $LE_EMAIL ]; then echo "Checking for cert update" @@ -42,15 +42,17 @@ else echo "Checking if letsencrypt email is set." if [ -z $LE_EMAIL ]; then echo "No letsencrypt email is set using http config." - cp .github/docker/default.conf /etc/nginx/conf.d/default.conf + cp .github/docker/default.conf /etc/nginx/http.d/panel.conf else echo "writing ssl config" - cp .github/docker/default_ssl.conf /etc/nginx/conf.d/default.conf + cp .github/docker/default_ssl.conf /etc/nginx/http.d/panel.conf echo "updating ssl config for domain" - sed -i "s||$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/conf.d/default.conf + sed -i "s||$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/http.d/panel.conf echo "generating certs" certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n fi + echo "Removing the default nginx config" + rm -rf /etc/nginx/http.d/default.conf fi ## check for DB up before starting the panel diff --git a/README.md b/README.md index 42e02d5c0..6d0eec5ad 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ I would like to extend my sincere thanks to the following sponsors for helping f | [**Spill Hosting**](https://spillhosting.no/) | Spill Hosting is a Norwegian hosting service, which aims for inexpensive services on quality servers. Premium i9-9900K processors will run your game like a dream. | | [**DeinServerHost**](https://deinserverhost.de/) | DeinServerHost offers Dedicated, vps and Gameservers for many popular Games like Minecraft and Rust in Germany since 2013. | | [**HostBend**](https://hostbend.com/) | HostBend offers a variety of solutions for developers, students, and others who have a tight budget but don't want to compromise quality and support. | -| [**Capitol Hosting Solutions**](https://capitolsolutions.cloud/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! | +| [**Capitol Hosting Solutions**](https://chs.gg/) | CHS is *the* budget friendly hosting company for Australian and American gamers, offering a variety of plans from Web Hosting to Game Servers; Custom Solutions too! | | [**ByteAnia**](https://byteania.com/?utm_source=pterodactyl) | ByteAnia offers the best performing and most affordable **Ryzen 5000 Series hosting** on the market for *unbeatable prices*! | | [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. | | [**VibeGAMES**](https://vibegames.net/) | VibeGAMES is a game server provider that specializes in DDOS protection for the games we offer. We have multiple locations in the US, Brazil, France, Germany, Singapore, Australia and South Africa.| diff --git a/SECURITY.md b/SECURITY.md index 5b412f853..a00178658 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -5,14 +5,9 @@ The following versions of Pterodactyl are receiving active support and maintenan | Panel | Daemon | Supported | | ----- | ------------ | ------------------ | -| 1.4.x | wings@1.4.x | :white_check_mark: | -| 1.3.x | wings@1.3.x | :x: | -| 1.2.x | wings@1.2.x | :x: | -| 1.1.x | wings@1.1.x | :x: | -| 1.0.x | wings@1.0.x | :x: | +| 1.6.x | wings@1.5.x | :white_check_mark: | | 0.7.x | daemon@0.6.x | :x: | -| 0.6.x | daemon@0.5.x | :x: | -| 0.5.x | daemon@0.4.x | :x: | + ## Reporting a Vulnerability diff --git a/app/Console/Commands/UpgradeCommand.php b/app/Console/Commands/UpgradeCommand.php index feb21ec60..224329869 100644 --- a/app/Console/Commands/UpgradeCommand.php +++ b/app/Console/Commands/UpgradeCommand.php @@ -57,7 +57,7 @@ class UpgradeCommand extends Command $userDetails = posix_getpwuid(fileowner('public')); $user = $userDetails['name'] ?? 'www-data'; - if (!$this->confirm("Your webserver user has been detected as [{$user}]: is this correct?", true)) { + if (!$this->confirm("Your webserver user has been detected as [{$user}]: is this correct?", true)) { $user = $this->anticipate( 'Please enter the name of the user running your webserver process. This varies from system to system, but is generally "www-data", "nginx", or "apache".', [ @@ -73,7 +73,7 @@ class UpgradeCommand extends Command $groupDetails = posix_getgrgid(filegroup('public')); $group = $groupDetails['name'] ?? 'www-data'; - if (!$this->confirm("Your webserver group has been detected as [{$group}]: is this correct?", true)) { + if (!$this->confirm("Your webserver group has been detected as [{$group}]: is this correct?", true)) { $group = $this->anticipate( 'Please enter the name of the group running your webserver process. Normally this is the same as your user.', [ @@ -86,6 +86,7 @@ class UpgradeCommand extends Command } if (!$this->confirm('Are you sure you want to run the upgrade process for your Panel?')) { + $this->warn('Upgrade process terminated by user.'); return; } } @@ -173,8 +174,8 @@ class UpgradeCommand extends Command $this->call('up'); }); - $this->newLine(); - $this->info('Finished running upgrade.'); + $this->newLine(2); + $this->info('Panel has been successfully upgraded. Please ensure you also update any Wings instances: https://pterodactyl.io/wings/1.0/upgrading.html'); } protected function withProgress(ProgressBar $bar, Closure $callback) diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index fd3cf0b95..78accbc08 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -109,5 +109,7 @@ class Kernel extends HttpKernel 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, + // API Specific Middleware + 'api..key' => AuthenticateKey::class, ]; } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index db07b1627..06100b572 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,6 +2,7 @@ namespace Pterodactyl\Providers; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Support\Facades\RateLimiter; @@ -19,44 +20,87 @@ class RouteServiceProvider extends ServiceProvider protected $namespace = 'Pterodactyl\Http\Controllers'; /** - * Define the routes for the application. + * Define your route model bindings, pattern filters, etc. */ - public function map() + public function boot() { - Route::middleware(['web', 'auth', 'csrf']) - ->namespace($this->namespace . '\Base') - ->group(base_path('routes/base.php')); + $this->configureRateLimiting(); - Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') - ->namespace($this->namespace . '\Admin') - ->group(base_path('routes/admin.php')); + $this->routes(function () { + Route::middleware(['web', 'auth', 'csrf']) + ->namespace("$this->namespace\\Base") + ->group(base_path('routes/base.php')); - Route::middleware(['web', 'csrf'])->prefix('/auth') - ->namespace($this->namespace . '\Auth') - ->group(base_path('routes/auth.php')); + Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') + ->namespace("$this->namespace\\Admin") + ->group(base_path('routes/admin.php')); - Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) - ->prefix('/api/server/{server}') - ->namespace($this->namespace . '\Server') - ->group(base_path('routes/server.php')); + Route::middleware(['web', 'csrf'])->prefix('/auth') + ->namespace("$this->namespace\\Auth") + ->group(base_path('routes/auth.php')); - Route::middleware([ - sprintf('throttle:%s,%s', config('http.rate_limit.application'), config('http.rate_limit.application_period')), - 'api', - ])->prefix('/api/application') - ->namespace($this->namespace . '\Api\Application') - ->group(base_path('routes/api-application.php')); + Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) + ->prefix('/api/server/{server}') + ->namespace("$this->namespace\\Server") + ->group(base_path('routes/server.php')); - Route::middleware([ - //sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), - 'client-api', - ])->prefix('/api/client') - ->namespace($this->namespace . '\Api\Client') - ->group(base_path('routes/api-client.php')); + Route::middleware(['api', 'throttle:api.application']) + ->prefix('/api/application') + ->namespace("$this->namespace\\Api\\Application") + ->group(base_path('routes/api-application.php')); - Route::middleware(['daemon'])->prefix('/api/remote') - ->namespace($this->namespace . '\Api\Remote') - ->group(base_path('routes/api-remote.php')); + Route::middleware(['client-api', 'throttle:api.client']) + ->prefix('/api/client') + ->namespace("$this->namespace\\Api\\Client") + ->group(base_path('routes/api-client.php')); + + Route::middleware(['daemon'])->prefix('/api/remote') + ->namespace("$this->namespace\\Api\\Remote") + ->group(base_path('routes/api-remote.php')); + }); + } + + /** + * Configure the rate limiters for the application. + */ + protected function configureRateLimiting() + { + // Authentication rate limiting. For login and checkpoint endpoints we'll apply + // a limit of 10 requests per minute, for the forgot password endpoint apply a + // limit of two per minute for the requester so that there is less ability to + // trigger email spam. + RateLimiter::for('authentication', function (Request $request) { + if ($request->route()->named('auth.post.forgot-password')) { + return Limit::perMinute(2)->by($request->ip()); + } + + return Limit::perMinute(10); + }); + + // Configure the throttles for both the application and client APIs below. + // This is configurable per-instance in "config/http.php". By default this + // limiter will be tied to the specific request user, and falls back to the + // request IP if there is no request user present for the key. + // + // This means that an authenticated API user cannot use IP switching to get + // around the limits. + RateLimiter::for('api.client', function (Request $request) { + $key = optional($request->user())->uuid ?: $request->ip(); + + return Limit::perMinutes( + config('http.rate_limit.client_period'), + config('http.rate_limit.client') + )->by($key); + }); + + RateLimiter::for('api.application', function (Request $request) { + $key = optional($request->user())->uuid ?: $request->ip(); + + return Limit::perMinutes( + config('http.rate_limit.application_period'), + config('http.rate_limit.application') + )->by($key); + }); RateLimiter::for('pull', function () { return Limit::perMinute(10); diff --git a/app/Services/Servers/SuspensionService.php b/app/Services/Servers/SuspensionService.php index f7d0f77b1..27bc622f5 100644 --- a/app/Services/Servers/SuspensionService.php +++ b/app/Services/Servers/SuspensionService.php @@ -58,15 +58,20 @@ class SuspensionService throw new ConflictHttpException('Cannot toggle suspension status on a server that is currently being transferred.'); } - $this->connection->transaction(function () use ($action, $server, $isSuspending) { - $server->update([ - 'status' => $isSuspending ? Server::STATUS_SUSPENDED : null, - ]); + // Update the server's suspension status. + $server->update([ + 'status' => $isSuspending ? Server::STATUS_SUSPENDED : null, + ]); - // Only trigger a Wings server sync if it is not currently being transferred. - if (is_null($server->transfer)) { - $this->daemonServerRepository->setServer($server)->sync(); - } - }); + try { + // Tell wings to re-sync the server state. + $this->daemonServerRepository->setServer($server)->sync(); + } catch (\Exception $exception) { + // Rollback the server's suspension status if wings fails to sync the server. + $server->update([ + 'status' => $isSuspending ? null : Server::STATUS_SUSPENDED, + ]); + throw $exception; + } } } diff --git a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json index fa750a449..6ac6eaaf7 100644 --- a/database/Seeders/eggs/minecraft/egg-forge-minecraft.json +++ b/database/Seeders/eggs/minecraft/egg-forge-minecraft.json @@ -4,7 +4,7 @@ "version": "PTDL_v1", "update_url": null }, - "exported_at": "2021-07-04T19:18:55-04:00", + "exported_at": "2021-10-10T07:10:13-04:00", "name": "Forge Minecraft", "author": "support@pterodactyl.io", "description": "Minecraft Forge Server. Minecraft Forge is a modding API (Application Programming Interface), which makes it easier to create mods, and also make sure mods are compatible with each other.", @@ -18,16 +18,16 @@ "ghcr.io\/pterodactyl\/yolks:java_16" ], "file_denylist": [], - "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}", + "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true $( [ ! -f unix_args.txt ] && printf %s \"-jar {{SERVER_JARFILE}}\" || printf %s \"@unix_args.txt\" )", "config": { "files": "{\r\n \"server.properties\": {\r\n \"parser\": \"properties\",\r\n \"find\": {\r\n \"server-ip\": \"0.0.0.0\",\r\n \"enable-query\": \"true\",\r\n \"server-port\": \"{{server.build.default.port}}\",\r\n \"query.port\": \"{{server.build.default.port}}\"\r\n }\r\n }\r\n}", "startup": "{\r\n \"done\": \")! For help, type \"\r\n}", - "logs": "{\r\n \"custom\": false,\r\n \"location\": \"logs\/latest.log\"\r\n}", + "logs": "{}", "stop": "stop" }, "scripts": { "installation": { - "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\n#Go into main direction\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest recommended version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=recommended\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n\tFILE_SITE=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version on minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then \r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid closing out\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link closing out\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nmv $FORGE_JAR $SERVER_JARFILE\r\n\r\n#Deleting installer.jar\r\necho -e \"Deleting installer.jar file.\\n\"\r\nrm -rf installer.jar", + "script": "#!\/bin\/bash\r\n# Forge Installation Script\r\n#\r\n# Server Files: \/mnt\/server\r\napt update\r\napt install -y curl jq\r\n\r\nif [ ! -d \/mnt\/server ]; then\r\n mkdir \/mnt\/server\r\nfi\r\n\r\ncd \/mnt\/server\r\n\r\n# Remove spaces from the version number to avoid issues with curl\r\nFORGE_VERSION=\"$(echo \"$FORGE_VERSION\" | tr -d ' ')\"\r\nMC_VERSION=\"$(echo \"$MC_VERSION\" | tr -d ' ')\"\r\n\r\nif [ ! -z ${FORGE_VERSION} ]; then\r\n DOWNLOAD_LINK=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/${FORGE_VERSION}\/forge-${FORGE_VERSION}\r\n FORGE_JAR=forge-${FORGE_VERSION}*.jar\r\nelse\r\n JSON_DATA=$(curl -sSL https:\/\/files.minecraftforge.net\/maven\/net\/minecraftforge\/forge\/promotions_slim.json)\r\n\r\n if [ \"${MC_VERSION}\" == \"latest\" ] || [ \"${MC_VERSION}\" == \"\" ] ; then\r\n echo -e \"getting latest version of forge.\"\r\n MC_VERSION=$(echo -e ${JSON_DATA} | jq -r '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains(\"recommended\")) | split(\"-\")[0]' | sort -t. -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -1)\r\n \tBUILD_TYPE=latest\r\n fi\r\n\r\n if [ \"${BUILD_TYPE}\" != \"recommended\" ] && [ \"${BUILD_TYPE}\" != \"latest\" ]; then\r\n BUILD_TYPE=recommended\r\n fi\r\n\r\n echo -e \"minecraft version: ${MC_VERSION}\"\r\n echo -e \"build type: ${BUILD_TYPE}\"\r\n\r\n ## some variables for getting versions and things\r\n\tFILE_SITE=https:\/\/maven.minecraftforge.net\/net\/minecraftforge\/forge\/\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" --arg BUILD_TYPE \"${BUILD_TYPE}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains($BUILD_TYPE))')\r\n\r\n ## locating the forge version\r\n if [ \"${VERSION_KEY}\" == \"\" ] && [ \"${BUILD_TYPE}\" == \"recommended\" ]; then\r\n echo -e \"dropping back to latest from recommended due to there not being a recommended version of forge for the mc version requested.\"\r\n VERSION_KEY=$(echo -e ${JSON_DATA} | jq -r --arg MC_VERSION \"${MC_VERSION}\" '.promos | del(.\"latest-1.7.10\") | del(.\"1.7.10-latest-1.7.10\") | to_entries[] | .key | select(contains($MC_VERSION)) | select(contains(\"recommended\"))')\r\n fi\r\n\r\n ## Error if the mc version set wasn't valid.\r\n if [ \"${VERSION_KEY}\" == \"\" ] || [ \"${VERSION_KEY}\" == \"null\" ]; then\r\n \techo -e \"The install failed because there is no valid version of forge for the version of minecraft selected.\"\r\n \texit 1\r\n fi\r\n\r\n FORGE_VERSION=$(echo -e ${JSON_DATA} | jq -r --arg VERSION_KEY \"$VERSION_KEY\" '.promos | .[$VERSION_KEY]')\r\n\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ] || [ \"${MC_VERSION}\" == \"1.8.9\" ]; then\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}.jar\r\n if [ \"${MC_VERSION}\" == \"1.7.10\" ]; then\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}-${MC_VERSION}-universal.jar\r\n fi\r\n else\r\n DOWNLOAD_LINK=${FILE_SITE}${MC_VERSION}-${FORGE_VERSION}\/forge-${MC_VERSION}-${FORGE_VERSION}\r\n FORGE_JAR=forge-${MC_VERSION}-${FORGE_VERSION}.jar\r\n fi\r\nfi\r\n\r\n\r\n#Adding .jar when not eding by SERVER_JARFILE\r\nif [[ ! $SERVER_JARFILE = *\\.jar ]]; then\r\n SERVER_JARFILE=\"$SERVER_JARFILE.jar\"\r\nfi\r\n\r\n#Downloading jars\r\necho -e \"Downloading forge version ${FORGE_VERSION}\"\r\necho -e \"Download link is ${DOWNLOAD_LINK}\"\r\n\r\nif [ ! -z \"${DOWNLOAD_LINK}\" ]; then\r\n if curl --output \/dev\/null --silent --head --fail ${DOWNLOAD_LINK}-installer.jar; then\r\n echo -e \"installer jar download link is valid.\"\r\n else\r\n echo -e \"link is invalid. Exiting now\"\r\n exit 2\r\n fi\r\nelse\r\n echo -e \"no download link provided. Exiting now\"\r\n exit 3\r\nfi\r\n\r\ncurl -s -o installer.jar -sS ${DOWNLOAD_LINK}-installer.jar\r\n\r\n\r\n#Checking if downloaded jars exist\r\nif [ ! -f .\/installer.jar ]; then\r\n echo \"!!! Error by downloading forge version ${FORGE_VERSION} !!!\"\r\n exit\r\nfi\r\n\r\n# Delete args file to support downgrading to versions below 1.17\r\nif [ -f unix_args.txt ]; then\r\n rm unix_args.txt\r\n exit\r\nfi\r\n\r\n\r\n#Installing server\r\necho -e \"Installing forge server.\\n\"\r\njava -jar installer.jar --installServer || { echo -e \"install failed\"; exit 4; }\r\n\r\nif [[ $MC_VERSION == *\"1.17.\"* || $MC_VERSION == *\"1.18.\"* || $FORGE_VERSION == *\"1.17.\"* || $FORGE_VERSION == *\"1.18.\"* ]]; then\r\n # Create a symlink for 1.17+ Forge args\r\n ln -sf libraries\/net\/minecraftforge\/forge\/*\/unix_args.txt unix_args.txt\r\n else\r\n # For versions below 1.17 that ship with jar delete installer.jar\r\n mv $FORGE_JAR $SERVER_JARFILE\r\n echo -e \"Deleting installer.jar file.\\n\"\r\n rm -rf installer.jar\r\nfi", "container": "openjdk:8-jdk-slim", "entrypoint": "bash" } @@ -35,7 +35,7 @@ "variables": [ { "name": "Server Jar File", - "description": "The name of the Jarfile to use when running Forge Mod.", + "description": "The name of the Jarfile to use when running Forge version below 1.17.", "env_variable": "SERVER_JARFILE", "default_value": "server.jar", "user_viewable": true, @@ -58,7 +58,7 @@ "default_value": "recommended", "user_viewable": true, "user_editable": true, - "rules": "required|string|max:20" + "rules": "required|string|in:recommended,latest" }, { "name": "Forge Version", diff --git a/database/Seeders/eggs/rust/egg-rust.json b/database/Seeders/eggs/rust/egg-rust.json index 70eaadbad..60144bfa7 100644 --- a/database/Seeders/eggs/rust/egg-rust.json +++ b/database/Seeders/eggs/rust/egg-rust.json @@ -4,7 +4,7 @@ "version": "PTDL_v1", "update_url": null }, - "exported_at": "2021-05-29T19:02:43-04:00", + "exported_at": "2021-09-15T17:07:50-04:00", "name": "Rust", "author": "support@pterodactyl.io", "description": "The only aim in Rust is to survive. To do this you will need to overcome struggles such as hunger, thirst and cold. Build a fire. Build a shelter. Kill animals for meat. Protect yourself from other players, and kill them for meat. Create alliances with other players and form a town. Do whatever it takes to survive.", @@ -13,11 +13,11 @@ "quay.io\/pterodactyl\/core:rust" ], "file_denylist": [], - "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} {{ADDITIONAL_ARGS}}", + "startup": ".\/RustDedicated -batchmode +server.port {{SERVER_PORT}} +server.identity \"rust\" +rcon.port {{RCON_PORT}} +rcon.web true +server.hostname \\\"{{HOSTNAME}}\\\" +server.level \\\"{{LEVEL}}\\\" +server.description \\\"{{DESCRIPTION}}\\\" +server.url \\\"{{SERVER_URL}}\\\" +server.headerimage \\\"{{SERVER_IMG}}\\\" +server.logoimage \\\"{{SERVER_LOGO}}\\\" +server.maxplayers {{MAX_PLAYERS}} +rcon.password \\\"{{RCON_PASS}}\\\" +server.saveinterval {{SAVEINTERVAL}} +app.port {{APP_PORT}} $( [ -z ${MAP_URL} ] && printf %s \"+server.worldsize \\\"{{WORLD_SIZE}}\\\" +server.seed \\\"{{WORLD_SEED}}\\\"\" || printf %s \"+server.levelurl {{MAP_URL}}\" ) {{ADDITIONAL_ARGS}}", "config": { "files": "{}", - "startup": "{\r\n \"done\": \"Server startup complete\",\r\n \"userInteraction\": []\r\n}", - "logs": "{\r\n \"custom\": false,\r\n \"location\": \"latest.log\"\r\n}", + "startup": "{\r\n \"done\": \"Server startup complete\"\r\n}", + "logs": "{}", "stop": "quit" }, "scripts": { @@ -162,6 +162,15 @@ "user_viewable": true, "user_editable": true, "rules": "nullable|url" + }, + { + "name": "Custom Map URL", + "description": "Overwrites the map with the one from the direct download URL. Invalid URLs will cause the server to crash.", + "env_variable": "MAP_URL", + "default_value": "", + "user_viewable": true, + "user_editable": true, + "rules": "nullable|url" } ] -} +} \ No newline at end of file diff --git a/resources/scripts/components/server/ServerDetailsBlock.tsx b/resources/scripts/components/server/ServerDetailsBlock.tsx index 6df4a764c..a87dc3b01 100644 --- a/resources/scripts/components/server/ServerDetailsBlock.tsx +++ b/resources/scripts/components/server/ServerDetailsBlock.tsx @@ -7,14 +7,16 @@ import TitledGreyBox from '@/components/elements/TitledGreyBox'; import { ServerContext } from '@/state/server'; import CopyOnClick from '@/components/elements/CopyOnClick'; import { SocketEvent, SocketRequest } from '@/components/server/events'; +import UptimeDuration from '@/components/server/UptimeDuration'; interface Stats { memory: number; cpu: number; disk: number; + uptime: number; } -function statusToColor (status: string|null, installing: boolean): TwStyle { +function statusToColor (status: string | null, installing: boolean): TwStyle { if (installing) { status = ''; } @@ -30,7 +32,7 @@ function statusToColor (status: string|null, installing: boolean): TwStyle { } const ServerDetailsBlock = () => { - const [ stats, setStats ] = useState({ memory: 0, cpu: 0, disk: 0 }); + const [ stats, setStats ] = useState({ memory: 0, cpu: 0, disk: 0, uptime: 0 }); const status = ServerContext.useStoreState(state => state.status.value); const connected = ServerContext.useStoreState(state => state.socket.connected); @@ -48,6 +50,7 @@ const ServerDetailsBlock = () => { memory: stats.memory_bytes, cpu: stats.cpu_absolute, disk: stats.disk_bytes, + uptime: stats.uptime || 0, }); }; @@ -69,7 +72,7 @@ const ServerDetailsBlock = () => { const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring); const limits = ServerContext.useStoreState(state => state.server.data!.limits); const primaryAllocation = ServerContext.useStoreState(state => state.server.data!.allocations.filter(alloc => alloc.isDefault).map( - allocation => (allocation.alias || allocation.ip) + ':' + allocation.port + allocation => (allocation.alias || allocation.ip) + ':' + allocation.port, )).toString(); const diskLimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited'; @@ -88,6 +91,11 @@ const ServerDetailsBlock = () => { ]} />  {!status ? 'Connecting...' : (isInstalling ? 'Installing' : (isTransferring) ? 'Transferring' : status)} + {stats.uptime > 0 && + + () + + }

diff --git a/resources/scripts/components/server/UptimeDuration.tsx b/resources/scripts/components/server/UptimeDuration.tsx new file mode 100644 index 000000000..1406450fa --- /dev/null +++ b/resources/scripts/components/server/UptimeDuration.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export default ({ uptime }: { uptime: number }) => { + const hours = Math.floor(Math.floor(uptime) / 60 / 60); + const remainder = Math.floor(uptime - (hours * 60 * 60)); + const minutes = Math.floor(remainder / 60); + const seconds = remainder % 60; + + return ( + <> + {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')} + + ); +}; diff --git a/routes/auth.php b/routes/auth.php index 75eb75931..ff661e3a8 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,7 +1,5 @@ 'guest'], function () { Route::get('/password', 'LoginController@index')->name('auth.forgot-password'); Route::get('/password/reset/{token}', 'LoginController@index')->name('auth.reset'); - // Login endpoints. - Route::post('/login', 'LoginController@login')->middleware('recaptcha'); - Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.checkpoint'); - Route::post('/login/checkpoint/key', 'WebauthnController@auth')->name('auth.checkpoint.key'); + // Apply a throttle to authentication action endpoints, in addition to the + // recaptcha endpoints to slow down manual attack spammers even more. 🤷‍ + // + // @see \Pterodactyl\Providers\RouteServiceProvider + Route::middleware(['throttle:authentication'])->group(function () { + // Login endpoints. + Route::post('/login', 'LoginController@login')->middleware('recaptcha'); + Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.login-checkpoint'); + Route::post('/login/checkpoint/key', 'WebauthnController@auth')->name('auth.login-checkpoint-key'); - // Forgot password route. A post to this endpoint will trigger an - // email to be sent containing a reset token. - Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); + // Forgot password route. A post to this endpoint will trigger an + // email to be sent containing a reset token. + Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail') + ->name('auth.post.forgot-password') + ->middleware('recaptcha'); + }); // Password reset routes. This endpoint is hit after going through // the forgot password routes to acquire a token (or after an account