repository = $repository; $this->connection = $connection; $this->daemonBackupRepository = $daemonBackupRepository; $this->backupManager = $backupManager; $this->deleteBackupService = $deleteBackupService; } /** * Set if the backup should be locked once it is created which will prevent * its deletion by users or automated system processes. * * @return $this */ public function setIsLocked(bool $isLocked): self { $this->isLocked = $isLocked; return $this; } /** * Sets the files to be ignored by this backup. * * @param string[]|null $ignored * * @return $this */ public function setIgnoredFiles(?array $ignored) { if (is_array($ignored)) { foreach ($ignored as $value) { Assert::string($value); } } // Set the ignored files to be any values that are not empty in the array. Don't use // the PHP empty function here incase anything that is "empty" by default (0, false, etc.) // were passed as a file or folder name. $this->ignoredFiles = is_null($ignored) ? [] : array_filter($ignored, function ($value) { return strlen($value) > 0; }); return $this; } /** * Initiates the backup process for a server on Wings. * * @throws \Throwable * @throws \Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException * @throws \Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException */ public function handle(Server $server, string $name = null, bool $override = false): Backup { $limit = config('backups.throttles.limit'); $period = config('backups.throttles.period'); if ($period > 0) { $previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, $period); if ($previous->count() >= $limit) { $message = sprintf('Only %d backups may be generated within a %d second span of time.', $limit, $period); throw new TooManyRequestsHttpException(CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addSeconds($period)), $message); } } // Check if the server has reached or exceeded its backup limit. // completed_at == null will cover any ongoing backups, while is_successful == true will cover any completed backups. $successful = $this->repository->getNonFailedBackups($server); if (!$server->backup_limit || $successful->count() >= $server->backup_limit) { // Do not allow the user to continue if this server is already at its limit and can't override. if (!$override || $server->backup_limit <= 0) { throw new TooManyBackupsException($server->backup_limit); } // Get the oldest backup the server has that is not "locked" (indicating a backup that should // never be automatically purged). If we find a backup we will delete it and then continue with // this process. If no backup is found that can be used an exception is thrown. /** @var \Pterodactyl\Models\Backup|null $oldest */ $oldest = $successful->where('is_locked', false)->orderBy('created_at')->first(); if (!$oldest) { throw new TooManyBackupsException($server->backup_limit); } $this->deleteBackupService->handle($oldest); } return $this->connection->transaction(function () use ($server, $name) { /** @var \Pterodactyl\Models\Backup $backup */ $backup = $this->repository->create([ 'server_id' => $server->id, 'uuid' => Uuid::uuid4()->toString(), 'name' => trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()), 'ignored_files' => array_values($this->ignoredFiles ?? []), 'disk' => $this->backupManager->getDefaultAdapter(), 'is_locked' => $this->isLocked, ], true, true); $this->daemonBackupRepository->setServer($server) ->setBackupAdapter($this->backupManager->getDefaultAdapter()) ->backup($backup); return $backup; }); } public function getNonFailedBackups(Server $server): HasMany { return $server->backups()->where(function ($query) { $query->whereNull('completed_at') ->orWhere('is_successful', true); }); } }