diff --git a/.editorconfig b/.editorconfig index c3f635323..bc49d523e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,11 +1,12 @@ root = true [*] +end_of_line = lf +insert_final_newline = true indent_style = space indent_size = 4 charset = utf-8 trim_trailing_whitespace = true -insert_final_newline = true [*.md] trim_trailing_whitespace = false diff --git a/CHANGELOG.md b/CHANGELOG.md index 33682bd80..dd10b6458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ This file is a running track of new features and fixes to each version of the pa This project follows [Semantic Versioning](http://semver.org) guidelines. +## v0.6.0-pre.1 +### Added +* Remote routes for daemon to contact in order to allow Daemon to retrieve updated service configuration files on boot. Centralizes services to the panel rather than to each daemon. +* Basic service pack implementation to allow assignment of modpacks or software to a server to pre-install applications and allow users to update. +* Users can now have a username as well as client name assigned to thier account. + +### Fixed +* Bug causing error logs to be spammed if someone timed out on an ajax based page. + +### Changed +* Admin API and base routes for user management now define the fields that should be passed to repositories rather than passing all fields. +* User model now defines mass assignment fields using `$fillable` rather than `$guarded`. + +### Deprecated + ## v0.5.6 (Bodacious Boreopterus) ### Added * Added the following languages: Estonian `et`, Dutch `nl`, Norwegian `nb` (partial), Romanian `ro`, and Russian `ru`. Interested in helping us translate the panel into more languages, or improving existing translations? Contact us on Discord and let us know. diff --git a/app/Console/Commands/CleanServiceBackup.php b/app/Console/Commands/CleanServiceBackup.php new file mode 100644 index 000000000..93af785f4 --- /dev/null +++ b/app/Console/Commands/CleanServiceBackup.php @@ -0,0 +1,74 @@ +. + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +namespace Pterodactyl\Console\Commands; + +use Carbon; +use Storage; +use Illuminate\Console\Command; + +class CleanServiceBackup extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'pterodactyl:cleanservices'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Cleans .bak files assocaited with service backups whene editing files through the panel.'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $files = Storage::files('services/.bak'); + + foreach ($files as $file) { + $lastModified = Carbon::createFromTimestamp(Storage::lastModified($file)); + if ($lastModified->diffInMinutes(Carbon::now()) > 5) { + $this->info('Deleting ' . $file); + Storage::delete($file); + } + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c8276b447..53f80281d 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -21,6 +21,7 @@ class Kernel extends ConsoleKernel \Pterodactyl\Console\Commands\ClearTasks::class, \Pterodactyl\Console\Commands\ClearServices::class, \Pterodactyl\Console\Commands\UpdateEmailSettings::class, + \Pterodactyl\Console\Commands\CleanServiceBackup::class, ]; /** @@ -33,5 +34,6 @@ class Kernel extends ConsoleKernel { $schedule->command('pterodactyl:tasks')->everyMinute()->withoutOverlapping(); $schedule->command('pterodactyl:tasks:clearlog')->twiceDaily(3, 15); + $schedule->command('pterodactyl:cleanservices')->twiceDaily(1, 13); } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 2b89cacbf..fedfa1c4b 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -46,13 +46,12 @@ class Handler extends ExceptionHandler */ public function render($request, Exception $exception) { - if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('remote/*')) { + if ($request->expectsJson()) { $response = response()->json([ 'error' => ($exception instanceof DisplayException) ? $exception->getMessage() : 'An unhandled error occured while attempting to process this request.', - ], 500); + ], ($this->isHttpException($exception)) ? $e->getStatusCode() : 500); - // parent::render() will log it, we are bypassing it in this case. - Log::error($exception); + parent::report($exception); } return (isset($response)) ? $response : parent::render($request, $exception); diff --git a/app/Http/Controllers/API/UserController.php b/app/Http/Controllers/API/UserController.php index 59e9af975..c3a658a0e 100755 --- a/app/Http/Controllers/API/UserController.php +++ b/app/Http/Controllers/API/UserController.php @@ -122,6 +122,9 @@ class UserController extends BaseController { try { $user = new UserRepository; + $create = $user->create($request->only([ + 'email', 'username', 'name_first', 'name_last', 'password', 'root_admin', 'custom_id', + ])); $create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id')); return ['id' => $create]; @@ -156,7 +159,9 @@ class UserController extends BaseController { try { $user = new UserRepository; - $user->update($id, $request->all()); + $user->update($id, $request->only([ + 'username', 'email', 'name_first', 'name_last', 'password', 'root_admin', 'language', + ])); return Models\User::findOrFail($id); } catch (DisplayValidationException $ex) { diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php new file mode 100644 index 000000000..fdf1b5a1a --- /dev/null +++ b/app/Http/Controllers/Admin/PackController.php @@ -0,0 +1,252 @@ +. + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +namespace Pterodactyl\Http\Controllers\Admin; + +use DB; +use Log; +use Alert; +use Storage; +use Pterodactyl\Models; +use Illuminate\Http\Request; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Repositories\ServiceRepository\Pack; +use Pterodactyl\Exceptions\DisplayValidationException; + +class PackController extends Controller +{ + public function __construct() + { + // + } + + protected function formatServices() + { + $options = Models\ServiceOptions::select( + 'services.name AS p_service', + 'service_options.id', + 'service_options.name' + )->join('services', 'services.id', '=', 'service_options.parent_service')->get(); + + $array = []; + foreach ($options as &$option) { + if (! array_key_exists($option->p_service, $array)) { + $array[$option->p_service] = []; + } + + $array[$option->p_service] = array_merge($array[$option->p_service], [[ + 'id' => $option->id, + 'name' => $option->name, + ]]); + } + + return $array; + } + + public function listAll(Request $request) + { + return view('admin.services.packs.index', [ + 'services' => Models\Service::all(), + ]); + } + + public function listByOption(Request $request, $id) + { + $option = Models\ServiceOptions::findOrFail($id); + + return view('admin.services.packs.byoption', [ + 'packs' => Models\ServicePack::where('option', $option->id)->get(), + 'service' => Models\Service::findOrFail($option->parent_service), + 'option' => $option, + ]); + } + + public function listByService(Request $request, $id) + { + return view('admin.services.packs.byservice', [ + 'service' => Models\Service::findOrFail($id), + 'options' => Models\ServiceOptions::select( + 'service_options.id', + 'service_options.name', + DB::raw('(SELECT COUNT(id) FROM service_packs WHERE service_packs.option = service_options.id) AS p_count') + )->where('parent_service', $id)->get(), + ]); + } + + public function new(Request $request, $opt = null) + { + return view('admin.services.packs.new', [ + 'services' => $this->formatServices(), + 'packFor' => $opt, + ]); + } + + public function create(Request $request) + { + try { + $repo = new Pack; + $id = $repo->create($request->except([ + '_token', + ])); + Alert::success('Successfully created new service!')->flash(); + + return redirect()->route('admin.services.packs.edit', $id)->withInput(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.packs.new', $request->input('option'))->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An error occured while attempting to add a new service pack.')->flash(); + } + + return redirect()->route('admin.services.packs.new', $request->input('option'))->withInput(); + } + + public function edit(Request $request, $id) + { + $pack = Models\ServicePack::findOrFail($id); + $option = Models\ServiceOptions::select('id', 'parent_service', 'name')->where('id', $pack->option)->first(); + + return view('admin.services.packs.edit', [ + 'pack' => $pack, + 'services' => $this->formatServices(), + 'files' => Storage::files('packs/' . $pack->uuid), + 'service' => Models\Service::findOrFail($option->parent_service), + 'option' => $option, + ]); + } + + public function update(Request $request, $id) + { + if (! is_null($request->input('action_delete'))) { + try { + $repo = new Pack; + $repo->delete($id); + Alert::success('The requested service pack has been deleted from the system.')->flash(); + + return redirect()->route('admin.services.packs'); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An error occured while attempting to delete this pack.')->flash(); + } + + return redirect()->route('admin.services.packs.edit', $id); + } else { + try { + $repo = new Pack; + $repo->update($id, $request->except([ + '_token', + ])); + Alert::success('Service pack has been successfully updated.')->flash(); + } catch (DisplayValidationException $ex) { + return redirect()->route('admin.services.packs.edit', $id)->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An error occured while attempting to add edit this pack.')->flash(); + } + + return redirect()->route('admin.services.packs.edit', $id); + } + } + + public function export(Request $request, $id, $files = false) + { + $pack = Models\ServicePack::findOrFail($id); + $json = [ + 'name' => $pack->name, + 'version' => $pack->version, + 'description' => $pack->dscription, + 'selectable' => (bool) $pack->selectable, + 'visible' => (bool) $pack->visible, + 'build' => [ + 'memory' => $pack->build_memory, + 'swap' => $pack->build_swap, + 'cpu' => $pack->build_cpu, + 'io' => $pack->build_io, + 'container' => $pack->build_container, + 'script' => $pack->build_script, + ], + ]; + + $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); + if ((bool) $files) { + $zip = new \ZipArchive; + if (! $zip->open($filename, \ZipArchive::CREATE)) { + abort(503, 'Unable to open file for writing.'); + } + + $files = Storage::files('packs/' . $pack->uuid); + foreach ($files as $file) { + $zip->addFile(storage_path('app/' . $file), basename(storage_path('app/' . $file))); + } + + $zip->addFromString('import.json', json_encode($json, JSON_PRETTY_PRINT)); + $zip->close(); + + return response()->download($filename, 'pack-' . $pack->name . '.zip')->deleteFileAfterSend(true); + } else { + $fp = fopen($filename, 'a+'); + fwrite($fp, json_encode($json, JSON_PRETTY_PRINT)); + fclose($fp); + + return response()->download($filename, 'pack-' . $pack->name . '.json', [ + 'Content-Type' => 'application/json', + ])->deleteFileAfterSend(true); + } + } + + public function uploadForm(Request $request, $for = null) + { + return view('admin.services.packs.upload', [ + 'services' => $this->formatServices(), + 'for' => $for, + ]); + } + + public function postUpload(Request $request) + { + try { + $repo = new Pack; + $id = $repo->createWithTemplate($request->except([ + '_token', + ])); + Alert::success('Successfully created new service!')->flash(); + + return redirect()->route('admin.services.packs.edit', $id)->withInput(); + } catch (DisplayValidationException $ex) { + return redirect()->back()->withErrors(json_decode($ex->getMessage()))->withInput(); + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An error occured while attempting to add a new service pack.')->flash(); + } + + return redirect()->back(); + } +} diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index dd007881c..2dce69589 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -253,7 +253,7 @@ class ServersController extends Controller * @param \Illuminate\Http\Request $request * @return \Illuminate\Contracts\View\View */ - public function postNewServerServiceVariables(Request $request) + public function postNewServerOptionDetails(Request $request) { if (! $request->input('option')) { return response()->json([ @@ -269,6 +269,7 @@ class ServersController extends Controller ->first(); return response()->json([ + 'packs' => Models\ServicePack::select('id', 'name', 'version')->where('option', $request->input('option'))->where('selectable', true)->get(), 'variables' => Models\ServiceVariables::where('option_id', $request->input('option'))->get(), 'exec' => $option->executable, 'startup' => $option->startup, diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index e548ae07a..4e1b3b1b3 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -27,6 +27,7 @@ namespace Pterodactyl\Http\Controllers\Admin; use DB; use Log; use Alert; +use Storage; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Exceptions\DisplayException; @@ -286,4 +287,39 @@ class ServiceController extends Controller return redirect()->route('admin.services.option', [$service, $option]); } + + public function getConfiguration(Request $request, $serviceId) + { + $service = Models\Service::findOrFail($serviceId); + + return view('admin.services.config', [ + 'service' => $service, + 'contents' => [ + 'json' => Storage::get('services/' . $service->file . '/main.json'), + 'index' => Storage::get('services/' . $service->file . '/index.js'), + ], + ]); + } + + public function postConfiguration(Request $request, $serviceId) + { + try { + $repo = new ServiceRepository\Service; + $repo->updateFile($serviceId, $request->except([ + '_token', + ])); + + return response('', 204); + } catch (DisplayException $ex) { + return response()->json([ + 'error' => $ex->getMessage(), + ], 503); + } catch (\Exception $ex) { + Log::error($ex); + + return response()->json([ + 'error' => 'An error occured while attempting to save the file.', + ], 503); + } + } } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 36e2590ba..8e4425a80 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -116,7 +116,13 @@ class UserController extends Controller { try { $user = new UserRepository; - $userid = $user->create($request->input('email'), $request->input('password')); + $userid = $user->create($request->only([ + 'email', + 'password', + 'name_first', + 'name_last', + 'username', + ])); Alert::success('Account has been successfully created.')->flash(); return redirect()->route('admin.users.view', $userid); @@ -132,19 +138,16 @@ class UserController extends Controller public function updateUser(Request $request, $user) { - $data = [ - 'email' => $request->input('email'), - 'root_admin' => $request->input('root_admin'), - 'password_confirmation' => $request->input('password_confirmation'), - ]; - - if ($request->input('password')) { - $data['password'] = $request->input('password'); - } - try { $repo = new UserRepository; - $repo->update($user, $data); + $repo->update($user, $request->only([ + 'email', + 'password', + 'name_first', + 'name_last', + 'username', + 'root_admin', + ])); Alert::success('User account was successfully updated.')->flash(); } catch (DisplayValidationException $ex) { return redirect()->route('admin.users.view', $user)->withErrors(json_decode($ex->getMessage())); diff --git a/app/Http/Controllers/Daemon/ServiceController.php b/app/Http/Controllers/Daemon/ServiceController.php new file mode 100644 index 000000000..63c449f2d --- /dev/null +++ b/app/Http/Controllers/Daemon/ServiceController.php @@ -0,0 +1,79 @@ +. + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +namespace Pterodactyl\Http\Controllers\Daemon; + +use Storage; +use Pterodactyl\Models; +use Illuminate\Http\Request; +use Pterodactyl\Http\Controllers\Controller; + +class ServiceController extends Controller +{ + /** + * Controller Constructor. + */ + public function __construct() + { + // + } + + /** + * Returns a listing of all services currently on the system, + * as well as the associated files and the file hashes for + * caching purposes. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function list(Request $request) + { + $response = []; + foreach (Models\Service::all() as &$service) { + $response[$service->file] = [ + 'main.json' => sha1_file(storage_path('app/services/' . $service->file . '/main.json')), + 'index.js' => sha1_file(storage_path('app/services/' . $service->file . '/index.js')), + ]; + } + + return response()->json($response); + } + + /** + * Returns the contents of the requested file for the given service. + * + * @param \Illuminate\Http\Request $request + * @param string $service + * @param string $file + * @return \Illuminate\Http\Response + */ + public function pull(Request $request, $service, $file) + { + if (! Storage::exists('services/' . $service . '/' . $file)) { + return response()->json(['error' => 'No such file.'], 404); + } + + return response()->file(storage_path('app/services/' . $service . '/' . $file)); + } +} diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 3b94b2aeb..731052238 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -147,8 +147,8 @@ class AdminRoutes 'uses' => 'Admin\ServersController@postNewServerServiceOptions', ]); - $router->post('/new/service-variables', [ - 'uses' => 'Admin\ServersController@postNewServerServiceVariables', + $router->post('/new/option-details', [ + 'uses' => 'Admin\ServersController@postNewServerOptionDetails', ]); // End Assorted Page Helpers @@ -383,6 +383,15 @@ class AdminRoutes 'uses' => 'Admin\ServiceController@deleteService', ]); + $router->get('/service/{id}/configuration', [ + 'as' => 'admin.services.service.config', + 'uses' => 'Admin\ServiceController@getConfiguration', + ]); + + $router->post('/service/{id}/configuration', [ + 'uses' => 'Admin\ServiceController@postConfiguration', + ]); + $router->get('/service/{service}/option/new', [ 'as' => 'admin.services.option.new', 'uses' => 'Admin\ServiceController@newOption', @@ -424,5 +433,53 @@ class AdminRoutes 'uses' => 'Admin\ServiceController@deleteVariable', ]); }); + + // Service Packs + $router->group([ + 'prefix' => 'admin/services/packs', + 'middleware' => [ + 'auth', + 'admin', + 'csrf', + ], + ], function () use ($router) { + $router->get('/new/{option?}', [ + 'as' => 'admin.services.packs.new', + 'uses' => 'Admin\PackController@new', + ]); + $router->post('/new', [ + 'uses' => 'Admin\PackController@create', + ]); + $router->get('/upload/{option?}', [ + 'as' => 'admin.services.packs.uploadForm', + 'uses' => 'Admin\PackController@uploadForm', + ]); + $router->post('/upload', [ + 'uses' => 'Admin\PackController@postUpload', + ]); + $router->get('/', [ + 'as' => 'admin.services.packs', + 'uses' => 'Admin\PackController@listAll', + ]); + $router->get('/for/option/{option}', [ + 'as' => 'admin.services.packs.option', + 'uses' => 'Admin\PackController@listByOption', + ]); + $router->get('/for/service/{service}', [ + 'as' => 'admin.services.packs.service', + 'uses' => 'Admin\PackController@listByService', + ]); + $router->get('/edit/{pack}', [ + 'as' => 'admin.services.packs.edit', + 'uses' => 'Admin\PackController@edit', + ]); + $router->post('/edit/{pack}', [ + 'uses' => 'Admin\PackController@update', + ]); + $router->get('/edit/{pack}/export/{archive?}', [ + 'as' => 'admin.services.packs.export', + 'uses' => 'Admin\PackController@export', + ]); + }); } } diff --git a/app/Http/Routes/DaemonRoutes.php b/app/Http/Routes/DaemonRoutes.php new file mode 100644 index 000000000..ab8b733ab --- /dev/null +++ b/app/Http/Routes/DaemonRoutes.php @@ -0,0 +1,45 @@ +. + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +namespace Pterodactyl\Http\Routes; + +use Illuminate\Routing\Router; + +class DaemonRoutes +{ + public function map(Router $router) + { + $router->group(['prefix' => 'daemon'], function () use ($router) { + $router->get('services', [ + 'as' => 'daemon.services', + 'uses' => 'Daemon\ServiceController@list', + ]); + + $router->get('services/pull/{service}/{file}', [ + 'as' => 'remote.install', + 'uses' => 'Daemon\ServiceController@pull', + ]); + }); + } +} diff --git a/app/Models/Checksum.php b/app/Models/Checksum.php new file mode 100644 index 000000000..5e775a59f --- /dev/null +++ b/app/Models/Checksum.php @@ -0,0 +1,53 @@ +. + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +namespace Pterodactyl\Models; + +use Illuminate\Database\Eloquent\Model; + +class Checksum extends Model +{ + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'checksums'; + + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'service' => 'integer', + ]; +} diff --git a/app/Models/ServicePack.php b/app/Models/ServicePack.php new file mode 100644 index 000000000..9b5256a3b --- /dev/null +++ b/app/Models/ServicePack.php @@ -0,0 +1,59 @@ +. + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +namespace Pterodactyl\Models; + +use Illuminate\Database\Eloquent\Model; + +class ServicePack extends Model +{ + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'service_packs'; + + /** + * Fields that are not mass assignable. + * + * @var array + */ + protected $guarded = ['id', 'created_at', 'updated_at']; + + /** + * Cast values to correct type. + * + * @var array + */ + protected $casts = [ + 'option' => 'integer', + 'build_memory' => 'integer', + 'build_swap' => 'integer', + 'build_cpu' => 'integer', + 'build_io' => 'integer', + 'selectable' => 'boolean', + 'visible' => 'boolean', + ]; +} diff --git a/app/Models/User.php b/app/Models/User.php index ef7bda0bd..c13a9d133 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -37,13 +37,24 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; -class User extends Model implements - AuthenticatableContract, - AuthorizableContract, - CanResetPasswordContract +class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { use Authenticatable, Authorizable, CanResetPassword, Notifiable; + /** + * The rules for user passwords. + * + * @var string + */ + const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; + + /** + * The regex rules for usernames. + * + * @var string + */ + const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/'; + /** * The table associated with the model. * @@ -52,11 +63,11 @@ class User extends Model implements protected $table = 'users'; /** - * The attributes that are not mass assignable. + * A list of mass-assignable variables. * - * @var array + * @var [type] */ - protected $guarded = ['id', 'remeber_token', 'created_at', 'updated_at']; + protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar']; /** * Cast values to correct type. @@ -66,6 +77,7 @@ class User extends Model implements protected $casts = [ 'root_admin' => 'integer', 'use_totp' => 'integer', + 'gravatar' => 'integer', ]; /** @@ -76,12 +88,10 @@ class User extends Model implements protected $hidden = ['password', 'remember_token', 'totp_secret']; /** - * The rules for user passwords. + * Determines if a user has permissions. * - * @var string + * @return bool */ - const PASSWORD_RULES = 'min:8|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; - public function permissions() { return $this->hasMany(Permission::class); diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 0310cee93..deb6a1b75 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -79,8 +79,9 @@ class ServerRepository 'io' => 'required|numeric|min:10|max:1000', 'cpu' => 'required|numeric|min:0', 'disk' => 'required|numeric|min:0', - 'service' => 'bail|required|numeric|min:1|exists:services,id', - 'option' => 'bail|required|numeric|min:1|exists:service_options,id', + 'service' => 'required|numeric|min:1|exists:services,id', + 'option' => 'required|numeric|min:1|exists:service_options,id', + 'pack' => 'required|numeric|min:0', 'startup' => 'string', 'custom_image_name' => 'required_if:use_custom_image,on', 'auto_deploy' => 'sometimes|boolean', @@ -156,6 +157,18 @@ class ServerRepository throw new DisplayException('The requested service option does not exist for the specified service.'); } + // Validate the Pack + if ($data['pack'] == 0) { + $data['pack'] = null; + } + + if (! is_null($data['pack'])) { + $pack = Models\ServicePack::where('id', $data['pack'])->where('option', $data['option'])->first(); + if (! $pack) { + throw new DisplayException('The requested service pack does not seem to exist for this combination.'); + } + } + // Load up the Service Information $service = Models\Service::find($option->parent_service); @@ -247,6 +260,7 @@ class ServerRepository 'allocation' => $allocation->id, 'service' => $data['service'], 'option' => $data['option'], + 'pack' => $data['pack'], 'startup' => $data['startup'], 'daemonSecret' => $uuid->generate('servers', 'daemonSecret'), 'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image, @@ -311,6 +325,7 @@ class ServerRepository 'service' => [ 'type' => $service->file, 'option' => $option->tag, + 'pack' => (isset($pack)) ? $pack->uuid : null, ], 'keys' => [ (string) $server->daemonSecret => $this->daemonPermissions, diff --git a/app/Repositories/ServiceRepository/Pack.php b/app/Repositories/ServiceRepository/Pack.php new file mode 100644 index 000000000..61327261f --- /dev/null +++ b/app/Repositories/ServiceRepository/Pack.php @@ -0,0 +1,237 @@ +. + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ + +namespace Pterodactyl\Repositories\ServiceRepository; + +use DB; +use Uuid; +use Storage; +use Validator; +use Pterodactyl\Models; +use Pterodactyl\Services\UuidService; +use Pterodactyl\Exceptions\DisplayException; +use Pterodactyl\Exceptions\DisplayValidationException; + +class Pack +{ + public function __construct() + { + // + } + + public function create(array $data) + { + $validator = Validator::make($data, [ + 'name' => 'required|string', + 'version' => 'required|string', + 'description' => 'sometimes|nullable|string', + 'option' => 'required|exists:service_options,id', + 'selectable' => 'sometimes|boolean', + 'visible' => 'sometimes|boolean', + 'build_memory' => 'required|integer|min:0', + 'build_swap' => 'required|integer|min:0', + 'build_cpu' => 'required|integer|min:0', + 'build_io' => 'required|integer|min:10|max:1000', + 'build_container' => 'required|string', + 'build_script' => 'sometimes|nullable|string', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + if (isset($data['file_upload'])) { + if (! $data['file_upload']->isValid()) { + throw new DisplayException('The file provided does not appear to be valid.'); + } + + if (! in_array($data['file_upload']->getMimeType(), [ + 'application/zip', + 'application/gzip', + ])) { + throw new DisplayException('The file provided does not meet the required filetypes of application/zip or application/gzip.'); + } + } + + DB::beginTransaction(); + try { + $uuid = new UuidService; + $pack = Models\ServicePack::create([ + 'option' => $data['option'], + 'uuid' => $uuid->generate('servers', 'uuid'), + 'build_memory' => $data['build_memory'], + 'build_swap' => $data['build_swap'], + 'build_cpu' => $data['build_swap'], + 'build_io' => $data['build_io'], + 'build_script' => (empty($data['build_script'])) ? null : $data['build_script'], + 'build_container' => $data['build_container'], + 'name' => $data['name'], + 'version' => $data['version'], + 'description' => (empty($data['description'])) ? null : $data['description'], + 'selectable' => isset($data['selectable']), + 'visible' => isset($data['visible']), + ]); + + Storage::makeDirectory('packs/' . $pack->uuid); + if (isset($data['file_upload'])) { + $filename = ($data['file_upload']->getMimeType() === 'application/zip') ? 'archive.zip' : 'archive.tar.gz'; + $data['file_upload']->storeAs('packs/' . $pack->uuid, $filename); + } + + DB::commit(); + } catch (\Exception $ex) { + DB::rollBack(); + throw $ex; + } + + return $pack->id; + } + + public function createWithTemplate(array $data) + { + if (! isset($data['file_upload'])) { + throw new DisplayException('No template file was found submitted with this request.'); + } + + if (! $data['file_upload']->isValid()) { + throw new DisplayException('The file provided does not appear to be valid.'); + } + + if (! in_array($data['file_upload']->getMimeType(), [ + 'application/zip', + 'text/plain', + 'application/json', + ])) { + throw new DisplayException('The file provided (' . $data['file_upload']->getMimeType() . ') does not meet the required filetypes of application/zip or application/json.'); + } + + if ($data['file_upload']->getMimeType() === 'application/zip') { + $zip = new \ZipArchive; + if (! $zip->open($data['file_upload']->path())) { + throw new DisplayException('The uploaded archive was unable to be opened.'); + } + + $isZip = $zip->locateName('archive.zip'); + $isTar = $zip->locateName('archive.tar.gz'); + + if ($zip->locateName('import.json') === false || ($isZip === false && $isTar === false)) { + throw new DisplayException('This contents of the provided archive were in an invalid format.'); + } + + $json = json_decode($zip->getFromName('import.json')); + $id = $this->create([ + 'name' => $json->name, + 'version' => $json->version, + 'description' => $json->description, + 'option' => $data['option'], + 'selectable' => $json->selectable, + 'visible' => $json->visible, + 'build_memory' => $json->build->memory, + 'build_swap' => $json->build->swap, + 'build_cpu' => $json->build->cpu, + 'build_io' => $json->build->io, + 'build_container' => $json->build->container, + 'build_script' => $json->build->script, + ]); + + $pack = Models\ServicePack::findOrFail($id); + if (! $zip->extractTo(storage_path('app/packs/' . $pack->uuid), ($isZip === false) ? 'archive.tar.gz' : 'archive.zip')) { + $pack->delete(); + throw new DisplayException('Unable to extract the archive file to the correct location.'); + } + + $zip->close(); + + return $pack->id; + } else { + $json = json_decode(file_get_contents($data['file_upload']->path())); + + return $this->create([ + 'name' => $json->name, + 'version' => $json->version, + 'description' => $json->description, + 'option' => $data['option'], + 'selectable' => $json->selectable, + 'visible' => $json->visible, + 'build_memory' => $json->build->memory, + 'build_swap' => $json->build->swap, + 'build_cpu' => $json->build->cpu, + 'build_io' => $json->build->io, + 'build_container' => $json->build->container, + 'build_script' => $json->build->script, + ]); + } + } + + public function update($id, array $data) + { + $validator = Validator::make($data, [ + 'name' => 'required|string', + 'version' => 'required|string', + 'description' => 'string', + 'option' => 'required|exists:service_options,id', + 'selectable' => 'sometimes|boolean', + 'visible' => 'sometimes|boolean', + 'build_memory' => 'required|integer|min:0', + 'build_swap' => 'required|integer|min:0', + 'build_cpu' => 'required|integer|min:0', + 'build_io' => 'required|integer|min:10|max:1000', + 'build_container' => 'required|string', + 'build_script' => 'sometimes|string', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + DB::transaction(function () use ($id, $data) { + Models\ServicePack::findOrFail($id)->update([ + 'option' => $data['option'], + 'build_memory' => $data['build_memory'], + 'build_swap' => $data['build_swap'], + 'build_cpu' => $data['build_swap'], + 'build_io' => $data['build_io'], + 'build_script' => (empty($data['build_script'])) ? null : $data['build_script'], + 'build_container' => $data['build_container'], + 'name' => $data['name'], + 'version' => $data['version'], + 'description' => (empty($data['description'])) ? null : $data['description'], + 'selectable' => isset($data['selectable']), + 'visible' => isset($data['visible']), + ]); + + return true; + }); + } + + public function delete($id) + { + $pack = Models\ServicePack::findOrFail($id); + // @TODO Check for linked servers; foreign key should block this. + DB::transaction(function () use ($pack) { + $pack->delete(); + Storage::deleteDirectory('packs/' . $pack->uuid); + }); + } +} diff --git a/app/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php index 565e52468..becc290e6 100644 --- a/app/Repositories/ServiceRepository/Service.php +++ b/app/Repositories/ServiceRepository/Service.php @@ -26,6 +26,7 @@ namespace Pterodactyl\Repositories\ServiceRepository; use DB; use Uuid; +use Storage; use Validator; use Pterodactyl\Models; use Pterodactyl\Exceptions\DisplayException; @@ -43,7 +44,7 @@ class Service $validator = Validator::make($data, [ 'name' => 'required|string|min:1|max:255', 'description' => 'required|string', - 'file' => 'required|regex:/^[\w.-]{1,50}$/', + 'file' => 'required|unique:services,file|regex:/^[\w.-]{1,50}$/', 'executable' => 'max:255|regex:/^(.*)$/', 'startup' => 'string', ]); @@ -52,15 +53,23 @@ class Service throw new DisplayValidationException($validator->errors()); } - if (Models\Service::where('file', $data['file'])->first()) { - throw new DisplayException('A service using that configuration file already exists on the system.'); - } - $data['author'] = env('SERVICE_AUTHOR', (string) Uuid::generate(4)); $service = new Models\Service; - $service->fill($data); - $service->save(); + DB::beginTransaction(); + + try { + $service->fill($data); + $service->save(); + + Storage::put('services/' . $data['file'] . '/main.json', '{}'); + Storage::copy('services/.templates/index.js', 'services/' . $data['file'] . '/index.js'); + + DB::commit(); + } catch (\Exception $ex) { + DB::rollBack(); + throw $ex; + } return $service->id; } @@ -101,10 +110,38 @@ class Service Models\ServiceVariables::whereIn('option_id', $options->get()->toArray())->delete(); $options->delete(); $service->delete(); + + Storage::deleteDirectory('services/' . $service->file); DB::commit(); } catch (\Exception $ex) { DB::rollBack(); throw $ex; } } + + public function updateFile($id, array $data) + { + $service = Models\Service::findOrFail($id); + + $validator = Validator::make($data, [ + 'file' => 'required|in:index,main', + 'contents' => 'required|string', + ]); + + if ($validator->fails()) { + throw new DisplayValidationException($validator->errors()); + } + + $filename = ($data['file'] === 'main') ? 'main.json' : 'index.js'; + $filepath = 'services/' . $service->file . '/' . $filename; + $backup = 'services/.bak/' . str_random(12) . '.bak'; + + try { + Storage::move($filepath, $backup); + Storage::put($filepath, $data['contents']); + } catch (\Exception $ex) { + Storage::move($backup, $filepath); + throw $ex; + } + } } diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index add04c920..db715fbbc 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -29,6 +29,7 @@ use DB; use Auth; use Hash; use Carbon; +use Settings; use Validator; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; @@ -52,18 +53,16 @@ class UserRepository * @param int $token A custom user ID. * @return bool|int */ - public function create($email, $password = null, $admin = false, $token = null) + public function create(array $data) { - $validator = Validator::make([ - 'email' => $email, - 'password' => $password, - 'root_admin' => $admin, - 'custom_id' => $token, - ], [ + $validator = Validator::make($data, [ 'email' => 'required|email|unique:users,email', - 'password' => 'nullable|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES, + 'name_first' => 'required|string|between:1,255', + 'name_last' => 'required|string|between:1,255', + 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, 'root_admin' => 'required|boolean', - 'custom_id' => 'nullable|unique:users,id', + 'custom_id' => 'sometimes|nullable|unique:users,id', ]); // Run validator, throw catchable and displayable exception if it fails. @@ -79,26 +78,36 @@ class UserRepository $uuid = new UuidService; // Support for API Services - if (! is_null($token)) { + if (isset($data['custom_id']) && ! is_null($data['custom_id'])) { $user->id = $token; } + // UUIDs are not mass-fillable. $user->uuid = $uuid->generate('users', 'uuid'); - $user->email = $email; - $user->password = Hash::make((is_null($password)) ? str_random(30) : $password); - $user->language = 'en'; - $user->root_admin = ($admin) ? 1 : 0; + + $user->fill([ + 'email' => $data['email'], + 'username' => $data['username'], + 'name_first' => $data['name_first'], + 'name_last' => $data['name_last'], + 'password' => Hash::make((empty($data['password'])) ? str_random(30) : $password), + 'root_admin' => $data['root_admin'], + 'language' => Settings::get('default_language', 'en'), + ]); $user->save(); // Setup a Password Reset to use when they set a password. - $token = str_random(32); - DB::table('password_resets')->insert([ - 'email' => $user->email, - 'token' => $token, - 'created_at' => Carbon::now()->toDateTimeString(), - ]); + // Only used if no password is provided. + if (empty($data['password'])) { + $token = str_random(32); + DB::table('password_resets')->insert([ + 'email' => $user->email, + 'token' => $token, + 'created_at' => Carbon::now()->toDateTimeString(), + ]); - $user->notify((new AccountCreated($token))); + $user->notify((new AccountCreated($token))); + } DB::commit(); @@ -122,7 +131,10 @@ class UserRepository $validator = Validator::make($data, [ 'email' => 'sometimes|required|email|unique:users,email,' . $id, - 'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES, + 'name_first' => 'sometimes|required|string|between:1,255', + 'name_last' => 'sometimes|required|string|between:1,255', + 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, 'root_admin' => 'sometimes|required|boolean', 'language' => 'sometimes|required|string|min:1|max:5', 'use_totp' => 'sometimes|required|boolean', @@ -135,12 +147,15 @@ class UserRepository throw new DisplayValidationException($validator->errors()); } - if (array_key_exists('password', $data)) { + // The password and root_admin fields are not mass assignable. + if (! empty($data['password'])) { $data['password'] = Hash::make($data['password']); + } else { + unset($data['password']); } - if (isset($data['password_confirmation'])) { - unset($data['password_confirmation']); + if (! empty($data['root_admin'])) { + $user->root_admin = $data['root_admin']; } $user->fill($data); diff --git a/database/migrations/2016_11_11_220649_add_pack_support.php b/database/migrations/2016_11_11_220649_add_pack_support.php new file mode 100644 index 000000000..87a66b40a --- /dev/null +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -0,0 +1,46 @@ +increments('id'); + $table->unsignedInteger('option'); + $table->char('uuid', 36)->unique(); + $table->unsignedInteger('build_memory')->nullable(); + $table->unsignedInteger('build_swap')->nullable(); + $table->unsignedInteger('build_cpu')->nullable(); + $table->unsignedInteger('build_io')->nullable(); + $table->text('build_script')->nullable(); + $table->string('build_container')->default('alpine:latest'); + $table->string('name'); + $table->string('version'); + $table->text('description')->nullable(); + $table->boolean('selectable')->default(true); + $table->boolean('visible')->default(true); + $table->timestamps(); + + $table->foreign('option')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('service_packs'); + } +} diff --git a/database/migrations/2016_11_11_231731_set_service_name_unique.php b/database/migrations/2016_11_11_231731_set_service_name_unique.php new file mode 100644 index 000000000..4db76f8e8 --- /dev/null +++ b/database/migrations/2016_11_11_231731_set_service_name_unique.php @@ -0,0 +1,32 @@ +unique('name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropUnique('services_name_unique'); + }); + } +} diff --git a/database/migrations/2016_11_27_142519_add_pack_column.php b/database/migrations/2016_11_27_142519_add_pack_column.php new file mode 100644 index 000000000..f2c2f0964 --- /dev/null +++ b/database/migrations/2016_11_27_142519_add_pack_column.php @@ -0,0 +1,36 @@ +unsignedInteger('pack')->nullable()->after('option'); + + $table->foreign('pack')->references('id')->on('service_packs'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign('servers_pack_foreign'); + $table->dropIndex('servers_pack_foreign'); + $table->dropColumn('pack'); + }); + } +} diff --git a/database/migrations/2017_01_12_135449_add_more_user_data.php b/database/migrations/2017_01_12_135449_add_more_user_data.php new file mode 100644 index 000000000..67bc3f59d --- /dev/null +++ b/database/migrations/2017_01_12_135449_add_more_user_data.php @@ -0,0 +1,50 @@ +string('name_first')->after('email')->nullable(); + $table->string('name_last')->after('name_first')->nullable(); + $table->string('username')->after('uuid'); + $table->boolean('gravatar')->after('totp_secret')->default(true); + }); + + DB::transaction(function () { + foreach (User::all() as &$user) { + $user->username = $user->email; + $user->save(); + } + }); + + Schema::table('users', function (Blueprint $table) { + $table->string('username')->unique()->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('name_first'); + $table->dropColumn('name_last'); + $table->dropColumn('username'); + $table->dropColumn('gravatar'); + }); + } +} diff --git a/public/themes/default/css/pterodactyl.css b/public/themes/default/css/pterodactyl.css index 739f65fb1..f6e37f4e7 100755 --- a/public/themes/default/css/pterodactyl.css +++ b/public/themes/default/css/pterodactyl.css @@ -325,3 +325,14 @@ td.has-progress { padding:0; border:0; } + +.fuelux .checkbox-formheight.checkbox-custom.checkbox-inline.highlight { + height: 36px; + padding: 10px 8px 4px 28px; + width: 100%; +} + +.fuelux .checkbox-formheight.checkbox-custom.checkbox-inline.highlight:before { + left: 8px; + top: 11px; +} diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 12defcd97..5eda226f2 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -201,6 +201,15 @@

Select the type of service that this server will be running.

+ @@ -392,6 +401,7 @@ $(document).ready(function () { handleLoader('#load_services', true); $('#serviceOptions').slideUp(); $('#getOption').html(''); + $('#getPack').html(''); $.ajax({ method: 'POST', @@ -423,10 +433,11 @@ $(document).ready(function () { handleLoader('#serviceOptions', true); $('#serverVariables').html(''); $('input[name="custom_image_name"]').val($(this).find(':selected').data('image')); + $('#getPack').html(''); $.ajax({ method: 'POST', - url: '/admin/servers/new/service-variables', + url: '/admin/servers/new/option-details', headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' }, @@ -436,6 +447,12 @@ $(document).ready(function () { }).done(function (data) { $('#startupExec').html(data.exec); $('input[name="startup"]').val(data.startup); + + $.each(data.packs, function (i, item) { + $('#getPack').append(''); + }); + $('#getPack').append('').parent().parent().removeClass('hidden'); + $.each(data.variables, function (i, item) { var isRequired = (item.required === 1) ? 'Required ' : ''; var dataAppend = ' \ diff --git a/resources/views/admin/services/config.blade.php b/resources/views/admin/services/config.blade.php new file mode 100644 index 000000000..fbfa01d38 --- /dev/null +++ b/resources/views/admin/services/config.blade.php @@ -0,0 +1,180 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- 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: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- 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. --}} +@extends('layouts.admin') + +@section('title') + Manage Service Configuration +@endsection + +@section('content') +
+ +

Service Configuration


+ +
+
+
+
+
+
+
+
{{ $contents['json'] }}
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
{{ $contents['index'] }}
+
+
+
+
+ +
+
+
+
+
+
+
+{!! Theme::js('js/vendor/ace/ace.js') !!} +{!! Theme::js('js/vendor/ace/ext-modelist.js') !!} + +@endsection diff --git a/resources/views/admin/services/index.blade.php b/resources/views/admin/services/index.blade.php index e55fce78e..4106964e9 100644 --- a/resources/views/admin/services/index.blade.php +++ b/resources/views/admin/services/index.blade.php @@ -33,9 +33,10 @@ - + + @@ -44,9 +45,11 @@ + @endforeach + diff --git a/resources/views/admin/services/new.blade.php b/resources/views/admin/services/new.blade.php index cd421ef9c..ce7f0f4f0 100644 --- a/resources/views/admin/services/new.blade.php +++ b/resources/views/admin/services/new.blade.php @@ -55,7 +55,7 @@ /index.js -

This should be the name of the folder on the daemon that contains all of the service logic.

+

This should be a unique alpha-numeric (a-z) name used to identify the service.

diff --git a/resources/views/admin/services/packs/byoption.blade.php b/resources/views/admin/services/packs/byoption.blade.php new file mode 100644 index 000000000..626958566 --- /dev/null +++ b/resources/views/admin/services/packs/byoption.blade.php @@ -0,0 +1,90 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- 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: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- 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. --}} +@extends('layouts.admin') + +@section('title') + Service Packs for {{ $option->name }} +@endsection + +@section('content') +
+ +

Service Packs


+
Service TypeService Type Description Servers
{{ $service->name }} {!! $service->description !!} {{ $service->c_servers }}
+ + + + + + + + + + + @foreach ($packs as $pack) + + + + + + + + @endforeach + + + + +
Pack NameVersionUUIDSelectableVisible
{{ $pack->name }}{{ $pack->version }}{{ $pack->uuid }}@if($pack->selectable)@else@endif@if($pack->visible)@else@endif
+ + + + + + +
+ + +@endsection diff --git a/resources/views/admin/services/packs/byservice.blade.php b/resources/views/admin/services/packs/byservice.blade.php new file mode 100644 index 000000000..f73572c33 --- /dev/null +++ b/resources/views/admin/services/packs/byservice.blade.php @@ -0,0 +1,67 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- 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: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- 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. --}} +@extends('layouts.admin') + +@section('title') + Service Packs for {{ $service->name }} +@endsection + +@section('content') +
+ +

Service Packs


+ + + + + + + + + @foreach ($options as $option) + + + + + @endforeach + + + + +
Service OptionTotal Packs
{{ $option->name }}{{ $option->p_count }}
+ + + + + + +
+
+ +@endsection diff --git a/resources/views/admin/services/packs/edit.blade.php b/resources/views/admin/services/packs/edit.blade.php new file mode 100644 index 000000000..b71256fd9 --- /dev/null +++ b/resources/views/admin/services/packs/edit.blade.php @@ -0,0 +1,218 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- 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: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- 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. --}} +@extends('layouts.admin') + +@section('title') + Add New Service Pack +@endsection + +@section('content') +
+ +

Manage Service Pack


+
+
+
+ +
+ +

The name of the pack which will be seen in dropdown menus and to users.

+
+
+
+ +
+ +

The version of the program included in this pack.

+
+
+
+ +
+ +

Provide a description of the pack which will be shown to users.

+
+
+
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
Build Parameters
+
+
+
+ +
+ + MB +
+
+
+ +
+ + MB +
+
+
+ +
+ + % +
+
+
+ +
+ + I/O +
+
+
+
+

If you would like to set limits on the build container you may do so above. Setting memory, swap, or cpu to 0 will allow unlimited resource utilization. IO must be in a range between 10 to 1000 and is a relative weighting to other container IO usage.

+
+
+
+
+
+ +
+ +

Provide the docker container image that will be used to build this service pack. This container is only used if a build script is provided below.

+
+
+
+ +
{{ $pack->build_script }}
+ +

This script will be run inside the container if provided. You should use this script to download any additional dependencies or compile packages as necessary on the node. Your uploaded archive (if provided), will be available in /input as archive.tar.gz or as archive.zip depending on what format you uploaded as. Your completed pack should be saved as package.tar.gz in the /output directory (e.g. /output/package.tar.gz).

+
+
+
+
+
+
+
+
+
Package Archive
+
+
+
+ @if(count($files) > 1) +
Warning! Service packs should only contain a single pack archive in either .zip or .tar.gz format. We've detected more than one file for this pack.
+ @endif + + + + + + + + + + + @foreach($files as &$file) + + + + + + + @endforeach + +
FilenameFile SizeSHA1 HashLast Modified
{{ basename($file) }}{{ Storage::size($file) }} Bytes{{ sha1_file(storage_path('app/' . $file)) }}{{ Carbon::createFromTimestamp(Storage::lastModified($file))->toDateTimeString() }}
+

If you wish to modify or upload a new file it should be uploaded to {{ storage_path('app/packs/' . $pack->uuid) }} as either archive.zip or archive.tar.gz.

+
+
+
+
+
+
+
+
+
+ {!! csrf_field() !!} + + + + +
+
+ +
+{!! Theme::js('js/vendor/ace/ace.js') !!} +{!! Theme::js('js/vendor/ace/ext-modelist.js') !!} + +@endsection diff --git a/resources/views/admin/services/packs/index.blade.php b/resources/views/admin/services/packs/index.blade.php new file mode 100644 index 000000000..d346dc3ae --- /dev/null +++ b/resources/views/admin/services/packs/index.blade.php @@ -0,0 +1,47 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- 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: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- 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. --}} +@extends('layouts.admin') + +@section('title') + Service Packs +@endsection + +@section('content') +
+ +

Service Packs


+
+ @foreach ($services as $service) + + @endforeach +
+
+ +@endsection diff --git a/resources/views/admin/services/packs/new.blade.php b/resources/views/admin/services/packs/new.blade.php new file mode 100644 index 000000000..af658cce4 --- /dev/null +++ b/resources/views/admin/services/packs/new.blade.php @@ -0,0 +1,193 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- 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: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- 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. --}} +@extends('layouts.admin') + +@section('title') + Add New Service Pack +@endsection + +@section('content') +
+ +

New Service Pack


+
+
+
+ +
+ +

The name of the pack which will be seen in dropdown menus and to users.

+
+
+
+ +
+ +

The version of the program included in this pack.

+
+
+
+ +
+ +

Provide a description of the pack which will be shown to users.

+
+
+
+
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+
+
Build Parameters
+
+
+
+ +
+ + MB +
+
+
+ +
+ + MB +
+
+
+ +
+ + % +
+
+
+ +
+ + I/O +
+
+
+
+

If you would like to set limits on the build container you may do so above. Setting memory, swap, or cpu to 0 will allow unlimited resource utilization. IO must be in a range between 10 to 1000 and is a relative weighting to other container IO usage.

+
+
+
+
+
+ +
+ +

Provide the docker container image that will be used to build this service pack. This container is only used if a build script is provided below.

+
+
+
+ +
+ +

This script will be run inside the container if provided. You should use this script to download any additional dependencies or compile packages as necessary on the node. Your uploaded archive (if provided), will be available in /input as archive.tar.gz or as archive.zip depending on what format you uploaded as. Your completed pack should be saved as package.tar.gz in the /output directory (e.g. /output/package.tar.gz).

+
+
+
+
+
+
+
+
+
File Upload
+
+
+
+ + +

This package file must either be a .zip or .tar.gz archive of files to use for either building or running this pack.

If your file is larger than 20MB we recommend uploading it using SFTP. Once you have added this pack to the system, a path will be provided where you should upload the file. + This server is currently configured with the following limits: upload_max_filesize={{ ini_get('upload_max_filesize') }} and post_max_size={{ ini_get('post_max_size') }}. If your file is larger than either of those values this request will fail.

+
+
+
+
+
+
+
+
+
+ {!! csrf_field() !!} + +
+
+ +
+{!! Theme::js('js/vendor/ace/ace.js') !!} +{!! Theme::js('js/vendor/ace/ext-modelist.js') !!} + +@endsection diff --git a/resources/views/admin/services/packs/upload.blade.php b/resources/views/admin/services/packs/upload.blade.php new file mode 100644 index 000000000..e9ca020fa --- /dev/null +++ b/resources/views/admin/services/packs/upload.blade.php @@ -0,0 +1,45 @@ + diff --git a/resources/views/admin/services/view.blade.php b/resources/views/admin/services/view.blade.php index e05d061fd..4de4d6e9d 100644 --- a/resources/views/admin/services/view.blade.php +++ b/resources/views/admin/services/view.blade.php @@ -106,6 +106,7 @@
{!! csrf_field() !!} +
diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php index 04eaaf207..5b9f59ce0 100644 --- a/resources/views/admin/users/index.blade.php +++ b/resources/views/admin/users/index.blade.php @@ -42,17 +42,21 @@ - - - + + + @foreach ($users as $user) - - - - + + + + + + @endforeach diff --git a/resources/views/admin/users/new.blade.php b/resources/views/admin/users/new.blade.php index 39ad6ecab..abd7cadb0 100644 --- a/resources/views/admin/users/new.blade.php +++ b/resources/views/admin/users/new.blade.php @@ -34,15 +34,38 @@

Create New Account


-
- -
- +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+
+
+ +
+ +
-
+
+

Providing a user password is optional. New user emails prompt users to create a password the first time they login. If a password is provided here you will need to find a different method of providing it to the user.

diff --git a/resources/views/admin/users/view.blade.php b/resources/views/admin/users/view.blade.php index 6ec648ef5..7b817e3a4 100644 --- a/resources/views/admin/users/view.blade.php +++ b/resources/views/admin/users/view.blade.php @@ -31,7 +31,9 @@
  • Accounts
  • {{ $user->email }}
  • -

    Viewing User: {{ $user->email }}


    +

    Viewing User: {{ $user->email }}

    +

    Registered {{ (new Carbon($user->created_at))->toRfc1123String() }}

    +
    @@ -43,19 +45,21 @@
    - +
    - +
    - +
    - -

    Setting this to 'Yes' gives a user full administrative access.

    + +
    +
    +
    + +
    +
    @@ -66,7 +70,6 @@
    -

    {{ trans('base.account.update_pass') }}


    @@ -74,16 +77,22 @@
    -
    - -
    - -
    -
    +
    +
    + +
    + +

    Setting this to 'Yes' gives a user full administrative access.

    +
    +
    +
    diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 63f208d2a..2b309c2a4 100644 --- a/resources/views/layouts/admin.blade.php +++ b/resources/views/layouts/admin.blade.php @@ -35,6 +35,23 @@ {!! Theme::js('js/vendor/sweetalert/sweetalert.min.js') !!} {!! Theme::js('js/vendor/fuelux/fuelux.min.js') !!} {!! Theme::js('js/admin.min.js') !!} + {!! Theme::js('js/bootstrap-notify.min.js') !!} + @show {{ Settings::get('company') }} - @yield('title') @@ -85,6 +102,7 @@
    diff --git a/storage/app/.gitignore b/storage/app/.gitignore index c96a04f00..104980aa3 100755 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -1,2 +1,6 @@ * -!.gitignore \ No newline at end of file +!.gitignore +!services/* +packs/**/* +services/.bak/* +!packs/.githold diff --git a/storage/app/packs/.githold b/storage/app/packs/.githold new file mode 100644 index 000000000..e69de29bb diff --git a/storage/app/services/.templates/index.js b/storage/app/services/.templates/index.js new file mode 100644 index 000000000..aa60eec19 --- /dev/null +++ b/storage/app/services/.templates/index.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2016 Dane Everitt + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ +const rfr = require('rfr'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service; diff --git a/storage/app/services/minecraft/index.js b/storage/app/services/minecraft/index.js new file mode 100644 index 000000000..2d7f812c3 --- /dev/null +++ b/storage/app/services/minecraft/index.js @@ -0,0 +1,38 @@ +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2016 Dane Everitt + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ +const rfr = require('rfr'); +const _ = require('lodash'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core { + onConsole(data) { + // Hide the output spam from Bungeecord getting pinged. + if (_.endsWith(data, '<-> InitialHandler has connected')) return; + return super.onConsole(data); + } +} + +module.exports = Service; diff --git a/storage/app/services/minecraft/main.json b/storage/app/services/minecraft/main.json new file mode 100644 index 000000000..48a490cb3 --- /dev/null +++ b/storage/app/services/minecraft/main.json @@ -0,0 +1,74 @@ +{ + "vanilla": { + "startup": { + "done": ")! For help, type ", + "userInteraction": [ + "Go to eula.txt for more info." + ] + }, + "stop": "stop", + "configs": { + "server.properties": { + "parser": "properties", + "find": { + "server-ip": "0.0.0.0", + "enable-query": "true", + "server-port": "{{ server.build.default.port }}", + "query.port": "{{ server.build.default.port }}" + } + } + }, + "log": { + "custom": false, + "location": "logs/latest.log" + }, + "query": "minecraftping" + }, + "spigot": { + "symlink": "vanilla", + "configs": { + "spigot.yml": { + "parser": "yaml", + "find": { + "settings.restart-on-crash": "false" + } + } + } + }, + "bungeecord": { + "startup": { + "done": "Listening on ", + "userInteraction": [ + "Listening on /0.0.0.0:25577" + ] + }, + "stop": "end", + "configs": { + "config.yml": { + "parser": "yaml", + "find": { + "listeners[0].query_enabled": true, + "listeners[0].query_port": "{{ server.build.default.port }}", + "listeners[0].host": "0.0.0.0:{{ server.build.default.port }}", + "servers.*.address": { + "127.0.0.1": "{{ config.docker.interface }}", + "localhost": "{{ config.docker.interface }}" + } + } + } + }, + "log": { + "custom": false, + "location": "proxy.log.0" + }, + "query": "minecraftping" + }, + "sponge": { + "symlink": "vanilla", + "startup": { + "userInteraction": [ + "You need to agree to the EULA" + ] + } + } +} diff --git a/storage/app/services/srcds/index.js b/storage/app/services/srcds/index.js new file mode 100644 index 000000000..aa60eec19 --- /dev/null +++ b/storage/app/services/srcds/index.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2016 Dane Everitt + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ +const rfr = require('rfr'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service; diff --git a/storage/app/services/srcds/main.json b/storage/app/services/srcds/main.json new file mode 100644 index 000000000..989a67537 --- /dev/null +++ b/storage/app/services/srcds/main.json @@ -0,0 +1,23 @@ +{ + "srcds": { + "startup": { + "done": "Assigned anonymous gameserver Steam ID", + "userInteraction": [] + }, + "stop": "quit", + "configs": {}, + "log": { + "custom": true, + "location": "logs/latest.log" + }, + "query": "protocol-valve" + }, + "ark": { + "symlink": "srcds", + "startup": { + "done": "Setting breakpad minidump AppID" + }, + "stop": "^C", + "query": "none" + } +} diff --git a/storage/app/services/terraria/index.js b/storage/app/services/terraria/index.js new file mode 100644 index 000000000..aa60eec19 --- /dev/null +++ b/storage/app/services/terraria/index.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2016 Dane Everitt + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ +const rfr = require('rfr'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service; diff --git a/storage/app/services/terraria/main.json b/storage/app/services/terraria/main.json new file mode 100644 index 000000000..78f1b5bc0 --- /dev/null +++ b/storage/app/services/terraria/main.json @@ -0,0 +1,23 @@ +{ + "tshock": { + "startup": { + "done": "Type 'help' for a list of commands", + "userInteraction": [] + }, + "stop": "exit", + "configs": { + "tshock/config.json": { + "parser": "json", + "find": { + "ServerPort": "{{ server.build.default.port }}", + "MaxSlots": "{{ server.build.env.MAX_SLOTS }}" + } + } + }, + "log": { + "custom": false, + "location": "ServerLog.txt" + }, + "query": "none" + } +} diff --git a/storage/app/services/voice/index.js b/storage/app/services/voice/index.js new file mode 100644 index 000000000..aa60eec19 --- /dev/null +++ b/storage/app/services/voice/index.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2016 Dane Everitt + * + * 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: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * 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. + */ +const rfr = require('rfr'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service; diff --git a/storage/app/services/voice/main.json b/storage/app/services/voice/main.json new file mode 100644 index 000000000..bc7232f46 --- /dev/null +++ b/storage/app/services/voice/main.json @@ -0,0 +1,50 @@ +{ + "mumble": { + "startup": { + "done": "Server listening on", + "userInteraction": [ + "Generating new server certificate" + ] + }, + "stop": "^C", + "configs": { + "murmur.ini": { + "parser": "ini", + "find": { + "logfile": "murmur.log", + "port": "{{ server.build.default.port }}", + "host": "0.0.0.0", + "users": "{{ server.build.env.MAX_USERS }}" + } + } + }, + "log": { + "custom": true, + "location": "logs/murmur.log" + }, + "query": "mumbleping" + }, + "ts3": { + "startup": { + "done": "listening on 0.0.0.0:", + "userInteraction": [] + }, + "stop": "^C", + "configs": { + "ts3server.ini": { + "parser": "ini", + "find": { + "default_voice_port": "{{ server.build.default.port }}", + "voice_ip": "0.0.0.0", + "query_port": "{{ server.build.default.port }}", + "query_ip": "0.0.0.0" + } + } + }, + "log": { + "custom": true, + "location": "logs/ts3.log" + }, + "query": "none" + } +}
    EmailAccount CreatedAccount UpdatedID + Email + Client NameUsername
    {{ $user->email }} @if($user->root_admin === 1)Administrator@endif{{ $user->created_at }}{{ $user->updated_at }}
    #{{ $user->id }}{{ $user->email }}{{ $user->name_last }}, {{ $user->name_first }}{{ $user->username }}