Add basic support for backups via the scheduled tasks system
This commit is contained in:
parent
7a3263f57b
commit
973591d86e
5 changed files with 54 additions and 36 deletions
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Pterodactyl\Models\Backup;
|
use Pterodactyl\Models\Backup;
|
||||||
use Pterodactyl\Models\Server;
|
use Pterodactyl\Models\Server;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
@ -11,7 +10,6 @@ use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
||||||
use Pterodactyl\Services\Backups\InitiateBackupService;
|
use Pterodactyl\Services\Backups\InitiateBackupService;
|
||||||
use Pterodactyl\Transformers\Api\Client\BackupTransformer;
|
use Pterodactyl\Transformers\Api\Client\BackupTransformer;
|
||||||
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
use Pterodactyl\Http\Controllers\Api\Client\ClientApiController;
|
||||||
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\GetBackupsRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\StoreBackupRequest;
|
||||||
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DeleteBackupRequest;
|
use Pterodactyl\Http\Requests\Api\Client\Servers\Backups\DeleteBackupRequest;
|
||||||
|
@ -78,14 +76,6 @@ class BackupController extends ClientApiController
|
||||||
*/
|
*/
|
||||||
public function store(StoreBackupRequest $request, Server $server)
|
public function store(StoreBackupRequest $request, Server $server)
|
||||||
{
|
{
|
||||||
$previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10);
|
|
||||||
if ($previous->count() >= 2) {
|
|
||||||
throw new TooManyRequestsHttpException(
|
|
||||||
Carbon::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)),
|
|
||||||
'Only two backups may be generated within a 10 minute span of time.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$backup = $this->initiateBackupService
|
$backup = $this->initiateBackupService
|
||||||
->setIgnoredFiles(
|
->setIgnoredFiles(
|
||||||
explode(PHP_EOL, $request->input('ignored') ?? '')
|
explode(PHP_EOL, $request->input('ignored') ?? '')
|
||||||
|
|
|
@ -24,9 +24,9 @@ class StoreTaskRequest extends ViewScheduleRequest
|
||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'action' => 'required|in:command,power',
|
'action' => 'required|in:command,power,backup',
|
||||||
'payload' => 'required|string',
|
'payload' => 'required_unless:action,backup|string',
|
||||||
'time_offset' => 'required|numeric|min:0|max:900',
|
'time_offset' => 'r=equired|numeric|min:0|max:900',
|
||||||
'sequence_id' => 'sometimes|required|numeric|min:1',
|
'sequence_id' => 'sometimes|required|numeric|min:1',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||||
use Pterodactyl\Repositories\Eloquent\TaskRepository;
|
use Pterodactyl\Repositories\Eloquent\TaskRepository;
|
||||||
|
use Pterodactyl\Services\Backups\InitiateBackupService;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
|
use Pterodactyl\Repositories\Wings\DaemonPowerRepository;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
|
use Pterodactyl\Repositories\Wings\DaemonCommandRepository;
|
||||||
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
|
||||||
use Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService;
|
|
||||||
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
|
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
|
||||||
|
|
||||||
class RunTaskJob extends Job implements ShouldQueue
|
class RunTaskJob extends Job implements ShouldQueue
|
||||||
|
@ -54,16 +54,16 @@ class RunTaskJob extends Job implements ShouldQueue
|
||||||
* Run the job and send actions to the daemon running the server.
|
* Run the job and send actions to the daemon running the server.
|
||||||
*
|
*
|
||||||
* @param \Pterodactyl\Repositories\Wings\DaemonCommandRepository $commandRepository
|
* @param \Pterodactyl\Repositories\Wings\DaemonCommandRepository $commandRepository
|
||||||
* @param \Pterodactyl\Services\DaemonKeys\DaemonKeyProviderService $keyProviderService
|
* @param \Pterodactyl\Services\Backups\InitiateBackupService $backupService
|
||||||
* @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository
|
* @param \Pterodactyl\Repositories\Wings\DaemonPowerRepository $powerRepository
|
||||||
* @param \Pterodactyl\Repositories\Eloquent\TaskRepository $taskRepository
|
* @param \Pterodactyl\Repositories\Eloquent\TaskRepository $taskRepository
|
||||||
*
|
*
|
||||||
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
|
||||||
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
|
* @throws \Throwable
|
||||||
*/
|
*/
|
||||||
public function handle(
|
public function handle(
|
||||||
DaemonCommandRepository $commandRepository,
|
DaemonCommandRepository $commandRepository,
|
||||||
DaemonKeyProviderService $keyProviderService,
|
InitiateBackupService $backupService,
|
||||||
DaemonPowerRepository $powerRepository,
|
DaemonPowerRepository $powerRepository,
|
||||||
TaskRepository $taskRepository
|
TaskRepository $taskRepository
|
||||||
) {
|
) {
|
||||||
|
@ -88,6 +88,9 @@ class RunTaskJob extends Job implements ShouldQueue
|
||||||
case 'command':
|
case 'command':
|
||||||
$commandRepository->setServer($server)->send($task->payload);
|
$commandRepository->setServer($server)->send($task->payload);
|
||||||
break;
|
break;
|
||||||
|
case 'backup':
|
||||||
|
$backupService->setIgnoredFiles(explode(PHP_EOL, $task->payload))->handle($server, null);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.');
|
throw new InvalidArgumentException('Cannot run a task that points to a non-existent action.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Pterodactyl\Services\Backups;
|
namespace Pterodactyl\Services\Backups;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Webmozart\Assert\Assert;
|
use Webmozart\Assert\Assert;
|
||||||
|
@ -10,6 +11,7 @@ use Pterodactyl\Models\Server;
|
||||||
use Illuminate\Database\ConnectionInterface;
|
use Illuminate\Database\ConnectionInterface;
|
||||||
use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
||||||
use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
|
use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||||
|
|
||||||
class InitiateBackupService
|
class InitiateBackupService
|
||||||
{
|
{
|
||||||
|
@ -85,6 +87,14 @@ class InitiateBackupService
|
||||||
*/
|
*/
|
||||||
public function handle(Server $server, string $name = null): Backup
|
public function handle(Server $server, string $name = null): Backup
|
||||||
{
|
{
|
||||||
|
$previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10);
|
||||||
|
if ($previous->count() >= 2) {
|
||||||
|
throw new TooManyRequestsHttpException(
|
||||||
|
Carbon::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)),
|
||||||
|
'Only two backups may be generated within a 10 minute span of time.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->connection->transaction(function () use ($server, $name) {
|
return $this->connection->transaction(function () use ($server, $name) {
|
||||||
/** @var \Pterodactyl\Models\Backup $backup */
|
/** @var \Pterodactyl\Models\Backup $backup */
|
||||||
$backup = $this->repository->create([
|
$backup = $this->repository->create([
|
||||||
|
|
|
@ -36,14 +36,17 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form className={'m-0'}>
|
<Form className={'m-0'}>
|
||||||
<h3 className={'mb-6'}>Edit Task</h3>
|
<h3 className={'mb-6'}>{isEditingTask ? 'Edit Task' : 'Create Task'}</h3>
|
||||||
<div className={'flex'}>
|
<div className={'flex'}>
|
||||||
<div className={'mr-2'}>
|
<div className={'mr-2 w-1/3'}>
|
||||||
<label className={'input-dark-label'}>Action</label>
|
<label className={'input-dark-label'}>Action</label>
|
||||||
<FormikField as={'select'} name={'action'} className={'input-dark'}>
|
<FormikFieldWrapper name={'action'}>
|
||||||
<option value={'command'}>Send command</option>
|
<FormikField as={'select'} name={'action'} className={'input-dark'}>
|
||||||
<option value={'power'}>Send power action</option>
|
<option value={'command'}>Send command</option>
|
||||||
</FormikField>
|
<option value={'power'}>Send power action</option>
|
||||||
|
<option value={'backup'}>Create backup</option>
|
||||||
|
</FormikField>
|
||||||
|
</FormikFieldWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1'}>
|
<div className={'flex-1'}>
|
||||||
{action === 'command' ?
|
{action === 'command' ?
|
||||||
|
@ -53,17 +56,25 @@ const TaskDetailsForm = ({ isEditingTask }: { isEditingTask: boolean }) => {
|
||||||
description={'The command to send to the server when this task executes.'}
|
description={'The command to send to the server when this task executes.'}
|
||||||
/>
|
/>
|
||||||
:
|
:
|
||||||
<div>
|
action === 'power' ?
|
||||||
<label className={'input-dark-label'}>Payload</label>
|
<div>
|
||||||
<FormikFieldWrapper name={'payload'}>
|
<label className={'input-dark-label'}>Payload</label>
|
||||||
<FormikField as={'select'} name={'payload'} className={'input-dark'}>
|
<FormikFieldWrapper name={'payload'}>
|
||||||
<option value={'start'}>Start the server</option>
|
<FormikField as={'select'} name={'payload'} className={'input-dark'}>
|
||||||
<option value={'restart'}>Restart the server</option>
|
<option value={'start'}>Start the server</option>
|
||||||
<option value={'stop'}>Stop the server</option>
|
<option value={'restart'}>Restart the server</option>
|
||||||
<option value={'kill'}>Terminate the server</option>
|
<option value={'stop'}>Stop the server</option>
|
||||||
</FormikField>
|
<option value={'kill'}>Terminate the server</option>
|
||||||
</FormikFieldWrapper>
|
</FormikField>
|
||||||
</div>
|
</FormikFieldWrapper>
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div>
|
||||||
|
<label className={'input-dark-label'}>Ignored Files</label>
|
||||||
|
<FormikFieldWrapper name={'payload'}>
|
||||||
|
<FormikField as={'textarea'} name={'payload'} className={'input-dark h-32'}/>
|
||||||
|
</FormikFieldWrapper>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,8 +131,12 @@ export default ({ task, schedule, onDismissed }: Props) => {
|
||||||
timeOffset: task?.timeOffset.toString() || '0',
|
timeOffset: task?.timeOffset.toString() || '0',
|
||||||
}}
|
}}
|
||||||
validationSchema={object().shape({
|
validationSchema={object().shape({
|
||||||
action: string().required().oneOf([ 'command', 'power' ]),
|
action: string().required().oneOf([ 'command', 'power', 'backup' ]),
|
||||||
payload: string().required('A task payload must be provided.'),
|
payload: string().when('action', {
|
||||||
|
is: v => v !== 'backup',
|
||||||
|
then: string().required('A task payload must be provided.'),
|
||||||
|
otherwise: string(),
|
||||||
|
}),
|
||||||
timeOffset: number().typeError('The time offset must be a valid number between 0 and 900.')
|
timeOffset: number().typeError('The time offset must be a valid number between 0 and 900.')
|
||||||
.required('A time offset value must be provided.')
|
.required('A time offset value must be provided.')
|
||||||
.min(0, 'The time offset must be at least 0 seconds.')
|
.min(0, 'The time offset must be at least 0 seconds.')
|
||||||
|
|
Loading…
Reference in a new issue