diff --git a/app/Actions/RemoteProcess/RunRemoteProcess.php b/app/Actions/RemoteProcess/RunRemoteProcess.php index 790f21efd..038b681a8 100644 --- a/app/Actions/RemoteProcess/RunRemoteProcess.php +++ b/app/Actions/RemoteProcess/RunRemoteProcess.php @@ -70,22 +70,7 @@ protected function getCommand(): string $port = $this->activity->getExtraProperty('port'); $command = $this->activity->getExtraProperty('command'); - $delimiter = 'EOF-COOLIFY-SSH'; - Storage::disk('local')->makeDirectory('.ssh'); - - $ssh_command = "ssh " - . "-i {$private_key_location} " - . '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' - . '-o PasswordAuthentication=no ' - . '-o RequestTTY=no ' - . '-o LogLevel=ERROR ' - . '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/.ssh/ssh_mux_%h_%p_%r ' - . "-p {$port} " - . "{$user}@{$server_ip} " - . " 'bash -se' << \\$delimiter" . PHP_EOL - . $command . PHP_EOL - . $delimiter; - return $ssh_command; + return generateSshCommand($private_key_location, $server_ip, $user, $port, $command); } protected function handleOutput(string $type, string $output) diff --git a/app/Http/Livewire/DeployApplication.php b/app/Http/Livewire/DeployApplication.php index 461fe0765..9a5deb245 100644 --- a/app/Http/Livewire/DeployApplication.php +++ b/app/Http/Livewire/DeployApplication.php @@ -2,6 +2,7 @@ namespace App\Http\Livewire; +use App\Jobs\ContainerStatusJob; use App\Models\Application; use App\Models\CoolifyInstanceSettings; use DateTimeImmutable; @@ -220,7 +221,7 @@ public function deploy() $this->execute_in_builder("docker build -f {$workdir}/Dockerfile --build-arg SOURCE_COMMIT=$(cat {$workdir}/.git-commit) --progress plain -t {$this->application->uuid}:$(cat {$workdir}/.git-commit) {$workdir}"); $this->command[] = "echo 'Done.'"; - $this->execute_in_builder("test ! -z \"$(docker ps --format '{{.State}}' --filter 'name={$this->application->uuid}')\" && docker rm -f {$this->application->uuid} >/dev/null 2>&1"); + // $this->execute_in_builder("test ! -z \"$(docker ps --format '{{.State}}' --filter 'name={$this->application->uuid}')\" && docker rm -f {$this->application->uuid} >/dev/null 2>&1"); $this->command[] = "echo -n 'Deploying... '"; @@ -235,6 +236,7 @@ public function deploy() } public function cancel() { + ContainerStatusJob::dispatch(); } public function render() { diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php new file mode 100644 index 000000000..6ece825ea --- /dev/null +++ b/app/Jobs/ContainerStatusJob.php @@ -0,0 +1,61 @@ +where('settings->is_build_server', '=', false)->first(); + $applications = Application::all(); + $not_found_applications = $applications; + $containers = []; + // foreach ($servers as $server) { + $private_key_location = savePrivateKey($server); + $ssh_command = generateSshCommand($private_key_location, $server->ip, $server->user, $server->port, 'docker ps -a -q --format \'{{json .}}\''); + $process = Process::run($ssh_command); + $output = trim($process->output()); + $containers = formatDockerCmdOutputToJson($output); + foreach ($containers as $container) { + $found_application = $applications->filter(function ($value, $key) use ($container) { + return $value->uuid == $container['Names']; + })->first(); + if ($found_application) { + $not_found_applications = $not_found_applications->filter(function ($value, $key) use ($found_application) { + return $value->uuid != $found_application->uuid; + }); + $found_application->status = $container['State']; + $found_application->save(); + Log::info('Found application: ' . $found_application->uuid . ' settings status to: ' . $found_application->status); + } + } + foreach ($not_found_applications as $not_found_application) { + $not_found_application->status = 'exited'; + $not_found_application->save(); + Log::info('Not found application: ' . $not_found_application->uuid . ' settings status to: ' . $not_found_application->status); + } + } catch (\Exception $e) { + Log::error($e->getMessage()); + } + } +} diff --git a/app/Models/Server.php b/app/Models/Server.php index 2f5e67971..da302d056 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -4,8 +4,20 @@ class Server extends BaseModel { + protected static function booted() + { + static::created(function ($server) { + ServerSetting::create([ + 'server_id' => $server->id, + ]); + }); + } public function privateKey() { return $this->belongsTo(PrivateKey::class); } + public function settings() + { + return $this->hasOne(ServerSetting::class); + } } diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php new file mode 100644 index 000000000..7b76e829c --- /dev/null +++ b/app/Models/ServerSetting.php @@ -0,0 +1,11 @@ +belongsTo(Server::class); + } +} diff --git a/bootstrap/helpers.php b/bootstrap/helpers.php index 86bc7a6e0..64e2fa8b2 100644 --- a/bootstrap/helpers.php +++ b/bootstrap/helpers.php @@ -5,6 +5,8 @@ use App\Enums\ActivityTypes; use App\Models\Server; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Spatie\Activitylog\Contracts\Activity; @@ -24,9 +26,7 @@ function remoteProcess( // @TODO: Check if the user has access to this server // checkTeam($server->team_id); - $temp_file = 'id.rsa_' . 'root' . '@' . $server->ip; - Storage::disk('local')->put($temp_file, $server->privateKey->private_key, 'private'); - $private_key_location = '/var/www/html/storage/app/' . $temp_file; + $private_key_location = savePrivateKey($server); return resolve(DispatchRemoteProcess::class, [ 'remoteProcessArgs' => new RemoteProcessArgs( @@ -43,11 +43,72 @@ function remoteProcess( ), ])(); } - function checkTeam(string $team_id) + // function checkTeam(string $team_id) + // { + // $found_team = auth()->user()->teams->pluck('id')->contains($team_id); + // if (!$found_team) { + // throw new \RuntimeException('You do not have access to this server.'); + // } + // } +} + +if (!function_exists('savePrivateKey')) { + function savePrivateKey(Server $server) { - $found_team = auth()->user()->teams->pluck('id')->contains($team_id); - if (!$found_team) { - throw new \RuntimeException('You do not have access to this server.'); - } + $temp_file = 'id.rsa_' . 'root' . '@' . $server->ip; + Storage::disk('local')->put($temp_file, $server->privateKey->private_key, 'private'); + return '/var/www/html/storage/app/' . $temp_file; } } + +if (!function_exists('generateSshCommand')) { + function generateSshCommand(string $private_key_location, string $server_ip, string $user, string $port, $command) + { + $delimiter = 'EOF-COOLIFY-SSH'; + Storage::disk('local')->makeDirectory('.ssh'); + $ssh_command = "ssh " + . "-i {$private_key_location} " + . '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ' + . '-o PasswordAuthentication=no ' + . '-o RequestTTY=no ' + . '-o LogLevel=ERROR ' + . '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/.ssh/ssh_mux_%h_%p_%r ' + . "-p {$port} " + . "{$user}@{$server_ip} " + . " 'bash -se' << \\$delimiter" . PHP_EOL + . $command . PHP_EOL + . $delimiter; + return $ssh_command; + } +} +if (!function_exists('formatDockerCmdOutputToJson')) { + function formatDockerCmdOutputToJson($rawOutput): Collection + { + $outputLines = explode(PHP_EOL, $rawOutput); + + return collect($outputLines) + ->reject(fn ($line) => empty($line)) + ->map(fn ($outputLine) => json_decode($outputLine, true, flags: JSON_THROW_ON_ERROR)); + } +} +if (!function_exists('formatDockerLabelsToJson')) { + function formatDockerLabelsToJson($rawOutput): Collection + { + $outputLines = explode(PHP_EOL, $rawOutput); + + return collect($outputLines) + ->reject(fn ($line) => empty($line)) + ->map(function ($outputLine) { + $outputArray = explode(',', $outputLine); + return collect($outputArray) + ->map(function ($outputLine) { + return explode('=', $outputLine); + }) + ->mapWithKeys(function ($outputLine) { + return [$outputLine[0] => $outputLine[1]]; + }); + })[0]; + + } +} + diff --git a/composer.lock b/composer.lock index 28478c6fd..87f668056 100644 --- a/composer.lock +++ b/composer.lock @@ -1193,16 +1193,16 @@ }, { "name": "laravel/framework", - "version": "v10.4.1", + "version": "v10.5.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "7d15f7eef442633cff108f83d9fe43d8c3b8b76f" + "reference": "485f22333e8c1dff5bae0fe0421c1e2e139713de" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/7d15f7eef442633cff108f83d9fe43d8c3b8b76f", - "reference": "7d15f7eef442633cff108f83d9fe43d8c3b8b76f", + "url": "https://api.github.com/repos/laravel/framework/zipball/485f22333e8c1dff5bae0fe0421c1e2e139713de", + "reference": "485f22333e8c1dff5bae0fe0421c1e2e139713de", "shasum": "" }, "require": { @@ -1389,7 +1389,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-03-18T11:34:02+00:00" + "time": "2023-03-29T15:09:16+00:00" }, { "name": "laravel/sanctum", @@ -2683,16 +2683,16 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.7.0", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "1534aea9bde19a5c85c5d1e1f834ab63f4c5dcf5" + "reference": "dfc078e8af9c99210337325ff5aa152872c98714" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/1534aea9bde19a5c85c5d1e1f834ab63f4c5dcf5", - "reference": "1534aea9bde19a5c85c5d1e1f834ab63f4c5dcf5", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/dfc078e8af9c99210337325ff5aa152872c98714", + "reference": "dfc078e8af9c99210337325ff5aa152872c98714", "shasum": "" }, "require": { @@ -2735,9 +2735,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.0" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.7.1" }, - "time": "2023-03-12T10:13:29+00:00" + "time": "2023-03-27T19:02:04+00:00" }, { "name": "phpoption/phpoption", @@ -3378,16 +3378,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.13", + "version": "v0.11.14", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "722317c9f5627e588788e340f29b923e58f92f54" + "reference": "8c2e264def7a8263a68ef6f0b55ce90b77d41e17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/722317c9f5627e588788e340f29b923e58f92f54", - "reference": "722317c9f5627e588788e340f29b923e58f92f54", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/8c2e264def7a8263a68ef6f0b55ce90b77d41e17", + "reference": "8c2e264def7a8263a68ef6f0b55ce90b77d41e17", "shasum": "" }, "require": { @@ -3448,9 +3448,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.13" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.14" }, - "time": "2023-03-21T14:22:44+00:00" + "time": "2023-03-28T03:41:01+00:00" }, { "name": "ralouphie/getallheaders", @@ -7534,16 +7534,16 @@ }, { "name": "laravel/sail", - "version": "v1.21.2", + "version": "v1.21.3", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "19d6fe167e2389b41fe1b4ee52293d1eaf8a43fc" + "reference": "3042ff8cf403817c340d5a7762b2d32900239f46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/19d6fe167e2389b41fe1b4ee52293d1eaf8a43fc", - "reference": "19d6fe167e2389b41fe1b4ee52293d1eaf8a43fc", + "url": "https://api.github.com/repos/laravel/sail/zipball/3042ff8cf403817c340d5a7762b2d32900239f46", + "reference": "3042ff8cf403817c340d5a7762b2d32900239f46", "shasum": "" }, "require": { @@ -7595,7 +7595,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2023-03-06T14:23:15+00:00" + "time": "2023-03-13T01:22:10+00:00" }, { "name": "mockery/mockery", @@ -7889,16 +7889,16 @@ }, { "name": "pestphp/pest", - "version": "v2.2.3", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "6c8970e0a3b9bb36544bb1eacba0a4175dbafe97" + "reference": "5ae061d2080ea09608fe37fb05ebadcd183cef85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/6c8970e0a3b9bb36544bb1eacba0a4175dbafe97", - "reference": "6c8970e0a3b9bb36544bb1eacba0a4175dbafe97", + "url": "https://api.github.com/repos/pestphp/pest/zipball/5ae061d2080ea09608fe37fb05ebadcd183cef85", + "reference": "5ae061d2080ea09608fe37fb05ebadcd183cef85", "shasum": "" }, "require": { @@ -7908,10 +7908,10 @@ "pestphp/pest-plugin": "^2.0.1", "pestphp/pest-plugin-arch": "^2.0.2", "php": "^8.1.0", - "phpunit/phpunit": "^10.0.18" + "phpunit/phpunit": "^10.0.19" }, "conflict": { - "phpunit/phpunit": ">10.0.18", + "phpunit/phpunit": ">10.0.19", "webmozart/assert": "<1.11.0" }, "require-dev": { @@ -7972,7 +7972,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v2.2.3" + "source": "https://github.com/pestphp/pest/tree/v2.3.0" }, "funding": [ { @@ -7984,7 +7984,7 @@ "type": "github" } ], - "time": "2023-03-24T11:26:54+00:00" + "time": "2023-03-28T09:16:29+00:00" }, { "name": "pestphp/pest-plugin", @@ -8606,16 +8606,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.0.18", + "version": "10.0.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "582563ed2edc62d1455cdbe00ea49fe09428eef3" + "reference": "20c23e85c86e5c06d63538ba464e8054f4744e62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/582563ed2edc62d1455cdbe00ea49fe09428eef3", - "reference": "582563ed2edc62d1455cdbe00ea49fe09428eef3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/20c23e85c86e5c06d63538ba464e8054f4744e62", + "reference": "20c23e85c86e5c06d63538ba464e8054f4744e62", "shasum": "" }, "require": { @@ -8687,7 +8687,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.18" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.0.19" }, "funding": [ { @@ -8703,7 +8703,7 @@ "type": "tidelift" } ], - "time": "2023-03-22T06:15:31+00:00" + "time": "2023-03-27T11:46:33+00:00" }, { "name": "sebastian/cli-parser", diff --git a/database/migrations/2023_03_24_140712_create_server_settings_table.php b/database/migrations/2023_03_24_140712_create_server_settings_table.php new file mode 100644 index 000000000..a82f49a92 --- /dev/null +++ b/database/migrations/2023_03_24_140712_create_server_settings_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('uuid')->unique(); + $table->boolean('is_build_server')->default(false); + + $table->foreignId('server_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('server_settings'); + } +}; diff --git a/database/seeders/ApplicationSeeder.php b/database/seeders/ApplicationSeeder.php index ccfa74045..3a8fcde68 100644 --- a/database/seeders/ApplicationSeeder.php +++ b/database/seeders/ApplicationSeeder.php @@ -25,7 +25,7 @@ public function run(): void $github_private_source = GithubApp::find(2); Application::create([ 'id' => 1, - 'name' => 'My first application', + 'name' => 'Public application (from GitHub)', 'git_repository' => 'coollabsio/coolify-examples', 'git_branch' => 'nodejs-fastify', 'build_pack' => 'nixpacks', @@ -39,7 +39,7 @@ public function run(): void ]); Application::create([ 'id' => 2, - 'name' => 'My second application', + 'name' => 'Private application (through GitHub App)', 'git_repository' => 'coollabsio/nodejs-example', 'git_branch' => 'main', 'build_pack' => 'nixpacks', diff --git a/database/seeders/DBSeeder.php b/database/seeders/DBSeeder.php index 68bbeb01c..35effa0c6 100644 --- a/database/seeders/DBSeeder.php +++ b/database/seeders/DBSeeder.php @@ -13,12 +13,12 @@ public function run(): void { $environment_1 = Environment::find(1); $standalone_docker_1 = StandaloneDocker::find(1); - Database::create([ - 'id' => 1, - 'name'=> "My first database", - 'environment_id' => $environment_1->id, - 'destination_id' => $standalone_docker_1->id, - 'destination_type' => StandaloneDocker::class, - ]); + // Database::create([ + // 'id' => 1, + // 'name'=> "My first database", + // 'environment_id' => $environment_1->id, + // 'destination_id' => $standalone_docker_1->id, + // 'destination_type' => StandaloneDocker::class, + // ]); } } diff --git a/database/seeders/ServiceSeeder.php b/database/seeders/ServiceSeeder.php index f79b72144..360c946c5 100644 --- a/database/seeders/ServiceSeeder.php +++ b/database/seeders/ServiceSeeder.php @@ -16,12 +16,12 @@ public function run(): void { $environment_1 = Environment::find(1); $standalone_docker_1 = StandaloneDocker::find(1); - Service::create([ - 'id' => 1, - 'name'=> "My first service", - 'environment_id' => $environment_1->id, - 'destination_id' => $standalone_docker_1->id, - 'destination_type' => StandaloneDocker::class, - ]); + // Service::create([ + // 'id' => 1, + // 'name'=> "My first service", + // 'environment_id' => $environment_1->id, + // 'destination_id' => $standalone_docker_1->id, + // 'destination_type' => StandaloneDocker::class, + // ]); } } diff --git a/resources/views/livewire/demo-deploy-application.blade.php b/resources/views/livewire/demo-deploy-application.blade.php deleted file mode 100644 index 628c5de78..000000000 --- a/resources/views/livewire/demo-deploy-application.blade.php +++ /dev/null @@ -1,9 +0,0 @@ -
- @isset($activity?->id) -
- Activity: {{ $activity?->id ?? 'waiting' }} -
-
{{ data_get($activity, 'description') }}
- @endisset - -
diff --git a/resources/views/project/application.blade.php b/resources/views/project/application.blade.php index db9ba59ee..4a87abef2 100644 --- a/resources/views/project/application.blade.php +++ b/resources/views/project/application.blade.php @@ -1,7 +1,8 @@

Application

-

Name: {{ $project->name }}

-

UUID: {{ $project->uuid }}

+

Name: {{ $application->name }}

+

Application UUID: {{ $application->uuid }}

+

Status: {{ $application->status }}

Deployments

diff --git a/tests/Feature/DockerCommandsTest.php b/tests/Feature/DockerCommandsTest.php index a8418efc1..377777921 100644 --- a/tests/Feature/DockerCommandsTest.php +++ b/tests/Feature/DockerCommandsTest.php @@ -15,7 +15,7 @@ // Assert there's no containers start with coolify_test_* $activity = remoteProcess([$areThereCoolifyTestContainers], $host); - $containers = Output::containerList($activity->getExtraProperty('stdout')); + $containers = formatDockerCmdOutputToJson($activity->getExtraProperty('stdout')); expect($containers)->toBeEmpty(); // start a container nginx -d --name = $containerName @@ -24,7 +24,7 @@ // docker ps name = $container $activity = remoteProcess([$areThereCoolifyTestContainers], $host); - $containers = Output::containerList($activity->getExtraProperty('stdout')); + $containers = formatDockerCmdOutputToJson($activity->getExtraProperty('stdout')); expect($containers->where('Names', $containerName)->count())->toBe(1); // Stop testing containers diff --git a/tests/Support/Output.php b/tests/Support/Output.php deleted file mode 100644 index a3bd2c52e..000000000 --- a/tests/Support/Output.php +++ /dev/null @@ -1,17 +0,0 @@ -reject(fn($line) => empty($line)) - ->map(fn($outputLine) => json_decode($outputLine, flags: JSON_THROW_ON_ERROR)); - } -}