mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 20:49:28 +00:00
Compare commits
154 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a07fa8ccd2 | ||
|
|
c77f32e696 | ||
|
|
4391771416 | ||
|
|
01ab820459 | ||
|
|
c7218f2856 | ||
|
|
592221b4bf | ||
|
|
836458ad85 | ||
|
|
7233c86f3d | ||
|
|
154b1b05e4 | ||
|
|
4d88638d4d | ||
|
|
9403986643 | ||
|
|
ad48610b2f | ||
|
|
63487cf3ec | ||
|
|
4ae2087c2e | ||
|
|
5179129a6b | ||
|
|
6a00d8c88c | ||
|
|
50f43f9396 | ||
|
|
6f205f8931 | ||
|
|
3776ffa49b | ||
|
|
318b5beac1 | ||
|
|
344c5d6d12 | ||
|
|
4d319a8caa | ||
|
|
74b24a0690 | ||
|
|
1ca0464957 | ||
|
|
f7ebc8a88c | ||
|
|
a102099ac1 | ||
|
|
59ac22aa05 | ||
|
|
cd7244b3d7 | ||
|
|
f81b676abe | ||
|
|
234b154053 | ||
|
|
a1d09ad574 | ||
|
|
b983b23e7e | ||
|
|
0b81e77a94 | ||
|
|
b8cf314bfe | ||
|
|
4a3338e59c | ||
|
|
5b8538c0f4 | ||
|
|
88d6320d08 | ||
|
|
651c9c2c9b | ||
|
|
8dd45cd388 | ||
|
|
126ac354d5 | ||
|
|
024769c402 | ||
|
|
5acf141669 | ||
|
|
92e3e8ab7b | ||
|
|
187a29c666 | ||
|
|
4c24631795 | ||
|
|
7d6bd10cca | ||
|
|
f8c86769a7 | ||
|
|
e0b0dda382 | ||
|
|
b8708f086e | ||
|
|
3539e4dce9 | ||
|
|
5fdadcf557 | ||
|
|
acb3f01f79 | ||
|
|
83becdb19d | ||
|
|
e3e8fe7895 | ||
|
|
6ddff8fae1 | ||
|
|
f5cb2dbdcf | ||
|
|
fe19769d82 | ||
|
|
45e404b15b | ||
|
|
5bdaa68368 | ||
|
|
d903a377bf | ||
|
|
8e7745f4c1 | ||
|
|
a9ea6330d9 | ||
|
|
bfb0260550 | ||
|
|
bba1cb3832 | ||
|
|
29ad2144b7 | ||
|
|
38d367e709 | ||
|
|
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 | ||
|
|
43eb2fb00b | ||
|
|
9a899deeb8 | ||
|
|
9e1a7d5d9a | ||
|
|
5bdbab7276 | ||
|
|
13bceb934f | ||
|
|
78b194cb16 | ||
|
|
3616fc8ca9 | ||
|
|
10e307f92b | ||
|
|
01f027ac1b | ||
|
|
f357f40fc7 |
21
README.md
21
README.md
@@ -10,6 +10,17 @@ No vendor lock-in, which means that all the configuration for your applications/
|
||||
|
||||
For more information, take a look at our landing page [here](https://coolify.io).
|
||||
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
You can find the installation script source [here](./scripts/install.sh).
|
||||
|
||||
# Support
|
||||
|
||||
Contact us [here](https://coolify.io/docs/contact).
|
||||
|
||||
# Donations
|
||||
To stay completely free, open-source, no feature behind paywall and evolve the project, we need your help. If you like Coolify, please consider donating to help us fund the future development of the project.
|
||||
|
||||
@@ -66,16 +77,6 @@ By subscribing to the cloud version, you get the Coolify server for the same pri
|
||||
- Better support
|
||||
- Less maintenance for you
|
||||
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash
|
||||
```
|
||||
You can find the installation script source [here](./scripts/install.sh).
|
||||
|
||||
# Support
|
||||
|
||||
Contact us [here](https://coolify.io/docs/contact).
|
||||
|
||||
# Recognitions
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Notifications\Application\StatusChanged;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StopApplication
|
||||
@@ -10,13 +12,20 @@ class StopApplication
|
||||
use AsAction;
|
||||
public function handle(Application $application)
|
||||
{
|
||||
$server = $application->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
if ($application->destination->server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
||||
return;
|
||||
}
|
||||
if ($server->isSwarm()) {
|
||||
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||
} 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);
|
||||
if ($containers->count() > 0) {
|
||||
foreach ($containers as $container) {
|
||||
@@ -28,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,6 @@ class StartMariadb
|
||||
$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 up -d";
|
||||
$database_name = addslashes($database->name);
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -122,7 +122,6 @@ class StartMongodb
|
||||
$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 up -d";
|
||||
$database_name = addslashes($database->name);
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ class StartMysql
|
||||
$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 up -d";
|
||||
$database_name = addslashes($database->name);
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server,callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -128,7 +128,6 @@ class StartPostgresql
|
||||
$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 up -d";
|
||||
$database_name = addslashes($database->name);
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -117,7 +117,6 @@ class StartRedis
|
||||
$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 up -d";
|
||||
$database_name = addslashes($database->name);
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
@@ -128,9 +128,9 @@ class InstallLogDrain
|
||||
if ($type !== 'custom') {
|
||||
$parsers = base64_encode("
|
||||
[PARSER]
|
||||
Name empty_line_skipper
|
||||
Format regex
|
||||
Regex /^(?!\s*$).+/
|
||||
Name empty_line_skipper
|
||||
Format regex
|
||||
Regex /^(?!\s*$).+/
|
||||
");
|
||||
}
|
||||
$compose = base64_encode("
|
||||
@@ -198,7 +198,7 @@ Files:
|
||||
}
|
||||
$restart_command = [
|
||||
"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'",
|
||||
"cd $config_path && docker compose up -d --remove-orphans",
|
||||
];
|
||||
|
||||
@@ -25,7 +25,6 @@ class UpdateCoolify
|
||||
CleanupDocker::run($this->server, false);
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
ray('latest version:' . $this->latestVersion . " current version: " . $this->currentVersion . ' force: ' . $force);
|
||||
if ($settings->next_channel) {
|
||||
ray('next channel enabled');
|
||||
$this->latestVersion = 'next';
|
||||
@@ -44,7 +43,7 @@ class UpdateCoolify
|
||||
}
|
||||
$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) {
|
||||
ray('InstanceAutoUpdateJob failed');
|
||||
ray($e->getMessage());
|
||||
|
||||
@@ -45,6 +45,9 @@ class DeleteService
|
||||
foreach ($service->databases()->get() as $database) {
|
||||
$database->forceDelete();
|
||||
}
|
||||
foreach ($service->scheduled_tasks as $task) {
|
||||
$task->delete();
|
||||
}
|
||||
$service->tags()->detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,6 @@ class StartService
|
||||
{
|
||||
ray('Starting service: ' . $service->name);
|
||||
$service->saveComposeConfigs();
|
||||
|
||||
$service_name = addslashes($service->name);
|
||||
$server_name = addslashes($service->server->name);
|
||||
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||
$commands[] = "echo 'Creating Docker network.'";
|
||||
|
||||
@@ -10,24 +10,31 @@ class StopService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$server = $service->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
try {
|
||||
$server = $service->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
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();
|
||||
}
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ScheduledTask;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
@@ -20,7 +21,8 @@ class CleanupStuckedResources extends Command
|
||||
|
||||
public function handle()
|
||||
{
|
||||
echo "Running cleanup stucked...\n";
|
||||
ray('Running cleanup stucked resources.');
|
||||
echo "Running cleanup stucked resources.\n";
|
||||
$this->cleanup_stucked_resources();
|
||||
}
|
||||
private function cleanup_stucked_resources()
|
||||
@@ -107,24 +109,35 @@ class CleanupStuckedResources extends Command
|
||||
} 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 . ' soft deleting\n';
|
||||
$application->delete();
|
||||
echo 'Application without environment: ' . $application->name . '\n';
|
||||
$application->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$application->destination()) {
|
||||
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
echo 'Application without destination: ' . $application->name . '\n';
|
||||
$application->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($application, 'destination.server')) {
|
||||
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
||||
$application->delete();
|
||||
echo 'Application without server: ' . $application->name . '\n';
|
||||
$application->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -135,18 +148,18 @@ class CleanupStuckedResources extends Command
|
||||
$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();
|
||||
echo 'Postgresql without environment: ' . $postgresql->name . '\n';
|
||||
$postgresql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$postgresql->destination()) {
|
||||
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
echo 'Postgresql without destination: ' . $postgresql->name . '\n';
|
||||
$postgresql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($postgresql, 'destination.server')) {
|
||||
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
||||
$postgresql->delete();
|
||||
echo 'Postgresql without server: ' . $postgresql->name . '\n';
|
||||
$postgresql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -157,18 +170,18 @@ class CleanupStuckedResources extends Command
|
||||
$redis = StandaloneRedis::all();
|
||||
foreach ($redis as $redis) {
|
||||
if (!data_get($redis, 'environment')) {
|
||||
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
echo 'Redis without environment: ' . $redis->name . '\n';
|
||||
$redis->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$redis->destination()) {
|
||||
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
echo 'Redis without destination: ' . $redis->name . '\n';
|
||||
$redis->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($redis, 'destination.server')) {
|
||||
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
||||
$redis->delete();
|
||||
echo 'Redis without server: ' . $redis->name . '\n';
|
||||
$redis->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -180,18 +193,18 @@ class CleanupStuckedResources extends Command
|
||||
$mongodbs = StandaloneMongodb::all();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
if (!data_get($mongodb, 'environment')) {
|
||||
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
echo 'Mongodb without environment: ' . $mongodb->name . '\n';
|
||||
$mongodb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$mongodb->destination()) {
|
||||
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
echo 'Mongodb without destination: ' . $mongodb->name . '\n';
|
||||
$mongodb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mongodb, 'destination.server')) {
|
||||
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
||||
$mongodb->delete();
|
||||
echo 'Mongodb without server: ' . $mongodb->name . '\n';
|
||||
$mongodb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -203,18 +216,18 @@ class CleanupStuckedResources extends Command
|
||||
$mysqls = StandaloneMysql::all();
|
||||
foreach ($mysqls as $mysql) {
|
||||
if (!data_get($mysql, 'environment')) {
|
||||
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
echo 'Mysql without environment: ' . $mysql->name . '\n';
|
||||
$mysql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$mysql->destination()) {
|
||||
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
echo 'Mysql without destination: ' . $mysql->name . '\n';
|
||||
$mysql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mysql, 'destination.server')) {
|
||||
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
||||
$mysql->delete();
|
||||
echo 'Mysql without server: ' . $mysql->name . '\n';
|
||||
$mysql->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -226,18 +239,18 @@ class CleanupStuckedResources extends Command
|
||||
$mariadbs = StandaloneMariadb::all();
|
||||
foreach ($mariadbs as $mariadb) {
|
||||
if (!data_get($mariadb, 'environment')) {
|
||||
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
echo 'Mariadb without environment: ' . $mariadb->name . '\n';
|
||||
$mariadb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$mariadb->destination()) {
|
||||
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
echo 'Mariadb without destination: ' . $mariadb->name . '\n';
|
||||
$mariadb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($mariadb, 'destination.server')) {
|
||||
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
||||
$mariadb->delete();
|
||||
echo 'Mariadb without server: ' . $mariadb->name . '\n';
|
||||
$mariadb->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -249,18 +262,18 @@ class CleanupStuckedResources extends Command
|
||||
$services = Service::all();
|
||||
foreach ($services as $service) {
|
||||
if (!data_get($service, 'environment')) {
|
||||
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
echo 'Service without environment: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!$service->destination()) {
|
||||
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
echo 'Service without destination: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
if (!data_get($service, 'server')) {
|
||||
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
echo 'Service without server: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -271,8 +284,8 @@ class CleanupStuckedResources extends Command
|
||||
$serviceApplications = ServiceApplication::all();
|
||||
foreach ($serviceApplications as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
echo 'ServiceApplication without service: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -283,8 +296,8 @@ class CleanupStuckedResources extends Command
|
||||
$serviceDatabases = ServiceDatabase::all();
|
||||
foreach ($serviceDatabases as $service) {
|
||||
if (!data_get($service, 'service')) {
|
||||
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
||||
$service->delete();
|
||||
echo 'ServiceDatabase without service: ' . $service->name . '\n';
|
||||
$service->forceDelete();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,39 +14,44 @@ use Illuminate\Support\Facades\Http;
|
||||
|
||||
class Init extends Command
|
||||
{
|
||||
protected $signature = 'app:init {--cleanup}';
|
||||
protected $signature = 'app:init {--full-cleanup} {--cleanup-deployments}';
|
||||
protected $description = 'Cleanup instance related stuffs';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->alive();
|
||||
$cleanup = $this->option('cleanup');
|
||||
if ($cleanup) {
|
||||
echo "Running cleanups...\n";
|
||||
$this->call('cleanup:stucked-resources');
|
||||
$full_cleanup = $this->option('full-cleanup');
|
||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||
if ($cleanup_deployments) {
|
||||
echo "Running cleanup deployments.\n";
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
return;
|
||||
}
|
||||
if ($full_cleanup) {
|
||||
// Required for falsely deleted coolify db
|
||||
$this->restore_coolify_db_backup();
|
||||
|
||||
// $this->cleanup_ssh();
|
||||
}
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
|
||||
try {
|
||||
setup_dynamic_configuration();
|
||||
} 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]);
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
$this->call('cleanup:queue');
|
||||
$this->call('cleanup:stucked-resources');
|
||||
try {
|
||||
setup_dynamic_configuration();
|
||||
} 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]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->call('cleanup:queue');
|
||||
$this->cleanup_stucked_helper_containers();
|
||||
$this->call('cleanup:stucked-resources');
|
||||
}
|
||||
private function restore_coolify_db_backup()
|
||||
{
|
||||
@@ -120,8 +125,13 @@ class Init extends Command
|
||||
// Cleanup any failed deployments
|
||||
|
||||
try {
|
||||
$halted_deployments = ApplicationDeploymentQueue::where('status', '==', ApplicationDeploymentStatus::IN_PROGRESS)->where('status', '==', ApplicationDeploymentStatus::QUEUED)->get();
|
||||
foreach ($halted_deployments as $deployment) {
|
||||
if (isCloud()) {
|
||||
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->save();
|
||||
}
|
||||
@@ -129,5 +139,4 @@ class Init extends Command
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -73,6 +73,18 @@ class ServicesGenerate extends Command
|
||||
} else {
|
||||
$slogan = str($file)->headline()->value();
|
||||
}
|
||||
$logo = collect(preg_grep('/^# logo:/', explode("\n", $content)))->values();
|
||||
if ($logo->count() > 0) {
|
||||
$logo = str($logo[0])->after('# logo:')->trim()->value();
|
||||
} else {
|
||||
$logo = 'svgs/unknown.svg';
|
||||
}
|
||||
$minversion = collect(preg_grep('/^# minversion:/', explode("\n", $content)))->values();
|
||||
if ($minversion->count() > 0) {
|
||||
$minversion = str($minversion[0])->after('# minversion:')->trim()->value();
|
||||
} else {
|
||||
$minversion = '0.0.0';
|
||||
}
|
||||
$env_file = collect(preg_grep('/^# env_file:/', explode("\n", $content)))->values();
|
||||
if ($env_file->count() > 0) {
|
||||
$env_file = str($env_file[0])->after('# env_file:')->trim()->value();
|
||||
@@ -96,6 +108,8 @@ class ServicesGenerate extends Command
|
||||
'slogan' => $slogan,
|
||||
'compose' => $yaml,
|
||||
'tags' => $tags,
|
||||
'logo' => $logo,
|
||||
'minversion' => $minversion,
|
||||
];
|
||||
if ($env_file) {
|
||||
$env_file_content = file_get_contents(base_path("templates/compose/$env_file"));
|
||||
|
||||
@@ -91,7 +91,6 @@ class Kernel extends ConsoleKernel
|
||||
{
|
||||
$scheduled_backups = ScheduledDatabaseBackup::all();
|
||||
if ($scheduled_backups->isEmpty()) {
|
||||
ray('no scheduled backups');
|
||||
return;
|
||||
}
|
||||
foreach ($scheduled_backups as $scheduled_backup) {
|
||||
@@ -117,12 +116,11 @@ class Kernel extends ConsoleKernel
|
||||
{
|
||||
$scheduled_tasks = ScheduledTask::all();
|
||||
if ($scheduled_tasks->isEmpty()) {
|
||||
ray('no scheduled tasks');
|
||||
return;
|
||||
}
|
||||
foreach ($scheduled_tasks as $scheduled_task) {
|
||||
$service = $scheduled_task->service()->get();
|
||||
$application = $scheduled_task->application()->get();
|
||||
$service = $scheduled_task->service;
|
||||
$application = $scheduled_task->application;
|
||||
|
||||
if (!$application && !$service) {
|
||||
ray('application/service attached to scheduled task does not exist');
|
||||
|
||||
@@ -18,8 +18,7 @@ class Deploy extends Controller
|
||||
{
|
||||
public function deploy(Request $request)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$teamId = data_get($token, 'team_id');
|
||||
$teamId = get_team_id_from_token();
|
||||
$uuids = $request->query->get('uuid');
|
||||
$tags = $request->query->get('tag');
|
||||
$force = $request->query->get('force') ?? false;
|
||||
|
||||
39
app/Http/Controllers/Api/Project.php
Normal file
39
app/Http/Controllers/Api/Project.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project as ModelsProject;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Project extends Controller
|
||||
{
|
||||
public function projects(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||
return response()->json($projects);
|
||||
}
|
||||
public function project_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||
return response()->json($project);
|
||||
}
|
||||
public function environment_details(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||
return response()->json($environment);
|
||||
}
|
||||
}
|
||||
54
app/Http/Controllers/Api/Server.php
Normal file
54
app/Http/Controllers/Api/Server.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Server as ModelsServer;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Server extends Controller
|
||||
{
|
||||
public function servers(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
||||
$server['is_reachable'] = $server->settings->is_reachable;
|
||||
$server['is_usable'] = $server->settings->is_usable;
|
||||
return $server;
|
||||
});
|
||||
ray($servers);
|
||||
return response()->json($servers);
|
||||
}
|
||||
public function server_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||
}
|
||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
if (is_null($server)) {
|
||||
return response()->json(['error' => 'Server not found.'], 404);
|
||||
}
|
||||
$server->load(['settings']);
|
||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||
$payload = [
|
||||
'id' => $resource->id,
|
||||
'uuid' => $resource->uuid,
|
||||
'name' => $resource->name,
|
||||
'type' => $resource->type(),
|
||||
'created_at' => $resource->created_at,
|
||||
'updated_at' => $resource->updated_at,
|
||||
];
|
||||
if ($resource->type() === 'service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
return $payload;
|
||||
});
|
||||
return response()->json($server);
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private Server $mainServer;
|
||||
private ?ApplicationPreview $preview = null;
|
||||
private ?string $git_type = null;
|
||||
private bool $only_this_server = false;
|
||||
|
||||
private string $container_name;
|
||||
private ?string $currently_running_container_name = null;
|
||||
@@ -115,6 +116,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->commit = $this->application_deployment_queue->commit;
|
||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
||||
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||
$this->only_this_server = $this->application_deployment_queue->only_this_server;
|
||||
|
||||
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
|
||||
|
||||
@@ -123,6 +125,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->source = $source->getMorphClass()::where('id', $this->application->source->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->serverUser = $this->server->user;
|
||||
@@ -132,6 +135,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
|
||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||
ray('New container name: ', $this->container_name);
|
||||
|
||||
savePrivateKeyToFs($this->server);
|
||||
$this->saved_outputs = collect();
|
||||
|
||||
@@ -248,9 +253,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
// Otherwise built image needs to be pushed before from the build server.
|
||||
if (!$this->use_build_server) {
|
||||
$this->push_to_docker_registry();
|
||||
}
|
||||
// ray($this->use_build_server);
|
||||
// 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);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
if ($this->application->is_github_based()) {
|
||||
@@ -288,162 +298,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
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()
|
||||
{
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
$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->execute_remote_command(
|
||||
[
|
||||
@@ -451,19 +312,34 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
],
|
||||
);
|
||||
$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_build_env_variables();
|
||||
$this->add_build_env_variables_to_dockerfile();
|
||||
$this->build_image();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
}
|
||||
|
||||
private function deploy_dockerimage_buildpack()
|
||||
{
|
||||
$this->dockerImage = $this->application->docker_registry_image_name;
|
||||
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||
ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'");
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.");
|
||||
if (str($this->application->docker_registry_image_tag)->isEmpty()) {
|
||||
$this->dockerImageTag = 'latest';
|
||||
} else {
|
||||
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||
}
|
||||
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} to {$this->server->name}.");
|
||||
$this->generate_image_names();
|
||||
$this->prepare_builder_image();
|
||||
$this->generate_compose_file();
|
||||
@@ -481,9 +357,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->docker_compose_custom_build_command = $this->application->docker_compose_custom_build_command;
|
||||
}
|
||||
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 {
|
||||
$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->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
@@ -551,25 +427,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if (data_get($this->application, 'dockerfile_location')) {
|
||||
$this->dockerfile_location = $this->application->dockerfile_location;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
||||
$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();
|
||||
$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->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();
|
||||
@@ -580,6 +438,38 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$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->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();
|
||||
return;
|
||||
}
|
||||
@@ -592,8 +482,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->generate_nixpacks_confs();
|
||||
$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_static_buildpack()
|
||||
@@ -601,18 +491,205 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
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->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();
|
||||
$this->push_to_docker_registry();
|
||||
$this->rolling_update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->clone_repository();
|
||||
$this->cleanup_git();
|
||||
$this->build_image();
|
||||
$this->generate_compose_file();
|
||||
$this->build_image();
|
||||
$this->push_to_docker_registry();
|
||||
$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()
|
||||
{
|
||||
// Laravel old env variables
|
||||
@@ -630,9 +707,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private function rolling_update()
|
||||
{
|
||||
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->execute_remote_command(
|
||||
[
|
||||
@@ -642,13 +716,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
|
||||
} else {
|
||||
if ($this->use_build_server) {
|
||||
$this->push_to_docker_registry(forceFail: true);
|
||||
$this->write_deployment_configurations();
|
||||
$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("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->start_by_compose_file();
|
||||
} else {
|
||||
@@ -787,10 +865,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function deploy_to_additional_destinations()
|
||||
{
|
||||
if (str($this->application->additional_destinations)->isEmpty()) {
|
||||
if ($this->application->additional_networks->count() === 0) {
|
||||
return;
|
||||
}
|
||||
$destination_ids = collect(str($this->application->additional_destinations)->explode(','));
|
||||
$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;
|
||||
@@ -815,7 +893,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
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', [
|
||||
$this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: " . 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,
|
||||
@@ -950,11 +1028,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->env_nixpacks_args = collect([]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
|
||||
if (!is_null($env->real_value)) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
|
||||
if (!is_null($env->real_value)) {
|
||||
$this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -965,11 +1047,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->env_args = collect([]);
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
$this->env_args->put($env->key, $env->real_value);
|
||||
if (!is_null($env->real_value)) {
|
||||
$this->env_args->put($env->key, $env->real_value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($this->application->build_environment_variables_preview as $env) {
|
||||
$this->env_args->put($env->key, $env->real_value);
|
||||
if (!is_null($env->real_value)) {
|
||||
$this->env_args->put($env->key, $env->real_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->env_args->put('SOURCE_COMMIT', $this->commit);
|
||||
@@ -1134,13 +1220,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// ];
|
||||
// }
|
||||
|
||||
$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);
|
||||
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if (count($custom_compose) > 0) {
|
||||
$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);
|
||||
@@ -1425,6 +1516,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],
|
||||
);
|
||||
});
|
||||
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 {
|
||||
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
||||
$this->application_deployment_queue->update([
|
||||
@@ -1529,7 +1625,9 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
return;
|
||||
}
|
||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||
// $this->deploy_to_additional_destinations();
|
||||
if (!$this->only_this_server) {
|
||||
$this->deploy_to_additional_destinations();
|
||||
}
|
||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||
}
|
||||
}
|
||||
@@ -1542,10 +1640,14 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
|
||||
if ($this->application->build_pack !== 'dockercompose') {
|
||||
$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]
|
||||
);
|
||||
$code = $exception->getCode();
|
||||
if ($code !== 69420) {
|
||||
// 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);
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Jobs;
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Actions\Shared\ComplexStatusCheck;
|
||||
use App\Models\ApplicationPreview;
|
||||
use App\Models\Server;
|
||||
use App\Notifications\Container\ContainerRestarted;
|
||||
@@ -42,6 +43,21 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$applications = $this->server->applications();
|
||||
$skip_these_applications = collect([]);
|
||||
foreach ($applications as $application) {
|
||||
if ($application->additional_servers->count() > 0) {
|
||||
$skip_these_applications->push($application);
|
||||
ComplexStatusCheck::run($application);
|
||||
$applications = $applications->filter(function ($value, $key) use ($application) {
|
||||
return $value->id !== $application->id;
|
||||
});
|
||||
}
|
||||
}
|
||||
$applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||
return !$skip_these_applications->pluck('id')->contains($value->id);
|
||||
});
|
||||
|
||||
if (!$this->server->isFunctional()) {
|
||||
return 'Server is not ready.';
|
||||
};
|
||||
@@ -83,7 +99,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
});
|
||||
}
|
||||
}
|
||||
$applications = $this->server->applications();
|
||||
$databases = $this->server->databases();
|
||||
$services = $this->server->services()->get();
|
||||
$previews = $this->server->previews();
|
||||
@@ -160,10 +175,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
if ($serviceLabelId) {
|
||||
@@ -209,7 +223,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
$exitedServices = $exitedServices->unique('id');
|
||||
foreach ($exitedServices as $exitedService) {
|
||||
if ($exitedService->status === 'exited') {
|
||||
if (str($exitedService->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$name = data_get($exitedService, 'name');
|
||||
@@ -231,7 +245,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
||||
foreach ($notRunningApplications as $applicationId) {
|
||||
$application = $applications->where('id', $applicationId)->first();
|
||||
if ($application->status === 'exited') {
|
||||
if (str($application->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$application->update(['status' => 'exited']);
|
||||
@@ -256,7 +270,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||
$preview = $previews->where('id', $previewId)->first();
|
||||
if ($preview->status === 'exited') {
|
||||
if (str($preview->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$preview->update(['status' => 'exited']);
|
||||
@@ -281,7 +295,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||
foreach ($notRunningDatabases as $database) {
|
||||
$database = $databases->where('id', $database)->first();
|
||||
if ($database->status === 'exited') {
|
||||
if (str($database->status)->startsWith('exited')) {
|
||||
continue;
|
||||
}
|
||||
$database->update(['status' => 'exited']);
|
||||
|
||||
@@ -19,6 +19,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
@@ -49,8 +50,11 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
Artisan::queue('cleanup:stucked-resources');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
app/Jobs/GithubAppPermissionJob.php
Normal file
59
app/Jobs/GithubAppPermissionJob.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\GithubApp;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class GithubAppPermissionJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $tries = 4;
|
||||
public function backoff(): int
|
||||
{
|
||||
return isDev() ? 1 : 3;
|
||||
}
|
||||
public function __construct(public GithubApp $github_app)
|
||||
{
|
||||
}
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->github_app->uuid))];
|
||||
}
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->github_app->uuid;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$github_access_token = generate_github_jwt_token($this->github_app);
|
||||
$response = Http::withHeaders([
|
||||
'Authorization' => "Bearer $github_access_token",
|
||||
'Accept' => 'application/vnd.github+json'
|
||||
])->get("{$this->github_app->api_url}/app");
|
||||
$response = $response->json();
|
||||
$permissions = data_get($response, 'permissions');
|
||||
$this->github_app->contents = data_get($permissions, 'contents');
|
||||
$this->github_app->metadata = data_get($permissions, 'metadata');
|
||||
$this->github_app->pull_requests = data_get($permissions, 'pull_requests');
|
||||
$this->github_app->administration = data_get($permissions, 'administration');
|
||||
$this->github_app->save();
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('GithubAppPermissionJob failed with: ' . $e->getMessage());
|
||||
ray($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public ?int $disk_usage = null;
|
||||
public int|string|null $disk_usage = null;
|
||||
public $tries = 4;
|
||||
public function backoff(): int
|
||||
{
|
||||
@@ -41,6 +41,15 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
throw new \RuntimeException('Server is not ready.');
|
||||
};
|
||||
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()) {
|
||||
$this->cleanup(notify: false);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
use App\Actions\Server\InstallDocker;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
@@ -121,15 +122,16 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
}
|
||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
$this->validateServer();
|
||||
$this->installServer();
|
||||
}
|
||||
public function getProxyType()
|
||||
{
|
||||
$proxyTypeSet = $this->createdServer->proxy->type;
|
||||
if (!$proxyTypeSet) {
|
||||
$this->currentState = 'select-proxy';
|
||||
return;
|
||||
}
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||
// if (!$proxyTypeSet) {
|
||||
// $this->currentState = 'select-proxy';
|
||||
// return;
|
||||
// }
|
||||
$this->getProjects();
|
||||
}
|
||||
public function selectExistingPrivateKey()
|
||||
@@ -193,7 +195,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||
$this->createdServer->settings->save();
|
||||
$this->createdServer->addInitialNetwork();
|
||||
$this->validateServer();
|
||||
$this->currentState = 'validate-server';
|
||||
}
|
||||
public function installServer()
|
||||
{
|
||||
@@ -204,7 +206,8 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
try {
|
||||
config()->set('coolify.mux_enabled', false);
|
||||
|
||||
instant_remote_process(['uptime'], $this->createdServer, true);
|
||||
// EC2 does not have `uptime` command, lol
|
||||
instant_remote_process(['ls /'], $this->createdServer, true);
|
||||
|
||||
$this->createdServer->settings()->update([
|
||||
'is_reachable' => true,
|
||||
@@ -219,7 +222,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
|
||||
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||
if (is_null($dockerVersion)) {
|
||||
$this->currentState = 'install-docker';
|
||||
$this->currentState = 'validate-server';
|
||||
throw new \Exception('Docker not found or old version is installed.');
|
||||
}
|
||||
$this->createdServer->settings()->update([
|
||||
@@ -227,27 +230,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
]);
|
||||
$this->getProxyType();
|
||||
} catch (\Throwable $e) {
|
||||
// $this->dockerInstallationStarted = false;
|
||||
return handleError(error: $e, livewire: $this);
|
||||
}
|
||||
}
|
||||
public function installDocker()
|
||||
{
|
||||
try {
|
||||
$this->dockerInstallationStarted = true;
|
||||
$activity = InstallDocker::run($this->createdServer);
|
||||
$this->dispatch('installDocker');
|
||||
$this->dispatch('activityMonitor', $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)
|
||||
public function selectProxy(?string $proxyType = null)
|
||||
{
|
||||
if (!$proxyType) {
|
||||
return $this->getProjects();
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Livewire\Component;
|
||||
|
||||
class Dashboard extends Component
|
||||
@@ -19,6 +20,13 @@ class Dashboard extends Component
|
||||
$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([
|
||||
|
||||
@@ -63,7 +63,7 @@ class EmailSettings extends Component
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->dispatch('success', 'Settings saved successfully.');
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -71,7 +71,7 @@ class EmailSettings extends Component
|
||||
public function sendTestNotification()
|
||||
{
|
||||
$this->team?->notify(new Test($this->emails));
|
||||
$this->dispatch('success', 'Test Email sent successfully.');
|
||||
$this->dispatch('success', 'Test Email sent.');
|
||||
}
|
||||
public function instantSaveInstance()
|
||||
{
|
||||
@@ -83,7 +83,7 @@ class EmailSettings extends Component
|
||||
$this->team->resend_enabled = false;
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->dispatch('success', 'Settings saved successfully.');
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -131,7 +131,7 @@ class EmailSettings extends Component
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->dispatch('success', 'Settings saved successfully.');
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->smtp_enabled = false;
|
||||
return handleError($e, $this);
|
||||
@@ -148,7 +148,7 @@ class EmailSettings extends Component
|
||||
]);
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->dispatch('success', 'Settings saved successfully.');
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->team->resend_enabled = false;
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -27,7 +27,7 @@ class Index extends Component
|
||||
'name' => $this->name,
|
||||
]);
|
||||
|
||||
$this->dispatch('success', 'Profile updated successfully.');
|
||||
$this->dispatch('success', 'Profile updated');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -8,20 +8,25 @@ use Livewire\Component;
|
||||
class Advanced extends Component
|
||||
{
|
||||
public Application $application;
|
||||
public bool $is_force_https_enabled;
|
||||
protected $rules = [
|
||||
'application.settings.is_git_submodules_enabled' => 'boolean|required',
|
||||
'application.settings.is_git_lfs_enabled' => 'boolean|required',
|
||||
'application.settings.is_preview_deployments_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_gpu_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_count' => 'string|required',
|
||||
'application.settings.gpu_device_ids' => 'string|required',
|
||||
'application.settings.gpu_options' => 'string|required',
|
||||
];
|
||||
public function mount() {
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->application->isLogDrainEnabled()) {
|
||||
@@ -31,7 +36,8 @@ class Advanced extends Component
|
||||
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->application->settings->save();
|
||||
|
||||
@@ -48,6 +48,8 @@ class DeploymentNavbar extends Component
|
||||
{
|
||||
try {
|
||||
$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) {
|
||||
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
@@ -63,8 +65,8 @@ class DeploymentNavbar extends Component
|
||||
$this->application_deployment_queue->update([
|
||||
'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) {
|
||||
ray($e);
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -126,7 +126,6 @@ class General extends Component
|
||||
$this->application->save();
|
||||
}
|
||||
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
||||
$this->checkLabelUpdates();
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
@@ -164,6 +163,7 @@ class General extends Component
|
||||
}
|
||||
return $domain;
|
||||
}
|
||||
|
||||
public function updatedApplicationBuildPack()
|
||||
{
|
||||
if ($this->application->build_pack !== 'nixpacks') {
|
||||
@@ -184,15 +184,6 @@ class General extends Component
|
||||
$this->submit();
|
||||
$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()
|
||||
{
|
||||
$server = data_get($this->application, 'destination.server');
|
||||
@@ -212,6 +203,13 @@ class General extends Component
|
||||
|
||||
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->dispatch('success', 'Labels reset to default!');
|
||||
}
|
||||
@@ -234,24 +232,20 @@ class General extends Component
|
||||
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
||||
$this->validate([
|
||||
'application.docker_registry_image_name' => 'required',
|
||||
'application.docker_registry_image_tag' => 'required',
|
||||
]);
|
||||
}
|
||||
if (data_get($this->application, 'fqdn')) {
|
||||
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
|
||||
$domains = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||
return str($domain)->trim()->lower();
|
||||
});
|
||||
$domains = $domains->unique();
|
||||
foreach ($domains as $domain) {
|
||||
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
||||
$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.");
|
||||
$domains = str($this->application->fqdn)->trim()->explode(',');
|
||||
if ($this->application->additional_servers->count() === 0) {
|
||||
foreach ($domains as $domain) {
|
||||
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
||||
$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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
check_fqdn_usage($this->application);
|
||||
$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();
|
||||
}
|
||||
@@ -277,7 +271,6 @@ class General extends Component
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->checkLabelUpdates();
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Jobs\ComplexContainerStatusJob;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Jobs\ServerStatusJob;
|
||||
use App\Models\Application;
|
||||
@@ -31,14 +33,11 @@ class Heading extends Component
|
||||
{
|
||||
if ($this->application->destination->server->isFunctional()) {
|
||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||
$this->application->refresh();
|
||||
$this->application->previews->each(function ($preview) {
|
||||
$preview->refresh();
|
||||
});
|
||||
} else {
|
||||
dispatch(new ServerStatusJob($this->application->destination->server));
|
||||
}
|
||||
if ($showNotification) $this->dispatch('success', "Application status updated.");
|
||||
|
||||
if ($showNotification) $this->dispatch('success', "Success", "Application status updated.");
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
@@ -49,15 +48,19 @@ class Heading extends Component
|
||||
public function deploy(bool $force_rebuild = false)
|
||||
{
|
||||
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;
|
||||
}
|
||||
if ($this->application->destination->server->isSwarm() && is_null($this->application->docker_registry_image_name)) {
|
||||
$this->dispatch('error', 'To deploy to a Swarm cluster you must set a Docker image name first.');
|
||||
if ($this->application->destination->server->isSwarm() && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||
$this->dispatch('error', 'Failed to deploy.', 'To deploy to a Swarm cluster you must set a Docker image name first.');
|
||||
return;
|
||||
}
|
||||
if (data_get($this->application, 'settings.is_build_server_enabled') && is_null($this->application->docker_registry_image_name)) {
|
||||
$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>');
|
||||
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) {
|
||||
$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;
|
||||
}
|
||||
$this->setDeploymentUuid();
|
||||
@@ -85,26 +88,20 @@ class Heading extends Component
|
||||
StopApplication::run($this->application);
|
||||
$this->application->status = 'exited';
|
||||
$this->application->save();
|
||||
$this->application->refresh();
|
||||
}
|
||||
public function restartNew()
|
||||
{
|
||||
$this->setDeploymentUuid();
|
||||
queue_application_deployment(
|
||||
application: $this->application,
|
||||
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'],
|
||||
]);
|
||||
if ($this->application->additional_servers->count() > 0) {
|
||||
$this->application->additional_servers->each(function ($server) {
|
||||
$server->pivot->status = "exited:unhealthy";
|
||||
$server->pivot->save();
|
||||
});
|
||||
}
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||
}
|
||||
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();
|
||||
queue_application_deployment(
|
||||
application: $this->application,
|
||||
|
||||
@@ -46,7 +46,7 @@ class Form extends Component
|
||||
$this->validate();
|
||||
$this->application->preview_url_template = str_replace(' ', '', $this->application->preview_url_template);
|
||||
$this->application->save();
|
||||
$this->dispatch('success', 'Preview url template updated successfully.');
|
||||
$this->dispatch('success', 'Preview url template updated.');
|
||||
$this->generate_real_url();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class BackupExecutions extends Component
|
||||
delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server);
|
||||
}
|
||||
$execution->delete();
|
||||
$this->dispatch('success', 'Backup deleted successfully.');
|
||||
$this->dispatch('success', 'Backup deleted.');
|
||||
$this->dispatch('refreshBackupExecutions');
|
||||
}
|
||||
public function download($exeuctionId)
|
||||
|
||||
@@ -59,7 +59,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -73,7 +73,7 @@ class General extends Component
|
||||
}
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -75,7 +75,7 @@ class General extends Component
|
||||
}
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -74,7 +74,7 @@ class General extends Component
|
||||
}
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -105,7 +105,7 @@ class General extends Component
|
||||
$this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']);
|
||||
$this->database->init_scripts = array_merge($this->database->init_scripts, [$script]);
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Init script saved successfully.');
|
||||
$this->dispatch('success', 'Init script saved.');
|
||||
}
|
||||
|
||||
public function delete_init_script($script)
|
||||
@@ -116,7 +116,7 @@ class General extends Component
|
||||
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
|
||||
$this->database->save();
|
||||
$this->refresh();
|
||||
$this->dispatch('success', 'Init script deleted successfully.');
|
||||
$this->dispatch('success', 'Init script deleted.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ class General extends Component
|
||||
]
|
||||
]);
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Init script added successfully.');
|
||||
$this->dispatch('success', 'Init script added.');
|
||||
$this->new_content = '';
|
||||
$this->new_filename = '';
|
||||
}
|
||||
@@ -161,7 +161,7 @@ class General extends Component
|
||||
}
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -66,7 +66,7 @@ class General extends Component
|
||||
$this->database->redis_conf = null;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated successfully.');
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ class ScheduledBackups extends Component
|
||||
public function delete($scheduled_backup_id): void
|
||||
{
|
||||
$this->database->scheduledBackups->find($scheduled_backup_id)->delete();
|
||||
$this->dispatch('success', 'Scheduled backup deleted successfully.');
|
||||
$this->dispatch('success', 'Scheduled backup deleted.');
|
||||
$this->refreshScheduledBackups();
|
||||
}
|
||||
|
||||
|
||||
@@ -71,10 +71,10 @@ class Select extends Component
|
||||
// }
|
||||
// }
|
||||
|
||||
public function loadServices()
|
||||
public function loadServices(bool $force = false)
|
||||
{
|
||||
try {
|
||||
if (count($this->allServices) > 0) {
|
||||
if (count($this->allServices) > 0 && !$force) {
|
||||
if (!$this->search) {
|
||||
$this->services = $this->allServices;
|
||||
return;
|
||||
|
||||
@@ -104,7 +104,7 @@ class Index extends Component
|
||||
'environment_name' => data_get($service, 'environment.name'),
|
||||
'service_uuid' => data_get($service, 'uuid')
|
||||
]);
|
||||
$service->status = serviceStatus($service);
|
||||
$service->status = $service->status();
|
||||
}
|
||||
return $service;
|
||||
});
|
||||
|
||||
@@ -77,7 +77,7 @@ class Database extends Component
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
updateCompose($this->database);
|
||||
$this->dispatch('success', 'Database saved successfully.');
|
||||
$this->dispatch('success', 'Database saved.');
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
} finally {
|
||||
|
||||
@@ -41,8 +41,8 @@ class FileStorage extends Component
|
||||
$this->fileStorage->content = null;
|
||||
}
|
||||
$this->fileStorage->save();
|
||||
$this->fileStorage->saveStorageOnServer($this->service);
|
||||
$this->dispatch('success', 'File updated successfully.');
|
||||
$this->fileStorage->saveStorageOnServer();
|
||||
$this->dispatch('success', 'File updated.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->fileStorage->setRawAttributes($original);
|
||||
$this->fileStorage->save();
|
||||
|
||||
@@ -27,12 +27,12 @@ class Index extends Component
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$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) {
|
||||
$this->serviceApplication = $service;
|
||||
$this->serviceApplication->getFilesFromServer();
|
||||
} 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->s3s = currentTeam()->s3s;
|
||||
|
||||
@@ -64,9 +64,9 @@ class Navbar extends Component
|
||||
StopService::run($this->service);
|
||||
$this->service->refresh();
|
||||
if ($forceCleanup) {
|
||||
$this->dispatch('success', 'Force cleanup service successfully.');
|
||||
$this->dispatch('success', 'Force cleanup service.');
|
||||
} else {
|
||||
$this->dispatch('success', 'Service stopped successfully.');
|
||||
$this->dispatch('success', 'Service stopped.');
|
||||
}
|
||||
ServiceStatusChanged::dispatch();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ class ServiceApplicationView extends Component
|
||||
'application.exclude_from_status' => 'required|boolean',
|
||||
'application.required_fqdn' => 'required|boolean',
|
||||
'application.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'application.is_gzip_enabled' => 'nullable|boolean',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
@@ -40,7 +41,7 @@ class ServiceApplicationView extends Component
|
||||
{
|
||||
try {
|
||||
$this->application->delete();
|
||||
$this->dispatch('success', 'Application deleted successfully.');
|
||||
$this->dispatch('success', 'Application deleted.');
|
||||
return redirect()->route('project.service.configuration', $this->parameters);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -57,7 +58,7 @@ class ServiceApplicationView extends Component
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
updateCompose($this->application);
|
||||
$this->dispatch('success', 'Application saved successfully.');
|
||||
$this->dispatch('success', 'Application saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
|
||||
@@ -45,8 +45,10 @@ class StackForm extends Component
|
||||
$this->service->docker_compose_raw = $raw;
|
||||
$this->submit();
|
||||
}
|
||||
public function instantSave() {
|
||||
public function instantSave()
|
||||
{
|
||||
$this->service->save();
|
||||
$this->dispatch('success', 'Service settings saved.');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
@@ -60,7 +62,7 @@ class StackForm extends Component
|
||||
$this->service->saveComposeConfigs();
|
||||
$this->dispatch('refreshStacks');
|
||||
$this->dispatch('refreshEnvs');
|
||||
$this->dispatch('success', 'Service saved successfully.');
|
||||
$this->dispatch('success', 'Service saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,115 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Actions\Application\StopApplicationOneServer;
|
||||
use App\Events\ApplicationStatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Destination extends Component
|
||||
{
|
||||
public $resource;
|
||||
public $servers = [];
|
||||
public $additional_servers = [];
|
||||
public $networks = [];
|
||||
|
||||
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);
|
||||
});
|
||||
$this->networks = $this->networks->reject(function ($network) {
|
||||
return $this->resource->destination->server->id == $network->server->id;
|
||||
});
|
||||
if ($this->resource?->additional_servers?->count() > 0) {
|
||||
$this->networks = $this->networks->reject(function ($network) {
|
||||
return $this->resource->additional_servers->pluck('id')->contains($network->server->id);
|
||||
});
|
||||
}
|
||||
}
|
||||
public function stop(int $server_id)
|
||||
{
|
||||
$server = Server::find($server_id);
|
||||
StopApplicationOneServer::run($this->resource, $server);
|
||||
$this->refreshServers();
|
||||
}
|
||||
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,
|
||||
only_this_server: true,
|
||||
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 promote(int $network_id, int $server_id)
|
||||
{
|
||||
$main_destination = $this->resource->destination;
|
||||
$this->resource->update([
|
||||
'destination_id' => $network_id,
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
]);
|
||||
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
|
||||
$this->resource->additional_networks()->attach($main_destination->id, ['server_id' => $main_destination->server->id]);
|
||||
$this->refreshServers();
|
||||
}
|
||||
public function refreshServers()
|
||||
{
|
||||
ContainerStatusJob::dispatchSync($this->resource->destination->server);
|
||||
$this->loadData();
|
||||
$this->dispatch('refresh');
|
||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||
}
|
||||
public function addServer(int $network_id, int $server_id)
|
||||
{
|
||||
$this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]);
|
||||
$this->loadData();
|
||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||
}
|
||||
public function removeServer(int $network_id, int $server_id)
|
||||
{
|
||||
if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_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->loadData();
|
||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +120,9 @@ class All extends Component
|
||||
}
|
||||
}
|
||||
if ($isPreview) {
|
||||
$this->dispatch('success', 'Preview environment variables updated successfully.');
|
||||
$this->dispatch('success', 'Preview environment variables updated.');
|
||||
} else {
|
||||
$this->dispatch('success', 'Environment variables updated successfully.');
|
||||
$this->dispatch('success', 'Environment variables updated.');
|
||||
}
|
||||
$this->refreshEnvs();
|
||||
}
|
||||
@@ -171,7 +171,7 @@ class All extends Component
|
||||
}
|
||||
$environment->save();
|
||||
$this->refreshEnvs();
|
||||
$this->dispatch('success', 'Environment variable added successfully.');
|
||||
$this->dispatch('success', 'Environment variable added.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class Show extends Component
|
||||
}
|
||||
$this->serialize();
|
||||
$this->env->save();
|
||||
$this->dispatch('success', 'Environment variable updated successfully.');
|
||||
$this->dispatch('success', 'Environment variable updated.');
|
||||
$this->dispatch('refreshEnvs');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
|
||||
@@ -52,7 +52,7 @@ class ResourceLimits extends Component
|
||||
}
|
||||
$this->validate();
|
||||
$this->resource->save();
|
||||
$this->dispatch('success', 'Resource limits updated successfully.');
|
||||
$this->dispatch('success', 'Resource limits updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class All extends Component
|
||||
}
|
||||
$task->save();
|
||||
$this->refreshTasks();
|
||||
$this->dispatch('success', 'Scheduled task added successfully.');
|
||||
$this->dispatch('success', 'Scheduled task added.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class Show extends Component
|
||||
{
|
||||
$this->validate();
|
||||
$this->task->save();
|
||||
$this->dispatch('success', 'Scheduled task updated successfully.');
|
||||
$this->dispatch('success', 'Scheduled task updated.');
|
||||
$this->dispatch('refreshTasks');
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ class Form extends Component
|
||||
public ?string $wildcard_domain = null;
|
||||
public int $cleanup_after_percentage;
|
||||
public bool $dockerInstallationStarted = false;
|
||||
public bool $revalidate = false;
|
||||
|
||||
protected $listeners = ['serverInstalled'];
|
||||
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
|
||||
|
||||
protected $rules = [
|
||||
'server.name' => 'required',
|
||||
@@ -28,6 +29,7 @@ class Form extends Component
|
||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'server.settings.is_build_server' => 'required|boolean',
|
||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
@@ -42,6 +44,8 @@ class Form extends Component
|
||||
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||
'server.settings.is_build_server' => 'Build Server',
|
||||
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
||||
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
|
||||
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -60,11 +64,15 @@ class Form extends Component
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->validateServer(false);
|
||||
$this->server->settings->save();
|
||||
$this->dispatch('success', 'Server updated successfully.');
|
||||
$this->dispatch('success', 'Server updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function revalidate()
|
||||
{
|
||||
$this->revalidate = true;
|
||||
}
|
||||
public function checkLocalhostConnection()
|
||||
{
|
||||
$uptime = $this->server->validateConnection();
|
||||
@@ -80,7 +88,7 @@ class Form extends Component
|
||||
}
|
||||
public function validateServer($install = true)
|
||||
{
|
||||
$this->dispatch('validateServer', $install);
|
||||
$this->dispatch('init', $install);
|
||||
}
|
||||
|
||||
public function submit()
|
||||
@@ -105,6 +113,6 @@ class Form extends Component
|
||||
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
|
||||
$this->server->settings->save();
|
||||
$this->server->save();
|
||||
$this->dispatch('success', 'Server updated successfully.');
|
||||
$this->dispatch('success', 'Server updated.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class LogDrains extends Component
|
||||
return;
|
||||
}
|
||||
$this->dispatch('serverRefresh');
|
||||
$this->dispatch('success', 'Log drain service started successfully.');
|
||||
$this->dispatch('success', 'Log drain service started.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -126,7 +126,7 @@ class LogDrains extends Component
|
||||
]);
|
||||
}
|
||||
$this->server->settings->save();
|
||||
$this->dispatch('success', 'Settings saved successfully.');
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
if ($type === 'newrelic') {
|
||||
|
||||
28
app/Livewire/Server/Proxy/DynamicConfigurationNavbar.php
Normal file
28
app/Livewire/Server/Proxy/DynamicConfigurationNavbar.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class DynamicConfigurationNavbar extends Component
|
||||
{
|
||||
public $server_id;
|
||||
public $fileName = '';
|
||||
public $value = '';
|
||||
public $newFile = false;
|
||||
public function delete(string $fileName)
|
||||
{
|
||||
$server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
|
||||
$proxy_path = get_proxy_path();
|
||||
$file = str_replace('|', '.', $fileName);
|
||||
instant_remote_process(["rm -f {$proxy_path}/dynamic/{$file}"], $server);
|
||||
$this->dispatch('success', 'File deleted.');
|
||||
$this->dispatch('loadDynamicConfigurations');
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.proxy.dynamic-configuration-navbar');
|
||||
}
|
||||
}
|
||||
51
app/Livewire/Server/Proxy/DynamicConfigurations.php
Normal file
51
app/Livewire/Server/Proxy/DynamicConfigurations.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class DynamicConfigurations extends Component
|
||||
{
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
public Collection $contents;
|
||||
protected $listeners = ['loadDynamicConfigurations', 'refresh' => '$refresh'];
|
||||
protected $rules = [
|
||||
'contents.*' => 'nullable|string',
|
||||
];
|
||||
public function loadDynamicConfigurations()
|
||||
{
|
||||
$proxy_path = get_proxy_path();
|
||||
$files = instant_remote_process(["mkdir -p $proxy_path/dynamic && ls -1 {$proxy_path}/dynamic"], $this->server);
|
||||
$files = collect(explode("\n", $files))->filter(fn ($file) => !empty($file));
|
||||
$files = $files->map(fn ($file) => trim($file));
|
||||
$files = $files->sort();
|
||||
if ($files->contains('coolify.yaml')) {
|
||||
$files = $files->filter(fn ($file) => $file !== 'coolify.yaml')->prepend('coolify.yaml');
|
||||
}
|
||||
$contents = collect([]);
|
||||
foreach ($files as $file) {
|
||||
$without_extension = str_replace('.', '|', $file);
|
||||
$contents[$without_extension] = instant_remote_process(["cat {$proxy_path}/dynamic/{$file}"], $this->server);
|
||||
}
|
||||
$this->contents = $contents;
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.proxy.dynamic-configurations');
|
||||
}
|
||||
}
|
||||
74
app/Livewire/Server/Proxy/NewDynamicConfiguration.php
Normal file
74
app/Livewire/Server/Proxy/NewDynamicConfiguration.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Routing\Route;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class NewDynamicConfiguration extends Component
|
||||
{
|
||||
public string $fileName = '';
|
||||
public string $value = '';
|
||||
public bool $newFile = false;
|
||||
public Server $server;
|
||||
public $server_id;
|
||||
public $parameters = [];
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
if ($this->fileName !== '') {
|
||||
$this->fileName = str_replace('|', '.', $this->fileName);
|
||||
}
|
||||
}
|
||||
public function addDynamicConfiguration()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'fileName' => 'required',
|
||||
'value' => 'required',
|
||||
]);
|
||||
|
||||
if (data_get($this->parameters, 'server_uuid')) {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(data_get($this->parameters, 'server_uuid'))->first();
|
||||
}
|
||||
if (!is_null($this->server_id)) {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereId($this->server_id)->first();
|
||||
}
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
if (!str($this->fileName)->endsWith('.yaml') && !str($this->fileName)->endsWith('.yml')) {
|
||||
$this->fileName = "{$this->fileName}.yaml";
|
||||
}
|
||||
if ($this->fileName === 'coolify.yaml') {
|
||||
$this->dispatch('error', 'File name is reserved.');
|
||||
return;
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$file = "{$proxy_path}/dynamic/{$this->fileName}";
|
||||
if ($this->newFile) {
|
||||
$exists = instant_remote_process(["test -f $file && echo 1 || echo 0"], $this->server);
|
||||
if ($exists == 1) {
|
||||
$this->dispatch('error', 'File already exists');
|
||||
return;
|
||||
}
|
||||
}
|
||||
$yaml = Yaml::parse($this->value);
|
||||
$yaml = Yaml::dump($yaml, 10, 2);
|
||||
$this->value = $yaml;
|
||||
$base64_value = base64_encode($this->value);
|
||||
instant_remote_process(["echo '{$base64_value}' | base64 -d > {$file}"], $this->server);
|
||||
$this->dispatch('loadDynamicConfigurations');
|
||||
$this->dispatch('dynamic-configuration-added');
|
||||
$this->dispatch('success', 'Dynamic configuration saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.proxy.new-dynamic-configuration');
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class Status extends Component
|
||||
public Server $server;
|
||||
public bool $polling = false;
|
||||
public int $numberOfPolls = 0;
|
||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||
protected $listeners = ['proxyStatusUpdated' => '$refresh', 'startProxyPolling'];
|
||||
|
||||
public function startProxyPolling()
|
||||
{
|
||||
|
||||
64
app/Livewire/Server/Resources.php
Normal file
64
app/Livewire/Server/Resources.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Resources extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
public ?Server $server = null;
|
||||
public $parameters = [];
|
||||
public Collection $unmanagedContainers;
|
||||
public function getListeners()
|
||||
{
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
return [
|
||||
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'refreshStatus',
|
||||
];
|
||||
}
|
||||
|
||||
public function startUnmanaged($id) {
|
||||
$this->server->startUnmanaged($id);
|
||||
$this->dispatch('success', 'Container started.');
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function restartUnmanaged($id) {
|
||||
$this->server->restartUnmanaged($id);
|
||||
$this->dispatch('success', 'Container restarted.');
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function stopUnmanaged($id) {
|
||||
$this->server->stopUnmanaged($id);
|
||||
$this->dispatch('success', 'Container stopped.');
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function refreshStatus() {
|
||||
$this->server->refresh();
|
||||
$this->loadUnmanagedContainers();
|
||||
$this->dispatch('success', 'Resource statuses refreshed.');
|
||||
}
|
||||
public function loadUnmanagedContainers() {
|
||||
$this->unmanagedContainers = $this->server->loadUnmanagedContainers();
|
||||
}
|
||||
public function mount() {
|
||||
$this->unmanagedContainers = collect();
|
||||
$this->parameters = get_route_parameters();
|
||||
try {
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||
if (is_null($this->server)) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
$this->loadUnmanagedContainers();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.server.resources');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -14,10 +16,21 @@ class ValidateAndInstall extends Component
|
||||
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;
|
||||
public bool $ask = false;
|
||||
|
||||
protected $listeners = ['validateServer' => 'init', 'validateDockerEngine', 'validateServerNow' => 'validateServer'];
|
||||
protected $listeners = [
|
||||
'init',
|
||||
'validateConnection',
|
||||
'validateOS',
|
||||
'validateDockerEngine',
|
||||
'validateDockerVersion',
|
||||
'startProxy',
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
|
||||
public function init(bool $install = true)
|
||||
{
|
||||
@@ -26,24 +39,34 @@ class ValidateAndInstall extends Component
|
||||
$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');
|
||||
if (!$this->ask) {
|
||||
$this->dispatch('validateConnection');
|
||||
}
|
||||
}
|
||||
|
||||
public function validateServer()
|
||||
public function startValidatingAfterAsking()
|
||||
{
|
||||
$this->ask = false;
|
||||
$this->init();
|
||||
}
|
||||
public function startProxy()
|
||||
{
|
||||
try {
|
||||
$this->validateConnection();
|
||||
$this->validateOS();
|
||||
$this->validateDockerEngine();
|
||||
|
||||
if ($this->server->isSwarm()) {
|
||||
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||
if ($swarmInstalled) {
|
||||
$this->dispatch('success', 'Docker Swarm is initiated.');
|
||||
$shouldStart = CheckProxy::run($this->server);
|
||||
if ($shouldStart) {
|
||||
$proxy = StartProxy::run($this->server, false);
|
||||
if ($proxy === 'OK') {
|
||||
$this->proxy_started = true;
|
||||
} else {
|
||||
throw new \Exception("Proxy could not be started.");
|
||||
}
|
||||
} else {
|
||||
$this->proxy_started = true;
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -55,6 +78,7 @@ class ValidateAndInstall extends Component
|
||||
$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;
|
||||
}
|
||||
$this->dispatch('validateOS');
|
||||
}
|
||||
public function validateOS()
|
||||
{
|
||||
@@ -63,40 +87,51 @@ class ValidateAndInstall extends Component
|
||||
$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;
|
||||
}
|
||||
$this->dispatch('validateDockerEngine');
|
||||
}
|
||||
public function validateDockerEngine()
|
||||
{
|
||||
$this->docker_installed = $this->server->validateDockerEngine();
|
||||
if (!$this->docker_installed) {
|
||||
$this->docker_compose_installed = $this->server->validateDockerCompose();
|
||||
if (!$this->docker_installed || !$this->docker_compose_installed) {
|
||||
if ($this->install) {
|
||||
ray($this->number_of_tries, $this->max_tries);
|
||||
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');
|
||||
if ($this->number_of_tries == 0) {
|
||||
$activity = $this->server->installDocker();
|
||||
$this->number_of_tries++;
|
||||
$this->dispatch('newActivityMonitor', $activity->id, 'init');
|
||||
}
|
||||
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();
|
||||
}
|
||||
$this->dispatch('validateDockerVersion');
|
||||
}
|
||||
public function validateDockerVersion()
|
||||
{
|
||||
$this->docker_version = $this->server->validateDockerEngineVersion();
|
||||
if ($this->docker_version) {
|
||||
$this->dispatch('serverInstalled');
|
||||
$this->dispatch('success', 'Server validated successfully.');
|
||||
if ($this->server->isSwarm()) {
|
||||
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||
if ($swarmInstalled) {
|
||||
$this->dispatch('success', 'Docker Swarm is initiated.');
|
||||
}
|
||||
} 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;
|
||||
$this->docker_version = $this->server->validateDockerEngineVersion();
|
||||
if ($this->docker_version) {
|
||||
$this->dispatch('serverInstalled');
|
||||
$this->dispatch('success', 'Server validated.');
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
$this->dispatch('startProxy');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
||||
@@ -81,6 +81,6 @@ class Backup extends Component
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->dispatch('success', 'Backup updated successfully.');
|
||||
$this->dispatch('success', 'Backup updated.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class Email extends Component
|
||||
'settings.smtp_from_name' => 'required',
|
||||
]);
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'Settings saved successfully.');
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class Email extends Component
|
||||
'settings.resend_api_key' => 'required'
|
||||
]);
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'Settings saved successfully.');
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->settings->resend_enabled = false;
|
||||
return handleError($e, $this);
|
||||
@@ -98,7 +98,7 @@ class Email extends Component
|
||||
'settings.smtp_timeout' => 'nullable',
|
||||
]);
|
||||
$this->settings->save();
|
||||
$this->dispatch('success', 'Settings saved successfully.');
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Livewire\Source\Github;
|
||||
|
||||
use App\Jobs\GithubAppPermissionJob;
|
||||
use App\Models\GithubApp;
|
||||
use App\Models\InstanceSettings;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Component;
|
||||
|
||||
class Change extends Component
|
||||
@@ -15,6 +17,7 @@ class Change extends Component
|
||||
|
||||
public ?bool $default_permissions = true;
|
||||
public ?bool $preview_deployment_permissions = true;
|
||||
public ?bool $administration = false;
|
||||
|
||||
public $parameters;
|
||||
public ?GithubApp $github_app;
|
||||
@@ -34,8 +37,52 @@ class Change extends Component
|
||||
'github_app.client_secret' => 'required|string',
|
||||
'github_app.webhook_secret' => 'required|string',
|
||||
'github_app.is_system_wide' => 'required|bool',
|
||||
'github_app.contents' => 'nullable|string',
|
||||
'github_app.metadata' => 'nullable|string',
|
||||
'github_app.pull_requests' => 'nullable|string',
|
||||
'github_app.administration' => 'nullable|string',
|
||||
];
|
||||
|
||||
public function checkPermissions()
|
||||
{
|
||||
GithubAppPermissionJob::dispatchSync($this->github_app);
|
||||
$this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
$this->dispatch('success', 'Github App permissions updated.');
|
||||
}
|
||||
// public function check()
|
||||
// {
|
||||
|
||||
// Need administration:read:write permission
|
||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#list-self-hosted-runners-for-a-repository
|
||||
|
||||
|
||||
// $github_access_token = generate_github_installation_token($this->github_app);
|
||||
// $repositories = Http::withToken($github_access_token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100");
|
||||
// $runners_by_repository = collect([]);
|
||||
// $repositories = $repositories->json()['repositories'];
|
||||
// foreach ($repositories as $repository) {
|
||||
// $runners_downloads = Http::withToken($github_access_token)->get("{$this->github_app->api_url}/repos/{$repository['full_name']}/actions/runners/downloads");
|
||||
// $runners = Http::withToken($github_access_token)->get("{$this->github_app->api_url}/repos/{$repository['full_name']}/actions/runners");
|
||||
// $token = Http::withHeaders([
|
||||
// 'Authorization' => "Bearer $github_access_token",
|
||||
// 'Accept' => 'application/vnd.github+json'
|
||||
// ])->withBody(null)->post("{$this->github_app->api_url}/repos/{$repository['full_name']}/actions/runners/registration-token");
|
||||
// $token = $token->json();
|
||||
// $remove_token = Http::withHeaders([
|
||||
// 'Authorization' => "Bearer $github_access_token",
|
||||
// 'Accept' => 'application/vnd.github+json'
|
||||
// ])->withBody(null)->post("{$this->github_app->api_url}/repos/{$repository['full_name']}/actions/runners/remove-token");
|
||||
// $remove_token = $remove_token->json();
|
||||
// $runners_by_repository->put($repository['full_name'], [
|
||||
// 'token' => $token,
|
||||
// 'remove_token' => $remove_token,
|
||||
// 'runners' => $runners->json(),
|
||||
// 'runners_downloads' => $runners_downloads->json()
|
||||
// ]);
|
||||
// }
|
||||
|
||||
// ray($runners_by_repository);
|
||||
// }
|
||||
public function mount()
|
||||
{
|
||||
$github_app_uuid = request()->github_app_uuid;
|
||||
@@ -103,7 +150,7 @@ class Change extends Component
|
||||
'github_app.is_system_wide' => 'required|bool',
|
||||
]);
|
||||
$this->github_app->save();
|
||||
$this->dispatch('success', 'Github App updated successfully.');
|
||||
$this->dispatch('success', 'Github App updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -111,6 +158,13 @@ class Change extends Component
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||
$this->github_app->save();
|
||||
$this->dispatch('success', 'Github App updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function delete()
|
||||
|
||||
@@ -37,7 +37,7 @@ class Index extends Component
|
||||
try {
|
||||
$this->team->save();
|
||||
refreshSession();
|
||||
$this->dispatch('success', 'Team updated successfully.');
|
||||
$this->dispatch('success', 'Team updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class InviteLink extends Component
|
||||
]);
|
||||
$mail->subject('You have been invited to ' . currentTeam()->name . ' on ' . config('app.name') . '.');
|
||||
send_user_an_email($mail, $this->email);
|
||||
$this->dispatch('success', 'Invitation sent via email successfully.');
|
||||
$this->dispatch('success', 'Invitation sent via email.');
|
||||
$this->dispatch('refreshInvitations');
|
||||
return;
|
||||
} else {
|
||||
|
||||
@@ -15,7 +15,6 @@ class Application extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::saving(function ($application) {
|
||||
@@ -49,10 +48,23 @@ class Application extends BaseModel
|
||||
$application->persistentStorages()->delete();
|
||||
$application->environment_variables()->delete();
|
||||
$application->environment_variables_preview()->delete();
|
||||
foreach ($application->scheduled_tasks as $task) {
|
||||
$task->delete();
|
||||
}
|
||||
$application->tags()->detach();
|
||||
});
|
||||
}
|
||||
|
||||
public function additional_servers()
|
||||
{
|
||||
return $this->belongsToMany(Server::class, 'additional_destinations')
|
||||
->withPivot('standalone_docker_id', 'status');
|
||||
}
|
||||
public function additional_networks()
|
||||
{
|
||||
return $this->belongsToMany(StandaloneDocker::class, 'additional_destinations')
|
||||
->withPivot('server_id', 'status');
|
||||
}
|
||||
public function is_github_based(): bool
|
||||
{
|
||||
if (data_get($this, 'source')) {
|
||||
@@ -191,9 +203,6 @@ class Application extends BaseModel
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
|
||||
// Normal Deployments
|
||||
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
@@ -203,6 +212,79 @@ class Application extends BaseModel
|
||||
|
||||
);
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
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 ($main_server_status !== $server_status) {
|
||||
$complex_status = 'degraded';
|
||||
}
|
||||
if ($main_server_health !== $server_health) {
|
||||
$complex_health = 'unhealthy';
|
||||
}
|
||||
}
|
||||
return "$complex_status:$complex_health";
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public function portsExposesArray(): Attribute
|
||||
{
|
||||
@@ -216,7 +298,8 @@ class Application extends BaseModel
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
public function project() {
|
||||
public function project()
|
||||
{
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
@@ -387,7 +470,7 @@ class Application extends BaseModel
|
||||
{
|
||||
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;
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
@@ -435,17 +518,21 @@ class Application extends BaseModel
|
||||
{
|
||||
return "/artifacts/{$uuid}";
|
||||
}
|
||||
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
|
||||
function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
||||
{
|
||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||
|
||||
if ($this->git_commit_sha !== 'HEAD') {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
||||
}
|
||||
if ($this->settings->is_git_submodules_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git submodule update --init --recursive";
|
||||
if ($public) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && sed -i \"s#git@\(.*\):#https://\\1/#g\" {$baseDir}/.gitmodules || true";
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git submodule update --init --recursive";
|
||||
}
|
||||
if ($this->settings->is_git_lfs_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && git lfs pull";
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git lfs pull";
|
||||
}
|
||||
return $git_clone_command;
|
||||
}
|
||||
@@ -473,7 +560,7 @@ class Application extends BaseModel
|
||||
$fullRepoUrl = "{$this->source->html_url}/{$customRepository}";
|
||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$customRepository} {$baseDir}";
|
||||
if (!$only_checkout) {
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command);
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
||||
}
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $git_clone_command));
|
||||
@@ -572,7 +659,7 @@ class Application extends BaseModel
|
||||
if ($this->deploymentType() === 'other') {
|
||||
$fullRepoUrl = $customRepository;
|
||||
$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, public: true);
|
||||
|
||||
if ($pull_request_id !== 0) {
|
||||
if ($git_type === 'gitlab') {
|
||||
|
||||
@@ -26,7 +26,6 @@ class Environment extends Model
|
||||
{
|
||||
return $this->hasMany(Application::class);
|
||||
}
|
||||
|
||||
public function postgresqls()
|
||||
{
|
||||
return $this->hasMany(StandalonePostgresql::class);
|
||||
|
||||
@@ -10,20 +10,37 @@ class LocalFileVolume extends BaseModel
|
||||
use HasFactory;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function (LocalFileVolume $fileVolume) {
|
||||
$fileVolume->load(['service']);
|
||||
$fileVolume->saveStorageOnServer();
|
||||
});
|
||||
}
|
||||
public function service()
|
||||
{
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
public function saveStorageOnServer(ServiceApplication|ServiceDatabase $service)
|
||||
public function saveStorageOnServer()
|
||||
{
|
||||
$workdir = $service->service->workdir();
|
||||
$server = $service->service->server;
|
||||
$workdir = $this->resource->service->workdir();
|
||||
$server = $this->resource->service->server;
|
||||
$commands = collect([
|
||||
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
||||
"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;
|
||||
$path = Str::of(data_get($fileVolume, 'fs_path'));
|
||||
$path = str(data_get($fileVolume, 'fs_path'));
|
||||
$content = data_get($fileVolume, 'content');
|
||||
if ($path->startsWith('.')) {
|
||||
$path = $path->after('.');
|
||||
@@ -32,17 +49,19 @@ class LocalFileVolume extends BaseModel
|
||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||
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) {
|
||||
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') {
|
||||
$content = base64_encode($content);
|
||||
$commands->push("echo '$content' | base64 -d > $path");
|
||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||
if ($content) {
|
||||
$content = base64_encode($content);
|
||||
$commands->push("echo '$content' | base64 -d > $path");
|
||||
$commands->push("chmod +x $path");
|
||||
}
|
||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||
}
|
||||
ray($commands->toArray());
|
||||
return instant_remote_process($commands, $server);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProjectSetting extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'project_id'
|
||||
];
|
||||
protected $guarded = [];
|
||||
|
||||
public function project()
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Notifications\Server\Revived;
|
||||
use App\Notifications\Server\Unreachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||
use Illuminate\Support\Str;
|
||||
@@ -224,6 +225,32 @@ class Server extends BaseModel
|
||||
$services = $this->services();
|
||||
return $applications->concat($databases)->concat($services->get());
|
||||
}
|
||||
public function stopUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker stop -t 0 $id"], $this);
|
||||
}
|
||||
public function restartUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker restart $id"], $this);
|
||||
}
|
||||
public function startUnmanaged($id)
|
||||
{
|
||||
return instant_remote_process(["docker start $id"], $this);
|
||||
}
|
||||
public function loadUnmanagedContainers()
|
||||
{
|
||||
$containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this);
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$containers = $containers->map(function ($container) {
|
||||
$labels = data_get($container, 'Labels');
|
||||
if (!str($labels)->contains("coolify.managed")) {
|
||||
return $container;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
$containers = $containers->filter();
|
||||
return collect($containers);
|
||||
}
|
||||
public function hasDefinedResources()
|
||||
{
|
||||
$applications = $this->applications()->count() > 0;
|
||||
@@ -244,13 +271,23 @@ class Server extends BaseModel
|
||||
$mysqls = data_get($standaloneDocker, 'mysqls', collect([]));
|
||||
$mariadbs = data_get($standaloneDocker, 'mariadbs', collect([]));
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||
})->filter(function ($item) {
|
||||
return data_get($item, 'name') !== 'coolify-db';
|
||||
})->flatten();
|
||||
}
|
||||
public function applications()
|
||||
{
|
||||
return $this->destinations()->map(function ($standaloneDocker) {
|
||||
$applications = $this->destinations()->map(function ($standaloneDocker) {
|
||||
return $standaloneDocker->applications;
|
||||
})->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()
|
||||
{
|
||||
@@ -300,7 +337,8 @@ class Server extends BaseModel
|
||||
{
|
||||
$standalone_docker = $this->hasMany(StandaloneDocker::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()
|
||||
@@ -360,10 +398,10 @@ class Server extends BaseModel
|
||||
}
|
||||
});
|
||||
if ($supported->count() === 1) {
|
||||
ray('supported');
|
||||
// ray('supported');
|
||||
return str($supported->first());
|
||||
} else {
|
||||
ray('not supported');
|
||||
// ray('not supported');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -390,7 +428,9 @@ class Server extends BaseModel
|
||||
if ($server->skipServer()) {
|
||||
return false;
|
||||
}
|
||||
$uptime = instant_remote_process(['uptime'], $server, false);
|
||||
// EC2 does not have `uptime` command, lol
|
||||
|
||||
$uptime = instant_remote_process(['ls /'], $server, false);
|
||||
if (!$uptime) {
|
||||
$server->settings()->update([
|
||||
'is_reachable' => false,
|
||||
@@ -428,11 +468,36 @@ class Server extends BaseModel
|
||||
}
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
instant_remote_process(["docker version"], $this);
|
||||
} catch (\Throwable $e) {
|
||||
$this->settings->is_usable = false;
|
||||
$this->settings->save();
|
||||
if ($throwError) {
|
||||
throw new \Exception('Server is not usable. Docker Engine is not running.');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
$this->settings->is_usable = true;
|
||||
$this->settings->save();
|
||||
$this->validateCoolifyNetwork(isSwarm: false, isBuildServer: $this->settings->is_build_server);
|
||||
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()
|
||||
{
|
||||
$swarmStatus = instant_remote_process(["docker info|grep -i swarm"], $this, false);
|
||||
|
||||
@@ -28,6 +28,48 @@ class Service extends BaseModel
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
public function status() {
|
||||
$foundRunning = false;
|
||||
$isDegraded = false;
|
||||
$foundRestaring = false;
|
||||
$applications = $this->applications;
|
||||
$databases = $this->databases;
|
||||
foreach ($applications as $application) {
|
||||
if ($application->exclude_from_status) {
|
||||
continue;
|
||||
}
|
||||
if (Str::of($application->status)->startsWith('running')) {
|
||||
$foundRunning = true;
|
||||
} else if (Str::of($application->status)->startsWith('restarting')) {
|
||||
$foundRestaring = true;
|
||||
} else {
|
||||
$isDegraded = true;
|
||||
}
|
||||
}
|
||||
foreach ($databases as $database) {
|
||||
if ($database->exclude_from_status) {
|
||||
continue;
|
||||
}
|
||||
if (Str::of($database->status)->startsWith('running')) {
|
||||
$foundRunning = true;
|
||||
} else if (Str::of($database->status)->startsWith('restarting')) {
|
||||
$foundRestaring = true;
|
||||
} else {
|
||||
$isDegraded = true;
|
||||
}
|
||||
}
|
||||
if ($foundRestaring) {
|
||||
return 'degraded';
|
||||
}
|
||||
if ($foundRunning && !$isDegraded) {
|
||||
return 'running';
|
||||
} else if ($foundRunning && $isDegraded) {
|
||||
return 'degraded';
|
||||
} else if (!$foundRunning && !$isDegraded) {
|
||||
return 'exited';
|
||||
}
|
||||
return 'exited';
|
||||
}
|
||||
public function extraFields()
|
||||
{
|
||||
$fields = collect([]);
|
||||
|
||||
@@ -23,6 +23,10 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
}
|
||||
public function isGzipEnabled()
|
||||
{
|
||||
return data_get($this, 'is_gzip_enabled', true);
|
||||
}
|
||||
public function type()
|
||||
{
|
||||
return 'service';
|
||||
|
||||
@@ -21,6 +21,10 @@ class ServiceDatabase extends BaseModel
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
}
|
||||
public function isGzipEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public function type()
|
||||
{
|
||||
return 'service';
|
||||
|
||||
@@ -43,11 +43,48 @@ class StandaloneMariadb extends BaseModel
|
||||
$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 project() {
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
|
||||
@@ -46,11 +46,48 @@ class StandaloneMongodb extends BaseModel
|
||||
$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 project() {
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
|
||||
@@ -43,11 +43,48 @@ class StandaloneMysql extends BaseModel
|
||||
$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 project() {
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
|
||||
@@ -43,11 +43,49 @@ class StandalonePostgresql extends BaseModel
|
||||
$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 project()
|
||||
{
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
|
||||
@@ -38,11 +38,49 @@ class StandaloneRedis extends BaseModel
|
||||
$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 project()
|
||||
{
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
|
||||
31
app/View/Components/ResourceView.php
Normal file
31
app/View/Components/ResourceView.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class ResourceView extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $wire = null,
|
||||
public ?string $logo = null,
|
||||
public ?string $documentation = null,
|
||||
public bool $upgrade = false,
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.resource-view');
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,11 @@ class Index extends Component
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
|
||||
public function __construct(
|
||||
public string $status = 'exited',
|
||||
)
|
||||
{
|
||||
//
|
||||
public $resource = null,
|
||||
public bool $showRefreshButton = true,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,8 +15,9 @@ class Services extends Component
|
||||
public function __construct(
|
||||
public Service $service,
|
||||
public string $complexStatus = 'exited',
|
||||
public bool $showRefreshButton = true
|
||||
) {
|
||||
$this->complexStatus = serviceStatus($service);
|
||||
$this->complexStatus = $service->status();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
7
bootstrap/helpers/api.php
Normal file
7
bootstrap/helpers/api.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
function get_team_id_from_token()
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
return data_get($token, 'team_id');
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
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)
|
||||
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, bool $only_this_server = false)
|
||||
{
|
||||
$application_id = $application->id;
|
||||
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
|
||||
@@ -37,7 +37,8 @@ function queue_application_deployment(Application $application, string $deployme
|
||||
'is_webhook' => $is_webhook,
|
||||
'restart_only' => $restart_only,
|
||||
'commit' => $commit,
|
||||
'git_type' => $git_type
|
||||
'git_type' => $git_type,
|
||||
'only_this_server' => $only_this_server
|
||||
]);
|
||||
|
||||
if ($no_questions_asked) {
|
||||
|
||||
@@ -13,6 +13,11 @@ const VALID_CRON_STRINGS = [
|
||||
const RESTART_MODE = 'unless-stopped';
|
||||
|
||||
const DATABASE_DOCKER_IMAGES = [
|
||||
'bitnami/mariadb',
|
||||
'bitnami/mongodb',
|
||||
'bitnami/mysql',
|
||||
'bitnami/postgresql',
|
||||
'bitnami/redis',
|
||||
'mysql',
|
||||
'mariadb',
|
||||
'postgres',
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Models\ServiceApplication;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
|
||||
{
|
||||
@@ -123,10 +122,14 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
||||
|
||||
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
||||
{
|
||||
$consistent_container_name = $application->settings->is_consistent_container_name_enabled;
|
||||
$now = now()->format('Hisu');
|
||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||
return $application->uuid . '-pr-' . $pull_request_id;
|
||||
} else {
|
||||
if ($consistent_container_name) {
|
||||
return $application->uuid;
|
||||
}
|
||||
return $application->uuid . '-' . $now;
|
||||
}
|
||||
}
|
||||
@@ -209,15 +212,48 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource,
|
||||
}
|
||||
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, ?bool $is_gzip_enabled = true, ?string $service_name = null)
|
||||
{
|
||||
$labels = collect([]);
|
||||
$labels->push('traefik.enable=true');
|
||||
$labels->push("traefik.http.middlewares.gzip.compress=true");
|
||||
$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) {
|
||||
try {
|
||||
$uuid = new Cuid2(7);
|
||||
// $uuid = new Cuid2(7);
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
@@ -228,6 +264,10 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
}
|
||||
$http_label = "http-{$loop}-{$uuid}";
|
||||
$https_label = "https-{$loop}-{$uuid}";
|
||||
if ($service_name) {
|
||||
$http_label = "http-{$loop}-{$uuid}-{$service_name}";
|
||||
$https_label = "https-{$loop}-{$uuid}-{$service_name}";
|
||||
}
|
||||
|
||||
if ($schema === 'https') {
|
||||
// Set labels for https
|
||||
@@ -239,11 +279,36 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
}
|
||||
if ($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 = collect(["{$https_label}-stripprefix"]);
|
||||
if ($is_gzip_enabled) {
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
if ($basic_auth && $basic_auth_middleware) {
|
||||
$middlewares->push($basic_auth_middleware);
|
||||
}
|
||||
if ($redirect && $redirect_middleware) {
|
||||
$middlewares->push($redirect_middleware);
|
||||
}
|
||||
if ($middlewares->isNotEmpty()) {
|
||||
$middlewares = $middlewares->join(',');
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares={$middlewares}");
|
||||
}
|
||||
} else {
|
||||
$labels->push("traefik.http.routers.{$https_label}.middlewares=gzip");
|
||||
$middlewares = collect([]);
|
||||
if ($is_gzip_enabled) {
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
if ($basic_auth && $basic_auth_middleware) {
|
||||
$middlewares->push($basic_auth_middleware);
|
||||
}
|
||||
if ($redirect && $redirect_middleware) {
|
||||
$middlewares->push($redirect_middleware);
|
||||
}
|
||||
if ($middlewares->isNotEmpty()) {
|
||||
$middlewares = $middlewares->join(',');
|
||||
$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.certresolver=letsencrypt");
|
||||
|
||||
@@ -267,16 +332,41 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
||||
}
|
||||
if ($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 = collect(["{$http_label}-stripprefix"]);
|
||||
if ($is_gzip_enabled) {
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
if ($basic_auth && $basic_auth_middleware) {
|
||||
$middlewares->push($basic_auth_middleware);
|
||||
}
|
||||
if ($redirect && $redirect_middleware) {
|
||||
$middlewares->push($redirect_middleware);
|
||||
}
|
||||
if ($middlewares->isNotEmpty()) {
|
||||
$middlewares = $middlewares->join(',');
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
|
||||
}
|
||||
} else {
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares=gzip");
|
||||
$middlewares = collect([]);
|
||||
if ($is_gzip_enabled) {
|
||||
$middlewares->push('gzip');
|
||||
}
|
||||
if ($basic_auth && $basic_auth_middleware) {
|
||||
$middlewares->push($basic_auth_middleware);
|
||||
}
|
||||
if ($redirect && $redirect_middleware) {
|
||||
$middlewares->push($redirect_middleware);
|
||||
}
|
||||
if ($middlewares->isNotEmpty()) {
|
||||
$middlewares = $middlewares->join(',');
|
||||
$labels->push("traefik.http.routers.{$http_label}.middlewares={$middlewares}");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $labels->sort();
|
||||
}
|
||||
function generateLabelsApplication(Application $application, ?ApplicationPreview $preview = null): array
|
||||
@@ -340,25 +430,20 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
'--cap-drop' => 'cap_drop',
|
||||
'--security-opt' => 'security_opt',
|
||||
'--sysctl' => 'sysctls',
|
||||
'--device' => 'devices',
|
||||
'--ulimit' => 'ulimits',
|
||||
'--device' => 'devices',
|
||||
'--init' => 'init',
|
||||
'--ulimit' => 'ulimits',
|
||||
'--privileged' => 'privileged',
|
||||
]);
|
||||
foreach ($matches as $match) {
|
||||
$option = $match[1];
|
||||
$value = isset($match[2]) && $match[2] !== '' ? $match[2] : true;
|
||||
if ($list_options->contains($option)) {
|
||||
$value = explode(',', $value);
|
||||
}
|
||||
if (array_key_exists($option, $options)) {
|
||||
if (is_array($options[$option])) {
|
||||
$options[$option][] = $value;
|
||||
} else {
|
||||
$options[$option] = [$options[$option], $value];
|
||||
}
|
||||
if (isset($match[2]) && $match[2] !== '') {
|
||||
$value = $match[2];
|
||||
$options[$option][] = $value;
|
||||
$options[$option] = array_unique($options[$option]);
|
||||
} else {
|
||||
$value = true;
|
||||
$options[$option] = $value;
|
||||
}
|
||||
}
|
||||
@@ -370,7 +455,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
}
|
||||
if ($option === '--ulimit') {
|
||||
$ulimits = collect([]);
|
||||
collect($value)->map(function ($ulimit) use ($ulimits){
|
||||
collect($value)->map(function ($ulimit) use ($ulimits) {
|
||||
$ulimit = explode('=', $ulimit);
|
||||
$type = $ulimit[0];
|
||||
$limits = explode(':', $ulimit[1]);
|
||||
@@ -381,7 +466,6 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
'soft' => $soft_limit,
|
||||
'hard' => $hard_limit
|
||||
]);
|
||||
|
||||
} else {
|
||||
$soft_limit = $ulimit[1];
|
||||
$ulimits->put($type, [
|
||||
|
||||
@@ -86,3 +86,8 @@ function get_installation_path(GithubApp $source)
|
||||
$installation_path = $github->html_url === 'https://github.com' ? 'apps' : 'github-apps';
|
||||
return "$github->html_url/$installation_path/$name/installations/new";
|
||||
}
|
||||
function get_permissions_path(GithubApp $source) {
|
||||
$github = GithubApp::where('uuid', $source->uuid)->first();
|
||||
$name = Str::of(Str::kebab($github->name));
|
||||
return "$github->html_url/settings/apps/$name/permissions";
|
||||
}
|
||||
|
||||
@@ -103,6 +103,7 @@ function generate_default_proxy_configuration(Server $server)
|
||||
"traefik.http.routers.traefik.entrypoints=http",
|
||||
"traefik.http.routers.traefik.service=api@internal",
|
||||
"traefik.http.services.traefik.loadbalancer.server.port=8080",
|
||||
"coolify.managed=true",
|
||||
];
|
||||
$config = [
|
||||
"version" => "3.8",
|
||||
@@ -139,7 +140,9 @@ function generate_default_proxy_configuration(Server $server)
|
||||
"--entrypoints.http.address=:80",
|
||||
"--entrypoints.https.address=:443",
|
||||
"--entrypoints.http.http.encodequerysemicolons=true",
|
||||
"--entryPoints.http.http2.maxConcurrentStreams=50",
|
||||
"--entrypoints.https.http.encodequerysemicolons=true",
|
||||
"--entryPoints.https.http2.maxConcurrentStreams=50",
|
||||
"--providers.docker.exposedbydefault=false",
|
||||
"--providers.file.directory=/traefik/dynamic/",
|
||||
"--providers.file.watch=true",
|
||||
|
||||
@@ -228,56 +228,6 @@ function refresh_server_connection(?PrivateKey $private_key = null)
|
||||
}
|
||||
}
|
||||
|
||||
// function validateServer(Server $server, bool $throwError = false)
|
||||
// {
|
||||
// try {
|
||||
// $uptime = instant_remote_process(['uptime'], $server, $throwError);
|
||||
// if (!$uptime) {
|
||||
// $server->settings->is_reachable = false;
|
||||
// $server->team->notify(new Unreachable($server));
|
||||
// $server->unreachable_notification_sent = true;
|
||||
// $server->save();
|
||||
// return [
|
||||
// "uptime" => null,
|
||||
// "dockerVersion" => null,
|
||||
// ];
|
||||
// }
|
||||
// $server->settings->is_reachable = true;
|
||||
// instant_remote_process(["docker ps"], $server, $throwError);
|
||||
// $dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
|
||||
// if (!$dockerVersion) {
|
||||
// $dockerVersion = null;
|
||||
// return [
|
||||
// "uptime" => $uptime,
|
||||
// "dockerVersion" => null,
|
||||
// ];
|
||||
// }
|
||||
// $dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||
// if (is_null($dockerVersion)) {
|
||||
// $server->settings->is_usable = false;
|
||||
// } else {
|
||||
// $server->settings->is_usable = true;
|
||||
// if (data_get($server, 'unreachable_notification_sent') === true) {
|
||||
// $server->team->notify(new Revived($server));
|
||||
// $server->unreachable_notification_sent = false;
|
||||
// $server->save();
|
||||
// }
|
||||
// }
|
||||
// return [
|
||||
// "uptime" => $uptime,
|
||||
// "dockerVersion" => $dockerVersion,
|
||||
// ];
|
||||
// } catch (\Throwable $e) {
|
||||
// $server->settings->is_reachable = false;
|
||||
// $server->settings->is_usable = false;
|
||||
// throw $e;
|
||||
// } finally {
|
||||
// if (data_get($server, 'settings')) {
|
||||
// $server->settings->save();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
function checkRequiredCommands(Server $server)
|
||||
{
|
||||
$commands = collect(["jq", "jc"]);
|
||||
|
||||
@@ -4,8 +4,8 @@ use App\Models\EnvironmentVariable;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
function replaceRegex(?string $name = null)
|
||||
@@ -21,49 +21,6 @@ function replaceVariables($variable)
|
||||
return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', '');
|
||||
}
|
||||
|
||||
function serviceStatus(Service $service)
|
||||
{
|
||||
$foundRunning = false;
|
||||
$isDegraded = false;
|
||||
$foundRestaring = false;
|
||||
$applications = $service->applications;
|
||||
$databases = $service->databases;
|
||||
foreach ($applications as $application) {
|
||||
if ($application->exclude_from_status) {
|
||||
continue;
|
||||
}
|
||||
if (Str::of($application->status)->startsWith('running')) {
|
||||
$foundRunning = true;
|
||||
} else if (Str::of($application->status)->startsWith('restarting')) {
|
||||
$foundRestaring = true;
|
||||
} else {
|
||||
$isDegraded = true;
|
||||
}
|
||||
}
|
||||
foreach ($databases as $database) {
|
||||
if ($database->exclude_from_status) {
|
||||
continue;
|
||||
}
|
||||
if (Str::of($database->status)->startsWith('running')) {
|
||||
$foundRunning = true;
|
||||
} else if (Str::of($database->status)->startsWith('restarting')) {
|
||||
$foundRestaring = true;
|
||||
} else {
|
||||
$isDegraded = true;
|
||||
}
|
||||
}
|
||||
if ($foundRestaring) {
|
||||
return 'degraded';
|
||||
}
|
||||
if ($foundRunning && !$isDegraded) {
|
||||
return 'running';
|
||||
} else if ($foundRunning && $isDegraded) {
|
||||
return 'degraded';
|
||||
} else if (!$foundRunning && !$isDegraded) {
|
||||
return 'exited';
|
||||
}
|
||||
return 'exited';
|
||||
}
|
||||
function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false)
|
||||
{
|
||||
// TODO: make this async
|
||||
@@ -138,12 +95,17 @@ function updateCompose($resource)
|
||||
// Update FQDN
|
||||
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper();
|
||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
||||
$fqdn = Url::fromString($resource->fqdn);
|
||||
$fqdn = $fqdn->getScheme() . '://' . $fqdn->getHost();
|
||||
if ($generatedEnv) {
|
||||
$generatedEnv->value = $resource->fqdn;
|
||||
$generatedEnv->value = $fqdn;
|
||||
$generatedEnv->save();
|
||||
}
|
||||
$variableName = "SERVICE_URL_" . Str::of($resource->name)->upper();
|
||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
||||
$url = Url::fromString($resource->fqdn);
|
||||
$url = $url->getHost();
|
||||
ray($url);
|
||||
if ($generatedEnv) {
|
||||
$url = Str::of($resource->fqdn)->after('://');
|
||||
$generatedEnv->value = $url;
|
||||
|
||||
@@ -104,13 +104,13 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
||||
ray($error);
|
||||
if ($error instanceof TooManyRequestsException) {
|
||||
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.";
|
||||
}
|
||||
if ($error instanceof UniqueConstraintViolationException) {
|
||||
if (isset($livewire)) {
|
||||
return $livewire->dispatch('error', "Duplicate entry found.", "Please use a different name.");
|
||||
return $livewire->dispatch('error', 'Duplicate entry found. Please use a different name.');
|
||||
}
|
||||
return "Duplicate entry found. Please use a different name.";
|
||||
}
|
||||
@@ -527,28 +527,32 @@ function getTopLevelNetworks(Service|Application $resource)
|
||||
$definedNetwork = collect([$resource->uuid]);
|
||||
$services = collect($services)->map(function ($service, $_) use ($topLevelNetworks, $definedNetwork) {
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
|
||||
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (!$networkExists) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
// Only add 'networks' key if 'network_mode' is not 'host'
|
||||
if (!$hasHostNetworkMode) {
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (!$networkExists) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||
return $value == $definedNetwork;
|
||||
});
|
||||
if (!$definedNetworkExists) {
|
||||
foreach ($definedNetwork as $network) {
|
||||
$topLevelNetworks->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true
|
||||
]);
|
||||
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||
return $value == $definedNetwork;
|
||||
});
|
||||
if (!$definedNetworkExists) {
|
||||
foreach ($definedNetwork as $network) {
|
||||
$topLevelNetworks->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -628,6 +632,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
$serviceVariables = collect(data_get($service, 'environment', []));
|
||||
$serviceLabels = collect(data_get($service, 'labels', []));
|
||||
$hasHostNetworkMode = data_get($service, 'network_mode') === 'host' ? true : false;
|
||||
if ($serviceLabels->count() > 0) {
|
||||
$removedLabels = collect([]);
|
||||
$serviceLabels = $serviceLabels->filter(function ($serviceLabel, $serviceLabelName) use ($removedLabels) {
|
||||
@@ -698,7 +703,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$savedService->image = $image;
|
||||
$savedService->save();
|
||||
}
|
||||
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
@@ -729,37 +733,39 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$savedService->ports = $collectedPorts->implode(',');
|
||||
$savedService->save();
|
||||
|
||||
// Add Coolify specific networks
|
||||
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||
return $value == $definedNetwork;
|
||||
});
|
||||
if (!$definedNetworkExists) {
|
||||
foreach ($definedNetwork as $network) {
|
||||
$topLevelNetworks->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true
|
||||
]);
|
||||
if (!$hasHostNetworkMode) {
|
||||
// Add Coolify specific networks
|
||||
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||
return $value == $definedNetwork;
|
||||
});
|
||||
if (!$definedNetworkExists) {
|
||||
foreach ($definedNetwork as $network) {
|
||||
$topLevelNetworks->put($network, [
|
||||
'name' => $network,
|
||||
'external' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$networks = collect();
|
||||
foreach ($serviceNetworks as $key => $serviceNetwork) {
|
||||
if (gettype($serviceNetwork) === 'string') {
|
||||
// networks:
|
||||
// - appwrite
|
||||
$networks->put($serviceNetwork, null);
|
||||
} else if (gettype($serviceNetwork) === 'array') {
|
||||
// networks:
|
||||
// default:
|
||||
// ipv4_address: 192.168.203.254
|
||||
// $networks->put($serviceNetwork, null);
|
||||
ray($key);
|
||||
$networks->put($key, $serviceNetwork);
|
||||
$networks = collect();
|
||||
foreach ($serviceNetworks as $key => $serviceNetwork) {
|
||||
if (gettype($serviceNetwork) === 'string') {
|
||||
// networks:
|
||||
// - appwrite
|
||||
$networks->put($serviceNetwork, null);
|
||||
} else if (gettype($serviceNetwork) === 'array') {
|
||||
// networks:
|
||||
// default:
|
||||
// ipv4_address: 192.168.203.254
|
||||
// $networks->put($serviceNetwork, null);
|
||||
ray($key);
|
||||
$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
|
||||
if ($serviceVolumes->count() > 0) {
|
||||
@@ -782,14 +788,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$source = data_get_str($volume, 'source');
|
||||
$target = data_get_str($volume, 'target');
|
||||
$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();
|
||||
if ($foundConfig) {
|
||||
$contentNotNull = data_get($foundConfig, 'content');
|
||||
if ($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') {
|
||||
@@ -966,7 +972,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
]);
|
||||
}
|
||||
if (!$isDatabase) {
|
||||
if ($command->value() === 'FQDN' && is_null($savedService->fqdn)) {
|
||||
if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && !$foundEnv) {
|
||||
$savedService->fqdn = $fqdn;
|
||||
$savedService->save();
|
||||
}
|
||||
@@ -1030,7 +1036,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||
if (!$isDatabase && $fqdns->count() > 0) {
|
||||
if ($fqdns) {
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($resource->uuid, $fqdns, true, serviceLabels: $serviceLabels, is_gzip_enabled: $savedService->isGzipEnabled(), service_name: $serviceName));
|
||||
}
|
||||
}
|
||||
if ($resource->server->isLogDrainEnabled() && $savedService->isLogDrainEnabled()) {
|
||||
@@ -1054,6 +1060,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
data_set($service, 'container_name', $containerName);
|
||||
data_forget($service, 'volumes.*.content');
|
||||
data_forget($service, 'volumes.*.isDirectory');
|
||||
data_forget($service, 'volumes.*.is_directory');
|
||||
|
||||
// Remove unnecessary variables from service.environment
|
||||
// $withoutServiceEnvs = collect([]);
|
||||
// collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
|
||||
@@ -1469,7 +1477,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
return $preview_fqdn;
|
||||
});
|
||||
}
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns));
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($uuid, $fqdns, serviceLabels: $serviceLabels));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1690,7 +1698,7 @@ function check_fqdn_usage(ServiceApplication|Application $own_resource)
|
||||
$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.");
|
||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource:<br> {$app->name}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
803
composer.lock
generated
803
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -65,7 +65,7 @@ return [
|
||||
'driver' => 'redis',
|
||||
'connection' => 'default',
|
||||
'queue' => env('REDIS_QUEUE', 'default'),
|
||||
'retry_after' => 3600,
|
||||
'retry_after' => 86400,
|
||||
'block_for' => null,
|
||||
'after_commit' => true,
|
||||
],
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.211',
|
||||
'release' => '4.0.0-beta.223',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.211';
|
||||
return '4.0.0-beta.223';
|
||||
|
||||
@@ -24,7 +24,6 @@ return new class extends Migration
|
||||
$table->string('taggable_type');
|
||||
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
|
||||
$table->unique(['tag_id', 'taggable_id', 'taggable_type'], 'taggable_unique'); // Composite unique index
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user