mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-02 20:49:29 +00:00
Compare commits
189 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e81ff970f | ||
|
|
00feef40a3 | ||
|
|
dfba593072 | ||
|
|
c770c8d988 | ||
|
|
0f071031a9 | ||
|
|
99efa857f4 | ||
|
|
80035395ff | ||
|
|
d3490e1c95 | ||
|
|
dab13c92eb | ||
|
|
1f18542960 | ||
|
|
8f21ea9367 | ||
|
|
73e64d9052 | ||
|
|
6cdd87da41 | ||
|
|
2a5d49f9b3 | ||
|
|
cc7ba9eb9f | ||
|
|
c4cc42c8d5 | ||
|
|
2d0838b112 | ||
|
|
07b94a8e48 | ||
|
|
4b08abc144 | ||
|
|
93e4fc2f32 | ||
|
|
6dd86eec30 | ||
|
|
a7ab5d55d3 | ||
|
|
689547463c | ||
|
|
8a0046c571 | ||
|
|
fb3991321a | ||
|
|
ca6543a919 | ||
|
|
364a6aa3a2 | ||
|
|
82b0667277 | ||
|
|
74c126c731 | ||
|
|
d87a0fe74f | ||
|
|
9a858f628d | ||
|
|
fed01fa9d2 | ||
|
|
e1468da36a | ||
|
|
ddfc1440cd | ||
|
|
5fc46384e6 | ||
|
|
48d9df1e43 | ||
|
|
6b62d91f82 | ||
|
|
e6ca8cd167 | ||
|
|
059748ad3b | ||
|
|
a334f998a2 | ||
|
|
53a5ccef31 | ||
|
|
9eea73cefb | ||
|
|
b210e1f243 | ||
|
|
ef3202101c | ||
|
|
22d5159d16 | ||
|
|
1cbd30bd9e | ||
|
|
ad54358de7 | ||
|
|
3689b58b92 | ||
|
|
5a4180a750 | ||
|
|
047922b13a | ||
|
|
798d747164 | ||
|
|
29676ffb22 | ||
|
|
8a50b063d4 | ||
|
|
3d2444ab2e | ||
|
|
7c395edab4 | ||
|
|
7e7f322e21 | ||
|
|
9350fb4b97 | ||
|
|
59c3cc6ce1 | ||
|
|
d7001937ac | ||
|
|
3fe58ec66b | ||
|
|
ed12f73483 | ||
|
|
6914280fb1 | ||
|
|
3dd5546369 | ||
|
|
576bff1af9 | ||
|
|
3c4243d854 | ||
|
|
23d121d67a | ||
|
|
548304765c | ||
|
|
037ba3ff79 | ||
|
|
48b4c17391 | ||
|
|
6acc0e6025 | ||
|
|
43d7f746e4 | ||
|
|
146fee14e5 | ||
|
|
bde7fb2acb | ||
|
|
08a729dc7b | ||
|
|
7554de5993 | ||
|
|
3d7295fec3 | ||
|
|
fd814abd8a | ||
|
|
4c38a59995 | ||
|
|
642a6e3203 | ||
|
|
9edbc15828 | ||
|
|
43eb2fb00b | ||
|
|
9a899deeb8 | ||
|
|
9e1a7d5d9a | ||
|
|
5bdbab7276 | ||
|
|
13bceb934f | ||
|
|
78b194cb16 | ||
|
|
3616fc8ca9 | ||
|
|
10e307f92b | ||
|
|
01f027ac1b | ||
|
|
dadc7aaf08 | ||
|
|
b96807d34c | ||
|
|
45b736bb01 | ||
|
|
3d873a79a0 | ||
|
|
6869c582ff | ||
|
|
9b9e5e939c | ||
|
|
f626c15ecc | ||
|
|
8df1fe2e60 | ||
|
|
fd2a533057 | ||
|
|
0a6401f990 | ||
|
|
1326fcb345 | ||
|
|
8b8e534598 | ||
|
|
0b518a3b76 | ||
|
|
f357f40fc7 | ||
|
|
93fb14884e | ||
|
|
26ccc4afb4 | ||
|
|
5fda1bb932 | ||
|
|
409ba8a1bb | ||
|
|
49f5240ff8 | ||
|
|
0c3ed3d393 | ||
|
|
6e3dc474f2 | ||
|
|
d3eb87561e | ||
|
|
8b58c8f856 | ||
|
|
8c60ef5bd6 | ||
|
|
1d59383c78 | ||
|
|
60f590454d | ||
|
|
dcb61a553e | ||
|
|
e06e31642f | ||
|
|
9dfce48380 | ||
|
|
8eed87e2f7 | ||
|
|
d56d4eb8fc | ||
|
|
fd32cd04ab | ||
|
|
1d3b7ffd3b | ||
|
|
0b5baf60a5 | ||
|
|
bc31df6fb2 | ||
|
|
818399bc23 | ||
|
|
e7fdff0f69 | ||
|
|
6312c0ba84 | ||
|
|
44efe0b5e1 | ||
|
|
de7d584648 | ||
|
|
b9f12d2586 | ||
|
|
c76e8bb0de | ||
|
|
3b655f8e3f | ||
|
|
2b9df41444 | ||
|
|
628fec6904 | ||
|
|
f36135cbfc | ||
|
|
75fe005055 | ||
|
|
8ff7aeb78b | ||
|
|
f1a9e28d5a | ||
|
|
843cd90ee5 | ||
|
|
1cbfd03912 | ||
|
|
ce60a39dc5 | ||
|
|
f1e4395a83 | ||
|
|
52fd7ad571 | ||
|
|
5f797ec0ae | ||
|
|
21e77bf0c1 | ||
|
|
0686e48e89 | ||
|
|
1cef233db2 | ||
|
|
907e52572c | ||
|
|
795c8abf64 | ||
|
|
cc641d8cba | ||
|
|
d4668ef44a | ||
|
|
e8b539c3bd | ||
|
|
6555f0b50c | ||
|
|
bb05058dda | ||
|
|
9667cd4a7a | ||
|
|
3ae9501814 | ||
|
|
73d0948734 | ||
|
|
c3e2a741ea | ||
|
|
4792146f1d | ||
|
|
09b9305aa3 | ||
|
|
ff7d0d442d | ||
|
|
9a127bdc80 | ||
|
|
919e88afb4 | ||
|
|
1d1ec20cb7 | ||
|
|
5c29ecdf10 | ||
|
|
9e09c449cf | ||
|
|
09bcd693f5 | ||
|
|
0c15e45419 | ||
|
|
31cbf552a2 | ||
|
|
f7853ee174 | ||
|
|
de3a7b6eca | ||
|
|
b56c7c34cb | ||
|
|
49845f3da7 | ||
|
|
987409bae4 | ||
|
|
07d8461f96 | ||
|
|
f255a71434 | ||
|
|
2a2818ac0d | ||
|
|
fd3cdc2c7d | ||
|
|
84c3f832ae | ||
|
|
70c28fceeb | ||
|
|
f2c4f83f5a | ||
|
|
c8dd6f07ac | ||
|
|
561e424a7d | ||
|
|
c46d38907e | ||
|
|
5c334bbac6 | ||
|
|
9628072b0c | ||
|
|
39ecff9f90 | ||
|
|
053aa25d2c | ||
|
|
54d8cb9027 |
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Notifications\Application\StatusChanged;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopApplication
|
class StopApplication
|
||||||
@@ -10,10 +12,20 @@ class StopApplication
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Application $application)
|
public function handle(Application $application)
|
||||||
{
|
{
|
||||||
$server = $application->destination->server;
|
if ($application->destination->server->isSwarm()) {
|
||||||
if ($server->isSwarm()) {
|
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
return;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
$servers = collect([]);
|
||||||
|
$servers->push($application->destination->server);
|
||||||
|
$application->additional_servers->map(function ($server) use ($servers) {
|
||||||
|
$servers->push($server);
|
||||||
|
});
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
@@ -25,20 +37,7 @@ class StopApplication
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: make notification for application
|
|
||||||
// $application->environment->project->team->notify(new StatusChanged($application));
|
|
||||||
}
|
|
||||||
// Delete Preview Deployments
|
|
||||||
$previewDeployments = $application->previews;
|
|
||||||
foreach ($previewDeployments as $previewDeployment) {
|
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, $previewDeployment->pull_request_id);
|
|
||||||
foreach ($containers as $container) {
|
|
||||||
$name = str_replace('/', '', $container['Names']);
|
|
||||||
instant_remote_process(["docker rm -f $name"], $application->destination->server, throwError: false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
app/Actions/Application/StopApplicationOneServer.php
Normal file
38
app/Actions/Application/StopApplicationOneServer.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class StopApplicationOneServer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Application $application, Server $server)
|
||||||
|
{
|
||||||
|
if ($application->destination->server->isSwarm()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
|
if ($containers->count() > 0) {
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$containerName = data_get($container, 'Names');
|
||||||
|
if ($containerName) {
|
||||||
|
instant_remote_process(
|
||||||
|
["docker rm -f {$containerName}"],
|
||||||
|
$server
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
return $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -106,7 +106,8 @@ class StartMariadb
|
|||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '{$database->name} started.'";
|
$database_name = addslashes($database->name);
|
||||||
|
$this->commands[] = "echo 'Database started.'";
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ class StartMongodb
|
|||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '{$database->name} started.'";
|
$database_name = addslashes($database->name);
|
||||||
|
$this->commands[] = "echo 'Database started.'";
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ class StartMysql
|
|||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '{$database->name} started.'";
|
$database_name = addslashes($database->name);
|
||||||
|
$this->commands[] = "echo 'Database started.'";
|
||||||
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ class StartPostgresql
|
|||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '{$database->name} started.'";
|
$database_name = addslashes($database->name);
|
||||||
|
$this->commands[] = "echo 'Database started.'";
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ class StartRedis
|
|||||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||||
$this->commands[] = "echo '{$database->name} started.'";
|
$database_name = addslashes($database->name);
|
||||||
|
$this->commands[] = "echo 'Database started.'";
|
||||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ class StopDatabase
|
|||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||||
{
|
{
|
||||||
$server = $database->destination->server;
|
$server = $database->destination->server;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
instant_remote_process(
|
instant_remote_process(
|
||||||
["docker rm -f {$database->uuid}"],
|
["docker rm -f {$database->uuid}"],
|
||||||
$server
|
$server
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class StartProxy
|
|||||||
$server->save();
|
$server->save();
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path && cd $proxy_path",
|
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
||||||
@@ -35,7 +35,7 @@ class StartProxy
|
|||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path && cd $proxy_path",
|
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Pulling docker image.'",
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class InstallDocker
|
|||||||
"echo 'Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
"ls -l /tmp"
|
"ls -l /tmp"
|
||||||
]);
|
]);
|
||||||
|
return remote_process($command, $server);
|
||||||
} else {
|
} else {
|
||||||
if ($supported_os_type->contains('debian')) {
|
if ($supported_os_type->contains('debian')) {
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
@@ -89,7 +90,6 @@ class InstallDocker
|
|||||||
"echo 'Done!'",
|
"echo 'Done!'",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return remote_process($command, $server);
|
return remote_process($command, $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ Files:
|
|||||||
}
|
}
|
||||||
$restart_command = [
|
$restart_command = [
|
||||||
"echo 'Stopping old Fluent Bit'",
|
"echo 'Stopping old Fluent Bit'",
|
||||||
"cd $config_path && docker rm -f coolify-log-drain || true",
|
"cd $config_path && docker compose down --remove-orphans || true",
|
||||||
"echo 'Starting Fluent Bit'",
|
"echo 'Starting Fluent Bit'",
|
||||||
"cd $config_path && docker compose up -d --remove-orphans",
|
"cd $config_path && docker compose up -d --remove-orphans",
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class UpdateCoolify
|
|||||||
CleanupDocker::run($this->server, false);
|
CleanupDocker::run($this->server, false);
|
||||||
$this->latestVersion = get_latest_version_of_coolify();
|
$this->latestVersion = get_latest_version_of_coolify();
|
||||||
$this->currentVersion = config('version');
|
$this->currentVersion = config('version');
|
||||||
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
|
||||||
if ($settings->next_channel) {
|
if ($settings->next_channel) {
|
||||||
ray('next channel enabled');
|
ray('next channel enabled');
|
||||||
$this->latestVersion = 'next';
|
$this->latestVersion = 'next';
|
||||||
@@ -44,7 +43,7 @@ class UpdateCoolify
|
|||||||
}
|
}
|
||||||
$this->update();
|
$this->update();
|
||||||
}
|
}
|
||||||
send_internal_notification('InstanceAutoUpdateJob done to version: ' . $this->latestVersion . ' from version: ' . $this->currentVersion);
|
send_internal_notification("Instance updated from {$this->currentVersion} -> {$this->latestVersion}");
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray('InstanceAutoUpdateJob failed');
|
ray('InstanceAutoUpdateJob failed');
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|||||||
@@ -10,35 +10,45 @@ class DeleteService
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
$server = data_get($service, 'server');
|
try {
|
||||||
if ($server->isFunctional()) {
|
$server = data_get($service, 'server');
|
||||||
StopService::run($service);
|
if ($server->isFunctional()) {
|
||||||
}
|
$storagesToDelete = collect([]);
|
||||||
$storagesToDelete = collect([]);
|
|
||||||
|
|
||||||
$service->environment_variables()->delete();
|
$service->environment_variables()->delete();
|
||||||
$commands = [];
|
$commands = [];
|
||||||
foreach ($service->applications()->get() as $application) {
|
foreach ($service->applications()->get() as $application) {
|
||||||
$storages = $application->persistentStorages()->get();
|
$storages = $application->persistentStorages()->get();
|
||||||
foreach ($storages as $storage) {
|
foreach ($storages as $storage) {
|
||||||
$storagesToDelete->push($storage);
|
$storagesToDelete->push($storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($service->databases()->get() as $database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
$storagesToDelete->push($storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($storagesToDelete as $storage) {
|
||||||
|
$commands[] = "docker volume rm -f $storage->name";
|
||||||
|
}
|
||||||
|
$commands[] = "docker rm -f $service->uuid";
|
||||||
|
|
||||||
|
instant_remote_process($commands, $server, false);
|
||||||
}
|
}
|
||||||
$application->forceDelete();
|
} catch (\Exception $e) {
|
||||||
}
|
throw new \Exception($e->getMessage());
|
||||||
foreach ($service->databases()->get() as $database) {
|
} finally {
|
||||||
$storages = $database->persistentStorages()->get();
|
foreach ($service->applications()->get() as $application) {
|
||||||
foreach ($storages as $storage) {
|
$application->forceDelete();
|
||||||
$storagesToDelete->push($storage);
|
|
||||||
}
|
}
|
||||||
$database->forceDelete();
|
foreach ($service->databases()->get() as $database) {
|
||||||
|
$database->forceDelete();
|
||||||
|
}
|
||||||
|
foreach ($service->scheduled_tasks as $task) {
|
||||||
|
$task->delete();
|
||||||
|
}
|
||||||
|
$service->tags()->detach();
|
||||||
}
|
}
|
||||||
foreach ($storagesToDelete as $storage) {
|
|
||||||
$commands[] = "docker volume rm -f $storage->name";
|
|
||||||
}
|
|
||||||
$commands[] = "docker rm -f $service->uuid";
|
|
||||||
|
|
||||||
instant_remote_process($commands, $server, false);
|
|
||||||
|
|
||||||
$service->forceDelete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,15 @@ class StartService
|
|||||||
{
|
{
|
||||||
ray('Starting service: ' . $service->name);
|
ray('Starting service: ' . $service->name);
|
||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
|
|
||||||
|
$service_name = addslashes($service->name);
|
||||||
|
$server_name = addslashes($service->server->name);
|
||||||
|
|
||||||
$commands[] = "cd " . $service->workdir();
|
$commands[] = "cd " . $service->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
$commands[] = "echo 'Creating Docker network.'";
|
$commands[] = "echo 'Creating Docker network.'";
|
||||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
|
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid >/dev/null 2>&1 || true";
|
||||||
$commands[] = "echo 'Starting service $service->name on {$service->server->name}.'";
|
$commands[] = "echo Starting service.";
|
||||||
$commands[] = "echo 'Pulling images.'";
|
$commands[] = "echo 'Pulling images.'";
|
||||||
$commands[] = "docker compose pull";
|
$commands[] = "docker compose pull";
|
||||||
$commands[] = "echo 'Starting containers.'";
|
$commands[] = "echo 'Starting containers.'";
|
||||||
|
|||||||
@@ -10,20 +10,31 @@ class StopService
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
ray('Stopping service: ' . $service->name);
|
try {
|
||||||
$applications = $service->applications()->get();
|
$server = $service->destination->server;
|
||||||
foreach ($applications as $application) {
|
if (!$server->isFunctional()) {
|
||||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
return 'Server is not functional';
|
||||||
$application->update(['status' => 'exited']);
|
}
|
||||||
|
ray('Stopping service: ' . $service->name);
|
||||||
|
$applications = $service->applications()->get();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
||||||
|
$db->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo $e->getMessage();
|
||||||
|
ray($e->getMessage());
|
||||||
|
return $e->getMessage();
|
||||||
}
|
}
|
||||||
$dbs = $service->databases()->get();
|
|
||||||
foreach ($dbs as $db) {
|
|
||||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
|
||||||
$db->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
app/Actions/Shared/ComplexStatusCheck.php
Normal file
55
app/Actions/Shared/ComplexStatusCheck.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Shared;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ComplexStatusCheck
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Application $application)
|
||||||
|
{
|
||||||
|
$servers = $application->additional_servers;
|
||||||
|
$servers->push($application->destination->server);
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
$is_main_server = $application->destination->server->id === $server->id;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
if ($is_main_server) {
|
||||||
|
$application->update(['status' => 'exited:unhealthy']);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$container = instant_remote_process(["docker container inspect $(docker container ls -q --filter 'label=coolify.applicationId={$application->id}' --filter 'label=coolify.pullRequestId=0') --format '{{json .}}'"], $server, false);
|
||||||
|
$container = format_docker_command_output_to_json($container);
|
||||||
|
if ($container->count() === 1) {
|
||||||
|
$container = $container->first();
|
||||||
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
if ($is_main_server) {
|
||||||
|
$statusFromDb = $application->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$application->update(['status' => "$containerStatus:$containerHealth"]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$additional_server = $application->additional_servers()->wherePivot('server_id', $server->id);
|
||||||
|
$statusFromDb = $additional_server->first()->pivot->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$additional_server->updateExistingPivot($server->id, ['status' => "$containerStatus:$containerHealth"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($is_main_server) {
|
||||||
|
$application->update(['status' => 'exited:unhealthy']);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$application->additional_servers()->updateExistingPivot($server->id, ['status' => 'exited:unhealthy']);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
308
app/Console/Commands/CleanupStuckedResources.php
Normal file
308
app/Console/Commands/CleanupStuckedResources.php
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ScheduledTask;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanupStuckedResources extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:stucked-resources';
|
||||||
|
protected $description = 'Cleanup Stucked Resources';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
ray('Running cleanup stucked resources.');
|
||||||
|
echo "Running cleanup stucked resources.\n";
|
||||||
|
$this->cleanup_stucked_resources();
|
||||||
|
}
|
||||||
|
private function cleanup_stucked_resources()
|
||||||
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
echo "Deleting stuck application: {$application->name}\n";
|
||||||
|
$application->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
||||||
|
$postgresql->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($redis as $redis) {
|
||||||
|
echo "Deleting stuck redis: {$redis->name}\n";
|
||||||
|
$redis->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mongodbs as $mongodb) {
|
||||||
|
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
||||||
|
$mongodb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mysqls as $mysql) {
|
||||||
|
echo "Deleting stuck mysql: {$mysql->name}\n";
|
||||||
|
$mysql->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mariadbs as $mariadb) {
|
||||||
|
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
||||||
|
$mariadb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($services as $service) {
|
||||||
|
echo "Deleting stuck service: {$service->name}\n";
|
||||||
|
$service->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($serviceApps as $serviceApp) {
|
||||||
|
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
||||||
|
$serviceApp->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($serviceDbs as $serviceDb) {
|
||||||
|
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
||||||
|
$serviceDb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$scheduled_tasks = ScheduledTask::all();
|
||||||
|
foreach ($scheduled_tasks as $scheduled_task) {
|
||||||
|
if (!$scheduled_task->service && !$scheduled_task->application) {
|
||||||
|
echo "Deleting stuck scheduledtask: {$scheduled_task->name}\n";
|
||||||
|
$scheduled_task->delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck scheduledtasks: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup any resources that are not attached to any environment or destination or server
|
||||||
|
try {
|
||||||
|
$applications = Application::all();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
if (!data_get($application, 'environment')) {
|
||||||
|
echo 'Application without environment: ' . $application->name . '\n';
|
||||||
|
$application->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$application->destination()) {
|
||||||
|
echo 'Application without destination: ' . $application->name . '\n';
|
||||||
|
$application->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($application, 'destination.server')) {
|
||||||
|
echo 'Application without server: ' . $application->name . '\n';
|
||||||
|
$application->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
if (!data_get($postgresql, 'environment')) {
|
||||||
|
echo 'Postgresql without environment: ' . $postgresql->name . '\n';
|
||||||
|
$postgresql->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$postgresql->destination()) {
|
||||||
|
echo 'Postgresql without destination: ' . $postgresql->name . '\n';
|
||||||
|
$postgresql->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($postgresql, 'destination.server')) {
|
||||||
|
echo 'Postgresql without server: ' . $postgresql->name . '\n';
|
||||||
|
$postgresql->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in postgresql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$redis = StandaloneRedis::all();
|
||||||
|
foreach ($redis as $redis) {
|
||||||
|
if (!data_get($redis, 'environment')) {
|
||||||
|
echo 'Redis without environment: ' . $redis->name . '\n';
|
||||||
|
$redis->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$redis->destination()) {
|
||||||
|
echo 'Redis without destination: ' . $redis->name . '\n';
|
||||||
|
$redis->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($redis, 'destination.server')) {
|
||||||
|
echo 'Redis without server: ' . $redis->name . '\n';
|
||||||
|
$redis->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in redis: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mongodbs = StandaloneMongodb::all();
|
||||||
|
foreach ($mongodbs as $mongodb) {
|
||||||
|
if (!data_get($mongodb, 'environment')) {
|
||||||
|
echo 'Mongodb without environment: ' . $mongodb->name . '\n';
|
||||||
|
$mongodb->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mongodb->destination()) {
|
||||||
|
echo 'Mongodb without destination: ' . $mongodb->name . '\n';
|
||||||
|
$mongodb->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mongodb, 'destination.server')) {
|
||||||
|
echo 'Mongodb without server: ' . $mongodb->name . '\n';
|
||||||
|
$mongodb->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mongodb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mysqls = StandaloneMysql::all();
|
||||||
|
foreach ($mysqls as $mysql) {
|
||||||
|
if (!data_get($mysql, 'environment')) {
|
||||||
|
echo 'Mysql without environment: ' . $mysql->name . '\n';
|
||||||
|
$mysql->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mysql->destination()) {
|
||||||
|
echo 'Mysql without destination: ' . $mysql->name . '\n';
|
||||||
|
$mysql->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mysql, 'destination.server')) {
|
||||||
|
echo 'Mysql without server: ' . $mysql->name . '\n';
|
||||||
|
$mysql->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mysql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mariadbs = StandaloneMariadb::all();
|
||||||
|
foreach ($mariadbs as $mariadb) {
|
||||||
|
if (!data_get($mariadb, 'environment')) {
|
||||||
|
echo 'Mariadb without environment: ' . $mariadb->name . '\n';
|
||||||
|
$mariadb->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mariadb->destination()) {
|
||||||
|
echo 'Mariadb without destination: ' . $mariadb->name . '\n';
|
||||||
|
$mariadb->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mariadb, 'destination.server')) {
|
||||||
|
echo 'Mariadb without server: ' . $mariadb->name . '\n';
|
||||||
|
$mariadb->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mariadb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$services = Service::all();
|
||||||
|
foreach ($services as $service) {
|
||||||
|
if (!data_get($service, 'environment')) {
|
||||||
|
echo 'Service without environment: ' . $service->name . '\n';
|
||||||
|
$service->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$service->destination()) {
|
||||||
|
echo 'Service without destination: ' . $service->name . '\n';
|
||||||
|
$service->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($service, 'server')) {
|
||||||
|
echo 'Service without server: ' . $service->name . '\n';
|
||||||
|
$service->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in service: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceApplications = ServiceApplication::all();
|
||||||
|
foreach ($serviceApplications as $service) {
|
||||||
|
if (!data_get($service, 'service')) {
|
||||||
|
echo 'ServiceApplication without service: ' . $service->name . '\n';
|
||||||
|
$service->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceDatabases = ServiceDatabase::all();
|
||||||
|
foreach ($serviceDatabases as $service) {
|
||||||
|
if (!data_get($service, 'service')) {
|
||||||
|
echo 'ServiceDatabase without service: ' . $service->name . '\n';
|
||||||
|
$service->forceDelete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanupUnreachableServers extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:unreachable-servers';
|
||||||
|
protected $description = 'Cleanup Unreachable Servers (3 days)';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Running unreachable server cleanup...\n";
|
||||||
|
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get();
|
||||||
|
if ($servers->count() > 0) {
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
|
$server->update([
|
||||||
|
'ip' => '1.2.3.4'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,57 +4,54 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
|
||||||
use App\Models\ServiceApplication;
|
|
||||||
use App\Models\ServiceDatabase;
|
|
||||||
use App\Models\StandaloneMariadb;
|
|
||||||
use App\Models\StandaloneMongodb;
|
|
||||||
use App\Models\StandaloneMysql;
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class Init extends Command
|
class Init extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'app:init {--cleanup}';
|
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
||||||
protected $description = 'Cleanup instance related stuffs';
|
protected $description = 'Cleanup instance related stuffs';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$this->alive();
|
$this->alive();
|
||||||
$cleanup = $this->option('cleanup');
|
$full_cleanup = $this->option('full-cleanup');
|
||||||
if ($cleanup) {
|
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||||
echo "Running cleanups...\n";
|
if ($cleanup_deployments) {
|
||||||
$this->cleanup_stucked_resources();
|
echo "Running cleanup deployments.\n";
|
||||||
|
$this->cleanup_in_progress_application_deployments();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($full_cleanup) {
|
||||||
// Required for falsely deleted coolify db
|
// Required for falsely deleted coolify db
|
||||||
$this->restore_coolify_db_backup();
|
$this->restore_coolify_db_backup();
|
||||||
|
$this->cleanup_in_progress_application_deployments();
|
||||||
// $this->cleanup_ssh();
|
$this->cleanup_stucked_helper_containers();
|
||||||
}
|
$this->call('cleanup:queue');
|
||||||
$this->cleanup_in_progress_application_deployments();
|
$this->call('cleanup:stucked-resources');
|
||||||
$this->cleanup_stucked_helper_containers();
|
try {
|
||||||
|
setup_dynamic_configuration();
|
||||||
try {
|
} catch (\Throwable $e) {
|
||||||
setup_dynamic_configuration();
|
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Could not setup dynamic configuration: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if (!is_null(env('AUTOUPDATE', null))) {
|
|
||||||
if (env('AUTOUPDATE') == true) {
|
|
||||||
$settings->update(['is_auto_update_enabled' => true]);
|
|
||||||
} else {
|
|
||||||
$settings->update(['is_auto_update_enabled' => false]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if (!is_null(env('AUTOUPDATE', null))) {
|
||||||
|
if (env('AUTOUPDATE') == true) {
|
||||||
|
$settings->update(['is_auto_update_enabled' => true]);
|
||||||
|
} else {
|
||||||
|
$settings->update(['is_auto_update_enabled' => false]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
$this->call('cleanup:queue');
|
$this->cleanup_stucked_helper_containers();
|
||||||
|
$this->call('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
private function restore_coolify_db_backup()
|
private function restore_coolify_db_backup()
|
||||||
{
|
{
|
||||||
@@ -128,8 +125,13 @@ class Init extends Command
|
|||||||
// Cleanup any failed deployments
|
// Cleanup any failed deployments
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
|
if (isCloud()) {
|
||||||
foreach ($halted_deployments as $deployment) {
|
return;
|
||||||
|
}
|
||||||
|
$queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get();
|
||||||
|
foreach ($queued_inprogress_deployments as $deployment) {
|
||||||
|
ray($deployment->id, $deployment->status);
|
||||||
|
echo "Cleaning up deployment: {$deployment->id}\n";
|
||||||
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
$deployment->status = ApplicationDeploymentStatus::FAILED->value;
|
||||||
$deployment->save();
|
$deployment->save();
|
||||||
}
|
}
|
||||||
@@ -137,273 +139,4 @@ class Init extends Command
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function cleanup_stucked_resources()
|
|
||||||
{
|
|
||||||
|
|
||||||
try {
|
|
||||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
echo "Deleting stuck application: {$application->name}\n";
|
|
||||||
$application->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($postgresqls as $postgresql) {
|
|
||||||
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
|
||||||
$postgresql->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($redis as $redis) {
|
|
||||||
echo "Deleting stuck redis: {$redis->name}\n";
|
|
||||||
$redis->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($mongodbs as $mongodb) {
|
|
||||||
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
|
||||||
$mongodb->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($mysqls as $mysql) {
|
|
||||||
echo "Deleting stuck mysql: {$mysql->name}\n";
|
|
||||||
$mysql->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($mariadbs as $mariadb) {
|
|
||||||
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
|
||||||
$mariadb->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($services as $service) {
|
|
||||||
echo "Deleting stuck service: {$service->name}\n";
|
|
||||||
$service->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($serviceApps as $serviceApp) {
|
|
||||||
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
|
||||||
$serviceApp->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($serviceDbs as $serviceDb) {
|
|
||||||
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
|
||||||
$serviceDb->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup any resources that are not attached to any environment or destination or server
|
|
||||||
try {
|
|
||||||
$applications = Application::all();
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
if (!data_get($application, 'environment')) {
|
|
||||||
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
|
|
||||||
$application->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$application->destination()) {
|
|
||||||
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
|
||||||
$application->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($application, 'destination.server')) {
|
|
||||||
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
|
||||||
$application->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in application: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
|
||||||
foreach ($postgresqls as $postgresql) {
|
|
||||||
if (!data_get($postgresql, 'environment')) {
|
|
||||||
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
|
||||||
$postgresql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$postgresql->destination()) {
|
|
||||||
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
|
||||||
$postgresql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($postgresql, 'destination.server')) {
|
|
||||||
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
|
||||||
$postgresql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in postgresql: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$redis = StandaloneRedis::all();
|
|
||||||
foreach ($redis as $redis) {
|
|
||||||
if (!data_get($redis, 'environment')) {
|
|
||||||
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
|
||||||
$redis->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$redis->destination()) {
|
|
||||||
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
|
||||||
$redis->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($redis, 'destination.server')) {
|
|
||||||
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
|
||||||
$redis->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in redis: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mongodbs = StandaloneMongodb::all();
|
|
||||||
foreach ($mongodbs as $mongodb) {
|
|
||||||
if (!data_get($mongodb, 'environment')) {
|
|
||||||
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
|
||||||
$mongodb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$mongodb->destination()) {
|
|
||||||
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
|
||||||
$mongodb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($mongodb, 'destination.server')) {
|
|
||||||
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
|
||||||
$mongodb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in mongodb: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mysqls = StandaloneMysql::all();
|
|
||||||
foreach ($mysqls as $mysql) {
|
|
||||||
if (!data_get($mysql, 'environment')) {
|
|
||||||
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
|
||||||
$mysql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$mysql->destination()) {
|
|
||||||
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
|
||||||
$mysql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($mysql, 'destination.server')) {
|
|
||||||
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
|
||||||
$mysql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in mysql: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mariadbs = StandaloneMariadb::all();
|
|
||||||
foreach ($mariadbs as $mariadb) {
|
|
||||||
if (!data_get($mariadb, 'environment')) {
|
|
||||||
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
|
||||||
$mariadb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$mariadb->destination()) {
|
|
||||||
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
|
||||||
$mariadb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($mariadb, 'destination.server')) {
|
|
||||||
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
|
||||||
$mariadb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in mariadb: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$services = Service::all();
|
|
||||||
foreach ($services as $service) {
|
|
||||||
if (!data_get($service, 'environment')) {
|
|
||||||
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$service->destination()) {
|
|
||||||
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($service, 'server')) {
|
|
||||||
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in service: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$serviceApplications = ServiceApplication::all();
|
|
||||||
foreach ($serviceApplications as $service) {
|
|
||||||
if (!data_get($service, 'service')) {
|
|
||||||
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$serviceDatabases = ServiceDatabase::all();
|
|
||||||
foreach ($serviceDatabases as $service) {
|
|
||||||
if (!data_get($service, 'service')) {
|
|
||||||
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class SyncBunny extends Command
|
|||||||
|
|
||||||
$versions = "versions.json";
|
$versions = "versions.json";
|
||||||
|
|
||||||
PendingRequest::macro('storage', function ($fileName) use($that) {
|
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
@@ -76,23 +76,26 @@ class SyncBunny extends Command
|
|||||||
}
|
}
|
||||||
if ($only_template) {
|
if ($only_template) {
|
||||||
$this->info('About to sync service-templates.json to BunnyCDN.');
|
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||||
}
|
$confirmed = confirm("Are you sure you want to sync?");
|
||||||
if ($only_version) {
|
if (!$confirmed) {
|
||||||
$this->info('About to sync versions.json to BunnyCDN.');
|
return;
|
||||||
}
|
}
|
||||||
$confirmed = confirm('Are you sure you want to sync?');
|
|
||||||
if (!$confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($only_template) {
|
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||||
]);
|
]);
|
||||||
$this->info('Service template uploaded & purged...');
|
$this->info('Service template uploaded & purged...');
|
||||||
return;
|
return;
|
||||||
}
|
} else if ($only_version) {
|
||||||
if ($only_version) {
|
$this->info('About to sync versions.json to BunnyCDN.');
|
||||||
|
$file = file_get_contents("$parent_dir/$versions");
|
||||||
|
$json = json_decode($file, true);
|
||||||
|
$actual_version = data_get($json, 'coolify.v4.version');
|
||||||
|
|
||||||
|
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
|
||||||
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||||
@@ -101,6 +104,7 @@ class SyncBunny extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Console;
|
|||||||
|
|
||||||
use App\Jobs\CheckLogDrainContainerJob;
|
use App\Jobs\CheckLogDrainContainerJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
|
use App\Jobs\ComplexContainerStatusJob;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\InstanceAutoUpdateJob;
|
use App\Jobs\InstanceAutoUpdateJob;
|
||||||
@@ -36,6 +37,8 @@ class Kernel extends ConsoleKernel
|
|||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
|
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||||
|
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
|
|
||||||
@@ -89,7 +92,6 @@ class Kernel extends ConsoleKernel
|
|||||||
{
|
{
|
||||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||||
if ($scheduled_backups->isEmpty()) {
|
if ($scheduled_backups->isEmpty()) {
|
||||||
ray('no scheduled backups');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($scheduled_backups as $scheduled_backup) {
|
foreach ($scheduled_backups as $scheduled_backup) {
|
||||||
@@ -115,12 +117,11 @@ class Kernel extends ConsoleKernel
|
|||||||
{
|
{
|
||||||
$scheduled_tasks = ScheduledTask::all();
|
$scheduled_tasks = ScheduledTask::all();
|
||||||
if ($scheduled_tasks->isEmpty()) {
|
if ($scheduled_tasks->isEmpty()) {
|
||||||
ray('no scheduled tasks');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
foreach ($scheduled_tasks as $scheduled_task) {
|
foreach ($scheduled_tasks as $scheduled_task) {
|
||||||
$service = $scheduled_task->service()->get();
|
$service = $scheduled_task->service;
|
||||||
$application = $scheduled_task->application()->get();
|
$application = $scheduled_task->application;
|
||||||
|
|
||||||
if (!$application && !$service) {
|
if (!$application && !$service) {
|
||||||
ray('application/service attached to scheduled task does not exist');
|
ray('application/service attached to scheduled task does not exist');
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ enum ProcessStatus: string
|
|||||||
case ERROR = 'error';
|
case ERROR = 'error';
|
||||||
case KILLED = 'killed';
|
case KILLED = 'killed';
|
||||||
case CANCELLED = 'cancelled';
|
case CANCELLED = 'cancelled';
|
||||||
|
case CLOSED = 'closed';
|
||||||
}
|
}
|
||||||
|
|||||||
162
app/Http/Controllers/Api/Deploy.php
Normal file
162
app/Http/Controllers/Api/Deploy.php
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartMariadb;
|
||||||
|
use App\Actions\Database\StartMongodb;
|
||||||
|
use App\Actions\Database\StartMysql;
|
||||||
|
use App\Actions\Database\StartPostgresql;
|
||||||
|
use App\Actions\Database\StartRedis;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class Deploy extends Controller
|
||||||
|
{
|
||||||
|
public function deploy(Request $request)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$teamId = data_get($token, 'team_id');
|
||||||
|
$uuids = $request->query->get('uuid');
|
||||||
|
$tags = $request->query->get('tag');
|
||||||
|
$force = $request->query->get('force') ?? false;
|
||||||
|
|
||||||
|
if ($uuids && $tags) {
|
||||||
|
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
|
} else if ($uuids) {
|
||||||
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||||
|
{
|
||||||
|
$uuids = explode(',', $uuid);
|
||||||
|
$uuids = collect(array_filter($uuids));
|
||||||
|
|
||||||
|
if (count($uuids) === 0) {
|
||||||
|
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
foreach ($uuids as $uuid) {
|
||||||
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
|
if ($resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||||
|
{
|
||||||
|
$tags = explode(',', $tags);
|
||||||
|
$tags = collect(array_filter($tags));
|
||||||
|
|
||||||
|
if (count($tags) === 0) {
|
||||||
|
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||||
|
if (!$found_tag) {
|
||||||
|
$message->push("Tag {$tag} not found.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$applications = $found_tag->applications()->get();
|
||||||
|
$services = $found_tag->services()->get();
|
||||||
|
if ($applications->count() === 0 && $services->count() === 0) {
|
||||||
|
$message->push("No resources found for tag {$tag}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($applications as $resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
foreach ($services as $resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
public function deploy_resource($resource, bool $force = false): Collection
|
||||||
|
{
|
||||||
|
$message = collect([]);
|
||||||
|
if (gettype($resource) !== 'object') {
|
||||||
|
return $message->push("Resource ($resource) not found.");
|
||||||
|
}
|
||||||
|
$type = $resource?->getMorphClass();
|
||||||
|
if ($type === 'App\Models\Application') {
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: new Cuid2(7),
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message->push("Application {$resource->name} deployment queued.");
|
||||||
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartPostgresql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartRedis::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMongodb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMysql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMariadb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\Service') {
|
||||||
|
StartService::run($resource);
|
||||||
|
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
||||||
|
}
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProcessStatus;
|
||||||
use App\Events\ApplicationStatusChanged;
|
use App\Events\ApplicationStatusChanged;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
@@ -122,7 +122,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($source) {
|
if ($source) {
|
||||||
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
|
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
|
||||||
}
|
}
|
||||||
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
|
$this->server = Server::find($this->application_deployment_queue->server_id);
|
||||||
|
$this->timeout = $this->server->settings->dynamic_timeout;
|
||||||
|
$this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first();
|
||||||
$this->server = $this->mainServer = $this->destination->server;
|
$this->server = $this->mainServer = $this->destination->server;
|
||||||
$this->serverUser = $this->server->user;
|
$this->serverUser = $this->server->user;
|
||||||
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
||||||
@@ -131,6 +133,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
|
|
||||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||||
|
ray('New container name: ', $this->container_name);
|
||||||
|
|
||||||
savePrivateKeyToFs($this->server);
|
savePrivateKeyToFs($this->server);
|
||||||
$this->saved_outputs = collect();
|
$this->saved_outputs = collect();
|
||||||
|
|
||||||
@@ -158,15 +162,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->preview->fqdn = $preview_fqdn;
|
$this->preview->fqdn = $preview_fqdn;
|
||||||
$this->preview->save();
|
$this->preview->save();
|
||||||
}
|
}
|
||||||
|
if ($this->application->is_github_based()) {
|
||||||
|
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::IN_PROGRESS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$this->application_deployment_queue->update([
|
|
||||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Generate custom host<->ip mapping
|
// Generate custom host<->ip mapping
|
||||||
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||||
if (!is_null($allContainers)) {
|
if (!is_null($allContainers)) {
|
||||||
@@ -229,6 +232,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||||
$this->application->isConfigurationChanged(false);
|
$this->application->isConfigurationChanged(false);
|
||||||
return;
|
return;
|
||||||
|
} else if ($this->pull_request_id !== 0) {
|
||||||
|
$this->deploy_pull_request();
|
||||||
} else if ($this->application->dockerfile) {
|
} else if ($this->application->dockerfile) {
|
||||||
$this->deploy_simple_dockerfile();
|
$this->deploy_simple_dockerfile();
|
||||||
} else if ($this->application->build_pack === 'dockercompose') {
|
} else if ($this->application->build_pack === 'dockercompose') {
|
||||||
@@ -240,22 +245,32 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
} else if ($this->application->build_pack === 'static') {
|
} else if ($this->application->build_pack === 'static') {
|
||||||
$this->deploy_static_buildpack();
|
$this->deploy_static_buildpack();
|
||||||
} else {
|
} else {
|
||||||
if ($this->pull_request_id !== 0) {
|
$this->deploy_nixpacks_buildpack();
|
||||||
$this->deploy_pull_request();
|
|
||||||
} else {
|
|
||||||
$this->deploy_nixpacks_buildpack();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if ($this->server->isProxyShouldRun()) {
|
if ($this->server->isProxyShouldRun()) {
|
||||||
dispatch(new ContainerStatusJob($this->server));
|
dispatch(new ContainerStatusJob($this->server));
|
||||||
}
|
}
|
||||||
// Otherwise built image needs to be pushed before from the build server.
|
// Otherwise built image needs to be pushed before from the build server.
|
||||||
if (!$this->use_build_server) {
|
// ray($this->use_build_server);
|
||||||
$this->push_to_docker_registry();
|
// if (!$this->use_build_server) {
|
||||||
}
|
// if ($this->application->additional_servers->count() > 0) {
|
||||||
|
// $this->push_to_docker_registry(forceFail: true);
|
||||||
|
// } else {
|
||||||
|
// $this->push_to_docker_registry();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
if ($this->application->is_github_based()) {
|
||||||
|
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED);
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->application->isConfigurationChanged(true);
|
$this->application->isConfigurationChanged(true);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
if ($this->pull_request_id !== 0 && $this->application->is_github_based()) {
|
||||||
|
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::ERROR);
|
||||||
|
}
|
||||||
|
ray($e);
|
||||||
$this->fail($e);
|
$this->fail($e);
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -281,162 +296,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function write_deployment_configurations()
|
|
||||||
{
|
|
||||||
if (isset($this->docker_compose_base64)) {
|
|
||||||
if ($this->use_build_server) {
|
|
||||||
$this->server = $this->original_server;
|
|
||||||
}
|
|
||||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
|
||||||
}
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
"mkdir -p $this->configuration_dir"
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"echo '{$readme}' > $this->configuration_dir/README.md",
|
|
||||||
]
|
|
||||||
);
|
|
||||||
if ($this->use_build_server) {
|
|
||||||
$this->server = $this->build_server;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private function push_to_docker_registry($forceFail = false)
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
$this->application->docker_registry_image_name &&
|
|
||||||
$this->application->build_pack !== 'dockerimage' &&
|
|
||||||
!$this->application->destination->server->isSwarm() &&
|
|
||||||
!$this->restart_only &&
|
|
||||||
!(str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged())
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
|
|
||||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
|
||||||
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if ($this->application->docker_registry_image_tag) {
|
|
||||||
// Tag image with latest
|
|
||||||
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
|
||||||
],
|
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.'");
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.'");
|
|
||||||
if ($forceFail) {
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
ray($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private function generate_image_names()
|
|
||||||
{
|
|
||||||
if ($this->application->dockerfile) {
|
|
||||||
if ($this->application->docker_registry_image_name) {
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest");
|
|
||||||
} else {
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
|
||||||
}
|
|
||||||
} else if ($this->application->build_pack === 'dockerimage') {
|
|
||||||
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
|
||||||
} else if ($this->pull_request_id !== 0) {
|
|
||||||
if ($this->application->docker_registry_image_name) {
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}");
|
|
||||||
} else {
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->dockerImageTag = str($this->commit)->substr(0, 128);
|
|
||||||
if ($this->application->docker_registry_image_name) {
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}");
|
|
||||||
} else {
|
|
||||||
$this->build_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}-build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private function just_restart()
|
|
||||||
{
|
|
||||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
|
||||||
$this->prepare_builder_image();
|
|
||||||
$this->check_git_if_build_needed();
|
|
||||||
$this->set_base_dir();
|
|
||||||
$this->generate_image_names();
|
|
||||||
$this->check_image_locally_or_remotely();
|
|
||||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
|
||||||
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
|
|
||||||
$this->create_workdir();
|
|
||||||
$this->generate_compose_file();
|
|
||||||
$this->rolling_update();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
|
||||||
}
|
|
||||||
private function check_image_locally_or_remotely()
|
|
||||||
{
|
|
||||||
$this->execute_remote_command([
|
|
||||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
|
||||||
]);
|
|
||||||
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
|
|
||||||
$this->execute_remote_command([
|
|
||||||
"docker pull {$this->production_image_name} 2>/dev/null", "ignore_errors" => true, "hidden" => true
|
|
||||||
]);
|
|
||||||
$this->execute_remote_command([
|
|
||||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private function save_environment_variables()
|
|
||||||
{
|
|
||||||
$envs = collect([]);
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
foreach ($this->application->environment_variables_preview as $env) {
|
|
||||||
$envs->push($env->key . '=' . $env->real_value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foreach ($this->application->environment_variables as $env) {
|
|
||||||
$envs->push($env->key . '=' . $env->real_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function deploy_simple_dockerfile()
|
private function deploy_simple_dockerfile()
|
||||||
{
|
{
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->server = $this->build_server;
|
$this->server = $this->build_server;
|
||||||
}
|
}
|
||||||
$dockerfile_base64 = base64_encode($this->application->dockerfile);
|
$dockerfile_base64 = base64_encode($this->application->dockerfile);
|
||||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
|
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}.");
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@@ -444,19 +310,30 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
|
if (!$this->force_rebuild) {
|
||||||
|
$this->check_image_locally_or_remotely();
|
||||||
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||||
|
$this->create_workdir();
|
||||||
|
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||||
|
$this->generate_compose_file();
|
||||||
|
$this->push_to_docker_registry();
|
||||||
|
$this->rolling_update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
$this->add_build_env_variables_to_dockerfile();
|
$this->add_build_env_variables_to_dockerfile();
|
||||||
$this->build_image();
|
$this->build_image();
|
||||||
|
$this->push_to_docker_registry();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function deploy_dockerimage_buildpack()
|
private function deploy_dockerimage_buildpack()
|
||||||
{
|
{
|
||||||
$this->dockerImage = $this->application->docker_registry_image_name;
|
$this->dockerImage = $this->application->docker_registry_image_name;
|
||||||
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||||
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'");
|
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.'");
|
||||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.");
|
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.");
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
@@ -474,14 +351,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
|
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id === 0) {
|
if ($this->pull_request_id === 0) {
|
||||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name}.");
|
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->application->name} to {$this->server->name}.");
|
||||||
} else {
|
} else {
|
||||||
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||||
}
|
}
|
||||||
$this->server->executeRemoteCommand(
|
$this->prepare_builder_image();
|
||||||
commands: $this->application->prepareHelperImage($this->deployment_uuid),
|
|
||||||
loggingModel: $this->application_deployment_queue
|
|
||||||
);
|
|
||||||
$this->check_git_if_build_needed();
|
$this->check_git_if_build_needed();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
@@ -547,30 +421,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (data_get($this->application, 'dockerfile_location')) {
|
if (data_get($this->application, 'dockerfile_location')) {
|
||||||
$this->dockerfile_location = $this->application->dockerfile_location;
|
$this->dockerfile_location = $this->application->dockerfile_location;
|
||||||
}
|
}
|
||||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||||
$this->prepare_builder_image();
|
|
||||||
$this->check_git_if_build_needed();
|
|
||||||
$this->clone_repository();
|
|
||||||
$this->set_base_dir();
|
|
||||||
$this->generate_image_names();
|
|
||||||
$this->cleanup_git();
|
|
||||||
$this->generate_compose_file();
|
|
||||||
$this->generate_build_env_variables();
|
|
||||||
$this->add_build_env_variables_to_dockerfile();
|
|
||||||
$this->build_image();
|
|
||||||
// if ($this->application->additional_destinations) {
|
|
||||||
// $this->push_to_docker_registry();
|
|
||||||
// $this->deploy_to_additional_destinations();
|
|
||||||
// } else {
|
|
||||||
$this->rolling_update();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
private function deploy_nixpacks_buildpack()
|
|
||||||
{
|
|
||||||
if ($this->use_build_server) {
|
|
||||||
$this->server = $this->build_server;
|
|
||||||
}
|
|
||||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->check_git_if_build_needed();
|
$this->check_git_if_build_needed();
|
||||||
$this->set_base_dir();
|
$this->set_base_dir();
|
||||||
@@ -581,6 +432,38 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->create_workdir();
|
$this->create_workdir();
|
||||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
|
$this->push_to_docker_registry();
|
||||||
|
$this->rolling_update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->clone_repository();
|
||||||
|
$this->cleanup_git();
|
||||||
|
$this->generate_compose_file();
|
||||||
|
$this->generate_build_env_variables();
|
||||||
|
$this->add_build_env_variables_to_dockerfile();
|
||||||
|
$this->build_image();
|
||||||
|
$this->push_to_docker_registry();
|
||||||
|
$this->rolling_update();
|
||||||
|
}
|
||||||
|
private function deploy_nixpacks_buildpack()
|
||||||
|
{
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->build_server;
|
||||||
|
}
|
||||||
|
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||||
|
$this->prepare_builder_image();
|
||||||
|
$this->check_git_if_build_needed();
|
||||||
|
$this->set_base_dir();
|
||||||
|
$this->generate_image_names();
|
||||||
|
if (!$this->force_rebuild) {
|
||||||
|
$this->check_image_locally_or_remotely();
|
||||||
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||||
|
$this->create_workdir();
|
||||||
|
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||||
|
$this->generate_compose_file();
|
||||||
|
ray('pushing to docker registry');
|
||||||
|
$this->push_to_docker_registry();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -593,8 +476,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->generate_nixpacks_confs();
|
$this->generate_nixpacks_confs();
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
// $this->add_build_env_variables_to_dockerfile();
|
|
||||||
$this->build_image();
|
$this->build_image();
|
||||||
|
$this->push_to_docker_registry();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
}
|
}
|
||||||
private function deploy_static_buildpack()
|
private function deploy_static_buildpack()
|
||||||
@@ -602,18 +485,205 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->server = $this->build_server;
|
$this->server = $this->build_server;
|
||||||
}
|
}
|
||||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->check_git_if_build_needed();
|
$this->check_git_if_build_needed();
|
||||||
$this->set_base_dir();
|
$this->set_base_dir();
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
|
if (!$this->force_rebuild) {
|
||||||
|
$this->check_image_locally_or_remotely();
|
||||||
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||||
|
$this->create_workdir();
|
||||||
|
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||||
|
$this->generate_compose_file();
|
||||||
|
$this->push_to_docker_registry();
|
||||||
|
$this->rolling_update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
$this->build_image();
|
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
|
$this->build_image();
|
||||||
|
$this->push_to_docker_registry();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function write_deployment_configurations()
|
||||||
|
{
|
||||||
|
if (isset($this->docker_compose_base64)) {
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->original_server;
|
||||||
|
}
|
||||||
|
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||||
|
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||||
|
}
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"mkdir -p $this->configuration_dir"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"echo '{$readme}' > $this->configuration_dir/README.md",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->build_server;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function push_to_docker_registry()
|
||||||
|
{
|
||||||
|
$forceFail = true;
|
||||||
|
if (str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||||
|
ray('empty docker_registry_image_name');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->restart_only) {
|
||||||
|
ray('restart_only');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->application->build_pack === 'dockerimage') {
|
||||||
|
ray('dockerimage');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
ray('use_build_server');
|
||||||
|
$forceFail = true;
|
||||||
|
}
|
||||||
|
if ($this->server->isSwarm() && $this->build_pack !== 'dockerimage') {
|
||||||
|
ray('isSwarm');
|
||||||
|
$forceFail = true;
|
||||||
|
}
|
||||||
|
if ($this->application->additional_servers->count() > 0) {
|
||||||
|
ray('additional_servers');
|
||||||
|
$forceFail = true;
|
||||||
|
}
|
||||||
|
if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) {
|
||||||
|
ray('this is an additional_servers, no pushy pushy');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ray('push_to_docker_registry noww: ' . $this->production_image_name);
|
||||||
|
try {
|
||||||
|
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
|
||||||
|
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||||
|
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if ($this->application->docker_registry_image_tag) {
|
||||||
|
// Tag image with latest
|
||||||
|
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
|
||||||
|
if ($forceFail) {
|
||||||
|
throw new RuntimeException($e->getMessage(), 69420);
|
||||||
|
}
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function generate_image_names()
|
||||||
|
{
|
||||||
|
if ($this->application->dockerfile) {
|
||||||
|
if ($this->application->docker_registry_image_name) {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest");
|
||||||
|
} else {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
|
}
|
||||||
|
} else if ($this->application->build_pack === 'dockerimage') {
|
||||||
|
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||||
|
} else if ($this->pull_request_id !== 0) {
|
||||||
|
if ($this->application->docker_registry_image_name) {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}");
|
||||||
|
} else {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->dockerImageTag = str($this->commit)->substr(0, 128);
|
||||||
|
if ($this->application->docker_registry_image_tag) {
|
||||||
|
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||||
|
}
|
||||||
|
if ($this->application->docker_registry_image_name) {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}");
|
||||||
|
} else {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}-build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function just_restart()
|
||||||
|
{
|
||||||
|
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||||
|
$this->prepare_builder_image();
|
||||||
|
$this->check_git_if_build_needed();
|
||||||
|
$this->set_base_dir();
|
||||||
|
$this->generate_image_names();
|
||||||
|
$this->check_image_locally_or_remotely();
|
||||||
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
|
||||||
|
$this->create_workdir();
|
||||||
|
$this->generate_compose_file();
|
||||||
|
$this->rolling_update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
||||||
|
}
|
||||||
|
private function check_image_locally_or_remotely()
|
||||||
|
{
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||||
|
]);
|
||||||
|
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"docker pull {$this->production_image_name} 2>/dev/null", "ignore_errors" => true, "hidden" => true
|
||||||
|
]);
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function save_environment_variables()
|
||||||
|
{
|
||||||
|
$envs = collect([]);
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
foreach ($this->application->environment_variables_preview as $env) {
|
||||||
|
$envs->push($env->key . '=' . $env->real_value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($this->application->environment_variables as $env) {
|
||||||
|
$envs->push($env->key . '=' . $env->real_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private function framework_based_notification()
|
private function framework_based_notification()
|
||||||
{
|
{
|
||||||
// Laravel old env variables
|
// Laravel old env variables
|
||||||
@@ -631,9 +701,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private function rolling_update()
|
private function rolling_update()
|
||||||
{
|
{
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
if ($this->build_pack !== 'dockerimage') {
|
|
||||||
$this->push_to_docker_registry(forceFail: true);
|
|
||||||
}
|
|
||||||
$this->application_deployment_queue->addLogEntry("Rolling update started.");
|
$this->application_deployment_queue->addLogEntry("Rolling update started.");
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
@@ -643,13 +710,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
||||||
} else {
|
} else {
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->push_to_docker_registry(forceFail: true);
|
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$this->server = $this->original_server;
|
$this->server = $this->original_server;
|
||||||
}
|
}
|
||||||
if (count($this->application->ports_mappings_array) > 0) {
|
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||||
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
if (count($this->application->ports_mappings_array) > 0) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
||||||
|
}
|
||||||
|
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
|
||||||
|
}
|
||||||
$this->stop_running_container(force: true);
|
$this->stop_running_container(force: true);
|
||||||
$this->start_by_compose_file();
|
$this->start_by_compose_file();
|
||||||
} else {
|
} else {
|
||||||
@@ -679,7 +750,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($this->full_healthcheck_url) {
|
if ($this->full_healthcheck_url) {
|
||||||
$this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}");
|
$this->application_deployment_queue->addLogEntry("Healthcheck URL (inside the container): {$this->full_healthcheck_url}");
|
||||||
}
|
}
|
||||||
while ($counter < $this->application->health_check_retries) {
|
while ($counter <= $this->application->health_check_retries) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
||||||
@@ -722,10 +793,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->generate_nixpacks_confs();
|
$this->generate_nixpacks_confs();
|
||||||
}
|
}
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
|
|
||||||
// Needs separate preview variables
|
|
||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
if ($this->application->build_pack !== 'nixpacks') {
|
if ($this->application->build_pack === 'dockerfile') {
|
||||||
$this->add_build_env_variables_to_dockerfile();
|
$this->add_build_env_variables_to_dockerfile();
|
||||||
}
|
}
|
||||||
$this->build_image();
|
$this->build_image();
|
||||||
@@ -790,7 +859,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
private function deploy_to_additional_destinations()
|
private function deploy_to_additional_destinations()
|
||||||
{
|
{
|
||||||
$destination_ids = collect(str($this->application->additional_destinations)->explode(','));
|
if ($this->application->additional_networks->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$destination_ids = $this->application->additional_networks->pluck('id');
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Additional destinations are not supported in swarm mode.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($destination_ids->contains($this->destination->id)) {
|
||||||
|
ray('Same destination found in additional destinations. Skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach ($destination_ids as $destination_id) {
|
foreach ($destination_ids as $destination_id) {
|
||||||
$destination = StandaloneDocker::find($destination_id);
|
$destination = StandaloneDocker::find($destination_id);
|
||||||
$server = $destination->server;
|
$server = $destination->server;
|
||||||
@@ -798,11 +878,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
|
$this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$this->server = $server;
|
// ray('Deploying to additional destination: ', $server->name);
|
||||||
$this->application_deployment_queue->addLogEntry("Deploying to {$this->server->name}.");
|
$deployment_uuid = new Cuid2();
|
||||||
$this->prepare_builder_image();
|
queue_application_deployment(
|
||||||
$this->generate_image_names();
|
deployment_uuid: $deployment_uuid,
|
||||||
$this->rolling_update();
|
application: $this->application,
|
||||||
|
server: $server,
|
||||||
|
destination: $destination,
|
||||||
|
no_questions_asked: true,
|
||||||
|
);
|
||||||
|
$this->application_deployment_queue->addLogEntry("Deploying to additional server: {$server->name}. Click here to see the deployment status: " . route('project.application.deployment.show', [
|
||||||
|
'project_uuid' => data_get($this->application, 'environment.project.uuid'),
|
||||||
|
'application_uuid' => data_get($this->application, 'uuid'),
|
||||||
|
'deployment_uuid' => $deployment_uuid,
|
||||||
|
'environment_name' => data_get($this->application, 'environment.name'),
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function set_base_dir()
|
private function set_base_dir()
|
||||||
@@ -861,7 +951,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function generate_git_import_commands()
|
private function generate_git_import_commands()
|
||||||
{
|
{
|
||||||
['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands($this->deployment_uuid, $this->pull_request_id, $this->git_type);
|
['commands' => $commands, 'branch' => $this->branch, 'fullRepoUrl' => $this->fullRepoUrl] = $this->application->generateGitImportCommands(
|
||||||
|
deployment_uuid: $this->deployment_uuid,
|
||||||
|
pull_request_id: $this->pull_request_id,
|
||||||
|
git_type: $this->git_type,
|
||||||
|
commit: $this->commit
|
||||||
|
);
|
||||||
return $commands;
|
return $commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1111,9 +1206,19 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// ];
|
// ];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
|
||||||
|
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
if (count($custom_compose) > 0) {
|
||||||
|
$docker_compose['services'][$this->container_name] = array_merge_recursive($docker_compose['services'][$this->container_name], $custom_compose);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||||
|
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||||
|
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||||
|
if (count($custom_compose) > 0) {
|
||||||
|
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||||
@@ -1397,6 +1502,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
if ($this->application->settings->is_consistent_container_name_enabled) {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
@@ -1489,31 +1599,39 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
|
|
||||||
private function next(string $status)
|
private function next(string $status)
|
||||||
{
|
{
|
||||||
|
queue_next_deployment($this->application);
|
||||||
// If the deployment is cancelled by the user, don't update the status
|
// If the deployment is cancelled by the user, don't update the status
|
||||||
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value) {
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
queue_next_deployment($this->application);
|
|
||||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
|
||||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
|
||||||
}
|
|
||||||
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||||
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||||
|
$this->deploy_to_additional_destinations();
|
||||||
|
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function failed(Throwable $exception): void
|
public function failed(Throwable $exception): void
|
||||||
{
|
{
|
||||||
$this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr');
|
$this->application_deployment_queue->addLogEntry("Oops something is not okay, are you okay? 😢", 'stderr');
|
||||||
$this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
|
if (str($exception->getMessage())->isNotEmpty()) {
|
||||||
|
$this->application_deployment_queue->addLogEntry($exception->getMessage(), 'stderr');
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->application->build_pack !== 'dockercompose') {
|
if ($this->application->build_pack !== 'dockercompose') {
|
||||||
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
|
$code = $exception->getCode();
|
||||||
$this->execute_remote_command(
|
if ($code !== 69420) {
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
// 69420 means failed to push the image to the registry, so we don't need to remove the new version as it is the currently running one
|
||||||
);
|
$this->application_deployment_queue->addLogEntry("Deployment failed. Removing the new version of your application.", 'stderr');
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Traits\ExecuteRemoteCommand;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use RuntimeException;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
class ApplicationDeploymentNewJob implements ShouldQueue, ShouldBeEncrypted
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
|
|
||||||
|
|
||||||
public $timeout = 3600;
|
|
||||||
public $tries = 1;
|
|
||||||
|
|
||||||
public static int $batch_counter = 0;
|
|
||||||
public Server $mainServer;
|
|
||||||
public $servers;
|
|
||||||
public string $basedir;
|
|
||||||
public string $workdir;
|
|
||||||
|
|
||||||
public string $deploymentUuid;
|
|
||||||
public int $pullRequestId = 0;
|
|
||||||
|
|
||||||
// Git related
|
|
||||||
public string $gitImportCommands;
|
|
||||||
public ?string $gitType = null;
|
|
||||||
public string $gitRepository;
|
|
||||||
public string $gitBranch;
|
|
||||||
public int $gitPort;
|
|
||||||
public string $gitFullRepoUrl;
|
|
||||||
|
|
||||||
public function __construct(public ApplicationDeploymentQueue $deployment, public Application $application)
|
|
||||||
{
|
|
||||||
$this->mainServer = data_get($this->application, 'destination.server');
|
|
||||||
$this->deploymentUuid = data_get($this->deployment, 'deployment_uuid');
|
|
||||||
$this->pullRequestId = data_get($this->deployment, 'pull_request_id', 0);
|
|
||||||
$this->gitType = data_get($this->deployment, 'git_type');
|
|
||||||
|
|
||||||
$this->basedir = $this->application->generateBaseDir($this->deploymentUuid);
|
|
||||||
$this->workdir = $this->basedir . rtrim($this->application->base_directory, '/');
|
|
||||||
}
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
ray()->clearAll();
|
|
||||||
$this->deployment->setStatus(ApplicationDeploymentStatus::IN_PROGRESS->value);
|
|
||||||
|
|
||||||
$hostIpMappings = $this->mainServer->getHostIPMappings($this->application->destination->network);
|
|
||||||
if ($this->application->dockerfile_target_build) {
|
|
||||||
$buildTarget = " --target {$this->application->dockerfile_target_build} ";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the git repository and port (custom port or default port)
|
|
||||||
[
|
|
||||||
'repository' => $this->gitRepository,
|
|
||||||
'port' => $this->gitPort
|
|
||||||
] = $this->application->customRepository();
|
|
||||||
|
|
||||||
// Get the git branch and git import commands
|
|
||||||
[
|
|
||||||
'commands' => $this->gitImportCommands,
|
|
||||||
'branch' => $this->gitBranch,
|
|
||||||
'fullRepoUrl' => $this->gitFullRepoUrl
|
|
||||||
] = $this->application->generateGitImportCommands($this->deploymentUuid, $this->pullRequestId, $this->gitType);
|
|
||||||
|
|
||||||
$this->servers = $this->application->servers();
|
|
||||||
|
|
||||||
if ($this->deployment->restart_only) {
|
|
||||||
if ($this->application->build_pack === 'dockerimage') {
|
|
||||||
throw new \Exception('Restart only is not supported for docker image based deployments');
|
|
||||||
}
|
|
||||||
$this->deployment->addLogEntry("Starting deployment of {$this->application->name}.");
|
|
||||||
$this->servers->each(function ($server) {
|
|
||||||
$this->deployment->addLogEntry("Restarting {$this->application->name} on {$server->name}.");
|
|
||||||
$this->restartOnly($server);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
$this->fail($exception);
|
|
||||||
} finally {
|
|
||||||
$this->servers->each(function ($server) {
|
|
||||||
$this->deployment->addLogEntry("Cleaning up temporary containers on {$server->name}.");
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: collect([])->push([
|
|
||||||
"command" => "docker rm -f {$this->deploymentUuid}",
|
|
||||||
"hidden" => true,
|
|
||||||
"ignoreErrors" => true,
|
|
||||||
]),
|
|
||||||
loggingModel: $this->deployment
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function restartOnly(Server $server)
|
|
||||||
{
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: $this->application->prepareHelperImage($this->deploymentUuid),
|
|
||||||
loggingModel: $this->deployment
|
|
||||||
);
|
|
||||||
|
|
||||||
$privateKey = data_get($this->application, 'private_key.private_key', null);
|
|
||||||
$gitLsRemoteCommand = collect([]);
|
|
||||||
if ($privateKey) {
|
|
||||||
$privateKey = base64_decode($privateKey);
|
|
||||||
$gitLsRemoteCommand
|
|
||||||
->push([
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "mkdir -p /root/.ssh")
|
|
||||||
])
|
|
||||||
->push([
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "echo '{$privateKey}' | base64 -d > /root/.ssh/id_rsa")
|
|
||||||
])
|
|
||||||
->push([
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "chmod 600 /root/.ssh/id_rsa")
|
|
||||||
])
|
|
||||||
->push([
|
|
||||||
"name" => "git_commit_sha",
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$gitLsRemoteCommand->push([
|
|
||||||
"name" => "git_commit_sha",
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$this->deployment->addLogEntry("Checking if there is any new commit on {$this->gitBranch} branch.");
|
|
||||||
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: $gitLsRemoteCommand,
|
|
||||||
loggingModel: $this->deployment
|
|
||||||
);
|
|
||||||
$commit = str($this->deployment->getOutput('git_commit_sha'))->before("\t");
|
|
||||||
|
|
||||||
[
|
|
||||||
'productionImageName' => $productionImageName
|
|
||||||
] = $this->application->generateImageNames($commit, $this->pullRequestId);
|
|
||||||
|
|
||||||
$this->deployment->addLogEntry("Checking if the image {$productionImageName} already exists.");
|
|
||||||
$server->checkIfDockerImageExists($productionImageName, $this->deployment);
|
|
||||||
|
|
||||||
if (str($this->deployment->getOutput('local_image_found'))->isNotEmpty()) {
|
|
||||||
$this->deployment->addLogEntry("Image {$productionImageName} already exists. Skipping the build.");
|
|
||||||
|
|
||||||
$server->createWorkDirForDeployment($this->workdir, $this->deployment);
|
|
||||||
|
|
||||||
$this->application->generateDockerComposeFile($server, $this->deployment, $this->workdir);
|
|
||||||
$this->application->rollingUpdateApplication($server, $this->deployment, $this->workdir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
|
||||||
}
|
|
||||||
public function failed(Throwable $exception): void
|
|
||||||
{
|
|
||||||
ray($exception);
|
|
||||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
|
||||||
}
|
|
||||||
private function next(string $status)
|
|
||||||
{
|
|
||||||
// If the deployment is cancelled by the user, don't update the status
|
|
||||||
if ($this->deployment->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
|
||||||
$this->deployment->update([
|
|
||||||
'status' => $status,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
queue_next_deployment($this->application, isNew: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,38 +17,34 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public string $build_logs_url;
|
public string $build_logs_url;
|
||||||
public Application $application;
|
|
||||||
public ApplicationPreview $preview;
|
|
||||||
public string $body;
|
public string $body;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $application_id,
|
public Application $application,
|
||||||
public int $pull_request_id,
|
public ApplicationPreview $preview,
|
||||||
public string $deployment_uuid,
|
public ProcessStatus $status,
|
||||||
public string $status
|
public ?string $deployment_uuid = null
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->application = Application::findOrFail($this->application_id);
|
if ($this->status === ProcessStatus::CLOSED) {
|
||||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
$this->delete_comment();
|
||||||
|
return;
|
||||||
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
} else if ($this->status === ProcessStatus::IN_PROGRESS) {
|
||||||
|
|
||||||
if ($this->status === ProcessStatus::IN_PROGRESS->value) {
|
|
||||||
$this->body = "The preview deployment is in progress. 🟡\n\n";
|
$this->body = "The preview deployment is in progress. 🟡\n\n";
|
||||||
}
|
} else if ($this->status === ProcessStatus::FINISHED) {
|
||||||
if ($this->status === ProcessStatus::FINISHED->value) {
|
|
||||||
$this->body = "The preview deployment is ready. 🟢\n\n";
|
$this->body = "The preview deployment is ready. 🟢\n\n";
|
||||||
if ($this->preview->fqdn) {
|
if ($this->preview->fqdn) {
|
||||||
$this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
|
$this->body .= "[Open Preview]({$this->preview->fqdn}) | ";
|
||||||
}
|
}
|
||||||
}
|
} else if ($this->status === ProcessStatus::ERROR) {
|
||||||
if ($this->status === ProcessStatus::ERROR->value) {
|
|
||||||
$this->body = "The preview deployment failed. 🔴\n\n";
|
$this->body = "The preview deployment failed. 🔴\n\n";
|
||||||
}
|
}
|
||||||
|
$this->build_logs_url = base_url() . "/project/{$this->application->environment->project->uuid}/{$this->application->environment->name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||||
|
|
||||||
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
|
$this->body .= "[Open Build Logs](" . $this->build_logs_url . ")\n\n\n";
|
||||||
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
|
$this->body .= "Last updated at: " . now()->toDateTimeString() . " CET";
|
||||||
|
|
||||||
@@ -77,10 +73,14 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function create_comment()
|
private function create_comment()
|
||||||
{
|
{
|
||||||
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [
|
['data' => $data] = githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->preview->pull_request_id}/comments", method: 'post', data: [
|
||||||
'body' => $this->body,
|
'body' => $this->body,
|
||||||
]);
|
]);
|
||||||
$this->preview->pull_request_issue_comment_id = $data['id'];
|
$this->preview->pull_request_issue_comment_id = $data['id'];
|
||||||
$this->preview->save();
|
$this->preview->save();
|
||||||
}
|
}
|
||||||
|
private function delete_comment()
|
||||||
|
{
|
||||||
|
githubApi(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'delete');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Jobs;
|
|||||||
use App\Actions\Database\StartDatabaseProxy;
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
use App\Actions\Proxy\CheckProxy;
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Actions\Shared\ComplexStatusCheck;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Container\ContainerRestarted;
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
@@ -42,6 +43,19 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$applications = $this->server->applications();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
if ($application->additional_servers->count() > 0) {
|
||||||
|
$is_main_server = $application->destination->server->id === $this->server->id;
|
||||||
|
if ($is_main_server) {
|
||||||
|
ComplexStatusCheck::run($application);
|
||||||
|
$applications = $applications->filter(function ($value, $key) use ($application) {
|
||||||
|
return $value->id !== $application->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$this->server->isFunctional()) {
|
if (!$this->server->isFunctional()) {
|
||||||
return 'Server is not ready.';
|
return 'Server is not ready.';
|
||||||
};
|
};
|
||||||
@@ -83,7 +97,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$applications = $this->server->applications();
|
|
||||||
$databases = $this->server->databases();
|
$databases = $this->server->databases();
|
||||||
$services = $this->server->services()->get();
|
$services = $this->server->services()->get();
|
||||||
$previews = $this->server->previews();
|
$previews = $this->server->previews();
|
||||||
@@ -160,10 +173,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// Notify user that this container should not be there.
|
// Notify user that this container should not be there.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data_get($container,'Name') === '/coolify-db') {
|
if (data_get($container, 'Name') === '/coolify-db') {
|
||||||
$foundDatabases[] = 0;
|
$foundDatabases[] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
if ($serviceLabelId) {
|
if ($serviceLabelId) {
|
||||||
@@ -209,7 +221,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
$exitedServices = $exitedServices->unique('id');
|
$exitedServices = $exitedServices->unique('id');
|
||||||
foreach ($exitedServices as $exitedService) {
|
foreach ($exitedServices as $exitedService) {
|
||||||
if ($exitedService->status === 'exited') {
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$name = data_get($exitedService, 'name');
|
$name = data_get($exitedService, 'name');
|
||||||
@@ -231,7 +243,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
||||||
foreach ($notRunningApplications as $applicationId) {
|
foreach ($notRunningApplications as $applicationId) {
|
||||||
$application = $applications->where('id', $applicationId)->first();
|
$application = $applications->where('id', $applicationId)->first();
|
||||||
if ($application->status === 'exited') {
|
if (str($application->status)->startsWith('exited')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$application->update(['status' => 'exited']);
|
$application->update(['status' => 'exited']);
|
||||||
@@ -256,7 +268,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
$preview = $previews->where('id', $previewId)->first();
|
$preview = $previews->where('id', $previewId)->first();
|
||||||
if ($preview->status === 'exited') {
|
if (str($preview->status)->startsWith('exited')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$preview->update(['status' => 'exited']);
|
$preview->update(['status' => 'exited']);
|
||||||
@@ -281,7 +293,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
foreach ($notRunningDatabases as $database) {
|
foreach ($notRunningDatabases as $database) {
|
||||||
$database = $databases->where('id', $database)->first();
|
$database = $databases->where('id', $database)->first();
|
||||||
if ($database->status === 'exited') {
|
if (str($database->status)->startsWith('exited')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$database->update(['status' => 'exited']);
|
$database->update(['status' => 'exited']);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Jobs;
|
|||||||
use App\Actions\Application\StopApplication;
|
use App\Actions\Application\StopApplication;
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Actions\Service\DeleteService;
|
use App\Actions\Service\DeleteService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
@@ -18,6 +19,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
|
||||||
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
@@ -30,45 +32,29 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = $this->resource->destination->server;
|
$this->resource->forceDelete();
|
||||||
$this->resource->delete();
|
|
||||||
if (!$server->isFunctional()) {
|
|
||||||
if ($this->resource->type() === 'service') {
|
|
||||||
ray('dispatching delete service');
|
|
||||||
DeleteService::dispatch($this->resource);
|
|
||||||
} else {
|
|
||||||
$this->resource->forceDelete();
|
|
||||||
}
|
|
||||||
return 'Server is not functional';
|
|
||||||
}
|
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
case 'application':
|
case 'application':
|
||||||
StopApplication::run($this->resource);
|
StopApplication::run($this->resource);
|
||||||
break;
|
break;
|
||||||
case 'standalone-postgresql':
|
case 'standalone-postgresql':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-mongodb':
|
case 'standalone-mongodb':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-mysql':
|
case 'standalone-mysql':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-mariadb':
|
case 'standalone-mariadb':
|
||||||
StopDatabase::run($this->resource);
|
StopDatabase::run($this->resource);
|
||||||
break;
|
break;
|
||||||
}
|
case 'service':
|
||||||
if ($this->resource->type() === 'service') {
|
StopService::run($this->resource);
|
||||||
DeleteService::dispatch($this->resource);
|
DeleteService::run($this->resource);
|
||||||
} else {
|
break;
|
||||||
$this->resource->forceDelete();
|
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
|
} finally {
|
||||||
|
Artisan::queue('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
} else if ($application = $task->application()->first()) {
|
} else if ($application = $task->application()->first()) {
|
||||||
$this->resource = $application;
|
$this->resource = $application;
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('ScheduledTaskJob failed: No resource found.');
|
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
|
||||||
}
|
}
|
||||||
$this->team = Team::find($task->team_id);
|
$this->team = Team::find($task->team_id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public ?int $disk_usage = null;
|
public int|string|null $disk_usage = null;
|
||||||
public $tries = 4;
|
public $tries = 4;
|
||||||
public function backoff(): int
|
public function backoff(): int
|
||||||
{
|
{
|
||||||
@@ -41,6 +41,15 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
throw new \RuntimeException('Server is not ready.');
|
throw new \RuntimeException('Server is not ready.');
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
|
// $this->server->validateConnection();
|
||||||
|
// $this->server->validateOS();
|
||||||
|
// $docker_installed = $this->server->validateDockerEngine();
|
||||||
|
// if (!$docker_installed) {
|
||||||
|
// $this->server->installDocker();
|
||||||
|
// $this->server->validateDockerEngine();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// $this->server->validateDockerEngineVersion();
|
||||||
if ($this->server->isFunctional()) {
|
if ($this->server->isFunctional()) {
|
||||||
$this->cleanup(notify: false);
|
$this->cleanup(notify: false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class ActivityMonitor extends Component
|
|||||||
public $isPollingActive = false;
|
public $isPollingActive = false;
|
||||||
|
|
||||||
protected $activity;
|
protected $activity;
|
||||||
protected $listeners = ['newMonitorActivity'];
|
protected $listeners = ['activityMonitor' => 'newMonitorActivity'];
|
||||||
|
|
||||||
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
|
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
|
||||||
{
|
{
|
||||||
|
|||||||
44
app/Livewire/Admin/Index.php
Normal file
44
app/Livewire/Admin/Index.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Admin;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
public $users = [];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
if (!isCloud()) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
if (auth()->user()->id !== 0 && session('adminToken') === null) {
|
||||||
|
return redirect()->route('dashboard');
|
||||||
|
}
|
||||||
|
$this->users = User::whereHas('teams', function ($query) {
|
||||||
|
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
|
||||||
|
})->get();
|
||||||
|
}
|
||||||
|
public function switchUser(int $user_id)
|
||||||
|
{
|
||||||
|
$user = User::find($user_id);
|
||||||
|
auth()->login($user);
|
||||||
|
|
||||||
|
if ($user_id === 0) {
|
||||||
|
session()->forget('adminToken');
|
||||||
|
} else {
|
||||||
|
$token_payload = [
|
||||||
|
'valid' => true,
|
||||||
|
];
|
||||||
|
$token = Crypt::encrypt($token_payload);
|
||||||
|
session(['adminToken' => $token]);
|
||||||
|
}
|
||||||
|
return refreshSession();
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.admin.index');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Boarding;
|
namespace App\Livewire\Boarding;
|
||||||
|
|
||||||
use App\Actions\Server\InstallDocker;
|
use App\Actions\Server\InstallDocker;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Models\PrivateKey;
|
use App\Models\PrivateKey;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -12,6 +13,7 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Index extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
|
protected $listeners = ['serverInstalled' => 'validateServer'];
|
||||||
public string $currentState = 'welcome';
|
public string $currentState = 'welcome';
|
||||||
|
|
||||||
public ?string $selectedServerType = null;
|
public ?string $selectedServerType = null;
|
||||||
@@ -93,7 +95,11 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||||
return $this->validateServer('localhost');
|
return $this->validateServer('localhost');
|
||||||
} elseif ($this->selectedServerType === 'remote') {
|
} elseif ($this->selectedServerType === 'remote') {
|
||||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
if (isDev()) {
|
||||||
|
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->get();
|
||||||
|
} else {
|
||||||
|
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||||
|
}
|
||||||
if ($this->privateKeys->count() > 0) {
|
if ($this->privateKeys->count() > 0) {
|
||||||
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
||||||
}
|
}
|
||||||
@@ -116,15 +122,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
}
|
}
|
||||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||||
$this->validateServer();
|
$this->installServer();
|
||||||
}
|
}
|
||||||
public function getProxyType()
|
public function getProxyType()
|
||||||
{
|
{
|
||||||
$proxyTypeSet = $this->createdServer->proxy->type;
|
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
||||||
if (!$proxyTypeSet) {
|
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||||
$this->currentState = 'select-proxy';
|
// if (!$proxyTypeSet) {
|
||||||
return;
|
// $this->currentState = 'select-proxy';
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
$this->getProjects();
|
$this->getProjects();
|
||||||
}
|
}
|
||||||
public function selectExistingPrivateKey()
|
public function selectExistingPrivateKey()
|
||||||
@@ -188,7 +195,11 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||||
$this->createdServer->settings->save();
|
$this->createdServer->settings->save();
|
||||||
$this->createdServer->addInitialNetwork();
|
$this->createdServer->addInitialNetwork();
|
||||||
$this->validateServer();
|
$this->currentState = 'validate-server';
|
||||||
|
}
|
||||||
|
public function installServer()
|
||||||
|
{
|
||||||
|
$this->dispatch('validateServer', true);
|
||||||
}
|
}
|
||||||
public function validateServer()
|
public function validateServer()
|
||||||
{
|
{
|
||||||
@@ -210,7 +221,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
|
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
|
||||||
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||||
if (is_null($dockerVersion)) {
|
if (is_null($dockerVersion)) {
|
||||||
$this->currentState = 'install-docker';
|
$this->currentState = 'validate-server';
|
||||||
throw new \Exception('Docker not found or old version is installed.');
|
throw new \Exception('Docker not found or old version is installed.');
|
||||||
}
|
}
|
||||||
$this->createdServer->settings()->update([
|
$this->createdServer->settings()->update([
|
||||||
@@ -218,27 +229,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
]);
|
]);
|
||||||
$this->getProxyType();
|
$this->getProxyType();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
// $this->dockerInstallationStarted = false;
|
|
||||||
return handleError(error: $e, livewire: $this);
|
return handleError(error: $e, livewire: $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function installDocker()
|
public function selectProxy(?string $proxyType = null)
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->dockerInstallationStarted = true;
|
|
||||||
$activity = InstallDocker::run($this->createdServer);
|
|
||||||
$this->dispatch('installDocker');
|
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$this->dockerInstallationStarted = false;
|
|
||||||
return handleError(error: $e, livewire: $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function dockerInstalledOrSkipped()
|
|
||||||
{
|
|
||||||
$this->validateServer();
|
|
||||||
}
|
|
||||||
public function selectProxy(string|null $proxyType = null)
|
|
||||||
{
|
{
|
||||||
if (!$proxyType) {
|
if (!$proxyType) {
|
||||||
return $this->getProjects();
|
return $this->getProjects();
|
||||||
|
|||||||
@@ -2,18 +2,43 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Dashboard extends Component
|
class Dashboard extends Component
|
||||||
{
|
{
|
||||||
public $projects = [];
|
public $projects = [];
|
||||||
public $servers = [];
|
public Collection $servers;
|
||||||
|
public $deployments_per_server;
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->servers = Server::ownedByCurrentTeam()->get();
|
$this->servers = Server::ownedByCurrentTeam()->get();
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||||
|
$this->get_deployments();
|
||||||
|
}
|
||||||
|
public function cleanup_queue()
|
||||||
|
{
|
||||||
|
$this->dispatch('success', 'Cleanup started.');
|
||||||
|
Artisan::queue('app:init', [
|
||||||
|
'--cleanup-deployments' => 'true'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
public function get_deployments()
|
||||||
|
{
|
||||||
|
$this->deployments_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn("server_id", $this->servers->pluck("id"))->get([
|
||||||
|
"id",
|
||||||
|
"application_id",
|
||||||
|
"application_name",
|
||||||
|
"deployment_url",
|
||||||
|
"pull_request_id",
|
||||||
|
"server_name",
|
||||||
|
"server_id",
|
||||||
|
"status"
|
||||||
|
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||||
}
|
}
|
||||||
// public function getIptables()
|
// public function getIptables()
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -28,9 +30,8 @@ class Help extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->rateLimit(3, 60);
|
$this->rateLimit(3, 30);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
|
|
||||||
$debug = "Route: {$this->path}";
|
$debug = "Route: {$this->path}";
|
||||||
$mail = new MailMessage();
|
$mail = new MailMessage();
|
||||||
$mail->view(
|
$mail->view(
|
||||||
@@ -40,9 +41,21 @@ class Help extends Component
|
|||||||
'debug' => $debug
|
'debug' => $debug
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
$mail->subject("[HELP]: {$this->subject}");
|
||||||
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
$settings = InstanceSettings::get();
|
||||||
$this->dispatch('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
|
$type = set_transanctional_email_settings($settings);
|
||||||
|
if (!$type) {
|
||||||
|
$url = "https://app.coolify.io/api/feedback";
|
||||||
|
if (isDev()) {
|
||||||
|
$url = "http://localhost:80/api/feedback";
|
||||||
|
}
|
||||||
|
Http::post($url, [
|
||||||
|
'content' => "User: `" . auth()->user()?->email . "` with subject: `" . $this->subject . "` has the following problem: `" . $this->description . "`"
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
||||||
|
}
|
||||||
|
$this->dispatch('success', 'Feedback sent.', 'We will get in touch with you as soon as possible.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
63
app/Livewire/NewActivityMonitor.php
Normal file
63
app/Livewire/NewActivityMonitor.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
|
class NewActivityMonitor extends Component
|
||||||
|
{
|
||||||
|
public ?string $header = null;
|
||||||
|
public $activityId;
|
||||||
|
public $eventToDispatch = 'activityFinished';
|
||||||
|
public $isPollingActive = false;
|
||||||
|
|
||||||
|
protected $activity;
|
||||||
|
protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
|
||||||
|
|
||||||
|
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
|
||||||
|
{
|
||||||
|
$this->activityId = $activityId;
|
||||||
|
$this->eventToDispatch = $eventToDispatch;
|
||||||
|
|
||||||
|
$this->hydrateActivity();
|
||||||
|
|
||||||
|
$this->isPollingActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hydrateActivity()
|
||||||
|
{
|
||||||
|
$this->activity = Activity::find($this->activityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function polling()
|
||||||
|
{
|
||||||
|
$this->hydrateActivity();
|
||||||
|
// $this->setStatus(ProcessStatus::IN_PROGRESS);
|
||||||
|
$exit_code = data_get($this->activity, 'properties.exitCode');
|
||||||
|
if ($exit_code !== null) {
|
||||||
|
// if ($exit_code === 0) {
|
||||||
|
// // $this->setStatus(ProcessStatus::FINISHED);
|
||||||
|
// } else {
|
||||||
|
// // $this->setStatus(ProcessStatus::ERROR);
|
||||||
|
// }
|
||||||
|
$this->isPollingActive = false;
|
||||||
|
if ($this->eventToDispatch !== null) {
|
||||||
|
if (str($this->eventToDispatch)->startsWith('App\\Events\\')) {
|
||||||
|
$causer_id = data_get($this->activity, 'causer_id');
|
||||||
|
$user = User::find($causer_id);
|
||||||
|
if ($user) {
|
||||||
|
foreach ($user->teams as $team) {
|
||||||
|
$teamId = $team->id;
|
||||||
|
$this->eventToDispatch::dispatch($teamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->dispatch($this->eventToDispatch);
|
||||||
|
ray('Dispatched event: ' . $this->eventToDispatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,20 +8,25 @@ use Livewire\Component;
|
|||||||
class Advanced extends Component
|
class Advanced extends Component
|
||||||
{
|
{
|
||||||
public Application $application;
|
public Application $application;
|
||||||
|
public bool $is_force_https_enabled;
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'application.settings.is_git_submodules_enabled' => 'boolean|required',
|
'application.settings.is_git_submodules_enabled' => 'boolean|required',
|
||||||
'application.settings.is_git_lfs_enabled' => 'boolean|required',
|
'application.settings.is_git_lfs_enabled' => 'boolean|required',
|
||||||
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
|
'application.settings.is_preview_deployments_enabled' => 'boolean|required',
|
||||||
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
|
'application.settings.is_auto_deploy_enabled' => 'boolean|required',
|
||||||
'application.settings.is_force_https_enabled' => 'boolean|required',
|
'is_force_https_enabled' => 'boolean|required',
|
||||||
'application.settings.is_log_drain_enabled' => 'boolean|required',
|
'application.settings.is_log_drain_enabled' => 'boolean|required',
|
||||||
'application.settings.is_gpu_enabled' => 'boolean|required',
|
'application.settings.is_gpu_enabled' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
|
||||||
'application.settings.gpu_driver' => 'string|required',
|
'application.settings.gpu_driver' => 'string|required',
|
||||||
'application.settings.gpu_count' => 'string|required',
|
'application.settings.gpu_count' => 'string|required',
|
||||||
'application.settings.gpu_device_ids' => 'string|required',
|
'application.settings.gpu_device_ids' => 'string|required',
|
||||||
'application.settings.gpu_options' => 'string|required',
|
'application.settings.gpu_options' => 'string|required',
|
||||||
];
|
];
|
||||||
|
public function mount() {
|
||||||
|
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||||
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
if ($this->application->isLogDrainEnabled()) {
|
if ($this->application->isLogDrainEnabled()) {
|
||||||
@@ -31,7 +36,8 @@ class Advanced extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->settings->is_force_https_enabled) {
|
if ($this->application->settings->is_force_https_enabled !== $this->is_force_https_enabled) {
|
||||||
|
$this->application->settings->is_force_https_enabled = $this->is_force_https_enabled;
|
||||||
$this->dispatch('resetDefaultLabels', false);
|
$this->dispatch('resetDefaultLabels', false);
|
||||||
}
|
}
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class Index extends Component
|
|||||||
public int $skip = 0;
|
public int $skip = 0;
|
||||||
public int $default_take = 40;
|
public int $default_take = 40;
|
||||||
public bool $show_next = false;
|
public bool $show_next = false;
|
||||||
|
public bool $show_prev = false;
|
||||||
public ?string $pull_request_id = null;
|
public ?string $pull_request_id = null;
|
||||||
protected $queryString = ['pull_request_id'];
|
protected $queryString = ['pull_request_id'];
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -60,15 +61,30 @@ class Index extends Component
|
|||||||
{
|
{
|
||||||
$this->load_deployments();
|
$this->load_deployments();
|
||||||
}
|
}
|
||||||
|
public function previous_page(?int $take = null)
|
||||||
|
{
|
||||||
|
|
||||||
public function load_deployments(int|null $take = null)
|
if ($take) {
|
||||||
|
$this->skip = $this->skip - $take;
|
||||||
|
}
|
||||||
|
$this->skip = $this->skip - $this->default_take;
|
||||||
|
if ($this->skip < 0) {
|
||||||
|
$this->show_prev = false;
|
||||||
|
$this->skip = 0;
|
||||||
|
}
|
||||||
|
$this->load_deployments();
|
||||||
|
}
|
||||||
|
public function next_page(?int $take = null)
|
||||||
{
|
{
|
||||||
if ($take) {
|
if ($take) {
|
||||||
$this->skip = $this->skip + $take;
|
$this->skip = $this->skip + $take;
|
||||||
}
|
}
|
||||||
$take = $this->default_take;
|
$this->show_prev = true;
|
||||||
|
$this->load_deployments();
|
||||||
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $take);
|
}
|
||||||
|
public function load_deployments()
|
||||||
|
{
|
||||||
|
['deployments' => $deployments, 'count' => $count] = $this->application->deployments($this->skip, $this->default_take);
|
||||||
$this->deployments = $deployments;
|
$this->deployments = $deployments;
|
||||||
$this->deployments_count = $count;
|
$this->deployments_count = $count;
|
||||||
$this->show_pull_request_only();
|
$this->show_pull_request_only();
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ use App\Models\Application;
|
|||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class DeploymentNavbar extends Component
|
class DeploymentNavbar extends Component
|
||||||
@@ -37,11 +35,21 @@ class DeploymentNavbar extends Component
|
|||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
$this->dispatch('refreshQueue');
|
$this->dispatch('refreshQueue');
|
||||||
}
|
}
|
||||||
|
public function force_start()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
force_start_deployment($this->application_deployment_queue);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function cancel()
|
public function cancel()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
|
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
|
||||||
|
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
|
||||||
|
$server = Server::find($server_id);
|
||||||
if ($this->application_deployment_queue->logs) {
|
if ($this->application_deployment_queue->logs) {
|
||||||
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
@@ -57,8 +65,8 @@ class DeploymentNavbar extends Component
|
|||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
|
'logs' => json_encode($previous_logs, flags: JSON_THROW_ON_ERROR),
|
||||||
]);
|
]);
|
||||||
instant_remote_process([$kill_command], $this->server);
|
|
||||||
}
|
}
|
||||||
|
instant_remote_process([$kill_command], $server);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e);
|
ray($e);
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -67,7 +75,6 @@ class DeploymentNavbar extends Component
|
|||||||
'current_process_id' => null,
|
'current_process_id' => null,
|
||||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||||
]);
|
]);
|
||||||
// queue_next_deployment($this->application);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,12 @@ class General extends Component
|
|||||||
'application.docker_compose_pr' => 'nullable',
|
'application.docker_compose_pr' => 'nullable',
|
||||||
'application.docker_compose_raw' => 'nullable',
|
'application.docker_compose_raw' => 'nullable',
|
||||||
'application.docker_compose_pr_raw' => 'nullable',
|
'application.docker_compose_pr_raw' => 'nullable',
|
||||||
'application.custom_labels' => 'nullable',
|
|
||||||
'application.dockerfile_target_build' => 'nullable',
|
'application.dockerfile_target_build' => 'nullable',
|
||||||
'application.settings.is_static' => 'boolean|required',
|
|
||||||
'application.docker_compose_custom_start_command' => 'nullable',
|
'application.docker_compose_custom_start_command' => 'nullable',
|
||||||
'application.docker_compose_custom_build_command' => 'nullable',
|
'application.docker_compose_custom_build_command' => 'nullable',
|
||||||
|
'application.custom_labels' => 'nullable',
|
||||||
|
'application.custom_docker_run_options' => 'nullable',
|
||||||
|
'application.settings.is_static' => 'boolean|required',
|
||||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
];
|
];
|
||||||
@@ -97,9 +98,10 @@ class General extends Component
|
|||||||
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
||||||
'application.custom_labels' => 'Custom labels',
|
'application.custom_labels' => 'Custom labels',
|
||||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||||
'application.settings.is_static' => 'Is static',
|
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||||
|
'application.settings.is_static' => 'Is static',
|
||||||
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
|
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
|
||||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||||
];
|
];
|
||||||
@@ -124,7 +126,6 @@ class General extends Component
|
|||||||
$this->application->save();
|
$this->application->save();
|
||||||
}
|
}
|
||||||
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
||||||
$this->checkLabelUpdates();
|
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
@@ -162,6 +163,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
return $domain;
|
return $domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedApplicationBuildPack()
|
public function updatedApplicationBuildPack()
|
||||||
{
|
{
|
||||||
if ($this->application->build_pack !== 'nixpacks') {
|
if ($this->application->build_pack !== 'nixpacks') {
|
||||||
@@ -182,15 +184,6 @@ class General extends Component
|
|||||||
$this->submit();
|
$this->submit();
|
||||||
$this->dispatch('build_pack_updated');
|
$this->dispatch('build_pack_updated');
|
||||||
}
|
}
|
||||||
public function checkLabelUpdates()
|
|
||||||
{
|
|
||||||
if (md5($this->application->custom_labels) !== md5(implode("|", generateLabelsApplication($this->application)))) {
|
|
||||||
$this->labelsChanged = true;
|
|
||||||
} else {
|
|
||||||
$this->labelsChanged = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWildcardDomain()
|
public function getWildcardDomain()
|
||||||
{
|
{
|
||||||
$server = data_get($this->application, 'destination.server');
|
$server = data_get($this->application, 'destination.server');
|
||||||
@@ -210,6 +203,13 @@ class General extends Component
|
|||||||
|
|
||||||
public function updatedApplicationFqdn()
|
public function updatedApplicationFqdn()
|
||||||
{
|
{
|
||||||
|
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
|
||||||
|
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
||||||
|
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||||
|
return str($domain)->trim()->lower();
|
||||||
|
});
|
||||||
|
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
||||||
|
$this->application->save();
|
||||||
$this->resetDefaultLabels(false);
|
$this->resetDefaultLabels(false);
|
||||||
// $this->dispatch('success', 'Labels reset to default!');
|
// $this->dispatch('success', 'Labels reset to default!');
|
||||||
}
|
}
|
||||||
@@ -236,19 +236,20 @@ class General extends Component
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if (data_get($this->application, 'fqdn')) {
|
if (data_get($this->application, 'fqdn')) {
|
||||||
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
|
$domains = str($this->application->fqdn)->trim()->explode(',');
|
||||||
$domains = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
if ($this->application->additional_servers->count() === 0) {
|
||||||
return str($domain)->trim()->lower();
|
foreach ($domains as $domain) {
|
||||||
});
|
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
||||||
$domains = $domains->unique();
|
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
|
||||||
foreach ($domains as $domain) {
|
}
|
||||||
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
|
||||||
$showToaster && $this->dispatch('error', "Validating DNS settings for: $domain failed.<br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
check_fqdn_usage($this->application);
|
||||||
$this->application->fqdn = $domains->implode(',');
|
$this->application->fqdn = $domains->implode(',');
|
||||||
}
|
}
|
||||||
|
if (data_get($this->application, 'custom_docker_run_options')) {
|
||||||
|
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
|
||||||
|
}
|
||||||
if (data_get($this->application, 'dockerfile')) {
|
if (data_get($this->application, 'dockerfile')) {
|
||||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||||
if ($port && !$this->application->ports_exposes) {
|
if ($port && !$this->application->ports_exposes) {
|
||||||
@@ -271,7 +272,6 @@ class General extends Component
|
|||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
$this->checkLabelUpdates();
|
|
||||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Actions\Application\StopApplication;
|
use App\Actions\Application\StopApplication;
|
||||||
|
use App\Events\ApplicationStatusChanged;
|
||||||
|
use App\Jobs\ComplexContainerStatusJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Jobs\ServerStatusJob;
|
use App\Jobs\ServerStatusJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
@@ -31,13 +33,14 @@ class Heading extends Component
|
|||||||
{
|
{
|
||||||
if ($this->application->destination->server->isFunctional()) {
|
if ($this->application->destination->server->isFunctional()) {
|
||||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||||
$this->application->refresh();
|
// $this->application->refresh();
|
||||||
$this->application->previews->each(function ($preview) {
|
// $this->application->previews->each(function ($preview) {
|
||||||
$preview->refresh();
|
// $preview->refresh();
|
||||||
});
|
// });
|
||||||
} else {
|
} else {
|
||||||
dispatch(new ServerStatusJob($this->application->destination->server));
|
dispatch(new ServerStatusJob($this->application->destination->server));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($showNotification) $this->dispatch('success', "Application status updated.");
|
if ($showNotification) $this->dispatch('success', "Application status updated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,45 +49,27 @@ class Heading extends Component
|
|||||||
$this->deploy(force_rebuild: true);
|
$this->deploy(force_rebuild: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deployNew()
|
|
||||||
{
|
|
||||||
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
|
||||||
$this->dispatch('error', 'Please load a Compose file first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->setDeploymentUuid();
|
|
||||||
queue_application_deployment(
|
|
||||||
application_id: $this->application->id,
|
|
||||||
server_id: $this->application->destination->server->id,
|
|
||||||
deployment_uuid: $this->deploymentUuid,
|
|
||||||
force_rebuild: false,
|
|
||||||
is_new_deployment: true,
|
|
||||||
);
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
|
||||||
'deployment_uuid' => $this->deploymentUuid,
|
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
public function deploy(bool $force_rebuild = false)
|
public function deploy(bool $force_rebuild = false)
|
||||||
{
|
{
|
||||||
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
||||||
$this->dispatch('error', 'Please load a Compose file first.');
|
$this->dispatch('error', 'Failed to deploy', 'Please load a Compose file first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
|
if ($this->application->destination->server->isSwarm() && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||||
$this->dispatch('error', 'To deploy to a Swarm cluster you must set a Docker image name first.');
|
$this->dispatch('error', 'Failed to deploy.', 'To deploy to a Swarm cluster you must set a Docker image name first.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data_get($this->application, 'settings.is_build_server_enabled') && is_null($this->application->docker_registry_image_name)) {
|
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||||
$this->dispatch('error', 'To use a build server you must set a Docker image name first.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
|
$this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/build-server">documentation</a>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||||
|
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->setDeploymentUuid();
|
$this->setDeploymentUuid();
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application_id: $this->application->id,
|
application: $this->application,
|
||||||
server_id: $this->application->destination->server->id,
|
|
||||||
deployment_uuid: $this->deploymentUuid,
|
deployment_uuid: $this->deploymentUuid,
|
||||||
force_rebuild: $force_rebuild,
|
force_rebuild: $force_rebuild,
|
||||||
);
|
);
|
||||||
@@ -107,31 +92,23 @@ class Heading extends Component
|
|||||||
StopApplication::run($this->application);
|
StopApplication::run($this->application);
|
||||||
$this->application->status = 'exited';
|
$this->application->status = 'exited';
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->application->refresh();
|
if ($this->application->additional_servers->count() > 0) {
|
||||||
}
|
$this->application->additional_servers->each(function ($server) {
|
||||||
public function restartNew()
|
$server->pivot->status = "exited:unhealthy";
|
||||||
{
|
$server->pivot->save();
|
||||||
$this->setDeploymentUuid();
|
});
|
||||||
queue_application_deployment(
|
}
|
||||||
application_id: $this->application->id,
|
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||||
server_id: $this->application->destination->server->id,
|
|
||||||
deployment_uuid: $this->deploymentUuid,
|
|
||||||
restart_only: true,
|
|
||||||
is_new_deployment: true,
|
|
||||||
);
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
|
||||||
'deployment_uuid' => $this->deploymentUuid,
|
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
public function restart()
|
public function restart()
|
||||||
{
|
{
|
||||||
|
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||||
|
$this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->setDeploymentUuid();
|
$this->setDeploymentUuid();
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application_id: $this->application->id,
|
application: $this->application,
|
||||||
server_id: $this->application->destination->server->id,
|
|
||||||
deployment_uuid: $this->deploymentUuid,
|
deployment_uuid: $this->deploymentUuid,
|
||||||
restart_only: true,
|
restart_only: true,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ class Previews extends Component
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application_id: $this->application->id,
|
application: $this->application,
|
||||||
server_id: $this->application->destination->server->id,
|
|
||||||
deployment_uuid: $this->deployment_uuid,
|
deployment_uuid: $this->deployment_uuid,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ class Rollback extends Component
|
|||||||
$deployment_uuid = new Cuid2(7);
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
|
||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application_id: $this->application->id,
|
application: $this->application,
|
||||||
server_id: $this->application->destination->server->id,
|
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
commit: $commit,
|
commit: $commit,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ class CloneMe extends Component
|
|||||||
public ?int $selectedDestination = null;
|
public ?int $selectedDestination = null;
|
||||||
public ?Server $server = null;
|
public ?Server $server = null;
|
||||||
public $resources = [];
|
public $resources = [];
|
||||||
public string $newProjectName = '';
|
public string $newName = '';
|
||||||
|
|
||||||
protected $messages = [
|
protected $messages = [
|
||||||
'selectedServer' => 'Please select a server.',
|
'selectedServer' => 'Please select a server.',
|
||||||
'selectedDestination' => 'Please select a server & destination.',
|
'selectedDestination' => 'Please select a server & destination.',
|
||||||
'newProjectName' => 'Please enter a name for the new project.',
|
'newName' => 'Please enter a name for the new project or environment.',
|
||||||
];
|
];
|
||||||
public function mount($project_uuid)
|
public function mount($project_uuid)
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@ class CloneMe extends Component
|
|||||||
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
||||||
$this->project_id = $this->project->id;
|
$this->project_id = $this->project->id;
|
||||||
$this->servers = currentTeam()->servers;
|
$this->servers = currentTeam()->servers;
|
||||||
$this->newProjectName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
|
$this->newName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
@@ -46,34 +46,50 @@ class CloneMe extends Component
|
|||||||
|
|
||||||
public function selectServer($server_id, $destination_id)
|
public function selectServer($server_id, $destination_id)
|
||||||
{
|
{
|
||||||
|
if ($server_id == $this->selectedServer && $destination_id == $this->selectedDestination) {
|
||||||
|
$this->selectedServer = null;
|
||||||
|
$this->selectedDestination = null;
|
||||||
|
$this->server = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->selectedServer = $server_id;
|
$this->selectedServer = $server_id;
|
||||||
$this->selectedDestination = $destination_id;
|
$this->selectedDestination = $destination_id;
|
||||||
$this->server = $this->servers->where('id', $server_id)->first();
|
$this->server = $this->servers->where('id', $server_id)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clone()
|
public function clone(string $type)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'selectedDestination' => 'required',
|
'selectedDestination' => 'required',
|
||||||
'newProjectName' => 'required',
|
'newName' => 'required',
|
||||||
]);
|
]);
|
||||||
$foundProject = Project::where('name', $this->newProjectName)->first();
|
if ($type === 'project') {
|
||||||
if ($foundProject) {
|
$foundProject = Project::where('name', $this->newName)->first();
|
||||||
throw new \Exception('Project with the same name already exists.');
|
if ($foundProject) {
|
||||||
}
|
throw new \Exception('Project with the same name already exists.');
|
||||||
$newProject = Project::create([
|
}
|
||||||
'name' => $this->newProjectName,
|
$project = Project::create([
|
||||||
'team_id' => currentTeam()->id,
|
'name' => $this->newName,
|
||||||
'description' => $this->project->description . ' (clone)',
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
'description' => $this->project->description . ' (clone)',
|
||||||
if ($this->environment->name !== 'production') {
|
]);
|
||||||
$newProject->environments()->create([
|
if ($this->environment->name !== 'production') {
|
||||||
'name' => $this->environment->name,
|
$project->environments()->create([
|
||||||
|
'name' => $this->environment->name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$environment = $project->environments->where('name', $this->environment->name)->first();
|
||||||
|
} else {
|
||||||
|
$foundEnv = $this->project->environments()->where('name', $this->newName)->first();
|
||||||
|
if ($foundEnv) {
|
||||||
|
throw new \Exception('Environment with the same name already exists.');
|
||||||
|
}
|
||||||
|
$project = $this->project;
|
||||||
|
$environment = $this->project->environments()->create([
|
||||||
|
'name' => $this->newName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$newEnvironment = $newProject->environments->where('name', $this->environment->name)->first();
|
|
||||||
// Clone Applications
|
|
||||||
$applications = $this->environment->applications;
|
$applications = $this->environment->applications;
|
||||||
$databases = $this->environment->databases();
|
$databases = $this->environment->databases();
|
||||||
$services = $this->environment->services;
|
$services = $this->environment->services;
|
||||||
@@ -83,7 +99,7 @@ class CloneMe extends Component
|
|||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'fqdn' => generateFqdn($this->server, $uuid),
|
'fqdn' => generateFqdn($this->server, $uuid),
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'environment_id' => $newEnvironment->id,
|
'environment_id' => $environment->id,
|
||||||
// This is not correct, but we need to set it to something
|
// This is not correct, but we need to set it to something
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
@@ -110,7 +126,7 @@ class CloneMe extends Component
|
|||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'started_at' => null,
|
'started_at' => null,
|
||||||
'environment_id' => $newEnvironment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newDatabase->save();
|
$newDatabase->save();
|
||||||
@@ -136,7 +152,7 @@ class CloneMe extends Component
|
|||||||
$uuid = (string)new Cuid2(7);
|
$uuid = (string)new Cuid2(7);
|
||||||
$newService = $service->replicate()->fill([
|
$newService = $service->replicate()->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'environment_id' => $newEnvironment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newService->save();
|
$newService->save();
|
||||||
@@ -153,8 +169,8 @@ class CloneMe extends Component
|
|||||||
$newService->parse();
|
$newService->parse();
|
||||||
}
|
}
|
||||||
return redirect()->route('project.resource.index', [
|
return redirect()->route('project.resource.index', [
|
||||||
'project_uuid' => $newProject->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_name' => $newEnvironment->name,
|
'environment_name' => $environment->name,
|
||||||
]);
|
]);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -58,19 +58,19 @@ class Heading extends Component
|
|||||||
{
|
{
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$activity = StartPostgresql::run($this->database);
|
$activity = StartPostgresql::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} else if ($this->database->type() === 'standalone-redis') {
|
} else if ($this->database->type() === 'standalone-redis') {
|
||||||
$activity = StartRedis::run($this->database);
|
$activity = StartRedis::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} else if ($this->database->type() === 'standalone-mongodb') {
|
} else if ($this->database->type() === 'standalone-mongodb') {
|
||||||
$activity = StartMongodb::run($this->database);
|
$activity = StartMongodb::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} else if ($this->database->type() === 'standalone-mysql') {
|
} else if ($this->database->type() === 'standalone-mysql') {
|
||||||
$activity = StartMysql::run($this->database);
|
$activity = StartMysql::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} else if ($this->database->type() === 'standalone-mariadb') {
|
} else if ($this->database->type() === 'standalone-mariadb') {
|
||||||
$activity = StartMariadb::run($this->database);
|
$activity = StartMariadb::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class Import extends Component
|
|||||||
|
|
||||||
if (!empty($this->importCommands)) {
|
if (!empty($this->importCommands)) {
|
||||||
$activity = remote_process($this->importCommands, $this->server, ignore_errors: true);
|
$activity = remote_process($this->importCommands, $this->server, ignore_errors: true);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->validated = false;
|
$this->validated = false;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class DeleteEnvironment extends Component
|
|||||||
{
|
{
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public int $environment_id;
|
public int $environment_id;
|
||||||
|
public bool $disabled = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class DeleteProject extends Component
|
|||||||
{
|
{
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public int $project_id;
|
public int $project_id;
|
||||||
|
public bool $disabled = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Models\PrivateKey;
|
|||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@@ -18,7 +19,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
public $current_step = 'private_keys';
|
public $current_step = 'private_keys';
|
||||||
public $parameters;
|
public $parameters;
|
||||||
public $query;
|
public $query;
|
||||||
public $private_keys;
|
public $private_keys =[];
|
||||||
public int $private_key_id;
|
public int $private_key_id;
|
||||||
|
|
||||||
public int $port = 3000;
|
public int $port = 3000;
|
||||||
@@ -33,6 +34,11 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
public $build_pack = 'nixpacks';
|
public $build_pack = 'nixpacks';
|
||||||
public bool $show_is_static = true;
|
public bool $show_is_static = true;
|
||||||
|
|
||||||
|
private object $repository_url_parsed;
|
||||||
|
private GithubApp|GitlabApp|string $git_source = 'other';
|
||||||
|
private ?string $git_host = null;
|
||||||
|
private string $git_repository;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'repository_url' => 'required',
|
'repository_url' => 'required',
|
||||||
'branch' => 'required|string',
|
'branch' => 'required|string',
|
||||||
@@ -49,10 +55,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
'publish_directory' => 'Publish directory',
|
'publish_directory' => 'Publish directory',
|
||||||
'build_pack' => 'Build pack',
|
'build_pack' => 'Build pack',
|
||||||
];
|
];
|
||||||
private object $repository_url_parsed;
|
|
||||||
private GithubApp|GitlabApp|string $git_source = 'other';
|
|
||||||
private ?string $git_host = null;
|
|
||||||
private string $git_repository;
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
$this->environment = $environment;
|
$this->environment = $environment;
|
||||||
$this->applications = $environment->applications->sortBy('name');
|
|
||||||
|
$this->applications = $this->environment->applications->load(['tags']);
|
||||||
$this->applications = $this->applications->map(function ($application) {
|
$this->applications = $this->applications->map(function ($application) {
|
||||||
if (data_get($application, 'environment.project.uuid')) {
|
if (data_get($application, 'environment.project.uuid')) {
|
||||||
$application->hrefLink = route('project.application.configuration', [
|
$application->hrefLink = route('project.application.configuration', [
|
||||||
@@ -40,8 +41,8 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $application;
|
return $application;
|
||||||
});
|
});
|
||||||
$this->postgresqls = $environment->postgresqls->sortBy('name');
|
$this->postgresqls = $this->environment->postgresqls->load(['tags'])->sortBy('name');
|
||||||
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
||||||
if (data_get($postgresql, 'environment.project.uuid')) {
|
if (data_get($postgresql, 'environment.project.uuid')) {
|
||||||
$postgresql->hrefLink = route('project.database.configuration', [
|
$postgresql->hrefLink = route('project.database.configuration', [
|
||||||
'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
|
'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
|
||||||
@@ -51,7 +52,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $postgresql;
|
return $postgresql;
|
||||||
});
|
});
|
||||||
$this->redis = $environment->redis->sortBy('name');
|
$this->redis = $this->environment->redis->load(['tags'])->sortBy('name');
|
||||||
$this->redis = $this->redis->map(function ($redis) {
|
$this->redis = $this->redis->map(function ($redis) {
|
||||||
if (data_get($redis, 'environment.project.uuid')) {
|
if (data_get($redis, 'environment.project.uuid')) {
|
||||||
$redis->hrefLink = route('project.database.configuration', [
|
$redis->hrefLink = route('project.database.configuration', [
|
||||||
@@ -62,7 +63,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $redis;
|
return $redis;
|
||||||
});
|
});
|
||||||
$this->mongodbs = $environment->mongodbs->sortBy('name');
|
$this->mongodbs = $this->environment->mongodbs->load(['tags'])->sortBy('name');
|
||||||
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
||||||
if (data_get($mongodb, 'environment.project.uuid')) {
|
if (data_get($mongodb, 'environment.project.uuid')) {
|
||||||
$mongodb->hrefLink = route('project.database.configuration', [
|
$mongodb->hrefLink = route('project.database.configuration', [
|
||||||
@@ -73,7 +74,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mongodb;
|
return $mongodb;
|
||||||
});
|
});
|
||||||
$this->mysqls = $environment->mysqls->sortBy('name');
|
$this->mysqls = $this->environment->mysqls->load(['tags'])->sortBy('name');
|
||||||
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
||||||
if (data_get($mysql, 'environment.project.uuid')) {
|
if (data_get($mysql, 'environment.project.uuid')) {
|
||||||
$mysql->hrefLink = route('project.database.configuration', [
|
$mysql->hrefLink = route('project.database.configuration', [
|
||||||
@@ -84,7 +85,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mysql;
|
return $mysql;
|
||||||
});
|
});
|
||||||
$this->mariadbs = $environment->mariadbs->sortBy('name');
|
$this->mariadbs = $this->environment->mariadbs->load(['tags'])->sortBy('name');
|
||||||
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
||||||
if (data_get($mariadb, 'environment.project.uuid')) {
|
if (data_get($mariadb, 'environment.project.uuid')) {
|
||||||
$mariadb->hrefLink = route('project.database.configuration', [
|
$mariadb->hrefLink = route('project.database.configuration', [
|
||||||
@@ -95,7 +96,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mariadb;
|
return $mariadb;
|
||||||
});
|
});
|
||||||
$this->services = $environment->services->sortBy('name');
|
$this->services = $this->environment->services->load(['tags'])->sortBy('name');
|
||||||
$this->services = $this->services->map(function ($service) {
|
$this->services = $this->services->map(function ($service) {
|
||||||
if (data_get($service, 'environment.project.uuid')) {
|
if (data_get($service, 'environment.project.uuid')) {
|
||||||
$service->hrefLink = route('project.service.configuration', [
|
$service->hrefLink = route('project.service.configuration', [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Configuration extends Component
|
class Configuration extends Component
|
||||||
{
|
{
|
||||||
public Service $service;
|
public ?Service $service = null;
|
||||||
public $applications;
|
public $applications;
|
||||||
public $databases;
|
public $databases;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class FileStorage extends Component
|
|||||||
$this->fileStorage->content = null;
|
$this->fileStorage->content = null;
|
||||||
}
|
}
|
||||||
$this->fileStorage->save();
|
$this->fileStorage->save();
|
||||||
$this->fileStorage->saveStorageOnServer($this->service);
|
$this->fileStorage->saveStorageOnServer();
|
||||||
$this->dispatch('success', 'File updated successfully.');
|
$this->dispatch('success', 'File updated successfully.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->fileStorage->setRawAttributes($original);
|
$this->fileStorage->setRawAttributes($original);
|
||||||
|
|||||||
@@ -27,12 +27,12 @@ class Index extends Component
|
|||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->query = request()->query();
|
$this->query = request()->query();
|
||||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||||
$service = $this->service->applications()->whereName($this->parameters['service_name'])->first();
|
$service = $this->service->applications()->whereUuid($this->parameters['stack_service_uuid'])->first();
|
||||||
if ($service) {
|
if ($service) {
|
||||||
$this->serviceApplication = $service;
|
$this->serviceApplication = $service;
|
||||||
$this->serviceApplication->getFilesFromServer();
|
$this->serviceApplication->getFilesFromServer();
|
||||||
} else {
|
} else {
|
||||||
$this->serviceDatabase = $this->service->databases()->whereName($this->parameters['service_name'])->first();
|
$this->serviceDatabase = $this->service->databases()->whereUuid($this->parameters['stack_service_uuid'])->first();
|
||||||
$this->serviceDatabase->getFilesFromServer();
|
$this->serviceDatabase->getFilesFromServer();
|
||||||
}
|
}
|
||||||
$this->s3s = currentTeam()->s3s;
|
$this->s3s = currentTeam()->s3s;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class Navbar extends Component
|
|||||||
}
|
}
|
||||||
$this->service->parse();
|
$this->service->parse();
|
||||||
$activity = StartService::run($this->service);
|
$activity = StartService::run($this->service);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
public function stop(bool $forceCleanup = false)
|
public function stop(bool $forceCleanup = false)
|
||||||
{
|
{
|
||||||
@@ -82,6 +82,6 @@ class Navbar extends Component
|
|||||||
StopService::run($this->service);
|
StopService::run($this->service);
|
||||||
$this->service->parse();
|
$this->service->parse();
|
||||||
$activity = StartService::run($this->service);
|
$activity = StartService::run($this->service);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace App\Livewire\Project\Service;
|
|||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Application extends Component
|
class ServiceApplicationView extends Component
|
||||||
{
|
{
|
||||||
public ServiceApplication $application;
|
public ServiceApplication $application;
|
||||||
public $parameters;
|
public $parameters;
|
||||||
@@ -20,7 +20,7 @@ class Application extends Component
|
|||||||
];
|
];
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.application');
|
return view('livewire.project.service.service-application-view');
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
@@ -53,6 +53,7 @@ class Application extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
check_fqdn_usage($this->application);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
updateCompose($this->application);
|
updateCompose($this->application);
|
||||||
@@ -17,14 +17,15 @@ class Danger extends Component
|
|||||||
{
|
{
|
||||||
$this->modalId = new Cuid2(7);
|
$this->modalId = new Cuid2(7);
|
||||||
$parameters = get_route_parameters();
|
$parameters = get_route_parameters();
|
||||||
$this->projectUuid = $parameters['project_uuid'];
|
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||||
$this->environmentName = $parameters['environment_name'];
|
$this->environmentName = data_get($parameters, 'environment_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
DeleteResourceJob::dispatchSync($this->resource);
|
$this->resource->delete();
|
||||||
|
DeleteResourceJob::dispatch($this->resource);
|
||||||
return redirect()->route('project.resource.index', [
|
return redirect()->route('project.resource.index', [
|
||||||
'project_uuid' => $this->projectUuid,
|
'project_uuid' => $this->projectUuid,
|
||||||
'environment_name' => $this->environmentName
|
'environment_name' => $this->environmentName
|
||||||
|
|||||||
@@ -2,11 +2,83 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Shared;
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Actions\Application\StopApplicationOneServer;
|
||||||
|
use App\Events\ApplicationStatusChanged;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Destination extends Component
|
class Destination extends Component
|
||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
public $servers = [];
|
public $networks = [];
|
||||||
public $additional_servers = [];
|
|
||||||
|
public function getListeners()
|
||||||
|
{
|
||||||
|
$teamId = auth()->user()->currentTeam()->id;
|
||||||
|
return [
|
||||||
|
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'loadData',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->loadData();
|
||||||
|
}
|
||||||
|
public function loadData()
|
||||||
|
{
|
||||||
|
$all_networks = collect([]);
|
||||||
|
$all_networks = $all_networks->push($this->resource->destination);
|
||||||
|
$all_networks = $all_networks->merge($this->resource->additional_networks);
|
||||||
|
|
||||||
|
$this->networks = Server::isUsable()->get()->map(function ($server) {
|
||||||
|
return $server->standaloneDockers;
|
||||||
|
})->flatten();
|
||||||
|
$this->networks = $this->networks->reject(function ($network) use ($all_networks) {
|
||||||
|
return $all_networks->pluck('id')->contains($network->id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public function redeploy(int $network_id, int $server_id)
|
||||||
|
{
|
||||||
|
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
|
||||||
|
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/server/multiple-servers">documentation</a>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
$server = Server::find($server_id);
|
||||||
|
$destination = StandaloneDocker::find($network_id);
|
||||||
|
queue_application_deployment(
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
application: $this->resource,
|
||||||
|
server: $server,
|
||||||
|
destination: $destination,
|
||||||
|
no_questions_asked: true,
|
||||||
|
);
|
||||||
|
return redirect()->route('project.application.deployment.show', [
|
||||||
|
'project_uuid' => data_get($this->resource, 'environment.project.uuid'),
|
||||||
|
'application_uuid' => data_get($this->resource, 'uuid'),
|
||||||
|
'deployment_uuid' => $deployment_uuid,
|
||||||
|
'environment_name' => data_get($this->resource, 'environment.name'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
public function addServer(int $network_id, int $server_id)
|
||||||
|
{
|
||||||
|
$this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]);
|
||||||
|
$this->resource->load(['additional_networks']);
|
||||||
|
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||||
|
$this->loadData();
|
||||||
|
}
|
||||||
|
public function removeServer(int $network_id, int $server_id)
|
||||||
|
{
|
||||||
|
if ($this->resource->destination->server->id == $server_id) {
|
||||||
|
$this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = Server::find($server_id);
|
||||||
|
StopApplicationOneServer::run($this->resource, $server);
|
||||||
|
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
|
||||||
|
$this->resource->load(['additional_networks']);
|
||||||
|
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||||
|
$this->loadData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ class Add extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
|
||||||
|
$type = str($this->value)->after("{{")->before(".")->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->dispatch('saveKey', [
|
$this->dispatch('saveKey', [
|
||||||
'key' => $this->key,
|
'key' => $this->key,
|
||||||
'value' => $this->value,
|
'value' => $this->value,
|
||||||
|
|||||||
@@ -71,12 +71,26 @@ class All extends Component
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$found->value = $variable;
|
$found->value = $variable;
|
||||||
|
if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
|
||||||
|
$type = str($found->value)->after("{{")->before(".")->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$found->save();
|
$found->save();
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$environment = new EnvironmentVariable();
|
$environment = new EnvironmentVariable();
|
||||||
$environment->key = $key;
|
$environment->key = $key;
|
||||||
$environment->value = $variable;
|
$environment->value = $variable;
|
||||||
|
if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
|
||||||
|
$type = str($environment->value)->after("{{")->before(".")->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$environment->is_build_time = false;
|
$environment->is_build_time = false;
|
||||||
$environment->is_preview = $isPreview ? true : false;
|
$environment->is_preview = $isPreview ? true : false;
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
@@ -157,7 +171,7 @@ class All extends Component
|
|||||||
}
|
}
|
||||||
$environment->save();
|
$environment->save();
|
||||||
$this->refreshEnvs();
|
$this->refreshEnvs();
|
||||||
$this->dispatch('success', 'Environment variable added successfully.');
|
$this->dispatch('success', 'Environment variable added.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ class Show extends Component
|
|||||||
$this->isLocked = true;
|
$this->isLocked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function serialize() {
|
public function serialize()
|
||||||
|
{
|
||||||
data_forget($this->env, 'real_value');
|
data_forget($this->env, 'real_value');
|
||||||
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
|
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
|
||||||
data_forget($this->env, 'is_build_time');
|
data_forget($this->env, 'is_build_time');
|
||||||
@@ -80,11 +81,18 @@ class Show extends Component
|
|||||||
} else {
|
} else {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
}
|
}
|
||||||
|
if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
|
||||||
|
$type = str($this->env->value)->after("{{")->before(".")->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->serialize();
|
$this->serialize();
|
||||||
$this->env->save();
|
$this->env->save();
|
||||||
$this->dispatch('success', 'Environment variable updated successfully.');
|
$this->dispatch('success', 'Environment variable updated successfully.');
|
||||||
$this->dispatch('refreshEnvs');
|
$this->dispatch('refreshEnvs');
|
||||||
} catch(\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class ExecuteContainerCommand extends Component
|
|||||||
$exec = "docker exec {$this->container} {$cmd}";
|
$exec = "docker exec {$this->container} {$cmd}";
|
||||||
}
|
}
|
||||||
$activity = remote_process([$exec], $this->server, ignore_errors: true);
|
$activity = remote_process([$exec], $this->server, ignore_errors: true);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ class ResourceOperations extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$parameters = get_route_parameters();
|
$parameters = get_route_parameters();
|
||||||
$this->projectUuid = $parameters['project_uuid'];
|
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||||
$this->environmentName = $parameters['environment_name'];
|
$this->environmentName = data_get($parameters, 'environment_name');
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||||
$this->servers = currentTeam()->servers;
|
$this->servers = currentTeam()->servers;
|
||||||
}
|
}
|
||||||
@@ -45,6 +45,11 @@ class ResourceOperations extends Component
|
|||||||
'destination_id' => $new_destination->id,
|
'destination_id' => $new_destination->id,
|
||||||
]);
|
]);
|
||||||
$new_resource->save();
|
$new_resource->save();
|
||||||
|
if ($new_resource->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||||
|
$customLabels = str(implode("|", generateLabelsApplication($new_resource)))->replace("|", "\n");
|
||||||
|
$new_resource->custom_labels = base64_encode($customLabels);
|
||||||
|
$new_resource->save();
|
||||||
|
}
|
||||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
||||||
|
|||||||
90
app/Livewire/Project/Shared/Tags.php
Normal file
90
app/Livewire/Project/Shared/Tags.php
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Tags extends Component
|
||||||
|
{
|
||||||
|
public $resource = null;
|
||||||
|
public ?string $new_tag = null;
|
||||||
|
public $tags = [];
|
||||||
|
protected $listeners = [
|
||||||
|
'refresh' => '$refresh',
|
||||||
|
];
|
||||||
|
protected $rules = [
|
||||||
|
'resource.tags.*.name' => 'required|string|min:2',
|
||||||
|
'new_tag' => 'required|string|min:2'
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'new_tag' => 'tag'
|
||||||
|
];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||||
|
}
|
||||||
|
public function addTag(string $id, string $name)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->resource->tags()->where('id', $id)->exists()) {
|
||||||
|
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$name</span> already added.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->resource->tags()->syncWithoutDetaching($id);
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function deleteTag(string $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->resource->tags()->detach($id);
|
||||||
|
|
||||||
|
$found_more_tags = Tag::where(['id' => $id, 'team_id' => currentTeam()->id])->first();
|
||||||
|
if ($found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0){
|
||||||
|
$found_more_tags->delete();
|
||||||
|
}
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function refresh()
|
||||||
|
{
|
||||||
|
$this->resource->load(['tags']);
|
||||||
|
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||||
|
$this->new_tag = null;
|
||||||
|
}
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate([
|
||||||
|
'new_tag' => 'required|string|min:2'
|
||||||
|
]);
|
||||||
|
$tags = str($this->new_tag)->trim()->explode(' ');
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
if ($this->resource->tags()->where('name', $tag)->exists()) {
|
||||||
|
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$tag</span> already added.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();
|
||||||
|
if (!$found) {
|
||||||
|
$found = Tag::create([
|
||||||
|
'name' => $tag,
|
||||||
|
'team_id' => currentTeam()->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->resource->tags()->syncWithoutDetaching($found->id);
|
||||||
|
}
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.project.shared.tags');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,11 @@ class Webhooks extends Component
|
|||||||
public ?string $deploywebhook = null;
|
public ?string $deploywebhook = null;
|
||||||
public ?string $githubManualWebhook = null;
|
public ?string $githubManualWebhook = null;
|
||||||
public ?string $gitlabManualWebhook = null;
|
public ?string $gitlabManualWebhook = null;
|
||||||
|
public ?string $bitbucketManualWebhook = null;
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'resource.manual_webhook_secret_github' => 'nullable|string',
|
'resource.manual_webhook_secret_github' => 'nullable|string',
|
||||||
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
|
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
|
||||||
|
'resource.manual_webhook_secret_bitbucket' => 'nullable|string',
|
||||||
];
|
];
|
||||||
public function saveSecret()
|
public function saveSecret()
|
||||||
{
|
{
|
||||||
@@ -29,6 +31,7 @@ class Webhooks extends Component
|
|||||||
$this->deploywebhook = generateDeployWebhook($this->resource);
|
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||||
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
||||||
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
||||||
|
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class RunCommand extends Component
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
try {
|
try {
|
||||||
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
|
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
use App\Actions\Server\InstallDocker;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -14,7 +13,8 @@ class Form extends Component
|
|||||||
public ?string $wildcard_domain = null;
|
public ?string $wildcard_domain = null;
|
||||||
public int $cleanup_after_percentage;
|
public int $cleanup_after_percentage;
|
||||||
public bool $dockerInstallationStarted = false;
|
public bool $dockerInstallationStarted = false;
|
||||||
protected $listeners = ['serverRefresh'];
|
|
||||||
|
protected $listeners = ['serverInstalled'];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'server.name' => 'required',
|
'server.name' => 'required',
|
||||||
@@ -28,6 +28,7 @@ class Form extends Component
|
|||||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||||
'server.settings.is_build_server' => 'required|boolean',
|
'server.settings.is_build_server' => 'required|boolean',
|
||||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||||
|
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
||||||
'wildcard_domain' => 'nullable|url',
|
'wildcard_domain' => 'nullable|url',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -42,6 +43,8 @@ class Form extends Component
|
|||||||
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||||
'server.settings.is_build_server' => 'Build Server',
|
'server.settings.is_build_server' => 'Build Server',
|
||||||
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
||||||
|
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -49,9 +52,10 @@ class Form extends Component
|
|||||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||||
}
|
}
|
||||||
public function serverRefresh($install = true)
|
public function serverInstalled()
|
||||||
{
|
{
|
||||||
$this->validateServer($install);
|
$this->server->refresh();
|
||||||
|
$this->server->settings->refresh();
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
@@ -64,13 +68,6 @@ class Form extends Component
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function installDocker()
|
|
||||||
{
|
|
||||||
$this->dispatch('installDocker');
|
|
||||||
$this->dockerInstallationStarted = true;
|
|
||||||
$activity = InstallDocker::run($this->server);
|
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
|
||||||
}
|
|
||||||
public function checkLocalhostConnection()
|
public function checkLocalhostConnection()
|
||||||
{
|
{
|
||||||
$uptime = $this->server->validateConnection();
|
$uptime = $this->server->validateConnection();
|
||||||
@@ -80,48 +77,13 @@ class Form extends Component
|
|||||||
$this->server->settings->is_usable = true;
|
$this->server->settings->is_usable = true;
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function validateServer($install = true)
|
public function validateServer($install = true)
|
||||||
{
|
{
|
||||||
try {
|
$this->dispatch('validateServer', $install);
|
||||||
$uptime = $this->server->validateConnection();
|
|
||||||
if (!$uptime) {
|
|
||||||
$install && $this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$supported_os_type = $this->server->validateOS();
|
|
||||||
if (!$supported_os_type) {
|
|
||||||
$install && $this->dispatch('error', 'Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$dockerInstalled = $this->server->validateDockerEngine();
|
|
||||||
if ($dockerInstalled) {
|
|
||||||
$install && $this->dispatch('success', 'Docker Engine is installed.<br> Checking version.');
|
|
||||||
} else {
|
|
||||||
$install && $this->installDocker();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$dockerVersion = $this->server->validateDockerEngineVersion();
|
|
||||||
if ($dockerVersion) {
|
|
||||||
$install && $this->dispatch('success', 'Docker Engine version is 22+.');
|
|
||||||
} else {
|
|
||||||
$install && $this->installDocker();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($this->server->isSwarm()) {
|
|
||||||
$swarmInstalled = $this->server->validateDockerSwarm();
|
|
||||||
if ($swarmInstalled) {
|
|
||||||
$install && $this->dispatch('success', 'Docker Swarm is initiated.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
} finally {
|
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class Deploy extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$activity = StartProxy::run($this->server);
|
$activity = StartProxy::run($this->server);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id, ProxyStatusChanged::class);
|
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ class Status extends Component
|
|||||||
public Server $server;
|
public Server $server;
|
||||||
public bool $polling = false;
|
public bool $polling = false;
|
||||||
public int $numberOfPolls = 0;
|
public int $numberOfPolls = 0;
|
||||||
|
protected $listeners = ['proxyStatusUpdated' => '$refresh', 'startProxyPolling'];
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
|
||||||
public function mount() {
|
|
||||||
}
|
|
||||||
public function startProxyPolling()
|
public function startProxyPolling()
|
||||||
{
|
{
|
||||||
$this->checkProxy();
|
$this->checkProxy();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class Show extends Component
|
|||||||
use AuthorizesRequests;
|
use AuthorizesRequests;
|
||||||
public ?Server $server = null;
|
public ?Server $server = null;
|
||||||
public $parameters = [];
|
public $parameters = [];
|
||||||
|
protected $listeners = ['serverInstalled' => '$refresh'];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
@@ -19,14 +20,13 @@ class Show extends Component
|
|||||||
if (is_null($this->server)) {
|
if (is_null($this->server)) {
|
||||||
return redirect()->route('server.index');
|
return redirect()->route('server.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->dispatch('serverRefresh',false);
|
$this->dispatch('serverRefresh', false);
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ShowPrivateKey extends Component
|
|||||||
if ($uptime) {
|
if ($uptime) {
|
||||||
$this->dispatch('success', 'Server is reachable.');
|
$this->dispatch('success', 'Server is reachable.');
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/configuration#openssh-server">documentation</a> for further help.');
|
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh#openssh">documentation</a> for further help.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
116
app/Livewire/Server/ValidateAndInstall.php
Normal file
116
app/Livewire/Server/ValidateAndInstall.php
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class ValidateAndInstall extends Component
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
public int $number_of_tries = 0;
|
||||||
|
public int $max_tries = 1;
|
||||||
|
public bool $install = true;
|
||||||
|
public $uptime = null;
|
||||||
|
public $supported_os_type = null;
|
||||||
|
public $docker_installed = null;
|
||||||
|
public $docker_compose_installed = null;
|
||||||
|
public $docker_version = null;
|
||||||
|
public $proxy_started = false;
|
||||||
|
public $error = null;
|
||||||
|
|
||||||
|
protected $listeners = ['validateServer' => 'init', 'validateDockerEngine', 'validateServerNow' => 'validateServer'];
|
||||||
|
|
||||||
|
public function init(bool $install = true)
|
||||||
|
{
|
||||||
|
|
||||||
|
$this->install = $install;
|
||||||
|
$this->uptime = null;
|
||||||
|
$this->supported_os_type = null;
|
||||||
|
$this->docker_installed = null;
|
||||||
|
$this->docker_version = null;
|
||||||
|
$this->docker_compose_installed = null;
|
||||||
|
$this->proxy_started = null;
|
||||||
|
$this->error = null;
|
||||||
|
$this->number_of_tries = 0;
|
||||||
|
$this->dispatch('validateServerNow');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateServer()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validateConnection();
|
||||||
|
$this->validateOS();
|
||||||
|
$this->validateDockerEngine();
|
||||||
|
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||||
|
if ($swarmInstalled) {
|
||||||
|
$this->dispatch('success', 'Docker Swarm is initiated.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$proxy = StartProxy::run($this->server);
|
||||||
|
if ($proxy) {
|
||||||
|
$this->proxy_started = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateConnection()
|
||||||
|
{
|
||||||
|
$this->uptime = $this->server->validateConnection();
|
||||||
|
if (!$this->uptime) {
|
||||||
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateOS()
|
||||||
|
{
|
||||||
|
$this->supported_os_type = $this->server->validateOS();
|
||||||
|
if (!$this->supported_os_type) {
|
||||||
|
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateDockerEngine()
|
||||||
|
{
|
||||||
|
$this->docker_installed = $this->server->validateDockerEngine();
|
||||||
|
$this->docker_compose_installed = $this->server->validateDockerCompose();
|
||||||
|
if (!$this->docker_installed || !$this->docker_compose_installed) {
|
||||||
|
if ($this->install) {
|
||||||
|
if ($this->number_of_tries == $this->max_tries) {
|
||||||
|
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
$activity = $this->server->installDocker();
|
||||||
|
$this->number_of_tries++;
|
||||||
|
$this->dispatch('newActivityMonitor', $activity->id, 'validateDockerEngine');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->validateDockerVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateDockerVersion()
|
||||||
|
{
|
||||||
|
$this->docker_version = $this->server->validateDockerEngineVersion();
|
||||||
|
if ($this->docker_version) {
|
||||||
|
$this->dispatch('serverInstalled');
|
||||||
|
$this->dispatch('success', 'Server validated successfully.');
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.validate-and-install');
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Livewire/Tags/Index.php
Normal file
18
app/Livewire/Tags/Index.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
public $tags = [];
|
||||||
|
public function mount() {
|
||||||
|
$this->tags = Tag::where('team_id', currentTeam()->id)->get()->unique('name')->sortBy('name');
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.tags.index');
|
||||||
|
}
|
||||||
|
}
|
||||||
72
app/Livewire/Tags/Show.php
Normal file
72
app/Livewire/Tags/Show.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\Deploy;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public $tags;
|
||||||
|
public Tag $tag;
|
||||||
|
public $applications;
|
||||||
|
public $services;
|
||||||
|
public $webhook = null;
|
||||||
|
public $deployments_per_tag_per_server = [];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
|
||||||
|
$tag = $this->tags->where('name', request()->tag_name)->first();
|
||||||
|
if (!$tag) {
|
||||||
|
return redirect()->route('tags.index');
|
||||||
|
}
|
||||||
|
$this->webhook = generatTagDeployWebhook($tag->name);
|
||||||
|
$this->applications = $tag->applications()->get();
|
||||||
|
$this->services = $tag->services()->get();
|
||||||
|
$this->tag = $tag;
|
||||||
|
$this->get_deployments();
|
||||||
|
}
|
||||||
|
public function get_deployments()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$resource_ids = $this->applications->pluck('id');
|
||||||
|
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
|
||||||
|
"id",
|
||||||
|
"application_id",
|
||||||
|
"application_name",
|
||||||
|
"deployment_url",
|
||||||
|
"pull_request_id",
|
||||||
|
"server_name",
|
||||||
|
"server_id",
|
||||||
|
"status"
|
||||||
|
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function redeploy_all()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$message = collect([]);
|
||||||
|
$this->applications->each(function ($resource) use ($message) {
|
||||||
|
$deploy = new Deploy();
|
||||||
|
$message->push($deploy->deploy_resource($resource));
|
||||||
|
});
|
||||||
|
$this->services->each(function ($resource) use ($message) {
|
||||||
|
$deploy = new Deploy();
|
||||||
|
$message->push($deploy->deploy_resource($resource));
|
||||||
|
});
|
||||||
|
$this->dispatch('success', 'Mass deployment started.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.tags.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,11 @@ class Invitations extends Component
|
|||||||
|
|
||||||
public function deleteInvitation(int $invitation_id)
|
public function deleteInvitation(int $invitation_id)
|
||||||
{
|
{
|
||||||
TeamInvitation::find($invitation_id)->delete();
|
$initiation_found = TeamInvitation::find($invitation_id);
|
||||||
|
if (!$initiation_found) {
|
||||||
|
return $this->dispatch('error', 'Invitation not found.');
|
||||||
|
}
|
||||||
|
$initiation_found->delete();
|
||||||
$this->refreshInvitations();
|
$this->refreshInvitations();
|
||||||
$this->dispatch('success', 'Invitation revoked.');
|
$this->dispatch('success', 'Invitation revoked.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class Create extends Component
|
|||||||
$this->storage->save();
|
$this->storage->save();
|
||||||
return redirect()->route('team.storage.show', $this->storage->uuid);
|
return redirect()->route('team.storage.show', $this->storage->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
$this->dispatch('error', 'Failed to create storage.', $e->getMessage());
|
||||||
|
// return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ class Form extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->storage->testConnection(shouldSave: true);
|
$this->storage->testConnection(shouldSave: true);
|
||||||
return $this->dispatch('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
return $this->dispatch('success', 'Connection is working.', 'Tested with "ListObjectsV2" action.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
$this->dispatch('error', 'Failed to create storage.', $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,18 +6,15 @@ use App\Enums\ApplicationDeploymentStatus;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Application extends BaseModel
|
class Application extends BaseModel
|
||||||
{
|
{
|
||||||
use SoftDeletes;
|
use SoftDeletes;
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::saving(function ($application) {
|
static::saving(function ($application) {
|
||||||
@@ -39,6 +36,7 @@ class Application extends BaseModel
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($application) {
|
static::deleting(function ($application) {
|
||||||
|
$application->update(['fqdn' => null]);
|
||||||
$application->settings()->delete();
|
$application->settings()->delete();
|
||||||
$storages = $application->persistentStorages()->get();
|
$storages = $application->persistentStorages()->get();
|
||||||
$server = data_get($application, 'destination.server');
|
$server = data_get($application, 'destination.server');
|
||||||
@@ -50,67 +48,30 @@ class Application extends BaseModel
|
|||||||
$application->persistentStorages()->delete();
|
$application->persistentStorages()->delete();
|
||||||
$application->environment_variables()->delete();
|
$application->environment_variables()->delete();
|
||||||
$application->environment_variables_preview()->delete();
|
$application->environment_variables_preview()->delete();
|
||||||
|
foreach ($application->scheduled_tasks as $task) {
|
||||||
|
$task->delete();
|
||||||
|
}
|
||||||
|
$application->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Build packs / deployment types
|
|
||||||
|
|
||||||
|
public function additional_servers()
|
||||||
public function servers(): Collection
|
|
||||||
{
|
{
|
||||||
$mainServer = data_get($this, 'destination.server');
|
return $this->belongsToMany(Server::class, 'additional_destinations')
|
||||||
$additionalDestinations = data_get($this, 'additional_destinations', null);
|
->withPivot('standalone_docker_id', 'status');
|
||||||
$additionalServers = collect([]);
|
|
||||||
if ($this->isMultipleServerDeployment()) {
|
|
||||||
ray('asd');
|
|
||||||
if (str($additionalDestinations)->isNotEmpty()) {
|
|
||||||
$additionalDestinations = str($additionalDestinations)->explode(',');
|
|
||||||
foreach ($additionalDestinations as $destinationId) {
|
|
||||||
$destination = StandaloneDocker::find($destinationId)->whereNot('id', $mainServer->id)->first();
|
|
||||||
$server = data_get($destination, 'server');
|
|
||||||
$additionalServers->push($server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return collect([$mainServer])->merge($additionalServers);
|
|
||||||
}
|
}
|
||||||
|
public function additional_networks()
|
||||||
public function generateImageNames(string $commit, int $pullRequestId)
|
|
||||||
{
|
{
|
||||||
if ($this->dockerfile) {
|
return $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')
|
||||||
if ($this->docker_registry_image_name) {
|
->withPivot('server_id', 'status');
|
||||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:build");
|
}
|
||||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:latest");
|
public function is_github_based(): bool
|
||||||
} else {
|
{
|
||||||
$buildImageName = Str::lower("{$this->uuid}:build");
|
if (data_get($this, 'source')) {
|
||||||
$productionImageName = Str::lower("{$this->uuid}:latest");
|
return true;
|
||||||
}
|
}
|
||||||
} else if ($this->build_pack === 'dockerimage') {
|
return false;
|
||||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$this->docker_registry_image_tag}");
|
|
||||||
} else if ($pullRequestId === 0) {
|
|
||||||
$dockerImageTag = str($commit)->substr(0, 128);
|
|
||||||
if ($this->docker_registry_image_name) {
|
|
||||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}-build");
|
|
||||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}");
|
|
||||||
} else {
|
|
||||||
$buildImageName = Str::lower("{$this->uuid}:{$dockerImageTag}-build");
|
|
||||||
$productionImageName = Str::lower("{$this->uuid}:{$dockerImageTag}");
|
|
||||||
}
|
|
||||||
} else if ($pullRequestId !== 0) {
|
|
||||||
if ($this->docker_registry_image_name) {
|
|
||||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}-build");
|
|
||||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}");
|
|
||||||
} else {
|
|
||||||
$buildImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}-build");
|
|
||||||
$productionImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
'buildImageName' => $buildImageName,
|
|
||||||
'productionImageName' => $productionImageName,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
// End of build packs / deployment types
|
|
||||||
|
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
if (data_get($this, 'environment.project.uuid')) {
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
@@ -242,9 +203,6 @@ class Application extends BaseModel
|
|||||||
set: fn ($value) => $value === "" ? null : $value,
|
set: fn ($value) => $value === "" ? null : $value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal Deployments
|
|
||||||
|
|
||||||
public function portsMappingsArray(): Attribute
|
public function portsMappingsArray(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -254,6 +212,79 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
public function realStatus()
|
||||||
|
{
|
||||||
|
return $this->getRawOriginal('status');
|
||||||
|
}
|
||||||
|
public function status(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
if ($this->additional_servers->count() === 0) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
} else {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get: function ($value) {
|
||||||
|
if ($this->additional_servers->count() === 0) {
|
||||||
|
//running (healthy)
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
} else {
|
||||||
|
$complex_status = null;
|
||||||
|
$complex_health = null;
|
||||||
|
$complex_status = $main_server_status = str($value)->before(':')->value();
|
||||||
|
$complex_health = $main_server_health = str($value)->after(':')->value() ?? 'unhealthy';
|
||||||
|
$additional_servers_status = $this->additional_servers->pluck('pivot.status');
|
||||||
|
foreach ($additional_servers_status as $status) {
|
||||||
|
$server_status = str($status)->before(':')->value();
|
||||||
|
$server_health = str($status)->after(':')->value() ?? 'unhealthy';
|
||||||
|
if ($server_status !== 'running') {
|
||||||
|
if ($main_server_status !== $server_status) {
|
||||||
|
$complex_status = 'degraded';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($server_health !== 'healthy') {
|
||||||
|
if ($main_server_health !== $server_health) {
|
||||||
|
$complex_health = 'unhealthy';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "$complex_status:$complex_health";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function portsExposesArray(): Attribute
|
public function portsExposesArray(): Attribute
|
||||||
{
|
{
|
||||||
@@ -263,6 +294,14 @@ class Application extends BaseModel
|
|||||||
: explode(',', $this->ports_exposes)
|
: explode(',', $this->ports_exposes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return data_get($this, 'environment.project');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
@@ -431,7 +470,7 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
return data_get($this, 'settings.is_log_drain_enabled', false);
|
return data_get($this, 'settings.is_log_drain_enabled', false);
|
||||||
}
|
}
|
||||||
public function isConfigurationChanged($save = false)
|
public function isConfigurationChanged(bool $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->dockerfile . $this->dockerfile_location . $this->custom_labels;
|
$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->dockerfile . $this->dockerfile_location . $this->custom_labels;
|
||||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||||
@@ -458,31 +497,6 @@ class Application extends BaseModel
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function isMultipleServerDeployment()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
if (data_get($this, 'additional_destinations') && data_get($this, 'docker_registry_image_name')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
public function healthCheckUrl()
|
|
||||||
{
|
|
||||||
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!$this->health_check_port) {
|
|
||||||
$health_check_port = $this->ports_exposes_array[0];
|
|
||||||
} else {
|
|
||||||
$health_check_port = $this->health_check_port;
|
|
||||||
}
|
|
||||||
if ($this->health_check_path) {
|
|
||||||
$full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
|
|
||||||
} else {
|
|
||||||
$full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
|
|
||||||
}
|
|
||||||
return $full_healthcheck_url;
|
|
||||||
}
|
|
||||||
function customRepository()
|
function customRepository()
|
||||||
{
|
{
|
||||||
preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
|
preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
|
||||||
@@ -504,295 +518,6 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
return "/artifacts/{$uuid}";
|
return "/artifacts/{$uuid}";
|
||||||
}
|
}
|
||||||
function generateHealthCheckCommands()
|
|
||||||
{
|
|
||||||
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->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';
|
|
||||||
}
|
|
||||||
if (!$this->health_check_port) {
|
|
||||||
$health_check_port = $this->ports_exposes_array[0];
|
|
||||||
} else {
|
|
||||||
$health_check_port = $this->health_check_port;
|
|
||||||
}
|
|
||||||
if ($this->health_check_path) {
|
|
||||||
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
|
|
||||||
$generated_healthchecks_commands = [
|
|
||||||
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path} > /dev/null"
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
|
|
||||||
$generated_healthchecks_commands = [
|
|
||||||
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return implode(' ', $generated_healthchecks_commands);
|
|
||||||
}
|
|
||||||
function generateLocalPersistentVolumes(int $pullRequestId)
|
|
||||||
{
|
|
||||||
$persistentStorages = [];
|
|
||||||
$volumeNames = [];
|
|
||||||
foreach ($this->persistentStorages as $persistentStorage) {
|
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$volume_name = $volume_name . '-pr-' . $pullRequestId;
|
|
||||||
}
|
|
||||||
$persistentStorages[] = $volume_name . ':' . $persistentStorage->mount_path;
|
|
||||||
|
|
||||||
if ($persistentStorage->host_path) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = $persistentStorage->name;
|
|
||||||
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$name = $name . '-pr-' . $pullRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$volumeNames[$name] = [
|
|
||||||
'name' => $name,
|
|
||||||
'external' => false,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'persistentStorages' => $persistentStorages,
|
|
||||||
'volumeNames' => $volumeNames,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
public function generateEnvironmentVariables($ports)
|
|
||||||
{
|
|
||||||
$environmentVariables = collect();
|
|
||||||
// ray('Generate Environment Variables')->green();
|
|
||||||
if ($this->pull_request_id === 0) {
|
|
||||||
// ray($this->runtime_environment_variables)->green();
|
|
||||||
foreach ($this->runtime_environment_variables as $env) {
|
|
||||||
$environmentVariables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
foreach ($this->nixpacks_environment_variables as $env) {
|
|
||||||
$environmentVariables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ray($this->runtime_environment_variables_preview)->green();
|
|
||||||
foreach ($this->runtime_environment_variables_preview as $env) {
|
|
||||||
$environmentVariables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
foreach ($this->nixpacks_environment_variables_preview as $env) {
|
|
||||||
$environmentVariables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add PORT if not exists, use the first port as default
|
|
||||||
if ($environmentVariables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
|
||||||
$environmentVariables->push("PORT={$ports[0]}");
|
|
||||||
}
|
|
||||||
return $environmentVariables->all();
|
|
||||||
}
|
|
||||||
function generateDockerComposeFile(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
|
|
||||||
{
|
|
||||||
$pullRequestId = $deployment->pull_request_id;
|
|
||||||
$ports = $this->settings->is_static ? [80] : $this->ports_exposes_array;
|
|
||||||
$container_name = generateApplicationContainerName($this, $this->pull_request_id);
|
|
||||||
$commit = str($deployment->getOutput('git_commit_sha'))->before("\t");
|
|
||||||
|
|
||||||
[
|
|
||||||
'productionImageName' => $productionImageName
|
|
||||||
] = $this->generateImageNames($commit, $pullRequestId);
|
|
||||||
|
|
||||||
[
|
|
||||||
'persistentStorages' => $persistentStorages,
|
|
||||||
'volumeNames' => $volumeNames
|
|
||||||
] = $this->generateLocalPersistentVolumes($pullRequestId);
|
|
||||||
|
|
||||||
$environmentVariables = $this->generateEnvironmentVariables($ports);
|
|
||||||
|
|
||||||
if (data_get($this, 'custom_labels')) {
|
|
||||||
$labels = collect(str($this->custom_labels)->explode(','));
|
|
||||||
$labels = $labels->filter(function ($value, $key) {
|
|
||||||
return !Str::startsWith($value, 'coolify.');
|
|
||||||
});
|
|
||||||
$this->custom_labels = $labels->implode(',');
|
|
||||||
$this->save();
|
|
||||||
} else {
|
|
||||||
$labels = collect(generateLabelsApplication($this, $this->preview));
|
|
||||||
}
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
$labels = collect(generateLabelsApplication($this, $this->preview));
|
|
||||||
}
|
|
||||||
$labels = $labels->merge(defaultLabels($this->id, $this->uuid, $this->pull_request_id))->toArray();
|
|
||||||
$docker_compose = [
|
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
|
||||||
$container_name => [
|
|
||||||
'image' => $productionImageName,
|
|
||||||
'container_name' => $container_name,
|
|
||||||
'restart' => RESTART_MODE,
|
|
||||||
'environment' => $environmentVariables,
|
|
||||||
'expose' => $ports,
|
|
||||||
'networks' => [
|
|
||||||
$this->destination->network,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
|
||||||
'test' => [
|
|
||||||
'CMD-SHELL',
|
|
||||||
$this->generateHealthCheckCommands()
|
|
||||||
],
|
|
||||||
'interval' => $this->health_check_interval . 's',
|
|
||||||
'timeout' => $this->health_check_timeout . 's',
|
|
||||||
'retries' => $this->health_check_retries,
|
|
||||||
'start_period' => $this->health_check_start_period . 's'
|
|
||||||
],
|
|
||||||
'mem_limit' => $this->limits_memory,
|
|
||||||
'memswap_limit' => $this->limits_memory_swap,
|
|
||||||
'mem_swappiness' => $this->limits_memory_swappiness,
|
|
||||||
'mem_reservation' => $this->limits_memory_reservation,
|
|
||||||
'cpus' => (float) $this->limits_cpus,
|
|
||||||
'cpu_shares' => $this->limits_cpu_shares,
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'networks' => [
|
|
||||||
$this->destination->network => [
|
|
||||||
'external' => true,
|
|
||||||
'name' => $this->destination->network,
|
|
||||||
'attachable' => true
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
if (!is_null($this->limits_cpuset)) {
|
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->limits_cpuset);
|
|
||||||
}
|
|
||||||
if ($server->isSwarm()) {
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.container_name');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.expose');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.restart');
|
|
||||||
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_limit');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.memswap_limit');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_swappiness');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_reservation');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.cpus');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.cpuset');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.cpu_shares');
|
|
||||||
|
|
||||||
$docker_compose['services'][$container_name]['deploy'] = [
|
|
||||||
'placement' => [
|
|
||||||
'constraints' => [
|
|
||||||
'node.role == worker'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'mode' => 'replicated',
|
|
||||||
'replicas' => 1,
|
|
||||||
'update_config' => [
|
|
||||||
'order' => 'start-first'
|
|
||||||
],
|
|
||||||
'rollback_config' => [
|
|
||||||
'order' => 'start-first'
|
|
||||||
],
|
|
||||||
'labels' => $labels,
|
|
||||||
'resources' => [
|
|
||||||
'limits' => [
|
|
||||||
'cpus' => $this->limits_cpus,
|
|
||||||
'memory' => $this->limits_memory,
|
|
||||||
],
|
|
||||||
'reservations' => [
|
|
||||||
'cpus' => $this->limits_cpus,
|
|
||||||
'memory' => $this->limits_memory,
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$docker_compose['services'][$container_name]['labels'] = $labels;
|
|
||||||
}
|
|
||||||
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
|
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if ($this->settings->is_gpu_enabled) {
|
|
||||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'] = [
|
|
||||||
[
|
|
||||||
'driver' => data_get($this, 'settings.gpu_driver', 'nvidia'),
|
|
||||||
'capabilities' => ['gpu'],
|
|
||||||
'options' => data_get($this, 'settings.gpu_options', [])
|
|
||||||
]
|
|
||||||
];
|
|
||||||
if (data_get($this, 'settings.gpu_count')) {
|
|
||||||
$count = data_get($this, 'settings.gpu_count');
|
|
||||||
if ($count === 'all') {
|
|
||||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
|
||||||
} else {
|
|
||||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
|
|
||||||
}
|
|
||||||
} else if (data_get($this, 'settings.gpu_device_ids')) {
|
|
||||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($this, 'settings.gpu_device_ids');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->isHealthcheckDisabled()) {
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.healthcheck');
|
|
||||||
}
|
|
||||||
if (count($this->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->ports_mappings_array;
|
|
||||||
}
|
|
||||||
if (count($persistentStorages) > 0) {
|
|
||||||
$docker_compose['services'][$container_name]['volumes'] = $persistentStorages;
|
|
||||||
}
|
|
||||||
if (count($volumeNames) > 0) {
|
|
||||||
$docker_compose['volumes'] = $volumeNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
$docker_compose['services'][$this->uuid] = $docker_compose['services'][$container_name];
|
|
||||||
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name);
|
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: collect([])->push([
|
|
||||||
'command' => executeInDocker($deployment->deployment_uuid, "echo '{$docker_compose_base64}' | base64 -d > {$workdir}/docker-compose.yml"),
|
|
||||||
'hidden' => true,
|
|
||||||
'ignoreErrors' => true
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function rollingUpdateApplication(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
|
|
||||||
{
|
|
||||||
$pullRequestId = $deployment->pull_request_id;
|
|
||||||
$containerName = generateApplicationContainerName($this, $pullRequestId);
|
|
||||||
// if (count($this->ports_mappings_array) > 0) {
|
|
||||||
// $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
|
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId);
|
|
||||||
// if ($pullRequestId === 0) {
|
|
||||||
// $containers = $containers->filter(function ($container) use ($containerName) {
|
|
||||||
// return data_get($container, 'Names') !== $containerName;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
$containers->each(function ($container) use ($server, $deployment) {
|
|
||||||
$removingContainerName = data_get($container, 'Names');
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: collect([])->push([
|
|
||||||
'command' => "docker rm -f $removingContainerName",
|
|
||||||
'hidden' => true,
|
|
||||||
'ignoreErrors' => true
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
});
|
|
||||||
// }
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: collect([])->push([
|
|
||||||
'command' => executeInDocker($deployment->deployment_uuid, "docker compose --project-directory {$workdir} up --build -d"),
|
|
||||||
'hidden' => true,
|
|
||||||
'ignoreErrors' => true
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
$deployment->addLogEntry("New container started.");
|
|
||||||
}
|
|
||||||
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
|
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
|
||||||
{
|
{
|
||||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||||
@@ -807,7 +532,7 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
return $git_clone_command;
|
return $git_clone_command;
|
||||||
}
|
}
|
||||||
function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null)
|
function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null)
|
||||||
{
|
{
|
||||||
$branch = $this->git_branch;
|
$branch = $this->git_branch;
|
||||||
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
|
['repository' => $customRepository, 'port' => $customPort] = $this->customRepository();
|
||||||
@@ -820,7 +545,6 @@ class Application extends BaseModel
|
|||||||
if ($pull_request_id !== 0) {
|
if ($pull_request_id !== 0) {
|
||||||
$pr_branch_name = "pr-{$pull_request_id}-coolify";
|
$pr_branch_name = "pr-{$pull_request_id}-coolify";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->deploymentType() === 'source') {
|
if ($this->deploymentType() === 'source') {
|
||||||
$source_html_url = data_get($this, 'source.html_url');
|
$source_html_url = data_get($this, 'source.html_url');
|
||||||
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
|
$url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL));
|
||||||
@@ -899,8 +623,7 @@ class Application extends BaseModel
|
|||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||||
}
|
} else if ($git_type === 'github') {
|
||||||
if ($git_type === 'github') {
|
|
||||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
@@ -908,6 +631,13 @@ class Application extends BaseModel
|
|||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||||
|
} else if ($git_type === 'bitbucket') {
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
|
} else {
|
||||||
|
$commands->push("echo 'Checking out $branch'");
|
||||||
|
}
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -926,6 +656,34 @@ class Application extends BaseModel
|
|||||||
$fullRepoUrl = $customRepository;
|
$fullRepoUrl = $customRepository;
|
||||||
$git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}";
|
$git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}";
|
||||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
|
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
|
||||||
|
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
if ($git_type === 'gitlab') {
|
||||||
|
$branch = "merge-requests/{$pull_request_id}/head:$pr_branch_name";
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
|
} else {
|
||||||
|
$commands->push("echo 'Checking out $branch'");
|
||||||
|
}
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||||
|
} else if ($git_type === 'github') {
|
||||||
|
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
|
} else {
|
||||||
|
$commands->push("echo 'Checking out $branch'");
|
||||||
|
}
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||||
|
} else if ($git_type === 'bitbucket') {
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
|
} else {
|
||||||
|
$commands->push("echo 'Checking out $branch'");
|
||||||
|
}
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||||
} else {
|
} else {
|
||||||
@@ -938,34 +696,6 @@ class Application extends BaseModel
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function prepareHelperImage(string $deploymentUuid)
|
|
||||||
{
|
|
||||||
$basedir = $this->generateBaseDir($deploymentUuid);
|
|
||||||
$helperImage = config('coolify.helper_image');
|
|
||||||
$server = data_get($this, 'destination.server');
|
|
||||||
$network = data_get($this, 'destination.network');
|
|
||||||
|
|
||||||
$serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server);
|
|
||||||
$dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server);
|
|
||||||
|
|
||||||
$commands = collect([]);
|
|
||||||
if ($dockerConfigFileExists === 'OK') {
|
|
||||||
$commands->push([
|
|
||||||
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$commands->push([
|
|
||||||
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$commands->push([
|
|
||||||
"command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
return $commands;
|
|
||||||
}
|
|
||||||
function parseCompose(int $pull_request_id = 0)
|
function parseCompose(int $pull_request_id = 0)
|
||||||
{
|
{
|
||||||
if ($this->docker_compose_raw) {
|
if ($this->docker_compose_raw) {
|
||||||
@@ -1076,4 +806,12 @@ class Application extends BaseModel
|
|||||||
$this->save();
|
$this->save();
|
||||||
return $customLabels;
|
return $customLabels;
|
||||||
}
|
}
|
||||||
|
public function fqdns(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->fqdn)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->fqdn),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ class Environment extends Model
|
|||||||
return $this->applications()->count() == 0 &&
|
return $this->applications()->count() == 0 &&
|
||||||
$this->redis()->count() == 0 &&
|
$this->redis()->count() == 0 &&
|
||||||
$this->postgresqls()->count() == 0 &&
|
$this->postgresqls()->count() == 0 &&
|
||||||
|
$this->mysqls()->count() == 0 &&
|
||||||
|
$this->mariadbs()->count() == 0 &&
|
||||||
$this->mongodbs()->count() == 0 &&
|
$this->mongodbs()->count() == 0 &&
|
||||||
$this->services()->count() == 0;
|
$this->services()->count() == 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class EnvironmentVariable extends Model
|
|||||||
}
|
}
|
||||||
private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
|
private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
|
||||||
{
|
{
|
||||||
if (!$environment_variable) {
|
if (!$environment_variable || !$resource) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$environment_variable = trim($environment_variable);
|
$environment_variable = trim($environment_variable);
|
||||||
@@ -100,6 +100,9 @@ class EnvironmentVariable extends Model
|
|||||||
$variable = Str::after($environment_variable, "{$type}.");
|
$variable = Str::after($environment_variable, "{$type}.");
|
||||||
$variable = Str::before($variable, '}}');
|
$variable = Str::before($variable, '}}');
|
||||||
$variable = Str::of($variable)->trim()->value;
|
$variable = Str::of($variable)->trim()->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
return $variable;
|
||||||
|
}
|
||||||
if ($type === 'environment') {
|
if ($type === 'environment') {
|
||||||
$id = $resource->environment->id;
|
$id = $resource->environment->id;
|
||||||
} else if ($type === 'project') {
|
} else if ($type === 'project') {
|
||||||
|
|||||||
@@ -10,20 +10,37 @@ class LocalFileVolume extends BaseModel
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::created(function (LocalFileVolume $fileVolume) {
|
||||||
|
$fileVolume->load(['service']);
|
||||||
|
$fileVolume->saveStorageOnServer();
|
||||||
|
});
|
||||||
|
}
|
||||||
public function service()
|
public function service()
|
||||||
{
|
{
|
||||||
return $this->morphTo('resource');
|
return $this->morphTo('resource');
|
||||||
}
|
}
|
||||||
public function saveStorageOnServer(ServiceApplication|ServiceDatabase $service)
|
public function saveStorageOnServer()
|
||||||
{
|
{
|
||||||
$workdir = $service->service->workdir();
|
$workdir = $this->resource->service->workdir();
|
||||||
$server = $service->service->server;
|
$server = $this->resource->service->server;
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
||||||
"cd $workdir"
|
"cd $workdir"
|
||||||
]);
|
]);
|
||||||
|
$is_directory = $this->is_directory;
|
||||||
|
if ($is_directory) {
|
||||||
|
$commands->push("mkdir -p $this->fs_path > /dev/null 2>&1 || true");
|
||||||
|
}
|
||||||
|
if (str($this->fs_path)->startsWith('.') || str($this->fs_path)->startsWith('/') || str($this->fs_path)->startsWith('~')) {
|
||||||
|
$parent_dir = str($this->fs_path)->beforeLast('/');
|
||||||
|
if ($parent_dir != '') {
|
||||||
|
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
|
||||||
|
}
|
||||||
|
}
|
||||||
$fileVolume = $this;
|
$fileVolume = $this;
|
||||||
$path = Str::of(data_get($fileVolume, 'fs_path'));
|
$path = str(data_get($fileVolume, 'fs_path'));
|
||||||
$content = data_get($fileVolume, 'content');
|
$content = data_get($fileVolume, 'content');
|
||||||
if ($path->startsWith('.')) {
|
if ($path->startsWith('.')) {
|
||||||
$path = $path->after('.');
|
$path = $path->after('.');
|
||||||
@@ -32,17 +49,18 @@ class LocalFileVolume extends BaseModel
|
|||||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||||
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
||||||
throw new \Exception("File $path is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
|
throw new \Exception("The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.");
|
||||||
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
|
} else if ($isDir == 'OK' && !$fileVolume->is_directory) {
|
||||||
throw new \Exception("File $path is a directory on the server, but you are trying to mark it as a file. Please delete the directory on the server or mark it as directory.");
|
throw new \Exception("The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.");
|
||||||
}
|
}
|
||||||
if (!$fileVolume->is_directory && $isDir == 'NOK') {
|
if (!$fileVolume->is_directory && $isDir == 'NOK') {
|
||||||
$content = base64_encode($content);
|
if ($content) {
|
||||||
$commands->push("echo '$content' | base64 -d > $path");
|
$content = base64_encode($content);
|
||||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
$commands->push("echo '$content' | base64 -d > $path");
|
||||||
|
}
|
||||||
|
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||||
}
|
}
|
||||||
ray($commands->toArray());
|
|
||||||
return instant_remote_process($commands, $server);
|
return instant_remote_process($commands, $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,13 @@ class Project extends BaseModel
|
|||||||
}
|
}
|
||||||
public function mysqls()
|
public function mysqls()
|
||||||
{
|
{
|
||||||
return $this->hasMany(StandaloneMysql::class, Environment::class);
|
return $this->hasManyThrough(StandaloneMysql::class, Environment::class);
|
||||||
}
|
}
|
||||||
public function mariadbs()
|
public function mariadbs()
|
||||||
{
|
{
|
||||||
return $this->hasMany(StandaloneMariadb::class, Environment::class);
|
return $this->hasManyThrough(StandaloneMariadb::class, Environment::class);
|
||||||
|
}
|
||||||
|
public function resource_count() {
|
||||||
|
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Actions\Server\InstallDocker;
|
||||||
use App\Enums\ProxyStatus;
|
use App\Enums\ProxyStatus;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Notifications\Server\Revived;
|
use App\Notifications\Server\Revived;
|
||||||
use App\Notifications\Server\Unreachable;
|
use App\Notifications\Server\Unreachable;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@@ -251,9 +249,17 @@ class Server extends BaseModel
|
|||||||
}
|
}
|
||||||
public function applications()
|
public function applications()
|
||||||
{
|
{
|
||||||
return $this->destinations()->map(function ($standaloneDocker) {
|
$applications = $this->destinations()->map(function ($standaloneDocker) {
|
||||||
return $standaloneDocker->applications;
|
return $standaloneDocker->applications;
|
||||||
})->flatten();
|
})->flatten();
|
||||||
|
$additionalApplicationIds = DB::table('additional_destinations')->where('server_id', $this->id)->get('application_id');
|
||||||
|
$additionalApplicationIds = collect($additionalApplicationIds)->map(function ($item) {
|
||||||
|
return $item->application_id;
|
||||||
|
});
|
||||||
|
Application::whereIn('id', $additionalApplicationIds)->get()->each(function ($application) use ($applications) {
|
||||||
|
$applications->push($application);
|
||||||
|
});
|
||||||
|
return $applications;
|
||||||
}
|
}
|
||||||
public function dockerComposeBasedApplications()
|
public function dockerComposeBasedApplications()
|
||||||
{
|
{
|
||||||
@@ -303,7 +309,8 @@ class Server extends BaseModel
|
|||||||
{
|
{
|
||||||
$standalone_docker = $this->hasMany(StandaloneDocker::class)->get();
|
$standalone_docker = $this->hasMany(StandaloneDocker::class)->get();
|
||||||
$swarm_docker = $this->hasMany(SwarmDocker::class)->get();
|
$swarm_docker = $this->hasMany(SwarmDocker::class)->get();
|
||||||
return $standalone_docker->concat($swarm_docker);
|
$asd = $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')->withPivot('server_id')->get();
|
||||||
|
return $standalone_docker->concat($swarm_docker)->concat($asd);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function standaloneDockers()
|
public function standaloneDockers()
|
||||||
@@ -335,20 +342,6 @@ class Server extends BaseModel
|
|||||||
if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
|
if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
public function isFunctional()
|
public function isFunctional()
|
||||||
@@ -429,6 +422,11 @@ class Server extends BaseModel
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public function installDocker()
|
||||||
|
{
|
||||||
|
$activity = InstallDocker::run($this);
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
public function validateDockerEngine($throwError = false)
|
public function validateDockerEngine($throwError = false)
|
||||||
{
|
{
|
||||||
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
|
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
|
||||||
@@ -445,6 +443,21 @@ class Server extends BaseModel
|
|||||||
$this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server);
|
$this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public function validateDockerCompose($throwError = false)
|
||||||
|
{
|
||||||
|
$dockerCompose = instant_remote_process(["docker compose version"], $this, false);
|
||||||
|
if (is_null($dockerCompose)) {
|
||||||
|
$this->settings->is_usable = false;
|
||||||
|
$this->settings->save();
|
||||||
|
if ($throwError) {
|
||||||
|
throw new \Exception('Server is not usable. Docker Compose is not installed.');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$this->settings->is_usable = true;
|
||||||
|
$this->settings->save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
public function validateDockerSwarm()
|
public function validateDockerSwarm()
|
||||||
{
|
{
|
||||||
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false);
|
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false);
|
||||||
@@ -483,153 +496,4 @@ class Server extends BaseModel
|
|||||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null)
|
|
||||||
{
|
|
||||||
static::$batch_counter++;
|
|
||||||
foreach ($commands as $command) {
|
|
||||||
$realCommand = data_get($command, 'command');
|
|
||||||
if (is_null($realCommand)) {
|
|
||||||
throw new \RuntimeException('Command is not set');
|
|
||||||
}
|
|
||||||
$hidden = data_get($command, 'hidden', false);
|
|
||||||
$ignoreErrors = data_get($command, 'ignoreErrors', false);
|
|
||||||
$customOutputType = data_get($command, 'customOutputType');
|
|
||||||
$name = data_get($command, 'name');
|
|
||||||
$remoteCommand = generateSshCommand($this, $realCommand);
|
|
||||||
|
|
||||||
$process = Process::timeout(3600)->idleTimeout(3600)->start($remoteCommand, function (string $type, string $output) use ($realCommand, $hidden, $customOutputType, $loggingModel, $name) {
|
|
||||||
$output = str($output)->trim();
|
|
||||||
if ($output->startsWith('╔')) {
|
|
||||||
$output = "\n" . $output;
|
|
||||||
}
|
|
||||||
$newLogEntry = [
|
|
||||||
'command' => remove_iip($realCommand),
|
|
||||||
'output' => remove_iip($output),
|
|
||||||
'type' => $customOutputType ?? $type === 'err' ? 'stderr' : 'stdout',
|
|
||||||
'timestamp' => Carbon::now('UTC'),
|
|
||||||
'hidden' => $hidden,
|
|
||||||
'batch' => static::$batch_counter,
|
|
||||||
];
|
|
||||||
if ($loggingModel) {
|
|
||||||
if (!$loggingModel->logs) {
|
|
||||||
$newLogEntry['order'] = 1;
|
|
||||||
} else {
|
|
||||||
$previousLogs = json_decode($loggingModel->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
|
||||||
$newLogEntry['order'] = count($previousLogs) + 1;
|
|
||||||
}
|
|
||||||
if ($name) {
|
|
||||||
$newLogEntry['name'] = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
$previousLogs[] = $newLogEntry;
|
|
||||||
$loggingModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR);
|
|
||||||
$loggingModel->save();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if ($loggingModel) {
|
|
||||||
$loggingModel->update([
|
|
||||||
'current_process_id' => $process->id(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$processResult = $process->wait();
|
|
||||||
if ($processResult->exitCode() !== 0) {
|
|
||||||
if (!$ignoreErrors) {
|
|
||||||
if ($loggingModel) {
|
|
||||||
$status = ApplicationDeploymentStatus::FAILED->value;
|
|
||||||
$loggingModel->status = $status;
|
|
||||||
$loggingModel->save();
|
|
||||||
}
|
|
||||||
throw new \RuntimeException($processResult->errorOutput());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function stopApplicationRelatedRunningContainers(string $applicationId, string $containerName)
|
|
||||||
{
|
|
||||||
$containers = getCurrentApplicationContainerStatus($this, $applicationId, 0);
|
|
||||||
$containers = $containers->filter(function ($container) use ($containerName) {
|
|
||||||
return data_get($container, 'Names') !== $containerName;
|
|
||||||
});
|
|
||||||
$containers->each(function ($container) {
|
|
||||||
$removableContainer = data_get($container, 'Names');
|
|
||||||
$this->server->executeRemoteCommand(
|
|
||||||
commands: collect([
|
|
||||||
'command' => "docker rm -f $removableContainer >/dev/null 2>&1",
|
|
||||||
'hidden' => true,
|
|
||||||
'ignoreErrors' => true
|
|
||||||
]),
|
|
||||||
loggingModel: $this->deploymentQueueEntry
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public function getHostIPMappings($network)
|
|
||||||
{
|
|
||||||
$addHosts = null;
|
|
||||||
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $this);
|
|
||||||
if (!is_null($allContainers)) {
|
|
||||||
$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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$addHosts = $ips->map(function ($ip, $name) {
|
|
||||||
return "--add-host $name:$ip";
|
|
||||||
})->implode(' ');
|
|
||||||
}
|
|
||||||
return $addHosts;
|
|
||||||
}
|
|
||||||
public function checkIfDockerImageExists(string $imageName, ApplicationDeploymentQueue $deployment)
|
|
||||||
{
|
|
||||||
$this->executeRemoteCommand(
|
|
||||||
commands: collect([
|
|
||||||
[
|
|
||||||
"name" => "local_image_found",
|
|
||||||
"command" => "docker images -q {$imageName} 2>/dev/null",
|
|
||||||
"hidden" => true,
|
|
||||||
]
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
if (str($deployment->getOutput('local_image_found'))->isEmpty()) {
|
|
||||||
$this->executeRemoteCommand(
|
|
||||||
commands: collect([
|
|
||||||
[
|
|
||||||
"command" => "docker pull {$imageName} 2>/dev/null",
|
|
||||||
"ignoreErrors" => true,
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"name" => "local_image_found",
|
|
||||||
"command" => "docker images -q {$imageName} 2>/dev/null",
|
|
||||||
"hidden" => true,
|
|
||||||
]
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function createWorkDirForDeployment(string $workdir, ApplicationDeploymentQueue $deployment)
|
|
||||||
{
|
|
||||||
$this->executeRemoteCommand(
|
|
||||||
commands: collect([
|
|
||||||
[
|
|
||||||
"command" => executeInDocker($deployment->deployment_uuid, "mkdir -p {$workdir}"),
|
|
||||||
"ignoreErrors" => true,
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Actions\Service\DeleteService;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
|
|
||||||
class Service extends BaseModel
|
class Service extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -18,10 +16,18 @@ class Service extends BaseModel
|
|||||||
{
|
{
|
||||||
return 'service';
|
return 'service';
|
||||||
}
|
}
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return data_get($this, 'environment.project');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
}
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function extraFields()
|
public function extraFields()
|
||||||
{
|
{
|
||||||
$fields = collect([]);
|
$fields = collect([]);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
|
|
||||||
class ServiceApplication extends BaseModel
|
class ServiceApplication extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -15,6 +14,7 @@ class ServiceApplication extends BaseModel
|
|||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::deleting(function ($service) {
|
static::deleting(function ($service) {
|
||||||
|
$service->update(['fqdn' => null]);
|
||||||
$service->persistentStorages()->delete();
|
$service->persistentStorages()->delete();
|
||||||
$service->fileStorages()->delete();
|
$service->fileStorages()->delete();
|
||||||
});
|
});
|
||||||
@@ -55,7 +55,6 @@ class ServiceApplication extends BaseModel
|
|||||||
get: fn () => is_null($this->fqdn)
|
get: fn () => is_null($this->fqdn)
|
||||||
? []
|
? []
|
||||||
: explode(',', $this->fqdn),
|
: explode(',', $this->fqdn),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public function getFilesFromServer(bool $isInit = false)
|
public function getFilesFromServer(bool $isInit = false)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
|||||||
|
|
||||||
class StandaloneMariadb extends BaseModel
|
class StandaloneMariadb extends BaseModel
|
||||||
{
|
{
|
||||||
use HasFactory,SoftDeletes;
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
@@ -40,8 +40,48 @@ class StandaloneMariadb extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public function realStatus()
|
||||||
|
{
|
||||||
|
return $this->getRawOriginal('status');
|
||||||
|
}
|
||||||
|
public function status(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
get: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
|||||||
@@ -43,8 +43,48 @@ class StandaloneMongodb extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public function realStatus()
|
||||||
|
{
|
||||||
|
return $this->getRawOriginal('status');
|
||||||
|
}
|
||||||
|
public function status(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
get: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
|||||||
@@ -40,8 +40,48 @@ class StandaloneMysql extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public function realStatus()
|
||||||
|
{
|
||||||
|
return $this->getRawOriginal('status');
|
||||||
|
}
|
||||||
|
public function status(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
get: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
|||||||
@@ -40,8 +40,48 @@ class StandalonePostgresql extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public function realStatus()
|
||||||
|
{
|
||||||
|
return $this->getRawOriginal('status');
|
||||||
|
}
|
||||||
|
public function status(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
get: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
if (data_get($this, 'environment.project.uuid')) {
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
|
|||||||
@@ -35,8 +35,48 @@ class StandaloneRedis extends BaseModel
|
|||||||
}
|
}
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
public function realStatus()
|
||||||
|
{
|
||||||
|
return $this->getRawOriginal('status');
|
||||||
|
}
|
||||||
|
public function status(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
set: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
get: function ($value) {
|
||||||
|
if (str($value)->contains('(')) {
|
||||||
|
$status = str($value)->before('(')->trim()->value();
|
||||||
|
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else if (str($value)->contains(':')) {
|
||||||
|
$status = str($value)->before(':')->trim()->value();
|
||||||
|
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||||
|
} else {
|
||||||
|
$status = $value;
|
||||||
|
$health = 'unhealthy';
|
||||||
|
}
|
||||||
|
return "$status:$health";
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
|||||||
31
app/Models/Tag.php
Normal file
31
app/Models/Tag.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
|
||||||
|
class Tag extends BaseModel
|
||||||
|
{
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
|
||||||
|
public function name(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn ($value) => strtolower($value),
|
||||||
|
set: fn ($value) => strtolower($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
static public function ownedByCurrentTeam()
|
||||||
|
{
|
||||||
|
return Tag::whereTeamId(currentTeam()->id)->orderBy('name');
|
||||||
|
}
|
||||||
|
public function applications()
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Application::class, 'taggable');
|
||||||
|
}
|
||||||
|
public function services()
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Service::class, 'taggable');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,7 +38,7 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
||||||
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
||||||
}
|
}
|
||||||
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue
|
|||||||
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
||||||
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
||||||
}
|
}
|
||||||
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
$this->deployment_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class StatusChanged extends Notification implements ShouldQueue
|
|||||||
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
if (Str::of($this->fqdn)->explode(',')->count() > 1) {
|
||||||
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
$this->fqdn = Str::of($this->fqdn)->explode(',')->first();
|
||||||
}
|
}
|
||||||
$this->resource_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->resource->uuid}";
|
$this->resource_url = base_url() . "/project/{$this->project_uuid}/" . urlencode($this->environment_name) . "/application/{$this->resource->uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Traits;
|
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
|
|
||||||
trait ExecuteRemoteCommandNew
|
|
||||||
{
|
|
||||||
public static $batch_counter = 0;
|
|
||||||
public function executeRemoteCommand(Server $server, $logModel, $commands)
|
|
||||||
{
|
|
||||||
static::$batch_counter++;
|
|
||||||
if ($commands instanceof Collection) {
|
|
||||||
$commandsText = $commands;
|
|
||||||
} else {
|
|
||||||
$commandsText = collect($commands);
|
|
||||||
}
|
|
||||||
$commandsText->each(function ($singleCommand) use ($server, $logModel) {
|
|
||||||
$command = data_get($singleCommand, 'command') ?? $singleCommand[0] ?? null;
|
|
||||||
if ($command === null) {
|
|
||||||
throw new \RuntimeException('Command is not set');
|
|
||||||
}
|
|
||||||
$hidden = data_get($singleCommand, 'hidden', false);
|
|
||||||
$customType = data_get($singleCommand, 'type');
|
|
||||||
$ignoreErrors = data_get($singleCommand, 'ignore_errors', false);
|
|
||||||
$save = data_get($singleCommand, 'save');
|
|
||||||
|
|
||||||
$remote_command = generateSshCommand($server, $command);
|
|
||||||
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $logModel, $save) {
|
|
||||||
$output = str($output)->trim();
|
|
||||||
if ($output->startsWith('╔')) {
|
|
||||||
$output = "\n" . $output;
|
|
||||||
}
|
|
||||||
$newLogEntry = [
|
|
||||||
'command' => remove_iip($command),
|
|
||||||
'output' => remove_iip($output),
|
|
||||||
'type' => $customType ?? $type === 'err' ? 'stderr' : 'stdout',
|
|
||||||
'timestamp' => Carbon::now('UTC'),
|
|
||||||
'hidden' => $hidden,
|
|
||||||
'batch' => static::$batch_counter,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!$logModel->logs) {
|
|
||||||
$newLogEntry['order'] = 1;
|
|
||||||
} else {
|
|
||||||
$previousLogs = json_decode($logModel->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
|
||||||
$newLogEntry['order'] = count($previousLogs) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$previousLogs[] = $newLogEntry;
|
|
||||||
$logModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR);
|
|
||||||
$logModel->save();
|
|
||||||
|
|
||||||
if ($save) {
|
|
||||||
$this->remoteCommandOutputs[$save] = str($output)->trim();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$logModel->update([
|
|
||||||
'current_process_id' => $process->id(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$processResult = $process->wait();
|
|
||||||
if ($processResult->exitCode() !== 0) {
|
|
||||||
if (!$ignoreErrors) {
|
|
||||||
$status = ApplicationDeploymentStatus::FAILED->value;
|
|
||||||
$logModel->status = $status;
|
|
||||||
$logModel->save();
|
|
||||||
throw new \RuntimeException($processResult->errorOutput());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,37 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\ApplicationDeploymentJob;
|
use App\Jobs\ApplicationDeploymentJob;
|
||||||
use App\Jobs\ApplicationDeploymentNewJob;
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\ApplicationPreview;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use App\Models\StandaloneDocker;
|
||||||
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
function queue_application_deployment(int $application_id, int $server_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $is_new_deployment = false)
|
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null)
|
||||||
{
|
{
|
||||||
$server = Application::find($application_id)->destination->server;
|
$application_id = $application->id;
|
||||||
|
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
|
||||||
|
$deployment_url = $deployment_link->getPath();
|
||||||
|
$server_id = $application->destination->server->id;
|
||||||
|
$server_name = $application->destination->server->name;
|
||||||
|
$destination_id = $application->destination->id;
|
||||||
|
|
||||||
|
if ($server) {
|
||||||
|
$server_id = $server->id;
|
||||||
|
$server_name = $server->name;
|
||||||
|
}
|
||||||
|
if ($destination) {
|
||||||
|
$destination_id = $destination->id;
|
||||||
|
}
|
||||||
$deployment = ApplicationDeploymentQueue::create([
|
$deployment = ApplicationDeploymentQueue::create([
|
||||||
'application_id' => $application_id,
|
'application_id' => $application_id,
|
||||||
|
'application_name' => $application->name,
|
||||||
'server_id' => $server_id,
|
'server_id' => $server_id,
|
||||||
|
'server_name' => $server_name,
|
||||||
|
'destination_id' => $destination_id,
|
||||||
'deployment_uuid' => $deployment_uuid,
|
'deployment_uuid' => $deployment_uuid,
|
||||||
|
'deployment_url' => $deployment_url,
|
||||||
'pull_request_id' => $pull_request_id,
|
'pull_request_id' => $pull_request_id,
|
||||||
'force_rebuild' => $force_rebuild,
|
'force_rebuild' => $force_rebuild,
|
||||||
'is_webhook' => $is_webhook,
|
'is_webhook' => $is_webhook,
|
||||||
@@ -22,317 +39,63 @@ function queue_application_deployment(int $application_id, int $server_id, strin
|
|||||||
'commit' => $commit,
|
'commit' => $commit,
|
||||||
'git_type' => $git_type
|
'git_type' => $git_type
|
||||||
]);
|
]);
|
||||||
$deployments_per_server = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get();
|
|
||||||
|
|
||||||
$deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('server_id', $server_id);
|
if ($no_questions_asked) {
|
||||||
$queued_deployments = $deployments->where('status', 'queued')->get()->sortByDesc('created_at');
|
$deployment->update([
|
||||||
$running_deployments = $deployments->where('status', 'in_progress')->get()->sortByDesc('created_at');
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
|
]);
|
||||||
ray("serverId:{$server->id}", "concurrentBuilds:{$server->settings->concurrent_builds}", "deployments:{$deployments_per_server->count()}", "queued:{$queued_deployments->count()}", "running:{$running_deployments->count()}");
|
dispatch(new ApplicationDeploymentJob(
|
||||||
// ray('Q:' . $queued_deployments->count() . 'R:' . $running_deployments->count() . '| Queuing deployment: ' . $deployment_uuid . ' of applicationID: ' . $application_id . ' pull request: ' . $pull_request_id . ' with commit: ' . $commit . ' and is it forced: ' . $force_rebuild);
|
application_deployment_queue_id: $deployment->id,
|
||||||
|
|
||||||
if ($queued_deployments->count() > 1) {
|
|
||||||
$queued_deployments = $queued_deployments->skip(1);
|
|
||||||
$queued_deployments->each(function ($queued_deployment, $key) {
|
|
||||||
$queued_deployment->status = 'cancelled by system';
|
|
||||||
$queued_deployment->save();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if ($running_deployments->count() > 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($deployments_per_server->count() > $server->settings->concurrent_builds) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($is_new_deployment) {
|
|
||||||
dispatch(new ApplicationDeploymentNewJob(
|
|
||||||
deployment: $deployment,
|
|
||||||
application: Application::find($application_id)
|
|
||||||
));
|
));
|
||||||
} else {
|
} else if (next_queuable($server_id, $application_id)) {
|
||||||
|
$deployment->update([
|
||||||
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
|
]);
|
||||||
dispatch(new ApplicationDeploymentJob(
|
dispatch(new ApplicationDeploymentJob(
|
||||||
application_deployment_queue_id: $deployment->id,
|
application_deployment_queue_id: $deployment->id,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function force_start_deployment(ApplicationDeploymentQueue $deployment)
|
||||||
function queue_next_deployment(Application $application, bool $isNew = false)
|
{
|
||||||
|
$deployment->update([
|
||||||
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
|
]);
|
||||||
|
dispatch(new ApplicationDeploymentJob(
|
||||||
|
application_deployment_queue_id: $deployment->id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
function queue_next_deployment(Application $application)
|
||||||
{
|
{
|
||||||
$server_id = $application->destination->server_id;
|
$server_id = $application->destination->server_id;
|
||||||
$next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();;
|
$next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();
|
||||||
// $next_found = ApplicationDeploymentQueue::where('status', 'queued')->get()->sortBy('created_at')->first();
|
|
||||||
ray($next_found, $server_id);
|
|
||||||
if ($next_found) {
|
if ($next_found) {
|
||||||
if ($isNew) {
|
$next_found->update([
|
||||||
dispatch(new ApplicationDeploymentNewJob(
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
deployment: $next_found,
|
|
||||||
application: $application
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
dispatch(new ApplicationDeploymentJob(
|
|
||||||
application_deployment_queue_id: $next_found->id,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Deployment things
|
|
||||||
function generateHostIpMapping(Server $server, string $network)
|
|
||||||
{
|
|
||||||
// Generate custom host<->ip hostnames
|
|
||||||
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $ips->map(function ($ip, $name) {
|
|
||||||
return "--add-host $name:$ip";
|
|
||||||
})->implode(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateBaseDir(string $deplyomentUuid)
|
|
||||||
{
|
|
||||||
return "/artifacts/$deplyomentUuid";
|
|
||||||
}
|
|
||||||
function generateWorkdir(string $deplyomentUuid, Application $application)
|
|
||||||
{
|
|
||||||
return generateBaseDir($deplyomentUuid) . rtrim($application->base_directory, '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareHelperContainer(Server $server, string $network, string $deploymentUuid)
|
|
||||||
{
|
|
||||||
$basedir = generateBaseDir($deploymentUuid);
|
|
||||||
$helperImage = config('coolify.helper_image');
|
|
||||||
|
|
||||||
$serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server);
|
|
||||||
$dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server);
|
|
||||||
|
|
||||||
$commands = collect([]);
|
|
||||||
if ($dockerConfigFileExists === 'OK') {
|
|
||||||
$commands->push([
|
|
||||||
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$commands->push([
|
|
||||||
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
]);
|
||||||
|
dispatch(new ApplicationDeploymentJob(
|
||||||
|
application_deployment_queue_id: $next_found->id,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
$commands->push([
|
|
||||||
"command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
return $commands;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateComposeFile(string $deploymentUuid, Server $server, string $network, Application $application, string $containerName, string $imageName, ?ApplicationPreview $preview = null, int $pullRequestId = 0)
|
function next_queuable(string $server_id, string $application_id): bool
|
||||||
{
|
{
|
||||||
$ports = $application->settings->is_static ? [80] : $application->ports_exposes_array;
|
$deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get()->sortByDesc('created_at');
|
||||||
$workDir = generateWorkdir($deploymentUuid, $application);
|
$same_application_deployments = $deployments->where('application_id', $application_id);
|
||||||
$persistent_storages = generateLocalPersistentVolumes($application, $pullRequestId);
|
$in_progress = $same_application_deployments->filter(function ($value, $key) {
|
||||||
$volume_names = generateLocalPersistentVolumesOnlyVolumeNames($application, $pullRequestId);
|
return $value->status === 'in_progress';
|
||||||
$environment_variables = generateEnvironmentVariables($application, $ports, $pullRequestId);
|
});
|
||||||
|
if ($in_progress->count() > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$server = Server::find($server_id);
|
||||||
|
$concurrent_builds = $server->settings->concurrent_builds;
|
||||||
|
|
||||||
if (data_get($application, 'custom_labels')) {
|
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}");
|
||||||
$labels = collect(str($application->custom_labels)->explode(','));
|
|
||||||
$labels = $labels->filter(function ($value, $key) {
|
|
||||||
return !str($value)->startsWith('coolify.');
|
|
||||||
});
|
|
||||||
$application->custom_labels = $labels->implode(',');
|
|
||||||
$application->save();
|
|
||||||
} else {
|
|
||||||
$labels = collect(generateLabelsApplication($application, $preview));
|
|
||||||
}
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$labels = collect(generateLabelsApplication($application, $preview));
|
|
||||||
}
|
|
||||||
$labels = $labels->merge(defaultLabels($application->id, $application->uuid, 0))->toArray();
|
|
||||||
$docker_compose = [
|
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
|
||||||
$containerName => [
|
|
||||||
'image' => $imageName,
|
|
||||||
'container_name' => $containerName,
|
|
||||||
'restart' => RESTART_MODE,
|
|
||||||
'environment' => $environment_variables,
|
|
||||||
'labels' => $labels,
|
|
||||||
'expose' => $ports,
|
|
||||||
'networks' => [
|
|
||||||
$network,
|
|
||||||
],
|
|
||||||
'mem_limit' => $application->limits_memory,
|
|
||||||
'memswap_limit' => $application->limits_memory_swap,
|
|
||||||
'mem_swappiness' => $application->limits_memory_swappiness,
|
|
||||||
'mem_reservation' => $application->limits_memory_reservation,
|
|
||||||
'cpus' => (int) $application->limits_cpus,
|
|
||||||
'cpu_shares' => $application->limits_cpu_shares,
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'networks' => [
|
|
||||||
$network => [
|
|
||||||
'external' => true,
|
|
||||||
'name' => $network,
|
|
||||||
'attachable' => true
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
if (!is_null($application->limits_cpuset)) {
|
|
||||||
data_set($docker_compose, "services.{$containerName}.cpuset", $application->limits_cpuset);
|
|
||||||
}
|
|
||||||
if ($server->isLogDrainEnabled() && $application->isLogDrainEnabled()) {
|
|
||||||
$docker_compose['services'][$containerName]['logging'] = [
|
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if ($application->settings->is_gpu_enabled) {
|
|
||||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'] = [
|
|
||||||
[
|
|
||||||
'driver' => data_get($application, 'settings.gpu_driver', 'nvidia'),
|
|
||||||
'capabilities' => ['gpu'],
|
|
||||||
'options' => data_get($application, 'settings.gpu_options', [])
|
|
||||||
]
|
|
||||||
];
|
|
||||||
if (data_get($application, 'settings.gpu_count')) {
|
|
||||||
$count = data_get($application, 'settings.gpu_count');
|
|
||||||
if ($count === 'all') {
|
|
||||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
|
||||||
} else {
|
|
||||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
|
|
||||||
}
|
|
||||||
} else if (data_get($application, 'settings.gpu_device_ids')) {
|
|
||||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($application, 'settings.gpu_device_ids');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($application->isHealthcheckDisabled()) {
|
|
||||||
data_forget($docker_compose, 'services.' . $containerName . '.healthcheck');
|
|
||||||
}
|
|
||||||
if (count($application->ports_mappings_array) > 0 && $pullRequestId === 0) {
|
|
||||||
$docker_compose['services'][$containerName]['ports'] = $application->ports_mappings_array;
|
|
||||||
}
|
|
||||||
if (count($persistent_storages) > 0) {
|
|
||||||
$docker_compose['services'][$containerName]['volumes'] = $persistent_storages;
|
|
||||||
}
|
|
||||||
if (count($volume_names) > 0) {
|
|
||||||
$docker_compose['volumes'] = $volume_names;
|
|
||||||
}
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
|
||||||
$commands = collect([]);
|
|
||||||
$commands->push([
|
|
||||||
"command" => executeInDocker($deploymentUuid, "echo '{$docker_compose_base64}' | base64 -d > {$workDir}/docker-compose.yml"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
return $commands;
|
|
||||||
}
|
|
||||||
function generateLocalPersistentVolumes(Application $application, int $pullRequestId = 0)
|
|
||||||
{
|
|
||||||
$local_persistent_volumes = [];
|
|
||||||
foreach ($application->persistentStorages as $persistentStorage) {
|
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$volume_name = $volume_name . '-pr-' . $pullRequestId;
|
|
||||||
}
|
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
|
||||||
}
|
|
||||||
return $local_persistent_volumes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateLocalPersistentVolumesOnlyVolumeNames(Application $application, int $pullRequestId = 0)
|
if ($deployments->count() > $concurrent_builds) {
|
||||||
{
|
return false;
|
||||||
$local_persistent_volumes_names = [];
|
|
||||||
foreach ($application->persistentStorages as $persistentStorage) {
|
|
||||||
if ($persistentStorage->host_path) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$name = $persistentStorage->name;
|
|
||||||
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$name = $name . '-pr-' . $pullRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$local_persistent_volumes_names[$name] = [
|
|
||||||
'name' => $name,
|
|
||||||
'external' => false,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
return $local_persistent_volumes_names;
|
return true;
|
||||||
}
|
|
||||||
function generateEnvironmentVariables(Application $application, $ports, int $pullRequestId = 0)
|
|
||||||
{
|
|
||||||
$environment_variables = collect();
|
|
||||||
// ray('Generate Environment Variables')->green();
|
|
||||||
if ($pullRequestId === 0) {
|
|
||||||
// ray($this->application->runtime_environment_variables)->green();
|
|
||||||
foreach ($application->runtime_environment_variables as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
foreach ($application->nixpacks_environment_variables as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ray($this->application->runtime_environment_variables_preview)->green();
|
|
||||||
foreach ($application->runtime_environment_variables_preview as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
foreach ($application->nixpacks_environment_variables_preview as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add PORT if not exists, use the first port as default
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('PORT'))->isEmpty()) {
|
|
||||||
$environment_variables->push("PORT={$ports[0]}");
|
|
||||||
}
|
|
||||||
return $environment_variables->all();
|
|
||||||
}
|
|
||||||
|
|
||||||
function startNewApplication(Application $application, string $deploymentUuid, ApplicationDeploymentQueue $loggingModel)
|
|
||||||
{
|
|
||||||
$commands = collect([]);
|
|
||||||
$workDir = generateWorkdir($deploymentUuid, $application);
|
|
||||||
if ($application->build_pack === 'dockerimage') {
|
|
||||||
$loggingModel->addLogEntry('Pulling latest images from the registry.');
|
|
||||||
$commands->push(
|
|
||||||
[
|
|
||||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} pull"),
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"),
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$commands->push(
|
|
||||||
[
|
|
||||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"),
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $commands;
|
|
||||||
}
|
|
||||||
function removeOldDeployment(string $containerName)
|
|
||||||
{
|
|
||||||
$commands = collect([]);
|
|
||||||
$commands->push(
|
|
||||||
["docker rm -f $containerName >/dev/null 2>&1"],
|
|
||||||
);
|
|
||||||
return $commands;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ const VALID_CRON_STRINGS = [
|
|||||||
const RESTART_MODE = 'unless-stopped';
|
const RESTART_MODE = 'unless-stopped';
|
||||||
|
|
||||||
const DATABASE_DOCKER_IMAGES = [
|
const DATABASE_DOCKER_IMAGES = [
|
||||||
|
'bitnami/mariadb',
|
||||||
|
'bitnami/mongodb',
|
||||||
|
'bitnami/mysql',
|
||||||
|
'bitnami/postgresql',
|
||||||
|
'bitnami/redis',
|
||||||
'mysql',
|
'mysql',
|
||||||
'mariadb',
|
'mariadb',
|
||||||
'postgres',
|
'postgres',
|
||||||
@@ -34,3 +39,5 @@ const SUPPORTED_OS = [
|
|||||||
'centos fedora rhel ol rocky',
|
'centos fedora rhel ol rocky',
|
||||||
'sles opensuse-leap opensuse-tumbleweed'
|
'sles opensuse-leap opensuse-tumbleweed'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
@@ -125,10 +123,14 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
|||||||
|
|
||||||
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
||||||
{
|
{
|
||||||
|
$consistent_container_name = $application->settings->is_consistent_container_name_enabled;
|
||||||
$now = now()->format('Hisu');
|
$now = now()->format('Hisu');
|
||||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||||
return $application->uuid . '-pr-' . $pull_request_id;
|
return $application->uuid . '-pr-' . $pull_request_id;
|
||||||
} else {
|
} else {
|
||||||
|
if ($consistent_container_name) {
|
||||||
|
return $application->uuid;
|
||||||
|
}
|
||||||
return $application->uuid . '-' . $now;
|
return $application->uuid . '-' . $now;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,15 +213,48 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
|
|||||||
}
|
}
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null)
|
function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_https_enabled = false, $onlyPort = null, ?Collection $serviceLabels = null)
|
||||||
{
|
{
|
||||||
$labels = collect([]);
|
$labels = collect([]);
|
||||||
$labels->push('traefik.enable=true');
|
$labels->push('traefik.enable=true');
|
||||||
$labels->push("traefik.http.middlewares.gzip.compress=true");
|
$labels->push("traefik.http.middlewares.gzip.compress=true");
|
||||||
$labels->push("traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https");
|
$labels->push("traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https");
|
||||||
|
|
||||||
|
$basic_auth = false;
|
||||||
|
$basic_auth_middleware = null;
|
||||||
|
$redirect = false;
|
||||||
|
$redirect_middleware = null;
|
||||||
|
if ($serviceLabels) {
|
||||||
|
$basic_auth = $serviceLabels->contains(function ($value) {
|
||||||
|
return str_contains($value, 'basicauth');
|
||||||
|
});
|
||||||
|
if ($basic_auth) {
|
||||||
|
$basic_auth_middleware = $serviceLabels
|
||||||
|
->map(function ($item) {
|
||||||
|
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.basicauth\.users/', $item, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->filter()
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
$redirect = $serviceLabels->contains(function ($value) {
|
||||||
|
return str_contains($value, 'redirectregex');
|
||||||
|
});
|
||||||
|
if ($redirect) {
|
||||||
|
$redirect_middleware = $serviceLabels
|
||||||
|
->map(function ($item) {
|
||||||
|
if (preg_match('/traefik\.http\.middlewares\.(.*?)\.redirectregex\.regex/', $item, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->filter()
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
foreach ($domains as $loop => $domain) {
|
foreach ($domains as $loop => $domain) {
|
||||||
try {
|
try {
|
||||||
$uuid = new Cuid2(7);
|
// $uuid = new Cuid2(7);
|
||||||
$url = Url::fromString($domain);
|
$url = Url::fromString($domain);
|
||||||
$host = $url->getHost();
|
$host = $url->getHost();
|
||||||
$path = $url->getPath();
|
$path = $url->getPath();
|
||||||
@@ -241,11 +276,24 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||||||
}
|
}
|
||||||
if ($path !== '/') {
|
if ($path !== '/') {
|
||||||
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||||
$labels->push("traefik.http.routers.{$https_label}.middlewares={$https_label}-stripprefix,gzip");
|
$middlewares = "gzip,{$https_label}-stripprefix";
|
||||||
|
if ($basic_auth && $basic_auth_middleware) {
|
||||||
|
$middlewares = $middlewares . ',' . $basic_auth_middleware;
|
||||||
|
}
|
||||||
|
if ($redirect && $redirect_middleware) {
|
||||||
|
$middlewares = $middlewares . ',' . $redirect_middleware;
|
||||||
|
}
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
|
||||||
} else {
|
} else {
|
||||||
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
|
$middlewares = "gzip";
|
||||||
|
if ($basic_auth && $basic_auth_middleware) {
|
||||||
|
$middlewares = $middlewares . ',' . $basic_auth_middleware;
|
||||||
|
}
|
||||||
|
if ($redirect && $redirect_middleware) {
|
||||||
|
$middlewares = $middlewares . ',' . $redirect_middleware;
|
||||||
|
}
|
||||||
|
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
|
||||||
}
|
}
|
||||||
|
|
||||||
$labels->push("traefik.http.routers.{$https_label}.tls=true");
|
$labels->push("traefik.http.routers.{$https_label}.tls=true");
|
||||||
$labels->push("traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt");
|
$labels->push("traefik.http.routers.{$https_label}.tls.certresolver=letsencrypt");
|
||||||
|
|
||||||
@@ -269,16 +317,29 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||||||
}
|
}
|
||||||
if ($path !== '/') {
|
if ($path !== '/') {
|
||||||
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
|
$labels->push("traefik.http.middlewares.{$http_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||||
$labels->push("traefik.http.routers.{$http_label}.middlewares={$http_label}-stripprefix,gzip");
|
$middlewares = "gzip,{$http_label}-stripprefix";
|
||||||
|
if ($basic_auth && $basic_auth_middleware) {
|
||||||
|
$middlewares = $middlewares . ',' . $basic_auth_middleware;
|
||||||
|
}
|
||||||
|
if ($redirect && $redirect_middleware) {
|
||||||
|
$middlewares = $middlewares . ',' . $redirect_middleware;
|
||||||
|
}
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
|
||||||
} else {
|
} else {
|
||||||
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
|
$middlewares = "gzip";
|
||||||
|
if ($basic_auth && $basic_auth_middleware) {
|
||||||
|
$middlewares = $middlewares . ',' . $basic_auth_middleware;
|
||||||
|
}
|
||||||
|
if ($redirect && $redirect_middleware) {
|
||||||
|
$middlewares = $middlewares . ',' . $redirect_middleware;
|
||||||
|
}
|
||||||
|
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $labels->sort();
|
return $labels->sort();
|
||||||
}
|
}
|
||||||
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
|
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
|
||||||
@@ -323,3 +384,83 @@ function isDatabaseImage(?string $image = null)
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convert_docker_run_to_compose(?string $custom_docker_run_options = null)
|
||||||
|
{
|
||||||
|
$options = [];
|
||||||
|
$compose_options = collect([]);
|
||||||
|
preg_match_all('/(--\w+(?:-\w+)*)(?:\s|=)?([^\s-]+)?/', $custom_docker_run_options, $matches, PREG_SET_ORDER);
|
||||||
|
$list_options = collect([
|
||||||
|
'--cap-add',
|
||||||
|
'--cap-drop',
|
||||||
|
'--security-opt',
|
||||||
|
'--sysctl',
|
||||||
|
'--ulimit',
|
||||||
|
'--device'
|
||||||
|
]);
|
||||||
|
$mapping = collect([
|
||||||
|
'--cap-add' => 'cap_add',
|
||||||
|
'--cap-drop' => 'cap_drop',
|
||||||
|
'--security-opt' => 'security_opt',
|
||||||
|
'--sysctl' => 'sysctls',
|
||||||
|
'--ulimit' => 'ulimits',
|
||||||
|
'--device' => 'devices',
|
||||||
|
'--init' => 'init',
|
||||||
|
'--ulimit' => 'ulimits',
|
||||||
|
'--privileged' => 'privileged',
|
||||||
|
]);
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$option = $match[1];
|
||||||
|
if (isset($match[2]) && $match[2] !== '') {
|
||||||
|
$value = $match[2];
|
||||||
|
$options[$option][] = $value;
|
||||||
|
$options[$option] = array_unique($options[$option]);
|
||||||
|
} else {
|
||||||
|
$value = true;
|
||||||
|
$options[$option] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$options = collect($options);
|
||||||
|
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
||||||
|
foreach ($options as $option => $value) {
|
||||||
|
if (!data_get($mapping, $option)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($option === '--ulimit') {
|
||||||
|
$ulimits = collect([]);
|
||||||
|
collect($value)->map(function ($ulimit) use ($ulimits) {
|
||||||
|
$ulimit = explode('=', $ulimit);
|
||||||
|
$type = $ulimit[0];
|
||||||
|
$limits = explode(':', $ulimit[1]);
|
||||||
|
if (count($limits) == 2) {
|
||||||
|
$soft_limit = $limits[0];
|
||||||
|
$hard_limit = $limits[1];
|
||||||
|
$ulimits->put($type, [
|
||||||
|
'soft' => $soft_limit,
|
||||||
|
'hard' => $hard_limit
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$soft_limit = $ulimit[1];
|
||||||
|
$ulimits->put($type, [
|
||||||
|
'soft' => $soft_limit,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$compose_options->put($mapping[$option], $ulimits);
|
||||||
|
} else {
|
||||||
|
if ($list_options->contains($option)) {
|
||||||
|
if ($compose_options->has($mapping[$option])) {
|
||||||
|
$compose_options->put($mapping[$option], $options->get($mapping[$option]) . ',' . $value);
|
||||||
|
} else {
|
||||||
|
$compose_options->put($mapping[$option], $value);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$compose_options->put($mapping[$option], $value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$compose_options->forget($option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $compose_options->toArray();
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function generate_github_installation_token(GithubApp $source)
|
|||||||
'Accept' => 'application/vnd.github.machine-man-preview+json'
|
'Accept' => 'application/vnd.github.machine-man-preview+json'
|
||||||
])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
|
])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
|
||||||
if ($token->failed()) {
|
if ($token->failed()) {
|
||||||
throw new \Exception("Failed to get access token for " . $source->name . " with error: " . $token->json()['message']);
|
throw new RuntimeException("Failed to get access token for " . $source->name . " with error: " . $token->json()['message']);
|
||||||
}
|
}
|
||||||
return $token->json()['token'];
|
return $token->json()['token'];
|
||||||
}
|
}
|
||||||
@@ -69,6 +69,7 @@ function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $m
|
|||||||
}
|
}
|
||||||
$json = $response->json();
|
$json = $response->json();
|
||||||
if ($response->failed() && $throwError) {
|
if ($response->failed() && $throwError) {
|
||||||
|
ray($json);
|
||||||
throw new \Exception("Failed to get data from {$source->name} with error:<br><br>" . $json['message'] . "<br><br>Rate Limit resets at: " . Carbon::parse((int)$response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s') . 'UTC');
|
throw new \Exception("Failed to get data from {$source->name} with error:<br><br>" . $json['message'] . "<br><br>Rate Limit resets at: " . Carbon::parse((int)$response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s') . 'UTC');
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use App\Notifications\Channels\EmailChannel;
|
|||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\Internal\GeneralNotification;
|
use App\Notifications\Internal\GeneralNotification;
|
||||||
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||||
|
use Illuminate\Database\UniqueConstraintViolationException;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
@@ -103,10 +104,16 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
|||||||
ray($error);
|
ray($error);
|
||||||
if ($error instanceof TooManyRequestsException) {
|
if ($error instanceof TooManyRequestsException) {
|
||||||
if (isset($livewire)) {
|
if (isset($livewire)) {
|
||||||
return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
|
return $livewire->dispatch('error', "Too many requests.", "Please try again in {$error->secondsUntilAvailable} seconds.");
|
||||||
}
|
}
|
||||||
return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.";
|
return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.";
|
||||||
}
|
}
|
||||||
|
if ($error instanceof UniqueConstraintViolationException) {
|
||||||
|
if (isset($livewire)) {
|
||||||
|
return $livewire->dispatch('error', "Duplicate entry found.", "Please use a different name.");
|
||||||
|
}
|
||||||
|
return "Duplicate entry found. Please use a different name.";
|
||||||
|
}
|
||||||
|
|
||||||
if ($error instanceof Throwable) {
|
if ($error instanceof Throwable) {
|
||||||
$message = $error->getMessage();
|
$message = $error->getMessage();
|
||||||
@@ -118,6 +125,9 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($livewire)) {
|
if (isset($livewire)) {
|
||||||
|
if (str($message)->length() > 20) {
|
||||||
|
return $livewire->dispatch('error', 'Error occured', $message);
|
||||||
|
}
|
||||||
return $livewire->dispatch('error', $message);
|
return $livewire->dispatch('error', $message);
|
||||||
}
|
}
|
||||||
throw new Exception($message);
|
throw new Exception($message);
|
||||||
@@ -291,10 +301,8 @@ function validate_cron_expression($expression_to_validate): bool
|
|||||||
function send_internal_notification(string $message): void
|
function send_internal_notification(string $message): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$baseUrl = config('app.name');
|
|
||||||
$team = Team::find(0);
|
$team = Team::find(0);
|
||||||
$team?->notify(new GeneralNotification("👀 {$baseUrl}: " . $message));
|
$team?->notify(new GeneralNotification($message));
|
||||||
ray("👀 {$baseUrl}: " . $message);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
}
|
}
|
||||||
@@ -474,7 +482,14 @@ function queryResourcesByUuid(string $uuid)
|
|||||||
if ($mariadb) return $mariadb;
|
if ($mariadb) return $mariadb;
|
||||||
return $resource;
|
return $resource;
|
||||||
}
|
}
|
||||||
|
function generatTagDeployWebhook($tag_name)
|
||||||
|
{
|
||||||
|
$baseUrl = base_url();
|
||||||
|
$api = Url::fromString($baseUrl) . '/api/v1';
|
||||||
|
$endpoint = "/deploy?tag=$tag_name";
|
||||||
|
$url = $api . $endpoint;
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
function generateDeployWebhook($resource)
|
function generateDeployWebhook($resource)
|
||||||
{
|
{
|
||||||
$baseUrl = base_url();
|
$baseUrl = base_url();
|
||||||
@@ -515,28 +530,32 @@ function getTopLevelNetworks(Service|Application $resource)
|
|||||||
$definedNetwork = collect([$resource->uuid]);
|
$definedNetwork = collect([$resource->uuid]);
|
||||||
$services = collect($services)->map(function ($service, $_) use ($topLevelNetworks, $definedNetwork) {
|
$services = collect($services)->map(function ($service, $_) use ($topLevelNetworks, $definedNetwork) {
|
||||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||||
|
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
|
||||||
|
|
||||||
// Collect/create/update networks
|
// Only add 'networks' key if 'network_mode' is not 'host'
|
||||||
if ($serviceNetworks->count() > 0) {
|
if (!$hasHostNetworkMode) {
|
||||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
// Collect/create/update networks
|
||||||
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
if ($serviceNetworks->count() > 0) {
|
||||||
return $value == $networkName || $key == $networkName;
|
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||||
});
|
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
||||||
if (!$networkExists) {
|
return $value == $networkName || $key == $networkName;
|
||||||
$topLevelNetworks->put($networkDetails, null);
|
});
|
||||||
|
if (!$networkExists) {
|
||||||
|
$topLevelNetworks->put($networkDetails, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||||
return $value == $definedNetwork;
|
return $value == $definedNetwork;
|
||||||
});
|
});
|
||||||
if (!$definedNetworkExists) {
|
if (!$definedNetworkExists) {
|
||||||
foreach ($definedNetwork as $network) {
|
foreach ($definedNetwork as $network) {
|
||||||
$topLevelNetworks->put($network, [
|
$topLevelNetworks->put($network, [
|
||||||
'name' => $network,
|
'name' => $network,
|
||||||
'external' => true
|
'external' => true
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,6 +635,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||||
$serviceVariables = collect(data_get($service, 'environment', []));
|
$serviceVariables = collect(data_get($service, 'environment', []));
|
||||||
$serviceLabels = collect(data_get($service, 'labels', []));
|
$serviceLabels = collect(data_get($service, 'labels', []));
|
||||||
|
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
|
||||||
if ($serviceLabels->count() > 0) {
|
if ($serviceLabels->count() > 0) {
|
||||||
$removedLabels = collect([]);
|
$removedLabels = collect([]);
|
||||||
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
|
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
|
||||||
@@ -686,7 +706,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$savedService->image = $image;
|
$savedService->image = $image;
|
||||||
$savedService->save();
|
$savedService->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect/create/update networks
|
// Collect/create/update networks
|
||||||
if ($serviceNetworks->count() > 0) {
|
if ($serviceNetworks->count() > 0) {
|
||||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||||
@@ -717,37 +736,39 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$savedService->ports = $collectedPorts->implode(',');
|
$savedService->ports = $collectedPorts->implode(',');
|
||||||
$savedService->save();
|
$savedService->save();
|
||||||
|
|
||||||
// Add Coolify specific networks
|
if (!$hasHostNetworkMode) {
|
||||||
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
// Add Coolify specific networks
|
||||||
return $value == $definedNetwork;
|
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||||
});
|
return $value == $definedNetwork;
|
||||||
if (!$definedNetworkExists) {
|
});
|
||||||
foreach ($definedNetwork as $network) {
|
if (!$definedNetworkExists) {
|
||||||
$topLevelNetworks->put($network, [
|
foreach ($definedNetwork as $network) {
|
||||||
'name' => $network,
|
$topLevelNetworks->put($network, [
|
||||||
'external' => true
|
'name' => $network,
|
||||||
]);
|
'external' => true
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
$networks = collect();
|
||||||
$networks = collect();
|
foreach ($serviceNetworks as $key => $serviceNetwork) {
|
||||||
foreach ($serviceNetworks as $key => $serviceNetwork) {
|
if (gettype($serviceNetwork) === 'string') {
|
||||||
if (gettype($serviceNetwork) === 'string') {
|
// networks:
|
||||||
// networks:
|
// - appwrite
|
||||||
// - appwrite
|
$networks->put($serviceNetwork, null);
|
||||||
$networks->put($serviceNetwork, null);
|
} else if (gettype($serviceNetwork) === 'array') {
|
||||||
} else if (gettype($serviceNetwork) === 'array') {
|
// networks:
|
||||||
// networks:
|
// default:
|
||||||
// default:
|
// ipv4_address: 192.168.203.254
|
||||||
// ipv4_address: 192.168.203.254
|
// $networks->put($serviceNetwork, null);
|
||||||
// $networks->put($serviceNetwork, null);
|
ray($key);
|
||||||
ray($key);
|
$networks->put($key, $serviceNetwork);
|
||||||
$networks->put($key, $serviceNetwork);
|
}
|
||||||
}
|
}
|
||||||
|
foreach ($definedNetwork as $key => $network) {
|
||||||
|
$networks->put($network, null);
|
||||||
|
}
|
||||||
|
data_set($service, 'networks', $networks->toArray());
|
||||||
}
|
}
|
||||||
foreach ($definedNetwork as $key => $network) {
|
|
||||||
$networks->put($network, null);
|
|
||||||
}
|
|
||||||
data_set($service, 'networks', $networks->toArray());
|
|
||||||
|
|
||||||
// Collect/create/update volumes
|
// Collect/create/update volumes
|
||||||
if ($serviceVolumes->count() > 0) {
|
if ($serviceVolumes->count() > 0) {
|
||||||
@@ -770,14 +791,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$source = data_get_str($volume, 'source');
|
$source = data_get_str($volume, 'source');
|
||||||
$target = data_get_str($volume, 'target');
|
$target = data_get_str($volume, 'target');
|
||||||
$content = data_get($volume, 'content');
|
$content = data_get($volume, 'content');
|
||||||
$isDirectory = (bool) data_get($volume, 'isDirectory', false);
|
$isDirectory = (bool) data_get($volume, 'isDirectory', false) || (bool) data_get($volume, 'is_directory', false);
|
||||||
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
|
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
|
||||||
if ($foundConfig) {
|
if ($foundConfig) {
|
||||||
$contentNotNull = data_get($foundConfig, 'content');
|
$contentNotNull = data_get($foundConfig, 'content');
|
||||||
if ($contentNotNull) {
|
if ($contentNotNull) {
|
||||||
$content = $contentNotNull;
|
$content = $contentNotNull;
|
||||||
}
|
}
|
||||||
$isDirectory = (bool) data_get($foundConfig, 'is_directory');
|
$isDirectory = (bool) data_get($volume, 'isDirectory', false) || (bool) data_get($volume, 'is_directory', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($type->value() === 'bind') {
|
if ($type->value() === 'bind') {
|
||||||
@@ -930,7 +951,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
'service_id' => $resource->id,
|
'service_id' => $resource->id,
|
||||||
])->first();
|
])->first();
|
||||||
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
||||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
|
||||||
if (Str::lower($forService) === $serviceName) {
|
if (Str::lower($forService) === $serviceName) {
|
||||||
$fqdn = generateFqdn($resource->server, $containerName);
|
$fqdn = generateFqdn($resource->server, $containerName);
|
||||||
} else {
|
} else {
|
||||||
@@ -1018,7 +1039,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||||
if (!$isDatabase && $fqdns->count() > 0) {
|
if (!$isDatabase && $fqdns->count() > 0) {
|
||||||
if ($fqdns) {
|
if ($fqdns) {
|
||||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true));
|
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true, serviceLabels: $serviceLabels));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
|
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
|
||||||
@@ -1042,6 +1063,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
data_set($service, 'container_name', $containerName);
|
data_set($service, 'container_name', $containerName);
|
||||||
data_forget($service, 'volumes.*.content');
|
data_forget($service, 'volumes.*.content');
|
||||||
data_forget($service, 'volumes.*.isDirectory');
|
data_forget($service, 'volumes.*.isDirectory');
|
||||||
|
data_forget($service, 'volumes.*.is_directory');
|
||||||
|
|
||||||
// Remove unnecessary variables from service.environment
|
// Remove unnecessary variables from service.environment
|
||||||
// $withoutServiceEnvs = collect([]);
|
// $withoutServiceEnvs = collect([]);
|
||||||
// collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
|
// collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
|
||||||
@@ -1350,7 +1373,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
'application_id' => $resource->id,
|
'application_id' => $resource->id,
|
||||||
])->first();
|
])->first();
|
||||||
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
||||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
|
||||||
if (Str::lower($forService) === $serviceName) {
|
if (Str::lower($forService) === $serviceName) {
|
||||||
$fqdn = generateFqdn($server, $containerName);
|
$fqdn = generateFqdn($server, $containerName);
|
||||||
} else {
|
} else {
|
||||||
@@ -1457,7 +1480,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
return $preview_fqdn;
|
return $preview_fqdn;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns));
|
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns,serviceLabels: $serviceLabels));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1660,3 +1683,42 @@ function ip_match($ip, $cidrs, &$match = null)
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
function check_fqdn_usage(ServiceApplication|Application $own_resource)
|
||||||
|
{
|
||||||
|
$domains = collect($own_resource->fqdns)->map(function ($domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
return str($domain)->replace('http://', '')->replace('https://', '');
|
||||||
|
});
|
||||||
|
$apps = Application::all();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
|
foreach ($list_of_domains as $domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
$naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value();
|
||||||
|
if ($domains->contains($naked_domain)) {
|
||||||
|
if ($app->uuid !== $own_resource->uuid) {
|
||||||
|
throw new \RuntimeException("Domain $naked_domain is already in use by another resource:<br> {$app->name}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$apps = ServiceApplication::all();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
|
foreach ($list_of_domains as $domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
$naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value();
|
||||||
|
if ($domains->contains($naked_domain)) {
|
||||||
|
if ($app->uuid !== $own_resource->uuid) {
|
||||||
|
throw new \RuntimeException("Domain $naked_domain is already in use by another resource.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1111
composer.lock
generated
1111
composer.lock
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user