Add base support for AWS/Wings backup adapters
This commit is contained in:
parent
194688389d
commit
b774622faa
9 changed files with 447 additions and 15 deletions
227
app/Extensions/Backups/BackupManager.php
Normal file
227
app/Extensions/Backups/BackupManager.php
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Extensions\Backups;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Webmozart\Assert\Assert;
|
||||
use InvalidArgumentException;
|
||||
use Aws\S3\S3MultiRegionClient;
|
||||
use League\Flysystem\AdapterInterface;
|
||||
use League\Flysystem\AwsS3v3\AwsS3Adapter;
|
||||
use League\Flysystem\Memory\MemoryAdapter;
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
|
||||
class BackupManager
|
||||
{
|
||||
/**
|
||||
* @var \Illuminate\Foundation\Application
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Contracts\Config\Repository
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* The array of resolved backup drivers.
|
||||
*
|
||||
* @var \League\Flysystem\AdapterInterface[]
|
||||
*/
|
||||
protected $adapters = [];
|
||||
|
||||
/**
|
||||
* The registered custom driver creators.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $customCreators;
|
||||
|
||||
/**
|
||||
* BackupManager constructor.
|
||||
*
|
||||
* @param \Illuminate\Foundation\Application $app
|
||||
*/
|
||||
public function __construct($app)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->config = $app->make(Repository::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a backup adapter instance.
|
||||
*
|
||||
* @param string|null $name
|
||||
* @return \League\Flysystem\AdapterInterface
|
||||
*/
|
||||
public function adapter(string $name = null)
|
||||
{
|
||||
return $this->get($name ?: $this->getDefaultAdapter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the given backup adapter instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param \League\Flysystem\AdapterInterface $disk
|
||||
* @return $this
|
||||
*/
|
||||
public function set(string $name, $disk)
|
||||
{
|
||||
$this->adapters[$name] = $disk;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a backup adapter.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \League\Flysystem\AdapterInterface
|
||||
*/
|
||||
protected function get(string $name)
|
||||
{
|
||||
return $this->adapters[$name] = $this->resolve($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given backup disk.
|
||||
*
|
||||
* @param string $name
|
||||
* @return \League\Flysystem\AdapterInterface
|
||||
*/
|
||||
protected function resolve(string $name)
|
||||
{
|
||||
$config = $this->getConfig($name);
|
||||
|
||||
if (empty($config['adapter'])) {
|
||||
throw new InvalidArgumentException(
|
||||
"Backup disk [{$name}] does not have a configured adapter."
|
||||
);
|
||||
}
|
||||
|
||||
$adapter = $config['adapter'];
|
||||
|
||||
if (isset($this->customCreators[$name])) {
|
||||
return $this->callCustomCreator($config);
|
||||
}
|
||||
|
||||
$adapterMethod = 'create' . Str::studly($adapter) . 'Adapter';
|
||||
if (method_exists($this, $adapterMethod)) {
|
||||
$instance = $this->{$adapterMethod}($config);
|
||||
|
||||
Assert::isInstanceOf($instance, AdapterInterface::class);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Adapter [{$adapter}] is not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a custom creator for a given adapter type.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \League\Flysystem\AdapterInterface
|
||||
*/
|
||||
protected function callCustomCreator(array $config)
|
||||
{
|
||||
$adapter = $this->customCreators[$config['adapter']]($this->app, $config);
|
||||
|
||||
Assert::isInstanceOf($adapter, AdapterInterface::class);
|
||||
|
||||
return $adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new wings adapter.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \League\Flysystem\AdapterInterface
|
||||
*/
|
||||
public function createWingsAdapter(array $config)
|
||||
{
|
||||
return new MemoryAdapter(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new S3 adapter.
|
||||
*
|
||||
* @param array $config
|
||||
* @return \League\Flysystem\AdapterInterface
|
||||
*/
|
||||
public function createS3Adapter(array $config)
|
||||
{
|
||||
$config['version'] = 'latest';
|
||||
|
||||
if (! empty($config['key']) && ! empty($config['secret'])) {
|
||||
$config['credentials'] = Arr::only($config, ['key', 'secret', 'token']);
|
||||
}
|
||||
|
||||
$client = new S3MultiRegionClient($config);
|
||||
|
||||
return new AwsS3Adapter($client, $config['bucket'], $config['prefix'] ?? '', $config['options'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configuration associated with a given backup type.
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
protected function getConfig(string $name)
|
||||
{
|
||||
return $this->config->get("backups.disks.{$name}") ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default backup driver name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultAdapter()
|
||||
{
|
||||
return $this->config->get('backups.default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default session driver name.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
public function setDefaultAdapter(string $name)
|
||||
{
|
||||
$this->config->set('backups.default', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset the given adapter instances.
|
||||
*
|
||||
* @param string|string[] $adapter
|
||||
* @return $this
|
||||
*/
|
||||
public function forget($adapter)
|
||||
{
|
||||
foreach ((array) $adapter as $adapterName) {
|
||||
unset($this->adapters[$adapter]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom adapter creator closure.
|
||||
*
|
||||
* @param string $adapter
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function extend(string $adapter, Closure $callback)
|
||||
{
|
||||
$this->customCreators[$adapter] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -26,8 +26,8 @@ class Backup extends Model
|
|||
|
||||
const RESOURCE_NAME = 'backup';
|
||||
|
||||
const DISK_LOCAL = 'local';
|
||||
const DISK_AWS_S3 = 's3';
|
||||
const ADAPTER_WINGS = 'wings';
|
||||
const ADAPTER_AWS_S3 = 's3';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
|
|
28
app/Providers/BackupsServiceProvider.php
Normal file
28
app/Providers/BackupsServiceProvider.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Pterodactyl\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Pterodactyl\Extensions\Backups\BackupManager;
|
||||
use Illuminate\Contracts\Support\DeferrableProvider;
|
||||
|
||||
class BackupsServiceProvider extends ServiceProvider implements DeferrableProvider
|
||||
{
|
||||
/**
|
||||
* Register the S3 backup disk.
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->singleton(BackupManager::class, function ($app) {
|
||||
return new BackupManager($app);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function provides()
|
||||
{
|
||||
return [BackupManager::class];
|
||||
}
|
||||
}
|
|
@ -11,25 +11,48 @@ use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
|
|||
|
||||
class DaemonBackupRepository extends DaemonRepository
|
||||
{
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $adapter;
|
||||
|
||||
/**
|
||||
* Sets the backup adapter for this execution instance.
|
||||
*
|
||||
* @param string $adapter
|
||||
* @return $this
|
||||
*/
|
||||
public function setBackupAdapter(string $adapter)
|
||||
{
|
||||
$this->adapter = $adapter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the remote Daemon to begin generating a backup for the server.
|
||||
*
|
||||
* @param \Pterodactyl\Models\Backup $backup
|
||||
* @param string|null $presignedUrl
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*
|
||||
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
|
||||
*/
|
||||
public function backup(Backup $backup): ResponseInterface
|
||||
public function backup(Backup $backup, string $presignedUrl = null): ResponseInterface
|
||||
{
|
||||
Assert::isInstanceOf($this->server, Server::class);
|
||||
|
||||
$this->app->make('config')->get();
|
||||
|
||||
try {
|
||||
return $this->getHttpClient()->post(
|
||||
sprintf('/api/servers/%s/backup', $this->server->uuid),
|
||||
[
|
||||
'json' => [
|
||||
'adapter' => $this->adapter ?? config('backups.default'),
|
||||
'uuid' => $backup->uuid,
|
||||
'ignored_files' => $backup->ignored_files,
|
||||
'presigned_url' => $presignedUrl,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
|
|
@ -8,7 +8,9 @@ use Carbon\CarbonImmutable;
|
|||
use Webmozart\Assert\Assert;
|
||||
use Pterodactyl\Models\Backup;
|
||||
use Pterodactyl\Models\Server;
|
||||
use League\Flysystem\AwsS3v3\AwsS3Adapter;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Pterodactyl\Extensions\Backups\BackupManager;
|
||||
use Pterodactyl\Repositories\Eloquent\BackupRepository;
|
||||
use Pterodactyl\Repositories\Wings\DaemonBackupRepository;
|
||||
use Pterodactyl\Exceptions\Service\Backup\TooManyBackupsException;
|
||||
|
@ -36,21 +38,29 @@ class InitiateBackupService
|
|||
*/
|
||||
private $daemonBackupRepository;
|
||||
|
||||
/**
|
||||
* @var \Pterodactyl\Extensions\Backups\BackupManager
|
||||
*/
|
||||
private $backupManager;
|
||||
|
||||
/**
|
||||
* InitiateBackupService constructor.
|
||||
*
|
||||
* @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository
|
||||
* @param \Illuminate\Database\ConnectionInterface $connection
|
||||
* @param \Pterodactyl\Repositories\Wings\DaemonBackupRepository $daemonBackupRepository
|
||||
* @param \Pterodactyl\Extensions\Backups\BackupManager $backupManager
|
||||
*/
|
||||
public function __construct(
|
||||
BackupRepository $repository,
|
||||
ConnectionInterface $connection,
|
||||
DaemonBackupRepository $daemonBackupRepository
|
||||
DaemonBackupRepository $daemonBackupRepository,
|
||||
BackupManager $backupManager
|
||||
) {
|
||||
$this->repository = $repository;
|
||||
$this->connection = $connection;
|
||||
$this->daemonBackupRepository = $daemonBackupRepository;
|
||||
$this->backupManager = $backupManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,12 +120,45 @@ class InitiateBackupService
|
|||
'uuid' => Uuid::uuid4()->toString(),
|
||||
'name' => trim($name) ?: sprintf('Backup at %s', CarbonImmutable::now()->toDateTimeString()),
|
||||
'ignored_files' => is_array($this->ignoredFiles) ? array_values($this->ignoredFiles) : [],
|
||||
'disk' => 'local',
|
||||
'disk' => $this->backupManager->getDefaultAdapter(),
|
||||
], true, true);
|
||||
|
||||
$this->daemonBackupRepository->setServer($server)->backup($backup);
|
||||
$url = $this->getS3PresignedUrl(sprintf('%s/%s.tar.gz', $server->uuid, $backup->uuid));
|
||||
|
||||
$this->daemonBackupRepository->setServer($server)
|
||||
->setBackupAdapter($this->backupManager->getDefaultAdapter())
|
||||
->backup($backup, $url);
|
||||
|
||||
return $backup;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a presigned URL for the wings daemon to upload the completed archive
|
||||
* to. We use a 30 minute expiration on these URLs to avoid issues with large backups
|
||||
* that may take some time to complete.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getS3PresignedUrl(string $path)
|
||||
{
|
||||
$adapter = $this->backupManager->adapter();
|
||||
if (! $adapter instanceof AwsS3Adapter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$client = $adapter->getClient();
|
||||
|
||||
$request = $client->createPresignedRequest(
|
||||
$client->getCommand('PutObject', [
|
||||
'Bucket' => $adapter->getBucket(),
|
||||
'Key' => $path,
|
||||
'ContentType' => 'binary/octet-stream',
|
||||
]),
|
||||
CarbonImmutable::now()->addMinutes(30)
|
||||
);
|
||||
|
||||
return $request->getUri()->__toString();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue