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 000000000..82453d6c7 --- /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 c8276b447..4cd65f46e 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 dc5977470..35294343e 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 bfc07b725..726fccc26 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 000000000..8038b7bf0 --- /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 8feb92c1e..4dc1755b3 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 000000000..4935faaf2 --- /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 000000000..fbfa01d38 --- /dev/null +++ b/resources/views/admin/services/config.blade.php @@ -0,0 +1,180 @@ +{{-- Copyright (c) 2015 - 2016 Dane Everitt --}} + +{{-- Permission is hereby granted, free of charge, to any person obtaining a copy --}} +{{-- of this software and associated documentation files (the "Software"), to deal --}} +{{-- in the Software without restriction, including without limitation the rights --}} +{{-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell --}} +{{-- copies of the Software, and to permit persons to whom the Software is --}} +{{-- furnished to do so, subject to the following conditions: --}} + +{{-- The above copyright notice and this permission notice shall be included in all --}} +{{-- copies or substantial portions of the Software. --}} + +{{-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR --}} +{{-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, --}} +{{-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE --}} +{{-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER --}} +{{-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, --}} +{{-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE --}} +{{-- SOFTWARE. --}} +@extends('layouts.admin') + +@section('title') + Manage Service Configuration +@endsection + +@section('content') +
+ +

Service Configuration


+ +
+
+
+
+
+
+
+
{{ $contents['json'] }}
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
{{ $contents['index'] }}
+
+
+
+
+ +
+
+
+
+
+
+
+{!! Theme::js('js/vendor/ace/ace.js') !!} +{!! Theme::js('js/vendor/ace/ext-modelist.js') !!} + +@endsection diff --git a/resources/views/admin/services/view.blade.php b/resources/views/admin/services/view.blade.php index e05d061fd..4de4d6e9d 100644 --- a/resources/views/admin/services/view.blade.php +++ b/resources/views/admin/services/view.blade.php @@ -106,6 +106,7 @@
{!! csrf_field() !!} +
diff --git a/resources/views/layouts/admin.blade.php b/resources/views/layouts/admin.blade.php index 803c222a2..f8ff0a493 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 c96a04f00..8d9045b3f 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 000000000..67c4dbb2f --- /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 000000000..1a94258ad --- /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 000000000..29b0439f4 --- /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 000000000..fdd65e333 --- /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 000000000..55b396afe --- /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 000000000..c2a74d5b9 --- /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 000000000..022b417aa --- /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 000000000..0479dd884 --- /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 4cd65f46e..53f80281d 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 000000000..9d2474395 --- /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 726fccc26..73679de69 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 000000000..f43be94b6 --- /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 000000000..b644dae56 --- /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 4dc1755b3..91062dcb7 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 4935faaf2..000000000 --- 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 000000000..87a66b40a --- /dev/null +++ b/database/migrations/2016_11_11_220649_add_pack_support.php @@ -0,0 +1,46 @@ +increments('id'); + $table->unsignedInteger('option'); + $table->char('uuid', 36)->unique(); + $table->unsignedInteger('build_memory')->nullable(); + $table->unsignedInteger('build_swap')->nullable(); + $table->unsignedInteger('build_cpu')->nullable(); + $table->unsignedInteger('build_io')->nullable(); + $table->text('build_script')->nullable(); + $table->string('build_container')->default('alpine:latest'); + $table->string('name'); + $table->string('version'); + $table->text('description')->nullable(); + $table->boolean('selectable')->default(true); + $table->boolean('visible')->default(true); + $table->timestamps(); + + $table->foreign('option')->references('id')->on('service_options'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('service_packs'); + } +} diff --git a/database/migrations/2016_11_11_231731_set_service_name_unique.php b/database/migrations/2016_11_11_231731_set_service_name_unique.php new file mode 100644 index 000000000..4db76f8e8 --- /dev/null +++ b/database/migrations/2016_11_11_231731_set_service_name_unique.php @@ -0,0 +1,32 @@ +unique('name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('services', function (Blueprint $table) { + $table->dropUnique('services_name_unique'); + }); + } +} diff --git a/public/themes/default/css/pterodactyl.css b/public/themes/default/css/pterodactyl.css index 607da8d1f..fb24beda9 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 d5de1a910..414f8974a 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 000000000..e69de29bb diff --git a/resources/views/admin/services/packs/index.blade.php b/resources/views/admin/services/packs/index.blade.php new file mode 100644 index 000000000..2ec5ed0e4 --- /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 000000000..0c75e8d05 --- /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 8d9045b3f..104980aa3 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 000000000..e69de29bb 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 9d2474395..5a7ecf49d 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 73679de69..d84de184c 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 414f8974a..d5de1a910 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 2ec5ed0e4..6ec8f03e1 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 000000000..ddb538ac5 --- /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 e69de29bb..abc89fe99 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 0c75e8d05..b6895636f 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 e16c94686..acb05c7a7 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 d84de184c..96819bbbe 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 4e3d190bc..c4b3529de 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 6ec8f03e1..cc285d16f 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 b6895636f..af658cce4 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 000000000..e9ca020fa --- /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 acb05c7a7..c94a001d6 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 c4b3529de..5d8591b69 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 abc89fe99..b71256fd9 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 91062dcb7..9e8328839 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 000000000..aa60eec19 --- /dev/null +++ b/storage/app/services/.templates/index.js @@ -0,0 +1,31 @@ +'use strict'; + +/** + * Pterodactyl - Daemon + * Copyright (c) 2015 - 2016 Dane Everitt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +const rfr = require('rfr'); + +const Core = rfr('src/services/index.js'); + +class Service extends Core {} + +module.exports = Service; 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 67c4dbb2f..2d7f812c3 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 1a94258ad..b131d9885 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 29b0439f4..aa60eec19 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 fdd65e333..989a67537 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 55b396afe..aa60eec19 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 c2a74d5b9..6eacd7498 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 022b417aa..aa60eec19 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 0479dd884..6bc8e5c95 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 cd421ef9c..ce7f0f4f0 100644 --- a/resources/views/admin/services/new.blade.php +++ b/resources/views/admin/services/new.blade.php @@ -55,7 +55,7 @@ /index.js -

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

    +

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

    diff --git a/resources/views/admin/services/packs/byoption.blade.php b/resources/views/admin/services/packs/byoption.blade.php index cc285d16f..626958566 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 ddb538ac5..f73572c33 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 5ec7d9a2b..310af7b97 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 96819bbbe..27b6bae32 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 8db93ffcd..276a10958 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 310af7b97..c15a4a658 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 47b1a4a97..5d25f0e68 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 000000000..f2c2f0964 --- /dev/null +++ b/database/migrations/2016_11_27_142519_add_pack_column.php @@ -0,0 +1,36 @@ +unsignedInteger('pack')->nullable()->after('option'); + + $table->foreign('pack')->references('id')->on('service_packs'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('servers', function (Blueprint $table) { + $table->dropForeign('servers_pack_foreign'); + $table->dropIndex('servers_pack_foreign'); + $table->dropColumn('pack'); + }); + } +} diff --git a/resources/views/admin/servers/new.blade.php b/resources/views/admin/servers/new.blade.php index 276a10958..5d831ce6f 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 c15a4a658..8b19fafea 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 5d25f0e68..bb8f52c56 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 5d831ce6f..7fe9b76d9 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 82453d6c7..93af785f4 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 c94a001d6..fdf1b5a1a 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 db05448b6..adcde0f61 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 cef9eeead..fb3d16ba7 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 8038b7bf0..5e775a59f 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 f43be94b6..9b5256a3b 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 5541c8c55..a9f64a490 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 5d8591b69..61327261f 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 4a5b030b0..becc290e6 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 000000000..557990c52 --- /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 a9f64a490..a133e0521 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 b131d9885..48a490cb3 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 6eacd7498..78f1b5bc0 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 6bc8e5c95..bc7232f46 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 000000000..6b71f3295 --- /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 000000000..00564e9a5 --- /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 33682bd80..4ce55ec5a 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 e55fce78e..4106964e9 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 2b89cacbf..fedfa1c4b 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -46,13 +46,12 @@ class Handler extends ExceptionHandler */ public function render($request, Exception $exception) { - if ($request->isXmlHttpRequest() || $request->ajax() || $request->is('remote/*')) { + if ($request->expectsJson()) { $response = response()->json([ 'error' => ($exception instanceof DisplayException) ? $exception->getMessage() : 'An unhandled error occured while attempting to process this request.', - ], 500); + ], ($this->isHttpException($exception)) ? $e->getStatusCode() : 500); - // parent::render() will log it, we are bypassing it in this case. - Log::error($exception); + parent::report($exception); } return (isset($response)) ? $response : parent::render($request, $exception); 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 4ce55ec5a..dd10b6458 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 59e9af975..c3a658a0e 100755 --- a/app/Http/Controllers/API/UserController.php +++ b/app/Http/Controllers/API/UserController.php @@ -122,6 +122,9 @@ class UserController extends BaseController { try { $user = new UserRepository; + $create = $user->create($request->only([ + 'email', 'username', 'name_first', 'name_last', 'password', 'root_admin', 'custom_id', + ])); $create = $user->create($request->input('email'), $request->input('password'), $request->input('admin'), $request->input('custom_id')); return ['id' => $create]; @@ -156,7 +159,9 @@ class UserController extends BaseController { try { $user = new UserRepository; - $user->update($id, $request->all()); + $user->update($id, $request->only([ + 'username', 'email', 'name_first', 'name_last', 'password', 'root_admin', 'language', + ])); return Models\User::findOrFail($id); } catch (DisplayValidationException $ex) { diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 36e2590ba..8854d39ad 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 ef7bda0bd..c13a9d133 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -37,13 +37,24 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; -class User extends Model implements - AuthenticatableContract, - AuthorizableContract, - CanResetPasswordContract +class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract { use Authenticatable, Authorizable, CanResetPassword, Notifiable; + /** + * The rules for user passwords. + * + * @var string + */ + const PASSWORD_RULES = 'regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; + + /** + * The regex rules for usernames. + * + * @var string + */ + const USERNAME_RULES = 'regex:/^([\w\d\.\-]{1,255})$/'; + /** * The table associated with the model. * @@ -52,11 +63,11 @@ class User extends Model implements protected $table = 'users'; /** - * The attributes that are not mass assignable. + * A list of mass-assignable variables. * - * @var array + * @var [type] */ - protected $guarded = ['id', 'remeber_token', 'created_at', 'updated_at']; + protected $fillable = ['username', 'email', 'name_first', 'name_last', 'password', 'language', 'use_totp', 'totp_secret', 'gravatar']; /** * Cast values to correct type. @@ -66,6 +77,7 @@ class User extends Model implements protected $casts = [ 'root_admin' => 'integer', 'use_totp' => 'integer', + 'gravatar' => 'integer', ]; /** @@ -76,12 +88,10 @@ class User extends Model implements protected $hidden = ['password', 'remember_token', 'totp_secret']; /** - * The rules for user passwords. + * Determines if a user has permissions. * - * @var string + * @return bool */ - const PASSWORD_RULES = 'min:8|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})'; - public function permissions() { return $this->hasMany(Permission::class); diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index add04c920..db715fbbc 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -29,6 +29,7 @@ use DB; use Auth; use Hash; use Carbon; +use Settings; use Validator; use Pterodactyl\Models; use Pterodactyl\Services\UuidService; @@ -52,18 +53,16 @@ class UserRepository * @param int $token A custom user ID. * @return bool|int */ - public function create($email, $password = null, $admin = false, $token = null) + public function create(array $data) { - $validator = Validator::make([ - 'email' => $email, - 'password' => $password, - 'root_admin' => $admin, - 'custom_id' => $token, - ], [ + $validator = Validator::make($data, [ 'email' => 'required|email|unique:users,email', - 'password' => 'nullable|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'username' => 'required|string|between:1,255|unique:users,username|' . Models\User::USERNAME_RULES, + 'name_first' => 'required|string|between:1,255', + 'name_last' => 'required|string|between:1,255', + 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, 'root_admin' => 'required|boolean', - 'custom_id' => 'nullable|unique:users,id', + 'custom_id' => 'sometimes|nullable|unique:users,id', ]); // Run validator, throw catchable and displayable exception if it fails. @@ -79,26 +78,36 @@ class UserRepository $uuid = new UuidService; // Support for API Services - if (! is_null($token)) { + if (isset($data['custom_id']) && ! is_null($data['custom_id'])) { $user->id = $token; } + // UUIDs are not mass-fillable. $user->uuid = $uuid->generate('users', 'uuid'); - $user->email = $email; - $user->password = Hash::make((is_null($password)) ? str_random(30) : $password); - $user->language = 'en'; - $user->root_admin = ($admin) ? 1 : 0; + + $user->fill([ + 'email' => $data['email'], + 'username' => $data['username'], + 'name_first' => $data['name_first'], + 'name_last' => $data['name_last'], + 'password' => Hash::make((empty($data['password'])) ? str_random(30) : $password), + 'root_admin' => $data['root_admin'], + 'language' => Settings::get('default_language', 'en'), + ]); $user->save(); // Setup a Password Reset to use when they set a password. - $token = str_random(32); - DB::table('password_resets')->insert([ - 'email' => $user->email, - 'token' => $token, - 'created_at' => Carbon::now()->toDateTimeString(), - ]); + // Only used if no password is provided. + if (empty($data['password'])) { + $token = str_random(32); + DB::table('password_resets')->insert([ + 'email' => $user->email, + 'token' => $token, + 'created_at' => Carbon::now()->toDateTimeString(), + ]); - $user->notify((new AccountCreated($token))); + $user->notify((new AccountCreated($token))); + } DB::commit(); @@ -122,7 +131,10 @@ class UserRepository $validator = Validator::make($data, [ 'email' => 'sometimes|required|email|unique:users,email,' . $id, - 'password' => 'sometimes|required|regex:((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})', + 'username' => 'sometimes|required|string|between:1,255|unique:users,username,' . $user->id . '|' . Models\User::USERNAME_RULES, + 'name_first' => 'sometimes|required|string|between:1,255', + 'name_last' => 'sometimes|required|string|between:1,255', + 'password' => 'sometimes|nullable|' . Models\User::PASSWORD_RULES, 'root_admin' => 'sometimes|required|boolean', 'language' => 'sometimes|required|string|min:1|max:5', 'use_totp' => 'sometimes|required|boolean', @@ -135,12 +147,15 @@ class UserRepository throw new DisplayValidationException($validator->errors()); } - if (array_key_exists('password', $data)) { + // The password and root_admin fields are not mass assignable. + if (! empty($data['password'])) { $data['password'] = Hash::make($data['password']); + } else { + unset($data['password']); } - if (isset($data['password_confirmation'])) { - unset($data['password_confirmation']); + if (! empty($data['root_admin'])) { + $user->root_admin = $data['root_admin']; } $user->fill($data); diff --git a/database/migrations/2017_01_12_135449_add_more_user_data.php b/database/migrations/2017_01_12_135449_add_more_user_data.php new file mode 100644 index 000000000..7240d2838 --- /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 04eaaf207..5b9f59ce0 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 39ad6ecab..abd7cadb0 100644 --- a/resources/views/admin/users/new.blade.php +++ b/resources/views/admin/users/new.blade.php @@ -34,15 +34,38 @@

    Create New Account


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

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

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

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


    +

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

    +

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

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

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

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

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


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

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

    +
    +
    +
    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 8854d39ad..8e4425a80 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 6b71f3295..63c449f2d 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 00564e9a5..ab8b733ab 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 7240d2838..67bc3f59d 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 }}