2015-12-14 03:22:16 +00:00
< ? php
2016-01-20 00:10:39 +00:00
/**
2016-01-20 21:05:16 +00:00
* Pterodactyl - Panel
2017-01-24 22:57:08 +00:00
* Copyright ( c ) 2015 - 2017 Dane Everitt < dane @ daneeveritt . com >.
2016-01-20 00:10:39 +00:00
*
2016-01-20 20:56:40 +00:00
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ), to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
2016-01-20 00:10:39 +00:00
*
2016-01-20 20:56:40 +00:00
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software .
2016-01-20 00:10:39 +00:00
*
2016-01-20 20:56:40 +00:00
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE .
2016-01-20 00:10:39 +00:00
*/
2016-12-07 22:46:38 +00:00
2015-12-14 03:22:16 +00:00
namespace Pterodactyl\Repositories ;
use DB ;
2016-12-07 22:46:38 +00:00
use Crypt ;
use Validator ;
2015-12-14 03:22:16 +00:00
use Pterodactyl\Models ;
use Pterodactyl\Services\UuidService ;
2017-04-18 00:16:05 +00:00
use GuzzleHttp\Exception\ClientException ;
2017-02-09 22:43:54 +00:00
use GuzzleHttp\Exception\TransferException ;
2016-09-28 01:01:46 +00:00
use Pterodactyl\Services\DeploymentService ;
2015-12-14 03:22:16 +00:00
use Pterodactyl\Exceptions\DisplayException ;
use Pterodactyl\Exceptions\DisplayValidationException ;
class ServerRepository
{
2017-03-19 23:36:50 +00:00
/**
* An array of daemon permission to assign to this server .
*
* @ var array
*/
2016-01-19 02:35:37 +00:00
protected $daemonPermissions = [
2016-12-07 22:46:38 +00:00
's:*' ,
2016-01-19 02:35:37 +00:00
];
2015-12-14 03:22:16 +00:00
/**
* Generates a SFTP username for a server given a server name .
2016-12-07 22:46:38 +00:00
* format : mumble_67c7a4b0 .
2015-12-14 03:22:16 +00:00
*
2017-03-19 23:36:50 +00:00
* @ param string $name
* @ param null | string $identifier
2015-12-14 03:22:16 +00:00
* @ return string
*/
2017-01-14 03:22:25 +00:00
protected function generateSFTPUsername ( $name , $identifier = null )
2015-12-14 03:22:16 +00:00
{
2017-01-14 03:22:25 +00:00
if ( is_null ( $identifier ) || ! ctype_alnum ( $identifier )) {
$unique = str_random ( 8 );
} else {
if ( strlen ( $identifier ) < 8 ) {
$unique = $identifier . str_random (( 8 - strlen ( $identifier )));
} else {
$unique = substr ( $identifier , 0 , 8 );
}
}
2015-12-14 03:22:16 +00:00
2017-01-14 03:22:25 +00:00
// Filter the Server Name
$name = trim ( preg_replace ( '/[^\w]+/' , '' , $name ), '_' );
$name = ( strlen ( $name ) < 1 ) ? str_random ( 6 ) : $name ;
return strtolower ( substr ( $name , 0 , 6 ) . '_' . $unique );
2015-12-14 03:22:16 +00:00
}
/**
* Adds a new server to the system .
2017-03-19 23:36:50 +00:00
*
* @ param array $data
* @ return \Pterodactyl\Models\Server
*
* @ throws \Pterodactyl\Exceptions\DisplayException
* @ throws \Pterodactyl\Exceptions\DisplayValidationException
2015-12-14 03:22:16 +00:00
*/
public function create ( array $data )
{
// Validate Fields
$validator = Validator :: make ( $data , [
2017-02-24 23:19:03 +00:00
'user_id' => 'required|exists:users,id' ,
2016-12-30 20:46:10 +00:00
'name' => 'required|regex:/^([\w .-]{1,200})$/' ,
2017-04-01 03:07:19 +00:00
'description' => 'sometimes|nullable|string' ,
2016-02-21 06:18:30 +00:00
'memory' => 'required|numeric|min:0' ,
'swap' => 'required|numeric|min:-1' ,
2015-12-14 03:22:16 +00:00
'io' => 'required|numeric|min:10|max:1000' ,
2016-02-21 06:18:30 +00:00
'cpu' => 'required|numeric|min:0' ,
'disk' => 'required|numeric|min:0' ,
2017-02-11 01:26:38 +00:00
'service_id' => 'required|numeric|min:1|exists:services,id' ,
'option_id' => 'required|numeric|min:1|exists:service_options,id' ,
'location_id' => 'required|numeric|min:1|exists:locations,id' ,
'pack_id' => 'sometimes|nullable|numeric|min:0' ,
2017-03-17 00:49:11 +00:00
'custom_container' => 'string' ,
2016-09-16 22:39:36 +00:00
'startup' => 'string' ,
2017-03-19 16:39:22 +00:00
'auto_deploy' => 'sometimes|required|accepted' ,
2016-11-30 16:01:22 +00:00
'custom_id' => 'sometimes|required|numeric|unique:servers,id' ,
2017-04-20 21:57:40 +00:00
'skip_scripting' => 'sometimes|required|boolean' ,
2015-12-14 03:22:16 +00:00
]);
2017-02-24 23:19:03 +00:00
$validator -> sometimes ( 'node_id' , 'required|numeric|min:1|exists:nodes,id' , function ( $input ) {
2016-12-07 22:46:38 +00:00
return ! ( $input -> auto_deploy );
2016-09-28 01:01:46 +00:00
});
2017-02-24 23:19:03 +00:00
$validator -> sometimes ( 'allocation_id' , 'required|numeric|exists:allocations,id' , function ( $input ) {
return ! ( $input -> auto_deploy );
2016-09-28 01:01:46 +00:00
});
2017-02-24 23:19:03 +00:00
$validator -> sometimes ( 'allocation_additional.*' , 'sometimes|required|numeric|exists:allocations,id' , function ( $input ) {
return ! ( $input -> auto_deploy );
2016-09-28 01:01:46 +00:00
});
2015-12-14 03:22:16 +00:00
// Run validator, throw catchable and displayable exception if it fails.
// Exception includes a JSON result of failed validation rules.
if ( $validator -> fails ()) {
2017-03-16 23:30:04 +00:00
throw new DisplayValidationException ( json_encode ( $validator -> errors ()));
2015-12-14 03:22:16 +00:00
}
2017-02-24 23:19:03 +00:00
$user = Models\User :: findOrFail ( $data [ 'user_id' ]);
2015-12-14 03:22:16 +00:00
2016-09-28 01:01:46 +00:00
$autoDeployed = false ;
2017-02-24 23:19:03 +00:00
if ( isset ( $data [ 'auto_deploy' ]) && $data [ 'auto_deploy' ]) {
2016-09-28 01:01:46 +00:00
// This is an auto-deployment situation
// Ignore any other passed node data
2017-02-24 23:19:03 +00:00
unset ( $data [ 'node_id' ], $data [ 'allocation_id' ]);
2016-09-28 01:01:46 +00:00
$autoDeployed = true ;
2017-02-11 01:26:38 +00:00
$node = DeploymentService :: smartRandomNode ( $data [ 'memory' ], $data [ 'disk' ], $data [ 'location_id' ]);
2016-09-28 01:01:46 +00:00
$allocation = DeploymentService :: randomAllocation ( $node -> id );
} else {
2017-02-11 01:26:38 +00:00
$node = Models\Node :: findOrFail ( $data [ 'node_id' ]);
2016-09-28 01:01:46 +00:00
}
2016-09-05 20:21:36 +00:00
2015-12-14 03:22:16 +00:00
// Verify IP & Port are a.) free and b.) assigned to the node.
// We know the node exists because of 'exists:nodes,id' in the validation
2016-12-07 22:46:38 +00:00
if ( ! $autoDeployed ) {
2017-02-24 23:19:03 +00:00
$allocation = Models\Allocation :: where ( 'id' , $data [ 'allocation_id' ]) -> where ( 'node_id' , $data [ 'node_id' ]) -> whereNull ( 'server_id' ) -> first ();
2016-09-05 20:21:36 +00:00
}
2015-12-14 03:22:16 +00:00
// Something failed in the query, either that combo doesn't exist, or it is in use.
2016-12-07 22:46:38 +00:00
if ( ! $allocation ) {
2017-02-24 23:19:03 +00:00
throw new DisplayException ( 'The selected Allocation ID is either already in use, or unavaliable for this node.' );
2015-12-14 03:22:16 +00:00
}
// Validate those Service Option Variables
// We know the service and option exists because of the validation.
// We need to verify that the option exists for the service, and then check for
// any required variable fields. (fields are labeled env_<env_variable>)
2017-02-16 18:56:28 +00:00
$option = Models\ServiceOption :: where ( 'id' , $data [ 'option_id' ]) -> where ( 'service_id' , $data [ 'service_id' ]) -> first ();
2016-12-07 22:46:38 +00:00
if ( ! $option ) {
2015-12-14 03:22:16 +00:00
throw new DisplayException ( 'The requested service option does not exist for the specified service.' );
}
2016-11-27 19:50:10 +00:00
// Validate the Pack
2017-02-24 23:19:03 +00:00
if ( ! isset ( $data [ 'pack_id' ]) || ( int ) $data [ 'pack_id' ] < 1 ) {
2017-02-11 01:26:38 +00:00
$data [ 'pack_id' ] = null ;
2017-02-24 23:19:03 +00:00
} else {
2017-03-15 01:18:36 +00:00
$pack = Models\Pack :: where ( 'id' , $data [ 'pack_id' ]) -> where ( 'option_id' , $data [ 'option_id' ]) -> first ();
2016-12-14 21:56:25 +00:00
if ( ! $pack ) {
2016-11-27 19:50:10 +00:00
throw new DisplayException ( 'The requested service pack does not seem to exist for this combination.' );
}
}
2016-01-03 23:10:28 +00:00
// Load up the Service Information
2017-02-05 22:58:17 +00:00
$service = Models\Service :: find ( $option -> service_id );
2016-01-03 23:10:28 +00:00
2015-12-14 03:22:16 +00:00
// Check those Variables
2017-02-12 21:02:23 +00:00
$variables = Models\ServiceVariable :: where ( 'option_id' , $data [ 'option_id' ]) -> get ();
2015-12-15 20:08:41 +00:00
$variableList = [];
2015-12-14 03:22:16 +00:00
if ( $variables ) {
2016-12-07 22:46:38 +00:00
foreach ( $variables as $variable ) {
2015-12-14 03:22:16 +00:00
// Is the variable required?
2016-12-30 21:00:51 +00:00
if ( ! isset ( $data [ 'env_' . $variable -> env_variable ])) {
2017-02-24 23:19:03 +00:00
if ( $variable -> required ) {
2015-12-14 03:22:16 +00:00
throw new DisplayException ( 'A required service option variable field (env_' . $variable -> env_variable . ') was missing from the request.' );
}
2016-12-12 19:30:57 +00:00
$variableList [] = [
2016-01-03 23:10:28 +00:00
'id' => $variable -> id ,
'env' => $variable -> env_variable ,
2016-12-07 22:46:38 +00:00
'val' => $variable -> default_value ,
2016-12-12 19:30:57 +00:00
];
2015-12-14 03:22:16 +00:00
continue ;
}
// Check aganist Regex Pattern
2016-12-07 22:46:38 +00:00
if ( ! is_null ( $variable -> regex ) && ! preg_match ( $variable -> regex , $data [ 'env_' . $variable -> env_variable ])) {
2015-12-14 03:22:16 +00:00
throw new DisplayException ( 'Failed to validate service option variable field (env_' . $variable -> env_variable . ') aganist regex (' . $variable -> regex . ').' );
}
2016-12-12 19:30:57 +00:00
$variableList [] = [
2016-01-03 23:10:28 +00:00
'id' => $variable -> id ,
'env' => $variable -> env_variable ,
2016-12-07 22:46:38 +00:00
'val' => $data [ 'env_' . $variable -> env_variable ],
2016-12-12 19:30:57 +00:00
];
2015-12-14 03:22:16 +00:00
continue ;
2015-12-15 20:08:41 +00:00
}
}
// Check Overallocation
2016-12-07 22:46:38 +00:00
if ( ! $autoDeployed ) {
2016-09-28 01:01:46 +00:00
if ( is_numeric ( $node -> memory_overallocate ) || is_numeric ( $node -> disk_overallocate )) {
2017-02-17 17:08:11 +00:00
$totals = Models\Server :: select ( DB :: raw ( 'SUM(memory) as memory, SUM(disk) as disk' )) -> where ( 'node_id' , $node -> id ) -> first ();
2015-12-15 20:08:41 +00:00
2016-09-28 01:01:46 +00:00
// Check memory limits
if ( is_numeric ( $node -> memory_overallocate )) {
$newMemory = $totals -> memory + $data [ 'memory' ];
$memoryLimit = ( $node -> memory * ( 1 + ( $node -> memory_overallocate / 100 )));
2016-12-07 22:46:38 +00:00
if ( $newMemory > $memoryLimit ) {
2016-09-28 01:01:46 +00:00
throw new DisplayException ( 'The amount of memory allocated to this server would put the node over its allocation limits. This node is allowed ' . ( $node -> memory_overallocate + 100 ) . '% of its assigned ' . $node -> memory . 'Mb of memory (' . $memoryLimit . 'Mb) of which ' . (( $totals -> memory / $node -> memory ) * 100 ) . '% (' . $totals -> memory . 'Mb) is in use already. By allocating this server the node would be at ' . (( $newMemory / $node -> memory ) * 100 ) . '% (' . $newMemory . 'Mb) usage.' );
}
2015-12-15 20:08:41 +00:00
}
2016-09-28 01:01:46 +00:00
// Check Disk Limits
if ( is_numeric ( $node -> disk_overallocate )) {
$newDisk = $totals -> disk + $data [ 'disk' ];
$diskLimit = ( $node -> disk * ( 1 + ( $node -> disk_overallocate / 100 )));
2016-12-07 22:46:38 +00:00
if ( $newDisk > $diskLimit ) {
2016-09-28 01:01:46 +00:00
throw new DisplayException ( 'The amount of disk allocated to this server would put the node over its allocation limits. This node is allowed ' . ( $node -> disk_overallocate + 100 ) . '% of its assigned ' . $node -> disk . 'Mb of disk (' . $diskLimit . 'Mb) of which ' . (( $totals -> disk / $node -> disk ) * 100 ) . '% (' . $totals -> disk . 'Mb) is in use already. By allocating this server the node would be at ' . (( $newDisk / $node -> disk ) * 100 ) . '% (' . $newDisk . 'Mb) usage.' );
}
2015-12-15 20:08:41 +00:00
}
}
}
DB :: beginTransaction ();
2016-02-27 15:30:59 +00:00
try {
$uuid = new UuidService ;
// Add Server to the Database
$server = new Models\Server ;
2016-10-14 19:34:01 +00:00
$genUuid = $uuid -> generate ( 'servers' , 'uuid' );
2016-10-14 19:58:52 +00:00
$genShortUuid = $uuid -> generateShort ( 'servers' , 'uuidShort' , $genUuid );
2016-12-07 22:46:38 +00:00
2016-11-30 16:01:22 +00:00
if ( isset ( $data [ 'custom_id' ])) {
$server -> id = $data [ 'custom_id' ];
}
2016-02-27 15:30:59 +00:00
$server -> fill ([
2016-10-14 19:34:01 +00:00
'uuid' => $genUuid ,
'uuidShort' => $genShortUuid ,
2017-02-11 01:26:38 +00:00
'node_id' => $node -> id ,
2016-02-27 15:30:59 +00:00
'name' => $data [ 'name' ],
2017-04-01 16:29:56 +00:00
'description' => $data [ 'description' ],
2016-09-02 01:21:01 +00:00
'suspended' => 0 ,
2017-02-11 01:26:38 +00:00
'owner_id' => $user -> id ,
2016-02-27 15:30:59 +00:00
'memory' => $data [ 'memory' ],
'swap' => $data [ 'swap' ],
'disk' => $data [ 'disk' ],
'io' => $data [ 'io' ],
'cpu' => $data [ 'cpu' ],
'oom_disabled' => ( isset ( $data [ 'oom_disabled' ])) ? true : false ,
2017-02-16 18:56:28 +00:00
'allocation_id' => $allocation -> id ,
2017-02-11 01:26:38 +00:00
'service_id' => $data [ 'service_id' ],
'option_id' => $data [ 'option_id' ],
'pack_id' => $data [ 'pack_id' ],
2016-02-27 15:30:59 +00:00
'startup' => $data [ 'startup' ],
'daemonSecret' => $uuid -> generate ( 'servers' , 'daemonSecret' ),
2017-03-17 00:49:11 +00:00
'image' => ( isset ( $data [ 'custom_container' ]) && ! empty ( $data [ 'custom_container' ])) ? $data [ 'custom_container' ] : $option -> docker_image ,
2016-10-14 19:34:01 +00:00
'username' => $this -> generateSFTPUsername ( $data [ 'name' ], $genShortUuid ),
2016-12-07 22:46:38 +00:00
'sftp_password' => Crypt :: encrypt ( 'not set' ),
2016-02-27 15:30:59 +00:00
]);
$server -> save ();
2015-12-15 20:08:41 +00:00
2016-02-27 15:30:59 +00:00
// Mark Allocation in Use
2017-02-11 01:26:38 +00:00
$allocation -> server_id = $server -> id ;
2016-02-27 15:30:59 +00:00
$allocation -> save ();
2015-12-15 20:08:41 +00:00
2017-02-24 23:19:03 +00:00
// Add Additional Allocations
if ( isset ( $data [ 'allocation_additional' ]) && is_array ( $data [ 'allocation_additional' ])) {
2017-02-24 23:23:03 +00:00
foreach ( $data [ 'allocation_additional' ] as $allocation ) {
2017-02-24 23:19:03 +00:00
$model = Models\Allocation :: where ( 'id' , $allocation ) -> where ( 'node_id' , $data [ 'node_id' ]) -> whereNull ( 'server_id' ) -> first ();
if ( ! $model ) {
continue ;
}
$model -> server_id = $server -> id ;
$model -> save ();
}
}
2016-02-27 15:30:59 +00:00
// Add Variables
2016-12-12 19:30:57 +00:00
$environmentVariables = [
2017-03-17 23:35:48 +00:00
'STARTUP' => $data [ 'startup' ],
2016-12-12 19:30:57 +00:00
];
2016-12-07 22:46:38 +00:00
foreach ( $variableList as $item ) {
2016-12-12 19:30:57 +00:00
$environmentVariables [ $item [ 'env' ]] = $item [ 'val' ];
2017-02-12 21:03:17 +00:00
Models\ServerVariable :: create ([
2016-02-27 15:30:59 +00:00
'server_id' => $server -> id ,
'variable_id' => $item [ 'id' ],
2016-12-07 22:46:38 +00:00
'variable_value' => $item [ 'val' ],
2016-02-27 15:30:59 +00:00
]);
}
2015-12-15 20:08:41 +00:00
2017-02-24 23:19:03 +00:00
$server -> load ( 'allocation' , 'allocations' );
2017-02-09 22:43:54 +00:00
$node -> guzzleClient ([ 'X-Access-Token' => $node -> daemonSecret ]) -> request ( 'POST' , '/servers' , [
2016-01-03 23:10:28 +00:00
'json' => [
'uuid' => ( string ) $server -> uuid ,
'user' => $server -> username ,
'build' => [
'default' => [
2017-02-24 23:19:03 +00:00
'ip' => $server -> allocation -> ip ,
'port' => $server -> allocation -> port ,
2016-01-03 23:10:28 +00:00
],
2017-02-24 23:19:03 +00:00
'ports' => $server -> allocations -> groupBy ( 'ip' ) -> map ( function ( $item ) {
return $item -> pluck ( 'port' );
}) -> toArray (),
2016-01-03 23:10:28 +00:00
'env' => $environmentVariables ,
'memory' => ( int ) $server -> memory ,
'swap' => ( int ) $server -> swap ,
'io' => ( int ) $server -> io ,
'cpu' => ( int ) $server -> cpu ,
'disk' => ( int ) $server -> disk ,
2017-03-17 01:11:15 +00:00
'image' => $server -> image ,
2016-01-03 23:10:28 +00:00
],
'service' => [
2017-03-17 01:11:15 +00:00
'type' => $service -> folder ,
2016-11-27 19:50:10 +00:00
'option' => $option -> tag ,
'pack' => ( isset ( $pack )) ? $pack -> uuid : null ,
2017-04-20 21:57:40 +00:00
'skip_scripting' => isset ( $data [ 'skip_scripting' ]),
2016-01-03 23:10:28 +00:00
],
'keys' => [
2016-11-27 19:50:10 +00:00
( string ) $server -> daemonSecret => $this -> daemonPermissions ,
2016-01-03 23:10:28 +00:00
],
2016-11-27 19:50:10 +00:00
'rebuild' => false ,
2017-04-01 16:29:49 +00:00
'start_on_completion' => isset ( $data [ 'start_on_completion' ]),
2016-12-07 22:46:38 +00:00
],
2016-01-03 23:10:28 +00:00
]);
2015-12-15 20:08:41 +00:00
DB :: commit ();
2016-12-07 22:46:38 +00:00
2017-02-11 01:26:38 +00:00
return $server ;
2016-01-03 23:10:28 +00:00
} catch ( \Exception $ex ) {
2015-12-15 20:08:41 +00:00
DB :: rollBack ();
2016-01-03 23:10:28 +00:00
throw $ex ;
2015-12-15 20:08:41 +00:00
}
2015-12-14 03:22:16 +00:00
}
2016-01-02 23:04:18 +00:00
/**
2017-03-19 23:36:50 +00:00
* Update the details for a server .
*
* @ param int $id
* @ param array $data
2017-04-09 17:15:15 +00:00
* @ return \Pterodactyl\Models\Server
2017-03-19 23:36:50 +00:00
*
* @ throws \Pterodactyl\Exceptions\DisplayException
* @ throws \Pterodactyl\Exceptions\DisplayValidationException
2016-01-02 23:04:18 +00:00
*/
public function updateDetails ( $id , array $data )
{
$uuid = new UuidService ;
$resetDaemonKey = false ;
// Validate Fields
$validator = Validator :: make ( $data , [
2017-03-05 00:03:49 +00:00
'owner_id' => 'sometimes|required|integer|exists:users,id' ,
2017-02-25 05:48:12 +00:00
'name' => 'sometimes|required|regex:([\w .-]{1,200})' ,
2017-04-01 03:07:19 +00:00
'description' => 'sometimes|required|string' ,
2017-03-04 04:38:21 +00:00
'reset_token' => 'sometimes|required|accepted' ,
2016-01-02 23:04:18 +00:00
]);
// Run validator, throw catchable and displayable exception if it fails.
// Exception includes a JSON result of failed validation rules.
if ( $validator -> fails ()) {
2017-03-16 23:30:04 +00:00
throw new DisplayValidationException ( json_encode ( $validator -> errors ()));
2016-01-02 23:04:18 +00:00
}
DB :: beginTransaction ();
2016-02-27 15:30:59 +00:00
try {
2017-02-09 22:43:54 +00:00
$server = Models\Server :: with ( 'user' ) -> findOrFail ( $id );
2016-02-27 15:30:59 +00:00
// Update daemon secret if it was passed.
2017-03-05 00:03:49 +00:00
if ( isset ( $data [ 'reset_token' ]) || ( isset ( $data [ 'owner_id' ]) && ( int ) $data [ 'owner_id' ] !== $server -> user -> id )) {
2016-02-27 15:30:59 +00:00
$oldDaemonKey = $server -> daemonSecret ;
$server -> daemonSecret = $uuid -> generate ( 'servers' , 'daemonSecret' );
$resetDaemonKey = true ;
}
2016-01-02 23:04:18 +00:00
2016-02-27 15:30:59 +00:00
// Save our changes
2017-04-01 03:07:19 +00:00
$server -> fill ( $data ) -> save ();
2016-01-02 23:04:18 +00:00
2016-02-27 15:30:59 +00:00
// Do we need to update? If not, return successful.
2016-12-07 22:46:38 +00:00
if ( ! $resetDaemonKey ) {
2017-04-01 03:07:19 +00:00
return DB :: commit ();
2016-02-27 15:30:59 +00:00
}
2016-01-02 23:04:18 +00:00
2017-02-09 22:43:54 +00:00
$res = $server -> node -> guzzleClient ([
'X-Access-Server' => $server -> uuid ,
'X-Access-Token' => $server -> node -> daemonSecret ,
]) -> request ( 'PATCH' , '/server' , [
2016-01-02 23:04:18 +00:00
'exceptions' => false ,
'json' => [
'keys' => [
( string ) $oldDaemonKey => [],
2016-12-07 22:46:38 +00:00
( string ) $server -> daemonSecret => $this -> daemonPermissions ,
],
],
2016-01-02 23:04:18 +00:00
]);
if ( $res -> getStatusCode () === 204 ) {
2017-04-09 17:15:15 +00:00
DB :: commit ();
return $server ;
2016-01-02 23:04:18 +00:00
} else {
throw new DisplayException ( 'Daemon returned a a non HTTP/204 error code. HTTP/' + $res -> getStatusCode ());
}
} catch ( \Exception $ex ) {
2016-01-03 04:21:22 +00:00
DB :: rollBack ();
2017-04-01 03:07:19 +00:00
throw $ex ;
2016-01-02 23:04:18 +00:00
}
}
2016-09-18 00:14:36 +00:00
/**
2017-03-19 23:36:50 +00:00
* Update the container for a server .
*
* @ param int $id
* @ param array $data
2017-04-09 17:15:15 +00:00
* @ return \Pterodactyl\Models\Server
2017-03-19 23:36:50 +00:00
*
* @ throws \Pterodactyl\Exceptions\DisplayValidationException
2016-09-18 00:14:36 +00:00
*/
public function updateContainer ( $id , array $data )
{
$validator = Validator :: make ( $data , [
2017-02-25 05:48:12 +00:00
'docker_image' => 'required|string' ,
2016-09-18 00:14:36 +00:00
]);
// Run validator, throw catchable and displayable exception if it fails.
// Exception includes a JSON result of failed validation rules.
if ( $validator -> fails ()) {
2017-03-16 23:30:04 +00:00
throw new DisplayValidationException ( json_encode ( $validator -> errors ()));
2016-09-18 00:14:36 +00:00
}
DB :: beginTransaction ();
try {
$server = Models\Server :: findOrFail ( $id );
2017-02-25 05:48:12 +00:00
$server -> image = $data [ 'docker_image' ];
2016-09-18 00:14:36 +00:00
$server -> save ();
2017-02-09 22:43:54 +00:00
$server -> node -> guzzleClient ([
'X-Access-Server' => $server -> uuid ,
'X-Access-Token' => $server -> node -> daemonSecret ,
]) -> request ( 'PATCH' , '/server' , [
2016-09-18 00:14:36 +00:00
'json' => [
'build' => [
2016-12-07 22:46:38 +00:00
'image' => $server -> image ,
],
],
2016-09-18 00:14:36 +00:00
]);
DB :: commit ();
2016-12-07 22:46:38 +00:00
2017-04-09 17:15:15 +00:00
return $server ;
2016-09-18 00:14:36 +00:00
} catch ( \Exception $ex ) {
DB :: rollBack ();
throw $ex ;
}
}
2016-01-03 04:21:22 +00:00
/**
2017-03-19 23:36:50 +00:00
* Update the build details for a server .
*
* @ param int $id
* @ param array $data
* @ return \Pterodactyl\Models\Server
*
* @ throws \Pterodactyl\Exceptions\DisplayException
* @ throws \Pterodactyl\Exceptions\DisplayValidationException
2016-01-03 04:21:22 +00:00
*/
public function changeBuild ( $id , array $data )
{
$validator = Validator :: make ( $data , [
2017-02-25 05:48:12 +00:00
'allocation_id' => 'sometimes|required|exists:allocations,id' ,
'add_allocations' => 'sometimes|required|array' ,
'remove_allocations' => 'sometimes|required|array' ,
'memory' => 'sometimes|required|integer|min:0' ,
'swap' => 'sometimes|required|integer|min:-1' ,
'io' => 'sometimes|required|integer|min:10|max:1000' ,
'cpu' => 'sometimes|required|integer|min:0' ,
'disk' => 'sometimes|required|integer|min:0' ,
2016-01-03 04:21:22 +00:00
]);
// Run validator, throw catchable and displayable exception if it fails.
// Exception includes a JSON result of failed validation rules.
if ( $validator -> fails ()) {
2017-03-16 23:30:04 +00:00
throw new DisplayValidationException ( json_encode ( $validator -> errors ()));
2016-01-03 04:21:22 +00:00
}
DB :: beginTransaction ();
2016-02-27 15:30:59 +00:00
try {
2017-02-09 22:43:54 +00:00
$server = Models\Server :: with ( 'allocation' , 'allocations' ) -> findOrFail ( $id );
2016-09-14 22:36:12 +00:00
$newBuild = [];
2017-02-25 05:48:12 +00:00
$newAllocations = [];
2016-09-14 22:36:12 +00:00
2017-02-25 05:48:12 +00:00
if ( isset ( $data [ 'allocation_id' ])) {
if (( int ) $data [ 'allocation_id' ] !== $server -> allocation_id ) {
$selection = $server -> allocations -> where ( 'id' , $data [ 'allocation_id' ]) -> first ();
2016-12-07 22:46:38 +00:00
if ( ! $selection ) {
2017-02-25 05:48:12 +00:00
throw new DisplayException ( 'The requested default connection is not allocated to this server.' );
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
2017-02-02 23:21:36 +00:00
$server -> allocation_id = $selection -> id ;
2017-02-25 05:48:12 +00:00
$newBuild [ 'default' ] = [ 'ip' => $selection -> ip , 'port' => $selection -> port ];
2016-08-31 20:03:37 +00:00
2017-02-09 22:43:54 +00:00
$server -> load ( 'allocation' );
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
}
2016-09-14 22:36:12 +00:00
$newPorts = false ;
2017-04-18 01:07:37 +00:00
$firstNewAllocation = null ;
// Add Assignments
if ( isset ( $data [ 'add_allocations' ])) {
foreach ( $data [ 'add_allocations' ] as $allocation ) {
$model = Models\Allocation :: where ( 'id' , $allocation ) -> whereNull ( 'server_id' ) -> first ();
if ( ! $model ) {
2017-02-25 05:48:12 +00:00
continue ;
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
2016-10-23 23:07:29 +00:00
$newPorts = true ;
2017-04-18 01:07:37 +00:00
$firstNewAllocation = ( is_null ( $firstNewAllocation )) ? $model -> id : $firstNewAllocation ;
$model -> update ([
'server_id' => $server -> id ,
2016-02-27 15:30:59 +00:00
]);
}
2017-02-09 22:43:54 +00:00
$server -> load ( 'allocations' );
2016-01-03 04:21:22 +00:00
}
2017-04-18 01:07:37 +00:00
// Remove Assignments
if ( isset ( $data [ 'remove_allocations' ])) {
foreach ( $data [ 'remove_allocations' ] as $allocation ) {
// Can't remove the assigned IP/Port combo
if (( int ) $allocation === $server -> allocation_id ) {
// No New Allocation
if ( is_null ( $firstNewAllocation )) {
continue ;
}
// New Allocation, set as the default.
$server -> allocation_id = $firstNewAllocation ;
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
2016-10-23 23:07:29 +00:00
$newPorts = true ;
2017-04-18 01:07:37 +00:00
Models\Allocation :: where ( 'id' , $allocation ) -> where ( 'server_id' , $server -> id ) -> update ([
'server_id' => null ,
2016-02-27 15:30:59 +00:00
]);
}
2017-02-09 22:43:54 +00:00
$server -> load ( 'allocations' );
2016-01-03 04:21:22 +00:00
}
2017-02-25 05:48:12 +00:00
if ( $newPorts ) {
$newBuild [ 'ports|overwrite' ] = $server -> allocations -> groupBy ( 'ip' ) -> map ( function ( $item ) {
return $item -> pluck ( 'port' );
}) -> toArray ();
2016-09-14 22:36:12 +00:00
}
2016-02-27 15:30:59 +00:00
// @TODO: verify that server can be set to this much memory without
// going over node limits.
2016-10-23 23:07:29 +00:00
if ( isset ( $data [ 'memory' ]) && $server -> memory !== ( int ) $data [ 'memory' ]) {
2016-02-27 15:30:59 +00:00
$server -> memory = $data [ 'memory' ];
2016-10-23 23:07:29 +00:00
$newBuild [ 'memory' ] = ( int ) $server -> memory ;
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
2016-10-23 23:07:29 +00:00
if ( isset ( $data [ 'swap' ]) && $server -> swap !== ( int ) $data [ 'swap' ]) {
2016-02-27 15:30:59 +00:00
$server -> swap = $data [ 'swap' ];
2016-10-23 23:07:29 +00:00
$newBuild [ 'swap' ] = ( int ) $server -> swap ;
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
2016-02-27 15:30:59 +00:00
// @TODO: verify that server can be set to this much disk without
// going over node limits.
2016-10-23 23:07:29 +00:00
if ( isset ( $data [ 'disk' ]) && $server -> disk !== ( int ) $data [ 'disk' ]) {
2016-02-27 15:30:59 +00:00
$server -> disk = $data [ 'disk' ];
2016-10-23 23:07:29 +00:00
$newBuild [ 'disk' ] = ( int ) $server -> disk ;
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
2016-10-23 23:07:29 +00:00
if ( isset ( $data [ 'cpu' ]) && $server -> cpu !== ( int ) $data [ 'cpu' ]) {
2016-02-27 15:30:59 +00:00
$server -> cpu = $data [ 'cpu' ];
2016-10-23 23:07:29 +00:00
$newBuild [ 'cpu' ] = ( int ) $server -> cpu ;
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
2016-10-23 23:07:29 +00:00
if ( isset ( $data [ 'io' ]) && $server -> io !== ( int ) $data [ 'io' ]) {
2016-02-27 15:30:59 +00:00
$server -> io = $data [ 'io' ];
2016-10-23 23:07:29 +00:00
$newBuild [ 'io' ] = ( int ) $server -> io ;
2016-02-27 15:30:59 +00:00
}
2016-01-03 04:21:22 +00:00
2016-08-31 20:09:23 +00:00
// Try save() here so if it fails we haven't contacted the daemon
// This won't be committed unless the HTTP request succeedes anyways
$server -> save ();
2016-12-07 22:46:38 +00:00
if ( ! empty ( $newBuild )) {
2017-02-09 22:43:54 +00:00
$server -> node -> guzzleClient ([
'X-Access-Server' => $server -> uuid ,
'X-Access-Token' => $server -> node -> daemonSecret ,
]) -> request ( 'PATCH' , '/server' , [
2016-10-23 23:07:29 +00:00
'json' => [
2016-12-07 22:46:38 +00:00
'build' => $newBuild ,
],
2016-10-23 23:07:29 +00:00
]);
}
2016-08-31 20:09:23 +00:00
2016-01-03 04:21:22 +00:00
DB :: commit ();
2016-12-07 22:46:38 +00:00
2017-02-25 05:48:12 +00:00
return $server ;
2016-01-23 02:43:56 +00:00
} catch ( \Exception $ex ) {
DB :: rollBack ();
throw $ex ;
2016-01-03 04:21:22 +00:00
}
}
2017-03-19 23:36:50 +00:00
/**
* Update the startup details for a server .
*
* @ param int $id
* @ param array $data
* @ param bool $admin
* @ return void
*
* @ throws \GuzzleHttp\Exception\RequestException
* @ throws \Pterodactyl\Exceptions\DisplayException
* @ throws \Pterodactyl\Exceptions\DisplayValidationException
*/
2016-02-13 22:36:03 +00:00
public function updateStartup ( $id , array $data , $admin = false )
2016-01-10 23:57:22 +00:00
{
2017-02-09 22:43:54 +00:00
$server = Models\Server :: with ( 'variables' , 'option.variables' ) -> findOrFail ( $id );
2016-01-10 23:57:22 +00:00
2017-03-05 04:45:22 +00:00
DB :: transaction ( function () use ( $admin , $data , $server ) {
2017-02-16 19:23:22 +00:00
if ( isset ( $data [ 'startup' ]) && $admin ) {
2016-02-27 15:30:59 +00:00
$server -> startup = $data [ 'startup' ];
$server -> save ();
}
2017-02-09 22:43:54 +00:00
if ( $server -> option -> variables ) {
2017-03-06 01:28:29 +00:00
foreach ( $server -> option -> variables as & $variable ) {
2017-03-05 04:45:22 +00:00
$set = isset ( $data [ 'env_' . $variable -> id ]);
// If user is not an admin and are trying to edit a non-editable field
// or an invisible field just silently skip the variable.
if ( ! $admin && ( ! $variable -> user_editable || ! $variable -> user_viewable )) {
2016-02-13 22:29:52 +00:00
continue ;
}
2017-03-12 23:34:06 +00:00
// Perform Field Validation
$validator = Validator :: make ([
'variable_value' => ( $set ) ? $data [ 'env_' . $variable -> id ] : null ,
], [
'variable_value' => $variable -> rules ,
]);
if ( $validator -> fails ()) {
throw new DisplayValidationException ( json_encode (
collect ([
2017-03-12 23:38:50 +00:00
'notice' => [ 'There was a validation error with the `' . $variable -> name . '` variable.' ],
2017-03-12 23:34:06 +00:00
]) -> merge ( $validator -> errors () -> toArray ())
));
2016-02-13 22:29:52 +00:00
}
2017-03-05 04:45:22 +00:00
$svar = Models\ServerVariable :: firstOrNew ([
'server_id' => $server -> id ,
'variable_id' => $variable -> id ,
]);
// Set the value; if one was not passed set it to the default value
if ( $set ) {
$svar -> variable_value = $data [ 'env_' . $variable -> id ];
2016-02-13 22:29:52 +00:00
2017-03-05 04:45:22 +00:00
// Not passed, check if this record exists if so keep value, otherwise set default
} else {
$svar -> variable_value = ( $svar -> exists ) ? $svar -> variable_value : $variable -> default_value ;
2016-02-13 22:29:52 +00:00
}
2016-01-10 23:57:22 +00:00
2017-03-05 04:45:22 +00:00
$svar -> save ();
2016-01-10 23:57:22 +00:00
}
}
2017-03-05 04:45:22 +00:00
// Reload Variables
$server -> load ( 'variables' );
$environment = $server -> option -> variables -> map ( function ( $item , $key ) use ( $server ) {
$display = $server -> variables -> where ( 'variable_id' , $item -> id ) -> pluck ( 'variable_value' ) -> first ();
2016-01-10 23:57:22 +00:00
2017-03-05 04:45:22 +00:00
return [
'variable' => $item -> env_variable ,
'value' => ( ! is_null ( $display )) ? $display : $item -> default_value ,
];
});
2016-01-10 23:57:22 +00:00
2017-02-09 22:43:54 +00:00
$server -> node -> guzzleClient ([
'X-Access-Server' => $server -> uuid ,
'X-Access-Token' => $server -> node -> daemonSecret ,
]) -> request ( 'PATCH' , '/server' , [
2016-01-10 23:57:22 +00:00
'json' => [
'build' => [
2017-03-17 23:35:48 +00:00
'env|overwrite' => $environment -> pluck ( 'value' , 'variable' ) -> merge ([ 'STARTUP' => $server -> startup ]),
2016-12-07 22:46:38 +00:00
],
],
2016-01-10 23:57:22 +00:00
]);
2017-03-05 04:45:22 +00:00
});
2016-01-10 23:57:22 +00:00
}
2017-03-19 23:36:50 +00:00
/**
* Delete a server from the system permanetly .
*
* @ param int $id
* @ param bool $force
* @ return void
*
* @ throws \Pterodactyl\Exceptions\DisplayException
*/
2017-03-05 00:03:49 +00:00
public function delete ( $id , $force = false )
2016-12-07 22:46:38 +00:00
{
2017-04-01 02:12:31 +00:00
$server = Models\Server :: with ( 'node' , 'allocations' , 'variables' ) -> findOrFail ( $id );
2016-10-28 00:05:29 +00:00
2017-03-17 01:11:15 +00:00
// Due to MySQL lockouts if the daemon response fails, we need to
// delete the server from the daemon first. If it succeedes and then
// MySQL fails, users just need to force delete the server.
//
// If this is a force delete, continue anyways.
2016-10-28 00:05:29 +00:00
try {
2017-03-17 01:11:15 +00:00
$server -> node -> guzzleClient ([
'X-Access-Token' => $server -> node -> daemonSecret ,
'X-Access-Server' => $server -> uuid ,
]) -> request ( 'DELETE' , '/servers' );
2017-04-18 00:16:05 +00:00
} catch ( ClientException $ex ) {
// Exception is thrown on 4XX HTTP errors, so catch and determine
// if we should continue, or if there is a permissions error.
//
// Daemon throws a 404 if the server doesn't exist, if that is returned
// continue with deletion, even if not a force deletion.
$response = $ex -> getResponse ();
if ( $ex -> getResponse () -> getStatusCode () !== 404 && ! $force ) {
throw new DisplayException ( $ex -> getMessage ());
}
2017-03-17 01:11:15 +00:00
} catch ( TransferException $ex ) {
2017-04-01 02:12:31 +00:00
if ( ! $force ) {
2017-03-17 01:11:15 +00:00
throw new DisplayException ( $ex -> getMessage ());
}
} catch ( \Exception $ex ) {
throw $ex ;
}
DB :: transaction ( function () use ( $server ) {
$server -> allocations -> each ( function ( $item ) {
$item -> server_id = null ;
$item -> save ();
});
2016-01-04 04:16:03 +00:00
2017-04-18 00:16:05 +00:00
$server -> variables -> each -> delete ();
2016-01-04 04:16:03 +00:00
2017-04-18 00:16:05 +00:00
$server -> load ( 'subusers.permissions' );
$server -> subusers -> each ( function ( $subuser ) {
$subuser -> permissions -> each ( function ( $permission ) {
$perm -> delete ();
});
2017-02-16 18:26:39 +00:00
$subuser -> delete ();
2017-04-18 00:16:05 +00:00
});
2016-01-04 04:16:03 +00:00
2017-04-18 00:16:05 +00:00
$server -> downloads -> each -> delete ();
$server -> tasks -> each -> delete ();
2016-10-28 00:05:29 +00:00
2016-10-23 23:21:57 +00:00
// Delete Databases
2016-10-28 00:05:29 +00:00
// This is the one un-recoverable point where
// transactions will not save us.
2017-03-17 01:11:15 +00:00
$repository = new DatabaseRepository ;
2017-04-18 00:16:05 +00:00
$server -> databases -> each ( function ( $item ) {
$repository -> drop ( $item -> id );
});
2016-01-04 04:16:03 +00:00
2017-03-17 01:11:15 +00:00
// Fully delete the server.
2017-04-01 02:12:31 +00:00
$server -> delete ();
2017-03-17 01:11:15 +00:00
});
2016-01-04 04:16:03 +00:00
}
2017-03-19 23:36:50 +00:00
/**
* Toggle the install status of a serve .
*
* @ param int $id
* @ return bool
*
* @ throws \Pterodactyl\Exceptions\DisplayException
*/
2016-01-04 21:09:22 +00:00
public function toggleInstall ( $id )
{
$server = Models\Server :: findOrFail ( $id );
2017-03-05 00:03:49 +00:00
if ( $server -> installed > 1 ) {
throw new DisplayException ( 'This server was marked as having a failed install or being deleted, you cannot override this.' );
2016-01-23 01:39:16 +00:00
}
2017-02-15 21:59:50 +00:00
$server -> installed = ! $server -> installed ;
2016-12-07 22:46:38 +00:00
2016-01-04 21:09:22 +00:00
return $server -> save ();
}
2016-01-16 05:25:21 +00:00
/**
2017-04-18 00:16:05 +00:00
* Suspends or unsuspends a server .
2017-03-19 23:36:50 +00:00
*
* @ param int $id
2017-04-18 00:16:05 +00:00
* @ param bool $unsuspend
2017-03-19 23:36:50 +00:00
* @ return void
2016-01-16 05:25:21 +00:00
*/
2017-04-18 00:16:05 +00:00
public function toggleAccess ( $id , $unsuspend = true )
2016-01-16 05:25:21 +00:00
{
2017-02-09 22:43:54 +00:00
$server = Models\Server :: with ( 'node' ) -> findOrFail ( $id );
2016-09-02 01:16:38 +00:00
2017-04-18 00:16:05 +00:00
DB :: transaction ( function () use ( $server , $unsuspend ) {
if (
( ! $unsuspend && $server -> suspended ) ||
( $unsuspend && ! $server -> suspended )
) {
2016-09-05 20:21:36 +00:00
return true ;
}
2017-04-18 00:16:05 +00:00
$server -> suspended = ! $unsuspend ;
2016-09-02 01:16:38 +00:00
$server -> save ();
2017-02-09 22:43:54 +00:00
$server -> node -> guzzleClient ([
'X-Access-Token' => $server -> node -> daemonSecret ,
'X-Access-Server' => $server -> uuid ,
2017-04-18 00:16:05 +00:00
]) -> request ( 'POST' , ( $unsuspend ) ? '/server/unsuspend' : '/server/suspend' );
});
2016-01-16 05:25:21 +00:00
}
2017-03-19 23:36:50 +00:00
/**
* Updates the SFTP password for a server .
*
* @ param int $id
* @ param string $password
* @ return void
*
* @ throws \Pterodactyl\Exceptions\DisplayValidationException
*/
2016-01-22 04:53:48 +00:00
public function updateSFTPPassword ( $id , $password )
{
2017-02-09 22:43:54 +00:00
$server = Models\Server :: with ( 'node' ) -> findOrFail ( $id );
2016-01-22 04:53:48 +00:00
2017-02-09 22:43:54 +00:00
$validator = Validator :: make ([ 'password' => $password ], [
2016-12-07 22:46:38 +00:00
'password' => 'required|regex:/^((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})$/' ,
2016-01-22 04:53:48 +00:00
]);
if ( $validator -> fails ()) {
throw new DisplayValidationException ( json_encode ( $validator -> errors ()));
}
2017-04-18 00:16:05 +00:00
DB :: transaction ( function () use ( $password , $server ) {
$server -> sftp_password = Crypt :: encrypt ( $password );
2016-09-02 23:26:48 +00:00
$server -> save ();
2017-02-09 22:43:54 +00:00
$server -> node -> guzzleClient ([
'X-Access-Token' => $server -> node -> daemonSecret ,
'X-Access-Server' => $server -> uuid ,
]) -> request ( 'POST' , '/server/password' , [
'json' => [ 'password' => $password ],
2016-01-22 04:53:48 +00:00
]);
2017-04-18 00:16:05 +00:00
});
2016-01-22 04:53:48 +00:00
}
2017-04-20 22:52:43 +00:00
/**
* Marks a server for reinstallation on the node .
*
* @ param int $id
* @ return void
*/
public function reinstall ( $id )
{
$server = Models\Server :: with ( 'node' ) -> findOrFail ( $id );
DB :: transaction ( function () use ( $server ) {
$server -> installed = 0 ;
$server -> save ();
$server -> node -> guzzleClient ([
'X-Access-Token' => $server -> node -> daemonSecret ,
'X-Access-Server' => $server -> uuid ,
]) -> request ( 'POST' , '/server/reinstall' );
});
}
2015-12-14 03:22:16 +00:00
}