Compare commits

...

78 Commits

Author SHA1 Message Date
Andras Bacsai
12fc5a8f91 Merge pull request #1223 from coollabsio/next
v4.0.0-beta.35
2023-09-13 12:34:45 +02:00
Andras Bacsai
9eba058cf7 fix: disable dockerfile based healtcheck for now 2023-09-13 12:08:44 +02:00
Andras Bacsai
fa4f5fea8c fix: forgot password 2023-09-13 11:29:31 +02:00
Andras Bacsai
898563fe7c email: server lost connection 2023-09-13 11:05:10 +02:00
Andras Bacsai
c418a17161 ui: show trial instead of sub 2023-09-12 17:24:46 +02:00
Andras Bacsai
cd0da04ea2 wip: server check instead of app check 2023-09-12 15:47:30 +02:00
Andras Bacsai
01e942c6a0 fix: show help modal everywhere 2023-09-12 15:06:07 +02:00
Andras Bacsai
bb9abafa82 test gh runner 2023-09-12 15:00:05 +02:00
Andras Bacsai
d0cd926517 fix: sub type 2023-09-12 14:53:54 +02:00
Andras Bacsai
9baf0161c7 fix: help should send cc on email 2023-09-12 14:51:35 +02:00
Andras Bacsai
8ba18b2ce1 fix: confirm email before sending 2023-09-12 13:19:55 +02:00
Andras Bacsai
ab021ee535 fix: server is functional check 2023-09-12 13:14:01 +02:00
Andras Bacsai
c76a1b1ba5 fix: webhooks should not run if server is not functional 2023-09-12 13:10:39 +02:00
Andras Bacsai
6266a5e500 internal: trial emails 2023-09-12 12:03:17 +02:00
Andras Bacsai
5d27e89bfa feat: dynamic trial period 2023-09-12 11:23:31 +02:00
Andras Bacsai
6da4e78374 feat: trial 2023-09-12 11:19:21 +02:00
Andras Bacsai
be30651172 fix: remove nixpkgarchive from ui 2023-09-12 09:46:10 +02:00
Andras Bacsai
95764c2b76 fix: remove nixpkgarchive 2023-09-12 09:45:20 +02:00
Andras Bacsai
92a75685b5 version plus plus 2023-09-11 23:06:24 +02:00
Andras Bacsai
a1592373aa Merge pull request #1222 from coollabsio/next
v4.0.0-beta.34
2023-09-11 23:05:30 +02:00
Andras Bacsai
5747a87f66 ui: fix 2023-09-11 22:55:23 +02:00
Andras Bacsai
9cda671aef Add email to user 2023-09-11 22:45:07 +02:00
Andras Bacsai
2c9983046c fix 2023-09-11 22:43:07 +02:00
Andras Bacsai
11d33f328e fix: queue after commit 2023-09-11 22:39:18 +02:00
Andras Bacsai
f79c741d95 prepare: global env variables 2023-09-11 22:29:47 +02:00
Andras Bacsai
8a39a4469a fix: proxy check, reduce jobs, etc 2023-09-11 22:29:34 +02:00
Andras Bacsai
42daae10c6 feat: able to invite more people at once 2023-09-11 20:51:31 +02:00
Andras Bacsai
64a65e2018 fix: errors 2023-09-11 17:36:30 +02:00
Andras Bacsai
16c71f3647 fix: old docker version error 2023-09-11 17:19:30 +02:00
Andras Bacsai
363f525ad1 fix: sentry 4469575117 2023-09-11 16:51:38 +02:00
Andras Bacsai
f4fb519d55 internal: add Plausible analytics 2023-09-11 16:41:43 +02:00
Andras Bacsai
b7786504b8 wip: nixpacksarchive 2023-09-11 15:53:05 +02:00
Andras Bacsai
f0adf10e6a version++ 2023-09-11 14:11:22 +02:00
Andras Bacsai
cc8c6c5d16 Merge pull request #1221 from coollabsio/next
UI/UX fixes
2023-09-11 13:59:19 +02:00
Andras Bacsai
4782446f42 ui: show registered users on waitlist page 2023-09-11 13:53:08 +02:00
Andras Bacsai
230155312f ui: services are not availble yet 2023-09-11 13:52:54 +02:00
Andras Bacsai
3ab4365fca ui: user should know that the public key 2023-09-11 13:42:32 +02:00
Andras Bacsai
7349068b95 Merge pull request #1220 from coollabsio/next
Email issues fix
2023-09-11 12:55:52 +02:00
Andras Bacsai
da6cc151d1 fix: email sending error 2023-09-11 12:31:31 +02:00
Andras Bacsai
50527cf0a3 fix: testing email
fix: expired invitiation link
2023-09-11 12:16:26 +02:00
Andras Bacsai
26f490bb00 Merge pull request #1219 from coollabsio/next
Fixes
2023-09-11 10:50:18 +02:00
Andras Bacsai
dc4f412227 fix: recovery code 2023-09-11 10:41:39 +02:00
Andras Bacsai
ec4234e243 fix: only send internal notifcations to enabled channels 2023-09-11 10:19:46 +02:00
Andras Bacsai
f47fcb01ce Merge pull request #1218 from coollabsio/next
feat: generate public key from private keys
2023-09-11 10:16:48 +02:00
Andras Bacsai
02f6673345 feat: generate public key from private keys 2023-09-11 10:15:45 +02:00
Andras Bacsai
e6cd8702b5 Merge pull request #1216 from coollabsio/next
v4.0.0-beta.32
2023-09-10 15:34:08 +02:00
Andras Bacsai
fda4ea8cca fix: errors in views 2023-09-10 15:33:00 +02:00
Andras Bacsai
655d004ce7 Merge pull request #1214 from coollabsio/next
v4.0.0-beta.31
2023-09-09 15:33:16 +02:00
Andras Bacsai
56981d134c fix: improve startProxy action
fix: improve installDocker action and process
feat: add noSubmit prop to custom modal
2023-09-09 15:30:46 +02:00
Andras Bacsai
b9d49d2951 fix: remove -q from docker compose 2023-09-09 13:52:40 +02:00
Andras Bacsai
0c1e7c499e Merge pull request #1213 from coollabsio/next
v4.0.0-beta.30
2023-09-09 13:23:59 +02:00
Andras Bacsai
32fead5753 version++ 2023-09-09 13:23:03 +02:00
Andras Bacsai
e5e9faba35 fix: delete database related things when delete database 2023-09-09 13:18:49 +02:00
Andras Bacsai
2852630d6c Update DatabaseBackupJob.php 2023-09-09 10:34:10 +02:00
Andras Bacsai
a4cc406114 Merge pull request #1212 from coollabsio/next
v4.0.0-beta.29
2023-09-08 18:52:41 +02:00
Andras Bacsai
53b15a5762 update sentry dsn 2023-09-08 18:40:32 +02:00
Andras Bacsai
929a4e6474 fix: coolify already exists should not throw error 2023-09-08 18:40:25 +02:00
Andras Bacsai
45b597bbab feat: cache team settings 2023-09-08 18:33:26 +02:00
Andras Bacsai
0d1a2aa5d1 update testemail command 2023-09-08 17:51:19 +02:00
Andras Bacsai
b82353d5e2 update testemail command 2023-09-08 17:42:08 +02:00
Andras Bacsai
b17c09f7a7 update testemail command 2023-09-08 17:31:02 +02:00
Andras Bacsai
f6c3fe7888 fix: test email on for admins or custom smtp 2023-09-08 17:26:59 +02:00
Andras Bacsai
2e855e030f fix: ui 2023-09-08 16:59:49 +02:00
Andras Bacsai
49f86621f4 fix: instance email settings 2023-09-08 16:56:14 +02:00
Andras Bacsai
03d9f93397 fix: retry notifications 2023-09-08 16:53:19 +02:00
Andras Bacsai
c472042a94 fix: ui 2023-09-08 16:46:53 +02:00
Andras Bacsai
9f4356f67d version++ 2023-09-08 16:28:56 +02:00
Andras Bacsai
a50317cc76 Merge pull request #1209 from coollabsio/next
v4.0.0-beta.28
2023-09-08 16:28:04 +02:00
Andras Bacsai
8afa98a1ca fix: sentry 4451028626 2023-09-08 16:19:45 +02:00
Andras Bacsai
f6737f21dd feat: developer view for env variables 2023-09-08 16:16:59 +02:00
Andras Bacsai
e4a51cc116 fix: sentry 4459819517 2023-09-08 16:16:28 +02:00
Andras Bacsai
acd78ae196 feat: Telegram topics separation 2023-09-08 14:15:28 +02:00
Andras Bacsai
953bcfb5bb fix: db backup job 2023-09-08 12:23:46 +02:00
Andras Bacsai
dacfab8b29 Update BUG_REPORT.yml 2023-09-08 12:08:16 +02:00
Andras Bacsai
48b3e99939 Merge pull request #1203 from coollabsio/next
v4.0.0-beta.27
2023-09-08 12:06:34 +02:00
Andras Bacsai
41ad67c7c9 updates 2023-09-08 12:05:15 +02:00
Andras Bacsai
b49725cb1c fix: bug 2023-09-08 09:37:58 +02:00
Andras Bacsai
75e674a966 version++ 2023-09-08 09:27:44 +02:00
156 changed files with 1565 additions and 698 deletions

26
.github/ISSUE_TEMPLATE/BUG_REPORT.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Bug report
description: Create a new bug report
title: '[Bug]: '
body:
- type: textarea
attributes:
label: Description
description: A clear and concise description of the problem
validations:
required: true
- type: textarea
attributes:
label: Minimal Reproduction (if possible, example repository)
description: Please provide a step by step guide to reproduce the issue
validations:
required: true
- type: textarea
attributes:
label: Exception or Error
description: Please provide error logs if possible.
- type: input
attributes:
label: Version
description: Coolify's version (see bottom left corner).
validations:
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: 🤔 Community Support (Chat)
url: https://coollabs.io/discord
about: Reach out to us on Discord.
- name: 🙋‍♂️ Feature Requests
url: https://github.com/coollabsio/coolify/discussions/categories/feature-requests-ideas
about: All feature requests will be discussed here.

View File

@@ -13,7 +13,7 @@ env:
jobs:
amd64:
runs-on: ubuntu-latest
runs-on: [self-hosted, x64]
steps:
- uses: actions/checkout@v3
- name: Login to ghcr.io
@@ -52,7 +52,7 @@ jobs:
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:next-aarch64
merge-manifest:
runs-on: ubuntu-latest
runs-on: [self-hosted, x64]
permissions:
contents: read
packages: write

View File

@@ -97,7 +97,6 @@ class RunRemoteProcess
'status' => $status->value,
]);
$this->activity->save();
if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
throw new \RuntimeException($processResult->errorOutput());
}

View File

@@ -57,13 +57,13 @@ class CheckResaleLicense
throw new \Exception('Invalid license key.');
}
throw new \Exception('Cannot activate license key.');
} catch (\Throwable $th) {
ray($th);
} catch (\Throwable $e) {
ray($e);
$settings->update([
'resale_license' => null,
'is_resale_license_active' => false,
]);
throw $th;
throw $e;
}
}
}

View File

@@ -20,8 +20,8 @@ class SaveConfigurationSync
"mkdir -p $proxy_path",
"echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml",
], $server);
} catch (\Throwable $th) {
ray($th);
} catch (\Throwable $e) {
ray($e);
}
}

View File

@@ -2,8 +2,6 @@
namespace App\Actions\Proxy;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Illuminate\Support\Str;
use Spatie\Activitylog\Models\Activity;
@@ -30,19 +28,22 @@ class StartProxy
$server->save();
$activity = remote_process([
"echo 'Creating required Docker networks...'",
"echo '####### Creating required Docker networks...'",
...$create_networks_command,
"cd $proxy_path",
"echo 'Creating Docker Compose file...'",
"echo 'Pulling docker image...'",
'docker compose pull -q',
"echo 'Stopping existing proxy...'",
"echo '####### Creating Docker Compose file...'",
"echo '####### Pulling docker image...'",
'docker compose pull',
"echo '####### Stopping existing coolify-proxy...'",
'docker compose down -v --remove-orphans',
"lsof -nt -i:80 | xargs -r kill -9",
"lsof -nt -i:443 | xargs -r kill -9",
"echo 'Starting proxy...'",
"systemctl disable nginx > /dev/null 2>&1 || true",
"systemctl disable apache2 > /dev/null 2>&1 || true",
"systemctl disable apache > /dev/null 2>&1 || true",
"echo '####### Starting coolify-proxy...'",
'docker compose up -d --remove-orphans',
"echo 'Proxy installed successfully...'"
"echo '####### Proxy installed successfully...'"
], $server);
return $activity;

View File

@@ -18,42 +18,42 @@ class InstallDocker
"max-file": "3"
}
}');
$found = StandaloneDocker::where('server_id', $server->id);
if ($found->count() == 0) {
StandaloneDocker::create([
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $server->id,
]);
}
if (isDev()) {
$activity = remote_process([
"echo ####### Installing Prerequisites...",
"echo ####### Installing/updating Docker Engine...",
"echo ####### Configuring Docker Engine (merging existing configuration with the required)...",
"echo ####### Restarting Docker Engine...",
return remote_process([
"echo '####### Installing Prerequisites...'",
"sleep 1",
"echo '####### Installing/updating Docker Engine...'",
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
"sleep 4",
"echo '####### Restarting Docker Engine...'",
"ls -l /tmp"
], $server);
} else {
$activity = remote_process([
"echo ####### Installing Prerequisites...",
return remote_process([
"echo '####### Installing Prerequisites...'",
"command -v jq >/dev/null || apt-get update",
"command -v jq >/dev/null || apt install -y jq",
"echo ####### Installing/updating Docker Engine...",
"echo '####### Installing/updating Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh",
"echo ####### Configuring Docker Engine (merging existing configuration with the required)...",
"echo '####### Configuring Docker Engine (merging existing configuration with the required)...'",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-`date +\"%Y%m%d-%H%M%S\"`\" || echo '{$config}' | base64 -d > /etc/docker/daemon.json",
"echo '{$config}' | base64 -d > /etc/docker/daemon.json.coolify",
"cat <<< $(jq . /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json.coolify",
"cat <<< $(jq -s '.[0] * .[1]' /etc/docker/daemon.json /etc/docker/daemon.json.coolify) > /etc/docker/daemon.json",
"echo ####### Restarting Docker Engine...",
"echo '####### Restarting Docker Engine...'",
"systemctl restart docker",
"echo ####### Creating default network...",
"docker network create --attachable coolify",
"echo ####### Done!"
"echo '####### Creating default Docker network (coolify)...'",
"docker network create --attachable coolify >/dev/null 2>&1 || true",
"echo '####### Done!'"
], $server);
$found = StandaloneDocker::where('server_id', $server->id);
if ($found->count() == 0) {
StandaloneDocker::create([
'name' => 'coolify',
'network' => 'coolify',
'server_id' => $server->id,
]);
}
}
return $activity;
}
}

View File

@@ -43,11 +43,11 @@ class UpdateCoolify
$this->update();
}
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
} catch (\Exception $th) {
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($th->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $th->getMessage());
throw $th;
ray($e->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
throw $e;
}
}

View File

@@ -2,12 +2,16 @@
namespace App\Console\Commands;
use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\Team;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Models\Waitlist;
use App\Notifications\Application\DeploymentFailed;
use App\Notifications\Application\DeploymentSuccess;
use App\Notifications\Application\StatusChanged;
@@ -22,31 +26,34 @@ use Illuminate\Notifications\Messages\MailMessage;
use Mail;
use Str;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
class TestEmail extends Command
class Emails extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'email:test';
protected $signature = 'emails';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a test email to the admin';
protected $description = 'Send out test / prod emails';
/**
* Execute the console command.
*/
private ?MailMessage $mail = null;
private ?string $email = null;
public function handle()
{
$email = select(
$type = select(
'Which Email should be sent?',
options: [
'emails-test' => 'Test',
@@ -58,17 +65,22 @@ class TestEmail extends Command
'invitation-link' => 'Invitation Link',
'waitlist-invitation-link' => 'Waitlist Invitation Link',
'waitlist-confirmation' => 'Waitlist Confirmation',
'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription',
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
],
);
$type = set_transanctional_email_settings();
if (!$type) {
throw new Exception('No email settings found.');
$emailsGathered = ['realusers-before-trial','realusers-server-lost-connection'];
if (!in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to');
}
set_transanctional_email_settings();
$this->mail = new MailMessage();
$this->mail->subject("Test Email");
switch ($email) {
switch ($type) {
case 'emails-test':
$this->mail = (new Test())->toMail();
$this->sendEmail();
break;
case 'application-deployment-success':
$application = Application::all()->first();
@@ -146,39 +158,90 @@ class TestEmail extends Command
case 'waitlist-invitation-link':
$this->mail = new MailMessage();
$this->mail->view('emails.waitlist-invitation', [
'email' => 'test2@example.com',
'password' => "supersecretpassword",
'loginLink' => 'https://coolify.io',
]);
$this->mail->subject('Congratulations! You are invited to join Coolify Cloud.');
$this->sendEmail();
break;
case 'waitlist-confirmation':
$found = Waitlist::where('email', $this->email)->first();
if ($found) {
SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid);
} else {
throw new Exception('Waitlist not found');
}
break;
case 'realusers-before-trial':
$this->mail = new MailMessage();
$this->mail->view(
'emails.waitlist-confirmation',
[
'confirmation_url' => 'http://example.com',
'cancel_url' => 'http://example.com',
]
);
$this->mail->subject('You are on the waitlist!');
$this->sendEmail();
$this->mail->view('emails.before-trial-conversion');
$this->mail->subject('Trial period has been added for all subscription plans.');
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
if (!$teams || $teams->isEmpty()) {
echo 'No teams found.' . PHP_EOL;
return;
}
$emails = [];
foreach ($teams as $team) {
foreach ($team->members as $member) {
if ($member->email) {
$emails[] = $member->email;
}
}
}
$emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails.");
foreach ($emails as $email) {
$this->info($email);
}
$confirmed = confirm('Are you sure?');
if ($confirmed) {
foreach ($emails as $email) {
$this->sendEmail($email);
}
}
break;
case 'realusers-server-lost-connection':
$serverId = text('Server Id');
$server = Server::find($serverId);
if (!$server) {
throw new Exception('Server not found');
}
$admins = [];
$members = $server->team->members;
foreach ($members as $member) {
if ($member->isAdmin()) {
$admins[] = $member->email;
}
}
$this->info('Sending to ' . count($admins) . ' admins.');
foreach ($admins as $admin) {
$this->info($admin);
}
$this->mail = new MailMessage();
$this->mail->view('emails.server-lost-connection', [
'name' => $server->name,
]);
$this->mail->subject('Action required: Server ' . $server->name . ' lost connection.');
foreach ($admins as $email) {
$this->sendEmail($email);
}
break;
}
}
private function sendEmail()
private function sendEmail(string $email = null)
{
if ($email) {
$this->email = $email;
}
Mail::send(
[],
[],
fn (Message $message) => $message
->from(
'internal@example.com',
'Test Email',
)
->to('test@example.com')
->to($this->email)
->subject($this->mail->subject)
->html((string)$this->mail->render())
);
$this->info("Email sent to $this->email successfully. 📧");
}
}

View File

@@ -26,7 +26,7 @@ class Init extends Command
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
$deployment->save();
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
}
}

View File

@@ -82,7 +82,7 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]);
echo "All files uploaded & purged...\n";
} catch (\Exception $e) {
} catch (\Throwable $e) {
echo $e->getMessage();
}
}

View File

@@ -19,7 +19,7 @@ class WaitlistInvite extends Command
*
* @var string
*/
protected $signature = 'waitlist:invite {email?} {--only-email}';
protected $signature = 'waitlist:invite {--people=1} {--only-email} {email?}';
/**
* The console command description.
@@ -33,6 +33,12 @@ class WaitlistInvite extends Command
*/
public function handle()
{
$people = $this->option('people');
for ($i = 0; $i < $people; $i++) {
$this->main();
}
}
private function main() {
if ($this->argument('email')) {
if ($this->option('only-email')) {
$this->next_patient = User::whereEmail($this->argument('email'))->first();
@@ -86,13 +92,10 @@ class WaitlistInvite extends Command
}
private function send_email()
{
ray($this->next_patient->email, $this->password);
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
$loginLink = route('auth.link', ['token' => $token]);
$mail = new MailMessage();
$mail->view('emails.waitlist-invitation', [
'email' => $this->next_patient->email,
'password' => $this->password,
'loginLink' => $loginLink,
]);
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');

View File

@@ -2,6 +2,7 @@
namespace App\Console;
use App\Enums\ProxyTypes;
use App\Jobs\ApplicationContainerStatusJob;
use App\Jobs\CheckResaleLicenseJob;
use App\Jobs\CleanupInstanceStuffsJob;
@@ -9,11 +10,12 @@ use App\Jobs\DatabaseBackupJob;
use App\Jobs\DatabaseContainerStatusJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ProxyCheckJob;
use App\Jobs\ResourceStatusJob;
use App\Jobs\ProxyContainerStatusJob;
use App\Jobs\ServerDetailsCheckJob;
use App\Models\Application;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\Server;
use App\Models\StandalonePostgresql;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -23,23 +25,32 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule): void
{
if (isDev()) {
$schedule->command('horizon:snapshot')->everyMinute();
// $schedule->job(new ResourceStatusJob)->everyMinute();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
$schedule->job(new ServerDetailsCheckJob(Server::find(0)))->everyTenMinutes()->onOneServer();
// $schedule->command('horizon:snapshot')->everyMinute();
// $schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
// $schedule->job(new CheckResaleLicenseJob)->hourly();
$schedule->job(new DockerCleanupJob)->everyOddHour();
// $schedule->job(new DockerCleanupJob)->everyOddHour();
// $this->instance_auto_update($schedule);
// $this->check_scheduled_backups($schedule);
// $this->check_resources($schedule);
// $this->check_proxies($schedule);
} else {
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyTenMinutes()->onOneServer();
// $schedule->job(new ResourceStatusJob)->everyMinute()->onOneServer();
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes()->onOneServer();
$schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($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();
}
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
}
private function check_resources($schedule)
{
@@ -53,7 +64,8 @@ class Kernel extends ConsoleKernel
$schedule->job(new DatabaseContainerStatusJob($postgresql))->everyMinute()->onOneServer();
}
}
private function instance_auto_update($schedule){
private function instance_auto_update($schedule)
{
if (isDev()) {
return;
}
@@ -74,6 +86,11 @@ class Kernel extends ConsoleKernel
if (!$scheduled_backup->enabled) {
continue;
}
if (is_null(data_get($scheduled_backup, 'database'))) {
ray('database not found');
$scheduled_backup->delete();
continue;
}
if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) {
$scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency];

View File

@@ -25,7 +25,7 @@ class Handler extends ExceptionHandler
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
//
ProcessException::class
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
@@ -50,8 +50,13 @@ class Handler extends ExceptionHandler
return;
}
app('sentry')->configureScope(
function (Scope $scope){
$scope->setUser(['id'=> config('sentry.server_name')]);
function (Scope $scope) {
$scope->setUser(
[
'id' => config('sentry.server_name'),
'email' => auth()->user()->email
]
);
}
);
Integration::captureUnhandledException($e);

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class ProcessException extends Exception
{
}

View File

@@ -154,8 +154,8 @@ class Controller extends BaseController
$invitation->delete();
abort(401);
}
} catch (Throwable $th) {
throw $th;
} catch (Throwable $e) {
throw $e;
}
}
@@ -172,8 +172,8 @@ class Controller extends BaseController
}
$invitation->delete();
return redirect()->route('team.index');
} catch (Throwable $th) {
throw $th;
} catch (Throwable $e) {
throw $e;
}
}
}

View File

@@ -6,6 +6,7 @@ use App\Actions\Server\InstallDocker;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use App\Models\Team;
use Illuminate\Support\Collection;
use Livewire\Component;
@@ -70,9 +71,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
public function skipBoarding()
{
currentTeam()->update([
Team::find(currentTeam()->id)->update([
'show_boarding' => false
]);
ray(currentTeam());
refreshSession();
return redirect()->route('dashboard');
}
@@ -133,7 +135,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{
$this->selectedExistingPrivateKey = null;
$this->privateKeyType = $type;
if ($type === 'create' && !isDev()) {
if ($type === 'create') {
$this->createNewPrivateKey();
}
$this->currentState = 'create-private-key';
@@ -191,7 +193,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
$this->getProxyType();
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
}
}

View File

@@ -34,9 +34,9 @@ class CheckLicense extends Component
try {
resolve(CheckResaleLicense::class)();
$this->emit('reloadWindow');
} catch (\Throwable $th) {
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $th->getMessage());
ray($th->getMessage());
} catch (\Throwable $e) {
session()->flash('error', 'Something went wrong. Please contact support. <br>Error: ' . $e->getMessage());
ray($e->getMessage());
return redirect()->to('/settings/license');
}
}

View File

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

View File

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

View File

@@ -36,7 +36,7 @@ class ForcePasswordReset extends Component
send_internal_notification('First login for ' . auth()->user()->email);
}
return redirect()->route('dashboard');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
}
}

View File

@@ -30,7 +30,7 @@ class Help extends Component
try {
$this->rateLimit(1, 60);
$this->validate();
$subscriptionType = auth()->user()?->subscription?->type() ?? 'unknown';
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
$debug = "Route: {$this->path}";
$mail = new MailMessage();
$mail->view(
@@ -41,9 +41,9 @@ class Help extends Component
]
);
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
send_user_an_email($mail, 'hi@coollabs.io');
send_user_an_email($mail, 'hi@coollabs.io', auth()->user()?->email);
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}

View File

@@ -29,7 +29,7 @@ class DiscordSettings extends Component
{
try {
$this->submit();
} catch (\Exception $e) {
} catch (\Throwable $e) {
ray($e->getMessage());
$this->team->discord_enabled = false;
$this->validate();

View File

@@ -63,7 +63,7 @@ class EmailSettings extends Component
]);
$this->team->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}
@@ -82,7 +82,7 @@ class EmailSettings extends Component
$this->team->resend_enabled = false;
$this->team->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}
@@ -92,7 +92,7 @@ class EmailSettings extends Component
try {
$this->team->smtp_enabled = false;
$this->submitResend();
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->team->smtp_enabled = false;
return general_error_handler($e, $this);
}
@@ -102,7 +102,7 @@ class EmailSettings extends Component
try {
$this->team->resend_enabled = false;
$this->submit();
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->team->smtp_enabled = false;
return general_error_handler($e, $this);
}
@@ -131,7 +131,7 @@ class EmailSettings extends Component
]);
$this->team->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->team->smtp_enabled = false;
return general_error_handler($e, $this);
}
@@ -146,7 +146,7 @@ class EmailSettings extends Component
$this->team->save();
refreshSession();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->team->resend_enabled = false;
return general_error_handler($e, $this);
}

View File

@@ -17,6 +17,10 @@ class TelegramSettings extends Component
'team.telegram_notifications_deployments' => 'nullable|boolean',
'team.telegram_notifications_status_changes' => 'nullable|boolean',
'team.telegram_notifications_database_backups' => 'nullable|boolean',
'team.telegram_notifications_test_message_thread_id' => 'nullable|string',
'team.telegram_notifications_deployments_message_thread_id' => 'nullable|string',
'team.telegram_notifications_status_changes_message_thread_id' => 'nullable|string',
'team.telegram_notifications_database_backups_message_thread_id' => 'nullable|string',
];
protected $validationAttributes = [
'team.telegram_token' => 'Token',
@@ -31,7 +35,7 @@ class TelegramSettings extends Component
{
try {
$this->submit();
} catch (\Exception $e) {
} catch (\Throwable $e) {
ray($e->getMessage());
$this->team->telegram_enabled = false;
$this->validate();

View File

@@ -8,7 +8,7 @@ use Livewire\Component;
class Change extends Component
{
public PrivateKey $private_key;
public $public_key;
protected $rules = [
'private_key.name' => 'required|string',
'private_key.description' => 'nullable|string',
@@ -21,6 +21,14 @@ class Change extends Component
'private_key.private_key' => 'private key'
];
public function mount()
{
try {
$this->public_key = $this->private_key->publicKey();
}catch(\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
public function delete()
{
try {
@@ -30,7 +38,7 @@ class Change extends Component
return redirect()->route('security.private-key.index');
}
$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 (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
}
}
@@ -41,7 +49,7 @@ class Change extends Component
$this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
$this->private_key->save();
refresh_server_connection($this->private_key);
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
}
}

View File

@@ -37,8 +37,8 @@ class Create extends Component
if ($this->from === 'server') {
return redirect()->route('server.create');
}
return redirect()->route('private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Exception $e) {
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
}
}

View File

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

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ class BackupEdit extends Component
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->emit('error', $e->getMessage());
}
}
@@ -76,7 +76,7 @@ class BackupEdit extends Component
$this->backup->save();
$this->backup->refresh();
$this->emit('success', 'Backup updated successfully');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->emit('error', $e->getMessage());
}
}

View File

@@ -42,7 +42,7 @@ class CreateScheduledBackup extends Component
'team_id' => currentTeam()->id,
]);
$this->emit('refreshScheduledBackups');
} catch (\Exception $e) {
} catch (\Throwable $e) {
general_error_handler($e, $this);
} finally {
$this->frequency = '';

View File

@@ -64,6 +64,7 @@ class General extends Component
return;
}
if ($this->database->is_public) {
$this->emit('success', 'Starting TCP proxy...');
startPostgresProxy($this->database);
$this->emit('success', 'Database is now publicly accessible.');
} else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,21 +5,89 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
use App\Models\EnvironmentVariable;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
use Str;
class All extends Component
{
public $resource;
public bool $showPreview = false;
public string|null $modalId = null;
public ?string $variables = null;
public ?string $variablesPreview = null;
public string $view = 'normal';
protected $listeners = ['refreshEnvs', 'submit'];
public function mount()
{
$resourceClass = get_class($this->resource);
$resourceWithPreviews = ['App\Models\Application'];
$simpleDockerfile = !is_null(data_get($this->resource, 'dockerfile'));
if (Str::of($resourceClass)->contains($resourceWithPreviews) && !$simpleDockerfile) {
$this->showPreview = true;
}
$this->modalId = new Cuid2(7);
$this->getDevView();
}
public function getDevView()
{
$this->variables = $this->resource->environment_variables->map(function ($item) {
return "$item->key=$item->value";
})->sort()->join('
');
if ($this->showPreview) {
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
return "$item->key=$item->value";
})->sort()->join('
');
}
}
public function switch()
{
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
}
public function saveVariables($isPreview)
{
if ($isPreview) {
$variables = parseEnvFormatToArray($this->variablesPreview);
$existingVariables = $this->resource->environment_variables_preview();
$this->resource->environment_variables_preview()->delete();
} else {
$variables = parseEnvFormatToArray($this->variables);
$existingVariables = $this->resource->environment_variables();
$this->resource->environment_variables()->delete();
}
foreach ($variables as $key => $variable) {
$found = $existingVariables->where('key', $key)->first();
if ($found) {
$found->value = $variable;
$found->save();
continue;
} else {
$environment = new EnvironmentVariable();
$environment->key = $key;
$environment->value = $variable;
$environment->is_build_time = false;
$environment->is_preview = $isPreview ? true : false;
if ($this->resource->type() === 'application') {
$environment->application_id = $this->resource->id;
}
if ($this->resource->type() === 'standalone-postgresql') {
$environment->standalone_postgresql_id = $this->resource->id;
}
$environment->save();
}
}
if ($isPreview) {
$this->emit('success', 'Preview environment variables updated successfully.');
} else {
$this->emit('success', 'Environment variables updated successfully.');
}
$this->refreshEnvs();
}
public function refreshEnvs()
{
$this->resource->refresh();
$this->getDevView();
}
public function submit($data)
@@ -43,9 +111,9 @@ class All extends Component
$environment->standalone_postgresql_id = $this->resource->id;
}
$environment->save();
$this->resource->refresh();
$this->refreshEnvs();
$this->emit('success', 'Environment variable added successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
}
}

View File

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

View File

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

View File

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

View File

@@ -54,13 +54,20 @@ class Form extends Component
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
if ($uptime) {
$this->uptime = $uptime;
$this->emit('success', 'Server is reachable!');
} else {
$this->emit('error', 'Server is not rachable');
return;
}
if ($dockerVersion) {
$this->dockerVersion = $dockerVersion;
$this->emit('proxyStatusUpdated');
$this->emit('success', 'Docker Engine 23+ is installed!');
} else {
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
}
} catch (\Exception $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
} catch (\Throwable $e) {
return general_error_handler($e, that: $this);
}
}
@@ -74,11 +81,9 @@ class Form extends Component
}
$this->server->delete();
return redirect()->route('server.all');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
}
}
public function submit()
{

View File

@@ -58,7 +58,7 @@ class ByIp extends Component
{
$this->validate();
try {
if (!$this->private_key_id) {
if (is_null($this->private_key_id)) {
return $this->emit('error', 'You must select a private key');
}
$server = Server::create([
@@ -78,7 +78,7 @@ class ByIp extends Component
$server->settings->is_part_of_swarm = $this->is_part_of_swarm;
$server->settings->save();
return redirect()->route('server.show', $server->uuid);
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e);
}
}

View File

@@ -14,14 +14,14 @@ class Proxy extends Component
public ?string $selectedProxy = null;
public $proxy_settings = null;
public string|null $redirect_url = null;
public ?string $redirect_url = null;
protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit'];
public function mount()
{
$this->selectedProxy = $this->server->proxy->type;
$this->redirect_url = $this->server->proxy->redirect_url;
$this->selectedProxy = data_get($this->server, 'proxy.type');
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
}
public function proxyStatusUpdated()
@@ -55,7 +55,7 @@ class Proxy extends Component
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
$this->emit('success', 'Proxy configuration saved.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e);
}
}
@@ -64,16 +64,17 @@ class Proxy extends Component
{
try {
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true);
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e);
}
}
public function load_proxy_configuration()
public function loadProxyConfiguration()
{
try {
ray('loadProxyConfiguration');
$this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server);
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e);
}
}

View File

@@ -10,15 +10,20 @@ class Deploy extends Component
{
public Server $server;
public $proxy_settings = null;
protected $listeners = ['proxyStatusUpdated'];
public function start_proxy()
public function proxyStatusUpdated() {
$this->server->refresh();
}
public function startProxy()
{
if (
$this->server->proxy->last_applied_settings &&
$this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings
) {
$this->emit('saveConfiguration', $this->server);
resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings);
}
$activity = resolve(StartProxy::class)($this->server);
$this->emit('newMonitorActivity', $activity->id);
}

View File

@@ -2,7 +2,6 @@
namespace App\Http\Livewire\Server\Proxy;
use App\Jobs\ProxyContainerStatusJob;
use App\Models\Server;
use Livewire\Component;
@@ -10,14 +9,27 @@ class Status extends Component
{
public Server $server;
public function get_status()
protected $listeners = ['proxyStatusUpdated'];
public function proxyStatusUpdated()
{
if (data_get($this->server,'settings.is_usable')) {
dispatch_sync(new ProxyContainerStatusJob(
server: $this->server
));
$this->server->refresh();
$this->emit('proxyStatusUpdated');
$this->server->refresh();
}
public function getProxyStatus()
{
try {
if ($this->server->isFunctional()) {
$container = getContainerStatus(server: $this->server, container_id: 'coolify-proxy');
$this->server->proxy->status = $container;
$this->server->save();
$this->emit('proxyStatusUpdated');
}
} catch (\Throwable $e) {
return general_error_handler(err: $e);
}
}
public function getProxyStatusWithNoti()
{
$this->emit('success', 'Refreshed proxy status.');
$this->getProxyStatus();
}
}

View File

@@ -4,7 +4,6 @@ namespace App\Http\Livewire\Server;
use App\Models\Server;
use Livewire\Component;
use Masmerise\Toaster\Toaster;
class ShowPrivateKey extends Component
{
@@ -12,14 +11,24 @@ class ShowPrivateKey extends Component
public $privateKeys;
public $parameters;
public function setPrivateKey($private_key_id)
public function setPrivateKey($newPrivateKeyId)
{
$this->server->update([
'private_key_id' => $private_key_id
]);
refresh_server_connection($this->server->privateKey);
$this->server->refresh();
$this->checkConnection();
try {
$oldPrivateKeyId = $this->server->private_key_id;
$this->server->update([
'private_key_id' => $newPrivateKeyId
]);
$this->server->refresh();
refresh_server_connection($this->server->privateKey);
$this->checkConnection();
} catch (\Throwable $e) {
$this->server->update([
'private_key_id' => $oldPrivateKeyId
]);
$this->server->refresh();
refresh_server_connection($this->server->privateKey);
return general_error_handler($e, that: $this);
}
}
public function checkConnection()
@@ -27,13 +36,18 @@ class ShowPrivateKey extends Component
try {
['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server);
if ($uptime) {
Toaster::success('Server is reachable with this private key.');
$this->emit('success', 'Server is reachable with this private key.');
} else {
$this->emit('error', 'Server is not reachable with this private key.');
return;
}
if ($dockerVersion) {
Toaster::success('Server is usable for Coolify.');
$this->emit('success', 'Server is usable for Coolify.');
} else {
$this->emit('error', 'Old (lower than 23) or no Docker version detected. Install Docker Engine on the General tab.');
}
} catch (\Exception $e) {
return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this);
} catch (\Throwable $e) {
throw new \Exception($e->getMessage());
}
}

View File

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

View File

@@ -50,7 +50,7 @@ class Email extends Component
]);
$this->settings->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}
@@ -60,19 +60,27 @@ class Email extends Component
$this->validate([
'settings.resend_api_key' => 'required'
]);
$this->settings->smtp_enabled = false;
$this->settings->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
$this->settings->resend_enabled = false;
return general_error_handler($e, $this);
}
}
public function instantSaveResend() {
try {
$this->settings->smtp_enabled = false;
$this->submitResend();
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}
public function instantSave()
{
try {
$this->settings->resend_enabled = false;
$this->submit();
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}
@@ -89,10 +97,9 @@ class Email extends Component
'settings.smtp_password' => 'nullable',
'settings.smtp_timeout' => 'nullable',
]);
$this->settings->resend_enabled = false;
$this->settings->save();
$this->emit('success', 'Settings saved successfully.');
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}

View File

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

View File

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

View File

@@ -30,7 +30,7 @@ class Actions extends Component
$this->emit('success', 'Subscription cancelled successfully. Reloading in 5s.');
$this->emit('reloadWindow', 5000);
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}
@@ -65,7 +65,7 @@ class Actions extends Component
$this->emit('success', 'Subscription resumed successfully. Reloading in 5s.');
$this->emit('reloadWindow', 5000);
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}

View File

@@ -8,8 +8,13 @@ use Stripe\Checkout\Session;
class PricingPlans extends Component
{
public bool $isTrial = false;
public function mount() {
$this->isTrial = !data_get(currentTeam(),'subscription.stripe_trial_already_ended');
}
public function subscribeStripe($type)
{
$team = currentTeam();
Stripe::setApiKey(config('subscription.stripe_api_key'));
switch ($type) {
case 'basic-monthly':
@@ -50,10 +55,23 @@ class PricingPlans extends Component
'automatic_tax' => [
'enabled' => true,
],
'mode' => 'subscription',
'success_url' => route('dashboard', ['success' => true]),
'cancel_url' => route('subscription.index', ['cancelled' => true]),
];
if (!data_get($team,'subscription.stripe_trial_already_ended')) {
$payload['subscription_data'] = [
'trial_period_days' => config('constants.limits.trial_period'),
'trial_settings' => [
'end_behavior' => [
'missing_payment_method' => 'cancel',
]
],
];
$payload['payment_method_collection'] = 'if_required';
}
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
if ($customer) {
$payload['customer'] = $customer;

View File

@@ -23,7 +23,7 @@ class SwitchTeam extends Component
if (!$team_to_switch_to) {
return;
}
session(['currentTeam' => $team_to_switch_to]);
refreshSession($team_to_switch_to);
return redirect(request()->header('Referer'));
}
}

View File

@@ -31,8 +31,8 @@ class Create extends Component
auth()->user()->teams()->attach($team, ['role' => 'admin']);
refreshSession();
return redirect()->route('team.index');
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}
}

View File

@@ -28,8 +28,8 @@ class Form extends Component
try {
$this->team->save();
refreshSession();
} catch (\Throwable $th) {
return general_error_handler($th, $this);
} catch (\Throwable $e) {
return general_error_handler($e, $this);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ use Livewire\Component;
class Index extends Component
{
public string $email;
public int $users = 0;
public int $waitingInLine = 0;
protected $rules = [
@@ -22,6 +23,7 @@ class Index extends Component
public function mount()
{
$this->waitingInLine = Waitlist::whereVerified(true)->count();
$this->users = User::count();
if (isDev()) {
$this->email = 'waitlist@example.com';
}
@@ -50,7 +52,7 @@ class Index extends Component
$this->emit('success', 'Check your email to verify your email address.');
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
} catch (\Exception $e) {
} catch (\Throwable $e) {
return general_error_handler(err: $e, that: $this);
}
}

View File

@@ -15,7 +15,7 @@ class IsBoardingFlow
*/
public function handle(Request $request, Closure $next): Response
{
ray()->showQueries()->color('orange');
// ray()->showQueries()->color('orange');
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
return redirect('boarding');
}

View File

@@ -46,9 +46,9 @@ class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique
$this->application->status = $status;
$this->application->save();
}
} catch (\Exception $th) {
ray($th->getMessage());
throw $th;
} catch (\Throwable $e) {
ray($e->getMessage());
throw $e;
}
}
}

View File

@@ -28,6 +28,8 @@ use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
use Throwable;
use Visus\Cuid2\Cuid2;
use Yosymfony\Toml\Toml;
use Yosymfony\Toml\TomlArray;
class ApplicationDeploymentJob implements ShouldQueue
{
@@ -133,7 +135,7 @@ class ApplicationDeploymentJob implements ShouldQueue
$this->deploy();
}
}
if ($this->application->fqdn) dispatch(new ProxyStartJob($this->server));
if ($this->application->fqdn) dispatch(new ProxyContainerStatusJob($this->server));
$this->next(ApplicationDeploymentStatus::FINISHED->value);
} catch (Exception $e) {
ray($e);
@@ -498,7 +500,7 @@ class ApplicationDeploymentJob implements ShouldQueue
$this->destination->network => [
'external' => true,
'name' => $this->destination->network,
'attachable' => true,
'attachable' => true
]
]
];
@@ -638,6 +640,10 @@ class ApplicationDeploymentJob implements ShouldQueue
private function generate_healthcheck_commands()
{
if ($this->application->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.
return 'exit 0';
}
if (!$this->application->health_check_port) {
$this->application->health_check_port = $this->application->ports_exposes_array[0];
}

View File

@@ -57,7 +57,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue
} else {
$this->create_comment();
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
ray($e);
throw $e;
}

View File

@@ -21,10 +21,10 @@ class CheckResaleLicenseJob implements ShouldQueue
{
try {
resolve(CheckResaleLicense::class)();
} catch (\Throwable $th) {
send_internal_notification('CheckResaleLicenseJob failed with: ' . $th->getMessage());
ray($th);
throw $th;
} catch (\Throwable $e) {
send_internal_notification('CheckResaleLicenseJob failed with: ' . $e->getMessage());
ray($e);
throw $e;
}
}
}

View File

@@ -27,8 +27,8 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique
public function handle(): void
{
try {
$this->cleanup_waitlist();
} catch (\Exception $e) {
// $this->cleanup_waitlist();
} catch (\Throwable $e) {
send_internal_notification('CleanupInstanceStuffsJob failed with error: ' . $e->getMessage());
ray($e->getMessage());
throw $e;

View File

@@ -24,31 +24,27 @@ class DatabaseBackupJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public Team|null $team = null;
public ?Team $team = null;
public Server $server;
public ScheduledDatabaseBackup|null $backup;
public string $database_type;
public ScheduledDatabaseBackup $backup;
public StandalonePostgresql $database;
public string $database_status;
public string|null $container_name = null;
public ScheduledDatabaseBackupExecution|null $backup_log = null;
public ?string $container_name = null;
public ?ScheduledDatabaseBackupExecution $backup_log = null;
public string $backup_status;
public string|null $backup_location = null;
public ?string $backup_location = null;
public string $backup_dir;
public string $backup_file;
public int $size = 0;
public string|null $backup_output = null;
public S3Storage|null $s3 = null;
public ?string $backup_output = null;
public ?S3Storage $s3 = null;
public function __construct($backup)
{
$this->backup = $backup;
$this->team = Team::find($backup->team_id);
$this->database = $this->backup->database;
$this->database_type = $this->database->type();
$this->database = data_get($this->backup, 'database');
$this->server = $this->database->destination->server;
$this->database_status = $this->database->status;
$this->s3 = $this->backup->s3;
}
@@ -65,7 +61,7 @@ class DatabaseBackupJob implements ShouldQueue
public function handle(): void
{
try {
if ($this->database_status !== 'running') {
if (data_get($this->database, 'status') !== 'running') {
ray('database not running');
return;
}
@@ -84,7 +80,7 @@ class DatabaseBackupJob implements ShouldQueue
'filename' => $this->backup_location,
'scheduled_database_backup_id' => $this->backup->id,
]);
if ($this->database_type === 'standalone-postgresql') {
if ($this->database->type() === 'standalone-postgresql') {
$this->backup_standalone_postgresql();
}
$this->calculate_size();
@@ -94,12 +90,11 @@ class DatabaseBackupJob implements ShouldQueue
}
$this->save_backup_logs();
// TODO: Notify user
} catch (\Throwable $th) {
ray($th->getMessage());
send_internal_notification('DatabaseBackupJob failed with: ' . $th->getMessage());
throw $th;
} catch (\Throwable $e) {
ray($e->getMessage());
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
throw $e;
}
}
private function backup_standalone_postgresql(): void
@@ -121,10 +116,10 @@ class DatabaseBackupJob implements ShouldQueue
$this->backup_status = 'success';
$this->team->notify(new BackupSuccess($this->backup, $this->database));
} catch (Throwable $th) {
} catch (Throwable $e) {
$this->backup_status = 'failed';
$this->add_to_backup_output($th->getMessage());
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $th->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());
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
} finally {
$this->backup_log->update([
@@ -178,9 +173,9 @@ class DatabaseBackupJob implements ShouldQueue
instant_remote_process($commands, $this->server);
$this->add_to_backup_output('Uploaded to S3.');
ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir);
} catch (\Throwable $th) {
$this->add_to_backup_output($th->getMessage());
ray($th->getMessage());
} catch (\Throwable $e) {
$this->add_to_backup_output($e->getMessage());
ray($e->getMessage());
} finally {
$command = "docker rm -f backup-of-{$this->backup->uuid}";
instant_remote_process([$command], $this->server);

View File

@@ -47,7 +47,7 @@ class DatabaseContainerStatusJob implements ShouldQueue, ShouldBeUnique
$this->database->status = $status;
$this->database->save();
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
send_internal_notification('DatabaseContainerStatusJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;

View File

@@ -36,11 +36,11 @@ class DockerCleanupJob implements ShouldQueue
return;
}
try {
ray()->showQueries()->color('orange');
// ray()->showQueries()->color('orange');
$servers = Server::all();
foreach ($servers as $server) {
if (
!$server->settings->is_reachable && !$server->settings->is_usable
!$server->isFunctional()
) {
continue;
}
@@ -75,7 +75,7 @@ class DockerCleanupJob implements ShouldQueue
ray('No need to clean up ' . $server->name)->color('orange');
}
}
} catch (\Exception $e) {
} catch (\Throwable $e) {
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
ray($e->getMessage())->color('orange');
throw $e;

View File

@@ -1,46 +0,0 @@
<?php
namespace App\Jobs;
use App\Actions\Proxy\StartProxy;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProxyCheckJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
public function handle()
{
try {
$container_name = 'coolify-proxy';
$servers = Server::all();
foreach ($servers as $server) {
if (
$server->settings->is_reachable === false || $server->settings->is_usable === false
) {
continue;
}
$status = getContainerStatus(server: $server, container_id: $container_name);
if ($status === 'running') {
continue;
}
if (data_get($server, 'proxy.type')) {
resolve(StartProxy::class)($server);
}
}
} catch (\Throwable $th) {
ray($th->getMessage());
send_internal_notification('ProxyCheckJob failed with: ' . $th->getMessage());
throw $th;
}
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Jobs;
use App\Actions\Proxy\StartProxy;
use App\Enums\ProxyStatus;
use App\Enums\ProxyTypes;
use App\Models\Server;
use Illuminate\Bus\Queueable;
@@ -11,7 +13,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique
{
@@ -28,31 +29,51 @@ class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique
public function middleware(): array
{
return [new WithoutOverlapping($this->server->id)];
return [new WithoutOverlapping($this->server->uuid)];
}
public function uniqueId(): int
public function uniqueId(): string
{
return $this->server->id;
ray($this->server->uuid);
return $this->server->uuid;
}
public function handle(): void
{
try {
$container = getContainerStatus(server: $this->server, all_data: true, container_id: 'coolify-proxy', throwError: false);
$status = data_get($container, 'State.Status');
if ($status && data_get($this->server, 'proxy.status') !== $status) {
$this->server->proxy->status = $status;
if ($this->server->proxy->status === 'running') {
$traefik = $container['Config']['Labels']['org.opencontainers.image.title'];
$version = $container['Config']['Labels']['org.opencontainers.image.version'];
if (isset($version) && isset($traefik) && $traefik === 'Traefik' && Str::of($version)->startsWith('v2')) {
$this->server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
}
}
$this->server->save();
$proxyType = data_get($this->server, 'proxy.type');
if ($proxyType === ProxyTypes::NONE->value) {
return;
}
} catch (\Exception $e) {
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();

View File

@@ -1,44 +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\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ProxyStartJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(protected Server $server)
{
}
public function handle()
{
try {
$container_name = 'coolify-proxy';
ray('Starting proxy for server: ' . $this->server->name);
$status = getContainerStatus(server: $this->server, container_id: $container_name);
if ($status === 'running') {
return;
}
if (is_null(data_get($this->server, 'proxy.type'))) {
$this->server->proxy->type = ProxyTypes::TRAEFIK_V2->value;
$this->server->proxy->status = ProxyStatus::EXITED->value;
$this->server->save();
}
resolve(StartProxy::class)($this->server);
} catch (\Throwable $th) {
send_internal_notification('ProxyStartJob failed with: ' . $th->getMessage());
ray($th->getMessage());
throw $th;
}
}
}

View File

@@ -1,46 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Application;
use App\Models\StandalonePostgresql;
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 ResourceStatusJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $applications;
public $postgresqls;
public function __construct()
{
$this->applications = Application::all();
$this->postgresqls = StandalonePostgresql::all();
}
public function handle(): void
{
try {
foreach ($this->applications as $application) {
dispatch(new ApplicationContainerStatusJob(
application: $application,
));
}
foreach ($this->postgresqls as $postgresql) {
dispatch(new DatabaseContainerStatusJob(
database: $postgresql,
));
}
} catch (\Exception $th) {
send_internal_notification('ResourceStatusJob failed with: ' . $th->getMessage());
ray($th);
throw $th;
}
}
}

View File

@@ -25,10 +25,8 @@ class SendConfirmationForWaitlistJob implements ShouldQueue
{
try {
$mail = new MailMessage();
$confirmation_url = base_url() . '/webhooks/waitlist/confirm?email=' . $this->email . '&confirmation_code=' . $this->uuid;
$cancel_url = base_url() . '/webhooks/waitlist/cancel?email=' . $this->email . '&confirmation_code=' . $this->uuid;
$mail->view('emails.waitlist-confirmation',
[
'confirmation_url' => $confirmation_url,
@@ -36,10 +34,10 @@ class SendConfirmationForWaitlistJob implements ShouldQueue
]);
$mail->subject('You are on the waitlist!');
send_user_an_email($mail, $this->email);
} catch (\Throwable $th) {
send_internal_notification("SendConfirmationForWaitlistJob failed for {$mail} with error: " . $th->getMessage());
ray($th->getMessage());
throw $th;
} catch (\Throwable $e) {
send_internal_notification("SendConfirmationForWaitlistJob failed for {$this->email} with error: " . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

View File

@@ -31,6 +31,7 @@ class SendMessageToTelegramJob implements ShouldQueue
public array $buttons,
public string $token,
public string $chatId,
public ?string $topicId = null,
) {
}
@@ -63,7 +64,9 @@ class SendMessageToTelegramJob implements ShouldQueue
'chat_id' => $this->chatId,
'text' => $this->text,
];
ray($payload);
if ($this->topicId) {
$payload['message_thread_id'] = $this->topicId;
}
$response = Http::post($url, $payload);
if ($response->failed()) {
throw new \Exception('Telegram notification failed with ' . $response->status() . ' status code.' . $response->body());

View File

@@ -0,0 +1,79 @@
<?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

@@ -33,10 +33,10 @@ class SubscriptionInvoiceFailedJob implements ShouldQueue
send_user_an_email($mail, $member->email);
}
});
} catch (\Throwable $th) {
send_internal_notification('SubscriptionInvoiceFailedJob failed with: ' . $th->getMessage());
ray($th->getMessage());
throw $th;
} catch (\Throwable $e) {
send_internal_notification('SubscriptionInvoiceFailedJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Jobs;
use App\Models\Team;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SubscriptionTrialEndedJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public Team $team
) {
}
public function handle(): void
{
try {
$session = getStripeCustomerPortalSession($this->team);
$mail = new MailMessage();
$mail->subject('Action required: You trial in Coolify Cloud ended.');
$mail->view('emails.trial-ended', [
'stripeCustomerPortal' => $session->url,
]);
$this->team->members()->each(function ($member) use ($mail) {
if ($member->isAdmin()) {
ray('Sending trial ended email to ' . $member->email);
send_user_an_email($mail, $member->email);
send_internal_notification('Trial reminder email sent to ' . $member->email);
}
});
} catch (\Throwable $e) {
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Jobs;
use App\Models\Team;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SubscriptionTrialEndsSoonJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public Team $team
) {
}
public function handle(): void
{
try {
$session = getStripeCustomerPortalSession($this->team);
$mail = new MailMessage();
$mail->subject('You trial in Coolify Cloud ends soon.');
$mail->view('emails.trial-ends-soon', [
'stripeCustomerPortal' => $session->url,
]);
$this->team->members()->each(function ($member) use ($mail) {
if ($member->isAdmin()) {
ray('Sending trial ending email to ' . $member->email);
send_user_an_email($mail, $member->email);
send_internal_notification('Trial reminder email sent to ' . $member->email);
}
});
} catch (\Throwable $e) {
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: ' . $e->getMessage());
ray($e->getMessage());
throw $e;
}
}
}

View File

@@ -105,7 +105,7 @@ class Application extends BaseModel
public function environment_variables(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false);
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc');
}
public function runtime_environment_variables(): HasMany
@@ -127,7 +127,7 @@ class Application extends BaseModel
public function environment_variables_preview(): HasMany
{
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true);
return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc');
}
public function runtime_environment_variables_preview(): HasMany

View File

@@ -20,13 +20,16 @@ class EnvironmentVariable extends Model
{
static::created(function ($environment_variable) {
if ($environment_variable->application_id && !$environment_variable->is_preview) {
ModelsEnvironmentVariable::create([
'key' => $environment_variable->key,
'value' => $environment_variable->value,
'is_build_time' => $environment_variable->is_build_time,
'application_id' => $environment_variable->application_id,
'is_preview' => true,
]);
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first();
if (!$found) {
ModelsEnvironmentVariable::create([
'key' => $environment_variable->key,
'value' => $environment_variable->value,
'is_build_time' => $environment_variable->is_build_time,
'application_id' => $environment_variable->application_id,
'is_preview' => true,
]);
}
}
});
}
@@ -42,26 +45,22 @@ class EnvironmentVariable extends Model
private function get_environment_variables(string $environment_variable): string|null
{
// $team_id = currentTeam()->id;
if (str_contains(trim($environment_variable), '{{') && str_contains(trim($environment_variable), '}}')) {
$environment_variable = preg_replace('/\s+/', '', $environment_variable);
$environment_variable = str_replace('{{', '', $environment_variable);
$environment_variable = str_replace('}}', '', $environment_variable);
if (str_starts_with($environment_variable, 'global.')) {
$environment_variable = str_replace('global.', '', $environment_variable);
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
return $environment_variable;
}
$environment_variable = trim(decrypt($environment_variable));
if (Str::startsWith($environment_variable, '{{') && Str::endsWith($environment_variable, '}}') && Str::contains($environment_variable, 'global.')) {
$variable = Str::after($environment_variable, 'global.');
$variable = Str::before($variable, '}}');
$variable = Str::of($variable)->trim()->value;
// $environment_variable = GlobalEnvironmentVariable::where('name', $environment_variable)->where('team_id', $team_id)->first()?->value;
ray('global env variable');
return $environment_variable;
}
return decrypt($environment_variable);
return $environment_variable;
}
private function set_environment_variables(string $environment_variable): string|null
{
$environment_variable = trim($environment_variable);
if (!str_contains(trim($environment_variable), '{{') && !str_contains(trim($environment_variable), '}}')) {
return encrypt($environment_variable);
}
return $environment_variable;
return encrypt($environment_variable);
}
protected function key(): Attribute

View File

@@ -2,6 +2,7 @@
namespace App\Models;
use phpseclib3\Crypt\PublicKeyLoader;
class PrivateKey extends BaseModel
{
@@ -19,6 +20,15 @@ class PrivateKey extends BaseModel
return PrivateKey::whereTeamId(currentTeam()->id)->select($selectArray->all());
}
public function publicKey()
{
try {
return PublicKeyLoader::load($this->private_key)->getPublicKey()->toString('OpenSSH',['comment' => '']);
} catch (\Throwable $e) {
return 'Error loading private key';
}
}
public function isEmpty()
{
if ($this->servers()->count() === 0 && $this->applications()->count() === 0 && $this->githubApps()->count() === 0 && $this->gitlabApps()->count() === 0) {

View File

@@ -30,7 +30,6 @@ class Server extends BaseModel
'server_id' => $server->id,
]);
}
});
static::deleting(function ($server) {
$server->destinations()->each(function ($destination) {
@@ -72,7 +71,6 @@ class Server extends BaseModel
$swarmDocker = collect($server->swarmDockers->all());
return $standaloneDocker->concat($swarmDocker);
}
public function settings()
{
return $this->hasOne(ServerSetting::class);
@@ -93,7 +91,8 @@ class Server extends BaseModel
return false;
}
public function databases() {
public function databases()
{
return $this->destinations()->map(function ($standaloneDocker) {
$postgresqls = $standaloneDocker->postgresqls;
return $postgresqls?->concat([]) ?? collect([]);
@@ -137,4 +136,24 @@ class Server extends BaseModel
{
return $this->belongsTo(Team::class);
}
public function isProxyShouldRun()
{
$shouldRun = false;
foreach ($this->applications() as $application) {
if (data_get($application, 'fqdn')) {
$shouldRun = true;
break;
}
}
if ($this->id === 0) {
$settings = InstanceSettings::get();
if (data_get($settings, 'fqdn')) {
$shouldRun = true;
}
}
return $shouldRun;
}
public function isFunctional() {
return $this->settings->is_reachable && $this->settings->is_usable;
}
}

View File

@@ -23,6 +23,6 @@ class StandaloneDocker extends BaseModel
public function attachedTo()
{
return $this->applications->count() > 0 || $this->databases->count() > 0;
return $this->applications?->count() > 0 || $this->databases?->count() > 0;
}
}

View File

@@ -28,6 +28,11 @@ class StandalonePostgresql extends BaseModel
'is_readonly' => true
]);
});
static::deleted(function ($database) {
$database->scheduledBackups()->delete();
$database->persistentStorages()->delete();
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
});
}
public function portsMappings(): Attribute

View File

@@ -33,7 +33,7 @@ class Subscription extends Model
}
if (isStripe()) {
if (!$this->stripe_plan_id) {
return 'unknown';
return 'zero';
}
$subscription = Subscription::where('id', $this->id)->first();
if (!$subscription) {
@@ -54,6 +54,6 @@ class Subscription extends Model
return Str::of($stripePlanId)->after('stripe_price_id_')->before('_')->lower();
}
}
return 'unknown';
return 'zero';
}
}

View File

@@ -28,7 +28,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
{
return [
"token" => data_get($this, 'telegram_token', null),
"chat_id" => data_get($this, 'telegram_chat_id', null)
"chat_id" => data_get($this, 'telegram_chat_id', null),
];
}
@@ -120,4 +120,20 @@ class Team extends Model implements SendsDiscord, SendsEmail
{
return $this->hasMany(S3Storage::class);
}
public function trialEnded() {
foreach ($this->servers as $server) {
$server->settings()->update([
'is_usable' => false,
'is_reachable' => false,
]);
}
}
public function trialEndedButSubscribed() {
foreach ($this->servers as $server) {
$server->settings()->update([
'is_usable' => true,
'is_reachable' => true,
]);
}
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Models;
use App\Notifications\Channels\SendsEmail;
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
use Cache;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@@ -94,7 +95,9 @@ class User extends Authenticatable implements SendsEmail
public function currentTeam()
{
return Team::find(session('currentTeam')->id);
return Cache::remember('team:' . auth()->user()->id, 3600, function() {
return Team::find(session('currentTeam')->id);
});
}
public function otherTeams()

View File

@@ -16,6 +16,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public Application $application;
public string $deployment_uuid;
public ?ApplicationPreview $preview = null;

View File

@@ -16,6 +16,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public Application $application;
public string $deployment_uuid;
public ApplicationPreview|null $preview = null;

View File

@@ -14,6 +14,7 @@ class StatusChanged extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public $application;
public string $application_name;

View File

@@ -10,16 +10,30 @@ class TelegramChannel
{
$data = $notification->toTelegram($notifiable);
$telegramData = $notifiable->routeNotificationForTelegram();
$message = data_get($data, 'message');
$buttons = data_get($data, 'buttons', []);
ray($message, $buttons);
$telegramToken = data_get($telegramData, 'token');
$chatId = data_get($telegramData, 'chat_id');
$topicId = null;
$topicsInstance = get_class($notification);
if (!$telegramToken || !$chatId || !$message) {
throw new \Exception('Telegram token, chat id and message are required');
switch ($topicsInstance) {
case 'App\Notifications\StatusChange':
$topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id');
break;
case 'App\Notifications\Test':
$topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id');
break;
case 'App\Notifications\Deployment':
$topicId = data_get($notifiable, 'telegram_notifications_deployments_message_thread_id');
break;
case 'App\Notifications\DatabaseBackup':
$topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id');
break;
}
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId));
if (!$telegramToken || !$chatId || !$message) {
return;
}
dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId, $topicId));
}
}

View File

@@ -14,6 +14,7 @@ class BackupFailed extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public string $name;
public string $frequency;

View File

@@ -14,6 +14,7 @@ class BackupSuccess extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public string $name;
public string $frequency;

View File

@@ -12,13 +12,24 @@ class GeneralNotification extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public function __construct(public string $message)
{
}
public function via(object $notifiable): array
{
return [TelegramChannel::class, DiscordChannel::class];
$channels = [];
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
if ($isDiscordEnabled) {
$channels[] = DiscordChannel::class;
}
if ($isTelegramEnabled) {
$channels[] = TelegramChannel::class;
}
return $channels;
}
public function toDiscord(): string

View File

@@ -15,6 +15,7 @@ class NotReachable extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public function __construct(public Server $server)
{

View File

@@ -14,6 +14,7 @@ class Test extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public function __construct(public string|null $emails = null)
{
}

View File

@@ -15,6 +15,7 @@ class InvitationLink extends Notification implements ShouldQueue
{
use Queueable;
public $tries = 5;
public function via(): array
{
return [TransactionalEmailChannel::class];

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