Merge pull request #1224 from coollabsio/next

v4.0.0-beta.36
This commit is contained in:
Andras Bacsai 2023-09-14 16:28:52 +02:00 committed by GitHub
commit f93317cd2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 725 additions and 300 deletions

View File

@ -10,9 +10,6 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\Process;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
const TIMEOUT = 3600;
const IDLE_TIMEOUT = 3600;
class RunRemoteProcess class RunRemoteProcess
{ {
public Activity $activity; public Activity $activity;
@ -76,8 +73,7 @@ class RunRemoteProcess
$this->time_start = hrtime(true); $this->time_start = hrtime(true);
$status = ProcessStatus::IN_PROGRESS; $status = ProcessStatus::IN_PROGRESS;
$processResult = processWithEnv()->forever()->run($this->getCommand(), $this->handleOutput(...));
$processResult = Process::timeout(TIMEOUT)->idleTimeout(IDLE_TIMEOUT)->run($this->getCommand(), $this->handleOutput(...));
if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) { if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) {
$status = ProcessStatus::ERROR; $status = ProcessStatus::ERROR;
@ -108,11 +104,10 @@ class RunRemoteProcess
{ {
$user = $this->activity->getExtraProperty('user'); $user = $this->activity->getExtraProperty('user');
$server_ip = $this->activity->getExtraProperty('server_ip'); $server_ip = $this->activity->getExtraProperty('server_ip');
$private_key_location = $this->activity->getExtraProperty('private_key_location');
$port = $this->activity->getExtraProperty('port'); $port = $this->activity->getExtraProperty('port');
$command = $this->activity->getExtraProperty('command'); $command = $this->activity->getExtraProperty('command');
return generate_ssh_command($private_key_location, $server_ip, $user, $port, $command); return generateSshCommand($server_ip, $user, $port, $command);
} }
protected function handleOutput(string $type, string $output) protected function handleOutput(string $type, string $output)

View File

@ -8,7 +8,7 @@ use Spatie\Activitylog\Models\Activity;
class StartProxy class StartProxy
{ {
public function __invoke(Server $server): Activity public function __invoke(Server $server, bool $async = true): Activity|string
{ {
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();
$networks = collect($server->standaloneDockers)->map(function ($docker) { $networks = collect($server->standaloneDockers)->map(function ($docker) {
@ -26,8 +26,7 @@ class StartProxy
$docker_compose_yml_base64 = base64_encode($configuration); $docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save(); $server->save();
$commands = [
$activity = remote_process([
"echo '####### Creating required Docker networks...'", "echo '####### Creating required Docker networks...'",
...$create_networks_command, ...$create_networks_command,
"cd $proxy_path", "cd $proxy_path",
@ -44,8 +43,13 @@ class StartProxy
"echo '####### Starting coolify-proxy...'", "echo '####### Starting coolify-proxy...'",
'docker compose up -d --remove-orphans', 'docker compose up -d --remove-orphans',
"echo '####### Proxy installed successfully...'" "echo '####### Proxy installed successfully...'"
], $server); ];
if (!$async) {
return $activity; instant_remote_process($commands, $server);
return 'OK';
} else {
$activity = remote_process($commands, $server);
return $activity;
}
} }
} }

View File

@ -2,21 +2,15 @@
namespace App\Console; namespace App\Console;
use App\Enums\ProxyTypes;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\CheckResaleLicenseJob; use App\Jobs\CheckResaleLicenseJob;
use App\Jobs\CleanupInstanceStuffsJob; use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob; use App\Jobs\DatabaseBackupJob;
use App\Jobs\DatabaseContainerStatusJob;
use App\Jobs\DockerCleanupJob; use App\Jobs\DockerCleanupJob;
use App\Jobs\InstanceAutoUpdateJob; use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ProxyContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Jobs\ServerDetailsCheckJob;
use App\Models\Application;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use App\Models\Server; use App\Models\Server;
use App\Models\StandalonePostgresql;
use Illuminate\Console\Scheduling\Schedule; use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel; use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@ -25,15 +19,14 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void protected function schedule(Schedule $schedule): void
{ {
if (isDev()) { if (isDev()) {
$schedule->job(new ServerDetailsCheckJob(Server::find(0)))->everyTenMinutes()->onOneServer(); // $schedule->job(new ContainerStatusJob(Server::find(0)))->everyTenMinutes()->onOneServer();
// $schedule->command('horizon:snapshot')->everyMinute(); // $schedule->command('horizon:snapshot')->everyMinute();
// $schedule->job(new CleanupInstanceStuffsJob)->everyMinute(); // $schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
// $schedule->job(new CheckResaleLicenseJob)->hourly(); // $schedule->job(new CheckResaleLicenseJob)->hourly();
// $schedule->job(new DockerCleanupJob)->everyOddHour(); // $schedule->job(new DockerCleanupJob)->everyOddHour();
// $this->instance_auto_update($schedule); // $this->instance_auto_update($schedule);
// $this->check_scheduled_backups($schedule); // $this->check_scheduled_backups($schedule);
// $this->check_resources($schedule); $this->check_resources($schedule);
// $this->check_proxies($schedule);
} else { } else {
$schedule->command('horizon:snapshot')->everyFiveMinutes(); $schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer();
@ -42,26 +35,15 @@ class Kernel extends ConsoleKernel
$this->instance_auto_update($schedule); $this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
$this->check_proxies($schedule);
}
}
private function check_proxies($schedule)
{
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->whereNotNull('proxy.type')->where('proxy.type', '!=', ProxyTypes::NONE->value);
foreach ($servers as $server) {
$schedule->job(new ProxyContainerStatusJob($server))->everyMinute()->onOneServer();
} }
} }
private function check_resources($schedule) private function check_resources($schedule)
{ {
$applications = Application::all(); $servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
foreach ($applications as $application) { ray($servers);
$schedule->job(new ApplicationContainerStatusJob($application))->everyMinute()->onOneServer();
}
$postgresqls = StandalonePostgresql::all(); foreach ($servers as $server) {
foreach ($postgresqls as $postgresql) { $schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
$schedule->job(new DatabaseContainerStatusJob($postgresql))->everyMinute()->onOneServer();
} }
} }
private function instance_auto_update($schedule) private function instance_auto_update($schedule)

View File

@ -13,7 +13,6 @@ class CoolifyTaskArgs extends Data
{ {
public function __construct( public function __construct(
public string $server_ip, public string $server_ip,
public string $private_key_location,
public string $command, public string $command,
public int $port, public int $port,
public string $user, public string $user,

View File

@ -53,12 +53,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->remoteServerHost = 'coolify-testing-host'; $this->remoteServerHost = 'coolify-testing-host';
} }
} }
public function welcome() { public function explanation() {
if (isCloud()) { if (isCloud()) {
return $this->setServerType('remote'); return $this->setServerType('remote');
} }
$this->currentState = 'select-server-type'; $this->currentState = 'select-server-type';
} }
public function restartBoarding() public function restartBoarding()
{ {
if ($this->createdServer) { if ($this->createdServer) {

View File

@ -2,9 +2,8 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Models\Application; use App\Models\Application;
use App\Notifications\Application\StatusChanged;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@ -22,10 +21,11 @@ class Heading extends Component
public function check_status() public function check_status()
{ {
dispatch_sync(new ApplicationContainerStatusJob( dispatch_sync(new ContainerStatusJob($this->application->destination->server));
application: $this->application,
));
$this->application->refresh(); $this->application->refresh();
$this->application->previews->each(function ($preview) {
$preview->refresh();
});
} }
public function force_deploy_without_cache() public function force_deploy_without_cache()

View File

@ -3,6 +3,7 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob; use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\ContainerStatusJob;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@ -23,14 +24,6 @@ class Previews extends Component
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
} }
public function loadStatus($pull_request_id)
{
dispatch(new ApplicationContainerStatusJob(
application: $this->application,
pullRequestId: $pull_request_id
));
}
public function load_prs() public function load_prs()
{ {
try { try {

View File

@ -3,8 +3,7 @@
namespace App\Http\Livewire\Project\Database; namespace App\Http\Livewire\Project\Database;
use App\Actions\Database\StartPostgresql; use App\Actions\Database\StartPostgresql;
use App\Jobs\DatabaseContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Notifications\Application\StatusChanged;
use Livewire\Component; use Livewire\Component;
class Heading extends Component class Heading extends Component
@ -25,9 +24,7 @@ class Heading extends Component
public function check_status() public function check_status()
{ {
dispatch_sync(new DatabaseContainerStatusJob( dispatch_sync(new ContainerStatusJob($this->database->destination->server));
database: $this->database,
));
$this->database->refresh(); $this->database->refresh();
} }

View File

@ -56,7 +56,7 @@ class Form extends Component
$this->uptime = $uptime; $this->uptime = $uptime;
$this->emit('success', 'Server is reachable!'); $this->emit('success', 'Server is reachable!');
} else { } else {
$this->emit('error', 'Server is not rachable'); $this->emit('error', 'Server is not reachable');
return; return;
} }
if ($dockerVersion) { if ($dockerVersion) {
@ -88,17 +88,11 @@ class Form extends Component
public function submit() public function submit()
{ {
$this->validate(); $this->validate();
// $validation = Validator::make($this->server->toArray(), [ $uniqueIPs = Server::all()->pluck('ip')->toArray();
// 'ip' => [ if (in_array($this->server->ip, $uniqueIPs)) {
// 'ip' $this->emit('error', 'IP address is already in use by another team.');
// ], return;
// ]); }
// if ($validation->fails()) {
// foreach ($validation->errors()->getMessages() as $key => $value) {
// $this->addError("server.{$key}", $value[0]);
// }
// return;
// }
$this->server->settings->wildcard_domain = $this->wildcard_domain; $this->server->settings->wildcard_domain = $this->wildcard_domain;
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage; $this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
$this->server->settings->save(); $this->server->settings->save();

View File

@ -14,6 +14,7 @@ class ShowPrivateKey extends Component
public function setPrivateKey($newPrivateKeyId) public function setPrivateKey($newPrivateKeyId)
{ {
try { try {
refresh_server_connection($this->server->privateKey);
$oldPrivateKeyId = $this->server->private_key_id; $oldPrivateKeyId = $this->server->private_key_id;
$this->server->update([ $this->server->update([
'private_key_id' => $newPrivateKeyId 'private_key_id' => $newPrivateKeyId
@ -26,7 +27,7 @@ class ShowPrivateKey extends Component
'private_key_id' => $oldPrivateKeyId 'private_key_id' => $oldPrivateKeyId
]); ]);
$this->server->refresh(); $this->server->refresh();
refresh_server_connection($this->server->privateKey); refresh_server_connection($this->server->privateKey);
return general_error_handler($e, that: $this); return general_error_handler($e, that: $this);
} }
} }

View File

@ -2,7 +2,7 @@
namespace App\Http\Livewire\Settings; namespace App\Http\Livewire\Settings;
use App\Jobs\ProxyContainerStatusJob; use App\Jobs\ContainerStatusJob;
use App\Models\InstanceSettings as ModelsInstanceSettings; use App\Models\InstanceSettings as ModelsInstanceSettings;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@ -124,7 +124,7 @@ class Configuration extends Component
]; ];
} }
$this->save_configuration_to_disk($traefik_dynamic_conf, $file); $this->save_configuration_to_disk($traefik_dynamic_conf, $file);
dispatch(new ProxyContainerStatusJob($this->server)); dispatch(new ContainerStatusJob($this->server));
} }
} }

View File

@ -6,6 +6,7 @@ use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\User; use App\Models\User;
use App\Models\Waitlist; use App\Models\Waitlist;
use Livewire\Component; use Livewire\Component;
use Str;
class Index extends Component class Index extends Component
{ {
@ -46,7 +47,7 @@ class Index extends Component
return; return;
} }
$waitlist = Waitlist::create([ $waitlist = Waitlist::create([
'email' => $this->email, 'email' => Str::lower($this->email),
'type' => 'registration', 'type' => 'registration',
]); ]);

View File

@ -4,15 +4,15 @@ namespace App\Jobs;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Notifications\Application\StatusChanged;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -17,10 +17,10 @@ use App\Notifications\Application\DeploymentSuccess;
use App\Traits\ExecuteRemoteCommand; use App\Traits\ExecuteRemoteCommand;
use Exception; use Exception;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -28,10 +28,8 @@ use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Throwable; use Throwable;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use Yosymfony\Toml\Toml;
use Yosymfony\Toml\TomlArray;
class ApplicationDeploymentJob implements ShouldQueue class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
@ -49,7 +47,6 @@ class ApplicationDeploymentJob implements ShouldQueue
private GithubApp|GitlabApp $source; private GithubApp|GitlabApp $source;
private StandaloneDocker|SwarmDocker $destination; private StandaloneDocker|SwarmDocker $destination;
private Server $server; private Server $server;
private string $private_key_location;
private ApplicationPreview|null $preview = null; private ApplicationPreview|null $preview = null;
private string $container_name; private string $container_name;
@ -92,7 +89,7 @@ class ApplicationDeploymentJob implements ShouldQueue
$this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application->uuid, $this->pull_request_id); $this->container_name = generateApplicationContainerName($this->application->uuid, $this->pull_request_id);
$this->private_key_location = save_private_key_for_server($this->server); addPrivateKeyToSshAgent($this->server);
$this->saved_outputs = collect(); $this->saved_outputs = collect();
// Set preview fqdn // Set preview fqdn
@ -122,6 +119,9 @@ class ApplicationDeploymentJob implements ShouldQueue
if ($containers->count() > 0) { if ($containers->count() > 0) {
$this->currently_running_container_name = data_get($containers[0], 'Names'); $this->currently_running_container_name = data_get($containers[0], 'Names');
} }
if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) {
$this->currently_running_container_name = $this->container_name;
}
$this->application_deployment_queue->update([ $this->application_deployment_queue->update([
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
]); ]);
@ -135,7 +135,9 @@ class ApplicationDeploymentJob implements ShouldQueue
$this->deploy(); $this->deploy();
} }
} }
if ($this->application->fqdn) dispatch(new ProxyContainerStatusJob($this->server)); if ($this->server->isProxyShouldRun()) {
dispatch(new ContainerStatusJob($this->server));
}
$this->next(ApplicationDeploymentStatus::FINISHED->value); $this->next(ApplicationDeploymentStatus::FINISHED->value);
} catch (Exception $e) { } catch (Exception $e) {
ray($e); ray($e);
@ -270,6 +272,7 @@ class ApplicationDeploymentJob implements ShouldQueue
"echo 'Rolling update completed.'" "echo 'Rolling update completed.'"
], ],
); );
$this->application->update(['status' => 'running']);
break; break;
} }
$counter++; $counter++;
@ -296,7 +299,11 @@ class ApplicationDeploymentJob implements ShouldQueue
// $this->generate_build_env_variables(); // $this->generate_build_env_variables();
// $this->add_build_env_variables_to_dockerfile(); // $this->add_build_env_variables_to_dockerfile();
$this->build_image(); $this->build_image();
$this->rolling_update(); $this->stop_running_container();
$this->execute_remote_command(
["echo -n 'Starting preview deployment.'"],
[$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
);
} }
private function prepare_builder_image() private function prepare_builder_image()
@ -576,10 +583,15 @@ class ApplicationDeploymentJob implements ShouldQueue
private function set_labels_for_applications() private function set_labels_for_applications()
{ {
$appId = $this->application->id;
if ($this->pull_request_id !== 0) {
$appId = $appId . '-pr-' . $this->pull_request_id;
}
$labels = []; $labels = [];
$labels[] = 'coolify.managed=true'; $labels[] = 'coolify.managed=true';
$labels[] = 'coolify.version=' . config('version'); $labels[] = 'coolify.version=' . config('version');
$labels[] = 'coolify.applicationId=' . $this->application->id; $labels[] = 'coolify.applicationId=' . $appId;
$labels[] = 'coolify.type=application'; $labels[] = 'coolify.type=application';
$labels[] = 'coolify.name=' . $this->application->name; $labels[] = 'coolify.name=' . $this->application->name;
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {

View File

@ -6,12 +6,13 @@ use App\Enums\ProcessStatus;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ApplicationPullRequestUpdateJob implements ShouldQueue class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -4,12 +4,13 @@ namespace App\Jobs;
use App\Actions\License\CheckResaleLicense; use App\Actions\License\CheckResaleLicense;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class CheckResaleLicenseJob implements ShouldQueue class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -4,13 +4,14 @@ namespace App\Jobs;
use App\Models\Waitlist; use App\Models\Waitlist;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -0,0 +1,291 @@
<?php
namespace App\Jobs;
use App\Actions\Proxy\StartProxy;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use App\Notifications\Server\Unreachable;
use Arr;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Str;
class ContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $timeout = 120;
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [new WithoutOverlapping($this->server->uuid)];
}
public function uniqueId(): string
{
return $this->server->uuid;
}
private function checkServerConnection() {
ray("Checking server connection to {$this->server->ip}");
$uptime = instant_remote_process(['uptime'], $this->server, false);
if (!is_null($uptime)) {
ray('Server is up');
return true;
}
}
public function handle(): void
{
try {
ray()->clearAll();
$serverUptimeCheckNumber = 0;
$serverUptimeCheckNumberMax = 5;
while (true) {
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
$this->server->settings()->update(['is_reachable' => false]);
$this->server->team->notify(new Unreachable($this->server));
return;
}
$result = $this->checkServerConnection();
if ($result) {
break;
}
$serverUptimeCheckNumber++;
sleep(5);
}
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
$containers = format_docker_command_output_to_json($containers);
$applications = $this->server->applications();
$databases = $this->server->databases();
$previews = $this->server->previews();
if ($this->server->isProxyShouldRun()) {
$foundProxyContainer = $containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-proxy';
})->first();
if (!$foundProxyContainer) {
resolve(StartProxy::class)($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
}
$foundApplications = [];
$foundApplicationPreviews = [];
$foundDatabases = [];
foreach ($containers as $container) {
$containerStatus = data_get($container, 'State.Status');
$labels = data_get($container, 'Config.Labels');
$labels = Arr::undot(format_docker_labels_to_json($labels));
$labelId = data_get($labels, 'coolify.applicationId');
ray($labelId);
if ($labelId) {
if (str_contains($labelId,'-pr-')) {
$previewId = (int) Str::after($labelId, '-pr-');
$applicationId = (int) Str::before($labelId, '-pr-');
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id',$previewId)->first();
if ($preview) {
$foundApplicationPreviews[] = $preview->id;
$statusFromDb = $preview->status;
if ($statusFromDb !== $containerStatus) {
$preview->update(['status' => $containerStatus]);
}
} else {
//Notify user that this container should not be there.
}
} else {
$application = $applications->where('id', $labelId)->first();
if ($application) {
$foundApplications[] = $application->id;
$statusFromDb = $application->status;
if ($statusFromDb !== $containerStatus) {
$application->update(['status' => $containerStatus]);
}
} else {
//Notify user that this container should not be there.
}
}
} else {
$uuid = data_get($labels, 'com.docker.compose.service');
if ($uuid) {
$database = $databases->where('uuid', $uuid)->first();
if ($database) {
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
} else {
// Notify user that this container should not be there.
}
}
}
}
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
foreach($notRunningApplications as $applicationId) {
$application = $applications->where('id', $applicationId)->first();
if ($application->status === 'exited') {
continue;
}
$application->update(['status' => 'exited']);
$name = data_get($application, 'name');
$fqdn = data_get($application, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$project = data_get($application, 'environment.project');
$environment = data_get($application, 'environment');
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $application->uuid;
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
foreach ($notRunningApplicationPreviews as $previewId) {
$preview = $previews->where('id', $previewId)->first();
if ($preview->status === 'exited') {
continue;
}
$preview->update(['status' => 'exited']);
$name = data_get($preview, 'name');
$fqdn = data_get($preview, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$project = data_get($preview, 'application.environment.project');
$environment = data_get($preview, 'application.environment');
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $preview->application->uuid;
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
}
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
foreach($notRunningDatabases as $database) {
$database = $databases->where('id', $database)->first();
if ($database->status === 'exited') {
continue;
}
$database->update(['status' => 'exited']);
$name = data_get($database, 'name');
$fqdn = data_get($database, 'fqdn');
$containerName = $name;
$project = data_get($database, 'environment.project');
$environment = data_get($database, 'environment');
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/database/" . $database->uuid;
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
}
return;
foreach ($applications as $application) {
$uuid = data_get($application, 'uuid');
$id = data_get($application, 'id');
$foundContainer = $containers->filter(function ($value, $key) use ($id, $uuid) {
$labels = data_get($value, 'Config.Labels');
$labels = Arr::undot(format_docker_labels_to_json($labels));
$labelId = data_get($labels, 'coolify.applicationId');
if ($labelId == $id) {
return $value;
}
$isPR = Str::startsWith(data_get($value, 'Name'), "/$uuid");
$isPR = Str::contains(data_get($value, 'Name'), "-pr-");
if ($isPR) {
ray('is pr');
return false;
}
return $value;
})->first();
ray($foundContainer);
if ($foundContainer) {
$containerStatus = data_get($foundContainer, 'State.Status');
$databaseStatus = data_get($application, 'status');
if ($containerStatus !== $databaseStatus) {
$application->update(['status' => $containerStatus]);
}
} else {
$databaseStatus = data_get($application, 'status');
if ($databaseStatus !== 'exited') {
$application->update(['status' => 'exited']);
$name = data_get($application, 'name');
$fqdn = data_get($application, 'fqdn');
$containerName = $name ? "$name ($fqdn)" : $fqdn;
$project = data_get($application, 'environment.project');
$environment = data_get($application, 'environment');
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/application/" . $application->uuid;
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
}
}
$previews = $application->previews;
foreach ($previews as $preview) {
$foundContainer = $containers->filter(function ($value, $key) use ($id, $uuid, $preview) {
$labels = data_get($value, 'Config.Labels');
$labels = Arr::undot(format_docker_labels_to_json($labels));
$labelId = data_get($labels, 'coolify.applicationId');
if ($labelId == "$id-pr-{$preview->id}") {
return $value;
}
return Str::startsWith(data_get($value, 'Name'), "/$uuid-pr-{$preview->id}");
})->first();
}
}
foreach ($databases as $database) {
$uuid = data_get($database, 'uuid');
$foundContainer = $containers->filter(function ($value, $key) use ($uuid) {
return Str::startsWith(data_get($value, 'Name'), "/$uuid");
})->first();
if ($foundContainer) {
$containerStatus = data_get($foundContainer, 'State.Status');
$databaseStatus = data_get($database, 'status');
if ($containerStatus !== $databaseStatus) {
$database->update(['status' => $containerStatus]);
}
} else {
$databaseStatus = data_get($database, 'status');
if ($databaseStatus !== 'exited') {
$database->update(['status' => 'exited']);
$name = data_get($database, 'name');
$containerName = $name;
$project = data_get($database, 'environment.project');
$environment = data_get($database, 'environment');
$url = base_url() . '/project/' . $project->uuid . "/" . $environment->name . "/database/" . $database->uuid;
$this->server->team->notify(new ContainerStopped($containerName, $this->server, $url));
}
}
}
// TODO Monitor other containers not managed by Coolify
} catch (\Throwable $e) {
send_internal_notification('ContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

View File

@ -4,13 +4,14 @@ namespace App\Jobs;
use App\Actions\CoolifyTask\RunRemoteProcess; use App\Actions\CoolifyTask\RunRemoteProcess;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
class CoolifyTask implements ShouldQueue class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -12,6 +12,7 @@ use App\Notifications\Database\BackupFailed;
use App\Notifications\Database\BackupSuccess; use App\Notifications\Database\BackupSuccess;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
@ -20,7 +21,7 @@ use Illuminate\Queue\SerializesModels;
use Throwable; use Throwable;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class DatabaseBackupJob implements ShouldQueue class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -6,13 +6,14 @@ use App\Models\ApplicationPreview;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use App\Notifications\Application\StatusChanged; use App\Notifications\Application\StatusChanged;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class DatabaseContainerStatusJob implements ShouldQueue, ShouldBeUnique class DatabaseContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -5,13 +5,14 @@ namespace App\Jobs;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\Server; use App\Models\Server;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class DockerCleanupJob implements ShouldQueue class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -4,13 +4,14 @@ namespace App\Jobs;
use App\Actions\Server\UpdateCoolify; use App\Actions\Server\UpdateCoolify;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -7,6 +7,7 @@ use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes; use App\Enums\ProxyTypes;
use App\Models\Server; use App\Models\Server;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
@ -14,7 +15,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -5,6 +5,7 @@ namespace App\Jobs;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Waitlist; use App\Models\Waitlist;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Mail\Message; use Illuminate\Mail\Message;
@ -13,7 +14,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
class SendConfirmationForWaitlistJob implements ShouldQueue class SendConfirmationForWaitlistJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -3,13 +3,14 @@
namespace App\Jobs; namespace App\Jobs;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
class SendMessageToDiscordJob implements ShouldQueue class SendMessageToDiscordJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -3,6 +3,7 @@
namespace App\Jobs; namespace App\Jobs;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
@ -10,7 +11,7 @@ use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Str; use Str;
class SendMessageToTelegramJob implements ShouldQueue class SendMessageToTelegramJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -1,79 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Str;
class ServerDetailsCheckJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public $timeout = 120;
public function __construct(public Server $server)
{
}
public function middleware(): array
{
return [new WithoutOverlapping($this->server->uuid)];
}
public function uniqueId(): string
{
return $this->server->uuid;
}
public function handle(): void
{
try {
ray()->clearAll();
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server);
$containers = format_docker_command_output_to_json($containers);
$applications = $this->server->applications();
// ray($applications);
// ray(format_docker_command_output_to_json($containers));
foreach ($applications as $application) {
$uuid = data_get($application, 'uuid');
$foundContainer = $containers->filter(function ($value, $key) use ($uuid) {
$image = data_get($value, 'Config.Image');
return Str::startsWith($image, $uuid);
})->first();
if ($foundContainer) {
$containerStatus = data_get($foundContainer, 'State.Status');
$databaseStatus = data_get($application, 'status');
ray($containerStatus, $databaseStatus);
if ($containerStatus !== $databaseStatus) {
// $application->update(['status' => $containerStatus]);
}
}
}
// foreach ($containers as $container) {
// $labels = format_docker_labels_to_json(data_get($container,'Config.Labels'));
// $foundLabel = $labels->filter(fn ($value, $key) => Str::startsWith($key, 'coolify.applicationId'));
// if ($foundLabel->count() > 0) {
// $appFound = $applications->where('id', $foundLabel['coolify.applicationId'])->first();
// if ($appFound) {
// $containerStatus = data_get($container, 'State.Status');
// $databaseStatus = data_get($appFound, 'status');
// ray($containerStatus, $databaseStatus);
// }
// }
// }
} catch (\Throwable $e) {
// send_internal_notification('ServerDetailsCheckJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

View File

@ -4,13 +4,14 @@ namespace App\Jobs;
use App\Models\Team; use App\Models\Team;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class SubscriptionInvoiceFailedJob implements ShouldQueue class SubscriptionInvoiceFailedJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -4,13 +4,14 @@ namespace App\Jobs;
use App\Models\Team; use App\Models\Team;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class SubscriptionTrialEndedJob implements ShouldQueue class SubscriptionTrialEndedJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -4,13 +4,14 @@ namespace App\Jobs;
use App\Models\Team; use App\Models\Team;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
class SubscriptionTrialEndsSoonJob implements ShouldQueue class SubscriptionTrialEndsSoonJob implements ShouldQueue, ShouldBeEncrypted
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

View File

@ -105,6 +105,14 @@ class Server extends BaseModel
})->flatten(); })->flatten();
} }
public function previews() {
return $this->destinations()->map(function ($standaloneDocker) {
return $standaloneDocker->applications->map(function ($application) {
return $application->previews;
})->flatten();
})->flatten();
}
public function destinations() public function destinations()
{ {
$standalone_docker = $this->hasMany(StandaloneDocker::class)->get(); $standalone_docker = $this->hasMany(StandaloneDocker::class)->get();

View File

@ -53,7 +53,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
$pull_request_id = data_get($this->preview, 'pull_request_id', 0); $pull_request_id = data_get($this->preview, 'pull_request_id', 0);
$fqdn = $this->fqdn; $fqdn = $this->fqdn;
if ($pull_request_id === 0) { if ($pull_request_id === 0) {
$mail->subject("New version is deployed of {$this->application_name}"); $mail->subject(" New version is deployed of {$this->application_name}");
} else { } else {
$fqdn = $this->preview->fqdn; $fqdn = $this->preview->fqdn;
$mail->subject("✅ Pull request #{$pull_request_id} of {$this->application_name} deployed successfully"); $mail->subject("✅ Pull request #{$pull_request_id} of {$this->application_name} deployed successfully");

View File

@ -0,0 +1,62 @@
<?php
namespace App\Notifications\Container;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ContainerRestarted extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public function __construct(public string $name, public Server $server, public ?string $url = null)
{
}
public function via(object $notifiable): array
{
return setNotificationChannels($notifiable, 'status_changes');
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject("✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}");
$mail->view('emails.container-restarted', [
'containerName' => $this->name,
'serverName' => $this->server->name,
'url' => $this->url ,
]);
return $mail;
}
public function toDiscord(): string
{
$message = "✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}";
return $message;
}
public function toTelegram(): array
{
$message = "✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}";
$payload = [
"message" => $message,
];
if ($this->url) {
$payload['buttons'] = [
[
[
"text" => "Check Proxy in Coolify",
"url" => $this->url
]
]
];
};
return $payload;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Notifications\Container;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ContainerStopped extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
public function __construct(public string $name, public Server $server, public ?string $url = null)
{
}
public function via(object $notifiable): array
{
return setNotificationChannels($notifiable, 'status_changes');
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject("⛔ Container {$this->name} has been stopped on {$this->server->name}");
$mail->view('emails.container-stopped', [
'containerName' => $this->name,
'serverName' => $this->server->name,
'url' => $this->url,
]);
return $mail;
}
public function toDiscord(): string
{
$message = "⛔ Container {$this->name} has been stopped on {$this->server->name}";
return $message;
}
public function toTelegram(): array
{
$message = "⛔ Container ($this->name} has been stopped on {$this->server->name}";
$payload = [
"message" => $message,
];
if ($this->url) {
$payload['buttons'] = [
[
[
"text" => "Open Application in Coolify",
"url" => $this->url
]
]
];
}
return $payload;
}
}

View File

@ -1,53 +0,0 @@
<?php
namespace App\Notifications\Server;
use App\Models\Server;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Str;
class NotReachable extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public function __construct(public Server $server)
{
}
public function via(object $notifiable): array
{
return setNotificationChannels($notifiable, 'status_changes');
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
// $fqdn = $this->fqdn;
$mail->subject("⛔ Server '{$this->server->name}' is unreachable");
// $mail->view('emails.application-status-changes', [
// 'name' => $this->application_name,
// 'fqdn' => $fqdn,
// 'application_url' => $this->application_url,
// ]);
return $mail;
}
public function toDiscord(): string
{
$message = '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.';
return $message;
}
public function toTelegram(): array
{
return [
"message" => '⛔ Server \'' . $this->server->name . '\' is unreachable (could be a temporary issue). If you receive this more than twice in a row, please check your server.'
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Notifications\Server;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class Unreachable extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 1;
public function __construct(public Server $server)
{
}
public function via(object $notifiable): array
{
return setNotificationChannels($notifiable, 'status_changes');
}
public function toMail(): MailMessage
{
$mail = new MailMessage();
$mail->subject("⛔ Server ({$this->server->name}) is unreachable after trying to connect to it 5 times");
$mail->view('emails.server-lost-connection', [
'name' => $this->server->name,
]);
return $mail;
}
public function toDiscord(): string
{
$message = "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: You have to validate your server again after you fix the issue.";
return $message;
}
public function toTelegram(): array
{
return [
"message" => "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: You have to validate your server again after you fix the issue."
];
}
}

View File

@ -28,9 +28,8 @@ trait ExecuteRemoteCommand
$ip = data_get($this->server, 'ip'); $ip = data_get($this->server, 'ip');
$user = data_get($this->server, 'user'); $user = data_get($this->server, 'user');
$port = data_get($this->server, 'port'); $port = data_get($this->server, 'port');
$private_key_location = get_private_key_for_server($this->server);
$commandsText->each(function ($single_command) use ($private_key_location, $ip, $user, $port) { $commandsText->each(function ($single_command) use ($ip, $user, $port) {
$command = data_get($single_command, 'command') ?? $single_command[0] ?? null; $command = data_get($single_command, 'command') ?? $single_command[0] ?? null;
if ($command === null) { if ($command === null) {
throw new \RuntimeException('Command is not set'); throw new \RuntimeException('Command is not set');
@ -39,8 +38,8 @@ trait ExecuteRemoteCommand
$ignore_errors = data_get($single_command, 'ignore_errors', false); $ignore_errors = data_get($single_command, 'ignore_errors', false);
$this->save = data_get($single_command, 'save'); $this->save = data_get($single_command, 'save');
$remote_command = generate_ssh_command($private_key_location, $ip, $user, $port, $command); $remote_command = generateSshCommand( $ip, $user, $port, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) { $process = processWithEnv()->timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) {
$output = Str::of($output)->trim(); $output = Str::of($output)->trim();
$new_log_entry = [ $new_log_entry = [
'command' => $command, 'command' => $command,

View File

@ -91,7 +91,7 @@ function generateApplicationContainerName(string $uuid, int $pull_request_id = 0
{ {
$now = now()->format('Hisu'); $now = now()->format('Hisu');
if ($pull_request_id !== 0 && $pull_request_id !== null) { if ($pull_request_id !== 0 && $pull_request_id !== null) {
return $uuid . '-pr-' . $pull_request_id . '-' . $now; return $uuid . '-pr-' . $pull_request_id;
} else { } else {
return $uuid . '-' . $now; return $uuid . '-' . $now;
} }

View File

@ -33,12 +33,9 @@ function remote_process(
} }
} }
$private_key_location = save_private_key_for_server($server);
return resolve(PrepareCoolifyTask::class, [ return resolve(PrepareCoolifyTask::class, [
'remoteProcessArgs' => new CoolifyTaskArgs( 'remoteProcessArgs' => new CoolifyTaskArgs(
server_ip: $server->ip, server_ip: $server->ip,
private_key_location: $private_key_location,
command: <<<EOT command: <<<EOT
{$command_string} {$command_string}
EOT, EOT,
@ -52,37 +49,44 @@ function remote_process(
])(); ])();
} }
function get_private_key_for_server(Server $server) function removePrivateKeyFromSshAgent(Server $server)
{
$temp_file = "id.root@{$server->ip}";
return '/var/www/html/storage/app/ssh/keys/' . $temp_file;
}
function save_private_key_for_server(Server $server)
{ {
if (data_get($server, 'privateKey.private_key') === null) { if (data_get($server, 'privateKey.private_key') === null) {
throw new \Exception("Server {$server->name} does not have a private key"); throw new \Exception("Server {$server->name} does not have a private key");
} }
$temp_file = "id.root@{$server->ip}"; processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -d -");
Storage::disk('ssh-keys')->put($temp_file, $server->privateKey->private_key); }
Storage::disk('ssh-mux')->makeDirectory('.'); function addPrivateKeyToSshAgent(Server $server)
return '/var/www/html/storage/app/ssh/keys/' . $temp_file; {
if (data_get($server, 'privateKey.private_key') === null) {
throw new \Exception("Server {$server->name} does not have a private key");
}
// ray('adding key', $server->privateKey->private_key);
processWithEnv()->run("echo '{$server->privateKey->private_key}' | ssh-add -q -");
} }
function generate_ssh_command(string $private_key_location, string $server_ip, string $user, string $port, string $command, bool $isMux = true) function generateSshCommand(string $server_ip, string $user, string $port, string $command, bool $isMux = true)
{ {
$server = Server::where('ip', $server_ip)->first();
if (!$server) {
throw new \Exception("Server with ip {$server_ip} not found");
}
addPrivateKeyToSshAgent($server);
$timeout = config('constants.ssh.command_timeout');
$connectionTimeout = config('constants.ssh.connection_timeout');
$serverInterval = config('constants.ssh.server_interval');
$delimiter = 'EOF-COOLIFY-SSH'; $delimiter = 'EOF-COOLIFY-SSH';
$ssh_command = "ssh "; $ssh_command = "timeout $timeout ssh ";
if ($isMux && config('coolify.mux_enabled')) { if ($isMux && config('coolify.mux_enabled')) {
$ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r '; $ssh_command .= '-o ControlMaster=auto -o ControlPersist=1m -o ControlPath=/var/www/html/storage/app/ssh/mux/%h_%p_%r ';
} }
$command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command"; $command = "PATH=\$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin && $command";
$ssh_command .= "-i {$private_key_location} " $ssh_command .= '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
. '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null '
. '-o PasswordAuthentication=no ' . '-o PasswordAuthentication=no '
. '-o ConnectTimeout=3600 ' . "-o ConnectTimeout=$connectionTimeout "
. '-o ServerAliveInterval=20 ' . "-o ServerAliveInterval=$serverInterval "
. '-o RequestTTY=no ' . '-o RequestTTY=no '
. '-o LogLevel=ERROR ' . '-o LogLevel=ERROR '
. "-p {$port} " . "-p {$port} "
@ -90,11 +94,16 @@ function generate_ssh_command(string $private_key_location, string $server_ip, s
. " 'bash -se' << \\$delimiter" . PHP_EOL . " 'bash -se' << \\$delimiter" . PHP_EOL
. $command . PHP_EOL . $command . PHP_EOL
. $delimiter; . $delimiter;
// ray($ssh_command);
return $ssh_command; return $ssh_command;
} }
function instantCommand(string $command, $throwError = true) { function processWithEnv()
$process = Process::run($command); {
return Process::env(['SSH_AUTH_SOCK' => config('coolify.ssh_auth_sock')]);
}
function instantCommand(string $command, $throwError = true)
{
$process = processWithEnv()->run($command);
$output = trim($process->output()); $output = trim($process->output());
$exitCode = $process->exitCode(); $exitCode = $process->exitCode();
if ($exitCode !== 0) { if ($exitCode !== 0) {
@ -108,9 +117,8 @@ function instantCommand(string $command, $throwError = true) {
function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1) function instant_remote_process(array $command, Server $server, $throwError = true, $repeat = 1)
{ {
$command_string = implode("\n", $command); $command_string = implode("\n", $command);
$private_key_location = save_private_key_for_server($server); $ssh_command = generateSshCommand($server->ip, $server->user, $server->port, $command_string);
$ssh_command = generate_ssh_command($private_key_location, $server->ip, $server->user, $server->port, $command_string); $process = processWithEnv()->run($ssh_command);
$process = Process::run($ssh_command);
$output = trim($process->output()); $output = trim($process->output());
$exitCode = $process->exitCode(); $exitCode = $process->exitCode();
if ($exitCode !== 0) { if ($exitCode !== 0) {
@ -168,6 +176,7 @@ function refresh_server_connection(PrivateKey $private_key)
// currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get(); // currentTeam()->privateKeys = PrivateKey::where('team_id', currentTeam()->id)->get();
// } // }
} }
removePrivateKeyFromSshAgent($server);
} }
function validateServer(Server $server) function validateServer(Server $server)
@ -208,7 +217,7 @@ function validateServer(Server $server)
$server->settings->is_usable = false; $server->settings->is_usable = false;
throw $e; throw $e;
} finally { } finally {
if(data_get($server,'settings')) $server->settings->save(); if (data_get($server, 'settings')) $server->settings->save();
} }
} }

View File

@ -1,5 +1,10 @@
<?php <?php
return [ return [
'ssh' =>[
'connection_timeout' => 10,
'server_interval' => 20,
'command_timeout' => 7200,
],
'waitlist' => [ 'waitlist' => [
'expiration' => 10, 'expiration' => 10,
], ],

View File

@ -8,4 +8,5 @@ return [
'dev_webhook' => env('SERVEO_URL'), 'dev_webhook' => env('SERVEO_URL'),
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'), 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
'ssh_auth_sock' => env('SSH_AUTH_SOCK', '/tmp/coolify-ssh-agent.sock'),
]; ];

View File

@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.35', 'release' => '4.0.0-beta.36',
'server_name' => env('APP_ID', 'coolify'), 'server_name' => env('APP_ID', 'coolify'),
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.35'; return '4.0.0-beta.36';

View File

@ -21,6 +21,7 @@ services:
SSL_MODE: "off" SSL_MODE: "off"
AUTORUN_LARAVEL_STORAGE_LINK: "false" AUTORUN_LARAVEL_STORAGE_LINK: "false"
AUTORUN_LARAVEL_MIGRATION: "false" AUTORUN_LARAVEL_MIGRATION: "false"
SSH_AUTH_SOCK: "/tmp/coolify-ssh-agent.sock"
volumes: volumes:
- .:/var/www/html/:cached - .:/var/www/html/:cached
postgres: postgres:

View File

@ -64,6 +64,7 @@ services:
- LEMON_SQUEEZY_BASIC_PLAN_IDS - LEMON_SQUEEZY_BASIC_PLAN_IDS
- LEMON_SQUEEZY_PRO_PLAN_IDS - LEMON_SQUEEZY_PRO_PLAN_IDS
- LEMON_SQUEEZY_ULTIMATE_PLAN_IDS - LEMON_SQUEEZY_ULTIMATE_PLAN_IDS
- SSH_AUTH_SOCK="/tmp/coolify-ssh-agent.sock"
ports: ports:
- "${APP_PORT:-8000}:80" - "${APP_PORT:-8000}:80"
expose: expose:

View File

@ -24,4 +24,4 @@ RUN echo "alias mfs='php artisan migrate:fresh --seed'" >>/etc/bash.bashrc
RUN echo "alias cda='composer dump-autoload'" >>/etc/bash.bashrc RUN echo "alias cda='composer dump-autoload'" >>/etc/bash.bashrc
RUN echo "alias run='./scripts/run'" >>/etc/bash.bashrc RUN echo "alias run='./scripts/run'" >>/etc/bash.bashrc
# COPY --chmod=755 docker/dev-ssu/etc/s6-overlay/ /etc/s6-overlay/ COPY --chmod=755 docker/dev-ssu/etc/s6-overlay/ /etc/s6-overlay/

View File

@ -0,0 +1,5 @@
#!/command/execlineb -P
foreground {
s6-sleep 5
su - webuser -c "php /var/www/html/artisan horizon"
}

View File

@ -1,2 +0,0 @@
#!/command/execlineb -P
su - webuser -c "php /var/www/html/artisan queue:listen"

View File

@ -1,2 +1,5 @@
#!/command/execlineb -P #!/command/execlineb -P
su - webuser -c "php /var/www/html/artisan schedule:work" foreground {
s6-sleep 5
su - webuser -c "php /var/www/html/artisan schedule:work"
}

View File

@ -0,0 +1 @@
oneshot

View File

@ -0,0 +1,5 @@
#!/usr/bin/execlineb -P
foreground {
s6-sleep 5
su - webuser -c "ssh-agent -a /tmp/coolify-ssh-agent.sock"
}

View File

@ -0,0 +1 @@
oneshot

View File

@ -0,0 +1,5 @@
#!/usr/bin/execlineb -P
foreground {
s6-sleep 5
su - webuser -c "ssh-agent -a /tmp/coolify-ssh-agent.sock"
}

View File

@ -15,7 +15,7 @@
@if (is_transactional_emails_active()) @if (is_transactional_emails_active())
<form action="/forgot-password" method="POST" class="flex flex-col gap-2"> <form action="/forgot-password" method="POST" class="flex flex-col gap-2">
@csrf @csrf
<x-forms.input required value="test@example.com" type="email" name="email" <x-forms.input required type="email" name="email"
label="{{ __('input.email') }}" autofocus /> label="{{ __('input.email') }}" autofocus />
<x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button> <x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button>
</form> </form>

View File

@ -14,12 +14,12 @@
</div> </div>
@endif @endif
</div> </div>
@if($explanation) @isset($explanation)
<div class="col-span-1"> <div class="col-span-1">
<h1 class="pb-8 font-bold">Explanation</h1> <h1 class="pb-8 font-bold">Explanation</h1>
<div class="space-y-4"> <div class="space-y-4">
{{$explanation}} {{$explanation}}
</div> </div>
</div> </div>
@endif @endisset
</div> </div>

View File

@ -0,0 +1,15 @@
<x-emails.layout>
Container ({{ $containerName }}) has been restarted automatically on {{$serverName}}, because it was stopped unexpected.
@if ($containerName === 'coolify-proxy')
Coolify Proxy should run on your server as you have FQDNs set up in one of your resources.
Note: The proxy should not stop unexpectedly, so please check what is going on your server.
If you don't want to use Coolify Proxy, please remove FQDN from your resources or set Proxy type to Custom(None).
@endif
</x-emails.layout>

View File

@ -0,0 +1,9 @@
<x-emails.layout>
Container {{ $containerName }} has been stopped unexpected on {{$serverName}}.
@if ($url)
Please check what is going on [here]({{ $url }}).
@endif
</x-emails.layout>

View File

@ -1,5 +1,10 @@
<x-emails.layout> <x-emails.layout>
Coolify Cloud cannot connect to your server ({{$name}}). Please check your server and make sure it is running.
Coolify cannot connect to your server ({{$name}}). Please check your server and make sure it is running.
All automations & integrations are turned off!
IMPORTANT: You have to validate your server again after you fix the issue.
If you have any questions, please contact us. If you have any questions, please contact us.

View File

@ -5,12 +5,34 @@
<h1 class="text-5xl font-bold">Welcome to Coolify</h1> <h1 class="text-5xl font-bold">Welcome to Coolify</h1>
<p class="py-6 text-xl text-center">Let me help you to set the basics.</p> <p class="py-6 text-xl text-center">Let me help you to set the basics.</p>
<div class="flex justify-center "> <div class="flex justify-center ">
<x-forms.button class="justify-center box" wire:click="welcome">Get Started <x-forms.button class="justify-center box" wire:click="$set('currentState','explanation')">Get Started
</x-forms.button> </x-forms.button>
</div> </div>
@endif @endif
</div> </div>
<div> <div>
@if ($currentState === 'explanation')
<x-boarding-step title="What is Coolify?">
<x-slot:question>
Coolify is an all-in-one application to automate tasks on your servers, deploy application with Git
integrations, deploy databases and services, monitor these resources with notifications and alerts
without vendor lock-in
and <a href="https://coolify.io" class="text-white hover:underline">much much more</a>.
<br><br>
<span class="text-xl">
<x-highlighted text="Self-hosting with superpowers!" /></span>
</x-slot:question>
<x-slot:explanation>
<p><x-highlighted text="Task automation:" /> You do not to manage your servers too much. Coolify do it for you.</p>
<p><x-highlighted text="No vendor lock-in:" /> All configurations are stored on your server, so everything works without Coolify (except integrations and automations).</p>
<p><x-highlighted text="Monitoring:" />You will get notified on your favourite platform (Discord, Telegram, Email, etc.) when something goes wrong, or an action needed from your side.</p>
</x-slot:explanation>
<x-slot:actions>
<x-forms.button class="justify-center box" wire:click="explanation">Next
</x-forms.button>
</x-slot:actions>
</x-boarding-step>
@endif
@if ($currentState === 'select-server-type') @if ($currentState === 'select-server-type')
<x-boarding-step title="Server"> <x-boarding-step title="Server">
<x-slot:question> <x-slot:question>
@ -18,9 +40,11 @@
or on a <x-highlighted text="Remote Server" />? or on a <x-highlighted text="Remote Server" />?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center box" wire:target="setServerType('localhost')" wire:click="setServerType('localhost')">Localhost <x-forms.button class="justify-center box" wire:target="setServerType('localhost')"
wire:click="setServerType('localhost')">Localhost
</x-forms.button> </x-forms.button>
<x-forms.button class="justify-center box" wire:target="setServerType('remote')" wire:click="setServerType('remote')">Remote Server <x-forms.button class="justify-center box" wire:target="setServerType('remote')"
wire:click="setServerType('remote')">Remote Server
</x-forms.button> </x-forms.button>
</x-slot:actions> </x-slot:actions>
<x-slot:explanation> <x-slot:explanation>
@ -42,9 +66,11 @@
Do you have your own SSH Private Key? Do you have your own SSH Private Key?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center box" wire:target="setPrivateKey('own')" wire:click="setPrivateKey('own')">Yes <x-forms.button class="justify-center box" wire:target="setPrivateKey('own')"
wire:click="setPrivateKey('own')">Yes
</x-forms.button> </x-forms.button>
<x-forms.button class="justify-center box" wire:target="setPrivateKey('create')" wire:click="setPrivateKey('create')">No (create one for me) <x-forms.button class="justify-center box" wire:target="setPrivateKey('create')"
wire:click="setPrivateKey('create')">No (create one for me)
</x-forms.button> </x-forms.button>
@if (count($privateKeys) > 0) @if (count($privateKeys) > 0)
<form wire:submit.prevent='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10"> <form wire:submit.prevent='selectExistingPrivateKey' class="flex flex-col w-full gap-4 pr-10">
@ -115,9 +141,10 @@
<x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key" <x-forms.textarea required placeholder="-----BEGIN OPENSSH PRIVATE KEY-----" label="Private Key"
id="privateKey" /> id="privateKey" />
@if ($privateKeyType === 'create') @if ($privateKeyType === 'create')
<x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" /> <x-forms.textarea rows="7" readonly label="Public Key" id="publicKey" />
<span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's ~/.ssh/authorized_keys <span class="font-bold text-warning">ACTION REQUIRED: Copy the 'Public Key' to your server's
file.</span> ~/.ssh/authorized_keys
file.</span>
@endif @endif
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Save</x-forms.button>
</form> </form>
@ -182,7 +209,8 @@
Could not find Docker Engine on your server. Do you want me to install it for you? Could not find Docker Engine on your server. Do you want me to install it for you?
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center box" wire:click="installDocker" onclick="installDocker.showModal()"> <x-forms.button class="justify-center box" wire:click="installDocker"
onclick="installDocker.showModal()">
Let's do Let's do
it!</x-forms.button> it!</x-forms.button>
</x-slot:actions> </x-slot:actions>
@ -233,12 +261,14 @@
@endif @endif
</x-slot:question> </x-slot:question>
<x-slot:actions> <x-slot:actions>
<x-forms.button class="justify-center box" wire:click="createNewProject">Let's create a new one!</x-forms.button> <x-forms.button class="justify-center box" wire:click="createNewProject">Let's create a new
one!</x-forms.button>
<div> <div>
@if (count($projects) > 0) @if (count($projects) > 0)
<form wire:submit.prevent='selectExistingProject' <form wire:submit.prevent='selectExistingProject'
class="flex flex-col w-full gap-4 lg:w-96"> class="flex flex-col w-full gap-4 lg:w-96">
<x-forms.select label="Existing projects" class="w-96" id='selectedExistingProject'> <x-forms.select label="Existing projects" class="w-96"
id='selectedExistingProject'>
@foreach ($projects as $project) @foreach ($projects as $project)
<option wire:key="{{ $loop->index }}" value="{{ $project->id }}"> <option wire:key="{{ $loop->index }}" value="{{ $project->id }}">
{{ $project->name }}</option> {{ $project->name }}</option>

View File

@ -48,10 +48,10 @@
@endif @endif
</div> </div>
@if ($application->previews->count() > 0) @if ($application->previews->count() > 0)
<h4 class="py-4" wire:poll.10000ms='previewRefresh'>Deployed Previews</h4> <h4 class="py-4">Deployed Previews</h4>
<div class="flex gap-6 "> <div class="flex gap-6 ">
@foreach ($application->previews as $preview) @foreach ($application->previews as $preview)
<div class="flex flex-col p-4 bg-coolgray-200 " x-init="$wire.loadStatus('{{ data_get($preview, 'pull_request_id') }}')"> <div class="flex flex-col p-4 bg-coolgray-200">
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} | <div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |
@if (data_get($preview, 'status') === 'running') @if (data_get($preview, 'status') === 'running')
<x-status.running /> <x-status.running />

View File

@ -35,11 +35,7 @@
label="Is it part of a Swarm cluster?" /> --}} label="Is it part of a Swarm cluster?" /> --}}
</div> </div>
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
@if ($server->id === 0) <x-forms.input id="server.ip" label="IP Address" required />
<x-forms.input id="server.ip" label="IP Address" required />
@else
<x-forms.input id="server.ip" label="IP Address" readonly required />
@endif
<div class="flex gap-2"> <div class="flex gap-2">
<x-forms.input id="server.user" label="User" required /> <x-forms.input id="server.user" label="User" required />
<x-forms.input type="number" id="server.port" label="Port" required /> <x-forms.input type="number" id="server.port" label="Port" required />
@ -52,15 +48,15 @@
</x-forms.button> </x-forms.button>
@endif @endif
@if ($server->settings->is_reachable && !$server->settings->is_usable && $server->id !== 0) @if ($server->settings->is_reachable && !$server->settings->is_usable && $server->id !== 0)
<x-forms.button wire:poll.2000ms='validateServer' class="mt-8 mb-4 box" onclick="installDocker.showModal()" <x-forms.button class="mt-8 mb-4 box" onclick="installDocker.showModal()" wire:click.prevent='installDocker'
wire:click.prevent='installDocker' isHighlighted> isHighlighted>
Install Docker Engine 24.0 Install Docker Engine 24.0
</x-forms.button> </x-forms.button>
@endif @endif
@if ($server->isFunctional()) @if ($server->isFunctional())
<h3 class="py-4">Settings</h3> <h3 class="py-4">Settings</h3>
<x-forms.input id="cleanup_after_percentage" label="Disk Cleanup threshold (%)" required <x-forms.input id="cleanup_after_percentage" label="Disk Cleanup threshold (%)" required
helper="Disk cleanup job will be executed if disk usage is more than this number." /> helper="Disk cleanup job will be executed if disk usage is more than this number." />
@endif @endif
</form> </form>
<h2 class="pt-4">Danger Zone</h2> <h2 class="pt-4">Danger Zone</h2>

View File

@ -11,7 +11,7 @@
@if (data_get($server, 'proxy.status') === 'running') @if (data_get($server, 'proxy.status') === 'running')
<div class="flex gap-4"> <div class="flex gap-4">
<button> <button>
<a target="_blank" href="{{ base_url(false) }}:8080"> <a target="_blank" href="http://{{$server->ip}}:8080">
Traefik Dashboard Traefik Dashboard
<x-external-link /> <x-external-link />
</a> </a>

View File

@ -30,6 +30,10 @@ use Laravel\Fortify\Fortify;
Route::post('/forgot-password', function (Request $request) { Route::post('/forgot-password', function (Request $request) {
if (is_transactional_emails_active()) { if (is_transactional_emails_active()) {
$arrayOfRequest = $request->only(Fortify::email());
$request->merge([
'email' => Str::lower($arrayOfRequest['email']),
]);
$type = set_transanctional_email_settings(); $type = set_transanctional_email_settings();
if (!$type) { if (!$type) {
return response()->json(['message' => 'Transactional emails are not active'], 400); return response()->json(['message' => 'Transactional emails are not active'], 400);

View File

@ -116,7 +116,7 @@ Route::post('/source/github/events', function () {
$applications = $applications->where('git_branch', $base_branch)->get(); $applications = $applications->where('git_branch', $base_branch)->get();
} }
if ($applications->isEmpty()) { if ($applications->isEmpty()) {
return response('Nothing to do. No applications found.'); return response("Nothing to do. No applications found with branch '$base_branch'.");
} }
foreach ($applications as $application) { foreach ($applications as $application) {
$isFunctional = $application->destination->server->isFunctional(); $isFunctional = $application->destination->server->isFunctional();
@ -178,6 +178,7 @@ Route::post('/source/github/events', function () {
} }
} }
} catch (Exception $e) { } catch (Exception $e) {
ray($e->getMessage());
return general_error_handler(err: $e); return general_error_handler(err: $e);
} }
}); });

View File

@ -4,7 +4,7 @@
"version": "3.12.36" "version": "3.12.36"
}, },
"v4": { "v4": {
"version": "4.0.0-beta.35" "version": "4.0.0-beta.36"
} }
} }
} }