mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
155 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2af083b2e5 | ||
|
|
de4d0961da | ||
|
|
c8d48ccbff | ||
|
|
695d3b82b5 | ||
|
|
965625ad01 | ||
|
|
379733938c | ||
|
|
3198999746 | ||
|
|
1f03499fc5 | ||
|
|
a53d888747 | ||
|
|
62905f084f | ||
|
|
ba7ee4fba7 | ||
|
|
6cb3df9350 | ||
|
|
5c1c71c625 | ||
|
|
7fd0cfc85f | ||
|
|
efad3b1284 | ||
|
|
a06de9682c | ||
|
|
0d4ad05c1c | ||
|
|
aef088a9d2 | ||
|
|
e8f3aa681e | ||
|
|
42293fb11a | ||
|
|
65e0eb5205 | ||
|
|
2f1a7f8f40 | ||
|
|
73e9410264 | ||
|
|
c835c02bf2 | ||
|
|
336d44a5cc | ||
|
|
7027931095 | ||
|
|
87b56d538d | ||
|
|
0519ce2001 | ||
|
|
d4d0330f70 | ||
|
|
25ae54cab7 | ||
|
|
a67576b447 | ||
|
|
4d181eef8e | ||
|
|
1835a91467 | ||
|
|
bcc61b0d8b | ||
|
|
f8055e7976 | ||
|
|
2509406d1c | ||
|
|
b3d15f91e4 | ||
|
|
85c36df2a3 | ||
|
|
6ef79f5213 | ||
|
|
b576014d07 | ||
|
|
6950966b06 | ||
|
|
8eacf67725 | ||
|
|
52120e7a38 | ||
|
|
1490828069 | ||
|
|
9bdad6bb67 | ||
|
|
f24063cfea | ||
|
|
1defed27a0 | ||
|
|
34d6a12d95 | ||
|
|
5d3de967f0 | ||
|
|
8b73f9da17 | ||
|
|
820099622e | ||
|
|
366d39a7a3 | ||
|
|
853a14c6b8 | ||
|
|
15ca68f7e1 | ||
|
|
8b9548a463 | ||
|
|
6688120aee | ||
|
|
93e4e723fa | ||
|
|
8b74e50c50 | ||
|
|
129a644781 | ||
|
|
bbfbd4a105 | ||
|
|
9d31d990fc | ||
|
|
c7f15c42fa | ||
|
|
515d401746 | ||
|
|
2a03b452d3 | ||
|
|
7aa8c765f6 | ||
|
|
038f65aae6 | ||
|
|
db24828a5a | ||
|
|
c7693d0ec3 | ||
|
|
5e2afd4b4d | ||
|
|
7a21312daf | ||
|
|
051a1405e7 | ||
|
|
a6669ed876 | ||
|
|
f1b00436aa | ||
|
|
e699103d3e | ||
|
|
ba9cb88ca3 | ||
|
|
365850d922 | ||
|
|
46ed17c99e | ||
|
|
fadff798a7 | ||
|
|
81512bb3b7 | ||
|
|
3dd00dd91a | ||
|
|
fd97c5085b | ||
|
|
fa6a249fb4 | ||
|
|
0aa9b1735b | ||
|
|
6027bee3b8 | ||
|
|
4d72787c83 | ||
|
|
c6740cfea0 | ||
|
|
9c1d585c43 | ||
|
|
863acf988e | ||
|
|
a6b3beafbb | ||
|
|
2ffc3f497b | ||
|
|
f3a279be26 | ||
|
|
9ad6631747 | ||
|
|
0131f5e341 | ||
|
|
b5ab9a8da6 | ||
|
|
57fa2709da | ||
|
|
96c6a198d7 | ||
|
|
d106d4bd4e | ||
|
|
53cd3091f7 | ||
|
|
f1e7b870aa | ||
|
|
99fe076b5a | ||
|
|
65fcaa17d9 | ||
|
|
89c6563e00 | ||
|
|
76b0bef32e | ||
|
|
c20aa0b256 | ||
|
|
b4908cfcb4 | ||
|
|
4fb5b04d27 | ||
|
|
8385bbb0a0 | ||
|
|
cee6b54033 | ||
|
|
0dd591a5ff | ||
|
|
62278126e4 | ||
|
|
0aa85a3701 | ||
|
|
0e1ba64836 | ||
|
|
f7e1ce8656 | ||
|
|
5030c14dc2 | ||
|
|
1333cd1d84 | ||
|
|
112c259d27 | ||
|
|
130d1e1756 | ||
|
|
a7df9fa625 | ||
|
|
9064aedc89 | ||
|
|
fda5d23d32 | ||
|
|
60be51dbe0 | ||
|
|
e9f451339f | ||
|
|
4d8ffd05a9 | ||
|
|
b630105572 | ||
|
|
9fa71f847f | ||
|
|
f70a9c6974 | ||
|
|
a4d173c733 | ||
|
|
2eb7712e09 | ||
|
|
54923b7640 | ||
|
|
bb927505fe | ||
|
|
5e66e314d2 | ||
|
|
6fe791c1f1 | ||
|
|
860c537f81 | ||
|
|
a352e4cbf7 | ||
|
|
5322d446bd | ||
|
|
604ab0afd8 | ||
|
|
3d87a88d3d | ||
|
|
10f9e22a8e | ||
|
|
8edda0cdda | ||
|
|
21047afc02 | ||
|
|
2e9793ffb2 | ||
|
|
fcd100df39 | ||
|
|
dfd564a3a4 | ||
|
|
a43c916009 | ||
|
|
c8332ca9bf | ||
|
|
e98170f921 | ||
|
|
b8f25406cd | ||
|
|
76dcc12b13 | ||
|
|
baa2228c9b | ||
|
|
5275ae8e9c | ||
|
|
c71e1e107e | ||
|
|
8ab72c7e10 | ||
|
|
a8970df91b | ||
|
|
0538c2f478 | ||
|
|
77a0179822 |
@@ -33,7 +33,9 @@ Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and
|
||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
||||
<a href="https://appwrite.io" target="_blank"><img src="./other/logos/appwrite.svg" alt="appwrite logo" width="200"/></a>
|
||||
|
||||
## Github Sponsors ($15+)
|
||||
## Github Sponsors ($40+)
|
||||
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
||||
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
||||
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
|
||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
|
||||
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
||||
|
||||
@@ -39,7 +39,7 @@ class PrepareCoolifyTask
|
||||
|
||||
public function __invoke(): Activity
|
||||
{
|
||||
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish);
|
||||
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data);
|
||||
dispatch($job);
|
||||
$this->activity->refresh();
|
||||
return $this->activity;
|
||||
|
||||
@@ -21,6 +21,8 @@ class RunRemoteProcess
|
||||
|
||||
public $call_event_on_finish = null;
|
||||
|
||||
public $call_event_data = null;
|
||||
|
||||
protected $time_start;
|
||||
|
||||
protected $current_time;
|
||||
@@ -34,7 +36,7 @@ class RunRemoteProcess
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null)
|
||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
|
||||
{
|
||||
|
||||
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
|
||||
@@ -45,6 +47,7 @@ class RunRemoteProcess
|
||||
$this->hide_from_output = $hide_from_output;
|
||||
$this->ignore_errors = $ignore_errors;
|
||||
$this->call_event_on_finish = $call_event_on_finish;
|
||||
$this->call_event_data = $call_event_data;
|
||||
}
|
||||
|
||||
public static function decodeOutput(?Activity $activity = null): string
|
||||
@@ -111,9 +114,15 @@ class RunRemoteProcess
|
||||
}
|
||||
if ($this->call_event_on_finish) {
|
||||
try {
|
||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||
'userId' => $this->activity->causer_id,
|
||||
]));
|
||||
if ($this->call_event_data) {
|
||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||
"data" => $this->call_event_data,
|
||||
]));
|
||||
} else {
|
||||
event(resolve("App\\Events\\$this->call_event_on_finish", [
|
||||
'userId' => $this->activity->causer_id,
|
||||
]));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,12 @@ class CheckConfiguration
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $reset = false)
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxyType = $server->proxyType();
|
||||
if ($proxyType === 'NONE') {
|
||||
return 'OK';
|
||||
}
|
||||
$proxy_path = $server->proxyPath();
|
||||
|
||||
$proxy_configuration = instant_remote_process([
|
||||
"mkdir -p $proxy_path",
|
||||
"cat $proxy_path/docker-compose.yml",
|
||||
|
||||
@@ -10,6 +10,9 @@ class CheckProxy
|
||||
use AsAction;
|
||||
public function handle(Server $server, $fromUI = false)
|
||||
{
|
||||
if ($server->proxyType() === 'NONE') {
|
||||
return false;
|
||||
}
|
||||
if (!$server->isProxyShouldRun()) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
|
||||
|
||||
@@ -15,7 +15,7 @@ class SaveConfiguration
|
||||
if (is_null($proxy_settings)) {
|
||||
$proxy_settings = CheckConfiguration::run($server, true);
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $server->proxyPath();
|
||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||
|
||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Events\ProxyStatusChanged;
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
@@ -15,8 +15,11 @@ class StartProxy
|
||||
{
|
||||
try {
|
||||
$proxyType = $server->proxyType();
|
||||
if (is_null($proxyType) || $proxyType === 'NONE') {
|
||||
return 'OK';
|
||||
}
|
||||
$commands = collect([]);
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $server->proxyPath();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
@@ -34,8 +37,10 @@ class StartProxy
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
} else {
|
||||
$caddfile = "import /dynamic/*.caddy";
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||
"echo '$caddfile' > $proxy_path/dynamic/Caddyfile",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
@@ -49,13 +54,14 @@ class StartProxy
|
||||
}
|
||||
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
$server->save();
|
||||
ProxyStarted::dispatch($server);
|
||||
return 'OK';
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -16,7 +16,7 @@ class StartService
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||
$commands[] = "echo 'Creating Docker network.'";
|
||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
|
||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||
$commands[] = "echo Starting service.";
|
||||
$commands[] = "echo 'Pulling images.'";
|
||||
$commands[] = "docker compose pull";
|
||||
|
||||
@@ -12,9 +12,13 @@ class CleanupDatabase extends Command
|
||||
|
||||
public function handle()
|
||||
{
|
||||
echo "Running database cleanup...\n";
|
||||
if ($this->option('yes')) {
|
||||
echo "Running database cleanup...\n";
|
||||
} else {
|
||||
echo "Running database cleanup in dry-run mode...\n";
|
||||
}
|
||||
$keep_days = 60;
|
||||
|
||||
echo "Keep days: $keep_days\n";
|
||||
// Cleanup failed jobs table
|
||||
$failed_jobs = DB::table('failed_jobs')->where('failed_at', '<', now()->subDays(7));
|
||||
$count = $failed_jobs->count();
|
||||
@@ -32,7 +36,7 @@ class CleanupDatabase extends Command
|
||||
}
|
||||
|
||||
// Cleanup activity_log table
|
||||
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days));
|
||||
$activity_log = DB::table('activity_log')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
|
||||
$count = $activity_log->count();
|
||||
echo "Delete $count entries from activity_log.\n";
|
||||
if ($this->option('yes')) {
|
||||
@@ -40,7 +44,7 @@ class CleanupDatabase extends Command
|
||||
}
|
||||
|
||||
// Cleanup application_deployment_queues table
|
||||
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days));
|
||||
$application_deployment_queues = DB::table('application_deployment_queues')->where('created_at', '<', now()->subDays($keep_days))->orderBy('created_at', 'desc')->skip(10);
|
||||
$count = $application_deployment_queues->count();
|
||||
echo "Delete $count entries from application_deployment_queues.\n";
|
||||
if ($this->option('yes')) {
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\DatabaseBackupStatusJob;
|
||||
use App\Jobs\SendConfirmationForWaitlistJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\ScheduledDatabaseBackupExecution;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\Team;
|
||||
@@ -15,6 +17,7 @@ use App\Notifications\Application\DeploymentSuccess;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use App\Notifications\Database\BackupFailed;
|
||||
use App\Notifications\Database\BackupSuccess;
|
||||
use App\Notifications\Database\DailyBackup;
|
||||
use App\Notifications\Test;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -54,6 +57,8 @@ class Emails extends Command
|
||||
options: [
|
||||
'updates' => 'Send Update Email to all users',
|
||||
'emails-test' => 'Test',
|
||||
'database-backup-statuses-daily' => 'Database - Backup Statuses (Daily)',
|
||||
'application-deployment-success-daily' => 'Application - Deployment Success (Daily)',
|
||||
'application-deployment-success' => 'Application - Deployment Success',
|
||||
'application-deployment-failed' => 'Application - Deployment Failed',
|
||||
'application-status-changed' => 'Application - Status Changed',
|
||||
@@ -67,8 +72,12 @@ class Emails extends Command
|
||||
],
|
||||
);
|
||||
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
||||
if (!in_array($type, $emailsGathered)) {
|
||||
$this->email = text('Email Address to send to');
|
||||
if (isDev()) {
|
||||
$this->email = "test@example.com";
|
||||
} else {
|
||||
if (!in_array($type, $emailsGathered)) {
|
||||
$this->email = text('Email Address to send to:');
|
||||
}
|
||||
}
|
||||
set_transanctional_email_settings();
|
||||
|
||||
@@ -102,7 +111,7 @@ class Emails extends Command
|
||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||
'token' => encrypt($email),
|
||||
]);
|
||||
$this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
|
||||
$this->mail->view('emails.updates', ["unsubscribeUrl" => $unsubscribeUrl]);
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
@@ -111,6 +120,35 @@ class Emails extends Command
|
||||
$this->mail = (new Test())->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'database-backup-statuses-daily':
|
||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
$databases = collect();
|
||||
foreach ($scheduled_backups as $scheduled_backup) {
|
||||
$last_days_backups = $scheduled_backup->get_last_days_backup_status();
|
||||
if ($last_days_backups->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
$failed = $last_days_backups->where('status', 'failed');
|
||||
$database = $scheduled_backup->database;
|
||||
$databases->put($database->name, [
|
||||
'failed_count' => $failed->count(),
|
||||
]);
|
||||
}
|
||||
$this->mail = (new DailyBackup($databases))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
case 'application-deployment-success-daily':
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
$deployments = $application->get_last_days_deployments();
|
||||
ray($deployments);
|
||||
if ($deployments->isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||
$this->sendEmail();
|
||||
}
|
||||
break;
|
||||
case 'application-deployment-success':
|
||||
$application = Application::all()->first();
|
||||
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||
|
||||
@@ -35,7 +35,8 @@ class Init extends Command
|
||||
$this->call('cleanup:queue');
|
||||
$this->call('cleanup:stucked-resources');
|
||||
try {
|
||||
setup_dynamic_configuration();
|
||||
$server = Server::find(0)->first();
|
||||
$server->setupDynamicProxyConfiguration();
|
||||
} catch (\Throwable $e) {
|
||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
@@ -100,6 +100,12 @@ class ServicesGenerate extends Command
|
||||
} else {
|
||||
$tags = null;
|
||||
}
|
||||
$port = collect(preg_grep('/^# port:/', explode("\n", $content)))->values();
|
||||
if ($port->count() > 0) {
|
||||
$port = str($port[0])->after('# port:')->trim()->value();
|
||||
} else {
|
||||
$port = null;
|
||||
}
|
||||
$json = Yaml::parse($content);
|
||||
$yaml = base64_encode(Yaml::dump($json, 10, 2));
|
||||
$payload = [
|
||||
@@ -111,6 +117,9 @@ class ServicesGenerate extends Command
|
||||
'logo' => $logo,
|
||||
'minversion' => $minversion,
|
||||
];
|
||||
if ($port) {
|
||||
$payload['port'] = $port;
|
||||
}
|
||||
if ($env_file) {
|
||||
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
||||
$env_file_base64 = base64_encode($env_file_content);
|
||||
|
||||
@@ -47,6 +47,10 @@ class Kernel extends ConsoleKernel
|
||||
$this->check_resources($schedule);
|
||||
$this->pull_helper_image($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
|
||||
if (!isCloud()) {
|
||||
$schedule->command('cleanup:database --yes')->daily();
|
||||
}
|
||||
}
|
||||
}
|
||||
private function pull_helper_image($schedule)
|
||||
@@ -69,35 +73,42 @@ class Kernel extends ConsoleKernel
|
||||
}
|
||||
foreach ($containerServers as $server) {
|
||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||
// $schedule
|
||||
// ->call(function () use ($server) {
|
||||
// $randomSeconds = rand(1, 40);
|
||||
// $job = new ContainerStatusJob($server);
|
||||
// $job->delay($randomSeconds);
|
||||
// ray('dispatching container status job in ' . $randomSeconds . ' seconds');
|
||||
// dispatch($job);
|
||||
// })->name('container-status-' . $server->id)->everyMinute()->onOneServer();
|
||||
if ($server->isLogDrainEnabled()) {
|
||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
||||
// $schedule
|
||||
// ->call(function () use ($server) {
|
||||
// $randomSeconds = rand(1, 40);
|
||||
// $job = new CheckLogDrainContainerJob($server);
|
||||
// $job->delay($randomSeconds);
|
||||
// dispatch($job);
|
||||
// })->name('log-drain-container-check-' . $server->id)->everyMinute()->onOneServer();
|
||||
}
|
||||
}
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
||||
// $schedule
|
||||
// ->call(function () use ($server) {
|
||||
// $randomSeconds = rand(1, 40);
|
||||
// $job = new ServerStatusJob($server);
|
||||
// $job->delay($randomSeconds);
|
||||
// dispatch($job);
|
||||
// })->name('server-status-job-' . $server->id)->everyMinute()->onOneServer();
|
||||
}
|
||||
// Delayed Jobs
|
||||
// foreach ($containerServers as $server) {
|
||||
// $schedule
|
||||
// ->call(function () use ($server) {
|
||||
// $randomSeconds = rand(1, 40);
|
||||
// $job = new ContainerStatusJob($server);
|
||||
// $job->delay($randomSeconds);
|
||||
// ray('dispatching container status job in ' . $randomSeconds . ' seconds');
|
||||
// dispatch($job);
|
||||
// })->name('container-status-' . $server->id)->everyMinute()->onOneServer();
|
||||
// if ($server->isLogDrainEnabled()) {
|
||||
// $schedule
|
||||
// ->call(function () use ($server) {
|
||||
// $randomSeconds = rand(1, 40);
|
||||
// $job = new CheckLogDrainContainerJob($server);
|
||||
// $job->delay($randomSeconds);
|
||||
// dispatch($job);
|
||||
// })->name('log-drain-container-check-' . $server->id)->everyMinute()->onOneServer();
|
||||
// }
|
||||
// }
|
||||
// foreach ($servers as $server) {
|
||||
// $schedule
|
||||
// ->call(function () use ($server) {
|
||||
// $randomSeconds = rand(1, 40);
|
||||
// $job = new ServerStatusJob($server);
|
||||
// $job->delay($randomSeconds);
|
||||
// dispatch($job);
|
||||
// })->name('server-status-job-' . $server->id)->everyMinute()->onOneServer();
|
||||
// }
|
||||
}
|
||||
private function instance_auto_update($schedule)
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ class CoolifyTaskArgs extends Data
|
||||
public ?string $status = null ,
|
||||
public bool $ignore_errors = false,
|
||||
public $call_event_on_finish = null,
|
||||
public $call_event_data = null
|
||||
) {
|
||||
if(is_null($status)){
|
||||
$this->status = ProcessStatus::QUEUED->value;
|
||||
|
||||
16
app/Events/ProxyStarted.php
Normal file
16
app/Events/ProxyStarted.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ProxyStarted
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
public function __construct(public $data)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,9 @@ class Handler extends ExceptionHandler
|
||||
);
|
||||
}
|
||||
);
|
||||
if (str($e->getMessage())->contains('No space left on device')) {
|
||||
return;
|
||||
}
|
||||
ray('reporting to sentry');
|
||||
Integration::captureUnhandledException($e);
|
||||
});
|
||||
|
||||
@@ -9,13 +9,33 @@ use App\Actions\Database\StartPostgresql;
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class APIDeploy extends Controller
|
||||
class Deploy extends Controller
|
||||
{
|
||||
public function deployments(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$servers = Server::whereTeamId($teamId)->get();
|
||||
$deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $servers->pluck("id"))->get([
|
||||
"id",
|
||||
"application_id",
|
||||
"application_name",
|
||||
"deployment_url",
|
||||
"pull_request_id",
|
||||
"server_name",
|
||||
"server_id",
|
||||
"status"
|
||||
])->sortBy('id')->toArray();
|
||||
return response()->json($deployments_per_server, 200);
|
||||
}
|
||||
public function deploy(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
@@ -27,7 +47,7 @@ class APIDeploy extends Controller
|
||||
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
if ($tags) {
|
||||
return $this->by_tags($tags, $teamId, $force);
|
||||
@@ -44,16 +64,22 @@ class APIDeploy extends Controller
|
||||
if (count($uuids) === 0) {
|
||||
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
$message = collect([]);
|
||||
$deployments = collect();
|
||||
$payload = collect();
|
||||
foreach ($uuids as $uuid) {
|
||||
$resource = getResourceByUuid($uuid, $teamId);
|
||||
if ($resource) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||
if ($deployment_uuid) {
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||
} else {
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($message->count() > 0) {
|
||||
return response()->json(['message' => $message->toArray()], 200);
|
||||
if ($deployments->count() > 0) {
|
||||
$payload->put('deployments', $deployments->toArray());
|
||||
return response()->json($payload->toArray(), 200);
|
||||
}
|
||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||
}
|
||||
@@ -66,10 +92,12 @@ class APIDeploy extends Controller
|
||||
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||
}
|
||||
$message = collect([]);
|
||||
$deployments = collect();
|
||||
$payload = collect();
|
||||
foreach ($tags as $tag) {
|
||||
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||
if (!$found_tag) {
|
||||
$message->push("Tag {$tag} not found.");
|
||||
// $message->push("Tag {$tag} not found.");
|
||||
continue;
|
||||
}
|
||||
$applications = $found_tag->applications()->get();
|
||||
@@ -79,83 +107,78 @@ class APIDeploy extends Controller
|
||||
continue;
|
||||
}
|
||||
foreach ($applications as $resource) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||
if ($deployment_uuid) {
|
||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||
}
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
foreach ($services as $resource) {
|
||||
$return_message = $this->deploy_resource($resource, $force);
|
||||
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
}
|
||||
ray($message);
|
||||
if ($message->count() > 0) {
|
||||
return response()->json(['message' => $message->toArray()], 200);
|
||||
$payload->put('message', $message->toArray());
|
||||
if ($deployments->count() > 0) {
|
||||
$payload->put('details', $deployments->toArray());
|
||||
}
|
||||
return response()->json($payload->toArray(), 200);
|
||||
}
|
||||
|
||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||
return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||
}
|
||||
public function deploy_resource($resource, bool $force = false): Collection
|
||||
public function deploy_resource($resource, bool $force = false): array
|
||||
{
|
||||
$message = collect([]);
|
||||
$message = null;
|
||||
$deployment_uuid = null;
|
||||
if (gettype($resource) !== 'object') {
|
||||
return $message->push("Resource ($resource) not found.");
|
||||
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||
}
|
||||
$type = $resource?->getMorphClass();
|
||||
if ($type === 'App\Models\Application') {
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
queue_application_deployment(
|
||||
application: $resource,
|
||||
deployment_uuid: new Cuid2(7),
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: $force,
|
||||
);
|
||||
$message->push("Application {$resource->name} deployment queued.");
|
||||
$message = "Application {$resource->name} deployment queued.";
|
||||
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartPostgresql::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartRedis::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMongodb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMysql::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||
if (str($resource->status)->startsWith('running')) {
|
||||
$message->push("Database {$resource->name} already running.");
|
||||
}
|
||||
StartMariadb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message->push("Database {$resource->name} started.");
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\Service') {
|
||||
StartService::run($resource);
|
||||
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
||||
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||
}
|
||||
return $message;
|
||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||
}
|
||||
}
|
||||
|
||||
104
app/Http/Controllers/Api/Domains.php
Normal file
104
app/Http/Controllers/Api/Domains.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project as ModelsProject;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Domains extends Controller
|
||||
{
|
||||
public function domains(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$projects = ModelsProject::where('team_id', $teamId)->get();
|
||||
$domains = collect();
|
||||
$applications = $projects->pluck('applications')->flatten();
|
||||
$settings = InstanceSettings::get();
|
||||
if ($applications->count() > 0) {
|
||||
foreach ($applications as $application) {
|
||||
$ip = $application->destination->server->ip;
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (!$settings->public_ipv4 && !$settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$services = $projects->pluck('services')->flatten();
|
||||
if ($services->count() > 0) {
|
||||
foreach ($services as $service) {
|
||||
$service_applications = $service->applications;
|
||||
if ($service_applications->count() > 0) {
|
||||
foreach ($service_applications as $application) {
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (!$settings->public_ipv4 && !$settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
||||
return $domain->pluck('domain')->flatten();
|
||||
})->map(function ($domain, $ip) {
|
||||
return [
|
||||
'ip' => $ip,
|
||||
'domains' => $domain,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json($domains);
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,13 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\Project as ModelsProject;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class APIProject extends Controller
|
||||
class Project extends Controller
|
||||
{
|
||||
public function projects(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||
return response()->json($projects);
|
||||
@@ -21,7 +21,7 @@ class APIProject extends Controller
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||
return response()->json($project);
|
||||
@@ -30,7 +30,7 @@ class APIProject extends Controller
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||
|
||||
38
app/Http/Controllers/Api/Resources.php
Normal file
38
app/Http/Controllers/Api/Resources.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Resources extends Controller
|
||||
{
|
||||
public function resources(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$resources = collect();
|
||||
$resources->push($projects->pluck('applications')->flatten());
|
||||
$resources->push($projects->pluck('services')->flatten());
|
||||
foreach (collect(DATABASE_TYPES) as $db) {
|
||||
$resources->push($projects->pluck(str($db)->plural(2))->flatten());
|
||||
}
|
||||
$resources = $resources->flatten();
|
||||
$resources = $resources->map(function ($resource) {
|
||||
$payload = $resource->toArray();
|
||||
if ($resource->getMorphClass() === 'App\Models\Service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
$payload['type'] = $resource->type();
|
||||
return $payload;
|
||||
});
|
||||
return response()->json($resources);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,49 +6,52 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\Server as ModelsServer;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class APIServer extends Controller
|
||||
class Server extends Controller
|
||||
{
|
||||
public function servers(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
||||
$server['is_reachable'] = $server->settings->is_reachable;
|
||||
$server['is_usable'] = $server->settings->is_usable;
|
||||
return $server;
|
||||
});
|
||||
ray($servers);
|
||||
return response()->json($servers);
|
||||
}
|
||||
public function server_by_uuid(Request $request)
|
||||
{
|
||||
$with_resources = $request->query('resources');
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
return invalid_token();
|
||||
}
|
||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
if (is_null($server)) {
|
||||
return response()->json(['error' => 'Server not found.'], 404);
|
||||
}
|
||||
$server->load(['settings']);
|
||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||
$payload = [
|
||||
'id' => $resource->id,
|
||||
'uuid' => $resource->uuid,
|
||||
'name' => $resource->name,
|
||||
'type' => $resource->type(),
|
||||
'created_at' => $resource->created_at,
|
||||
'updated_at' => $resource->updated_at,
|
||||
];
|
||||
if ($resource->type() === 'service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
return $payload;
|
||||
});
|
||||
if ($with_resources) {
|
||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||
$payload = [
|
||||
'id' => $resource->id,
|
||||
'uuid' => $resource->uuid,
|
||||
'name' => $resource->name,
|
||||
'type' => $resource->type(),
|
||||
'created_at' => $resource->created_at,
|
||||
'updated_at' => $resource->updated_at,
|
||||
];
|
||||
if ($resource->type() === 'service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
return $payload;
|
||||
});
|
||||
} else {
|
||||
$server->load(['settings']);
|
||||
}
|
||||
return response()->json($server);
|
||||
}
|
||||
}
|
||||
|
||||
65
app/Http/Controllers/Api/Team.php
Normal file
65
app/Http/Controllers/Api/Team.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Team extends Controller
|
||||
{
|
||||
public function teams(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$teams = auth()->user()->teams;
|
||||
return response()->json($teams);
|
||||
}
|
||||
public function team_by_id(Request $request)
|
||||
{
|
||||
$id = $request->id;
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$teams = auth()->user()->teams;
|
||||
$team = $teams->where('id', $id)->first();
|
||||
if (is_null($team)) {
|
||||
return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api/team-by-id"], 404);
|
||||
}
|
||||
return response()->json($team);
|
||||
}
|
||||
public function members_by_id(Request $request)
|
||||
{
|
||||
$id = $request->id;
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$teams = auth()->user()->teams;
|
||||
$team = $teams->where('id', $id)->first();
|
||||
if (is_null($team)) {
|
||||
return response()->json(['error' => 'Team not found.', "docs" => "https://coolify.io/docs/api/team-by-id-members"], 404);
|
||||
}
|
||||
return response()->json($team->members);
|
||||
}
|
||||
public function current_team(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$team = auth()->user()->currentTeam();
|
||||
return response()->json($team);
|
||||
}
|
||||
public function current_team_members(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$team = auth()->user()->currentTeam();
|
||||
return response()->json($team->members);
|
||||
}
|
||||
}
|
||||
186
app/Http/Controllers/Webhook/Bitbucket.php
Normal file
186
app/Http/Controllers/Webhook/Bitbucket.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Livewire\Project\Service\Storage;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Bitbucket extends Controller
|
||||
{
|
||||
public function manual(Request $request)
|
||||
{
|
||||
try {
|
||||
if (app()->isDownForMaintenance()) {
|
||||
ray('Maintenance mode is on');
|
||||
$epoch = now()->valueOf();
|
||||
$data = [
|
||||
'attributes' => $request->attributes->all(),
|
||||
'request' => $request->request->all(),
|
||||
'query' => $request->query->all(),
|
||||
'server' => $request->server->all(),
|
||||
'files' => $request->files->all(),
|
||||
'cookies' => $request->cookies->all(),
|
||||
'headers' => $request->headers->all(),
|
||||
'content' => $request->getContent(),
|
||||
];
|
||||
$json = json_encode($data);
|
||||
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Bitbicket::manual_bitbucket", $json);
|
||||
return;
|
||||
}
|
||||
$return_payloads = collect([]);
|
||||
$payload = $request->collect();
|
||||
$headers = $request->headers->all();
|
||||
$x_bitbucket_token = data_get($headers, 'x-hub-signature.0', "");
|
||||
$x_bitbucket_event = data_get($headers, 'x-event-key.0', "");
|
||||
$handled_events = collect(['repo:push', 'pullrequest:created', 'pullrequest:rejected', 'pullrequest:fulfilled']);
|
||||
if (!$handled_events->contains($x_bitbucket_event)) {
|
||||
return response([
|
||||
'status' => 'failed',
|
||||
'message' => 'Nothing to do. Event not handled.',
|
||||
]);
|
||||
}
|
||||
if ($x_bitbucket_event === 'repo:push') {
|
||||
$branch = data_get($payload, 'push.changes.0.new.name');
|
||||
$full_name = data_get($payload, 'repository.full_name');
|
||||
|
||||
if (!$branch) {
|
||||
return response([
|
||||
'status' => 'failed',
|
||||
'message' => 'Nothing to do. No branch found in the request.',
|
||||
]);
|
||||
}
|
||||
ray('Manual webhook bitbucket push event with branch: ' . $branch);
|
||||
}
|
||||
if ($x_bitbucket_event === 'pullrequest:created' || $x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
|
||||
$branch = data_get($payload, 'pullrequest.destination.branch.name');
|
||||
$base_branch = data_get($payload, 'pullrequest.source.branch.name');
|
||||
$full_name = data_get($payload, 'repository.full_name');
|
||||
$pull_request_id = data_get($payload, 'pullrequest.id');
|
||||
$pull_request_html_url = data_get($payload, 'pullrequest.links.html.href');
|
||||
$commit = data_get($payload, 'pullrequest.source.commit.hash');
|
||||
}
|
||||
$applications = Application::where('git_repository', 'like', "%$full_name%");
|
||||
$applications = $applications->where('git_branch', $branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response([
|
||||
'status' => 'failed',
|
||||
'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.",
|
||||
]);
|
||||
}
|
||||
foreach ($applications as $application) {
|
||||
$webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket');
|
||||
$payload = $request->getContent();
|
||||
|
||||
list($algo, $hash) = explode('=', $x_bitbucket_token, 2);
|
||||
$payloadHash = hash_hmac($algo, $payload, $webhook_secret);
|
||||
if (!hash_equals($hash, $payloadHash) && !isDev()) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Invalid token.',
|
||||
]);
|
||||
ray('Invalid signature');
|
||||
continue;
|
||||
}
|
||||
$isFunctional = $application->destination->server->isFunctional();
|
||||
if (!$isFunctional) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Server is not functional.',
|
||||
]);
|
||||
ray('Server is not functional: ' . $application->destination->server->name);
|
||||
continue;
|
||||
}
|
||||
if ($x_bitbucket_event === 'repo:push') {
|
||||
if ($application->isDeployable()) {
|
||||
ray('Deploying ' . $application->name . ' with branch ' . $branch);
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
is_webhook: true
|
||||
);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment queued.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Auto deployment disabled.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
if ($x_bitbucket_event === 'pullrequest:created') {
|
||||
if ($application->isPRDeployable()) {
|
||||
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (!$found) {
|
||||
ApplicationPreview::create([
|
||||
'git_type' => 'bitbucket',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
]);
|
||||
}
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
pull_request_id: $pull_request_id,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
commit: $commit,
|
||||
is_webhook: true,
|
||||
git_type: 'bitbucket'
|
||||
);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment queued.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Preview deployments disabled.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
if ($x_bitbucket_event === 'pullrequest:rejected' || $x_bitbucket_event === 'pullrequest:fulfilled') {
|
||||
ray('Pull request rejected');
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
$found->delete();
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment closed.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'No preview deployment found.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
ray($return_payloads);
|
||||
return response($return_payloads);
|
||||
} catch (Exception $e) {
|
||||
ray($e);
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
459
app/Http/Controllers/Webhook/Github.php
Normal file
459
app/Http/Controllers/Webhook/Github.php
Normal file
@@ -0,0 +1,459 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Enums\ProcessStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\ApplicationPullRequestUpdateJob;
|
||||
use App\Jobs\GithubAppPermissionJob;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\PrivateKey;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Github extends Controller
|
||||
{
|
||||
public function manual(Request $request)
|
||||
{
|
||||
try {
|
||||
ray($request);
|
||||
$return_payloads = collect([]);
|
||||
$x_github_delivery = request()->header('X-GitHub-Delivery');
|
||||
if (app()->isDownForMaintenance()) {
|
||||
ray('Maintenance mode is on');
|
||||
$epoch = now()->valueOf();
|
||||
$files = Storage::disk('webhooks-during-maintenance')->files();
|
||||
$github_delivery_found = collect($files)->filter(function ($file) use ($x_github_delivery) {
|
||||
return Str::contains($file, $x_github_delivery);
|
||||
})->first();
|
||||
if ($github_delivery_found) {
|
||||
ray('Webhook already found');
|
||||
return;
|
||||
}
|
||||
$data = [
|
||||
'attributes' => $request->attributes->all(),
|
||||
'request' => $request->request->all(),
|
||||
'query' => $request->query->all(),
|
||||
'server' => $request->server->all(),
|
||||
'files' => $request->files->all(),
|
||||
'cookies' => $request->cookies->all(),
|
||||
'headers' => $request->headers->all(),
|
||||
'content' => $request->getContent(),
|
||||
];
|
||||
$json = json_encode($data);
|
||||
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::manual_{$x_github_delivery}", $json);
|
||||
return;
|
||||
}
|
||||
$x_github_event = Str::lower($request->header('X-GitHub-Event'));
|
||||
$x_hub_signature_256 = Str::after($request->header('X-Hub-Signature-256'), 'sha256=');
|
||||
$content_type = $request->header('Content-Type');
|
||||
$payload = $request->collect();
|
||||
if ($x_github_event === 'ping') {
|
||||
// Just pong
|
||||
return response('pong');
|
||||
}
|
||||
|
||||
if ($content_type !== 'application/json') {
|
||||
$payload = json_decode(data_get($payload, 'payload'), true);
|
||||
}
|
||||
if ($x_github_event === 'push') {
|
||||
$branch = data_get($payload, 'ref');
|
||||
$full_name = data_get($payload, 'repository.full_name');
|
||||
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
|
||||
$branch = Str::after($branch, 'refs/heads/');
|
||||
}
|
||||
ray('Manual Webhook GitHub Push Event with branch: ' . $branch);
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
$action = data_get($payload, 'action');
|
||||
$full_name = data_get($payload, 'repository.full_name');
|
||||
$pull_request_id = data_get($payload, 'number');
|
||||
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
|
||||
$branch = data_get($payload, 'pull_request.head.ref');
|
||||
$base_branch = data_get($payload, 'pull_request.base.ref');
|
||||
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
|
||||
}
|
||||
if (!$branch) {
|
||||
return response('Nothing to do. No branch found in the request.');
|
||||
}
|
||||
$applications = Application::where('git_repository', 'like', "%$full_name%");
|
||||
if ($x_github_event === 'push') {
|
||||
$applications = $applications->where('git_branch', $branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.");
|
||||
}
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
$applications = $applications->where('git_branch', $base_branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||
}
|
||||
}
|
||||
foreach ($applications as $application) {
|
||||
$webhook_secret = data_get($application, 'manual_webhook_secret_github');
|
||||
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
|
||||
if (!hash_equals($x_hub_signature_256, $hmac) && !isDev()) {
|
||||
ray('Invalid signature');
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Invalid token.',
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
$isFunctional = $application->destination->server->isFunctional();
|
||||
if (!$isFunctional) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Server is not functional.',
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
if ($x_github_event === 'push') {
|
||||
if ($application->isDeployable()) {
|
||||
ray('Deploying ' . $application->name . ' with branch ' . $branch);
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
is_webhook: true,
|
||||
);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Deployment queued.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Deployments disabled.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||
if ($application->isPRDeployable()) {
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (!$found) {
|
||||
ApplicationPreview::create([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
]);
|
||||
}
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
pull_request_id: $pull_request_id,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
is_webhook: true,
|
||||
git_type: 'github'
|
||||
);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment queued.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Preview deployments disabled.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
if ($action === 'closed') {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
$found->delete();
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
// ray('Stopping container: ' . $container_name);
|
||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment closed.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'No preview deployment found.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ray($return_payloads);
|
||||
return response($return_payloads);
|
||||
} catch (Exception $e) {
|
||||
ray($e->getMessage());
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
public function normal(Request $request)
|
||||
{
|
||||
try {
|
||||
$return_payloads = collect([]);
|
||||
$id = null;
|
||||
$x_github_delivery = $request->header('X-GitHub-Delivery');
|
||||
if (app()->isDownForMaintenance()) {
|
||||
ray('Maintenance mode is on');
|
||||
$epoch = now()->valueOf();
|
||||
$files = Storage::disk('webhooks-during-maintenance')->files();
|
||||
$github_delivery_found = collect($files)->filter(function ($file) use ($x_github_delivery) {
|
||||
return Str::contains($file, $x_github_delivery);
|
||||
})->first();
|
||||
if ($github_delivery_found) {
|
||||
ray('Webhook already found');
|
||||
return;
|
||||
}
|
||||
$data = [
|
||||
'attributes' => $request->attributes->all(),
|
||||
'request' => $request->request->all(),
|
||||
'query' => $request->query->all(),
|
||||
'server' => $request->server->all(),
|
||||
'files' => $request->files->all(),
|
||||
'cookies' => $request->cookies->all(),
|
||||
'headers' => $request->headers->all(),
|
||||
'content' => $request->getContent(),
|
||||
];
|
||||
$json = json_encode($data);
|
||||
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::normal_{$x_github_delivery}", $json);
|
||||
return;
|
||||
}
|
||||
$x_github_event = Str::lower($request->header('X-GitHub-Event'));
|
||||
$x_github_hook_installation_target_id = $request->header('X-GitHub-Hook-Installation-Target-Id');
|
||||
$x_hub_signature_256 = Str::after($request->header('X-Hub-Signature-256'), 'sha256=');
|
||||
$payload = $request->collect();
|
||||
if ($x_github_event === 'ping') {
|
||||
// Just pong
|
||||
return response('pong');
|
||||
}
|
||||
$github_app = GithubApp::where('app_id', $x_github_hook_installation_target_id)->first();
|
||||
if (is_null($github_app)) {
|
||||
return response('Nothing to do. No GitHub App found.');
|
||||
}
|
||||
$webhook_secret = data_get($github_app, 'webhook_secret');
|
||||
$hmac = hash_hmac('sha256', $request->getContent(), $webhook_secret);
|
||||
if (config('app.env') !== 'local') {
|
||||
if (!hash_equals($x_hub_signature_256, $hmac)) {
|
||||
return response('Invalid signature.');
|
||||
}
|
||||
}
|
||||
if ($x_github_event === 'installation' || $x_github_event === 'installation_repositories') {
|
||||
// Installation handled by setup redirect url. Repositories queried on-demand.
|
||||
$action = data_get($payload, 'action');
|
||||
if ($action === 'new_permissions_accepted') {
|
||||
GithubAppPermissionJob::dispatch($github_app);
|
||||
}
|
||||
return response('cool');
|
||||
}
|
||||
if ($x_github_event === 'push') {
|
||||
$id = data_get($payload, 'repository.id');
|
||||
$branch = data_get($payload, 'ref');
|
||||
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
|
||||
$branch = Str::after($branch, 'refs/heads/');
|
||||
}
|
||||
ray('Webhook GitHub Push Event: ' . $id . ' with branch: ' . $branch);
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
$action = data_get($payload, 'action');
|
||||
$id = data_get($payload, 'repository.id');
|
||||
$pull_request_id = data_get($payload, 'number');
|
||||
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
|
||||
$branch = data_get($payload, 'pull_request.head.ref');
|
||||
$base_branch = data_get($payload, 'pull_request.base.ref');
|
||||
ray('Webhook GitHub Pull Request Event: ' . $id . ' with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
|
||||
}
|
||||
if (!$id || !$branch) {
|
||||
return response('Nothing to do. No id or branch found.');
|
||||
}
|
||||
$applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false);
|
||||
if ($x_github_event === 'push') {
|
||||
$applications = $applications->where('git_branch', $branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$branch'.");
|
||||
}
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
$applications = $applications->where('git_branch', $base_branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($applications as $application) {
|
||||
$isFunctional = $application->destination->server->isFunctional();
|
||||
if (!$isFunctional) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Server is not functional.',
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
if ($x_github_event === 'push') {
|
||||
if ($application->isDeployable()) {
|
||||
ray('Deploying ' . $application->name . ' with branch ' . $branch);
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
is_webhook: true
|
||||
);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Deployment queued.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Deployments disabled.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||
if ($application->isPRDeployable()) {
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (!$found) {
|
||||
ApplicationPreview::create([
|
||||
'git_type' => 'github',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
]);
|
||||
}
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
pull_request_id: $pull_request_id,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
is_webhook: true,
|
||||
git_type: 'github'
|
||||
);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment queued.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Preview deployments disabled.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
if ($action === 'closed' || $action === 'close') {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
||||
$found->delete();
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
// ray('Stopping container: ' . $container_name);
|
||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview deployment closed.',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'No preview deployment found.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ray($return_payloads);
|
||||
return response($return_payloads);
|
||||
} catch (Exception $e) {
|
||||
ray($e->getMessage());
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
public function redirect(Request $request)
|
||||
{
|
||||
try {
|
||||
$code = $request->get('code');
|
||||
$state = $request->get('state');
|
||||
$github_app = GithubApp::where('uuid', $state)->firstOrFail();
|
||||
$api_url = data_get($github_app, 'api_url');
|
||||
$data = Http::withBody(null)->accept('application/vnd.github+json')->post("$api_url/app-manifests/$code/conversions")->throw()->json();
|
||||
$id = data_get($data, 'id');
|
||||
$slug = data_get($data, 'slug');
|
||||
$client_id = data_get($data, 'client_id');
|
||||
$client_secret = data_get($data, 'client_secret');
|
||||
$private_key = data_get($data, 'pem');
|
||||
$webhook_secret = data_get($data, 'webhook_secret');
|
||||
$private_key = PrivateKey::create([
|
||||
'name' => $slug,
|
||||
'private_key' => $private_key,
|
||||
'team_id' => $github_app->team_id,
|
||||
'is_git_related' => true,
|
||||
]);
|
||||
$github_app->name = $slug;
|
||||
$github_app->app_id = $id;
|
||||
$github_app->client_id = $client_id;
|
||||
$github_app->client_secret = $client_secret;
|
||||
$github_app->webhook_secret = $webhook_secret;
|
||||
$github_app->private_key_id = $private_key->id;
|
||||
$github_app->save();
|
||||
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
|
||||
} catch (Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
public function install(Request $request)
|
||||
{
|
||||
try {
|
||||
$installation_id = $request->get('installation_id');
|
||||
if (app()->isDownForMaintenance()) {
|
||||
ray('Maintenance mode is on');
|
||||
$epoch = now()->valueOf();
|
||||
$data = [
|
||||
'attributes' => $request->attributes->all(),
|
||||
'request' => $request->request->all(),
|
||||
'query' => $request->query->all(),
|
||||
'server' => $request->server->all(),
|
||||
'files' => $request->files->all(),
|
||||
'cookies' => $request->cookies->all(),
|
||||
'headers' => $request->headers->all(),
|
||||
'content' => $request->getContent(),
|
||||
];
|
||||
$json = json_encode($data);
|
||||
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::install_{$installation_id}", $json);
|
||||
return;
|
||||
}
|
||||
$source = $request->get('source');
|
||||
$setup_action = $request->get('setup_action');
|
||||
$github_app = GithubApp::where('uuid', $source)->firstOrFail();
|
||||
if ($setup_action === 'install') {
|
||||
$github_app->installation_id = $installation_id;
|
||||
$github_app->save();
|
||||
}
|
||||
return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]);
|
||||
} catch (Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
202
app/Http/Controllers/Webhook/Gitlab.php
Normal file
202
app/Http/Controllers/Webhook/Gitlab.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Gitlab extends Controller
|
||||
{
|
||||
public function manual(Request $request)
|
||||
{
|
||||
try {
|
||||
if (app()->isDownForMaintenance()) {
|
||||
ray('Maintenance mode is on');
|
||||
$epoch = now()->valueOf();
|
||||
$data = [
|
||||
'attributes' => $request->attributes->all(),
|
||||
'request' => $request->request->all(),
|
||||
'query' => $request->query->all(),
|
||||
'server' => $request->server->all(),
|
||||
'files' => $request->files->all(),
|
||||
'cookies' => $request->cookies->all(),
|
||||
'headers' => $request->headers->all(),
|
||||
'content' => $request->getContent(),
|
||||
];
|
||||
$json = json_encode($data);
|
||||
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitlab::manual_gitlab", $json);
|
||||
return;
|
||||
}
|
||||
$return_payloads = collect([]);
|
||||
$payload = $request->collect();
|
||||
$headers = $request->headers->all();
|
||||
$x_gitlab_token = data_get($headers, 'x-gitlab-token.0');
|
||||
$x_gitlab_event = data_get($payload, 'object_kind');
|
||||
if ($x_gitlab_event === 'push') {
|
||||
$branch = data_get($payload, 'ref');
|
||||
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
|
||||
$branch = Str::after($branch, 'refs/heads/');
|
||||
}
|
||||
if (!$branch) {
|
||||
$return_payloads->push([
|
||||
'status' => 'failed',
|
||||
'message' => 'Nothing to do. No branch found in the request.',
|
||||
]);
|
||||
return response($return_payloads);
|
||||
}
|
||||
ray('Manual Webhook GitLab Push Event with branch: ' . $branch);
|
||||
}
|
||||
if ($x_gitlab_event === 'merge_request') {
|
||||
$action = data_get($payload, 'object_attributes.action');
|
||||
$branch = data_get($payload, 'object_attributes.source_branch');
|
||||
$base_branch = data_get($payload, 'object_attributes.target_branch');
|
||||
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||
$pull_request_id = data_get($payload, 'object_attributes.iid');
|
||||
$pull_request_html_url = data_get($payload, 'object_attributes.url');
|
||||
if (!$branch) {
|
||||
$return_payloads->push([
|
||||
'status' => 'failed',
|
||||
'message' => 'Nothing to do. No branch found in the request.',
|
||||
]);
|
||||
return response($return_payloads);
|
||||
}
|
||||
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
|
||||
}
|
||||
$applications = Application::where('git_repository', 'like', "%$full_name%");
|
||||
if ($x_gitlab_event === 'push') {
|
||||
$applications = $applications->where('git_branch', $branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
$return_payloads->push([
|
||||
'status' => 'failed',
|
||||
'message' => "Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.",
|
||||
]);
|
||||
return response($return_payloads);
|
||||
}
|
||||
}
|
||||
if ($x_gitlab_event === 'merge_request') {
|
||||
$applications = $applications->where('git_branch', $base_branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
$return_payloads->push([
|
||||
'status' => 'failed',
|
||||
'message' => "Nothing to do. No applications found with branch '$base_branch'.",
|
||||
]);
|
||||
return response($return_payloads);
|
||||
}
|
||||
}
|
||||
foreach ($applications as $application) {
|
||||
$webhook_secret = data_get($application, 'manual_webhook_secret_gitlab');
|
||||
if ($webhook_secret !== $x_gitlab_token) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Invalid token.',
|
||||
]);
|
||||
ray('Invalid signature');
|
||||
continue;
|
||||
}
|
||||
$isFunctional = $application->destination->server->isFunctional();
|
||||
if (!$isFunctional) {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Server is not functional',
|
||||
]);
|
||||
ray('Server is not functional: ' . $application->destination->server->name);
|
||||
continue;
|
||||
}
|
||||
if ($x_gitlab_event === 'push') {
|
||||
if ($application->isDeployable()) {
|
||||
ray('Deploying ' . $application->name . ' with branch ' . $branch);
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
is_webhook: true
|
||||
);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Deployments disabled',
|
||||
]);
|
||||
ray('Deployments disabled for ' . $application->name);
|
||||
}
|
||||
}
|
||||
if ($x_gitlab_event === 'merge_request') {
|
||||
if ($action === 'open' || $action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
|
||||
if ($application->isPRDeployable()) {
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (!$found) {
|
||||
ApplicationPreview::create([
|
||||
'git_type' => 'gitlab',
|
||||
'application_id' => $application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
]);
|
||||
}
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
pull_request_id: $pull_request_id,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: false,
|
||||
is_webhook: true,
|
||||
git_type: 'gitlab'
|
||||
);
|
||||
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview Deployment queued',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'Preview deployments disabled',
|
||||
]);
|
||||
ray('Preview deployments disabled for ' . $application->name);
|
||||
}
|
||||
} else if ($action === 'closed' || $action === 'close') {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
$found->delete();
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
// ray('Stopping container: ' . $container_name);
|
||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'success',
|
||||
'message' => 'Preview Deployment closed',
|
||||
]);
|
||||
return response($return_payloads);
|
||||
}
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'No Preview Deployment found',
|
||||
]);
|
||||
} else {
|
||||
$return_payloads->push([
|
||||
'application' => $application->name,
|
||||
'status' => 'failed',
|
||||
'message' => 'No action found. Contact us for debugging.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return response($return_payloads);
|
||||
} catch (Exception $e) {
|
||||
ray($e->getMessage());
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
258
app/Http/Controllers/Webhook/Stripe.php
Normal file
258
app/Http/Controllers/Webhook/Stripe.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\ServerLimitCheckJob;
|
||||
use App\Jobs\SubscriptionInvoiceFailedJob;
|
||||
use App\Jobs\SubscriptionTrialEndedJob;
|
||||
use App\Jobs\SubscriptionTrialEndsSoonJob;
|
||||
use App\Models\Subscription;
|
||||
use App\Models\Team;
|
||||
use App\Models\Webhook;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Stripe extends Controller
|
||||
{
|
||||
public function events(Request $request)
|
||||
{
|
||||
try {
|
||||
if (app()->isDownForMaintenance()) {
|
||||
ray('Maintenance mode is on');
|
||||
$epoch = now()->valueOf();
|
||||
$data = [
|
||||
'attributes' => $request->attributes->all(),
|
||||
'request' => $request->request->all(),
|
||||
'query' => $request->query->all(),
|
||||
'server' => $request->server->all(),
|
||||
'files' => $request->files->all(),
|
||||
'cookies' => $request->cookies->all(),
|
||||
'headers' => $request->headers->all(),
|
||||
'content' => $request->getContent(),
|
||||
];
|
||||
$json = json_encode($data);
|
||||
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json);
|
||||
return;
|
||||
}
|
||||
$webhookSecret = config('subscription.stripe_webhook_secret');
|
||||
$signature = $request->header('Stripe-Signature');
|
||||
$excludedPlans = config('subscription.stripe_excluded_plans');
|
||||
$event = \Stripe\Webhook::constructEvent(
|
||||
$request->getContent(),
|
||||
$signature,
|
||||
$webhookSecret
|
||||
);
|
||||
$webhook = Webhook::create([
|
||||
'type' => 'stripe',
|
||||
'payload' => $request->getContent()
|
||||
]);
|
||||
$type = data_get($event, 'type');
|
||||
$data = data_get($event, 'data.object');
|
||||
switch ($type) {
|
||||
case 'checkout.session.completed':
|
||||
$clientReferenceId = data_get($data, 'client_reference_id');
|
||||
if (is_null($clientReferenceId)) {
|
||||
send_internal_notification('Checkout session completed without client reference id.');
|
||||
break;
|
||||
}
|
||||
$userId = Str::before($clientReferenceId, ':');
|
||||
$teamId = Str::after($clientReferenceId, ':');
|
||||
$subscriptionId = data_get($data, 'subscription');
|
||||
$customerId = data_get($data, 'customer');
|
||||
$team = Team::find($teamId);
|
||||
$found = $team->members->where('id', $userId)->first();
|
||||
if (!$found->isAdmin()) {
|
||||
send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
|
||||
throw new Exception("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.");
|
||||
}
|
||||
$subscription = Subscription::where('team_id', $teamId)->first();
|
||||
if ($subscription) {
|
||||
send_internal_notification('Old subscription activated for team: ' . $teamId);
|
||||
$subscription->update([
|
||||
'stripe_subscription_id' => $subscriptionId,
|
||||
'stripe_customer_id' => $customerId,
|
||||
'stripe_invoice_paid' => true,
|
||||
]);
|
||||
} else {
|
||||
send_internal_notification('New subscription for team: ' . $teamId);
|
||||
Subscription::create([
|
||||
'team_id' => $teamId,
|
||||
'stripe_subscription_id' => $subscriptionId,
|
||||
'stripe_customer_id' => $customerId,
|
||||
'stripe_invoice_paid' => true,
|
||||
]);
|
||||
}
|
||||
break;
|
||||
case 'invoice.paid':
|
||||
$customerId = data_get($data, 'customer');
|
||||
$planId = data_get($data, 'lines.data.0.plan.id');
|
||||
if (Str::contains($excludedPlans, $planId)) {
|
||||
send_internal_notification('Subscription excluded.');
|
||||
break;
|
||||
}
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
if (!$subscription) {
|
||||
Sleep::for(5)->seconds();
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
}
|
||||
$subscription->update([
|
||||
'stripe_invoice_paid' => true,
|
||||
]);
|
||||
break;
|
||||
case 'invoice.payment_failed':
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
if (!$subscription) {
|
||||
send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: ' . $customerId);
|
||||
return response('No subscription found in Coolify.');
|
||||
}
|
||||
$team = data_get($subscription, 'team');
|
||||
if (!$team) {
|
||||
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: ' . $customerId);
|
||||
return response('No team found in Coolify.');
|
||||
}
|
||||
if (!$subscription->stripe_invoice_paid) {
|
||||
SubscriptionInvoiceFailedJob::dispatch($team);
|
||||
send_internal_notification('Invoice payment failed: ' . $customerId);
|
||||
} else {
|
||||
send_internal_notification('Invoice payment failed but already paid: ' . $customerId);
|
||||
}
|
||||
break;
|
||||
case 'payment_intent.payment_failed':
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
if (!$subscription) {
|
||||
send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: ' . $customerId);
|
||||
return response('No subscription found in Coolify.');
|
||||
}
|
||||
if ($subscription->stripe_invoice_paid) {
|
||||
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: ' . $customerId);
|
||||
return;
|
||||
}
|
||||
send_internal_notification('Subscription payment failed for customer: ' . $customerId);
|
||||
break;
|
||||
case 'customer.subscription.updated':
|
||||
$customerId = data_get($data, 'customer');
|
||||
$status = data_get($data, 'status');
|
||||
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
||||
$planId = data_get($data, 'items.data.0.plan.id');
|
||||
if (Str::contains($excludedPlans, $planId)) {
|
||||
send_internal_notification('Subscription excluded.');
|
||||
break;
|
||||
}
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
if (!$subscription) {
|
||||
Sleep::for(5)->seconds();
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
}
|
||||
if (!$subscription) {
|
||||
send_internal_notification('No subscription found for: ' . $customerId);
|
||||
return response("No subscription found", 400);
|
||||
}
|
||||
$trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
|
||||
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
|
||||
$alreadyCancelAtPeriodEnd = data_get($subscription, 'stripe_cancel_at_period_end');
|
||||
$feedback = data_get($data, 'cancellation_details.feedback');
|
||||
$comment = data_get($data, 'cancellation_details.comment');
|
||||
$lookup_key = data_get($data, 'items.data.0.price.lookup_key');
|
||||
if (str($lookup_key)->contains('ultimate')) {
|
||||
$quantity = data_get($data, 'items.data.0.quantity', 10);
|
||||
$team = data_get($subscription, 'team');
|
||||
$team->update([
|
||||
'custom_server_limit' => $quantity,
|
||||
]);
|
||||
ServerLimitCheckJob::dispatch($team);
|
||||
}
|
||||
$subscription->update([
|
||||
'stripe_feedback' => $feedback,
|
||||
'stripe_comment' => $comment,
|
||||
'stripe_plan_id' => $planId,
|
||||
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
|
||||
]);
|
||||
if ($status === 'paused' || $status === 'incomplete_expired') {
|
||||
$subscription->update([
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
send_internal_notification('Subscription paused or incomplete for customer: ' . $customerId);
|
||||
}
|
||||
|
||||
// Trial ended but subscribed, reactive servers
|
||||
if ($trialEndedAlready && $status === 'active') {
|
||||
$team = data_get($subscription, 'team');
|
||||
$team->trialEndedButSubscribed();
|
||||
}
|
||||
|
||||
if ($feedback) {
|
||||
$reason = "Cancellation feedback for {$customerId}: '" . $feedback . "'";
|
||||
if ($comment) {
|
||||
$reason .= ' with comment: \'' . $comment . "'";
|
||||
}
|
||||
send_internal_notification($reason);
|
||||
}
|
||||
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
|
||||
if ($cancelAtPeriodEnd) {
|
||||
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
|
||||
} else {
|
||||
send_internal_notification('customer.subscription.updated for customer: ' . $customerId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'customer.subscription.deleted':
|
||||
// End subscription
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
$team->trialEnded();
|
||||
$subscription->update([
|
||||
'stripe_subscription_id' => null,
|
||||
'stripe_plan_id' => null,
|
||||
'stripe_cancel_at_period_end' => false,
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => true,
|
||||
]);
|
||||
send_internal_notification('customer.subscription.deleted for customer: ' . $customerId);
|
||||
break;
|
||||
case 'customer.subscription.trial_will_end':
|
||||
// Not used for now
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
if (!$team) {
|
||||
throw new Exception('No team found for subscription: ' . $subscription->id);
|
||||
}
|
||||
SubscriptionTrialEndsSoonJob::dispatch($team);
|
||||
break;
|
||||
case 'customer.subscription.paused':
|
||||
$customerId = data_get($data, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||
$team = data_get($subscription, 'team');
|
||||
if (!$team) {
|
||||
throw new Exception('No team found for subscription: ' . $subscription->id);
|
||||
}
|
||||
$team->trialEnded();
|
||||
$subscription->update([
|
||||
'stripe_trial_already_ended' => true,
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
SubscriptionTrialEndedJob::dispatch($team);
|
||||
send_internal_notification('Subscription paused for customer: ' . $customerId);
|
||||
break;
|
||||
default:
|
||||
// Unhandled event type
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
if ($type !== 'payment_intent.payment_failed') {
|
||||
send_internal_notification("Subscription webhook ($type) failed: " . $e->getMessage());
|
||||
}
|
||||
$webhook->update([
|
||||
'status' => 'failed',
|
||||
'failure_reason' => $e->getMessage(),
|
||||
]);
|
||||
return response($e->getMessage(), 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
app/Http/Controllers/Webhook/Waitlist.php
Normal file
58
app/Http/Controllers/Webhook/Waitlist.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Webhook;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Waitlist as ModelsWaitlist;
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Waitlist extends Controller
|
||||
{
|
||||
public function confirm(Request $request)
|
||||
{
|
||||
$email = request()->get('email');
|
||||
$confirmation_code = request()->get('confirmation_code');
|
||||
ray($email, $confirmation_code);
|
||||
try {
|
||||
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
|
||||
if ($found) {
|
||||
if (!$found->verified) {
|
||||
if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) {
|
||||
$found->verified = true;
|
||||
$found->save();
|
||||
send_internal_notification('Waitlist confirmed: ' . $email);
|
||||
return 'Thank you for confirming your email address. We will notify you when you are next in line.';
|
||||
} else {
|
||||
$found->delete();
|
||||
send_internal_notification('Waitlist expired: ' . $email);
|
||||
return 'Your confirmation code has expired. Please sign up again.';
|
||||
}
|
||||
}
|
||||
}
|
||||
return redirect()->route('dashboard');
|
||||
} catch (Exception $e) {
|
||||
send_internal_notification('Waitlist confirmation failed: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
public function cancel(Request $request)
|
||||
{
|
||||
$email = request()->get('email');
|
||||
$confirmation_code = request()->get('confirmation_code');
|
||||
try {
|
||||
$found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
|
||||
if ($found && !$found->verified) {
|
||||
$found->delete();
|
||||
send_internal_notification('Waitlist cancelled: ' . $email);
|
||||
return 'Your email address has been removed from the waitlist.';
|
||||
}
|
||||
return redirect()->route('dashboard');
|
||||
} catch (Exception $e) {
|
||||
send_internal_notification('Waitlist cancellation failed: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,12 +12,16 @@ class DecideWhatToDoWithUser
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (auth()?->user()?->teams?->count() === 0) {
|
||||
$currentTeam = auth()->user()?->recreate_personal_team();
|
||||
refreshSession($currentTeam);
|
||||
}
|
||||
if(auth()?->user()?->currentTeam()){
|
||||
refreshSession(auth()->user()->currentTeam());
|
||||
}
|
||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
return redirect()->route('boarding');
|
||||
return redirect()->route('onboarding');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
@@ -39,7 +43,7 @@ class DecideWhatToDoWithUser
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect()->route('boarding');
|
||||
return redirect()->route('onboarding');
|
||||
}
|
||||
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
|
||||
@@ -12,6 +12,7 @@ class PreventRequestsDuringMaintenance extends Middleware
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
'webhooks/*',
|
||||
'/api/health'
|
||||
];
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Sleep;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
use Spatie\Url\Url;
|
||||
@@ -92,6 +93,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private ?string $buildTarget = null;
|
||||
private Collection $saved_outputs;
|
||||
private ?string $full_healthcheck_url = null;
|
||||
private bool $custom_healthcheck_found = false;
|
||||
|
||||
private string $serverUser = 'root';
|
||||
private string $serverUserHomeDir = '/root';
|
||||
@@ -218,12 +220,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$teamId = data_get($this->application, 'environment.project.team.id');
|
||||
$buildServers = Server::buildServers($teamId)->get();
|
||||
if ($buildServers->count() === 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Build server feature activated, but no suitable build server found. Using the deployment server.");
|
||||
$this->application_deployment_queue->addLogEntry("No suitable build server found. Using the deployment server.");
|
||||
$this->build_server = $this->server;
|
||||
$this->original_server = $this->server;
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Build server feature activated and found a suitable build server. Using it to build your application - if needed.");
|
||||
$this->build_server = $buildServers->random();
|
||||
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
|
||||
$this->original_server = $this->server;
|
||||
$this->use_build_server = true;
|
||||
}
|
||||
@@ -239,6 +241,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(false);
|
||||
$this->run_post_deployment_command();
|
||||
return;
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
@@ -273,6 +276,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED);
|
||||
}
|
||||
}
|
||||
$this->run_post_deployment_command();
|
||||
$this->application->isConfigurationChanged(true);
|
||||
} catch (Exception $e) {
|
||||
if ($this->pull_request_id !== 0 && $this->application->is_github_based()) {
|
||||
@@ -294,13 +298,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
"ignore_errors" => true,
|
||||
]
|
||||
);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"docker image prune -f >/dev/null 2>&1",
|
||||
"hidden" => true,
|
||||
"ignore_errors" => true,
|
||||
]
|
||||
);
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// "docker image prune -f >/dev/null 2>&1",
|
||||
// "hidden" => true,
|
||||
// "ignore_errors" => true,
|
||||
// ]
|
||||
// );
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||
}
|
||||
}
|
||||
@@ -374,6 +378,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->cleanup_git();
|
||||
$this->application->loadComposeFile(isInit: false);
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
$this->application->parseRawCompose();
|
||||
$yaml = $composeFile = $this->application->docker_compose_raw;
|
||||
} else {
|
||||
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id);
|
||||
@@ -413,31 +418,49 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]);
|
||||
}
|
||||
$this->write_deployment_configurations();
|
||||
|
||||
// Start compose file
|
||||
if ($this->docker_compose_custom_start_command) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
|
||||
);
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
if ($this->docker_compose_custom_start_command) {
|
||||
$this->execute_remote_command(
|
||||
["cd {$this->basedir} && {$this->docker_compose_custom_start_command}", "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$server_workdir = $this->application->workdir();
|
||||
ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
|
||||
$this->execute_remote_command(
|
||||
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
|
||||
);
|
||||
if ($this->docker_compose_custom_start_command) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$this->application_deployment_queue->addLogEntry("New container started.");
|
||||
}
|
||||
private function deploy_dockerfile_buildpack()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
if (data_get($this->application, 'dockerfile_location')) {
|
||||
$this->dockerfile_location = $this->application->dockerfile_location;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
$this->generate_image_names();
|
||||
$this->clone_repository();
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
@@ -449,7 +472,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->clone_repository();
|
||||
$this->cleanup_git();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
@@ -528,9 +550,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->server = $this->original_server;
|
||||
}
|
||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||
if ($this->pull_request_id !== 0) {
|
||||
if ($this->pull_request_id === 0) {
|
||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||
} else {
|
||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
@@ -725,7 +749,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->write_deployment_configurations();
|
||||
$this->server = $this->original_server;
|
||||
}
|
||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || $this->pull_request_id !== 0) {
|
||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
||||
@@ -733,6 +757,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->application->settings->is_consistent_container_name_enabled = true;
|
||||
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
|
||||
}
|
||||
$this->stop_running_container(force: true);
|
||||
$this->start_by_compose_file();
|
||||
} else {
|
||||
@@ -751,7 +779,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->server->isSwarm()) {
|
||||
// Implement healthcheck for swarm
|
||||
} else {
|
||||
if ($this->application->isHealthcheckDisabled()) {
|
||||
if ($this->application->isHealthcheckDisabled() && $this->custom_healthcheck_found === false) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
return;
|
||||
}
|
||||
@@ -784,7 +812,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
break;
|
||||
}
|
||||
$counter++;
|
||||
sleep($this->application->health_check_interval);
|
||||
Sleep::for($this->application->health_check_interval)->seconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -810,26 +838,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
}
|
||||
$this->build_image();
|
||||
$this->stop_running_container();
|
||||
if ($this->application->destination->server->isSwarm()) {
|
||||
$this->push_to_docker_registry();
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker stack deploy --with-registry-auth -c {$this->workdir}{$this->docker_compose_location} {$this->application->uuid}-{$this->pull_request_id}")
|
||||
],
|
||||
);
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Starting preview deployment.");
|
||||
if ($this->use_build_server) {
|
||||
$this->execute_remote_command(
|
||||
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
$this->push_to_docker_registry();
|
||||
// $this->stop_running_container();
|
||||
$this->rolling_update();
|
||||
}
|
||||
private function create_workdir()
|
||||
{
|
||||
@@ -866,8 +877,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
[
|
||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->basedir}")
|
||||
],
|
||||
|
||||
);
|
||||
$this->run_pre_deployment_command();
|
||||
}
|
||||
private function deploy_to_additional_destinations()
|
||||
{
|
||||
@@ -1070,7 +1081,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function generate_compose_file()
|
||||
{
|
||||
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
|
||||
|
||||
$onlyPort = null;
|
||||
if (count($ports) > 0) {
|
||||
$onlyPort = $ports[0];
|
||||
}
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables($ports);
|
||||
@@ -1081,6 +1095,25 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$labels = $labels->filter(function ($value, $key) {
|
||||
return !Str::startsWith($value, 'coolify.');
|
||||
});
|
||||
$found_caddy_labels = $labels->filter(function ($value, $key) {
|
||||
return Str::startsWith($value, 'caddy_');
|
||||
});
|
||||
if ($found_caddy_labels->count() === 0) {
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$domains = str(data_get($this->preview, 'fqdn'))->explode(',');
|
||||
} else {
|
||||
$domains = str(data_get($this->application, 'fqdn'))->explode(',');
|
||||
}
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $this->application->destination->network,
|
||||
uuid: $this->application->uuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $this->application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $this->application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $this->application->isStripprefixEnabled()
|
||||
));
|
||||
}
|
||||
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
||||
$this->application->save();
|
||||
} else {
|
||||
@@ -1090,6 +1123,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||
}
|
||||
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
||||
|
||||
// Check for custom HEALTHCHECK
|
||||
$this->custom_healthcheck_found = false;
|
||||
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile', "ignore_errors" => true
|
||||
]);
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
if (str($dockerfile)->contains('HEALTHCHECK')) {
|
||||
$this->custom_healthcheck_found = true;
|
||||
}
|
||||
}
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
@@ -1102,16 +1147,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
'networks' => [
|
||||
$this->destination->network,
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
$this->generate_healthcheck_commands()
|
||||
],
|
||||
'interval' => $this->application->health_check_interval . 's',
|
||||
'timeout' => $this->application->health_check_timeout . 's',
|
||||
'retries' => $this->application->health_check_retries,
|
||||
'start_period' => $this->application->health_check_start_period . 's'
|
||||
],
|
||||
'mem_limit' => $this->application->limits_memory,
|
||||
'memswap_limit' => $this->application->limits_memory_swap,
|
||||
'mem_swappiness' => $this->application->limits_memory_swappiness,
|
||||
@@ -1128,6 +1163,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]
|
||||
]
|
||||
];
|
||||
if (!$this->custom_healthcheck_found) {
|
||||
$docker_compose['services'][$this->container_name]['healthcheck'] = [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
$this->generate_healthcheck_commands()
|
||||
],
|
||||
'interval' => $this->application->health_check_interval . 's',
|
||||
'timeout' => $this->application->health_check_timeout . 's',
|
||||
'retries' => $this->application->health_check_retries,
|
||||
'start_period' => $this->application->health_check_start_period . 's'
|
||||
];
|
||||
}
|
||||
if (!is_null($this->application->limits_cpuset)) {
|
||||
data_set($docker_compose, 'services.' . $this->container_name . '.cpuset', $this->application->limits_cpuset);
|
||||
}
|
||||
@@ -1226,43 +1273,45 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// ];
|
||||
// }
|
||||
|
||||
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||
data_forget($custom_compose, 'ip');
|
||||
data_forget($custom_compose, 'ip6');
|
||||
if ($ipv4 || $ipv6) {
|
||||
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||
if ($this->pull_request_id === 0) {
|
||||
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
|
||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||
data_forget($custom_compose, 'ip');
|
||||
data_forget($custom_compose, 'ip6');
|
||||
if ($ipv4 || $ipv6) {
|
||||
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||
}
|
||||
if ($ipv4) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||
}
|
||||
if ($ipv6) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||
}
|
||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||
}
|
||||
if ($ipv4) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||
} else {
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||
data_forget($custom_compose, 'ip');
|
||||
data_forget($custom_compose, 'ip6');
|
||||
if ($ipv4 || $ipv6) {
|
||||
data_forget($docker_compose['services'][$this->container_name], 'networks');
|
||||
}
|
||||
if ($ipv4) {
|
||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||
}
|
||||
if ($ipv6) {
|
||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||
}
|
||||
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
|
||||
}
|
||||
if ($ipv6) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||
}
|
||||
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
|
||||
}
|
||||
} else {
|
||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||
data_forget($custom_compose, 'ip');
|
||||
data_forget($custom_compose, 'ip6');
|
||||
if ($ipv4 || $ipv6) {
|
||||
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||
}
|
||||
if ($ipv4) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||
}
|
||||
if ($ipv6) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||
}
|
||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1539,18 +1588,18 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
if ($this->pull_request_id === 0) {
|
||||
$containers = $containers->filter(function ($container) {
|
||||
return data_get($container, 'Names') !== $this->container_name;
|
||||
return data_get($container, 'Names') !== $this->container_name && data_get($container, 'Names') !== $this->container_name . '-pr-' . $this->pull_request_id;
|
||||
});
|
||||
}
|
||||
$containers->each(function ($container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
});
|
||||
if ($this->application->settings->is_consistent_container_name_enabled) {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@@ -1559,7 +1608,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
'status' => ApplicationDeploymentStatus::FAILED->value,
|
||||
]);
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1643,16 +1692,69 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
]);
|
||||
}
|
||||
|
||||
private function run_pre_deployment_command()
|
||||
{
|
||||
if (empty($this->application->pre_deployment_command)) {
|
||||
return;
|
||||
}
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
if ($containers->count() == 0) {
|
||||
return;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Executing pre-deployment command (see debug log for output): {$this->application->pre_deployment_command}");
|
||||
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->pre_deployment_command_container . '-' . $this->application->uuid)) {
|
||||
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->pre_deployment_command) . '"';
|
||||
$exec = "docker exec {$containerName} {$cmd}";
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException('Pre-deployment command: Could not find a valid container. Is the container name correct?');
|
||||
}
|
||||
|
||||
private function run_post_deployment_command()
|
||||
{
|
||||
if (empty($this->application->post_deployment_command)) {
|
||||
return;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Executing post-deployment command (see debug log for output): {$this->application->post_deployment_command}");
|
||||
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containers->count() == 1 || str_starts_with($containerName, $this->application->post_deployment_command_container . '-' . $this->application->uuid)) {
|
||||
$cmd = 'sh -c "' . str_replace('"', '\"', $this->application->post_deployment_command) . '"';
|
||||
$exec = "docker exec {$containerName} {$cmd}";
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, $exec), 'hidden' => true
|
||||
],
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException('Post-deployment command: Could not find a valid container. Is the container name correct?');
|
||||
}
|
||||
|
||||
private function next(string $status)
|
||||
{
|
||||
queue_next_deployment($this->application);
|
||||
// If the deployment is cancelled by the user, don't update the status
|
||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value) {
|
||||
if (
|
||||
$this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value
|
||||
) {
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => $status,
|
||||
]);
|
||||
}
|
||||
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||
if ($this->application_deployment_queue->status === ApplicationDeploymentStatus::FAILED->value) {
|
||||
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||
return;
|
||||
}
|
||||
@@ -1680,7 +1782,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
||||
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
if ($this->application->is_public_repository()) {
|
||||
return;
|
||||
}
|
||||
if ($this->status === ProcessStatus::CLOSED) {
|
||||
$this->delete_comment();
|
||||
return;
|
||||
|
||||
@@ -21,7 +21,8 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
|
||||
public function __construct(
|
||||
public Activity $activity,
|
||||
public bool $ignore_errors = false,
|
||||
public $call_event_on_finish = null
|
||||
public $call_event_on_finish = null,
|
||||
public $call_event_data = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -33,7 +34,8 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
|
||||
$remote_process = resolve(RunRemoteProcess::class, [
|
||||
'activity' => $this->activity,
|
||||
'ignore_errors' => $this->ignore_errors,
|
||||
'call_event_on_finish' => $this->call_event_on_finish
|
||||
'call_event_on_finish' => $this->call_event_on_finish,
|
||||
'call_event_data' => $this->call_event_data
|
||||
]);
|
||||
|
||||
$remote_process();
|
||||
|
||||
@@ -51,6 +51,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
$this->backup = $backup;
|
||||
$this->team = Team::find($backup->team_id);
|
||||
if (is_null($this->team)) {
|
||||
return;
|
||||
}
|
||||
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||
$this->database = data_get($this->backup, 'database');
|
||||
$this->server = $this->database->service->server;
|
||||
@@ -316,7 +319,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||
{
|
||||
try {
|
||||
$url = $this->database->getDbUrl(useInternal: true);
|
||||
$url = $this->database->get_db_url(useInternal: true);
|
||||
if ($databaseWithCollections === 'all') {
|
||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
||||
|
||||
74
app/Jobs/DatabaseBackupStatusJob.php
Normal file
74
app/Jobs/DatabaseBackupStatusJob.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Database\DailyBackup;
|
||||
use App\Notifications\Server\HighDiskUsage;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class DatabaseBackupStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// $teams = Team::all();
|
||||
// foreach ($teams as $team) {
|
||||
// $scheduled_backups = $team->scheduledDatabaseBackups()->get();
|
||||
// if ($scheduled_backups->isEmpty()) {
|
||||
// continue;
|
||||
// }
|
||||
// foreach ($scheduled_backups as $scheduled_backup) {
|
||||
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
|
||||
// if ($last_days_backups->isEmpty()) {
|
||||
// continue;
|
||||
// }
|
||||
// $failed = $last_days_backups->where('status', 'failed');
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// $scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
// $databases = collect();
|
||||
// $teams = collect();
|
||||
// foreach ($scheduled_backups as $scheduled_backup) {
|
||||
// $last_days_backups = $scheduled_backup->get_last_days_backup_status();
|
||||
// if ($last_days_backups->isEmpty()) {
|
||||
// continue;
|
||||
// }
|
||||
// $failed = $last_days_backups->where('status', 'failed');
|
||||
// $database = $scheduled_backup->database;
|
||||
// $team = $database->team();
|
||||
// $teams->put($team->id, $team);
|
||||
// $databases->put("{$team->id}:{$database->name}", [
|
||||
// 'failed_count' => $failed->count(),
|
||||
// ]);
|
||||
// }
|
||||
// foreach ($databases as $name => $database) {
|
||||
// [$team_id, $name] = explode(':', $name);
|
||||
// $team = $teams->get($team_id);
|
||||
// $team?->notify(new DailyBackup($databases));
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Jobs;
|
||||
|
||||
use App\Actions\Server\CleanupDocker;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Server\DockerCleanup;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
@@ -47,8 +48,9 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
CleanupDocker::run($this->server);
|
||||
$usageAfter = $this->server->getDiskUsage();
|
||||
if ($usageAfter < $this->usageBefore) {
|
||||
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved ' . ($this->usageBefore - $usageAfter) . '% disk space.'));
|
||||
// ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||
// send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||
Log::info('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||
} else {
|
||||
Log::info('DockerCleanupJob failed to save disk space on ' . $this->server->name);
|
||||
|
||||
26
app/Jobs/ServerFilesFromServerJob.php
Normal file
26
app/Jobs/ServerFilesFromServerJob.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ServerFilesFromServerJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
|
||||
public function __construct(public ServiceApplication|ServiceDatabase $service)
|
||||
{
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
$this->service->getFilesFromServer(isInit: true);
|
||||
}
|
||||
}
|
||||
26
app/Jobs/ServerStorageSaveJob.php
Normal file
26
app/Jobs/ServerStorageSaveJob.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\LocalFileVolume;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ServerStorageSaveJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
|
||||
public function __construct(public LocalFileVolume $localFileVolume)
|
||||
{
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
$this->localFileVolume->saveStorageOnServer();
|
||||
}
|
||||
|
||||
}
|
||||
52
app/Listeners/MaintenanceModeDisabledNotification.php
Normal file
52
app/Listeners/MaintenanceModeDisabledNotification.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use Illuminate\Foundation\Events\MaintenanceModeDisabled as EventsMaintenanceModeDisabled;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
|
||||
|
||||
class MaintenanceModeDisabledNotification
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(EventsMaintenanceModeDisabled $event): void
|
||||
{
|
||||
ray('Maintenance mode disabled!');
|
||||
$files = Storage::disk('webhooks-during-maintenance')->files();
|
||||
$files = collect($files);
|
||||
$files = $files->sort();
|
||||
foreach ($files as $file) {
|
||||
$content = Storage::disk('webhooks-during-maintenance')->get($file);
|
||||
$data = json_decode($content, true);
|
||||
$symfonyRequest = new SymfonyRequest(
|
||||
$data['query'],
|
||||
$data['request'],
|
||||
$data['attributes'],
|
||||
$data['cookies'],
|
||||
$data['files'],
|
||||
$data['server'],
|
||||
$data['content']
|
||||
);
|
||||
|
||||
foreach ($data['headers'] as $key => $value) {
|
||||
$symfonyRequest->headers->set($key, $value);
|
||||
}
|
||||
$request = Request::createFromBase($symfonyRequest);
|
||||
$endpoint = str($file)->after('_')->beforeLast('_')->value();
|
||||
$class = "App\Http\Controllers\Webhook\\" . ucfirst(str($endpoint)->before('::')->value());
|
||||
$method = str($endpoint)->after('::')->value();
|
||||
try {
|
||||
$instance = new $class();
|
||||
$instance->$method($request);
|
||||
} catch (\Throwable $th) {
|
||||
ray($th);
|
||||
} finally {
|
||||
Storage::disk('webhooks-during-maintenance')->delete($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
app/Listeners/MaintenanceModeEnabledNotification.php
Normal file
27
app/Listeners/MaintenanceModeEnabledNotification.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\MaintenanceModeEnabled;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Events\MaintenanceModeEnabled as EventsMaintenanceModeEnabled;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class MaintenanceModeEnabledNotification
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*/
|
||||
public function handle(EventsMaintenanceModeEnabled $event): void
|
||||
{
|
||||
ray('Maintenance mode enabled!');
|
||||
}
|
||||
}
|
||||
21
app/Listeners/ProxyStartedNotification.php
Normal file
21
app/Listeners/ProxyStartedNotification.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Models\Server;
|
||||
|
||||
class ProxyStartedNotification
|
||||
{
|
||||
public Server $server;
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(ProxyStarted $event): void
|
||||
{
|
||||
$this->server = data_get($event, 'data');
|
||||
$this->server->setupDefault404Redirect();
|
||||
$this->server->setupDynamicProxyConfiguration();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ namespace App\Livewire\Admin;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
|
||||
@@ -73,7 +73,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
|
||||
public function restartBoarding()
|
||||
{
|
||||
return redirect()->route('boarding');
|
||||
return redirect()->route('onboarding');
|
||||
}
|
||||
public function skipBoarding()
|
||||
{
|
||||
@@ -122,10 +122,11 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
}
|
||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
$this->installServer();
|
||||
$this->currentState = 'validate-server';
|
||||
}
|
||||
public function getProxyType()
|
||||
{
|
||||
// Set Default Proxy Type
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||
// if (!$proxyTypeSet) {
|
||||
@@ -199,7 +200,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
}
|
||||
public function installServer()
|
||||
{
|
||||
$this->dispatch('validateServer', true);
|
||||
$this->dispatch('init', true);
|
||||
}
|
||||
public function validateServer()
|
||||
{
|
||||
|
||||
@@ -17,10 +17,14 @@ class LayoutPopups extends Component
|
||||
{
|
||||
$this->dispatch('success', 'Realtime events configured!');
|
||||
}
|
||||
public function disable()
|
||||
public function disableSponsorship()
|
||||
{
|
||||
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
|
||||
}
|
||||
public function disableNotifications()
|
||||
{
|
||||
auth()->user()->update(['is_notification_notifications_enabled' => false]);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.layout-popups');
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Profile;
|
||||
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -10,6 +11,13 @@ class Index extends Component
|
||||
public int $userId;
|
||||
public string $email;
|
||||
|
||||
#[Validate('required')]
|
||||
public string $current_password;
|
||||
#[Validate('required|min:8')]
|
||||
public string $new_password;
|
||||
#[Validate('required|min:8|same:new_password')]
|
||||
public string $new_password_confirmation;
|
||||
|
||||
#[Validate('required')]
|
||||
public string $name;
|
||||
public function mount()
|
||||
@@ -19,7 +27,6 @@ class Index extends Component
|
||||
$this->email = auth()->user()->email;
|
||||
}
|
||||
public function submit()
|
||||
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
@@ -27,7 +34,30 @@ class Index extends Component
|
||||
'name' => $this->name,
|
||||
]);
|
||||
|
||||
$this->dispatch('success', 'Profile updated');
|
||||
$this->dispatch('success', 'Profile updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function resetPassword()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
if (!Hash::check($this->current_password, auth()->user()->password)) {
|
||||
$this->dispatch('error', 'Current password is incorrect.');
|
||||
return;
|
||||
}
|
||||
if ($this->new_password !== $this->new_password_confirmation) {
|
||||
$this->dispatch('error', 'The two new passwords does not match.');
|
||||
return;
|
||||
}
|
||||
auth()->user()->update([
|
||||
'password' => Hash::make($this->new_password),
|
||||
]);
|
||||
$this->dispatch('success', 'Password updated.');
|
||||
$this->current_password = '';
|
||||
$this->new_password = '';
|
||||
$this->new_password_confirmation = '';
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ class Advanced extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public bool $is_force_https_enabled;
|
||||
public bool $is_gzip_enabled;
|
||||
public bool $is_stripprefix_enabled;
|
||||
protected $rules = [
|
||||
'application.settings.is_git_submodules_enabled' => 'boolean|required',
|
||||
'application.settings.is_git_lfs_enabled' => 'boolean|required',
|
||||
@@ -19,13 +21,17 @@ class Advanced extends Component
|
||||
'application.settings.is_gpu_enabled' => 'boolean|required',
|
||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
|
||||
'application.settings.is_gzip_enabled' => 'boolean|required',
|
||||
'application.settings.is_stripprefix_enabled' => 'boolean|required',
|
||||
'application.settings.gpu_driver' => 'string|required',
|
||||
'application.settings.gpu_count' => 'string|required',
|
||||
'application.settings.gpu_device_ids' => 'string|required',
|
||||
'application.settings.gpu_options' => 'string|required',
|
||||
];
|
||||
public function mount() {
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
|
||||
$this->is_gzip_enabled = $this->application->isGzipEnabled();
|
||||
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
@@ -40,6 +46,14 @@ class Advanced extends Component
|
||||
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
|
||||
$this->dispatch('resetDefaultLabels', false);
|
||||
}
|
||||
if ($this->application->settings->is_gzip_enabled !== $this->is_gzip_enabled) {
|
||||
$this->application->settings->is_gzip_enabled = $this->is_gzip_enabled;
|
||||
$this->dispatch('resetDefaultLabels', false);
|
||||
}
|
||||
if ($this->application->settings->is_stripprefix_enabled !== $this->is_stripprefix_enabled) {
|
||||
$this->application->settings->is_stripprefix_enabled = $this->is_stripprefix_enabled;
|
||||
$this->dispatch('resetDefaultLabels', false);
|
||||
}
|
||||
$this->application->settings->save();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ class Configuration extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public $servers;
|
||||
protected $listeners = ['build_pack_updated' => '$refresh'];
|
||||
protected $listeners = ['buildPackUpdated' => '$refresh'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
|
||||
@@ -66,6 +66,10 @@ class General extends Component
|
||||
'application.docker_compose_custom_build_command' => 'nullable',
|
||||
'application.custom_labels' => 'nullable',
|
||||
'application.custom_docker_run_options' => 'nullable',
|
||||
'application.pre_deployment_command' => 'nullable',
|
||||
'application.pre_deployment_command_container' => 'nullable',
|
||||
'application.post_deployment_command' => 'nullable',
|
||||
'application.post_deployment_command_container' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||
@@ -112,6 +116,10 @@ class General extends Component
|
||||
} catch (\Throwable $e) {
|
||||
$this->dispatch('error', $e->getMessage());
|
||||
}
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->application->fqdn = null;
|
||||
$this->application->settings->save();
|
||||
}
|
||||
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||
|
||||
$this->ports_exposes = $this->application->ports_exposes;
|
||||
@@ -120,7 +128,7 @@ class General extends Component
|
||||
}
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
$this->customLabels = $this->application->parseContainerLabels();
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
@@ -163,7 +171,12 @@ class General extends Component
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
public function updatedApplicationBaseDirectory() {
|
||||
raY('asdf');
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->loadComposeFile();
|
||||
}
|
||||
}
|
||||
public function updatedApplicationBuildPack()
|
||||
{
|
||||
if ($this->application->build_pack !== 'nixpacks') {
|
||||
@@ -182,7 +195,7 @@ class General extends Component
|
||||
$this->resetDefaultLabels(false);
|
||||
}
|
||||
$this->submit();
|
||||
$this->dispatch('build_pack_updated');
|
||||
$this->dispatch('buildPackUpdated');
|
||||
}
|
||||
public function getWildcardDomain()
|
||||
{
|
||||
@@ -211,12 +224,11 @@ class General extends Component
|
||||
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
||||
$this->application->save();
|
||||
$this->resetDefaultLabels(false);
|
||||
// $this->dispatch('success', 'Labels reset to default!');
|
||||
}
|
||||
public function submit($showToaster = true)
|
||||
{
|
||||
try {
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
@@ -263,7 +275,11 @@ class General extends Component
|
||||
}
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||
$this->parsedServices = $this->application->parseCompose();
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
$this->application->parseRawCompose();
|
||||
} else {
|
||||
$this->parsedServices = $this->application->parseCompose();
|
||||
}
|
||||
}
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
|
||||
@@ -10,7 +10,8 @@ class Execution extends Component
|
||||
public $backup;
|
||||
public $executions;
|
||||
public $s3s;
|
||||
public function mount() {
|
||||
public function mount()
|
||||
{
|
||||
$backup_uuid = request()->route('backup_uuid');
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
@@ -34,6 +35,11 @@ class Execution extends Component
|
||||
$this->executions = $executions;
|
||||
$this->s3s = currentTeam()->s3s;
|
||||
}
|
||||
public function cleanupFailed()
|
||||
{
|
||||
$this->backup->executions()->where('status', 'failed')->delete();
|
||||
$this->dispatch('refreshBackupExecutions');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.backup.execution');
|
||||
|
||||
@@ -34,7 +34,7 @@ class BackupExecutions extends Component
|
||||
}
|
||||
$execution->delete();
|
||||
$this->dispatch('success', 'Backup deleted.');
|
||||
$this->dispatch('refreshBackupExecutions');
|
||||
$this->refreshBackupExecutions();
|
||||
}
|
||||
public function download($exeuctionId)
|
||||
{
|
||||
@@ -65,6 +65,6 @@ class BackupExecutions extends Component
|
||||
}
|
||||
public function refreshBackupExecutions(): void
|
||||
{
|
||||
$this->executions = data_get($this->backup, 'executions', []);
|
||||
$this->executions = $this->backup->executions()->get()->sortByDesc('created_at');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,9 +46,9 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->getDbUrl(true);
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
@@ -93,7 +93,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
|
||||
@@ -44,9 +44,9 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->getDbUrl(true);
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced()
|
||||
@@ -95,7 +95,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
|
||||
@@ -46,9 +46,9 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->getDbUrl(true);
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced()
|
||||
@@ -94,7 +94,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
|
||||
@@ -53,9 +53,9 @@ class General extends Component
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->getDbUrl(true);
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
@@ -87,7 +87,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
|
||||
@@ -39,9 +39,9 @@ class General extends Component
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->getDbUrl(true);
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
@@ -86,7 +86,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->getDbUrl();
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
|
||||
@@ -17,7 +17,6 @@ class DockerCompose extends Component
|
||||
public array $query;
|
||||
public function mount()
|
||||
{
|
||||
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
if (isDev()) {
|
||||
@@ -40,12 +39,17 @@ class DockerCompose extends Component
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$server_id = $this->query['server_id'];
|
||||
try {
|
||||
$this->validate([
|
||||
'dockerComposeRaw' => 'required'
|
||||
]);
|
||||
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||
$server_id = $this->query['server_id'];
|
||||
|
||||
$isValid = validateComposeFile($this->dockerComposeRaw, $server_id);
|
||||
if ($isValid !== 'OK') {
|
||||
return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
|
||||
}
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
@@ -74,7 +78,6 @@ class DockerCompose extends Component
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,10 @@ class GithubPrivateRepository extends Component
|
||||
$this->loadRepositoryByPage();
|
||||
}
|
||||
}
|
||||
$this->selected_repository_id = $this->repositories[0]['id'];
|
||||
$this->repositories = $this->repositories->sortBy('name');
|
||||
if ($this->repositories->count() > 0) {
|
||||
$this->selected_repository_id = data_get($this->repositories->first(), 'id');
|
||||
}
|
||||
$this->current_step = 'repository';
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ use Livewire\Component;
|
||||
class Create extends Component
|
||||
{
|
||||
public $type;
|
||||
public function mount() {
|
||||
public function mount()
|
||||
{
|
||||
$services = getServiceTemplates();
|
||||
$type = str(request()->query('type'));
|
||||
$destination_uuid = request()->query('destination');
|
||||
@@ -70,7 +71,7 @@ class Create extends Component
|
||||
$generatedValue = $value;
|
||||
if ($value->contains('SERVICE_')) {
|
||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||
$generatedValue = generateEnvValue($command->value());
|
||||
$generatedValue = generateEnvValue($command->value(), $service);
|
||||
}
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
|
||||
@@ -17,9 +17,8 @@ class Configuration extends Component
|
||||
{
|
||||
$userId = auth()->user()->id;
|
||||
return [
|
||||
"echo-private:user.{$userId},ServiceStatusChanged" => 'checkStatus',
|
||||
"refreshStacks",
|
||||
"checkStatus",
|
||||
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
|
||||
"check_status"
|
||||
];
|
||||
}
|
||||
public function render()
|
||||
@@ -37,21 +36,14 @@ class Configuration extends Component
|
||||
$this->applications = $this->service->applications->sort();
|
||||
$this->databases = $this->service->databases->sort();
|
||||
}
|
||||
public function checkStatus()
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->refreshStacks();
|
||||
$this->dispatch('serviceStatusChanged');
|
||||
}
|
||||
public function refreshStacks()
|
||||
{
|
||||
$this->applications = $this->service->applications->sort();
|
||||
$this->applications->each(function ($application) {
|
||||
$application->refresh();
|
||||
});
|
||||
$this->databases = $this->service->databases->sort();
|
||||
$this->databases->each(function ($database) {
|
||||
$database->refresh();
|
||||
});
|
||||
try {
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->dispatch('refresh')->self();
|
||||
$this->dispatch('serviceStatusChanged');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Actions\Shared\PullImage;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Events\ServiceStatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Service;
|
||||
use Livewire\Component;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
@@ -17,7 +16,24 @@ class Navbar extends Component
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
public $isDeploymentProgress = false;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
return [
|
||||
"serviceStatusChanged"
|
||||
];
|
||||
}
|
||||
public function serviceStatusChanged()
|
||||
{
|
||||
$this->dispatch('refresh')->self();
|
||||
}
|
||||
public function check_status() {
|
||||
$this->dispatch('check_status');
|
||||
$this->dispatch('success', 'Service status updated.');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar');
|
||||
}
|
||||
public function checkDeployments()
|
||||
{
|
||||
$activity = Activity::where('properties->type_uuid', $this->service->uuid)->latest()->first();
|
||||
@@ -28,26 +44,6 @@ class Navbar extends Component
|
||||
$this->isDeploymentProgress = false;
|
||||
}
|
||||
}
|
||||
public function getListeners()
|
||||
{
|
||||
return [
|
||||
"serviceStatusChanged"
|
||||
];
|
||||
}
|
||||
public function serviceStatusChanged()
|
||||
{
|
||||
$this->service->refresh();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar');
|
||||
}
|
||||
public function check_status($showNotification = false)
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->service->destination->server));
|
||||
$this->service->refresh();
|
||||
if ($showNotification) $this->dispatch('success', 'Service status updated.');
|
||||
}
|
||||
public function deploy()
|
||||
{
|
||||
$this->checkDeployments();
|
||||
@@ -62,7 +58,6 @@ class Navbar extends Component
|
||||
public function stop(bool $forceCleanup = false)
|
||||
{
|
||||
StopService::run($this->service);
|
||||
$this->service->refresh();
|
||||
if ($forceCleanup) {
|
||||
$this->dispatch('success', 'Containers cleaned up.');
|
||||
} else {
|
||||
|
||||
@@ -18,6 +18,7 @@ class ServiceApplicationView extends Component
|
||||
'application.required_fqdn' => 'required|boolean',
|
||||
'application.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'application.is_gzip_enabled' => 'nullable|boolean',
|
||||
'application.is_stripprefix_enabled' => 'nullable|boolean',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -41,7 +41,6 @@ class StackForm extends Component
|
||||
}
|
||||
public function saveCompose($raw)
|
||||
{
|
||||
|
||||
$this->service->docker_compose_raw = $raw;
|
||||
$this->submit();
|
||||
}
|
||||
@@ -55,6 +54,10 @@ class StackForm extends Component
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$isValid = validateComposeFile($this->service->docker_compose_raw, $this->service->server->id);
|
||||
if ($isValid !== 'OK') {
|
||||
throw new \Exception("Invalid docker-compose file.\n$isValid");
|
||||
}
|
||||
$this->service->save();
|
||||
$this->service->saveExtraFields($this->fields);
|
||||
$this->service->parse();
|
||||
|
||||
@@ -10,29 +10,21 @@ use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class ExecuteContainerCommand extends Component
|
||||
{
|
||||
public string $command;
|
||||
public string $container;
|
||||
public $containers;
|
||||
public Collection $containers;
|
||||
public $parameters;
|
||||
public $resource;
|
||||
public string $type;
|
||||
public string $workDir = '';
|
||||
public Server $server;
|
||||
public $servers = [];
|
||||
public function getListeners()
|
||||
{
|
||||
return [
|
||||
"serviceStatusChanged",
|
||||
];
|
||||
}
|
||||
public function serviceStatusChanged()
|
||||
{
|
||||
$this->getContainers();
|
||||
}
|
||||
public Collection $servers;
|
||||
|
||||
protected $rules = [
|
||||
'server' => 'required',
|
||||
'container' => 'required',
|
||||
@@ -43,20 +35,18 @@ class ExecuteContainerCommand extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->getContainers();
|
||||
}
|
||||
public function getContainers()
|
||||
{
|
||||
$this->containers = collect();
|
||||
$this->servers = collect();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$this->type = 'application';
|
||||
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||
$this->server = $this->resource->destination->server;
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
|
||||
if ($containers->count() > 0) {
|
||||
$containers->each(function ($container) {
|
||||
$this->containers->push(str_replace('/', '', $container['Names']));
|
||||
});
|
||||
if ($this->resource->destination->server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($this->resource->destination->server);
|
||||
}
|
||||
foreach ($this->resource->additional_servers as $server) {
|
||||
if ($server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($server);
|
||||
}
|
||||
}
|
||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->type = 'database';
|
||||
@@ -77,47 +67,85 @@ class ExecuteContainerCommand extends Component
|
||||
}
|
||||
}
|
||||
$this->resource = $resource;
|
||||
$this->server = $this->resource->destination->server;
|
||||
if ($this->resource->destination->server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($this->resource->destination->server);
|
||||
}
|
||||
$this->container = $this->resource->uuid;
|
||||
// if (!str(data_get($this,'resource.status'))->startsWith('exited')) {
|
||||
$this->containers->push($this->container);
|
||||
// }
|
||||
$this->containers->push($this->container);
|
||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->resource->applications()->get()->each(function ($application) {
|
||||
// if (str(data_get($application, 'status'))->contains('running')) {
|
||||
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
// }
|
||||
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
});
|
||||
$this->resource->databases()->get()->each(function ($database) {
|
||||
// if (str(data_get($database, 'status'))->contains('running')) {
|
||||
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
// }
|
||||
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
});
|
||||
|
||||
$this->server = $this->resource->server;
|
||||
if ($this->resource->server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($this->resource->server);
|
||||
}
|
||||
}
|
||||
if ($this->containers->count() > 0) {
|
||||
$this->container = $this->containers->first();
|
||||
}
|
||||
}
|
||||
public function loadContainers()
|
||||
{
|
||||
foreach ($this->servers as $server) {
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
if ($server->isSwarm()) {
|
||||
$containers = collect([
|
||||
[
|
||||
'Names' => $this->resource->uuid . '_' . $this->resource->uuid,
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
|
||||
}
|
||||
foreach ($containers as $container) {
|
||||
$payload = [
|
||||
'server' => $server,
|
||||
'container' => $container,
|
||||
];
|
||||
$this->containers = $this->containers->push($payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->containers->count() > 0) {
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$this->container = data_get($this->containers->first(), 'container.Names');
|
||||
} elseif (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->container = $this->containers->first();
|
||||
} elseif (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->container = $this->containers->first();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function runCommand()
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
if ($this->server->isForceDisabled()) {
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$container = $this->containers->where('container.Names', $this->container)->first();
|
||||
$container_name = data_get($container, 'container.Names');
|
||||
if (is_null($container)) {
|
||||
throw new \RuntimeException('Container not found.');
|
||||
}
|
||||
$server = data_get($container, 'server');
|
||||
} else {
|
||||
$container_name = $this->container;
|
||||
$server = $this->servers->first();
|
||||
}
|
||||
if ($server->isForceDisabled()) {
|
||||
throw new \RuntimeException('Server is disabled.');
|
||||
}
|
||||
// Wrap command to prevent escaped execution in the host.
|
||||
$cmd = 'sh -c "if [ -f ~/.profile ]; then . ~/.profile; fi; ' . str_replace('"', '\"', $this->command) . '"';
|
||||
if (!empty($this->workDir)) {
|
||||
$exec = "docker exec -w {$this->workDir} {$this->container} {$cmd}";
|
||||
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
|
||||
} else {
|
||||
$exec = "docker exec {$this->container} {$cmd}";
|
||||
$exec = "docker exec {$container_name} {$cmd}";
|
||||
}
|
||||
$activity = remote_process([$exec], $this->server, ignore_errors: true);
|
||||
$activity = remote_process([$exec], $server, ignore_errors: true);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -23,6 +23,7 @@ class GetLogs extends Component
|
||||
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
|
||||
public Server $server;
|
||||
public ?string $container = null;
|
||||
public ?string $pull_request = null;
|
||||
public ?bool $streamLogs = false;
|
||||
public ?bool $showTimeStamps = true;
|
||||
public int $numberOfLines = 100;
|
||||
@@ -70,7 +71,14 @@ class GetLogs extends Component
|
||||
}
|
||||
public function getLogs($refresh = false)
|
||||
{
|
||||
if (!$refresh && $this->resource?->getMorphClass() === 'App\Models\Service') return;
|
||||
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
|
||||
if (str($this->container)->contains('-pr-')) {
|
||||
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
|
||||
} else {
|
||||
$this->pull_request = 'branch';
|
||||
}
|
||||
}
|
||||
if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return;
|
||||
if ($this->container) {
|
||||
if ($this->showTimeStamps) {
|
||||
if ($this->server->isSwarm()) {
|
||||
|
||||
@@ -10,84 +10,99 @@ use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Logs extends Component
|
||||
{
|
||||
public ?string $type = null;
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
|
||||
public Server $server;
|
||||
public Collection $servers;
|
||||
public Collection $containers;
|
||||
public $container = [];
|
||||
public $containers;
|
||||
public $parameters;
|
||||
public $query;
|
||||
public $status;
|
||||
public $serviceSubType;
|
||||
|
||||
public function mount()
|
||||
public function loadContainers($server_id)
|
||||
{
|
||||
$this->containers = collect();
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$this->type = 'application';
|
||||
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
if ($this->server->isSwarm()) {
|
||||
try {
|
||||
$server = $this->servers->firstWhere('id', $server_id);
|
||||
if ($server->isSwarm()) {
|
||||
$containers = collect([
|
||||
[
|
||||
'Names' => $this->resource->uuid . '_' . $this->resource->uuid,
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
|
||||
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
|
||||
}
|
||||
if ($containers->count() > 0) {
|
||||
$containers->each(function ($container) {
|
||||
$this->containers->push(str_replace('/', '', $container['Names']));
|
||||
});
|
||||
}
|
||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->type = 'database';
|
||||
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
$server->containers = $containers;
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->containers = collect();
|
||||
$this->servers = collect();
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
$this->type = 'application';
|
||||
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||
$this->status = $this->resource->status;
|
||||
if ($this->resource->destination->server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($this->resource->destination->server);
|
||||
}
|
||||
foreach ($this->resource->additional_servers as $server) {
|
||||
if ($server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($server);
|
||||
}
|
||||
}
|
||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->type = 'database';
|
||||
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
abort(404);
|
||||
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->resource = $resource;
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$this->container = $this->resource->uuid;
|
||||
// if (str(data_get($this, 'resource.status'))->startsWith('running')) {
|
||||
$this->resource = $resource;
|
||||
$this->status = $this->resource->status;
|
||||
if ($this->resource->destination->server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($this->resource->destination->server);
|
||||
}
|
||||
$this->container = $this->resource->uuid;
|
||||
$this->containers->push($this->container);
|
||||
// }
|
||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->resource->applications()->get()->each(function ($application) {
|
||||
// if (str(data_get($application, 'status'))->contains('running')) {
|
||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||
$this->type = 'service';
|
||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->resource->applications()->get()->each(function ($application) {
|
||||
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
// }
|
||||
});
|
||||
$this->resource->databases()->get()->each(function ($database) {
|
||||
// if (str(data_get($database, 'status'))->contains('running')) {
|
||||
});
|
||||
$this->resource->databases()->get()->each(function ($database) {
|
||||
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||
// }
|
||||
});
|
||||
});
|
||||
|
||||
$this->server = $this->resource->server;
|
||||
if ($this->resource->server->isFunctional()) {
|
||||
$this->servers = $this->servers->push($this->resource->server);
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class ResourceLimits extends Component
|
||||
if (!$this->resource->limits_memory_swap) {
|
||||
$this->resource->limits_memory_swap = "0";
|
||||
}
|
||||
if (!$this->resource->limits_memory_swappiness) {
|
||||
if (is_null($this->resource->limits_memory_swappiness)) {
|
||||
$this->resource->limits_memory_swappiness = "60";
|
||||
}
|
||||
if (!$this->resource->limits_memory_reservation) {
|
||||
@@ -47,7 +47,7 @@ class ResourceLimits extends Component
|
||||
if ($this->resource->limits_cpuset === "") {
|
||||
$this->resource->limits_cpuset = null;
|
||||
}
|
||||
if (!$this->resource->limits_cpu_shares) {
|
||||
if (is_null($this->resource->limits_cpu_shares)) {
|
||||
$this->resource->limits_cpu_shares = 1024;
|
||||
}
|
||||
$this->validate();
|
||||
|
||||
@@ -45,7 +45,7 @@ class ResourceOperations extends Component
|
||||
'destination_id' => $new_destination->id,
|
||||
]);
|
||||
$new_resource->save();
|
||||
if ($new_resource->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||
if ($new_resource->destination->server->proxyType() !== 'NONE') {
|
||||
$customLabels = str(implode("|", generateLabelsApplication($new_resource)))->replace("|", "\n");
|
||||
$new_resource->custom_labels = base64_encode($customLabels);
|
||||
$new_resource->save();
|
||||
|
||||
@@ -89,6 +89,7 @@ class ByIp extends Component
|
||||
'team_id' => currentTeam()->id,
|
||||
'private_key_id' => $this->private_key_id,
|
||||
'proxy' => [
|
||||
// set default proxy type to traefik v2
|
||||
"type" => ProxyTypes::TRAEFIK_V2->value,
|
||||
"status" => ProxyStatus::EXITED->value,
|
||||
],
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Livewire\Server;
|
||||
|
||||
use App\Actions\Proxy\CheckConfiguration;
|
||||
use App\Actions\Proxy\SaveConfiguration;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -20,13 +21,13 @@ class Proxy extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->selectedProxy = data_get($this->server, 'proxy.type');
|
||||
$this->selectedProxy = $this->server->proxyType();
|
||||
$this->redirect_url = data_get($this->server, 'proxy.redirect_url');
|
||||
}
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->refresh();
|
||||
$this->dispatch('refresh')->self();
|
||||
}
|
||||
|
||||
public function change_proxy()
|
||||
@@ -41,6 +42,9 @@ class Proxy extends Component
|
||||
$this->server->proxy->set('type', $proxy_type);
|
||||
$this->server->save();
|
||||
$this->selectedProxy = $this->server->proxy->type;
|
||||
if ($this->selectedProxy !== 'NONE') {
|
||||
StartProxy::run($this->server, false);
|
||||
}
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
}
|
||||
|
||||
@@ -50,8 +54,7 @@ class Proxy extends Component
|
||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||
$this->server->proxy->redirect_url = $this->redirect_url;
|
||||
$this->server->save();
|
||||
|
||||
setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server);
|
||||
$this->server->setupDefault404Redirect();
|
||||
$this->dispatch('success', 'Proxy configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -62,6 +65,9 @@ class Proxy extends Component
|
||||
{
|
||||
try {
|
||||
$this->proxy_settings = CheckConfiguration::run($this->server, true);
|
||||
SaveConfiguration::run($this->server, $this->proxy_settings);
|
||||
$this->server->save();
|
||||
$this->dispatch('success', 'Proxy configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,17 @@ class DynamicConfigurationNavbar extends Component
|
||||
public function delete(string $fileName)
|
||||
{
|
||||
$server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $server->proxyPath();
|
||||
$proxy_type = $server->proxyType();
|
||||
$file = str_replace('|', '.', $fileName);
|
||||
if ($proxy_type === 'CADDY' && $file === "Caddyfile") {
|
||||
$this->dispatch('error', 'Cannot delete Caddyfile.');
|
||||
return;
|
||||
}
|
||||
instant_remote_process(["rm -f {$proxy_path}/dynamic/{$file}"], $server);
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$server->reloadCaddy();
|
||||
}
|
||||
$this->dispatch('success', 'File deleted.');
|
||||
$this->dispatch('loadDynamicConfigurations');
|
||||
$this->dispatch('refresh');
|
||||
|
||||
@@ -11,26 +11,32 @@ class DynamicConfigurations extends Component
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
public Collection $contents;
|
||||
protected $listeners = ['loadDynamicConfigurations', 'refresh' => '$refresh'];
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
return [
|
||||
"echo-private:team.{$teamId},ProxyStatusChanged" => 'loadDynamicConfigurations',
|
||||
'loadDynamicConfigurations',
|
||||
'refresh' => '$refresh'
|
||||
];
|
||||
}
|
||||
protected $rules = [
|
||||
'contents.*' => 'nullable|string',
|
||||
];
|
||||
public function loadDynamicConfigurations()
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $this->server->proxyPath();
|
||||
$files = instant_remote_process(["mkdir -p $proxy_path/dynamic && ls -1 {$proxy_path}/dynamic"], $this->server);
|
||||
$files = collect(explode("\n", $files))->filter(fn ($file) => !empty($file));
|
||||
$files = $files->map(fn ($file) => trim($file));
|
||||
$files = $files->sort();
|
||||
if ($files->contains('coolify.yaml')) {
|
||||
$files = $files->filter(fn ($file) => $file !== 'coolify.yaml')->prepend('coolify.yaml');
|
||||
}
|
||||
$contents = collect([]);
|
||||
foreach ($files as $file) {
|
||||
$without_extension = str_replace('.', '|', $file);
|
||||
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
|
||||
}
|
||||
$this->contents = $contents;
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
|
||||
@@ -29,7 +29,6 @@ class NewDynamicConfiguration extends Component
|
||||
'fileName' => 'required',
|
||||
'value' => 'required',
|
||||
]);
|
||||
|
||||
if (data_get($this->parameters, 'server_uuid')) {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(data_get($this->parameters, 'server_uuid'))->first();
|
||||
}
|
||||
@@ -39,14 +38,21 @@ class NewDynamicConfiguration extends Component
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
if (!str($this->fileName)->endsWith('.yaml') && !str($this->fileName)->endsWith('.yml')) {
|
||||
$this->fileName = "{$this->fileName}.yaml";
|
||||
$proxy_type = $this->server->proxyType();
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
if (!str($this->fileName)->endsWith('.yaml') && !str($this->fileName)->endsWith('.yml')) {
|
||||
$this->fileName = "{$this->fileName}.yaml";
|
||||
}
|
||||
if ($this->fileName === 'coolify.yaml') {
|
||||
$this->dispatch('error', 'File name is reserved.');
|
||||
return;
|
||||
}
|
||||
} else if ($proxy_type === 'CADDY') {
|
||||
if (!str($this->fileName)->endsWith('.caddy')) {
|
||||
$this->fileName = "{$this->fileName}.caddy";
|
||||
}
|
||||
}
|
||||
if ($this->fileName === 'coolify.yaml') {
|
||||
$this->dispatch('error', 'File name is reserved.');
|
||||
return;
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $this->server->proxyPath();
|
||||
$file = "{$proxy_path}/dynamic/{$this->fileName}";
|
||||
if ($this->newFile) {
|
||||
$exists = instant_remote_process(["test -f $file && echo 1 || echo 0"], $this->server);
|
||||
@@ -55,11 +61,18 @@ class NewDynamicConfiguration extends Component
|
||||
return;
|
||||
}
|
||||
}
|
||||
$yaml = Yaml::parse($this->value);
|
||||
$yaml = Yaml::dump($yaml, 10, 2);
|
||||
$this->value = $yaml;
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
$yaml = Yaml::parse($this->value);
|
||||
$yaml = Yaml::dump($yaml, 10, 2);
|
||||
$this->value = $yaml;
|
||||
}
|
||||
$base64_value = base64_encode($this->value);
|
||||
instant_remote_process(["echo '{$base64_value}' | base64 -d > {$file}"], $this->server);
|
||||
instant_remote_process([
|
||||
"echo '{$base64_value}' | base64 -d > {$file}",
|
||||
], $this->server);
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$this->server->reloadCaddy();
|
||||
}
|
||||
$this->dispatch('loadDynamicConfigurations');
|
||||
$this->dispatch('dynamic-configuration-added');
|
||||
$this->dispatch('success', 'Dynamic configuration saved.');
|
||||
|
||||
@@ -55,7 +55,6 @@ class Resources extends Component
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
|
||||
namespace App\Livewire\Settings;
|
||||
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\InstanceSettings as ModelsInstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Configuration extends Component
|
||||
{
|
||||
@@ -78,13 +75,7 @@ class Configuration extends Component
|
||||
|
||||
$this->settings->save();
|
||||
$this->server = Server::findOrFail(0);
|
||||
$this->setup_instance_fqdn();
|
||||
$this->server->setupDynamicProxyConfiguration();
|
||||
$this->dispatch('success', 'Instance settings updated successfully!');
|
||||
}
|
||||
|
||||
private function setup_instance_fqdn()
|
||||
{
|
||||
setup_dynamic_configuration();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ class Change extends Component
|
||||
public string $name;
|
||||
public bool $is_system_wide;
|
||||
|
||||
public $applications;
|
||||
|
||||
protected $rules = [
|
||||
'github_app.name' => 'required|string',
|
||||
'github_app.organization' => 'nullable|string',
|
||||
@@ -90,6 +92,7 @@ class Change extends Component
|
||||
if (!$this->github_app) {
|
||||
return redirect()->route('source.all');
|
||||
}
|
||||
$this->applications = $this->github_app->applications;
|
||||
$settings = InstanceSettings::get();
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
|
||||
@@ -170,6 +173,11 @@ class Change extends Component
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
if ($this->github_app->applications->isNotEmpty()) {
|
||||
$this->dispatch('error', 'This source is being used by an application. Please delete all applications first.');
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
return;
|
||||
}
|
||||
$this->github_app->delete();
|
||||
return redirect()->route('source.all');
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Tags;
|
||||
|
||||
use App\Http\Controllers\Api\APIDeploy as Deploy;
|
||||
use App\Http\Controllers\Api\Deploy;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Tag;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Application extends BaseModel
|
||||
@@ -65,6 +66,13 @@ class Application extends BaseModel
|
||||
return $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')
|
||||
->withPivot('server_id', 'status');
|
||||
}
|
||||
public function is_public_repository(): bool
|
||||
{
|
||||
if (data_get($this, 'source.is_public')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function is_github_based(): bool
|
||||
{
|
||||
if (data_get($this, 'source')) {
|
||||
@@ -72,6 +80,18 @@ class Application extends BaseModel
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function isForceHttpsEnabled()
|
||||
{
|
||||
return data_get($this, 'settings.is_force_https_enabled', false);
|
||||
}
|
||||
public function isStripprefixEnabled()
|
||||
{
|
||||
return data_get($this, 'settings.is_stripprefix_enabled', true);
|
||||
}
|
||||
public function isGzipEnabled()
|
||||
{
|
||||
return data_get($this, 'settings.is_gzip_enabled', true);
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
@@ -395,7 +415,10 @@ class Application extends BaseModel
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function get_last_days_deployments()
|
||||
{
|
||||
return ApplicationDeploymentQueue::where('application_id', $this->id)->where('created_at', '>=', now()->subDays(7))->orderBy('created_at', 'desc')->get();
|
||||
}
|
||||
public function deployments(int $skip = 0, int $take = 10)
|
||||
{
|
||||
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->orderBy('created_at', 'desc');
|
||||
@@ -466,6 +489,10 @@ class Application extends BaseModel
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return application_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
return data_get($this, 'settings.is_log_drain_enabled', false);
|
||||
@@ -700,6 +727,64 @@ class Application extends BaseModel
|
||||
];
|
||||
}
|
||||
}
|
||||
function parseRawCompose()
|
||||
{
|
||||
try {
|
||||
$yaml = Yaml::parse($this->docker_compose_raw);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
$services = data_get($yaml, 'services');
|
||||
$commands = collect([]);
|
||||
$services = collect($services)->map(function ($service) use ($commands) {
|
||||
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||
if ($serviceVolumes->count() > 0) {
|
||||
foreach ($serviceVolumes as $volume) {
|
||||
$workdir = $this->workdir();
|
||||
$type = null;
|
||||
$source = null;
|
||||
if (is_string($volume)) {
|
||||
$source = Str::of($volume)->before(':');
|
||||
if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
|
||||
$type = Str::of('bind');
|
||||
}
|
||||
} else if (is_array($volume)) {
|
||||
$type = data_get_str($volume, 'type');
|
||||
$source = data_get_str($volume, 'source');
|
||||
}
|
||||
if ($type->value() === 'bind') {
|
||||
if ($source->value() === "/var/run/docker.sock") {
|
||||
continue;
|
||||
}
|
||||
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
|
||||
continue;
|
||||
}
|
||||
if ($source->startsWith('.')) {
|
||||
$source = $source->after('.');
|
||||
$source = $workdir . $source;
|
||||
}
|
||||
$commands->push("mkdir -p $source > /dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
}
|
||||
$labels = collect(data_get($service, 'labels', []));
|
||||
if (!$labels->contains('coolify.managed')) {
|
||||
$labels->push('coolify.managed=true');
|
||||
}
|
||||
if (!$labels->contains('coolify.applicationId')) {
|
||||
$labels->push('coolify.applicationId=' . $this->id);
|
||||
}
|
||||
if (!$labels->contains('coolify.type')) {
|
||||
$labels->push('coolify.type=application');
|
||||
}
|
||||
data_set($service, 'labels', $labels->toArray());
|
||||
return $service;
|
||||
});
|
||||
data_set($yaml, 'services', $services->toArray());
|
||||
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||
|
||||
instant_remote_process($commands, $this->destination->server, false);
|
||||
}
|
||||
function parseCompose(int $pull_request_id = 0)
|
||||
{
|
||||
if ($this->docker_compose_raw) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class LocalFileVolume extends BaseModel
|
||||
{
|
||||
@@ -14,7 +13,7 @@ class LocalFileVolume extends BaseModel
|
||||
{
|
||||
static::created(function (LocalFileVolume $fileVolume) {
|
||||
$fileVolume->load(['service']);
|
||||
$fileVolume->saveStorageOnServer();
|
||||
dispatch(new \App\Jobs\ServerStorageSaveJob($fileVolume));
|
||||
});
|
||||
}
|
||||
public function service()
|
||||
|
||||
@@ -27,7 +27,8 @@ class Project extends BaseModel
|
||||
$project->settings()->delete();
|
||||
});
|
||||
}
|
||||
public function environment_variables() {
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->hasMany(SharedEnvironmentVariable::class);
|
||||
}
|
||||
public function environments()
|
||||
@@ -45,6 +46,10 @@ class Project extends BaseModel
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function services()
|
||||
{
|
||||
return $this->hasManyThrough(Service::class, Environment::class);
|
||||
}
|
||||
public function applications()
|
||||
{
|
||||
return $this->hasManyThrough(Application::class, Environment::class);
|
||||
@@ -70,7 +75,8 @@ class Project extends BaseModel
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneMariadb::class, Environment::class);
|
||||
}
|
||||
public function resource_count() {
|
||||
public function resource_count()
|
||||
{
|
||||
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,8 @@ class ScheduledDatabaseBackup extends BaseModel
|
||||
{
|
||||
return $this->belongsTo(S3Storage::class, 's3_storage_id');
|
||||
}
|
||||
public function get_last_days_backup_status($days = 7)
|
||||
{
|
||||
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Enums\ProxyStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Notifications\Server\Revived;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
@@ -15,6 +14,8 @@ use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Stringable;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class Server extends BaseModel
|
||||
{
|
||||
@@ -118,18 +119,304 @@ class Server extends BaseModel
|
||||
}
|
||||
}
|
||||
}
|
||||
public function setupDefault404Redirect()
|
||||
{
|
||||
$dynamic_conf_path = $this->proxyPath() . "/dynamic";
|
||||
$proxy_type = $this->proxyType();
|
||||
$redirect_url = $this->proxy->redirect_url;
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml";
|
||||
} else if ($proxy_type === 'CADDY') {
|
||||
$default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy";
|
||||
}
|
||||
if (empty($redirect_url)) {
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$conf = ":80, :443 {
|
||||
respond 404
|
||||
}";
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d > $default_redirect_file",
|
||||
], $this);
|
||||
$this->reloadCaddy();
|
||||
return;
|
||||
}
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"rm -f $default_redirect_file",
|
||||
], $this);
|
||||
return;
|
||||
}
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
$dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'routers' =>
|
||||
[
|
||||
'catchall' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
1 => 'https',
|
||||
],
|
||||
'service' => 'noop',
|
||||
'rule' => "HostRegexp(`{catchall:.*}`)",
|
||||
'priority' => 1,
|
||||
'middlewares' => [
|
||||
0 => 'redirect-regexp@file',
|
||||
],
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'noop' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'middlewares' =>
|
||||
[
|
||||
'redirect-regexp' =>
|
||||
[
|
||||
'redirectRegex' =>
|
||||
[
|
||||
'regex' => '(.*)',
|
||||
'replacement' => $redirect_url,
|
||||
'permanent' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$conf = Yaml::dump($dynamic_conf, 12, 2);
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$conf;
|
||||
|
||||
$base64 = base64_encode($conf);
|
||||
} else if ($proxy_type === 'CADDY') {
|
||||
$conf = ":80, :443 {
|
||||
redir $redirect_url
|
||||
}";
|
||||
$conf =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$conf;
|
||||
$base64 = base64_encode($conf);
|
||||
}
|
||||
|
||||
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d > $default_redirect_file",
|
||||
], $this);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
ray($conf);
|
||||
}
|
||||
if ($proxy_type === 'CADDY') {
|
||||
$this->reloadCaddy();
|
||||
}
|
||||
}
|
||||
public function setupDynamicProxyConfiguration()
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
$dynamic_config_path = $this->proxyPath() . "/dynamic";
|
||||
if ($this->proxyType() === 'TRAEFIK_V2') {
|
||||
$file = "$dynamic_config_path/coolify.yaml";
|
||||
if (empty($settings->fqdn)) {
|
||||
instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $this);
|
||||
} else {
|
||||
$url = Url::fromString($settings->fqdn);
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'middlewares' => [
|
||||
'redirect-to-https' => [
|
||||
'redirectscheme' => [
|
||||
'scheme' => 'https',
|
||||
],
|
||||
],
|
||||
'gzip' => [
|
||||
'compress' => true,
|
||||
],
|
||||
],
|
||||
'routers' =>
|
||||
[
|
||||
'coolify-http' =>
|
||||
[
|
||||
'middlewares' => [
|
||||
0 => 'gzip',
|
||||
],
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
],
|
||||
'coolify-realtime-ws' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'coolify' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify:80',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'coolify-realtime' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify-realtime:6001',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($schema === 'https') {
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
|
||||
0 => 'redirect-to-https',
|
||||
];
|
||||
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-realtime-wss'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
}
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$yaml;
|
||||
|
||||
$base64 = base64_encode($yaml);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_config_path",
|
||||
"echo '$base64' | base64 -d > $file",
|
||||
], $this);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
// ray($yaml);
|
||||
}
|
||||
}
|
||||
} else if ($this->proxyType() === 'CADDY') {
|
||||
$file = "$dynamic_config_path/coolify.caddy";
|
||||
if (empty($settings->fqdn)) {
|
||||
instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $this);
|
||||
$this->reloadCaddy();
|
||||
} else {
|
||||
$url = Url::fromString($settings->fqdn);
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$caddy_file = "
|
||||
$schema://$host {
|
||||
handle /app/* {
|
||||
reverse_proxy coolify-realtime:6001
|
||||
}
|
||||
reverse_proxy coolify:80
|
||||
}";
|
||||
$base64 = base64_encode($caddy_file);
|
||||
instant_remote_process([
|
||||
"echo '$base64' | base64 -d > $file",
|
||||
], $this);
|
||||
$this->reloadCaddy();
|
||||
}
|
||||
}
|
||||
}
|
||||
public function reloadCaddy()
|
||||
{
|
||||
return instant_remote_process([
|
||||
"docker exec coolify-proxy caddy reload --config /config/caddy/Caddyfile.autosave",
|
||||
], $this);
|
||||
}
|
||||
public function proxyPath()
|
||||
{
|
||||
$base_path = config('coolify.base_config_path');
|
||||
$proxyType = $this->proxyType();
|
||||
$proxy_path = "$base_path/proxy";
|
||||
// TODO: should use /traefik for already exisiting configurations?
|
||||
// Should move everything except /caddy and /nginx to /traefik
|
||||
// The code needs to be modified as well, so maybe it does not worth it
|
||||
if ($proxyType === ProxyTypes::TRAEFIK_V2->value) {
|
||||
$proxy_path = $proxy_path;
|
||||
} else if ($proxyType === ProxyTypes::CADDY->value) {
|
||||
$proxy_path = $proxy_path . '/caddy';
|
||||
} else if ($proxyType === ProxyTypes::NGINX->value) {
|
||||
$proxy_path = $proxy_path . '/nginx';
|
||||
}
|
||||
return $proxy_path;
|
||||
}
|
||||
public function proxyType()
|
||||
{
|
||||
$proxyType = $this->proxy->get('type');
|
||||
if ($proxyType === ProxyTypes::NONE->value) {
|
||||
return $proxyType;
|
||||
}
|
||||
if (is_null($proxyType)) {
|
||||
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||
$this->proxy->status = ProxyStatus::EXITED->value;
|
||||
$this->save();
|
||||
}
|
||||
return $this->proxy->get('type');
|
||||
// $proxyType = $this->proxy->get('type');
|
||||
// if ($proxyType === ProxyTypes::NONE->value) {
|
||||
// return $proxyType;
|
||||
// }
|
||||
// if (is_null($proxyType)) {
|
||||
// $this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||
// $this->proxy->status = ProxyStatus::EXITED->value;
|
||||
// $this->save();
|
||||
// }
|
||||
return data_get($this->proxy, 'type');
|
||||
}
|
||||
public function scopeWithProxy(): Builder
|
||||
{
|
||||
@@ -263,17 +550,21 @@ class Server extends BaseModel
|
||||
}
|
||||
public function loadUnmanagedContainers()
|
||||
{
|
||||
$containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.managed")) {
|
||||
return $container;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
return collect($containers);
|
||||
if ($this->isFunctional()) {
|
||||
$containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.managed")) {
|
||||
return $container;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
return collect($containers);
|
||||
} else {
|
||||
return collect([]);
|
||||
}
|
||||
}
|
||||
public function hasDefinedResources()
|
||||
{
|
||||
@@ -361,8 +652,9 @@ class Server extends BaseModel
|
||||
{
|
||||
$standalone_docker = $this->hasMany(StandaloneDocker::class)->get();
|
||||
$swarm_docker = $this->hasMany(SwarmDocker::class)->get();
|
||||
$asd = $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')->withPivot('server_id')->get();
|
||||
return $standalone_docker->concat($swarm_docker)->concat($asd);
|
||||
// $additional_dockers = $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')->withPivot('server_id')->get();
|
||||
// return $standalone_docker->concat($swarm_docker)->concat($additional_dockers);
|
||||
return $standalone_docker->concat($swarm_docker);
|
||||
}
|
||||
|
||||
public function standaloneDockers()
|
||||
|
||||
@@ -102,6 +102,79 @@ class Service extends BaseModel
|
||||
foreach ($applications as $application) {
|
||||
$image = str($application->image)->before(':')->value();
|
||||
switch ($image) {
|
||||
case str($image)?->contains('grafana'):
|
||||
$data = collect([]);
|
||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first();
|
||||
$data = $data->merge([
|
||||
'Admin User' => [
|
||||
'key' => 'GF_SECURITY_ADMIN_USER',
|
||||
'value' => 'admin',
|
||||
'readonly' => true,
|
||||
'rules' => 'required',
|
||||
],
|
||||
]);
|
||||
if ($admin_password) {
|
||||
$data = $data->merge([
|
||||
'Admin Password' => [
|
||||
'key' => 'GF_SECURITY_ADMIN_PASSWORD',
|
||||
'value' => data_get($admin_password, 'value'),
|
||||
'rules' => 'required',
|
||||
'isPassword' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Grafana', $data);
|
||||
break;
|
||||
case str($image)?->contains('directus'):
|
||||
$data = collect([]);
|
||||
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
|
||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
|
||||
|
||||
if ($admin_email) {
|
||||
$data = $data->merge([
|
||||
'Admin Email' => [
|
||||
'key' => data_get($admin_email, 'key'),
|
||||
'value' => data_get($admin_email, 'value'),
|
||||
'rules' => 'required|email',
|
||||
],
|
||||
]);
|
||||
}
|
||||
if ($admin_password) {
|
||||
$data = $data->merge([
|
||||
'Admin Password' => [
|
||||
'key' => data_get($admin_password, 'key'),
|
||||
'value' => data_get($admin_password, 'value'),
|
||||
'rules' => 'required',
|
||||
'isPassword' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Directus', $data);
|
||||
break;
|
||||
case str($image)?->contains('kong'):
|
||||
$data = collect([]);
|
||||
$dashboard_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
|
||||
$dashboard_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
|
||||
if ($dashboard_user) {
|
||||
$data = $data->merge([
|
||||
'Dashboard User' => [
|
||||
'key' => data_get($dashboard_user, 'key'),
|
||||
'value' => data_get($dashboard_user, 'value'),
|
||||
'rules' => 'required',
|
||||
],
|
||||
]);
|
||||
}
|
||||
if ($dashboard_password) {
|
||||
$data = $data->merge([
|
||||
'Dashboard Password' => [
|
||||
'key' => data_get($dashboard_password, 'key'),
|
||||
'value' => data_get($dashboard_password, 'value'),
|
||||
'rules' => 'required',
|
||||
'isPassword' => true,
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Supabase', $data->toArray());
|
||||
case str($image)?->contains('minio'):
|
||||
$data = collect([]);
|
||||
$console_url = $this->environment_variables()->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first();
|
||||
|
||||
@@ -23,6 +23,10 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
}
|
||||
public function isStripprefixEnabled()
|
||||
{
|
||||
return data_get($this, 'is_stripprefix_enabled', true);
|
||||
}
|
||||
public function isGzipEnabled()
|
||||
{
|
||||
return data_get($this, 'is_gzip_enabled', true);
|
||||
|
||||
@@ -21,9 +21,13 @@ class ServiceDatabase extends BaseModel
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
}
|
||||
public function isStripprefixEnabled()
|
||||
{
|
||||
return data_get($this, 'is_stripprefix_enabled', true);
|
||||
}
|
||||
public function isGzipEnabled()
|
||||
{
|
||||
return true;
|
||||
return data_get($this, 'is_gzip_enabled', true);
|
||||
}
|
||||
public function type()
|
||||
{
|
||||
|
||||
@@ -126,7 +126,7 @@ class StandaloneMariadb extends BaseModel
|
||||
);
|
||||
}
|
||||
|
||||
public function getDbUrl(bool $useInternal = false): string
|
||||
public function get_db_url(bool $useInternal = false): string
|
||||
{
|
||||
if ($this->is_public && !$useInternal) {
|
||||
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
|
||||
|
||||
@@ -142,7 +142,7 @@ class StandaloneMongodb extends BaseModel
|
||||
{
|
||||
return 'standalone-mongodb';
|
||||
}
|
||||
public function getDbUrl(bool $useInternal = false)
|
||||
public function get_db_url(bool $useInternal = false)
|
||||
{
|
||||
if ($this->is_public && !$useInternal) {
|
||||
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
|
||||
|
||||
@@ -127,7 +127,7 @@ class StandaloneMysql extends BaseModel
|
||||
);
|
||||
}
|
||||
|
||||
public function getDbUrl(bool $useInternal = false): string
|
||||
public function get_db_url(bool $useInternal = false): string
|
||||
{
|
||||
if ($this->is_public && !$useInternal) {
|
||||
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
|
||||
|
||||
@@ -126,7 +126,7 @@ class StandalonePostgresql extends BaseModel
|
||||
{
|
||||
return 'standalone-postgresql';
|
||||
}
|
||||
public function getDbUrl(bool $useInternal = false): string
|
||||
public function get_db_url(bool $useInternal = false): string
|
||||
{
|
||||
if ($this->is_public && !$useInternal) {
|
||||
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
|
||||
|
||||
@@ -122,7 +122,7 @@ class StandaloneRedis extends BaseModel
|
||||
{
|
||||
return 'standalone-redis';
|
||||
}
|
||||
public function getDbUrl(bool $useInternal = false): string
|
||||
public function get_db_url(bool $useInternal = false): string
|
||||
{
|
||||
if ($this->is_public && !$useInternal) {
|
||||
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||
|
||||
@@ -48,13 +48,15 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
}
|
||||
return explode(',', $recipients);
|
||||
}
|
||||
static public function serverLimitReached() {
|
||||
static public function serverLimitReached()
|
||||
{
|
||||
$serverLimit = Team::serverLimit();
|
||||
$team = currentTeam();
|
||||
$servers = $team->servers->count();
|
||||
return $servers >= $serverLimit;
|
||||
}
|
||||
public function serverOverflow() {
|
||||
public function serverOverflow()
|
||||
{
|
||||
if ($this->serverLimit() < $this->servers->count()) {
|
||||
return true;
|
||||
}
|
||||
@@ -62,6 +64,9 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
}
|
||||
static public function serverLimit()
|
||||
{
|
||||
if (currentTeam()->id === 0 && isDev()) {
|
||||
return 9999999;
|
||||
}
|
||||
return Team::find(currentTeam()->id)->limits['serverLimit'];
|
||||
}
|
||||
public function limits(): Attribute
|
||||
@@ -141,7 +146,6 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
$sources = collect([]);
|
||||
$github_apps = $this->hasMany(GithubApp::class)->whereisPublic(false)->get();
|
||||
$gitlab_apps = $this->hasMany(GitlabApp::class)->whereisPublic(false)->get();
|
||||
// $bitbucket_apps = $this->hasMany(BitbucketApp::class)->get();
|
||||
$sources = $sources->merge($github_apps)->merge($gitlab_apps);
|
||||
return $sources;
|
||||
}
|
||||
@@ -168,4 +172,17 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
]);
|
||||
}
|
||||
}
|
||||
public function isAnyNotificationEnabled()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return true;
|
||||
}
|
||||
if (!data_get(auth()->user(), 'is_notification_notifications_enabled')) {
|
||||
return true;
|
||||
}
|
||||
if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ class TeamInvitation extends Model
|
||||
return true;
|
||||
} else {
|
||||
$this->delete();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ class User extends Authenticatable implements SendsEmail
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
'two_factor_recovery_codes',
|
||||
'two_factor_secret',
|
||||
];
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime',
|
||||
@@ -50,6 +52,21 @@ class User extends Authenticatable implements SendsEmail
|
||||
$user->teams()->attach($new_team, ['role' => 'owner']);
|
||||
});
|
||||
}
|
||||
public function recreate_personal_team()
|
||||
{
|
||||
$team = [
|
||||
'name' => $this->name . "'s Team",
|
||||
'personal_team' => true,
|
||||
'show_boarding' => true
|
||||
];
|
||||
if ($this->id === 0) {
|
||||
$team['id'] = 0;
|
||||
$team['name'] = 'Root Team';
|
||||
}
|
||||
$new_team = Team::create($team);
|
||||
$this->teams()->attach($new_team, ['role' => 'owner']);
|
||||
return $new_team;
|
||||
}
|
||||
public function createToken(string $name, array $abilities = ['*'], DateTimeInterface $expiresAt = null)
|
||||
{
|
||||
$plainTextToken = sprintf(
|
||||
@@ -143,7 +160,7 @@ class User extends Authenticatable implements SendsEmail
|
||||
public function currentTeam()
|
||||
{
|
||||
return Cache::remember('team:' . auth()->user()->id, 3600, function () {
|
||||
if (is_null(data_get(session('currentTeam'), 'id'))) {
|
||||
if (is_null(data_get(session('currentTeam'), 'id')) && auth()->user()->teams->count() > 0){
|
||||
return auth()->user()->teams[0];
|
||||
}
|
||||
return Team::find(session('currentTeam')->id);
|
||||
|
||||
@@ -43,7 +43,13 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'deployments');
|
||||
$channels = setNotificationChannels($notifiable, 'deployments');
|
||||
if (isCloud()) {
|
||||
$channels = array_filter($channels, function ($channel) {
|
||||
return $channel !== 'App\Notifications\Channels\EmailChannel';
|
||||
});
|
||||
}
|
||||
return $channels;
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
@@ -69,7 +75,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
public function toDiscord(): string
|
||||
{
|
||||
if ($this->preview) {
|
||||
$message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '
|
||||
$message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '
|
||||
|
||||
';
|
||||
if ($this->preview->fqdn) {
|
||||
|
||||
50
app/Notifications/Database/DailyBackup.php
Normal file
50
app/Notifications/Database/DailyBackup.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Database;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Channels\MailChannel;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class DailyBackup extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public $databases)
|
||||
{
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return [DiscordChannel::class, TelegramChannel::class, MailChannel::class];
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("Coolify: Daily backup statuses");
|
||||
$mail->view('emails.daily-backup', [
|
||||
'databases' => $this->databases,
|
||||
]);
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
return "Coolify: Daily backup statuses";
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$message = "Coolify: Daily backup statuses";
|
||||
return [
|
||||
"message" => $message,
|
||||
];
|
||||
}
|
||||
}
|
||||
63
app/Notifications/Server/DockerCleanup.php
Normal file
63
app/Notifications/Server/DockerCleanup.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class DockerCleanup extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 1;
|
||||
public function __construct(public Server $server, public string $message)
|
||||
{
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
$channels = [];
|
||||
// $isEmailEnabled = isEmailEnabled($notifiable);
|
||||
$isDiscordEnabled = data_get($notifiable, 'discord_enabled');
|
||||
$isTelegramEnabled = data_get($notifiable, 'telegram_enabled');
|
||||
|
||||
if ($isDiscordEnabled) {
|
||||
$channels[] = DiscordChannel::class;
|
||||
}
|
||||
// if ($isEmailEnabled) {
|
||||
// $channels[] = EmailChannel::class;
|
||||
// }
|
||||
if ($isTelegramEnabled) {
|
||||
$channels[] = TelegramChannel::class;
|
||||
}
|
||||
return $channels;
|
||||
}
|
||||
|
||||
// public function toMail(): MailMessage
|
||||
// {
|
||||
// $mail = new MailMessage();
|
||||
// $mail->subject("Coolify: Server ({$this->server->name}) high disk usage detected!");
|
||||
// $mail->view('emails.high-disk-usage', [
|
||||
// 'name' => $this->server->name,
|
||||
// 'disk_usage' => $this->disk_usage,
|
||||
// 'threshold' => $this->cleanup_after_percentage,
|
||||
// ]);
|
||||
// return $mail;
|
||||
// }
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
"message" => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}"
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,26 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Listeners\MaintenanceModeDisabledNotification;
|
||||
use App\Listeners\MaintenanceModeEnabledNotification;
|
||||
use App\Listeners\ProxyStartedNotification;
|
||||
use Illuminate\Foundation\Events\MaintenanceModeDisabled;
|
||||
use Illuminate\Foundation\Events\MaintenanceModeEnabled;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected $listen = [
|
||||
// Registered::class => [
|
||||
// SendEmailVerificationNotification::class,
|
||||
// ],
|
||||
MaintenanceModeEnabled::class => [
|
||||
MaintenanceModeEnabledNotification::class,
|
||||
],
|
||||
MaintenanceModeDisabled::class => [
|
||||
MaintenanceModeDisabledNotification::class,
|
||||
],
|
||||
ProxyStarted::class => [
|
||||
ProxyStartedNotification::class,
|
||||
],
|
||||
];
|
||||
public function boot(): void
|
||||
{
|
||||
|
||||
@@ -8,14 +8,12 @@ use App\Actions\Fortify\UpdateUserPassword;
|
||||
use App\Actions\Fortify\UpdateUserProfileInformation;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use App\Models\Waitlist;
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Fortify\Contracts\RegisterResponse;
|
||||
use Laravel\Fortify\Features;
|
||||
use Laravel\Fortify\Fortify;
|
||||
|
||||
class FortifyServiceProvider extends ServiceProvider
|
||||
@@ -76,7 +74,11 @@ class FortifyServiceProvider extends ServiceProvider
|
||||
) {
|
||||
$user->updated_at = now();
|
||||
$user->save();
|
||||
session(['currentTeam' => $user->currentTeam = $user->teams->firstWhere('personal_team', true)]);
|
||||
$user->currentTeam = $user->teams->firstWhere('personal_team', true);
|
||||
if (!$user->currentTeam) {
|
||||
$user->currentTeam = $user->recreate_personal_team();
|
||||
}
|
||||
session(['currentTeam' => $user->currentTeam]);
|
||||
return $user;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -5,3 +5,7 @@ function get_team_id_from_token()
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
return data_get($token, 'team_id');
|
||||
}
|
||||
function invalid_token()
|
||||
{
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
@@ -8,18 +9,21 @@ use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
|
||||
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null, ?bool $includePullrequests = false): Collection
|
||||
{
|
||||
$containers = collect([]);
|
||||
if (!$server->isSwarm()) {
|
||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) use ($pullRequestId) {
|
||||
$containers = $containers->map(function ($container) use ($pullRequestId, $includePullrequests) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.pullRequestId=")) {
|
||||
data_set($container, 'Labels', $labels . ",coolify.pullRequestId={$pullRequestId}");
|
||||
return $container;
|
||||
}
|
||||
if ($includePullrequests) {
|
||||
return $container;
|
||||
}
|
||||
if (str($labels)->contains("coolify.pullRequestId=$pullRequestId")) {
|
||||
return $container;
|
||||
}
|
||||
@@ -212,7 +216,46 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
|
||||
}
|
||||
return $payload;
|
||||
}
|
||||
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?string $service_name = null)
|
||||
function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null)
|
||||
{
|
||||
$labels = collect([]);
|
||||
if ($serviceLabels) {
|
||||
$labels->push("caddy_ingress_network={$uuid}");
|
||||
} else {
|
||||
$labels->push("caddy_ingress_network={$network}");
|
||||
}
|
||||
foreach ($domains as $loop => $domain) {
|
||||
$loop = $loop;
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
// $stripped_path = str($path)->replaceEnd('/', '');
|
||||
|
||||
$schema = $url->getScheme();
|
||||
$port = $url->getPort();
|
||||
if (is_null($port) && !is_null($onlyPort)) {
|
||||
$port = $onlyPort;
|
||||
}
|
||||
$labels->push("caddy_{$loop}={$schema}://{$host}");
|
||||
$labels->push("caddy_{$loop}.header=-Server");
|
||||
$labels->push("caddy_{$loop}.try_files={path} /index.html /index.php");
|
||||
|
||||
if ($port) {
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams $port}}");
|
||||
} else {
|
||||
$labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams}}");
|
||||
}
|
||||
$labels->push("caddy_{$loop}.handle_path={$path}*");
|
||||
if ($is_gzip_enabled) {
|
||||
$labels->push("caddy_{$loop}.encode=zstd gzip");
|
||||
}
|
||||
if (isDev()) {
|
||||
// $labels->push("caddy_{$loop}.tls=internal");
|
||||
}
|
||||
}
|
||||
return $labels->sort();
|
||||
}
|
||||
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null, ?bool $is_gzip_enabled = true, ?bool $is_stripprefix_enabled = true, ?string $service_name = null)
|
||||
{
|
||||
$labels = collect([]);
|
||||
$labels->push('traefik.enable=true');
|
||||
@@ -278,8 +321,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$labels->push("traefik.http.services.{$https_label}.loadbalancer.server.port=$port");
|
||||
}
|
||||
if ($path !== '/') {
|
||||
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
$middlewares = collect(["{$https_label}-stripprefix"]);
|
||||
if ($is_stripprefix_enabled) {
|
||||
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
$middlewares = collect(["{$https_label}-stripprefix"]);
|
||||
}
|
||||
if ($is_gzip_enabled) {
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
@@ -331,8 +376,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
$labels->push("traefik.http.routers.{$http_label}.service={$http_label}");
|
||||
}
|
||||
if ($path !== '/') {
|
||||
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
$middlewares = collect(["{$http_label}-stripprefix"]);
|
||||
if ($is_stripprefix_enabled) {
|
||||
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||
$middlewares = collect(["{$http_label}-stripprefix"]);
|
||||
}
|
||||
if ($is_gzip_enabled) {
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
@@ -388,8 +435,25 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
} else {
|
||||
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||
}
|
||||
// Add Traefik labels no matter which proxy is selected
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik($appUuid, $domains, $application->settings->is_force_https_enabled, $onlyPort));
|
||||
// Add Traefik labels
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik(
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
// Add Caddy labels
|
||||
$labels = $labels->merge(fqdnLabelsForCaddy(
|
||||
network: $application->destination->network,
|
||||
uuid: $appUuid,
|
||||
domains: $domains,
|
||||
onlyPort: $onlyPort,
|
||||
is_force_https_enabled: $application->isForceHttpsEnabled(),
|
||||
is_gzip_enabled: $application->isGzipEnabled(),
|
||||
is_stripprefix_enabled: $application->isStripprefixEnabled()
|
||||
));
|
||||
}
|
||||
return $labels->all();
|
||||
}
|
||||
@@ -492,3 +556,24 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
}
|
||||
return $compose_options->toArray();
|
||||
}
|
||||
|
||||
function validateComposeFile(string $compose, int $server_id): string|Throwable {
|
||||
try {
|
||||
$uuid = Str::random(10);
|
||||
$server = Server::findOrFail($server_id);
|
||||
$base64_compose = base64_encode($compose);
|
||||
$output = instant_remote_process([
|
||||
"echo {$base64_compose} | base64 -d > /tmp/{$uuid}.yml",
|
||||
"docker compose -f /tmp/{$uuid}.yml config",
|
||||
], $server);
|
||||
ray($output);
|
||||
return 'OK';
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
return $e->getMessage();
|
||||
} finally {
|
||||
instant_remote_process([
|
||||
"rm /tmp/{$uuid}.yml",
|
||||
], $server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,7 @@ use App\Models\Server;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
function get_proxy_path()
|
||||
{
|
||||
$base_path = config('coolify.base_config_path');
|
||||
$proxy_path = "$base_path/proxy";
|
||||
return $proxy_path;
|
||||
}
|
||||
|
||||
function connectProxyToNetworks(Server $server)
|
||||
{
|
||||
if ($server->isSwarm()) {
|
||||
@@ -75,7 +70,9 @@ function connectProxyToNetworks(Server $server)
|
||||
}
|
||||
function generate_default_proxy_configuration(Server $server)
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$proxy_path = $server->proxyPath();
|
||||
$proxy_type = $server->proxyType();
|
||||
|
||||
if ($server->isSwarm()) {
|
||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
@@ -98,287 +95,126 @@ function generate_default_proxy_configuration(Server $server)
|
||||
"external" => true,
|
||||
];
|
||||
});
|
||||
$labels = [
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.traefik.entrypoints=http",
|
||||
"traefik.http.routers.traefik.service=api@internal",
|
||||
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
||||
"coolify.managed=true",
|
||||
];
|
||||
$config = [
|
||||
"version" => "3.8",
|
||||
"networks" => $array_of_networks->toArray(),
|
||||
"services" => [
|
||||
"traefik" => [
|
||||
"container_name" => "coolify-proxy",
|
||||
"image" => "traefik:v2.10",
|
||||
"restart" => RESTART_MODE,
|
||||
"extra_hosts" => [
|
||||
"host.docker.internal:host-gateway",
|
||||
if ($proxy_type === 'TRAEFIK_V2') {
|
||||
$labels = [
|
||||
"traefik.enable=true",
|
||||
"traefik.http.routers.traefik.entrypoints=http",
|
||||
"traefik.http.routers.traefik.service=api@internal",
|
||||
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
||||
"coolify.managed=true",
|
||||
];
|
||||
$config = [
|
||||
"version" => "3.8",
|
||||
"networks" => $array_of_networks->toArray(),
|
||||
"services" => [
|
||||
"traefik" => [
|
||||
"container_name" => "coolify-proxy",
|
||||
"image" => "traefik:v2.10",
|
||||
"restart" => RESTART_MODE,
|
||||
"extra_hosts" => [
|
||||
"host.docker.internal:host-gateway",
|
||||
],
|
||||
"networks" => $networks->toArray(),
|
||||
"ports" => [
|
||||
"80:80",
|
||||
"443:443",
|
||||
"8080:8080",
|
||||
],
|
||||
"healthcheck" => [
|
||||
"test" => "wget -qO- http://localhost:80/ping || exit 1",
|
||||
"interval" => "4s",
|
||||
"timeout" => "2s",
|
||||
"retries" => 5,
|
||||
],
|
||||
"volumes" => [
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro",
|
||||
"{$proxy_path}:/traefik",
|
||||
],
|
||||
"command" => [
|
||||
"--ping=true",
|
||||
"--ping.entrypoint=http",
|
||||
"--api.dashboard=true",
|
||||
"--api.insecure=false",
|
||||
"--entrypoints.http.address=:80",
|
||||
"--entrypoints.https.address=:443",
|
||||
"--entrypoints.http.http.encodequerysemicolons=true",
|
||||
"--entryPoints.http.http2.maxConcurrentStreams=50",
|
||||
"--entrypoints.https.http.encodequerysemicolons=true",
|
||||
"--entryPoints.https.http2.maxConcurrentStreams=50",
|
||||
"--providers.docker.exposedbydefault=false",
|
||||
"--providers.file.directory=/traefik/dynamic/",
|
||||
"--providers.file.watch=true",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge=true",
|
||||
"--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http",
|
||||
],
|
||||
"labels" => $labels,
|
||||
],
|
||||
"networks" => $networks->toArray(),
|
||||
"ports" => [
|
||||
"80:80",
|
||||
"443:443",
|
||||
"8080:8080",
|
||||
],
|
||||
"healthcheck" => [
|
||||
"test" => "wget -qO- http://localhost:80/ping || exit 1",
|
||||
"interval" => "4s",
|
||||
"timeout" => "2s",
|
||||
"retries" => 5,
|
||||
],
|
||||
"volumes" => [
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro",
|
||||
"{$proxy_path}:/traefik",
|
||||
],
|
||||
"command" => [
|
||||
"--ping=true",
|
||||
"--ping.entrypoint=http",
|
||||
"--api.dashboard=true",
|
||||
"--api.insecure=false",
|
||||
"--entrypoints.http.address=:80",
|
||||
"--entrypoints.https.address=:443",
|
||||
"--entrypoints.http.http.encodequerysemicolons=true",
|
||||
"--entryPoints.http.http2.maxConcurrentStreams=50",
|
||||
"--entrypoints.https.http.encodequerysemicolons=true",
|
||||
"--entryPoints.https.http2.maxConcurrentStreams=50",
|
||||
"--providers.docker.exposedbydefault=false",
|
||||
"--providers.file.directory=/traefik/dynamic/",
|
||||
"--providers.file.watch=true",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge=true",
|
||||
"--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json",
|
||||
"--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http",
|
||||
],
|
||||
"labels" => $labels,
|
||||
],
|
||||
],
|
||||
];
|
||||
if (isDev()) {
|
||||
// $config['services']['traefik']['command'][] = "--log.level=debug";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($config, 'services.traefik.container_name');
|
||||
data_forget($config, 'services.traefik.restart');
|
||||
data_forget($config, 'services.traefik.labels');
|
||||
];
|
||||
if (isDev()) {
|
||||
// $config['services']['traefik']['command'][] = "--log.level=debug";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
data_forget($config, 'services.traefik.container_name');
|
||||
data_forget($config, 'services.traefik.restart');
|
||||
data_forget($config, 'services.traefik.labels');
|
||||
|
||||
$config['services']['traefik']['command'][] = "--providers.docker.swarmMode=true";
|
||||
$config['services']['traefik']['deploy'] = [
|
||||
"labels" => $labels,
|
||||
"placement" => [
|
||||
"constraints" => [
|
||||
"node.role==manager",
|
||||
$config['services']['traefik']['command'][] = "--providers.docker.swarmMode=true";
|
||||
$config['services']['traefik']['deploy'] = [
|
||||
"labels" => $labels,
|
||||
"placement" => [
|
||||
"constraints" => [
|
||||
"node.role==manager",
|
||||
],
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$config['services']['traefik']['command'][] = "--providers.docker=true";
|
||||
}
|
||||
} else if ($proxy_type === 'CADDY') {
|
||||
$config = [
|
||||
"version" => "3.8",
|
||||
"networks" => $array_of_networks->toArray(),
|
||||
"services" => [
|
||||
"caddy" => [
|
||||
"container_name" => "coolify-proxy",
|
||||
"image" => "lucaslorentz/caddy-docker-proxy:2.8-alpine",
|
||||
"restart" => RESTART_MODE,
|
||||
"extra_hosts" => [
|
||||
"host.docker.internal:host-gateway",
|
||||
],
|
||||
"environment" => [
|
||||
"CADDY_DOCKER_POLLING_INTERVAL=5s",
|
||||
"CADDY_DOCKER_CADDYFILE_PATH=/dynamic/Caddyfile",
|
||||
],
|
||||
"networks" => $networks->toArray(),
|
||||
"ports" => [
|
||||
"80:80",
|
||||
"443:443",
|
||||
],
|
||||
// "healthcheck" => [
|
||||
// "test" => "wget -qO- http://localhost:80|| exit 1",
|
||||
// "interval" => "4s",
|
||||
// "timeout" => "2s",
|
||||
// "retries" => 5,
|
||||
// ],
|
||||
"volumes" => [
|
||||
"/var/run/docker.sock:/var/run/docker.sock:ro",
|
||||
"{$proxy_path}/dynamic:/dynamic",
|
||||
"{$proxy_path}/config:/config",
|
||||
"{$proxy_path}/data:/data",
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$config['services']['traefik']['command'][] = "--providers.docker=true";
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = Yaml::dump($config, 12, 2);
|
||||
SaveConfiguration::run($server, $config);
|
||||
return $config;
|
||||
}
|
||||
function setup_dynamic_configuration()
|
||||
{
|
||||
$dynamic_config_path = get_proxy_path() . "/dynamic";
|
||||
$settings = InstanceSettings::get();
|
||||
$server = Server::find(0);
|
||||
if ($server) {
|
||||
$file = "$dynamic_config_path/coolify.yaml";
|
||||
if (empty($settings->fqdn)) {
|
||||
instant_remote_process([
|
||||
"rm -f $file",
|
||||
], $server);
|
||||
} else {
|
||||
$url = Url::fromString($settings->fqdn);
|
||||
$host = $url->getHost();
|
||||
$schema = $url->getScheme();
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'middlewares' => [
|
||||
'redirect-to-https' => [
|
||||
'redirectscheme' => [
|
||||
'scheme' => 'https',
|
||||
],
|
||||
],
|
||||
'gzip' => [
|
||||
'compress' => true,
|
||||
],
|
||||
],
|
||||
'routers' =>
|
||||
[
|
||||
'coolify-http' =>
|
||||
[
|
||||
'middlewares' => [
|
||||
0 => 'gzip',
|
||||
],
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
],
|
||||
'coolify-realtime-ws' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'coolify' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify:80',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'coolify-realtime' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => 'http://coolify-realtime:6001',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($schema === 'https') {
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-http']['middlewares'] = [
|
||||
0 => 'redirect-to-https',
|
||||
];
|
||||
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-https'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify',
|
||||
'rule' => "Host(`{$host}`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
$traefik_dynamic_conf['http']['routers']['coolify-realtime-wss'] = [
|
||||
'entryPoints' => [
|
||||
0 => 'https',
|
||||
],
|
||||
'service' => 'coolify-realtime',
|
||||
'rule' => "Host(`{$host}`) && PathPrefix(`/app`)",
|
||||
'tls' => [
|
||||
'certresolver' => 'letsencrypt',
|
||||
],
|
||||
];
|
||||
}
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$yaml;
|
||||
|
||||
$base64 = base64_encode($yaml);
|
||||
instant_remote_process([
|
||||
"mkdir -p $dynamic_config_path",
|
||||
"echo '$base64' | base64 -d > $file",
|
||||
], $server);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
// ray($yaml);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
||||
{
|
||||
$traefik_dynamic_conf_path = get_proxy_path() . "/dynamic";
|
||||
$traefik_default_redirect_file = "$traefik_dynamic_conf_path/default_redirect_404.yaml";
|
||||
if (empty($redirect_url)) {
|
||||
instant_remote_process([
|
||||
"mkdir -p $traefik_dynamic_conf_path",
|
||||
"rm -f $traefik_default_redirect_file",
|
||||
], $server);
|
||||
} else {
|
||||
$traefik_dynamic_conf = [
|
||||
'http' =>
|
||||
[
|
||||
'routers' =>
|
||||
[
|
||||
'catchall' =>
|
||||
[
|
||||
'entryPoints' => [
|
||||
0 => 'http',
|
||||
1 => 'https',
|
||||
],
|
||||
'service' => 'noop',
|
||||
'rule' => "HostRegexp(`{catchall:.*}`)",
|
||||
'priority' => 1,
|
||||
'middlewares' => [
|
||||
0 => 'redirect-regexp@file',
|
||||
],
|
||||
],
|
||||
],
|
||||
'services' =>
|
||||
[
|
||||
'noop' =>
|
||||
[
|
||||
'loadBalancer' =>
|
||||
[
|
||||
'servers' =>
|
||||
[
|
||||
0 =>
|
||||
[
|
||||
'url' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'middlewares' =>
|
||||
[
|
||||
'redirect-regexp' =>
|
||||
[
|
||||
'redirectRegex' =>
|
||||
[
|
||||
'regex' => '(.*)',
|
||||
'replacement' => $redirect_url,
|
||||
'permanent' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$yaml = Yaml::dump($traefik_dynamic_conf, 12, 2);
|
||||
$yaml =
|
||||
"# This file is automatically generated by Coolify.\n" .
|
||||
"# Do not edit it manually (only if you know what are you doing).\n\n" .
|
||||
$yaml;
|
||||
|
||||
$base64 = base64_encode($yaml);
|
||||
instant_remote_process([
|
||||
"mkdir -p $traefik_dynamic_conf_path",
|
||||
"echo '$base64' | base64 -d > $traefik_default_redirect_file",
|
||||
], $server);
|
||||
|
||||
if (config('app.env') == 'local') {
|
||||
ray($yaml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,8 @@ function remote_process(
|
||||
?string $type_uuid = null,
|
||||
?Model $model = null,
|
||||
bool $ignore_errors = false,
|
||||
$callEventOnFinish = null
|
||||
$callEventOnFinish = null,
|
||||
$callEventData = null
|
||||
): Activity {
|
||||
if (is_null($type)) {
|
||||
$type = ActivityTypes::INLINE->value;
|
||||
@@ -50,6 +51,7 @@ function remote_process(
|
||||
model: $model,
|
||||
ignore_errors: $ignore_errors,
|
||||
call_event_on_finish: $callEventOnFinish,
|
||||
call_event_data: $callEventData,
|
||||
),
|
||||
])();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user