diff --git a/.env.example b/.env.example index 3da515951..47166434c 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ APP_KEY=SomeRandomString3232RandomString APP_THEME=default APP_TIMEZONE=UTC APP_CLEAR_TASKLOG=720 +APP_DELETE_MINUTES=10 DB_HOST=localhost DB_PORT=3306 diff --git a/app/Events/ServerDeleted.php b/app/Events/ServerDeleted.php new file mode 100644 index 000000000..4451b01bc --- /dev/null +++ b/app/Events/ServerDeleted.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\Events; + +use Illuminate\Queue\SerializesModels; + +class ServerDeleted +{ + use SerializesModels; + + public $server; + + /** + * Create a new event instance. + * + * @return void + */ + public function __construct($id) + { + $this->server = $id; + } + +} diff --git a/app/Http/Controllers/Admin/ServersController.php b/app/Http/Controllers/Admin/ServersController.php index 05cbe134d..8f0b184c6 100644 --- a/app/Http/Controllers/Admin/ServersController.php +++ b/app/Http/Controllers/Admin/ServersController.php @@ -51,7 +51,7 @@ class ServersController extends Controller public function getIndex(Request $request) { - $query = Models\Server::select( + $query = Models\Server::withTrashed()->select( 'servers.*', 'nodes.name as a_nodeName', 'users.email as a_ownerEmail', @@ -84,7 +84,7 @@ class ServersController extends Controller $servers = $query->paginate(20); } catch (\Exception $ex) { Alert::warning('There was an error with the search parameters provided.'); - $servers = Models\Server::select( + $servers = Models\Server::withTrashed()->select( 'servers.*', 'nodes.name as a_nodeName', 'users.email as a_ownerEmail', @@ -112,7 +112,7 @@ class ServersController extends Controller public function getView(Request $request, $id) { - $server = Models\Server::select( + $server = Models\Server::withTrashed()->select( 'servers.*', 'nodes.name as a_nodeName', 'users.email as a_ownerEmail', @@ -394,7 +394,7 @@ class ServersController extends Controller try { $server = new ServerRepository; $server->deleteServer($id, $force); - Alert::success('Server was successfully deleted from the panel and the daemon.')->flash(); + Alert::success('Server has been marked for deletion on the system.')->flash(); return redirect()->route('admin.servers'); } catch (DisplayException $ex) { Alert::danger($ex->getMessage())->flash(); @@ -510,4 +510,31 @@ class ServersController extends Controller } } + public function postQueuedDeletionHandler(Request $request, $id) + { + try { + $repo = new ServerRepository; + if (!is_null($request->input('cancel'))) { + $repo->cancelDeletion($id); + Alert::success('Server deletion has been cancelled. This server will remain suspended until you unsuspend it.')->flash(); + return redirect()->route('admin.servers.view', $id); + } else if(!is_null($request->input('delete'))) { + $repo->deleteNow($id); + Alert::success('Server was successfully deleted from the system.')->flash(); + return redirect()->route('admin.servers'); + } else if(!is_null($request->input('force_delete'))) { + $repo->deleteNow($id, true); + Alert::success('Server was successfully force deleted from the system.')->flash(); + return redirect()->route('admin.servers'); + } + } catch (DisplayException $ex) { + Alert::danger($ex->getMessage())->flash(); + return redirect()->route('admin.servers.view', $id); + } catch (\Exception $ex) { + Log::error($ex); + Alert::danger('An unhandled error occured while attempting to perform this action.')->flash(); + return redirect()->route('admin.servers.view', $id); + } + } + } diff --git a/app/Http/Routes/AdminRoutes.php b/app/Http/Routes/AdminRoutes.php index f8df15274..bfc07b725 100644 --- a/app/Http/Routes/AdminRoutes.php +++ b/app/Http/Routes/AdminRoutes.php @@ -210,6 +210,11 @@ class AdminRoutes { 'uses' => 'Admin\ServersController@deleteServer' ]); + $router->post('/view/{id}/queuedDeletion', [ + 'uses' => 'Admin\ServersController@postQueuedDeletionHandler', + 'as' => 'admin.servers.post.queuedDeletion' + ]); + }); // Node Routes diff --git a/app/Jobs/DeleteServer.php b/app/Jobs/DeleteServer.php new file mode 100644 index 000000000..78e1e1f48 --- /dev/null +++ b/app/Jobs/DeleteServer.php @@ -0,0 +1,67 @@ + + * + * 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\Jobs; + +use DB; + +use Illuminate\Bus\Queueable; +use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Contracts\Queue\ShouldQueue; + +use Pterodactyl\Models; +use Pterodactyl\Repositories\ServerRepository; + +class DeleteServer extends Job implements ShouldQueue +{ + use InteractsWithQueue, SerializesModels; + + /** + * Id of server to be deleted. + * @var object + */ + protected $id; + + /** + * Create a new job instance. + * + * @param integer $server + * @return void + */ + public function __construct($id) + { + $this->id = $id; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $repo = new ServerRepository; + $repo->deleteNow($this->id); + } +} diff --git a/app/Jobs/SuspendServer.php b/app/Jobs/SuspendServer.php new file mode 100644 index 000000000..912f8a0a9 --- /dev/null +++ b/app/Jobs/SuspendServer.php @@ -0,0 +1,65 @@ + + * + * 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\Jobs; + +use Debugbar; +use Illuminate\Bus\Queueable; +use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Contracts\Queue\ShouldQueue; + +use Pterodactyl\Repositories\ServerRepository; + +class SuspendServer extends Job implements ShouldQueue +{ + use InteractsWithQueue, SerializesModels; + + /** + * ID of associated server model. + * @var object + */ + protected $id; + + /** + * Create a new job instance. + * + * @param integer $id + * @return void + */ + public function __construct($id) + { + $this->id = $id; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $repo = new ServerRepository; + $repo->suspend($this->id, true); + } +} diff --git a/app/Listeners/DeleteServerListener.php b/app/Listeners/DeleteServerListener.php new file mode 100644 index 000000000..6a7833504 --- /dev/null +++ b/app/Listeners/DeleteServerListener.php @@ -0,0 +1,64 @@ + + * + * 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\Listeners; + +use Carbon; + +use Pterodactyl\Events\ServerDeleted; +use Illuminate\Foundation\Bus\DispatchesJobs; + +use Pterodactyl\Jobs\SuspendServer; +use Pterodactyl\Jobs\DeleteServer; + +class DeleteServerListener +{ + + use DispatchesJobs; + + /** + * Create the event listener. + * + * @return void + */ + public function __construct() + { + // + } + + /** + * Handle the event. + * + * @param DeleteServerEvent $event + * @return void + */ + public function handle(ServerDeleted $event) + { + $this->dispatch((new SuspendServer($event->server))->onQueue(env('QUEUE_HIGH', 'high'))); + $this->dispatch( + (new DeleteServer($event->server)) + ->delay(Carbon::now()->addMinutes(env('APP_DELETE_MINUTES', 10))) + ->onQueue(env('QUEUE_STANDARD', 'standard')) + ); + } +} diff --git a/app/Models/Server.php b/app/Models/Server.php index d9c2f958a..9948c7a10 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -26,12 +26,15 @@ namespace Pterodactyl\Models; use Auth; use Pterodactyl\Models\Subuser; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Pterodactyl\Exceptions\DisplayException; class Server extends Model { + use SoftDeletes; + /** * The table associated with the model. * @@ -44,17 +47,21 @@ class Server extends Model * * @var array */ - protected $hidden = [ - 'daemonSecret', - 'sftp_password' - ]; + protected $hidden = ['daemonSecret', 'sftp_password']; + + /** + * The attributes that should be mutated to dates. + * + * @var array + */ + protected $dates = ['deleted_at']; /** * Fields that are not mass assignable. * * @var array */ - protected $guarded = ['id', 'installed', 'created_at', 'updated_at']; + protected $guarded = ['id', 'installed', 'created_at', 'updated_at', 'deleted_at']; /** * Cast values to correct type. @@ -92,6 +99,7 @@ class Server extends Model */ public function __construct() { + parent::__construct(); self::$user = Auth::user(); } @@ -181,10 +189,6 @@ class Server extends Model $result = $query->first(); - if (!$result) { - throw new DisplayException('No server was found belonging to this user.'); - } - if(!is_null($result)) { $result->daemonSecret = self::getUserDaemonSecret($result); } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index d706f31ce..7ec0d48ae 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -13,8 +13,8 @@ class EventServiceProvider extends ServiceProvider * @var array */ protected $listen = [ - 'Pterodactyl\Events\SomeEvent' => [ - 'Pterodactyl\Listeners\EventListener', + 'Pterodactyl\Events\ServerDeleted' => [ + 'Pterodactyl\Listeners\DeleteServerListener', ], ]; diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/ServerRepository.php index 979554800..47b1a4a97 100644 --- a/app/Repositories/ServerRepository.php +++ b/app/Repositories/ServerRepository.php @@ -33,6 +33,7 @@ use Pterodactyl\Models; use Pterodactyl\Services\UuidService; use Pterodactyl\Services\DeploymentService; use Pterodactyl\Notifications\ServerCreated; +use Pterodactyl\Events\ServerDeleted; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Exceptions\AccountNotFoundException; @@ -767,11 +768,37 @@ class ServerRepository public function deleteServer($id, $force) { $server = Models\Server::findOrFail($id); - $node = Models\Node::findOrFail($server->node); DB::beginTransaction(); try { - // Delete Allocations + if ($force === 'force' || $force === true) { + $server->installed = 3; + $server->save(); + } + + $server->delete(); + DB::commit(); + + event(new ServerDeleted($server->id)); + } catch (\Exception $ex) { + DB::rollBack(); + throw $ex; + } + } + + public function deleteNow($id, $force = false) { + $server = Models\Server::withTrashed()->findOrFail($id); + $node = Models\Node::findOrFail($server->node); + + // Handle server being restored previously or + // an accidental queue. + if (!$server->trashed()) { + return; + } + + DB::beginTransaction(); + try { + // Unassign Allocations Models\Allocation::where('assigned_to', $server->id)->update([ 'assigned_to' => null ]); @@ -779,20 +806,23 @@ class ServerRepository // Remove Variables Models\ServerVariables::where('server_id', $server->id)->delete(); + // Remove Permissions (Foreign Key requires before Subusers) + Models\Permission::where('server_id', $server->id)->delete(); + // Remove SubUsers Models\Subuser::where('server_id', $server->id)->delete(); - // Remove Permissions - Models\Permission::where('server_id', $server->id)->delete(); - // Remove Downloads Models\Download::where('server', $server->uuid)->delete(); + // Clear Tasks + Models\Task::where('server', $server->id)->delete(); + // Delete Databases - $databases = Models\Database::select('id')->where('server_id', $server->id)->get(); + // This is the one un-recoverable point where + // transactions will not save us. $repository = new DatabaseRepository; - foreach($databases as &$database) { - // Use the repository to drop the database, we don't need to delete here because it is now gone. + foreach(Models\Database::select('id')->where('server_id', $server->id)->get() as &$database) { $repository->drop($database->id); } @@ -804,17 +834,16 @@ class ServerRepository ] ]); - $server->delete(); + $server->forceDelete(); DB::commit(); - return true; } catch (\GuzzleHttp\Exception\TransferException $ex) { - if ($force === 'force') { - $server->delete(); + // Set installed is set to 3 when force deleting. + if ($server->installed === 3 || $force) { + $server->forceDelete(); DB::commit(); - return true; } else { DB::rollBack(); - throw new DisplayException('An error occured while attempting to delete the server on the daemon.', $ex); + throw $ex; } } catch (\Exception $ex) { DB::rollBack(); @@ -822,6 +851,15 @@ class ServerRepository } } + public function cancelDeletion($id) + { + $server = Models\Server::withTrashed()->findOrFail($id); + $server->restore(); + + $server->installed = 1; + $server->save(); + } + public function toggleInstall($id) { $server = Models\Server::findOrFail($id); @@ -837,9 +875,9 @@ class ServerRepository * @param integer $id * @return boolean */ - public function suspend($id) + public function suspend($id, $deleted = false) { - $server = Models\Server::findOrFail($id); + $server = ($deleted) ? Models\Server::withTrashed()->findOrFail($id) : Models\Server::findOrFail($id); $node = Models\Node::findOrFail($server->node); DB::beginTransaction(); diff --git a/resources/views/admin/servers/index.blade.php b/resources/views/admin/servers/index.blade.php index d49d7a585..67a913037 100644 --- a/resources/views/admin/servers/index.blade.php +++ b/resources/views/admin/servers/index.blade.php @@ -49,8 +49,21 @@
@foreach ($servers as $server) -{{ $server->username }}