Path: blob/1.0-develop/app/Http/Controllers/Api/Client/Servers/BackupController.php
10280 views
<?php12namespace Pterodactyl\Http\Controllers\Api\Client\Servers;34use Illuminate\Http\Request;5use Pterodactyl\Models\Backup;6use Pterodactyl\Models\Server;7use Illuminate\Http\JsonResponse;8use Pterodactyl\Facades\Activity;9use Pterodactyl\Models\Permission;10use Illuminate\Auth\Access\AuthorizationException;11use Pterodactyl\Services\Backups\DeleteBackupService;12use Pterodactyl\Services\Backups\DownloadLinkService;13use Pterodactyl\Repositories\Eloquent\BackupRepository;14use Pterodactyl\Services\Backups\InitiateBackupService;15use Pterodactyl\Repositories\Wings\DaemonBackupRepository;16use Pterodactyl\Transformers\Api\Client\BackupTransformer;17use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;18use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;19use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;20use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\RestoreBackupRequest;2122class BackupController extends ClientApiController23{24/**25* BackupController constructor.26*/27public function __construct(28private DaemonBackupRepository $daemonRepository,29private DeleteBackupService $deleteBackupService,30private InitiateBackupService $initiateBackupService,31private DownloadLinkService $downloadLinkService,32private BackupRepository $repository,33) {34parent::__construct();35}3637/**38* Returns all the backups for a given server instance in a paginated39* result set.40*41* @throws AuthorizationException42*/43public function index(Request $request, Server $server): array44{45if (!$request->user()->can(Permission::ACTION_BACKUP_READ, $server)) {46throw new AuthorizationException();47}4849$limit = min($request->query('per_page') ?? 20, 50);5051return $this->fractal->collection($server->backups()->paginate($limit))52->transformWith($this->getTransformer(BackupTransformer::class))53->addMeta([54'backup_count' => $this->repository->getNonFailedBackups($server)->count(),55])56->toArray();57}5859/**60* Starts the backup process for a server.61*62* @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation63* @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified64* @throws \Throwable65*/66public function store(StoreBackupRequest $request, Server $server): array67{68$action = $this->initiateBackupService69->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? ''));7071// Only set the lock status if the user even has permission to delete backups,72// otherwise ignore this status. This gets a little funky since it isn't clear73// how best to allow a user to create a backup that is locked without also preventing74// them from just filling up a server with backups that can never be deleted?75if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {76$action->setIsLocked($request->boolean('is_locked'));77}7879$backup = Activity::event('server:backup.start')->transaction(function ($log) use ($action, $server, $request) {80$server->backups()->lockForUpdate();8182$backup = $action->handle($server, $request->input('name'));8384$log->subject($backup)->property([85'name' => $backup->name,86'locked' => $request->boolean('is_locked'),87]);8889return $backup;90});9192return $this->fractal->item($backup)93->transformWith($this->getTransformer(BackupTransformer::class))94->toArray();95}9697/**98* Toggles the lock status of a given backup for a server.99*100* @throws \Throwable101* @throws AuthorizationException102*/103public function toggleLock(Request $request, Server $server, Backup $backup): array104{105if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {106throw new AuthorizationException();107}108109$action = $backup->is_locked ? 'server:backup.unlock' : 'server:backup.lock';110111$backup->update(['is_locked' => !$backup->is_locked]);112113Activity::event($action)->subject($backup)->property('name', $backup->name)->log();114115return $this->fractal->item($backup)116->transformWith($this->getTransformer(BackupTransformer::class))117->toArray();118}119120/**121* Returns information about a single backup.122*123* @throws AuthorizationException124*/125public function view(Request $request, Server $server, Backup $backup): array126{127if (!$request->user()->can(Permission::ACTION_BACKUP_READ, $server)) {128throw new AuthorizationException();129}130131return $this->fractal->item($backup)132->transformWith($this->getTransformer(BackupTransformer::class))133->toArray();134}135136/**137* Deletes a backup from the panel as well as the remote source where it is currently138* being stored.139*140* @throws \Throwable141*/142public function delete(Request $request, Server $server, Backup $backup): JsonResponse143{144if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {145throw new AuthorizationException();146}147148$this->deleteBackupService->handle($backup);149150Activity::event('server:backup.delete')151->subject($backup)152->property(['name' => $backup->name, 'failed' => !$backup->is_successful])153->log();154155return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);156}157158/**159* Download the backup for a given server instance. For daemon local files, the file160* will be streamed back through the Panel. For AWS S3 files, a signed URL will be generated161* which the user is redirected to.162*163* @throws \Throwable164* @throws AuthorizationException165*/166public function download(Request $request, Server $server, Backup $backup): JsonResponse167{168if (!$request->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server)) {169throw new AuthorizationException();170}171172if ($backup->disk !== Backup::ADAPTER_AWS_S3 && $backup->disk !== Backup::ADAPTER_WINGS) {173throw new BadRequestHttpException('The backup requested references an unknown disk driver type and cannot be downloaded.');174}175176$url = $this->downloadLinkService->handle($backup, $request->user());177178Activity::event('server:backup.download')->subject($backup)->property('name', $backup->name)->log();179180return new JsonResponse([181'object' => 'signed_url',182'attributes' => ['url' => $url],183]);184}185186/**187* Handles restoring a backup by making a request to the Wings instance telling it188* to begin the process of finding (or downloading) the backup and unpacking it189* over the server files.190*191* If the "truncate" flag is passed through in this request then all the192* files that currently exist on the server will be deleted before restoring.193* Otherwise, the archive will simply be unpacked over the existing files.194*195* @throws \Throwable196*/197public function restore(RestoreBackupRequest $request, Server $server, Backup $backup): JsonResponse198{199// Cannot restore a backup unless a server is fully installed and not currently200// processing a different backup restoration request.201if (!is_null($server->status)) {202throw new BadRequestHttpException('This server is not currently in a state that allows for a backup to be restored.');203}204205if (!$backup->is_successful && is_null($backup->completed_at)) {206throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.');207}208209$log = Activity::event('server:backup.restore')210->subject($backup)211->property(['name' => $backup->name, 'truncate' => $request->input('truncate')]);212213$log->transaction(function () use ($backup, $server, $request) {214// If the backup is for an S3 file we need to generate a unique Download link for215// it that will allow Wings to actually access the file.216if ($backup->disk === Backup::ADAPTER_AWS_S3) {217$url = $this->downloadLinkService->handle($backup, $request->user());218}219220// Update the status right away for the server so that we know not to allow certain221// actions against it via the Panel API.222$server->update(['status' => Server::STATUS_RESTORING_BACKUP]);223224$this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate'));225});226227return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);228}229}230231232