From cfd5e0e85415c412474d726f58028f0681228e74 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 9 Nov 2016 17:58:14 -0500 Subject: [PATCH 01/22] Implement base service file modification through panel --- app/Console/Commands/CleanServiceBackup.php | 73 +++++++ app/Console/Kernel.php | 1 + .../Controllers/Admin/ServiceController.php | 33 ++++ app/Http/Routes/AdminRoutes.php | 9 + app/Models/Checksum.php | 54 ++++++ .../ServiceRepository/Service.php | 41 ++++ .../2016_11_09_163911_add_checksums_table.php | 36 ++++ .../views/admin/services/config.blade.php | 180 ++++++++++++++++++ resources/views/admin/services/view.blade.php | 1 + resources/views/layouts/admin.blade.php | 17 ++ storage/app/.gitignore | 4 +- storage/app/services/minecraft/index.js | 60 ++++++ storage/app/services/minecraft/main.json | 75 ++++++++ storage/app/services/srcds/index.js | 57 ++++++ storage/app/services/srcds/main.json | 25 +++ storage/app/services/terraria/index.js | 57 ++++++ storage/app/services/terraria/main.json | 24 +++ storage/app/services/voice/index.js | 57 ++++++ storage/app/services/voice/main.json | 52 +++++ 19 files changed, 855 insertions(+), 1 deletion(-) create mode 100644 app/Console/Commands/CleanServiceBackup.php create mode 100644 app/Models/Checksum.php create mode 100644 database/migrations/2016_11_09_163911_add_checksums_table.php create mode 100644 resources/views/admin/services/config.blade.php create mode 100644 storage/app/services/minecraft/index.js create mode 100644 storage/app/services/minecraft/main.json create mode 100644 storage/app/services/srcds/index.js create mode 100644 storage/app/services/srcds/main.json create mode 100644 storage/app/services/terraria/index.js create mode 100644 storage/app/services/terraria/main.json create mode 100644 storage/app/services/voice/index.js create mode 100644 storage/app/services/voice/main.json diff --git a/app/Console/Commands/CleanServiceBackup.php b/app/Console/Commands/CleanServiceBackup.php new file mode 100644 index 00000000..82453d6c --- /dev/null +++ b/app/Console/Commands/CleanServiceBackup.php @@ -0,0 +1,73 @@ + + * + * 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 c8276b44..4cd65f46 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, ]; /** diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index dc597747..35294343 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -27,6 +27,7 @@ use Alert; use DB; use Log; use Validator; +use Storage; use Pterodactyl\Models; use Pterodactyl\Repositories\ServiceRepository; @@ -274,4 +275,36 @@ 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/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index bfc07b72..726fccc2 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -379,6 +379,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' diff --git a/app/Models/Checksum.php b/app/Models/Checksum.php new file mode 100644 index 00000000..8038b7bf --- /dev/null +++ b/app/Models/Checksum.php @@ -0,0 +1,54 @@ + + * + * 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/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php index 8feb92c1..4dc1755b 100644 --- a/app/Repositories/ServiceRepository/Service.php +++ b/app/Repositories/ServiceRepository/Service.php @@ -26,6 +26,7 @@ namespace Pterodactyl\Repositories\ServiceRepository; use DB; use Validator; use Uuid; +use Storage; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; @@ -110,4 +111,44 @@ class Service } } + 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'; + + DB::beginTransaction(); + + try { + Storage::move($filepath, $backup); + Storage::put($filepath, $data['contents']); + + $checksum = Models\Checksum::firstOrNew([ + 'service' => $service->id, + 'filename' => $filename + ]); + + $checksum->checksum = sha1_file(storage_path('app/' . $filepath)); + $checksum->save(); + + DB::commit(); + } catch(\Exception $ex) { + DB::rollback(); + Storage::move($backup, $filepath); + throw $ex; + } + + } + } diff --git a/database/migrations/2016_11_09_163911_add_checksums_table.php b/database/migrations/2016_11_09_163911_add_checksums_table.php new file mode 100644 index 00000000..4935faaf --- /dev/null +++ b/database/migrations/2016_11_09_163911_add_checksums_table.php @@ -0,0 +1,36 @@ +increments('id'); + $table->integer('service')->unsigned(); + $table->string('filename'); + $table->char('checksum', 40); + $table->timestamps(); + + $table->foreign('service')->references('id')->on('services'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('checksums'); + } +} diff --git a/resources/views/admin/services/config.blade.php b/resources/views/admin/services/config.blade.php new file mode 100644 index 00000000..fbfa01d3 --- /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/view.blade.php b/resources/views/admin/services/view.blade.php index e05d061f..4de4d6e9 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/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 803c222a..f8ff0a49 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') diff --git a/storage/app/.gitignore b/storage/app/.gitignore index c96a04f0..8d9045b3 100755 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -1,2 +1,4 @@ * -!.gitignore \ No newline at end of file +!.gitignore +!services/* +services/.bak/* diff --git a/storage/app/services/minecraft/index.js b/storage/app/services/minecraft/index.js new file mode 100644 index 00000000..67c4dbb2 --- /dev/null +++ b/storage/app/services/minecraft/index.js @@ -0,0 +1,60 @@ +'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 Configuration = rfr('src/services/minecraft/main.json'); +const Core = rfr('src/services/index.js'); + +class Service extends Core { + constructor(server) { + super(server, Configuration); + } + + onPreflight(next) { + return super.onPreflight(next); + } + + onStart(next) { + return super.onStart(next); + } + + onConsole(data) { + // Hide the output spam from Bungeecord getting pinged. + if (_.endsWith(data, '<-> InitialHandler has connected')) return; + return super.onConsole(data); + } + + onStop(next) { + return super.onStop(next); + } + + doQuery(next) { + return super.doQuery(next); + } + +} + +module.exports = Service; diff --git a/storage/app/services/minecraft/main.json b/storage/app/services/minecraft/main.json new file mode 100644 index 00000000..1a94258a --- /dev/null +++ b/storage/app/services/minecraft/main.json @@ -0,0 +1,75 @@ +{ + "latest": { + "tag": "^(latest)$", + "symlink": "vanilla" + }, + "vanilla": { + "tag": "^(vanilla){1}(-[\\w\\d.-]+)?$", + "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": "{{ build.default.port }}", + "query.port": "{{ build.default.port }}" + } + } + }, + "log": { + "custom": false, + "location": "logs/latest.log" + }, + "query": "minecraftping" + }, + "spigot": { + "tag": "^(spigot)$", + "symlink": "vanilla", + "configs": { + "spigot.yml": { + "parser": "yaml", + "find": { + "settings.restart-on-crash": "false" + } + } + } + }, + "bungeecord": { + "tag": "^(bungeecord)$", + "startup": { + "done": "Listening on " + }, + "stop": "end", + "configs": { + "config.yml": { + "parser": "yaml", + "find": { + "listeners[0].query_enabled": true, + "listeners[0].query_port": "{{ build.default.port }}", + "listeners[0].host": "0.0.0.0:{{ build.default.port }}" + } + } + }, + "log": { + "custom": false, + "location": "proxy.log.0" + }, + "query": "minecraftping" + }, + "sponge": { + "tag": "^(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 00000000..29b0439f --- /dev/null +++ b/storage/app/services/srcds/index.js @@ -0,0 +1,57 @@ +'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 Configuration = rfr('src/services/srcds/main.json'); +const Core = rfr('src/services/index.js'); + +class Service extends Core { + constructor(server) { + super(server, Configuration); + } + + onPreflight(next) { + return super.onPreflight(next); + } + + onStart(next) { + return super.onStart(next); + } + + onConsole(data) { + return super.onConsole(data); + } + + onStop(next) { + return super.onStop(next); + } + + doQuery(next) { + return super.doQuery(next); + } + +} + +module.exports = Service; diff --git a/storage/app/services/srcds/main.json b/storage/app/services/srcds/main.json new file mode 100644 index 00000000..fdd65e33 --- /dev/null +++ b/storage/app/services/srcds/main.json @@ -0,0 +1,25 @@ +{ + "srcds": { + "tag": "^(srcds)$", + "startup": { + "done": "Assigned anonymous gameserver Steam ID", + "userInteraction": [] + }, + "stop": "quit", + "configs": {}, + "log": { + "custom": true, + "location": "logs/latest.log" + }, + "query": "protocol-valve" + }, + "ark": { + "tag": "^(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 00000000..55b396af --- /dev/null +++ b/storage/app/services/terraria/index.js @@ -0,0 +1,57 @@ +'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 Configuration = rfr('src/services/terraria/main.json'); +const Core = rfr('src/services/index.js'); + +class Service extends Core { + constructor(server) { + super(server, Configuration); + } + + onPreflight(next) { + return super.onPreflight(next); + } + + onStart(next) { + return super.onStart(next); + } + + onConsole(data) { + return super.onConsole(data); + } + + onStop(next) { + return super.onStop(next); + } + + doQuery(next) { + return super.doQuery(next); + } + +} + +module.exports = Service; diff --git a/storage/app/services/terraria/main.json b/storage/app/services/terraria/main.json new file mode 100644 index 00000000..c2a74d5b --- /dev/null +++ b/storage/app/services/terraria/main.json @@ -0,0 +1,24 @@ +{ + "tshock": { + "tag": "^(tshock)$", + "startup": { + "done": "Type 'help' for a list of commands", + "userInteraction": [] + }, + "stop": "exit", + "configs": { + "tshock/config.json": { + "parser": "json", + "find": { + "ServerPort": "{{ build.default.port }}", + "MaxSlots": "{{ 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 00000000..022b417a --- /dev/null +++ b/storage/app/services/voice/index.js @@ -0,0 +1,57 @@ +'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 Configuration = rfr('src/services/voice/main.json'); +const Core = rfr('src/services/index.js'); + +class Service extends Core { + constructor(server) { + super(server, Configuration); + } + + onPreflight(next) { + return super.onPreflight(next); + } + + onStart(next) { + return super.onStart(next); + } + + onConsole(data) { + return super.onConsole(data); + } + + onStop(next) { + return super.onStop(next); + } + + doQuery(next) { + return super.doQuery(next); + } + +} + +module.exports = Service; diff --git a/storage/app/services/voice/main.json b/storage/app/services/voice/main.json new file mode 100644 index 00000000..0479dd88 --- /dev/null +++ b/storage/app/services/voice/main.json @@ -0,0 +1,52 @@ +{ + "mumble": { + "tag": "^(mumble)$", + "startup": { + "done": "Server listening on", + "userInteraction": [ + "Generating new server certificate" + ] + }, + "stop": "^C", + "configs": { + "murmur.ini": { + "parser": "ini", + "find": { + "logfile": "murmur.log", + "port": "{{ build.default.port }}", + "host": "0.0.0.0", + "users": "{{ build.env.MAX_USERS }}" + } + } + }, + "log": { + "custom": true, + "location": "logs/murmur.log" + }, + "query": "mumbleping" + }, + "teamspeak": { + "tag": "^(ts3)$", + "startup": { + "done": "listening on 0.0.0.0:", + "userInteraction": [] + }, + "stop": "^C", + "configs": { + "ts3server.ini": { + "parser": "ini", + "find": { + "default_voice_port": "{{ build.default.port }}", + "voice_ip": "0.0.0.0", + "query_port": "{{ build.default.port }}", + "query_ip": "0.0.0.0" + } + } + }, + "log": { + "custom": true, + "location": "logs/ts3.log" + }, + "query": "none" + } +} From 1f47eda3b39f43ea578abf176e09f06fafc21f33 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 9 Nov 2016 17:59:57 -0500 Subject: [PATCH 02/22] Run 'pterodactyl:cleanservices' twice a day to prevent a huge file buildup --- app/Console/Kernel.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 4cd65f46..53f80281 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -34,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); } } From a1bc6fa2d3275aff647dd23f7202cb99aaeedacb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 15 Nov 2016 20:20:32 -0500 Subject: [PATCH 03/22] Push changes that support creations of service packs and basic listing --- app/Http/Controllers/Admin/PackController.php | 108 ++++++++++ app/Http/Routes/AdminRoutes.php | 26 +++ app/Models/ServicePack.php | 60 ++++++ app/Repositories/ServiceRepository/Pack.php | 105 ++++++++++ .../ServiceRepository/Service.php | 15 +- .../2016_11_09_163911_add_checksums_table.php | 36 ---- .../2016_11_11_220649_add_pack_support.php | 46 +++++ ...6_11_11_231731_set_service_name_unique.php | 32 +++ public/themes/default/css/pterodactyl.css | 11 + .../admin/services/options/view.blade.php | 1 + .../views/admin/services/packs/edit.blade.php | 0 .../admin/services/packs/index.blade.php | 69 +++++++ .../views/admin/services/packs/new.blade.php | 192 ++++++++++++++++++ storage/app/.gitignore | 2 + storage/app/packs/.githold | 0 15 files changed, 654 insertions(+), 49 deletions(-) create mode 100644 app/Http/Controllers/Admin/PackController.php create mode 100644 app/Models/ServicePack.php create mode 100644 app/Repositories/ServiceRepository/Pack.php delete mode 100644 database/migrations/2016_11_09_163911_add_checksums_table.php create mode 100644 database/migrations/2016_11_11_220649_add_pack_support.php create mode 100644 database/migrations/2016_11_11_231731_set_service_name_unique.php create mode 100644 resources/views/admin/services/packs/edit.blade.php create mode 100644 resources/views/admin/services/packs/index.blade.php create mode 100644 resources/views/admin/services/packs/new.blade.php create mode 100644 storage/app/packs/.githold diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php new file mode 100644 index 00000000..9d247439 --- /dev/null +++ b/app/Http/Controllers/Admin/PackController.php @@ -0,0 +1,108 @@ + + * + * 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 Alert; +use Log; +use Storage; + +use Pterodactyl\Models; +use Pterodactyl\Repositories\ServiceRepository\Pack; +use Pterodactyl\Http\Controllers\Controller; +use Pterodactyl\Exceptions\DisplayValidationException; +use Pterodactyl\Exceptions\DisplayException; + +use Illuminate\Http\Request; + +class PackController extends Controller +{ + public function __construct() + { + // + } + + public function list(Request $request, $id) + { + $option = Models\ServiceOptions::findOrFail($id); + return view('admin.services.packs.index', [ + 'packs' => Models\ServicePack::where('option', $option->id)->get(), + 'service' => Models\Service::findOrFail($option->parent_service), + 'option' => $option + ]); + } + + public function new(Request $request, $opt = null) + { + $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 view('admin.services.packs.new', [ + 'services' => $array, + 'packFor' => $opt, + ]); + } + + public function create(Request $request) + { + // dd($request->all()); + 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); + dd($pack, Storage::url('packs/' . $pack->uuid)); + } +} diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 726fccc2..73679de6 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -430,6 +430,32 @@ class AdminRoutes { ]); }); + // 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('/for/{option}', [ + 'as' => 'admin.services.packs.for', + 'uses' => 'Admin\PackController@list' + ]); + $router->get('/edit/{pack}', [ + 'as' => 'admin.services.packs.edit', + 'uses' => 'Admin\PackController@edit' + ]); + }); + } } diff --git a/app/Models/ServicePack.php b/app/Models/ServicePack.php new file mode 100644 index 00000000..f43be94b --- /dev/null +++ b/app/Models/ServicePack.php @@ -0,0 +1,60 @@ + + * + * 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/Repositories/ServiceRepository/Pack.php b/app/Repositories/ServiceRepository/Pack.php new file mode 100644 index 00000000..b644dae5 --- /dev/null +++ b/app/Repositories/ServiceRepository/Pack.php @@ -0,0 +1,105 @@ + + * + * 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 Storage; +use Uuid; +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' => '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()); + } + + 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::transaction(function () use ($data) { + $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']) + ]); + + $filename = ($data['file_upload']->getMimeType() === 'application/zip') ? 'archive.zip' : 'archive.tar.gz'; + $data['file_upload']->storeAs('packs/' . $pack->uuid, $filename); + + $pack->save(); + + return $pack->id; + }); + } + +} diff --git a/app/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php index 4dc1755b..91062dcb 100644 --- a/app/Repositories/ServiceRepository/Service.php +++ b/app/Repositories/ServiceRepository/Service.php @@ -101,6 +101,8 @@ class Service DB::beginTransaction(); try { + Storage::deleteDirectory('services/' . $service->file); + Models\ServiceVariables::whereIn('option_id', $options->get()->toArray())->delete(); $options->delete(); $service->delete(); @@ -128,23 +130,10 @@ class Service $filepath = 'services/' . $service->file . '/' . $filename; $backup = 'services/.bak/' . str_random(12) . '.bak'; - DB::beginTransaction(); - try { Storage::move($filepath, $backup); Storage::put($filepath, $data['contents']); - - $checksum = Models\Checksum::firstOrNew([ - 'service' => $service->id, - 'filename' => $filename - ]); - - $checksum->checksum = sha1_file(storage_path('app/' . $filepath)); - $checksum->save(); - - DB::commit(); } catch(\Exception $ex) { - DB::rollback(); Storage::move($backup, $filepath); throw $ex; } diff --git a/database/migrations/2016_11_09_163911_add_checksums_table.php b/database/migrations/2016_11_09_163911_add_checksums_table.php deleted file mode 100644 index 4935faaf..00000000 --- a/database/migrations/2016_11_09_163911_add_checksums_table.php +++ /dev/null @@ -1,36 +0,0 @@ -increments('id'); - $table->integer('service')->unsigned(); - $table->string('filename'); - $table->char('checksum', 40); - $table->timestamps(); - - $table->foreign('service')->references('id')->on('services'); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::drop('checksums'); - } -} 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 00000000..87a66b40 --- /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 00000000..4db76f8e --- /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/public/themes/default/css/pterodactyl.css b/public/themes/default/css/pterodactyl.css index 607da8d1..fb24beda 100755 --- a/public/themes/default/css/pterodactyl.css +++ b/public/themes/default/css/pterodactyl.css @@ -307,3 +307,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/services/options/view.blade.php b/resources/views/admin/services/options/view.blade.php index d5de1a91..414f8974 100644 --- a/resources/views/admin/services/options/view.blade.php +++ b/resources/views/admin/services/options/view.blade.php @@ -30,6 +30,7 @@
  • Services
  • {{ $service->name }}
  • {{ $option->name }}
  • +
  • Service Packs
  • Warning! This page contains advanced settings that the panel and daemon use to control servers. Modifying information on this page is not recommended unless you are absolutely sure of what you are doing.

    Settings


    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 00000000..e69de29b 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 00000000..2ec5ed0e --- /dev/null +++ b/resources/views/admin/services/packs/index.blade.php @@ -0,0 +1,69 @@ +{{-- 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


    + + + + + + + + + + + + @foreach ($packs as $pack) + + + + + + + + @endforeach + + + + +
    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/new.blade.php b/resources/views/admin/services/packs/new.blade.php new file mode 100644 index 00000000..0c75e8d0 --- /dev/null +++ b/resources/views/admin/services/packs/new.blade.php @@ -0,0 +1,192 @@ +{{-- 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 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/storage/app/.gitignore b/storage/app/.gitignore index 8d9045b3..104980aa 100755 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -1,4 +1,6 @@ * !.gitignore !services/* +packs/**/* services/.bak/* +!packs/.githold diff --git a/storage/app/packs/.githold b/storage/app/packs/.githold new file mode 100644 index 00000000..e69de29b From 09c2dcc1b6123e4b70b938a81907cb6c7c2915dd Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Tue, 15 Nov 2016 23:12:47 -0500 Subject: [PATCH 04/22] Support for viewing and exporting packs --- app/Http/Controllers/Admin/PackController.php | 102 ++++++-- app/Http/Routes/AdminRoutes.php | 21 +- .../admin/services/options/view.blade.php | 1 - .../{index.blade.php => byoption.blade.php} | 13 +- .../admin/services/packs/byservice.blade.php | 67 ++++++ .../views/admin/services/packs/edit.blade.php | 217 ++++++++++++++++++ .../views/admin/services/packs/new.blade.php | 1 + resources/views/layouts/admin.blade.php | 2 + 8 files changed, 403 insertions(+), 21 deletions(-) rename resources/views/admin/services/packs/{index.blade.php => byoption.blade.php} (86%) create mode 100644 resources/views/admin/services/packs/byservice.blade.php diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index 9d247439..5a7ecf49 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -24,6 +24,7 @@ namespace Pterodactyl\Http\Controllers\Admin; use Alert; +use DB; use Log; use Storage; @@ -42,17 +43,7 @@ class PackController extends Controller // } - public function list(Request $request, $id) - { - $option = Models\ServiceOptions::findOrFail($id); - return view('admin.services.packs.index', [ - 'packs' => Models\ServicePack::where('option', $option->id)->get(), - 'service' => Models\Service::findOrFail($option->parent_service), - 'option' => $option - ]); - } - - public function new(Request $request, $opt = null) + protected function formatServices() { $options = Models\ServiceOptions::select( 'services.name AS p_service', @@ -72,8 +63,41 @@ class PackController extends Controller ]]); } + return $array; + } + + public function listAll(Request $request) + { + // + } + + 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' => $array, + 'services' => $this->formatServices(), 'packFor' => $opt, ]); } @@ -103,6 +127,58 @@ class PackController extends Controller public function edit(Request $request, $id) { $pack = Models\ServicePack::findOrFail($id); - dd($pack, Storage::url('packs/' . $pack->uuid)); + $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 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)) { + exit("cannot open <$filename>\n"); + } + + $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); + } } } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 73679de6..d84de184 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -446,14 +446,29 @@ class AdminRoutes { $router->post('/new', [ 'uses' => 'Admin\PackController@create' ]); - $router->get('/for/{option}', [ - 'as' => 'admin.services.packs.for', - 'uses' => 'Admin\PackController@list' + $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/resources/views/admin/services/options/view.blade.php b/resources/views/admin/services/options/view.blade.php index 414f8974..d5de1a91 100644 --- a/resources/views/admin/services/options/view.blade.php +++ b/resources/views/admin/services/options/view.blade.php @@ -30,7 +30,6 @@
  • Services
  • {{ $service->name }}
  • {{ $option->name }}
  • -
  • Service Packs
  • Warning! This page contains advanced settings that the panel and daemon use to control servers. Modifying information on this page is not recommended unless you are absolutely sure of what you are doing.

    Settings


    diff --git a/resources/views/admin/services/packs/index.blade.php b/resources/views/admin/services/packs/byoption.blade.php similarity index 86% rename from resources/views/admin/services/packs/index.blade.php rename to resources/views/admin/services/packs/byoption.blade.php index 2ec5ed0e..6ec8f03e 100644 --- a/resources/views/admin/services/packs/index.blade.php +++ b/resources/views/admin/services/packs/byoption.blade.php @@ -28,9 +28,9 @@

    Service Packs


    @@ -46,7 +46,7 @@ @foreach ($packs as $pack) - + @@ -66,4 +66,9 @@
    {{ $pack->name }}{{ $pack->name }} {{ $pack->version }} {{ $pack->uuid }} @if($pack->selectable)@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 00000000..ddb538ac --- /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 + + + + +
    NameTotal 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 index e69de29b..abc89fe9 100644 --- a/resources/views/admin/services/packs/edit.blade.php +++ b/resources/views/admin/services/packs/edit.blade.php @@ -0,0 +1,217 @@ +{{-- 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/new.blade.php b/resources/views/admin/services/packs/new.blade.php index 0c75e8d0..b6895636 100644 --- a/resources/views/admin/services/packs/new.blade.php +++ b/resources/views/admin/services/packs/new.blade.php @@ -172,6 +172,7 @@ {!! Theme::js('js/vendor/ace/ext-modelist.js') !!} +@endsection From d4729427aa9a267b79edcfe8cab2c1e88f6f5279 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 16 Nov 2016 17:22:22 -0500 Subject: [PATCH 06/22] Support for uploading templates for installing packs --- app/Http/Controllers/Admin/PackController.php | 32 ++++++- app/Http/Routes/AdminRoutes.php | 7 ++ app/Repositories/ServiceRepository/Pack.php | 96 +++++++++++++++++-- .../admin/services/packs/byoption.blade.php | 18 +++- .../views/admin/services/packs/new.blade.php | 2 +- .../admin/services/packs/upload.blade.php | 45 +++++++++ 6 files changed, 187 insertions(+), 13 deletions(-) create mode 100644 resources/views/admin/services/packs/upload.blade.php diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index e16c9468..acb05c7a 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -97,7 +97,6 @@ class PackController extends Controller public function new(Request $request, $opt = null) { - return view('admin.services.packs.new', [ 'services' => $this->formatServices(), 'packFor' => $opt, @@ -106,7 +105,6 @@ class PackController extends Controller public function create(Request $request) { - // dd($request->all()); try { $repo = new Pack; $id = $repo->create($request->except([ @@ -123,7 +121,6 @@ class PackController extends Controller 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) @@ -179,7 +176,7 @@ class PackController extends Controller if ((bool) $files) { $zip = new \ZipArchive; if (!$zip->open($filename, \ZipArchive::CREATE)) { - exit("cannot open <$filename>\n"); + abort(503, 'Unable to open file for writing.'); } $files = Storage::files('packs/' . $pack->uuid); @@ -200,4 +197,31 @@ class PackController extends Controller ])->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/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index d84de184..96819bbb 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -446,6 +446,13 @@ class AdminRoutes { $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' diff --git a/app/Repositories/ServiceRepository/Pack.php b/app/Repositories/ServiceRepository/Pack.php index 4e3d190b..c4b3529d 100644 --- a/app/Repositories/ServiceRepository/Pack.php +++ b/app/Repositories/ServiceRepository/Pack.php @@ -46,7 +46,7 @@ class Pack $validator = Validator::make($data, [ 'name' => 'required|string', 'version' => 'required|string', - 'description' => 'string', + 'description' => 'sometimes|nullable|string', 'option' => 'required|exists:service_options,id', 'selectable' => 'sometimes|boolean', 'visible' => 'sometimes|boolean', @@ -55,7 +55,7 @@ class Pack 'build_cpu' => 'required|integer|min:0', 'build_io' => 'required|integer|min:10|max:1000', 'build_container' => 'required|string', - 'build_script' => 'sometimes|string' + 'build_script' => 'sometimes|nullable|string' ]); if ($validator->fails()) { @@ -75,7 +75,8 @@ class Pack } } - DB::transaction(function () use ($data) { + DB::beginTransaction(); + try { $uuid = new UuidService; $pack = Models\ServicePack::create([ 'option' => $data['option'], @@ -93,13 +94,94 @@ class Pack 'visible' => isset($data['visible']) ]); - $filename = ($data['file_upload']->getMimeType() === 'application/zip') ? 'archive.zip' : 'archive.tar.gz'; - $data['file_upload']->storeAs('packs/' . $pack->uuid, $filename); + 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); + } - $pack->save(); + 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) diff --git a/resources/views/admin/services/packs/byoption.blade.php b/resources/views/admin/services/packs/byoption.blade.php index 6ec8f03e..cc285d16 100644 --- a/resources/views/admin/services/packs/byoption.blade.php +++ b/resources/views/admin/services/packs/byoption.blade.php @@ -58,7 +58,7 @@ - + @@ -69,6 +69,22 @@ @endsection diff --git a/resources/views/admin/services/packs/new.blade.php b/resources/views/admin/services/packs/new.blade.php index b6895636..af658cce 100644 --- a/resources/views/admin/services/packs/new.blade.php +++ b/resources/views/admin/services/packs/new.blade.php @@ -152,7 +152,7 @@

    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 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.

    + 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.

    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 00000000..e9ca020f --- /dev/null +++ b/resources/views/admin/services/packs/upload.blade.php @@ -0,0 +1,45 @@ + From 5600f3201c85e43de14965c2ee2d522c8e77baba Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 18 Nov 2016 17:31:57 -0500 Subject: [PATCH 07/22] Add support for deleting service packs. --- app/Http/Controllers/Admin/PackController.php | 39 +++++++++++++------ app/Repositories/ServiceRepository/Pack.php | 9 +++++ .../views/admin/services/packs/edit.blade.php | 5 ++- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index acb05c7a..c94a001d 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -138,19 +138,34 @@ class PackController extends Controller public function update(Request $request, $id) { - 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(); + 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); } - return redirect()->route('admin.services.packs.edit', $id); } public function export(Request $request, $id, $files = false) diff --git a/app/Repositories/ServiceRepository/Pack.php b/app/Repositories/ServiceRepository/Pack.php index c4b3529d..5d8591b6 100644 --- a/app/Repositories/ServiceRepository/Pack.php +++ b/app/Repositories/ServiceRepository/Pack.php @@ -225,4 +225,13 @@ class Pack }); } + 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/resources/views/admin/services/packs/edit.blade.php b/resources/views/admin/services/packs/edit.blade.php index abc89fe9..b71256fd 100644 --- a/resources/views/admin/services/packs/edit.blade.php +++ b/resources/views/admin/services/packs/edit.blade.php @@ -186,8 +186,9 @@
    {!! csrf_field() !!} - - + + +
    From fc2ce11a394a6ef0b6e9467d3b74bc9db6587e9a Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 18 Nov 2016 18:22:26 -0500 Subject: [PATCH 08/22] Add template, add files when new service is added. --- .../ServiceRepository/Service.php | 26 ++++++++++------ storage/app/services/.templates/index.js | 31 +++++++++++++++++++ 2 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 storage/app/services/.templates/index.js diff --git a/app/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php index 91062dcb..9e832883 100644 --- a/app/Repositories/ServiceRepository/Service.php +++ b/app/Repositories/ServiceRepository/Service.php @@ -47,7 +47,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' ]); @@ -56,15 +56,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,11 +109,11 @@ class Service DB::beginTransaction(); try { - Storage::deleteDirectory('services/' . $service->file); - 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(); diff --git a/storage/app/services/.templates/index.js b/storage/app/services/.templates/index.js new file mode 100644 index 00000000..aa60eec1 --- /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; From 00e125c042f5b2297108c47e887c3ec09045a9d7 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 18 Nov 2016 18:22:34 -0500 Subject: [PATCH 09/22] Update templates --- storage/app/services/minecraft/index.js | 22 ------------------- storage/app/services/minecraft/main.json | 8 ------- storage/app/services/srcds/index.js | 28 +----------------------- storage/app/services/srcds/main.json | 2 -- storage/app/services/terraria/index.js | 28 +----------------------- storage/app/services/terraria/main.json | 1 - storage/app/services/voice/index.js | 28 +----------------------- storage/app/services/voice/main.json | 2 -- 8 files changed, 3 insertions(+), 116 deletions(-) diff --git a/storage/app/services/minecraft/index.js b/storage/app/services/minecraft/index.js index 67c4dbb2..2d7f812c 100644 --- a/storage/app/services/minecraft/index.js +++ b/storage/app/services/minecraft/index.js @@ -25,36 +25,14 @@ const rfr = require('rfr'); const _ = require('lodash'); -const Configuration = rfr('src/services/minecraft/main.json'); const Core = rfr('src/services/index.js'); class Service extends Core { - constructor(server) { - super(server, Configuration); - } - - onPreflight(next) { - return super.onPreflight(next); - } - - onStart(next) { - return super.onStart(next); - } - onConsole(data) { // Hide the output spam from Bungeecord getting pinged. if (_.endsWith(data, '<-> InitialHandler has connected')) return; return super.onConsole(data); } - - onStop(next) { - return super.onStop(next); - } - - doQuery(next) { - return super.doQuery(next); - } - } module.exports = Service; diff --git a/storage/app/services/minecraft/main.json b/storage/app/services/minecraft/main.json index 1a94258a..b131d988 100644 --- a/storage/app/services/minecraft/main.json +++ b/storage/app/services/minecraft/main.json @@ -1,10 +1,5 @@ { - "latest": { - "tag": "^(latest)$", - "symlink": "vanilla" - }, "vanilla": { - "tag": "^(vanilla){1}(-[\\w\\d.-]+)?$", "startup": { "done": ")! For help, type ", "userInteraction": [ @@ -30,7 +25,6 @@ "query": "minecraftping" }, "spigot": { - "tag": "^(spigot)$", "symlink": "vanilla", "configs": { "spigot.yml": { @@ -42,7 +36,6 @@ } }, "bungeecord": { - "tag": "^(bungeecord)$", "startup": { "done": "Listening on " }, @@ -64,7 +57,6 @@ "query": "minecraftping" }, "sponge": { - "tag": "^(sponge)$", "symlink": "vanilla", "startup": { "userInteraction": [ diff --git a/storage/app/services/srcds/index.js b/storage/app/services/srcds/index.js index 29b0439f..aa60eec1 100644 --- a/storage/app/services/srcds/index.js +++ b/storage/app/services/srcds/index.js @@ -24,34 +24,8 @@ */ const rfr = require('rfr'); -const Configuration = rfr('src/services/srcds/main.json'); const Core = rfr('src/services/index.js'); -class Service extends Core { - constructor(server) { - super(server, Configuration); - } - - onPreflight(next) { - return super.onPreflight(next); - } - - onStart(next) { - return super.onStart(next); - } - - onConsole(data) { - return super.onConsole(data); - } - - onStop(next) { - return super.onStop(next); - } - - doQuery(next) { - return super.doQuery(next); - } - -} +class Service extends Core {} module.exports = Service; diff --git a/storage/app/services/srcds/main.json b/storage/app/services/srcds/main.json index fdd65e33..989a6753 100644 --- a/storage/app/services/srcds/main.json +++ b/storage/app/services/srcds/main.json @@ -1,6 +1,5 @@ { "srcds": { - "tag": "^(srcds)$", "startup": { "done": "Assigned anonymous gameserver Steam ID", "userInteraction": [] @@ -14,7 +13,6 @@ "query": "protocol-valve" }, "ark": { - "tag": "^(ark)$", "symlink": "srcds", "startup": { "done": "Setting breakpad minidump AppID" diff --git a/storage/app/services/terraria/index.js b/storage/app/services/terraria/index.js index 55b396af..aa60eec1 100644 --- a/storage/app/services/terraria/index.js +++ b/storage/app/services/terraria/index.js @@ -24,34 +24,8 @@ */ const rfr = require('rfr'); -const Configuration = rfr('src/services/terraria/main.json'); const Core = rfr('src/services/index.js'); -class Service extends Core { - constructor(server) { - super(server, Configuration); - } - - onPreflight(next) { - return super.onPreflight(next); - } - - onStart(next) { - return super.onStart(next); - } - - onConsole(data) { - return super.onConsole(data); - } - - onStop(next) { - return super.onStop(next); - } - - doQuery(next) { - return super.doQuery(next); - } - -} +class Service extends Core {} module.exports = Service; diff --git a/storage/app/services/terraria/main.json b/storage/app/services/terraria/main.json index c2a74d5b..6eacd749 100644 --- a/storage/app/services/terraria/main.json +++ b/storage/app/services/terraria/main.json @@ -1,6 +1,5 @@ { "tshock": { - "tag": "^(tshock)$", "startup": { "done": "Type 'help' for a list of commands", "userInteraction": [] diff --git a/storage/app/services/voice/index.js b/storage/app/services/voice/index.js index 022b417a..aa60eec1 100644 --- a/storage/app/services/voice/index.js +++ b/storage/app/services/voice/index.js @@ -24,34 +24,8 @@ */ const rfr = require('rfr'); -const Configuration = rfr('src/services/voice/main.json'); const Core = rfr('src/services/index.js'); -class Service extends Core { - constructor(server) { - super(server, Configuration); - } - - onPreflight(next) { - return super.onPreflight(next); - } - - onStart(next) { - return super.onStart(next); - } - - onConsole(data) { - return super.onConsole(data); - } - - onStop(next) { - return super.onStop(next); - } - - doQuery(next) { - return super.doQuery(next); - } - -} +class Service extends Core {} module.exports = Service; diff --git a/storage/app/services/voice/main.json b/storage/app/services/voice/main.json index 0479dd88..6bc8e5c9 100644 --- a/storage/app/services/voice/main.json +++ b/storage/app/services/voice/main.json @@ -1,6 +1,5 @@ { "mumble": { - "tag": "^(mumble)$", "startup": { "done": "Server listening on", "userInteraction": [ @@ -26,7 +25,6 @@ "query": "mumbleping" }, "teamspeak": { - "tag": "^(ts3)$", "startup": { "done": "listening on 0.0.0.0:", "userInteraction": [] From ee78a3947b38106b4df82d3849696dfb2c23cc8c Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Fri, 18 Nov 2016 18:34:45 -0500 Subject: [PATCH 10/22] Grammatical display changes --- resources/views/admin/services/new.blade.php | 2 +- resources/views/admin/services/packs/byoption.blade.php | 4 ++-- resources/views/admin/services/packs/byservice.blade.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/views/admin/services/new.blade.php b/resources/views/admin/services/new.blade.php index cd421ef9..ce7f0f4f 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 index cc285d16..62695856 100644 --- a/resources/views/admin/services/packs/byoption.blade.php +++ b/resources/views/admin/services/packs/byoption.blade.php @@ -36,7 +36,7 @@ - + @@ -47,7 +47,7 @@ @foreach ($packs as $pack) - + diff --git a/resources/views/admin/services/packs/byservice.blade.php b/resources/views/admin/services/packs/byservice.blade.php index ddb538ac..f73572c3 100644 --- a/resources/views/admin/services/packs/byservice.blade.php +++ b/resources/views/admin/services/packs/byservice.blade.php @@ -35,7 +35,7 @@
    NamePack Name Version UUID Selectable
    {{ $pack->name }}{{ $pack->version }}{{ $pack->version }} {{ $pack->uuid }} @if($pack->selectable)@else@endif @if($pack->visible)@else@endif
    - + From 238f08f222739cff40df7f51a2b02f92c41692e0 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Nov 2016 14:30:44 -0500 Subject: [PATCH 11/22] Add pack selection to view --- .../Controllers/Admin/ServersController.php | 3 ++- app/Http/Routes/AdminRoutes.php | 4 ++-- resources/views/admin/servers/new.blade.php | 19 ++++++++++++++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 5ec7d9a2..310af7b9 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -257,7 +257,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')) { @@ -274,6 +274,7 @@ class ServersController extends Controller ->first(); return response()->json([ + 'packs' => Models\ServicePack::select('uuid', '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/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index 96819bbb..27b6bae3 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -146,8 +146,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 diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 8db93ffc..276a1095 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 = ' \ From c4a4b84bd3e6050fc1db6044963bd27010fd9863 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Nov 2016 14:50:10 -0500 Subject: [PATCH 12/22] Add service pack reference to server and send to daemon --- .../Controllers/Admin/ServersController.php | 4 ++- app/Repositories/ServerRepository.php | 27 ++++++++++---- .../2016_11_27_142519_add_pack_column.php | 36 +++++++++++++++++++ resources/views/admin/servers/new.blade.php | 2 +- 4 files changed, 61 insertions(+), 8 deletions(-) create mode 100644 database/migrations/2016_11_27_142519_add_pack_column.php diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 310af7b9..c15a4a65 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -168,7 +168,9 @@ class ServersController extends Controller try { $server = new ServerRepository; - $response = $server->create($request->all()); + $response = $server->create($request->except([ + '_token' + ])); return redirect()->route('admin.servers.view', [ 'id' => $response ]); } catch (DisplayValidationException $ex) { return redirect()->route('admin.servers.new')->withErrors(json_decode($ex->getMessage()))->withInput(); diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 47b1a4a9..5d25f0e6 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -86,6 +86,7 @@ class ServerRepository 'disk' => 'required|numeric|min:0', 'service' => 'bail|required|numeric|min:1|exists:services,id', 'option' => 'bail|required|numeric|min:1|exists:service_options,id', + 'pack' => 'bail|required|numeric|min:0' 'startup' => 'string', 'custom_image_name' => 'required_if:use_custom_image,on', 'auto_deploy' => 'sometimes|boolean' @@ -161,6 +162,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); @@ -248,6 +261,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, @@ -297,10 +311,10 @@ class ServerRepository 'build' => [ 'default' => [ 'ip' => $allocation->ip, - 'port' => (int) $allocation->port + 'port' => (int) $allocation->port, ], 'ports' => [ - (string) $allocation->ip => [ (int) $allocation->port ] + (string) $allocation->ip => [ (int) $allocation->port ], ], 'env' => $environmentVariables, 'memory' => (int) $server->memory, @@ -308,16 +322,17 @@ class ServerRepository 'io' => (int) $server->io, 'cpu' => (int) $server->cpu, 'disk' => (int) $server->disk, - 'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image + 'image' => (isset($data['custom_image_name'])) ? $data['custom_image_name'] : $option->docker_image, ], 'service' => [ 'type' => $service->file, - 'option' => $option->tag + 'option' => $option->tag, + 'pack' => (isset($pack)) ? $pack->uuid : null, ], 'keys' => [ - (string) $server->daemonSecret => $this->daemonPermissions + (string) $server->daemonSecret => $this->daemonPermissions, ], - 'rebuild' => false + 'rebuild' => false, ] ]); 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 00000000..f2c2f096 --- /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/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 276a1095..5d831ce6 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -451,7 +451,7 @@ $(document).ready(function () { $.each(data.packs, function (i, item) { $('#getPack').append(''); }); - $('#getPack').append('').parent().parent().removeClass('hidden'); + $('#getPack').append('').parent().parent().removeClass('hidden'); $.each(data.variables, function (i, item) { var isRequired = (item.required === 1) ? 'Required ' : ''; From 75de060a5587a71dc4a91792eb29ccb7799796a2 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Nov 2016 14:57:23 -0500 Subject: [PATCH 13/22] Fix pack selector --- app/Http/Controllers/Admin/ServersController.php | 2 +- app/Repositories/ServerRepository.php | 2 +- resources/views/admin/servers/new.blade.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index c15a4a65..8b19fafe 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -276,7 +276,7 @@ class ServersController extends Controller ->first(); return response()->json([ - 'packs' => Models\ServicePack::select('uuid', 'name', 'version')->where('option', $request->input('option'))->where('selectable', true)->get(), + '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/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 5d25f0e6..bb8f52c5 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -86,7 +86,7 @@ class ServerRepository 'disk' => 'required|numeric|min:0', 'service' => 'bail|required|numeric|min:1|exists:services,id', 'option' => 'bail|required|numeric|min:1|exists:service_options,id', - 'pack' => 'bail|required|numeric|min:0' + 'pack' => 'bail|required|numeric|min:0', 'startup' => 'string', 'custom_image_name' => 'required_if:use_custom_image,on', 'auto_deploy' => 'sometimes|boolean' diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 5d831ce6..7fe9b76d 100644 --- a/resources/views/admin/servers/new.blade.php +++ b/resources/views/admin/servers/new.blade.php @@ -449,7 +449,7 @@ $(document).ready(function () { $('input[name="startup"]').val(data.startup); $.each(data.packs, function (i, item) { - $('#getPack').append(''); + $('#getPack').append(''); }); $('#getPack').append('').parent().parent().removeClass('hidden'); From efda0dd009d1399c56e66567520abf634cecc8bb Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 14 Dec 2016 21:56:25 +0000 Subject: [PATCH 14/22] Apply fixes from StyleCI --- app/Console/Commands/CleanServiceBackup.php | 5 +- app/Http/Controllers/Admin/PackController.php | 60 +++++++++++-------- .../Controllers/Admin/ServiceController.php | 14 +++-- app/Http/Routes/AdminRoutes.php | 30 +++++----- app/Models/Checksum.php | 7 +-- app/Models/ServicePack.php | 17 +++--- app/Repositories/ServerRepository.php | 4 +- app/Repositories/ServiceRepository/Pack.php | 44 +++++++------- .../ServiceRepository/Service.php | 5 +- 9 files changed, 98 insertions(+), 88 deletions(-) diff --git a/app/Console/Commands/CleanServiceBackup.php b/app/Console/Commands/CleanServiceBackup.php index 82453d6c..93af785f 100644 --- a/app/Console/Commands/CleanServiceBackup.php +++ b/app/Console/Commands/CleanServiceBackup.php @@ -1,7 +1,7 @@ + * 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 @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Console\Commands; use Carbon; @@ -62,7 +63,7 @@ class CleanServiceBackup extends Command { $files = Storage::files('services/.bak'); - foreach($files as $file) { + foreach ($files as $file) { $lastModified = Carbon::createFromTimestamp(Storage::lastModified($file)); if ($lastModified->diffInMinutes(Carbon::now()) > 5) { $this->info('Deleting ' . $file); diff --git a/app/Http/Controllers/Admin/PackController.php b/app/Http/Controllers/Admin/PackController.php index c94a001d..fdf1b5a1 100644 --- a/app/Http/Controllers/Admin/PackController.php +++ b/app/Http/Controllers/Admin/PackController.php @@ -1,7 +1,7 @@ + * 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 @@ -21,20 +21,19 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Http\Controllers\Admin; -use Alert; use DB; use Log; +use Alert; use Storage; - use Pterodactyl\Models; -use Pterodactyl\Repositories\ServiceRepository\Pack; -use Pterodactyl\Http\Controllers\Controller; -use Pterodactyl\Exceptions\DisplayValidationException; -use Pterodactyl\Exceptions\DisplayException; - 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 { @@ -52,14 +51,14 @@ class PackController extends Controller )->join('services', 'services.id', '=', 'service_options.parent_service')->get(); $array = []; - foreach($options as &$option) { - if (!array_key_exists($option->p_service, $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 + 'name' => $option->name, ]]); } @@ -69,17 +68,18 @@ class PackController extends Controller public function listAll(Request $request) { return view('admin.services.packs.index', [ - 'services' => Models\Service::all() + '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 + 'option' => $option, ]); } @@ -91,7 +91,7 @@ class PackController extends Controller '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() + )->where('parent_service', $id)->get(), ]); } @@ -108,9 +108,10 @@ class PackController extends Controller try { $repo = new Pack; $id = $repo->create($request->except([ - '_token' + '_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(); @@ -120,6 +121,7 @@ class PackController extends Controller 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(); } @@ -127,22 +129,24 @@ class PackController extends Controller { $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 + 'option' => $option, ]); } public function update(Request $request, $id) { - if (!is_null($request->input('action_delete'))) { + 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(); @@ -150,12 +154,13 @@ class PackController extends Controller 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' + '_token', ])); Alert::success('Service pack has been successfully updated.')->flash(); } catch (DisplayValidationException $ex) { @@ -164,6 +169,7 @@ class PackController extends Controller Log::error($ex); Alert::danger('An error occured while attempting to add edit this pack.')->flash(); } + return redirect()->route('admin.services.packs.edit', $id); } } @@ -183,14 +189,14 @@ class PackController extends Controller 'cpu' => $pack->build_cpu, 'io' => $pack->build_io, 'container' => $pack->build_container, - 'script' => $pack->build_script - ] + 'script' => $pack->build_script, + ], ]; $filename = tempnam(sys_get_temp_dir(), 'pterodactyl_'); if ((bool) $files) { $zip = new \ZipArchive; - if (!$zip->open($filename, \ZipArchive::CREATE)) { + if (! $zip->open($filename, \ZipArchive::CREATE)) { abort(503, 'Unable to open file for writing.'); } @@ -207,16 +213,18 @@ class PackController extends Controller $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' + 'Content-Type' => 'application/json', ])->deleteFileAfterSend(true); } } - public function uploadForm(Request $request, $for = null) { + public function uploadForm(Request $request, $for = null) + { return view('admin.services.packs.upload', [ 'services' => $this->formatServices(), - 'for' => $for + 'for' => $for, ]); } @@ -225,9 +233,10 @@ class PackController extends Controller try { $repo = new Pack; $id = $repo->createWithTemplate($request->except([ - '_token' + '_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(); @@ -237,6 +246,7 @@ class PackController extends Controller 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/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index db05448b..adcde0f6 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -28,7 +28,6 @@ use DB; use Log; use Alert; use Storage; -use Validator; use Pterodactyl\Models; use Illuminate\Http\Request; use Pterodactyl\Exceptions\DisplayException; @@ -292,12 +291,13 @@ class ServiceController extends Controller 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') - ] + 'index' => Storage::get('services/' . $service->file . '/index.js'), + ], ]); } @@ -306,17 +306,19 @@ class ServiceController extends Controller try { $repo = new ServiceRepository\Service; $repo->updateFile($serviceId, $request->except([ - '_token' + '_token', ])); + return response('', 204); } catch (DisplayException $ex) { return response()->json([ - 'error' => $ex->getMessage() + 'error' => $ex->getMessage(), ], 503); } catch (\Exception $ex) { Log::error($ex); + return response()->json([ - 'error' => 'An error occured while attempting to save the file.' + 'error' => 'An error occured while attempting to save the file.', ], 503); } } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index cef9eeea..fb3d16ba 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -148,7 +148,7 @@ class AdminRoutes ]); $router->post('/new/option-details', [ - 'uses' => 'Admin\ServersController@postNewServerOptionDetails' + 'uses' => 'Admin\ServersController@postNewServerOptionDetails', ]); // End Assorted Page Helpers @@ -380,11 +380,11 @@ class AdminRoutes $router->get('/service/{id}/configuration', [ 'as' => 'admin.services.service.config', - 'uses' => 'Admin\ServiceController@getConfiguration' + 'uses' => 'Admin\ServiceController@getConfiguration', ]); $router->post('/service/{id}/configuration', [ - 'uses' => 'Admin\ServiceController@postConfiguration' + 'uses' => 'Admin\ServiceController@postConfiguration', ]); $router->get('/service/{service}/option/new', [ @@ -435,45 +435,45 @@ class AdminRoutes 'middleware' => [ 'auth', 'admin', - 'csrf' - ] + 'csrf', + ], ], function () use ($router) { $router->get('/new/{option?}', [ 'as' => 'admin.services.packs.new', - 'uses' => 'Admin\PackController@new' + 'uses' => 'Admin\PackController@new', ]); $router->post('/new', [ - 'uses' => 'Admin\PackController@create' + 'uses' => 'Admin\PackController@create', ]); $router->get('/upload/{option?}', [ 'as' => 'admin.services.packs.uploadForm', - 'uses' => 'Admin\PackController@uploadForm' + 'uses' => 'Admin\PackController@uploadForm', ]); $router->post('/upload', [ - 'uses' => 'Admin\PackController@postUpload' + 'uses' => 'Admin\PackController@postUpload', ]); $router->get('/', [ 'as' => 'admin.services.packs', - 'uses' => 'Admin\PackController@listAll' + 'uses' => 'Admin\PackController@listAll', ]); $router->get('/for/option/{option}', [ 'as' => 'admin.services.packs.option', - 'uses' => 'Admin\PackController@listByOption' + 'uses' => 'Admin\PackController@listByOption', ]); $router->get('/for/service/{service}', [ 'as' => 'admin.services.packs.service', - 'uses' => 'Admin\PackController@listByService' + 'uses' => 'Admin\PackController@listByService', ]); $router->get('/edit/{pack}', [ 'as' => 'admin.services.packs.edit', - 'uses' => 'Admin\PackController@edit' + 'uses' => 'Admin\PackController@edit', ]); $router->post('/edit/{pack}', [ - 'uses' => 'Admin\PackController@update' + 'uses' => 'Admin\PackController@update', ]); $router->get('/edit/{pack}/export/{archive?}', [ 'as' => 'admin.services.packs.export', - 'uses' => 'Admin\PackController@export' + 'uses' => 'Admin\PackController@export', ]); }); } diff --git a/app/Models/Checksum.php b/app/Models/Checksum.php index 8038b7bf..5e775a59 100644 --- a/app/Models/Checksum.php +++ b/app/Models/Checksum.php @@ -1,7 +1,7 @@ + * 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 @@ -21,13 +21,13 @@ * 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. * @@ -48,7 +48,6 @@ class Checksum extends Model * @var array */ protected $casts = [ - 'service' => 'integer' + 'service' => 'integer', ]; - } diff --git a/app/Models/ServicePack.php b/app/Models/ServicePack.php index f43be94b..9b5256a3 100644 --- a/app/Models/ServicePack.php +++ b/app/Models/ServicePack.php @@ -1,7 +1,7 @@ + * 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 @@ -21,13 +21,13 @@ * 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. * @@ -42,11 +42,11 @@ class ServicePack extends Model */ protected $guarded = ['id', 'created_at', 'updated_at']; - /** - * Cast values to correct type. - * - * @var array - */ + /** + * Cast values to correct type. + * + * @var array + */ protected $casts = [ 'option' => 'integer', 'build_memory' => 'integer', @@ -54,7 +54,6 @@ class ServicePack extends Model 'build_cpu' => 'integer', 'build_io' => 'integer', 'selectable' => 'boolean', - 'visible' => 'boolean' + 'visible' => 'boolean', ]; - } diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 5541c8c5..a9f64a49 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -162,9 +162,9 @@ class ServerRepository $data['pack'] = null; } - if (!is_null($data['pack'])) { + if (! is_null($data['pack'])) { $pack = Models\ServicePack::where('id', $data['pack'])->where('option', $data['option'])->first(); - if (!$pack) { + if (! $pack) { throw new DisplayException('The requested service pack does not seem to exist for this combination.'); } } diff --git a/app/Repositories/ServiceRepository/Pack.php b/app/Repositories/ServiceRepository/Pack.php index 5d8591b6..61327261 100644 --- a/app/Repositories/ServiceRepository/Pack.php +++ b/app/Repositories/ServiceRepository/Pack.php @@ -1,7 +1,7 @@ + * 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 @@ -21,13 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + namespace Pterodactyl\Repositories\ServiceRepository; use DB; -use Storage; use Uuid; +use Storage; use Validator; - use Pterodactyl\Models; use Pterodactyl\Services\UuidService; use Pterodactyl\Exceptions\DisplayException; @@ -35,7 +35,6 @@ use Pterodactyl\Exceptions\DisplayValidationException; class Pack { - public function __construct() { // @@ -55,7 +54,7 @@ class Pack 'build_cpu' => 'required|integer|min:0', 'build_io' => 'required|integer|min:10|max:1000', 'build_container' => 'required|string', - 'build_script' => 'sometimes|nullable|string' + 'build_script' => 'sometimes|nullable|string', ]); if ($validator->fails()) { @@ -63,13 +62,13 @@ class Pack } if (isset($data['file_upload'])) { - if (!$data['file_upload']->isValid()) { + if (! $data['file_upload']->isValid()) { throw new DisplayException('The file provided does not appear to be valid.'); } - if (!in_array($data['file_upload']->getMimeType(), [ + if (! in_array($data['file_upload']->getMimeType(), [ 'application/zip', - 'application/gzip' + 'application/gzip', ])) { throw new DisplayException('The file provided does not meet the required filetypes of application/zip or application/gzip.'); } @@ -91,7 +90,7 @@ class Pack 'version' => $data['version'], 'description' => (empty($data['description'])) ? null : $data['description'], 'selectable' => isset($data['selectable']), - 'visible' => isset($data['visible']) + 'visible' => isset($data['visible']), ]); Storage::makeDirectory('packs/' . $pack->uuid); @@ -111,25 +110,25 @@ class Pack public function createWithTemplate(array $data) { - if (!isset($data['file_upload'])) { + if (! isset($data['file_upload'])) { throw new DisplayException('No template file was found submitted with this request.'); } - if (!$data['file_upload']->isValid()) { + if (! $data['file_upload']->isValid()) { throw new DisplayException('The file provided does not appear to be valid.'); } - if (!in_array($data['file_upload']->getMimeType(), [ + if (! in_array($data['file_upload']->getMimeType(), [ 'application/zip', 'text/plain', - 'application/json' + '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())) { + if (! $zip->open($data['file_upload']->path())) { throw new DisplayException('The uploaded archive was unable to be opened.'); } @@ -153,19 +152,21 @@ class Pack 'build_cpu' => $json->build->cpu, 'build_io' => $json->build->io, 'build_container' => $json->build->container, - 'build_script' => $json->build->script + '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')) { + 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, @@ -178,10 +179,9 @@ class Pack 'build_cpu' => $json->build->cpu, 'build_io' => $json->build->io, 'build_container' => $json->build->container, - 'build_script' => $json->build->script + 'build_script' => $json->build->script, ]); } - } public function update($id, array $data) @@ -198,7 +198,7 @@ class Pack 'build_cpu' => 'required|integer|min:0', 'build_io' => 'required|integer|min:10|max:1000', 'build_container' => 'required|string', - 'build_script' => 'sometimes|string' + 'build_script' => 'sometimes|string', ]); if ($validator->fails()) { @@ -218,14 +218,15 @@ class Pack 'version' => $data['version'], 'description' => (empty($data['description'])) ? null : $data['description'], 'selectable' => isset($data['selectable']), - 'visible' => isset($data['visible']) + 'visible' => isset($data['visible']), ]); return true; }); } - public function delete($id) { + public function delete($id) + { $pack = Models\ServicePack::findOrFail($id); // @TODO Check for linked servers; foreign key should block this. DB::transaction(function () use ($pack) { @@ -233,5 +234,4 @@ class Pack Storage::deleteDirectory('packs/' . $pack->uuid); }); } - } diff --git a/app/Repositories/ServiceRepository/Service.php b/app/Repositories/ServiceRepository/Service.php index 4a5b030b..becc290e 100644 --- a/app/Repositories/ServiceRepository/Service.php +++ b/app/Repositories/ServiceRepository/Service.php @@ -125,7 +125,7 @@ class Service $validator = Validator::make($data, [ 'file' => 'required|in:index,main', - 'contents' => 'required|string' + 'contents' => 'required|string', ]); if ($validator->fails()) { @@ -139,10 +139,9 @@ class Service try { Storage::move($filepath, $backup); Storage::put($filepath, $data['contents']); - } catch(\Exception $ex) { + } catch (\Exception $ex) { Storage::move($backup, $filepath); throw $ex; } - } } From 84f87680d82c9a4a2a3e4ca7f8b551dbb5873b7d Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 14 Dec 2016 17:07:47 -0500 Subject: [PATCH 15/22] Add editorconfig --- .editorconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..557990c5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true + +[*.{php,js,html,css}] +charset = utf-8 +indent_style = space +indent_size = 4 From fd360f647511be41d525805746312db1e0ef879f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 14 Dec 2016 17:17:16 -0500 Subject: [PATCH 16/22] Fix data pack assignment --- app/Repositories/ServerRepository.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index a9f64a49..a133e052 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -79,9 +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', - 'pack' => 'bail|required|numeric|min:0', + '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', @@ -158,7 +158,7 @@ class ServerRepository } // Validate the Pack - if ($data['pack'] === 0) { + if ($data['pack'] == 0) { $data['pack'] = null; } From 51ce4d4d4771cc39eb3efa77d2b0262bc223a658 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 14 Dec 2016 18:54:02 -0500 Subject: [PATCH 17/22] Update existing configs --- storage/app/services/minecraft/main.json | 17 ++++++++++++----- storage/app/services/terraria/main.json | 4 ++-- storage/app/services/voice/main.json | 10 +++++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/storage/app/services/minecraft/main.json b/storage/app/services/minecraft/main.json index b131d988..48a490cb 100644 --- a/storage/app/services/minecraft/main.json +++ b/storage/app/services/minecraft/main.json @@ -13,8 +13,8 @@ "find": { "server-ip": "0.0.0.0", "enable-query": "true", - "server-port": "{{ build.default.port }}", - "query.port": "{{ build.default.port }}" + "server-port": "{{ server.build.default.port }}", + "query.port": "{{ server.build.default.port }}" } } }, @@ -37,7 +37,10 @@ }, "bungeecord": { "startup": { - "done": "Listening on " + "done": "Listening on ", + "userInteraction": [ + "Listening on /0.0.0.0:25577" + ] }, "stop": "end", "configs": { @@ -45,8 +48,12 @@ "parser": "yaml", "find": { "listeners[0].query_enabled": true, - "listeners[0].query_port": "{{ build.default.port }}", - "listeners[0].host": "0.0.0.0:{{ build.default.port }}" + "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 }}" + } } } }, diff --git a/storage/app/services/terraria/main.json b/storage/app/services/terraria/main.json index 6eacd749..78f1b5bc 100644 --- a/storage/app/services/terraria/main.json +++ b/storage/app/services/terraria/main.json @@ -9,8 +9,8 @@ "tshock/config.json": { "parser": "json", "find": { - "ServerPort": "{{ build.default.port }}", - "MaxSlots": "{{ build.env.MAX_SLOTS }}" + "ServerPort": "{{ server.build.default.port }}", + "MaxSlots": "{{ server.build.env.MAX_SLOTS }}" } } }, diff --git a/storage/app/services/voice/main.json b/storage/app/services/voice/main.json index 6bc8e5c9..bc7232f4 100644 --- a/storage/app/services/voice/main.json +++ b/storage/app/services/voice/main.json @@ -12,9 +12,9 @@ "parser": "ini", "find": { "logfile": "murmur.log", - "port": "{{ build.default.port }}", + "port": "{{ server.build.default.port }}", "host": "0.0.0.0", - "users": "{{ build.env.MAX_USERS }}" + "users": "{{ server.build.env.MAX_USERS }}" } } }, @@ -24,7 +24,7 @@ }, "query": "mumbleping" }, - "teamspeak": { + "ts3": { "startup": { "done": "listening on 0.0.0.0:", "userInteraction": [] @@ -34,9 +34,9 @@ "ts3server.ini": { "parser": "ini", "find": { - "default_voice_port": "{{ build.default.port }}", + "default_voice_port": "{{ server.build.default.port }}", "voice_ip": "0.0.0.0", - "query_port": "{{ build.default.port }}", + "query_port": "{{ server.build.default.port }}", "query_ip": "0.0.0.0" } } From a49dee2416f403e57e3b3fac6e65370ab31c779f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Wed, 14 Dec 2016 18:54:43 -0500 Subject: [PATCH 18/22] Add base implementation of service retrieval. :horse_racing: There is currently no authentication middleware on this route. --- .../Controllers/Daemon/ServiceController.php | 80 +++++++++++++++++++ app/Http/Routes/DaemonRoutes.php | 44 ++++++++++ 2 files changed, 124 insertions(+) create mode 100644 app/Http/Controllers/Daemon/ServiceController.php create mode 100644 app/Http/Routes/DaemonRoutes.php diff --git a/app/Http/Controllers/Daemon/ServiceController.php b/app/Http/Controllers/Daemon/ServiceController.php new file mode 100644 index 00000000..6b71f329 --- /dev/null +++ b/app/Http/Controllers/Daemon/ServiceController.php @@ -0,0 +1,80 @@ +. + * + * 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/DaemonRoutes.php b/app/Http/Routes/DaemonRoutes.php new file mode 100644 index 00000000..00564e9a --- /dev/null +++ b/app/Http/Routes/DaemonRoutes.php @@ -0,0 +1,44 @@ + + * + * 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', + ]); + }); + } +} From 8323477d0cc5a505885aeb56c58b85dae6e4f383 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 12 Jan 2017 13:38:59 -0500 Subject: [PATCH 19/22] Better display of configuration button for services --- CHANGELOG.md | 11 +++++++++++ resources/views/admin/services/index.blade.php | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33682bd8..4ce55ec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ 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. + +### Fixed + +### Changed + +### 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/resources/views/admin/services/index.blade.php b/resources/views/admin/services/index.blade.php index e55fce78..4106964e 100644 --- a/resources/views/admin/services/index.blade.php +++ b/resources/views/admin/services/index.blade.php @@ -33,9 +33,10 @@
    NameService Option Total Packs
    - + + @@ -44,9 +45,11 @@ + @endforeach + From f2920804831c5049b5761fb48320dcfd7e72ea7f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 12 Jan 2017 13:44:23 -0500 Subject: [PATCH 20/22] Should close #244 What a peculiar bug. Also modifies code to try and return the correct status code, as well as return JSON based errors on any request that Laravel thinks should have a JSON based response. --- app/Exceptions/Handler.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 2b89cacb..fedfa1c4 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); From e91362eee6e604635c9dcce900b534688147d643 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 12 Jan 2017 15:40:24 -0500 Subject: [PATCH 21/22] Update user controller --- CHANGELOG.md | 4 ++ app/Http/Controllers/API/UserController.php | 7 +- app/Http/Controllers/Admin/UserController.php | 27 ++++---- app/Models/User.php | 32 +++++---- app/Repositories/UserRepository.php | 65 ++++++++++++------- .../2017_01_12_135449_add_more_user_data.php | 50 ++++++++++++++ resources/views/admin/users/index.blade.php | 18 +++-- resources/views/admin/users/new.blade.php | 33 ++++++++-- resources/views/admin/users/view.blade.php | 41 +++++++----- 9 files changed, 200 insertions(+), 77 deletions(-) create mode 100644 database/migrations/2017_01_12_135449_add_more_user_data.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ce55ec5..dd10b645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,14 @@ This project follows [Semantic Versioning](http://semver.org) guidelines. ### 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 diff --git a/app/Http/Controllers/API/UserController.php b/app/Http/Controllers/API/UserController.php index 59e9af97..c3a658a0 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/UserController.php b/app/Http/Controllers/Admin/UserController.php index 36e2590b..8854d39a 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/Models/User.php b/app/Models/User.php index ef7bda0b..c13a9d13 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/UserRepository.php b/app/Repositories/UserRepository.php index add04c92..db715fbb 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/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 00000000..7240d283 --- /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/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php index 04eaaf20..5b9f59ce 100644 --- a/resources/views/admin/users/index.blade.php +++ b/resources/views/admin/users/index.blade.php @@ -42,17 +42,21 @@
    Service TypeService Type Description Servers
    {{ $service->name }} {!! $service->description !!} {{ $service->c_servers }}
    - - - + + + @foreach ($users as $user) - - - - + + + + + + @endforeach diff --git a/resources/views/admin/users/new.blade.php b/resources/views/admin/users/new.blade.php index 39ad6eca..abd7cadb 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 6ec648ef..7b817e3a 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.

    +
    +
    +
    From a5aa089d660b1ddb00f098e097205b1665c1ab77 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Thu, 12 Jan 2017 20:48:12 +0000 Subject: [PATCH 22/22] Apply fixes from StyleCI --- app/Http/Controllers/Admin/UserController.php | 2 +- app/Http/Controllers/Daemon/ServiceController.php | 1 - app/Http/Routes/DaemonRoutes.php | 5 +++-- database/migrations/2017_01_12_135449_add_more_user_data.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 8854d39a..8e4425a8 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -121,7 +121,7 @@ class UserController extends Controller 'password', 'name_first', 'name_last', - 'username' + 'username', ])); Alert::success('Account has been successfully created.')->flash(); diff --git a/app/Http/Controllers/Daemon/ServiceController.php b/app/Http/Controllers/Daemon/ServiceController.php index 6b71f329..63c449f2 100644 --- a/app/Http/Controllers/Daemon/ServiceController.php +++ b/app/Http/Controllers/Daemon/ServiceController.php @@ -76,5 +76,4 @@ class ServiceController extends Controller return response()->file(storage_path('app/services/' . $service . '/' . $file)); } - } diff --git a/app/Http/Routes/DaemonRoutes.php b/app/Http/Routes/DaemonRoutes.php index 00564e9a..ab8b733a 100644 --- a/app/Http/Routes/DaemonRoutes.php +++ b/app/Http/Routes/DaemonRoutes.php @@ -1,7 +1,7 @@ + * 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 @@ -26,7 +26,8 @@ namespace Pterodactyl\Http\Routes; use Illuminate\Routing\Router; -class DaemonRoutes { +class DaemonRoutes +{ public function map(Router $router) { $router->group(['prefix' => 'daemon'], function () use ($router) { 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 index 7240d283..67bc3f59 100644 --- a/database/migrations/2017_01_12_135449_add_more_user_data.php +++ b/database/migrations/2017_01_12_135449_add_more_user_data.php @@ -22,7 +22,7 @@ class AddMoreUserData extends Migration }); DB::transaction(function () { - foreach(User::all() as &$user) { + foreach (User::all() as &$user) { $user->username = $user->email; $user->save(); }
    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 }}