Skip to content

Commit 973591d

Browse files
committed
Add basic support for backups via the scheduled tasks system
1 parent 7a3263f commit 973591d

File tree

5 files changed

+54
-36
lines changed

5 files changed

+54
-36
lines changed

app/Http/Controllers/Api/Client/Servers/BackupController.php

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
44

5-
use Carbon\Carbon;
65
use Pterodactyl\Models\Backup;
76
use Pterodactyl\Models\Server;
87
use Illuminate\Http\JsonResponse;
@@ -11,7 +10,6 @@
1110
use Pterodactyl\Services\Backups\InitiateBackupService;
1211
use Pterodactyl\Transformers\Api\Client\BackupTransformer;
1312
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
14-
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
1513
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest;
1614
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
1715
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DeleteBackupRequest;
@@ -78,14 +76,6 @@ public function index(GetBackupsRequest $request, Server $server)
7876
*/
7977
public function store(StoreBackupRequest $request, Server $server)
8078
{
81-
$previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10);
82-
if ($previous->count() >= 2) {
83-
throw new TooManyRequestsHttpException(
84-
Carbon::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)),
85-
'Only two backups may be generated within a 10 minute span of time.'
86-
);
87-
}
88-
8979
$backup = $this->initiateBackupService
9080
->setIgnoredFiles(
9181
explode(PHP_EOL, $request->input('ignored') ?? '')

app/Http/Requests/Api/Client/Servers/Schedules/StoreTaskRequest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ public function permission(): string
2424
public function rules(): array
2525
{
2626
return [
27-
'action' => 'required|in:command,power',
28-
'payload' => 'required|string',
29-
'time_offset' => 'required|numeric|min:0|max:900',
27+
'action' => 'required|in:command,power,backup',
28+
'payload' => 'required_unless:action,backup|string',
29+
'time_offset' => 'r=equired|numeric|min:0|max:900',
3030
'sequence_id' => 'sometimes|required|numeric|min:1',
3131
];
3232
}

app/Jobs/Schedule/RunTaskJob.php

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
use Illuminate\Contracts\Queue\ShouldQueue;
1313
use Illuminate\Foundation\Bus\DispatchesJobs;
1414
use Pterodactyl\Repositories\Eloquent\TaskRepository;
15+
use Pterodactyl\Services\Backups\InitiateBackupService;
1516
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
1617
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
1718
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
18-
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
1919
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
2020

2121
class RunTaskJob extends Job implements ShouldQueue
@@ -54,16 +54,16 @@ public function __construct(int $task, int $schedule)
5454
* Run the job and send actions to the daemon running the server.
5555
*
5656
* @param \Pterodactyl\Repositories\Wings\DaemonCommandRepository $commandRepository
57-
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService
57+
* @param \Pterodactyl\Services\Backups\InitiateBackupService $backupService
5858
* @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository
5959
* @param \Pterodactyl\Repositories\Eloquent\TaskRepository $taskRepository
6060
*
6161
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
62-
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
62+
* @throws \Throwable
6363
*/
6464
public function handle(
6565
DaemonCommandRepository $commandRepository,
66-
DaemonKeyProviderService $keyProviderService,
66+
InitiateBackupService $backupService,
6767
DaemonPowerRepository $powerRepository,
6868
TaskRepository $taskRepository
6969
) {
@@ -88,6 +88,9 @@ public function handle(
8888
case 'command':
8989
$commandRepository->setServer($server)->send($task->payload);
9090
break;
91+
case 'backup':
92+
$backupService->setIgnoredFiles(explode(PHP_EOL, $task->payload))->handle($server, null);
93+
break;
9194
default:
9295
throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.');
9396
}

app/Services/Backups/InitiateBackupService.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Pterodactyl\Services\Backups;
44

5+
use Carbon\Carbon;
56
use Ramsey\Uuid\Uuid;
67
use Carbon\CarbonImmutable;
78
use Webmozart\Assert\Assert;
@@ -10,6 +11,7 @@
1011
use Illuminate\Database\ConnectionInterface;
1112
use Pterodactyl\Repositories\Eloquent\BackupRepository;
1213
use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
14+
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
1315

1416
class InitiateBackupService
1517
{
@@ -85,6 +87,14 @@ public function setIgnoredFiles(?array $ignored)
8587
*/
8688
public function handle(Server $server, string $name = null): Backup
8789
{
90+
$previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10);
91+
if ($previous->count() >= 2) {
92+
throw new TooManyRequestsHttpException(
93+
Carbon::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)),
94+
'Only two backups may be generated within a 10 minute span of time.'
95+
);
96+
}
97+
8898
return $this->connection->transaction(function () use ($server, $name) {
8999
/** @var \Pterodactyl\Models\Backup $backup */
90100
$backup = $this->repository->create([

resources/scripts/components/server/schedules/TaskDetailsModal.tsx

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
3636

3737
return (
3838
<Form className={'m-0'}>
39-
<h3 className={'mb-6'}>Edit Task</h3>
39+
<h3 className={'mb-6'}>{isEditingTask ? 'Edit Task' : 'Create Task'}</h3>
4040
<div className={'flex'}>
41-
<div className={'mr-2'}>
41+
<div className={'mr-2 w-1/3'}>
4242
<label className={'input-dark-label'}>Action</label>
43-
<FormikField as={'select'} name={'action'} className={'input-dark'}>
44-
<option value={'command'}>Send command</option>
45-
<option value={'power'}>Send power action</option>
46-
</FormikField>
43+
<FormikFieldWrapper name={'action'}>
44+
<FormikField as={'select'} name={'action'} className={'input-dark'}>
45+
<option value={'command'}>Send command</option>
46+
<option value={'power'}>Send power action</option>
47+
<option value={'backup'}>Create backup</option>
48+
</FormikField>
49+
</FormikFieldWrapper>
4750
</div>
4851
<div className={'flex-1'}>
4952
{action === 'command' ?
@@ -53,17 +56,25 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
5356
description={'The command to send to the server when this task executes.'}
5457
/>
5558
:
56-
<div>
57-
<label className={'input-dark-label'}>Payload</label>
58-
<FormikFieldWrapper name={'payload'}>
59-
<FormikField as={'select'} name={'payload'} className={'input-dark'}>
60-
<option value={'start'}>Start the server</option>
61-
<option value={'restart'}>Restart the server</option>
62-
<option value={'stop'}>Stop the server</option>
63-
<option value={'kill'}>Terminate the server</option>
64-
</FormikField>
65-
</FormikFieldWrapper>
66-
</div>
59+
action === 'power' ?
60+
<div>
61+
<label className={'input-dark-label'}>Payload</label>
62+
<FormikFieldWrapper name={'payload'}>
63+
<FormikField as={'select'} name={'payload'} className={'input-dark'}>
64+
<option value={'start'}>Start the server</option>
65+
<option value={'restart'}>Restart the server</option>
66+
<option value={'stop'}>Stop the server</option>
67+
<option value={'kill'}>Terminate the server</option>
68+
</FormikField>
69+
</FormikFieldWrapper>
70+
</div>
71+
:
72+
<div>
73+
<label className={'input-dark-label'}>Ignored Files</label>
74+
<FormikFieldWrapper name={'payload'}>
75+
<FormikField as={'textarea'} name={'payload'} className={'input-dark h-32'}/>
76+
</FormikFieldWrapper>
77+
</div>
6778
}
6879
</div>
6980
</div>
@@ -120,8 +131,12 @@ export default ({ task, schedule, onDismissed }: Props) => {
120131
timeOffset: task?.timeOffset.toString() || '0',
121132
}}
122133
validationSchema={object().shape({
123-
action: string().required().oneOf([ 'command', 'power' ]),
124-
payload: string().required('A task payload must be provided.'),
134+
action: string().required().oneOf([ 'command', 'power', 'backup' ]),
135+
payload: string().when('action', {
136+
is: v => v !== 'backup',
137+
then: string().required('A task payload must be provided.'),
138+
otherwise: string(),
139+
}),
125140
timeOffset: number().typeError('The time offset must be a valid number between 0 and 900.')
126141
.required('A time offset value must be provided.')
127142
.min(0, 'The time offset must be at least 0 seconds.')

0 commit comments

Comments
 (0)