Compare commits

...

62 Commits

Author SHA1 Message Date
Andras Bacsai
6bb6de188c Merge pull request #1236 from coollabsio/next
v4.0.0-beta.41
2023-09-18 12:26:00 +02:00
Andras Bacsai
61f58fa30f fix: proxy configuration saving 2023-09-18 12:18:45 +02:00
Andras Bacsai
026f3fd72d fix 2023-09-18 12:09:31 +02:00
Andras Bacsai
9a706d55b4 fix: proxy container status 2023-09-18 12:08:19 +02:00
Andras Bacsai
9646969107 fix: errors 2023-09-18 11:49:26 +02:00
Andras Bacsai
df433efe62 fix: boarding 2023-09-18 11:21:10 +02:00
Andras Bacsai
efa7cab3e2 fix: installDocker id not found 2023-09-18 10:44:32 +02:00
Andras Bacsai
a386a1bde5 fix: allow non ip address (DNS) 2023-09-18 10:41:19 +02:00
Andras Bacsai
cc1bf023be fix: do not remove localhost in boarding 2023-09-18 10:41:06 +02:00
Andras Bacsai
edb06fa233 version++ 2023-09-18 10:06:35 +02:00
Andras Bacsai
abdb8074b1 Merge pull request #1235 from coollabsio/next
4.0.0-beta.40
2023-09-18 10:05:52 +02:00
Andras Bacsai
157da798dd fix: proxy start (if not proxy defined, use Traefik) 2023-09-18 09:58:13 +02:00
Andras Bacsai
9c5501326e Merge pull request #1230 from coollabsio/next
4.0.0-beta.39
2023-09-16 16:53:17 +02:00
Andras Bacsai
3ea462efc9 fix: localhost 2023-09-16 16:49:33 +02:00
Andras Bacsai
f77df5b732 feat: sentry add email for better support 2023-09-15 21:13:50 +02:00
Andras Bacsai
b77074fe4e Merge pull request #1228 from coollabsio/next
v4.0.0-beta.38
2023-09-15 18:05:59 +02:00
Andras Bacsai
a7b7d3fa32 version++ 2023-09-15 18:02:55 +02:00
Andras Bacsai
f4f3034389 fix: 4.0.0-beta.37 2023-09-15 17:57:17 +02:00
Andras Bacsai
4b2ffb456f fix: team error 2023-09-15 17:30:26 +02:00
Andras Bacsai
e17ff99c5b fix: missing upgrade js 2023-09-15 15:50:37 +02:00
Andras Bacsai
1cf036bbc6 fix: generate new key 2023-09-15 15:39:25 +02:00
Andras Bacsai
da4c2ee60f fix: boarding
fix: error handling
fix: restarting state
2023-09-15 15:34:25 +02:00
Andras Bacsai
fcf7c5ddd5 fix: restarting container state on ui 2023-09-15 13:44:41 +02:00
Andras Bacsai
27926322e1 version++ 2023-09-15 13:06:27 +02:00
Andras Bacsai
c735ff545e Merge pull request #1226 from coollabsio/next
v4.0.0-beta.37
2023-09-15 12:44:50 +02:00
Andras Bacsai
b4f048b028 fixes 2023-09-15 12:43:03 +02:00
Andras Bacsai
54a57d217f fix: ssh-agent revert 2023-09-15 12:30:25 +02:00
Andras Bacsai
cf28490acc feat: generate ssh key 2023-09-15 11:55:58 +02:00
Andras Bacsai
019670d5d1 fix: smtp view 2023-09-15 11:28:44 +02:00
Andras Bacsai
b07cc500e7 fix: invitation 2023-09-15 11:19:36 +02:00
Andras Bacsai
82c235d5af add features to pricing 2023-09-14 23:32:58 +02:00
Andras Bacsai
307e4a6990 fix: collect billing address 2023-09-14 20:42:12 +02:00
Andras Bacsai
ddb7af63a6 fix 2023-09-14 20:39:33 +02:00
Andras Bacsai
7a429ee5bb fix: rate limit 2023-09-14 18:49:15 +02:00
Andras Bacsai
bb591604ac workflow update 2023-09-14 18:44:55 +02:00
Andras Bacsai
81f7a65dd5 fix: help 2023-09-14 18:41:21 +02:00
Andras Bacsai
4b313bb1c6 fix: simply reply to help messages 2023-09-14 18:33:05 +02:00
Andras Bacsai
aba0b2f13c remove unnecessary jobs 2023-09-14 18:29:54 +02:00
Andras Bacsai
9a284e47da version++ 2023-09-14 18:22:25 +02:00
Andras Bacsai
9f2fbc661a fix: registration
fix: user deletion
2023-09-14 18:22:08 +02:00
Andras Bacsai
949407368e fix: uniqueips 2023-09-14 18:10:13 +02:00
Andras Bacsai
93c65f6a79 fix: ip check 2023-09-14 18:07:29 +02:00
Andras Bacsai
d9fe16a3ee fix: redirect on server not found 2023-09-14 17:45:00 +02:00
Andras Bacsai
adaca4d4e3 fix: sub for root 2023-09-14 17:32:33 +02:00
Andras Bacsai
a6d5f3038c fix: help uri 2023-09-14 17:28:58 +02:00
Andras Bacsai
4d49132821 hm 2023-09-14 17:22:21 +02:00
Andras Bacsai
c287276d0e temporary fix for proxy 2023-09-14 17:10:37 +02:00
Andras Bacsai
e89868c692 fix: SaveConfigurationSync 2023-09-14 16:55:13 +02:00
Andras Bacsai
f93317cd2e Merge pull request #1224 from coollabsio/next
v4.0.0-beta.36
2023-09-14 16:28:52 +02:00
Andras Bacsai
8412802f4d oh wow, it is cool! 2023-09-14 15:52:04 +02:00
Andras Bacsai
53c20e1e99 feat: new container status checks 2023-09-14 12:45:50 +02:00
Andras Bacsai
3c8c8e20b1 fix: editable ip
fix: traefik dashboard
2023-09-14 11:37:20 +02:00
Andras Bacsai
49f8abcd79 remove unnecessary things 2023-09-14 10:56:25 +02:00
Andras Bacsai
4a4d73b87b fix: plus boarding step about Coolify 2023-09-14 10:39:05 +02:00
Andras Bacsai
046eab3776 fix: processWithEnv()->run 2023-09-14 10:26:48 +02:00
Andras Bacsai
17c0e91a0d feat: ssh-agent instead of filesystem based ssh keys 2023-09-14 10:12:58 +02:00
Andras Bacsai
fe4a0ae166 fix: encrypt jobs 2023-09-14 10:12:44 +02:00
Andras Bacsai
52c84f8d22 fix: lower case email on waitlist 2023-09-13 20:48:13 +02:00
Andras Bacsai
b22fecb615 fix: lowercase email in forgot password 2023-09-13 20:27:58 +02:00
Andras Bacsai
23b7fc3c54 fix: prevent weird ui bug for validateServer 2023-09-13 13:00:43 +02:00
Andras Bacsai
1efb1235b4 fix: add timeout for ssh commands 2023-09-13 13:00:16 +02:00
Andras Bacsai
0924070a13 version++ 2023-09-13 12:42:59 +02:00
152 changed files with 1883 additions and 1087 deletions

View File

@@ -52,7 +52,7 @@ jobs:
push: true push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64 tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
merge-manifest: merge-manifest:
runs-on: [self-hosted, x64] runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
packages: write packages: write

View File

@@ -10,7 +10,7 @@ env:
jobs: jobs:
amd64: amd64:
runs-on: ubuntu-latest runs-on: [self-hosted, x64]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Login to ghcr.io - name: Login to ghcr.io

View File

@@ -5,14 +5,12 @@ namespace App\Actions\CoolifyTask;
use App\Enums\ActivityTypes; use App\Enums\ActivityTypes;
use App\Enums\ProcessStatus; use App\Enums\ProcessStatus;
use App\Jobs\ApplicationDeploymentJob; use App\Jobs\ApplicationDeploymentJob;
use App\Models\Server;
use Illuminate\Process\ProcessResult; use Illuminate\Process\ProcessResult;
use Illuminate\Support\Facades\DB; 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 +74,7 @@ class RunRemoteProcess
$this->time_start = hrtime(true); $this->time_start = hrtime(true);
$status = ProcessStatus::IN_PROGRESS; $status = ProcessStatus::IN_PROGRESS;
$processResult = Process::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;
@@ -98,7 +95,7 @@ class RunRemoteProcess
]); ]);
$this->activity->save(); $this->activity->save();
if ($processResult->exitCode() != 0 && !$this->ignore_errors) { if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
throw new \RuntimeException($processResult->errorOutput()); throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode());
} }
return $processResult; return $processResult;
@@ -106,13 +103,11 @@ class RunRemoteProcess
protected function getCommand(): string protected function getCommand(): string
{ {
$user = $this->activity->getExtraProperty('user'); $server_uuid = $this->activity->getExtraProperty('server_uuid');
$server_ip = $this->activity->getExtraProperty('server_ip');
$private_key_location = $this->activity->getExtraProperty('private_key_location');
$port = $this->activity->getExtraProperty('port');
$command = $this->activity->getExtraProperty('command'); $command = $this->activity->getExtraProperty('command');
$server = Server::whereUuid($server_uuid)->firstOrFail();
return generate_ssh_command($private_key_location, $server_ip, $user, $port, $command); return generateSshCommand($server, $command);
} }
protected function handleOutput(string $type, string $output) protected function handleOutput(string $type, string $output)

View File

@@ -2,12 +2,14 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class CheckConfigurationSync class CheckConfiguration
{ {
public function __invoke(Server $server, bool $reset = false) use AsAction;
public function handle(Server $server, bool $reset = false)
{ {
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();
$proxy_configuration = instant_remote_process([ $proxy_configuration = instant_remote_process([
@@ -16,10 +18,7 @@ class CheckConfigurationSync
if ($reset || is_null($proxy_configuration)) { if ($reset || is_null($proxy_configuration)) {
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value; $proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
resolve(SaveConfigurationSync::class)($server, $proxy_configuration);
return $proxy_configuration;
} }
return $proxy_configuration; return $proxy_configuration;
} }
} }

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
class SaveConfiguration
{
use AsAction;
public function handle(Server $server)
{
$proxy_settings = CheckConfiguration::run($server, true);
$proxy_path = get_proxy_path();
$docker_compose_yml_base64 = base64_encode($proxy_settings);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
return instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace App\Actions\Proxy;
use App\Models\Server;
use Illuminate\Support\Str;
class SaveConfigurationSync
{
public function __invoke(Server $server, string $configuration)
{
try {
$proxy_path = get_proxy_path();
$docker_compose_yml_base64 = base64_encode($configuration);
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
$server->save();
instant_remote_process([
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
} catch (\Throwable $e) {
ray($e);
}
}
}

View File

@@ -2,14 +2,25 @@
namespace App\Actions\Proxy; namespace App\Actions\Proxy;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Models\Server; use App\Models\Server;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Spatie\Activitylog\Models\Activity; 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
{ {
$proxyType = data_get($server,'proxy.type');
if ($proxyType === 'none') {
return 'OK';
}
if (is_null($proxyType)) {
$server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
$server->proxy->status = ProxyStatus::EXITED->value;
$server->save();
}
$proxy_path = get_proxy_path(); $proxy_path = get_proxy_path();
$networks = collect($server->standaloneDockers)->map(function ($docker) { $networks = collect($server->standaloneDockers)->map(function ($docker) {
return $docker['network']; return $docker['network'];
@@ -21,13 +32,18 @@ class StartProxy
return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1"; return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1";
}); });
$configuration = resolve(CheckConfigurationSync::class)($server); $configuration = CheckConfiguration::run($server);
if (!$configuration) {
throw new \Exception("Configuration is not synced");
}
$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([ "command -v lsof >/dev/null || echo '####### Installing lsof...'",
"command -v lsof >/dev/null || apt-get update",
"command -v lsof >/dev/null || apt install -y lsof",
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
"echo '####### Creating required Docker networks...'", "echo '####### Creating required Docker networks...'",
...$create_networks_command, ...$create_networks_command,
"cd $proxy_path", "cd $proxy_path",
@@ -35,17 +51,25 @@ class StartProxy
"echo '####### Pulling docker image...'", "echo '####### Pulling docker image...'",
'docker compose pull', 'docker compose pull',
"echo '####### Stopping existing coolify-proxy...'", "echo '####### Stopping existing coolify-proxy...'",
'docker compose down -v --remove-orphans', "docker compose down -v --remove-orphans > /dev/null 2>&1 || true",
"lsof -nt -i:80 | xargs -r kill -9", "command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
"lsof -nt -i:443 | xargs -r kill -9", "command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
"systemctl disable nginx > /dev/null 2>&1 || true", "systemctl disable nginx > /dev/null 2>&1 || true",
"systemctl disable apache2 > /dev/null 2>&1 || true", "systemctl disable apache2 > /dev/null 2>&1 || true",
"systemctl disable apache > /dev/null 2>&1 || true", "systemctl disable apache > /dev/null 2>&1 || true",
"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

@@ -4,11 +4,10 @@ namespace App\Actions\Server;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use App\Models\Team;
class InstallDocker class InstallDocker
{ {
public function __invoke(Server $server, Team $team) public function __invoke(Server $server, bool $instant = false)
{ {
$dockerVersion = '24.0'; $dockerVersion = '24.0';
$config = base64_encode('{ $config = base64_encode('{
@@ -19,15 +18,16 @@ class InstallDocker
} }
}'); }');
$found = StandaloneDocker::where('server_id', $server->id); $found = StandaloneDocker::where('server_id', $server->id);
if ($found->count() == 0) { if ($found->count() == 0 && $server->id) {
StandaloneDocker::create([ StandaloneDocker::create([
'name' => 'coolify', 'name' => 'coolify',
'network' => 'coolify', 'network' => 'coolify',
'server_id' => $server->id, 'server_id' => $server->id,
]); ]);
} }
if (isDev()) {
return remote_process([ if (isDev() && $server->id === 0) {
$command = [
"echo '####### Installing Prerequisites...'", "echo '####### Installing Prerequisites...'",
"sleep 1", "sleep 1",
"echo '####### Installing/updating Docker Engine...'", "echo '####### Installing/updating Docker Engine...'",
@@ -35,9 +35,9 @@ class InstallDocker
"sleep 4", "sleep 4",
"echo '####### Restarting Docker Engine...'", "echo '####### Restarting Docker Engine...'",
"ls -l /tmp" "ls -l /tmp"
], $server); ];
} else { } else {
return remote_process([ $command = [
"echo '####### Installing Prerequisites...'", "echo '####### Installing Prerequisites...'",
"command -v jq >/dev/null || apt-get update", "command -v jq >/dev/null || apt-get update",
"command -v jq >/dev/null || apt install -y jq", "command -v jq >/dev/null || apt install -y jq",
@@ -53,7 +53,11 @@ class InstallDocker
"echo '####### Creating default Docker network (coolify)...'", "echo '####### Creating default Docker network (coolify)...'",
"docker network create --attachable coolify >/dev/null 2>&1 || true", "docker network create --attachable coolify >/dev/null 2>&1 || true",
"echo '####### Done!'" "echo '####### Done!'"
], $server); ];
} }
if ($instant) {
return instant_remote_process($command, $server);
}
return remote_process($command, $server);
} }
} }

View File

@@ -24,7 +24,7 @@ use Illuminate\Console\Command;
use Illuminate\Mail\Message; use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Mail; use Mail;
use Str; use Illuminate\Support\Str;
use function Laravel\Prompts\confirm; use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select; use function Laravel\Prompts\select;
@@ -62,7 +62,7 @@ class Emails extends Command
'application-status-changed' => 'Application - Status Changed', 'application-status-changed' => 'Application - Status Changed',
'backup-success' => 'Database - Backup Success', 'backup-success' => 'Database - Backup Success',
'backup-failed' => 'Database - Backup Failed', 'backup-failed' => 'Database - Backup Failed',
'invitation-link' => 'Invitation Link', // 'invitation-link' => 'Invitation Link',
'waitlist-invitation-link' => 'Waitlist Invitation Link', 'waitlist-invitation-link' => 'Waitlist Invitation Link',
'waitlist-confirmation' => 'Waitlist Confirmation', 'waitlist-confirmation' => 'Waitlist Confirmation',
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription', 'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
@@ -141,20 +141,20 @@ class Emails extends Command
$this->mail = (new BackupSuccess($backup, $db))->toMail(); $this->mail = (new BackupSuccess($backup, $db))->toMail();
$this->sendEmail(); $this->sendEmail();
break; break;
case 'invitation-link': // case 'invitation-link':
$user = User::all()->first(); // $user = User::all()->first();
$invitation = TeamInvitation::whereEmail($user->email)->first(); // $invitation = TeamInvitation::whereEmail($user->email)->first();
if (!$invitation) { // if (!$invitation) {
$invitation = TeamInvitation::create([ // $invitation = TeamInvitation::create([
'uuid' => Str::uuid(), // 'uuid' => Str::uuid(),
'email' => $user->email, // 'email' => $user->email,
'team_id' => 1, // 'team_id' => 1,
'link' => 'http://example.com', // 'link' => 'http://example.com',
]); // ]);
} // }
$this->mail = (new InvitationLink($user))->toMail(); // $this->mail = (new InvitationLink($user))->toMail();
$this->sendEmail(); // $this->sendEmail();
break; // break;
case 'waitlist-invitation-link': case 'waitlist-invitation-link':
$this->mail = new MailMessage(); $this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [ $this->mail->view('emails.waitlist-invitation', [

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,43 +19,31 @@ 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()->onOneServer();
// $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)->everyTwoMinutes()->onOneServer();
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer(); $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer(); $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
$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

@@ -12,11 +12,8 @@ use Spatie\LaravelData\Data;
class CoolifyTaskArgs extends Data class CoolifyTaskArgs extends Data
{ {
public function __construct( public function __construct(
public string $server_ip, public string $server_uuid,
public string $private_key_location,
public string $command, public string $command,
public int $port,
public string $user,
public string $type, public string $type,
public ?string $type_uuid = null, public ?string $type_uuid = null,
public ?Model $model = null, public ?Model $model = null,

View File

@@ -3,6 +3,7 @@
namespace App\Exceptions; namespace App\Exceptions;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\User;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Sentry\Laravel\Integration; use Sentry\Laravel\Integration;
use Sentry\State\Scope; use Sentry\State\Scope;
@@ -45,16 +46,19 @@ class Handler extends ExceptionHandler
public function register(): void public function register(): void
{ {
$this->reportable(function (Throwable $e) { $this->reportable(function (Throwable $e) {
if (isDev()) {
return;
}
$this->settings = InstanceSettings::get(); $this->settings = InstanceSettings::get();
if ($this->settings->do_not_track || isDev()) { if ($this->settings->do_not_track) {
return; return;
} }
app('sentry')->configureScope( app('sentry')->configureScope(
function (Scope $scope) { function (Scope $scope) {
$scope->setUser( $scope->setUser(
[ [
'id' => config('sentry.server_name'), 'email' => auth()->user()->email,
'email' => auth()->user()->email 'instanceAdmin' => User::find(0)->email
] ]
); );
} }

View File

@@ -3,21 +3,17 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\S3Storage; use App\Models\S3Storage;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation; use App\Models\TeamInvitation;
use App\Models\User; use App\Models\User;
use Auth;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController; use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt; use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Str;
use Throwable;
use Str;
class Controller extends BaseController class Controller extends BaseController
{ {
@@ -35,8 +31,15 @@ class Controller extends BaseController
return redirect()->route('login'); return redirect()->route('login');
} }
if (Hash::check($password, $user->password)) { if (Hash::check($password, $user->password)) {
$invitation = TeamInvitation::whereEmail($email);
if ($invitation->exists()) {
$team = $invitation->first()->team;
$user->teams()->attach($team->id, ['role' => $invitation->first()->role]);
$invitation->delete();
} else {
$team = $user->teams()->first();
}
Auth::login($user); Auth::login($user);
$team = $user->teams()->first();
session(['currentTeam' => $team]); session(['currentTeam' => $team]);
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
@@ -137,24 +140,20 @@ class Controller extends BaseController
try { try {
$invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail(); $invitation = TeamInvitation::whereUuid(request()->route('uuid'))->firstOrFail();
$user = User::whereEmail($invitation->email)->firstOrFail(); $user = User::whereEmail($invitation->email)->firstOrFail();
if (is_null(auth()->user())) {
return redirect()->route('login');
}
if (auth()->user()->id !== $user->id) { if (auth()->user()->id !== $user->id) {
abort(401); abort(401);
} }
$invitationValid = $invitation->isValid();
$createdAt = $invitation->created_at; if ($invitationValid) {
$diff = $createdAt->diffInMinutes(now());
if ($diff <= config('constants.invitation.link.expiration')) {
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
refreshSession($invitation->team);
$invitation->delete(); $invitation->delete();
return redirect()->route('team.index'); return redirect()->route('team.index');
} else { } else {
$invitation->delete();
abort(401); abort(401);
} }
} catch (Throwable $e) { } catch (\Throwable $e) {
ray($e->getMessage());
throw $e; throw $e;
} }
} }
@@ -172,7 +171,7 @@ class Controller extends BaseController
} }
$invitation->delete(); $invitation->delete();
return redirect()->route('team.index'); return redirect()->route('team.index');
} catch (Throwable $e) { } catch (\Throwable $e) {
throw $e; throw $e;
} }
} }

View File

@@ -9,11 +9,13 @@ use App\Models\Server;
use App\Models\Team; use App\Models\Team;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Index extends Component class Index extends Component
{ {
public string $currentState = 'welcome'; public string $currentState = 'welcome';
public ?string $selectedServerType = null;
public ?Collection $privateKeys = null; public ?Collection $privateKeys = null;
public ?int $selectedExistingPrivateKey = null; public ?int $selectedExistingPrivateKey = null;
public ?string $privateKeyType = null; public ?string $privateKeyType = null;
@@ -36,6 +38,7 @@ class Index extends Component
public ?int $selectedExistingProject = null; public ?int $selectedExistingProject = null;
public ?Project $createdProject = null; public ?Project $createdProject = null;
public bool $dockerInstallationStarted = false;
public function mount() public function mount()
{ {
$this->privateKeyName = generate_random_name(); $this->privateKeyName = generate_random_name();
@@ -53,20 +56,24 @@ 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->selectedServerType !== 'localhost') {
$this->createdServer->delete(); // if ($this->createdServer) {
} // $this->createdServer->delete();
if ($this->createdPrivateKey) { // }
$this->createdPrivateKey->delete(); // if ($this->createdPrivateKey) {
} // $this->createdPrivateKey->delete();
// }
// }
return redirect()->route('boarding'); return redirect()->route('boarding');
} }
public function skipBoarding() public function skipBoarding()
@@ -81,13 +88,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
public function setServerType(string $type) public function setServerType(string $type)
{ {
if ($type === 'localhost') { $this->selectedServerType = $type;
if ($this->selectedServerType === 'localhost') {
$this->createdServer = Server::find(0); $this->createdServer = Server::find(0);
if (!$this->createdServer) { if (!$this->createdServer) {
return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.'); return $this->emit('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
} }
return $this->validateServer(); return $this->validateServer('localhost');
} elseif ($type === 'remote') { } elseif ($this->selectedServerType === 'remote') {
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get(); $this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
if ($this->privateKeys->count() > 0) { if ($this->privateKeys->count() > 0) {
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id; $this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
@@ -111,10 +119,9 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
} }
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id; $this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
$this->validateServer(); $this->validateServer();
$this->getProxyType();
$this->getProjects();
} }
public function getProxyType() { public function getProxyType()
{
$proxyTypeSet = $this->createdServer->proxy->type; $proxyTypeSet = $this->createdServer->proxy->type;
if (!$proxyTypeSet) { if (!$proxyTypeSet) {
$this->currentState = 'select-proxy'; $this->currentState = 'select-proxy';
@@ -124,6 +131,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
} }
public function selectExistingPrivateKey() public function selectExistingPrivateKey()
{ {
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->currentState = 'create-server'; $this->currentState = 'create-server';
} }
public function createNewServer() public function createNewServer()
@@ -146,6 +154,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'privateKeyName' => 'required', 'privateKeyName' => 'required',
'privateKey' => 'required', 'privateKey' => 'required',
]); ]);
$this->createdPrivateKey = PrivateKey::create([
'name' => $this->privateKeyName,
'description' => $this->privateKeyDescription,
'private_key' => $this->privateKey,
'team_id' => currentTeam()->id
]);
$this->createdPrivateKey->save();
$this->currentState = 'create-server'; $this->currentState = 'create-server';
} }
public function saveServer() public function saveServer()
@@ -153,16 +168,14 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->validate([ $this->validate([
'remoteServerName' => 'required', 'remoteServerName' => 'required',
'remoteServerHost' => 'required', 'remoteServerHost' => 'required',
'remoteServerPort' => 'required', 'remoteServerPort' => 'required|integer',
'remoteServerUser' => 'required', 'remoteServerUser' => 'required',
]); ]);
$this->privateKey = formatPrivateKey($this->privateKey); $this->privateKey = formatPrivateKey($this->privateKey);
$this->createdPrivateKey = PrivateKey::create([ $foundServer = Server::whereIp($this->remoteServerHost)->first();
'name' => $this->privateKeyName, if ($foundServer) {
'description' => $this->privateKeyDescription, return $this->emit('error', 'IP address is already in use by another team.');
'private_key' => $this->privateKey, }
'team_id' => currentTeam()->id
]);
$this->createdServer = Server::create([ $this->createdServer = Server::create([
'name' => $this->remoteServerName, 'name' => $this->remoteServerName,
'ip' => $this->remoteServerHost, 'ip' => $this->remoteServerHost,
@@ -170,38 +183,46 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
'user' => $this->remoteServerUser, 'user' => $this->remoteServerUser,
'description' => $this->remoteServerDescription, 'description' => $this->remoteServerDescription,
'private_key_id' => $this->createdPrivateKey->id, 'private_key_id' => $this->createdPrivateKey->id,
'team_id' => currentTeam()->id 'team_id' => currentTeam()->id,
]); ]);
$this->createdServer->save();
$this->validateServer(); $this->validateServer();
} }
public function validateServer() { public function validateServer(?string $type = null)
{
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->createdServer); $customErrorMessage = "Server is not reachable:";
if (!$uptime) { config()->set('coolify.mux_enabled', false);
throw new \Exception('Server is not reachable.');
} else {
$this->createdServer->settings->update([
'is_reachable' => true,
]);
$this->emit('success', 'Server is reachable.');
}
ray($dockerVersion, $uptime);
if (!$dockerVersion) {
$this->emit('error', 'Docker is not installed on the server.');
$this->currentState = 'install-docker';
return;
}
$this->getProxyType();
instant_remote_process(['uptime'], $this->createdServer, true);
$this->createdServer->settings->update([
'is_reachable' => true,
]);
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
if (is_null($dockerVersion)) {
$this->currentState = 'install-docker';
throw new \Exception('Docker version is not supported or not installed.');
}
$this->dockerInstalledOrSkipped();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this); return handleError(error: $e, customErrorMessage: $customErrorMessage, livewire: $this);
} }
} }
public function installDocker() public function installDocker()
{ {
$activity = resolve(InstallDocker::class)($this->createdServer, currentTeam()); $this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->createdServer);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
$this->currentState = 'select-proxy'; }
public function dockerInstalledOrSkipped()
{
$this->createdServer->settings->update([
'is_usable' => true,
]);
$this->getProxyType();
} }
public function selectProxy(string|null $proxyType = null) public function selectProxy(string|null $proxyType = null)
{ {
@@ -214,14 +235,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->getProjects(); $this->getProjects();
} }
public function getProjects() { public function getProjects()
{
$this->projects = Project::ownedByCurrentTeam(['name'])->get(); $this->projects = Project::ownedByCurrentTeam(['name'])->get();
if ($this->projects->count() > 0) { if ($this->projects->count() > 0) {
$this->selectedExistingProject = $this->projects->first()->id; $this->selectedExistingProject = $this->projects->first()->id;
} }
$this->currentState = 'create-project'; $this->currentState = 'create-project';
} }
public function selectExistingProject() { public function selectExistingProject()
{
$this->createdProject = Project::find($this->selectedExistingProject); $this->createdProject = Project::find($this->selectedExistingProject);
$this->currentState = 'create-resource'; $this->currentState = 'create-resource';
} }
@@ -241,7 +264,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
[ [
'project_uuid' => $this->createdProject->uuid, 'project_uuid' => $this->createdProject->uuid,
'environment_name' => 'production', 'environment_name' => 'production',
'server'=> $this->createdServer->id, 'server' => $this->createdServer->id,
] ]
); );
} }

View File

@@ -38,7 +38,7 @@ class Form extends Component
$this->destination->delete(); $this->destination->delete();
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
} }

View File

@@ -72,7 +72,7 @@ class StandaloneDocker extends Component
$this->createNetworkAndAttachToProxy(); $this->createNetworkAndAttachToProxy();
return redirect()->route('destination.show', $docker->uuid); return redirect()->route('destination.show', $docker->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -37,7 +37,7 @@ class ForcePasswordReset extends Component
} }
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -19,7 +19,7 @@ class Help extends Component
]; ];
public function mount() public function mount()
{ {
$this->path = Route::current()->uri(); $this->path = Route::current()?->uri() ?? null;
if (isDev()) { if (isDev()) {
$this->description = "I'm having trouble with {$this->path}"; $this->description = "I'm having trouble with {$this->path}";
$this->subject = "Help with {$this->path}"; $this->subject = "Help with {$this->path}";
@@ -41,10 +41,10 @@ class Help extends Component
] ]
); );
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}"); $mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
send_user_an_email($mail, 'hi@coollabs.io', auth()->user()?->email); send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.'); $this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function render() public function render()

View File

@@ -46,9 +46,7 @@ class DiscordSettings extends Component
public function saveModel() public function saveModel()
{ {
$this->team->save(); $this->team->save();
if (is_a($this->team, Team::class)) { refreshSession();
refreshSession();
}
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
} }

View File

@@ -62,9 +62,10 @@ class EmailSettings extends Component
'team.smtp_from_name' => 'required', 'team.smtp_from_name' => 'required',
]); ]);
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function sendTestNotification() public function sendTestNotification()
@@ -81,9 +82,10 @@ class EmailSettings extends Component
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
$this->team->resend_enabled = false; $this->team->resend_enabled = false;
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -94,7 +96,7 @@ class EmailSettings extends Component
$this->submitResend(); $this->submitResend();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function instantSave() public function instantSave()
@@ -104,15 +106,13 @@ class EmailSettings extends Component
$this->submit(); $this->submit();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function saveModel() public function saveModel()
{ {
$this->team->save(); $this->team->save();
if (is_a($this->team, Team::class)) { refreshSession();
refreshSession();
}
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
} }
public function submit() public function submit()
@@ -130,10 +130,11 @@ class EmailSettings extends Component
'team.smtp_timeout' => 'nullable', 'team.smtp_timeout' => 'nullable',
]); ]);
$this->team->save(); $this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->smtp_enabled = false; $this->team->smtp_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function submitResend() public function submitResend()
@@ -141,6 +142,8 @@ class EmailSettings extends Component
try { try {
$this->resetErrorBag(); $this->resetErrorBag();
$this->validate([ $this->validate([
'team.smtp_from_address' => 'required|email',
'team.smtp_from_name' => 'required',
'team.resend_api_key' => 'required' 'team.resend_api_key' => 'required'
]); ]);
$this->team->save(); $this->team->save();
@@ -148,7 +151,7 @@ class EmailSettings extends Component
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->team->resend_enabled = false; $this->team->resend_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function copyFromInstanceSettings() public function copyFromInstanceSettings()

View File

@@ -52,9 +52,7 @@ class TelegramSettings extends Component
public function saveModel() public function saveModel()
{ {
$this->team->save(); $this->team->save();
if (is_a($this->team, Team::class)) { refreshSession();
refreshSession();
}
$this->emit('success', 'Settings saved.'); $this->emit('success', 'Settings saved.');
} }

View File

@@ -25,8 +25,8 @@ class Change extends Component
{ {
try { try {
$this->public_key = $this->private_key->publicKey(); $this->public_key = $this->private_key->publicKey();
}catch(\Exception $e) { }catch(\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
public function delete() public function delete()
@@ -39,7 +39,7 @@ class Change extends Component
} }
$this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.'); $this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -50,7 +50,7 @@ class Change extends Component
$this->private_key->save(); $this->private_key->save();
refresh_server_connection($this->private_key); refresh_server_connection($this->private_key);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -3,14 +3,20 @@
namespace App\Http\Livewire\PrivateKey; namespace App\Http\Livewire\PrivateKey;
use App\Models\PrivateKey; use App\Models\PrivateKey;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Livewire\Component; use Livewire\Component;
use phpseclib3\Crypt\PublicKeyLoader;
class Create extends Component class Create extends Component
{ {
public string|null $from = null; use WithRateLimiting;
public string $name; public string $name;
public string|null $description = null;
public string $value; public string $value;
public ?string $from = null;
public ?string $description = null;
public ?string $publicKey = null;
protected $rules = [ protected $rules = [
'name' => 'required|string', 'name' => 'required|string',
'value' => 'required|string', 'value' => 'required|string',
@@ -20,6 +26,32 @@ class Create extends Component
'value' => 'private Key', 'value' => 'private Key',
]; ];
public function generateNewKey()
{
try {
$this->rateLimit(10);
$this->name = generate_random_name();
$this->description = 'Created by Coolify';
['private' => $this->value, 'public' => $this->publicKey] = generateSSHKey();
} catch(\Throwable $e) {
return handleError($e, $this);
}
}
public function updated($updateProperty)
{
if ($updateProperty === 'value') {
try {
$this->publicKey = PublicKeyLoader::load($this->$updateProperty)->getPublicKey()->toString('OpenSSH',['comment' => '']);
} catch (\Throwable $e) {
if ($this->$updateProperty === "") {
$this->publicKey = "";
} else {
$this->publicKey = "Invalid private key";
}
}
}
$this->validateOnly($updateProperty);
}
public function createPrivateKey() public function createPrivateKey()
{ {
$this->validate(); $this->validate();
@@ -39,7 +71,7 @@ class Create extends Component
} }
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]); return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -34,7 +34,7 @@ class Form extends Component
'name' => $this->name, 'name' => $this->name,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -29,7 +29,7 @@ class AddEmpty extends Component
]); ]);
return redirect()->route('project.show', $project->uuid); return redirect()->route('project.show', $project->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
general_error_handler($e, $this); return handleError($e, $this);
} finally { } finally {
$this->name = ''; $this->name = '';
} }

View File

@@ -32,7 +32,7 @@ class AddEnvironment extends Component
'environment_name' => $environment->name, 'environment_name' => $environment->name,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
general_error_handler($e, $this); handleError($e, $this);
} finally { } finally {
$this->name = ''; $this->name = '';
} }

View File

@@ -65,7 +65,7 @@ class DeploymentNavbar extends Component
]); ]);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -160,7 +160,7 @@ class General extends Component
$this->application->save(); $this->application->save();
$this->emit('success', 'Application settings updated!'); $this->emit('success', 'Application settings updated!');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

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

@@ -2,7 +2,6 @@
namespace App\Http\Livewire\Project\Application; namespace App\Http\Livewire\Project\Application;
use App\Jobs\ApplicationContainerStatusJob;
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 +22,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 {
@@ -39,7 +30,7 @@ class Previews extends Component
$this->pull_requests = $data->sortBy('number')->values(); $this->pull_requests = $data->sortBy('number')->values();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->rate_limit_remaining = 0; $this->rate_limit_remaining = 0;
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -68,7 +59,7 @@ class Previews extends Component
'environment_name' => $this->parameters['environment_name'], 'environment_name' => $this->parameters['environment_name'],
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -88,7 +79,7 @@ class Previews extends Component
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete(); ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
$this->application->refresh(); $this->application->refresh();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -65,7 +65,7 @@ class Rollback extends Component
]; ];
})->toArray(); })->toArray();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -43,7 +43,7 @@ class CreateScheduledBackup extends Component
]); ]);
$this->emit('refreshScheduledBackups'); $this->emit('refreshScheduledBackups');
} catch (\Throwable $e) { } catch (\Throwable $e) {
general_error_handler($e, $this); handleError($e, $this);
} finally { } finally {
$this->frequency = ''; $this->frequency = '';
$this->save_s3 = true; $this->save_s3 = true;

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();
} }
@@ -38,7 +35,7 @@ class Heading extends Component
public function stop() public function stop()
{ {
remote_process( instant_remote_process(
["docker rm -f {$this->database->uuid}"], ["docker rm -f {$this->database->uuid}"],
$this->database->destination->server $this->database->destination->server
); );
@@ -48,7 +45,7 @@ class Heading extends Component
} }
$this->database->status = 'stopped'; $this->database->status = 'stopped';
$this->database->save(); $this->database->save();
$this->emit('refresh'); $this->check_status();
// $this->database->environment->project->team->notify(new StatusChanged($this->database)); // $this->database->environment->project->team->notify(new StatusChanged($this->database));
} }

View File

@@ -36,7 +36,7 @@ class InitScript extends Component
$this->script['filename'] = $this->filename; $this->script['filename'] = $this->filename;
$this->emitUp('save_init_script', $this->script); $this->emitUp('save_init_script', $this->script);
} catch (Exception $e) { } catch (Exception $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -5,6 +5,7 @@ namespace App\Http\Livewire\Project\Database\Postgresql;
use App\Models\StandalonePostgresql; use App\Models\StandalonePostgresql;
use Exception; use Exception;
use Livewire\Component; use Livewire\Component;
use function Aws\filter; use function Aws\filter;
class General extends Component class General extends Component
@@ -73,9 +74,9 @@ class General extends Component
} }
$this->getDbUrl(); $this->getDbUrl();
$this->database->save(); $this->database->save();
} catch(Exception $e) { } catch(\Throwable $e) {
$this->database->is_public = !$this->database->is_public; $this->database->is_public = !$this->database->is_public;
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -140,7 +141,7 @@ class General extends Component
$this->database->save(); $this->database->save();
$this->emit('success', 'Database updated successfully.'); $this->emit('success', 'Database updated successfully.');
} catch (Exception $e) { } catch (Exception $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -20,7 +20,7 @@ class Edit extends Component
$this->project->save(); $this->project->save();
$this->emit('saved'); $this->emit('saved');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -165,7 +165,7 @@ class GithubPrivateRepository extends Component
'project_uuid' => $project->uuid, 'project_uuid' => $project->uuid,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -118,7 +118,7 @@ class GithubPrivateRepositoryDeployKey extends Component
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }

View File

@@ -76,14 +76,14 @@ class PublicGitRepository extends Component
$this->get_branch(); $this->get_branch();
$this->selected_branch = $this->git_branch; $this->selected_branch = $this->git_branch;
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
if (!$this->branch_found && $this->git_branch == 'main') { if (!$this->branch_found && $this->git_branch == 'main') {
try { try {
$this->git_branch = 'master'; $this->git_branch = 'master';
$this->get_branch(); $this->get_branch();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }
@@ -162,7 +162,7 @@ class PublicGitRepository extends Component
'application_uuid' => $application->uuid, 'application_uuid' => $application->uuid,
]); ]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -41,7 +41,7 @@ class Select extends Component
// instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'"); // instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'");
// $this->emit('success', 'Successfully connected to the database.'); // $this->emit('success', 'Successfully connected to the database.');
// } catch (\Throwable $e) { // } catch (\Throwable $e) {
// return general_error_handler($e, $this); // return handleError($e, $this);
// } // }
// } // }
public function setType(string $type) public function setType(string $type)

View File

@@ -5,7 +5,7 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use Str; use Illuminate\Support\Str;
class All extends Component class All extends Component
{ {
@@ -114,7 +114,7 @@ class All extends Component
$this->refreshEnvs(); $this->refreshEnvs();
$this->emit('success', 'Environment variable added successfully.'); $this->emit('success', 'Environment variable added successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -54,7 +54,7 @@ class ResourceLimits extends Component
$this->resource->save(); $this->resource->save();
$this->emit('success', 'Resource limits updated successfully.'); $this->emit('success', 'Resource limits updated successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -29,7 +29,7 @@ class All extends Component
$this->emit('success', 'Storage added successfully'); $this->emit('success', 'Storage added successfully');
$this->emit('clearAddStorage'); $this->emit('clearAddStorage');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -33,7 +33,7 @@ class RunCommand extends Component
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true); $activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e, $this);
} }
} }
} }

View File

@@ -15,6 +15,7 @@ class Form extends Component
public $dockerVersion; public $dockerVersion;
public string|null $wildcard_domain = null; public string|null $wildcard_domain = null;
public int $cleanup_after_percentage; public int $cleanup_after_percentage;
public bool $dockerInstallationStarted = false;
protected $rules = [ protected $rules = [
'server.name' => 'required|min:6', 'server.name' => 'required|min:6',
@@ -44,19 +45,23 @@ class Form extends Component
public function installDocker() public function installDocker()
{ {
$activity = resolve(InstallDocker::class)($this->server, currentTeam()); $this->dockerInstallationStarted = true;
$activity = resolve(InstallDocker::class)($this->server);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} }
public function validateServer() public function validateServer()
{ {
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server); ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
if ($uptime) { if ($uptime) {
$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'); ray($this->uptime);
$this->emit('error', 'Server is not reachable.');
return; return;
} }
if ($dockerVersion) { if ($dockerVersion) {
@@ -64,10 +69,10 @@ class Form extends Component
$this->emit('proxyStatusUpdated'); $this->emit('proxyStatusUpdated');
$this->emit('success', 'Docker Engine 23+ is installed!'); $this->emit('success', 'Docker Engine 23+ is installed!');
} else { } else {
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.'); $this->emit('error', 'No Docker Engine or older than 23 version installed.');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, that: $this); return handleError($e, $this, customErrorMessage: "Server is not reachable: ");
} }
} }
@@ -82,23 +87,19 @@ class Form extends Component
$this->server->delete(); $this->server->delete();
return redirect()->route('server.all'); return redirect()->route('server.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
public function submit() public function submit()
{ {
$this->validate(); $this->validate();
// $validation = Validator::make($this->server->toArray(), [ $uniqueIPs = Server::all()->reject(function (Server $server) {
// 'ip' => [ return $server->id === $this->server->id;
// 'ip' })->pluck('ip')->toArray();
// ], if (in_array($this->server->ip, $uniqueIPs)) {
// ]); $this->emit('error', 'IP address is already in use by another team.');
// if ($validation->fails()) { return;
// 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

@@ -26,7 +26,7 @@ class ByIp extends Component
protected $rules = [ protected $rules = [
'name' => 'required|string', 'name' => 'required|string',
'description' => 'nullable|string', 'description' => 'nullable|string',
'ip' => 'required|ip', 'ip' => 'required',
'user' => 'required|string', 'user' => 'required|string',
'port' => 'required|integer', 'port' => 'required|integer',
]; ];
@@ -79,7 +79,7 @@ class ByIp extends Component
$server->settings->save(); $server->settings->save();
return redirect()->route('server.show', $server->uuid); return redirect()->route('server.show', $server->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
} }

View File

@@ -2,9 +2,8 @@
namespace App\Http\Livewire\Server; namespace App\Http\Livewire\Server;
use App\Actions\Proxy\CheckConfigurationSync; use App\Actions\Proxy\CheckConfiguration;
use App\Actions\Proxy\SaveConfigurationSync; use App\Actions\Proxy\SaveConfiguration;
use App\Enums\ProxyTypes;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@@ -48,34 +47,32 @@ class Proxy extends Component
public function submit() public function submit()
{ {
try { try {
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings); SaveConfiguration::run($this->server);
$this->server->proxy->redirect_url = $this->redirect_url; $this->server->proxy->redirect_url = $this->redirect_url;
$this->server->save(); $this->server->save();
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server); setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
$this->emit('success', 'Proxy configuration saved.'); $this->emit('success', 'Proxy configuration saved.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
public function reset_proxy_configuration() public function reset_proxy_configuration()
{ {
try { try {
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true); $this->proxy_settings = CheckConfiguration::run($this->server, true);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
public function loadProxyConfiguration() public function loadProxyConfiguration()
{ {
try { try {
ray('loadProxyConfiguration'); $this->proxy_settings = CheckConfiguration::run($this->server);
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
} }

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Server\Proxy; namespace App\Http\Livewire\Server\Proxy;
use App\Actions\Proxy\SaveConfiguration;
use App\Actions\Proxy\StartProxy; use App\Actions\Proxy\StartProxy;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@@ -12,20 +13,25 @@ class Deploy extends Component
public $proxy_settings = null; public $proxy_settings = null;
protected $listeners = ['proxyStatusUpdated']; protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated() { public function proxyStatusUpdated()
{
$this->server->refresh(); $this->server->refresh();
} }
public function startProxy() public function startProxy()
{ {
if ( try {
$this->server->proxy->last_applied_settings && if (
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings $this->server->proxy->last_applied_settings &&
) { $this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings); ) {
} SaveConfiguration::run($this->server);
}
$activity = resolve(StartProxy::class)($this->server); $activity = resolve(StartProxy::class)($this->server);
$this->emit('newMonitorActivity', $activity->id); $this->emit('newMonitorActivity', $activity->id);
} catch (\Throwable $e) {
return handleError($e);
}
} }
public function stop() public function stop()

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Livewire\Server\Proxy;
use App\Models\Server;
use Livewire\Component;
class Modal extends Component
{
public Server $server;
public function proxyStatusUpdated()
{
$this->emit('proxyStatusUpdated');
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Livewire\Server\Proxy; namespace App\Http\Livewire\Server\Proxy;
use App\Jobs\ContainerStatusJob;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@@ -18,13 +19,11 @@ class Status extends Component
{ {
try { try {
if ($this->server->isFunctional()) { if ($this->server->isFunctional()) {
$container = getContainerStatus(server: $this->server, container_id: 'coolify-proxy'); dispatch_sync(new ContainerStatusJob($this->server));
$this->server->proxy->status = $container;
$this->server->save();
$this->emit('proxyStatusUpdated'); $this->emit('proxyStatusUpdated');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e); return handleError($e);
} }
} }
public function getProxyStatusWithNoti() public function getProxyStatusWithNoti()

View File

@@ -13,9 +13,12 @@ class Show extends Component
public function mount() public function mount()
{ {
try { try {
$this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail(); $this->server = Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->first();
if (is_null($this->server)) {
return redirect()->route('server.all');
}
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
public function render() public function render()

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,28 +27,41 @@ 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 handleError($e, $this);
} }
} }
public function checkConnection() public function checkConnection()
{ {
try { try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server); ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server, true);
if ($uptime) { if ($uptime) {
$this->server->settings->update([
'is_reachable' => true
]);
$this->emit('success', 'Server is reachable with this private key.'); $this->emit('success', 'Server is reachable with this private key.');
} else { } else {
$this->server->settings->update([
'is_reachable' => false,
'is_usable' => false
]);
$this->emit('error', 'Server is not reachable with this private key.'); $this->emit('error', 'Server is not reachable with this private key.');
return; return;
} }
if ($dockerVersion) { if ($dockerVersion) {
$this->server->settings->update([
'is_usable' => true
]);
$this->emit('success', 'Server is usable for Coolify.'); $this->emit('success', 'Server is usable for Coolify.');
} else { } else {
$this->server->settings->update([
'is_usable' => false
]);
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.'); $this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
throw new \Exception($e->getMessage()); return handleError($e, $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

@@ -51,7 +51,7 @@ class Email extends Component
$this->settings->save(); $this->settings->save();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function submitResend() { public function submitResend() {
@@ -64,7 +64,7 @@ class Email extends Component
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->settings->resend_enabled = false; $this->settings->resend_enabled = false;
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function instantSaveResend() { public function instantSaveResend() {
@@ -72,7 +72,7 @@ class Email extends Component
$this->settings->smtp_enabled = false; $this->settings->smtp_enabled = false;
$this->submitResend(); $this->submitResend();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function instantSave() public function instantSave()
@@ -81,7 +81,7 @@ class Email extends Component
$this->settings->resend_enabled = false; $this->settings->resend_enabled = false;
$this->submit(); $this->submit();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -100,7 +100,7 @@ class Email extends Component
$this->settings->save(); $this->settings->save();
$this->emit('success', 'Settings saved successfully.'); $this->emit('success', 'Settings saved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }

View File

@@ -52,7 +52,7 @@ class Change extends Component
$this->validate(); $this->validate();
$this->github_app->save(); $this->github_app->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
@@ -66,7 +66,7 @@ class Change extends Component
$this->github_app->delete(); $this->github_app->delete();
redirect()->route('source.all'); redirect()->route('source.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -50,7 +50,7 @@ class Create extends Component
} }
redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -31,7 +31,7 @@ class Actions extends Component
$this->emit('reloadWindow', 5000); $this->emit('reloadWindow', 5000);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function resume() public function resume()
@@ -66,7 +66,7 @@ class Actions extends Component
$this->emit('reloadWindow', 5000); $this->emit('reloadWindow', 5000);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
public function stripeCustomerPortal() { public function stripeCustomerPortal() {

View File

@@ -44,6 +44,7 @@ class PricingPlans extends Component
return; return;
} }
$payload = [ $payload = [
'billing_address_collection' => 'required',
'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id, 'client_reference_id' => auth()->user()->id . ':' . currentTeam()->id,
'line_items' => [[ 'line_items' => [[
'price' => $priceId, 'price' => $priceId,

View File

@@ -32,7 +32,7 @@ class Create extends Component
refreshSession(); refreshSession();
return redirect()->route('team.index'); return redirect()->route('team.index');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -12,7 +12,6 @@ class Delete extends Component
$currentTeam = currentTeam(); $currentTeam = currentTeam();
$currentTeam->delete(); $currentTeam->delete();
$team = auth()->user()->teams()->first();
$currentTeam->members->each(function ($user) use ($currentTeam) { $currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === auth()->user()->id) { if ($user->id === auth()->user()->id) {
return; return;

View File

@@ -29,7 +29,7 @@ class Form extends Component
$this->team->save(); $this->team->save();
refreshSession(); refreshSession();
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -4,9 +4,13 @@ namespace App\Http\Livewire\Team;
use App\Models\TeamInvitation; use App\Models\TeamInvitation;
use App\Models\User; use App\Models\User;
use App\Notifications\TransactionalEmails\InvitationLink; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Artisan;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class InviteLink extends Component class InviteLink extends Component
{ {
@@ -20,64 +24,74 @@ class InviteLink extends Component
public function viaEmail() public function viaEmail()
{ {
$this->generate_invite_link(isEmail: true); $this->generate_invite_link(sendEmail: true);
} }
private function generate_invite_link(bool $isEmail = false) public function viaLink()
{
$this->generate_invite_link(sendEmail: false);
}
private function generate_invite_link(bool $sendEmail = false)
{ {
try { try {
$uuid = new Cuid2(32);
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
$user = User::whereEmail($this->email);
if (!$user->exists()) {
return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email).");
}
$member_emails = currentTeam()->members()->get()->pluck('email'); $member_emails = currentTeam()->members()->get()->pluck('email');
if ($member_emails->contains($this->email)) { if ($member_emails->contains($this->email)) {
return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . "."); return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of " . currentTeam()->name . ".");
} }
$uuid = new Cuid2(32);
$link = url('/') . config('constants.invitation.link.base_url') . $uuid;
$user = User::whereEmail($this->email)->first();
$invitation = TeamInvitation::whereEmail($this->email); if (is_null($user)) {
$password = Str::password();
if ($invitation->exists()) { $user = User::create([
$created_at = $invitation->first()->created_at; 'name' => Str::of($this->email)->before('@'),
$diff = $created_at->diffInMinutes(now()); 'email' => $this->email,
if ($diff <= config('constants.invitation.link.expiration')) { 'password' => Hash::make($password),
return general_error_handler(that: $this, customErrorMessage: "Invitation already sent to $this->email and waiting for action."); 'force_password_reset' => true,
]);
$token = Crypt::encryptString("{$user->email}@@@$password");
$link = route('auth.link', ['token' => $token]);
}
$invitation = TeamInvitation::whereEmail($this->email)->first();
if (!is_null($invitation)) {
$invitationValid = $invitation->isValid();
if ($invitationValid) {
return handleError(livewire: $this, customErrorMessage: "Pending invitation already exists for $this->email.");
} else { } else {
$invitation->delete(); $invitation->delete();
} }
} }
TeamInvitation::firstOrCreate([ $invitation = TeamInvitation::firstOrCreate([
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
'uuid' => $uuid, 'uuid' => $uuid,
'email' => $this->email, 'email' => $this->email,
'role' => $this->role, 'role' => $this->role,
'link' => $link, 'link' => $link,
'via' => $isEmail ? 'email' : 'link', 'via' => $sendEmail ? 'email' : 'link',
]); ]);
if ($isEmail) { if ($sendEmail) {
$user->first()->notify(new InvitationLink); $mail = new MailMessage();
$mail->view('emails.invitation-link', [
'team' => currentTeam()->name,
'invitation_link' => $link,
]);
$mail->subject('You have been invited to ' . currentTeam()->name . ' on ' . config('app.name') . '.');
send_user_an_email($mail, $this->email);
$this->emit('success', 'Invitation sent via email successfully.'); $this->emit('success', 'Invitation sent via email successfully.');
$this->emit('refreshInvitations');
return;
} else { } else {
$this->emit('success', 'Invitation link generated.'); $this->emit('success', 'Invitation link generated.');
$this->emit('refreshInvitations');
} }
$this->emit('refreshInvitations');
} catch (\Throwable $e) { } catch (\Throwable $e) {
$error_message = $e->getMessage(); $error_message = $e->getMessage();
if ($e->getCode() === '23505') { if ($e->getCode() === '23505') {
$error_message = 'Invitation already sent.'; $error_message = 'Invitation already sent.';
} }
return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message); return handleError(error: $e, livewire: $this, customErrorMessage: $error_message);
} }
} }
public function viaLink()
{
$this->generate_invite_link();
}
} }

View File

@@ -3,6 +3,7 @@
namespace App\Http\Livewire\Team; namespace App\Http\Livewire\Team;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Livewire\Component; use Livewire\Component;
class Member extends Component class Member extends Component
@@ -24,6 +25,10 @@ class Member extends Component
public function remove() public function remove()
{ {
$this->member->teams()->detach(currentTeam()); $this->member->teams()->detach(currentTeam());
Cache::forget("team:{$this->member->id}");
Cache::remember('team:' . $this->member->id, 3600, function() {
return $this->member->teams()->first();
});
$this->emit('reloadWindow'); $this->emit('reloadWindow');
} }
} }

View File

@@ -68,7 +68,7 @@ class Create extends Component
$this->storage->save(); $this->storage->save();
return redirect()->route('team.storages.show', $this->storage->uuid); return redirect()->route('team.storages.show', $this->storage->uuid);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -78,7 +78,7 @@ class Create extends Component
$this->storage->testConnection(); $this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -33,7 +33,7 @@ class Form extends Component
$this->storage->testConnection(); $this->storage->testConnection();
return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -43,7 +43,7 @@ class Form extends Component
$this->storage->delete(); $this->storage->delete();
return redirect()->route('team.storages.all'); return redirect()->route('team.storages.all');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
@@ -56,7 +56,7 @@ class Form extends Component
$this->storage->save(); $this->storage->save();
$this->emit('success', 'Storage settings saved.'); $this->emit('success', 'Storage settings saved.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler($e, $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -36,9 +36,9 @@ class Upgrade extends Component
} }
$this->showProgress = true; $this->showProgress = true;
resolve(UpdateCoolify::class)(true); resolve(UpdateCoolify::class)(true);
Toaster::success("Upgrading to {$this->latestVersion} version..."); $this->emit('success', "Upgrading to {$this->latestVersion} version...");
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

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 Illuminate\Support\Str;
class Index extends Component class Index extends Component
{ {
@@ -46,14 +47,14 @@ class Index extends Component
return; return;
} }
$waitlist = Waitlist::create([ $waitlist = Waitlist::create([
'email' => $this->email, 'email' => Str::lower($this->email),
'type' => 'registration', 'type' => 'registration',
]); ]);
$this->emit('success', 'Check your email to verify your email address.'); $this->emit('success', 'Check your email to verify your email address.');
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid)); dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
} catch (\Throwable $e) { } catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this); return handleError($e, $this);
} }
} }
} }

View File

@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class IsBoardingFlow class IsBoardingFlow
{ {
@@ -17,6 +18,9 @@ class IsBoardingFlow
{ {
// ray()->showQueries()->color('orange'); // ray()->showQueries()->color('orange');
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) { if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('boarding'); return redirect('boarding');
} }
return $next($request); return $next($request);

View File

@@ -5,6 +5,7 @@ namespace App\Http\Middleware;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
class IsSubscriptionValid class IsSubscriptionValid
{ {
@@ -31,6 +32,9 @@ class IsSubscriptionValid
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) { if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
// ray('SubscriptionValid Middleware'); // ray('SubscriptionValid Middleware');
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) { if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect('subscription'); return redirect('subscription');
} else { } else {
return $next($request); return $next($request);

View File

@@ -1,54 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Notifications\Application\StatusChanged;
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\SerializesModels;
class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public string $containerName;
public function __construct(
public Application $application,
public int $pullRequestId = 0)
{
$this->containerName = generateApplicationContainerName($application->uuid, $pullRequestId);
}
public function uniqueId(): string
{
return $this->containerName;
}
public function handle(): void
{
try {
$status = getApplicationContainerStatus(application: $this->application);
if ($this->application->status === 'running' && $status !== 'running') {
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
}
if ($this->pullRequestId !== 0) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pullRequestId);
$preview->status = $status;
$preview->save();
} else {
$this->application->status = $status;
$this->application->save();
}
} catch (\Throwable $e) {
ray($e->getMessage());
throw $e;
}
}
}

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);
@@ -175,7 +177,7 @@ class ApplicationDeploymentJob implements ShouldQueue
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->execute_remote_command( $this->execute_remote_command(
[ [
$this->execute_in_builder("echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile") executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
], ],
); );
$this->build_image_name = Str::lower("{$this->application->git_repository}:build"); $this->build_image_name = Str::lower("{$this->application->git_repository}:build");
@@ -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.'"],
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
);
} }
private function prepare_builder_image() private function prepare_builder_image()
@@ -317,16 +324,12 @@ class ApplicationDeploymentJob implements ShouldQueue
"hidden" => true, "hidden" => true,
], ],
[ [
"command" => $this->execute_in_builder("mkdir -p {$this->workdir}") "command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
], ],
); );
} }
private function execute_in_builder(string $command)
{
return "docker exec {$this->deployment_uuid} bash -c '{$command}'";
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
}
private function clone_repository() private function clone_repository()
{ {
@@ -338,7 +341,7 @@ class ApplicationDeploymentJob implements ShouldQueue
$this->importing_git_repository() $this->importing_git_repository()
], ],
[ [
$this->execute_in_builder("cd {$this->workdir} && git rev-parse HEAD"), executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git rev-parse HEAD"),
"hidden" => true, "hidden" => true,
"save" => "git_commit_sha" "save" => "git_commit_sha"
], ],
@@ -365,13 +368,13 @@ class ApplicationDeploymentJob implements ShouldQueue
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}"; $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command); $git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands->push($this->execute_in_builder($git_clone_command)); $commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
} else { } else {
$github_access_token = generate_github_installation_token($this->source); $github_access_token = generate_github_installation_token($this->source);
$commands->push($this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}")); $commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}"));
} }
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
$commands->push($this->execute_in_builder("cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name")); $commands->push(executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
} }
return $commands->implode(' && '); return $commands->implode(' && ');
} }
@@ -381,10 +384,10 @@ class ApplicationDeploymentJob implements ShouldQueue
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}"; $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}";
$git_clone_command = $this->set_git_import_settings($git_clone_command); $git_clone_command = $this->set_git_import_settings($git_clone_command);
$commands = collect([ $commands = collect([
$this->execute_in_builder("mkdir -p /root/.ssh"), executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
$this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
$this->execute_in_builder("chmod 600 /root/.ssh/id_rsa"), executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
$this->execute_in_builder($git_clone_command) executeInDocker($this->deployment_uuid, $git_clone_command)
]); ]);
return $commands->implode(' && '); return $commands->implode(' && ');
} }
@@ -407,7 +410,7 @@ class ApplicationDeploymentJob implements ShouldQueue
private function cleanup_git() private function cleanup_git()
{ {
$this->execute_remote_command( $this->execute_remote_command(
[$this->execute_in_builder("rm -fr {$this->workdir}/.git")], [executeInDocker($this->deployment_uuid, "rm -fr {$this->workdir}/.git")],
); );
} }
@@ -418,8 +421,8 @@ class ApplicationDeploymentJob implements ShouldQueue
"echo -n 'Generating nixpacks configuration.'", "echo -n 'Generating nixpacks configuration.'",
], ],
[$this->nixpacks_build_cmd()], [$this->nixpacks_build_cmd()],
[$this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")], [executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
[$this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile")] [executeInDocker($this->deployment_uuid, "rm -f {$this->workdir}/.nixpacks/Dockerfile")]
); );
} }
@@ -437,7 +440,7 @@ class ApplicationDeploymentJob implements ShouldQueue
$nixpacks_command .= " --install-cmd \"{$this->application->install_command}\""; $nixpacks_command .= " --install-cmd \"{$this->application->install_command}\"";
} }
$nixpacks_command .= " {$this->workdir}"; $nixpacks_command .= " {$this->workdir}";
return $this->execute_in_builder($nixpacks_command); return executeInDocker($this->deployment_uuid, $nixpacks_command);
} }
private function generate_env_variables() private function generate_env_variables()
@@ -515,7 +518,7 @@ class ApplicationDeploymentJob implements ShouldQueue
} }
$this->docker_compose = Yaml::dump($docker_compose, 10); $this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose); $this->docker_compose_base64 = base64_encode($this->docker_compose);
$this->execute_remote_command([$this->execute_in_builder("echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
} }
private function generate_local_persistent_volumes() private function generate_local_persistent_volumes()
@@ -576,10 +579,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) {
@@ -640,7 +648,7 @@ class ApplicationDeploymentJob implements ShouldQueue
private function generate_healthcheck_commands() private function generate_healthcheck_commands()
{ {
if ($this->application->dockerfile) { if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl. // TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
return 'exit 0'; return 'exit 0';
} }
@@ -667,7 +675,7 @@ class ApplicationDeploymentJob implements ShouldQueue
if ($this->application->settings->is_static) { if ($this->application->settings->is_static) {
$this->execute_remote_command([ $this->execute_remote_command([
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
]); ]);
$dockerfile = base64_encode("FROM {$this->application->static_image} $dockerfile = base64_encode("FROM {$this->application->static_image}
@@ -694,18 +702,18 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}"); }");
$this->execute_remote_command( $this->execute_remote_command(
[ [
$this->execute_in_builder("echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod") executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod")
], ],
[ [
$this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
], ],
[ [
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
] ]
); );
} else { } else {
$this->execute_remote_command([ $this->execute_remote_command([
$this->execute_in_builder("docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
]); ]);
} }
} }
@@ -715,7 +723,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
if ($this->currently_running_container_name) { if ($this->currently_running_container_name) {
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Removing old version of your application.'"], ["echo -n 'Removing old version of your application.'"],
[$this->execute_in_builder("docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
); );
} }
} }
@@ -724,7 +732,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
{ {
$this->execute_remote_command( $this->execute_remote_command(
["echo -n 'Rolling update started.'"], ["echo -n 'Rolling update started.'"],
[$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true], [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
); );
} }
@@ -747,7 +755,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function add_build_env_variables_to_dockerfile() private function add_build_env_variables_to_dockerfile()
{ {
$this->execute_remote_command([ $this->execute_remote_command([
$this->execute_in_builder("cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile' executeInDocker($this->deployment_uuid, "cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile'
]); ]);
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
@@ -756,7 +764,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} }
$dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
$this->execute_remote_command([ $this->execute_remote_command([
$this->execute_in_builder("echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"), executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"),
"hidden" => true "hidden" => true
]); ]);
} }

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

@@ -2,15 +2,17 @@
namespace App\Jobs; namespace App\Jobs;
use App\Models\TeamInvitation;
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;
@@ -31,7 +33,12 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage()); send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
throw $e; }
try {
$this->cleanup_invitation_link();
} catch (\Throwable $e) {
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
ray($e->getMessage());
} }
} }
@@ -42,4 +49,11 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique
$item->delete(); $item->delete();
} }
} }
private function cleanup_invitation_link()
{
$invitation = TeamInvitation::all();
foreach ($invitation as $item) {
$item->isValid();
}
}
} }

View File

@@ -0,0 +1,295 @@
<?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 Illuminate\Support\Str;
class ContainerStatusJob implements ShouldQueue, 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()
{
$uptime = instant_remote_process(['uptime'], $this->server, false);
if (!is_null($uptime)) {
return true;
}
}
public function handle(): void
{
try {
// ray()->clearAll();
$serverUptimeCheckNumber = 0;
$serverUptimeCheckNumberMax = 3;
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 ls -q"], $this->server);
if (!$containers) {
return;
}
$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();
/// Check if proxy is running
$foundProxyContainer = $containers->filter(function ($value, $key) {
return data_get($value, 'Name') === '/coolify-proxy';
})->first();
if (!$foundProxyContainer) {
if ($this->server->isProxyShouldRun()) {
resolve(StartProxy::class)($this->server, false);
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
}
} else {
ray($foundProxyContainer);
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
$this->server->save();
}
$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');
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) {
return false;
}
return $value;
})->first();
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,15 +12,15 @@ 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;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
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;
@@ -116,7 +116,7 @@ class DatabaseBackupJob implements ShouldQueue
$this->backup_status = 'success'; $this->backup_status = 'success';
$this->team->notify(new BackupSuccess($this->backup, $this->database)); $this->team->notify(new BackupSuccess($this->backup, $this->database));
} catch (Throwable $e) { } catch (\Throwable $e) {
$this->backup_status = 'failed'; $this->backup_status = 'failed';
$this->add_to_backup_output($e->getMessage()); $this->add_to_backup_output($e->getMessage());
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());

View File

@@ -1,56 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\ApplicationPreview;
use App\Models\StandalonePostgresql;
use App\Notifications\Application\StatusChanged;
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\SerializesModels;
class DatabaseContainerStatusJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public string $containerName;
public function __construct(
public StandalonePostgresql $database,
) {
$this->containerName = $database->uuid;
}
public function uniqueId(): string
{
return $this->containerName;
}
public function handle(): void
{
try {
$status = getContainerStatus(
server: $this->database->destination->server,
container_id: $this->containerName,
throwError: false
);
if ($this->database->status === 'running' && $status !== 'running') {
if (data_get($this->database, 'environment.project.team')) {
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
}
}
if ($this->database->status !== $status) {
$this->database->status = $status;
$this->database->save();
}
} catch (\Throwable $e) {
send_internal_notification('DatabaseContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

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

@@ -1,86 +0,0 @@
<?php
namespace App\Jobs;
use App\Actions\Proxy\StartProxy;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
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;
class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public Server $server;
public $tries = 1;
public $timeout = 120;
public function __construct(Server $server)
{
$this->server = $server;
}
public function middleware(): array
{
return [new WithoutOverlapping($this->server->uuid)];
}
public function uniqueId(): string
{
ray($this->server->uuid);
return $this->server->uuid;
}
public function handle(): void
{
try {
$proxyType = data_get($this->server, 'proxy.type');
if ($proxyType === ProxyTypes::NONE->value) {
return;
}
if (is_null($proxyType)) {
if ($this->server->isProxyShouldRun()) {
$this->server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
$this->server->proxy->status = ProxyStatus::EXITED->value;
$this->server->save();
resolve(StartProxy::class)($this->server);
return;
}
}
$container = getContainerStatus(server: $this->server, all_data: true, container_id: 'coolify-proxy', throwError: false);
$containerStatus = data_get($container, 'State.Status');
$databaseContainerStatus = data_get($this->server, 'proxy.status', 'exited');
if ($proxyType !== ProxyTypes::NONE->value) {
if ($containerStatus === 'running') {
$this->server->proxy->status = $containerStatus;
$this->server->save();
return;
}
if ((is_null($containerStatus) ||$containerStatus !== 'running' || $databaseContainerStatus !== 'running' || ($containerStatus && $databaseContainerStatus !== $containerStatus)) && $this->server->isProxyShouldRun()) {
$this->server->proxy->status = $containerStatus;
$this->server->save();
resolve(StartProxy::class)($this->server);
return;
}
}
} catch (\Throwable $e) {
if ($e->getCode() === 1) {
$this->server->proxy->status = 'exited';
$this->server->save();
}
send_internal_notification('ProxyContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

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,14 +3,15 @@
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;
use Str; use Illuminate\Support\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

@@ -19,6 +19,13 @@ class Team extends Model implements SendsDiscord, SendsEmail
'resend_api_key' => 'encrypted', 'resend_api_key' => 'encrypted',
]; ];
protected static function booted()
{
// static::saved(function () {
// refreshSession();
// });
}
public function routeNotificationForDiscord() public function routeNotificationForDiscord()
{ {
return data_get($this, 'discord_webhook_url', null); return data_get($this, 'discord_webhook_url', null);

View File

@@ -19,4 +19,13 @@ class TeamInvitation extends Model
{ {
return $this->belongsTo(Team::class); return $this->belongsTo(Team::class);
} }
public function isValid() {
$createdAt = $this->created_at;
$diff = $createdAt->diffInMinutes(now());
if ($diff <= config('constants.invitation.link.expiration')) {
return true;
} else {
$this->delete();
}
}
} }

View File

@@ -4,10 +4,10 @@ namespace App\Models;
use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsEmail;
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword; use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
use Cache;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Cache;
use Laravel\Fortify\TwoFactorAuthenticatable; use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
@@ -61,7 +61,7 @@ class User extends Authenticatable implements SendsEmail
public function isAdmin() public function isAdmin()
{ {
return $this->pivot->role === 'admin' || $this->pivot->role === 'owner'; return data_get($this->pivot,'role') === 'admin' || data_get($this->pivot,'role') === 'owner';
} }
public function isAdminFromSession() public function isAdminFromSession()
@@ -78,7 +78,8 @@ class User extends Authenticatable implements SendsEmail
if ($is_part_of_root_team && $is_admin_of_root_team) { if ($is_part_of_root_team && $is_admin_of_root_team) {
return true; return true;
} }
$role = $teams->where('id', auth()->user()->id)->first()->pivot->role; $team = $teams->where('id', session('currentTeam')->id)->first();
$role = data_get($team,'pivot.role');
return $role === 'admin' || $role === 'owner'; return $role === 'admin' || $role === 'owner';
} }

View File

@@ -4,8 +4,6 @@ namespace App\Notifications\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -18,13 +16,14 @@ class DeploymentFailed extends Notification implements ShouldQueue
public $tries = 5; public $tries = 5;
public Application $application; public Application $application;
public string $deployment_uuid;
public ?ApplicationPreview $preview = null; public ?ApplicationPreview $preview = null;
public string $deployment_uuid;
public string $application_name; public string $application_name;
public ?string $deployment_url = null;
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
public ?string $deployment_url = null;
public ?string $fqdn = null; public ?string $fqdn = null;
public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null) public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null)

View File

@@ -4,8 +4,6 @@ namespace App\Notifications\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\ApplicationPreview; use App\Models\ApplicationPreview;
use App\Notifications\Channels\DiscordChannel;
use App\Notifications\Channels\EmailChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -18,14 +16,15 @@ class DeploymentSuccess extends Notification implements ShouldQueue
public $tries = 5; public $tries = 5;
public Application $application; public Application $application;
public string $deployment_uuid;
public ApplicationPreview|null $preview = null; public ApplicationPreview|null $preview = null;
public string $deployment_uuid;
public string $application_name; public string $application_name;
public string|null $deployment_url = null;
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
public string|null $fqdn;
public ?string $deployment_url = null;
public ?string $fqdn;
public function __construct(Application $application, string $deployment_uuid, ApplicationPreview|null $preview = null) public function __construct(Application $application, string $deployment_uuid, ApplicationPreview|null $preview = null)
{ {
@@ -53,7 +52,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

@@ -2,8 +2,7 @@
namespace App\Notifications\Application; namespace App\Notifications\Application;
use App\Notifications\Channels\DiscordChannel; use App\Models\Application;
use App\Notifications\Channels\EmailChannel;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
@@ -15,13 +14,14 @@ class StatusChanged extends Notification implements ShouldQueue
use Queueable; use Queueable;
public $tries = 5; public $tries = 5;
public $application;
public Application $application;
public string $application_name; public string $application_name;
public string|null $application_url = null;
public string $project_uuid; public string $project_uuid;
public string $environment_name; public string $environment_name;
public string|null $fqdn;
public ?string $application_url = null;
public ?string $fqdn;
public function __construct($application) public function __construct($application)
{ {

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

@@ -11,7 +11,7 @@ use Illuminate\Support\Str;
trait ExecuteRemoteCommand trait ExecuteRemoteCommand
{ {
public string|null $save = null; public ?string $save = null;
public function execute_remote_command(...$commands) public function execute_remote_command(...$commands)
{ {
@@ -25,12 +25,8 @@ trait ExecuteRemoteCommand
throw new \RuntimeException('Server is not set or is not an instance of Server model'); throw new \RuntimeException('Server is not set or is not an instance of Server model');
} }
$ip = data_get($this->server, 'ip');
$user = data_get($this->server, 'user');
$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) {
$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 +35,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($this->server, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) { $process = Process::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,

Some files were not shown because too many files have changed in this diff Show More