diff --git a/app/Actions/Server/CreateServer.php b/app/Actions/Server/CreateServer.php index 51c69aa..b2e6595 100755 --- a/app/Actions/Server/CreateServer.php +++ b/app/Actions/Server/CreateServer.php @@ -189,7 +189,7 @@ private static function providerRules(array $input): array return $server->provider()->createRules($input); } - private function createFirewallRules(Server $server): void + public function createFirewallRules(Server $server): void { $server->firewallRules()->createMany([ [ diff --git a/app/Providers/DemoServiceProvider.php b/app/Providers/DemoServiceProvider.php new file mode 100644 index 0000000..11bbe95 --- /dev/null +++ b/app/Providers/DemoServiceProvider.php @@ -0,0 +1,96 @@ +runningInConsole()) { + return; + } + + // get all classes inside App\Models namespace + $models = collect(scandir(app_path('Models'))) + ->filter(fn ($file) => ! in_array($file, ['.', '..'])) + ->map(fn ($file) => 'App\\Models\\'.str_replace('.php', '', $file)); + + foreach ($models as $model) { + if (! in_array($model, $this->canCreate)) { + $this->preventCreating($model); + } + if (! in_array($model, $this->canUpdate)) { + $this->preventUpdating($model); + } + if (! in_array($model, $this->canDelete)) { + $this->preventDeletion($model); + } + } + + SSH::fake('Demo SSH is enabled. No SSH commands will be executed.'); + Http::fake([ + '*' => Http::response([]), + ]); + + config()->set('queue.default', 'sync'); + config()->set('logging.default'); + config()->set('session.driver', 'file'); + } + + private function preventUpdating(string $model): void + { + $model::updating(function ($m) { + $throw = true; + if ($m instanceof User && ! $m->isDirty(['name', 'email', 'password', 'two_factor_secret', 'two_factor_recovery_codes'])) { + $throw = false; + } + if ($throw) { + abort(403, $this->error); + } + }); + } + + private function preventDeletion(string $model): void + { + $model::deleting(function ($m) { + abort(403, $this->error); + }); + } + + private function preventCreating(string $model): void + { + $model::creating(function ($m) { + abort(403, $this->error); + }); + } +} diff --git a/app/Support/Testing/SSHFake.php b/app/Support/Testing/SSHFake.php index 01c0711..e4cbcb9 100644 --- a/app/Support/Testing/SSHFake.php +++ b/app/Support/Testing/SSHFake.php @@ -4,6 +4,7 @@ use App\Exceptions\SSHConnectionError; use App\Helpers\SSH; +use App\Models\Server; use Illuminate\Support\Traits\ReflectsClosures; use PHPUnit\Framework\Assert; @@ -28,6 +29,21 @@ public function __construct(?string $output = null) $this->output = $output; } + public function init(Server $server, ?string $asUser = null): self + { + $this->connection = null; + $this->log = null; + $this->asUser = null; + $this->server = $server->refresh(); + $this->user = $server->getSshUser(); + if ($asUser && $asUser != $server->getSshUser()) { + $this->user = $asUser; + $this->asUser = $asUser; + } + + return $this; + } + public function connectionWillFail(): void { $this->connectionWillFail = true; diff --git a/app/Web/Pages/Login.php b/app/Web/Pages/Login.php index 9eec130..cb05dfa 100644 --- a/app/Web/Pages/Login.php +++ b/app/Web/Pages/Login.php @@ -24,6 +24,13 @@ public function mount(): void $this->initTwoFactor(); $this->form->fill(); + + if (config('app.demo')) { + $this->form->fill([ + 'email' => 'demo@vitodeploy.com', + 'password' => 'password', + ]); + } } public function logoutAction(): Action diff --git a/config/app.php b/config/app.php index 73c8a8e..9e60891 100644 --- a/config/app.php +++ b/config/app.php @@ -193,6 +193,7 @@ App\Providers\AuthServiceProvider::class, App\Providers\RouteServiceProvider::class, App\Providers\WebServiceProvider::class, + App\Providers\DemoServiceProvider::class, ], /* @@ -211,4 +212,6 @@ ])->toArray(), 'version' => '2.0.0-beta-20241210', + + 'demo' => env('APP_DEMO', false), ]; diff --git a/database/seeders/CronJobsSeeder.php b/database/seeders/CronJobsSeeder.php new file mode 100644 index 0000000..ae40e88 --- /dev/null +++ b/database/seeders/CronJobsSeeder.php @@ -0,0 +1,22 @@ +create([ + 'server_id' => $server->id, + 'command' => 'php /home/vito/'.$server->project->name.'.com/artisan schedule:run', + ]); + } + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index e34fb18..335a722 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,12 +2,6 @@ namespace Database\Seeders; -use App\Enums\ServiceStatus; -use App\Enums\SiteType; -use App\Models\Project; -use App\Models\Server; -use App\Models\Site; -use App\Models\User; use Illuminate\Database\Seeder; use Illuminate\Foundation\Testing\WithFaker; @@ -20,51 +14,21 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - $user = User::factory()->create([ - 'name' => 'Test User', - 'email' => 'user@example.com', - 'current_project_id' => Project::factory()->create(), - ]); - - $this->createResources($user); - } - - private function createResources(User $user): void - { - $server = Server::factory()->create([ - 'user_id' => $user->id, - 'project_id' => $user->currentProject->id, - ]); - $server->services()->create([ - 'type' => 'database', - 'name' => config('core.databases_name.mysql80'), - 'version' => config('core.databases_version.mysql80'), - 'status' => ServiceStatus::READY, - ]); - $server->services()->create([ - 'type' => 'php', - 'type_data' => [ - 'extensions' => [], - ], - 'name' => 'php', - 'version' => '8.1', - 'status' => ServiceStatus::READY, - ]); - $server->services()->create([ - 'type' => 'webserver', - 'name' => 'nginx', - 'version' => 'latest', - 'status' => ServiceStatus::READY, - ]); - $server->services()->create([ - 'type' => 'firewall', - 'name' => 'ufw', - 'version' => 'latest', - 'status' => ServiceStatus::READY, - ]); - Site::factory()->create([ - 'server_id' => $server->id, - 'type' => SiteType::LARAVEL, + $this->call([ + ProjectsSeeder::class, + UsersSeeder::class, + TagsSeeder::class, + ServerProvidersSeeder::class, + StorageProvidersSeeder::class, + SourceControlsSeeder::class, + NotificationChannelsSeeder::class, + ServersSeeder::class, + SitesSeeder::class, + DatabasesSeeder::class, + CronJobsSeeder::class, + SshKeysSeeder::class, + MetricsSeeder::class, + ServerLogsSeeder::class, ]); } } diff --git a/database/seeders/DatabasesSeeder.php b/database/seeders/DatabasesSeeder.php new file mode 100644 index 0000000..6ad3653 --- /dev/null +++ b/database/seeders/DatabasesSeeder.php @@ -0,0 +1,53 @@ +whereHas('services', function (Builder $query) { + $query->where('type', 'database'); + })->get(); + + $storageProviders = StorageProvider::all(); + + /** @var Server $server */ + foreach ($servers as $server) { + /** @var Database $database */ + $database = Database::factory()->create([ + 'server_id' => $server->id, + 'name' => 'main', + ]); + DatabaseUser::factory()->create([ + 'server_id' => $server->id, + 'username' => 'main_user', + 'password' => 'password', + 'host' => '%', + 'databases' => [$database->name], + ]); + /** @var Backup $backup */ + $backup = Backup::factory()->create([ + 'server_id' => $server->id, + 'database_id' => $database->id, + 'storage_id' => $storageProviders->random()->id, + ]); + BackupFile::factory(10)->create([ + 'name' => $database->name.'-'.now()->format('Y-m-d-H-i-s').'.zip', + 'size' => rand(1000000, 10000000), + 'backup_id' => $backup->id, + 'status' => BackupFileStatus::CREATED, + ]); + } + } +} diff --git a/database/seeders/MetricsSeeder.php b/database/seeders/MetricsSeeder.php index 826f6bc..a3fcfe8 100644 --- a/database/seeders/MetricsSeeder.php +++ b/database/seeders/MetricsSeeder.php @@ -3,30 +3,32 @@ namespace Database\Seeders; use App\Models\Metric; -use App\Models\Service; +use App\Models\Server; use Carbon\Carbon; use Carbon\CarbonPeriod; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Seeder; class MetricsSeeder extends Seeder { - /** - * Run the database seeds. - */ public function run(): void { - Metric::query()->delete(); + $servers = Server::query()->whereHas('services', function (Builder $query) { + $query->where('type', 'monitoring'); + })->get(); - $monitoring = Service::query() - ->where('type', 'monitoring') - ->firstOrFail(); - - $range = CarbonPeriod::create(Carbon::now()->subDays(7), '1 minute', Carbon::now()); - foreach ($range as $date) { - Metric::factory()->create([ - 'server_id' => $monitoring->server_id, - 'created_at' => $date, - ]); + /** @var Server $server */ + foreach ($servers as $server) { + $monitoring = $server->services() + ->where('type', 'monitoring') + ->first(); + $range = CarbonPeriod::create(Carbon::now()->subHour(), '1 minute', Carbon::now()); + foreach ($range as $date) { + Metric::factory()->create([ + 'server_id' => $monitoring->server_id, + 'created_at' => $date, + ]); + } } } } diff --git a/database/seeders/NotificationChannelsSeeder.php b/database/seeders/NotificationChannelsSeeder.php new file mode 100644 index 0000000..066a463 --- /dev/null +++ b/database/seeders/NotificationChannelsSeeder.php @@ -0,0 +1,49 @@ +create([ + 'label' => 'Slack', + 'provider' => \App\Enums\NotificationChannel::SLACK, + 'data' => [ + 'webhook' => 'slack_webhook', + ], + 'connected' => 1, + ]); + + NotificationChannel::factory()->create([ + 'label' => 'Discord', + 'provider' => \App\Enums\NotificationChannel::DISCORD, + 'data' => [ + 'webhook' => 'discord_webhook', + ], + 'connected' => 1, + ]); + + NotificationChannel::factory()->create([ + 'label' => 'Telegram', + 'provider' => \App\Enums\NotificationChannel::TELEGRAM, + 'data' => [ + 'token' => 'telegram_token', + 'chat_id' => 'telegram_chat_id', + ], + 'connected' => 1, + ]); + + NotificationChannel::factory()->create([ + 'label' => 'Email', + 'provider' => \App\Enums\NotificationChannel::EMAIL, + 'data' => [ + 'email' => 'email@vitodeploy.com', + ], + 'connected' => 1, + ]); + } +} diff --git a/database/seeders/ProjectsSeeder.php b/database/seeders/ProjectsSeeder.php new file mode 100644 index 0000000..23c1449 --- /dev/null +++ b/database/seeders/ProjectsSeeder.php @@ -0,0 +1,19 @@ +create([ + 'name' => 'vitodeploy', + ]); + Project::query()->create([ + 'name' => 'laravel', + ]); + } +} diff --git a/database/seeders/ServerLogsSeeder.php b/database/seeders/ServerLogsSeeder.php new file mode 100644 index 0000000..5169c85 --- /dev/null +++ b/database/seeders/ServerLogsSeeder.php @@ -0,0 +1,26 @@ +create([ + 'server_id' => $server->id, + 'type' => 'remote', + 'name' => '/var/log/nginx/error.log', + 'disk' => 'ssh', + 'is_remote' => true, + ]); + } + } +} diff --git a/database/seeders/ServerProvidersSeeder.php b/database/seeders/ServerProvidersSeeder.php new file mode 100644 index 0000000..524f1b4 --- /dev/null +++ b/database/seeders/ServerProvidersSeeder.php @@ -0,0 +1,58 @@ +create([ + 'profile' => 'AWS', + 'provider' => 'aws', + 'credentials' => [ + 'key' => 'aws_key', + 'secret' => 'aws_secret', + ], + 'connected' => 1, + ]); + + ServerProvider::factory()->create([ + 'profile' => 'Digital Ocean', + 'provider' => 'digitalocean', + 'credentials' => [ + 'token' => 'do_token', + ], + 'connected' => 1, + ]); + + ServerProvider::factory()->create([ + 'profile' => 'Linode', + 'provider' => 'linode', + 'credentials' => [ + 'token' => 'linode_token', + ], + 'connected' => 1, + ]); + + ServerProvider::factory()->create([ + 'profile' => 'Vultr', + 'provider' => 'vultr', + 'credentials' => [ + 'token' => 'vultr_token', + ], + 'connected' => 1, + ]); + + ServerProvider::factory()->create([ + 'profile' => 'Hetzner', + 'provider' => 'hetzner', + 'credentials' => [ + 'token' => 'hetzner_token', + ], + 'connected' => 1, + ]); + } +} diff --git a/database/seeders/ServersSeeder.php b/database/seeders/ServersSeeder.php new file mode 100644 index 0000000..71e5b20 --- /dev/null +++ b/database/seeders/ServersSeeder.php @@ -0,0 +1,146 @@ +first(); + + $projects = Project::all(); + foreach ($projects as $project) { + $this->createResources($user, $project); + } + } + + private function createResources(User $user, Project $project): void + { + $providers = ServerProvider::all(); + + /** @var Tag $tag */ + foreach ($project->tags()->get() as $tag) { + $provider = $providers->random(); + // database + /** @var Server $db */ + $db = Server::factory()->create([ + 'user_id' => $user->id, + 'project_id' => $project->id, + 'name' => $tag->name.'-'.'database', + 'provider' => $provider->provider, + 'provider_id' => $provider->id, + ]); + $db->tags()->attach($tag->id); + $this->database($db); + $this->firewall($db); + $this->monitoring($db); + $this->redis($db); + + // app-1 + /** @var Server $app */ + $app = Server::factory()->create([ + 'user_id' => $user->id, + 'project_id' => $project->id, + 'name' => $tag->name.'-'.'app-1', + 'provider' => $provider->provider, + 'provider_id' => $provider->id, + ]); + $app->tags()->attach($tag->id); + $this->webserver($app); + $this->php($app); + $this->firewall($app); + $this->monitoring($app); + $this->supervisor($app); + $this->redis($app); + } + } + + private function database(Server $server): void + { + Service::query()->create([ + 'server_id' => $server->id, + 'type' => 'database', + 'name' => config('core.databases_name.mysql80'), + 'version' => config('core.databases_version.mysql80'), + 'status' => ServiceStatus::READY, + ]); + } + + private function webserver(Server $server): void + { + Service::query()->create([ + 'server_id' => $server->id, + 'type' => 'webserver', + 'name' => 'nginx', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); + } + + private function php(Server $server): void + { + Service::query()->create([ + 'server_id' => $server->id, + 'type' => 'php', + 'name' => 'php', + 'version' => '8.2', + 'status' => ServiceStatus::READY, + ]); + } + + private function firewall(Server $server): void + { + Service::query()->create([ + 'server_id' => $server->id, + 'type' => 'firewall', + 'name' => 'ufw', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); + app(CreateServer::class)->createFirewallRules($server); + } + + private function monitoring(Server $server): void + { + Service::query()->create([ + 'server_id' => $server->id, + 'type' => 'monitoring', + 'name' => 'remote-monitor', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); + } + + private function redis(Server $server): void + { + Service::query()->create([ + 'server_id' => $server->id, + 'type' => 'memory_database', + 'name' => 'redis', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); + } + + private function supervisor(Server $server): void + { + Service::query()->create([ + 'server_id' => $server->id, + 'type' => 'process_manager', + 'name' => 'supervisor', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); + } +} diff --git a/database/seeders/SitesSeeder.php b/database/seeders/SitesSeeder.php new file mode 100644 index 0000000..71fb00c --- /dev/null +++ b/database/seeders/SitesSeeder.php @@ -0,0 +1,68 @@ +whereHas('services', function (Builder $query) { + $query->where('type', 'webserver'); + })->get(); + + $sourceControls = SourceControl::all(); + + /** @var Server $server */ + foreach ($servers as $server) { + /** @var Site $app */ + $app = Site::factory()->create([ + 'server_id' => $server->id, + 'domain' => $server->project->name.'.com', + 'source_control_id' => $sourceControls->random()->id, + 'type' => SiteType::LARAVEL, + 'path' => '/home/vito/'.$server->project->name.'.com', + 'aliases' => ['www.'.$server->project->name.'.com'], + ]); + $app->tags()->attach($server->tags()->first()); + Queue::factory()->create([ + 'site_id' => $app->id, + 'command' => 'php artisan queue:work', + 'status' => QueueStatus::RUNNING, + ]); + Ssl::factory()->create([ + 'site_id' => $app->id, + 'type' => SslType::LETSENCRYPT, + 'expires_at' => now()->addYear(), + 'status' => SslStatus::CREATED, + ]); + + /** @var Site $blog */ + $blog = Site::factory()->create([ + 'server_id' => $server->id, + 'domain' => 'blog.'.$server->project->name.'.com', + 'type' => SiteType::WORDPRESS, + 'path' => '/home/vito/'.'blog.'.$server->project->name.'.com', + 'aliases' => ['www.'.'blog.'.$server->project->name.'.com'], + ]); + $blog->tags()->attach($server->tags()->first()); + Ssl::factory()->create([ + 'site_id' => $blog->id, + 'type' => SslType::LETSENCRYPT, + 'expires_at' => now()->addYear(), + 'status' => SslStatus::CREATED, + ]); + } + } +} diff --git a/database/seeders/SourceControlsSeeder.php b/database/seeders/SourceControlsSeeder.php new file mode 100644 index 0000000..f7062a3 --- /dev/null +++ b/database/seeders/SourceControlsSeeder.php @@ -0,0 +1,37 @@ +create([ + 'profile' => 'GitHub', + 'provider' => \App\Enums\SourceControl::GITHUB, + 'provider_data' => [ + 'token' => 'github_token', + ], + ]); + + SourceControl::factory()->create([ + 'profile' => 'GitLab', + 'provider' => \App\Enums\SourceControl::GITLAB, + 'provider_data' => [ + 'token' => 'gitlab_token', + ], + ]); + + SourceControl::factory()->create([ + 'profile' => 'Bitbucket', + 'provider' => \App\Enums\SourceControl::BITBUCKET, + 'provider_data' => [ + 'username' => 'bitbucket_username', + 'password' => 'bitbucket_password', + ], + ]); + } +} diff --git a/database/seeders/SshKeysSeeder.php b/database/seeders/SshKeysSeeder.php new file mode 100644 index 0000000..26f76c8 --- /dev/null +++ b/database/seeders/SshKeysSeeder.php @@ -0,0 +1,27 @@ +create([ + 'user_id' => $server->user_id, + 'name' => 'id_rsa', + 'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDZ', + ]); + $server->sshKeys()->attach($sshKey, [ + 'status' => SshKeyStatus::ADDED, + ]); + } + } +} diff --git a/database/seeders/StorageProvidersSeeder.php b/database/seeders/StorageProvidersSeeder.php new file mode 100644 index 0000000..288de8e --- /dev/null +++ b/database/seeders/StorageProvidersSeeder.php @@ -0,0 +1,30 @@ +create([ + 'profile' => 'FTP', + 'provider' => \App\Enums\StorageProvider::FTP, + 'credentials' => [ + 'host' => 'ftp.example.com', + 'username' => 'ftp_user', + 'password' => 'ftp_password', + ], + ]); + + StorageProvider::factory()->create([ + 'profile' => 'S3', + 'provider' => \App\Enums\StorageProvider::S3, + 'credentials' => [ + 'secret' => 's3_secret', + ], + ]); + } +} diff --git a/database/seeders/TagsSeeder.php b/database/seeders/TagsSeeder.php new file mode 100644 index 0000000..4510088 --- /dev/null +++ b/database/seeders/TagsSeeder.php @@ -0,0 +1,25 @@ +tags()->create([ + 'name' => 'production', + 'color' => 'red', + ]); + $project->tags()->create([ + 'name' => 'staging', + 'color' => 'yellow', + ]); + } + } +} diff --git a/database/seeders/UsersSeeder.php b/database/seeders/UsersSeeder.php new file mode 100644 index 0000000..5453551 --- /dev/null +++ b/database/seeders/UsersSeeder.php @@ -0,0 +1,19 @@ +create([ + 'name' => 'Demo User', + 'email' => 'demo@vitodeploy.com', + 'current_project_id' => Project::query()->first()->id, + ]); + } +}