Merge branch 'develop' into v2

This commit is contained in:
Matthew Penner 2021-10-23 13:26:25 -06:00
commit b966069946
No known key found for this signature in database
GPG key ID: BAB67850901908A8
12 changed files with 164 additions and 78 deletions

View file

@ -30,7 +30,7 @@ else
fi fi
echo "Checking if https is required." 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." echo "Using nginx config already in place."
if [ $LE_EMAIL ]; then if [ $LE_EMAIL ]; then
echo "Checking for cert update" echo "Checking for cert update"
@ -42,15 +42,17 @@ else
echo "Checking if letsencrypt email is set." echo "Checking if letsencrypt email is set."
if [ -z $LE_EMAIL ]; then if [ -z $LE_EMAIL ]; then
echo "No letsencrypt email is set using http config." 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 else
echo "writing ssl config" 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" echo "updating ssl config for domain"
sed -i "s|<domain>|$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/conf.d/default.conf sed -i "s|<domain>|$(echo $APP_URL | sed 's~http[s]*://~~g')|g" /etc/nginx/http.d/panel.conf
echo "generating certs" echo "generating certs"
certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n certbot certonly -d $(echo $APP_URL | sed 's~http[s]*://~~g') --standalone -m $LE_EMAIL --agree-tos -n
fi fi
echo "Removing the default nginx config"
rm -rf /etc/nginx/http.d/default.conf
fi fi
## check for DB up before starting the panel ## check for DB up before starting the panel

View file

@ -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. | | [**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. | | [**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. | | [**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*! | | [**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. | | [**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.| | [**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.|

View file

@ -5,14 +5,9 @@ The following versions of Pterodactyl are receiving active support and maintenan
| Panel | Daemon | Supported | | Panel | Daemon | Supported |
| ----- | ------------ | ------------------ | | ----- | ------------ | ------------------ |
| 1.4.x | wings@1.4.x | :white_check_mark: | | 1.6.x | wings@1.5.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: |
| 0.7.x | daemon@0.6.x | :x: | | 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 ## Reporting a Vulnerability

View file

@ -57,7 +57,7 @@ class UpgradeCommand extends Command
$userDetails = posix_getpwuid(fileowner('public')); $userDetails = posix_getpwuid(fileowner('public'));
$user = $userDetails['name'] ?? 'www-data'; $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 <fg=blue>[{$user}]:</> is this correct?", true)) {
$user = $this->anticipate( $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".', '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')); $groupDetails = posix_getgrgid(filegroup('public'));
$group = $groupDetails['name'] ?? 'www-data'; $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 <fg=blue>[{$group}]:</> is this correct?", true)) {
$group = $this->anticipate( $group = $this->anticipate(
'Please enter the name of the group running your webserver process. Normally this is the same as your user.', '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?')) { if (!$this->confirm('Are you sure you want to run the upgrade process for your Panel?')) {
$this->warn('Upgrade process terminated by user.');
return; return;
} }
} }
@ -173,8 +174,8 @@ class UpgradeCommand extends Command
$this->call('up'); $this->call('up');
}); });
$this->newLine(); $this->newLine(2);
$this->info('Finished running upgrade.'); $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) protected function withProgress(ProgressBar $bar, Closure $callback)

View file

@ -109,5 +109,7 @@ class Kernel extends HttpKernel
'bindings' => SubstituteBindings::class, 'bindings' => SubstituteBindings::class,
'recaptcha' => VerifyReCaptcha::class, 'recaptcha' => VerifyReCaptcha::class,
'node.maintenance' => MaintenanceMiddleware::class, 'node.maintenance' => MaintenanceMiddleware::class,
// API Specific Middleware
'api..key' => AuthenticateKey::class,
]; ];
} }

View file

@ -2,6 +2,7 @@
namespace Pterodactyl\Providers; namespace Pterodactyl\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Cache\RateLimiting\Limit; use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\RateLimiter;
@ -19,44 +20,87 @@ class RouteServiceProvider extends ServiceProvider
protected $namespace = 'Pterodactyl\Http\Controllers'; 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']) $this->configureRateLimiting();
->namespace($this->namespace . '\Base')
->group(base_path('routes/base.php'));
Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin') $this->routes(function () {
->namespace($this->namespace . '\Admin') Route::middleware(['web', 'auth', 'csrf'])
->group(base_path('routes/admin.php')); ->namespace("$this->namespace\\Base")
->group(base_path('routes/base.php'));
Route::middleware(['web', 'csrf'])->prefix('/auth') Route::middleware(['web', 'auth', 'admin', 'csrf'])->prefix('/admin')
->namespace($this->namespace . '\Auth') ->namespace("$this->namespace\\Admin")
->group(base_path('routes/auth.php')); ->group(base_path('routes/admin.php'));
Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance']) Route::middleware(['web', 'csrf'])->prefix('/auth')
->prefix('/api/server/{server}') ->namespace("$this->namespace\\Auth")
->namespace($this->namespace . '\Server') ->group(base_path('routes/auth.php'));
->group(base_path('routes/server.php'));
Route::middleware([ Route::middleware(['web', 'csrf', 'auth', 'server', 'node.maintenance'])
sprintf('throttle:%s,%s', config('http.rate_limit.application'), config('http.rate_limit.application_period')), ->prefix('/api/server/{server}')
'api', ->namespace("$this->namespace\\Server")
])->prefix('/api/application') ->group(base_path('routes/server.php'));
->namespace($this->namespace . '\Api\Application')
->group(base_path('routes/api-application.php'));
Route::middleware([ Route::middleware(['api', 'throttle:api.application'])
//sprintf('throttle:%s,%s', config('http.rate_limit.client'), config('http.rate_limit.client_period')), ->prefix('/api/application')
'client-api', ->namespace("$this->namespace\\Api\\Application")
])->prefix('/api/client') ->group(base_path('routes/api-application.php'));
->namespace($this->namespace . '\Api\Client')
->group(base_path('routes/api-client.php'));
Route::middleware(['daemon'])->prefix('/api/remote') Route::middleware(['client-api', 'throttle:api.client'])
->namespace($this->namespace . '\Api\Remote') ->prefix('/api/client')
->group(base_path('routes/api-remote.php')); ->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 () { RateLimiter::for('pull', function () {
return Limit::perMinute(10); return Limit::perMinute(10);

View file

@ -58,15 +58,20 @@ class SuspensionService
throw new ConflictHttpException('Cannot toggle suspension status on a server that is currently being transferred.'); throw new ConflictHttpException('Cannot toggle suspension status on a server that is currently being transferred.');
} }
$this->connection->transaction(function () use ($action, $server, $isSuspending) { // Update the server's suspension status.
$server->update([ $server->update([
'status' => $isSuspending ? Server::STATUS_SUSPENDED : null, 'status' => $isSuspending ? Server::STATUS_SUSPENDED : null,
]); ]);
// Only trigger a Wings server sync if it is not currently being transferred. try {
if (is_null($server->transfer)) { // Tell wings to re-sync the server state.
$this->daemonServerRepository->setServer($server)->sync(); $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;
}
} }
} }

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@
"version": "PTDL_v1", "version": "PTDL_v1",
"update_url": null "update_url": null
}, },
"exported_at": "2021-05-29T19:02:43-04:00", "exported_at": "2021-09-15T17:07:50-04:00",
"name": "Rust", "name": "Rust",
"author": "support@pterodactyl.io", "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.", "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" "quay.io\/pterodactyl\/core:rust"
], ],
"file_denylist": [], "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": { "config": {
"files": "{}", "files": "{}",
"startup": "{\r\n \"done\": \"Server startup complete\",\r\n \"userInteraction\": []\r\n}", "startup": "{\r\n \"done\": \"Server startup complete\"\r\n}",
"logs": "{\r\n \"custom\": false,\r\n \"location\": \"latest.log\"\r\n}", "logs": "{}",
"stop": "quit" "stop": "quit"
}, },
"scripts": { "scripts": {
@ -162,6 +162,15 @@
"user_viewable": true, "user_viewable": true,
"user_editable": true, "user_editable": true,
"rules": "nullable|url" "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"
} }
] ]
} }

View file

@ -7,14 +7,16 @@ import TitledGreyBox from '@/components/elements/TitledGreyBox';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import CopyOnClick from '@/components/elements/CopyOnClick'; import CopyOnClick from '@/components/elements/CopyOnClick';
import { SocketEvent, SocketRequest } from '@/components/server/events'; import { SocketEvent, SocketRequest } from '@/components/server/events';
import UptimeDuration from '@/components/server/UptimeDuration';
interface Stats { interface Stats {
memory: number; memory: number;
cpu: number; cpu: number;
disk: number; disk: number;
uptime: number;
} }
function statusToColor (status: string|null, installing: boolean): TwStyle { function statusToColor (status: string | null, installing: boolean): TwStyle {
if (installing) { if (installing) {
status = ''; status = '';
} }
@ -30,7 +32,7 @@ function statusToColor (status: string|null, installing: boolean): TwStyle {
} }
const ServerDetailsBlock = () => { const ServerDetailsBlock = () => {
const [ stats, setStats ] = useState<Stats>({ memory: 0, cpu: 0, disk: 0 }); const [ stats, setStats ] = useState<Stats>({ memory: 0, cpu: 0, disk: 0, uptime: 0 });
const status = ServerContext.useStoreState(state => state.status.value); const status = ServerContext.useStoreState(state => state.status.value);
const connected = ServerContext.useStoreState(state => state.socket.connected); const connected = ServerContext.useStoreState(state => state.socket.connected);
@ -48,6 +50,7 @@ const ServerDetailsBlock = () => {
memory: stats.memory_bytes, memory: stats.memory_bytes,
cpu: stats.cpu_absolute, cpu: stats.cpu_absolute,
disk: stats.disk_bytes, disk: stats.disk_bytes,
uptime: stats.uptime || 0,
}); });
}; };
@ -69,7 +72,7 @@ const ServerDetailsBlock = () => {
const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring); const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring);
const limits = ServerContext.useStoreState(state => state.server.data!.limits); const limits = ServerContext.useStoreState(state => state.server.data!.limits);
const primaryAllocation = ServerContext.useStoreState(state => state.server.data!.allocations.filter(alloc => alloc.isDefault).map( 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(); )).toString();
const diskLimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited'; const diskLimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited';
@ -88,6 +91,11 @@ const ServerDetailsBlock = () => {
]} ]}
/> />
&nbsp;{!status ? 'Connecting...' : (isInstalling ? 'Installing' : (isTransferring) ? 'Transferring' : status)} &nbsp;{!status ? 'Connecting...' : (isInstalling ? 'Installing' : (isTransferring) ? 'Transferring' : status)}
{stats.uptime > 0 &&
<span css={tw`ml-2`}>
(<UptimeDuration uptime={stats.uptime / 1000}/>)
</span>
}
</p> </p>
<CopyOnClick text={primaryAllocation}> <CopyOnClick text={primaryAllocation}>
<p css={tw`text-xs mt-2`}> <p css={tw`text-xs mt-2`}>

View file

@ -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')}
</>
);
};

View file

@ -1,7 +1,5 @@
<?php <?php
use Illuminate\Support\Facades\Route;
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Authentication Routes | Authentication Routes
@ -17,14 +15,22 @@ Route::group(['middleware' => 'guest'], function () {
Route::get('/password', 'LoginController@index')->name('auth.forgot-password'); Route::get('/password', 'LoginController@index')->name('auth.forgot-password');
Route::get('/password/reset/{token}', 'LoginController@index')->name('auth.reset'); Route::get('/password/reset/{token}', 'LoginController@index')->name('auth.reset');
// Login endpoints. // Apply a throttle to authentication action endpoints, in addition to the
Route::post('/login', 'LoginController@login')->middleware('recaptcha'); // recaptcha endpoints to slow down manual attack spammers even more. 🤷‍
Route::post('/login/checkpoint', 'LoginCheckpointController')->name('auth.checkpoint'); //
Route::post('/login/checkpoint/key', 'WebauthnController@auth')->name('auth.checkpoint.key'); // @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 // Forgot password route. A post to this endpoint will trigger an
// email to be sent containing a reset token. // email to be sent containing a reset token.
Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')->middleware('recaptcha'); Route::post('/password', 'ForgotPasswordController@sendResetLinkEmail')
->name('auth.post.forgot-password')
->middleware('recaptcha');
});
// Password reset routes. This endpoint is hit after going through // Password reset routes. This endpoint is hit after going through
// the forgot password routes to acquire a token (or after an account // the forgot password routes to acquire a token (or after an account