mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
147 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59d6818f70 | ||
|
|
7678cd47df | ||
|
|
b101fbacd4 | ||
|
|
a61a86dc3b | ||
|
|
0b3cde44c3 | ||
|
|
6b302ab786 | ||
|
|
3c623f13e2 | ||
|
|
da54c24e8d | ||
|
|
1e39c3d5ab | ||
|
|
6071412986 | ||
|
|
ba7148206a | ||
|
|
59c5b22e6c | ||
|
|
be7f2ad9c4 | ||
|
|
62295ef573 | ||
|
|
ceb9fcf3b6 | ||
|
|
60282f7b6c | ||
|
|
f14b0a3411 | ||
|
|
30af317bd9 | ||
|
|
95faa1c3ad | ||
|
|
fb280afe41 | ||
|
|
fd488a561a | ||
|
|
ab57a5d8ef | ||
|
|
f5ae222a6e | ||
|
|
5d95d8b79a | ||
|
|
fbb5f2ca2e | ||
|
|
16cbca36c1 | ||
|
|
24a578bedb | ||
|
|
36dc479772 | ||
|
|
83d6e488e4 | ||
|
|
a4d358d512 | ||
|
|
c0c197101d | ||
|
|
62e39ccc7f | ||
|
|
a88a016137 | ||
|
|
f4c8986ab3 | ||
|
|
e286eae53b | ||
|
|
bc3e59e4ef | ||
|
|
17ebc650c9 | ||
|
|
0ef386b4a8 | ||
|
|
26fce85bb0 | ||
|
|
2a079e3365 | ||
|
|
5fb5ed75c4 | ||
|
|
a2008fe9d1 | ||
|
|
3c96485e3d | ||
|
|
1c97d47ea0 | ||
|
|
8f8f5878dd | ||
|
|
6e6f39dc1f | ||
|
|
d2d1f984e1 | ||
|
|
d635e5dbae | ||
|
|
49c56524e1 | ||
|
|
6ced607f2a | ||
|
|
aaa2febef4 | ||
|
|
10e6eddcfe | ||
|
|
2639bf92ad | ||
|
|
59eae3a44e | ||
|
|
5aa8ccfcf4 | ||
|
|
aea7cc9638 | ||
|
|
c970907c73 | ||
|
|
38c6c1ee40 | ||
|
|
a6118f5daf | ||
|
|
b196c138d9 | ||
|
|
8f9949160c | ||
|
|
beae0b545f | ||
|
|
b8dd7704b3 | ||
|
|
d913be66e6 | ||
|
|
63de538879 | ||
|
|
1d733b2282 | ||
|
|
1d0ad51fdf | ||
|
|
83d00bbe3c | ||
|
|
c422b4dbcf | ||
|
|
767fd334dd | ||
|
|
972223f01b | ||
|
|
9318cac189 | ||
|
|
7aa991fd7c | ||
|
|
5c27f43b3d | ||
|
|
a2f4d4ed6d | ||
|
|
6aca2740fb | ||
|
|
cd13b5b83e | ||
|
|
758dbafbf1 | ||
|
|
f6663661df | ||
|
|
9666099408 | ||
|
|
d382af6860 | ||
|
|
4905454269 | ||
|
|
ed8bd37230 | ||
|
|
ec1a7aa893 | ||
|
|
62adf2c5dc | ||
|
|
3e4538de98 | ||
|
|
5a7b16ea5f | ||
|
|
aa7bc40f85 | ||
|
|
4fd83dc727 | ||
|
|
0e451f87a9 | ||
|
|
0b0ae55f0b | ||
|
|
40ec3d9753 | ||
|
|
9535c8df29 | ||
|
|
6ca1d36d5d | ||
|
|
f5d16c46cb | ||
|
|
725c3fd547 | ||
|
|
dcfcee1db6 | ||
|
|
9f8c44d96b | ||
|
|
5b74fd34f5 | ||
|
|
5a4c9422b2 | ||
|
|
81b916724e | ||
|
|
9540f60fa2 | ||
|
|
8eb1686125 | ||
|
|
daf3710a5e | ||
|
|
1067f37e4d | ||
|
|
3b3c0b94e5 | ||
|
|
8b0a0d67da | ||
|
|
5541c135df | ||
|
|
2552cb2208 | ||
|
|
f001e9bc34 | ||
|
|
f943fdc5be | ||
|
|
a4f1fcba58 | ||
|
|
68091b44fc | ||
|
|
9f8caac91c | ||
|
|
8082dc1a01 | ||
|
|
a71cf5bc66 | ||
|
|
0775074509 | ||
|
|
242d2fb283 | ||
|
|
5646818965 | ||
|
|
ffc5320940 | ||
|
|
7c10c55b1c | ||
|
|
7c96b6207a | ||
|
|
0be8ffbdc9 | ||
|
|
24fa56762e | ||
|
|
84d8e35411 | ||
|
|
3d3ccc435c | ||
|
|
be3b01472e | ||
|
|
de6f5b1105 | ||
|
|
14d9c06dcd | ||
|
|
8abfaa1967 | ||
|
|
46f7ae9588 | ||
|
|
f2c32b9aeb | ||
|
|
3dab1eb92e | ||
|
|
9c22e01716 | ||
|
|
d1c47a4062 | ||
|
|
6c3f97d9ae | ||
|
|
ebd8e2ce40 | ||
|
|
b650f3f754 | ||
|
|
f33ba40478 | ||
|
|
5cea9c4603 | ||
|
|
d32832fabc | ||
|
|
165f0a3d4a | ||
|
|
f14995200b | ||
|
|
12bb2ecc4a | ||
|
|
933ec5741d | ||
|
|
8004a40139 | ||
|
|
a6209fbe5c |
@@ -27,3 +27,5 @@ You can ask for guidance anytime on our
|
||||
You can login your Coolify instance at `localhost:8000` with `test@example.com` and `password`.
|
||||
|
||||
Your horizon (Laravel scheduler): `localhost:8000/horizon` - Only reachable if you logged in with root user.
|
||||
|
||||
Mails are caught by Mailpit: `localhost:8025`
|
||||
|
||||
@@ -36,7 +36,7 @@ You can find the installation script [here](./scripts/install.sh).
|
||||
|
||||
## Support
|
||||
|
||||
Contact us [here](https://docs.coollabs.io/contact).
|
||||
Contact us [here](https://coolify.io/docs/contact).
|
||||
|
||||
## Recognitions
|
||||
|
||||
|
||||
30
app/Actions/Application/StopApplication.php
Normal file
30
app/Actions/Application/StopApplication.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopApplication
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Application $application)
|
||||
{
|
||||
$server = $application->destination->server;
|
||||
$containers = getCurrentApplicationContainerStatus($server, $application->id);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$server
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: make notification for application
|
||||
// $application->environment->project->team->notify(new StatusChanged($application));
|
||||
}
|
||||
}
|
||||
}
|
||||
93
app/Actions/Database/StartDatabaseProxy.php
Normal file
93
app/Actions/Database/StartDatabaseProxy.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class StartDatabaseProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
||||
{
|
||||
$internalPort = null;
|
||||
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
|
||||
$internalPort = 6379;
|
||||
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
|
||||
$internalPort = 5432;
|
||||
}
|
||||
$containerName = "{$database->uuid}-proxy";
|
||||
$configuration_dir = database_proxy_dir($database->uuid);
|
||||
$nginxconf = <<<EOF
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
stream {
|
||||
server {
|
||||
listen $database->public_port;
|
||||
proxy_pass $database->uuid:$internalPort;
|
||||
}
|
||||
}
|
||||
EOF;
|
||||
$dockerfile = <<< EOF
|
||||
FROM nginx:stable-alpine
|
||||
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
EOF;
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$containerName => [
|
||||
'build' => [
|
||||
'context' => $configuration_dir,
|
||||
'dockerfile' => 'Dockerfile',
|
||||
],
|
||||
'image' => "nginx:stable-alpine",
|
||||
'container_name' => $containerName,
|
||||
'restart' => RESTART_MODE,
|
||||
'ports' => [
|
||||
"$database->public_port:$database->public_port",
|
||||
],
|
||||
'networks' => [
|
||||
$database->destination->network,
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
'stat /etc/nginx/nginx.conf || exit 1',
|
||||
],
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 3,
|
||||
'start_period' => '1s'
|
||||
],
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$database->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $database->destination->network,
|
||||
'attachable' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
|
||||
$nginxconf_base64 = base64_encode($nginxconf);
|
||||
$dockerfile_base64 = base64_encode($dockerfile);
|
||||
instant_remote_process([
|
||||
"mkdir -p $configuration_dir",
|
||||
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",
|
||||
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||
"docker compose --project-directory {$configuration_dir} up --build -d",
|
||||
], $database->destination->server);
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,18 @@ use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartPostgresql
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public StandalonePostgresql $database;
|
||||
public array $commands = [];
|
||||
public array $init_scripts = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
public function __invoke(Server $server, StandalonePostgresql $database)
|
||||
public function handle(Server $server, StandalonePostgresql $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
$container_name = $this->database->uuid;
|
||||
@@ -41,6 +44,9 @@ class StartPostgresql
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed' => 'true',
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
|
||||
160
app/Actions/Database/StartRedis.php
Normal file
160
app/Actions/Database/StartRedis.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartRedis
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public StandaloneRedis $database;
|
||||
public array $commands = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
|
||||
public function handle(Server $server, StandaloneRedis $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo '####### Starting {$database->name}.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_redis();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
'command' => $startCommand,
|
||||
'container_name' => $container_name,
|
||||
'environment' => $environment_variables,
|
||||
'restart' => RESTART_MODE,
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed' => 'true',
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
'redis-cli',
|
||||
'ping'
|
||||
],
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 10,
|
||||
'start_period' => '5s'
|
||||
],
|
||||
'mem_limit' => $this->database->limits_memory,
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => $this->database->limits_cpus,
|
||||
'cpuset' => $this->database->limits_cpuset,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$this->database->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->database->destination->network,
|
||||
'attachable' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
if (count($this->database->ports_mappings_array) > 0) {
|
||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||
}
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
if (!is_null($this->database->redis_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/redis.conf',
|
||||
'target' => '/usr/local/etc/redis/redis.conf',
|
||||
'read_only' => true,
|
||||
];
|
||||
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||
$readme = generate_readme_file($this->database->name, now());
|
||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||
return remote_process($this->commands, $server);
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes_only_volume_names()
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
$name = $persistentStorage->name;
|
||||
$local_persistent_volumes_names[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
|
||||
private function generate_environment_variables()
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||
}
|
||||
|
||||
return $environment_variables->all();
|
||||
}
|
||||
private function add_custom_redis()
|
||||
{
|
||||
if (is_null($this->database->redis_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'redis.conf';
|
||||
$content = $this->database->redis_conf;
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||
|
||||
}
|
||||
}
|
||||
27
app/Actions/Database/StopDatabase.php
Normal file
27
app/Actions/Database/StopDatabase.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopDatabase
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
instant_remote_process(
|
||||
["docker rm -f {$database->uuid}"],
|
||||
$server
|
||||
);
|
||||
if ($database->is_public) {
|
||||
StopDatabaseProxy::run($database);
|
||||
}
|
||||
// TODO: make notification for services
|
||||
// $database->environment->project->team->notify(new StatusChanged($database));
|
||||
}
|
||||
}
|
||||
19
app/Actions/Database/StopDatabaseProxy.php
Normal file
19
app/Actions/Database/StopDatabaseProxy.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopDatabaseProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql $database)
|
||||
{
|
||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
||||
$database->is_public = false;
|
||||
$database->save();
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,11 @@ class CreateNewUser implements CreatesNewUsers
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
$team = $user->teams()->first();
|
||||
if (isCloud()) {
|
||||
$user->sendVerificationEmail();
|
||||
} else {
|
||||
$user->markEmailAsVerified();
|
||||
}
|
||||
}
|
||||
// Set session variable
|
||||
session(['currentTeam' => $user->currentTeam = $team]);
|
||||
|
||||
51
app/Actions/Proxy/CheckProxy.php
Normal file
51
app/Actions/Proxy/CheckProxy.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class CheckProxy
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, $fromUI = false)
|
||||
{
|
||||
if (!$server->isProxyShouldRun()) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Proxy should not run. You selected the Custom Proxy.");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$status = getContainerStatus($server, 'coolify-proxy');
|
||||
if ($status === 'running') {
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->save();
|
||||
return false;
|
||||
}
|
||||
$ip = $server->ip;
|
||||
if ($server->id === 0) {
|
||||
$ip = 'host.docker.internal';
|
||||
}
|
||||
|
||||
$connection80 = @fsockopen($ip, '80');
|
||||
$connection443 = @fsockopen($ip, '443');
|
||||
$port80 = is_resource($connection80) && fclose($connection80);
|
||||
$port443 = is_resource($connection443) && fclose($connection443);
|
||||
if ($port80) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($port443) {
|
||||
if ($fromUI) {
|
||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -10,56 +10,47 @@ use Spatie\Activitylog\Models\Activity;
|
||||
class StartProxy
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $async = true): Activity|string
|
||||
public function handle(Server $server, bool $async = true): string|Activity
|
||||
{
|
||||
$commands = collect([]);
|
||||
$proxyType = $server->proxyType();
|
||||
if ($proxyType === 'none') {
|
||||
return 'OK';
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
}
|
||||
SaveConfiguration::run($server, $configuration);
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
|
||||
$commands = $commands->merge([
|
||||
"apt-get update > /dev/null 2>&1 || true",
|
||||
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
|
||||
"command -v lsof >/dev/null || apt install -y lsof",
|
||||
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo '####### Creating Docker Compose file.'",
|
||||
"echo '####### Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo '####### Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
|
||||
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
|
||||
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
|
||||
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
|
||||
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
|
||||
"systemctl disable nginx > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache2 > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache > /dev/null 2>&1 || true",
|
||||
"echo '####### Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo '####### Proxy installed successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
try {
|
||||
$proxyType = $server->proxyType();
|
||||
$commands = collect([]);
|
||||
$proxy_path = get_proxy_path();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
}
|
||||
SaveConfiguration::run($server, $configuration);
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
return 'OK';
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo 'Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
$server->save();
|
||||
return 'OK';
|
||||
}
|
||||
} catch(\Throwable $e) {
|
||||
ray($e);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
|
||||
class UpdateCoolify
|
||||
{
|
||||
use AsAction;
|
||||
public ?Server $server = null;
|
||||
public ?string $latestVersion = null;
|
||||
public ?string $currentVersion = null;
|
||||
|
||||
public function __invoke(bool $force)
|
||||
public function handle(bool $force)
|
||||
{
|
||||
try {
|
||||
$settings = InstanceSettings::get();
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Actions\Service;
|
||||
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use App\Models\Service;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
|
||||
class StopService
|
||||
{
|
||||
@@ -22,5 +23,7 @@ class StopService
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
||||
// TODO: make notification for databases
|
||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -34,7 +35,7 @@ class ResourcesDelete extends Command
|
||||
{
|
||||
$resource = select(
|
||||
'What resource do you want to delete?',
|
||||
['Application', 'Database', 'Service'],
|
||||
['Application', 'Database', 'Service', 'Server'],
|
||||
);
|
||||
if ($resource === 'Application') {
|
||||
$this->deleteApplication();
|
||||
@@ -42,6 +43,30 @@ class ResourcesDelete extends Command
|
||||
$this->deleteDatabase();
|
||||
} elseif ($resource === 'Service') {
|
||||
$this->deleteService();
|
||||
} elseif ($resource === 'Server') {
|
||||
$this->deleteServer();
|
||||
}
|
||||
}
|
||||
private function deleteServer()
|
||||
{
|
||||
$servers = Server::all();
|
||||
if ($servers->count() === 0) {
|
||||
$this->error('There are no applications to delete.');
|
||||
return;
|
||||
}
|
||||
$serversToDelete = multiselect(
|
||||
label: 'What server do you want to delete?',
|
||||
options: $servers->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($serversToDelete as $server) {
|
||||
$toDelete = $servers->where('id', $server)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
break;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
private function deleteApplication()
|
||||
@@ -53,14 +78,17 @@ class ResourcesDelete extends Command
|
||||
}
|
||||
$applicationsToDelete = multiselect(
|
||||
'What application do you want to delete?',
|
||||
$applications->pluck('name')->toArray(),
|
||||
$applications->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($applicationsToDelete as $application) {
|
||||
$toDelete = $applications->where('name', $application)->first();
|
||||
ray($application);
|
||||
$toDelete = $applications->where('id', $application)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
||||
if (!$confirmed) {
|
||||
break;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
@@ -73,17 +101,18 @@ class ResourcesDelete extends Command
|
||||
}
|
||||
$databasesToDelete = multiselect(
|
||||
'What database do you want to delete?',
|
||||
$databases->pluck('name')->toArray(),
|
||||
$databases->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($databasesToDelete as $database) {
|
||||
$toDelete = $databases->where('name', $database)->first();
|
||||
$toDelete = $databases->where('id', $database)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
|
||||
}
|
||||
private function deleteService()
|
||||
{
|
||||
@@ -94,14 +123,16 @@ class ResourcesDelete extends Command
|
||||
}
|
||||
$servicesToDelete = multiselect(
|
||||
'What service do you want to delete?',
|
||||
$services->pluck('name')->toArray(),
|
||||
$services->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($servicesToDelete as $service) {
|
||||
$toDelete = $services->where('name', $service)->first();
|
||||
$toDelete = $services->where('id', $service)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ class Kernel extends ConsoleKernel
|
||||
// $this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
$this->cleanup_servers($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
} else {
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
$schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||
// $schedule->job(new DockerCleanupJob)->everyTenMinutes()->onOneServer();
|
||||
$this->instance_auto_update($schedule);
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
|
||||
@@ -46,15 +46,6 @@ class Controller extends BaseController
|
||||
}
|
||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||
}
|
||||
public function subscription()
|
||||
{
|
||||
if (!isCloud()) {
|
||||
abort(404);
|
||||
}
|
||||
return view('subscription.index', [
|
||||
'settings' => InstanceSettings::get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function license()
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ class DatabaseController extends Controller
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class DatabaseController extends Controller
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
@@ -64,10 +64,18 @@ class DatabaseController extends Controller
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
// No backups for redis
|
||||
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
|
||||
return redirect()->route('project.database.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'database_uuid' => $database->uuid,
|
||||
]);
|
||||
}
|
||||
return view('project.database.backups.all', [
|
||||
'database' => $database,
|
||||
's3s' => currentTeam()->s3s,
|
||||
|
||||
@@ -59,11 +59,15 @@ class ProjectController extends Controller
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
if (in_array($type, DATABASE_TYPES)) {
|
||||
$standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid);
|
||||
if ($type->value() === "postgresql") {
|
||||
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
||||
} else if ($type->value() === 'redis') {
|
||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||
}
|
||||
return redirect()->route('project.database.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'database_uuid' => $standalone_postgresql->uuid,
|
||||
'database_uuid' => $database->uuid,
|
||||
]);
|
||||
}
|
||||
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
|
||||
|
||||
@@ -38,8 +38,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\CheckForcePasswordReset::class,
|
||||
\App\Http\Middleware\IsSubscriptionValid::class,
|
||||
\App\Http\Middleware\IsBoardingFlow::class,
|
||||
\App\Http\Middleware\DecideWhatToDoWithUser::class,
|
||||
|
||||
],
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
{
|
||||
$this->validate([
|
||||
'remoteServerName' => 'required',
|
||||
'remoteServerHost' => 'required',
|
||||
'remoteServerHost' => 'required|ip',
|
||||
'remoteServerPort' => 'required|integer',
|
||||
'remoteServerUser' => 'required',
|
||||
]);
|
||||
|
||||
@@ -9,21 +9,13 @@ use Livewire\Component;
|
||||
|
||||
class Dashboard extends Component
|
||||
{
|
||||
public int $projects = 0;
|
||||
public int $servers = 0;
|
||||
public int $s3s = 0;
|
||||
public int $resources = 0;
|
||||
public $projects = [];
|
||||
public $servers = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->servers = Server::ownedByCurrentTeam()->get()->count();
|
||||
$this->s3s = S3Storage::ownedByCurrentTeam()->get()->count();
|
||||
$projects = Project::ownedByCurrentTeam()->get();
|
||||
foreach ($projects as $project) {
|
||||
$this->resources += $project->applications->count();
|
||||
$this->resources += $project->postgresqls->count();
|
||||
}
|
||||
$this->projects = $projects->count();
|
||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||
}
|
||||
// public function getIptables()
|
||||
// {
|
||||
|
||||
@@ -60,12 +60,16 @@ class DeploymentNavbar extends Component
|
||||
$previous_logs[] = $new_log_entry;
|
||||
$this->application_deployment_queue->update([
|
||||
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
|
||||
'current_process_id' => null,
|
||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->application_deployment_queue->update([
|
||||
'current_process_id' => null,
|
||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||
]);
|
||||
queue_next_deployment($this->application);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,9 @@
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
@@ -23,6 +20,10 @@ class General extends Component
|
||||
public ?string $git_commit_sha = null;
|
||||
public string $build_pack;
|
||||
|
||||
public $customLabels;
|
||||
public bool $labelsChanged = false;
|
||||
public bool $isConfigurationChanged = false;
|
||||
|
||||
public bool $is_static;
|
||||
public bool $is_git_submodules_enabled;
|
||||
public bool $is_git_lfs_enabled;
|
||||
@@ -49,6 +50,10 @@ class General extends Component
|
||||
'application.ports_exposes' => 'required',
|
||||
'application.ports_mappings' => 'nullable',
|
||||
'application.dockerfile' => 'nullable',
|
||||
'application.docker_registry_image_name' => 'nullable',
|
||||
'application.docker_registry_image_tag' => 'nullable',
|
||||
'application.dockerfile_location' => 'nullable',
|
||||
'application.custom_labels' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'application.name' => 'name',
|
||||
@@ -67,9 +72,50 @@ class General extends Component
|
||||
'application.ports_exposes' => 'Ports exposes',
|
||||
'application.ports_mappings' => 'Ports mappings',
|
||||
'application.dockerfile' => 'Dockerfile',
|
||||
|
||||
'application.docker_registry_image_name' => 'Docker registry image name',
|
||||
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
||||
'application.dockerfile_location' => 'Dockerfile location',
|
||||
'application.custom_labels' => 'Custom labels',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
|
||||
$this->application->isConfigurationChanged(true);
|
||||
}
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
if (is_null(data_get($this->application, 'custom_labels'))) {
|
||||
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||
} else {
|
||||
$this->customLabels = str($this->application->custom_labels)->replace(',', "\n");
|
||||
}
|
||||
if (data_get($this->application, 'settings')) {
|
||||
$this->is_static = $this->application->settings->is_static;
|
||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
}
|
||||
$this->checkLabelUpdates();
|
||||
}
|
||||
public function updatedApplicationBuildPack()
|
||||
{
|
||||
if ($this->application->build_pack !== 'nixpacks') {
|
||||
$this->application->settings->is_static = $this->is_static = false;
|
||||
$this->application->settings->save();
|
||||
}
|
||||
$this->submit();
|
||||
}
|
||||
public function checkLabelUpdates()
|
||||
{
|
||||
if (md5($this->application->custom_labels) !== md5(implode(",", generateLabelsApplication($this->application)))) {
|
||||
$this->labelsChanged = true;
|
||||
} else {
|
||||
$this->labelsChanged = false;
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
// @TODO: find another way - if possible
|
||||
@@ -89,36 +135,41 @@ class General extends Component
|
||||
$this->application->save();
|
||||
$this->application->refresh();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
$this->checkLabelUpdates();
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
}
|
||||
|
||||
public function getWildcardDomain() {
|
||||
public function getWildcardDomain()
|
||||
{
|
||||
$server = data_get($this->application, 'destination.server');
|
||||
if ($server) {
|
||||
$fqdn = generateFqdn($server, $this->application->uuid);
|
||||
ray($fqdn);
|
||||
$this->application->fqdn = $fqdn;
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
}
|
||||
|
||||
}
|
||||
public function mount()
|
||||
public function resetDefaultLabels($showToaster = true)
|
||||
{
|
||||
if (data_get($this->application,'settings')) {
|
||||
$this->is_static = $this->application->settings->is_static;
|
||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
}
|
||||
$this->customLabels = str(implode(",", generateLabelsApplication($this->application)))->replace(',', "\n");
|
||||
$this->submit($showToaster);
|
||||
}
|
||||
|
||||
public function submit()
|
||||
public function updatedApplicationFqdn()
|
||||
{
|
||||
$this->resetDefaultLabels(false);
|
||||
$this->emit('success', 'Labels reseted to default!');
|
||||
}
|
||||
public function submit($showToaster = true)
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
||||
$this->validate([
|
||||
'application.docker_registry_image_name' => 'required',
|
||||
'application.docker_registry_image_tag' => 'required',
|
||||
]);
|
||||
}
|
||||
if (data_get($this->application, 'fqdn')) {
|
||||
$domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||
return Str::of($domain)->trim()->lower();
|
||||
@@ -137,10 +188,17 @@ class General extends Component
|
||||
if ($this->application->publish_directory && $this->application->publish_directory !== '/') {
|
||||
$this->application->publish_directory = rtrim($this->application->publish_directory, '/');
|
||||
}
|
||||
if (gettype($this->customLabels) === 'string') {
|
||||
$this->customLabels = str($this->customLabels)->replace(',', "\n");
|
||||
}
|
||||
$this->application->custom_labels = $this->customLabels->explode("\n")->implode(',');
|
||||
$this->application->save();
|
||||
$this->emit('success', 'Application settings updated!');
|
||||
$showToaster && $this->emit('success', 'Application settings updated!');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->checkLabelUpdates();
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Application;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Application;
|
||||
use Livewire\Component;
|
||||
@@ -21,11 +22,13 @@ class Heading extends Component
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
$this->application->refresh();
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
$this->application->refresh();
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
@@ -57,22 +60,9 @@ class Heading extends Component
|
||||
|
||||
public function stop()
|
||||
{
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||
if ($containers->count() === 0) {
|
||||
return;
|
||||
}
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$this->application->destination->server
|
||||
);
|
||||
$this->application->status = 'exited';
|
||||
$this->application->save();
|
||||
// $this->application->environment->project->team->notify(new StatusChanged($this->application));
|
||||
}
|
||||
}
|
||||
StopApplication::run($this->application);
|
||||
$this->application->status = 'exited';
|
||||
$this->application->save();
|
||||
$this->application->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ class BackupEdit extends Component
|
||||
{
|
||||
public $backup;
|
||||
public $s3s;
|
||||
public ?string $status = null;
|
||||
public array $parameters;
|
||||
|
||||
protected $rules = [
|
||||
@@ -16,6 +17,7 @@ class BackupEdit extends Component
|
||||
'backup.number_of_backups_locally' => 'required|integer|min:1',
|
||||
'backup.save_s3' => 'required|boolean',
|
||||
'backup.s3_storage_id' => 'nullable|integer',
|
||||
'backup.databases_to_backup' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'backup.enabled' => 'Enabled',
|
||||
@@ -23,6 +25,7 @@ class BackupEdit extends Component
|
||||
'backup.number_of_backups_locally' => 'Number of Backups Locally',
|
||||
'backup.save_s3' => 'Save to S3',
|
||||
'backup.s3_storage_id' => 'S3 Storage',
|
||||
'backup.databases_to_backup' => 'Databases to Backup',
|
||||
];
|
||||
protected $messages = [
|
||||
'backup.s3_storage_id' => 'Select a S3 Storage',
|
||||
@@ -36,7 +39,6 @@ class BackupEdit extends Component
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function delete()
|
||||
{
|
||||
// TODO: Delete backup from server and add a confirmation modal
|
||||
@@ -48,6 +50,7 @@ class BackupEdit extends Component
|
||||
{
|
||||
try {
|
||||
$this->custom_validate();
|
||||
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
@@ -70,9 +73,11 @@ class BackupEdit extends Component
|
||||
|
||||
public function submit()
|
||||
{
|
||||
ray($this->backup->s3_storage_id);
|
||||
try {
|
||||
$this->custom_validate();
|
||||
if ($this->backup->databases_to_backup == '' || $this->backup->databases_to_backup === null) {
|
||||
$this->backup->databases_to_backup = null;
|
||||
}
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
|
||||
@@ -13,6 +13,6 @@ class BackupNow extends Component
|
||||
dispatch(new DatabaseBackupJob(
|
||||
backup: $this->backup
|
||||
));
|
||||
$this->emit('success', 'Backup queued. It will be available in a few minutes');
|
||||
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class CreateScheduledBackup extends Component
|
||||
$this->emit('error', 'Invalid Cron / Human expression.');
|
||||
return;
|
||||
}
|
||||
ScheduledDatabaseBackup::create([
|
||||
$payload = [
|
||||
'enabled' => true,
|
||||
'frequency' => $this->frequency,
|
||||
'save_s3' => $this->save_s3,
|
||||
@@ -40,7 +40,11 @@ class CreateScheduledBackup extends Component
|
||||
'database_id' => $this->database->id,
|
||||
'database_type' => $this->database->getMorphClass(),
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
];
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$payload['databases_to_backup'] = $this->database->postgres_db;
|
||||
}
|
||||
ScheduledDatabaseBackup::create($payload);
|
||||
$this->emit('refreshScheduledBackups');
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Http\Livewire\Project\Database;
|
||||
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -35,24 +37,20 @@ class Heading extends Component
|
||||
|
||||
public function stop()
|
||||
{
|
||||
instant_remote_process(
|
||||
["docker rm -f {$this->database->uuid}"],
|
||||
$this->database->destination->server
|
||||
);
|
||||
if ($this->database->is_public) {
|
||||
stopPostgresProxy($this->database);
|
||||
$this->database->is_public = false;
|
||||
}
|
||||
StopDatabase::run($this->database);
|
||||
$this->database->status = 'exited';
|
||||
$this->database->save();
|
||||
$this->check_status();
|
||||
// $this->database->environment->project->team->notify(new StatusChanged($this->database));
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
|
||||
$activity = StartPostgresql::run($this->database->destination->server, $this->database);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
if ($this->database->type() === 'standalone-redis') {
|
||||
$activity = StartRedis::run($this->database->destination->server, $this->database);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Database\Postgresql;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
@@ -67,10 +69,10 @@ class General extends Component
|
||||
}
|
||||
if ($this->database->is_public) {
|
||||
$this->emit('success', 'Starting TCP proxy...');
|
||||
startPostgresProxy($this->database);
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->emit('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
stopPostgresProxy($this->database);
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->getDbUrl();
|
||||
|
||||
94
app/Http/Livewire/Project/Database/Redis/General.php
Normal file
94
app/Http/Livewire/Project/Database/Redis/General.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Database\Redis;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public StandaloneRedis $database;
|
||||
public string $db_url;
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
'database.redis_conf' => 'nullable',
|
||||
'database.redis_password' => 'required',
|
||||
'database.image' => 'required',
|
||||
'database.ports_mappings' => 'nullable',
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'database.name' => 'Name',
|
||||
'database.description' => 'Description',
|
||||
'database.redis_conf' => 'Redis Configuration',
|
||||
'database.redis_password' => 'Redis Password',
|
||||
'database.image' => 'Image',
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
];
|
||||
public function submit() {
|
||||
try {
|
||||
$this->validate();
|
||||
if ($this->database->redis_conf === "") {
|
||||
$this->database->redis_conf = null;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->emit('success', 'Database updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
if ($this->database->is_public && !$this->database->public_port) {
|
||||
$this->emit('error', 'Public port is required.');
|
||||
$this->database->is_public = false;
|
||||
return;
|
||||
}
|
||||
if ($this->database->is_public) {
|
||||
$this->emit('success', 'Starting TCP proxy...');
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->emit('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->getDbUrl();
|
||||
$this->database->save();
|
||||
} catch(\Throwable $e) {
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->getDbUrl();
|
||||
}
|
||||
public function getDbUrl() {
|
||||
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0";
|
||||
} else {
|
||||
$this->db_url = "redis://:{$this->database->redis_password}@{$this->database->uuid}:6379/0";
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.redis.general');
|
||||
}
|
||||
}
|
||||
78
app/Http/Livewire/Project/New/DockerImage.php
Normal file
78
app/Http/Livewire/Project/New/DockerImage.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\New;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Project;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DockerImage extends Component
|
||||
{
|
||||
public string $dockerImage = '';
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate([
|
||||
'dockerImage' => 'required'
|
||||
]);
|
||||
$image = Str::of($this->dockerImage)->before(':');
|
||||
if (Str::of($this->dockerImage)->contains(':')) {
|
||||
$tag = Str::of($this->dockerImage)->after(':');
|
||||
} else {
|
||||
$tag = 'latest';
|
||||
}
|
||||
$destination_uuid = $this->query['destination'];
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
if (!$destination) {
|
||||
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
|
||||
}
|
||||
if (!$destination) {
|
||||
throw new \Exception('Destination not found. What?!');
|
||||
}
|
||||
$destination_class = $destination->getMorphClass();
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
ray($image,$tag);
|
||||
$application = Application::create([
|
||||
'name' => 'docker-image-' . new Cuid2(7),
|
||||
'repository_project_id' => 0,
|
||||
'git_repository' => "coollabsio/coolify",
|
||||
'git_branch' => 'main',
|
||||
'build_pack' => 'dockerimage',
|
||||
'ports_exposes' => 80,
|
||||
'docker_registry_image_name' => $image,
|
||||
'docker_registry_image_tag' => $tag,
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination_class,
|
||||
'health_check_enabled' => false,
|
||||
]);
|
||||
|
||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||
$application->update([
|
||||
'name' => 'docker-image-' . $application->uuid,
|
||||
'fqdn' => $fqdn
|
||||
]);
|
||||
|
||||
redirect()->route('project.application.configuration', [
|
||||
'application_uuid' => $application->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.new.docker-image');
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,6 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'name' => generate_random_name(),
|
||||
'git_repository' => $this->git_repository,
|
||||
'git_branch' => $this->branch,
|
||||
'git_full_url' => $this->git_repository,
|
||||
'build_pack' => 'nixpacks',
|
||||
'ports_exposes' => $this->port,
|
||||
'publish_directory' => $this->publish_directory,
|
||||
@@ -112,7 +111,6 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
'name' => generate_random_name(),
|
||||
'git_repository' => $this->git_repository,
|
||||
'git_branch' => $this->branch,
|
||||
'git_full_url' => "git@$this->git_host:$this->git_repository.git",
|
||||
'build_pack' => 'nixpacks',
|
||||
'ports_exposes' => $this->port,
|
||||
'publish_directory' => $this->publish_directory,
|
||||
@@ -158,6 +156,8 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
$this->git_host = $this->repository_url_parsed->getHost();
|
||||
$this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2);
|
||||
$this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git');
|
||||
} else {
|
||||
$this->git_repository = $this->repository_url;
|
||||
}
|
||||
$this->git_source = 'other';
|
||||
}
|
||||
|
||||
@@ -72,10 +72,14 @@ class PublicGitRepository extends Component
|
||||
public function load_branch()
|
||||
{
|
||||
try {
|
||||
$this->branch_found = false;
|
||||
$this->validate([
|
||||
'repository_url' => 'required|url'
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
try {
|
||||
$this->branch_found = false;
|
||||
$this->get_git_source();
|
||||
$this->get_branch();
|
||||
$this->selected_branch = $this->git_branch;
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\New;
|
||||
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use Countable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Component;
|
||||
|
||||
class Select extends Component
|
||||
@@ -24,7 +23,8 @@ class Select extends Component
|
||||
public Collection|array $services = [];
|
||||
public bool $loadingServices = true;
|
||||
public bool $loading = false;
|
||||
|
||||
public $environments = [];
|
||||
public ?string $selectedEnvironment = null;
|
||||
public ?string $existingPostgresqlUrl = null;
|
||||
|
||||
protected $queryString = [
|
||||
@@ -37,8 +37,18 @@ class Select extends Component
|
||||
if (isDev()) {
|
||||
$this->existingPostgresqlUrl = 'postgres://coolify:password@coolify-db:5432';
|
||||
}
|
||||
$projectUuid = data_get($this->parameters, 'project_uuid');
|
||||
$this->environments = Project::whereUuid($projectUuid)->first()->environments;
|
||||
$this->selectedEnvironment = data_get($this->parameters, 'environment_name');
|
||||
}
|
||||
|
||||
public function updatedSelectedEnvironment()
|
||||
{
|
||||
return redirect()->route('project.resources.new', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'environment_name' => $this->selectedEnvironment,
|
||||
]);
|
||||
}
|
||||
// public function addExistingPostgresql()
|
||||
// {
|
||||
// try {
|
||||
|
||||
@@ -13,13 +13,7 @@ class Index extends Component
|
||||
public $databases;
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
protected $rules = [
|
||||
'service.docker_compose_raw' => 'required',
|
||||
'service.docker_compose' => 'required',
|
||||
'service.name' => 'required',
|
||||
'service.description' => 'nullable',
|
||||
];
|
||||
protected $listeners = ["saveCompose"];
|
||||
protected $listeners = ["refreshStacks","checkStatus"];
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.index');
|
||||
@@ -32,17 +26,12 @@ class Index extends Component
|
||||
$this->applications = $this->service->applications->sort();
|
||||
$this->databases = $this->service->databases->sort();
|
||||
}
|
||||
public function saveCompose($raw)
|
||||
{
|
||||
$this->service->docker_compose_raw = $raw;
|
||||
$this->submit();
|
||||
}
|
||||
public function checkStatus()
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->refreshStack();
|
||||
$this->refreshStacks();
|
||||
}
|
||||
public function refreshStack()
|
||||
public function refreshStacks()
|
||||
{
|
||||
$this->applications = $this->service->applications->sort();
|
||||
$this->applications->each(function ($application) {
|
||||
@@ -53,21 +42,4 @@ class Index extends Component
|
||||
$database->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->service->save();
|
||||
$this->service->parse();
|
||||
$this->service->refresh();
|
||||
$this->service->saveComposeConfigs();
|
||||
$this->refreshStack();
|
||||
$this->emit('refreshEnvs');
|
||||
$this->emit('success', 'Service saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ use Livewire\Component;
|
||||
|
||||
class Modal extends Component
|
||||
{
|
||||
public function serviceStatusUpdated() {
|
||||
$this->emit('serviceStatusUpdated');
|
||||
public function checkStatus() {
|
||||
$this->emit('checkStatus');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -13,20 +13,15 @@ class Navbar extends Component
|
||||
public Service $service;
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
protected $listeners = ['serviceStatusUpdated'];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.navbar');
|
||||
}
|
||||
public function serviceStatusUpdated()
|
||||
|
||||
public function checkStatus()
|
||||
{
|
||||
$this->check_status();
|
||||
}
|
||||
public function check_status()
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->service->refresh();
|
||||
$this->emit('checkStatus');
|
||||
}
|
||||
public function deploy()
|
||||
{
|
||||
@@ -39,5 +34,6 @@ class Navbar extends Component
|
||||
StopService::run($this->service);
|
||||
$this->service->refresh();
|
||||
$this->emit('success', 'Service stopped successfully.');
|
||||
$this->checkStatus();
|
||||
}
|
||||
}
|
||||
|
||||
42
app/Http/Livewire/Project/Service/StackForm.php
Normal file
42
app/Http/Livewire/Project/Service/StackForm.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class StackForm extends Component
|
||||
{
|
||||
public $service;
|
||||
protected $listeners = ["saveCompose"];
|
||||
protected $rules = [
|
||||
'service.docker_compose_raw' => 'required',
|
||||
'service.docker_compose' => 'required',
|
||||
'service.name' => 'required',
|
||||
'service.description' => 'nullable',
|
||||
];
|
||||
public function saveCompose($raw)
|
||||
{
|
||||
$this->service->docker_compose_raw = $raw;
|
||||
$this->submit();
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->service->save();
|
||||
$this->service->parse();
|
||||
$this->service->refresh();
|
||||
$this->service->saveComposeConfigs();
|
||||
$this->emit('refreshStacks');
|
||||
$this->emit('refreshEnvs');
|
||||
$this->emit('success', 'Service saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.stack-form');
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Shared;
|
||||
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Jobs\StopResourceJob;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -10,7 +10,7 @@ class Danger extends Component
|
||||
{
|
||||
public $resource;
|
||||
public array $parameters;
|
||||
public string|null $modalId = null;
|
||||
public ?string $modalId = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -20,20 +20,8 @@ class Danger extends Component
|
||||
|
||||
public function delete()
|
||||
{
|
||||
// Should be queued
|
||||
try {
|
||||
if ($this->resource->type() === 'service') {
|
||||
$server = $this->resource->server;
|
||||
StopService::run($this->resource);
|
||||
} else {
|
||||
$destination = data_get($this->resource, 'destination');
|
||||
if ($destination) {
|
||||
$destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first();
|
||||
$server = $destination->server;
|
||||
}
|
||||
instant_remote_process(["docker rm -f {$this->resource->uuid}"], $server);
|
||||
}
|
||||
$this->resource->delete();
|
||||
StopResourceJob::dispatchSync($this->resource);
|
||||
return redirect()->route('project.resources', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'environment_name' => $this->parameters['environment_name']
|
||||
|
||||
@@ -75,6 +75,9 @@ class All extends Component
|
||||
case 'standalone-postgresql':
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
break;
|
||||
case 'standalone-redis':
|
||||
$environment->standalone_redis_id = $this->resource->id;
|
||||
break;
|
||||
case 'service':
|
||||
$environment->service_id = $this->resource->id;
|
||||
break;
|
||||
|
||||
@@ -6,12 +6,13 @@ use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Livewire\Component;
|
||||
|
||||
class Logs extends Component
|
||||
{
|
||||
public ?string $type = null;
|
||||
public Application|StandalonePostgresql|Service $resource;
|
||||
public Application|StandalonePostgresql|Service|StandaloneRedis $resource;
|
||||
public Server $server;
|
||||
public ?string $container = null;
|
||||
public $parameters;
|
||||
@@ -33,7 +34,14 @@ class Logs extends Component
|
||||
}
|
||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->type = 'database';
|
||||
$this->resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->firstOrFail();
|
||||
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
$this->resource = $resource;
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$this->container = $this->resource->uuid;
|
||||
|
||||
@@ -30,11 +30,11 @@ class Form extends Component
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'server.name' => 'name',
|
||||
'server.description' => 'description',
|
||||
'server.ip' => 'ip',
|
||||
'server.user' => 'user',
|
||||
'server.port' => 'port',
|
||||
'server.name' => 'Name',
|
||||
'server.description' => 'Description',
|
||||
'server.ip' => 'IP address',
|
||||
'server.user' => 'User',
|
||||
'server.port' => 'Port',
|
||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||
'server.settings.is_reachable' => 'is reachable',
|
||||
'server.settings.is_part_of_swarm' => 'is part of swarm'
|
||||
@@ -45,7 +45,8 @@ class Form extends Component
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
}
|
||||
public function serverRefresh() {
|
||||
public function serverRefresh()
|
||||
{
|
||||
$this->validateServer();
|
||||
}
|
||||
public function instantSave()
|
||||
@@ -61,7 +62,19 @@ class Form extends Component
|
||||
$activity = InstallDocker::run($this->server);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
|
||||
public function checkLocalhostConnection()
|
||||
{
|
||||
$uptime = $this->server->validateConnection();
|
||||
if ($uptime) {
|
||||
$this->emit('success', 'Server is reachable.');
|
||||
$this->server->settings->is_reachable = true;
|
||||
$this->server->settings->is_usable = true;
|
||||
$this->server->settings->save();
|
||||
} else {
|
||||
$this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
public function validateServer($install = true)
|
||||
{
|
||||
try {
|
||||
@@ -69,7 +82,7 @@ class Form extends Component
|
||||
if ($uptime) {
|
||||
$install && $this->emit('success', 'Server is reachable.');
|
||||
} else {
|
||||
$install &&$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
|
||||
$install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
||||
return;
|
||||
}
|
||||
$dockerInstalled = $this->server->validateDockerEngine();
|
||||
@@ -109,7 +122,14 @@ class Form extends Component
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
if(isCloud() && !isDev()) {
|
||||
$this->validate();
|
||||
$this->validate([
|
||||
'server.ip' => 'required|ip',
|
||||
]);
|
||||
} else {
|
||||
$this->validate();
|
||||
}
|
||||
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
||||
return $server->id === $this->server->id;
|
||||
})->pluck('ip')->toArray();
|
||||
@@ -117,6 +137,7 @@ class Form extends Component
|
||||
$this->emit('error', 'IP address is already in use by another team.');
|
||||
return;
|
||||
}
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
||||
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
|
||||
$this->server->settings->save();
|
||||
|
||||
@@ -11,13 +11,13 @@ class ByIp extends Component
|
||||
{
|
||||
public $private_keys;
|
||||
public $limit_reached;
|
||||
public int|null $private_key_id = null;
|
||||
public ?int $private_key_id = null;
|
||||
public $new_private_key_name;
|
||||
public $new_private_key_description;
|
||||
public $new_private_key_value;
|
||||
|
||||
public string $name;
|
||||
public string|null $description = null;
|
||||
public ?string $description = null;
|
||||
public string $ip;
|
||||
public string $user = 'root';
|
||||
public int $port = 22;
|
||||
@@ -26,16 +26,16 @@ class ByIp extends Component
|
||||
protected $rules = [
|
||||
'name' => 'required|string',
|
||||
'description' => 'nullable|string',
|
||||
'ip' => 'required',
|
||||
'ip' => 'required|ip',
|
||||
'user' => 'required|string',
|
||||
'port' => 'required|integer',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'name' => 'name',
|
||||
'description' => 'description',
|
||||
'ip' => 'ip',
|
||||
'user' => 'user',
|
||||
'port' => 'port',
|
||||
'name' => 'Name',
|
||||
'description' => 'Description',
|
||||
'ip' => 'IP Address',
|
||||
'user' => 'User',
|
||||
'port' => 'Port',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
|
||||
@@ -15,7 +15,7 @@ class Show extends Component
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
@@ -11,18 +12,40 @@ class Deploy extends Component
|
||||
public Server $server;
|
||||
public bool $traefikDashboardAvailable = false;
|
||||
public ?string $currentRoute = null;
|
||||
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated'];
|
||||
public ?string $serverIp = null;
|
||||
|
||||
public function mount() {
|
||||
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', "checkProxy", "startProxy"];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if ($this->server->id === 0) {
|
||||
$this->serverIp = base_ip();
|
||||
} else {
|
||||
$this->serverIp = $this->server->ip;
|
||||
}
|
||||
$this->currentRoute = request()->route()->getName();
|
||||
}
|
||||
public function traefikDashboardAvailable(bool $data) {
|
||||
public function traefikDashboardAvailable(bool $data)
|
||||
{
|
||||
$this->traefikDashboardAvailable = $data;
|
||||
}
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function ip()
|
||||
{
|
||||
}
|
||||
public function checkProxy()
|
||||
{
|
||||
try {
|
||||
CheckProxy::run($this->server, true);
|
||||
$this->emit('startProxyPolling');
|
||||
$this->emit('proxyChecked');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function startProxy()
|
||||
{
|
||||
try {
|
||||
|
||||
28
app/Http/Livewire/Server/Proxy/Logs.php
Normal file
28
app/Http/Livewire/Server/Proxy/Logs.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class Logs extends Component
|
||||
{
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.all');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.proxy.logs');
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@ class Modal extends Component
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->proxy->set('status', 'running');
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ class Show extends Component
|
||||
{
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
protected $listeners = ['proxyStatusUpdated'];
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
@@ -9,12 +10,42 @@ use Livewire\Component;
|
||||
class Status extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public bool $polling = false;
|
||||
public int $numberOfPolls = 0;
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated'];
|
||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||
public function startProxyPolling()
|
||||
{
|
||||
$this->polling = true;
|
||||
}
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function checkProxy(bool $notification = false)
|
||||
{
|
||||
try {
|
||||
if ($this->polling) {
|
||||
if ($this->numberOfPolls >= 10) {
|
||||
$this->polling = false;
|
||||
$this->numberOfPolls = 0;
|
||||
$notification && $this->emit('error', 'Proxy is not running.');
|
||||
return;
|
||||
}
|
||||
$this->numberOfPolls++;
|
||||
}
|
||||
CheckProxy::run($this->server, true);
|
||||
$this->emit('proxyStatusUpdated');
|
||||
if ($this->server->proxy->status === 'running') {
|
||||
$this->polling = false;
|
||||
$notification && $this->emit('success', 'Proxy is running.');
|
||||
} else {
|
||||
$notification && $this->emit('error', 'Proxy is not running.');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function getProxyStatus()
|
||||
{
|
||||
try {
|
||||
@@ -24,11 +55,4 @@ class Status extends Component
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function getProxyStatusWithNoti()
|
||||
{
|
||||
if ($this->server->isFunctional()) {
|
||||
$this->emit('success', 'Refreshed proxy status.');
|
||||
$this->getProxyStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,34 +32,18 @@ class ShowPrivateKey extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function checkConnection($install = false)
|
||||
public function checkConnection()
|
||||
{
|
||||
try {
|
||||
$uptime = $this->server->validateConnection();
|
||||
if ($uptime) {
|
||||
$install && $this->emit('success', 'Server is reachable.');
|
||||
$this->emit('success', 'Server is reachable.');
|
||||
} else {
|
||||
$install && $this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
|
||||
return;
|
||||
}
|
||||
$dockerInstalled = $this->server->validateDockerEngine();
|
||||
if ($dockerInstalled) {
|
||||
$install && $this->emit('success', 'Docker Engine is installed.<br> Checking version.');
|
||||
} else {
|
||||
$install && $this->installDocker();
|
||||
return;
|
||||
}
|
||||
$dockerVersion = $this->server->validateDockerEngineVersion();
|
||||
if ($dockerVersion) {
|
||||
$install && $this->emit('success', 'Docker Engine version is 23+.');
|
||||
} else {
|
||||
$install && $this->installDocker();
|
||||
$this->emit('error', 'Server is not reachable. Please check your connection and private key configuration.');
|
||||
return;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,10 +78,10 @@ class Backup extends Component
|
||||
dispatch(new DatabaseBackupJob(
|
||||
backup: $this->backup
|
||||
));
|
||||
$this->emit('success', 'Backup queued. It will be available in a few minutes');
|
||||
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
$this->emit('success', 'Backup updated successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
30
app/Http/Livewire/Subscription/Show.php
Normal file
30
app/Http/Livewire/Subscription/Show.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Subscription;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public InstanceSettings $settings;
|
||||
public bool $alreadySubscribed = false;
|
||||
public function mount() {
|
||||
if (!isCloud()) {
|
||||
return redirect('/');
|
||||
}
|
||||
$this->settings = InstanceSettings::get();
|
||||
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
||||
}
|
||||
public function stripeCustomerPortal() {
|
||||
$session = getStripeCustomerPortalSession(currentTeam());
|
||||
if (is_null($session)) {
|
||||
return;
|
||||
}
|
||||
return redirect($session->url);
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.subscription.show')->layout('layouts.subscription');
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class Create extends Component
|
||||
}
|
||||
$this->storage->team_id = currentTeam()->id;
|
||||
$this->storage->testConnection();
|
||||
$this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
||||
$this->storage->is_usable = true;
|
||||
$this->storage->save();
|
||||
return redirect()->route('team.storages.show', $this->storage->uuid);
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@@ -9,6 +9,7 @@ class Form extends Component
|
||||
{
|
||||
public S3Storage $storage;
|
||||
protected $rules = [
|
||||
'storage.is_usable' => 'nullable|boolean',
|
||||
'storage.name' => 'nullable|min:3|max:255',
|
||||
'storage.description' => 'nullable|min:3|max:255',
|
||||
'storage.region' => 'required|max:255',
|
||||
@@ -18,6 +19,7 @@ class Form extends Component
|
||||
'storage.endpoint' => 'required|url|max:255',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'storage.is_usable' => 'Is Usable',
|
||||
'storage.name' => 'Name',
|
||||
'storage.description' => 'Description',
|
||||
'storage.region' => 'Region',
|
||||
|
||||
@@ -5,10 +5,11 @@ namespace App\Http\Livewire;
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use App\Models\InstanceSettings;
|
||||
use Livewire\Component;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
|
||||
class Upgrade extends Component
|
||||
{
|
||||
use WithRateLimiting;
|
||||
public bool $showProgress = false;
|
||||
public bool $isUpgradeAvailable = false;
|
||||
public string $latestVersion = '';
|
||||
@@ -31,11 +32,12 @@ class Upgrade extends Component
|
||||
public function upgrade()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(1, 30);
|
||||
if ($this->showProgress) {
|
||||
return;
|
||||
}
|
||||
$this->showProgress = true;
|
||||
resolve(UpdateCoolify::class)(true);
|
||||
UpdateCoolify::run(true);
|
||||
$this->emit('success', "Upgrading to {$this->latestVersion} version...");
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
26
app/Http/Livewire/VerifyEmail.php
Normal file
26
app/Http/Livewire/VerifyEmail.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
|
||||
class VerifyEmail extends Component
|
||||
{
|
||||
use WithRateLimiting;
|
||||
public function again() {
|
||||
try {
|
||||
$this->rateLimit(1, 300);
|
||||
auth()->user()->sendVerificationEmail();
|
||||
$this->emit('success', 'Email verification link sent!');
|
||||
|
||||
} catch(\Exception $e) {
|
||||
ray($e);
|
||||
return handleError($e,$this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.verify-email');
|
||||
}
|
||||
}
|
||||
48
app/Http/Middleware/DecideWhatToDoWithUser.php
Normal file
48
app/Http/Middleware/DecideWhatToDoWithUser.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DecideWhatToDoWithUser
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (!auth()->user() || !isCloud() || isInstanceAdmin()) {
|
||||
if (!isCloud() && showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
return redirect('boarding');
|
||||
}
|
||||
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('/verify');
|
||||
}
|
||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('subscription');
|
||||
}
|
||||
}
|
||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('boarding');
|
||||
}
|
||||
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
||||
return redirect('/');
|
||||
}
|
||||
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
||||
return redirect('/');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -45,15 +45,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private string $commit;
|
||||
private bool $force_rebuild;
|
||||
|
||||
private ?string $dockerImage = null;
|
||||
private ?string $dockerImageTag = null;
|
||||
|
||||
private GithubApp|GitlabApp|string $source = 'other';
|
||||
private StandaloneDocker|SwarmDocker $destination;
|
||||
private Server $server;
|
||||
private ApplicationPreview|null $preview = null;
|
||||
|
||||
private string $container_name;
|
||||
private string|null $currently_running_container_name = null;
|
||||
private ?string $currently_running_container_name = null;
|
||||
private string $basedir;
|
||||
private string $workdir;
|
||||
private ?string $build_pack = null;
|
||||
private string $configuration_dir;
|
||||
private string $build_image_name;
|
||||
private string $production_image_name;
|
||||
@@ -62,17 +66,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private $env_args;
|
||||
private $docker_compose;
|
||||
private $docker_compose_base64;
|
||||
|
||||
private string $dockerfile_location = '/Dockerfile';
|
||||
private ?string $addHosts = null;
|
||||
private $log_model;
|
||||
private Collection $saved_outputs;
|
||||
|
||||
private string $serverUser = 'root';
|
||||
private string $serverUserHomeDir = '/root';
|
||||
private string $dockerConfigFileExists = 'NOK';
|
||||
|
||||
public $tries = 1;
|
||||
public function __construct(int $application_deployment_queue_id)
|
||||
{
|
||||
ray()->clearScreen();
|
||||
// ray()->clearScreen();
|
||||
$this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id);
|
||||
$this->log_model = $this->application_deployment_queue;
|
||||
$this->application = Application::find($this->application_deployment_queue->application_id);
|
||||
$this->build_pack = data_get($this->application, 'build_pack');
|
||||
|
||||
$this->application_deployment_queue_id = $application_deployment_queue_id;
|
||||
$this->deployment_uuid = $this->application_deployment_queue->deployment_uuid;
|
||||
@@ -86,13 +96,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
|
||||
$this->server = $this->destination->server;
|
||||
|
||||
$this->serverUser = $this->server->user;
|
||||
$this->basedir = "/artifacts/{$this->deployment_uuid}";
|
||||
$this->workdir = "{$this->basedir}" . rtrim($this->application->base_directory, '/');
|
||||
$this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}";
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
|
||||
ray($this->basedir, $this->workdir);
|
||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||
savePrivateKeyToFs($this->server);
|
||||
$this->saved_outputs = collect();
|
||||
@@ -132,20 +141,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application_deployment_queue->update([
|
||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||
]);
|
||||
|
||||
// Generate custom host<->ip mapping
|
||||
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
||||
$ips = collect([]);
|
||||
if (count($allContainers) > 0) {
|
||||
$allContainers = $allContainers[0];
|
||||
foreach ($allContainers as $container) {
|
||||
$containerName = data_get($container, 'Name');
|
||||
if ($containerName === 'coolify-proxy') {
|
||||
continue;
|
||||
}
|
||||
$containerIp = data_get($container, 'IPv4Address');
|
||||
if ($containerName && $containerIp) {
|
||||
$containerIp = str($containerIp)->before('/');
|
||||
$ips->put($containerName, $containerIp->value());
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->addHosts = $ips->map(function ($ip, $name) {
|
||||
return "--add-host $name:$ip";
|
||||
})->implode(' ');
|
||||
|
||||
// Get user home directory
|
||||
$this->serverUserHomeDir = instant_remote_process(["echo \$HOME"], $this->server);
|
||||
$this->dockerConfigFileExists = instant_remote_process(["test -f {$this->serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $this->server);
|
||||
try {
|
||||
if ($this->application->dockerfile) {
|
||||
$this->deploy_simple_dockerfile();
|
||||
} else if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->deploy_dockerimage_buildpack();
|
||||
} else if ($this->application->build_pack === 'dockerfile') {
|
||||
$this->deploy_dockerfile_buildpack();
|
||||
} else {
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
} else {
|
||||
$this->deploy();
|
||||
$this->deploy_nixpacks_buildpack();
|
||||
}
|
||||
}
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(true);
|
||||
} catch (Exception $e) {
|
||||
ray($e);
|
||||
$this->fail($e);
|
||||
@@ -173,41 +213,43 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
);
|
||||
}
|
||||
}
|
||||
private function deploy_docker_compose()
|
||||
{
|
||||
$dockercompose_base64 = base64_encode($this->application->dockercompose);
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Starting deployment of {$this->application->name}.'"
|
||||
],
|
||||
);
|
||||
$this->prepare_builder_image();
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
||||
],
|
||||
);
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||
$this->save_environment_variables();
|
||||
$containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
$containerName = data_get($container, 'Names');
|
||||
if ($containerName) {
|
||||
instant_remote_process(
|
||||
["docker rm -f {$containerName}"],
|
||||
$this->application->destination->server
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting services (could take a while)...'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
// private function deploy_docker_compose()
|
||||
// {
|
||||
// $dockercompose_base64 = base64_encode($this->application->dockercompose);
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// "echo 'Starting deployment of {$this->application->name}.'"
|
||||
// ],
|
||||
// );
|
||||
// $this->prepare_builder_image();
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
||||
// ],
|
||||
// );
|
||||
// $this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
||||
// $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||
// $this->save_environment_variables();
|
||||
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||
// ray($containers);
|
||||
// if ($containers->count() > 0) {
|
||||
// foreach ($containers as $container) {
|
||||
// $containerName = data_get($container, 'Names');
|
||||
// if ($containerName) {
|
||||
// instant_remote_process(
|
||||
// ["docker rm -f {$containerName}"],
|
||||
// $this->application->destination->server
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// $this->execute_remote_command(
|
||||
// ["echo -n 'Starting services (could take a while)...'"],
|
||||
// [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
// );
|
||||
// }
|
||||
private function save_environment_variables()
|
||||
{
|
||||
$envs = collect([]);
|
||||
@@ -245,7 +287,51 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->rolling_update();
|
||||
}
|
||||
|
||||
private function deploy()
|
||||
private function deploy_dockerimage_buildpack()
|
||||
{
|
||||
$this->dockerImage = $this->application->docker_registry_image_name;
|
||||
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"
|
||||
],
|
||||
);
|
||||
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||
$this->prepare_builder_image();
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
}
|
||||
|
||||
private function deploy_dockerfile_buildpack()
|
||||
{
|
||||
if (data_get($this->application, 'dockerfile_location')) {
|
||||
$this->dockerfile_location = $this->application->dockerfile_location;
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
||||
],
|
||||
);
|
||||
$this->prepare_builder_image();
|
||||
$this->clone_repository();
|
||||
$this->set_base_dir();
|
||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
||||
if (strlen($tag) > 128) {
|
||||
$tag = $tag->substr(0, 128);
|
||||
}
|
||||
|
||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
||||
// ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green();
|
||||
$this->cleanup_git();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->rolling_update();
|
||||
}
|
||||
private function deploy_nixpacks_buildpack()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
@@ -268,19 +354,22 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->execute_remote_command([
|
||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||
]);
|
||||
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
$this->execute_remote_command([
|
||||
"echo 'Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped...'"
|
||||
"echo 'No configuration changed & Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped.'",
|
||||
]);
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
return;
|
||||
}
|
||||
if ($this->application->isConfigurationChanged()) {
|
||||
$this->execute_remote_command([
|
||||
"echo 'Configuration changed. Rebuilding image.'",
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->cleanup_git();
|
||||
if ($this->application->build_pack === 'nixpacks') {
|
||||
$this->generate_nixpacks_confs();
|
||||
}
|
||||
$this->generate_nixpacks_confs();
|
||||
$this->generate_compose_file();
|
||||
$this->generate_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
@@ -374,7 +463,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->stop_running_container();
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting preview deployment.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -382,7 +471,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
$pull = "--pull=always";
|
||||
$helperImage = config('coolify.helper_image');
|
||||
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
if ($this->dockerConfigFileExists === 'OK') {
|
||||
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v {$this->serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
} else {
|
||||
$runCommand = "docker run {$pull} -d --network {$this->destination->network} -v /:/host --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}";
|
||||
}
|
||||
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
@@ -398,7 +491,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
);
|
||||
}
|
||||
|
||||
private function set_base_dir() {
|
||||
private function set_base_dir()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo -n 'Setting base directory to {$this->workdir}.'"
|
||||
@@ -455,8 +549,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
}
|
||||
if ($this->application->deploymentType() === 'deploy_key') {
|
||||
$port = 22;
|
||||
preg_match('/(?<=:)\d+(?=\/)/', $this->application->git_repository, $matches);
|
||||
if (count($matches) === 1) {
|
||||
$port = $matches[0];
|
||||
}
|
||||
$private_key = base64_encode($this->application->private_key->private_key);
|
||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->basedir}";
|
||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -p $port -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}";
|
||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||
$commands = collect([
|
||||
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
||||
@@ -556,6 +655,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables($ports);
|
||||
|
||||
$labels = generateLabelsApplication($this->application, $this->preview);
|
||||
if (data_get($this->application, 'custom_labels')) {
|
||||
$labels = str($this->application->custom_labels)->explode(',')->toArray();
|
||||
}
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
@@ -564,7 +667,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
'container_name' => $this->container_name,
|
||||
'restart' => RESTART_MODE,
|
||||
'environment' => $environment_variables,
|
||||
'labels' => generateLabelsApplication($this->application, $this->preview),
|
||||
'labels' => $labels,
|
||||
'expose' => $ports,
|
||||
'networks' => [
|
||||
$this->destination->network,
|
||||
@@ -608,6 +711,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
// if ($this->build_pack === 'dockerfile') {
|
||||
// $docker_compose['services'][$this->container_name]['build'] = [
|
||||
// 'context' => $this->workdir,
|
||||
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
||||
// ];
|
||||
// }
|
||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||
@@ -671,7 +780,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function generate_healthcheck_commands()
|
||||
{
|
||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') {
|
||||
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
||||
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
||||
return 'exit 0';
|
||||
}
|
||||
@@ -700,7 +809,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
if ($this->application->settings->is_static) {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
|
||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||
@@ -733,12 +842,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
||||
],
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "docker build --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -764,7 +873,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Starting application (could take a while).'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -787,7 +896,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
private function add_build_env_variables_to_dockerfile()
|
||||
{
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile'
|
||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile'
|
||||
]);
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
|
||||
@@ -796,7 +905,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"),
|
||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/{$this->dockerfile_location}"),
|
||||
"hidden" => true
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
@@ -27,11 +28,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public $tries = 1;
|
||||
public $timeout = 120;
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->handle();
|
||||
}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||
@@ -41,34 +37,56 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
return $this->server->uuid;
|
||||
}
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
if (isDev()) {
|
||||
$this->handle();
|
||||
}
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
ray("checking server status for {$this->server->name}");
|
||||
// ray("checking server status for {$this->server->id}");
|
||||
// ray()->clearAll();
|
||||
$serverUptimeCheckNumber = 0;
|
||||
$serverUptimeCheckNumber = $this->server->unreachable_count;
|
||||
$serverUptimeCheckNumberMax = 3;
|
||||
while (true) {
|
||||
ray('checking # ' . $serverUptimeCheckNumber);
|
||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||
send_internal_notification('Server unreachable: ' . $this->server->name);
|
||||
if ($this->server->unreachable_email_sent === false) {
|
||||
ray('Server unreachable, sending notification...');
|
||||
$this->server->team->notify(new Unreachable($this->server));
|
||||
}
|
||||
$this->server->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
|
||||
// ray('checking # ' . $serverUptimeCheckNumber);
|
||||
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||
if ($this->server->unreachable_email_sent === false) {
|
||||
ray('Server unreachable, sending notification...');
|
||||
$this->server->team->notify(new Unreachable($this->server));
|
||||
$this->server->update(['unreachable_email_sent' => true]);
|
||||
return;
|
||||
}
|
||||
$result = $this->server->validateConnection();
|
||||
if ($result) {
|
||||
break;
|
||||
}
|
||||
$serverUptimeCheckNumber++;
|
||||
sleep(5);
|
||||
$this->server->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
$this->server->update([
|
||||
'unreachable_count' => 0,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
$result = $this->server->validateConnection();
|
||||
if ($result) {
|
||||
$this->server->settings()->update([
|
||||
'is_reachable' => true,
|
||||
]);
|
||||
$this->server->update([
|
||||
'unreachable_count' => 0,
|
||||
]);
|
||||
} else {
|
||||
$serverUptimeCheckNumber++;
|
||||
$this->server->settings()->update([
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
$this->server->update([
|
||||
'unreachable_count' => $serverUptimeCheckNumber,
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data_get($this->server, 'unreachable_email_sent') === true) {
|
||||
ray('Server is reachable again, sending notification...');
|
||||
$this->server->team->notify(new Revived($this->server));
|
||||
@@ -83,7 +101,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
'is_usable' => true
|
||||
]);
|
||||
}
|
||||
$this->server->validateDockerEngine(true);
|
||||
// $this->server->validateDockerEngine(true);
|
||||
$containers = instant_remote_process(["docker container ls -q"], $this->server);
|
||||
if (!$containers) {
|
||||
return;
|
||||
@@ -100,11 +118,18 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
return data_get($value, 'Name') === '/coolify-proxy';
|
||||
})->first();
|
||||
if (!$foundProxyContainer) {
|
||||
ray('Proxy not found, starting it...');
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
StartProxy::run($this->server, false);
|
||||
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
try {
|
||||
$shouldStart = CheckProxy::run($this->server);
|
||||
if ($shouldStart) {
|
||||
StartProxy::run($this->server, false);
|
||||
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
} else {
|
||||
ray('Proxy could not be started.');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
}
|
||||
|
||||
} else {
|
||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||
$this->server->save();
|
||||
|
||||
@@ -31,7 +31,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
public ?string $container_name = null;
|
||||
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||
public string $backup_status;
|
||||
public string $backup_status = 'failed';
|
||||
public ?string $backup_location = null;
|
||||
public string $backup_dir;
|
||||
public string $backup_file;
|
||||
@@ -66,65 +66,87 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
ray('database not running');
|
||||
return;
|
||||
}
|
||||
$databaseType = $this->database->type();
|
||||
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||
|
||||
if (is_null($databasesToBackup)) {
|
||||
if ($databaseType === 'standalone-postgresql') {
|
||||
$databasesToBackup = [$this->database->postgres_db];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$databasesToBackup = explode(',', $databasesToBackup);
|
||||
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||
}
|
||||
$this->container_name = $this->database->uuid;
|
||||
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_name;
|
||||
|
||||
if ($this->database->name === 'coolify-db') {
|
||||
$databasesToBackup = ['coolify'];
|
||||
$this->container_name = "coolify-db";
|
||||
$ip = Str::slug($this->server->ip);
|
||||
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
||||
}
|
||||
$this->backup_file = "/pg_dump-" . Carbon::now()->timestamp . ".dump";
|
||||
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||
|
||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||
'filename' => $this->backup_location,
|
||||
'scheduled_database_backup_id' => $this->backup->id,
|
||||
]);
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$this->backup_standalone_postgresql();
|
||||
foreach ($databasesToBackup as $database) {
|
||||
$size = 0;
|
||||
ray('Backing up ' . $database);
|
||||
try {
|
||||
$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,
|
||||
'filename' => $this->backup_location,
|
||||
'scheduled_database_backup_id' => $this->backup->id,
|
||||
]);
|
||||
if ($databaseType === 'standalone-postgresql') {
|
||||
$this->backup_standalone_postgresql($database);
|
||||
}
|
||||
$size = $this->calculate_size();
|
||||
$this->remove_old_backups();
|
||||
if ($this->backup->save_s3) {
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
||||
$this->backup_log->update([
|
||||
'status' => 'success',
|
||||
'message' => $this->backup_output,
|
||||
'size' => $size,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->backup_log->update([
|
||||
'status' => 'failed',
|
||||
'message' => $this->backup_output,
|
||||
'size' => $size,
|
||||
'filename' => null
|
||||
]);
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
$this->calculate_size();
|
||||
$this->remove_old_backups();
|
||||
if ($this->backup->save_s3) {
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->save_backup_logs();
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function backup_standalone_postgresql(): void
|
||||
private function backup_standalone_postgresql(string $database): void
|
||||
{
|
||||
try {
|
||||
ray($this->backup_dir);
|
||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||
$commands[] = "docker exec $this->container_name pg_dump -Fc -U {$this->database->postgres_user} > $this->backup_location";
|
||||
|
||||
$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);
|
||||
|
||||
if ($this->backup_output === '') {
|
||||
$this->backup_output = null;
|
||||
}
|
||||
|
||||
ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location);
|
||||
|
||||
$this->backup_status = 'success';
|
||||
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
||||
} catch (\Throwable $e) {
|
||||
$this->backup_status = 'failed';
|
||||
$this->add_to_backup_output($e->getMessage());
|
||||
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||
} finally {
|
||||
$this->backup_log->update([
|
||||
'status' => $this->backup_status,
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,9 +159,9 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
}
|
||||
|
||||
private function calculate_size(): void
|
||||
private function calculate_size()
|
||||
{
|
||||
$this->size = instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server);
|
||||
return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false);
|
||||
}
|
||||
|
||||
private function remove_old_backups(): void
|
||||
@@ -163,11 +185,16 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
$key = $this->s3->key;
|
||||
$secret = $this->s3->secret;
|
||||
// $region = $this->s3->region;
|
||||
// $region = $this->s3->region;
|
||||
$bucket = $this->s3->bucket;
|
||||
$endpoint = $this->s3->endpoint;
|
||||
$this->s3->testConnection();
|
||||
if (isDev()) {
|
||||
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v coolify_coolify-data-dev:/data/coolify:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
|
||||
} else {
|
||||
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1";
|
||||
}
|
||||
|
||||
$commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper";
|
||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret";
|
||||
$commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/";
|
||||
instant_remote_process($commands, $this->server);
|
||||
@@ -175,19 +202,10 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir);
|
||||
} catch (\Throwable $e) {
|
||||
$this->add_to_backup_output($e->getMessage());
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
$command = "docker rm -f backup-of-{$this->backup->uuid}";
|
||||
instant_remote_process([$command], $this->server);
|
||||
}
|
||||
}
|
||||
|
||||
private function save_backup_logs(): void
|
||||
{
|
||||
$this->backup_log->update([
|
||||
'status' => $this->backup_status,
|
||||
'message' => $this->backup_output,
|
||||
'size' => $this->size,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,20 +47,7 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (!$this->server->isFunctional()) {
|
||||
return;
|
||||
}
|
||||
if (isDev()) {
|
||||
$this->dockerRootFilesystem = "/";
|
||||
} else {
|
||||
$this->dockerRootFilesystem = instant_remote_process(
|
||||
[
|
||||
"stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')"
|
||||
],
|
||||
$this->server,
|
||||
false
|
||||
);
|
||||
}
|
||||
if (!$this->dockerRootFilesystem) {
|
||||
return;
|
||||
}
|
||||
$this->dockerRootFilesystem = "/";
|
||||
$this->usageBefore = $this->getFilesystemUsage();
|
||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||
ray('Cleaning up ' . $this->server->name)->color('orange');
|
||||
|
||||
@@ -23,6 +23,6 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique, ShouldBeEncr
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
resolve(UpdateCoolify::class)($this->force);
|
||||
UpdateCoolify::run($this->force);
|
||||
}
|
||||
}
|
||||
|
||||
55
app/Jobs/StopResourceJob.php
Normal file
55
app/Jobs/StopResourceJob.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Models\Application;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class StopResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis $resource)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$server = $this->resource->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
StopApplication::run($this->resource);
|
||||
break;
|
||||
case 'standalone-postgresql':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'standalone-redis':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'service':
|
||||
StopService::run($this->resource);
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->resource->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,14 +32,8 @@ class Application extends BaseModel
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($application) {
|
||||
// Stop Container
|
||||
instant_remote_process(
|
||||
["docker rm -f {$application->uuid}"],
|
||||
$application->destination->server,
|
||||
false
|
||||
);
|
||||
$application->settings()->delete();
|
||||
$storages = $application->persistentStorages()->get();
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $application->destination->server, false);
|
||||
}
|
||||
@@ -99,7 +93,21 @@ class Application extends BaseModel
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function dockerfileLocation(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: function ($value) {
|
||||
if (is_null($value) || $value === '') {
|
||||
return '/Dockerfile';
|
||||
} else {
|
||||
if ($value !== '/') {
|
||||
return Str::start(Str::replaceEnd('/', '', $value), '/');
|
||||
}
|
||||
return Str::start($value, '/');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
public function baseDirectory(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@@ -257,15 +265,43 @@ class Application extends BaseModel
|
||||
if ($this->dockerfile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->build_pack === 'dockerimage') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public function isHealthcheckDisabled(): bool
|
||||
{
|
||||
if (data_get($this, 'dockerfile') || data_get($this, 'build_pack') === 'dockerfile' || data_get($this, 'health_check_enabled') === false) {
|
||||
ray('dockerfile');
|
||||
if (data_get($this, 'health_check_enabled') === false) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public function isConfigurationChanged($save = false)
|
||||
{
|
||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->health_check_path . $this->health_check_port . $this->health_check_host . $this->health_check_method . $this->health_check_return_code . $this->health_check_scheme . $this->health_check_response_text . $this->health_check_interval . $this->health_check_timeout . $this->health_check_retries . $this->health_check_start_period . $this->health_check_enabled . $this->limits_memory . $this->limits_swap . $this->limits_swappiness . $this->limits_reservation . $this->limits_cpus . $this->limits_cpuset . $this->limits_cpu_shares . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
|
||||
if ($this->pull_request_id === 0) {
|
||||
$newConfigHash .= json_encode($this->environment_variables->all());
|
||||
} else {
|
||||
$newConfigHash .= json_encode($this->environment_variables_preview->all());
|
||||
}
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class Environment extends Model
|
||||
|
||||
public function can_delete_environment()
|
||||
{
|
||||
return $this->applications()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
|
||||
return $this->applications()->count() == 0 && $this->redis()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
|
||||
}
|
||||
|
||||
public function applications()
|
||||
@@ -26,10 +26,16 @@ class Environment extends Model
|
||||
{
|
||||
return $this->hasMany(StandalonePostgresql::class);
|
||||
}
|
||||
public function redis()
|
||||
{
|
||||
return $this->hasMany(StandaloneRedis::class);
|
||||
}
|
||||
|
||||
public function databases()
|
||||
{
|
||||
return $this->postgresqls();
|
||||
$postgresqls = $this->postgresqls;
|
||||
$redis = $this->redis;
|
||||
return $postgresqls->concat($redis);
|
||||
}
|
||||
|
||||
public function project()
|
||||
|
||||
@@ -52,4 +52,8 @@ class Project extends BaseModel
|
||||
{
|
||||
return $this->hasManyThrough(StandalonePostgresql::class, Environment::class);
|
||||
}
|
||||
public function redis()
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneRedis::class, Environment::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class S3Storage extends BaseModel
|
||||
{
|
||||
@@ -10,6 +12,7 @@ class S3Storage extends BaseModel
|
||||
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
'is_usable' => 'boolean',
|
||||
'key' => 'encrypted',
|
||||
'secret' => 'encrypted',
|
||||
];
|
||||
@@ -19,7 +22,15 @@ class S3Storage extends BaseModel
|
||||
$selectArray = collect($select)->concat(['id']);
|
||||
return S3Storage::whereTeamId(currentTeam()->id)->select($selectArray->all())->orderBy('name');
|
||||
}
|
||||
public function isUsable()
|
||||
{
|
||||
return $this->is_usable;
|
||||
}
|
||||
|
||||
public function team()
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
public function awsUrl()
|
||||
{
|
||||
return "{$this->endpoint}/{$this->bucket}";
|
||||
@@ -27,7 +38,34 @@ class S3Storage extends BaseModel
|
||||
|
||||
public function testConnection()
|
||||
{
|
||||
set_s3_target($this);
|
||||
return \Storage::disk('custom-s3')->files();
|
||||
try {
|
||||
set_s3_target($this);
|
||||
Storage::disk('custom-s3')->files();
|
||||
$this->unusable_email_sent = false;
|
||||
$this->is_usable = true;
|
||||
return;
|
||||
} catch (\Throwable $e) {
|
||||
$this->is_usable = false;
|
||||
if ($this->unusable_email_sent === false && is_transactional_emails_active()) {
|
||||
$mail = new MailMessage();
|
||||
$mail->subject('Coolify: S3 Storage Connection Error');
|
||||
$mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storages.show', ['storage_uuid' => $this->uuid])]);
|
||||
$users = collect([]);
|
||||
$members = $this->team->members()->get();
|
||||
foreach ($members as $user) {
|
||||
if ($user->isAdmin()) {
|
||||
$users->push($user);
|
||||
}
|
||||
}
|
||||
foreach ($users as $user) {
|
||||
send_user_an_email($mail, $user->email);
|
||||
}
|
||||
$this->unusable_email_sent = true;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,14 @@ class Server extends BaseModel
|
||||
protected static function booted()
|
||||
{
|
||||
static::saving(function ($server) {
|
||||
$server->forceFill([
|
||||
'ip' => Str::of($server->ip)->trim(),
|
||||
'user' => Str::of($server->user)->trim(),
|
||||
]);
|
||||
$payload = [];
|
||||
if ($server->user) {
|
||||
$payload['user'] = Str::of($server->user)->trim();
|
||||
}
|
||||
if ($server->ip) {
|
||||
$payload['ip'] = Str::of($server->ip)->trim();
|
||||
}
|
||||
$server->forceFill($payload);
|
||||
});
|
||||
|
||||
static::created(function ($server) {
|
||||
@@ -89,8 +93,11 @@ class Server extends BaseModel
|
||||
|
||||
public function proxyType()
|
||||
{
|
||||
$type = $this->proxy->get('type');
|
||||
if (is_null($type)) {
|
||||
$proxyType = $this->proxy->get('type');
|
||||
if ($proxyType === ProxyTypes::NONE->value) {
|
||||
return $proxyType;
|
||||
}
|
||||
if (is_null($proxyType)) {
|
||||
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||
$this->proxy->status = ProxyStatus::EXITED->value;
|
||||
$this->save();
|
||||
@@ -116,7 +123,8 @@ class Server extends BaseModel
|
||||
{
|
||||
return $this->destinations()->map(function ($standaloneDocker) {
|
||||
$postgresqls = $standaloneDocker->postgresqls;
|
||||
return $postgresqls?->concat([]) ?? collect([]);
|
||||
$redis = $standaloneDocker->redis;
|
||||
return $postgresqls->concat($redis);
|
||||
})->flatten();
|
||||
}
|
||||
public function applications()
|
||||
@@ -185,23 +193,24 @@ class Server extends BaseModel
|
||||
}
|
||||
public function isProxyShouldRun()
|
||||
{
|
||||
$shouldRun = false;
|
||||
if ($this->proxyType() === ProxyTypes::NONE->value) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->applications() as $application) {
|
||||
if (data_get($application, 'fqdn')) {
|
||||
$shouldRun = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($this->id === 0) {
|
||||
$settings = InstanceSettings::get();
|
||||
if (data_get($settings, 'fqdn')) {
|
||||
$shouldRun = true;
|
||||
}
|
||||
}
|
||||
return $shouldRun;
|
||||
// foreach ($this->applications() as $application) {
|
||||
// if (data_get($application, 'fqdn')) {
|
||||
// $shouldRun = true;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// ray($this->services()->get());
|
||||
|
||||
// if ($this->id === 0) {
|
||||
// $settings = InstanceSettings::get();
|
||||
// if (data_get($settings, 'fqdn')) {
|
||||
// $shouldRun = true;
|
||||
// }
|
||||
// }
|
||||
return true;
|
||||
}
|
||||
public function isFunctional()
|
||||
{
|
||||
|
||||
@@ -19,7 +19,6 @@ class Service extends BaseModel
|
||||
static::deleting(function ($service) {
|
||||
$storagesToDelete = collect([]);
|
||||
foreach ($service->applications()->get() as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
|
||||
$storages = $application->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
@@ -27,7 +26,6 @@ class Service extends BaseModel
|
||||
$application->persistentStorages()->delete();
|
||||
}
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
instant_remote_process(["docker rm -f {$database->name}-{$service->uuid}"], $service->server, false);
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
$storagesToDelete->push($storage);
|
||||
@@ -384,7 +382,7 @@ class Service extends BaseModel
|
||||
$value = Str::of($variable);
|
||||
}
|
||||
if ($key->startsWith('SERVICE_FQDN')) {
|
||||
if ($isNew) {
|
||||
if ($isNew || $savedService->fqdn === null) {
|
||||
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
||||
$fqdn = generateFqdn($this->server, "{$name->value()}-{$this->uuid}");
|
||||
if (substr_count($key->value(), '_') === 3) {
|
||||
@@ -540,7 +538,7 @@ class Service extends BaseModel
|
||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||
if (!$isDatabase && $fqdns->count() > 0) {
|
||||
if ($fqdns) {
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, true));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($this->uuid, $fqdns, true));
|
||||
}
|
||||
}
|
||||
data_set($service, 'labels', $serviceLabels->toArray());
|
||||
@@ -570,7 +568,7 @@ class Service extends BaseModel
|
||||
'networks' => $topLevelNetworks->toArray(),
|
||||
];
|
||||
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||
$this->save();
|
||||
$this->saveComposeConfigs();
|
||||
return collect([]);
|
||||
|
||||
@@ -15,6 +15,10 @@ class StandaloneDocker extends BaseModel
|
||||
{
|
||||
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
||||
}
|
||||
public function redis()
|
||||
{
|
||||
return $this->morphMany(StandaloneRedis::class, 'destination');
|
||||
}
|
||||
|
||||
public function server()
|
||||
{
|
||||
|
||||
@@ -29,21 +29,13 @@ class StandalonePostgresql extends BaseModel
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($database) {
|
||||
// Stop Container
|
||||
instant_remote_process(
|
||||
["docker rm -f {$database->uuid}"],
|
||||
$database->destination->server,
|
||||
false
|
||||
);
|
||||
// Stop TCP Proxy
|
||||
if ($database->is_public) {
|
||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server, false);
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
||||
}
|
||||
$database->scheduledBackups()->delete();
|
||||
$database->persistentStorages()->delete();
|
||||
$database->environment_variables()->delete();
|
||||
// Remove Volume
|
||||
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
95
app/Models/StandaloneRedis.php
Normal file
95
app/Models/StandaloneRedis.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class StandaloneRedis extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($database) {
|
||||
LocalPersistentVolume::create([
|
||||
'name' => 'redis-data-' . $database->uuid,
|
||||
'mount_path' => '/data',
|
||||
'host_path' => null,
|
||||
'resource_id' => $database->id,
|
||||
'resource_type' => $database->getMorphClass(),
|
||||
'is_readonly' => true
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($database) {
|
||||
$database->scheduledBackups()->delete();
|
||||
$storages = $database->persistentStorages()->get();
|
||||
foreach ($storages as $storage) {
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $database->destination->server, false);
|
||||
}
|
||||
$database->persistentStorages()->delete();
|
||||
$database->environment_variables()->delete();
|
||||
});
|
||||
}
|
||||
|
||||
public function portsMappings(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
|
||||
// Normal Deployments
|
||||
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => is_null($this->ports_mappings)
|
||||
? []
|
||||
: explode(',', $this->ports_mappings),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function type(): string
|
||||
{
|
||||
return 'standalone-redis';
|
||||
}
|
||||
|
||||
public function environment()
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function fileStorages()
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class);
|
||||
}
|
||||
|
||||
public function runtime_environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class);
|
||||
}
|
||||
|
||||
public function persistentStorages()
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function scheduledBackups()
|
||||
{
|
||||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class Subscription extends Model
|
||||
}
|
||||
if (isStripe()) {
|
||||
if (!$this->stripe_plan_id) {
|
||||
return 'zero';
|
||||
return 'zero';
|
||||
}
|
||||
$subscription = Subscription::where('id', $this->id)->first();
|
||||
if (!$subscription) {
|
||||
|
||||
@@ -48,6 +48,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
}
|
||||
return explode(',', $recipients);
|
||||
}
|
||||
|
||||
public function limits(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@@ -125,7 +126,7 @@ class Team extends Model implements SendsDiscord, SendsEmail
|
||||
|
||||
public function s3s()
|
||||
{
|
||||
return $this->hasMany(S3Storage::class);
|
||||
return $this->hasMany(S3Storage::class)->where('is_usable', true);
|
||||
}
|
||||
public function trialEnded() {
|
||||
foreach ($this->servers as $server) {
|
||||
|
||||
@@ -6,8 +6,12 @@ use App\Notifications\Channels\SendsEmail;
|
||||
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
@@ -54,6 +58,23 @@ class User extends Authenticatable implements SendsEmail
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function sendVerificationEmail()
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$url = Url::temporarySignedRoute(
|
||||
'verify.verify',
|
||||
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
|
||||
[
|
||||
'id' => $this->getKey(),
|
||||
'hash' => sha1($this->getEmailForVerification()),
|
||||
]
|
||||
);
|
||||
$mail->view('emails.email-verification', [
|
||||
'url' => $url,
|
||||
]);
|
||||
$mail->subject('Coolify: Verify your email.');
|
||||
send_user_an_email($mail, $this->email);
|
||||
}
|
||||
public function sendPasswordResetNotification($token): void
|
||||
{
|
||||
$this->notify(new TransactionalEmailsResetPassword($token));
|
||||
@@ -61,7 +82,7 @@ class User extends Authenticatable implements SendsEmail
|
||||
|
||||
public function isAdmin()
|
||||
{
|
||||
return data_get($this->pivot,'role') === 'admin' || data_get($this->pivot,'role') === 'owner';
|
||||
return data_get($this->pivot, 'role') === 'admin' || data_get($this->pivot, 'role') === 'owner';
|
||||
}
|
||||
|
||||
public function isAdminFromSession()
|
||||
@@ -79,7 +100,7 @@ class User extends Authenticatable implements SendsEmail
|
||||
return true;
|
||||
}
|
||||
$team = $teams->where('id', session('currentTeam')->id)->first();
|
||||
$role = data_get($team,'pivot.role');
|
||||
$role = data_get($team, 'pivot.role');
|
||||
return $role === 'admin' || $role === 'owner';
|
||||
}
|
||||
|
||||
@@ -96,7 +117,10 @@ class User extends Authenticatable implements SendsEmail
|
||||
|
||||
public function currentTeam()
|
||||
{
|
||||
return Cache::remember('team:' . auth()->user()->id, 3600, function() {
|
||||
return Cache::remember('team:' . auth()->user()->id, 3600, function () {
|
||||
if (is_null(data_get(session('currentTeam'), 'id'))) {
|
||||
return auth()->user()->teams[0];
|
||||
}
|
||||
return Team::find(session('currentTeam')->id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
||||
$pull_request_id = data_get($this->preview, 'pull_request_id', 0);
|
||||
$fqdn = $this->fqdn;
|
||||
if ($pull_request_id === 0) {
|
||||
$mail->subject('❌ Deployment failed of ' . $this->application_name . '.');
|
||||
$mail->subject('Coolify: Deployment failed of ' . $this->application_name . '.');
|
||||
} else {
|
||||
$fqdn = $this->preview->fqdn;
|
||||
$mail->subject('❌ Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.');
|
||||
$mail->subject('Coolify: Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.');
|
||||
}
|
||||
$mail->view('emails.application-deployment-failed', [
|
||||
'name' => $this->application_name,
|
||||
@@ -69,10 +69,10 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
||||
public function toDiscord(): string
|
||||
{
|
||||
if ($this->preview) {
|
||||
$message = '❌ Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
|
||||
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
|
||||
$message .= '[View Deployment Logs](' . $this->deployment_url . ')';
|
||||
} else {
|
||||
$message = '❌ Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
||||
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
||||
$message .= '[View Deployment Logs](' . $this->deployment_url . ')';
|
||||
}
|
||||
return $message;
|
||||
@@ -80,9 +80,9 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
||||
public function toTelegram(): array
|
||||
{
|
||||
if ($this->preview) {
|
||||
$message = '❌ Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
|
||||
$message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: ';
|
||||
} else {
|
||||
$message = '❌ Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
||||
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
||||
}
|
||||
return [
|
||||
"message" => $message,
|
||||
|
||||
@@ -52,10 +52,10 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
$pull_request_id = data_get($this->preview, 'pull_request_id', 0);
|
||||
$fqdn = $this->fqdn;
|
||||
if ($pull_request_id === 0) {
|
||||
$mail->subject("✅ New version is deployed of {$this->application_name}");
|
||||
$mail->subject("Coolify: New version is deployed of {$this->application_name}");
|
||||
} else {
|
||||
$fqdn = $this->preview->fqdn;
|
||||
$mail->subject("✅ Pull request #{$pull_request_id} of {$this->application_name} deployed successfully");
|
||||
$mail->subject("Coolify: Pull request #{$pull_request_id} of {$this->application_name} deployed successfully");
|
||||
}
|
||||
$mail->view('emails.application-deployment-success', [
|
||||
'name' => $this->application_name,
|
||||
@@ -69,7 +69,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
public function toDiscord(): string
|
||||
{
|
||||
if ($this->preview) {
|
||||
$message = '✅ New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '
|
||||
$message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '
|
||||
|
||||
';
|
||||
if ($this->preview->fqdn) {
|
||||
@@ -77,7 +77,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
}
|
||||
$message .= '[Deployment logs](' . $this->deployment_url . ')';
|
||||
} else {
|
||||
$message = '✅ New version successfully deployed of ' . $this->application_name . '
|
||||
$message = 'Coolify: New version successfully deployed of ' . $this->application_name . '
|
||||
|
||||
';
|
||||
if ($this->fqdn) {
|
||||
@@ -90,7 +90,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
||||
public function toTelegram(): array
|
||||
{
|
||||
if ($this->preview) {
|
||||
$message = '✅ New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
|
||||
$message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . '';
|
||||
if ($this->preview->fqdn) {
|
||||
$buttons[] = [
|
||||
"text" => "Open Application",
|
||||
|
||||
@@ -15,25 +15,23 @@ class StatusChanged extends Notification implements ShouldQueue
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public Application $application;
|
||||
public string $application_name;
|
||||
public string $resource_name;
|
||||
public string $project_uuid;
|
||||
public string $environment_name;
|
||||
|
||||
public ?string $application_url = null;
|
||||
public ?string $resource_url = null;
|
||||
public ?string $fqdn;
|
||||
|
||||
public function __construct($application)
|
||||
public function __construct(public Application $resource)
|
||||
{
|
||||
$this->application = $application;
|
||||
$this->application_name = data_get($application, 'name');
|
||||
$this->project_uuid = data_get($application, 'environment.project.uuid');
|
||||
$this->environment_name = data_get($application, 'environment.name');
|
||||
$this->fqdn = data_get($application, 'fqdn', null);
|
||||
$this->resource_name = data_get($resource, 'name');
|
||||
$this->project_uuid = data_get($resource, 'environment.project.uuid');
|
||||
$this->environment_name = data_get($resource, 'environment.name');
|
||||
$this->fqdn = data_get($resource, 'fqdn', null);
|
||||
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
||||
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
||||
}
|
||||
$this->application_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}";
|
||||
$this->resource_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->resource->uuid}";
|
||||
}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
@@ -45,32 +43,32 @@ class StatusChanged extends Notification implements ShouldQueue
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$fqdn = $this->fqdn;
|
||||
$mail->subject("⛔ {$this->application_name} has been stopped");
|
||||
$mail->subject("Coolify: {$this->resource_name} has been stopped");
|
||||
$mail->view('emails.application-status-changes', [
|
||||
'name' => $this->application_name,
|
||||
'name' => $this->resource_name,
|
||||
'fqdn' => $fqdn,
|
||||
'application_url' => $this->application_url,
|
||||
'resource_url' => $this->resource_url,
|
||||
]);
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = '⛔ ' . $this->application_name . ' has been stopped.
|
||||
$message = 'Coolify: ' . $this->resource_name . ' has been stopped.
|
||||
|
||||
';
|
||||
$message .= '[Open Application in Coolify](' . $this->application_url . ')';
|
||||
$message .= '[Open Application in Coolify](' . $this->resource_url . ')';
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$message = '⛔ ' . $this->application_name . ' has been stopped.';
|
||||
$message = 'Coolify: ' . $this->resource_name . ' has been stopped.';
|
||||
return [
|
||||
"message" => $message,
|
||||
"buttons" => [
|
||||
[
|
||||
"text" => "Open Application in Coolify",
|
||||
"url" => $this->application_url
|
||||
"url" => $this->resource_url
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -27,7 +27,7 @@ class ContainerRestarted extends Notification implements ShouldQueue
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}");
|
||||
$mail->subject("Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}");
|
||||
$mail->view('emails.container-restarted', [
|
||||
'containerName' => $this->name,
|
||||
'serverName' => $this->server->name,
|
||||
@@ -38,12 +38,12 @@ class ContainerRestarted extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}";
|
||||
$message = "Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$message = "✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}";
|
||||
$message = "Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}";
|
||||
$payload = [
|
||||
"message" => $message,
|
||||
];
|
||||
|
||||
@@ -26,7 +26,7 @@ class ContainerStopped extends Notification implements ShouldQueue
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("⛔ Container {$this->name} has been stopped on {$this->server->name}");
|
||||
$mail->subject("Coolify: Container ({$this->name}) has been stopped on {$this->server->name}");
|
||||
$mail->view('emails.container-stopped', [
|
||||
'containerName' => $this->name,
|
||||
'serverName' => $this->server->name,
|
||||
@@ -37,12 +37,12 @@ class ContainerStopped extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "⛔ Container {$this->name} has been stopped on {$this->server->name}";
|
||||
$message = "Coolify: Container ({$this->name}) has been stopped on {$this->server->name}";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$message = "⛔ Container ($this->name} has been stopped on {$this->server->name}";
|
||||
$message = "Coolify: Container ($this->name} has been stopped on {$this->server->name}";
|
||||
$payload = [
|
||||
"message" => $message,
|
||||
];
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
namespace App\Notifications\Database;
|
||||
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Channels\MailChannel;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
@@ -24,13 +27,13 @@ class BackupFailed extends Notification implements ShouldQueue
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
return setNotificationChannels($notifiable, 'database_backups');
|
||||
return [DiscordChannel::class, TelegramChannel::class, MailChannel::class];
|
||||
}
|
||||
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("❌ [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
|
||||
$mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}");
|
||||
$mail->view('emails.backup-failed', [
|
||||
'name' => $this->name,
|
||||
'frequency' => $this->frequency,
|
||||
@@ -41,11 +44,11 @@ class BackupFailed extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
return "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
|
||||
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$message = "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
|
||||
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}";
|
||||
return [
|
||||
"message" => $message,
|
||||
];
|
||||
|
||||
@@ -30,7 +30,7 @@ class BackupSuccess extends Notification implements ShouldQueue
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("✅ Backup successfully done for {$this->database->name}");
|
||||
$mail->subject("Coolify: Backup successfully done for {$this->database->name}");
|
||||
$mail->view('emails.backup-success', [
|
||||
'name' => $this->name,
|
||||
'frequency' => $this->frequency,
|
||||
@@ -40,11 +40,11 @@ class BackupSuccess extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
return "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
|
||||
return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
$message = "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
|
||||
$message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful.";
|
||||
return [
|
||||
"message" => $message,
|
||||
];
|
||||
|
||||
@@ -45,7 +45,7 @@ class Revived extends Notification implements ShouldQueue
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("✅ Server ({$this->server->name}) revived.");
|
||||
$mail->subject("Coolify: Server ({$this->server->name}) revived.");
|
||||
$mail->view('emails.server-revived', [
|
||||
'name' => $this->server->name,
|
||||
]);
|
||||
@@ -54,13 +54,13 @@ class Revived extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
|
||||
$message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
"message" => "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!"
|
||||
"message" => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("⛔ Server ({$this->server->name}) is unreachable after trying to connect to it 5 times");
|
||||
$mail->subject("Coolify: Server ({$this->server->name}) is unreachable after trying to connect to it 5 times");
|
||||
$mail->view('emails.server-lost-connection', [
|
||||
'name' => $this->server->name,
|
||||
]);
|
||||
@@ -52,13 +52,13 @@ class Unreachable extends Notification implements ShouldQueue
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.";
|
||||
$message = "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations.";
|
||||
return $message;
|
||||
}
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
"message" => "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."
|
||||
"message" => "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ class Test extends Notification implements ShouldQueue
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject("Test Email");
|
||||
$mail->subject("Coolify: Test Email");
|
||||
$mail->view('emails.test');
|
||||
return $mail;
|
||||
}
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = 'This is a test Discord notification from Coolify.';
|
||||
$message = 'Coolify: This is a test Discord notification from Coolify.';
|
||||
$message .= "\n\n";
|
||||
$message .= '[Go to your dashboard](' . base_url() . ')';
|
||||
return $message;
|
||||
@@ -39,7 +39,7 @@ class Test extends Notification implements ShouldQueue
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
"message" => 'This is a test Telegram notification from Coolify.',
|
||||
"message" => 'Coolify: This is a test Telegram notification from Coolify.',
|
||||
"buttons" => [
|
||||
[
|
||||
"text" => "Go to your dashboard",
|
||||
|
||||
@@ -30,7 +30,7 @@ class InvitationLink extends Notification implements ShouldQueue
|
||||
$invitation_team = Team::find($invitation->team->id);
|
||||
|
||||
$mail = new MailMessage();
|
||||
$mail->subject('Invitation for ' . $invitation_team->name);
|
||||
$mail->subject('Coolify: Invitation for ' . $invitation_team->name);
|
||||
$mail->view('emails.invitation-link', [
|
||||
'team' => $invitation_team->name,
|
||||
'email' => $this->user->email,
|
||||
|
||||
@@ -50,7 +50,7 @@ class ResetPassword extends Notification
|
||||
protected function buildMailMessage($url)
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject('Reset Password');
|
||||
$mail->subject('Coolify: Reset Password');
|
||||
$mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]);
|
||||
return $mail;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class Test extends Notification implements ShouldQueue
|
||||
public function toMail(): MailMessage
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$mail->subject('Test Email');
|
||||
$mail->subject('Coolify: Test Email');
|
||||
$mail->view('emails.test');
|
||||
return $mail;
|
||||
}
|
||||
|
||||
27
app/View/Components/Server/Sidebar.php
Normal file
27
app/View/Components/Server/Sidebar.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Sidebar extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(public Server $server, public $parameters)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.server.sidebar');
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
const DATABASE_TYPES = ['postgresql'];
|
||||
const DATABASE_TYPES = ['postgresql','redis'];
|
||||
const VALID_CRON_STRINGS = [
|
||||
'every_minute' => '* * * * *',
|
||||
'hourly' => '0 * * * *',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
function generate_database_name(string $type): string
|
||||
@@ -27,6 +28,21 @@ function create_standalone_postgresql($environment_id, $destination_uuid): Stand
|
||||
]);
|
||||
}
|
||||
|
||||
function create_standalone_redis($environment_id, $destination_uuid): StandaloneRedis
|
||||
{
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
if (!$destination) {
|
||||
throw new Exception('Destination not found');
|
||||
}
|
||||
return StandaloneRedis::create([
|
||||
'name' => generate_database_name('redis'),
|
||||
'redis_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||
'environment_id' => $environment_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file locally on the filesystem.
|
||||
* @param string $filename
|
||||
|
||||
@@ -147,18 +147,19 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled)
|
||||
function fqdnLabelsForTraefik($uuid, Collection $domains, bool $is_force_https_enabled, $onlyPort = null)
|
||||
{
|
||||
$labels = collect([]);
|
||||
$labels->push('traefik.enable=true');
|
||||
foreach ($domains as $domain) {
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
$schema = $url->getScheme();
|
||||
$port = $url->getPort();
|
||||
|
||||
if (is_null($port) && !is_null($onlyPort)) {
|
||||
$port = $onlyPort;
|
||||
}
|
||||
$http_label = "{$uuid}-http";
|
||||
$https_label = "{$uuid}-https";
|
||||
|
||||
@@ -205,15 +206,19 @@ function fqdnLabelsForTraefik(Collection $domains, bool $is_force_https_enabled)
|
||||
}
|
||||
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
|
||||
{
|
||||
|
||||
$ports = $application->settings->is_static ? [80] : $application->ports_exposes_array;
|
||||
$onlyPort = null;
|
||||
if (count($ports) === 1) {
|
||||
$onlyPort = $ports[0];
|
||||
}
|
||||
$pull_request_id = data_get($preview, 'pull_request_id', 0);
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
// $container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
$appId = $application->id;
|
||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||
$appId = $appId . '-pr-' . $pull_request_id;
|
||||
}
|
||||
$labels = collect([]);
|
||||
$labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id));
|
||||
$labels = $labels->merge(defaultLabels($appId, $application->uuid, $pull_request_id));
|
||||
if ($application->fqdn) {
|
||||
if ($pull_request_id !== 0) {
|
||||
$domains = Str::of(data_get($preview, 'fqdn'))->explode(',');
|
||||
@@ -221,7 +226,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||
}
|
||||
// Add Traefik labels no matter which proxy is selected
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $application->settings->is_force_https_enabled));
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik($application->uuid, $domains, $application->settings->is_force_https_enabled, $onlyPort));
|
||||
}
|
||||
return $labels->all();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
use App\Actions\Proxy\SaveConfiguration;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
function get_proxy_path()
|
||||
@@ -21,7 +20,7 @@ function connectProxyToNetworks(Server $server)
|
||||
}
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo '####### Connecting coolify-proxy to $network network...'",
|
||||
"echo 'Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
@@ -102,6 +101,8 @@ function generate_default_proxy_configuration(Server $server)
|
||||
];
|
||||
if (isDev()) {
|
||||
$config['services']['traefik']['command'][] = "--log.level=debug";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.filepath=/traefik/access.log";
|
||||
$config['services']['traefik']['command'][] = "--accesslog.bufferingsize=100";
|
||||
}
|
||||
$config = Yaml::dump($config, 4, 2);
|
||||
SaveConfiguration::run($server, $config);
|
||||
@@ -184,75 +185,3 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startPostgresProxy(StandalonePostgresql $database)
|
||||
{
|
||||
$containerName = "{$database->uuid}-proxy";
|
||||
$configuration_dir = database_proxy_dir($database->uuid);
|
||||
$nginxconf = <<<EOF
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
stream {
|
||||
server {
|
||||
listen $database->public_port;
|
||||
proxy_pass $database->uuid:5432;
|
||||
}
|
||||
}
|
||||
EOF;
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$containerName => [
|
||||
'image' => "nginx:stable-alpine",
|
||||
'container_name' => $containerName,
|
||||
'restart' => RESTART_MODE,
|
||||
'volumes' => [
|
||||
"$configuration_dir/nginx.conf:/etc/nginx/nginx.conf:ro",
|
||||
],
|
||||
'ports' => [
|
||||
"$database->public_port:$database->public_port",
|
||||
],
|
||||
'networks' => [
|
||||
$database->destination->network,
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
'stat /etc/nginx/nginx.conf || exit 1',
|
||||
],
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 3,
|
||||
'start_period' => '1s'
|
||||
],
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$database->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $database->destination->network,
|
||||
'attachable' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
|
||||
$nginxconf_base64 = base64_encode($nginxconf);
|
||||
instant_remote_process([
|
||||
"mkdir -p $configuration_dir",
|
||||
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||
"docker compose --project-directory {$configuration_dir} up -d >/dev/null",
|
||||
|
||||
|
||||
], $database->destination->server);
|
||||
}
|
||||
function stopPostgresProxy(StandalonePostgresql $database)
|
||||
{
|
||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
||||
}
|
||||
|
||||
@@ -108,12 +108,13 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
|
||||
}
|
||||
function instant_remote_process(Collection|array $command, Server $server, $throwError = true)
|
||||
{
|
||||
$timeout = config('constants.ssh.command_timeout');
|
||||
if ($command instanceof Collection) {
|
||||
$command = $command->toArray();
|
||||
}
|
||||
$command_string = implode("\n", $command);
|
||||
$ssh_command = generateSshCommand($server, $command_string);
|
||||
$process = Process::run($ssh_command);
|
||||
$process = Process::timeout($timeout)->run($ssh_command);
|
||||
$output = trim($process->output());
|
||||
$exitCode = $process->exitCode();
|
||||
if ($exitCode !== 0) {
|
||||
@@ -180,55 +181,55 @@ function refresh_server_connection(PrivateKey $private_key)
|
||||
}
|
||||
}
|
||||
|
||||
function validateServer(Server $server, bool $throwError = false)
|
||||
{
|
||||
try {
|
||||
$uptime = instant_remote_process(['uptime'], $server, $throwError);
|
||||
if (!$uptime) {
|
||||
$server->settings->is_reachable = false;
|
||||
$server->team->notify(new Unreachable($server));
|
||||
$server->unreachable_email_sent = true;
|
||||
$server->save();
|
||||
return [
|
||||
"uptime" => null,
|
||||
"dockerVersion" => null,
|
||||
];
|
||||
}
|
||||
$server->settings->is_reachable = true;
|
||||
instant_remote_process(["docker ps"], $server, $throwError);
|
||||
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
|
||||
if (!$dockerVersion) {
|
||||
$dockerVersion = null;
|
||||
return [
|
||||
"uptime" => $uptime,
|
||||
"dockerVersion" => null,
|
||||
];
|
||||
}
|
||||
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||
if (is_null($dockerVersion)) {
|
||||
$server->settings->is_usable = false;
|
||||
} else {
|
||||
$server->settings->is_usable = true;
|
||||
if (data_get($server, 'unreachable_email_sent') === true) {
|
||||
$server->team->notify(new Revived($server));
|
||||
$server->unreachable_email_sent = false;
|
||||
$server->save();
|
||||
}
|
||||
}
|
||||
return [
|
||||
"uptime" => $uptime,
|
||||
"dockerVersion" => $dockerVersion,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
$server->settings->is_reachable = false;
|
||||
$server->settings->is_usable = false;
|
||||
throw $e;
|
||||
} finally {
|
||||
if (data_get($server, 'settings')) {
|
||||
$server->settings->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
// function validateServer(Server $server, bool $throwError = false)
|
||||
// {
|
||||
// try {
|
||||
// $uptime = instant_remote_process(['uptime'], $server, $throwError);
|
||||
// if (!$uptime) {
|
||||
// $server->settings->is_reachable = false;
|
||||
// $server->team->notify(new Unreachable($server));
|
||||
// $server->unreachable_email_sent = true;
|
||||
// $server->save();
|
||||
// return [
|
||||
// "uptime" => null,
|
||||
// "dockerVersion" => null,
|
||||
// ];
|
||||
// }
|
||||
// $server->settings->is_reachable = true;
|
||||
// instant_remote_process(["docker ps"], $server, $throwError);
|
||||
// $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
|
||||
// if (!$dockerVersion) {
|
||||
// $dockerVersion = null;
|
||||
// return [
|
||||
// "uptime" => $uptime,
|
||||
// "dockerVersion" => null,
|
||||
// ];
|
||||
// }
|
||||
// $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||
// if (is_null($dockerVersion)) {
|
||||
// $server->settings->is_usable = false;
|
||||
// } else {
|
||||
// $server->settings->is_usable = true;
|
||||
// if (data_get($server, 'unreachable_email_sent') === true) {
|
||||
// $server->team->notify(new Revived($server));
|
||||
// $server->unreachable_email_sent = false;
|
||||
// $server->save();
|
||||
// }
|
||||
// }
|
||||
// return [
|
||||
// "uptime" => $uptime,
|
||||
// "dockerVersion" => $dockerVersion,
|
||||
// ];
|
||||
// } catch (\Throwable $e) {
|
||||
// $server->settings->is_reachable = false;
|
||||
// $server->settings->is_usable = false;
|
||||
// throw $e;
|
||||
// } finally {
|
||||
// if (data_get($server, 'settings')) {
|
||||
// $server->settings->save();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function checkRequiredCommands(Server $server)
|
||||
{
|
||||
|
||||
@@ -110,7 +110,10 @@ function getStripeCustomerPortalSession(Team $team)
|
||||
{
|
||||
Stripe::setApiKey(config('subscription.stripe_api_key'));
|
||||
$return_url = route('team.index');
|
||||
$stripe_customer_id = $team->subscription->stripe_customer_id;
|
||||
$stripe_customer_id = data_get($team,'subscription.stripe_customer_id');
|
||||
if (!$stripe_customer_id) {
|
||||
return null;
|
||||
}
|
||||
$session = \Stripe\BillingPortal\Session::create([
|
||||
'customer' => $stripe_customer_id,
|
||||
'return_url' => $return_url,
|
||||
@@ -122,14 +125,14 @@ function allowedPathsForUnsubscribedAccounts()
|
||||
return [
|
||||
'subscription',
|
||||
'login',
|
||||
'register',
|
||||
'logout',
|
||||
'waitlist',
|
||||
'force-password-reset',
|
||||
'logout',
|
||||
'livewire/message/force-password-reset',
|
||||
'livewire/message/check-license',
|
||||
'livewire/message/switch-team',
|
||||
'livewire/message/subscription.pricing-plans'
|
||||
'livewire/message/subscription.pricing-plans',
|
||||
'livewire/message/help'
|
||||
];
|
||||
}
|
||||
function allowedPathsForBoardingAccounts()
|
||||
@@ -141,3 +144,11 @@ function allowedPathsForBoardingAccounts()
|
||||
'livewire/message/activity-monitor'
|
||||
];
|
||||
}
|
||||
function allowedPathsForInvalidAccounts() {
|
||||
return [
|
||||
'logout',
|
||||
'verify',
|
||||
'livewire/message/verify-email',
|
||||
'livewire/message/help'
|
||||
];
|
||||
}
|
||||
|
||||
136
composer.lock
generated
136
composer.lock
generated
@@ -62,16 +62,16 @@
|
||||
},
|
||||
{
|
||||
"name": "aws/aws-sdk-php",
|
||||
"version": "3.283.0",
|
||||
"version": "3.283.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||
"reference": "5084c03431ecda0003e35d7fc7a12eeca4242685"
|
||||
"reference": "6616677d76e39af28138512740199d38a461859f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/5084c03431ecda0003e35d7fc7a12eeca4242685",
|
||||
"reference": "5084c03431ecda0003e35d7fc7a12eeca4242685",
|
||||
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6616677d76e39af28138512740199d38a461859f",
|
||||
"reference": "6616677d76e39af28138512740199d38a461859f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -151,9 +151,9 @@
|
||||
"support": {
|
||||
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.283.0"
|
||||
"source": "https://github.com/aws/aws-sdk-php/tree/3.283.2"
|
||||
},
|
||||
"time": "2023-10-04T18:08:32+00:00"
|
||||
"time": "2023-10-06T18:09:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "bacon/bacon-qr-code",
|
||||
@@ -1083,16 +1083,16 @@
|
||||
},
|
||||
{
|
||||
"name": "egulias/email-validator",
|
||||
"version": "4.0.1",
|
||||
"version": "4.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/egulias/EmailValidator.git",
|
||||
"reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff"
|
||||
"reference": "ebaaf5be6c0286928352e054f2d5125608e5405e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff",
|
||||
"reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff",
|
||||
"url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e",
|
||||
"reference": "ebaaf5be6c0286928352e054f2d5125608e5405e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1101,8 +1101,8 @@
|
||||
"symfony/polyfill-intl-idn": "^1.26"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.5.27",
|
||||
"vimeo/psalm": "^4.30"
|
||||
"phpunit/phpunit": "^10.2",
|
||||
"vimeo/psalm": "^5.12"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
|
||||
@@ -1138,7 +1138,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/egulias/EmailValidator/issues",
|
||||
"source": "https://github.com/egulias/EmailValidator/tree/4.0.1"
|
||||
"source": "https://github.com/egulias/EmailValidator/tree/4.0.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1146,7 +1146,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-01-14T14:17:03+00:00"
|
||||
"time": "2023-10-06T06:47:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fruitcake/php-cors",
|
||||
@@ -2724,16 +2724,16 @@
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729"
|
||||
"reference": "bd4c9b26849d82364119c68429541f1631fba94b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fdf372ca6b63c6e281b1c01a624349ccb757729",
|
||||
"reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/bd4c9b26849d82364119c68429541f1631fba94b",
|
||||
"reference": "bd4c9b26849d82364119c68429541f1631fba94b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2751,8 +2751,8 @@
|
||||
"symfony/http-client": "<5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"async-aws/s3": "^1.5",
|
||||
"async-aws/simple-s3": "^1.1",
|
||||
"async-aws/s3": "^1.5 || ^2.0",
|
||||
"async-aws/simple-s3": "^1.1 || ^2.0",
|
||||
"aws/aws-sdk-php": "^3.220.0",
|
||||
"composer/semver": "^3.0",
|
||||
"ext-fileinfo": "*",
|
||||
@@ -2798,7 +2798,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/thephpleague/flysystem/issues",
|
||||
"source": "https://github.com/thephpleague/flysystem/tree/3.16.0"
|
||||
"source": "https://github.com/thephpleague/flysystem/tree/3.17.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2810,7 +2810,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-09-07T19:22:17+00:00"
|
||||
"time": "2023-10-05T20:15:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/flysystem-aws-s3-v3",
|
||||
@@ -3557,16 +3557,16 @@
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
"version": "v1.2.4",
|
||||
"version": "v1.2.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/schema.git",
|
||||
"reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab"
|
||||
"reference": "0462f0166e823aad657c9224d0f849ecac1ba10a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab",
|
||||
"reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab",
|
||||
"url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a",
|
||||
"reference": "0462f0166e823aad657c9224d0f849ecac1ba10a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3613,9 +3613,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/schema/issues",
|
||||
"source": "https://github.com/nette/schema/tree/v1.2.4"
|
||||
"source": "https://github.com/nette/schema/tree/v1.2.5"
|
||||
},
|
||||
"time": "2023-08-05T18:56:25+00:00"
|
||||
"time": "2023-10-05T20:37:59+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
@@ -6757,16 +6757,16 @@
|
||||
},
|
||||
{
|
||||
"name": "stripe/stripe-php",
|
||||
"version": "v12.5.0",
|
||||
"version": "v12.6.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stripe/stripe-php.git",
|
||||
"reference": "a4249b4a90437844f6c35e8701f8c68acd206f56"
|
||||
"reference": "e0c15e4cbf252e708b937482bb1a50fa7e01bc9d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/a4249b4a90437844f6c35e8701f8c68acd206f56",
|
||||
"reference": "a4249b4a90437844f6c35e8701f8c68acd206f56",
|
||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/e0c15e4cbf252e708b937482bb1a50fa7e01bc9d",
|
||||
"reference": "e0c15e4cbf252e708b937482bb1a50fa7e01bc9d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6811,9 +6811,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||
"source": "https://github.com/stripe/stripe-php/tree/v12.5.0"
|
||||
"source": "https://github.com/stripe/stripe-php/tree/v12.6.0"
|
||||
},
|
||||
"time": "2023-09-28T23:06:27+00:00"
|
||||
"time": "2023-10-05T18:01:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
@@ -10258,16 +10258,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "brianium/paratest",
|
||||
"version": "v7.2.8",
|
||||
"version": "v7.2.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paratestphp/paratest.git",
|
||||
"reference": "882b02d197328138686bb06ce7d8cbb98fc0a16c"
|
||||
"reference": "1f9e41c0779be4540654d92a9314016713f5e62c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/882b02d197328138686bb06ce7d8cbb98fc0a16c",
|
||||
"reference": "882b02d197328138686bb06ce7d8cbb98fc0a16c",
|
||||
"url": "https://api.github.com/repos/paratestphp/paratest/zipball/1f9e41c0779be4540654d92a9314016713f5e62c",
|
||||
"reference": "1f9e41c0779be4540654d92a9314016713f5e62c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -10275,13 +10275,13 @@
|
||||
"ext-pcre": "*",
|
||||
"ext-reflection": "*",
|
||||
"ext-simplexml": "*",
|
||||
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1",
|
||||
"fidry/cpu-core-counter": "^0.5.1",
|
||||
"jean85/pretty-package-versions": "^2.0.5",
|
||||
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
|
||||
"phpunit/php-code-coverage": "^10.1.3",
|
||||
"phpunit/php-file-iterator": "^4.0.2",
|
||||
"phpunit/php-code-coverage": "^10.1.7",
|
||||
"phpunit/php-file-iterator": "^4.1.0",
|
||||
"phpunit/php-timer": "^6.0",
|
||||
"phpunit/phpunit": "^10.3.2",
|
||||
"phpunit/phpunit": "^10.4.0",
|
||||
"sebastian/environment": "^6.0.1",
|
||||
"symfony/console": "^6.3.4",
|
||||
"symfony/process": "^6.3.4"
|
||||
@@ -10290,8 +10290,8 @@
|
||||
"doctrine/coding-standard": "^12.0.0",
|
||||
"ext-pcov": "*",
|
||||
"ext-posix": "*",
|
||||
"infection/infection": "^0.27.0",
|
||||
"phpstan/phpstan": "^1.10.32",
|
||||
"infection/infection": "^0.27.3",
|
||||
"phpstan/phpstan": "^1.10.37",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1.4",
|
||||
"phpstan/phpstan-phpunit": "^1.3.14",
|
||||
"phpstan/phpstan-strict-rules": "^1.5.1",
|
||||
@@ -10337,7 +10337,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/paratestphp/paratest/issues",
|
||||
"source": "https://github.com/paratestphp/paratest/tree/v7.2.8"
|
||||
"source": "https://github.com/paratestphp/paratest/tree/v7.2.9"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -10349,7 +10349,7 @@
|
||||
"type": "paypal"
|
||||
}
|
||||
],
|
||||
"time": "2023-10-04T13:38:04+00:00"
|
||||
"time": "2023-10-06T07:53:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fakerphp/faker",
|
||||
@@ -10983,35 +10983,35 @@
|
||||
},
|
||||
{
|
||||
"name": "pestphp/pest",
|
||||
"version": "v2.20.0",
|
||||
"version": "v2.21.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pestphp/pest.git",
|
||||
"reference": "a8b785f69e44ae3f902cbf08fe6b79359ba46945"
|
||||
"reference": "2ffafd445d42c8b7b7e1874bde1c29945767a49d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pestphp/pest/zipball/a8b785f69e44ae3f902cbf08fe6b79359ba46945",
|
||||
"reference": "a8b785f69e44ae3f902cbf08fe6b79359ba46945",
|
||||
"url": "https://api.github.com/repos/pestphp/pest/zipball/2ffafd445d42c8b7b7e1874bde1c29945767a49d",
|
||||
"reference": "2ffafd445d42c8b7b7e1874bde1c29945767a49d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"brianium/paratest": "^7.2.7",
|
||||
"brianium/paratest": "^7.2.9",
|
||||
"nunomaduro/collision": "^7.9.0",
|
||||
"nunomaduro/termwind": "^1.15.1",
|
||||
"pestphp/pest-plugin": "^2.1.1",
|
||||
"pestphp/pest-plugin-arch": "^2.3.3",
|
||||
"php": "^8.1.0",
|
||||
"phpunit/phpunit": "^10.3.5"
|
||||
"phpunit/phpunit": "^10.4.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpunit/phpunit": ">10.3.5",
|
||||
"phpunit/phpunit": ">10.4.0",
|
||||
"sebastian/exporter": "<5.1.0",
|
||||
"webmozart/assert": "<1.11.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"pestphp/pest-dev-tools": "^2.16.0",
|
||||
"pestphp/pest-plugin-type-coverage": "^2.2.0",
|
||||
"pestphp/pest-plugin-type-coverage": "^2.4.0",
|
||||
"symfony/process": "^6.3.4"
|
||||
},
|
||||
"bin": [
|
||||
@@ -11070,7 +11070,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/pestphp/pest/issues",
|
||||
"source": "https://github.com/pestphp/pest/tree/v2.20.0"
|
||||
"source": "https://github.com/pestphp/pest/tree/v2.21.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11082,7 +11082,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-09-29T18:05:52+00:00"
|
||||
"time": "2023-10-06T12:33:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pestphp/pest-plugin",
|
||||
@@ -11454,16 +11454,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "1.10.37",
|
||||
"version": "1.10.38",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpstan/phpstan.git",
|
||||
"reference": "058ba07e92f744d4dcf6061ae75283d0c6456f2e"
|
||||
"reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/058ba07e92f744d4dcf6061ae75283d0c6456f2e",
|
||||
"reference": "058ba07e92f744d4dcf6061ae75283d0c6456f2e",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/5302bb402c57f00fb3c2c015bac86e0827e4b691",
|
||||
"reference": "5302bb402c57f00fb3c2c015bac86e0827e4b691",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11512,7 +11512,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-10-02T16:18:37+00:00"
|
||||
"time": "2023-10-06T14:19:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
@@ -11837,16 +11837,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "10.3.5",
|
||||
"version": "10.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503"
|
||||
"reference": "9784e877e3700de37475545bdbdce8383ff53d25"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/747c3b2038f1139e3dcd9886a3f5a948648b7503",
|
||||
"reference": "747c3b2038f1139e3dcd9886a3f5a948648b7503",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9784e877e3700de37475545bdbdce8383ff53d25",
|
||||
"reference": "9784e877e3700de37475545bdbdce8383ff53d25",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -11886,7 +11886,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "10.3-dev"
|
||||
"dev-main": "10.4-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -11918,7 +11918,7 @@
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.5"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -11934,7 +11934,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-09-19T05:42:37+00:00"
|
||||
"time": "2023-10-06T03:41:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'docs' => 'https://coolify.io/docs/contact',
|
||||
'self_hosted' => env('SELF_HOSTED', true),
|
||||
'waitlist' => env('WAITLIST', false),
|
||||
'license_url' => 'https://licenses.coollabs.io',
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.70',
|
||||
'release' => '4.0.0-beta.93',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user