Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/app/Http/Controllers/Api/Client/Servers/BackupController.php
10280 views
1
<?php
2
3
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
4
5
use Illuminate\Http\Request;
6
use Pterodactyl\Models\Backup;
7
use Pterodactyl\Models\Server;
8
use Illuminate\Http\JsonResponse;
9
use Pterodactyl\Facades\Activity;
10
use Pterodactyl\Models\Permission;
11
use Illuminate\Auth\Access\AuthorizationException;
12
use Pterodactyl\Services\Backups\DeleteBackupService;
13
use Pterodactyl\Services\Backups\DownloadLinkService;
14
use Pterodactyl\Repositories\Eloquent\BackupRepository;
15
use Pterodactyl\Services\Backups\InitiateBackupService;
16
use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
17
use Pterodactyl\Transformers\Api\Client\BackupTransformer;
18
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
19
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
20
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
21
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\RestoreBackupRequest;
22
23
class BackupController extends ClientApiController
24
{
25
/**
26
* BackupController constructor.
27
*/
28
public function __construct(
29
private DaemonBackupRepository $daemonRepository,
30
private DeleteBackupService $deleteBackupService,
31
private InitiateBackupService $initiateBackupService,
32
private DownloadLinkService $downloadLinkService,
33
private BackupRepository $repository,
34
) {
35
parent::__construct();
36
}
37
38
/**
39
* Returns all the backups for a given server instance in a paginated
40
* result set.
41
*
42
* @throws AuthorizationException
43
*/
44
public function index(Request $request, Server $server): array
45
{
46
if (!$request->user()->can(Permission::ACTION_BACKUP_READ, $server)) {
47
throw new AuthorizationException();
48
}
49
50
$limit = min($request->query('per_page') ?? 20, 50);
51
52
return $this->fractal->collection($server->backups()->paginate($limit))
53
->transformWith($this->getTransformer(BackupTransformer::class))
54
->addMeta([
55
'backup_count' => $this->repository->getNonFailedBackups($server)->count(),
56
])
57
->toArray();
58
}
59
60
/**
61
* Starts the backup process for a server.
62
*
63
* @throws \Spatie\Fractalistic\Exceptions\InvalidTransformation
64
* @throws \Spatie\Fractalistic\Exceptions\NoTransformerSpecified
65
* @throws \Throwable
66
*/
67
public function store(StoreBackupRequest $request, Server $server): array
68
{
69
$action = $this->initiateBackupService
70
->setIgnoredFiles(explode(PHP_EOL, $request->input('ignored') ?? ''));
71
72
// Only set the lock status if the user even has permission to delete backups,
73
// otherwise ignore this status. This gets a little funky since it isn't clear
74
// how best to allow a user to create a backup that is locked without also preventing
75
// them from just filling up a server with backups that can never be deleted?
76
if ($request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {
77
$action->setIsLocked($request->boolean('is_locked'));
78
}
79
80
$backup = Activity::event('server:backup.start')->transaction(function ($log) use ($action, $server, $request) {
81
$server->backups()->lockForUpdate();
82
83
$backup = $action->handle($server, $request->input('name'));
84
85
$log->subject($backup)->property([
86
'name' => $backup->name,
87
'locked' => $request->boolean('is_locked'),
88
]);
89
90
return $backup;
91
});
92
93
return $this->fractal->item($backup)
94
->transformWith($this->getTransformer(BackupTransformer::class))
95
->toArray();
96
}
97
98
/**
99
* Toggles the lock status of a given backup for a server.
100
*
101
* @throws \Throwable
102
* @throws AuthorizationException
103
*/
104
public function toggleLock(Request $request, Server $server, Backup $backup): array
105
{
106
if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {
107
throw new AuthorizationException();
108
}
109
110
$action = $backup->is_locked ? 'server:backup.unlock' : 'server:backup.lock';
111
112
$backup->update(['is_locked' => !$backup->is_locked]);
113
114
Activity::event($action)->subject($backup)->property('name', $backup->name)->log();
115
116
return $this->fractal->item($backup)
117
->transformWith($this->getTransformer(BackupTransformer::class))
118
->toArray();
119
}
120
121
/**
122
* Returns information about a single backup.
123
*
124
* @throws AuthorizationException
125
*/
126
public function view(Request $request, Server $server, Backup $backup): array
127
{
128
if (!$request->user()->can(Permission::ACTION_BACKUP_READ, $server)) {
129
throw new AuthorizationException();
130
}
131
132
return $this->fractal->item($backup)
133
->transformWith($this->getTransformer(BackupTransformer::class))
134
->toArray();
135
}
136
137
/**
138
* Deletes a backup from the panel as well as the remote source where it is currently
139
* being stored.
140
*
141
* @throws \Throwable
142
*/
143
public function delete(Request $request, Server $server, Backup $backup): JsonResponse
144
{
145
if (!$request->user()->can(Permission::ACTION_BACKUP_DELETE, $server)) {
146
throw new AuthorizationException();
147
}
148
149
$this->deleteBackupService->handle($backup);
150
151
Activity::event('server:backup.delete')
152
->subject($backup)
153
->property(['name' => $backup->name, 'failed' => !$backup->is_successful])
154
->log();
155
156
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
157
}
158
159
/**
160
* Download the backup for a given server instance. For daemon local files, the file
161
* will be streamed back through the Panel. For AWS S3 files, a signed URL will be generated
162
* which the user is redirected to.
163
*
164
* @throws \Throwable
165
* @throws AuthorizationException
166
*/
167
public function download(Request $request, Server $server, Backup $backup): JsonResponse
168
{
169
if (!$request->user()->can(Permission::ACTION_BACKUP_DOWNLOAD, $server)) {
170
throw new AuthorizationException();
171
}
172
173
if ($backup->disk !== Backup::ADAPTER_AWS_S3 && $backup->disk !== Backup::ADAPTER_WINGS) {
174
throw new BadRequestHttpException('The backup requested references an unknown disk driver type and cannot be downloaded.');
175
}
176
177
$url = $this->downloadLinkService->handle($backup, $request->user());
178
179
Activity::event('server:backup.download')->subject($backup)->property('name', $backup->name)->log();
180
181
return new JsonResponse([
182
'object' => 'signed_url',
183
'attributes' => ['url' => $url],
184
]);
185
}
186
187
/**
188
* Handles restoring a backup by making a request to the Wings instance telling it
189
* to begin the process of finding (or downloading) the backup and unpacking it
190
* over the server files.
191
*
192
* If the "truncate" flag is passed through in this request then all the
193
* files that currently exist on the server will be deleted before restoring.
194
* Otherwise, the archive will simply be unpacked over the existing files.
195
*
196
* @throws \Throwable
197
*/
198
public function restore(RestoreBackupRequest $request, Server $server, Backup $backup): JsonResponse
199
{
200
// Cannot restore a backup unless a server is fully installed and not currently
201
// processing a different backup restoration request.
202
if (!is_null($server->status)) {
203
throw new BadRequestHttpException('This server is not currently in a state that allows for a backup to be restored.');
204
}
205
206
if (!$backup->is_successful && is_null($backup->completed_at)) {
207
throw new BadRequestHttpException('This backup cannot be restored at this time: not completed or failed.');
208
}
209
210
$log = Activity::event('server:backup.restore')
211
->subject($backup)
212
->property(['name' => $backup->name, 'truncate' => $request->input('truncate')]);
213
214
$log->transaction(function () use ($backup, $server, $request) {
215
// If the backup is for an S3 file we need to generate a unique Download link for
216
// it that will allow Wings to actually access the file.
217
if ($backup->disk === Backup::ADAPTER_AWS_S3) {
218
$url = $this->downloadLinkService->handle($backup, $request->user());
219
}
220
221
// Update the status right away for the server so that we know not to allow certain
222
// actions against it via the Panel API.
223
$server->update(['status' => Server::STATUS_RESTORING_BACKUP]);
224
225
$this->daemonRepository->setServer($server)->restore($backup, $url ?? null, $request->input('truncate'));
226
});
227
228
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
229
}
230
}
231
232