diff --git a/app/Actions/Database/CreateDatabase.php b/app/Actions/Database/CreateDatabase.php index 844b27bd..5e41bce5 100755 --- a/app/Actions/Database/CreateDatabase.php +++ b/app/Actions/Database/CreateDatabase.php @@ -29,7 +29,7 @@ public function create(Server $server, array $input): Database /** @var Service $service */ $service = $server->database(); - /** @var \App\SSH\Services\Database\Database $databaseHandler */ + /** @var \App\Services\Database\Database $databaseHandler */ $databaseHandler = $service->handler(); $databaseHandler->create($database->name, $database->charset, $database->collation); $database->status = DatabaseStatus::READY; diff --git a/app/Actions/Database/CreateDatabaseUser.php b/app/Actions/Database/CreateDatabaseUser.php index c59de47a..d2cf0823 100755 --- a/app/Actions/Database/CreateDatabaseUser.php +++ b/app/Actions/Database/CreateDatabaseUser.php @@ -6,7 +6,7 @@ use App\Models\DatabaseUser; use App\Models\Server; use App\Models\Service; -use App\SSH\Services\Database\Database; +use App\Services\Database\Database; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; diff --git a/app/Actions/Database/DeleteDatabase.php b/app/Actions/Database/DeleteDatabase.php index b333c904..51a91e1c 100755 --- a/app/Actions/Database/DeleteDatabase.php +++ b/app/Actions/Database/DeleteDatabase.php @@ -13,7 +13,7 @@ public function delete(Server $server, Database $database): void { /** @var Service $service */ $service = $server->database(); - /** @var \App\SSH\Services\Database\Database $handler */ + /** @var \App\Services\Database\Database $handler */ $handler = $service->handler(); $handler->delete($database->name); $database->delete(); diff --git a/app/Actions/Database/DeleteDatabaseUser.php b/app/Actions/Database/DeleteDatabaseUser.php index 5993c3b6..02bd2458 100755 --- a/app/Actions/Database/DeleteDatabaseUser.php +++ b/app/Actions/Database/DeleteDatabaseUser.php @@ -5,7 +5,7 @@ use App\Models\DatabaseUser; use App\Models\Server; use App\Models\Service; -use App\SSH\Services\Database\Database; +use App\Services\Database\Database; class DeleteDatabaseUser { diff --git a/app/Actions/Database/LinkUser.php b/app/Actions/Database/LinkUser.php index 27362068..3fcbe0a5 100755 --- a/app/Actions/Database/LinkUser.php +++ b/app/Actions/Database/LinkUser.php @@ -39,7 +39,7 @@ public function link(DatabaseUser $databaseUser, array $input): DatabaseUser /** @var Service $service */ $service = $databaseUser->server->database(); - /** @var \App\SSH\Services\Database\Database $handler */ + /** @var \App\Services\Database\Database $handler */ $handler = $service->handler(); // Unlink the user from all databases diff --git a/app/Actions/Database/RestoreBackup.php b/app/Actions/Database/RestoreBackup.php index 0d57ad1f..b12a0176 100644 --- a/app/Actions/Database/RestoreBackup.php +++ b/app/Actions/Database/RestoreBackup.php @@ -28,7 +28,7 @@ public function restore(BackupFile $backupFile, array $input): void dispatch(function () use ($backupFile, $database): void { /** @var Service $service */ $service = $database->server->database(); - /** @var \App\SSH\Services\Database\Database $databaseHandler */ + /** @var \App\Services\Database\Database $databaseHandler */ $databaseHandler = $service->handler(); $databaseHandler->restoreBackup($backupFile, $database->name); $backupFile->status = BackupFileStatus::RESTORED; diff --git a/app/Actions/Database/RunBackup.php b/app/Actions/Database/RunBackup.php index 20935e7d..c9d473ef 100644 --- a/app/Actions/Database/RunBackup.php +++ b/app/Actions/Database/RunBackup.php @@ -7,7 +7,7 @@ use App\Models\Backup; use App\Models\BackupFile; use App\Models\Service; -use App\SSH\Services\Database\Database; +use App\Services\Database\Database; use Illuminate\Support\Str; class RunBackup diff --git a/app/Actions/Database/SyncDatabaseUsers.php b/app/Actions/Database/SyncDatabaseUsers.php index 8ddd42b7..aab78f25 100644 --- a/app/Actions/Database/SyncDatabaseUsers.php +++ b/app/Actions/Database/SyncDatabaseUsers.php @@ -5,14 +5,15 @@ use App\Enums\DatabaseUserStatus; use App\Models\DatabaseUser; use App\Models\Server; -use App\SSH\Services\Database\Database; +use App\Models\Service; +use App\Services\Database\Database; class SyncDatabaseUsers { public function sync(Server $server): void { $service = $server->database(); - if (! $service instanceof \App\Models\Service) { + if (! $service instanceof Service) { return; } /** @var Database $handler */ diff --git a/app/Actions/Database/SyncDatabases.php b/app/Actions/Database/SyncDatabases.php index 6d477113..e29895e9 100644 --- a/app/Actions/Database/SyncDatabases.php +++ b/app/Actions/Database/SyncDatabases.php @@ -5,14 +5,14 @@ use App\Enums\DatabaseStatus; use App\Models\Server; use App\Models\Service; -use App\SSH\Services\Database\Database; +use App\Services\Database\Database; class SyncDatabases { public function sync(Server $server): void { $service = $server->database(); - if (! $service instanceof \App\Models\Service) { + if (! $service instanceof Service) { return; } /** @var Database $handler */ diff --git a/app/Actions/FirewallRule/ManageRule.php b/app/Actions/FirewallRule/ManageRule.php index e341c30f..6791915f 100755 --- a/app/Actions/FirewallRule/ManageRule.php +++ b/app/Actions/FirewallRule/ManageRule.php @@ -6,7 +6,7 @@ use App\Models\FirewallRule; use App\Models\Server; use App\Models\Service; -use App\SSH\Services\Firewall\Firewall; +use App\Services\Firewall\Firewall; use Exception; use Illuminate\Support\Facades\Validator; diff --git a/app/Actions/Monitoring/GetMetrics.php b/app/Actions/Monitoring/GetMetrics.php index 9ad8a543..168a98e2 100644 --- a/app/Actions/Monitoring/GetMetrics.php +++ b/app/Actions/Monitoring/GetMetrics.php @@ -9,6 +9,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; +use stdClass; class GetMetrics { @@ -70,7 +71,7 @@ private function metrics( ->groupByRaw('date_interval') ->orderBy('date_interval') ->get() - ->map(function ($item): \stdClass { + ->map(function ($item): stdClass { $item->date = Carbon::parse($item->date)->format('Y-m-d H:i'); return $item; diff --git a/app/Actions/Monitoring/UpdateMetricSettings.php b/app/Actions/Monitoring/UpdateMetricSettings.php index b277f718..e09f545f 100644 --- a/app/Actions/Monitoring/UpdateMetricSettings.php +++ b/app/Actions/Monitoring/UpdateMetricSettings.php @@ -4,7 +4,7 @@ use App\Models\Server; use App\Models\Service; -use App\SSH\Services\ServiceInterface; +use App\Services\ServiceInterface; use Illuminate\Support\Facades\Validator; class UpdateMetricSettings diff --git a/app/Actions/NodeJS/ChangeDefaultCli.php b/app/Actions/NodeJS/ChangeDefaultCli.php index 121d437e..97933d45 100644 --- a/app/Actions/NodeJS/ChangeDefaultCli.php +++ b/app/Actions/NodeJS/ChangeDefaultCli.php @@ -6,7 +6,7 @@ use App\Exceptions\SSHError; use App\Models\Server; use App\Models\Service; -use App\SSH\Services\NodeJS\NodeJS; +use App\Services\NodeJS\NodeJS; use Illuminate\Validation\ValidationException; class ChangeDefaultCli diff --git a/app/Actions/NotificationChannels/AddChannel.php b/app/Actions/NotificationChannels/AddChannel.php index 1a9a1faa..e346b599 100644 --- a/app/Actions/NotificationChannels/AddChannel.php +++ b/app/Actions/NotificationChannels/AddChannel.php @@ -4,6 +4,7 @@ use App\Models\NotificationChannel; use App\Models\User; +use App\NotificationChannels\Email; use Exception; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; @@ -33,7 +34,7 @@ public function add(User $user, array $input): void if (! $channel->provider()->connect()) { $channel->delete(); - if ($channel->provider === \App\Enums\NotificationChannel::EMAIL) { + if ($channel->provider === Email::id()) { throw ValidationException::withMessages([ 'email' => __('Could not connect! Make sure you configured `.env` file correctly.'), ]); @@ -64,7 +65,7 @@ public static function rules(array $input): array $rules = [ 'provider' => [ 'required', - Rule::in(config('core.notification_channels_providers')), + Rule::in(array_keys(config('notification-channel.providers'))), ], 'name' => 'required', ]; diff --git a/app/Actions/PHP/ChangeDefaultCli.php b/app/Actions/PHP/ChangeDefaultCli.php index c8f2037e..ba807ebc 100644 --- a/app/Actions/PHP/ChangeDefaultCli.php +++ b/app/Actions/PHP/ChangeDefaultCli.php @@ -6,7 +6,7 @@ use App\Exceptions\SSHError; use App\Models\Server; use App\Models\Service; -use App\SSH\Services\PHP\PHP; +use App\Services\PHP\PHP; use Illuminate\Validation\ValidationException; class ChangeDefaultCli diff --git a/app/Actions/PHP/GetPHPIni.php b/app/Actions/PHP/GetPHPIni.php index 4a96768e..248e0416 100644 --- a/app/Actions/PHP/GetPHPIni.php +++ b/app/Actions/PHP/GetPHPIni.php @@ -5,7 +5,7 @@ use App\Enums\PHPIniType; use App\Models\Server; use App\Models\Service; -use App\SSH\Services\PHP\PHP; +use App\Services\PHP\PHP; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; diff --git a/app/Actions/PHP/InstallPHPExtension.php b/app/Actions/PHP/InstallPHPExtension.php index e7951ae2..958a96d3 100755 --- a/app/Actions/PHP/InstallPHPExtension.php +++ b/app/Actions/PHP/InstallPHPExtension.php @@ -4,7 +4,7 @@ use App\Models\Server; use App\Models\Service; -use App\SSH\Services\PHP\PHP; +use App\Services\PHP\PHP; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -57,7 +57,7 @@ public static function rules(Server $server): array return [ 'extension' => [ 'required', - Rule::in(config('core.php_extensions')), + Rule::in(config('service.services.php.data.extensions', []) ?? []), ], 'version' => [ 'required', diff --git a/app/Actions/Redirect/CreateRedirect.php b/app/Actions/Redirect/CreateRedirect.php index b91aa916..fd667721 100644 --- a/app/Actions/Redirect/CreateRedirect.php +++ b/app/Actions/Redirect/CreateRedirect.php @@ -6,7 +6,7 @@ use App\Models\Redirect; use App\Models\Service; use App\Models\Site; -use App\SSH\Services\Webserver\Webserver; +use App\Services\Webserver\Webserver; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; @@ -33,7 +33,9 @@ public function create(Site $site, array $input): Redirect $service = $site->server->webserver(); /** @var Webserver $webserver */ $webserver = $service->handler(); - $webserver->updateVHost($site); + $webserver->updateVHost($site, regenerate: [ + 'redirects', + ]); $redirect->status = RedirectStatus::READY; $redirect->save(); }) diff --git a/app/Actions/Redirect/DeleteRedirect.php b/app/Actions/Redirect/DeleteRedirect.php index c6ff964d..cf8f6d80 100644 --- a/app/Actions/Redirect/DeleteRedirect.php +++ b/app/Actions/Redirect/DeleteRedirect.php @@ -6,7 +6,7 @@ use App\Models\Redirect; use App\Models\Service; use App\Models\Site; -use App\SSH\Services\Webserver\Webserver; +use App\Services\Webserver\Webserver; class DeleteRedirect { @@ -20,7 +20,9 @@ public function delete(Site $site, Redirect $redirect): void $service = $site->server->webserver(); /** @var Webserver $webserver */ $webserver = $service->handler(); - $webserver->updateVHost($site); + $webserver->updateVHost($site, regenerate: [ + 'redirects', + ]); $redirect->delete(); })->catch(function () use ($redirect): void { $redirect->status = RedirectStatus::FAILED; diff --git a/app/Actions/SSL/ActivateSSL.php b/app/Actions/SSL/ActivateSSL.php index 4cf8f64c..3cd39512 100644 --- a/app/Actions/SSL/ActivateSSL.php +++ b/app/Actions/SSL/ActivateSSL.php @@ -11,6 +11,8 @@ public function activate(Ssl $ssl): void $ssl->site->ssls()->update(['is_active' => false]); $ssl->is_active = true; $ssl->save(); - $ssl->site->webserver()->updateVHost($ssl->site); + $ssl->site->webserver()->updateVHost($ssl->site, regenerate: [ + 'port', + ]); } } diff --git a/app/Actions/SSL/CreateSSL.php b/app/Actions/SSL/CreateSSL.php index c4eb3dc6..6172d1df 100644 --- a/app/Actions/SSL/CreateSSL.php +++ b/app/Actions/SSL/CreateSSL.php @@ -8,7 +8,7 @@ use App\Models\Service; use App\Models\Site; use App\Models\Ssl; -use App\SSH\Services\Webserver\Webserver; +use App\Services\Webserver\Webserver; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -54,7 +54,9 @@ public function create(Site $site, array $input): void $webserver->setupSSL($ssl); $ssl->status = SslStatus::CREATED; $ssl->save(); - $webserver->updateVHost($site); + $webserver->updateVHost($site, regenerate: [ + 'port', + ]); })->catch(function () use ($ssl): void { $ssl->status = SslStatus::FAILED; $ssl->save(); diff --git a/app/Actions/SSL/DeactivateSSL.php b/app/Actions/SSL/DeactivateSSL.php index 55c00477..219289ea 100644 --- a/app/Actions/SSL/DeactivateSSL.php +++ b/app/Actions/SSL/DeactivateSSL.php @@ -10,6 +10,8 @@ public function deactivate(Ssl $ssl): void { $ssl->is_active = false; $ssl->save(); - $ssl->site->webserver()->updateVHost($ssl->site); + $ssl->site->webserver()->updateVHost($ssl->site, regenerate: [ + 'port', + ]); } } diff --git a/app/Actions/SSL/DeleteSSL.php b/app/Actions/SSL/DeleteSSL.php index a25c648c..45c7cfa9 100644 --- a/app/Actions/SSL/DeleteSSL.php +++ b/app/Actions/SSL/DeleteSSL.php @@ -5,7 +5,7 @@ use App\Enums\SslStatus; use App\Models\Service; use App\Models\Ssl; -use App\SSH\Services\Webserver\Webserver; +use App\Services\Webserver\Webserver; class DeleteSSL { diff --git a/app/Actions/Server/CreateServer.php b/app/Actions/Server/CreateServer.php index 0af7d88f..63c00140 100755 --- a/app/Actions/Server/CreateServer.php +++ b/app/Actions/Server/CreateServer.php @@ -3,16 +3,13 @@ namespace App\Actions\Server; use App\Enums\FirewallRuleStatus; -use App\Enums\ServerProvider; use App\Enums\ServerStatus; -use App\Enums\ServerType; -use App\Exceptions\SSHConnectionError; use App\Facades\Notifier; use App\Models\Project; use App\Models\Server; use App\Models\User; use App\Notifications\ServerInstallationFailed; -use App\Notifications\ServerInstallationSucceed; +use App\ServerProviders\Custom; use App\ValidationRules\RestrictedIPAddressesRule; use Exception; use Illuminate\Database\Query\Builder; @@ -25,6 +22,8 @@ class CreateServer { + protected Server $server; + /** * @param array $input */ @@ -32,15 +31,14 @@ public function create(User $creator, Project $project, array $input): Server { Validator::make($input, self::rules($project, $input))->validate(); - $server = new Server([ + $this->server = new Server([ 'project_id' => $project->id, 'user_id' => $creator->id, 'name' => $input['name'], - 'ssh_user' => config('core.server_providers_default_user')[$input['provider']][$input['os']], + 'ssh_user' => data_get(config('server-provider.providers'), $input['provider'].'.default_user') ?? 'root', 'ip' => $input['ip'] ?? '', 'port' => $input['port'] ?? 22, 'os' => $input['os'], - 'type' => ServerType::REGULAR, 'provider' => $input['provider'], 'authentication' => [ 'user' => config('core.ssh_user'), @@ -52,32 +50,42 @@ public function create(User $creator, Project $project, array $input): Server ]); try { - if ($server->provider != 'custom') { - $server->provider_id = $input['server_provider']; + if ($this->server->provider != 'custom') { + $this->server->provider_id = $input['server_provider']; } - $server->type_data = $server->type()->data($input); - - $server->provider_data = $server->provider()->data($input); + $this->server->provider_data = $this->server->provider()->data($input); // save - $server->save(); + $this->server->save(); // create firewall rules - $this->createFirewallRules($server); + $this->createFirewallRules($this->server); // create instance - $server->provider()->create(); + $this->server->provider()->create(); - // add services - $server->type()->createServices($input); + // create services + $this->createServices(); // install server - $this->install($server); + dispatch(function (): void { + app(InstallServer::class)->run($this->server); + }) + ->catch(function (Throwable $e): void { + $this->server->update([ + 'status' => ServerStatus::INSTALLATION_FAILED, + ]); + Notifier::send($this->server, new ServerInstallationFailed($this->server)); + Log::error('server-installation-error', [ + 'error' => (string) $e, + ]); + }) + ->onConnection('ssh'); - return $server; + return $this->server; } catch (Exception $e) { - $server->delete(); + $this->server->delete(); throw ValidationException::withMessages([ 'provider' => $e->getMessage(), @@ -85,41 +93,6 @@ public function create(User $creator, Project $project, array $input): Server } } - private function install(Server $server): void - { - dispatch(function () use ($server): void { - $maxWait = 180; - while ($maxWait > 0) { - sleep(10); - $maxWait -= 10; - if (! $server->provider()->isRunning()) { - continue; - } - try { - $server->ssh()->connect(); - break; - } catch (SSHConnectionError) { - // ignore - } - } - $server->type()->install(); - $server->update([ - 'status' => ServerStatus::READY, - ]); - Notifier::send($server, new ServerInstallationSucceed($server)); - }) - ->catch(function (Throwable $e) use ($server): void { - $server->update([ - 'status' => ServerStatus::INSTALLATION_FAILED, - ]); - Notifier::send($server, new ServerInstallationFailed($server)); - Log::error('server-installation-error', [ - 'error' => (string) $e, - ]); - }) - ->onConnection('ssh'); - } - /** * @param array $input * @return array @@ -129,7 +102,7 @@ public static function rules(Project $project, array $input): array $rules = [ 'provider' => [ 'required', - Rule::in(config('core.server_providers')), + Rule::in(array_keys(config('server-provider.providers'))), ], 'name' => [ 'required', @@ -139,7 +112,7 @@ public static function rules(Project $project, array $input): array Rule::in(config('core.operating_systems')), ], 'server_provider' => [ - Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] != ServerProvider::CUSTOM, [ + Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] != Custom::id(), [ 'required', Rule::exists('server_providers', 'id')->where(function (Builder $query) use ($project): void { $query->where('project_id', $project->id) @@ -148,13 +121,13 @@ public static function rules(Project $project, array $input): array ]), ], 'ip' => [ - Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] == ServerProvider::CUSTOM, [ + Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] == Custom::id(), [ 'required', new RestrictedIPAddressesRule, ]), ], 'port' => [ - Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] == ServerProvider::CUSTOM, [ + Rule::when(fn (): bool => isset($input['provider']) && $input['provider'] == Custom::id(), [ 'required', 'numeric', 'min:1', @@ -163,22 +136,7 @@ public static function rules(Project $project, array $input): array ], ]; - return array_merge($rules, self::typeRules($input), self::providerRules($input)); - } - - /** - * @param array $input - * @return array> - */ - private static function typeRules(array $input): array - { - if (! isset($input['type']) || ! in_array($input['type'], config('core.server_types'))) { - return []; - } - - $server = new Server(['type' => $input['type']]); - - return $server->type()->createRules($input); + return array_merge($rules, self::providerRules($input)); } /** @@ -190,8 +148,8 @@ private static function providerRules(array $input): array if ( ! isset($input['provider']) || ! isset($input['server_provider']) || - ! in_array($input['provider'], config('core.server_providers')) || - $input['provider'] == ServerProvider::CUSTOM + ! config('server-provider.providers.'.$input['provider']) || + $input['provider'] == Custom::id() ) { return []; } @@ -236,4 +194,49 @@ public function createFirewallRules(Server $server): void ], ]); } + + private function createServices(): void + { + $this->server->services()->forceDelete(); + $this->addSupervisor(); + $this->addRedis(); + $this->addUfw(); + $this->addMonitoring(); + } + + private function addSupervisor(): void + { + $this->server->services()->create([ + 'type' => 'process_manager', + 'name' => 'supervisor', + 'version' => 'latest', + ]); + } + + private function addRedis(): void + { + $this->server->services()->create([ + 'type' => 'memory_database', + 'name' => 'redis', + 'version' => 'latest', + ]); + } + + private function addUfw(): void + { + $this->server->services()->create([ + 'type' => 'firewall', + 'name' => 'ufw', + 'version' => 'latest', + ]); + } + + private function addMonitoring(): void + { + $this->server->services()->create([ + 'type' => 'monitoring', + 'name' => 'remote-monitor', + 'version' => 'latest', + ]); + } } diff --git a/app/Actions/Server/InstallServer.php b/app/Actions/Server/InstallServer.php new file mode 100644 index 00000000..eacbaca2 --- /dev/null +++ b/app/Actions/Server/InstallServer.php @@ -0,0 +1,97 @@ +server = $server; + + $maxWait = 180; + while ($maxWait > 0) { + if (! $this->server->provider()->isRunning()) { + continue; + } + try { + $this->server->ssh()->connect(); + break; + } catch (SSHConnectionError) { + // ignore + } + sleep(10); + $maxWait -= 10; + } + $this->install(); + $this->server->update([ + 'status' => ServerStatus::READY, + ]); + Notifier::send($this->server, new ServerInstallationSucceed($this->server)); + } + + /** + * @throws SSHError + */ + public function install(): void + { + $this->createUser(); + $this->progress(15, 'installing-updates'); + $this->server->os()->upgrade(); + $this->progress(25, 'installing-dependencies'); + $this->server->os()->installDependencies(); + $services = $this->server->services; + $currentProgress = 45; + $progressPerService = (100 - $currentProgress) / count($services); + foreach ($services as $service) { + $currentProgress += $progressPerService; + $this->progress($currentProgress, 'installing- '.$service->name); + $service->handler()->install(); + $service->update(['status' => ServiceStatus::READY]); + if ($service->type == 'php') { + $this->progress($currentProgress, 'installing-composer'); + /** @var PHP $handler */ + $handler = $service->handler(); + $handler->installComposer(); + } + } + $this->progress(100, 'finishing'); + } + + /** + * @throws SSHError + */ + protected function createUser(): void + { + $this->server->os()->createUser( + $this->server->authentication['user'], + $this->server->authentication['pass'], + $this->server->sshKey()['public_key'] + ); + $this->server->ssh_user = config('core.ssh_user'); + $this->server->save(); + $this->server->refresh(); + $this->server->public_key = $this->server->os()->getPublicKey($this->server->getSshUser()); + $this->server->save(); + } + + protected function progress(int|float $percentage, ?string $step = null): void + { + $this->server->progress = $percentage; + $this->server->progress_step = $step; + $this->server->save(); + } +} diff --git a/app/Actions/ServerProvider/CreateServerProvider.php b/app/Actions/ServerProvider/CreateServerProvider.php index 893e5dcc..55d9afa0 100644 --- a/app/Actions/ServerProvider/CreateServerProvider.php +++ b/app/Actions/ServerProvider/CreateServerProvider.php @@ -48,7 +48,7 @@ public function create(User $user, Project $project, array $input): ServerProvid private static function getProvider(string $name): ServerProviderContract { - $providerClass = config('core.server_providers_class.'.$name); + $providerClass = config('server-provider.providers.'.$name.'.handler'); /** @var ServerProviderContract $provider */ $provider = new $providerClass(new ServerProvider, new Server); @@ -67,7 +67,7 @@ public static function rules(array $input): array ], 'provider' => [ 'required', - Rule::in(config('core.server_providers')), + Rule::in(array_keys(config('server-provider.providers'))), Rule::notIn('custom'), ], ]; diff --git a/app/Actions/Service/Install.php b/app/Actions/Service/Install.php index a7873961..d2fc068a 100644 --- a/app/Actions/Service/Install.php +++ b/app/Actions/Service/Install.php @@ -17,7 +17,12 @@ public function install(Server $server, array $input): Service { Validator::make($input, self::rules($input))->validate(); - $input['type'] = config('core.service_types')[$input['name']]; + $name = $input['name']; + $input['type'] = config("service.services.$name.type"); + + if (! $input['type']) { + throw new \InvalidArgumentException("Service type is not defined for $name"); + } $service = new Service([ 'server_id' => $server->id, @@ -55,14 +60,14 @@ public static function rules(array $input): array $rules = [ 'name' => [ 'required', - Rule::in(array_keys(config('core.service_types'))), + Rule::in(array_keys(config('service.services'))), ], 'version' => [ 'required', ], ]; if (isset($input['name'])) { - $rules['version'][] = Rule::in(config("core.service_versions.{$input['name']}")); + $rules['version'][] = Rule::in(config("service.services.{$input['name']}.versions", [])); } return $rules; diff --git a/app/Actions/Service/Manage.php b/app/Actions/Service/Manage.php index 50391337..bada2a95 100644 --- a/app/Actions/Service/Manage.php +++ b/app/Actions/Service/Manage.php @@ -4,15 +4,17 @@ use App\Enums\ServiceStatus; use App\Models\Service; +use Illuminate\Validation\ValidationException; class Manage { public function start(Service $service): void { + $this->validate($service); $service->status = ServiceStatus::STARTING; $service->save(); dispatch(function () use ($service): void { - $status = $service->server->systemd()->start($service->unit); + $status = $service->server->systemd()->start($service->handler()->unit()); if (str($status)->contains('Active: active')) { $service->status = ServiceStatus::READY; } else { @@ -24,10 +26,11 @@ public function start(Service $service): void public function stop(Service $service): void { + $this->validate($service); $service->status = ServiceStatus::STOPPING; $service->save(); dispatch(function () use ($service): void { - $status = $service->server->systemd()->stop($service->unit); + $status = $service->server->systemd()->stop($service->handler()->unit()); if (str($status)->contains('Active: inactive')) { $service->status = ServiceStatus::STOPPED; } else { @@ -39,10 +42,11 @@ public function stop(Service $service): void public function restart(Service $service): void { + $this->validate($service); $service->status = ServiceStatus::RESTARTING; $service->save(); dispatch(function () use ($service): void { - $status = $service->server->systemd()->restart($service->unit); + $status = $service->server->systemd()->restart($service->handler()->unit()); if (str($status)->contains('Active: active')) { $service->status = ServiceStatus::READY; } else { @@ -54,10 +58,11 @@ public function restart(Service $service): void public function enable(Service $service): void { + $this->validate($service); $service->status = ServiceStatus::ENABLING; $service->save(); dispatch(function () use ($service): void { - $status = $service->server->systemd()->enable($service->unit); + $status = $service->server->systemd()->enable($service->handler()->unit()); if (str($status)->contains('Active: active')) { $service->status = ServiceStatus::READY; } else { @@ -69,10 +74,11 @@ public function enable(Service $service): void public function disable(Service $service): void { + $this->validate($service); $service->status = ServiceStatus::DISABLING; $service->save(); dispatch(function () use ($service): void { - $status = $service->server->systemd()->disable($service->unit); + $status = $service->server->systemd()->disable($service->handler()->unit()); if (str($status)->contains('Active: inactive')) { $service->status = ServiceStatus::DISABLED; } else { @@ -81,4 +87,13 @@ public function disable(Service $service): void $service->save(); })->onConnection('ssh'); } + + private function validate(Service $service): void + { + if (! $service->handler()->unit()) { + throw ValidationException::withMessages([ + 'service' => __('This service does not have a systemd unit configured.'), + ]); + } + } } diff --git a/app/Actions/Site/CreateSite.php b/app/Actions/Site/CreateSite.php index dd294baf..81dcf8f8 100755 --- a/app/Actions/Site/CreateSite.php +++ b/app/Actions/Site/CreateSite.php @@ -13,7 +13,6 @@ use App\Notifications\SiteInstallationSucceed; use App\ValidationRules\DomainRule; use Exception; -use Illuminate\Database\Query\Builder; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; @@ -39,7 +38,6 @@ public function create(Server $server, array $input): Site 'type' => $input['type'], 'domain' => $input['domain'], 'aliases' => $input['aliases'] ?? [], - 'port' => $input['port'] ?? null, 'user' => $user, 'path' => '/home/'.$user.'/'.$input['domain'], 'status' => SiteStatus::INSTALLING, @@ -116,7 +114,7 @@ public static function rules(Server $server, array $input): array $rules = [ 'type' => [ 'required', - Rule::in(config('core.site_types')), + Rule::in(array_keys(config('site.types'))), ], 'domain' => [ 'required', @@ -134,14 +132,6 @@ public static function rules(Server $server, array $input): array Rule::unique('sites', 'user')->where('server_id', $server->id), Rule::notIn($server->getSshUsers()), ], - 'port' => [ - 'nullable', - 'integer', - 'min:1', - 'max:65535', - Rule::unique('sites', 'port') - ->where(fn (Builder $query) => $query->where('server_id', $server->id)), - ], ]; return array_merge($rules, self::typeRules($server, $input)); @@ -153,7 +143,7 @@ public static function rules(Server $server, array $input): array */ private static function typeRules(Server $server, array $input): array { - if (! isset($input['type']) || ! in_array($input['type'], config('core.site_types'))) { + if (! isset($input['type']) || ! config('site.types.'.$input['type'])) { return []; } diff --git a/app/Actions/Site/DeleteSite.php b/app/Actions/Site/DeleteSite.php index 631722df..f111fa4f 100644 --- a/app/Actions/Site/DeleteSite.php +++ b/app/Actions/Site/DeleteSite.php @@ -5,8 +5,8 @@ use App\Exceptions\SSHError; use App\Models\Service; use App\Models\Site; -use App\SSH\Services\PHP\PHP; -use App\SSH\Services\Webserver\Webserver; +use App\Services\PHP\PHP; +use App\Services\Webserver\Webserver; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; diff --git a/app/Actions/Site/UpdateAliases.php b/app/Actions/Site/UpdateAliases.php index 0f2c8dfb..d8cc8afb 100644 --- a/app/Actions/Site/UpdateAliases.php +++ b/app/Actions/Site/UpdateAliases.php @@ -4,7 +4,7 @@ use App\Models\Service; use App\Models\Site; -use App\SSH\Services\Webserver\Webserver; +use App\Services\Webserver\Webserver; use App\ValidationRules\DomainRule; class UpdateAliases @@ -21,7 +21,9 @@ public function update(Site $site, array $input): void /** @var Webserver $webserver */ $webserver = $service->handler(); - $webserver->updateVHost($site); + $webserver->updateVHost($site, regenerate: [ + 'core', + ]); $site->save(); } diff --git a/app/Actions/Site/UpdateBranch.php b/app/Actions/Site/UpdateBranch.php index aa6c7815..8ed88843 100755 --- a/app/Actions/Site/UpdateBranch.php +++ b/app/Actions/Site/UpdateBranch.php @@ -4,7 +4,7 @@ use App\Exceptions\SSHError; use App\Models\Site; -use App\SSH\Git\Git; +use App\SSH\OS\Git; use Illuminate\Support\Facades\Validator; class UpdateBranch diff --git a/app/Actions/Site/UpdateLoadBalancer.php b/app/Actions/Site/UpdateLoadBalancer.php index f3b58197..de307e05 100644 --- a/app/Actions/Site/UpdateLoadBalancer.php +++ b/app/Actions/Site/UpdateLoadBalancer.php @@ -30,7 +30,10 @@ public function update(Site $site, array $input): void $loadBalancerServer->save(); } - $site->webserver()->updateVHost($site); + $site->webserver()->updateVHost($site, regenerate: [ + 'load-balancer-upstream', + 'load-balancer', + ]); } /** diff --git a/app/Actions/SourceControl/ConnectSourceControl.php b/app/Actions/SourceControl/ConnectSourceControl.php index f711e47a..c8473aa4 100644 --- a/app/Actions/SourceControl/ConnectSourceControl.php +++ b/app/Actions/SourceControl/ConnectSourceControl.php @@ -51,7 +51,7 @@ public static function rules(array $input): array ], 'provider' => [ 'required', - Rule::in(config('core.source_control_providers')), + Rule::in(array_keys(config('source-control.providers'))), ], ]; diff --git a/app/Actions/StorageProvider/CreateStorageProvider.php b/app/Actions/StorageProvider/CreateStorageProvider.php index f39752d3..49e46644 100644 --- a/app/Actions/StorageProvider/CreateStorageProvider.php +++ b/app/Actions/StorageProvider/CreateStorageProvider.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; +use Throwable; class CreateStorageProvider { @@ -35,7 +36,7 @@ public function create(User $user, Project $project, array $input): StorageProvi 'provider' => __("Couldn't connect to the provider"), ]); } - } catch (\Throwable $e) { + } catch (Throwable $e) { throw ValidationException::withMessages([ 'provider' => $e->getMessage(), ]); @@ -55,7 +56,7 @@ public static function rules(array $input): array $rules = [ 'provider' => [ 'required', - Rule::in(config('core.storage_providers')), + Rule::in(array_keys(config('storage-provider.providers'))), ], 'name' => [ 'required', diff --git a/app/Actions/Worker/CreateWorker.php b/app/Actions/Worker/CreateWorker.php index 7aba055c..aa98d904 100644 --- a/app/Actions/Worker/CreateWorker.php +++ b/app/Actions/Worker/CreateWorker.php @@ -7,7 +7,7 @@ use App\Models\Service; use App\Models\Site; use App\Models\Worker; -use App\SSH\Services\ProcessManager\ProcessManager; +use App\Services\ProcessManager\ProcessManager; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -26,6 +26,7 @@ public function create(Server $server, array $input, ?Site $site = null): void $worker = new Worker([ 'server_id' => $server->id, 'site_id' => $site?->id, + 'name' => $input['name'], 'command' => $input['command'], 'user' => $input['user'], 'auto_start' => $input['auto_start'] ? 1 : 0, @@ -63,6 +64,19 @@ public function create(Server $server, array $input, ?Site $site = null): void public static function rules(Server $server, ?Site $site = null): array { return [ + 'name' => [ + 'required', + 'string', + 'max:255', + Rule::unique('workers')->where(function ($query) use ($server, $site) { + return $query->where('server_id', $server->id) + ->where(function ($query) use ($site) { + if ($site) { + $query->where('site_id', $site->id); + } + }); + }), + ], 'command' => [ 'required', ], diff --git a/app/Actions/Worker/EditWorker.php b/app/Actions/Worker/EditWorker.php index c53468df..f3273a48 100644 --- a/app/Actions/Worker/EditWorker.php +++ b/app/Actions/Worker/EditWorker.php @@ -3,11 +3,10 @@ namespace App\Actions\Worker; use App\Enums\WorkerStatus; -use App\Models\Server; use App\Models\Service; use App\Models\Site; use App\Models\Worker; -use App\SSH\Services\ProcessManager\ProcessManager; +use App\Services\ProcessManager\ProcessManager; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -21,9 +20,10 @@ class EditWorker */ public function edit(Worker $worker, array $input): void { - Validator::make($input, self::rules($worker->server, $worker->site))->validate(); + Validator::make($input, self::rules($worker, $worker->site))->validate(); $worker->fill([ + 'name' => $input['name'], 'command' => $input['command'], 'user' => $input['user'], 'auto_start' => $input['auto_start'] ? 1 : 0, @@ -61,15 +61,29 @@ public function edit(Worker $worker, array $input): void /** * @return array> */ - public static function rules(Server $server, ?Site $site = null): array + public static function rules(Worker $worker, ?Site $site = null): array { return [ + 'name' => [ + 'required', + 'string', + 'max:255', + Rule::unique('workers')->where(function ($query) use ($worker, $site) { + return $query->where('server_id', $worker->server_id) + ->where(function ($query) use ($site) { + if ($site) { + $query->where('site_id', $site->id); + } + }); + }) + ->ignore($worker->id), + ], 'command' => [ 'required', ], 'user' => [ 'required', - Rule::in($site?->getSshUsers() ?? $server->getSshUsers()), + Rule::in($site?->getSshUsers() ?? $worker->server->getSshUsers()), ], 'numprocs' => [ 'required', diff --git a/app/Actions/Worker/GetWorkerLogs.php b/app/Actions/Worker/GetWorkerLogs.php index 2a54b2e7..6cb6faba 100644 --- a/app/Actions/Worker/GetWorkerLogs.php +++ b/app/Actions/Worker/GetWorkerLogs.php @@ -4,7 +4,7 @@ use App\Models\Service; use App\Models\Worker; -use App\SSH\Services\ProcessManager\ProcessManager; +use App\Services\ProcessManager\ProcessManager; class GetWorkerLogs { diff --git a/app/Actions/Worker/ManageWorker.php b/app/Actions/Worker/ManageWorker.php index f3c8069b..deee226f 100644 --- a/app/Actions/Worker/ManageWorker.php +++ b/app/Actions/Worker/ManageWorker.php @@ -5,7 +5,7 @@ use App\Enums\WorkerStatus; use App\Models\Service; use App\Models\Worker; -use App\SSH\Services\ProcessManager\ProcessManager; +use App\Services\ProcessManager\ProcessManager; class ManageWorker { diff --git a/app/Console/Commands/CheckServersConnectionCommand.php b/app/Console/Commands/CheckServersConnectionCommand.php index 1acc0d75..57d4b74a 100644 --- a/app/Console/Commands/CheckServersConnectionCommand.php +++ b/app/Console/Commands/CheckServersConnectionCommand.php @@ -18,9 +18,14 @@ public function handle(): void ServerStatus::READY, ServerStatus::DISCONNECTED, ])->chunk(50, function ($servers) { + $dispatchTime = now(); /** @var Server $server */ foreach ($servers as $server) { - dispatch(function () use ($server) { + dispatch(function () use ($server, $dispatchTime) { + // check connection if dispatch time is less than 5 minutes ago + if ($dispatchTime->diffInMinutes(now()) > 5) { + return; + } $server->checkConnection(); })->onConnection('ssh'); } diff --git a/app/Console/Commands/GetMetricsCommand.php b/app/Console/Commands/GetMetricsCommand.php index 0c52444a..a4fa3903 100644 --- a/app/Console/Commands/GetMetricsCommand.php +++ b/app/Console/Commands/GetMetricsCommand.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Enums\ServerStatus; use App\Models\Server; use Illuminate\Console\Command; use Illuminate\Database\Eloquent\Builder; @@ -15,17 +16,19 @@ class GetMetricsCommand extends Command public function handle(): void { $checkedMetrics = 0; - Server::query()->whereHas('services', function (Builder $query): void { - $query->where('type', 'monitoring') - ->where('name', 'remote-monitor'); - })->chunk(10, function ($servers) use (&$checkedMetrics): void { - /** @var Server $server */ - foreach ($servers as $server) { - $info = $server->os()->resourceInfo(); - $server->metrics()->create(array_merge($info, ['server_id' => $server->id])); - $checkedMetrics++; - } - }); + Server::query() + ->where('status', ServerStatus::READY) + ->whereHas('services', function (Builder $query): void { + $query->where('type', 'monitoring') + ->where('name', 'remote-monitor'); + })->chunk(10, function ($servers) use (&$checkedMetrics): void { + /** @var Server $server */ + foreach ($servers as $server) { + $info = $server->os()->resourceInfo(); + $server->metrics()->create(array_merge($info, ['server_id' => $server->id])); + $checkedMetrics++; + } + }); $this->info("Checked $checkedMetrics metrics"); } } diff --git a/app/Console/Commands/MigrateFromMysqlToSqlite.php b/app/Console/Commands/MigrateFromMysqlToSqlite.php index c75a3bfc..36043708 100644 --- a/app/Console/Commands/MigrateFromMysqlToSqlite.php +++ b/app/Console/Commands/MigrateFromMysqlToSqlite.php @@ -2,6 +2,28 @@ namespace App\Console\Commands; +use App\Models\Backup; +use App\Models\BackupFile; +use App\Models\CronJob; +use App\Models\Database; +use App\Models\DatabaseUser; +use App\Models\Deployment; +use App\Models\DeploymentScript; +use App\Models\FirewallRule; +use App\Models\GitHook; +use App\Models\NotificationChannel; +use App\Models\Project; +use App\Models\Server; +use App\Models\ServerLog; +use App\Models\ServerProvider; +use App\Models\Service; +use App\Models\Site; +use App\Models\SourceControl; +use App\Models\SshKey; +use App\Models\Ssl; +use App\Models\StorageProvider; +use App\Models\User; +use App\Models\Worker; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\File; @@ -26,28 +48,28 @@ public function handle(): void $this->call('migrate', ['--force' => true]); - $this->migrateModel(\App\Models\Backup::class); - $this->migrateModel(\App\Models\BackupFile::class); - $this->migrateModel(\App\Models\CronJob::class); - $this->migrateModel(\App\Models\Database::class); - $this->migrateModel(\App\Models\DatabaseUser::class); - $this->migrateModel(\App\Models\Deployment::class); - $this->migrateModel(\App\Models\DeploymentScript::class); - $this->migrateModel(\App\Models\FirewallRule::class); - $this->migrateModel(\App\Models\GitHook::class); - $this->migrateModel(\App\Models\NotificationChannel::class); - $this->migrateModel(\App\Models\Project::class); - $this->migrateModel(\App\Models\Worker::class); - $this->migrateModel(\App\Models\Server::class); - $this->migrateModel(\App\Models\ServerLog::class); - $this->migrateModel(\App\Models\ServerProvider::class); - $this->migrateModel(\App\Models\Service::class); - $this->migrateModel(\App\Models\Site::class); - $this->migrateModel(\App\Models\SourceControl::class); - $this->migrateModel(\App\Models\SshKey::class); - $this->migrateModel(\App\Models\Ssl::class); - $this->migrateModel(\App\Models\StorageProvider::class); - $this->migrateModel(\App\Models\User::class); + $this->migrateModel(Backup::class); + $this->migrateModel(BackupFile::class); + $this->migrateModel(CronJob::class); + $this->migrateModel(Database::class); + $this->migrateModel(DatabaseUser::class); + $this->migrateModel(Deployment::class); + $this->migrateModel(DeploymentScript::class); + $this->migrateModel(FirewallRule::class); + $this->migrateModel(GitHook::class); + $this->migrateModel(NotificationChannel::class); + $this->migrateModel(Project::class); + $this->migrateModel(Worker::class); + $this->migrateModel(Server::class); + $this->migrateModel(ServerLog::class); + $this->migrateModel(ServerProvider::class); + $this->migrateModel(Service::class); + $this->migrateModel(Site::class); + $this->migrateModel(SourceControl::class); + $this->migrateModel(SshKey::class); + $this->migrateModel(Ssl::class); + $this->migrateModel(StorageProvider::class); + $this->migrateModel(User::class); $env = File::get(base_path('.env')); $env = str_replace('DB_CONNECTION=mysql', 'DB_CONNECTION=sqlite', $env); diff --git a/app/DTOs/DynamicFieldDTO.php b/app/DTOs/DynamicField.php similarity index 94% rename from app/DTOs/DynamicFieldDTO.php rename to app/DTOs/DynamicField.php index 584472f3..7f3dcd10 100644 --- a/app/DTOs/DynamicFieldDTO.php +++ b/app/DTOs/DynamicField.php @@ -2,7 +2,7 @@ namespace App\DTOs; -class DynamicFieldDTO +class DynamicField { /** * @param array|null $options @@ -50,6 +50,13 @@ public function checkbox(): self return $this; } + public function alert(): self + { + $this->type = 'alert'; + + return $this; + } + public function name(string $name): self { $this->name = $name; diff --git a/app/DTOs/DynamicFieldsCollectionDTO.php b/app/DTOs/DynamicForm.php similarity index 60% rename from app/DTOs/DynamicFieldsCollectionDTO.php rename to app/DTOs/DynamicForm.php index dbf20491..62dfb6eb 100644 --- a/app/DTOs/DynamicFieldsCollectionDTO.php +++ b/app/DTOs/DynamicForm.php @@ -2,15 +2,23 @@ namespace App\DTOs; -readonly class DynamicFieldsCollectionDTO +readonly class DynamicForm { /** - * @param array $fields + * @param array $fields */ public function __construct( private array $fields = [], ) {} + /** + * @param array $fields + */ + public static function make(array $fields): self + { + return new self($fields); + } + /** * @return array */ diff --git a/app/Enums/BackupFileStatus.php b/app/Enums/BackupFileStatus.php index 59ac3b55..f361977b 100644 --- a/app/Enums/BackupFileStatus.php +++ b/app/Enums/BackupFileStatus.php @@ -4,17 +4,17 @@ final class BackupFileStatus { - const CREATED = 'created'; + const string CREATED = 'created'; - const CREATING = 'creating'; + const string CREATING = 'creating'; - const FAILED = 'failed'; + const string FAILED = 'failed'; - const DELETING = 'deleting'; + const string DELETING = 'deleting'; - const RESTORING = 'restoring'; + const string RESTORING = 'restoring'; - const RESTORED = 'restored'; + const string RESTORED = 'restored'; - const RESTORE_FAILED = 'restore_failed'; + const string RESTORE_FAILED = 'restore_failed'; } diff --git a/app/Enums/BackupStatus.php b/app/Enums/BackupStatus.php index 428f9a1f..1a2a4cc1 100644 --- a/app/Enums/BackupStatus.php +++ b/app/Enums/BackupStatus.php @@ -4,11 +4,11 @@ final class BackupStatus { - const RUNNING = 'running'; + const string RUNNING = 'running'; - const FAILED = 'failed'; + const string FAILED = 'failed'; - const DELETING = 'deleting'; + const string DELETING = 'deleting'; - const STOPPED = 'stopped'; + const string STOPPED = 'stopped'; } diff --git a/app/Enums/CommandExecutionStatus.php b/app/Enums/CommandExecutionStatus.php index c66d6d9b..893ba4f9 100644 --- a/app/Enums/CommandExecutionStatus.php +++ b/app/Enums/CommandExecutionStatus.php @@ -4,9 +4,9 @@ final class CommandExecutionStatus { - const EXECUTING = 'executing'; + const string EXECUTING = 'executing'; - const COMPLETED = 'completed'; + const string COMPLETED = 'completed'; - const FAILED = 'failed'; + const string FAILED = 'failed'; } diff --git a/app/Enums/CronjobStatus.php b/app/Enums/CronjobStatus.php index 0c84aced..6baef958 100644 --- a/app/Enums/CronjobStatus.php +++ b/app/Enums/CronjobStatus.php @@ -4,17 +4,17 @@ final class CronjobStatus { - const CREATING = 'creating'; + const string CREATING = 'creating'; - const READY = 'ready'; + const string READY = 'ready'; - const DELETING = 'deleting'; + const string DELETING = 'deleting'; - const ENABLING = 'enabling'; + const string ENABLING = 'enabling'; - const DISABLING = 'disabling'; + const string DISABLING = 'disabling'; - const UPDATING = 'updating'; + const string UPDATING = 'updating'; - const DISABLED = 'disabled'; + const string DISABLED = 'disabled'; } diff --git a/app/Enums/Database.php b/app/Enums/Database.php deleted file mode 100644 index ba5ea52c..00000000 --- a/app/Enums/Database.php +++ /dev/null @@ -1,38 +0,0 @@ -json([ 'success' => true, diff --git a/app/Http/Controllers/API/ServerController.php b/app/Http/Controllers/API/ServerController.php index 4dfcd0f1..82e64f9d 100644 --- a/app/Http/Controllers/API/ServerController.php +++ b/app/Http/Controllers/API/ServerController.php @@ -5,10 +5,6 @@ use App\Actions\Server\CreateServer; use App\Actions\Server\RebootServer; use App\Actions\Server\Update; -use App\Enums\Database; -use App\Enums\PHP; -use App\Enums\ServerProvider; -use App\Enums\Webserver; use App\Http\Controllers\Controller; use App\Http\Resources\ServerResource; use App\Models\Project; @@ -45,16 +41,16 @@ public function index(Project $project): ResourceCollection #[Post('/', name: 'api.projects.servers.create', middleware: 'ability:write')] #[Endpoint(title: 'create', description: 'Create a new server.')] #[BodyParam(name: 'provider', description: 'The server provider type', required: true)] - #[BodyParam(name: 'server_provider', description: 'If the provider is not custom, the ID of the server provider profile', enum: [ServerProvider::CUSTOM, ServerProvider::HETZNER, ServerProvider::DIGITALOCEAN, ServerProvider::LINODE, ServerProvider::VULTR])] + #[BodyParam(name: 'server_provider', description: 'If the provider is not custom, the ID of the server provider profile')] #[BodyParam(name: 'region', description: 'Provider region if the provider is not custom')] #[BodyParam(name: 'plan', description: 'Provider plan if the provider is not custom')] #[BodyParam(name: 'ip', description: 'SSH IP address if the provider is custom')] #[BodyParam(name: 'port', description: 'SSH Port if the provider is custom')] #[BodyParam(name: 'name', description: 'The name of the server.', required: true)] #[BodyParam(name: 'os', description: 'The os of the server', required: true)] - #[BodyParam(name: 'webserver', description: 'Web server', required: true, enum: [Webserver::NONE, Webserver::NGINX, Webserver::CADDY])] - #[BodyParam(name: 'database', description: 'Database', required: true, enum: [Database::NONE, Database::MYSQL57, Database::MYSQL80, Database::MARIADB103, Database::MARIADB104, Database::MARIADB103, Database::POSTGRESQL12, Database::POSTGRESQL13, Database::POSTGRESQL14, Database::POSTGRESQL15, Database::POSTGRESQL16], )] - #[BodyParam(name: 'php', description: 'PHP version', required: true, enum: [PHP::V70, PHP::V71, PHP::V72, PHP::V73, PHP::V74, PHP::V80, PHP::V81, PHP::V82, PHP::V83])] + #[BodyParam(name: 'webserver', description: 'Web server', required: true)] + #[BodyParam(name: 'database', description: 'Database', required: true)] + #[BodyParam(name: 'php', description: 'PHP version', required: true)] #[ResponseFromApiResource(ServerResource::class, Server::class)] public function create(Request $request, Project $project): ServerResource { diff --git a/app/Http/Controllers/API/SiteController.php b/app/Http/Controllers/API/SiteController.php index e4b82565..ff89e835 100644 --- a/app/Http/Controllers/API/SiteController.php +++ b/app/Http/Controllers/API/SiteController.php @@ -9,13 +9,13 @@ use App\Actions\Site\UpdateEnv; use App\Actions\Site\UpdateLoadBalancer; use App\Enums\LoadBalancerMethod; -use App\Enums\SiteType; use App\Exceptions\DeploymentScriptIsEmptyException; use App\Http\Controllers\Controller; use App\Http\Resources\SiteResource; use App\Models\Project; use App\Models\Server; use App\Models\Site; +use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\ResourceCollection; use Knuckles\Scribe\Attributes\BodyParam; @@ -49,7 +49,7 @@ public function index(Project $project, Server $server): ResourceCollection #[Post('/', name: 'api.projects.servers.sites.create', middleware: 'ability:write')] #[Endpoint(title: 'create', description: 'Create a new site.')] - #[BodyParam(name: 'type', required: true, enum: [SiteType::PHP, SiteType::PHP_BLANK, SiteType::PHPMYADMIN, SiteType::LARAVEL, SiteType::WORDPRESS, SiteType::LOAD_BALANCER])] + #[BodyParam(name: 'type', required: true)] #[BodyParam(name: 'domain', required: true)] #[BodyParam(name: 'aliases', type: 'array')] #[BodyParam(name: 'php_version', description: 'One of the installed PHP Versions', required: true, example: '7.4')] @@ -172,7 +172,7 @@ public function updateDeploymentScript(Request $request, Project $project, Serve #[Get('{site}/deployment-script', name: 'api.projects.servers.sites.deployment-script.show', middleware: 'ability:read')] #[Endpoint(title: 'deployment-script', description: 'Get site deployment script content')] #[Response(status: 200)] - public function showDeploymentScript(Project $project, Server $server, Site $site): \Illuminate\Http\JsonResponse + public function showDeploymentScript(Project $project, Server $server, Site $site): JsonResponse { $this->authorize('view', [$site, $server]); @@ -190,7 +190,7 @@ public function showDeploymentScript(Project $project, Server $server, Site $sit 'env' => 'APP_NAME=Laravel\nAPP_ENV=production', ], ], status: 200)] - public function showEnv(Project $project, Server $server, Site $site): \Illuminate\Http\JsonResponse + public function showEnv(Project $project, Server $server, Site $site): JsonResponse { $this->authorize('view', [$site, $server]); diff --git a/app/Http/Controllers/API/SourceControlController.php b/app/Http/Controllers/API/SourceControlController.php index 72e435ed..7a5c4bdb 100644 --- a/app/Http/Controllers/API/SourceControlController.php +++ b/app/Http/Controllers/API/SourceControlController.php @@ -42,7 +42,7 @@ public function index(Project $project): ResourceCollection #[Post('/', name: 'api.projects.source-controls.create', middleware: 'ability:write')] #[Endpoint(title: 'create')] - #[BodyParam(name: 'provider', description: 'The provider', required: true, enum: [\App\Enums\SourceControl::GITLAB, \App\Enums\SourceControl::GITHUB, \App\Enums\SourceControl::BITBUCKET])] + #[BodyParam(name: 'provider', description: 'The provider', required: true)] #[BodyParam(name: 'name', description: 'The name of the storage provider.', required: true)] #[BodyParam(name: 'token', description: 'The token if provider requires api token')] #[BodyParam(name: 'url', description: 'The URL if the provider is Gitlab and it is self-hosted')] @@ -61,7 +61,7 @@ public function create(Request $request, Project $project): SourceControlResourc #[Get('{sourceControl}', name: 'api.projects.source-controls.show', middleware: 'ability:read')] #[Endpoint(title: 'show')] #[ResponseFromApiResource(SourceControlResource::class, SourceControl::class)] - public function show(Project $project, SourceControl $sourceControl): \App\Http\Resources\SourceControlResource + public function show(Project $project, SourceControl $sourceControl): SourceControlResource { $this->authorize('view', $sourceControl); @@ -79,7 +79,7 @@ public function show(Project $project, SourceControl $sourceControl): \App\Http\ #[BodyParam(name: 'password', description: 'The password if the provider is Bitbucket')] #[BodyParam(name: 'global', description: 'Accessible in all projects', enum: [true, false])] #[ResponseFromApiResource(SourceControlResource::class, SourceControl::class)] - public function update(Request $request, Project $project, SourceControl $sourceControl): \App\Http\Resources\SourceControlResource + public function update(Request $request, Project $project, SourceControl $sourceControl): SourceControlResource { $this->authorize('update', $sourceControl); diff --git a/app/Http/Controllers/SSLController.php b/app/Http/Controllers/SSLController.php index a70833b9..a5489165 100644 --- a/app/Http/Controllers/SSLController.php +++ b/app/Http/Controllers/SSLController.php @@ -63,7 +63,9 @@ public function enableForceSSL(Server $server, Site $site): RedirectResponse $site->force_ssl = true; $site->save(); - $site->webserver()->updateVHost($site); + $site->webserver()->updateVHost($site, regenerate: [ + 'force-ssl', + ]); return back() ->with('success', 'Force SSL enabled successfully.'); @@ -76,7 +78,9 @@ public function disableForceSSL(Server $server, Site $site): RedirectResponse $site->force_ssl = false; $site->save(); - $site->webserver()->updateVHost($site); + $site->webserver()->updateVHost($site, regenerate: [ + 'force-ssl', + ]); return back() ->with('success', 'Force SSL disabled successfully.'); diff --git a/app/Http/Controllers/SiteFeatureController.php b/app/Http/Controllers/SiteFeatureController.php new file mode 100644 index 00000000..bff57681 --- /dev/null +++ b/app/Http/Controllers/SiteFeatureController.php @@ -0,0 +1,45 @@ +authorize('view', [$site, $server]); + + return Inertia::render('site-features/index', [ + 'features' => $site->features(), + ]); + } + + #[Post('/{feature}/{action}', name: 'site-features.action')] + public function action(Request $request, Server $server, Site $site, string $feature, string $action): RedirectResponse + { + $this->authorize('update', [$site, $server]); + + $handler = config('site.types.'.$site->type.'.features.'.$feature.'.actions.'.$action.'.handler'); + if ($handler && class_exists($handler)) { + /** @var ActionInterface $actionHandler */ + $actionHandler = new $handler($site); + $actionHandler->handle($request); + } + + return back(); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index c520b73c..cee1d15e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,7 +2,37 @@ namespace App\Http; +use App\Http\Middleware\Authenticate; +use App\Http\Middleware\CanSeeProjectMiddleware; +use App\Http\Middleware\EncryptCookies; +use App\Http\Middleware\HandleAppearance; +use App\Http\Middleware\HandleInertiaRequests; +use App\Http\Middleware\HasProjectMiddleware; +use App\Http\Middleware\MustBeAdminMiddleware; +use App\Http\Middleware\PreventRequestsDuringMaintenance; +use App\Http\Middleware\RedirectIfAuthenticated; +use App\Http\Middleware\TrimStrings; +use App\Http\Middleware\TrustProxies; +use App\Http\Middleware\ValidateSignature; +use App\Http\Middleware\VerifyCsrfToken; +use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; +use Illuminate\Auth\Middleware\Authorize; +use Illuminate\Auth\Middleware\EnsureEmailIsVerified; +use Illuminate\Auth\Middleware\RequirePassword; +use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Foundation\Http\Kernel as HttpKernel; +use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; +use Illuminate\Foundation\Http\Middleware\ValidatePostSize; +use Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets; +use Illuminate\Http\Middleware\HandleCors; +use Illuminate\Http\Middleware\SetCacheHeaders; +use Illuminate\Routing\Middleware\SubstituteBindings; +use Illuminate\Routing\Middleware\ThrottleRequests; +use Illuminate\Session\Middleware\AuthenticateSession; +use Illuminate\Session\Middleware\StartSession; +use Illuminate\View\Middleware\ShareErrorsFromSession; +use Laravel\Sanctum\Http\Middleware\CheckAbilities; +use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility; class Kernel extends HttpKernel { @@ -15,12 +45,12 @@ class Kernel extends HttpKernel */ protected $middleware = [ // \App\Http\Middleware\TrustHosts::class, - \App\Http\Middleware\TrustProxies::class, - \Illuminate\Http\Middleware\HandleCors::class, - \App\Http\Middleware\PreventRequestsDuringMaintenance::class, - \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, - \App\Http\Middleware\TrimStrings::class, - \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + TrustProxies::class, + HandleCors::class, + PreventRequestsDuringMaintenance::class, + ValidatePostSize::class, + TrimStrings::class, + ConvertEmptyStringsToNull::class, ]; /** @@ -30,20 +60,20 @@ class Kernel extends HttpKernel */ protected $middlewareGroups = [ 'web' => [ - \App\Http\Middleware\EncryptCookies::class, - \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, - \Illuminate\Session\Middleware\StartSession::class, - \Illuminate\View\Middleware\ShareErrorsFromSession::class, - \App\Http\Middleware\VerifyCsrfToken::class, - \Illuminate\Routing\Middleware\SubstituteBindings::class, - \App\Http\Middleware\HandleInertiaRequests::class, - \App\Http\Middleware\HandleAppearance::class, - \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + ShareErrorsFromSession::class, + VerifyCsrfToken::class, + SubstituteBindings::class, + HandleInertiaRequests::class, + HandleAppearance::class, + AddLinkHeadersForPreloadedAssets::class, ], 'api' => [ - \Illuminate\Routing\Middleware\ThrottleRequests::class.':api', - \Illuminate\Routing\Middleware\SubstituteBindings::class, + ThrottleRequests::class.':api', + SubstituteBindings::class, ], ]; @@ -55,20 +85,20 @@ class Kernel extends HttpKernel * @var array */ protected $middlewareAliases = [ - 'auth' => \App\Http\Middleware\Authenticate::class, - 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, - 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, - 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, - 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, - 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, - 'signed' => \App\Http\Middleware\ValidateSignature::class, - 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, - 'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class, - 'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class, - 'has-project' => \App\Http\Middleware\HasProjectMiddleware::class, - 'can-see-project' => \App\Http\Middleware\CanSeeProjectMiddleware::class, - 'must-be-admin' => \App\Http\Middleware\MustBeAdminMiddleware::class, + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'auth.session' => AuthenticateSession::class, + 'cache.headers' => SetCacheHeaders::class, + 'can' => Authorize::class, + 'guest' => RedirectIfAuthenticated::class, + 'password.confirm' => RequirePassword::class, + 'signed' => ValidateSignature::class, + 'throttle' => ThrottleRequests::class, + 'verified' => EnsureEmailIsVerified::class, + 'abilities' => CheckAbilities::class, + 'ability' => CheckForAnyAbility::class, + 'has-project' => HasProjectMiddleware::class, + 'can-see-project' => CanSeeProjectMiddleware::class, + 'must-be-admin' => MustBeAdminMiddleware::class, ]; } diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 1aa5ee2d..91df4dd8 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -65,7 +65,7 @@ public function share(Request $request): array $sites = SiteResource::collection($server->sites); } - $data['serverSites'] = $sites; + $data['server_sites'] = $sites; if ($request->route('site')) { $data['site'] = SiteResource::make($request->route('site')); @@ -82,15 +82,37 @@ public function share(Request $request): array 'projects' => $user?->allProjects()->get(), 'currentProject' => $user?->currentProject, ], - 'publicKeyText' => __('servers.create.public_key_text', ['public_key' => get_public_key_content()]), - 'projectServers' => $servers, - 'configs' => config('core'), + 'public_key_text' => __('servers.create.public_key_text', ['public_key' => get_public_key_content()]), + 'project_servers' => $servers, + 'configs' => [ + 'operating_systems' => config('core.operating_systems'), + 'colors' => config('core.colors'), + 'cronjob_intervals' => config('core.cronjob_intervals'), + 'metrics_periods' => config('core.metrics_periods'), + 'site' => [ + 'types' => config('site.types'), + ], + 'source_control' => [ + 'providers' => config('source-control.providers'), + ], + 'server_provider' => [ + 'providers' => config('server-provider.providers'), + ], + 'storage_provider' => [ + 'providers' => config('storage-provider.providers'), + ], + 'notification_channel' => [ + 'providers' => config('notification-channel.providers'), + ], + 'service' => [ + 'services' => config('service.services'), + ], + ], 'ziggy' => fn (): array => [ ...(new Ziggy)->toArray(), 'location' => $request->url(), ], 'csrf_token' => csrf_token(), - 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', 'flash' => [ 'success' => fn () => $request->session()->get('success'), 'error' => fn () => $request->session()->get('error'), diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index fef34bdd..fd952703 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -13,7 +13,7 @@ class RedirectIfAuthenticated /** * Handle an incoming request. * - * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next + * @param Closure(Request): (Response) $next */ public function handle(Request $request, Closure $next, string ...$guards): Response { diff --git a/app/Http/Resources/ServerResource.php b/app/Http/Resources/ServerResource.php index f3676525..7547369f 100644 --- a/app/Http/Resources/ServerResource.php +++ b/app/Http/Resources/ServerResource.php @@ -27,8 +27,6 @@ public function toArray(Request $request): array 'local_ip' => $this->local_ip, 'port' => $this->port, 'os' => $this->os, - 'type' => $this->type, - 'type_data' => $this->type_data, 'provider' => $this->provider, 'provider_data' => $this->provider_data, 'public_key' => $this->public_key, diff --git a/app/Http/Resources/SiteResource.php b/app/Http/Resources/SiteResource.php index c55e67fb..e8025ec0 100644 --- a/app/Http/Resources/SiteResource.php +++ b/app/Http/Resources/SiteResource.php @@ -21,11 +21,10 @@ public function toArray(Request $request): array 'source_control_id' => $this->source_control_id, 'type' => $this->type, 'type_data' => $this->type_data, - 'features' => $this->type()->supportedFeatures(), 'domain' => $this->domain, 'aliases' => $this->aliases, 'web_directory' => $this->web_directory, - 'webserver' => $this->webserver()->name(), + 'webserver' => $this->webserver()->id(), 'path' => $this->path, 'php_version' => $this->php_version, 'repository' => $this->repository, diff --git a/app/Http/Resources/WorkerResource.php b/app/Http/Resources/WorkerResource.php index a67c8b79..64d95748 100644 --- a/app/Http/Resources/WorkerResource.php +++ b/app/Http/Resources/WorkerResource.php @@ -17,6 +17,7 @@ public function toArray(Request $request): array return [ 'id' => $this->id, 'server_id' => $this->server_id, + 'name' => $this->name, 'command' => $this->command, 'user' => $this->user, 'auto_start' => $this->auto_start, diff --git a/app/Models/BackupFile.php b/app/Models/BackupFile.php index 90fa00f9..dd427414 100644 --- a/app/Models/BackupFile.php +++ b/app/Models/BackupFile.php @@ -4,7 +4,10 @@ use App\Actions\Database\ManageBackupFile; use App\Enums\BackupFileStatus; -use App\Enums\StorageProvider as StorageProviderAlias; +use App\StorageProviders\Dropbox; +use App\StorageProviders\FTP; +use App\StorageProviders\Local; +use App\StorageProviders\S3; use Carbon\Carbon; use Database\Factories\BackupFileFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -81,7 +84,7 @@ public function isAvailable(): bool public function isLocal(): bool { - return $this->backup->storage->provider === StorageProviderAlias::LOCAL; + return $this->backup->storage->provider === Local::id(); } /** @@ -103,8 +106,8 @@ public function path(): string $databaseName = $this->backup->database->name; return match ($storage->provider) { - StorageProviderAlias::DROPBOX => '/'.$databaseName.'/'.$this->name.'.zip', - StorageProviderAlias::S3, StorageProviderAlias::FTP, StorageProviderAlias::LOCAL => implode('/', [ + Dropbox::id() => '/'.$databaseName.'/'.$this->name.'.zip', + S3::id(), FTP::id(), Local::id() => implode('/', [ rtrim((string) $storage->credentials['path'], '/'), $databaseName, $this->name.'.zip', diff --git a/app/Models/CronJob.php b/app/Models/CronJob.php index 80d44ab1..2d5737bc 100755 --- a/app/Models/CronJob.php +++ b/app/Models/CronJob.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\CronjobStatus; +use Database\Factories\CronJobFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -18,7 +19,7 @@ */ class CronJob extends AbstractModel { - /** @use HasFactory<\Database\Factories\CronJobFactory> */ + /** @use HasFactory */ use HasFactory; protected $fillable = [ diff --git a/app/Models/DatabaseUser.php b/app/Models/DatabaseUser.php index 4af54fac..171fbd4d 100755 --- a/app/Models/DatabaseUser.php +++ b/app/Models/DatabaseUser.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\DatabaseUserStatus; +use Database\Factories\DatabaseUserFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -17,7 +18,7 @@ */ class DatabaseUser extends AbstractModel { - /** @use HasFactory<\Database\Factories\DatabaseUserFactory> */ + /** @use HasFactory */ use HasFactory; protected $fillable = [ diff --git a/app/Models/File.php b/app/Models/File.php index 34b57d35..9b639787 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -2,6 +2,7 @@ namespace App\Models; +use Database\Factories\FileFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -23,7 +24,7 @@ */ class File extends AbstractModel { - /** @use HasFactory<\Database\Factories\FileFactory> */ + /** @use HasFactory */ use HasFactory; protected $fillable = [ diff --git a/app/Models/GitHook.php b/app/Models/GitHook.php index ffca9ab3..4d8c4230 100755 --- a/app/Models/GitHook.php +++ b/app/Models/GitHook.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Exceptions\FailedToDestroyGitHook; +use Database\Factories\GitHookFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -19,7 +20,7 @@ */ class GitHook extends AbstractModel { - /** @use HasFactory<\Database\Factories\GitHookFactory> */ + /** @use HasFactory */ use HasFactory; protected $fillable = [ diff --git a/app/Models/NotificationChannel.php b/app/Models/NotificationChannel.php index 33c422d1..f5f92d03 100644 --- a/app/Models/NotificationChannel.php +++ b/app/Models/NotificationChannel.php @@ -42,7 +42,7 @@ class NotificationChannel extends AbstractModel public function provider(): \App\NotificationChannels\NotificationChannel { - $class = config('core.notification_channels_providers_class')[$this->provider]; + $class = config('notification-channel.providers.'.$this->provider.'.handler'); /** @var \App\NotificationChannels\NotificationChannel $provider */ $provider = new $class($this); diff --git a/app/Models/Script.php b/app/Models/Script.php index d7c1f8a5..55b5adf6 100644 --- a/app/Models/Script.php +++ b/app/Models/Script.php @@ -3,6 +3,7 @@ namespace App\Models; use Carbon\Carbon; +use Database\Factories\ScriptFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -25,7 +26,7 @@ */ class Script extends AbstractModel { - /** @use HasFactory<\Database\Factories\ScriptFactory> */ + /** @use HasFactory */ use HasFactory; protected $fillable = [ diff --git a/app/Models/ScriptExecution.php b/app/Models/ScriptExecution.php index 88cb27cb..f0caaa31 100644 --- a/app/Models/ScriptExecution.php +++ b/app/Models/ScriptExecution.php @@ -4,6 +4,7 @@ use App\Enums\ScriptExecutionStatus; use Carbon\Carbon; +use Database\Factories\ScriptExecutionFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -23,7 +24,7 @@ */ class ScriptExecution extends AbstractModel { - /** @use HasFactory<\Database\Factories\ScriptExecutionFactory> */ + /** @use HasFactory */ use HasFactory; protected $fillable = [ diff --git a/app/Models/Server.php b/app/Models/Server.php index 4f1a64ce..127dd944 100755 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -7,10 +7,9 @@ use App\Enums\ServiceStatus; use App\Exceptions\SSHError; use App\Facades\SSH; -use App\ServerTypes\ServerType; -use App\SSH\Cron\Cron; +use App\SSH\OS\Cron; use App\SSH\OS\OS; -use App\SSH\Systemd\Systemd; +use App\SSH\OS\Systemd; use App\Support\Testing\SSHFake; use Carbon\Carbon; use Database\Factories\ServerFactory; @@ -81,8 +80,6 @@ class Server extends AbstractModel 'local_ip', 'port', 'os', - 'type', - 'type_data', 'provider', 'provider_id', 'provider_data', @@ -101,7 +98,6 @@ class Server extends AbstractModel protected $casts = [ 'project_id' => 'integer', 'user_id' => 'integer', - 'type_data' => 'json', 'port' => 'integer', 'provider_data' => 'json', 'authentication' => 'encrypted:json', @@ -410,19 +406,9 @@ public function installedNodejsVersions(): array return $versions; } - public function type(): ServerType - { - $typeClass = config('core.server_types_class')[$this->type]; - - /** @var ServerType $type */ - $type = new $typeClass($this); - - return $type; - } - public function provider(): \App\ServerProviders\ServerProvider { - $providerClass = config('core.server_providers_class')[$this->provider]; + $providerClass = config('server-provider.providers.'.$this->provider.'.handler'); /** @var \App\ServerProviders\ServerProvider $provider */ $provider = new $providerClass($this->serverProvider ?? new ServerProvider, $this); diff --git a/app/Models/ServerProvider.php b/app/Models/ServerProvider.php index 6bf2f84e..a44cbbce 100644 --- a/app/Models/ServerProvider.php +++ b/app/Models/ServerProvider.php @@ -67,7 +67,7 @@ public function servers(): HasMany public function provider(): \App\ServerProviders\ServerProvider { - $providerClass = config('core.server_providers_class')[$this->provider]; + $providerClass = config('server-provider.providers.'.$this->provider.'.handler'); /** @var \App\ServerProviders\ServerProvider $provider */ $provider = new $providerClass($this, new Server); diff --git a/app/Models/Service.php b/app/Models/Service.php index 5a529003..4aad15d6 100755 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -5,15 +5,16 @@ use App\Actions\Service\Manage; use App\Enums\ServiceStatus; use App\Exceptions\ServiceInstallationFailed; -use App\SSH\Services\Firewall\Firewall; -use App\SSH\Services\PHP\PHP; -use App\SSH\Services\ProcessManager\ProcessManager; -use App\SSH\Services\ServiceInterface; -use App\SSH\Services\Webserver\Webserver; +use App\Services\Firewall\Firewall; +use App\Services\PHP\PHP; +use App\Services\ProcessManager\ProcessManager; +use App\Services\ServiceInterface; +use App\Services\Webserver\Webserver; use Database\Factories\ServiceFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Str; +use InvalidArgumentException; /** * @property int $server_id @@ -50,17 +51,6 @@ class Service extends AbstractModel 'is_default' => 'boolean', ]; - public static function boot(): void - { - parent::boot(); - - static::creating(function (Service $service): void { - if (array_key_exists($service->name, config('core.service_units'))) { - $service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version]; - } - }); - } - /** * @var array */ @@ -87,9 +77,14 @@ public function server(): BelongsTo return $this->belongsTo(Server::class); } - public function handler(): ServiceInterface|Webserver|PHP|Firewall|\App\SSH\Services\Database\Database|ProcessManager + public function handler(): ServiceInterface|Webserver|PHP|Firewall|\App\Services\Database\Database|ProcessManager { - $handler = config('core.service_handlers')[$this->name]; + $name = $this->name; + $handler = config("service.services.$name.handler"); + + if (! $handler) { + throw new InvalidArgumentException("Service handler for $name is not defined."); + } /** @var ServiceInterface $service */ $service = new $handler($this); @@ -109,26 +104,26 @@ public function validateInstall(string $result): void public function start(): void { - $this->unit && app(Manage::class)->start($this); + $this->handler()->unit() && app(Manage::class)->start($this); } public function stop(): void { - $this->unit && app(Manage::class)->stop($this); + $this->handler()->unit() && app(Manage::class)->stop($this); } public function restart(): void { - $this->unit && app(Manage::class)->restart($this); + $this->handler()->unit() && app(Manage::class)->restart($this); } public function enable(): void { - $this->unit && app(Manage::class)->enable($this); + $this->handler()->unit() && app(Manage::class)->enable($this); } public function disable(): void { - $this->unit && app(Manage::class)->disable($this); + $this->handler()->unit() && app(Manage::class)->disable($this); } } diff --git a/app/Models/Site.php b/app/Models/Site.php index 9bdd9708..1bdaee39 100755 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -8,9 +8,10 @@ use App\Exceptions\FailedToDestroyGitHook; use App\Exceptions\SourceControlIsNotConnected; use App\Exceptions\SSHError; +use App\Services\PHP\PHP; +use App\Services\Webserver\Webserver; +use App\SiteFeatures\ActionInterface; use App\SiteTypes\SiteType; -use App\SSH\Services\PHP\PHP; -use App\SSH\Services\Webserver\Webserver; use App\Traits\HasProjectThroughServer; use Database\Factories\SiteFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -20,6 +21,7 @@ use Illuminate\Database\Eloquent\Relations\MorphToMany; use Illuminate\Support\Collection; use Illuminate\Support\Str; +use RuntimeException; /** * @property int $server_id @@ -237,12 +239,15 @@ public function getAliasesString(): string public function type(): SiteType { - $typeClass = config('core.site_types_class.'.$this->type); + $handlerClass = config('site.types.'.$this->type.'.handler'); + if (! class_exists($handlerClass)) { + throw new RuntimeException("Site type handler class {$handlerClass} does not exist."); + } - /** @var SiteType $type */ - $type = new $typeClass($this); + /** @var SiteType $handler */ + $handler = new $handlerClass($this); - return $type; + return $handler; } public function php(): ?Service @@ -352,11 +357,6 @@ public function getSshKeyName(): string return str('site_'.$this->id)->toString(); } - public function hasFeature(string $feature): bool - { - return in_array($feature, $this->type()->supportedFeatures()); - } - public function getEnv(): string { try { @@ -438,4 +438,25 @@ public function activeRedirects(): HasMany { return $this->redirects()->whereIn('status', [RedirectStatus::CREATING, RedirectStatus::READY]); } + + /** + * @return array + */ + public function features(): array + { + $features = config('site.types.'.$this->type.'.features', []); + foreach ($features as $featureKey => $feature) { + foreach ($feature['actions'] ?? [] as $actionKey => $action) { + $handlerClass = $action['handler'] ?? null; + if ($handlerClass && class_exists($handlerClass)) { + /** @var ActionInterface $handler */ + $handler = new $handlerClass($this); + $action['active'] = $handler->active(); + } + $features[$featureKey]['actions'][$actionKey] = $action; + } + } + + return $features; + } } diff --git a/app/Models/SourceControl.php b/app/Models/SourceControl.php index 0df84043..e36748f9 100755 --- a/app/Models/SourceControl.php +++ b/app/Models/SourceControl.php @@ -3,6 +3,7 @@ namespace App\Models; use App\SourceControlProviders\SourceControlProvider; +use Database\Factories\SourceControlFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -19,7 +20,7 @@ */ class SourceControl extends AbstractModel { - /** @use HasFactory<\Database\Factories\SourceControlFactory> */ + /** @use HasFactory */ use HasFactory; use SoftDeletes; @@ -41,7 +42,7 @@ class SourceControl extends AbstractModel public function provider(): SourceControlProvider { - $providerClass = config('core.source_control_providers_class')[$this->provider]; + $providerClass = config('source-control.providers.'.$this->provider.'.handler'); /** @var SourceControlProvider $provider */ $provider = new $providerClass($this); diff --git a/app/Models/StorageProvider.php b/app/Models/StorageProvider.php index fb834153..3e443882 100644 --- a/app/Models/StorageProvider.php +++ b/app/Models/StorageProvider.php @@ -45,10 +45,10 @@ public function user(): BelongsTo public function provider(): \App\StorageProviders\StorageProvider { - $providerClass = config('core.storage_providers_class')[$this->provider]; + $providerClass = config('storage-provider.providers.'.$this->provider.'.handler'); /** @var \App\StorageProviders\StorageProvider $provider */ - $provider = new $providerClass($this); + $provider = new $providerClass($this, new Server); return $provider; } diff --git a/app/Models/Worker.php b/app/Models/Worker.php index e951d2e7..f4b4a22c 100644 --- a/app/Models/Worker.php +++ b/app/Models/Worker.php @@ -3,7 +3,7 @@ namespace App\Models; use App\Enums\WorkerStatus; -use App\SSH\Services\ProcessManager\ProcessManager; +use App\Services\ProcessManager\ProcessManager; use Database\Factories\WorkerFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -21,6 +21,7 @@ * @property int $redirect_stderr * @property string $stdout_logfile * @property string $status + * @property string $name * @property Server $server * @property Site $site */ @@ -40,6 +41,7 @@ class Worker extends AbstractModel 'redirect_stderr', 'stdout_logfile', 'status', + 'name', ]; protected $casts = [ diff --git a/app/NotificationChannels/Discord.php b/app/NotificationChannels/Discord.php index be4197de..21f8704e 100644 --- a/app/NotificationChannels/Discord.php +++ b/app/NotificationChannels/Discord.php @@ -8,6 +8,11 @@ class Discord extends AbstractNotificationChannel { + public static function id(): string + { + return 'discord'; + } + public function createRules(array $input): array { return [ diff --git a/app/NotificationChannels/Email.php b/app/NotificationChannels/Email.php index 389d5052..cdff1e28 100644 --- a/app/NotificationChannels/Email.php +++ b/app/NotificationChannels/Email.php @@ -10,6 +10,11 @@ class Email extends AbstractNotificationChannel { + public static function id(): string + { + return 'email'; + } + public function createRules(array $input): array { return [ diff --git a/app/NotificationChannels/NotificationChannel.php b/app/NotificationChannels/NotificationChannel.php index 8c9fcbdc..c7eeba7d 100644 --- a/app/NotificationChannels/NotificationChannel.php +++ b/app/NotificationChannels/NotificationChannel.php @@ -6,6 +6,8 @@ interface NotificationChannel { + public static function id(): string; + /** * @param array $input * @return array diff --git a/app/NotificationChannels/Slack.php b/app/NotificationChannels/Slack.php index 4f7763cd..e1c05c5a 100644 --- a/app/NotificationChannels/Slack.php +++ b/app/NotificationChannels/Slack.php @@ -8,6 +8,11 @@ class Slack extends AbstractNotificationChannel { + public static function id(): string + { + return 'slack'; + } + public function createRules(array $input): array { return [ diff --git a/app/NotificationChannels/Telegram.php b/app/NotificationChannels/Telegram.php index 0ddae2ea..9a1f253d 100644 --- a/app/NotificationChannels/Telegram.php +++ b/app/NotificationChannels/Telegram.php @@ -11,6 +11,11 @@ class Telegram extends AbstractNotificationChannel { protected string $apiUrl = 'https://api.telegram.org/bot'; + public static function id(): string + { + return 'telegram'; + } + public function createRules(array $input): array { return [ diff --git a/app/Plugins/RegisterNotificationChannel.php b/app/Plugins/RegisterNotificationChannel.php new file mode 100644 index 00000000..7d2dd365 --- /dev/null +++ b/app/Plugins/RegisterNotificationChannel.php @@ -0,0 +1,61 @@ +name = $name; + + return $this; + } + + public function label(string $label): self + { + $this->label = $label; + + return $this; + } + + public function handler(string $handler): self + { + $this->handler = $handler; + + return $this; + } + + public function form(DynamicForm $form): self + { + $this->form = $form; + + return $this; + } + + public function register(): void + { + $providers = config('notification-channel.providers'); + + $providers[$this->name] = [ + 'label' => $this->label, + 'handler' => $this->handler, + 'form' => $this->form ? $this->form->toArray() : [], + ]; + + config(['notification-channel.providers' => $providers]); + } +} diff --git a/app/Plugins/RegisterServerProvider.php b/app/Plugins/RegisterServerProvider.php new file mode 100644 index 00000000..287eb85f --- /dev/null +++ b/app/Plugins/RegisterServerProvider.php @@ -0,0 +1,70 @@ +name = $name; + + return $this; + } + + public function label(string $label): self + { + $this->label = $label; + + return $this; + } + + public function handler(string $handler): self + { + $this->handler = $handler; + + return $this; + } + + public function form(DynamicForm $form): self + { + $this->form = $form; + + return $this; + } + + public function defaultUser(string $defaultUser): self + { + $this->defaultUser = $defaultUser; + + return $this; + } + + public function register(): void + { + $providers = config('server-provider.providers'); + + $providers[$this->name] = [ + 'label' => $this->label, + 'handler' => $this->handler, + 'form' => $this->form ? $this->form->toArray() : [], + 'default_user' => $this->defaultUser, + ]; + + config(['server-provider.providers' => $providers]); + } +} diff --git a/app/Plugins/RegisterServiceType.php b/app/Plugins/RegisterServiceType.php new file mode 100644 index 00000000..6b682bd9 --- /dev/null +++ b/app/Plugins/RegisterServiceType.php @@ -0,0 +1,112 @@ + $versions + * @param array $data + */ + public function __construct( + private string $name, + private string $type = '', + private string $unit = '', + private string $label = '', + private string $handler = '', + private ?DynamicForm $form = null, + private array $versions = ['latest'], + private array $data = [] + ) {} + + public static function make(string $name): self + { + return new self($name); + } + + public function name(string $name): self + { + $this->name = $name; + + return $this; + } + + public function type(string $type): self + { + $this->type = $type; + + return $this; + } + + public function unit(string $unit): self + { + $this->unit = $unit; + + return $this; + } + + public function label(string $label): self + { + $this->label = $label; + + return $this; + } + + public function handler(string $handler): self + { + $this->handler = $handler; + + return $this; + } + + public function form(DynamicForm $form): self + { + $this->form = $form; + + return $this; + } + + /** + * @param array $versions + */ + public function versions(array $versions): self + { + $this->versions = $versions; + + return $this; + } + + /** + * @param array $data + */ + public function data(array $data): self + { + $this->data = $data; + + return $this; + } + + public function register(): void + { + $types = config('service.services'); + + if (! $this->type) { + throw new InvalidArgumentException('Service type must be specified.'); + } + + $types[$this->name] = [ + 'type' => $this->type, + 'unit' => $this->unit, + 'label' => $this->label, + 'handler' => $this->handler, + 'form' => $this->form ? $this->form->toArray() : [], + 'versions' => $this->versions, + 'data' => $this->data, + ]; + + config(['service.services' => $types]); + } +} diff --git a/app/Plugins/RegisterSiteFeature.php b/app/Plugins/RegisterSiteFeature.php new file mode 100644 index 00000000..3a9523fc --- /dev/null +++ b/app/Plugins/RegisterSiteFeature.php @@ -0,0 +1,54 @@ +label = $label; + + return $this; + } + + public function description(string $description): self + { + $this->description = $description; + + return $this; + } + + public function register(): void + { + if (! config()->has('site.types.'.$this->type)) { + throw new RuntimeException('Site types not found'); + } + + $features = config('site.types.'.$this->type.'.features') ?? []; + + if (isset($features[$this->name])) { + throw new RuntimeException("Feature '{$this->name}' already exists for type '{$this->type}'"); + } + + $features[$this->name] = [ + 'label' => $this->label, + 'description' => $this->description, + ]; + + config(['site.types.'.$this->type.'.features' => $features]); + } +} diff --git a/app/Plugins/RegisterSiteFeatureAction.php b/app/Plugins/RegisterSiteFeatureAction.php new file mode 100644 index 00000000..d6ff2806 --- /dev/null +++ b/app/Plugins/RegisterSiteFeatureAction.php @@ -0,0 +1,70 @@ +label = $label; + + return $this; + } + + public function handler(string $handler): self + { + $this->handler = $handler; + + return $this; + } + + public function form(DynamicForm $form): self + { + $this->form = $form; + + return $this; + } + + public function register(): void + { + if (! config()->has('site.types.'.$this->type)) { + throw new RuntimeException('Site types not found'); + } + + $feature = config('site.types.'.$this->type.'.features.'.$this->feature); + + if (! $feature) { + throw new RuntimeException("Feature '{$this->feature}' not found for type '{$this->type}'"); + } + + $actions = $feature['actions'] ?? []; + if (isset($actions[$this->name])) { + throw new RuntimeException("Action '{$this->name}' already exists for feature '{$this->feature}' in type '{$this->type}'"); + } + + $actions[$this->name] = [ + 'label' => $this->label, + 'handler' => $this->handler, + 'form' => $this->form ? $this->form->toArray() : [], + ]; + + config(['site.types.'.$this->type.'.features.'.$this->feature.'.actions' => $actions]); + } +} diff --git a/app/Plugins/RegisterSiteType.php b/app/Plugins/RegisterSiteType.php new file mode 100644 index 00000000..582abf9b --- /dev/null +++ b/app/Plugins/RegisterSiteType.php @@ -0,0 +1,54 @@ +label = $label; + + return $this; + } + + public function handler(string $handler): self + { + $this->handler = $handler; + + return $this; + } + + public function form(DynamicForm $form): self + { + $this->form = $form; + + return $this; + } + + public function register(): void + { + $types = config('site.types'); + + $types[$this->name] = [ + 'label' => $this->label, + 'handler' => $this->handler, + 'form' => $this->form ? $this->form->toArray() : [], + ]; + + config(['site.types' => $types]); + } +} diff --git a/app/Plugins/RegisterSourceControl.php b/app/Plugins/RegisterSourceControl.php new file mode 100644 index 00000000..314f2798 --- /dev/null +++ b/app/Plugins/RegisterSourceControl.php @@ -0,0 +1,61 @@ +name = $name; + + return $this; + } + + public function label(string $label): self + { + $this->label = $label; + + return $this; + } + + public function handler(string $handler): self + { + $this->handler = $handler; + + return $this; + } + + public function form(DynamicForm $form): self + { + $this->form = $form; + + return $this; + } + + public function register(): void + { + $providers = config('source-control.providers'); + + $providers[$this->name] = [ + 'label' => $this->label, + 'handler' => $this->handler, + 'form' => $this->form ? $this->form->toArray() : [], + ]; + + config(['source-control.providers' => $providers]); + } +} diff --git a/app/Plugins/RegisterStorageProvider.php b/app/Plugins/RegisterStorageProvider.php new file mode 100644 index 00000000..b867583b --- /dev/null +++ b/app/Plugins/RegisterStorageProvider.php @@ -0,0 +1,61 @@ +name = $name; + + return $this; + } + + public function label(string $label): self + { + $this->label = $label; + + return $this; + } + + public function handler(string $handler): self + { + $this->handler = $handler; + + return $this; + } + + public function form(DynamicForm $form): self + { + $this->form = $form; + + return $this; + } + + public function register(): void + { + $providers = config('storage-provider.providers'); + + $providers[$this->name] = [ + 'label' => $this->label, + 'handler' => $this->handler, + 'form' => $this->form ? $this->form->toArray() : [], + ]; + + config(['storage-provider.providers' => $providers]); + } +} diff --git a/app/Policies/ServicePolicy.php b/app/Policies/ServicePolicy.php index eaddbd96..bb33781a 100644 --- a/app/Policies/ServicePolicy.php +++ b/app/Policies/ServicePolicy.php @@ -38,26 +38,26 @@ public function delete(User $user, Service $service): bool public function start(User $user, Service $service): bool { - return $this->update($user, $service) && $service->unit; + return $this->update($user, $service); } public function stop(User $user, Service $service): bool { - return $this->update($user, $service) && $service->unit; + return $this->update($user, $service); } public function restart(User $user, Service $service): bool { - return $this->update($user, $service) && $service->unit; + return $this->update($user, $service); } public function disable(User $user, Service $service): bool { - return $this->update($user, $service) && $service->unit; + return $this->update($user, $service); } public function enable(User $user, Service $service): bool { - return $this->update($user, $service) && $service->unit; + return $this->update($user, $service); } } diff --git a/app/Providers/DemoServiceProvider.php b/app/Providers/DemoServiceProvider.php index a1df1b43..63268e04 100644 --- a/app/Providers/DemoServiceProvider.php +++ b/app/Providers/DemoServiceProvider.php @@ -3,6 +3,11 @@ namespace App\Providers; use App\Facades\SSH; +use App\Models\FirewallRule; +use App\Models\PersonalAccessToken; +use App\Models\Script; +use App\Models\ScriptExecution; +use App\Models\ServerLog; use App\Models\User; use Illuminate\Support\Facades\Http; use Illuminate\Support\ServiceProvider; @@ -22,20 +27,20 @@ class DemoServiceProvider extends ServiceProvider * @var string[] */ protected array $canUpdate = [ - \App\Models\ServerLog::class, - \App\Models\Script::class, - \App\Models\ScriptExecution::class, + ServerLog::class, + Script::class, + ScriptExecution::class, ]; /** * @var string[] */ protected array $canCreate = [ - \App\Models\ServerLog::class, - \App\Models\Script::class, - \App\Models\ScriptExecution::class, - \App\Models\FirewallRule::class, - \App\Models\PersonalAccessToken::class, + ServerLog::class, + Script::class, + ScriptExecution::class, + FirewallRule::class, + PersonalAccessToken::class, ]; public function register(): void diff --git a/app/Providers/NotificationChannelServiceProvider.php b/app/Providers/NotificationChannelServiceProvider.php new file mode 100644 index 00000000..36db49c1 --- /dev/null +++ b/app/Providers/NotificationChannelServiceProvider.php @@ -0,0 +1,88 @@ +discord(); + $this->slack(); + $this->email(); + $this->telegram(); + } + + private function discord(): void + { + RegisterNotificationChannel::make(Discord::id()) + ->label('Discord') + ->handler(Discord::class) + ->form( + DynamicForm::make([ + DynamicField::make('webhook_url') + ->text() + ->label('Webhook URL'), + ]) + ) + ->register(); + } + + public function slack(): void + { + RegisterNotificationChannel::make(Slack::id()) + ->label('Slack') + ->handler(Slack::class) + ->form( + DynamicForm::make([ + DynamicField::make('webhook_url') + ->text() + ->label('Webhook URL'), + ]) + ) + ->register(); + } + + private function email(): void + { + RegisterNotificationChannel::make(Email::id()) + ->label('Email') + ->handler(Email::class) + ->form( + DynamicForm::make([ + DynamicField::make('email') + ->text() + ->label('Email address'), + ]) + ) + ->register(); + } + + private function telegram(): void + { + RegisterNotificationChannel::make(Telegram::id()) + ->label('Telegram') + ->handler(Telegram::class) + ->form( + DynamicForm::make([ + DynamicField::make('bot_token') + ->text() + ->label('Bot Token'), + DynamicField::make('chat_id') + ->text() + ->label('Chat ID'), + ]), + ) + ->register(); + } +} diff --git a/app/Providers/ServerProviderServiceProvider.php b/app/Providers/ServerProviderServiceProvider.php new file mode 100644 index 00000000..882d2b57 --- /dev/null +++ b/app/Providers/ServerProviderServiceProvider.php @@ -0,0 +1,120 @@ +custom(); + $this->aws(); + $this->hetzner(); + $this->digitalOcean(); + $this->linode(); + $this->vultr(); + } + + private function custom(): void + { + RegisterServerProvider::make(Custom::id()) + ->label('Custom') + ->handler(Custom::class) + ->register(); + } + + private function aws(): void + { + RegisterServerProvider::make(AWS::id()) + ->label('AWS') + ->handler(AWS::class) + ->form( + DynamicForm::make([ + DynamicField::make('key') + ->text() + ->label('Access Key'), + DynamicField::make('secret') + ->text() + ->label('Secret'), + ]) + ) + ->defaultUser('ubuntu') + ->register(); + } + + private function hetzner(): void + { + RegisterServerProvider::make(Hetzner::id()) + ->label('Hetzner') + ->handler(Hetzner::class) + ->form( + DynamicForm::make([ + DynamicField::make('token') + ->text() + ->label('Token'), + ]) + ) + ->defaultUser('root') + ->register(); + } + + private function digitalOcean(): void + { + RegisterServerProvider::make(DigitalOcean::id()) + ->label('DigitalOcean') + ->handler(DigitalOcean::class) + ->form( + DynamicForm::make([ + DynamicField::make('token') + ->text() + ->label('Token'), + ]) + ) + ->defaultUser('root') + ->register(); + } + + private function linode(): void + { + RegisterServerProvider::make(Linode::id()) + ->label('Linode') + ->handler(Linode::class) + ->form( + DynamicForm::make([ + DynamicField::make('token') + ->text() + ->label('Token'), + ]) + ) + ->defaultUser('root') + ->register(); + } + + private function vultr(): void + { + RegisterServerProvider::make(Vultr::id()) + ->label('Vultr') + ->handler(Vultr::class) + ->form( + DynamicForm::make([ + DynamicField::make('token') + ->text() + ->label('Token'), + ]) + ) + ->defaultUser('root') + ->register(); + } +} diff --git a/app/Providers/ServiceTypeServiceProvider.php b/app/Providers/ServiceTypeServiceProvider.php new file mode 100644 index 00000000..0b5a2bd4 --- /dev/null +++ b/app/Providers/ServiceTypeServiceProvider.php @@ -0,0 +1,180 @@ +webservers(); + $this->databases(); + $this->memoryDatabases(); + $this->firewalls(); + $this->processManagers(); + $this->monitoring(); + $this->php(); + $this->node(); + } + + private function webservers(): void + { + RegisterServiceType::make(Nginx::id()) + ->type(Nginx::type()) + ->label('Nginx') + ->handler(Nginx::class) + ->register(); + + RegisterServiceType::make(Caddy::id()) + ->type(Caddy::type()) + ->label('Caddy (beta)') + ->handler(Caddy::class) + ->register(); + } + + private function databases(): void + { + RegisterServiceType::make(Mysql::id()) + ->type(Mysql::type()) + ->label('MySQL') + ->handler(Mysql::class) + ->versions([ + '8.4', + '8.0', + '5.7', + ]) + ->register(); + RegisterServiceType::make(Postgresql::id()) + ->type(Postgresql::type()) + ->label('PostgreSQL') + ->handler(Postgresql::class) + ->versions([ + '16', + '15', + '14', + '13', + '12', + ]) + ->register(); + RegisterServiceType::make(Mariadb::id()) + ->type(Mariadb::type()) + ->label('MariaDB') + ->handler(Mariadb::class) + ->versions([ + '11.4', + '10.11', + '10.6', + '10.4', + '10.3', + ]) + ->register(); + } + + private function memoryDatabases(): void + { + RegisterServiceType::make(Redis::id()) + ->type(Redis::type()) + ->label('Redis') + ->handler(Redis::class) + ->register(); + } + + private function firewalls(): void + { + RegisterServiceType::make(Ufw::id()) + ->type(Ufw::type()) + ->label('UFW') + ->handler(Ufw::class) + ->register(); + } + + private function processManagers(): void + { + RegisterServiceType::make(Supervisor::id()) + ->type(Supervisor::type()) + ->label('Supervisor') + ->handler(Supervisor::class) + ->register(); + } + + private function monitoring(): void + { + RegisterServiceType::make(VitoAgent::id()) + ->type(VitoAgent::type()) + ->label('VitoAgent') + ->handler(VitoAgent::class) + ->register(); + + RegisterServiceType::make(RemoteMonitor::id()) + ->type(RemoteMonitor::type()) + ->label('RemoteMonitor') + ->handler(RemoteMonitor::class) + ->register(); + } + + private function php(): void + { + RegisterServiceType::make(PHP::id()) + ->type(PHP::type()) + ->label('PHP') + ->handler(PHP::class) + ->versions([ + '8.4', + '8.3', + '8.2', + '8.1', + '8.0', + '7.4', + '7.3', + '7.2', + '7.1', + '7.0', + '5.6', + ]) + ->data([ + 'extensions' => [ + 'imagick', + 'exif', + 'gmagick', + 'gmp', + 'intl', + 'sqlite3', + 'opcache', + ], + ]) + ->register(); + } + + private function node(): void + { + RegisterServiceType::make(NodeJS::id()) + ->type(NodeJS::type()) + ->label('Node.js') + ->handler(NodeJS::class) + ->versions([ + '22', + '20', + '18', + '16', + '14', + '12', + ]) + ->register(); + } +} diff --git a/app/Providers/SiteTypeServiceProvider.php b/app/Providers/SiteTypeServiceProvider.php new file mode 100644 index 00000000..bb6d7ac8 --- /dev/null +++ b/app/Providers/SiteTypeServiceProvider.php @@ -0,0 +1,210 @@ +php(); + $this->phpBlank(); + $this->laravel(); + $this->loadBalancer(); + $this->phpMyAdmin(); + $this->wordpress(); + } + + private function php(): void + { + RegisterSiteType::make(PHPSite::id()) + ->label('PHP') + ->handler(PHPSite::class) + ->form(DynamicForm::make([ + DynamicField::make('php_version') + ->component() + ->label('PHP Version'), + DynamicField::make('source_control') + ->component() + ->label('Source Control'), + DynamicField::make('web_directory') + ->text() + ->label('Web Directory') + ->placeholder('For / leave empty') + ->description('The relative path of your website from /home/vito/your-domain/'), + DynamicField::make('repository') + ->text() + ->label('Repository') + ->placeholder('organization/repository'), + DynamicField::make('branch') + ->text() + ->label('Branch') + ->default('main'), + DynamicField::make('composer') + ->checkbox() + ->label('Run `composer install --no-dev`') + ->default(false), + ])) + ->register(); + } + + private function phpBlank(): void + { + RegisterSiteType::make(PHPBlank::id()) + ->label('PHP Blank') + ->handler(PHPBlank::class) + ->form(DynamicForm::make([ + DynamicField::make('php_version') + ->component() + ->label('PHP Version'), + DynamicField::make('web_directory') + ->text() + ->label('Web Directory') + ->placeholder('For / leave empty') + ->description('The relative path of your website from /home/vito/your-domain/'), + ])) + ->register(); + } + + private function laravel(): void + { + RegisterSiteType::make(Laravel::id()) + ->label('Laravel') + ->handler(Laravel::class) + ->form(DynamicForm::make([ + DynamicField::make('php_version') + ->component() + ->label('PHP Version'), + DynamicField::make('source_control') + ->component() + ->label('Source Control'), + DynamicField::make('web_directory') + ->text() + ->label('Web Directory') + ->placeholder('For / leave empty') + ->description('The relative path of your website from /home/vito/your-domain/'), + DynamicField::make('repository') + ->text() + ->label('Repository') + ->placeholder('organization/repository'), + DynamicField::make('branch') + ->text() + ->label('Branch') + ->default('main'), + DynamicField::make('composer') + ->checkbox() + ->label('Run `composer install --no-dev`') + ->default(false), + ])) + ->register(); + RegisterSiteFeature::make('laravel', 'laravel-octane') + ->label('Laravel Octane') + ->description('Enable Laravel Octane for this site') + ->register(); + RegisterSiteFeatureAction::make('laravel', 'laravel-octane', 'enable') + ->label('Enable') + ->form(DynamicForm::make([ + DynamicField::make('alert') + ->alert() + ->label('Alert') + ->description('Make sure you have already set the `OCTANE_SERVER` in your `.env` file'), + DynamicField::make('port') + ->text() + ->label('Octane Port') + ->default(8000) + ->description('The port on which Laravel Octane will run.'), + ])) + ->handler(Enable::class) + ->register(); + RegisterSiteFeatureAction::make('laravel', 'laravel-octane', 'disable') + ->label('Disable') + ->handler(Disable::class) + ->register(); + } + + public function loadBalancer(): void + { + RegisterSiteType::make(LoadBalancer::id()) + ->label('Load Balancer') + ->handler(LoadBalancer::class) + ->form(DynamicForm::make([ + DynamicField::make('method') + ->select() + ->label('Load Balancing Method') + ->options([ + LoadBalancerMethod::IP_HASH, + LoadBalancerMethod::ROUND_ROBIN, + LoadBalancerMethod::LEAST_CONNECTIONS, + ]), + ])) + ->register(); + } + + public function phpMyAdmin(): void + { + RegisterSiteType::make(PHPMyAdmin::id()) + ->label('PHPMyAdmin') + ->handler(PHPMyAdmin::class) + ->form(DynamicForm::make([ + DynamicField::make('php_version') + ->component() + ->label('PHP Version'), + ])) + ->register(); + } + + public function wordpress(): void + { + RegisterSiteType::make(Wordpress::id()) + ->label('WordPress') + ->handler(Wordpress::class) + ->form(DynamicForm::make([ + DynamicField::make('php_version') + ->component() + ->label('PHP Version'), + DynamicField::make('title') + ->text() + ->label('Site Title') + ->placeholder('My WordPress Site'), + DynamicField::make('username') + ->text() + ->label('Admin Username') + ->placeholder('admin'), + DynamicField::make('password') + ->text() + ->label('Admin Password'), + DynamicField::make('email') + ->text() + ->label('Admin Email'), + DynamicField::make('database') + ->text() + ->label('Database Name') + ->placeholder('wordpress'), + DynamicField::make('database_user') + ->text() + ->label('Database User') + ->placeholder('wp_user'), + DynamicField::make('database_password') + ->text() + ->label('Database Password'), + ])) + ->register(); + } +} diff --git a/app/Providers/SourceControlServiceProvider.php b/app/Providers/SourceControlServiceProvider.php new file mode 100644 index 00000000..eb67ad11 --- /dev/null +++ b/app/Providers/SourceControlServiceProvider.php @@ -0,0 +1,74 @@ +github(); + $this->gitlab(); + $this->bitbucket(); + } + + private function github(): void + { + RegisterSourceControl::make(Github::id()) + ->label('Github') + ->handler(Github::class) + ->form( + DynamicForm::make([ + DynamicField::make('token') + ->text() + ->label('Token'), + ]) + ) + ->register(); + } + + private function gitlab(): void + { + RegisterSourceControl::make(Gitlab::id()) + ->label('Gitlab') + ->handler(Gitlab::class) + ->form( + DynamicForm::make([ + DynamicField::make('token') + ->text() + ->label('Token'), + DynamicField::make('url') + ->text() + ->label('Self hosted URL'), + ]) + ) + ->register(); + } + + private function bitbucket(): void + { + RegisterSourceControl::make(Bitbucket::id()) + ->label('Bitbucket') + ->handler(Bitbucket::class) + ->form( + DynamicForm::make([ + DynamicField::make('username') + ->text() + ->label('Username'), + DynamicField::make('password') + ->text() + ->label('Password'), + ]) + ) + ->register(); + } +} diff --git a/app/Providers/StorageProviderServiceProvider.php b/app/Providers/StorageProviderServiceProvider.php new file mode 100644 index 00000000..734256b0 --- /dev/null +++ b/app/Providers/StorageProviderServiceProvider.php @@ -0,0 +1,121 @@ +local(); + $this->aws(); + $this->dropbox(); + $this->ftp(); + } + + private function local(): void + { + RegisterStorageProvider::make(Local::id()) + ->label('Local') + ->handler(Local::class) + ->form( + DynamicForm::make([ + DynamicField::make('path') + ->text() + ->label('Path'), + ]) + ) + ->register(); + } + + private function aws(): void + { + RegisterStorageProvider::make(S3::id()) + ->label('S3') + ->handler(S3::class) + ->form( + DynamicForm::make([ + DynamicField::make('api_url') + ->text() + ->label('API URL'), + DynamicField::make('key') + ->text() + ->label('Access Key'), + DynamicField::make('secret') + ->text() + ->label('Secret Key'), + DynamicField::make('region') + ->text() + ->label('Region'), + DynamicField::make('bucket') + ->text() + ->label('Bucket Name'), + DynamicField::make('path') + ->text() + ->label('Path'), + ]) + ) + ->register(); + } + + private function dropbox(): void + { + RegisterStorageProvider::make(Dropbox::id()) + ->label('Dropbox') + ->handler(Dropbox::class) + ->form( + DynamicForm::make([ + DynamicField::make('token') + ->text() + ->label('Token'), + ]) + ) + ->register(); + } + + private function ftp(): void + { + RegisterStorageProvider::make(FTP::id()) + ->label('FTP') + ->handler(FTP::class) + ->form( + DynamicForm::make([ + DynamicField::make('host') + ->text() + ->label('Host'), + DynamicField::make('port') + ->text() + ->label('Port') + ->default(21), + DynamicField::make('path') + ->text() + ->label('Path'), + DynamicField::make('username') + ->text() + ->label('Username'), + DynamicField::make('password') + ->text() + ->label('Password'), + DynamicField::make('ssl') + ->checkbox() + ->label('Use SSL') + ->default(false), + DynamicField::make('passive') + ->checkbox() + ->label('Use Passive Mode') + ->default(true), + ]) + ) + ->register(); + } +} diff --git a/app/SSH/HasS3Storage.php b/app/SSH/HasS3Storage.php deleted file mode 100644 index adecb1cb..00000000 --- a/app/SSH/HasS3Storage.php +++ /dev/null @@ -1,24 +0,0 @@ -server->ssh($user); - if ($serverLog instanceof \App\Models\ServerLog) { + if ($serverLog instanceof ServerLog) { $ssh->setLog($serverLog); } $command = ''; diff --git a/app/SSH/Systemd/Systemd.php b/app/SSH/OS/Systemd.php similarity index 98% rename from app/SSH/Systemd/Systemd.php rename to app/SSH/OS/Systemd.php index 484a9157..8d947748 100644 --- a/app/SSH/Systemd/Systemd.php +++ b/app/SSH/OS/Systemd.php @@ -1,6 +1,6 @@ server->ssh($site->user)->exec( - view('ssh.phpmyadmin.install', [ - 'version' => $site->type_data['version'], - 'path' => $site->path, - ]), - 'install-phpmyadmin', - $site->id - ); - } -} diff --git a/app/SSH/Services/Webserver/AbstractWebserver.php b/app/SSH/Services/Webserver/AbstractWebserver.php deleted file mode 100755 index c40e2c88..00000000 --- a/app/SSH/Services/Webserver/AbstractWebserver.php +++ /dev/null @@ -1,39 +0,0 @@ - [ - 'required', - function (string $attribute, mixed $value, Closure $fail): void { - $webserverExists = $this->service->server->webserver(); - if ($webserverExists) { - $fail('You already have a webserver service on the server.'); - } - }, - ], - ]; - } - - public function deletionRules(): array - { - return [ - 'service' => [ - function (string $attribute, mixed $value, Closure $fail): void { - $hasSite = $this->service->server->sites() - ->exists(); - if ($hasSite) { - $fail('Cannot uninstall webserver while you have websites using it.'); - } - }, - ], - ]; - } -} diff --git a/app/SSH/Storage/S3.php b/app/SSH/Storage/S3.php index c3d4e2cc..e2fcc343 100644 --- a/app/SSH/Storage/S3.php +++ b/app/SSH/Storage/S3.php @@ -4,13 +4,10 @@ use App\Exceptions\SSHCommandError; use App\Exceptions\SSHError; -use App\SSH\HasS3Storage; use Illuminate\Support\Facades\Log; class S3 extends AbstractStorage { - use HasS3Storage; - /** * @throws SSHError */ @@ -89,4 +86,25 @@ public function delete(string $src): void 'delete-from-s3' ); } + + /** + * @throws SSHError + */ + private function prepareS3Path(string $path, string $prefix = ''): string + { + $path = trim($path); + $path = ltrim($path, '/'); + $path = preg_replace('/[^a-zA-Z0-9\-_\.\/]/', '_', $path); + $path = preg_replace('/\/+/', '/', (string) $path); + + if ($prefix !== '' && $prefix !== '0') { + return trim($prefix, '/').'/'.$path; + } + + if ($path === null) { + throw new SSHError('Invalid S3 path'); + } + + return $path; + } } diff --git a/app/SSH/Wordpress/Wordpress.php b/app/SSH/Wordpress/Wordpress.php deleted file mode 100644 index 4a9dfe9c..00000000 --- a/app/SSH/Wordpress/Wordpress.php +++ /dev/null @@ -1,35 +0,0 @@ -server->ssh($site->user)->exec( - view('ssh.wordpress.install', [ - 'path' => $site->path, - 'domain' => $site->domain, - 'isIsolated' => $site->isIsolated() ? 'true' : 'false', - 'isolatedUsername' => $site->user, - 'dbName' => $site->type_data['database'], - 'dbUser' => $site->type_data['database_user'], - 'dbPass' => $site->type_data['database_password'], - 'dbHost' => 'localhost', - 'dbPrefix' => 'wp_', - 'username' => $site->type_data['username'], - 'password' => $site->type_data['password'], - 'email' => $site->type_data['email'], - 'title' => $site->type_data['title'], - ]), - 'install-wordpress', - $site->id - ); - } -} diff --git a/app/ServerProviders/AWS.php b/app/ServerProviders/AWS.php index b1f532c1..8b274973 100755 --- a/app/ServerProviders/AWS.php +++ b/app/ServerProviders/AWS.php @@ -15,6 +15,11 @@ class AWS extends AbstractProvider { protected Ec2Client $ec2Client; + public static function id(): string + { + return 'aws'; + } + public function createRules(array $input): array { return [ diff --git a/app/ServerProviders/Custom.php b/app/ServerProviders/Custom.php index 981a8429..09c8a3ae 100755 --- a/app/ServerProviders/Custom.php +++ b/app/ServerProviders/Custom.php @@ -10,6 +10,11 @@ class Custom extends AbstractProvider { + public static function id(): string + { + return 'custom'; + } + public function createRules(array $input): array { return [ diff --git a/app/ServerProviders/DigitalOcean.php b/app/ServerProviders/DigitalOcean.php index 6fc83c61..a3733e5e 100644 --- a/app/ServerProviders/DigitalOcean.php +++ b/app/ServerProviders/DigitalOcean.php @@ -14,6 +14,11 @@ class DigitalOcean extends AbstractProvider { protected string $apiUrl = 'https://api.digitalocean.com/v2'; + public static function id(): string + { + return 'digitalocean'; + } + public function createRules(array $input): array { return [ diff --git a/app/ServerProviders/Hetzner.php b/app/ServerProviders/Hetzner.php index b59b42b1..25b13867 100644 --- a/app/ServerProviders/Hetzner.php +++ b/app/ServerProviders/Hetzner.php @@ -15,6 +15,11 @@ class Hetzner extends AbstractProvider { protected string $apiUrl = 'https://api.hetzner.cloud/v1'; + public static function id(): string + { + return 'hetzner'; + } + public function createRules(array $input): array { return [ diff --git a/app/ServerProviders/Linode.php b/app/ServerProviders/Linode.php index 7902eada..ac09c484 100644 --- a/app/ServerProviders/Linode.php +++ b/app/ServerProviders/Linode.php @@ -14,6 +14,11 @@ class Linode extends AbstractProvider { protected string $apiUrl = 'https://api.linode.com/v4'; + public static function id(): string + { + return 'linode'; + } + public function createRules(array $input): array { return [ diff --git a/app/ServerProviders/ServerProvider.php b/app/ServerProviders/ServerProvider.php index e4049245..e1277efd 100755 --- a/app/ServerProviders/ServerProvider.php +++ b/app/ServerProviders/ServerProvider.php @@ -4,6 +4,8 @@ interface ServerProvider { + public static function id(): string; + /** * @param array $input * @return array diff --git a/app/ServerProviders/Vultr.php b/app/ServerProviders/Vultr.php index 0a202d0e..c861b7c4 100644 --- a/app/ServerProviders/Vultr.php +++ b/app/ServerProviders/Vultr.php @@ -16,6 +16,11 @@ class Vultr extends AbstractProvider { protected string $apiUrl = 'https://api.vultr.com/v2'; + public static function id(): string + { + return 'vultr'; + } + public function createRules(array $input): array { return [ diff --git a/app/ServerTypes/AbstractType.php b/app/ServerTypes/AbstractType.php deleted file mode 100755 index 860e3eaa..00000000 --- a/app/ServerTypes/AbstractType.php +++ /dev/null @@ -1,134 +0,0 @@ -createUser(); - $this->progress(15, 'installing-updates'); - $this->server->os()->upgrade(); - $this->progress(25, 'installing-dependencies'); - $this->server->os()->installDependencies(); - $services = $this->server->services; - $currentProgress = 45; - $progressPerService = (100 - $currentProgress) / count($services); - foreach ($services as $service) { - $currentProgress += $progressPerService; - $this->progress($currentProgress, 'installing- '.$service->name); - $service->handler()->install(); - $service->update(['status' => ServiceStatus::READY]); - if ($service->type == 'php') { - $this->progress($currentProgress, 'installing-composer'); - /** @var PHP $handler */ - $handler = $service->handler(); - $handler->installComposer(); - } - } - $this->progress(100, 'finishing'); - } - - protected function createUser(): void - { - $this->server->os()->createUser( - $this->server->authentication['user'], - $this->server->authentication['pass'], - $this->server->sshKey()['public_key'] - ); - $this->server->ssh_user = config('core.ssh_user'); - $this->server->save(); - $this->server->refresh(); - $this->server->public_key = $this->server->os()->getPublicKey($this->server->getSshUser()); - $this->server->save(); - } - - protected function progress(int|float $percentage, ?string $step = null): void - { - $this->server->progress = $percentage; - $this->server->progress_step = $step; - $this->server->save(); - } - - protected function addWebserver(string $service): void - { - if ($service !== 'none') { - $this->server->services()->create([ - 'type' => 'webserver', - 'name' => $service, - 'version' => 'latest', - ]); - } - } - - protected function addDatabase(string $service): void - { - if ($service !== 'none') { - $this->server->services()->create([ - 'type' => 'database', - 'name' => config('core.databases_name.'.$service), - 'version' => config('core.databases_version.'.$service), - ]); - } - } - - protected function addPHP(string $version): void - { - if ($version !== 'none') { - $this->server->services()->create([ - 'type' => 'php', - 'type_data' => [ - 'extensions' => [], - ], - 'name' => 'php', - 'version' => $version, - ]); - } - } - - protected function addSupervisor(): void - { - $this->server->services()->create([ - 'type' => 'process_manager', - 'name' => 'supervisor', - 'version' => 'latest', - ]); - } - - protected function addRedis(): void - { - $this->server->services()->create([ - 'type' => 'memory_database', - 'name' => 'redis', - 'version' => 'latest', - ]); - } - - protected function addUfw(): void - { - $this->server->services()->create([ - 'type' => 'firewall', - 'name' => 'ufw', - 'version' => 'latest', - ]); - } - - protected function addMonitoring(): void - { - $this->server->services()->create([ - 'type' => 'monitoring', - 'name' => 'remote-monitor', - 'version' => 'latest', - ]); - } -} diff --git a/app/ServerTypes/Database.php b/app/ServerTypes/Database.php deleted file mode 100755 index b8e1925f..00000000 --- a/app/ServerTypes/Database.php +++ /dev/null @@ -1,30 +0,0 @@ - [ - 'required', - 'in:'.implode(',', config('core.databases')), - ], - ]; - } - - public function data(array $input): array - { - return []; - } - - public function createServices(array $input): void - { - $this->server->services()->forceDelete(); - $this->addDatabase($input['database']); - $this->addSupervisor(); - $this->addRedis(); - $this->addUfw(); - } -} diff --git a/app/ServerTypes/Regular.php b/app/ServerTypes/Regular.php deleted file mode 100755 index d9669042..00000000 --- a/app/ServerTypes/Regular.php +++ /dev/null @@ -1,44 +0,0 @@ - [ - 'required', - Rule::in(config('core.webservers')), - - ], - 'php' => [ - 'required', - Rule::in(config('core.php_versions')), - ], - 'database' => [ - 'required', - Rule::in(config('core.databases')), - ], - ]; - } - - public function data(array $input): array - { - return []; - } - - public function createServices(array $input): void - { - $this->server->services()->forceDelete(); - $this->addWebserver($input['webserver']); - $this->addDatabase($input['database']); - $this->addPHP($input['php']); - $this->addSupervisor(); - $this->addRedis(); - $this->addUfw(); - $this->addMonitoring(); - } -} diff --git a/app/ServerTypes/ServerType.php b/app/ServerTypes/ServerType.php deleted file mode 100755 index 11387a82..00000000 --- a/app/ServerTypes/ServerType.php +++ /dev/null @@ -1,25 +0,0 @@ - $input - * @return array - */ - public function createRules(array $input): array; - - /** - * @param array $input - * @return array - */ - public function data(array $input): array; - - /** - * @param array $input - */ - public function createServices(array $input): void; - - public function install(): void; -} diff --git a/app/SSH/Services/AbstractService.php b/app/Services/AbstractService.php similarity index 95% rename from app/SSH/Services/AbstractService.php rename to app/Services/AbstractService.php index fa6e9cc6..3816c90b 100644 --- a/app/SSH/Services/AbstractService.php +++ b/app/Services/AbstractService.php @@ -1,6 +1,6 @@ service->version); $command = view($this->getScriptView('install-'.$version)); $this->service->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version); - $status = $this->service->server->systemd()->status($this->service->unit); + $status = $this->service->server->systemd()->status($this->unit()); $this->service->validateInstall($status); $this->service->server->os()->cleanup(); /** @TODO implement post-install for services and move it there */ diff --git a/app/SSH/Services/Database/Database.php b/app/Services/Database/Database.php similarity index 93% rename from app/SSH/Services/Database/Database.php rename to app/Services/Database/Database.php index e72a2726..5672d1bc 100755 --- a/app/SSH/Services/Database/Database.php +++ b/app/Services/Database/Database.php @@ -1,9 +1,9 @@ service->server_id)->delete(); + Metric::query()->where('server_id', $this->service->server_id)->delete(); } } diff --git a/app/SSH/Services/Monitoring/VitoAgent/VitoAgent.php b/app/Services/Monitoring/VitoAgent/VitoAgent.php similarity index 82% rename from app/SSH/Services/Monitoring/VitoAgent/VitoAgent.php rename to app/Services/Monitoring/VitoAgent/VitoAgent.php index a810d57b..0a668bb2 100644 --- a/app/SSH/Services/Monitoring/VitoAgent/VitoAgent.php +++ b/app/Services/Monitoring/VitoAgent/VitoAgent.php @@ -1,21 +1,37 @@ service->server->systemd()->status($this->service->unit); + $status = $this->service->server->systemd()->status($this->unit()); $this->service->validateInstall($status); } diff --git a/app/SSH/Services/NodeJS/NodeJS.php b/app/Services/NodeJS/NodeJS.php similarity index 85% rename from app/SSH/Services/NodeJS/NodeJS.php rename to app/Services/NodeJS/NodeJS.php index f4fb63e5..7317c8a4 100644 --- a/app/SSH/Services/NodeJS/NodeJS.php +++ b/app/Services/NodeJS/NodeJS.php @@ -1,21 +1,35 @@ [ 'required', - Rule::in(config('core.nodejs_versions')), - Rule::notIn([\App\Enums\NodeJS::NONE]), + Rule::in(config('service.services.nodejs.versions')), Rule::unique('services', 'version') ->where('type', 'nodejs') ->where('server_id', $this->service->server_id), diff --git a/app/SSH/Services/PHP/PHP.php b/app/Services/PHP/PHP.php similarity index 90% rename from app/SSH/Services/PHP/PHP.php rename to app/Services/PHP/PHP.php index 46b41ed7..539b9455 100644 --- a/app/SSH/Services/PHP/PHP.php +++ b/app/Services/PHP/PHP.php @@ -1,23 +1,37 @@ service->version.'-fpm'; + } + public function creationRules(array $input): array { return [ 'version' => [ 'required', - Rule::in(config('core.php_versions')), - Rule::notIn([\App\Enums\PHP::NONE]), + Rule::in(config('service.services.php.versions')), Rule::unique('services', 'version') ->where('type', 'php') ->where('server_id', $this->service->server_id), @@ -142,7 +156,7 @@ public function createFpmPool(string $user, string $version): void 'root' ); - $this->service->server->systemd()->restart($this->service->unit); + $this->service->server->systemd()->restart($this->unit()); } /** diff --git a/app/SSH/Services/ProcessManager/AbstractProcessManager.php b/app/Services/ProcessManager/AbstractProcessManager.php similarity index 92% rename from app/SSH/Services/ProcessManager/AbstractProcessManager.php rename to app/Services/ProcessManager/AbstractProcessManager.php index 24702c37..2131d798 100644 --- a/app/SSH/Services/ProcessManager/AbstractProcessManager.php +++ b/app/Services/ProcessManager/AbstractProcessManager.php @@ -1,8 +1,8 @@ service->server->systemd()->status($this->service->unit); + $status = $this->service->server->systemd()->status($this->unit()); $this->service->validateInstall($status); $this->service->server->os()->cleanup(); } diff --git a/app/SSH/Services/ServiceInterface.php b/app/Services/ServiceInterface.php similarity index 80% rename from app/SSH/Services/ServiceInterface.php rename to app/Services/ServiceInterface.php index df3f854f..aa6f5736 100644 --- a/app/SSH/Services/ServiceInterface.php +++ b/app/Services/ServiceInterface.php @@ -1,9 +1,15 @@ $input * @return array diff --git a/app/Services/Webserver/AbstractWebserver.php b/app/Services/Webserver/AbstractWebserver.php new file mode 100755 index 00000000..c72e76f0 --- /dev/null +++ b/app/Services/Webserver/AbstractWebserver.php @@ -0,0 +1,101 @@ + [ + 'required', + function (string $attribute, mixed $value, Closure $fail): void { + $webserverExists = $this->service->server->webserver(); + if ($webserverExists) { + $fail('You already have a webserver service on the server.'); + } + }, + ], + ]; + } + + public function deletionRules(): array + { + return [ + 'service' => [ + function (string $attribute, mixed $value, Closure $fail): void { + $hasSite = $this->service->server->sites() + ->exists(); + if ($hasSite) { + $fail('Cannot uninstall webserver while you have websites using it.'); + } + }, + ], + ]; + } + + /** + * @param array $replace replace blocks + * @param array $regenerate regenerates the blocks + * @param array $append appends to the blocks + */ + protected function getUpdatedVHost(Site $site, string $vhost, array $replace = [], array $regenerate = [], array $append = []): string + { + foreach ($replace as $block => $replacement) { + $vhost = preg_replace( + '/#\['.$block.'](.*?)#\[\/'.$block.']/s', + $replacement, + $vhost + ); + } + + foreach ($regenerate as $block) { + $vhost = preg_replace( + '/#\['.$block.'](.*?)#\[\/'.$block.']/s', + $this->generateVhost($site, $block), + $vhost + ); + } + + foreach ($append as $block => $content) { + /** + * #[block] + * content + * content + * content + * content + * --append-here-- + * #[/block] + */ + $vhost = preg_replace( + '/(#\['.$block.'](.*?)\n)(?=#\[\/'.$block.'])/s', + "\$1$content\n", + $vhost + ); + } + + return $vhost; + } + + protected function generateVhost(Site $site, ?string $block = null): string + { + $viewPath = 'ssh.services.webserver.'.$this::id().'.vhost-blocks.'.$block; + if ($block) { + if (! view()->exists($viewPath)) { + throw new InvalidArgumentException("View for block '{$block}' does not exist."); + } + $vhost = view($viewPath, [ + 'site' => $site, + ]); + } else { + $vhost = $site->type()->vhost($this::id()); + } + + return format_nginx_config($vhost); + } +} diff --git a/app/SSH/Services/Webserver/Caddy.php b/app/Services/Webserver/Caddy.php similarity index 88% rename from app/SSH/Services/Webserver/Caddy.php rename to app/Services/Webserver/Caddy.php index dbb2917d..5164f15d 100755 --- a/app/SSH/Services/Webserver/Caddy.php +++ b/app/Services/Webserver/Caddy.php @@ -1,6 +1,6 @@ getVHost($site); + } + + if (! $vhost || ! preg_match('/#\[main]/', $vhost)) { + $vhost = $this->generateVhost($site); + } + $this->service->server->ssh()->write( '/etc/caddy/sites-available/'.$site->domain, - $vhost ?? $this->generateVhost($site), + format_nginx_config($this->getUpdatedVHost($site, $vhost, $replace, $regenerate, $append)), 'root' ); @@ -188,13 +206,4 @@ public function removeSSL(Ssl $ssl): void $this->updateVHost($ssl->site); } - - private function generateVhost(Site $site): string - { - $vhost = view('ssh.services.webserver.caddy.vhost', [ - 'site' => $site, - ]); - - return format_nginx_config($vhost); - } } diff --git a/app/SSH/Services/Webserver/Nginx.php b/app/Services/Webserver/Nginx.php similarity index 87% rename from app/SSH/Services/Webserver/Nginx.php rename to app/Services/Webserver/Nginx.php index 44009f6f..cf3fd2d0 100755 --- a/app/SSH/Services/Webserver/Nginx.php +++ b/app/Services/Webserver/Nginx.php @@ -1,6 +1,6 @@ getVHost($site); + } + + if (! $vhost || ! preg_match('/#\[header]/', $vhost) || ! preg_match('/#\[main]/', $vhost) || ! preg_match('/#\[footer]/', $vhost)) { + $vhost = $this->generateVhost($site); + } + $this->service->server->ssh()->write( '/etc/nginx/sites-available/'.$site->domain, - $vhost ?? $this->generateVhost($site), + format_nginx_config($this->getUpdatedVHost($site, $vhost, $replace, $regenerate, $append)), 'root' ); @@ -192,13 +210,4 @@ public function removeSSL(Ssl $ssl): void $this->updateVHost($ssl->site); } - - private function generateVhost(Site $site): string - { - $vhost = view('ssh.services.webserver.nginx.vhost', [ - 'site' => $site, - ]); - - return format_nginx_config($vhost); - } } diff --git a/app/SSH/Services/Webserver/Webserver.php b/app/Services/Webserver/Webserver.php similarity index 57% rename from app/SSH/Services/Webserver/Webserver.php rename to app/Services/Webserver/Webserver.php index 0c161c9b..fd71d0d9 100755 --- a/app/SSH/Services/Webserver/Webserver.php +++ b/app/Services/Webserver/Webserver.php @@ -1,18 +1,21 @@ $replace replace blocks + * @param array $regenerate regenerates the blocks + * @param array $append appends to the blocks + */ + public function updateVHost(Site $site, ?string $vhost = null, array $replace = [], array $regenerate = [], array $append = []): void; public function getVHost(Site $site): string; diff --git a/app/SiteFeatures/Action.php b/app/SiteFeatures/Action.php new file mode 100644 index 00000000..22a59b12 --- /dev/null +++ b/app/SiteFeatures/Action.php @@ -0,0 +1,10 @@ +site->type_data, 'octane', false); + } + + public function form(): ?DynamicForm + { + return DynamicForm::make([ + DynamicField::make('port') + ->text() + ->default(8000), + ]); + } + + public function handle(Request $request): void + { + $typeData = $this->site->type_data ?? []; + + /** @var ?Worker $worker */ + $worker = $this->site->workers()->where('name', 'laravel-octane')->first(); + if ($worker) { + app(DeleteWorker::class)->delete($worker); + } + + data_set($typeData, 'octane', false); + data_set($typeData, 'octane_port', $request->input('port')); + $this->site->type_data = $typeData; + $this->site->save(); + + $webserver = $this->site->webserver()->id(); + + if ($webserver === 'nginx') { + $this->site->webserver()->updateVHost( + $this->site, + replace: [ + 'laravel-octane-map' => '', + 'laravel-octane' => view('ssh.services.webserver.nginx.vhost-blocks.php', ['site' => $this->site]), + ], + ); + } + + $request->session()->flash('success', 'Laravel Octane has been disabled for this site.'); + } +} diff --git a/app/SiteFeatures/LaravelOctane/Enable.php b/app/SiteFeatures/LaravelOctane/Enable.php new file mode 100644 index 00000000..d4f3681c --- /dev/null +++ b/app/SiteFeatures/LaravelOctane/Enable.php @@ -0,0 +1,109 @@ +site->type_data, 'octane', false); + } + + public function form(): ?DynamicForm + { + return DynamicForm::make([ + DynamicField::make('port') + ->text() + ->default(8000), + ]); + } + + /** + * @throws SSHError + */ + public function handle(Request $request): void + { + Validator::make($request->all(), [ + 'port' => 'required|integer|min:1|max:65535', + ])->validate(); + + $this->site->server->ssh()->exec( + __('php :path/artisan octane:install --no-interaction', [ + 'path' => $this->site->path, + ]), + 'install-laravel-octane', + ); + + $command = __('php :path/artisan octane:start --port=:port --host=127.0.0.1', [ + 'path' => $this->site->path, + 'port' => $request->input('port'), + ]); + + /** @var ?Worker $worker */ + $worker = $this->site->workers()->where('name', 'laravel-octane')->first(); + if ($worker) { + app(ManageWorker::class)->restart($worker); + } else { + app(CreateWorker::class)->create( + $this->site->server, + [ + 'name' => 'laravel-octane', + 'command' => $command, + 'user' => $this->site->user ?? $this->site->server->getSshUser(), + 'auto_start' => true, + 'auto_restart' => true, + 'numprocs' => 1, + ], + $this->site, + ); + } + + $typeData = $this->site->type_data ?? []; + data_set($typeData, 'octane', true); + data_set($typeData, 'octane_port', $request->input('port')); + $this->site->type_data = $typeData; + $this->site->save(); + + $this->updateVHost(); + + $request->session()->flash('success', 'Laravel Octane has been enabled for this site.'); + } + + private function updateVHost(): void + { + $webserver = $this->site->webserver(); + + if ($webserver->id() === 'nginx') { + $this->site->webserver()->updateVHost( + $this->site, + replace: [ + 'php' => view('ssh.services.webserver.nginx.vhost-blocks.laravel-octane', ['site' => $this->site]), + 'laravel-octane-map' => '', + ], + append: [ + 'header' => view('ssh.services.webserver.nginx.vhost-blocks.laravel-octane-map', ['site' => $this->site]), + ] + ); + + return; + } + + throw new RuntimeException('Unsupported webserver: '.$webserver->id()); + } +} diff --git a/app/SiteTypes/AbstractSiteType.php b/app/SiteTypes/AbstractSiteType.php index de1429b2..eac54240 100755 --- a/app/SiteTypes/AbstractSiteType.php +++ b/app/SiteTypes/AbstractSiteType.php @@ -4,8 +4,9 @@ use App\Exceptions\FailedToDeployGitKey; use App\Exceptions\SSHError; +use App\Models\Service; use App\Models\Site; -use App\SSH\Services\PHP\PHP; +use App\Services\PHP\PHP; use Illuminate\Support\Str; use RuntimeException; @@ -76,7 +77,7 @@ protected function isolate(): void // Generate the FPM pool if ($this->site->php_version) { $service = $this->site->php(); - if (! $service instanceof \App\Models\Service) { + if (! $service instanceof Service) { throw new RuntimeException('PHP service not found'); } /** @var PHP $php */ diff --git a/app/SiteTypes/Laravel.php b/app/SiteTypes/Laravel.php index dc12a5d5..142f2ae6 100644 --- a/app/SiteTypes/Laravel.php +++ b/app/SiteTypes/Laravel.php @@ -6,57 +6,29 @@ class Laravel extends PHPSite { + public static function id(): string + { + return 'laravel'; + } + public static function make(): self { - return new self(new Site(['type' => \App\Enums\SiteType::LARAVEL])); + return new self(new Site(['type' => self::id()])); } public function baseCommands(): array { return array_merge(parent::baseCommands(), [ - // Initial Setup Commands [ - 'name' => 'Generate Application Key', - 'command' => 'php artisan key:generate', - ], - [ - 'name' => 'Create Storage Symbolic Link', - 'command' => 'php artisan storage:link', - ], - // Database Commands - [ - 'name' => 'Run Database Migrations', - 'command' => 'php artisan migrate --force', - ], - // Cache & Optimization Commands - [ - 'name' => 'Optimize Application', - 'command' => 'php artisan optimize', - ], - [ - 'name' => 'Clear All Application Optimizations', - 'command' => 'php artisan optimize:clear', - ], - [ - 'name' => 'Clear Application Cache Only', + 'name' => 'cache:clear', 'command' => 'php artisan cache:clear', ], - // Worker Commands [ - 'name' => 'Restart Workers', - 'command' => 'php artisan queue:restart', - ], - [ - 'name' => 'Clear All Failed Queue Jobs', - 'command' => 'php artisan queue:flush', - ], - // Application State Commands - [ - 'name' => 'Put Application in Maintenance Mode', + 'name' => 'down', 'command' => 'php artisan down --retry=5 --refresh=6 --quiet', ], [ - 'name' => 'Bring Application Online', + 'name' => 'up', 'command' => 'php artisan up', ], ]); diff --git a/app/SiteTypes/LoadBalancer.php b/app/SiteTypes/LoadBalancer.php index 6e6094b0..4904e9eb 100755 --- a/app/SiteTypes/LoadBalancer.php +++ b/app/SiteTypes/LoadBalancer.php @@ -2,19 +2,21 @@ namespace App\SiteTypes; -use App\DTOs\DynamicFieldDTO; -use App\DTOs\DynamicFieldsCollectionDTO; use App\Enums\LoadBalancerMethod; -use App\Enums\SiteFeature; use App\Exceptions\SSHError; use App\Models\Site; use Illuminate\Validation\Rule; class LoadBalancer extends AbstractSiteType { + public static function id(): string + { + return 'load-balancer'; + } + public static function make(): self { - return new self(new Site(['type' => \App\Enums\SiteType::LOAD_BALANCER])); + return new self(new Site(['type' => self::id()])); } public function language(): string @@ -22,27 +24,6 @@ public function language(): string return 'yaml'; } - public function supportedFeatures(): array - { - return [ - SiteFeature::SSL, - ]; - } - - public function fields(): DynamicFieldsCollectionDTO - { - return new DynamicFieldsCollectionDTO([ - DynamicFieldDTO::make('method') - ->select() - ->label('Load Balancing Method') - ->options([ - LoadBalancerMethod::IP_HASH, - LoadBalancerMethod::ROUND_ROBIN, - LoadBalancerMethod::LEAST_CONNECTIONS, - ]), - ]); - } - public function createRules(array $input): array { return [ @@ -73,4 +54,36 @@ public function install(): void $this->site->webserver()->createVHost($this->site); } + + public function vhost(string $webserver): string + { + if ($webserver === 'nginx') { + return view('ssh.services.webserver.nginx.vhost', [ + 'header' => [ + view('ssh.services.webserver.nginx.vhost-blocks.force-ssl', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.load-balancer-upstream', ['site' => $this->site]), + ], + 'main' => [ + view('ssh.services.webserver.nginx.vhost-blocks.port', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.core', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.load-balancer', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.redirects', ['site' => $this->site]), + ], + ]); + } + + if ($webserver === 'caddy') { + return view('ssh.services.webserver.caddy.vhost', [ + 'main' => implode("\n", [ + view('ssh.services.webserver.caddy.vhost-blocks.force-ssl', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.port', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.core', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.load-balancer', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.redirects', ['site' => $this->site]), + ]), + ]); + } + + return ''; + } } diff --git a/app/SiteTypes/PHPBlank.php b/app/SiteTypes/PHPBlank.php index 6895d735..cb67fd51 100755 --- a/app/SiteTypes/PHPBlank.php +++ b/app/SiteTypes/PHPBlank.php @@ -2,43 +2,20 @@ namespace App\SiteTypes; -use App\DTOs\DynamicFieldDTO; -use App\DTOs\DynamicFieldsCollectionDTO; -use App\Enums\SiteFeature; use App\Exceptions\SSHError; use App\Models\Site; use Illuminate\Validation\Rule; class PHPBlank extends PHPSite { + public static function id(): string + { + return 'php-blank'; + } + public static function make(): self { - return new self(new Site(['type' => \App\Enums\SiteType::PHP])); - } - - public function supportedFeatures(): array - { - return [ - SiteFeature::DEPLOYMENT, - SiteFeature::COMMANDS, - SiteFeature::ENV, - SiteFeature::SSL, - SiteFeature::WORKERS, - ]; - } - - public function fields(): DynamicFieldsCollectionDTO - { - return new DynamicFieldsCollectionDTO([ - DynamicFieldDTO::make('php_version') - ->component() - ->label('PHP Version'), - DynamicFieldDTO::make('web_directory') - ->text() - ->label('Web Directory') - ->placeholder('For / leave empty') - ->description('The relative path of your website from /home/vito/your-domain/'), - ]); + return new self(new Site(['type' => self::id()])); } public function createRules(array $input): array diff --git a/app/SiteTypes/PHPMyAdmin.php b/app/SiteTypes/PHPMyAdmin.php index 79c553ad..0e847ed0 100755 --- a/app/SiteTypes/PHPMyAdmin.php +++ b/app/SiteTypes/PHPMyAdmin.php @@ -2,34 +2,20 @@ namespace App\SiteTypes; -use App\DTOs\DynamicFieldDTO; -use App\DTOs\DynamicFieldsCollectionDTO; -use App\Enums\SiteFeature; use App\Exceptions\SSHError; use App\Models\Site; use Illuminate\Validation\Rule; class PHPMyAdmin extends PHPSite { + public static function id(): string + { + return 'phpmyadmin'; + } + public static function make(): self { - return new self(new Site(['type' => \App\Enums\SiteType::PHPMYADMIN])); - } - - public function supportedFeatures(): array - { - return [ - SiteFeature::SSL, - ]; - } - - public function fields(): DynamicFieldsCollectionDTO - { - return new DynamicFieldsCollectionDTO([ - DynamicFieldDTO::make('php_version') - ->component() - ->label('PHP Version'), - ]); + return new self(new Site(['type' => self::id()])); } public function createRules(array $input): array @@ -65,7 +51,14 @@ public function install(): void $this->isolate(); $this->site->webserver()->createVHost($this->site); $this->progress(30); - app(\App\SSH\PHPMyAdmin\PHPMyAdmin::class)->install($this->site); + $this->site->server->ssh($this->site->user)->exec( + view('ssh.phpmyadmin.install', [ + 'version' => $this->site->type_data['version'], + 'path' => $this->site->path, + ]), + 'install-phpmyadmin', + $this->site->id + ); $this->progress(65); $this->site->php()?->restart(); } diff --git a/app/SiteTypes/PHPSite.php b/app/SiteTypes/PHPSite.php index ced30584..fba62b3d 100755 --- a/app/SiteTypes/PHPSite.php +++ b/app/SiteTypes/PHPSite.php @@ -2,21 +2,18 @@ namespace App\SiteTypes; -use App\DTOs\DynamicFieldDTO; -use App\DTOs\DynamicFieldsCollectionDTO; -use App\Enums\SiteFeature; use App\Exceptions\FailedToDeployGitKey; use App\Exceptions\SSHError; use App\Models\Site; -use App\SSH\Composer\Composer; -use App\SSH\Git\Git; +use App\SSH\OS\Composer; +use App\SSH\OS\Git; use Illuminate\Validation\Rule; class PHPSite extends AbstractSiteType { - public static function make(): self + public static function id(): string { - return new self(new Site(['type' => \App\Enums\SiteType::PHP])); + return 'php'; } public function language(): string @@ -24,44 +21,9 @@ public function language(): string return 'php'; } - public function supportedFeatures(): array + public static function make(): self { - return [ - SiteFeature::DEPLOYMENT, - SiteFeature::COMMANDS, - SiteFeature::ENV, - SiteFeature::SSL, - SiteFeature::WORKERS, - ]; - } - - public function fields(): DynamicFieldsCollectionDTO - { - return new DynamicFieldsCollectionDTO([ - DynamicFieldDTO::make('php_version') - ->component() - ->label('PHP Version'), - DynamicFieldDTO::make('source_control') - ->component() - ->label('Source Control'), - DynamicFieldDTO::make('web_directory') - ->text() - ->label('Web Directory') - ->placeholder('For / leave empty') - ->description('The relative path of your website from /home/vito/your-domain/'), - DynamicFieldDTO::make('repository') - ->text() - ->label('Repository') - ->placeholder('organization/repository'), - DynamicFieldDTO::make('branch') - ->text() - ->label('Branch') - ->default('main'), - DynamicFieldDTO::make('composer') - ->checkbox() - ->label('Run `composer install --no-dev`') - ->default(false), - ]); + return new self(new Site(['type' => self::id()])); } public function createRules(array $input): array @@ -132,9 +94,40 @@ public function baseCommands(): array { return [ [ - 'name' => 'Install Composer Dependencies', + 'name' => 'composer:install', 'command' => 'composer install --no-dev --no-interaction --no-progress', ], ]; } + + public function vhost(string $webserver): string + { + if ($webserver === 'nginx') { + return view('ssh.services.webserver.nginx.vhost', [ + 'header' => [ + view('ssh.services.webserver.nginx.vhost-blocks.force-ssl', ['site' => $this->site]), + ], + 'main' => [ + view('ssh.services.webserver.nginx.vhost-blocks.port', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.core', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.php', ['site' => $this->site]), + view('ssh.services.webserver.nginx.vhost-blocks.redirects', ['site' => $this->site]), + ], + ]); + } + + if ($webserver === 'caddy') { + return view('ssh.services.webserver.caddy.vhost', [ + 'main' => [ + view('ssh.services.webserver.caddy.vhost-blocks.force-ssl', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.port', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.core', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.php', ['site' => $this->site]), + view('ssh.services.webserver.caddy.vhost-blocks.redirects', ['site' => $this->site]), + ], + ]); + } + + return ''; + } } diff --git a/app/SiteTypes/SiteType.php b/app/SiteTypes/SiteType.php index 3adbaf59..e9fec442 100755 --- a/app/SiteTypes/SiteType.php +++ b/app/SiteTypes/SiteType.php @@ -2,19 +2,12 @@ namespace App\SiteTypes; -use App\DTOs\DynamicFieldsCollectionDTO; - interface SiteType { + public static function id(): string; + public function language(): string; - /** - * @return array - */ - public function supportedFeatures(): array; - - public function fields(): DynamicFieldsCollectionDTO; - /** * @param array $input * @return array @@ -22,12 +15,16 @@ public function fields(): DynamicFieldsCollectionDTO; public function createRules(array $input): array; /** + * The fields here will be replaced in the Site model + * * @param array $input * @return array */ public function createFields(array $input): array; /** + * The fields here will be replaced in the type_data column as json + * * @param array $input * @return array */ @@ -39,4 +36,6 @@ public function install(): void; * @return array> */ public function baseCommands(): array; + + public function vhost(string $webserver): string; } diff --git a/app/SiteTypes/Wordpress.php b/app/SiteTypes/Wordpress.php index 9696bf8a..e412893e 100755 --- a/app/SiteTypes/Wordpress.php +++ b/app/SiteTypes/Wordpress.php @@ -5,9 +5,6 @@ use App\Actions\Database\CreateDatabase; use App\Actions\Database\CreateDatabaseUser; use App\Actions\Database\LinkUser; -use App\DTOs\DynamicFieldDTO; -use App\DTOs\DynamicFieldsCollectionDTO; -use App\Enums\SiteFeature; use App\Exceptions\SSHError; use App\Models\Database; use App\Models\DatabaseUser; @@ -15,11 +12,16 @@ use Closure; use Illuminate\Validation\Rule; -class Wordpress extends AbstractSiteType +class Wordpress extends PHPSite { + public static function id(): string + { + return 'wordpress'; + } + public static function make(): self { - return new self(new Site(['type' => \App\Enums\SiteType::WORDPRESS])); + return new self(new Site(['type' => self::id()])); } public function language(): string @@ -27,48 +29,6 @@ public function language(): string return 'php'; } - public function supportedFeatures(): array - { - return [ - SiteFeature::SSL, - SiteFeature::COMMANDS, - ]; - } - - public function fields(): DynamicFieldsCollectionDTO - { - return new DynamicFieldsCollectionDTO([ - DynamicFieldDTO::make('php_version') - ->component() - ->label('PHP Version'), - DynamicFieldDTO::make('title') - ->text() - ->label('Site Title') - ->placeholder('My WordPress Site'), - DynamicFieldDTO::make('username') - ->text() - ->label('Admin Username') - ->placeholder('admin'), - DynamicFieldDTO::make('password') - ->text() - ->label('Admin Password'), - DynamicFieldDTO::make('email') - ->text() - ->label('Admin Email'), - DynamicFieldDTO::make('database') - ->text() - ->label('Database Name') - ->placeholder('wordpress'), - DynamicFieldDTO::make('database_user') - ->text() - ->label('Database User') - ->placeholder('wp_user'), - DynamicFieldDTO::make('database_password') - ->text() - ->label('Database Password'), - ]); - } - public function createRules(array $input): array { return [ @@ -157,6 +117,25 @@ public function install(): void $this->site->php()?->restart(); $this->progress(60); - app(\App\SSH\Wordpress\Wordpress::class)->install($this->site); + + $this->site->server->ssh($this->site->user)->exec( + view('ssh.wordpress.install', [ + 'path' => $this->site->path, + 'domain' => $this->site->domain, + 'isIsolated' => $this->site->isIsolated() ? 'true' : 'false', + 'isolatedUsername' => $this->site->user, + 'dbName' => $this->site->type_data['database'], + 'dbUser' => $this->site->type_data['database_user'], + 'dbPass' => $this->site->type_data['database_password'], + 'dbHost' => 'localhost', + 'dbPrefix' => 'wp_', + 'username' => $this->site->type_data['username'], + 'password' => $this->site->type_data['password'], + 'email' => $this->site->type_data['email'], + 'title' => $this->site->type_data['title'], + ]), + 'install-wordpress', + $this->site->id + ); } } diff --git a/app/SourceControlProviders/Bitbucket.php b/app/SourceControlProviders/Bitbucket.php index 16940ae8..486dabed 100755 --- a/app/SourceControlProviders/Bitbucket.php +++ b/app/SourceControlProviders/Bitbucket.php @@ -13,6 +13,11 @@ class Bitbucket extends AbstractSourceControlProvider { protected string $apiUrl = 'https://api.bitbucket.org/2.0'; + public static function id(): string + { + return 'bitbucket'; + } + public function createRules(array $input): array { return [ diff --git a/app/SourceControlProviders/Github.php b/app/SourceControlProviders/Github.php index 5003b7a4..5889d240 100755 --- a/app/SourceControlProviders/Github.php +++ b/app/SourceControlProviders/Github.php @@ -12,6 +12,11 @@ class Github extends AbstractSourceControlProvider { protected string $apiUrl = 'https://api.github.com'; + public static function id(): string + { + return 'github'; + } + public function connect(): bool { try { diff --git a/app/SourceControlProviders/Gitlab.php b/app/SourceControlProviders/Gitlab.php index 3df66e58..51d76dd4 100755 --- a/app/SourceControlProviders/Gitlab.php +++ b/app/SourceControlProviders/Gitlab.php @@ -14,6 +14,11 @@ class Gitlab extends AbstractSourceControlProvider protected string $apiVersion = 'api/v4'; + public static function id(): string + { + return 'gitlab'; + } + public function createRules(array $input): array { return [ diff --git a/app/SourceControlProviders/SourceControlProvider.php b/app/SourceControlProviders/SourceControlProvider.php index b70f14a1..be97c46c 100755 --- a/app/SourceControlProviders/SourceControlProvider.php +++ b/app/SourceControlProviders/SourceControlProvider.php @@ -8,6 +8,8 @@ interface SourceControlProvider { + public static function id(): string; + /** * @param array $input * @return array diff --git a/app/StorageProviders/Dropbox.php b/app/StorageProviders/Dropbox.php index a2199ab4..18ab6916 100644 --- a/app/StorageProviders/Dropbox.php +++ b/app/StorageProviders/Dropbox.php @@ -10,6 +10,11 @@ class Dropbox extends AbstractStorageProvider { protected string $apiUrl = 'https://api.dropboxapi.com/2'; + public static function id(): string + { + return 'dropbox'; + } + public function validationRules(): array { return [ diff --git a/app/StorageProviders/FTP.php b/app/StorageProviders/FTP.php index ae5f7796..5f6319a3 100644 --- a/app/StorageProviders/FTP.php +++ b/app/StorageProviders/FTP.php @@ -8,6 +8,11 @@ class FTP extends AbstractStorageProvider { + public static function id(): string + { + return 'ftp'; + } + /** * @return array */ diff --git a/app/StorageProviders/Local.php b/app/StorageProviders/Local.php index bfbbd611..b64d516b 100644 --- a/app/StorageProviders/Local.php +++ b/app/StorageProviders/Local.php @@ -7,6 +7,11 @@ class Local extends AbstractStorageProvider { + public static function id(): string + { + return 'local'; + } + public function validationRules(): array { return [ diff --git a/app/StorageProviders/S3.php b/app/StorageProviders/S3.php index 1ebc4d20..b34df7b1 100644 --- a/app/StorageProviders/S3.php +++ b/app/StorageProviders/S3.php @@ -21,6 +21,11 @@ class S3 extends AbstractStorageProvider */ protected array $clientConfig = []; + public static function id(): string + { + return 's3'; + } + public function getApiUrl(): string { if (isset($this->storageProvider->credentials['api_url']) && $this->storageProvider->credentials['api_url']) { diff --git a/app/StorageProviders/StorageProvider.php b/app/StorageProviders/StorageProvider.php index ef70ce8c..81de76fb 100644 --- a/app/StorageProviders/StorageProvider.php +++ b/app/StorageProviders/StorageProvider.php @@ -7,6 +7,8 @@ interface StorageProvider { + public static function id(): string; + /** * @return array */ diff --git a/app/Support/Testing/SSHFake.php b/app/Support/Testing/SSHFake.php index d2a7670c..b2e106b2 100644 --- a/app/Support/Testing/SSHFake.php +++ b/app/Support/Testing/SSHFake.php @@ -5,6 +5,7 @@ use App\Exceptions\SSHConnectionError; use App\Helpers\SSH; use App\Models\Server; +use App\Models\ServerLog; use Illuminate\Support\Traits\ReflectsClosures; use PHPUnit\Framework\Assert; @@ -53,8 +54,8 @@ public function connect(bool $sftp = false): void public function exec(string $command, string $log = '', ?int $siteId = null, ?bool $stream = false, ?callable $streamCallback = null): string { - if (! $this->log instanceof \App\Models\ServerLog && $log) { - /** @var \App\Models\ServerLog $log */ + if (! $this->log instanceof ServerLog && $log) { + /** @var ServerLog $log */ $log = $this->server->logs()->create([ 'site_id' => $siteId, 'name' => $this->server->id.'-'.strtotime('now').'-'.$log.'.log', diff --git a/app/Traits/Enum.php b/app/Traits/Enum.php index 5520156a..37e7d45e 100644 --- a/app/Traits/Enum.php +++ b/app/Traits/Enum.php @@ -2,6 +2,8 @@ namespace App\Traits; +use ReflectionClass; + trait Enum { /** @@ -9,7 +11,7 @@ trait Enum */ public static function all(): array { - $reflection = new \ReflectionClass(self::class); + $reflection = new ReflectionClass(self::class); return $reflection->getConstants(); } diff --git a/app/ValidationRules/CronRule.php b/app/ValidationRules/CronRule.php index 2a913088..d3914e08 100755 --- a/app/ValidationRules/CronRule.php +++ b/app/ValidationRules/CronRule.php @@ -2,6 +2,7 @@ namespace App\ValidationRules; +use Closure; use Cron\CronExpression; use Illuminate\Contracts\Validation\ValidationRule; @@ -9,7 +10,7 @@ class CronRule implements ValidationRule { public function __construct(private readonly bool $acceptCustom = false) {} - public function validate(string $attribute, mixed $value, \Closure $fail): void + public function validate(string $attribute, mixed $value, Closure $fail): void { if (CronExpression::isValidExpression($value)) { return; diff --git a/config/app.php b/config/app.php index c2e2574e..15a8624e 100644 --- a/config/app.php +++ b/config/app.php @@ -193,6 +193,12 @@ App\Providers\AuthServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\DemoServiceProvider::class, + App\Providers\SiteTypeServiceProvider::class, + App\Providers\ServerProviderServiceProvider::class, + App\Providers\StorageProviderServiceProvider::class, + App\Providers\SourceControlServiceProvider::class, + App\Providers\NotificationChannelServiceProvider::class, + App\Providers\ServiceTypeServiceProvider::class, ], /* diff --git a/config/core.php b/config/core.php index f0512eea..3337acd7 100755 --- a/config/core.php +++ b/config/core.php @@ -1,5 +1,11 @@ [ - \App\Enums\OperatingSystem::UBUNTU20, - \App\Enums\OperatingSystem::UBUNTU22, - \App\Enums\OperatingSystem::UBUNTU24, + OperatingSystem::UBUNTU20, + OperatingSystem::UBUNTU22, + OperatingSystem::UBUNTU24, ], 'operating_system_versions' => [ - \App\Enums\OperatingSystem::UBUNTU20 => '20.04', - \App\Enums\OperatingSystem::UBUNTU22 => '22.04', - \App\Enums\OperatingSystem::UBUNTU24 => '24.04', - ], - 'webservers' => [ - \App\Enums\Webserver::NONE, - \App\Enums\Webserver::NGINX, - \App\Enums\Webserver::CADDY, - ], - 'php_versions' => [ - \App\Enums\PHP::NONE, - \App\Enums\PHP::V70, - \App\Enums\PHP::V71, - \App\Enums\PHP::V72, - \App\Enums\PHP::V73, - \App\Enums\PHP::V74, - \App\Enums\PHP::V80, - \App\Enums\PHP::V81, - \App\Enums\PHP::V82, - \App\Enums\PHP::V83, - \App\Enums\PHP::V84, - ], - 'nodejs_versions' => [ - \App\Enums\NodeJS::NONE, - \App\Enums\NodeJS::V4, - \App\Enums\NodeJS::V6, - \App\Enums\NodeJS::V8, - \App\Enums\NodeJS::V10, - \App\Enums\NodeJS::V12, - \App\Enums\NodeJS::V14, - \App\Enums\NodeJS::V16, - \App\Enums\NodeJS::V18, - \App\Enums\NodeJS::V20, - \App\Enums\NodeJS::V22, - ], - 'databases' => [ - \App\Enums\Database::NONE, - \App\Enums\Database::MYSQL57, - \App\Enums\Database::MYSQL80, - \App\Enums\Database::MYSQL84, - \App\Enums\Database::MARIADB103, - \App\Enums\Database::MARIADB104, - \App\Enums\Database::MARIADB106, - \App\Enums\Database::MARIADB1011, - \App\Enums\Database::MARIADB114, - \App\Enums\Database::POSTGRESQL12, - \App\Enums\Database::POSTGRESQL13, - \App\Enums\Database::POSTGRESQL14, - \App\Enums\Database::POSTGRESQL15, - \App\Enums\Database::POSTGRESQL16, - ], - 'databases_name' => [ - \App\Enums\Database::NONE => 'none', - \App\Enums\Database::MYSQL57 => 'mysql', - \App\Enums\Database::MYSQL80 => 'mysql', - \App\Enums\Database::MYSQL84 => 'mysql', - \App\Enums\Database::MARIADB103 => 'mariadb', - \App\Enums\Database::MARIADB104 => 'mariadb', - \App\Enums\Database::MARIADB106 => 'mariadb', - \App\Enums\Database::MARIADB1011 => 'mariadb', - \App\Enums\Database::MARIADB114 => 'mariadb', - \App\Enums\Database::POSTGRESQL12 => 'postgresql', - \App\Enums\Database::POSTGRESQL13 => 'postgresql', - \App\Enums\Database::POSTGRESQL14 => 'postgresql', - \App\Enums\Database::POSTGRESQL15 => 'postgresql', - \App\Enums\Database::POSTGRESQL16 => 'postgresql', - ], - 'databases_version' => [ - \App\Enums\Database::NONE => '', - \App\Enums\Database::MYSQL57 => '5.7', - \App\Enums\Database::MYSQL80 => '8.0', - \App\Enums\Database::MYSQL84 => '8.4', - \App\Enums\Database::MARIADB103 => '10.3', - \App\Enums\Database::MARIADB104 => '10.4', - \App\Enums\Database::MARIADB106 => '10.6', - \App\Enums\Database::MARIADB1011 => '10.11', - \App\Enums\Database::MARIADB114 => '11.4', - \App\Enums\Database::POSTGRESQL12 => '12', - \App\Enums\Database::POSTGRESQL13 => '13', - \App\Enums\Database::POSTGRESQL14 => '14', - \App\Enums\Database::POSTGRESQL15 => '15', - \App\Enums\Database::POSTGRESQL16 => '16', - ], - 'database_features' => [ - 'remote' => [ - 'mysql', - 'mariadb', - ], - ], - - /* - * Server - */ - 'server_types' => [ - \App\Enums\ServerType::REGULAR, - \App\Enums\ServerType::DATABASE, - ], - 'server_types_class' => [ - \App\Enums\ServerType::REGULAR => \App\ServerTypes\Regular::class, - \App\Enums\ServerType::DATABASE => \App\ServerTypes\Database::class, - ], - 'server_providers' => [ - \App\Enums\ServerProvider::CUSTOM, - \App\Enums\ServerProvider::AWS, - \App\Enums\ServerProvider::LINODE, - \App\Enums\ServerProvider::DIGITALOCEAN, - \App\Enums\ServerProvider::VULTR, - \App\Enums\ServerProvider::HETZNER, - ], - 'server_providers_class' => [ - \App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class, - \App\Enums\ServerProvider::AWS => \App\ServerProviders\AWS::class, - \App\Enums\ServerProvider::LINODE => \App\ServerProviders\Linode::class, - \App\Enums\ServerProvider::DIGITALOCEAN => \App\ServerProviders\DigitalOcean::class, - \App\Enums\ServerProvider::VULTR => \App\ServerProviders\Vultr::class, - \App\Enums\ServerProvider::HETZNER => \App\ServerProviders\Hetzner::class, - ], - 'server_providers_default_user' => [ - 'custom' => [ - \App\Enums\OperatingSystem::UBUNTU20 => 'root', - \App\Enums\OperatingSystem::UBUNTU22 => 'root', - \App\Enums\OperatingSystem::UBUNTU24 => 'root', - ], - 'aws' => [ - \App\Enums\OperatingSystem::UBUNTU20 => 'ubuntu', - \App\Enums\OperatingSystem::UBUNTU22 => 'ubuntu', - \App\Enums\OperatingSystem::UBUNTU24 => 'ubuntu', - ], - 'linode' => [ - \App\Enums\OperatingSystem::UBUNTU20 => 'root', - \App\Enums\OperatingSystem::UBUNTU22 => 'root', - \App\Enums\OperatingSystem::UBUNTU24 => 'root', - ], - 'digitalocean' => [ - \App\Enums\OperatingSystem::UBUNTU20 => 'root', - \App\Enums\OperatingSystem::UBUNTU22 => 'root', - \App\Enums\OperatingSystem::UBUNTU24 => 'root', - ], - 'vultr' => [ - \App\Enums\OperatingSystem::UBUNTU20 => 'root', - \App\Enums\OperatingSystem::UBUNTU22 => 'root', - \App\Enums\OperatingSystem::UBUNTU24 => 'root', - ], - 'hetzner' => [ - \App\Enums\OperatingSystem::UBUNTU20 => 'root', - \App\Enums\OperatingSystem::UBUNTU22 => 'root', - \App\Enums\OperatingSystem::UBUNTU24 => 'root', - ], - ], - 'server_providers_custom_fields' => [ - \App\Enums\ServerProvider::AWS => ['key', 'secret'], - \App\Enums\ServerProvider::LINODE => ['token'], - \App\Enums\ServerProvider::DIGITALOCEAN => ['token'], - \App\Enums\ServerProvider::VULTR => ['token'], - \App\Enums\ServerProvider::HETZNER => ['token'], - ], - - /* - * Service - */ - 'service_types' => [ - 'nginx' => 'webserver', - 'caddy' => 'webserver', - 'mysql' => 'database', - 'mariadb' => 'database', - 'postgresql' => 'database', - 'redis' => 'memory_database', - 'php' => 'php', - 'nodejs' => 'nodejs', - 'ufw' => 'firewall', - 'supervisor' => 'process_manager', - 'vito-agent' => 'monitoring', - 'remote-monitor' => 'monitoring', - ], - 'service_handlers' => [ - 'nginx' => \App\SSH\Services\Webserver\Nginx::class, - 'caddy' => \App\SSH\Services\Webserver\Caddy::class, - 'mysql' => \App\SSH\Services\Database\Mysql::class, - 'mariadb' => \App\SSH\Services\Database\Mariadb::class, - 'postgresql' => \App\SSH\Services\Database\Postgresql::class, - 'redis' => \App\SSH\Services\Redis\Redis::class, - 'php' => \App\SSH\Services\PHP\PHP::class, - 'nodejs' => \App\SSH\Services\NodeJS\NodeJS::class, - 'ufw' => \App\SSH\Services\Firewall\Ufw::class, - 'supervisor' => \App\SSH\Services\ProcessManager\Supervisor::class, - 'vito-agent' => \App\SSH\Services\Monitoring\VitoAgent\VitoAgent::class, - 'remote-monitor' => \App\SSH\Services\Monitoring\RemoteMonitor\RemoteMonitor::class, - ], - 'service_versions' => [ - 'nginx' => [ - 'latest', - ], - 'caddy' => [ - 'latest', - ], - 'mysql' => [ - '5.7', - '8.0', - '8.4', - ], - 'mariadb' => [ - '10.3', - '10.4', - '10.6', - '10.11', - '11.4', - ], - 'postgresql' => [ - '12', - '13', - '14', - '15', - '16', - ], - 'redis' => [ - 'latest', - ], - 'nodejs' => [ - '4', - '6', - '8', - '10', - '12', - '14', - '16', - '18', - '20', - '22', - ], - 'php' => [ - '5.6', - '7.0', - '7.1', - '7.2', - '7.3', - '7.4', - '8.0', - '8.1', - '8.2', - '8.3', - '8.4', - ], - 'ufw' => [ - 'latest', - ], - 'supervisor' => [ - 'latest', - ], - 'vito-agent' => [ - 'latest', - ], - 'remote-monitor' => [ - 'latest', - ], - ], - 'service_units' => [ - 'nginx' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - 'latest' => 'nginx', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - 'latest' => 'nginx', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - 'latest' => 'nginx', - ], - ], - 'caddy' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - 'latest' => 'caddy', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - 'latest' => 'caddy', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - 'latest' => 'caddy', - ], - ], - 'mysql' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - '5.7' => 'mysql', - '8.0' => 'mysql', - '8.4' => 'mysql', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - '5.7' => 'mysql', - '8.0' => 'mysql', - '8.4' => 'mysql', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - '5.7' => 'mysql', - '8.0' => 'mysql', - '8.4' => 'mysql', - ], - ], - 'mariadb' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - '10.3' => 'mariadb', - '10.4' => 'mariadb', - '10.6' => 'mariadb', - '10.11' => 'mariadb', - '11.4' => 'mariadb', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - '10.3' => 'mariadb', - '10.4' => 'mariadb', - '10.6' => 'mariadb', - '10.11' => 'mariadb', - '11.4' => 'mariadb', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - '10.3' => 'mariadb', - '10.4' => 'mariadb', - '10.6' => 'mariadb', - '10.11' => 'mariadb', - '11.4' => 'mariadb', - ], - ], - 'postgresql' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - '12' => 'postgresql', - '13' => 'postgresql', - '14' => 'postgresql', - '15' => 'postgresql', - '16' => 'postgresql', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - '12' => 'postgresql', - '13' => 'postgresql', - '14' => 'postgresql', - '15' => 'postgresql', - '16' => 'postgresql', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - '12' => 'postgresql', - '13' => 'postgresql', - '14' => 'postgresql', - '15' => 'postgresql', - '16' => 'postgresql', - ], - ], - 'php' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - '5.6' => 'php5.6-fpm', - '7.0' => 'php7.0-fpm', - '7.1' => 'php7.1-fpm', - '7.2' => 'php7.2-fpm', - '7.3' => 'php7.3-fpm', - '7.4' => 'php7.4-fpm', - '8.0' => 'php8.0-fpm', - '8.1' => 'php8.1-fpm', - '8.3' => 'php8.3-fpm', - '8.4' => 'php8.4-fpm', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - '5.6' => 'php5.6-fpm', - '7.0' => 'php7.0-fpm', - '7.1' => 'php7.1-fpm', - '7.2' => 'php7.2-fpm', - '7.3' => 'php7.3-fpm', - '7.4' => 'php7.4-fpm', - '8.0' => 'php8.0-fpm', - '8.1' => 'php8.1-fpm', - '8.2' => 'php8.2-fpm', - '8.3' => 'php8.3-fpm', - '8.4' => 'php8.4-fpm', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - '5.6' => 'php5.6-fpm', - '7.0' => 'php7.0-fpm', - '7.1' => 'php7.1-fpm', - '7.2' => 'php7.2-fpm', - '7.3' => 'php7.3-fpm', - '7.4' => 'php7.4-fpm', - '8.0' => 'php8.0-fpm', - '8.1' => 'php8.1-fpm', - '8.2' => 'php8.2-fpm', - '8.3' => 'php8.3-fpm', - '8.4' => 'php8.4-fpm', - ], - ], - 'redis' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - 'latest' => 'redis', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - 'latest' => 'redis', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - 'latest' => 'redis', - ], - ], - 'supervisor' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - 'latest' => 'supervisor', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - 'latest' => 'supervisor', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - 'latest' => 'supervisor', - ], - ], - 'ufw' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - 'latest' => 'ufw', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - 'latest' => 'ufw', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - 'latest' => 'ufw', - ], - ], - 'vito-agent' => [ - \App\Enums\OperatingSystem::UBUNTU20 => [ - 'latest' => 'vito-agent', - ], - \App\Enums\OperatingSystem::UBUNTU22 => [ - 'latest' => 'vito-agent', - ], - \App\Enums\OperatingSystem::UBUNTU24 => [ - 'latest' => 'vito-agent', - ], - ], - ], - - /* - * Site - */ - 'site_types' => [ - \App\Enums\SiteType::PHP, - \App\Enums\SiteType::PHP_BLANK, - \App\Enums\SiteType::LARAVEL, - \App\Enums\SiteType::WORDPRESS, - \App\Enums\SiteType::PHPMYADMIN, - \App\Enums\SiteType::LOAD_BALANCER, - ], - 'site_types_class' => [ - \App\Enums\SiteType::PHP => \App\SiteTypes\PHPSite::class, - \App\Enums\SiteType::PHP_BLANK => \App\SiteTypes\PHPBlank::class, - \App\Enums\SiteType::LARAVEL => \App\SiteTypes\Laravel::class, - \App\Enums\SiteType::WORDPRESS => \App\SiteTypes\Wordpress::class, - \App\Enums\SiteType::PHPMYADMIN => \App\SiteTypes\PHPMyAdmin::class, - \App\Enums\SiteType::LOAD_BALANCER => \App\SiteTypes\LoadBalancer::class, - ], - 'site_types_custom_fields' => [ - \App\Enums\SiteType::PHP => \App\SiteTypes\PHPSite::make()->fields()->toArray(), - \App\Enums\SiteType::PHP_BLANK => \App\SiteTypes\PHPBlank::make()->fields()->toArray(), - \App\Enums\SiteType::LARAVEL => \App\SiteTypes\Laravel::make()->fields()->toArray(), - \App\Enums\SiteType::WORDPRESS => \App\SiteTypes\Wordpress::make()->fields()->toArray(), - \App\Enums\SiteType::PHPMYADMIN => \App\SiteTypes\PHPMyAdmin::make()->fields()->toArray(), - \App\Enums\SiteType::LOAD_BALANCER => \App\SiteTypes\LoadBalancer::make()->fields()->toArray(), - ], - - /* - * Source Control - */ - 'source_control_providers' => [ - 'github', - 'gitlab', - 'bitbucket', - ], - 'source_control_providers_class' => [ - 'github' => \App\SourceControlProviders\Github::class, - 'gitlab' => \App\SourceControlProviders\Gitlab::class, - 'bitbucket' => \App\SourceControlProviders\Bitbucket::class, - ], - 'source_control_providers_custom_fields' => [ - \App\Enums\SourceControl::GITHUB => ['token'], - \App\Enums\SourceControl::GITLAB => ['token', 'url'], - \App\Enums\SourceControl::BITBUCKET => ['username', 'password'], - ], - - /* - * available php extensions - */ - 'php_extensions' => [ - 'imagick', - 'exif', - 'gmagick', - 'gmp', - 'intl', - 'sqlite3', - 'opcache', - ], - - /* - * php settings - */ - 'php_settings' => [ - 'upload_max_filesize' => '2', - 'memory_limit' => '128', - 'max_execution_time' => '30', - 'post_max_size' => '2', - ], - 'php_settings_unit' => [ - 'upload_max_filesize' => 'M', - 'memory_limit' => 'M', - 'max_execution_time' => 'S', - 'post_max_size' => 'M', + OperatingSystem::UBUNTU20 => '20.04', + OperatingSystem::UBUNTU22 => '22.04', + OperatingSystem::UBUNTU24 => '24.04', ], /* @@ -530,68 +38,9 @@ explode(',', (string) env('RESTRICTED_IP_ADDRESSES', '')) ), - /* - * Notification channels - */ - 'notification_channels_providers' => [ - \App\Enums\NotificationChannel::SLACK, - \App\Enums\NotificationChannel::DISCORD, - \App\Enums\NotificationChannel::EMAIL, - \App\Enums\NotificationChannel::TELEGRAM, - ], - 'notification_channels_providers_class' => [ - \App\Enums\NotificationChannel::SLACK => \App\NotificationChannels\Slack::class, - \App\Enums\NotificationChannel::DISCORD => \App\NotificationChannels\Discord::class, - \App\Enums\NotificationChannel::EMAIL => \App\NotificationChannels\Email::class, - \App\Enums\NotificationChannel::TELEGRAM => \App\NotificationChannels\Telegram::class, - ], - 'notification_channels_providers_custom_fields' => [ - \App\Enums\NotificationChannel::SLACK => ['webhook_url'], - \App\Enums\NotificationChannel::DISCORD => ['webhook_url'], - \App\Enums\NotificationChannel::EMAIL => ['email'], - \App\Enums\NotificationChannel::TELEGRAM => ['bot_token', 'chat_id'], - ], - - /* - * storage providers - */ - 'storage_providers' => [ - \App\Enums\StorageProvider::DROPBOX, - \App\Enums\StorageProvider::FTP, - \App\Enums\StorageProvider::LOCAL, - \App\Enums\StorageProvider::S3, - ], - 'storage_providers_class' => [ - \App\Enums\StorageProvider::DROPBOX => \App\StorageProviders\Dropbox::class, - \App\Enums\StorageProvider::FTP => \App\StorageProviders\FTP::class, - \App\Enums\StorageProvider::LOCAL => \App\StorageProviders\Local::class, - \App\Enums\StorageProvider::S3 => \App\StorageProviders\S3::class, - ], - 'storage_providers_custom_fields' => [ - \App\Enums\StorageProvider::DROPBOX => ['token'], - \App\Enums\StorageProvider::FTP => [ - 'host', - 'port', - 'path', - 'username', - 'password', - 'ssl', - 'passive', - ], - \App\Enums\StorageProvider::S3 => [ - 'api_url', - 'key', - 'secret', - 'region', - 'bucket', - 'path', - ], - \App\Enums\StorageProvider::LOCAL => ['path'], - ], - 'ssl_types' => [ - \App\Enums\SslType::LETSENCRYPT, - \App\Enums\SslType::CUSTOM, + SslType::LETSENCRYPT, + SslType::CUSTOM, ], 'metrics_data_retention' => [ @@ -602,13 +51,13 @@ ], 'taggable_types' => [ - \App\Models\Server::class, - \App\Models\Site::class, + Server::class, + Site::class, ], 'user_roles' => [ - \App\Enums\UserRole::USER, - \App\Enums\UserRole::ADMIN, + UserRole::USER, + UserRole::ADMIN, ], 'cronjob_intervals' => [ diff --git a/config/notification-channel.php b/config/notification-channel.php new file mode 100644 index 00000000..becd8923 --- /dev/null +++ b/config/notification-channel.php @@ -0,0 +1,7 @@ + [ + // this will be automatically registered by the StorageServiceProvider + ], +]; diff --git a/config/route-attributes.php b/config/route-attributes.php index 3356c39b..69f22c0b 100644 --- a/config/route-attributes.php +++ b/config/route-attributes.php @@ -1,5 +1,7 @@ [ - \Illuminate\Routing\Middleware\SubstituteBindings::class, + SubstituteBindings::class, ], /* diff --git a/config/server-provider.php b/config/server-provider.php new file mode 100644 index 00000000..ab0afb61 --- /dev/null +++ b/config/server-provider.php @@ -0,0 +1,7 @@ + [ + // this will be automatically registered by the ServerServiceProvider + ], +]; diff --git a/config/service.php b/config/service.php new file mode 100644 index 00000000..d3378ee6 --- /dev/null +++ b/config/service.php @@ -0,0 +1,7 @@ + [ + // this will be automatically registered by the ServiceTypeServiceProvider + ], +]; diff --git a/config/site.php b/config/site.php new file mode 100644 index 00000000..5cc30d9f --- /dev/null +++ b/config/site.php @@ -0,0 +1,7 @@ + [ + // this will be automatically registered by the SiteTypeServiceProvider + ], +]; diff --git a/config/source-control.php b/config/source-control.php new file mode 100644 index 00000000..becd8923 --- /dev/null +++ b/config/source-control.php @@ -0,0 +1,7 @@ + [ + // this will be automatically registered by the StorageServiceProvider + ], +]; diff --git a/config/storage-provider.php b/config/storage-provider.php new file mode 100644 index 00000000..becd8923 --- /dev/null +++ b/config/storage-provider.php @@ -0,0 +1,7 @@ + [ + // this will be automatically registered by the StorageServiceProvider + ], +]; diff --git a/config/web.php b/config/web.php index b4460b36..201890e7 100644 --- a/config/web.php +++ b/config/web.php @@ -1,9 +1,11 @@ 10, 'controllers' => [ - 'servers' => \App\Http\Controllers\ServerController::class, + 'servers' => ServerController::class, ], ]; diff --git a/database/factories/BackupFactory.php b/database/factories/BackupFactory.php index 3d92feec..c3b4c839 100644 --- a/database/factories/BackupFactory.php +++ b/database/factories/BackupFactory.php @@ -3,10 +3,11 @@ namespace Database\Factories; use App\Enums\BackupStatus; +use App\Models\Backup; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\Backup> + * @extends Factory */ class BackupFactory extends Factory { diff --git a/database/factories/BackupFileFactory.php b/database/factories/BackupFileFactory.php index 9521403a..68565637 100644 --- a/database/factories/BackupFileFactory.php +++ b/database/factories/BackupFileFactory.php @@ -2,10 +2,11 @@ namespace Database\Factories; +use App\Models\BackupFile; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\BackupFile> + * @extends Factory */ class BackupFileFactory extends Factory { diff --git a/database/factories/CommandExecutionFactory.php b/database/factories/CommandExecutionFactory.php index eb93d4d4..edca3697 100644 --- a/database/factories/CommandExecutionFactory.php +++ b/database/factories/CommandExecutionFactory.php @@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\CommandExecution> + * @extends Factory */ class CommandExecutionFactory extends Factory { diff --git a/database/factories/CommandFactory.php b/database/factories/CommandFactory.php index 70099a7e..dbee0a5a 100644 --- a/database/factories/CommandFactory.php +++ b/database/factories/CommandFactory.php @@ -8,7 +8,7 @@ use Illuminate\Support\Carbon; /** - * @extends Factory<\App\Models\Command> + * @extends Factory */ class CommandFactory extends Factory { diff --git a/database/factories/CronJobFactory.php b/database/factories/CronJobFactory.php index c4693c23..f2421f23 100644 --- a/database/factories/CronJobFactory.php +++ b/database/factories/CronJobFactory.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\CronJob> + * @extends Factory */ class CronJobFactory extends Factory { diff --git a/database/factories/DatabaseFactory.php b/database/factories/DatabaseFactory.php index 491e0e17..fb66e509 100755 --- a/database/factories/DatabaseFactory.php +++ b/database/factories/DatabaseFactory.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\Database> + * @extends Factory */ class DatabaseFactory extends Factory { diff --git a/database/factories/DatabaseUserFactory.php b/database/factories/DatabaseUserFactory.php index d2f54384..65443d80 100755 --- a/database/factories/DatabaseUserFactory.php +++ b/database/factories/DatabaseUserFactory.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\DatabaseUser> + * @extends Factory */ class DatabaseUserFactory extends Factory { diff --git a/database/factories/DeploymentFactory.php b/database/factories/DeploymentFactory.php index b2198b95..e006bdaa 100755 --- a/database/factories/DeploymentFactory.php +++ b/database/factories/DeploymentFactory.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\Deployment> + * @extends Factory */ class DeploymentFactory extends Factory { diff --git a/database/factories/DeploymentScriptFactory.php b/database/factories/DeploymentScriptFactory.php index da711fdf..2a4a866c 100644 --- a/database/factories/DeploymentScriptFactory.php +++ b/database/factories/DeploymentScriptFactory.php @@ -8,7 +8,7 @@ use Illuminate\Support\Carbon; /** - * @extends Factory<\App\Models\DeploymentScript> + * @extends Factory */ class DeploymentScriptFactory extends Factory { diff --git a/database/factories/FileFactory.php b/database/factories/FileFactory.php index b0f57f18..fce3df7d 100644 --- a/database/factories/FileFactory.php +++ b/database/factories/FileFactory.php @@ -7,7 +7,7 @@ use Illuminate\Support\Carbon; /** - * @extends Factory<\App\Models\File> + * @extends Factory */ class FileFactory extends Factory { diff --git a/database/factories/FirewallRuleFactory.php b/database/factories/FirewallRuleFactory.php index 917a7a62..9f9d1083 100644 --- a/database/factories/FirewallRuleFactory.php +++ b/database/factories/FirewallRuleFactory.php @@ -6,7 +6,7 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\FirewallRule> + * @extends Factory */ class FirewallRuleFactory extends Factory { diff --git a/database/factories/GitHookFactory.php b/database/factories/GitHookFactory.php index 5d8e81da..1a5b91cc 100644 --- a/database/factories/GitHookFactory.php +++ b/database/factories/GitHookFactory.php @@ -9,7 +9,7 @@ use Illuminate\Support\Carbon; /** - * @extends Factory<\App\Models\GitHook> + * @extends Factory */ class GitHookFactory extends Factory { diff --git a/database/factories/LoadBalancerServerFactory.php b/database/factories/LoadBalancerServerFactory.php index 83e025e9..d9aad9db 100644 --- a/database/factories/LoadBalancerServerFactory.php +++ b/database/factories/LoadBalancerServerFactory.php @@ -7,7 +7,7 @@ use Illuminate\Support\Carbon; /** - * @extends Factory<\App\Models\LoadBalancerServer> + * @extends Factory */ class LoadBalancerServerFactory extends Factory { diff --git a/database/factories/MetricFactory.php b/database/factories/MetricFactory.php index a6640072..9e6c5620 100644 --- a/database/factories/MetricFactory.php +++ b/database/factories/MetricFactory.php @@ -2,10 +2,11 @@ namespace Database\Factories; +use App\Models\Metric; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\Metric> + * @extends Factory */ class MetricFactory extends Factory { diff --git a/database/factories/NotificationChannelFactory.php b/database/factories/NotificationChannelFactory.php index dc09a3ad..3e10abcc 100644 --- a/database/factories/NotificationChannelFactory.php +++ b/database/factories/NotificationChannelFactory.php @@ -2,10 +2,11 @@ namespace Database\Factories; +use App\Models\NotificationChannel; use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends Factory<\App\Models\NotificationChannel> + * @extends Factory */ class NotificationChannelFactory extends Factory { diff --git a/database/factories/ProjectFactory.php b/database/factories/ProjectFactory.php index db6b1cde..239a96a0 100644 --- a/database/factories/ProjectFactory.php +++ b/database/factories/ProjectFactory.php @@ -7,7 +7,7 @@ use Illuminate\Support\Carbon; /** - * @extends Factory<\App\Models\Project> + * @extends Factory */ class ProjectFactory extends Factory { diff --git a/database/factories/ScriptExecutionFactory.php b/database/factories/ScriptExecutionFactory.php index 3c9ac54a..a33a8d8e 100644 --- a/database/factories/ScriptExecutionFactory.php +++ b/database/factories/ScriptExecutionFactory.php @@ -8,7 +8,7 @@ use Illuminate\Support\Carbon; /** - * @extends Factory<\App\Models\ScriptExecution> + * @extends Factory */ class ScriptExecutionFactory extends Factory { diff --git a/database/factories/ScriptFactory.php b/database/factories/ScriptFactory.php index 239cff93..3609a56f 100644 --- a/database/factories/ScriptFactory.php +++ b/database/factories/ScriptFactory.php @@ -7,7 +7,7 @@ use Illuminate\Support\Carbon; /** - * @extends Factory<\App\Models\Script> + * @extends Factory