From 15d38ce823ece395f18d867065665731b46a838f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 30 Sep 2017 19:23:44 -0500 Subject: [PATCH] Add ability to switch between new and existing daemon --- app/Providers/RepositoryServiceProvider.php | 18 ++- app/Repositories/Wings/BaseRepository.php | 146 ++++++++++++++++++ app/Repositories/Wings/CommandRepository.php | 30 ++++ .../Wings/ConfigurationRepository.php | 24 +++ app/Repositories/Wings/FileRepository.php | 114 ++++++++++++++ app/Repositories/Wings/PowerRepository.php | 40 +++++ app/Repositories/Wings/ServerRepository.php | 88 +++++++++++ config/pterodactyl.php | 11 ++ 8 files changed, 466 insertions(+), 5 deletions(-) create mode 100644 app/Repositories/Wings/BaseRepository.php create mode 100644 app/Repositories/Wings/CommandRepository.php create mode 100644 app/Repositories/Wings/ConfigurationRepository.php create mode 100644 app/Repositories/Wings/FileRepository.php create mode 100644 app/Repositories/Wings/PowerRepository.php create mode 100644 app/Repositories/Wings/ServerRepository.php diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php index 2f435bd97..4582b511c 100644 --- a/app/Providers/RepositoryServiceProvider.php +++ b/app/Providers/RepositoryServiceProvider.php @@ -94,10 +94,18 @@ class RepositoryServiceProvider extends ServiceProvider $this->app->bind(UserRepositoryInterface::class, UserRepository::class); // Daemon Repositories - $this->app->bind(ConfigurationRepositoryInterface::class, ConfigurationRepository::class); - $this->app->bind(CommandRepositoryInterface::class, CommandRepository::class); - $this->app->bind(DaemonServerRepositoryInterface::class, DaemonServerRepository::class); - $this->app->bind(FileRepositoryInterface::class, FileRepository::class); - $this->app->bind(PowerRepositoryInterface::class, PowerRepository::class); + if ($this->app->make('config')->get('pterodactyl.daemon.use_new_daemon')) { + $this->app->bind(ConfigurationRepositoryInterface::class, \Pterodactyl\Repositories\Wings\ConfigurationRepository::class); + $this->app->bind(CommandRepositoryInterface::class, \Pterodactyl\Repositories\Wings\CommandRepository::class); + $this->app->bind(DaemonServerRepositoryInterface::class, \Pterodactyl\Repositories\Wings\ServerRepository::class); + $this->app->bind(FileRepositoryInterface::class, \Pterodactyl\Repositories\Wings\FileRepository::class); + $this->app->bind(PowerRepositoryInterface::class, \Pterodactyl\Repositories\Wings\PowerRepository::class); + } else { + $this->app->bind(ConfigurationRepositoryInterface::class, ConfigurationRepository::class); + $this->app->bind(CommandRepositoryInterface::class, CommandRepository::class); + $this->app->bind(DaemonServerRepositoryInterface::class, DaemonServerRepository::class); + $this->app->bind(FileRepositoryInterface::class, FileRepository::class); + $this->app->bind(PowerRepositoryInterface::class, PowerRepository::class); + } } } diff --git a/app/Repositories/Wings/BaseRepository.php b/app/Repositories/Wings/BaseRepository.php new file mode 100644 index 000000000..eee71104a --- /dev/null +++ b/app/Repositories/Wings/BaseRepository.php @@ -0,0 +1,146 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Repositories\Wings; + +use GuzzleHttp\Client; +use Webmozart\Assert\Assert; +use Illuminate\Foundation\Application; +use Pterodactyl\Contracts\Repository\NodeRepositoryInterface; +use Illuminate\Contracts\Config\Repository as ConfigRepository; +use Pterodactyl\Contracts\Repository\Daemon\BaseRepositoryInterface; + +class BaseRepository implements BaseRepositoryInterface +{ + /** + * @var \Illuminate\Foundation\Application + */ + protected $app; + + /** + * @var + */ + protected $accessServer; + + /** + * @var + */ + protected $accessToken; + + /** + * @var + */ + protected $node; + + /** + * @var \Illuminate\Contracts\Config\Repository + */ + protected $config; + + /** + * @var \Pterodactyl\Contracts\Repository\NodeRepositoryInterface + */ + protected $nodeRepository; + + /** + * BaseRepository constructor. + * + * @param \Illuminate\Foundation\Application $app + * @param \Illuminate\Contracts\Config\Repository $config + * @param \Pterodactyl\Contracts\Repository\NodeRepositoryInterface $nodeRepository + */ + public function __construct( + Application $app, + ConfigRepository $config, + NodeRepositoryInterface $nodeRepository + ) { + $this->app = $app; + $this->config = $config; + $this->nodeRepository = $nodeRepository; + } + + /** + * {@inheritdoc} + */ + public function setNode($id) + { + Assert::numeric($id, 'The first argument passed to setNode must be numeric, received %s.'); + + $this->node = $this->nodeRepository->find($id); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getNode() + { + return $this->node; + } + + /** + * {@inheritdoc} + */ + public function setAccessServer($server = null) + { + Assert::nullOrString($server, 'The first argument passed to setAccessServer must be null or a string, received %s.'); + + $this->accessServer = $server; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAccessServer() + { + return $this->accessServer; + } + + /** + * {@inheritdoc} + */ + public function setAccessToken($token = null) + { + Assert::nullOrString($token, 'The first argument passed to setAccessToken must be null or a string, received %s.'); + + $this->accessToken = $token; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getAccessToken() + { + return $this->accessToken; + } + + /** + * {@inheritdoc} + */ + public function getHttpClient(array $headers = []) + { + if (! is_null($this->accessToken)) { + $headers['Authorization'] = 'Bearer ' . $this->getAccessToken(); + } elseif (! is_null($this->node)) { + $headers['Authorization'] = 'Bearer ' . $this->getNode()->daemonSecret; + } + + return new Client([ + 'base_uri' => sprintf('%s://%s:%s/v1/', $this->getNode()->scheme, $this->getNode()->fqdn, $this->getNode()->daemonListen), + 'timeout' => $this->config->get('pterodactyl.guzzle.timeout'), + 'connect_timeout' => $this->config->get('pterodactyl.guzzle.connect_timeout'), + 'headers' => $headers, + ]); + } +} diff --git a/app/Repositories/Wings/CommandRepository.php b/app/Repositories/Wings/CommandRepository.php new file mode 100644 index 000000000..6b5f85352 --- /dev/null +++ b/app/Repositories/Wings/CommandRepository.php @@ -0,0 +1,30 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Repositories\Wings; + +use Webmozart\Assert\Assert; +use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface; + +class CommandRepository extends BaseRepository implements CommandRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function send($command) + { + Assert::stringNotEmpty($command, 'First argument passed to send must be a non-empty string, received %s.'); + + return $this->getHttpClient()->request('POST', '/server/' . $this->getAccessServer() . '/command', [ + 'json' => [ + 'command' => $command, + ], + ]); + } +} diff --git a/app/Repositories/Wings/ConfigurationRepository.php b/app/Repositories/Wings/ConfigurationRepository.php new file mode 100644 index 000000000..db487d6ae --- /dev/null +++ b/app/Repositories/Wings/ConfigurationRepository.php @@ -0,0 +1,24 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Repositories\Wings; + +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Contracts\Repository\Daemon\ConfigurationRepositoryInterface; + +class ConfigurationRepository extends BaseRepository implements ConfigurationRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function update(array $overrides = []) + { + throw new PterodactylException('This has not yet been configured.'); + } +} diff --git a/app/Repositories/Wings/FileRepository.php b/app/Repositories/Wings/FileRepository.php new file mode 100644 index 000000000..3ed44a79f --- /dev/null +++ b/app/Repositories/Wings/FileRepository.php @@ -0,0 +1,114 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Repositories\Wings; + +use Webmozart\Assert\Assert; +use Pterodactyl\Contracts\Repository\Daemon\FileRepositoryInterface; + +class FileRepository extends BaseRepository implements FileRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function getFileStat($path) + { + Assert::stringNotEmpty($path, 'First argument passed to getStat must be a non-empty string, received %s.'); + + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; + + $response = $this->getHttpClient()->request('GET', sprintf( + '/server/' . $this->getAccessServer() . '/file/stat/%s', + rawurlencode($file['dirname'] . $file['basename']) + )); + + return json_decode($response->getBody()); + } + + /** + * {@inheritdoc} + */ + public function getContent($path) + { + Assert::stringNotEmpty($path, 'First argument passed to getContent must be a non-empty string, received %s.'); + + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; + + $response = $this->getHttpClient()->request('GET', sprintf( + '/server/' . $this->getAccessServer() . '/file/f/%s', + rawurlencode($file['dirname'] . $file['basename']) + )); + + return object_get(json_decode($response->getBody()), 'content'); + } + + /** + * {@inheritdoc} + */ + public function putContent($path, $content) + { + Assert::stringNotEmpty($path, 'First argument passed to putContent must be a non-empty string, received %s.'); + Assert::string($content, 'Second argument passed to putContent must be a string, received %s.'); + + $file = pathinfo($path); + $file['dirname'] = in_array($file['dirname'], ['.', './', '/']) ? null : trim($file['dirname'], '/') . '/'; + + return $this->getHttpClient()->request('POST', '/server/' . $this->getAccessServer() . '/file/save', [ + 'json' => [ + 'path' => rawurlencode($file['dirname'] . $file['basename']), + 'content' => $content, + ], + ]); + } + + /** + * {@inheritdoc} + */ + public function getDirectory($path) + { + Assert::string($path, 'First argument passed to getDirectory must be a string, received %s.'); + + $response = $this->getHttpClient()->request('GET', sprintf( + '/server/' . $this->getAccessServer() . '/directory/%s', + rawurlencode($path) + )); + + $contents = json_decode($response->getBody()); + $files = []; + $folders = []; + + foreach ($contents as $value) { + if ($value->directory) { + array_push($folders, [ + 'entry' => $value->name, + 'directory' => trim($path, '/'), + 'size' => null, + 'date' => strtotime($value->modified), + 'mime' => $value->mime, + ]); + } elseif ($value->file) { + array_push($files, [ + 'entry' => $value->name, + 'directory' => trim($path, '/'), + 'extension' => pathinfo($value->name, PATHINFO_EXTENSION), + 'size' => human_readable($value->size), + 'date' => strtotime($value->modified), + 'mime' => $value->mime, + ]); + } + } + + return [ + 'files' => $files, + 'folders' => $folders, + ]; + } +} diff --git a/app/Repositories/Wings/PowerRepository.php b/app/Repositories/Wings/PowerRepository.php new file mode 100644 index 000000000..6c42976ee --- /dev/null +++ b/app/Repositories/Wings/PowerRepository.php @@ -0,0 +1,40 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Repositories\Wings; + +use Webmozart\Assert\Assert; +use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface; +use Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException; + +class PowerRepository extends BaseRepository implements PowerRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function sendSignal($signal) + { + Assert::stringNotEmpty($signal, 'The first argument passed to sendSignal must be a non-empty string, received %s.'); + + switch ($signal) { + case self::SIGNAL_START: + case self::SIGNAL_STOP: + case self::SIGNAL_RESTART: + case self::SIGNAL_KILL: + return $this->getHttpClient()->request('PUT', '/server/' . $this->getAccessServer() . '/power', [ + 'json' => [ + 'action' => $signal, + ], + ]); + break; + default: + throw new InvalidPowerSignalException('The signal ' . $signal . ' is not defined and could not be processed.'); + } + } +} diff --git a/app/Repositories/Wings/ServerRepository.php b/app/Repositories/Wings/ServerRepository.php new file mode 100644 index 000000000..7d29787cc --- /dev/null +++ b/app/Repositories/Wings/ServerRepository.php @@ -0,0 +1,88 @@ +. + * + * This software is licensed under the terms of the MIT license. + * https://opensource.org/licenses/MIT + */ + +namespace Pterodactyl\Repositories\Wings; + +use Pterodactyl\Exceptions\PterodactylException; +use Pterodactyl\Contracts\Repository\Daemon\ServerRepositoryInterface; + +class ServerRepository extends BaseRepository implements ServerRepositoryInterface +{ + /** + * {@inheritdoc} + */ + public function create($id, array $overrides = [], $start = false) + { + throw new PterodactylException('This feature is not yet implemented.'); + } + + /** + * {@inheritdoc} + */ + public function update(array $data) + { + throw new PterodactylException('This feature is not yet implemented.'); + } + + /** + * {@inheritdoc} + */ + public function reinstall($data = null) + { + throw new PterodactylException('This feature is not yet implemented.'); + } + + /** + * {@inheritdoc} + */ + public function rebuild() + { + return $this->getHttpClient()->request('POST', '/server/' . $this->getAccessServer() . '/rebuild'); + } + + /** + * {@inheritdoc} + */ + public function suspend() + { + return $this->getHttpClient()->request('POST', '/server/' . $this->getAccessServer() . '/suspend'); + } + + /** + * {@inheritdoc} + */ + public function unsuspend() + { + return $this->getHttpClient()->request('POST', '/server/' . $this->getAccessServer() . '/unsuspend'); + } + + /** + * {@inheritdoc} + */ + public function delete() + { + return $this->getHttpClient()->request('DELETE', '/server/' . $this->getAccessServer()); + } + + /** + * {@inheritdoc} + */ + public function details() + { + return $this->getHttpClient()->request('GET', '/server/' . $this->getAccessServer()); + } + + /** + * {@inheritdoc} + */ + public function revokeAccessKey($key) + { + throw new PterodactylException('This feature is not yet implemented.'); + } +} diff --git a/config/pterodactyl.php b/config/pterodactyl.php index 25e664921..e2140fb09 100644 --- a/config/pterodactyl.php +++ b/config/pterodactyl.php @@ -99,6 +99,17 @@ return [ 'frequency' => env('CONSOLE_PUSH_FREQ', 200), ], + /* + |-------------------------------------------------------------------------- + | Daemon Connection Details + |-------------------------------------------------------------------------- + | + | Configuration for support of the new Golang based daemon. + */ + 'daemon' => [ + 'use_new_daemon' => env('APP_USE_NEW_DAEMON', false), + ], + /* |-------------------------------------------------------------------------- | Task Timers