Merge branch 'next' into es-translation

This commit is contained in:
Andras Bacsai
2024-06-19 16:07:06 +02:00
committed by GitHub
556 changed files with 7636 additions and 3843 deletions

View File

@@ -0,0 +1,25 @@
name: Fix PHP code style issues
on: [push]
permissions:
contents: write
jobs:
php-code-styling:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Fix PHP code style issues
uses: aglipanci/laravel-pint-action@2.4
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Fix styling

View File

@@ -5,13 +5,13 @@
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
It helps you to manage your servers, applications, databases on your own hardware, all you need is SSH connection. You can manage VPS, Bare Metal, Raspberry PI's anything.
It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else.
Imagine if you could have the ease of a cloud but with your own servers. That is **Coolify**.
Imagine having the ease of a cloud but with your own servers. That is **Coolify**.
No vendor lock-in, which means that all the configuration for your applications/databases/etc are saved to your server. So if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You just lose the automations and all the magic. 🪄️
No vendor lock-in, which means that all the configurations for your applications/databases/etc are saved to your server. So, if you decide to stop using Coolify (oh nooo), you could still manage your running resources. You lose the automations and all the magic. 🪄️
For more information, take a look at our landing page [here](https://coolify.io).
For more information, take a look at our landing page at [coolify.io](https://coolify.io).
# Installation
@@ -22,32 +22,40 @@ You can find the installation script source [here](./scripts/install.sh).
# Support
Contact us [here](https://coolify.io/docs/contact).
Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact).
# Donations
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
To stay completely free and open-source, with no feature behind the paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the project's future development.
https://coolify.io/sponsorships
[coolify.io/sponsorships](https://coolify.io/sponsorships)
Thank you so much!
Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
Special thanks to our biggest sponsors!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="150"/></a>
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="150"/></a>
<a href="https://bc.direct/?ref=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
<a href="https://www.quantcdn.io/?ref=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="150"/></a>
<a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
<a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a>
<a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="140"/></a>
<a href="https://fractalnetworks.co/?ref=coolify.io" target="_blank"><img src="./other/logos/fractal.svg" alt="fractal logo" width="180"/></a>
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
## Github Sponsors ($40+)
<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://serpapi.com/?utm_source=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></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://www.quantcdn.io/?utm_source=coolify.io"><img src="https://github.com/quantcdn.png" width="60px" alt="QuantCDN"/></a>
<a href="https://www.runpod.io/?utm_source=coolify.io">
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
<a href="https://typebot.io/?ref=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
<a href="https://www.runpod.io/?ref=coolify.io">
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
<a href="https://lightspeed.run/?utm_source=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://x.com/mrsmith9ja?utm_source=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://codext.link/coolify-io?ref=coolify.io"><img src="./other/logos/codext.jpg" width="60px" alt="Codext" /></a>
<a href="https://x.com/mrsmith9ja?ref=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
<a href="https://www.uxwizz.com/?ref=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
@@ -79,9 +87,9 @@ Special thanks to our biggest sponsor, [CCCareers](https://cccareers.org/)!
# Cloud
If you do not want to self-host Coolify, there is a paid cloud version available: https://app.coolify.io
If you do not want to self-host Coolify, there is a paid cloud version available: [app.coolify.io](https://app.coolify.io)
For more information & pricing, take a look at our landing page [here](https://coolify.io).
For more information & pricing, take a look at our landing page [coolify.io](https://coolify.io).
## Why should I use the Cloud version?
The recommended way to use Coolify is to have one server for Coolify and one (or more) for the resources you are deploying. A server is around 4-5$/month.
@@ -105,7 +113,7 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
</a>
</p>
<a href="https://www.producthunt.com/posts/coolify?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An&#0032;open&#0045;source&#0032;&#0038;&#0032;self&#0045;hostable&#0032;Heroku&#0044;&#0032;Netlify&#0032;alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://www.producthunt.com/posts/coolify?ref=badge-featured&utm_medium=badge&utm_souce=badge-coolify" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=338273&theme=light" alt="Coolify - An&#0032;open&#0045;source&#0032;&#0038;&#0032;self&#0045;hostable&#0032;Heroku&#0044;&#0032;Netlify&#0032;alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://trendshift.io/repositories/634" target="_blank"><img src="https://trendshift.io/api/badge/repositories/634" alt="coollabsio%2Fcoolify | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>

View File

@@ -3,17 +3,17 @@
namespace App\Actions\Application;
use App\Models\Application;
use App\Models\StandaloneDocker;
use App\Notifications\Application\StatusChanged;
use Lorisleiva\Actions\Concerns\AsAction;
class StopApplication
{
use AsAction;
public function handle(Application $application)
{
if ($application->destination->server->isSwarm()) {
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
return;
}

View File

@@ -9,6 +9,7 @@ use Lorisleiva\Actions\Concerns\AsAction;
class StopApplicationOneServer
{
use AsAction;
public function handle(Application $application, Server $server)
{
if ($application->destination->server->isSwarm()) {
@@ -32,6 +33,7 @@ class StopApplicationOneServer
}
} catch (\Exception $e) {
ray($e->getMessage());
return $e->getMessage();
}
}

View File

@@ -14,6 +14,7 @@ use Spatie\Activitylog\Models\Activity;
class PrepareCoolifyTask
{
protected Activity $activity;
protected CoolifyTaskArgs $remoteProcessArgs;
public function __construct(CoolifyTaskArgs $remoteProcessArgs)
@@ -28,12 +29,12 @@ class PrepareCoolifyTask
->withProperties($properties)
->performedOn($remoteProcessArgs->model)
->event($remoteProcessArgs->type)
->log("[]");
->log('[]');
} else {
$this->activity = activity()
->withProperties($remoteProcessArgs->toArray())
->event($remoteProcessArgs->type)
->log("[]");
->log('[]');
}
}
@@ -42,6 +43,7 @@ class PrepareCoolifyTask
$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;
}
}

View File

@@ -69,7 +69,7 @@ class RunRemoteProcess
return collect($decoded)
->sortBy(fn ($i) => $i['order'])
->map(fn ($i) => $i['output'])
->implode("");
->implode('');
}
public function __invoke(): ProcessResult
@@ -116,7 +116,7 @@ class RunRemoteProcess
try {
if ($this->call_event_data) {
event(resolve("App\\Events\\$this->call_event_on_finish", [
"data" => $this->call_event_data,
'data' => $this->call_event_data,
]));
} else {
event(resolve("App\\Events\\$this->call_event_on_finish", [
@@ -127,6 +127,7 @@ class RunRemoteProcess
ray($e);
}
}
return $processResult;
}
@@ -182,6 +183,7 @@ class RunRemoteProcess
if ($description === null || count($description) === 0) {
return 1;
}
return end($description)['order'] + 1;
}

View File

@@ -4,22 +4,23 @@ namespace App\Actions\Database;
use App\Models\StandaloneClickhouse;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartClickhouse
{
use AsAction;
public StandaloneClickhouse $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneClickhouse $database)
{
$this->database = $database;
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
@@ -57,7 +58,7 @@ class StartClickhouse
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
@@ -65,15 +66,15 @@ class StartClickhouse
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
@@ -82,10 +83,10 @@ class StartClickhouse
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
@@ -111,6 +112,7 @@ class StartClickhouse
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -125,6 +127,7 @@ class StartClickhouse
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
@@ -141,6 +144,7 @@ class StartClickhouse
'external' => false,
];
}
return $local_persistent_volumes_names;
}

View File

@@ -101,7 +101,7 @@ class StartDatabaseProxy
}
}
EOF;
$dockerfile = <<< EOF
$dockerfile = <<< 'EOF'
FROM nginx:stable-alpine
COPY nginx.conf /etc/nginx/nginx.conf
@@ -113,7 +113,7 @@ class StartDatabaseProxy
'context' => $configuration_dir,
'dockerfile' => 'Dockerfile',
],
'image' => "nginx:stable-alpine",
'image' => 'nginx:stable-alpine',
'container_name' => $proxyContainerName,
'restart' => RESTART_MODE,
'ports' => [
@@ -130,17 +130,17 @@ class StartDatabaseProxy
'interval' => '5s',
'timeout' => '5s',
'retries' => 3,
'start_period' => '1s'
'start_period' => '1s',
],
],
]
],
'networks' => [
$network => [
'external' => true,
'name' => $network,
'attachable' => true,
]
]
],
],
];
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf);

View File

@@ -3,19 +3,19 @@
namespace App\Actions\Database;
use App\Models\StandaloneDragonfly;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartDragonfly
{
use AsAction;
public StandaloneDragonfly $database;
public array $commands = [];
public string $configuration_dir;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneDragonfly $database)
{
@@ -48,7 +48,7 @@ class StartDragonfly
$this->database->destination->network,
],
'ulimits' => [
'memlock'=> '-1'
'memlock' => '-1',
],
'labels' => [
'coolify.managed' => 'true',
@@ -58,7 +58,7 @@ class StartDragonfly
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
@@ -66,15 +66,15 @@ class StartDragonfly
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
@@ -83,10 +83,10 @@ class StartDragonfly
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
@@ -112,6 +112,7 @@ class StartDragonfly
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -126,6 +127,7 @@ class StartDragonfly
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
@@ -142,6 +144,7 @@ class StartDragonfly
'external' => false,
];
}
return $local_persistent_volumes_names;
}

View File

@@ -5,17 +5,18 @@ namespace App\Actions\Database;
use App\Models\StandaloneKeydb;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartKeydb
{
use AsAction;
public StandaloneKeydb $database;
public array $commands = [];
public string $configuration_dir;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneKeydb $database)
{
@@ -56,7 +57,7 @@ class StartKeydb
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
@@ -64,15 +65,15 @@ class StartKeydb
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
@@ -81,10 +82,10 @@ class StartKeydb
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
@@ -119,6 +120,7 @@ class StartKeydb
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -133,6 +135,7 @@ class StartKeydb
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
@@ -149,6 +152,7 @@ class StartKeydb
'external' => false,
];
}
return $local_persistent_volumes_names;
}
@@ -165,6 +169,7 @@ class StartKeydb
return $environment_variables->all();
}
private function add_custom_keydb()
{
if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) {

View File

@@ -4,15 +4,17 @@ namespace App\Actions\Database;
use App\Models\StandaloneMariadb;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartMariadb
{
use AsAction;
public StandaloneMariadb $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneMariadb $database)
@@ -46,11 +48,11 @@ class StartMariadb
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"],
'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
@@ -58,15 +60,15 @@ class StartMariadb
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
@@ -75,10 +77,10 @@ class StartMariadb
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
@@ -112,6 +114,7 @@ class StartMariadb
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -126,6 +129,7 @@ class StartMariadb
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
@@ -142,6 +146,7 @@ class StartMariadb
'external' => false,
];
}
return $local_persistent_volumes_names;
}
@@ -166,8 +171,10 @@ class StartMariadb
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
}
return $environment_variables->all();
}
private function add_custom_mysql()
{
if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) {

View File

@@ -4,22 +4,24 @@ namespace App\Actions\Database;
use App\Models\StandaloneMongodb;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartMongodb
{
use AsAction;
public StandaloneMongodb $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneMongodb $database)
{
$this->database = $database;
$startCommand = "mongod";
$startCommand = 'mongod';
$container_name = $this->database->uuid;
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
@@ -51,14 +53,14 @@ class StartMongodb
],
'healthcheck' => [
'test' => [
"CMD",
"echo",
"ok"
'CMD',
'echo',
'ok',
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
@@ -66,15 +68,15 @@ class StartMongodb
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
@@ -83,10 +85,10 @@ class StartMongodb
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
@@ -129,6 +131,7 @@ class StartMongodb
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -143,6 +146,7 @@ class StartMongodb
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
@@ -159,6 +163,7 @@ class StartMongodb
'external' => false,
];
}
return $local_persistent_volumes_names;
}
@@ -180,8 +185,10 @@ class StartMongodb
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
}
return $environment_variables->all();
}
private function add_custom_mongo_conf()
{
if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) {
@@ -192,6 +199,7 @@ class StartMongodb
$content_base64 = base64_encode($content);
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
}
private function add_default_database()
{
$content = "db = db.getSiblingDB(\"{$this->database->mongo_initdb_database}\");db.createCollection('init_collection');db.createUser({user: \"{$this->database->mongo_initdb_root_username}\", pwd: \"{$this->database->mongo_initdb_root_password}\",roles: [{role:\"readWrite\",db:\"{$this->database->mongo_initdb_database}\"}]});";

View File

@@ -4,15 +4,17 @@ namespace App\Actions\Database;
use App\Models\StandaloneMysql;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartMysql
{
use AsAction;
public StandaloneMysql $database;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneMysql $database)
@@ -46,11 +48,11 @@ class StartMysql
'coolify.managed' => 'true',
],
'healthcheck' => [
'test' => ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p{$this->database->mysql_root_password}"],
'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
@@ -58,15 +60,15 @@ class StartMysql
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
@@ -75,10 +77,10 @@ class StartMysql
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
@@ -112,6 +114,7 @@ class StartMysql
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -126,6 +129,7 @@ class StartMysql
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
@@ -142,6 +146,7 @@ class StartMysql
'external' => false,
];
}
return $local_persistent_volumes_names;
}
@@ -166,8 +171,10 @@ class StartMysql
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
}
return $environment_variables->all();
}
private function add_custom_mysql()
{
if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) {

View File

@@ -4,16 +4,19 @@ namespace App\Actions\Database;
use App\Models\StandalonePostgresql;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartPostgresql
{
use AsAction;
public StandalonePostgresql $database;
public array $commands = [];
public array $init_scripts = [];
public string $configuration_dir;
public function handle(StandalonePostgresql $database)
@@ -25,7 +28,7 @@ class StartPostgresql
$this->commands = [
"echo 'Starting {$database->name}.'",
"mkdir -p $this->configuration_dir",
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/"
"mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/",
];
$persistent_storages = $this->generate_local_persistent_volumes();
@@ -50,13 +53,13 @@ class StartPostgresql
],
'healthcheck' => [
'test' => [
"CMD-SHELL",
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1"
'CMD-SHELL',
"psql -U {$this->database->postgres_user} -d {$this->database->postgres_db} -c 'SELECT 1' || exit 1",
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
@@ -64,15 +67,15 @@ class StartPostgresql
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
@@ -81,10 +84,10 @@ class StartPostgresql
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
@@ -133,6 +136,7 @@ class StartPostgresql
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -147,6 +151,7 @@ class StartPostgresql
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
@@ -163,6 +168,7 @@ class StartPostgresql
'external' => false,
];
}
return $local_persistent_volumes_names;
}
@@ -187,6 +193,7 @@ class StartPostgresql
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
}
return $environment_variables->all();
}
@@ -203,6 +210,7 @@ class StartPostgresql
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
}
}
private function add_custom_conf()
{
if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) {

View File

@@ -5,17 +5,18 @@ namespace App\Actions\Database;
use App\Models\StandaloneRedis;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Symfony\Component\Yaml\Yaml;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartRedis
{
use AsAction;
public StandaloneRedis $database;
public array $commands = [];
public string $configuration_dir;
public array $commands = [];
public string $configuration_dir;
public function handle(StandaloneRedis $database)
{
@@ -55,12 +56,12 @@ class StartRedis
'test' => [
'CMD-SHELL',
'redis-cli',
'ping'
'ping',
],
'interval' => '5s',
'timeout' => '5s',
'retries' => 10,
'start_period' => '5s'
'start_period' => '5s',
],
'mem_limit' => $this->database->limits_memory,
'memswap_limit' => $this->database->limits_memory_swap,
@@ -68,15 +69,15 @@ class StartRedis
'mem_reservation' => $this->database->limits_memory_reservation,
'cpus' => (float) $this->database->limits_cpus,
'cpu_shares' => $this->database->limits_cpu_shares,
]
],
],
'networks' => [
$this->database->destination->network => [
'external' => true,
'name' => $this->database->destination->network,
'attachable' => true,
]
]
],
],
];
if (! is_null($this->database->limits_cpuset)) {
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
@@ -85,10 +86,10 @@ class StartRedis
$docker_compose['services'][$container_name]['logging'] = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => "tcp://127.0.0.1:24224",
'fluentd-async' => "true",
'fluentd-sub-second-precision' => "true",
]
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
if (count($this->database->ports_mappings_array) > 0) {
@@ -123,6 +124,7 @@ class StartRedis
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
$this->commands[] = "echo 'Database started.'";
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
}
@@ -137,6 +139,7 @@ class StartRedis
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
}
}
return $local_persistent_volumes;
}
@@ -153,6 +156,7 @@ class StartRedis
'external' => false,
];
}
return $local_persistent_volumes_names;
}
@@ -169,6 +173,7 @@ class StartRedis
return $environment_variables->all();
}
private function add_custom_redis()
{
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {

View File

@@ -2,6 +2,7 @@
namespace App\Actions\Database;
use App\Events\DatabaseStatusChanged;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly;
@@ -28,5 +29,6 @@ class StopDatabaseProxy
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
$database->is_public = false;
$database->save();
DatabaseStatusChanged::dispatch();
}
}

View File

@@ -17,7 +17,9 @@ use Lorisleiva\Actions\Concerns\AsAction;
class GetContainersStatus
{
use AsAction;
public $applications;
public $server;
public function handle(Server $server)
@@ -28,7 +30,7 @@ class GetContainersStatus
$this->server = $server;
if (! $this->server->isFunctional()) {
return 'Server is not ready.';
};
}
$this->applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($this->applications as $application) {
@@ -239,7 +241,7 @@ class GetContainersStatus
$environmentName = data_get($service, 'environment.name');
if ($projectUuid && $serviceUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
} else {
$url = null;
}
@@ -265,7 +267,7 @@ class GetContainersStatus
$environment = data_get($application, 'environment.name');
if ($projectUuid && $applicationUuid && $environment) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
} else {
$url = null;
}
@@ -290,7 +292,7 @@ class GetContainersStatus
$applicationUuid = data_get($preview, 'application.uuid');
if ($projectUuid && $applicationUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
} else {
$url = null;
}
@@ -315,7 +317,7 @@ class GetContainersStatus
$databaseUuid = data_get($database, 'uuid');
if ($projectUuid && $databaseUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
} else {
$url = null;
}
@@ -351,9 +353,11 @@ class GetContainersStatus
} catch (\Exception $e) {
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
ray($e->getMessage());
return handleError($e);
}
}
private function old_way()
{
if ($this->server->isSwarm()) {
@@ -361,7 +365,7 @@ class GetContainersStatus
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
} else {
// Precheck for containers
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
if (! $containers) {
return;
}
@@ -390,6 +394,7 @@ class GetContainersStatus
data_set($container, 'State.Health.Status', 'unhealthy');
}
}
return $container;
});
}
@@ -569,7 +574,7 @@ class GetContainersStatus
$environmentName = data_get($service, 'environment.name');
if ($projectUuid && $serviceUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/service/'.$serviceUuid;
} else {
$url = null;
}
@@ -595,7 +600,7 @@ class GetContainersStatus
$environment = data_get($application, 'environment.name');
if ($projectUuid && $applicationUuid && $environment) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
$url = base_url().'/project/'.$projectUuid.'/'.$environment.'/application/'.$applicationUuid;
} else {
$url = null;
}
@@ -620,7 +625,7 @@ class GetContainersStatus
$applicationUuid = data_get($preview, 'application.uuid');
if ($projectUuid && $applicationUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/application/'.$applicationUuid;
} else {
$url = null;
}
@@ -645,7 +650,7 @@ class GetContainersStatus
$databaseUuid = data_get($database, 'uuid');
if ($projectUuid && $databaseUuid && $environmentName) {
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
$url = base_url().'/project/'.$projectUuid.'/'.$environmentName.'/database/'.$databaseUuid;
} else {
$url = null;
}

View File

@@ -66,6 +66,7 @@ class CreateNewUser implements CreatesNewUsers
}
// Set session variable
session(['currentTeam' => $user->currentTeam = $team]);
return $user;
}
}

View File

@@ -6,10 +6,10 @@ use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http;
use Lorisleiva\Actions\Concerns\AsAction;
class CheckResaleLicense
{
use AsAction;
public function handle()
{
try {
@@ -18,6 +18,7 @@ class CheckResaleLicense
$settings->update([
'is_resale_license_active' => true,
]);
return;
}
// if (!$settings->resale_license) {
@@ -38,6 +39,7 @@ class CheckResaleLicense
$settings->update([
'is_resale_license_active' => true,
]);
return;
}
$data = Http::withHeaders([
@@ -51,6 +53,7 @@ class CheckResaleLicense
$settings->update([
'is_resale_license_active' => true,
]);
return;
}
if (data_get($data, 'license_key.status') === 'active') {

View File

@@ -2,13 +2,14 @@
namespace App\Actions\Proxy;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use Illuminate\Support\Str;
use Lorisleiva\Actions\Concerns\AsAction;
class CheckConfiguration
{
use AsAction;
public function handle(Server $server, bool $reset = false)
{
$proxyType = $server->proxyType();
@@ -26,8 +27,9 @@ class CheckConfiguration
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
}
if (! $proxy_configuration || is_null($proxy_configuration)) {
throw new \Exception("Could not generate proxy configuration");
throw new \Exception('Could not generate proxy configuration');
}
return $proxy_configuration;
}
}

View File

@@ -8,6 +8,7 @@ use Lorisleiva\Actions\Concerns\AsAction;
class CheckProxy
{
use AsAction;
public function handle(Server $server, $fromUI = false)
{
if (! $server->isFunctional()) {
@@ -18,6 +19,7 @@ class CheckProxy
$server->proxy = null;
$server->save();
}
return false;
}
$proxyType = $server->proxyType();
@@ -30,7 +32,7 @@ class CheckProxy
}
if (! $server->isProxyShouldRun()) {
if ($fromUI) {
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
throw new \Exception('Proxy should not run. You selected the Custom Proxy.');
} else {
return false;
}
@@ -42,12 +44,14 @@ class CheckProxy
if ($status === 'running') {
return false;
}
return true;
} else {
$status = getContainerStatus($server, 'coolify-proxy');
if ($status === 'running') {
$server->proxy->set('status', 'running');
$server->save();
return false;
}
if ($server->settings->is_cloudflare_tunnel) {
@@ -76,6 +80,7 @@ class CheckProxy
return false;
}
}
return true;
}
}

View File

@@ -11,6 +11,7 @@ use Spatie\Activitylog\Models\Activity;
class StartProxy
{
use AsAction;
public function handle(Server $server, bool $async = true): string|Activity
{
try {
@@ -22,7 +23,7 @@ class StartProxy
$proxy_path = $server->proxyPath();
$configuration = CheckConfiguration::run($server);
if (! $configuration) {
throw new \Exception("Configuration is not synced");
throw new \Exception('Configuration is not synced');
}
SaveConfiguration::run($server, $configuration);
$docker_compose_yml_base64 = base64_encode($configuration);
@@ -34,11 +35,11 @@ class StartProxy
"cd $proxy_path",
"echo 'Creating required Docker Compose file.'",
"echo 'Starting coolify-proxy.'",
"docker stack deploy -c docker-compose.yml coolify-proxy",
"echo 'Proxy started successfully.'"
'docker stack deploy -c docker-compose.yml coolify-proxy',
"echo 'Proxy started successfully.'",
]);
} else {
$caddfile = "import /dynamic/*.caddy";
$caddfile = 'import /dynamic/*.caddy';
$commands = $commands->merge([
"mkdir -p $proxy_path/dynamic",
"cd $proxy_path",
@@ -47,16 +48,17 @@ class StartProxy
"echo 'Pulling docker image.'",
'docker compose pull',
"echo 'Stopping existing coolify-proxy.'",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
'docker compose down -v --remove-orphans > /dev/null 2>&1',
"echo 'Starting coolify-proxy.'",
'docker compose up -d --remove-orphans',
"echo 'Proxy started successfully.'"
"echo 'Proxy started successfully.'",
]);
$commands = $commands->merge(connectProxyToNetworks($server));
}
if ($async) {
$activity = remote_process($commands, $server, callEventOnFinish: 'ProxyStarted', callEventData: $server);
return $activity;
} else {
instant_remote_process($commands, $server);
@@ -64,6 +66,7 @@ class StartProxy
$server->proxy->set('type', $proxyType);
$server->save();
ProxyStarted::dispatch($server);
return 'OK';
}
} catch (\Throwable $e) {

View File

@@ -2,12 +2,13 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class CleanupDocker
{
use AsAction;
public function handle(Server $server, bool $force = true)
{
if ($force) {

View File

@@ -9,18 +9,19 @@ use Symfony\Component\Yaml\Yaml;
class ConfigureCloudflared
{
use AsAction;
public function handle(Server $server, string $cloudflare_token)
{
try {
$config = [
"services" => [
"coolify-cloudflared" => [
"container_name" => "coolify-cloudflared",
"image" => "cloudflare/cloudflared:latest",
"restart" => RESTART_MODE,
"network_mode" => "host",
"command" => "tunnel run",
"environment" => [
'services' => [
'coolify-cloudflared' => [
'container_name' => 'coolify-cloudflared',
'image' => 'cloudflare/cloudflared:latest',
'restart' => RESTART_MODE,
'network_mode' => 'host',
'command' => 'tunnel run',
'environment' => [
"TUNNEL_TOKEN={$cloudflare_token}",
],
],
@@ -29,12 +30,12 @@ class ConfigureCloudflared
$config = Yaml::dump($config, 12, 2);
$docker_compose_yml_base64 = base64_encode($config);
$commands = collect([
"mkdir -p /tmp/cloudflared",
"cd /tmp/cloudflared",
'mkdir -p /tmp/cloudflared',
'cd /tmp/cloudflared',
"echo '$docker_compose_yml_base64' | base64 -d | tee docker-compose.yml > /dev/null",
"docker compose pull",
"docker compose down -v --remove-orphans > /dev/null 2>&1",
"docker compose up -d --remove-orphans",
'docker compose pull',
'docker compose down -v --remove-orphans > /dev/null 2>&1',
'docker compose up -d --remove-orphans',
]);
instant_remote_process($commands, $server);
} catch (\Throwable $e) {
@@ -42,7 +43,7 @@ class ConfigureCloudflared
throw $e;
} finally {
$commands = collect([
"rm -fr /tmp/cloudflared",
'rm -fr /tmp/cloudflared',
]);
instant_remote_process($commands, $server);
}

View File

@@ -2,13 +2,14 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Lorisleiva\Actions\Concerns\AsAction;
class InstallDocker
{
use AsAction;
public function handle(Server $server)
{
$supported_os_type = $server->validateOS();
@@ -36,40 +37,41 @@ class InstallDocker
if (isDev() && $server->id === 0) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"sleep 1",
'sleep 1',
"echo 'Installing Docker Engine...'",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"sleep 4",
'sleep 4',
"echo 'Restarting Docker Engine...'",
"ls -l /tmp"
'ls -l /tmp',
]);
return remote_process($command, $server);
} else {
if ($supported_os_type->contains('debian')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"apt-get update -y",
"command -v curl >/dev/null || apt install -y curl",
"command -v wget >/dev/null || apt install -y wget",
"command -v git >/dev/null || apt install -y git",
"command -v jq >/dev/null || apt install -y jq",
'apt-get update -y',
'command -v curl >/dev/null || apt install -y curl',
'command -v wget >/dev/null || apt install -y wget',
'command -v git >/dev/null || apt install -y git',
'command -v jq >/dev/null || apt install -y jq',
]);
} elseif ($supported_os_type->contains('rhel')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"command -v curl >/dev/null || dnf install -y curl",
"command -v wget >/dev/null || dnf install -y wget",
"command -v git >/dev/null || dnf install -y git",
"command -v jq >/dev/null || dnf install -y jq",
'command -v curl >/dev/null || dnf install -y curl',
'command -v wget >/dev/null || dnf install -y wget',
'command -v git >/dev/null || dnf install -y git',
'command -v jq >/dev/null || dnf install -y jq',
]);
} elseif ($supported_os_type->contains('sles')) {
$command = $command->merge([
"echo 'Installing Prerequisites...'",
"zypper update -y",
"command -v curl >/dev/null || zypper install -y curl",
"command -v wget >/dev/null || zypper install -y wget",
"command -v git >/dev/null || zypper install -y git",
"command -v jq >/dev/null || zypper install -y jq",
'zypper update -y',
'command -v curl >/dev/null || zypper install -y curl',
'command -v wget >/dev/null || zypper install -y wget',
'command -v git >/dev/null || zypper install -y git',
'command -v jq >/dev/null || zypper install -y jq',
]);
} else {
throw new \Exception('Unsupported OS');
@@ -78,29 +80,30 @@ class InstallDocker
"echo 'Installing Docker Engine...'",
"curl https://releases.rancher.com/install-docker/{$dockerVersion}.sh | sh || curl https://get.docker.com | sh -s -- --version {$dockerVersion}",
"echo 'Configuring Docker Engine (merging existing configuration with the required)...'",
"test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json \"/etc/docker/daemon.json.original-$(date +\"%Y%m%d-%H%M%S\")\"",
'test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json "/etc/docker/daemon.json.original-$(date +"%Y%m%d-%H%M%S")"',
"test ! -s /etc/docker/daemon.json && echo '{$config}' | base64 -d | tee /etc/docker/daemon.json > /dev/null",
"echo '{$config}' | base64 -d | tee /etc/docker/daemon.json.coolify > /dev/null",
"jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null",
"mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify",
'jq . /etc/docker/daemon.json.coolify | tee /etc/docker/daemon.json.coolify.pretty > /dev/null',
'mv /etc/docker/daemon.json.coolify.pretty /etc/docker/daemon.json.coolify',
"jq -s '.[0] * .[1]' /etc/docker/daemon.json.coolify /etc/docker/daemon.json | tee /etc/docker/daemon.json.appended > /dev/null",
"mv /etc/docker/daemon.json.appended /etc/docker/daemon.json",
'mv /etc/docker/daemon.json.appended /etc/docker/daemon.json',
"echo 'Restarting Docker Engine...'",
"systemctl enable docker >/dev/null 2>&1 || true",
"systemctl restart docker",
'systemctl enable docker >/dev/null 2>&1 || true',
'systemctl restart docker',
]);
if ($server->isSwarm()) {
$command = $command->merge([
"docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true",
'docker network create --attachable --driver overlay coolify-overlay >/dev/null 2>&1 || true',
]);
} else {
$command = $command->merge([
"docker network create --attachable coolify >/dev/null 2>&1 || true",
'docker network create --attachable coolify >/dev/null 2>&1 || true',
]);
$command = $command->merge([
"echo 'Done!'",
]);
}
return remote_process($command, $server);
}
}

View File

@@ -2,12 +2,13 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class InstallLogDrain
{
use AsAction;
public function handle(Server $server)
{
if ($server->settings->is_logdrain_newrelic_enabled) {
@@ -25,8 +26,9 @@ class InstallLogDrain
if ($type === 'none') {
$command = [
"echo 'Stopping old Fluent Bit'",
"docker rm -f coolify-log-drain || true",
'docker rm -f coolify-log-drain || true',
];
return instant_remote_process($command, $server);
} elseif ($type === 'newrelic') {
if (! $server->settings->is_logdrain_newrelic_enabled) {
@@ -63,7 +65,7 @@ class InstallLogDrain
if (! $server->settings->is_logdrain_highlight_enabled) {
throw new \Exception('Highlight log drain is not enabled.');
}
$config = base64_encode("
$config = base64_encode('
[SERVICE]
Flush 5
Daemon off
@@ -71,7 +73,7 @@ class InstallLogDrain
Parsers_File parsers.conf
[INPUT]
Name forward
tag \${HIGHLIGHT_PROJECT_ID}
tag ${HIGHLIGHT_PROJECT_ID}
Buffer_Chunk_Size 1M
Buffer_Max_Size 6M
[OUTPUT]
@@ -79,7 +81,7 @@ class InstallLogDrain
Match *
Host otel.highlight.io
Port 24224
");
');
} elseif ($type === 'axiom') {
if (! $server->settings->is_logdrain_axiom_enabled) {
throw new \Exception('Axiom log drain is not enabled.');
@@ -133,7 +135,7 @@ class InstallLogDrain
Regex /^(?!\s*$).+/
");
}
$compose = base64_encode("
$compose = base64_encode('
services:
coolify-log-drain:
image: cr.fluentbit.io/fluent/fluent-bit:2.0
@@ -147,7 +149,7 @@ services:
ports:
- 127.0.0.1:24224:24224
restart: unless-stopped
");
');
$readme = base64_encode('# New Relic Log Drain
This log drain is based on [Fluent Bit](https://fluentbit.io/) and New Relic Log Forwarder.
@@ -191,7 +193,7 @@ Files:
];
} elseif ($type === 'custom') {
$add_envs_command = [
"touch $config_path/.env"
"touch $config_path/.env",
];
} else {
throw new \Exception('Unknown log drain type.');
@@ -203,6 +205,7 @@ Files:
"cd $config_path && docker compose up -d --remove-orphans",
];
$command = array_merge($command, $add_envs_command, $restart_command);
return instant_remote_process($command, $server);
} catch (\Throwable $e) {
return handleError($e);

View File

@@ -2,21 +2,25 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class StartSentinel
{
use AsAction;
public function handle(Server $server, $version = 'latest', bool $restart = false)
{
if ($restart) {
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
StopSentinel::run($server);
}
$metrics_history = $server->settings->metrics_history_days;
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
$token = $server->settings->metrics_token;
instant_remote_process([
"docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
"chown -R 9999:root /data/coolify/metrics /data/coolify/logs",
"chmod -R 700 /data/coolify/metrics /data/coolify/logs"
"docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
], $server, false);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class StopSentinel
{
use AsAction;
public function handle(Server $server)
{
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
}
}

View File

@@ -2,15 +2,18 @@
namespace App\Actions\Server;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\InstanceSettings;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class UpdateCoolify
{
use AsAction;
public ?Server $server = null;
public ?string $latestVersion = null;
public ?string $currentVersion = null;
public function handle($manual_update = false)
@@ -38,9 +41,6 @@ class UpdateCoolify
}
$this->update();
} catch (\Throwable $e) {
ray('InstanceAutoUpdateJob failed');
ray($e->getMessage());
send_internal_notification('InstanceAutoUpdateJob failed: ' . $e->getMessage());
throw $e;
}
}
@@ -49,15 +49,15 @@ class UpdateCoolify
{
if (isDev()) {
remote_process([
"sleep 10"
'sleep 10',
], $this->server);
return;
}
remote_process([
"curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh",
"bash /data/coolify/source/upgrade.sh $this->latestVersion"
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
"bash /data/coolify/source/upgrade.sh $this->latestVersion",
], $this->server);
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
return;
}
}

View File

@@ -2,12 +2,13 @@
namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
class DeleteService
{
use AsAction;
public function handle(Service $service)
{
try {

View File

@@ -2,26 +2,27 @@
namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use Symfony\Component\Yaml\Yaml;
class StartService
{
use AsAction;
public function handle(Service $service)
{
ray('Starting service: '.$service->name);
$service->saveComposeConfigs();
$commands[] = "cd " . $service->workdir();
$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";
$commands[] = "echo Starting service.";
$commands[] = 'echo Starting service.';
$commands[] = "echo 'Pulling images.'";
$commands[] = "docker compose pull";
$commands[] = 'docker compose pull';
$commands[] = "echo 'Starting containers.'";
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
$commands[] = 'docker compose up -d --remove-orphans --force-recreate --build';
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
if (data_get($service, 'connect_to_docker_network')) {
$compose = data_get($service, 'docker_compose', []);
@@ -32,6 +33,7 @@ class StartService
}
}
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
return $activity;
}
}

View File

@@ -2,12 +2,13 @@
namespace App\Actions\Service;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Service;
use Lorisleiva\Actions\Concerns\AsAction;
class StopService
{
use AsAction;
public function handle(Service $service)
{
try {
@@ -33,6 +34,7 @@ class StopService
} catch (\Exception $e) {
echo $e->getMessage();
ray($e->getMessage());
return $e->getMessage();
}

View File

@@ -8,6 +8,7 @@ use Lorisleiva\Actions\Concerns\AsAction;
class ComplexStatusCheck
{
use AsAction;
public function handle(Application $application)
{
$servers = $application->additional_servers;
@@ -17,9 +18,11 @@ class ComplexStatusCheck
if (! $server->isFunctional()) {
if ($is_main_server) {
$application->update(['status' => 'exited:unhealthy']);
continue;
} else {
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
continue;
}
}
@@ -44,9 +47,11 @@ class ComplexStatusCheck
} else {
if ($is_main_server) {
$application->update(['status' => 'exited:unhealthy']);
continue;
} else {
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
continue;
}
}

View File

@@ -8,17 +8,20 @@ use Lorisleiva\Actions\Concerns\AsAction;
class PullImage
{
use AsAction;
public function handle(Service $resource)
{
$resource->saveComposeConfigs();
$commands[] = "cd " . $resource->workdir();
$commands[] = 'cd '.$resource->workdir();
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
$commands[] = "docker compose pull";
$commands[] = 'docker compose pull';
$server = data_get($resource, 'server');
if (!$server) return;
if (! $server) {
return;
}
instant_remote_process($commands, $resource->server);
}

View File

@@ -2,7 +2,6 @@
namespace App\Console\Commands;
use App\Models\Server;
use App\Models\User;
use Illuminate\Console\Command;
@@ -32,6 +31,7 @@ class AdminRemoveUser extends Command
$confirm = $this->confirm('Are you sure you want to remove user with email: '.$email.'?');
if (! $confirm) {
$this->info('User removal cancelled.');
return;
}
$this->info("Removing user with email: $email");
@@ -40,6 +40,7 @@ class AdminRemoveUser extends Command
foreach ($teams as $team) {
if ($team->members->count() > 1) {
$this->error('User is a member of a team with more than one member. Please remove user from team first.');
return;
}
$team->delete();
@@ -48,6 +49,7 @@ class AdminRemoveUser extends Command
} catch (\Exception $e) {
$this->error('Failed to remove user.');
$this->error($e->getMessage());
return;
}
}

View File

@@ -8,6 +8,7 @@ use Illuminate\Console\Command;
class CleanupApplicationDeploymentQueue extends Command
{
protected $signature = 'cleanup:application-deployment-queue {--team-id=}';
protected $description = 'CleanupApplicationDeploymentQueue';
public function handle()
@@ -15,7 +16,7 @@ class CleanupApplicationDeploymentQueue extends Command
$team_id = $this->option('team-id');
$servers = \App\Models\Server::where('team_id', $team_id)->get();
foreach ($servers as $server) {
$deployments = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->where("server_id", $server->id)->get();
$deployments = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->where('server_id', $server->id)->get();
foreach ($deployments as $deployment) {
$deployment->update(['status' => 'failed']);
instant_remote_process(['docker rm -f '.$deployment->deployment_uuid], $server, false);

View File

@@ -8,6 +8,7 @@ use Illuminate\Support\Facades\DB;
class CleanupDatabase extends Command
{
protected $signature = 'cleanup:database {--yes}';
protected $description = 'Cleanup database';
public function handle()

View File

@@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Redis;
class CleanupQueue extends Command
{
protected $signature = 'cleanup:queue';
protected $description = 'Cleanup Queue';
public function handle()

View File

@@ -20,6 +20,7 @@ use Illuminate\Console\Command;
class CleanupStuckedResources extends Command
{
protected $signature = 'cleanup:stucked-resources';
protected $description = 'Cleanup Stucked Resources';
public function handle()
@@ -28,6 +29,7 @@ class CleanupStuckedResources extends Command
echo "Running cleanup stucked resources.\n";
$this->cleanup_stucked_resources();
}
private function cleanup_stucked_resources()
{
@@ -158,16 +160,19 @@ class CleanupStuckedResources extends Command
if (! data_get($application, 'environment')) {
echo 'Application without environment: '.$application->name.'\n';
$application->forceDelete();
continue;
}
if (! $application->destination()) {
echo 'Application without destination: '.$application->name.'\n';
$application->forceDelete();
continue;
}
if (! data_get($application, 'destination.server')) {
echo 'Application without server: '.$application->name.'\n';
$application->forceDelete();
continue;
}
}
@@ -180,16 +185,19 @@ class CleanupStuckedResources extends Command
if (! data_get($postgresql, 'environment')) {
echo 'Postgresql without environment: '.$postgresql->name.'\n';
$postgresql->forceDelete();
continue;
}
if (! $postgresql->destination()) {
echo 'Postgresql without destination: '.$postgresql->name.'\n';
$postgresql->forceDelete();
continue;
}
if (! data_get($postgresql, 'destination.server')) {
echo 'Postgresql without server: '.$postgresql->name.'\n';
$postgresql->forceDelete();
continue;
}
}
@@ -202,16 +210,19 @@ class CleanupStuckedResources extends Command
if (! data_get($redis, 'environment')) {
echo 'Redis without environment: '.$redis->name.'\n';
$redis->forceDelete();
continue;
}
if (! $redis->destination()) {
echo 'Redis without destination: '.$redis->name.'\n';
$redis->forceDelete();
continue;
}
if (! data_get($redis, 'destination.server')) {
echo 'Redis without server: '.$redis->name.'\n';
$redis->forceDelete();
continue;
}
}
@@ -225,16 +236,19 @@ class CleanupStuckedResources extends Command
if (! data_get($mongodb, 'environment')) {
echo 'Mongodb without environment: '.$mongodb->name.'\n';
$mongodb->forceDelete();
continue;
}
if (! $mongodb->destination()) {
echo 'Mongodb without destination: '.$mongodb->name.'\n';
$mongodb->forceDelete();
continue;
}
if (! data_get($mongodb, 'destination.server')) {
echo 'Mongodb without server: '.$mongodb->name.'\n';
$mongodb->forceDelete();
continue;
}
}
@@ -248,16 +262,19 @@ class CleanupStuckedResources extends Command
if (! data_get($mysql, 'environment')) {
echo 'Mysql without environment: '.$mysql->name.'\n';
$mysql->forceDelete();
continue;
}
if (! $mysql->destination()) {
echo 'Mysql without destination: '.$mysql->name.'\n';
$mysql->forceDelete();
continue;
}
if (! data_get($mysql, 'destination.server')) {
echo 'Mysql without server: '.$mysql->name.'\n';
$mysql->forceDelete();
continue;
}
}
@@ -271,16 +288,19 @@ class CleanupStuckedResources extends Command
if (! data_get($mariadb, 'environment')) {
echo 'Mariadb without environment: '.$mariadb->name.'\n';
$mariadb->forceDelete();
continue;
}
if (! $mariadb->destination()) {
echo 'Mariadb without destination: '.$mariadb->name.'\n';
$mariadb->forceDelete();
continue;
}
if (! data_get($mariadb, 'destination.server')) {
echo 'Mariadb without server: '.$mariadb->name.'\n';
$mariadb->forceDelete();
continue;
}
}
@@ -294,16 +314,19 @@ class CleanupStuckedResources extends Command
if (! data_get($service, 'environment')) {
echo 'Service without environment: '.$service->name.'\n';
$service->forceDelete();
continue;
}
if (! $service->destination()) {
echo 'Service without destination: '.$service->name.'\n';
$service->forceDelete();
continue;
}
if (! data_get($service, 'server')) {
echo 'Service without server: '.$service->name.'\n';
$service->forceDelete();
continue;
}
}
@@ -316,6 +339,7 @@ class CleanupStuckedResources extends Command
if (! data_get($service, 'service')) {
echo 'ServiceApplication without service: '.$service->name.'\n';
$service->forceDelete();
continue;
}
}
@@ -328,6 +352,7 @@ class CleanupStuckedResources extends Command
if (! data_get($service, 'service')) {
echo 'ServiceDatabase without service: '.$service->name.'\n';
$service->forceDelete();
continue;
}
}

View File

@@ -8,6 +8,7 @@ use Illuminate\Console\Command;
class CleanupUnreachableServers extends Command
{
protected $signature = 'cleanup:unreachable-servers';
protected $description = 'Cleanup Unreachable Servers (7 days)';
public function handle()
@@ -19,7 +20,7 @@ class CleanupUnreachableServers extends Command
echo "Cleanup unreachable server ($server->id) with name $server->name";
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
$server->update([
'ip' => '1.2.3.4'
'ip' => '1.2.3.4',
]);
}
}

View File

@@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Process;
class Dev extends Command
{
protected $signature = 'dev:init';
protected $description = 'Init the app in dev mode';
public function handle()

View File

@@ -2,12 +2,10 @@
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;
@@ -49,7 +47,9 @@ class Emails extends Command
* Execute the console command.
*/
private ?MailMessage $mail = null;
private ?string $email = null;
public function handle()
{
$type = select(
@@ -73,7 +73,7 @@ class Emails extends Command
);
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
if (isDev()) {
$this->email = "test@example.com";
$this->email = 'test@example.com';
} else {
if (! in_array($type, $emailsGathered)) {
$this->email = text('Email Address to send to:');
@@ -82,12 +82,13 @@ class Emails extends Command
set_transanctional_email_settings();
$this->mail = new MailMessage();
$this->mail->subject("Test Email");
$this->mail->subject('Test Email');
switch ($type) {
case 'updates':
$teams = Team::all();
if (! $teams || $teams->isEmpty()) {
echo 'No teams found.'.PHP_EOL;
return;
}
$emails = [];
@@ -99,7 +100,7 @@ class Emails extends Command
}
}
$emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails.");
$this->info('Sending to '.count($emails).' emails.');
foreach ($emails as $email) {
$this->info($email);
}
@@ -111,7 +112,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);
}
}
@@ -246,6 +247,7 @@ class Emails extends Command
$teams = Team::doesntHave('subscription')->where('id', '!=', 0)->get();
if (! $teams || $teams->isEmpty()) {
echo 'No teams found.'.PHP_EOL;
return;
}
$emails = [];
@@ -257,7 +259,7 @@ class Emails extends Command
}
}
$emails = array_unique($emails);
$this->info("Sending to " . count($emails) . " emails.");
$this->info('Sending to '.count($emails).' emails.');
foreach ($emails as $email) {
$this->info($email);
}
@@ -296,7 +298,8 @@ class Emails extends Command
break;
}
}
private function sendEmail(string $email = null)
private function sendEmail(?string $email = null)
{
if ($email) {
$this->email = $email;

View File

@@ -7,7 +7,9 @@ use Illuminate\Console\Command;
class Horizon extends Command
{
protected $signature = 'start:horizon';
protected $description = 'Start Horizon';
public function handle()
{
if (config('coolify.is_horizon_enabled')) {

View File

@@ -15,6 +15,7 @@ use Illuminate\Support\Facades\Http;
class Init extends Command
{
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
protected $description = 'Cleanup instance related stuffs';
public function handle()
@@ -26,6 +27,7 @@ class Init extends Command
if ($cleanup_deployments) {
echo "Running cleanup deployments.\n";
$this->cleanup_in_progress_application_deployments();
return;
}
if ($full_cleanup) {
@@ -52,6 +54,7 @@ class Init extends Command
$settings->update(['is_auto_update_enabled' => false]);
}
}
return;
}
$this->cleanup_stucked_helper_containers();
@@ -82,6 +85,7 @@ class Init extends Command
echo "Error in restoring coolify db backup: {$e->getMessage()}\n";
}
}
private function cleanup_stucked_helper_containers()
{
$servers = Server::all();
@@ -91,6 +95,7 @@ class Init extends Command
}
}
}
private function alive()
{
$id = config('app.id');
@@ -99,6 +104,7 @@ class Init extends Command
$do_not_track = data_get($settings, 'do_not_track');
if ($do_not_track == true) {
echo "Skipping alive as do_not_track is enabled\n";
return;
}
try {

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use Illuminate\Console\Command;
use function Termwind\ask;
use function Termwind\render;
use function Termwind\style;
@@ -32,6 +33,7 @@ class NotifyDemo extends Command
if (blank($channel)) {
$this->showHelp();
return;
}

View File

@@ -35,6 +35,7 @@ class RootChangeEmail extends Command
$this->info('Root user\'s email updated successfully.');
} catch (\Exception $e) {
$this->error('Failed to update root user\'s email.');
return;
}
}

View File

@@ -34,6 +34,7 @@ class RootResetPassword extends Command
$passwordAgain = password('Again');
if ($password != $passwordAgain) {
$this->error('Passwords do not match.');
return;
}
$this->info('Updating root password...');
@@ -42,6 +43,7 @@ class RootResetPassword extends Command
$this->info('Root password updated successfully.');
} catch (\Exception $e) {
$this->error('Failed to update root password.');
return;
}
}

View File

@@ -7,7 +7,9 @@ use Illuminate\Console\Command;
class Scheduler extends Command
{
protected $signature = 'start:scheduler';
protected $description = 'Start Scheduler';
public function handle()
{
if (config('coolify.is_scheduler_enabled')) {

View File

@@ -48,11 +48,13 @@ class ServicesDelete extends Command
$this->deleteServer();
}
}
private function deleteServer()
{
$servers = Server::all();
if ($servers->count() === 0) {
$this->error('There are no applications to delete.');
return;
}
$serversToDelete = multiselect(
@@ -64,7 +66,7 @@ class ServicesDelete extends Command
$toDelete = $servers->where('id', $server)->first();
if ($toDelete) {
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
$confirmed = confirm('Are you sure you want to delete all selected resources?');
if (! $confirmed) {
break;
}
@@ -72,11 +74,13 @@ class ServicesDelete extends Command
}
}
}
private function deleteApplication()
{
$applications = Application::all();
if ($applications->count() === 0) {
$this->error('There are no applications to delete.');
return;
}
$applicationsToDelete = multiselect(
@@ -88,7 +92,7 @@ class ServicesDelete extends Command
$toDelete = $applications->where('id', $application)->first();
if ($toDelete) {
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
$confirmed = confirm('Are you sure you want to delete all selected resources? ');
if (! $confirmed) {
break;
}
@@ -96,11 +100,13 @@ class ServicesDelete extends Command
}
}
}
private function deleteDatabase()
{
$databases = StandalonePostgresql::all();
if ($databases->count() === 0) {
$this->error('There are no databases to delete.');
return;
}
$databasesToDelete = multiselect(
@@ -112,7 +118,7 @@ class ServicesDelete extends Command
$toDelete = $databases->where('id', $database)->first();
if ($toDelete) {
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
$confirmed = confirm('Are you sure you want to delete all selected resources?');
if (! $confirmed) {
return;
}
@@ -120,11 +126,13 @@ class ServicesDelete extends Command
}
}
}
private function deleteService()
{
$services = Service::all();
if ($services->count() === 0) {
$this->error('There are no services to delete.');
return;
}
$servicesToDelete = multiselect(
@@ -136,7 +144,7 @@ class ServicesDelete extends Command
$toDelete = $services->where('id', $service)->first();
if ($toDelete) {
$this->info($toDelete);
$confirmed = confirm("Are you sure you want to delete all selected resources?");
$confirmed = confirm('Are you sure you want to delete all selected resources?');
if (! $confirmed) {
return;
}

View File

@@ -26,7 +26,6 @@ class ServicesGenerate extends Command
*/
public function handle()
{
// ray()->clearAll();
$files = array_diff(scandir(base_path('templates/compose')), ['.', '..']);
$files = array_filter($files, function ($file) {
return strpos($file, '.yaml') !== false;
@@ -57,12 +56,14 @@ class ServicesGenerate extends Command
}
if ($ignore) {
$this->info("Ignoring $file");
return;
}
$this->info("Processing $file");
$documentation = collect(preg_grep('/^# documentation:/', explode("\n", $content)))->values();
if ($documentation->count() > 0) {
$documentation = str($documentation[0])->after('# documentation:')->trim()->value();
$documentation = str($documentation)->append('?utm_source=coolify.io');
} else {
$documentation = 'https://coolify.io/docs';
}
@@ -125,6 +126,7 @@ class ServicesGenerate extends Command
$env_file_base64 = base64_encode($env_file_content);
$payload['envs'] = $env_file_base64;
}
return $payload;
}
}

View File

@@ -33,30 +33,31 @@ class SyncBunny extends Command
$that = $this;
$only_template = $this->option('templates');
$only_version = $this->option('release');
$bunny_cdn = "https://cdn.coollabs.io";
$bunny_cdn_path = "coolify";
$bunny_cdn_storage_name = "coolcdn";
$bunny_cdn = 'https://cdn.coollabs.io';
$bunny_cdn_path = 'coolify';
$bunny_cdn_storage_name = 'coolcdn';
$parent_dir = realpath(dirname(__FILE__).'/../../..');
$compose_file = "docker-compose.yml";
$compose_file_prod = "docker-compose.prod.yml";
$install_script = "install.sh";
$upgrade_script = "upgrade.sh";
$production_env = ".env.production";
$service_template = "service-templates.json";
$compose_file = 'docker-compose.yml';
$compose_file_prod = 'docker-compose.prod.yml';
$install_script = 'install.sh';
$upgrade_script = 'upgrade.sh';
$production_env = '.env.production';
$service_template = 'service-templates.json';
$versions = "versions.json";
$versions = 'versions.json';
PendingRequest::macro('storage', function ($fileName) use ($that) {
$headers = [
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
'Accept' => 'application/json',
'Content-Type' => 'application/octet-stream'
'Content-Type' => 'application/octet-stream',
];
$fileStream = fopen($fileName, "r");
$fileStream = fopen($fileName, 'r');
$file = fread($fileStream, filesize($fileName));
$that->info('Uploading: '.$fileName);
return PendingRequest::baseUrl('https://storage.bunnycdn.com')->withHeaders($headers)->withBody($file)->throw();
});
PendingRequest::macro('purge', function ($url) use ($that) {
@@ -65,9 +66,10 @@ class SyncBunny extends Command
'Accept' => 'application/json',
];
$that->info('Purging: '.$url);
return PendingRequest::withHeaders($headers)->get('https://api.bunny.net/purge', [
"url" => $url,
"async" => false
'url' => $url,
'async' => false,
]);
});
try {
@@ -76,7 +78,7 @@ class SyncBunny extends Command
}
if ($only_template) {
$this->info('About to sync service-templates.json to BunnyCDN.');
$confirmed = confirm("Are you sure you want to sync?");
$confirmed = confirm('Are you sure you want to sync?');
if (! $confirmed) {
return;
}
@@ -85,6 +87,7 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
]);
$this->info('Service template uploaded & purged...');
return;
} elseif ($only_version) {
$this->info('About to sync versions.json to BunnyCDN.');
@@ -101,10 +104,10 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
]);
$this->info('versions.json uploaded & purged...');
return;
}
Http::pool(fn (Pool $pool) => [
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
@@ -119,9 +122,9 @@ class SyncBunny extends Command
$pool->purge("$bunny_cdn/$bunny_cdn_path/$upgrade_script"),
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
]);
$this->info("All files uploaded & purged...");
$this->info('All files uploaded & purged...');
} catch (\Throwable $e) {
$this->error("Error: " . $e->getMessage());
$this->error('Error: '.$e->getMessage());
}
}
}

View File

@@ -13,7 +13,9 @@ use Illuminate\Support\Str;
class WaitlistInvite extends Command
{
public Waitlist|User|null $next_patient = null;
public string|null $password = null;
public ?string $password = null;
/**
* The name and signature of the console command.
*
@@ -38,7 +40,9 @@ class WaitlistInvite extends Command
$this->main();
}
}
private function main() {
private function main()
{
if ($this->argument('email')) {
if ($this->option('only-email')) {
$this->next_patient = User::whereEmail($this->argument('email'))->first();
@@ -52,6 +56,7 @@ class WaitlistInvite extends Command
}
if (! $this->next_patient) {
$this->error("{$this->argument('email')} not found in the waitlist.");
return;
}
} else {
@@ -60,6 +65,7 @@ class WaitlistInvite extends Command
if ($this->next_patient) {
if ($this->option('only-email')) {
$this->send_email();
return;
}
$this->register_user();
@@ -69,6 +75,7 @@ class WaitlistInvite extends Command
$this->info('No verified user found in the waitlist. 👀');
}
}
private function register_user()
{
$already_registered = User::whereEmail($this->next_patient->email)->first();
@@ -85,11 +92,13 @@ class WaitlistInvite extends Command
throw new \Exception('User already registered');
}
}
private function remove_from_waitlist()
{
$this->next_patient->delete();
$this->info("User removed from waitlist successfully.");
$this->info('User removed from waitlist successfully.');
}
private function send_email()
{
$token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password");
@@ -100,6 +109,6 @@ class WaitlistInvite extends Command
]);
$mail->subject('Congratulations! You are invited to join Coolify Cloud.');
send_user_an_email($mail, $this->next_patient->email);
$this->info("Email sent successfully. 📧");
$this->info('Email sent successfully. 📧');
}
}

View File

@@ -4,17 +4,14 @@ namespace App\Console;
use App\Jobs\CheckLogDrainContainerJob;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\InstanceAutoUpdateJob;
use App\Jobs\ContainerStatusJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\PullCoolifyImageJob;
use App\Jobs\PullHelperImageJob;
use App\Jobs\PullSentinelImageJob;
use App\Jobs\PullTemplatesAndVersions;
use App\Jobs\PullTemplatesFromCDN;
use App\Jobs\PullVersionsFromCDN;
use App\Jobs\ScheduledTaskJob;
use App\Jobs\ServerStatusJob;
use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledTask;
use App\Models\Server;
@@ -25,6 +22,7 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
private $all_servers;
protected function schedule(Schedule $schedule): void
{
$this->all_servers = Server::all();
@@ -32,46 +30,44 @@ class Kernel extends ConsoleKernel
// Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
$schedule->job(new PullVersionsFromCDN)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->check_scheduled_backups($schedule);
// $this->pull_helper_image($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('uploads:clear')->everyTwoMinutes();
} else {
// Instance Jobs
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->command('cleanup:unreachable-servers')->daily();
$schedule->job(new PullVersionsFromCDN)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyTwoHours()->onOneServer();
$schedule->job(new PullCoolifyImageJob)->everyTenMinutes()->onOneServer();
$schedule->job(new PullTemplatesFromCDN)->everyThirtyMinutes()->onOneServer();
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
// Server Jobs
$this->instance_auto_update($schedule);
$this->check_scheduled_backups($schedule);
$this->check_resources($schedule);
$this->pull_helper_image($schedule);
$this->pull_images($schedule);
$this->check_scheduled_tasks($schedule);
$schedule->command('cleanup:database --yes')->daily();
$schedule->command('uploads:clear')->everyTwoMinutes();
}
}
private function pull_helper_image($schedule)
private function pull_images($schedule)
{
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) {
if (config('coolify.is_sentinel_enabled')) {
if ($server->isMetricsEnabled()) {
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
}
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
}
}
private function check_resources($schedule)
{
if (isCloud()) {
@@ -93,16 +89,7 @@ class Kernel extends ConsoleKernel
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
}
}
private function instance_auto_update($schedule)
{
if (isDev() || isCloud()) {
return;
}
$settings = InstanceSettings::get();
if ($settings->is_auto_update_enabled) {
$schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes()->onOneServer();
}
}
private function check_scheduled_backups($schedule)
{
$scheduled_backups = ScheduledDatabaseBackup::all();
@@ -116,6 +103,7 @@ class Kernel extends ConsoleKernel
if (is_null(data_get($scheduled_backup, 'database'))) {
ray('database not found');
$scheduled_backup->delete();
continue;
}
@@ -144,6 +132,7 @@ class Kernel extends ConsoleKernel
if (! $application && ! $service) {
ray('application/service attached to scheduled task does not exist');
$scheduled_task->delete();
continue;
}
if ($application) {

View File

@@ -11,6 +11,5 @@ class ServerMetadata extends Data
public function __construct(
public ?ProxyTypes $type,
public ?ProxyStatus $status
) {
}
) {}
}

View File

@@ -2,9 +2,7 @@
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class ApplicationStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception("Team id is null");
throw new \Exception('Team id is null');
}
$this->teamId = $teamId;
}

View File

@@ -2,9 +2,7 @@
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class BackupCreated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception("Team id is null");
throw new \Exception('Team id is null');
}
$this->teamId = $teamId;
}

View File

@@ -2,9 +2,7 @@
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class DatabaseStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $userId;
public function __construct($userId = null)
{
if (is_null($userId)) {
$userId = auth()->user()->id ?? null;
}
if (is_null($userId)) {
throw new \Exception("User id is null");
throw new \Exception('User id is null');
}
$this->userId = $userId;
}

View File

@@ -9,8 +9,6 @@ use Illuminate\Queue\SerializesModels;
class ProxyStarted
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public $data)
{
}
public function __construct(public $data) {}
}

View File

@@ -2,9 +2,7 @@
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class ProxyStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct($teamId = null)
{
if (is_null($teamId)) {
$teamId = auth()->user()->currentTeam()->id ?? null;
}
if (is_null($teamId)) {
throw new \Exception("Team id is null");
throw new \Exception('Team id is null');
}
$this->teamId = $teamId;
}

View File

@@ -2,9 +2,7 @@
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
@@ -13,14 +11,16 @@ use Illuminate\Queue\SerializesModels;
class ServiceStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $userId;
public function __construct($userId = null)
{
if (is_null($userId)) {
$userId = auth()->user()->id ?? null;
}
if (is_null($userId)) {
throw new \Exception("User id is null");
throw new \Exception('User id is null');
}
$this->userId = $userId;
}

View File

@@ -2,9 +2,7 @@
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
@@ -13,7 +11,9 @@ use Illuminate\Queue\SerializesModels;
class TestEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $teamId;
public function __construct()
{
$this->teamId = auth()->user()->currentTeam()->id;

View File

@@ -13,7 +13,6 @@ use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of exception types with their corresponding custom log levels.
*
@@ -22,14 +21,16 @@ class Handler extends ExceptionHandler
protected $levels = [
//
];
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<\Throwable>>
*/
protected $dontReport = [
ProcessException::class
ProcessException::class,
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
@@ -40,6 +41,7 @@ class Handler extends ExceptionHandler
'password',
'password_confirmation',
];
private InstanceSettings $settings;
protected function unauthenticated($request, AuthenticationException $exception)
@@ -47,8 +49,10 @@ class Handler extends ExceptionHandler
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
return response()->json(['message' => $exception->getMessage()], 401);
}
return redirect()->guest($exception->redirectTo() ?? route('login'));
}
/**
* Register the exception handling callbacks for the application.
*/
@@ -72,7 +76,7 @@ class Handler extends ExceptionHandler
$scope->setUser(
[
'email' => $email,
'instanceAdmin' => $instanceAdmin
'instanceAdmin' => $instanceAdmin,
]
);
}

View File

@@ -4,7 +4,4 @@ namespace App\Exceptions;
use Exception;
class ProcessException extends Exception
{
}
class ProcessException extends Exception {}

View File

@@ -27,18 +27,20 @@ class Deploy extends Controller
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"
$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();
@@ -57,8 +59,10 @@ class Deploy extends Controller
} elseif ($uuids) {
return $this->by_uuids($uuids, $teamId, $force);
}
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
private function by_uuids(string $uuid, int $teamId, bool $force = false)
{
$uuids = explode(',', $uuid);
@@ -82,10 +86,13 @@ class Deploy extends Controller
}
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-reference/deploy-webhook'], 404);
return response()->json(['error' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
}
public function by_tags(string $tags, int $team_id, bool $force = false)
{
$tags = explode(',', $tags);
@@ -107,6 +114,7 @@ class Deploy extends Controller
$services = $found_tag->services()->get();
if ($applications->count() === 0 && $services->count() === 0) {
$message->push("No resources found for tag {$tag}.");
continue;
}
foreach ($applications as $resource) {
@@ -127,11 +135,13 @@ class Deploy extends Controller
if ($deployments->count() > 0) {
$payload->put('details', $deployments->toArray());
}
return response()->json($payload->toArray(), 200);
}
return response()->json(['error' => "No resources found with this tag.", 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
return response()->json(['error' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
}
public function deploy_resource($resource, bool $force = false): array
{
$message = null;
@@ -200,6 +210,7 @@ class Deploy extends Controller
StartService::run($resource);
$message = "Service {$resource->name} started. It could take a while, be patient.";
}
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
}
}

View File

@@ -15,8 +15,10 @@ class Project extends Controller
return invalid_token();
}
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
return response()->json($projects);
}
public function project_by_uuid(Request $request)
{
$teamId = get_team_id_from_token();
@@ -24,8 +26,10 @@ class Project extends Controller
return invalid_token();
}
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
return response()->json($project);
}
public function environment_details(Request $request)
{
$teamId = get_team_id_from_token();
@@ -34,6 +38,7 @@ class Project extends Controller
}
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
return response()->json($environment);
}
}

View File

@@ -30,9 +30,10 @@ class Resources extends Controller
$payload['status'] = $resource->status;
}
$payload['type'] = $resource->type();
return $payload;
});
return response()->json($resources);
}
}

View File

@@ -17,10 +17,13 @@ class Server extends Controller
$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;
});
return response()->json($servers);
}
public function server_by_uuid(Request $request)
{
$with_resources = $request->query('resources');
@@ -47,11 +50,13 @@ class Server extends Controller
} else {
$payload['status'] = $resource->status;
}
return $payload;
});
} else {
$server->load(['settings']);
}
return response()->json($server);
}
}

View File

@@ -14,8 +14,10 @@ class Team extends Controller
return invalid_token();
}
$teams = auth()->user()->teams;
return response()->json($teams);
}
public function team_by_id(Request $request)
{
$id = $request->id;
@@ -26,10 +28,12 @@ class Team extends Controller
$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-reference/get-team-by-teamid"], 404);
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
}
return response()->json($team);
}
public function members_by_id(Request $request)
{
$id = $request->id;
@@ -40,10 +44,12 @@ class Team extends Controller
$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-reference/get-team-by-teamid-members"], 404);
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
}
return response()->json($team->members);
}
public function current_team(Request $request)
{
$teamId = get_team_id_from_token();
@@ -51,8 +57,10 @@ class Team extends Controller
return invalid_token();
}
$team = auth()->user()->currentTeam();
return response()->json($team);
}
public function current_team_members(Request $request)
{
$teamId = get_team_id_from_token();
@@ -60,6 +68,7 @@ class Team extends Controller
return invalid_token();
}
$team = auth()->user()->currentTeam();
return response()->json($team->members);
}
}

View File

@@ -14,33 +14,42 @@ use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Laravel\Fortify\Fortify;
use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;
use Illuminate\Support\Facades\Password;
use Laravel\Fortify\Fortify;
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
public function realtime_test() {
public function realtime_test()
{
if (auth()->user()?->currentTeam()->id !== 0) {
return redirect(RouteServiceProvider::HOME);
}
TestEvent::dispatch();
return 'Look at your other tab.';
}
public function verify() {
public function verify()
{
return view('auth.verify-email');
}
public function email_verify(EmailVerificationRequest $request) {
public function email_verify(EmailVerificationRequest $request)
{
$request->fulfill();
$name = request()->user()?->name;
// send_internal_notification("User {$name} verified their email address.");
return redirect(RouteServiceProvider::HOME);
}
public function forgot_password(Request $request) {
public function forgot_password(Request $request)
{
if (is_transactional_emails_active()) {
$arrayOfRequest = $request->only(Fortify::email());
$request->merge([
@@ -60,10 +69,13 @@ class Controller extends BaseController
if ($status == Password::RESET_THROTTLED) {
return response('Already requested a password reset in the past minutes.', 400);
}
return app(FailedPasswordResetLinkRequestResponse::class, ['status' => $status]);
}
return response()->json(['message' => 'Transactional emails are not active'], 400);
}
public function link()
{
$token = request()->get('token');
@@ -90,9 +102,11 @@ class Controller extends BaseController
}
Auth::login($user);
session(['currentTeam' => $team]);
return redirect()->route('dashboard');
}
}
return redirect()->route('login')->with('error', 'Invalid credentials.');
}
@@ -108,11 +122,12 @@ class Controller extends BaseController
if ($resetPassword) {
$user->update([
'password' => Hash::make($invitationUuid),
'force_password_reset' => true
'force_password_reset' => true,
]);
}
if ($user->teams()->where('team_id', $invitation->team->id)->exists()) {
$invitation->delete();
return redirect()->route('team.index');
}
$user->teams()->attach($invitation->team->id, ['role' => $invitation->role]);
@@ -121,6 +136,7 @@ class Controller extends BaseController
return redirect()->route('login');
}
refreshSession($invitation->team);
return redirect()->route('team.index');
} else {
abort(401);
@@ -143,6 +159,7 @@ class Controller extends BaseController
abort(401);
}
$invitation->delete();
return redirect()->route('team.index');
} catch (\Throwable $e) {
throw $e;

View File

@@ -12,21 +12,21 @@ class MagicController extends Controller
public function servers()
{
return response()->json([
'servers' => Server::isUsable()->get()
'servers' => Server::isUsable()->get(),
]);
}
public function destinations()
{
return response()->json([
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name')
'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name'),
]);
}
public function projects()
{
return response()->json([
'projects' => Project::ownedByCurrentTeam()->get()
'projects' => Project::ownedByCurrentTeam()->get(),
]);
}
@@ -35,11 +35,12 @@ class MagicController extends Controller
$project = Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first();
if (! $project) {
return response()->json([
'environments' => []
'environments' => [],
]);
}
return response()->json([
'environments' => $project->environments
'environments' => $project->environments,
]);
}
@@ -49,8 +50,9 @@ class MagicController extends Controller
['name' => request()->query('name') ?? generate_random_name()],
['team_id' => currentTeam()->id]
);
return response()->json([
'project_uuid' => $project->uuid
'project_uuid' => $project->uuid,
]);
}
@@ -60,6 +62,7 @@ class MagicController extends Controller
['name' => request()->query('name') ?? generate_random_name()],
['project_id' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->firstOrFail()->id]
);
return response()->json([
'environment_name' => $environment->name,
]);
@@ -75,6 +78,7 @@ class MagicController extends Controller
);
auth()->user()->teams()->attach($team, ['role' => 'admin']);
refreshSession();
return redirect(request()->header('Referer'));
}
}

View File

@@ -2,15 +2,15 @@
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class OauthController extends Controller {
class OauthController extends Controller
{
public function redirect(string $provider)
{
$socialite_provider = get_socialite_provider($provider);
return $socialite_provider->redirect();
}
@@ -26,9 +26,11 @@ class OauthController extends Controller {
]);
}
Auth::login($user);
return redirect('/');
} catch (\Exception $e) {
ray($e->getMessage());
return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
}
}

View File

@@ -2,14 +2,11 @@
namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Http\JsonResponse;
use Pion\Laravel\ChunkUpload\Exceptions\UploadFailedException;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Storage;
use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException;
use Pion\Laravel\ChunkUpload\Handler\AbstractHandler;
use Pion\Laravel\ChunkUpload\Handler\HandlerFactory;
use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;
@@ -21,7 +18,7 @@ class UploadController extends BaseController
if (is_null($resource)) {
return response()->json(['error' => 'You do not have permission for this database'], 500);
}
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
$receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request));
if ($receiver->isUploaded() === false) {
throw new UploadMissingFileException();
@@ -34,9 +31,10 @@ class UploadController extends BaseController
}
$handler = $save->handler();
return response()->json([
"done" => $handler->getPercentageDone(),
'status' => true
'done' => $handler->getPercentageDone(),
'status' => true,
]);
}
// protected function saveFileToS3($file)
@@ -64,19 +62,20 @@ class UploadController extends BaseController
{
$mime = str_replace('/', '-', $file->getMimeType());
$filePath = "upload/{$resource->uuid}";
$finalPath = storage_path("app/" . $filePath);
$finalPath = storage_path('app/'.$filePath);
$file->move($finalPath, 'restore');
return response()->json([
'mime_type' => $mime
'mime_type' => $mime,
]);
}
protected function createFilename(UploadedFile $file)
{
$extension = $file->getClientOriginalExtension();
$filename = str_replace("." . $extension, "", $file->getClientOriginalName()); // Filename without extension
$filename = str_replace('.'.$extension, '', $file->getClientOriginalName()); // Filename without extension
$filename .= "_" . md5(time()) . "." . $extension;
$filename .= '_'.md5(time()).'.'.$extension;
return $filename;
}

View File

@@ -30,13 +30,14 @@ class Bitbucket extends Controller
];
$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', "");
$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([
@@ -76,7 +77,7 @@ class Bitbucket extends Controller
$webhook_secret = data_get($application, 'manual_webhook_secret_bitbucket');
$payload = $request->getContent();
list($algo, $hash) = explode('=', $x_bitbucket_token, 2);
[$algo, $hash] = explode('=', $x_bitbucket_token, 2);
$payloadHash = hash_hmac($algo, $payload, $webhook_secret);
if (! hash_equals($hash, $payloadHash) && ! isDev()) {
$return_payloads->push([
@@ -85,6 +86,7 @@ class Bitbucket extends Controller
'message' => 'Invalid signature.',
]);
ray('Invalid signature');
continue;
}
$isFunctional = $application->destination->server->isFunctional();
@@ -95,6 +97,7 @@ class Bitbucket extends Controller
'message' => 'Server is not functional.',
]);
ray('Server is not functional: '.$application->destination->server->name);
continue;
}
if ($x_bitbucket_event === 'repo:push') {
@@ -127,6 +130,16 @@ class Bitbucket extends Controller
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
$pr_app = ApplicationPreview::create([
'git_type' => 'bitbucket',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
'docker_compose_domains' => $application->docker_compose_domains,
]);
$pr_app->generate_preview_fqdn_compose();
} else {
ApplicationPreview::create([
'git_type' => 'bitbucket',
'application_id' => $application->id,
@@ -134,6 +147,7 @@ class Bitbucket extends Controller
'pull_request_html_url' => $pull_request_html_url,
]);
}
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
@@ -178,9 +192,11 @@ class Bitbucket extends Controller
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e);
return handleError($e);
}
}

View File

@@ -27,6 +27,7 @@ class Gitea extends Controller
})->first();
if ($gitea_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
@@ -41,6 +42,7 @@ class Gitea extends Controller
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitea::manual_{$x_gitea_delivery}", $json);
return;
}
$x_gitea_event = Str::lower($request->header('X-Gitea-Event'));
@@ -103,6 +105,7 @@ class Gitea extends Controller
'status' => 'failed',
'message' => 'Invalid signature.',
]);
continue;
}
$isFunctional = $application->destination->server->isFunctional();
@@ -112,6 +115,7 @@ class Gitea extends Controller
'status' => 'failed',
'message' => 'Server is not functional.',
]);
continue;
}
if ($x_gitea_event === 'push') {
@@ -161,6 +165,16 @@ class Gitea extends Controller
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
$pr_app = ApplicationPreview::create([
'git_type' => 'gitea',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
'docker_compose_domains' => $application->docker_compose_domains,
]);
$pr_app->generate_preview_fqdn_compose();
} else {
ApplicationPreview::create([
'git_type' => 'gitea',
'application_id' => $application->id,
@@ -168,6 +182,8 @@ class Gitea extends Controller
'pull_request_html_url' => $pull_request_html_url,
]);
}
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
@@ -213,9 +229,11 @@ class Gitea extends Controller
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}

View File

@@ -33,6 +33,7 @@ class Github extends Controller
})->first();
if ($github_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
@@ -47,6 +48,7 @@ class Github extends Controller
];
$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'));
@@ -108,6 +110,7 @@ class Github extends Controller
'status' => 'failed',
'message' => 'Invalid signature.',
]);
continue;
}
$isFunctional = $application->destination->server->isFunctional();
@@ -117,6 +120,7 @@ class Github extends Controller
'status' => 'failed',
'message' => 'Server is not functional.',
]);
continue;
}
if ($x_github_event === 'push') {
@@ -166,6 +170,16 @@ class Github extends Controller
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
$pr_app = ApplicationPreview::create([
'git_type' => 'github',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
'docker_compose_domains' => $application->docker_compose_domains,
]);
$pr_app->generate_preview_fqdn_compose();
} else {
ApplicationPreview::create([
'git_type' => 'github',
'application_id' => $application->id,
@@ -173,6 +187,7 @@ class Github extends Controller
'pull_request_html_url' => $pull_request_html_url,
]);
}
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
@@ -218,12 +233,15 @@ class Github extends Controller
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
public function normal(Request $request)
{
try {
@@ -239,6 +257,7 @@ class Github extends Controller
})->first();
if ($github_delivery_found) {
ray('Webhook already found');
return;
}
$data = [
@@ -253,6 +272,7 @@ class Github extends Controller
];
$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'));
@@ -280,6 +300,7 @@ class Github extends Controller
if ($action === 'new_permissions_accepted') {
GithubAppPermissionJob::dispatch($github_app);
}
return response('cool');
}
if ($x_github_event === 'push') {
@@ -329,6 +350,7 @@ class Github extends Controller
'application_uuid' => $application->uuid,
'application_name' => $application->name,
]);
continue;
}
if ($x_github_event === 'push') {
@@ -410,11 +432,12 @@ class Github extends Controller
if ($action === 'closed' || $action === 'close') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$container_name = generateApplicationContainerName($application, $pull_request_id);
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
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',
@@ -430,13 +453,15 @@ class Github extends Controller
}
}
}
ray($return_payloads);
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}
public function redirect(Request $request)
{
try {
@@ -464,11 +489,13 @@ class Github extends Controller
$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 {
@@ -488,6 +515,7 @@ class Github extends Controller
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Github::install_{$installation_id}", $json);
return;
}
$source = $request->get('source');
@@ -497,6 +525,7 @@ class Github extends Controller
$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);

View File

@@ -31,6 +31,7 @@ class Gitlab extends Controller
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Gitlab::manual_gitlab", $json);
return;
}
$return_payloads = collect([]);
@@ -44,6 +45,7 @@ class Gitlab extends Controller
'status' => 'failed',
'message' => 'Event not allowed. Only push and merge_request events are allowed.',
]);
return response($return_payloads);
}
@@ -58,6 +60,7 @@ class Gitlab extends Controller
'status' => 'failed',
'message' => 'Nothing to do. No branch found in the request.',
]);
return response($return_payloads);
}
$added_files = data_get($payload, 'commits.*.added');
@@ -78,6 +81,7 @@ class Gitlab extends Controller
'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);
@@ -90,6 +94,7 @@ class Gitlab extends Controller
'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);
}
}
@@ -100,6 +105,7 @@ class Gitlab extends Controller
'status' => 'failed',
'message' => "Nothing to do. No applications found with branch '$base_branch'.",
]);
return response($return_payloads);
}
}
@@ -112,6 +118,7 @@ class Gitlab extends Controller
'message' => 'Invalid signature.',
]);
ray('Invalid signature');
continue;
}
$isFunctional = $application->destination->server->isFunctional();
@@ -122,6 +129,7 @@ class Gitlab extends Controller
'message' => 'Server is not functional',
]);
ray('Server is not functional: '.$application->destination->server->name);
continue;
}
if ($x_gitlab_event === 'push') {
@@ -172,6 +180,16 @@ class Gitlab extends Controller
$deployment_uuid = new Cuid2(7);
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
$pr_app = ApplicationPreview::create([
'git_type' => 'gitlab',
'application_id' => $application->id,
'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url,
'docker_compose_domains' => $application->docker_compose_domains,
]);
$pr_app->generate_preview_fqdn_compose();
} else {
ApplicationPreview::create([
'git_type' => 'gitlab',
'application_id' => $application->id,
@@ -179,6 +197,7 @@ class Gitlab extends Controller
'pull_request_html_url' => $pull_request_html_url,
]);
}
}
queue_application_deployment(
application: $application,
pull_request_id: $pull_request_id,
@@ -202,7 +221,7 @@ class Gitlab extends Controller
]);
ray('Preview deployments disabled for '.$application->name);
}
} else if ($action === 'closed' || $action === 'close') {
} elseif ($action === 'closed' || $action === 'close' || $action === 'merge') {
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if ($found) {
$found->delete();
@@ -214,6 +233,7 @@ class Gitlab extends Controller
'status' => 'success',
'message' => 'Preview Deployment closed',
]);
return response($return_payloads);
}
$return_payloads->push([
@@ -230,9 +250,11 @@ class Gitlab extends Controller
}
}
}
return response($return_payloads);
} catch (Exception $e) {
ray($e->getMessage());
return handleError($e);
}
}

View File

@@ -36,6 +36,7 @@ class Stripe extends Controller
];
$json = json_encode($data);
Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json);
return;
}
$webhookSecret = config('subscription.stripe_webhook_secret');
@@ -48,7 +49,7 @@ class Stripe extends Controller
);
$webhook = Webhook::create([
'type' => 'stripe',
'payload' => $request->getContent()
'payload' => $request->getContent(),
]);
$type = data_get($event, 'type');
$data = data_get($event, 'data.object');
@@ -108,11 +109,13 @@ class Stripe extends Controller
$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) {
@@ -127,10 +130,12 @@ class Stripe extends Controller
$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);
@@ -152,10 +157,12 @@ class Stripe extends Controller
if (! $subscription) {
if ($status === 'incomplete_expired') {
send_internal_notification('Subscription incomplete expired for customer: '.$customerId);
return response("Subscription incomplete expired", 200);
return response('Subscription incomplete expired', 200);
}
send_internal_notification('No subscription found for: '.$customerId);
return response("No subscription found", 400);
return response('No subscription found', 400);
}
$trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
@@ -264,6 +271,7 @@ class Stripe extends Controller
'status' => 'failed',
'failure_reason' => $e->getMessage(),
]);
return response($e->getMessage(), 400);
}
}

View File

@@ -22,21 +22,26 @@ class Waitlist extends Controller
$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');
@@ -46,12 +51,15 @@ class Waitlist extends Controller
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');
}
}

View File

@@ -20,6 +20,7 @@ class CheckForcePasswordReset
auth()->logout();
request()->session()->invalidate();
request()->session()->regenerateToken();
return $next($request);
}
$force_password_reset = auth()->user()->force_password_reset;
@@ -27,9 +28,11 @@ class CheckForcePasswordReset
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'force-password-reset' || $request->path() === 'livewire/update' || $request->path() === 'logout') {
return $next($request);
}
return redirect()->route('auth.force-password-reset');
}
}
return $next($request);
}
}

View File

@@ -5,8 +5,8 @@ namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class DecideWhatToDoWithUser
{
@@ -23,12 +23,14 @@ class DecideWhatToDoWithUser
if (! isCloud() && showBoarding() && ! in_array($request->path(), allowedPathsForBoardingAccounts())) {
return redirect()->route('onboarding');
}
return $next($request);
}
if (! auth()->user()->hasVerifiedEmail()) {
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
return $next($request);
}
return redirect()->route('verify.email');
}
if (! isSubscriptionActive() && ! isSubscriptionOnGracePeriod()) {
@@ -36,6 +38,7 @@ class DecideWhatToDoWithUser
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect()->route('subscription.index');
}
}
@@ -43,6 +46,7 @@ class DecideWhatToDoWithUser
if (Str::startsWith($request->path(), 'invitations')) {
return $next($request);
}
return redirect()->route('onboarding');
}
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
@@ -51,6 +55,7 @@ class DecideWhatToDoWithUser
if (isSubscriptionActive() && $request->routeIs('subscription.index')) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
}

View File

@@ -13,6 +13,6 @@ class PreventRequestsDuringMaintenance extends Middleware
*/
protected $except = [
'webhooks/*',
'/api/health'
'/api/health',
];
}

View File

@@ -24,6 +24,7 @@ class RedirectIfAuthenticated
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,12 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public string $build_logs_url;
public string $body;
public function __construct(
@@ -24,17 +25,19 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
public ApplicationPreview $preview,
public ProcessStatus $status,
public ?string $deployment_uuid = null
) {
}
) {}
public function handle()
{
try {
if ($this->application->is_public_repository()) {
ray('Public repository. Skipping comment update.');
return;
}
if ($this->status === ProcessStatus::CLOSED) {
$this->delete_comment();
return;
} elseif ($this->status === ProcessStatus::IN_PROGRESS) {
$this->body = "The preview deployment is in progress. 🟡\n\n";
@@ -48,8 +51,8 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
}
$this->build_logs_url = base_url()."/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
$this->body .= '[Open Build Logs]('.$this->build_logs_url.")\n\n\n";
$this->body .= 'Last updated at: '.now()->toDateTimeString().' CET';
ray('Updating comment', $this->body);
if ($this->preview->pull_request_issue_comment_id) {
@@ -59,7 +62,8 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
}
} catch (\Throwable $e) {
ray($e);
throw $e;
return $e;
}
}
@@ -82,6 +86,7 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
$this->preview->pull_request_issue_comment_id = $data['id'];
$this->preview->save();
}
private function delete_comment()
{
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');

View File

@@ -10,19 +10,23 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ApplicationRestartJob implements ShouldQueue, ShouldBeEncrypted
class ApplicationRestartJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 3600;
public $tries = 1;
public string $applicationDeploymentQueueId;
public function __construct(string $applicationDeploymentQueueId)
{
$this->applicationDeploymentQueueId = $applicationDeploymentQueueId;
}
public function handle() {
public function handle()
{
ray('Restarting application');
}
}

View File

@@ -15,13 +15,12 @@ use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Sleep;
class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server)
{
}
public function __construct(public Server $server) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->id))->dontRelease()];
@@ -31,6 +30,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
{
return $this->server->id;
}
public function healthcheck()
{
$status = instant_remote_process(["docker inspect --format='{{json .State.Status}}' coolify-log-drain"], $this->server, false);
@@ -40,14 +40,15 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
return false;
}
}
public function handle(): void
public function handle()
{
// ray("checking log drain statuses for {$this->server->id}");
try {
if (! $this->server->isFunctional()) {
return;
};
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
}
$containers = instant_remote_process(['docker container ls -q'], $this->server, false);
if (! $containers) {
return;
}
@@ -66,6 +67,7 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
$this->server->team?->notify(new ContainerRestarted('Coolify Log Drainer', $this->server));
$this->server->update(['log_drain_notification_sent' => false]);
}
return;
}
if (! $this->server->log_drain_notification_sent) {
@@ -80,9 +82,12 @@ class CheckLogDrainContainerJob implements ShouldQueue, ShouldBeEncrypted
}
}
} catch (\Throwable $e) {
if (! isCloud()) {
send_internal_notification("CheckLogDrainContainerJob failed on ({$this->server->id}) with: ".$e->getMessage());
}
ray($e->getMessage());
handleError($e);
return handleError($e);
}
}
}

View File

@@ -10,13 +10,11 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CheckResaleLicenseJob implements ShouldQueue, ShouldBeEncrypted
class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
public function __construct() {}
public function handle(): void
{

View File

@@ -11,13 +11,11 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CleanupHelperContainersJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server)
{
}
public function __construct(public Server $server) {}
public function handle(): void
{

View File

@@ -12,14 +12,11 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
public function __construct() {}
// public function uniqueId(): string
// {
@@ -49,6 +46,7 @@ class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique, ShouldBeE
$item->delete();
}
}
private function cleanup_invitation_link()
{
$invitation = TeamInvitation::all();

View File

@@ -12,18 +12,19 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 4;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public Server $server)
{
}
public function __construct(public Server $server) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->server->uuid))];

View File

@@ -11,7 +11,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Spatie\Activitylog\Models\Activity;
class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@@ -23,8 +23,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
public bool $ignore_errors = false,
public $call_event_on_finish = null,
public $call_event_data = null
) {
}
) {}
/**
* Execute the job.
@@ -35,7 +34,7 @@ class CoolifyTask implements ShouldQueue, ShouldBeEncrypted
'activity' => $this->activity,
'ignore_errors' => $this->ignore_errors,
'call_event_on_finish' => $this->call_event_on_finish,
'call_event_data' => $this->call_event_data
'call_event_data' => $this->call_event_data,
]);
$remote_process();

View File

@@ -25,26 +25,37 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Throwable;
class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public ?Team $team = null;
public Server $server;
public ScheduledDatabaseBackup $backup;
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
public ?string $container_name = null;
public ?string $directory_name = null;
public ?ScheduledDatabaseBackupExecution $backup_log = null;
public string $backup_status = 'failed';
public ?string $backup_location = null;
public string $backup_dir;
public string $backup_file;
public int $size = 0;
public ?string $backup_output = null;
public ?S3Storage $s3 = null;
public function __construct($backup)
@@ -84,11 +95,13 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$this->backup->update(['status' => 'failed']);
StopDatabase::run($this->database);
$this->database->delete();
return;
}
$status = Str::of(data_get($this->database, 'status'));
if (! $status->startsWith('running') && $this->database->id !== 0) {
ray('database not running');
return;
}
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
@@ -223,20 +236,20 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
return;
}
}
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->directory_name;
$this->backup_dir = backup_dir().'/databases/'.Str::of($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
if ($this->database->name === 'coolify-db') {
$databasesToBackup = ['coolify'];
$this->directory_name = $this->container_name = "coolify-db";
$this->directory_name = $this->container_name = 'coolify-db';
$ip = Str::slug($this->server->ip);
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
$this->backup_dir = backup_dir().'/coolify'."/coolify-db-$ip";
}
foreach ($databasesToBackup as $database) {
$size = 0;
ray('Backing up '.$database);
try {
if (str($databaseType)->contains('postgres')) {
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_file = "/pg-dump-$database-".Carbon::now()->timestamp.'.dmp';
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -255,7 +268,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$databaseName = $database;
}
}
$this->backup_file = "/mongo-dump-$databaseName-" . Carbon::now()->timestamp . ".tar.gz";
$this->backup_file = "/mongo-dump-$databaseName-".Carbon::now()->timestamp.'.tar.gz';
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $databaseName,
@@ -264,7 +277,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
]);
$this->backup_standalone_mongodb($database);
} elseif (str($databaseType)->contains('mysql')) {
$this->backup_file = "/mysql-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_file = "/mysql-dump-$database-".Carbon::now()->timestamp.'.dmp';
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -273,7 +286,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
]);
$this->backup_standalone_mysql($database);
} elseif (str($databaseType)->contains('mariadb')) {
$this->backup_file = "/mariadb-dump-$database-" . Carbon::now()->timestamp . ".dmp";
$this->backup_file = "/mariadb-dump-$database-".Carbon::now()->timestamp.'.dmp';
$this->backup_location = $this->backup_dir.$this->backup_file;
$this->backup_log = ScheduledDatabaseBackupExecution::create([
'database_name' => $database,
@@ -301,7 +314,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
'status' => 'failed',
'message' => $this->backup_output,
'size' => $size,
'filename' => null
'filename' => null,
]);
}
send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage());
@@ -315,13 +328,14 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
BackupCreated::dispatch($this->team->id);
}
}
private function backup_standalone_mongodb(string $databaseWithCollections): void
{
try {
ray($this->database->toArray());
$url = $this->database->get_db_url(useInternal: true);
if ($databaseWithCollections === 'all') {
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = 'mkdir -p '.$this->backup_dir;
if (str($this->database->image)->startsWith('mongo:4.0')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else {
@@ -335,7 +349,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
$databaseName = $databaseWithCollections;
$collectionsToExclude = collect();
}
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = 'mkdir -p '.$this->backup_dir;
if ($collectionsToExclude->count() === 0) {
if (str($this->database->image)->startsWith('mongo:4.0')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
@@ -362,10 +376,11 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
throw $e;
}
}
private function backup_standalone_postgresql(string $database): void
{
try {
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
$this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output);
@@ -379,10 +394,11 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
throw $e;
}
}
private function backup_standalone_mysql(string $database): void
{
try {
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name mysqldump -u root -p{$this->database->mysql_root_password} $database > $this->backup_location";
ray($commands);
$this->backup_output = instant_remote_process($commands, $this->server);
@@ -397,10 +413,11 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
throw $e;
}
}
private function backup_standalone_mariadb(string $database): void
{
try {
$commands[] = "mkdir -p " . $this->backup_dir;
$commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name mariadb-dump -u root -p{$this->database->mariadb_root_password} $database > $this->backup_location";
ray($commands);
$this->backup_output = instant_remote_process($commands, $this->server);
@@ -415,6 +432,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
throw $e;
}
}
private function add_to_backup_output($output): void
{
if ($this->backup_output) {

View File

@@ -3,27 +3,22 @@
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
class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 1;
public function __construct()
{
}
public function __construct() {}
public function handle()
{
@@ -42,13 +37,6 @@ class DatabaseBackupStatusJob implements ShouldQueue, ShouldBeEncrypted
// }
// }
// $scheduled_backups = ScheduledDatabaseBackup::all();
// $databases = collect();
// $teams = collect();

View File

@@ -24,13 +24,11 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Artisan;
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false)
{
}
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {}
public function handle()
{

View File

@@ -10,21 +10,20 @@ 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;
use Illuminate\Support\Facades\Log;
use RuntimeException;
class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 300;
public ?int $usageBefore = null;
public function __construct(public Server $server)
{
}
public function __construct(public Server $server) {}
public function handle(): void
{
try {
@@ -32,6 +31,7 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
$this->server->applications()->each(function ($application) use (&$isInprogress) {
if ($application->isDeploymentInprogress()) {
$isInprogress = true;
return;
}
});
@@ -60,7 +60,7 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
Log::info('No need to clean up '.$this->server->name);
}
} catch (\Throwable $e) {
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
// send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
ray($e->getMessage());
throw $e;
}

View File

@@ -12,18 +12,19 @@ use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
class GithubAppPermissionJob implements ShouldQueue, ShouldBeEncrypted
class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $tries = 4;
public function backoff(): int
{
return isDev() ? 1 : 3;
}
public function __construct(public GithubApp $github_app)
{
}
public function __construct(public GithubApp $github_app) {}
public function middleware(): array
{
return [(new WithoutOverlapping($this->github_app->uuid))];
@@ -40,7 +41,7 @@ class GithubAppPermissionJob implements ShouldQueue, ShouldBeEncrypted
$github_access_token = generate_github_jwt_token($this->github_app);
$response = Http::withHeaders([
'Authorization' => "Bearer $github_access_token",
'Accept' => 'application/vnd.github+json'
'Accept' => 'application/vnd.github+json',
])->get("{$this->github_app->api_url}/app");
$response = $response->json();
$permissions = data_get($response, 'permissions');

View File

@@ -11,16 +11,15 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncrypted
class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 600;
public $tries = 1;
public function __construct()
{
}
public function __construct() {}
public function handle(): void
{

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
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;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $timeout = 1000;
public function __construct() {}
public function handle(): void
{
try {
if (isDev() || isCloud()) {
return;
}
$server = Server::findOrFail(0);
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) {
$versions = $response->json();
File::put(base_path('versions.json'), json_encode($versions, JSON_PRETTY_PRINT));
}
$latest_version = get_latest_version_of_coolify();
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
$settings = InstanceSettings::get();
$current_version = config('version');
if (! $settings->is_auto_update_enabled) {
return;
}
if ($latest_version === $current_version) {
return;
}
if (version_compare($latest_version, $current_version, '<')) {
return;
}
instant_remote_process([
'curl -fsSL https://cdn.coollabs.io/coolify/upgrade.sh -o /data/coolify/source/upgrade.sh',
"bash /data/coolify/source/upgrade.sh $latest_version",
], $server);
} catch (\Throwable $e) {
throw $e;
}
}
}

View File

@@ -11,7 +11,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@@ -26,9 +26,9 @@ class PullHelperImageJob implements ShouldQueue, ShouldBeEncrypted
{
return $this->server->uuid;
}
public function __construct(public Server $server)
{
}
public function __construct(public Server $server) {}
public function handle(): void
{
try {

View File

@@ -12,7 +12,7 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted
class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
@@ -27,15 +27,16 @@ class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted
{
return $this->server->uuid;
}
public function __construct(public Server $server)
{
}
public function __construct(public Server $server) {}
public function handle(): void
{
try {
$version = get_latest_sentinel_version();
if (! $version) {
ray('Failed to get latest Sentinel version');
return;
}
$local_version = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
@@ -44,6 +45,7 @@ class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted
}
if (version_compare($local_version, $version, '<')) {
StartSentinel::run($this->server, $version, true);
return;
}
ray('Sentinel image is up to date');

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