This commit is contained in:
Saeed Vaziry 2024-12-14 03:13:47 +01:00 committed by GitHub
parent 0d12dd0c69
commit 572d1df010
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 734 additions and 67 deletions

View File

@ -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([
[

View File

@ -0,0 +1,96 @@
<?php
namespace App\Providers;
use App\Facades\SSH;
use App\Models\User;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\ServiceProvider;
class DemoServiceProvider extends ServiceProvider
{
protected string $error = 'Cannot modify on Demo!';
protected array $canDelete = [
//
];
protected array $canUpdate = [
'App\Models\ServerLog',
'App\Models\Script',
'App\Models\ScriptExecution',
];
protected array $canCreate = [
'App\Models\ServerLog',
'App\Models\Script',
'App\Models\ScriptExecution',
'App\Models\FirewallRule',
'App\Models\PersonalAccessToken',
];
public function register(): void
{
//
}
public function boot(): void
{
if (! config('app.demo') || app()->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);
});
}
}

View File

@ -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;

View File

@ -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

View File

@ -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),
];

View File

@ -0,0 +1,22 @@
<?php
namespace Database\Seeders;
use App\Models\CronJob;
use App\Models\Server;
use Illuminate\Database\Seeder;
class CronJobsSeeder extends Seeder
{
public function run(): void
{
$servers = Server::all();
foreach ($servers as $server) {
CronJob::factory()->create([
'server_id' => $server->id,
'command' => 'php /home/vito/'.$server->project->name.'.com/artisan schedule:run',
]);
}
}
}

View File

@ -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,
]);
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Database\Seeders;
use App\Enums\BackupFileStatus;
use App\Models\Backup;
use App\Models\BackupFile;
use App\Models\Database;
use App\Models\DatabaseUser;
use App\Models\Server;
use App\Models\StorageProvider;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Seeder;
class DatabasesSeeder extends Seeder
{
public function run(): void
{
$servers = Server::query()->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,
]);
}
}
}

View File

@ -3,25 +3,26 @@
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()
/** @var Server $server */
foreach ($servers as $server) {
$monitoring = $server->services()
->where('type', 'monitoring')
->firstOrFail();
$range = CarbonPeriod::create(Carbon::now()->subDays(7), '1 minute', Carbon::now());
->first();
$range = CarbonPeriod::create(Carbon::now()->subHour(), '1 minute', Carbon::now());
foreach ($range as $date) {
Metric::factory()->create([
'server_id' => $monitoring->server_id,
@ -30,3 +31,4 @@ public function run(): void
}
}
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Database\Seeders;
use App\Models\NotificationChannel;
use Illuminate\Database\Seeder;
class NotificationChannelsSeeder extends Seeder
{
public function run(): void
{
NotificationChannel::factory()->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,
]);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Database\Seeders;
use App\Models\Project;
use Illuminate\Database\Seeder;
class ProjectsSeeder extends Seeder
{
public function run(): void
{
Project::query()->create([
'name' => 'vitodeploy',
]);
Project::query()->create([
'name' => 'laravel',
]);
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Database\Seeders;
use App\Models\Server;
use App\Models\ServerLog;
use Illuminate\Database\Seeder;
class ServerLogsSeeder extends Seeder
{
public function run(): void
{
$servers = Server::all();
foreach ($servers as $server) {
ServerLog::log($server, 'install-php', 'PHP 7.4 installed');
ServerLog::log($server, 'install-nginx', 'Nginx installed');
ServerLog::factory()->create([
'server_id' => $server->id,
'type' => 'remote',
'name' => '/var/log/nginx/error.log',
'disk' => 'ssh',
'is_remote' => true,
]);
}
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace Database\Seeders;
use App\Models\ServerProvider;
use Illuminate\Database\Seeder;
class ServerProvidersSeeder extends Seeder
{
public function run(): void
{
ServerProvider::factory()->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,
]);
}
}

View File

@ -0,0 +1,146 @@
<?php
namespace Database\Seeders;
use App\Actions\Server\CreateServer;
use App\Enums\ServiceStatus;
use App\Models\Project;
use App\Models\Server;
use App\Models\ServerProvider;
use App\Models\Service;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Database\Seeder;
class ServersSeeder extends Seeder
{
public function run(): void
{
/** @var User $user */
$user = User::query()->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,
]);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Database\Seeders;
use App\Enums\QueueStatus;
use App\Enums\SiteType;
use App\Enums\SslStatus;
use App\Enums\SslType;
use App\Models\Queue;
use App\Models\Server;
use App\Models\Site;
use App\Models\SourceControl;
use App\Models\Ssl;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Seeder;
class SitesSeeder extends Seeder
{
public function run(): void
{
$servers = Server::query()->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,
]);
}
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Database\Seeders;
use App\Models\SourceControl;
use Illuminate\Database\Seeder;
class SourceControlsSeeder extends Seeder
{
public function run(): void
{
SourceControl::factory()->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',
],
]);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Database\Seeders;
use App\Enums\SshKeyStatus;
use App\Models\Server;
use App\Models\SshKey;
use Illuminate\Database\Seeder;
class SshKeysSeeder extends Seeder
{
public function run(): void
{
$servers = Server::all();
foreach ($servers as $server) {
$sshKey = SshKey::factory()->create([
'user_id' => $server->user_id,
'name' => 'id_rsa',
'public_key' => 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDZ',
]);
$server->sshKeys()->attach($sshKey, [
'status' => SshKeyStatus::ADDED,
]);
}
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Database\Seeders;
use App\Models\StorageProvider;
use Illuminate\Database\Seeder;
class StorageProvidersSeeder extends Seeder
{
public function run(): void
{
StorageProvider::factory()->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',
],
]);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Database\Seeders;
use App\Models\Project;
use Illuminate\Database\Seeder;
class TagsSeeder extends Seeder
{
public function run(): void
{
$projects = Project::all();
foreach ($projects as $project) {
$project->tags()->create([
'name' => 'production',
'color' => 'red',
]);
$project->tags()->create([
'name' => 'staging',
'color' => 'yellow',
]);
}
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Database\Seeders;
use App\Models\Project;
use App\Models\User;
use Illuminate\Database\Seeder;
class UsersSeeder extends Seeder
{
public function run(): void
{
User::factory()->create([
'name' => 'Demo User',
'email' => 'demo@vitodeploy.com',
'current_project_id' => Project::query()->first()->id,
]);
}
}