mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-05 20:52:11 +00:00
Compare commits
191 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cc86a3c82 | ||
|
|
f1e5b61970 | ||
|
|
65380646f7 | ||
|
|
189a8347ed | ||
|
|
e96e8f6fec | ||
|
|
38299ab507 | ||
|
|
f134171855 | ||
|
|
320204d854 | ||
|
|
b68199a482 | ||
|
|
6f4436fd5e | ||
|
|
0d8cc19698 | ||
|
|
a3a1ff69e1 | ||
|
|
5df7e23aa4 | ||
|
|
35d9691b3f | ||
|
|
465f649641 | ||
|
|
d909e7d802 | ||
|
|
06db6b8502 | ||
|
|
12261b9082 | ||
|
|
583ec432e8 | ||
|
|
8ffbccf7db | ||
|
|
439fe43a04 | ||
|
|
7fd9a799b5 | ||
|
|
7459ab22d1 | ||
|
|
133a68f3eb | ||
|
|
3224110583 | ||
|
|
810488b115 | ||
|
|
b2276147ad | ||
|
|
6c1293c63e | ||
|
|
526d675272 | ||
|
|
14b2442d40 | ||
|
|
d6d194d414 | ||
|
|
0e99f97855 | ||
|
|
14dc933219 | ||
|
|
9497f123b4 | ||
|
|
6feb439d0a | ||
|
|
e4ca5ee5f5 | ||
|
|
f21c12f39b | ||
|
|
6c1e50a914 | ||
|
|
da064def7a | ||
|
|
3af3fa5773 | ||
|
|
005bd55fb2 | ||
|
|
82a5b4c55d | ||
|
|
b8e95b2099 | ||
|
|
8ea50dc029 | ||
|
|
ec191af874 | ||
|
|
d98c742aff | ||
|
|
2529496594 | ||
|
|
1b6114036a | ||
|
|
b33fb6c39a | ||
|
|
0a6826af58 | ||
|
|
1c7034ff78 | ||
|
|
7e11698c55 | ||
|
|
1c4eb31d59 | ||
|
|
b4b6a4294a | ||
|
|
4c031a7c05 | ||
|
|
997a262b6c | ||
|
|
c0e88df3e8 | ||
|
|
85e1cbad53 | ||
|
|
c37398af72 | ||
|
|
19cfe4e514 | ||
|
|
23a1b1925f | ||
|
|
1fb8d1e14c | ||
|
|
804c70b575 | ||
|
|
548c4a4c64 | ||
|
|
2978042162 | ||
|
|
4225ec7060 | ||
|
|
893339fc8e | ||
|
|
356e7b57d2 | ||
|
|
4ee1f1a507 | ||
|
|
7d64df60cd | ||
|
|
eb3a4ca157 | ||
|
|
a7b5157fa6 | ||
|
|
793e6d19eb | ||
|
|
674fa4d09c | ||
|
|
0089e86dd1 | ||
|
|
e1d802b507 | ||
|
|
9927b71af9 | ||
|
|
b1c0f105ab | ||
|
|
35cae1d4dc | ||
|
|
3dab3365e2 | ||
|
|
1bdc7c87ba | ||
|
|
cab8ad0ca0 | ||
|
|
43409f3ff0 | ||
|
|
a5dd4cab52 | ||
|
|
a815240f4e | ||
|
|
2a44e7c5bd | ||
|
|
28c7e439b1 | ||
|
|
4396c786b4 | ||
|
|
b67bb8595f | ||
|
|
bec47487dd | ||
|
|
b110d0c12b | ||
|
|
ae425475b4 | ||
|
|
dc6aee44b3 | ||
|
|
4ffea311e8 | ||
|
|
77a6a6e46a | ||
|
|
2278ba31e7 | ||
|
|
aaeec3d340 | ||
|
|
2cbe530b7e | ||
|
|
6ada6d145c | ||
|
|
0f55e83591 | ||
|
|
4017ea7b65 | ||
|
|
a85066c644 | ||
|
|
b08d38f339 | ||
|
|
d4f4632461 | ||
|
|
666aa041f4 | ||
|
|
1c565fd502 | ||
|
|
7de2b8cbd7 | ||
|
|
852e906736 | ||
|
|
5778466947 | ||
|
|
7006239b0d | ||
|
|
49d011574d | ||
|
|
046a358ae0 | ||
|
|
d23f5af957 | ||
|
|
20a3f4b200 | ||
|
|
73acda833e | ||
|
|
fa895db76e | ||
|
|
88f33be5b6 | ||
|
|
21612cccf7 | ||
|
|
39a7332343 | ||
|
|
21825876fb | ||
|
|
aaee887d3e | ||
|
|
cb44373eff | ||
|
|
4e6ea4f584 | ||
|
|
62a93d3e51 | ||
|
|
f60c281e80 | ||
|
|
43c40cdb09 | ||
|
|
c851262d81 | ||
|
|
91783ccc3e | ||
|
|
6ba3d5f86e | ||
|
|
a9a20755a9 | ||
|
|
d2693c1ac8 | ||
|
|
aaa6f434a9 | ||
|
|
314a3ac83f | ||
|
|
36e177479e | ||
|
|
cbeebed6c9 | ||
|
|
4b905dbfad | ||
|
|
6072e7efc7 | ||
|
|
19097c6692 | ||
|
|
d37f63c63c | ||
|
|
574bafd950 | ||
|
|
2b805f869a | ||
|
|
36c4be1d17 | ||
|
|
f2d82e16d6 | ||
|
|
c6658e1ac7 | ||
|
|
22a7d85e58 | ||
|
|
b3421b47b6 | ||
|
|
b5247f77ec | ||
|
|
e63e806572 | ||
|
|
62b84add36 | ||
|
|
858ae1266f | ||
|
|
3ae990aa40 | ||
|
|
deb4b16ae1 | ||
|
|
b37dc4c73e | ||
|
|
6b08100819 | ||
|
|
2c45e7146b | ||
|
|
7c4a722d72 | ||
|
|
f4bccefaba | ||
|
|
491bb93e95 | ||
|
|
f35700c9ee | ||
|
|
bd26aca3d9 | ||
|
|
71d24773b6 | ||
|
|
781bd29f40 | ||
|
|
fd4dd1edfa | ||
|
|
d2db26cb5e | ||
|
|
01977839f7 | ||
|
|
0116892b6b | ||
|
|
4d88873524 | ||
|
|
8e46c0186d | ||
|
|
1b0e589aab | ||
|
|
02ba149e26 | ||
|
|
2981aa876c | ||
|
|
cbcc7f6d88 | ||
|
|
906a3dc9b4 | ||
|
|
01abc26316 | ||
|
|
2dfe43fc3c | ||
|
|
f71861300a | ||
|
|
52d7841334 | ||
|
|
0c40c0d795 | ||
|
|
25f0a8f0b7 | ||
|
|
65a618d019 | ||
|
|
7af151d44e | ||
|
|
7028391e57 | ||
|
|
cbae0845e7 | ||
|
|
0e512962c6 | ||
|
|
ac694b855b | ||
|
|
490d45e788 | ||
|
|
b0863eb5ea | ||
|
|
2f87deb10b | ||
|
|
65253ca54e | ||
|
|
e7e85456ea | ||
|
|
440baf6009 |
13
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
13
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -1,6 +1,6 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
description: 'Create a new bug report.'
|
description: "Create a new bug report."
|
||||||
title: '[Bug]: '
|
title: "[Bug]: "
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -35,3 +35,12 @@ body:
|
|||||||
description: Coolify's version (see top of your screen).
|
description: Coolify's version (see top of your screen).
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Cloud?
|
||||||
|
description: "Are you using the cloud version of Coolify?"
|
||||||
|
options:
|
||||||
|
- label: 'Yes'
|
||||||
|
required: false
|
||||||
|
- label: 'No'
|
||||||
|
required: false
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
[](https://console.algora.io/org/coollabsio/bounties/new)
|
[](https://console.algora.io/org/coollabsio/bounties/new)
|
||||||
[](https://console.algora.io/org/coollabsio/bounties?status=open)
|
|
||||||
[](https://console.algora.io/org/coollabsio/bounties?status=completed)
|
|
||||||
|
|
||||||
# About the Project
|
# About the Project
|
||||||
|
|
||||||
@@ -49,6 +47,8 @@ Special thanks to our biggest sponsors!
|
|||||||
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
|
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
|
||||||
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
|
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
|
||||||
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
||||||
|
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
|
||||||
|
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
|
||||||
|
|
||||||
## Github Sponsors ($40+)
|
## Github Sponsors ($40+)
|
||||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class StopApplication
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Application $application)
|
public function handle(Application $application, bool $previewDeployments = false)
|
||||||
{
|
{
|
||||||
if ($application->destination->server->isSwarm()) {
|
if ($application->destination->server->isSwarm()) {
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
instant_remote_process(["docker stack rm {$application->uuid}"], $application->destination->server);
|
||||||
@@ -26,7 +26,12 @@ class StopApplication
|
|||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
return 'Server is not functional';
|
return 'Server is not functional';
|
||||||
}
|
}
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
if ($previewDeployments) {
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, includePullrequests: true);
|
||||||
|
} else {
|
||||||
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
|
}
|
||||||
|
ray($containers);
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
$containerName = data_get($container, 'Names');
|
$containerName = data_get($container, 'Names');
|
||||||
@@ -38,6 +43,12 @@ class StopApplication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($application->build_pack === 'dockercompose') {
|
||||||
|
// remove network
|
||||||
|
$uuid = $application->uuid;
|
||||||
|
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||||
|
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ class CheckConfiguration
|
|||||||
"cat $proxy_path/docker-compose.yml",
|
"cat $proxy_path/docker-compose.yml",
|
||||||
];
|
];
|
||||||
$proxy_configuration = instant_remote_process($payload, $server, false);
|
$proxy_configuration = instant_remote_process($payload, $server, false);
|
||||||
|
|
||||||
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
||||||
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ class StartProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $async = true): string|Activity
|
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$proxyType = $server->proxyType();
|
$proxyType = $server->proxyType();
|
||||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
|
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
|
||||||
return 'OK';
|
return 'OK';
|
||||||
}
|
}
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class CleanupDocker
|
|||||||
|
|
||||||
public function handle(Server $server, bool $force = true)
|
public function handle(Server $server, bool $force = true)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// cleanup docker images, containers, and builder caches
|
||||||
if ($force) {
|
if ($force) {
|
||||||
instant_remote_process(['docker image prune -af'], $server, false);
|
instant_remote_process(['docker image prune -af'], $server, false);
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||||
@@ -20,5 +22,15 @@ class CleanupDocker
|
|||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||||
instant_remote_process(['docker builder prune -f'], $server, false);
|
instant_remote_process(['docker builder prune -f'], $server, false);
|
||||||
}
|
}
|
||||||
|
// cleanup networks
|
||||||
|
// $networks = collectDockerNetworksByServer($server);
|
||||||
|
// $proxyNetworks = collectProxyDockerNetworksByServer($server);
|
||||||
|
// $diff = $proxyNetworks->diff($networks);
|
||||||
|
// if ($diff->count() > 0) {
|
||||||
|
// $diff->map(function ($network) use ($server) {
|
||||||
|
// instant_remote_process(["docker network disconnect $network coolify-proxy"], $server);
|
||||||
|
// instant_remote_process(["docker network rm $network"], $server);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
app/Actions/Server/ValidateServer.php
Normal file
67
app/Actions/Server/ValidateServer.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class ValidateServer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public ?string $uptime = null;
|
||||||
|
|
||||||
|
public ?string $error = null;
|
||||||
|
|
||||||
|
public ?string $supported_os_type = null;
|
||||||
|
|
||||||
|
public ?string $docker_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_compose_installed = null;
|
||||||
|
|
||||||
|
public ?string $docker_version = null;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => null,
|
||||||
|
]);
|
||||||
|
['uptime' => $this->uptime, 'error' => $error] = $server->validateConnection();
|
||||||
|
if (! $this->uptime) {
|
||||||
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->supported_os_type = $server->validateOS();
|
||||||
|
if (! $this->supported_os_type) {
|
||||||
|
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->docker_installed = $server->validateDockerEngine();
|
||||||
|
$this->docker_compose_installed = $server->validateDockerCompose();
|
||||||
|
if (! $this->docker_installed || ! $this->docker_compose_installed) {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
$this->docker_version = $server->validateDockerEngineVersion();
|
||||||
|
|
||||||
|
if ($this->docker_version) {
|
||||||
|
return 'OK';
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="text-black underline dark:text-white" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
$server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
throw new \Exception($this->error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,18 +19,16 @@ class StopService
|
|||||||
ray('Stopping service: '.$service->name);
|
ray('Stopping service: '.$service->name);
|
||||||
$applications = $service->applications()->get();
|
$applications = $service->applications()->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
|
||||||
$application->update(['status' => 'exited']);
|
$application->update(['status' => 'exited']);
|
||||||
}
|
}
|
||||||
$dbs = $service->databases()->get();
|
$dbs = $service->databases()->get();
|
||||||
foreach ($dbs as $db) {
|
foreach ($dbs as $db) {
|
||||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server, false);
|
||||||
$db->update(['status' => 'exited']);
|
$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 disconnect {$service->uuid} coolify-proxy"], $service->server);
|
||||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
|
||||||
// TODO: make notification for databases
|
|
||||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
echo $e->getMessage();
|
echo $e->getMessage();
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class CleanupUnreachableServers extends Command
|
|||||||
if ($servers->count() > 0) {
|
if ($servers->count() > 0) {
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
// send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up...");
|
||||||
$server->update([
|
$server->update([
|
||||||
'ip' => '1.2.3.4',
|
'ip' => '1.2.3.4',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Actions\Server\StopSentinel;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
@@ -23,6 +24,16 @@ class Init extends Command
|
|||||||
{
|
{
|
||||||
$this->alive();
|
$this->alive();
|
||||||
get_public_ips();
|
get_public_ips();
|
||||||
|
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||||
|
$servers = Server::all();
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
$server->settings->update(['is_metrics_enabled' => false]);
|
||||||
|
if ($server->isFunctional()) {
|
||||||
|
StopSentinel::dispatch($server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$full_cleanup = $this->option('full-cleanup');
|
$full_cleanup = $this->option('full-cleanup');
|
||||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use App\Jobs\CheckLogDrainContainerJob;
|
|||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullCoolifyImageJob;
|
use App\Jobs\PullCoolifyImageJob;
|
||||||
use App\Jobs\PullHelperImageJob;
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Jobs\PullSentinelImageJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
@@ -87,6 +88,7 @@ class Kernel extends ConsoleKernel
|
|||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
||||||
|
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class ServiceStatusChanged implements ShouldBroadcast
|
|||||||
|
|
||||||
public function broadcastOn(): ?array
|
public function broadcastOn(): ?array
|
||||||
{
|
{
|
||||||
if ($this->userId) {
|
if (! is_null($this->userId)) {
|
||||||
return [
|
return [
|
||||||
new PrivateChannel("user.{$this->userId}"),
|
new PrivateChannel("user.{$this->userId}"),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class Handler extends ExceptionHandler
|
|||||||
return response()->json(['message' => $exception->getMessage()], 401);
|
return response()->json(['message' => $exception->getMessage()], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
return redirect()->guest($exception->redirectTo($request) ?? route('login'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +65,7 @@ class Handler extends ExceptionHandler
|
|||||||
if ($e instanceof RuntimeException) {
|
if ($e instanceof RuntimeException) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = \App\Models\InstanceSettings::get();
|
||||||
if ($this->settings->do_not_track) {
|
if ($this->settings->do_not_track) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -620,7 +620,7 @@ class ApplicationsController extends Controller
|
|||||||
|
|
||||||
private function create_application(Request $request, $type)
|
private function create_application(Request $request, $type)
|
||||||
{
|
{
|
||||||
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
|
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
@@ -683,6 +683,9 @@ class ApplicationsController extends Controller
|
|||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
||||||
}
|
}
|
||||||
|
if ($request->build_pack === 'dockercompose') {
|
||||||
|
$request->offsetSet('ports_exposes', '80');
|
||||||
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
sharedDataApplications(),
|
sharedDataApplications(),
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
@@ -729,8 +732,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->save();
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
|
$application->save();
|
||||||
|
}
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
@@ -756,6 +761,9 @@ class ApplicationsController extends Controller
|
|||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
||||||
}
|
}
|
||||||
|
if ($request->build_pack === 'dockercompose') {
|
||||||
|
$request->offsetSet('ports_exposes', '80');
|
||||||
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
sharedDataApplications(),
|
sharedDataApplications(),
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
@@ -820,8 +828,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->source_id = $githubApp->id;
|
$application->source_id = $githubApp->id;
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->save();
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
|
$application->save();
|
||||||
|
}
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
@@ -847,6 +857,9 @@ class ApplicationsController extends Controller
|
|||||||
if (! $request->has('name')) {
|
if (! $request->has('name')) {
|
||||||
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
|
||||||
}
|
}
|
||||||
|
if ($request->build_pack === 'dockercompose') {
|
||||||
|
$request->offsetSet('ports_exposes', '80');
|
||||||
|
}
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
sharedDataApplications(),
|
sharedDataApplications(),
|
||||||
'git_repository' => 'string|required',
|
'git_repository' => 'string|required',
|
||||||
@@ -907,8 +920,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->environment_id = $environment->id;
|
$application->environment_id = $environment->id;
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->save();
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
|
$application->save();
|
||||||
|
}
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
@@ -987,8 +1002,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->git_branch = 'main';
|
$application->git_branch = 'main';
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->save();
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
|
$application->save();
|
||||||
|
}
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
@@ -1043,8 +1060,10 @@ class ApplicationsController extends Controller
|
|||||||
$application->git_branch = 'main';
|
$application->git_branch = 'main';
|
||||||
$application->save();
|
$application->save();
|
||||||
$application->refresh();
|
$application->refresh();
|
||||||
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->save();
|
$application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
|
$application->save();
|
||||||
|
}
|
||||||
$application->isConfigurationChanged(true);
|
$application->isConfigurationChanged(true);
|
||||||
|
|
||||||
if ($instantDeploy) {
|
if ($instantDeploy) {
|
||||||
@@ -1231,6 +1250,16 @@ class ApplicationsController extends Controller
|
|||||||
format: 'uuid',
|
format: 'uuid',
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'cleanup',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Delete configurations and volumes.',
|
||||||
|
required: false,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
)
|
||||||
|
),
|
||||||
],
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -1264,15 +1293,12 @@ class ApplicationsController extends Controller
|
|||||||
public function delete_by_uuid(Request $request)
|
public function delete_by_uuid(Request $request)
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
$cleanup = $request->query->get('cleanup') ?? false;
|
$cleanup = filter_var($request->query->get('cleanup', true), FILTER_VALIDATE_BOOLEAN);
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
|
if (! $request->uuid) {
|
||||||
if ($request->collect()->count() == 0) {
|
return response()->json(['message' => 'UUID is required.'], 404);
|
||||||
return response()->json([
|
|
||||||
'message' => 'Invalid request.',
|
|
||||||
], 400);
|
|
||||||
}
|
}
|
||||||
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
|
||||||
|
|
||||||
@@ -1281,7 +1307,10 @@ class ApplicationsController extends Controller
|
|||||||
'message' => 'Application not found',
|
'message' => 'Application not found',
|
||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
DeleteResourceJob::dispatch($application, $cleanup);
|
DeleteResourceJob::dispatch(
|
||||||
|
resource: $application,
|
||||||
|
deleteConfigurations: $cleanup,
|
||||||
|
deleteVolumes: $cleanup);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Application deletion request queued.',
|
'message' => 'Application deletion request queued.',
|
||||||
@@ -1475,8 +1504,10 @@ class ApplicationsController extends Controller
|
|||||||
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
$fqdn = str($fqdn)->replaceEnd(',', '')->trim();
|
||||||
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
$fqdn = str($fqdn)->replaceStart(',', '')->trim();
|
||||||
$application->fqdn = $fqdn;
|
$application->fqdn = $fqdn;
|
||||||
$customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
if (! $application->settings->is_container_label_readonly_enabled) {
|
||||||
$application->custom_labels = base64_encode($customLabels);
|
$customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
|
||||||
|
$application->custom_labels = base64_encode($customLabels);
|
||||||
|
}
|
||||||
$request->offsetUnset('domains');
|
$request->offsetUnset('domains');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Actions\Database\StopDatabase;
|
|||||||
use App\Actions\Database\StopDatabaseProxy;
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
use App\Enums\NewDatabaseTypes;
|
use App\Enums\NewDatabaseTypes;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Jobs\DeleteResourceJob;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -1528,6 +1529,16 @@ class DatabasesController extends Controller
|
|||||||
format: 'uuid',
|
format: 'uuid',
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'cleanup',
|
||||||
|
in: 'query',
|
||||||
|
description: 'Delete configurations and volumes.',
|
||||||
|
required: false,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
)
|
||||||
|
),
|
||||||
],
|
],
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
@@ -1561,6 +1572,7 @@ class DatabasesController extends Controller
|
|||||||
public function delete_by_uuid(Request $request)
|
public function delete_by_uuid(Request $request)
|
||||||
{
|
{
|
||||||
$teamId = getTeamIdFromToken();
|
$teamId = getTeamIdFromToken();
|
||||||
|
$cleanup = filter_var($request->query->get('cleanup', true), FILTER_VALIDATE_BOOLEAN);
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
@@ -1571,8 +1583,10 @@ class DatabasesController extends Controller
|
|||||||
if (! $database) {
|
if (! $database) {
|
||||||
return response()->json(['message' => 'Database not found.'], 404);
|
return response()->json(['message' => 'Database not found.'], 404);
|
||||||
}
|
}
|
||||||
StopDatabase::dispatch($database);
|
DeleteResourceJob::dispatch(
|
||||||
$database->forceDelete();
|
resource: $database,
|
||||||
|
deleteConfigurations: $cleanup,
|
||||||
|
deleteVolumes: $cleanup);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Database deletion request queued.',
|
'message' => 'Database deletion request queued.',
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
@@ -85,7 +84,7 @@ class OtherController extends Controller
|
|||||||
if ($teamId !== '0') {
|
if ($teamId !== '0') {
|
||||||
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
|
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
|
||||||
}
|
}
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$settings->update(['is_api_enabled' => true]);
|
$settings->update(['is_api_enabled' => true]);
|
||||||
|
|
||||||
return response()->json(['message' => 'API enabled.'], 200);
|
return response()->json(['message' => 'API enabled.'], 200);
|
||||||
@@ -136,7 +135,7 @@ class OtherController extends Controller
|
|||||||
if ($teamId !== '0') {
|
if ($teamId !== '0') {
|
||||||
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
|
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
|
||||||
}
|
}
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$settings->update(['is_api_enabled' => false]);
|
$settings->update(['is_api_enabled' => false]);
|
||||||
|
|
||||||
return response()->json(['message' => 'API disabled.'], 200);
|
return response()->json(['message' => 'API disabled.'], 200);
|
||||||
|
|||||||
@@ -135,8 +135,14 @@ class ProjectController extends Controller
|
|||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
if (! $request->uuid) {
|
||||||
$environment = $project->environments()->whereName(request()->environment_name)->first();
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
if (! $request->environment_name) {
|
||||||
|
return response()->json(['message' => 'Environment name is required.'], 422);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
$environment = $project->environments()->whereName($request->environment_name)->first();
|
||||||
if (! $environment) {
|
if (! $environment) {
|
||||||
return response()->json(['message' => 'Environment not found.'], 404);
|
return response()->json(['message' => 'Environment not found.'], 404);
|
||||||
}
|
}
|
||||||
@@ -144,4 +150,276 @@ class ProjectController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($environment));
|
return response()->json(serializeApiResponse($environment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create Project.',
|
||||||
|
path: '/projects',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Project created.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'description' => 'The name of the project.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Project created.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the project.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_project(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|required',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = Project::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'team_id' => $teamId,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $project->uuid,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update Project.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Project updated.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the project.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the project.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Project updated.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os'],
|
||||||
|
'name' => ['type' => 'string', 'example' => 'Project Name'],
|
||||||
|
'description' => ['type' => 'string', 'example' => 'Project Description'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function update_project(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|nullable',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$uuid = $request->uuid;
|
||||||
|
if (! $uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->update($request->only($allowedFields));
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $project->uuid,
|
||||||
|
'name' => $project->name,
|
||||||
|
'description' => $project->description,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete project by UUID.',
|
||||||
|
path: '/projects/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Projects'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the application.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Project deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Project deleted.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_project(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
$project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $project) {
|
||||||
|
return response()->json(['message' => 'Project not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($project->resource_count() > 0) {
|
||||||
|
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Project deleted.']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Server\ValidateServer;
|
||||||
|
use App\Enums\ProxyStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\PrivateKey;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Server as ModelsServer;
|
use App\Models\Server as ModelsServer;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -76,7 +79,7 @@ class ServersController extends Controller
|
|||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return invalidTokenResponse();
|
return invalidTokenResponse();
|
||||||
}
|
}
|
||||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port', 'description')->get()->load(['settings'])->map(function ($server) {
|
||||||
$server['is_reachable'] = $server->settings->is_reachable;
|
$server['is_reachable'] = $server->settings->is_reachable;
|
||||||
$server['is_usable'] = $server->settings->is_usable;
|
$server['is_usable'] = $server->settings->is_usable;
|
||||||
|
|
||||||
@@ -301,7 +304,7 @@ class ServersController extends Controller
|
|||||||
$projects = Project::where('team_id', $teamId)->get();
|
$projects = Project::where('team_id', $teamId)->get();
|
||||||
$domains = collect();
|
$domains = collect();
|
||||||
$applications = $projects->pluck('applications')->flatten();
|
$applications = $projects->pluck('applications')->flatten();
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if ($applications->count() > 0) {
|
if ($applications->count() > 0) {
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
$ip = $application->destination->server->ip;
|
$ip = $application->destination->server->ip;
|
||||||
@@ -393,4 +396,390 @@ class ServersController extends Controller
|
|||||||
|
|
||||||
return response()->json(serializeApiResponse($domains));
|
return response()->json(serializeApiResponse($domains));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[OA\Post(
|
||||||
|
summary: 'Create',
|
||||||
|
description: 'Create Server.',
|
||||||
|
path: '/servers',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Server created.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'example' => 'My Server', 'description' => 'The name of the server.'],
|
||||||
|
'description' => ['type' => 'string', 'example' => 'My Server Description', 'description' => 'The description of the server.'],
|
||||||
|
'ip' => ['type' => 'string', 'example' => '127.0.0.1', 'description' => 'The IP of the server.'],
|
||||||
|
'port' => ['type' => 'integer', 'example' => 22, 'description' => 'The port of the server.'],
|
||||||
|
'user' => ['type' => 'string', 'example' => 'root', 'description' => 'The user of the server.'],
|
||||||
|
'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'],
|
||||||
|
'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'],
|
||||||
|
'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server created.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the server.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function create_server(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'ip' => 'string|required',
|
||||||
|
'port' => 'integer|nullable',
|
||||||
|
'private_key_uuid' => 'string|required',
|
||||||
|
'user' => 'string|nullable',
|
||||||
|
'is_build_server' => 'boolean|nullable',
|
||||||
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
if (! $request->name) {
|
||||||
|
$request->offsetSet('name', generate_random_name());
|
||||||
|
}
|
||||||
|
if (! $request->user) {
|
||||||
|
$request->offsetSet('user', 'root');
|
||||||
|
}
|
||||||
|
if (is_null($request->port)) {
|
||||||
|
$request->offsetSet('port', 22);
|
||||||
|
}
|
||||||
|
if (is_null($request->is_build_server)) {
|
||||||
|
$request->offsetSet('is_build_server', false);
|
||||||
|
}
|
||||||
|
if (is_null($request->instant_validate)) {
|
||||||
|
$request->offsetSet('instant_validate', false);
|
||||||
|
}
|
||||||
|
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
|
||||||
|
if (! $privateKey) {
|
||||||
|
return response()->json(['message' => 'Private key not found.'], 404);
|
||||||
|
}
|
||||||
|
$allServers = ModelsServer::whereIp($request->ip)->get();
|
||||||
|
if ($allServers->count() > 0) {
|
||||||
|
return response()->json(['message' => 'Server with this IP already exists.'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$server = ModelsServer::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'description' => $request->description,
|
||||||
|
'ip' => $request->ip,
|
||||||
|
'port' => $request->port,
|
||||||
|
'user' => $request->user,
|
||||||
|
'private_key_id' => $privateKey->id,
|
||||||
|
'team_id' => $teamId,
|
||||||
|
'proxy' => [
|
||||||
|
'type' => ProxyTypes::TRAEFIK_V2->value,
|
||||||
|
'status' => ProxyStatus::EXITED->value,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_build_server' => $request->is_build_server,
|
||||||
|
]);
|
||||||
|
if ($request->instant_validate) {
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'uuid' => $server->uuid,
|
||||||
|
])->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Patch(
|
||||||
|
summary: 'Update',
|
||||||
|
description: 'Update Server.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
requestBody: new OA\RequestBody(
|
||||||
|
required: true,
|
||||||
|
description: 'Server updated.',
|
||||||
|
content: new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'name' => ['type' => 'string', 'description' => 'The name of the server.'],
|
||||||
|
'description' => ['type' => 'string', 'description' => 'The description of the server.'],
|
||||||
|
'ip' => ['type' => 'string', 'description' => 'The IP of the server.'],
|
||||||
|
'port' => ['type' => 'integer', 'description' => 'The port of the server.'],
|
||||||
|
'user' => ['type' => 'string', 'description' => 'The user of the server.'],
|
||||||
|
'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'],
|
||||||
|
'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'],
|
||||||
|
'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server updated.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'array',
|
||||||
|
items: new OA\Items(ref: '#/components/schemas/Server')
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function update_server(Request $request)
|
||||||
|
{
|
||||||
|
$allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate'];
|
||||||
|
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$return = validateIncomingRequest($request);
|
||||||
|
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
$validator = customApiValidator($request->all(), [
|
||||||
|
'name' => 'string|max:255|nullable',
|
||||||
|
'description' => 'string|nullable',
|
||||||
|
'ip' => 'string|nullable',
|
||||||
|
'port' => 'integer|nullable',
|
||||||
|
'private_key_uuid' => 'string|nullable',
|
||||||
|
'user' => 'string|nullable',
|
||||||
|
'is_build_server' => 'boolean|nullable',
|
||||||
|
'instant_validate' => 'boolean|nullable',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||||
|
if ($validator->fails() || ! empty($extraFields)) {
|
||||||
|
$errors = $validator->errors();
|
||||||
|
if (! empty($extraFields)) {
|
||||||
|
foreach ($extraFields as $field) {
|
||||||
|
$errors->add($field, 'This field is not allowed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Validation failed.',
|
||||||
|
'errors' => $errors,
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
$server->update($request->only(['name', 'description', 'ip', 'port', 'user']));
|
||||||
|
if ($request->is_build_server) {
|
||||||
|
$server->settings()->update([
|
||||||
|
'is_build_server' => $request->is_build_server,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($request->instant_validate) {
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(serializeApiResponse($server))->setStatusCode(201);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Delete(
|
||||||
|
summary: 'Delete',
|
||||||
|
description: 'Delete server by UUID.',
|
||||||
|
path: '/servers/{uuid}',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(
|
||||||
|
name: 'uuid',
|
||||||
|
in: 'path',
|
||||||
|
description: 'UUID of the server.',
|
||||||
|
required: true,
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'string',
|
||||||
|
format: 'uuid',
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 200,
|
||||||
|
description: 'Server deleted.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Server deleted.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function delete_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
if ($server->definedResources()->count() > 0) {
|
||||||
|
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
||||||
|
}
|
||||||
|
$server->delete();
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Server deleted.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OA\Get(
|
||||||
|
summary: 'Validate',
|
||||||
|
description: 'Validate server by UUID.',
|
||||||
|
path: '/servers/{uuid}/validate',
|
||||||
|
security: [
|
||||||
|
['bearerAuth' => []],
|
||||||
|
],
|
||||||
|
tags: ['Servers'],
|
||||||
|
parameters: [
|
||||||
|
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'integer')),
|
||||||
|
],
|
||||||
|
responses: [
|
||||||
|
new OA\Response(
|
||||||
|
response: 201,
|
||||||
|
description: 'Server validation started.',
|
||||||
|
content: [
|
||||||
|
new OA\MediaType(
|
||||||
|
mediaType: 'application/json',
|
||||||
|
schema: new OA\Schema(
|
||||||
|
type: 'object',
|
||||||
|
properties: [
|
||||||
|
'message' => ['type' => 'string', 'example' => 'Validation started.'],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
new OA\Response(
|
||||||
|
response: 401,
|
||||||
|
ref: '#/components/responses/401',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 400,
|
||||||
|
ref: '#/components/responses/400',
|
||||||
|
),
|
||||||
|
new OA\Response(
|
||||||
|
response: 404,
|
||||||
|
ref: '#/components/responses/404',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)]
|
||||||
|
public function validate_server(Request $request)
|
||||||
|
{
|
||||||
|
$teamId = getTeamIdFromToken();
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return invalidTokenResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $request->uuid) {
|
||||||
|
return response()->json(['message' => 'Uuid is required.'], 422);
|
||||||
|
}
|
||||||
|
$server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first();
|
||||||
|
|
||||||
|
if (! $server) {
|
||||||
|
return response()->json(['message' => 'Server not found.'], 404);
|
||||||
|
}
|
||||||
|
ValidateServer::dispatch($server);
|
||||||
|
|
||||||
|
return response()->json(['message' => 'Validation started.']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,7 +340,6 @@ class Github extends Controller
|
|||||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
$isFunctional = $application->destination->server->isFunctional();
|
$isFunctional = $application->destination->server->isFunctional();
|
||||||
if (! $isFunctional) {
|
if (! $isFunctional) {
|
||||||
@@ -432,8 +431,13 @@ class Github extends Controller
|
|||||||
if ($action === 'closed' || $action === 'close') {
|
if ($action === 'closed' || $action === 'close') {
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if ($found) {
|
if ($found) {
|
||||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
$containers = getCurrentApplicationContainerStatus($application->destination->server, $application->id, $pull_request_id);
|
||||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
if ($containers->isNotEmpty()) {
|
||||||
|
$containers->each(function ($container) use ($application) {
|
||||||
|
$container_name = data_get($container, 'Names');
|
||||||
|
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
ApplicationPullRequestUpdateJob::dispatchSync(application: $application, preview: $found, status: ProcessStatus::CLOSED);
|
||||||
$found->delete();
|
$found->delete();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -15,7 +14,7 @@ class ApiAllowed
|
|||||||
if (isCloud()) {
|
if (isCloud()) {
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if ($settings->is_api_enabled === false) {
|
if ($settings->is_api_enabled === false) {
|
||||||
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
|
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,6 +157,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private ?string $coolify_variables = null;
|
private ?string $coolify_variables = null;
|
||||||
|
|
||||||
|
private bool $preserveRepository = true;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
@@ -187,6 +189,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->server = $this->mainServer = $this->destination->server;
|
$this->server = $this->mainServer = $this->destination->server;
|
||||||
$this->serverUser = $this->server->user;
|
$this->serverUser = $this->server->user;
|
||||||
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
|
$this->is_this_additional_server = $this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0;
|
||||||
|
$this->preserveRepository = $this->application->settings->is_preserve_repository_enabled;
|
||||||
|
|
||||||
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
||||||
$this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/');
|
$this->workdir = "{$this->basedir}".rtrim($this->application->base_directory, '/');
|
||||||
@@ -462,7 +465,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->env_filename) {
|
if ($this->env_filename) {
|
||||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||||
}
|
}
|
||||||
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build";
|
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull";
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||||
);
|
);
|
||||||
@@ -487,10 +490,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
// Start compose file
|
// Start compose file
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
if ($this->docker_compose_custom_start_command) {
|
if ($this->docker_compose_custom_start_command) {
|
||||||
|
$this->write_deployment_configurations();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
|
||||||
} else {
|
} else {
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$server_workdir = $this->application->workdir();
|
$server_workdir = $this->application->workdir();
|
||||||
@@ -507,20 +510,21 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($this->docker_compose_custom_start_command) {
|
if ($this->docker_compose_custom_start_command) {
|
||||||
|
$this->write_deployment_configurations();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
|
||||||
} else {
|
} else {
|
||||||
$command = "{$this->coolify_variables} docker compose";
|
$command = "{$this->coolify_variables} docker compose";
|
||||||
if ($this->env_filename) {
|
if ($this->env_filename) {
|
||||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||||
}
|
}
|
||||||
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
||||||
|
|
||||||
|
$this->write_deployment_configurations();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,6 +609,28 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
private function write_deployment_configurations()
|
private function write_deployment_configurations()
|
||||||
{
|
{
|
||||||
|
if ($this->preserveRepository) {
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->original_server;
|
||||||
|
}
|
||||||
|
if (str($this->configuration_dir)->isNotEmpty()) {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"mkdir -p $this->configuration_dir",
|
||||||
|
],
|
||||||
|
// removing this now as we are using docker cp
|
||||||
|
// [
|
||||||
|
// "rm -rf $this->configuration_dir/{*,.*}",
|
||||||
|
// ],
|
||||||
|
[
|
||||||
|
"docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($this->use_build_server) {
|
||||||
|
$this->server = $this->build_server;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (isset($this->docker_compose_base64)) {
|
if (isset($this->docker_compose_base64)) {
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->server = $this->original_server;
|
$this->server = $this->original_server;
|
||||||
@@ -1007,7 +1033,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
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->application_deployment_queue->addLogEntry('Consistent container name feature enabled, rolling update is not supported.');
|
||||||
}
|
}
|
||||||
if (isset($this->application->settings->custom_internal_name)) {
|
if (str($this->application->settings->custom_internal_name)->isNotEmpty()) {
|
||||||
$this->application_deployment_queue->addLogEntry('Custom internal name is set, rolling update is not supported.');
|
$this->application_deployment_queue->addLogEntry('Custom internal name is set, rolling update is not supported.');
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
@@ -1421,6 +1447,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
$this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT);
|
||||||
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
$this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true);
|
||||||
|
if ($this->nixpacks_type === 'rust') {
|
||||||
|
// temporary: disable healthcheck for rust because the start phase does not have curl/wget
|
||||||
|
$this->application->health_check_enabled = false;
|
||||||
|
$this->application->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1523,7 +1554,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
$this->application->custom_labels = base64_encode($labels->implode("\n"));
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
} else {
|
} else {
|
||||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
if (! $this->application->settings->is_container_label_readonly_enabled) {
|
||||||
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
@@ -1624,12 +1657,15 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
if (data_get($this->application, 'swarm_placement_constraints')) {
|
||||||
|
$swarm_placement_constraints = Yaml::parse(base64_decode(data_get($this->application, 'swarm_placement_constraints')));
|
||||||
|
$docker_compose['services'][$this->container_name]['deploy'] = array_merge(
|
||||||
|
$docker_compose['services'][$this->container_name]['deploy'],
|
||||||
|
$swarm_placement_constraints
|
||||||
|
);
|
||||||
|
}
|
||||||
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
|
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
|
||||||
$docker_compose['services'][$this->container_name]['deploy']['placement'] = [
|
$docker_compose['services'][$this->container_name]['deploy']['placement']['constraints'][] = 'node.role == worker';
|
||||||
'constraints' => [
|
|
||||||
'node.role == worker',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
|
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
|
||||||
@@ -2028,39 +2064,22 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function build_by_compose_file()
|
|
||||||
{
|
|
||||||
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
|
|
||||||
if ($this->application->build_pack === 'dockerimage') {
|
|
||||||
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true],
|
|
||||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), 'hidden' => true],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), 'hidden' => true],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$this->application_deployment_queue->addLogEntry('New images built.');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function start_by_compose_file()
|
private function start_by_compose_file()
|
||||||
{
|
{
|
||||||
if ($this->application->build_pack === 'dockerimage') {
|
if ($this->application->build_pack === 'dockerimage') {
|
||||||
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
|
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} pull"), 'hidden' => true],
|
||||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} up --build -d"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} up --build -d"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["{$this->coolify_variables} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true],
|
["{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), 'hidden' => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), 'hidden' => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,15 +28,19 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {}
|
public function __construct(
|
||||||
|
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
|
||||||
|
public bool $deleteConfigurations = false,
|
||||||
|
public bool $deleteVolumes = false) {}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->resource->forceDelete();
|
$persistentStorages = collect();
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
case 'application':
|
case 'application':
|
||||||
StopApplication::run($this->resource);
|
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||||
|
StopApplication::run($this->resource, previewDeployments: true);
|
||||||
break;
|
break;
|
||||||
case 'standalone-postgresql':
|
case 'standalone-postgresql':
|
||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
@@ -46,6 +50,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
case 'standalone-keydb':
|
case 'standalone-keydb':
|
||||||
case 'standalone-dragonfly':
|
case 'standalone-dragonfly':
|
||||||
case 'standalone-clickhouse':
|
case 'standalone-clickhouse':
|
||||||
|
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||||
StopDatabase::run($this->resource);
|
StopDatabase::run($this->resource);
|
||||||
break;
|
break;
|
||||||
case 'service':
|
case 'service':
|
||||||
@@ -53,6 +58,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
DeleteService::run($this->resource);
|
DeleteService::run($this->resource);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->deleteVolumes && $this->resource->type() !== 'service') {
|
||||||
|
$this->resource?->delete_volumes($persistentStorages);
|
||||||
|
}
|
||||||
if ($this->deleteConfigurations) {
|
if ($this->deleteConfigurations) {
|
||||||
$this->resource?->delete_configurations();
|
$this->resource?->delete_configurations();
|
||||||
}
|
}
|
||||||
@@ -61,6 +70,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
} finally {
|
} finally {
|
||||||
|
$this->resource->forceDelete();
|
||||||
Artisan::queue('cleanup:stucked-resources');
|
Artisan::queue('cleanup:stucked-resources');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -20,47 +19,48 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 300;
|
public $timeout = 300;
|
||||||
|
|
||||||
public ?int $usageBefore = null;
|
public int|string|null $usageBefore = null;
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$isInprogress = false;
|
// $isInprogress = false;
|
||||||
$this->server->applications()->each(function ($application) use (&$isInprogress) {
|
// $this->server->applications()->each(function ($application) use (&$isInprogress) {
|
||||||
if ($application->isDeploymentInprogress()) {
|
// if ($application->isDeploymentInprogress()) {
|
||||||
$isInprogress = true;
|
// $isInprogress = true;
|
||||||
|
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
// if ($isInprogress) {
|
// if ($isInprogress) {
|
||||||
// throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
// throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||||
// }
|
// }
|
||||||
if (! $this->server->isFunctional()) {
|
if (! $this->server->isFunctional()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if ($this->server->settings->is_force_cleanup_enabled) {
|
||||||
|
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||||
|
CleanupDocker::run(server: $this->server, force: true);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->usageBefore = $this->server->getDiskUsage();
|
$this->usageBefore = $this->server->getDiskUsage();
|
||||||
ray('Usage before: '.$this->usageBefore);
|
|
||||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||||
ray('Cleaning up '.$this->server->name);
|
CleanupDocker::run(server: $this->server, force: false);
|
||||||
CleanupDocker::run($this->server);
|
|
||||||
$usageAfter = $this->server->getDiskUsage();
|
$usageAfter = $this->server->getDiskUsage();
|
||||||
if ($usageAfter < $this->usageBefore) {
|
if ($usageAfter < $this->usageBefore) {
|
||||||
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
|
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
|
||||||
// ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
|
||||||
// send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
|
||||||
Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
|
Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name);
|
||||||
} else {
|
} else {
|
||||||
Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
|
Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ray('No need to clean up '.$this->server->name);
|
|
||||||
Log::info('No need to clean up '.$this->server->name);
|
Log::info('No need to clean up '.$this->server->name);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
// send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
|
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
@@ -36,7 +35,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$latest_version = get_latest_version_of_coolify();
|
$latest_version = get_latest_version_of_coolify();
|
||||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
|
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
|
||||||
|
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$current_version = config('version');
|
$current_version = config('version');
|
||||||
if (! $settings->is_auto_update_enabled) {
|
if (! $settings->is_auto_update_enabled) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Notifications\Server\HighDiskUsage;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -44,7 +43,6 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($this->server->isFunctional()) {
|
if ($this->server->isFunctional()) {
|
||||||
$this->cleanup(notify: false);
|
|
||||||
$this->remove_unnecessary_coolify_yaml();
|
$this->remove_unnecessary_coolify_yaml();
|
||||||
if ($this->server->isSentinelEnabled()) {
|
if ($this->server->isSentinelEnabled()) {
|
||||||
$this->server->checkSentinel();
|
$this->server->checkSentinel();
|
||||||
@@ -56,45 +54,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
// $this->check_docker_engine();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function check_docker_engine()
|
|
||||||
{
|
|
||||||
$version = instant_remote_process([
|
|
||||||
'docker info',
|
|
||||||
], $this->server, false);
|
|
||||||
if (is_null($version)) {
|
|
||||||
$os = instant_remote_process([
|
|
||||||
'cat /etc/os-release | grep ^ID=',
|
|
||||||
], $this->server, false);
|
|
||||||
$os = str($os)->after('ID=')->trim();
|
|
||||||
if ($os === 'ubuntu') {
|
|
||||||
try {
|
|
||||||
instant_remote_process([
|
|
||||||
'systemctl start docker',
|
|
||||||
], $this->server);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
return handleError($e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
instant_remote_process([
|
|
||||||
'service docker start',
|
|
||||||
], $this->server);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
ray($e->getMessage());
|
|
||||||
|
|
||||||
return handleError($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function remove_unnecessary_coolify_yaml()
|
private function remove_unnecessary_coolify_yaml()
|
||||||
@@ -108,28 +68,4 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
], $this->server, false);
|
], $this->server, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cleanup(bool $notify = false): void
|
|
||||||
{
|
|
||||||
$this->disk_usage = $this->server->getDiskUsage();
|
|
||||||
if ($this->disk_usage >= $this->server->settings->cleanup_after_percentage) {
|
|
||||||
if ($notify) {
|
|
||||||
if ($this->server->high_disk_usage_notification_sent) {
|
|
||||||
ray('high disk usage notification already sent');
|
|
||||||
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
$this->server->high_disk_usage_notification_sent = true;
|
|
||||||
$this->server->save();
|
|
||||||
$this->server->team?->notify(new HighDiskUsage($this->server, $this->disk_usage, $this->server->settings->cleanup_after_percentage));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DockerCleanupJob::dispatchSync($this->server);
|
|
||||||
$this->cleanup(notify: true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->server->high_disk_usage_notification_sent = false;
|
|
||||||
$this->server->save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,7 +257,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
$this->createdServer->settings->is_swarm_manager = $this->isSwarmManager;
|
||||||
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||||
$this->createdServer->settings->save();
|
$this->createdServer->settings->save();
|
||||||
$this->createdServer->addInitialNetwork();
|
|
||||||
$this->selectedExistingServer = $this->createdServer->id;
|
$this->selectedExistingServer = $this->createdServer->id;
|
||||||
$this->currentState = 'validate-server';
|
$this->currentState = 'validate-server';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
@@ -48,7 +47,7 @@ class Help extends Component
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
$mail->subject("[HELP]: {$this->subject}");
|
$mail->subject("[HELP]: {$this->subject}");
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$type = set_transanctional_email_settings($settings);
|
$type = set_transanctional_email_settings($settings);
|
||||||
if (! $type) {
|
if (! $type) {
|
||||||
$url = 'https://app.coolify.io/api/feedback';
|
$url = 'https://app.coolify.io/api/feedback';
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Notifications;
|
namespace App\Livewire\Notifications;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Notifications\Test;
|
use App\Notifications\Test;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -173,7 +172,7 @@ class Email extends Component
|
|||||||
|
|
||||||
public function copyFromInstanceSettings()
|
public function copyFromInstanceSettings()
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if ($settings->smtp_enabled) {
|
if ($settings->smtp_enabled) {
|
||||||
$team = currentTeam();
|
$team = currentTeam();
|
||||||
$team->update([
|
$team->update([
|
||||||
|
|||||||
@@ -84,6 +84,8 @@ class General extends Component
|
|||||||
'application.settings.is_static' => 'boolean|required',
|
'application.settings.is_static' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_preserve_repository_enabled' => 'boolean|required',
|
||||||
'application.watch_paths' => 'nullable',
|
'application.watch_paths' => 'nullable',
|
||||||
'application.redirect' => 'string|required',
|
'application.redirect' => 'string|required',
|
||||||
];
|
];
|
||||||
@@ -119,6 +121,8 @@ class General extends Component
|
|||||||
'application.settings.is_static' => 'Is static',
|
'application.settings.is_static' => 'Is static',
|
||||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||||
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
||||||
|
'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly',
|
||||||
|
'application.settings.is_preserve_repository_enabled' => 'Is preserve repository enabled',
|
||||||
'application.watch_paths' => 'Watch paths',
|
'application.watch_paths' => 'Watch paths',
|
||||||
'application.redirect' => 'Redirect',
|
'application.redirect' => 'Redirect',
|
||||||
];
|
];
|
||||||
@@ -143,7 +147,7 @@ class General extends Component
|
|||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
$this->customLabels = $this->application->parseContainerLabels();
|
$this->customLabels = $this->application->parseContainerLabels();
|
||||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
@@ -290,6 +294,9 @@ class General extends Component
|
|||||||
|
|
||||||
public function resetDefaultLabels()
|
public function resetDefaultLabels()
|
||||||
{
|
{
|
||||||
|
if ($this->application->settings->is_container_label_readonly_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
@@ -350,8 +357,7 @@ class General extends Component
|
|||||||
$this->checkFqdns();
|
$this->checkFqdns();
|
||||||
|
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
|
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
||||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
|
||||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
@@ -364,6 +370,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|
||||||
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
||||||
$this->resetDefaultLabels();
|
$this->resetDefaultLabels();
|
||||||
}
|
}
|
||||||
@@ -390,6 +397,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
if ($this->application->build_pack === 'dockercompose') {
|
if ($this->application->build_pack === 'dockercompose') {
|
||||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||||
|
|
||||||
foreach ($this->parsedServiceDomains as $serviceName => $service) {
|
foreach ($this->parsedServiceDomains as $serviceName => $service) {
|
||||||
$domain = data_get($service, 'domain');
|
$domain = data_get($service, 'domain');
|
||||||
if ($domain) {
|
if ($domain) {
|
||||||
@@ -399,6 +407,9 @@ class General extends Component
|
|||||||
check_domain_usage(resource: $this->application);
|
check_domain_usage(resource: $this->application);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($this->application->isDirty('docker_compose_domains')) {
|
||||||
|
$this->resetDefaultLabels();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ class GithubPrivateRepository extends Component
|
|||||||
|
|
||||||
public ?string $publish_directory = null;
|
public ?string $publish_directory = null;
|
||||||
|
|
||||||
|
// In case of docker compose
|
||||||
|
public ?string $base_directory = null;
|
||||||
|
|
||||||
|
public ?string $docker_compose_location = '/docker-compose.yaml';
|
||||||
|
// End of docker compose
|
||||||
|
|
||||||
protected int $page = 1;
|
protected int $page = 1;
|
||||||
|
|
||||||
public $build_pack = 'nixpacks';
|
public $build_pack = 'nixpacks';
|
||||||
@@ -68,6 +74,16 @@ class GithubPrivateRepository extends Component
|
|||||||
$this->github_apps = GithubApp::private();
|
$this->github_apps = GithubApp::private();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatedBaseDirectory()
|
||||||
|
{
|
||||||
|
if ($this->base_directory) {
|
||||||
|
$this->base_directory = rtrim($this->base_directory, '/');
|
||||||
|
if (! str($this->base_directory)->startsWith('/')) {
|
||||||
|
$this->base_directory = '/'.$this->base_directory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function updatedBuildPack()
|
public function updatedBuildPack()
|
||||||
{
|
{
|
||||||
if ($this->build_pack === 'nixpacks') {
|
if ($this->build_pack === 'nixpacks') {
|
||||||
@@ -184,6 +200,10 @@ class GithubPrivateRepository extends Component
|
|||||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||||
$application->health_check_enabled = false;
|
$application->health_check_enabled = false;
|
||||||
}
|
}
|
||||||
|
if ($this->build_pack === 'dockercompose') {
|
||||||
|
$application['docker_compose_location'] = $this->docker_compose_location;
|
||||||
|
$application['base_directory'] = $this->base_directory;
|
||||||
|
}
|
||||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||||
$application->fqdn = $fqdn;
|
$application->fqdn = $fqdn;
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
|
|
||||||
public ?string $publish_directory = null;
|
public ?string $publish_directory = null;
|
||||||
|
|
||||||
|
// In case of docker compose
|
||||||
|
public ?string $base_directory = null;
|
||||||
|
|
||||||
|
public ?string $docker_compose_location = '/docker-compose.yaml';
|
||||||
|
// End of docker compose
|
||||||
|
|
||||||
public string $repository_url;
|
public string $repository_url;
|
||||||
|
|
||||||
public string $branch;
|
public string $branch;
|
||||||
@@ -163,6 +169,10 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||||
$application_init['health_check_enabled'] = false;
|
$application_init['health_check_enabled'] = false;
|
||||||
}
|
}
|
||||||
|
if ($this->build_pack === 'dockercompose') {
|
||||||
|
$application_init['docker_compose_location'] = $this->docker_compose_location;
|
||||||
|
$application_init['base_directory'] = $this->base_directory;
|
||||||
|
}
|
||||||
$application = Application::create($application_init);
|
$application = Application::create($application_init);
|
||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->is_static;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
|
|||||||
@@ -25,14 +25,20 @@ class PublicGitRepository extends Component
|
|||||||
|
|
||||||
public $query;
|
public $query;
|
||||||
|
|
||||||
public bool $branch_found = false;
|
public bool $branchFound = false;
|
||||||
|
|
||||||
public string $selected_branch = 'main';
|
public string $selectedBranch = 'main';
|
||||||
|
|
||||||
public bool $is_static = false;
|
public bool $isStatic = false;
|
||||||
|
|
||||||
public ?string $publish_directory = null;
|
public ?string $publish_directory = null;
|
||||||
|
|
||||||
|
// In case of docker compose
|
||||||
|
public ?string $base_directory = null;
|
||||||
|
|
||||||
|
public ?string $docker_compose_location = '/docker-compose.yaml';
|
||||||
|
// End of docker compose
|
||||||
|
|
||||||
public string $git_branch = 'main';
|
public string $git_branch = 'main';
|
||||||
|
|
||||||
public int $rate_limit_remaining = 0;
|
public int $rate_limit_remaining = 0;
|
||||||
@@ -56,17 +62,21 @@ class PublicGitRepository extends Component
|
|||||||
protected $rules = [
|
protected $rules = [
|
||||||
'repository_url' => 'required|url',
|
'repository_url' => 'required|url',
|
||||||
'port' => 'required|numeric',
|
'port' => 'required|numeric',
|
||||||
'is_static' => 'required|boolean',
|
'isStatic' => 'required|boolean',
|
||||||
'publish_directory' => 'nullable|string',
|
'publish_directory' => 'nullable|string',
|
||||||
'build_pack' => 'required|string',
|
'build_pack' => 'required|string',
|
||||||
|
'base_directory' => 'nullable|string',
|
||||||
|
'docker_compose_location' => 'nullable|string',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'repository_url' => 'repository',
|
'repository_url' => 'repository',
|
||||||
'port' => 'port',
|
'port' => 'port',
|
||||||
'is_static' => 'static',
|
'isStatic' => 'static',
|
||||||
'publish_directory' => 'publish directory',
|
'publish_directory' => 'publish directory',
|
||||||
'build_pack' => 'build pack',
|
'build_pack' => 'build pack',
|
||||||
|
'base_directory' => 'base directory',
|
||||||
|
'docker_compose_location' => 'docker compose location',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -79,6 +89,16 @@ class PublicGitRepository extends Component
|
|||||||
$this->query = request()->query();
|
$this->query = request()->query();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatedBaseDirectory()
|
||||||
|
{
|
||||||
|
if ($this->base_directory) {
|
||||||
|
$this->base_directory = rtrim($this->base_directory, '/');
|
||||||
|
if (! str($this->base_directory)->startsWith('/')) {
|
||||||
|
$this->base_directory = '/'.$this->base_directory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function updatedBuildPack()
|
public function updatedBuildPack()
|
||||||
{
|
{
|
||||||
if ($this->build_pack === 'nixpacks') {
|
if ($this->build_pack === 'nixpacks') {
|
||||||
@@ -86,17 +106,17 @@ class PublicGitRepository extends Component
|
|||||||
$this->port = 3000;
|
$this->port = 3000;
|
||||||
} elseif ($this->build_pack === 'static') {
|
} elseif ($this->build_pack === 'static') {
|
||||||
$this->show_is_static = false;
|
$this->show_is_static = false;
|
||||||
$this->is_static = false;
|
$this->isStatic = false;
|
||||||
$this->port = 80;
|
$this->port = 80;
|
||||||
} else {
|
} else {
|
||||||
$this->show_is_static = false;
|
$this->show_is_static = false;
|
||||||
$this->is_static = false;
|
$this->isStatic = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
if ($this->is_static) {
|
if ($this->isStatic) {
|
||||||
$this->port = 80;
|
$this->port = 80;
|
||||||
$this->publish_directory = '/dist';
|
$this->publish_directory = '/dist';
|
||||||
} else {
|
} else {
|
||||||
@@ -106,12 +126,7 @@ class PublicGitRepository extends Component
|
|||||||
$this->dispatch('success', 'Application settings updated!');
|
$this->dispatch('success', 'Application settings updated!');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function load_any_git()
|
public function loadBranch()
|
||||||
{
|
|
||||||
$this->branch_found = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function load_branch()
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (str($this->repository_url)->startsWith('git@')) {
|
if (str($this->repository_url)->startsWith('git@')) {
|
||||||
@@ -135,15 +150,21 @@ class PublicGitRepository extends Component
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$this->branch_found = false;
|
$this->branchFound = false;
|
||||||
$this->get_git_source();
|
$this->getGitSource();
|
||||||
$this->get_branch();
|
$this->getBranch();
|
||||||
$this->selected_branch = $this->git_branch;
|
$this->selectedBranch = $this->git_branch;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
if (! $this->branch_found && $this->git_branch == 'main') {
|
if ($this->rate_limit_remaining == 0) {
|
||||||
|
$this->selectedBranch = $this->git_branch;
|
||||||
|
$this->branchFound = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (! $this->branchFound && $this->git_branch == 'main') {
|
||||||
try {
|
try {
|
||||||
$this->git_branch = 'master';
|
$this->git_branch = 'master';
|
||||||
$this->get_branch();
|
$this->getBranch();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -153,13 +174,16 @@ class PublicGitRepository extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_git_source()
|
private function getGitSource()
|
||||||
{
|
{
|
||||||
$this->repository_url_parsed = Url::fromString($this->repository_url);
|
$this->repository_url_parsed = Url::fromString($this->repository_url);
|
||||||
$this->git_host = $this->repository_url_parsed->getHost();
|
$this->git_host = $this->repository_url_parsed->getHost();
|
||||||
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
|
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
|
||||||
$this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main';
|
if ($this->repository_url_parsed->getSegment(3) === 'tree') {
|
||||||
|
$this->git_branch = str($this->repository_url_parsed->getPath())->after('tree/')->value();
|
||||||
|
} else {
|
||||||
|
$this->git_branch = 'main';
|
||||||
|
}
|
||||||
if ($this->git_host == 'github.com') {
|
if ($this->git_host == 'github.com') {
|
||||||
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
|
$this->git_source = GithubApp::where('name', 'Public GitHub')->first();
|
||||||
|
|
||||||
@@ -169,17 +193,17 @@ class PublicGitRepository extends Component
|
|||||||
$this->git_source = 'other';
|
$this->git_source = 'other';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function get_branch()
|
private function getBranch()
|
||||||
{
|
{
|
||||||
if ($this->git_source === 'other') {
|
if ($this->git_source === 'other') {
|
||||||
$this->branch_found = true;
|
$this->branchFound = true;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->git_source->getMorphClass() === 'App\Models\GithubApp') {
|
if ($this->git_source->getMorphClass() === 'App\Models\GithubApp') {
|
||||||
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
|
['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = githubApi(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}");
|
||||||
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
|
$this->rate_limit_reset = Carbon::parse((int) $this->rate_limit_reset)->format('Y-M-d H:i:s');
|
||||||
$this->branch_found = true;
|
$this->branchFound = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,9 +285,13 @@ class PublicGitRepository extends Component
|
|||||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||||
$application_init['health_check_enabled'] = false;
|
$application_init['health_check_enabled'] = false;
|
||||||
}
|
}
|
||||||
|
if ($this->build_pack === 'dockercompose') {
|
||||||
|
$application_init['docker_compose_location'] = $this->docker_compose_location;
|
||||||
|
$application_init['base_directory'] = $this->base_directory;
|
||||||
|
}
|
||||||
$application = Application::create($application_init);
|
$application = Application::create($application_init);
|
||||||
|
|
||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->isStatic;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
|
|
||||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class Danger extends Component
|
|||||||
|
|
||||||
public bool $delete_configurations = true;
|
public bool $delete_configurations = true;
|
||||||
|
|
||||||
|
public bool $delete_volumes = true;
|
||||||
|
|
||||||
public ?string $modalId = null;
|
public ?string $modalId = null;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -31,7 +33,7 @@ class Danger extends Component
|
|||||||
try {
|
try {
|
||||||
// $this->authorize('delete', $this->resource);
|
// $this->authorize('delete', $this->resource);
|
||||||
$this->resource->delete();
|
$this->resource->delete();
|
||||||
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations);
|
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations, $this->delete_volumes);
|
||||||
|
|
||||||
return redirect()->route('project.resource.index', [
|
return redirect()->route('project.resource.index', [
|
||||||
'project_uuid' => $this->projectUuid,
|
'project_uuid' => $this->projectUuid,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Shared\Storages;
|
|||||||
|
|
||||||
use App\Models\LocalPersistentVolume;
|
use App\Models\LocalPersistentVolume;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
|
|
||||||
class Show extends Component
|
class Show extends Component
|
||||||
{
|
{
|
||||||
@@ -12,8 +11,6 @@ class Show extends Component
|
|||||||
|
|
||||||
public bool $isReadOnly = false;
|
public bool $isReadOnly = false;
|
||||||
|
|
||||||
public ?string $modalId = null;
|
|
||||||
|
|
||||||
public bool $isFirst = true;
|
public bool $isFirst = true;
|
||||||
|
|
||||||
public bool $isService = false;
|
public bool $isService = false;
|
||||||
@@ -32,11 +29,6 @@ class Show extends Component
|
|||||||
'host_path' => 'host',
|
'host_path' => 'host',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->modalId = new Cuid2(7);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
|||||||
@@ -3,32 +3,63 @@
|
|||||||
namespace App\Livewire\Project\Shared;
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
|
use Livewire\Attributes\Validate;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
|
// Refactored ✅
|
||||||
class Tags extends Component
|
class Tags extends Component
|
||||||
{
|
{
|
||||||
public $resource = null;
|
public $resource = null;
|
||||||
|
|
||||||
public ?string $new_tag = null;
|
#[Validate('required|string|min:2')]
|
||||||
|
public string $newTags;
|
||||||
|
|
||||||
public $tags = [];
|
public $tags = [];
|
||||||
|
|
||||||
protected $listeners = [
|
public $filteredTags = [];
|
||||||
'refresh' => '$refresh',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $rules = [
|
|
||||||
'resource.tags.*.name' => 'required|string|min:2',
|
|
||||||
'new_tag' => 'required|string|min:2',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $validationAttributes = [
|
|
||||||
'new_tag' => 'tag',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->loadTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadTags()
|
||||||
{
|
{
|
||||||
$this->tags = Tag::ownedByCurrentTeam()->get();
|
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||||
|
$this->filteredTags = $this->tags->filter(function ($tag) {
|
||||||
|
return ! $this->resource->tags->contains($tag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$tags = str($this->newTags)->trim()->explode(' ');
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
if (strlen($tag) < 2) {
|
||||||
|
$this->dispatch('error', 'Invalid tag.', "Tag <span class='dark:text-warning'>$tag</span> is invalid. Min length is 2.");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($this->resource->tags()->where('name', $tag)->exists()) {
|
||||||
|
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$tag</span> already added.");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$found = Tag::ownedByCurrentTeam()->where(['name' => $tag])->exists();
|
||||||
|
if (! $found) {
|
||||||
|
$found = Tag::create([
|
||||||
|
'name' => $tag,
|
||||||
|
'team_id' => currentTeam()->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->resource->tags()->attach($found->id);
|
||||||
|
}
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addTag(string $id, string $name)
|
public function addTag(string $id, string $name)
|
||||||
@@ -39,8 +70,9 @@ class Tags extends Component
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->resource->tags()->syncWithoutDetaching($id);
|
$this->resource->tags()->attach($id);
|
||||||
$this->refresh();
|
$this->refresh();
|
||||||
|
$this->dispatch('success', 'Tag added.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -50,12 +82,12 @@ class Tags extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->resource->tags()->detach($id);
|
$this->resource->tags()->detach($id);
|
||||||
|
$found_more_tags = Tag::ownedByCurrentTeam()->find($id);
|
||||||
$found_more_tags = Tag::where(['id' => $id, 'team_id' => currentTeam()->id])->first();
|
if ($found_more_tags && $found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) {
|
||||||
if ($found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) {
|
|
||||||
$found_more_tags->delete();
|
$found_more_tags->delete();
|
||||||
}
|
}
|
||||||
$this->refresh();
|
$this->refresh();
|
||||||
|
$this->dispatch('success', 'Tag deleted.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -63,41 +95,8 @@ class Tags extends Component
|
|||||||
|
|
||||||
public function refresh()
|
public function refresh()
|
||||||
{
|
{
|
||||||
$this->resource->load(['tags']);
|
$this->resource->refresh(); // Remove this when legacy_model_binding is false
|
||||||
$this->tags = Tag::ownedByCurrentTeam()->get();
|
$this->loadTags();
|
||||||
$this->new_tag = null;
|
$this->reset('newTags');
|
||||||
}
|
|
||||||
|
|
||||||
public function submit()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$this->validate([
|
|
||||||
'new_tag' => 'required|string|min:2',
|
|
||||||
]);
|
|
||||||
$tags = str($this->new_tag)->trim()->explode(' ');
|
|
||||||
foreach ($tags as $tag) {
|
|
||||||
if ($this->resource->tags()->where('name', $tag)->exists()) {
|
|
||||||
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$tag</span> already added.");
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();
|
|
||||||
if (! $found) {
|
|
||||||
$found = Tag::create([
|
|
||||||
'name' => $tag,
|
|
||||||
'team_id' => currentTeam()->id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$this->resource->tags()->syncWithoutDetaching($found->id);
|
|
||||||
}
|
|
||||||
$this->refresh();
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.project.shared.tags');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,49 +4,61 @@ namespace App\Livewire\Project\Shared;
|
|||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
|
// Refactored ✅
|
||||||
class Webhooks extends Component
|
class Webhooks extends Component
|
||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
|
|
||||||
public ?string $deploywebhook = null;
|
public ?string $deploywebhook;
|
||||||
|
|
||||||
public ?string $githubManualWebhook = null;
|
public ?string $githubManualWebhook;
|
||||||
|
|
||||||
public ?string $gitlabManualWebhook = null;
|
public ?string $gitlabManualWebhook;
|
||||||
|
|
||||||
public ?string $bitbucketManualWebhook = null;
|
public ?string $bitbucketManualWebhook;
|
||||||
|
|
||||||
public ?string $giteaManualWebhook = null;
|
public ?string $giteaManualWebhook;
|
||||||
|
|
||||||
protected $rules = [
|
public ?string $githubManualWebhookSecret = null;
|
||||||
'resource.manual_webhook_secret_github' => 'nullable|string',
|
|
||||||
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
|
|
||||||
'resource.manual_webhook_secret_bitbucket' => 'nullable|string',
|
|
||||||
'resource.manual_webhook_secret_gitea' => 'nullable|string',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function saveSecret()
|
public ?string $gitlabManualWebhookSecret = null;
|
||||||
|
|
||||||
|
public ?string $bitbucketManualWebhookSecret = null;
|
||||||
|
|
||||||
|
public ?string $giteaManualWebhookSecret = null;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
// ray()->clearAll();
|
||||||
|
// ray()->showQueries();
|
||||||
|
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||||
|
|
||||||
|
$this->githubManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_github');
|
||||||
|
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
||||||
|
|
||||||
|
$this->gitlabManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_gitlab');
|
||||||
|
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
||||||
|
|
||||||
|
$this->bitbucketManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_bitbucket');
|
||||||
|
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
|
||||||
|
|
||||||
|
$this->giteaManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_gitea');
|
||||||
|
$this->giteaManualWebhook = generateGitManualWebhook($this->resource, 'gitea');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->authorize('update', $this->resource);
|
||||||
$this->resource->save();
|
$this->resource->update([
|
||||||
|
'manual_webhook_secret_github' => $this->githubManualWebhookSecret,
|
||||||
|
'manual_webhook_secret_gitlab' => $this->gitlabManualWebhookSecret,
|
||||||
|
'manual_webhook_secret_bitbucket' => $this->bitbucketManualWebhookSecret,
|
||||||
|
'manual_webhook_secret_gitea' => $this->giteaManualWebhookSecret,
|
||||||
|
]);
|
||||||
$this->dispatch('success', 'Secret Saved.');
|
$this->dispatch('success', 'Secret Saved.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->deploywebhook = generateDeployWebhook($this->resource);
|
|
||||||
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
|
||||||
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
|
||||||
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
|
|
||||||
$this->giteaManualWebhook = generateGitManualWebhook($this->resource, 'gitea');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
|
||||||
{
|
|
||||||
return view('livewire.project.shared.webhooks');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class Form extends Component
|
|||||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||||
'server.settings.is_build_server' => 'required|boolean',
|
'server.settings.is_build_server' => 'required|boolean',
|
||||||
|
'server.settings.is_force_cleanup_enabled' => 'required|boolean',
|
||||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||||
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
||||||
'server.settings.is_metrics_enabled' => 'required|boolean',
|
'server.settings.is_metrics_enabled' => 'required|boolean',
|
||||||
@@ -163,6 +164,9 @@ class Form extends Component
|
|||||||
|
|
||||||
public function validateServer($install = true)
|
public function validateServer($install = true)
|
||||||
{
|
{
|
||||||
|
$this->server->update([
|
||||||
|
'validation_logs' => null,
|
||||||
|
]);
|
||||||
$this->dispatch('init', $install);
|
$this->dispatch('init', $install);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,6 @@ class ByIp extends Component
|
|||||||
}
|
}
|
||||||
$server->settings->is_build_server = $this->is_build_server;
|
$server->settings->is_build_server = $this->is_build_server;
|
||||||
$server->settings->save();
|
$server->settings->save();
|
||||||
$server->addInitialNetwork();
|
|
||||||
|
|
||||||
return redirect()->route('server.show', $server->uuid);
|
return redirect()->route('server.show', $server->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class Deploy extends Component
|
|||||||
public function proxyStarted()
|
public function proxyStarted()
|
||||||
{
|
{
|
||||||
CheckProxy::run($this->server, true);
|
CheckProxy::run($this->server, true);
|
||||||
$this->dispatch('success', 'Proxy started.');
|
$this->dispatch('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function proxyStatusUpdated()
|
public function proxyStatusUpdated()
|
||||||
@@ -61,7 +61,7 @@ class Deploy extends Component
|
|||||||
public function restart()
|
public function restart()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->stop();
|
$this->stop(forceStop: false);
|
||||||
$this->dispatch('checkProxy');
|
$this->dispatch('checkProxy');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
@@ -84,14 +84,14 @@ class Deploy extends Component
|
|||||||
try {
|
try {
|
||||||
$this->server->proxy->force_stop = false;
|
$this->server->proxy->force_stop = false;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$activity = StartProxy::run($this->server);
|
$activity = StartProxy::run($this->server, force: true);
|
||||||
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
|
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stop()
|
public function stop(bool $forceStop = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->server->isSwarm()) {
|
if ($this->server->isSwarm()) {
|
||||||
@@ -104,7 +104,7 @@ class Deploy extends Component
|
|||||||
], $this->server);
|
], $this->server);
|
||||||
}
|
}
|
||||||
$this->server->proxy->status = 'exited';
|
$this->server->proxy->status = 'exited';
|
||||||
$this->server->proxy->force_stop = true;
|
$this->server->proxy->force_stop = $forceStop;
|
||||||
$this->server->save();
|
$this->server->save();
|
||||||
$this->dispatch('proxyStatusUpdated');
|
$this->dispatch('proxyStatusUpdated');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ class Status extends Component
|
|||||||
|
|
||||||
public int $numberOfPolls = 0;
|
public int $numberOfPolls = 0;
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated' => '$refresh', 'startProxyPolling'];
|
protected $listeners = [
|
||||||
|
'proxyStatusUpdated',
|
||||||
|
'startProxyPolling',
|
||||||
|
];
|
||||||
|
|
||||||
public function startProxyPolling()
|
public function startProxyPolling()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,7 +87,10 @@ class ValidateAndInstall extends Component
|
|||||||
{
|
{
|
||||||
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
|
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
|
||||||
if (! $this->uptime) {
|
if (! $this->uptime) {
|
||||||
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error;
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
|
||||||
|
$this->server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -99,6 +102,9 @@ class ValidateAndInstall extends Component
|
|||||||
$this->supported_os_type = $this->server->validateOS();
|
$this->supported_os_type = $this->server->validateOS();
|
||||||
if (! $this->supported_os_type) {
|
if (! $this->supported_os_type) {
|
||||||
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
$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>.';
|
||||||
|
$this->server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -113,6 +119,9 @@ class ValidateAndInstall extends Component
|
|||||||
if ($this->install) {
|
if ($this->install) {
|
||||||
if ($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>.';
|
$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>.';
|
||||||
|
$this->server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -126,6 +135,9 @@ class ValidateAndInstall extends Component
|
|||||||
}
|
}
|
||||||
} else {
|
} 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>.';
|
$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>.';
|
||||||
|
$this->server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -148,6 +160,9 @@ class ValidateAndInstall extends Component
|
|||||||
$this->dispatch('success', 'Server validated.');
|
$this->dispatch('success', 'Server validated.');
|
||||||
} else {
|
} 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>.';
|
$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>.';
|
||||||
|
$this->server->update([
|
||||||
|
'validation_logs' => $this->error,
|
||||||
|
]);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class Index extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (isInstanceAdmin()) {
|
if (isInstanceAdmin()) {
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$database = StandalonePostgresql::whereName('coolify-db')->first();
|
$database = StandalonePostgresql::whereName('coolify-db')->first();
|
||||||
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
|
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
|
||||||
if ($database) {
|
if ($database) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class License extends Component
|
|||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
$this->instance_id = config('app.id');
|
$this->instance_id = config('app.id');
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = \App\Models\InstanceSettings::get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Livewire\Source\Github;
|
|||||||
|
|
||||||
use App\Jobs\GithubAppPermissionJob;
|
use App\Jobs\GithubAppPermissionJob;
|
||||||
use App\Models\GithubApp;
|
use App\Models\GithubApp;
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -100,7 +99,7 @@ class Change extends Component
|
|||||||
return redirect()->route('source.all');
|
return redirect()->route('source.all');
|
||||||
}
|
}
|
||||||
$this->applications = $this->github_app->applications;
|
$this->applications = $this->github_app->applications;
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
|
||||||
|
|
||||||
$this->name = str($this->github_app->name)->kebab();
|
$this->name = str($this->github_app->name)->kebab();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Index extends Component
|
|||||||
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
|
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
|
||||||
return redirect()->route('subscription.show');
|
return redirect()->route('subscription.show');
|
||||||
}
|
}
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = \App\Models\InstanceSettings::get();
|
||||||
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Tags;
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
use App\Http\Controllers\Api\Deploy;
|
use App\Http\Controllers\Api\DeployController;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Attributes\Url;
|
use Livewire\Attributes\Url;
|
||||||
@@ -51,11 +51,11 @@ class Index extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->applications->each(function ($resource) {
|
$this->applications->each(function ($resource) {
|
||||||
$deploy = new Deploy();
|
$deploy = new DeployController();
|
||||||
$deploy->deploy_resource($resource);
|
$deploy->deploy_resource($resource);
|
||||||
});
|
});
|
||||||
$this->services->each(function ($resource) {
|
$this->services->each(function ($resource) {
|
||||||
$deploy = new Deploy();
|
$deploy = new DeployController();
|
||||||
$deploy->deploy_resource($resource);
|
$deploy->deploy_resource($resource);
|
||||||
});
|
});
|
||||||
$this->dispatch('success', 'Mass deployment started.');
|
$this->dispatch('success', 'Mass deployment started.');
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Tags;
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
use App\Http\Controllers\Api\Deploy;
|
use App\Http\Controllers\Api\DeployController;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -59,11 +59,11 @@ class Show extends Component
|
|||||||
try {
|
try {
|
||||||
$message = collect([]);
|
$message = collect([]);
|
||||||
$this->applications->each(function ($resource) use ($message) {
|
$this->applications->each(function ($resource) use ($message) {
|
||||||
$deploy = new Deploy();
|
$deploy = new DeployController();
|
||||||
$message->push($deploy->deploy_resource($resource));
|
$message->push($deploy->deploy_resource($resource));
|
||||||
});
|
});
|
||||||
$this->services->each(function ($resource) use ($message) {
|
$this->services->each(function ($resource) use ($message) {
|
||||||
$deploy = new Deploy();
|
$deploy = new DeployController();
|
||||||
$message->push($deploy->deploy_resource($resource));
|
$message->push($deploy->deploy_resource($resource));
|
||||||
});
|
});
|
||||||
$this->dispatch('success', 'Mass deployment started.');
|
$this->dispatch('success', 'Mass deployment started.');
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ use Visus\Cuid2\Cuid2;
|
|||||||
'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was created.'],
|
'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was created.'],
|
||||||
'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'],
|
'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'],
|
||||||
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
|
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
|
||||||
|
'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'],
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
|
||||||
@@ -122,17 +123,12 @@ class Application extends BaseModel
|
|||||||
ApplicationSetting::create([
|
ApplicationSetting::create([
|
||||||
'application_id' => $application->id,
|
'application_id' => $application->id,
|
||||||
]);
|
]);
|
||||||
|
$application->compose_parsing_version = '2';
|
||||||
|
$application->save();
|
||||||
});
|
});
|
||||||
static::deleting(function ($application) {
|
static::forceDeleting(function ($application) {
|
||||||
$application->update(['fqdn' => null]);
|
$application->update(['fqdn' => null]);
|
||||||
$application->settings()->delete();
|
$application->settings()->delete();
|
||||||
$storages = $application->persistentStorages()->get();
|
|
||||||
$server = data_get($application, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$application->persistentStorages()->delete();
|
$application->persistentStorages()->delete();
|
||||||
$application->environment_variables()->delete();
|
$application->environment_variables()->delete();
|
||||||
$application->environment_variables_preview()->delete();
|
$application->environment_variables_preview()->delete();
|
||||||
@@ -158,6 +154,23 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(?Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($this->build_pack === 'dockercompose') {
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
ray('Deleting volumes');
|
||||||
|
instant_remote_process(["cd {$this->dirOnServer()} && docker compose down -v"], $server, false);
|
||||||
|
} else {
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function additional_servers()
|
public function additional_servers()
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Server::class, 'additional_destinations')
|
return $this->belongsToMany(Server::class, 'additional_destinations')
|
||||||
@@ -775,6 +788,11 @@ class Application extends BaseModel
|
|||||||
return "/artifacts/{$uuid}";
|
return "/artifacts/{$uuid}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function dirOnServer()
|
||||||
|
{
|
||||||
|
return application_configuration_dir()."/{$this->uuid}";
|
||||||
|
}
|
||||||
|
|
||||||
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
||||||
{
|
{
|
||||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||||
@@ -897,7 +915,7 @@ class Application extends BaseModel
|
|||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||||
} elseif ($git_type === 'github') {
|
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
@@ -941,7 +959,7 @@ class Application extends BaseModel
|
|||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||||
} elseif ($git_type === 'github') {
|
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
@@ -1063,45 +1081,55 @@ class Application extends BaseModel
|
|||||||
'git read-tree -mu HEAD',
|
'git read-tree -mu HEAD',
|
||||||
"cat .$workdir$composeFile",
|
"cat .$workdir$composeFile",
|
||||||
]);
|
]);
|
||||||
$composeFileContent = instant_remote_process($commands, $this->destination->server, false);
|
try {
|
||||||
if (! $composeFileContent) {
|
$composeFileContent = instant_remote_process($commands, $this->destination->server);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
if (str($e->getMessage())->contains('No such file')) {
|
||||||
|
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||||
|
}
|
||||||
|
if (str($e->getMessage())->contains('fatal: repository') && str($e->getMessage())->contains('does not exist')) {
|
||||||
|
if ($this->deploymentType() === 'deploy_key') {
|
||||||
|
throw new \RuntimeException('Your deploy key does not have access to the repository. Please check your deploy key and try again.');
|
||||||
|
}
|
||||||
|
throw new \RuntimeException('Repository does not exist. Please check your repository URL and try again.');
|
||||||
|
}
|
||||||
|
throw new \RuntimeException($e->getMessage());
|
||||||
|
} finally {
|
||||||
$this->docker_compose_location = $initialDockerComposeLocation;
|
$this->docker_compose_location = $initialDockerComposeLocation;
|
||||||
$this->save();
|
$this->save();
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
"rm -rf /tmp/{$uuid}",
|
"rm -rf /tmp/{$uuid}",
|
||||||
]);
|
]);
|
||||||
instant_remote_process($commands, $this->destination->server, false);
|
instant_remote_process($commands, $this->destination->server, false);
|
||||||
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
}
|
||||||
} else {
|
if ($composeFileContent) {
|
||||||
$this->docker_compose_raw = $composeFileContent;
|
$this->docker_compose_raw = $composeFileContent;
|
||||||
$this->save();
|
$this->save();
|
||||||
}
|
$parsedServices = $this->parseCompose();
|
||||||
|
if ($this->docker_compose_domains) {
|
||||||
$commands = collect([
|
$json = collect(json_decode($this->docker_compose_domains));
|
||||||
"rm -rf /tmp/{$uuid}",
|
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
|
||||||
]);
|
$jsonNames = $json->keys()->toArray();
|
||||||
instant_remote_process($commands, $this->destination->server, false);
|
$diff = array_diff($jsonNames, $names);
|
||||||
$parsedServices = $this->parseCompose();
|
$json = $json->filter(function ($value, $key) use ($diff) {
|
||||||
if ($this->docker_compose_domains) {
|
return ! in_array($key, $diff);
|
||||||
$json = collect(json_decode($this->docker_compose_domains));
|
});
|
||||||
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
|
if ($json) {
|
||||||
$jsonNames = $json->keys()->toArray();
|
$this->docker_compose_domains = json_encode($json);
|
||||||
$diff = array_diff($jsonNames, $names);
|
} else {
|
||||||
$json = $json->filter(function ($value, $key) use ($diff) {
|
$this->docker_compose_domains = null;
|
||||||
return ! in_array($key, $diff);
|
}
|
||||||
});
|
$this->save();
|
||||||
if ($json) {
|
|
||||||
$this->docker_compose_domains = json_encode($json);
|
|
||||||
} else {
|
|
||||||
$this->docker_compose_domains = null;
|
|
||||||
}
|
}
|
||||||
$this->save();
|
|
||||||
|
return [
|
||||||
|
'parsedServices' => $parsedServices,
|
||||||
|
'initialDockerComposeLocation' => $this->docker_compose_location,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
|
||||||
'parsedServices' => $parsedServices,
|
|
||||||
'initialDockerComposeLocation' => $this->docker_compose_location,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseContainerLabels(?ApplicationPreview $preview = null)
|
public function parseContainerLabels(?ApplicationPreview $preview = null)
|
||||||
|
|||||||
@@ -79,23 +79,29 @@ class LocalFileVolume extends BaseModel
|
|||||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||||
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
||||||
|
$fileVolume->is_directory = false;
|
||||||
|
$fileVolume->save();
|
||||||
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.');
|
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.');
|
||||||
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
|
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
|
||||||
|
$fileVolume->is_directory = true;
|
||||||
|
$fileVolume->save();
|
||||||
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.');
|
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
|
||||||
}
|
}
|
||||||
if (! $fileVolume->is_directory && $isDir == 'NOK') {
|
if ($isDir == 'NOK' && ! $fileVolume->is_directory) {
|
||||||
|
$chmod = data_get($fileVolume, 'chmod');
|
||||||
|
$chown = data_get($fileVolume, 'chown');
|
||||||
if ($content) {
|
if ($content) {
|
||||||
$content = base64_encode($content);
|
$content = base64_encode($content);
|
||||||
$chmod = $fileVolume->chmod;
|
|
||||||
$chown = $fileVolume->chown;
|
|
||||||
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
|
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
|
||||||
$commands->push("chmod +x $path");
|
} else {
|
||||||
if ($chown) {
|
$commands->push("touch $path");
|
||||||
$commands->push("chown $chown $path");
|
}
|
||||||
}
|
$commands->push("chmod +x $path");
|
||||||
if ($chmod) {
|
if ($chown) {
|
||||||
$commands->push("chmod $chmod $path");
|
$commands->push("chown $chown $path");
|
||||||
}
|
}
|
||||||
|
if ($chmod) {
|
||||||
|
$commands->push("chmod $chmod $path");
|
||||||
}
|
}
|
||||||
} elseif ($isDir == 'NOK' && $fileVolume->is_directory) {
|
} elseif ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class Project extends BaseModel
|
|||||||
|
|
||||||
public function resource_count()
|
public function resource_count()
|
||||||
{
|
{
|
||||||
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count() + $this->keydbs()->count() + $this->dragonflies()->count() + $this->services()->count() + $this->clickhouses()->count();
|
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count() + $this->keydbs()->count() + $this->dragonflies()->count() + $this->clickhouses()->count() + $this->services()->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function databases()
|
public function databases()
|
||||||
|
|||||||
@@ -19,85 +19,23 @@ use Spatie\Url\Url;
|
|||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
#[OA\Schema(
|
#[OA\Schema(
|
||||||
description: 'Application model',
|
description: 'Server model',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: [
|
properties: [
|
||||||
'id' => ['type' => 'integer'],
|
'id' => ['type' => 'integer'],
|
||||||
'repository_project_id' => ['type' => 'integer', 'nullable' => true],
|
|
||||||
'uuid' => ['type' => 'string'],
|
'uuid' => ['type' => 'string'],
|
||||||
'name' => ['type' => 'string'],
|
'name' => ['type' => 'string'],
|
||||||
'fqdn' => ['type' => 'string'],
|
'description' => ['type' => 'string'],
|
||||||
'config_hash' => ['type' => 'string'],
|
'ip' => ['type' => 'string'],
|
||||||
'git_repository' => ['type' => 'string'],
|
'user' => ['type' => 'string'],
|
||||||
'git_branch' => ['type' => 'string'],
|
'port' => ['type' => 'integer'],
|
||||||
'git_commit_sha' => ['type' => 'string'],
|
'proxy' => ['type' => 'object'],
|
||||||
'git_full_url' => ['type' => 'string', 'nullable' => true],
|
'high_disk_usage_notification_sent' => ['type' => 'boolean'],
|
||||||
'docker_registry_image_name' => ['type' => 'string', 'nullable' => true],
|
'unreachable_notification_sent' => ['type' => 'boolean'],
|
||||||
'docker_registry_image_tag' => ['type' => 'string', 'nullable' => true],
|
'unreachable_count' => ['type' => 'integer'],
|
||||||
'build_pack' => ['type' => 'string'],
|
'validation_logs' => ['type' => 'string'],
|
||||||
'static_image' => ['type' => 'string'],
|
'log_drain_notification_sent' => ['type' => 'boolean'],
|
||||||
'install_command' => ['type' => 'string'],
|
'swarm_cluster' => ['type' => 'string'],
|
||||||
'build_command' => ['type' => 'string'],
|
|
||||||
'start_command' => ['type' => 'string'],
|
|
||||||
'ports_exposes' => ['type' => 'string'],
|
|
||||||
'ports_mappings' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'base_directory' => ['type' => 'string'],
|
|
||||||
'publish_directory' => ['type' => 'string'],
|
|
||||||
'health_check_path' => ['type' => 'string'],
|
|
||||||
'health_check_port' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'health_check_host' => ['type' => 'string'],
|
|
||||||
'health_check_method' => ['type' => 'string'],
|
|
||||||
'health_check_return_code' => ['type' => 'integer'],
|
|
||||||
'health_check_scheme' => ['type' => 'string'],
|
|
||||||
'health_check_response_text' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'health_check_interval' => ['type' => 'integer'],
|
|
||||||
'health_check_timeout' => ['type' => 'integer'],
|
|
||||||
'health_check_retries' => ['type' => 'integer'],
|
|
||||||
'health_check_start_period' => ['type' => 'integer'],
|
|
||||||
'limits_memory' => ['type' => 'string'],
|
|
||||||
'limits_memory_swap' => ['type' => 'string'],
|
|
||||||
'limits_memory_swappiness' => ['type' => 'integer'],
|
|
||||||
'limits_memory_reservation' => ['type' => 'string'],
|
|
||||||
'limits_cpus' => ['type' => 'string'],
|
|
||||||
'limits_cpuset' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'limits_cpu_shares' => ['type' => 'integer'],
|
|
||||||
'status' => ['type' => 'string'],
|
|
||||||
'preview_url_template' => ['type' => 'string'],
|
|
||||||
'destination_type' => ['type' => 'string'],
|
|
||||||
'destination_id' => ['type' => 'integer'],
|
|
||||||
'source_type' => ['type' => 'string'],
|
|
||||||
'source_id' => ['type' => 'integer'],
|
|
||||||
'private_key_id' => ['type' => 'integer', 'nullable' => true],
|
|
||||||
'environment_id' => ['type' => 'integer'],
|
|
||||||
'created_at' => ['type' => 'string', 'format' => 'date-time'],
|
|
||||||
'updated_at' => ['type' => 'string', 'format' => 'date-time'],
|
|
||||||
'description' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'dockerfile' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'health_check_enabled' => ['type' => 'boolean'],
|
|
||||||
'dockerfile_location' => ['type' => 'string'],
|
|
||||||
'custom_labels' => ['type' => 'string'],
|
|
||||||
'dockerfile_target_build' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'manual_webhook_secret_github' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'manual_webhook_secret_gitlab' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'docker_compose_location' => ['type' => 'string'],
|
|
||||||
'docker_compose' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'docker_compose_raw' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'docker_compose_domains' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true],
|
|
||||||
'docker_compose_custom_start_command' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'docker_compose_custom_build_command' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'swarm_replicas' => ['type' => 'integer'],
|
|
||||||
'swarm_placement_constraints' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'manual_webhook_secret_bitbucket' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'custom_docker_run_options' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'post_deployment_command' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'post_deployment_command_container' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'pre_deployment_command' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'pre_deployment_command_container' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'watch_paths' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'custom_healthcheck_found' => ['type' => 'boolean'],
|
|
||||||
'manual_webhook_secret_gitea' => ['type' => 'string', 'nullable' => true],
|
|
||||||
'redirect' => ['type' => 'string'],
|
|
||||||
]
|
]
|
||||||
)]
|
)]
|
||||||
|
|
||||||
@@ -123,6 +61,37 @@ class Server extends BaseModel
|
|||||||
ServerSetting::create([
|
ServerSetting::create([
|
||||||
'server_id' => $server->id,
|
'server_id' => $server->id,
|
||||||
]);
|
]);
|
||||||
|
if ($server->id === 0) {
|
||||||
|
if ($server->isSwarm()) {
|
||||||
|
SwarmDocker::create([
|
||||||
|
'id' => 0,
|
||||||
|
'name' => 'coolify',
|
||||||
|
'network' => 'coolify-overlay',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
StandaloneDocker::create([
|
||||||
|
'id' => 0,
|
||||||
|
'name' => 'coolify',
|
||||||
|
'network' => 'coolify',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($server->isSwarm()) {
|
||||||
|
SwarmDocker::create([
|
||||||
|
'name' => 'coolify-overlay',
|
||||||
|
'network' => 'coolify-overlay',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
StandaloneDocker::create([
|
||||||
|
'name' => 'coolify',
|
||||||
|
'network' => 'coolify',
|
||||||
|
'server_id' => $server->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
static::deleting(function ($server) {
|
static::deleting(function ($server) {
|
||||||
$server->destinations()->each(function ($destination) {
|
$server->destinations()->each(function ($destination) {
|
||||||
@@ -176,41 +145,6 @@ class Server extends BaseModel
|
|||||||
return $this->hasOne(ServerSetting::class);
|
return $this->hasOne(ServerSetting::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addInitialNetwork()
|
|
||||||
{
|
|
||||||
if ($this->id === 0) {
|
|
||||||
if ($this->isSwarm()) {
|
|
||||||
SwarmDocker::create([
|
|
||||||
'id' => 0,
|
|
||||||
'name' => 'coolify',
|
|
||||||
'network' => 'coolify-overlay',
|
|
||||||
'server_id' => $this->id,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
StandaloneDocker::create([
|
|
||||||
'id' => 0,
|
|
||||||
'name' => 'coolify',
|
|
||||||
'network' => 'coolify',
|
|
||||||
'server_id' => $this->id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($this->isSwarm()) {
|
|
||||||
SwarmDocker::create([
|
|
||||||
'name' => 'coolify-overlay',
|
|
||||||
'network' => 'coolify-overlay',
|
|
||||||
'server_id' => $this->id,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
StandaloneDocker::create([
|
|
||||||
'name' => 'coolify',
|
|
||||||
'network' => 'coolify',
|
|
||||||
'server_id' => $this->id,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setupDefault404Redirect()
|
public function setupDefault404Redirect()
|
||||||
{
|
{
|
||||||
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
$dynamic_conf_path = $this->proxyPath().'/dynamic';
|
||||||
@@ -318,11 +252,11 @@ respond 404
|
|||||||
|
|
||||||
public function setupDynamicProxyConfiguration()
|
public function setupDynamicProxyConfiguration()
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$dynamic_config_path = $this->proxyPath().'/dynamic';
|
$dynamic_config_path = $this->proxyPath().'/dynamic';
|
||||||
if ($this->proxyType() === 'TRAEFIK_V2') {
|
if ($this->proxyType() === 'TRAEFIK_V2') {
|
||||||
$file = "$dynamic_config_path/coolify.yaml";
|
$file = "$dynamic_config_path/coolify.yaml";
|
||||||
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0)) {
|
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
"rm -f $file",
|
"rm -f $file",
|
||||||
], $this);
|
], $this);
|
||||||
@@ -428,7 +362,7 @@ respond 404
|
|||||||
}
|
}
|
||||||
} elseif ($this->proxyType() === 'CADDY') {
|
} elseif ($this->proxyType() === 'CADDY') {
|
||||||
$file = "$dynamic_config_path/coolify.caddy";
|
$file = "$dynamic_config_path/coolify.caddy";
|
||||||
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0)) {
|
if (empty($settings->fqdn) || (isCloud() && $this->id !== 0) || ! $this->isLocalhost()) {
|
||||||
instant_remote_process([
|
instant_remote_process([
|
||||||
"rm -f $file",
|
"rm -f $file",
|
||||||
], $this);
|
], $this);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
|
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
|
||||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
|
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
|
||||||
|
'is_container_label_readonly_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label readonly.'],
|
||||||
'config_hash' => ['type' => 'string', 'description' => 'The hash of the service configuration.'],
|
'config_hash' => ['type' => 'string', 'description' => 'The hash of the service configuration.'],
|
||||||
'service_type' => ['type' => 'string', 'description' => 'The type of the service.'],
|
'service_type' => ['type' => 'string', 'description' => 'The type of the service.'],
|
||||||
'created_at' => ['type' => 'string', 'description' => 'The date and time when the service was created.'],
|
'created_at' => ['type' => 'string', 'description' => 'The date and time when the service was created.'],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -31,16 +32,9 @@ class StandaloneClickhouse extends BaseModel
|
|||||||
'is_readonly' => true,
|
'is_readonly' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::forceDeleting(function ($database) {
|
||||||
$storages = $database->persistentStorages()->get();
|
|
||||||
$server = data_get($database, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$database->scheduledBackups()->delete();
|
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
@@ -91,6 +85,17 @@ class StandaloneClickhouse extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function realStatus()
|
public function realStatus()
|
||||||
{
|
{
|
||||||
return $this->getRawOriginal('status');
|
return $this->getRawOriginal('status');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -31,16 +32,9 @@ class StandaloneDragonfly extends BaseModel
|
|||||||
'is_readonly' => true,
|
'is_readonly' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::forceDeleting(function ($database) {
|
||||||
$database->scheduledBackups()->delete();
|
|
||||||
$storages = $database->persistentStorages()->get();
|
|
||||||
$server = data_get($database, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
@@ -91,6 +85,17 @@ class StandaloneDragonfly extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function realStatus()
|
public function realStatus()
|
||||||
{
|
{
|
||||||
return $this->getRawOriginal('status');
|
return $this->getRawOriginal('status');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -31,16 +32,9 @@ class StandaloneKeydb extends BaseModel
|
|||||||
'is_readonly' => true,
|
'is_readonly' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::forceDeleting(function ($database) {
|
||||||
$database->scheduledBackups()->delete();
|
|
||||||
$storages = $database->persistentStorages()->get();
|
|
||||||
$server = data_get($database, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
@@ -91,6 +85,17 @@ class StandaloneKeydb extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function realStatus()
|
public function realStatus()
|
||||||
{
|
{
|
||||||
return $this->getRawOriginal('status');
|
return $this->getRawOriginal('status');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -31,16 +32,9 @@ class StandaloneMariadb extends BaseModel
|
|||||||
'is_readonly' => true,
|
'is_readonly' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::forceDeleting(function ($database) {
|
||||||
$storages = $database->persistentStorages()->get();
|
|
||||||
$server = data_get($database, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$database->scheduledBackups()->delete();
|
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
@@ -91,6 +85,17 @@ class StandaloneMariadb extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function realStatus()
|
public function realStatus()
|
||||||
{
|
{
|
||||||
return $this->getRawOriginal('status');
|
return $this->getRawOriginal('status');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -35,16 +36,9 @@ class StandaloneMongodb extends BaseModel
|
|||||||
'is_readonly' => true,
|
'is_readonly' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::forceDeleting(function ($database) {
|
||||||
$storages = $database->persistentStorages()->get();
|
|
||||||
$server = data_get($database, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$database->scheduledBackups()->delete();
|
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
@@ -95,6 +89,17 @@ class StandaloneMongodb extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function realStatus()
|
public function realStatus()
|
||||||
{
|
{
|
||||||
return $this->getRawOriginal('status');
|
return $this->getRawOriginal('status');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -32,16 +33,9 @@ class StandaloneMysql extends BaseModel
|
|||||||
'is_readonly' => true,
|
'is_readonly' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::forceDeleting(function ($database) {
|
||||||
$storages = $database->persistentStorages()->get();
|
|
||||||
$server = data_get($database, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$database->scheduledBackups()->delete();
|
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
@@ -92,6 +86,17 @@ class StandaloneMysql extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function realStatus()
|
public function realStatus()
|
||||||
{
|
{
|
||||||
return $this->getRawOriginal('status');
|
return $this->getRawOriginal('status');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -32,16 +33,9 @@ class StandalonePostgresql extends BaseModel
|
|||||||
'is_readonly' => true,
|
'is_readonly' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::forceDeleting(function ($database) {
|
||||||
$storages = $database->persistentStorages()->get();
|
|
||||||
$server = data_get($database, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$database->scheduledBackups()->delete();
|
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
@@ -61,6 +55,18 @@ class StandalonePostgresql extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
ray('Deleting volume: '.$storage->name);
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function isConfigurationChanged(bool $save = false)
|
public function isConfigurationChanged(bool $save = false)
|
||||||
{
|
{
|
||||||
$newConfigHash = $this->image.$this->ports_mappings.$this->postgres_initdb_args.$this->postgres_host_auth_method;
|
$newConfigHash = $this->image.$this->ports_mappings.$this->postgres_initdb_args.$this->postgres_host_auth_method;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -27,16 +28,9 @@ class StandaloneRedis extends BaseModel
|
|||||||
'is_readonly' => true,
|
'is_readonly' => true,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($database) {
|
static::forceDeleting(function ($database) {
|
||||||
$database->scheduledBackups()->delete();
|
|
||||||
$storages = $database->persistentStorages()->get();
|
|
||||||
$server = data_get($database, 'destination.server');
|
|
||||||
if ($server) {
|
|
||||||
foreach ($storages as $storage) {
|
|
||||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
|
$database->scheduledBackups()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
$database->tags()->detach();
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
@@ -87,6 +81,17 @@ class StandaloneRedis extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete_volumes(Collection $persistentStorages)
|
||||||
|
{
|
||||||
|
if ($persistentStorages->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
foreach ($persistentStorages as $storage) {
|
||||||
|
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function realStatus()
|
public function realStatus()
|
||||||
{
|
{
|
||||||
return $this->getRawOriginal('status');
|
return $this->getRawOriginal('status');
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Notifications\Channels;
|
namespace App\Notifications\Channels;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
@@ -14,7 +13,7 @@ class TransactionalEmailChannel
|
|||||||
{
|
{
|
||||||
public function send(User $notifiable, Notification $notification): void
|
public function send(User $notifiable, Notification $notification): void
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
|
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
|
||||||
Log::info('SMTP/Resend not enabled');
|
Log::info('SMTP/Resend not enabled');
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class ResetPassword extends Notification
|
|||||||
|
|
||||||
public function __construct($token)
|
public function __construct($token)
|
||||||
{
|
{
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = \App\Models\InstanceSettings::get();
|
||||||
$this->token = $token;
|
$this->token = $token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\PersonalAccessToken;
|
use App\Models\PersonalAccessToken;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Laravel\Sanctum\Sanctum;
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
@@ -14,6 +16,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
|
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
|
||||||
|
|
||||||
Http::macro('github', function (string $api_url, ?string $github_access_token = null) {
|
Http::macro('github', function (string $api_url, ?string $github_access_token = null) {
|
||||||
if ($github_access_token) {
|
if ($github_access_token) {
|
||||||
return Http::withHeaders([
|
return Http::withHeaders([
|
||||||
@@ -27,5 +30,9 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
])->baseUrl($api_url);
|
])->baseUrl($api_url);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// if (! env('CI')) {
|
||||||
|
// View::share('instanceSettings', InstanceSettings::get());
|
||||||
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use App\Actions\Fortify\CreateNewUser;
|
|||||||
use App\Actions\Fortify\ResetUserPassword;
|
use App\Actions\Fortify\ResetUserPassword;
|
||||||
use App\Actions\Fortify\UpdateUserPassword;
|
use App\Actions\Fortify\UpdateUserPassword;
|
||||||
use App\Actions\Fortify\UpdateUserProfileInformation;
|
use App\Actions\Fortify\UpdateUserProfileInformation;
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\OauthSetting;
|
use App\Models\OauthSetting;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Cache\RateLimiting\Limit;
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
@@ -45,7 +44,7 @@ class FortifyServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
Fortify::createUsersUsing(CreateNewUser::class);
|
Fortify::createUsersUsing(CreateNewUser::class);
|
||||||
Fortify::registerView(function () {
|
Fortify::registerView(function () {
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if (! $settings->is_registration_enabled) {
|
if (! $settings->is_registration_enabled) {
|
||||||
return redirect()->route('login');
|
return redirect()->route('login');
|
||||||
}
|
}
|
||||||
@@ -57,7 +56,7 @@ class FortifyServiceProvider extends ServiceProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
Fortify::loginView(function () {
|
Fortify::loginView(function () {
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
|
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
|
||||||
$users = User::count();
|
$users = User::count();
|
||||||
if ($users == 0) {
|
if ($users == 0) {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const SUPPORTED_OS = [
|
|||||||
'ubuntu debian raspbian',
|
'ubuntu debian raspbian',
|
||||||
'centos fedora rhel ol rocky amzn almalinux',
|
'centos fedora rhel ol rocky amzn almalinux',
|
||||||
'sles opensuse-leap opensuse-tumbleweed',
|
'sles opensuse-leap opensuse-tumbleweed',
|
||||||
|
'arch',
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
|
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
|
||||||
|
|||||||
@@ -48,9 +48,13 @@ function format_docker_command_output_to_json($rawOutput): Collection
|
|||||||
$outputLines = collect($outputLines);
|
$outputLines = collect($outputLines);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $outputLines
|
try {
|
||||||
->reject(fn ($line) => empty($line))
|
return $outputLines
|
||||||
->map(fn ($outputLine) => json_decode($outputLine, true, flags: JSON_THROW_ON_ERROR));
|
->reject(fn ($line) => empty($line))
|
||||||
|
->map(fn ($outputLine) => json_decode($outputLine, true, flags: JSON_THROW_ON_ERROR));
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_docker_labels_to_json(string|array $rawOutput): Collection
|
function format_docker_labels_to_json(string|array $rawOutput): Collection
|
||||||
|
|||||||
@@ -5,7 +5,24 @@ use App\Models\Application;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
function connectProxyToNetworks(Server $server)
|
function collectProxyDockerNetworksByServer(Server $server)
|
||||||
|
{
|
||||||
|
if (! $server->isFunctional()) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
$proxyType = $server->proxyType();
|
||||||
|
if (is_null($proxyType) || $proxyType === 'NONE') {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
$networks = instant_remote_process(['docker inspect --format="{{json .NetworkSettings.Networks }}" coolify-proxy'], $server, false);
|
||||||
|
$networks = collect($networks)->map(function ($network) {
|
||||||
|
return collect(json_decode($network))->keys();
|
||||||
|
})->flatten()->unique();
|
||||||
|
|
||||||
|
return $networks;
|
||||||
|
|
||||||
|
}
|
||||||
|
function collectDockerNetworksByServer(Server $server)
|
||||||
{
|
{
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
$networks = collect($server->swarmDockers)->map(function ($docker) {
|
||||||
@@ -43,6 +60,18 @@ function connectProxyToNetworks(Server $server)
|
|||||||
if ($networks->count() === 0) {
|
if ($networks->count() === 0) {
|
||||||
$networks = collect(['coolify-overlay']);
|
$networks = collect(['coolify-overlay']);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if ($networks->count() === 0) {
|
||||||
|
$networks = collect(['coolify']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $networks;
|
||||||
|
}
|
||||||
|
function connectProxyToNetworks(Server $server)
|
||||||
|
{
|
||||||
|
$networks = collectDockerNetworksByServer($server);
|
||||||
|
if ($server->isSwarm()) {
|
||||||
$commands = $networks->map(function ($network) {
|
$commands = $networks->map(function ($network) {
|
||||||
return [
|
return [
|
||||||
"echo 'Connecting coolify-proxy to $network network...'",
|
"echo 'Connecting coolify-proxy to $network network...'",
|
||||||
@@ -51,9 +80,6 @@ function connectProxyToNetworks(Server $server)
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if ($networks->count() === 0) {
|
|
||||||
$networks = collect(['coolify']);
|
|
||||||
}
|
|
||||||
$commands = $networks->map(function ($network) {
|
$commands = $networks->map(function ($network) {
|
||||||
return [
|
return [
|
||||||
"echo 'Connecting coolify-proxy to $network network...'",
|
"echo 'Connecting coolify-proxy to $network network...'",
|
||||||
@@ -106,7 +132,7 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
'services' => [
|
'services' => [
|
||||||
'traefik' => [
|
'traefik' => [
|
||||||
'container_name' => 'coolify-proxy',
|
'container_name' => 'coolify-proxy',
|
||||||
'image' => 'traefik:v2.10',
|
'image' => 'traefik:v2.11',
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'extra_hosts' => [
|
'extra_hosts' => [
|
||||||
'host.docker.internal:host-gateway',
|
'host.docker.internal:host-gateway',
|
||||||
|
|||||||
@@ -73,6 +73,13 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
|||||||
"echo '$content' | base64 -d | tee $fileLocation",
|
"echo '$content' | base64 -d | tee $fileLocation",
|
||||||
], $server);
|
], $server);
|
||||||
} elseif ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) {
|
} elseif ($isFile == 'NOK' && $isDir == 'NOK' && $fileVolume->is_directory && $isInit) {
|
||||||
|
// Does not exists (no dir or file), flagged as directory, is init
|
||||||
|
$fileVolume->content = null;
|
||||||
|
$fileVolume->is_directory = true;
|
||||||
|
$fileVolume->save();
|
||||||
|
instant_remote_process(["mkdir -p $fileLocation"], $server);
|
||||||
|
} elseif ($isFile == 'NOK' && $isDir == 'NOK' && ! $fileVolume->is_directory && $isInit && ! $content) {
|
||||||
|
// Does not exists (no dir or file), not flagged as directory, is init, has no content => create directory
|
||||||
$fileVolume->content = null;
|
$fileVolume->content = null;
|
||||||
$fileVolume->is_directory = true;
|
$fileVolume->is_directory = true;
|
||||||
$fileVolume->save();
|
$fileVolume->save();
|
||||||
@@ -88,6 +95,9 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
|||||||
try {
|
try {
|
||||||
$name = data_get($resource, 'name');
|
$name = data_get($resource, 'name');
|
||||||
$dockerComposeRaw = data_get($resource, 'service.docker_compose_raw');
|
$dockerComposeRaw = data_get($resource, 'service.docker_compose_raw');
|
||||||
|
if (! $dockerComposeRaw) {
|
||||||
|
throw new \Exception('No compose file found or not a valid YAML file.');
|
||||||
|
}
|
||||||
$dockerCompose = Yaml::parse($dockerComposeRaw);
|
$dockerCompose = Yaml::parse($dockerComposeRaw);
|
||||||
|
|
||||||
// Switch Image
|
// Switch Image
|
||||||
@@ -106,7 +116,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
|||||||
if ($resourceFqdns->count() === 1) {
|
if ($resourceFqdns->count() === 1) {
|
||||||
$resourceFqdns = $resourceFqdns->first();
|
$resourceFqdns = $resourceFqdns->first();
|
||||||
$variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '');
|
$variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '');
|
||||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'LIKE', "{$variableName}_%")->first();
|
||||||
$fqdn = Url::fromString($resourceFqdns);
|
$fqdn = Url::fromString($resourceFqdns);
|
||||||
$port = $fqdn->getPort();
|
$port = $fqdn->getPort();
|
||||||
$path = $fqdn->getPath();
|
$path = $fqdn->getPath();
|
||||||
@@ -125,7 +135,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '');
|
$variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '');
|
||||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'LIKE', "{$variableName}_%")->first();
|
||||||
$url = Url::fromString($fqdn);
|
$url = Url::fromString($fqdn);
|
||||||
$port = $url->getPort();
|
$port = $url->getPort();
|
||||||
$path = $url->getPath();
|
$path = $url->getPath();
|
||||||
|
|||||||
@@ -244,13 +244,13 @@ function generate_application_name(string $git_repository, string $git_branch, ?
|
|||||||
|
|
||||||
function is_transactional_emails_active(): bool
|
function is_transactional_emails_active(): bool
|
||||||
{
|
{
|
||||||
return isEmailEnabled(InstanceSettings::get());
|
return isEmailEnabled(\App\Models\InstanceSettings::get());
|
||||||
}
|
}
|
||||||
|
|
||||||
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
|
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
|
||||||
{
|
{
|
||||||
if (! $settings) {
|
if (! $settings) {
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
}
|
}
|
||||||
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
|
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
|
||||||
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
|
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
|
||||||
@@ -284,7 +284,7 @@ function base_ip(): string
|
|||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
return 'localhost';
|
return 'localhost';
|
||||||
}
|
}
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if ($settings->public_ipv4) {
|
if ($settings->public_ipv4) {
|
||||||
return "$settings->public_ipv4";
|
return "$settings->public_ipv4";
|
||||||
}
|
}
|
||||||
@@ -312,7 +312,7 @@ function getFqdnWithoutPort(string $fqdn)
|
|||||||
*/
|
*/
|
||||||
function base_url(bool $withPort = true): string
|
function base_url(bool $withPort = true): string
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if ($settings->fqdn) {
|
if ($settings->fqdn) {
|
||||||
return $settings->fqdn;
|
return $settings->fqdn;
|
||||||
}
|
}
|
||||||
@@ -379,7 +379,7 @@ function send_internal_notification(string $message): void
|
|||||||
}
|
}
|
||||||
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void
|
function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void
|
||||||
{
|
{
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$type = set_transanctional_email_settings($settings);
|
$type = set_transanctional_email_settings($settings);
|
||||||
if (! $type) {
|
if (! $type) {
|
||||||
throw new Exception('No email settings found.');
|
throw new Exception('No email settings found.');
|
||||||
@@ -774,6 +774,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$allServices = get_service_templates();
|
$allServices = get_service_templates();
|
||||||
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
|
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
|
||||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||||
|
$topLevelConfigs = collect(data_get($yaml, 'configs', []));
|
||||||
|
$topLevelSecrets = collect(data_get($yaml, 'secrets', []));
|
||||||
$services = data_get($yaml, 'services');
|
$services = data_get($yaml, 'services');
|
||||||
|
|
||||||
$generatedServiceFQDNS = collect([]);
|
$generatedServiceFQDNS = collect([]);
|
||||||
@@ -1384,9 +1386,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
}
|
}
|
||||||
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
|
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
|
||||||
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
|
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
|
||||||
$found_env = $envs_from_coolify->where('key', $key)->first();
|
if (! str($value)->startsWith('$')) {
|
||||||
if ($found_env) {
|
$found_env = $envs_from_coolify->where('key', $key)->first();
|
||||||
return $found_env->value;
|
if ($found_env) {
|
||||||
|
return $found_env->value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
@@ -1400,6 +1404,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
'services' => $services->toArray(),
|
'services' => $services->toArray(),
|
||||||
'volumes' => $topLevelVolumes->toArray(),
|
'volumes' => $topLevelVolumes->toArray(),
|
||||||
'networks' => $topLevelNetworks->toArray(),
|
'networks' => $topLevelNetworks->toArray(),
|
||||||
|
'configs' => $topLevelConfigs->toArray(),
|
||||||
|
'secrets' => $topLevelSecrets->toArray(),
|
||||||
];
|
];
|
||||||
$yaml = data_forget($yaml, 'services.*.volumes.*.content');
|
$yaml = data_forget($yaml, 'services.*.volumes.*.content');
|
||||||
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||||
@@ -1439,6 +1445,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
}
|
}
|
||||||
|
|
||||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||||
|
$topLevelConfigs = collect(data_get($yaml, 'configs', []));
|
||||||
|
$topLevelSecrets = collect(data_get($yaml, 'secrets', []));
|
||||||
$services = data_get($yaml, 'services');
|
$services = data_get($yaml, 'services');
|
||||||
|
|
||||||
$generatedServiceFQDNS = collect([]);
|
$generatedServiceFQDNS = collect([]);
|
||||||
@@ -1480,128 +1488,259 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
|
|
||||||
$baseName = generateApplicationContainerName($resource, $pull_request_id);
|
$baseName = generateApplicationContainerName($resource, $pull_request_id);
|
||||||
$containerName = "$serviceName-$baseName";
|
$containerName = "$serviceName-$baseName";
|
||||||
if (count($serviceVolumes) > 0) {
|
if ($resource->compose_parsing_version === '1') {
|
||||||
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
|
if (count($serviceVolumes) > 0) {
|
||||||
if (is_string($volume)) {
|
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
|
||||||
$volume = str($volume);
|
if (is_string($volume)) {
|
||||||
if ($volume->contains(':') && ! $volume->startsWith('/')) {
|
$volume = str($volume);
|
||||||
$name = $volume->before(':');
|
if ($volume->contains(':') && ! $volume->startsWith('/')) {
|
||||||
$mount = $volume->after(':');
|
|
||||||
if ($name->startsWith('.') || $name->startsWith('~')) {
|
|
||||||
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
|
|
||||||
if ($name->startsWith('.')) {
|
|
||||||
$name = $name->replaceFirst('.', $dir);
|
|
||||||
}
|
|
||||||
if ($name->startsWith('~')) {
|
|
||||||
$name = $name->replaceFirst('~', $dir);
|
|
||||||
}
|
|
||||||
if ($pull_request_id !== 0) {
|
|
||||||
$name = $name."-pr-$pull_request_id";
|
|
||||||
}
|
|
||||||
$volume = str("$name:$mount");
|
|
||||||
} else {
|
|
||||||
if ($pull_request_id !== 0) {
|
|
||||||
$name = $name."-pr-$pull_request_id";
|
|
||||||
$volume = str("$name:$mount");
|
|
||||||
if ($topLevelVolumes->has($name)) {
|
|
||||||
$v = $topLevelVolumes->get($name);
|
|
||||||
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
|
||||||
// Do nothing
|
|
||||||
} else {
|
|
||||||
if (is_null(data_get($v, 'name'))) {
|
|
||||||
data_set($v, 'name', $name);
|
|
||||||
data_set($topLevelVolumes, $name, $v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$topLevelVolumes->put($name, [
|
|
||||||
'name' => $name,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($topLevelVolumes->has($name->value())) {
|
|
||||||
$v = $topLevelVolumes->get($name->value());
|
|
||||||
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
|
||||||
// Do nothing
|
|
||||||
} else {
|
|
||||||
if (is_null(data_get($v, 'name'))) {
|
|
||||||
data_set($topLevelVolumes, $name->value(), $v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$topLevelVolumes->put($name->value(), [
|
|
||||||
'name' => $name->value(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ($volume->startsWith('/')) {
|
|
||||||
$name = $volume->before(':');
|
$name = $volume->before(':');
|
||||||
$mount = $volume->after(':');
|
$mount = $volume->after(':');
|
||||||
if ($pull_request_id !== 0) {
|
if ($name->startsWith('.') || $name->startsWith('~')) {
|
||||||
$name = $name."-pr-$pull_request_id";
|
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
|
||||||
}
|
if ($name->startsWith('.')) {
|
||||||
$volume = str("$name:$mount");
|
$name = $name->replaceFirst('.', $dir);
|
||||||
}
|
}
|
||||||
}
|
if ($name->startsWith('~')) {
|
||||||
} elseif (is_array($volume)) {
|
$name = $name->replaceFirst('~', $dir);
|
||||||
$source = data_get($volume, 'source');
|
}
|
||||||
$target = data_get($volume, 'target');
|
if ($pull_request_id !== 0) {
|
||||||
$read_only = data_get($volume, 'read_only');
|
$name = $name."-pr-$pull_request_id";
|
||||||
if ($source && $target) {
|
}
|
||||||
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
|
$volume = str("$name:$mount");
|
||||||
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
|
|
||||||
if (str($source, '.')) {
|
|
||||||
$source = str($source)->replaceFirst('.', $dir);
|
|
||||||
}
|
|
||||||
if (str($source, '~')) {
|
|
||||||
$source = str($source)->replaceFirst('~', $dir);
|
|
||||||
}
|
|
||||||
if ($pull_request_id !== 0) {
|
|
||||||
$source = $source."-pr-$pull_request_id";
|
|
||||||
}
|
|
||||||
if ($read_only) {
|
|
||||||
data_set($volume, 'source', $source.':'.$target.':ro');
|
|
||||||
} else {
|
} else {
|
||||||
data_set($volume, 'source', $source.':'.$target);
|
if ($pull_request_id !== 0) {
|
||||||
}
|
$name = $name."-pr-$pull_request_id";
|
||||||
} else {
|
$volume = str("$name:$mount");
|
||||||
if ($pull_request_id !== 0) {
|
if ($topLevelVolumes->has($name)) {
|
||||||
$source = $source."-pr-$pull_request_id";
|
$v = $topLevelVolumes->get($name);
|
||||||
}
|
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||||
if ($read_only) {
|
// Do nothing
|
||||||
data_set($volume, 'source', $source.':'.$target.':ro');
|
} else {
|
||||||
} else {
|
if (is_null(data_get($v, 'name'))) {
|
||||||
data_set($volume, 'source', $source.':'.$target);
|
data_set($v, 'name', $name);
|
||||||
}
|
data_set($topLevelVolumes, $name, $v);
|
||||||
if (! str($source)->startsWith('/')) {
|
}
|
||||||
if ($topLevelVolumes->has($source)) {
|
|
||||||
$v = $topLevelVolumes->get($source);
|
|
||||||
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
|
||||||
// Do nothing
|
|
||||||
} else {
|
|
||||||
if (is_null(data_get($v, 'name'))) {
|
|
||||||
data_set($v, 'name', $source);
|
|
||||||
data_set($topLevelVolumes, $source, $v);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$topLevelVolumes->put($name, [
|
||||||
|
'name' => $name,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$topLevelVolumes->put($source, [
|
if ($topLevelVolumes->has($name->value())) {
|
||||||
'name' => $source,
|
$v = $topLevelVolumes->get($name->value());
|
||||||
]);
|
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
if (is_null(data_get($v, 'name'))) {
|
||||||
|
data_set($topLevelVolumes, $name->value(), $v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$topLevelVolumes->put($name->value(), [
|
||||||
|
'name' => $name->value(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($volume->startsWith('/')) {
|
||||||
|
$name = $volume->before(':');
|
||||||
|
$mount = $volume->after(':');
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$name = $name."-pr-$pull_request_id";
|
||||||
|
}
|
||||||
|
$volume = str("$name:$mount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (is_array($volume)) {
|
||||||
|
$source = data_get($volume, 'source');
|
||||||
|
$target = data_get($volume, 'target');
|
||||||
|
$read_only = data_get($volume, 'read_only');
|
||||||
|
if ($source && $target) {
|
||||||
|
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
|
||||||
|
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
|
||||||
|
if (str($source, '.')) {
|
||||||
|
$source = str($source)->replaceFirst('.', $dir);
|
||||||
|
}
|
||||||
|
if (str($source, '~')) {
|
||||||
|
$source = str($source)->replaceFirst('~', $dir);
|
||||||
|
}
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$source = $source."-pr-$pull_request_id";
|
||||||
|
}
|
||||||
|
if ($read_only) {
|
||||||
|
data_set($volume, 'source', $source.':'.$target.':ro');
|
||||||
|
} else {
|
||||||
|
data_set($volume, 'source', $source.':'.$target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$source = $source."-pr-$pull_request_id";
|
||||||
|
}
|
||||||
|
if ($read_only) {
|
||||||
|
data_set($volume, 'source', $source.':'.$target.':ro');
|
||||||
|
} else {
|
||||||
|
data_set($volume, 'source', $source.':'.$target);
|
||||||
|
}
|
||||||
|
if (! str($source)->startsWith('/')) {
|
||||||
|
if ($topLevelVolumes->has($source)) {
|
||||||
|
$v = $topLevelVolumes->get($source);
|
||||||
|
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
if (is_null(data_get($v, 'name'))) {
|
||||||
|
data_set($v, 'name', $source);
|
||||||
|
data_set($topLevelVolumes, $source, $v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$topLevelVolumes->put($source, [
|
||||||
|
'name' => $source,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (is_array($volume)) {
|
||||||
if (is_array($volume)) {
|
return data_get($volume, 'source');
|
||||||
return data_get($volume, 'source');
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return $volume->value();
|
return $volume->value();
|
||||||
});
|
});
|
||||||
data_set($service, 'volumes', $serviceVolumes->toArray());
|
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||||
|
}
|
||||||
|
} elseif ($resource->compose_parsing_version === '2') {
|
||||||
|
if (count($serviceVolumes) > 0) {
|
||||||
|
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
|
||||||
|
if (is_string($volume)) {
|
||||||
|
$volume = str($volume);
|
||||||
|
if ($volume->contains(':') && ! $volume->startsWith('/')) {
|
||||||
|
$name = $volume->before(':');
|
||||||
|
$mount = $volume->after(':');
|
||||||
|
if ($name->startsWith('.') || $name->startsWith('~')) {
|
||||||
|
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
|
||||||
|
if ($name->startsWith('.')) {
|
||||||
|
$name = $name->replaceFirst('.', $dir);
|
||||||
|
}
|
||||||
|
if ($name->startsWith('~')) {
|
||||||
|
$name = $name->replaceFirst('~', $dir);
|
||||||
|
}
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$name = $name."-pr-$pull_request_id";
|
||||||
|
}
|
||||||
|
$volume = str("$name:$mount");
|
||||||
|
} else {
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$uuid = $resource->uuid;
|
||||||
|
$name = $uuid."-$name-pr-$pull_request_id";
|
||||||
|
$volume = str("$name:$mount");
|
||||||
|
if ($topLevelVolumes->has($name)) {
|
||||||
|
$v = $topLevelVolumes->get($name);
|
||||||
|
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
if (is_null(data_get($v, 'name'))) {
|
||||||
|
data_set($v, 'name', $name);
|
||||||
|
data_set($topLevelVolumes, $name, $v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$topLevelVolumes->put($name, [
|
||||||
|
'name' => $name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$uuid = $resource->uuid;
|
||||||
|
$name = str($uuid."-$name");
|
||||||
|
$volume = str("$name:$mount");
|
||||||
|
if ($topLevelVolumes->has($name->value())) {
|
||||||
|
$v = $topLevelVolumes->get($name->value());
|
||||||
|
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
if (is_null(data_get($v, 'name'))) {
|
||||||
|
data_set($topLevelVolumes, $name->value(), $v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$topLevelVolumes->put($name->value(), [
|
||||||
|
'name' => $name->value(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($volume->startsWith('/')) {
|
||||||
|
$name = $volume->before(':');
|
||||||
|
$mount = $volume->after(':');
|
||||||
|
if ($pull_request_id !== 0) {
|
||||||
|
$name = $name."-pr-$pull_request_id";
|
||||||
|
}
|
||||||
|
$volume = str("$name:$mount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (is_array($volume)) {
|
||||||
|
$source = data_get($volume, 'source');
|
||||||
|
$target = data_get($volume, 'target');
|
||||||
|
$read_only = data_get($volume, 'read_only');
|
||||||
|
if ($source && $target) {
|
||||||
|
$uuid = $resource->uuid;
|
||||||
|
if ((str($source)->startsWith('.') || str($source)->startsWith('~') || str($source)->startsWith('/'))) {
|
||||||
|
$dir = base_configuration_dir().'/applications/'.$resource->uuid;
|
||||||
|
if (str($source, '.')) {
|
||||||
|
$source = str($source)->replaceFirst('.', $dir);
|
||||||
|
}
|
||||||
|
if (str($source, '~')) {
|
||||||
|
$source = str($source)->replaceFirst('~', $dir);
|
||||||
|
}
|
||||||
|
if ($read_only) {
|
||||||
|
data_set($volume, 'source', $source.':'.$target.':ro');
|
||||||
|
} else {
|
||||||
|
data_set($volume, 'source', $source.':'.$target);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($pull_request_id === 0) {
|
||||||
|
$source = $uuid."-$source";
|
||||||
|
} else {
|
||||||
|
$source = $uuid."-$source-pr-$pull_request_id";
|
||||||
|
}
|
||||||
|
if ($read_only) {
|
||||||
|
data_set($volume, 'source', $source.':'.$target.':ro');
|
||||||
|
} else {
|
||||||
|
data_set($volume, 'source', $source.':'.$target);
|
||||||
|
}
|
||||||
|
if (! str($source)->startsWith('/')) {
|
||||||
|
if ($topLevelVolumes->has($source)) {
|
||||||
|
$v = $topLevelVolumes->get($source);
|
||||||
|
if (data_get($v, 'driver_opts.type') === 'cifs') {
|
||||||
|
// Do nothing
|
||||||
|
} else {
|
||||||
|
if (is_null(data_get($v, 'name'))) {
|
||||||
|
data_set($v, 'name', $source);
|
||||||
|
data_set($topLevelVolumes, $source, $v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$topLevelVolumes->put($source, [
|
||||||
|
'name' => $source,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_array($volume)) {
|
||||||
|
return data_get($volume, 'source');
|
||||||
|
}
|
||||||
|
dispatch(new ServerFilesFromServerJob($resource));
|
||||||
|
|
||||||
|
return $volume->value();
|
||||||
|
});
|
||||||
|
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($pull_request_id !== 0 && count($serviceDependencies) > 0) {
|
if ($pull_request_id !== 0 && count($serviceDependencies) > 0) {
|
||||||
@@ -1890,14 +2029,20 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
domains: $fqdns,
|
domains: $fqdns,
|
||||||
serviceLabels: $serviceLabels,
|
serviceLabels: $serviceLabels,
|
||||||
generate_unique_uuid: $resource->build_pack === 'dockercompose',
|
generate_unique_uuid: $resource->build_pack === 'dockercompose',
|
||||||
image: data_get($service, 'image')
|
image: data_get($service, 'image'),
|
||||||
|
is_force_https_enabled: $resource->isForceHttpsEnabled(),
|
||||||
|
is_gzip_enabled: $resource->isGzipEnabled(),
|
||||||
|
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
|
||||||
));
|
));
|
||||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
|
$serviceLabels = $serviceLabels->merge(fqdnLabelsForCaddy(
|
||||||
network: $resource->destination->network,
|
network: $resource->destination->network,
|
||||||
uuid: $resource->uuid,
|
uuid: $resource->uuid,
|
||||||
domains: $fqdns,
|
domains: $fqdns,
|
||||||
serviceLabels: $serviceLabels,
|
serviceLabels: $serviceLabels,
|
||||||
image: data_get($service, 'image')
|
image: data_get($service, 'image'),
|
||||||
|
is_force_https_enabled: $resource->isForceHttpsEnabled(),
|
||||||
|
is_gzip_enabled: $resource->isGzipEnabled(),
|
||||||
|
is_stripprefix_enabled: $resource->isStripprefixEnabled(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1943,6 +2088,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
'services' => $services->toArray(),
|
'services' => $services->toArray(),
|
||||||
'volumes' => $topLevelVolumes->toArray(),
|
'volumes' => $topLevelVolumes->toArray(),
|
||||||
'networks' => $topLevelNetworks->toArray(),
|
'networks' => $topLevelNetworks->toArray(),
|
||||||
|
'configs' => $topLevelConfigs->toArray(),
|
||||||
|
'secrets' => $topLevelSecrets->toArray(),
|
||||||
];
|
];
|
||||||
if ($isSameDockerComposeFile) {
|
if ($isSameDockerComposeFile) {
|
||||||
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||||
@@ -2107,7 +2254,7 @@ function validate_dns_entry(string $fqdn, Server $server)
|
|||||||
if (str($host)->contains('sslip.io')) {
|
if (str($host)->contains('sslip.io')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
|
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
|
||||||
if (! $is_dns_validation_enabled) {
|
if (! $is_dns_validation_enabled) {
|
||||||
return true;
|
return true;
|
||||||
@@ -2166,7 +2313,7 @@ function ip_match($ip, $cidrs, &$match = null)
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId, string $uuid)
|
function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = null, ?string $uuid = null)
|
||||||
{
|
{
|
||||||
if (is_null($teamId)) {
|
if (is_null($teamId)) {
|
||||||
return response()->json(['error' => 'Team ID is required.'], 400);
|
return response()->json(['error' => 'Team ID is required.'], 400);
|
||||||
@@ -2182,8 +2329,12 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId,
|
|||||||
|
|
||||||
return str($domain);
|
return str($domain);
|
||||||
});
|
});
|
||||||
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid);
|
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
|
||||||
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid);
|
$serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid']);
|
||||||
|
if ($uuid) {
|
||||||
|
$applications = $applications->filter(fn ($app) => $app->uuid !== $uuid);
|
||||||
|
$serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid);
|
||||||
|
}
|
||||||
$domainFound = false;
|
$domainFound = false;
|
||||||
foreach ($applications as $app) {
|
foreach ($applications as $app) {
|
||||||
if (is_null($app->fqdn)) {
|
if (is_null($app->fqdn)) {
|
||||||
@@ -2223,7 +2374,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId,
|
|||||||
if ($domainFound) {
|
if ($domainFound) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if (data_get($settings, 'fqdn')) {
|
if (data_get($settings, 'fqdn')) {
|
||||||
$domain = data_get($settings, 'fqdn');
|
$domain = data_get($settings, 'fqdn');
|
||||||
if (str($domain)->endsWith('/')) {
|
if (str($domain)->endsWith('/')) {
|
||||||
@@ -2240,7 +2391,6 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
|
|||||||
if ($resource) {
|
if ($resource) {
|
||||||
if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') {
|
if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') {
|
||||||
$domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain');
|
$domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain');
|
||||||
ray($domains);
|
|
||||||
$domains = collect($domains);
|
$domains = collect($domains);
|
||||||
} else {
|
} else {
|
||||||
$domains = collect($resource->fqdns);
|
$domains = collect($resource->fqdns);
|
||||||
@@ -2296,7 +2446,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($resource) {
|
if ($resource) {
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if (data_get($settings, 'fqdn')) {
|
if (data_get($settings, 'fqdn')) {
|
||||||
$domain = data_get($settings, 'fqdn');
|
$domain = data_get($settings, 'fqdn');
|
||||||
if (str($domain)->endsWith('/')) {
|
if (str($domain)->endsWith('/')) {
|
||||||
@@ -2371,7 +2521,7 @@ function get_public_ips()
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
echo "Refreshing public ips!\n";
|
echo "Refreshing public ips!\n";
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
[$first, $second] = Process::concurrently(function (Pool $pool) {
|
[$first, $second] = Process::concurrently(function (Pool $pool) {
|
||||||
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
|
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
|
||||||
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');
|
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
"doctrine/dbal": "^3.6",
|
"doctrine/dbal": "^3.6",
|
||||||
"guzzlehttp/guzzle": "^7.5.0",
|
"guzzlehttp/guzzle": "^7.5.0",
|
||||||
"laravel/fortify": "^v1.16.0",
|
"laravel/fortify": "^v1.16.0",
|
||||||
"laravel/framework": "^v10.7.1",
|
"laravel/framework": "^v11",
|
||||||
"laravel/horizon": "^5.23.1",
|
"laravel/horizon": "^5.23.1",
|
||||||
"laravel/prompts": "^0.1.6",
|
"laravel/prompts": "^0.1.6",
|
||||||
"laravel/sanctum": "^v3.2.1",
|
"laravel/sanctum": "^v4.0",
|
||||||
"laravel/socialite": "^v5.14.0",
|
"laravel/socialite": "^v5.14.0",
|
||||||
"laravel/tinker": "^v2.8.1",
|
"laravel/tinker": "^v2.8.1",
|
||||||
"laravel/ui": "^4.2",
|
"laravel/ui": "^4.2",
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
"poliander/cron": "^3.0",
|
"poliander/cron": "^3.0",
|
||||||
"purplepixie/phpdns": "^2.1",
|
"purplepixie/phpdns": "^2.1",
|
||||||
"pusher/pusher-php-server": "^7.2",
|
"pusher/pusher-php-server": "^7.2",
|
||||||
"resend/resend-laravel": "^0.5.0",
|
"resend/resend-laravel": "^0.13.0",
|
||||||
"sentry/sentry-laravel": "^3.4",
|
"sentry/sentry-laravel": "^4.6",
|
||||||
"socialiteproviders/microsoft-azure": "^5.1",
|
"socialiteproviders/microsoft-azure": "^5.1",
|
||||||
"spatie/laravel-activitylog": "^4.7.3",
|
"spatie/laravel-activitylog": "^4.7.3",
|
||||||
"spatie/laravel-data": "^3.4.3",
|
"spatie/laravel-data": "^3.4.3",
|
||||||
@@ -48,10 +48,10 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^v1.21.0",
|
"fakerphp/faker": "^v1.21.0",
|
||||||
"laravel/dusk": "^v7.7.0",
|
"laravel/dusk": "^v8.0",
|
||||||
"laravel/pint": "^1.16",
|
"laravel/pint": "^1.16",
|
||||||
"mockery/mockery": "^1.5.1",
|
"mockery/mockery": "^1.5.1",
|
||||||
"nunomaduro/collision": "^v7.4.0",
|
"nunomaduro/collision": "^v8.1",
|
||||||
"pestphp/pest": "^2.16",
|
"pestphp/pest": "^2.16",
|
||||||
"phpstan/phpstan": "^1.10",
|
"phpstan/phpstan": "^1.10",
|
||||||
"phpunit/phpunit": "^10.0.19",
|
"phpunit/phpunit": "^10.0.19",
|
||||||
|
|||||||
2545
composer.lock
generated
2545
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -60,8 +60,9 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
|
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||||
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
|
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||||
|
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.307',
|
'release' => '4.0.0-beta.318',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.307';
|
return '4.0.0-beta.318';
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->string('compose_parsing_version')->default('1');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('compose_parsing_version');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_container_label_readonly_enabled')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_container_label_readonly_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_preserve_repository_enabled')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_preserve_repository_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_force_cleanup_enabled')->default(false)->after('is_sentinel_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_force_cleanup_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->boolean('health_check_enabled')->default(false)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->boolean('health_check_enabled')->default(true)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('servers', function (Blueprint $table) {
|
||||||
|
$table->text('validation_logs')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('servers', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('validation_logs');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -27,14 +27,14 @@ class InstanceSettingsSeeder extends Seeder
|
|||||||
$ipv4 = Process::run('curl -4s https://ifconfig.io')->output();
|
$ipv4 = Process::run('curl -4s https://ifconfig.io')->output();
|
||||||
$ipv4 = trim($ipv4);
|
$ipv4 = trim($ipv4);
|
||||||
$ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
|
$ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if (is_null($settings->public_ipv4) && $ipv4) {
|
if (is_null($settings->public_ipv4) && $ipv4) {
|
||||||
$settings->update(['public_ipv4' => $ipv4]);
|
$settings->update(['public_ipv4' => $ipv4]);
|
||||||
}
|
}
|
||||||
$ipv6 = Process::run('curl -6s https://ifconfig.io')->output();
|
$ipv6 = Process::run('curl -6s https://ifconfig.io')->output();
|
||||||
$ipv6 = trim($ipv6);
|
$ipv6 = trim($ipv6);
|
||||||
$ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
|
$ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
|
||||||
$settings = InstanceSettings::get();
|
$settings = \App\Models\InstanceSettings::get();
|
||||||
if (is_null($settings->public_ipv6) && $ipv6) {
|
if (is_null($settings->public_ipv6) && $ipv6) {
|
||||||
$settings->update(['public_ipv6' => $ipv6]);
|
$settings->update(['public_ipv6' => $ipv6]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
|
|
||||||
return new class extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*/
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
Schema::table('services', function (Blueprint $table) {
|
|
||||||
$table->string('git_repository')->nullable();
|
|
||||||
$table->string('git_branch')->nullable();
|
|
||||||
$table->nullableMorphs('source');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*/
|
|
||||||
public function down(): void
|
|
||||||
{
|
|
||||||
Schema::table('services', function (Blueprint $table) {
|
|
||||||
$table->dropColumn('git_repository');
|
|
||||||
$table->dropColumn('git_branch');
|
|
||||||
$table->dropMorphs('source');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -17,6 +17,7 @@ ARG TARGETPLATFORM
|
|||||||
# https://github.com/cloudflare/cloudflared/releases
|
# https://github.com/cloudflare/cloudflared/releases
|
||||||
ARG CLOUDFLARED_VERSION=2024.4.1
|
ARG CLOUDFLARED_VERSION=2024.4.1
|
||||||
ARG POSTGRES_VERSION=15
|
ARG POSTGRES_VERSION=15
|
||||||
|
ARG CI=true
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
|||||||
30
lang/zh-tw.json
Normal file
30
lang/zh-tw.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"auth.login": "登入",
|
||||||
|
"auth.login.azure": "使用 Microsoft 登入",
|
||||||
|
"auth.login.bitbucket": "使用 Bitbucket 登入",
|
||||||
|
"auth.login.github": "使用 GitHub 登入",
|
||||||
|
"auth.login.gitlab": "使用 Gitlab 登入",
|
||||||
|
"auth.login.google": "使用 Google 登入",
|
||||||
|
"auth.already_registered": "已經註冊?",
|
||||||
|
"auth.confirm_password": "確認密碼",
|
||||||
|
"auth.forgot_password": "忘記密碼",
|
||||||
|
"auth.forgot_password_send_email": "發送重設密碼電郵",
|
||||||
|
"auth.register_now": "註冊",
|
||||||
|
"auth.logout": "登出",
|
||||||
|
"auth.register": "註冊",
|
||||||
|
"auth.registration_disabled": "註冊已停用,請聯絡管理員。",
|
||||||
|
"auth.reset_password": "重設密碼",
|
||||||
|
"auth.failed": "這些憑證與我們的記錄不符。",
|
||||||
|
"auth.failed.callback": "無法處理來自登入提供者的回呼。",
|
||||||
|
"auth.failed.password": "密碼錯誤。",
|
||||||
|
"auth.failed.email": "找不到該電子郵件地址的使用者。",
|
||||||
|
"auth.throttle": "登入嘗試次數太多。請在 :seconds 秒後重試。",
|
||||||
|
"input.name": "名稱",
|
||||||
|
"input.email": "電子郵件",
|
||||||
|
"input.password": "密碼",
|
||||||
|
"input.password.again": "再次輸入密碼",
|
||||||
|
"input.code": "一次性代碼",
|
||||||
|
"input.recovery_code": "恢復碼",
|
||||||
|
"button.save": "儲存",
|
||||||
|
"repository.url": "<span class='text-helper'>例子</span><br>對於公共代碼倉庫,請使用 <span class='text-helper'>https://...</span>。<br>對於私有代碼倉庫,請使用 <span class='text-helper'>git@...</span>。<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> 分支將被選擇<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> 分支將被選擇。<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> 分支將被選擇。<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> 分支將被選擇。"
|
||||||
|
}
|
||||||
505
openapi.yaml
505
openapi.yaml
@@ -1343,6 +1343,14 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
-
|
||||||
|
name: cleanup
|
||||||
|
in: query
|
||||||
|
description: 'Delete configurations and volumes.'
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: 'Application deleted.'
|
description: 'Application deleted.'
|
||||||
@@ -1799,6 +1807,14 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
-
|
||||||
|
name: cleanup
|
||||||
|
in: query
|
||||||
|
description: 'Delete configurations and volumes.'
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: 'Database deleted.'
|
description: 'Database deleted.'
|
||||||
@@ -2994,6 +3010,44 @@ paths:
|
|||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
summary: Create
|
||||||
|
description: 'Create Project.'
|
||||||
|
operationId: cf067eb7cf18216cda3239329a2eeadb
|
||||||
|
requestBody:
|
||||||
|
description: 'Project created.'
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
uuid:
|
||||||
|
type: string
|
||||||
|
description: 'The name of the project.'
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: 'The description of the project.'
|
||||||
|
type: object
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: 'Project created.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
uuid: { type: string, example: og888os, description: 'The UUID of the project.' }
|
||||||
|
type: object
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
'/projects/{uuid}':
|
'/projects/{uuid}':
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -3025,6 +3079,79 @@ paths:
|
|||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
summary: Delete
|
||||||
|
description: 'Delete project by UUID.'
|
||||||
|
operationId: f668a936f505b4401948c74b6a663029
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'UUID of the application.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Project deleted.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string, example: 'Project deleted.' }
|
||||||
|
type: object
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
patch:
|
||||||
|
tags:
|
||||||
|
- Projects
|
||||||
|
summary: Update
|
||||||
|
description: 'Update Project.'
|
||||||
|
operationId: 2db343bd6fc14c658cb51a2b73b2f842
|
||||||
|
requestBody:
|
||||||
|
description: 'Project updated.'
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: 'The name of the project.'
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: 'The description of the project.'
|
||||||
|
type: object
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: 'Project updated.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
uuid: { type: string, example: og888os }
|
||||||
|
name: { type: string, example: 'Project Name' }
|
||||||
|
description: { type: string, example: 'Project Description' }
|
||||||
|
type: object
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
'/projects/{uuid}/{environment_name}':
|
'/projects/{uuid}/{environment_name}':
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -3272,6 +3399,70 @@ paths:
|
|||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- Servers
|
||||||
|
summary: Create
|
||||||
|
description: 'Create Server.'
|
||||||
|
operationId: fa44b42490379e428ba5b8747716a8d9
|
||||||
|
requestBody:
|
||||||
|
description: 'Server created.'
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
example: 'My Server'
|
||||||
|
description: 'The name of the server.'
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
example: 'My Server Description'
|
||||||
|
description: 'The description of the server.'
|
||||||
|
ip:
|
||||||
|
type: string
|
||||||
|
example: 127.0.0.1
|
||||||
|
description: 'The IP of the server.'
|
||||||
|
port:
|
||||||
|
type: integer
|
||||||
|
example: 22
|
||||||
|
description: 'The port of the server.'
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
example: root
|
||||||
|
description: 'The user of the server.'
|
||||||
|
private_key_uuid:
|
||||||
|
type: string
|
||||||
|
example: og888os
|
||||||
|
description: 'The UUID of the private key.'
|
||||||
|
is_build_server:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
description: 'Is build server.'
|
||||||
|
instant_validate:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
|
description: 'Instant validate.'
|
||||||
|
type: object
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: 'Server created.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
uuid: { type: string, example: og888os, description: 'The UUID of the server.' }
|
||||||
|
type: object
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
'/servers/{uuid}':
|
'/servers/{uuid}':
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -3303,6 +3494,95 @@ paths:
|
|||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- Servers
|
||||||
|
summary: Delete
|
||||||
|
description: 'Delete server by UUID.'
|
||||||
|
operationId: 0231fe0134f0306b21f006ce51b0a3dc
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'UUID of the server.'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 'Server deleted.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string, example: 'Server deleted.' }
|
||||||
|
type: object
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
patch:
|
||||||
|
tags:
|
||||||
|
- Servers
|
||||||
|
summary: Update
|
||||||
|
description: 'Update Server.'
|
||||||
|
operationId: 41bbdaf79eb1938592494fc5494442a0
|
||||||
|
requestBody:
|
||||||
|
description: 'Server updated.'
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: 'The name of the server.'
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: 'The description of the server.'
|
||||||
|
ip:
|
||||||
|
type: string
|
||||||
|
description: 'The IP of the server.'
|
||||||
|
port:
|
||||||
|
type: integer
|
||||||
|
description: 'The port of the server.'
|
||||||
|
user:
|
||||||
|
type: string
|
||||||
|
description: 'The user of the server.'
|
||||||
|
private_key_uuid:
|
||||||
|
type: string
|
||||||
|
description: 'The UUID of the private key.'
|
||||||
|
is_build_server:
|
||||||
|
type: boolean
|
||||||
|
description: 'Is build server.'
|
||||||
|
instant_validate:
|
||||||
|
type: boolean
|
||||||
|
description: 'Instant validate.'
|
||||||
|
type: object
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: 'Server updated.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Server'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
'/servers/{uuid}/resources':
|
'/servers/{uuid}/resources':
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -3367,6 +3647,39 @@ paths:
|
|||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
|
'/servers/{uuid}/validate':
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Servers
|
||||||
|
summary: Validate
|
||||||
|
description: 'Validate server by UUID.'
|
||||||
|
operationId: a543a12ef2cbc7a3dd22c3dbe6cbee89
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
description: 'Server UUID'
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: 'Server validation started.'
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
message: { type: string, example: 'Validation started.' }
|
||||||
|
type: object
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/401'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/400'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/404'
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
/services:
|
/services:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -4026,6 +4339,9 @@ components:
|
|||||||
format: date-time
|
format: date-time
|
||||||
nullable: true
|
nullable: true
|
||||||
description: 'The date and time when the application was deleted.'
|
description: 'The date and time when the application was deleted.'
|
||||||
|
compose_parsing_version:
|
||||||
|
type: string
|
||||||
|
description: 'How Coolify parse the compose file.'
|
||||||
type: object
|
type: object
|
||||||
ApplicationDeploymentQueue:
|
ApplicationDeploymentQueue:
|
||||||
description: 'Project model'
|
description: 'Project model'
|
||||||
@@ -4170,191 +4486,35 @@ components:
|
|||||||
$ref: '#/components/schemas/Environment'
|
$ref: '#/components/schemas/Environment'
|
||||||
type: object
|
type: object
|
||||||
Server:
|
Server:
|
||||||
description: 'Application model'
|
description: 'Server model'
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
repository_project_id:
|
|
||||||
type: integer
|
|
||||||
nullable: true
|
|
||||||
uuid:
|
uuid:
|
||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
fqdn:
|
|
||||||
type: string
|
|
||||||
config_hash:
|
|
||||||
type: string
|
|
||||||
git_repository:
|
|
||||||
type: string
|
|
||||||
git_branch:
|
|
||||||
type: string
|
|
||||||
git_commit_sha:
|
|
||||||
type: string
|
|
||||||
git_full_url:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
docker_registry_image_name:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
docker_registry_image_tag:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
build_pack:
|
|
||||||
type: string
|
|
||||||
static_image:
|
|
||||||
type: string
|
|
||||||
install_command:
|
|
||||||
type: string
|
|
||||||
build_command:
|
|
||||||
type: string
|
|
||||||
start_command:
|
|
||||||
type: string
|
|
||||||
ports_exposes:
|
|
||||||
type: string
|
|
||||||
ports_mappings:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
base_directory:
|
|
||||||
type: string
|
|
||||||
publish_directory:
|
|
||||||
type: string
|
|
||||||
health_check_path:
|
|
||||||
type: string
|
|
||||||
health_check_port:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
health_check_host:
|
|
||||||
type: string
|
|
||||||
health_check_method:
|
|
||||||
type: string
|
|
||||||
health_check_return_code:
|
|
||||||
type: integer
|
|
||||||
health_check_scheme:
|
|
||||||
type: string
|
|
||||||
health_check_response_text:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
health_check_interval:
|
|
||||||
type: integer
|
|
||||||
health_check_timeout:
|
|
||||||
type: integer
|
|
||||||
health_check_retries:
|
|
||||||
type: integer
|
|
||||||
health_check_start_period:
|
|
||||||
type: integer
|
|
||||||
limits_memory:
|
|
||||||
type: string
|
|
||||||
limits_memory_swap:
|
|
||||||
type: string
|
|
||||||
limits_memory_swappiness:
|
|
||||||
type: integer
|
|
||||||
limits_memory_reservation:
|
|
||||||
type: string
|
|
||||||
limits_cpus:
|
|
||||||
type: string
|
|
||||||
limits_cpuset:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
limits_cpu_shares:
|
|
||||||
type: integer
|
|
||||||
status:
|
|
||||||
type: string
|
|
||||||
preview_url_template:
|
|
||||||
type: string
|
|
||||||
destination_type:
|
|
||||||
type: string
|
|
||||||
destination_id:
|
|
||||||
type: integer
|
|
||||||
source_type:
|
|
||||||
type: string
|
|
||||||
source_id:
|
|
||||||
type: integer
|
|
||||||
private_key_id:
|
|
||||||
type: integer
|
|
||||||
nullable: true
|
|
||||||
environment_id:
|
|
||||||
type: integer
|
|
||||||
created_at:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
updated_at:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
ip:
|
||||||
dockerfile:
|
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
user:
|
||||||
health_check_enabled:
|
|
||||||
type: boolean
|
|
||||||
dockerfile_location:
|
|
||||||
type: string
|
type: string
|
||||||
custom_labels:
|
port:
|
||||||
type: string
|
|
||||||
dockerfile_target_build:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
manual_webhook_secret_github:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
manual_webhook_secret_gitlab:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
docker_compose_location:
|
|
||||||
type: string
|
|
||||||
docker_compose:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
docker_compose_raw:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
docker_compose_domains:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
deleted_at:
|
|
||||||
type: string
|
|
||||||
format: date-time
|
|
||||||
nullable: true
|
|
||||||
docker_compose_custom_start_command:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
docker_compose_custom_build_command:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
swarm_replicas:
|
|
||||||
type: integer
|
type: integer
|
||||||
swarm_placement_constraints:
|
proxy:
|
||||||
type: string
|
type: object
|
||||||
nullable: true
|
high_disk_usage_notification_sent:
|
||||||
manual_webhook_secret_bitbucket:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
custom_docker_run_options:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
post_deployment_command:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
post_deployment_command_container:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
pre_deployment_command:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
pre_deployment_command_container:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
watch_paths:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
custom_healthcheck_found:
|
|
||||||
type: boolean
|
type: boolean
|
||||||
manual_webhook_secret_gitea:
|
unreachable_notification_sent:
|
||||||
|
type: boolean
|
||||||
|
unreachable_count:
|
||||||
|
type: integer
|
||||||
|
validation_logs:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
log_drain_notification_sent:
|
||||||
redirect:
|
type: boolean
|
||||||
|
swarm_cluster:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
ServerSetting:
|
ServerSetting:
|
||||||
@@ -4461,6 +4621,9 @@ components:
|
|||||||
is_container_label_escape_enabled:
|
is_container_label_escape_enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'The flag to enable the container label escape.'
|
description: 'The flag to enable the container label escape.'
|
||||||
|
is_container_label_readonly_enabled:
|
||||||
|
type: boolean
|
||||||
|
description: 'The flag to enable the container label readonly.'
|
||||||
config_hash:
|
config_hash:
|
||||||
type: string
|
type: string
|
||||||
description: 'The hash of the service configuration.'
|
description: 'The hash of the service configuration.'
|
||||||
|
|||||||
BIN
other/logos/branddev.png
Normal file
BIN
other/logos/branddev.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
1
other/logos/latitude.svg
Normal file
1
other/logos/latitude.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="349" height="64" fill="#ffffff" viewBox="0 0 349 64" ><path fill="#ffffff" d="M76.094 52V7.581h-6.917V52h6.917zM95.64 52.761c3.616 0 7.677-1.776 9.518-4.759V52h6.853V22.43h-6.853v3.997c-1.841-2.982-5.394-4.759-9.328-4.759-9.201 0-14.722 6.854-14.722 15.547 0 8.693 5.013 15.546 14.531 15.546zm1.015-6.155c-5.394 0-8.63-3.997-8.63-9.391s3.236-9.392 8.63-9.392c5.901 0 8.756 4.316 8.756 9.392s-2.855 9.391-8.756 9.391zM132.621 46.162c-2.729 0-4.315-.634-4.315-5.774V28.204h6.79V22.43h-6.79v-8.947h-6.917v8.947h-5.013v5.774h5.013v13.77c0 9.963 6.219 10.407 9.582 10.407 2.031 0 4.061-.19 5.14-.381v-6.092c-.698.127-2.411.254-3.49.254zM144.247 18.178c3.045 0 5.14-2.284 5.14-5.14 0-2.855-2.095-5.14-5.14-5.14-3.046 0-5.14 2.285-5.14 5.14 0 2.856 2.094 5.14 5.14 5.14zM147.673 52V22.43h-6.853V52h6.853zM168.299 46.162c-2.728 0-4.315-.634-4.315-5.774V28.204h6.79V22.43h-6.79v-8.947h-6.916v8.947h-5.013v5.774h5.013v13.77c0 9.963 6.218 10.407 9.581 10.407 2.031 0 4.061-.19 5.14-.381v-6.092c-.698.127-2.411.254-3.49.254zM187.159 52.761c4.251 0 7.361-1.713 9.137-4.886V52h6.917V22.43h-6.917v16.689c0 4.251-1.903 7.741-7.17 7.741-4.505 0-6.028-2.792-6.028-7.551v-16.88h-6.917v18.593c0 7.932 4.505 11.74 10.978 11.74zM232.247 26.427c-1.84-2.982-5.394-4.759-9.328-4.759-9.201 0-14.722 6.854-14.722 15.547 0 8.693 5.013 15.546 14.532 15.546 3.616 0 7.678-1.776 9.518-4.759V52h6.853V7.581h-6.853v18.846zm-8.503 20.18c-5.394 0-8.63-3.998-8.63-9.392 0-5.394 3.236-9.392 8.63-9.392 5.901 0 8.757 4.316 8.757 9.392s-2.856 9.391-8.757 9.391zM273.099 35.692c0-8.313-5.711-14.024-14.15-14.024-9.011 0-14.849 6.092-14.849 15.547 0 9.582 5.774 15.546 15.102 15.546 7.107 0 12.691-3.87 13.58-9.39h-6.98c-1.142 2.283-3.173 3.362-6.6 3.362-5.013 0-8.058-2.538-8.249-7.234h21.956c.127-1.65.19-2.601.19-3.807zm-22.146-1.46c.064-4.188 2.983-7.107 7.869-7.107 4.759 0 7.424 2.539 7.487 7.108h-15.356zM281.59 52.508c2.983 0 5.394-2.475 5.394-5.394s-2.411-5.33-5.394-5.33a5.321 5.321 0 00-5.33 5.33c0 2.919 2.348 5.394 5.33 5.394zM303.931 52.761c7.17 0 11.675-3.426 11.675-9.2 0-4.189-2.665-7.234-6.853-8.44l-5.901-1.713c-2.729-.762-3.681-1.84-3.681-3.427 0-1.777 1.777-3.046 4.252-3.046 3.236 0 5.14 1.46 5.14 3.934h6.789c0-5.71-4.441-9.2-11.675-9.2-6.6 0-11.359 3.68-11.359 8.693 0 4.188 2.348 7.17 6.473 8.312l5.901 1.587c3.173.825 3.871 1.84 3.871 3.49 0 2.094-1.65 3.49-4.252 3.49-3.553 0-6.028-1.777-6.028-4.315h-6.917c0 5.647 5.331 9.835 12.565 9.835zM337.5 21.668c-4.378 0-7.805 1.714-9.645 4.886V7.581h-6.916V52h6.916V35.755c0-5.203 2.538-8.185 7.044-8.185 4.822 0 6.472 2.728 6.472 9.01V52h6.917V33.408c0-7.742-3.681-11.74-10.788-11.74zM50.552 47.654l-5.29 15.762H14.805l5.29-15.762h30.457zM33.744.584H14.863L0 44.875h18.88L33.744.584z" data-darkreader-inline-fill="" style="--darkreader-inline-fill: #ffffff;"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/svgs/docmost.png
Normal file
BIN
public/svgs/docmost.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
1
public/svgs/drupal.svg
Normal file
1
public/svgs/drupal.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="220.03239mm" height="167.50914mm" viewBox="0 0 623.71387 474.82906"><title>Risorsa 28</title><g id="Livello_2" data-name="Livello 2"><g id="Livello_1-2" data-name="Livello 1"><path d="M292.12129,345.00234h-26.212l.04275,49.04508c0,14.33211-6.14488,25.92692-20.477,25.92692-14.33924,0-20.54117-11.59481-20.54117-25.92692V345.04868H198.78953l-.00713,48.99874c0,28.32213,18.20655,51.27636,46.53223,51.27636,28.31849,0,46.79948-22.95423,46.79948-51.27636Z" style="fill:#009cde"/><rect x="528.48024" y="315.11614" width="26.08367" height="127.67755" style="fill:#009cde"/><polygon points="573.823 319.826 573.823 315.239 594.15 315.239 594.15 319.826 586.715 319.826 586.715 340.043 581.261 340.043 581.261 319.826 573.823 319.826" style="fill:#009cde"/><polygon points="604.673 315.24 610.476 332.295 610.544 332.295 616.029 315.24 623.714 315.24 623.714 340.044 618.606 340.044 618.606 322.465 618.535 322.465 612.458 340.044 608.252 340.044 602.175 322.64 602.103 322.64 602.103 340.044 596.999 340.044 596.999 315.24 604.673 315.24" style="fill:#009cde"/><path d="M177.46784,343.93624c-22.48375-5.18962-36.66617,17.148-37.25785,18.34557-.28871.58814-.2994.93035-1.29384.90533-.82337-.01773-.916-.90533-.916-.90533l-2.79086-17.08726H111.84157v97.51614h26.19419v-52.784c0-4.3128,11.61256-24.993,34.11766-19.67153,11.38088,2.69467,16.21054,7.52436,16.21054,7.52436V348.07087a41.85,41.85,0,0,0-10.89612-4.13463" style="fill:#009cde"/><path d="M353.05258,368.64446a26.13539,26.13539,0,1,1-26.13,26.13,26.13748,26.13748,0,0,1,26.13-26.13M327.664,474.82906V439.74191l.00712.00718.00713-13.14169s.03921-1.05141.98729-1.06218c.84474-.01066,1.03368.549,1.24041,1.06218,1.98529,4.94369,12.90641,23.76689,37.14374,17.86435A51.631,51.631,0,1,0,301.402,394.77446v80.0546Z" style="fill:#009cde"/><path d="M492.37655,394.77479a26.13539,26.13539,0,1,1-26.1336-26.13,26.139,26.139,0,0,1,26.1336,26.13m-.74494,47.9793H517.8935v-47.9793a51.62918,51.62918,0,1,0-65.64764,49.69381c24.23739,5.906,35.15845-12.92066,37.14374-17.86087.20673-.5132.39208-1.07284,1.24041-1.06566.94808.01425.98729,1.06566.98729,1.06566" style="fill:#009cde"/><path d="M36.905,337.16373h-10.529v83.26605l10.81417.278c22.18076,0,36.46656-2.01733,36.46656-41.90208,0-38.24519-12.61057-41.642-36.75172-41.642m-7.1108,105.38619H0V315.0649H31.96836c38.70852,0,68.06785,7.10374,68.06785,63.74083,0,56.09518-31.09872,63.74419-70.24206,63.74419" style="fill:#009cde"/><path d="M336.513,60.04433C316.67656,40.21728,297.75071,21.31654,292.11812,0c-5.63286,21.31654-24.56176,40.21728-44.39487,60.04433-29.74983,29.731-63.47995,63.42669-63.47995,113.96434a107.878,107.878,0,1,0,215.756,0c0-50.53435-33.72736-84.23329-63.48628-113.96434M230.09481,199.146c-6.61462-.22461-31.02654-42.30194,14.26152-87.1038l29.96891,32.73593a2.56174,2.56174,0,0,1-.20005,3.82276c-7.15132,7.33453-37.63207,37.90055-41.42061,48.46955-.782,2.18152-1.92407,2.099-2.60977,2.07556m62.02662,55.45668a37.10175,37.10175,0,0,1-37.102-37.102c0-9.39381,3.73446-17.765,9.24757-24.50685,6.68995-8.18054,27.84947-31.18907,27.84947-31.18907s20.83558,23.34627,27.79953,31.111a36.28369,36.28369,0,0,1,9.30744,24.58494,37.10209,37.10209,0,0,1-37.102,37.102m71.01314-60.16628c-.79965,1.74885-2.61363,4.66848-5.062,4.75761-4.36413.15894-4.83045-2.07721-8.05609-6.8511-7.08179-10.47987-68.88406-75.07043-80.44365-87.56214-10.16779-10.987-1.43181-18.733,2.62052-22.79219,5.084-5.09314,19.92417-19.92418,19.92417-19.92418s44.24974,41.984,62.6825,70.67071,12.08027,53.50972,8.33451,61.70129" style="fill:#009cde"/></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
6
public/svgs/plane.svg
Normal file
6
public/svgs/plane.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="155" height="155" viewBox="0 0 155 155" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="37" y="64" width="27" height="27" fill="#4075FC"/>
|
||||||
|
<rect x="91" y="64" width="27" height="27" fill="#4075FC"/>
|
||||||
|
<rect x="64" y="91" width="27" height="27" fill="#4075FC"/>
|
||||||
|
<rect x="64" y="36" width="54" height="28" fill="#4075FC"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 346 B |
@@ -18,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
|
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
|
||||||
@if (str($status)->contains('unhealthy'))
|
@if (str($status)->contains('unhealthy'))
|
||||||
<x-helper helper="Unhealthy state. <span class='dark:text-warning text-coollabs'>This doesn't mean that the resource is malfunctioning.</span><br><br>- If the resource is accessible, it indicates that no health check is configured - it is not mandatory.<br>- If the resource is not accessible (returning 404 or 503), it may indicate that a health check is needed and has not passed. <span class='dark:text-warning text-coollabs'>Your action is required.</span>" >
|
<x-helper helper="Unhealthy state. <span class='dark:text-warning text-coollabs'>This doesn't mean that the resource is malfunctioning.</span><br><br>- If the resource is accessible, it indicates that no health check is configured - it is not mandatory.<br>- If the resource is not accessible (returning 404 or 503), it may indicate that a health check is needed and has not passed. <span class='dark:text-warning text-coollabs'>Your action is required.</span><br><br>More details in the <a href='https://coolify.io/docs/knowledge-base/healthchecks' class='underline dark:text-warning text-coollabs' target='_blank'>documentation</a>." >
|
||||||
<x-slot:icon>
|
<x-slot:icon>
|
||||||
<svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
<svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill="currentColor" d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16"></path>
|
<path fill="currentColor" d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16"></path>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
@use('App\Models\InstanceSettings')
|
@use('App\Models\InstanceSettings')
|
||||||
@php
|
@php
|
||||||
|
|
||||||
$instanceSettings = InstanceSettings::first();
|
$instanceSettings = \App\Models\InstanceSettings::get();
|
||||||
$name = null;
|
$name = null;
|
||||||
|
|
||||||
if ($instanceSettings) {
|
if ($instanceSettings) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Notifications | Coolify
|
Notifications | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-notification.navbar />
|
<x-notification.navbar />
|
||||||
<form wire:submit='submit' class="flex flex-col gap-4">
|
<form wire:submit='submit' class="flex flex-col gap-4 pb-4">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2>Email</h2>
|
<h2>Email</h2>
|
||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
label="Use Hosted Email Service" />
|
label="Use Hosted Email Service" />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="pb-4 w-96">
|
<div class="w-96 pb-4">
|
||||||
<x-forms.checkbox disabled id="team.use_instance_email_settings"
|
<x-forms.checkbox disabled id="team.use_instance_email_settings"
|
||||||
label="Use Hosted Email Service (Pro+ subscription required)" />
|
label="Use Hosted Email Service (Pro+ subscription required)" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if (!$team->use_instance_email_settings)
|
@if (!$team->use_instance_email_settings)
|
||||||
<form class="flex flex-col items-end gap-2 pb-4 xl:flex-row" wire:submit='submitFromFields'>
|
<form class="flex flex-col items-end gap-2 pt-4 pb-4 xl:flex-row" wire:submit='submitFromFields'>
|
||||||
<x-forms.input required id="team.smtp_from_name" helper="Name used in emails." label="From Name" />
|
<x-forms.input required id="team.smtp_from_name" helper="Name used in emails." label="From Name" />
|
||||||
<x-forms.input required id="team.smtp_from_address" helper="Email address used in emails."
|
<x-forms.input required id="team.smtp_from_address" helper="Email address used in emails."
|
||||||
label="From Address" />
|
label="From Address" />
|
||||||
|
|||||||
@@ -16,12 +16,13 @@
|
|||||||
<x-forms.checkbox
|
<x-forms.checkbox
|
||||||
helper="Your application will be available only on https if your domain starts with https://..."
|
helper="Your application will be available only on https if your domain starts with https://..."
|
||||||
instantSave id="is_force_https_enabled" label="Force Https" />
|
instantSave id="is_force_https_enabled" label="Force Https" />
|
||||||
<x-forms.checkbox label="Enable gzip compression"
|
<x-forms.checkbox label="Enable Gzip Compression"
|
||||||
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this."
|
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this."
|
||||||
instantSave id="is_gzip_enabled" />
|
instantSave id="is_gzip_enabled" />
|
||||||
<x-forms.checkbox helper="Strip Prefix is used to remove prefixes from paths. Like /api/ to /api."
|
<x-forms.checkbox helper="Strip Prefix is used to remove prefixes from paths. Like /api/ to /api."
|
||||||
instantSave id="is_stripprefix_enabled" label="Strip Prefixes" />
|
instantSave id="is_stripprefix_enabled" label="Strip Prefixes" />
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
|
<h3>Docker Compose</h3>
|
||||||
<x-forms.checkbox instantSave id="application.settings.is_raw_compose_deployment_enabled"
|
<x-forms.checkbox instantSave id="application.settings.is_raw_compose_deployment_enabled"
|
||||||
label="Raw Compose Deployment"
|
label="Raw Compose Deployment"
|
||||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
|
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
|
||||||
|
|||||||
@@ -106,10 +106,10 @@
|
|||||||
<livewire:project.shared.destination :resource="$application" :servers="$servers" />
|
<livewire:project.shared.destination :resource="$application" :servers="$servers" />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'storages'">
|
<div x-cloak x-show="activeTab === 'storages'">
|
||||||
<livewire:project.service.storage :resource="$application" />
|
<livewire:project.service.storage :resource="$application" lazy />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'webhooks'">
|
<div x-cloak x-show="activeTab === 'webhooks'">
|
||||||
<livewire:project.shared.webhooks :resource="$application" />
|
<livewire:project.shared.webhooks :resource="$application" lazy />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'previews'">
|
<div x-cloak x-show="activeTab === 'previews'">
|
||||||
<livewire:project.application.previews :application="$application" />
|
<livewire:project.application.previews :application="$application" />
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
<livewire:project.shared.metrics :resource="$application" />
|
<livewire:project.shared.metrics :resource="$application" />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'tags'">
|
<div x-cloak x-show="activeTab === 'tags'">
|
||||||
<livewire:project.shared.tags :resource="$application" />
|
<livewire:project.shared.tags :resource="$application" lazy />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'danger'">
|
<div x-cloak x-show="activeTab === 'danger'">
|
||||||
<livewire:project.shared.danger :resource="$application" />
|
<livewire:project.shared.danger :resource="$application" />
|
||||||
|
|||||||
@@ -30,13 +30,7 @@
|
|||||||
</x-forms.select>
|
</x-forms.select>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@if ($application->could_set_build_commands())
|
|
||||||
<div class="w-64">
|
|
||||||
<x-forms.checkbox instantSave id="application.settings.is_static"
|
|
||||||
label="Is it a static site?"
|
|
||||||
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
@if (
|
@if (
|
||||||
!is_null($parsedServices) &&
|
!is_null($parsedServices) &&
|
||||||
@@ -57,6 +51,7 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if ($application->build_pack !== 'dockercompose')
|
@if ($application->build_pack !== 'dockercompose')
|
||||||
@@ -129,99 +124,127 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
<div class="py-4 border-b dark:border-coolgray-200">
|
||||||
@if ($application->build_pack !== 'dockerimage')
|
<h3>Build</h3>
|
||||||
<h3 class="pt-8">Build</h3>
|
@if ($application->build_pack === 'dockerimage')
|
||||||
@if ($application->build_pack !== 'dockercompose')
|
|
||||||
<div class="max-w-96">
|
|
||||||
<x-forms.checkbox
|
|
||||||
helper="Use a build server to build your application. You can configure your build server in the Server settings. This is experimental. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
|
|
||||||
instantSave id="application.settings.is_build_server_enabled"
|
|
||||||
label="Use a Build Server? (experimental)" />
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if ($application->could_set_build_commands())
|
|
||||||
@if ($application->build_pack === 'nixpacks')
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
|
||||||
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml"
|
|
||||||
id="application.install_command" label="Install Command" />
|
|
||||||
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml"
|
|
||||||
id="application.build_command" label="Build Command" />
|
|
||||||
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml"
|
|
||||||
id="application.start_command" label="Start Command" />
|
|
||||||
</div>
|
|
||||||
<div class="pb-4 text-xs">Nixpacks will detect the required configuration automatically.
|
|
||||||
<a class="underline" href="https://coolify.io/docs/resources/introduction">Framework
|
|
||||||
Specific Docs</a>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@endif
|
|
||||||
@if ($application->build_pack === 'dockercompose')
|
|
||||||
<div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/"
|
|
||||||
id="application.base_directory" label="Base Directory"
|
|
||||||
helper="Directory to use as root. Useful for monorepos." />
|
|
||||||
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/docker-compose.yaml"
|
|
||||||
id="application.docker_compose_location" label="Docker Compose Location"
|
|
||||||
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
|
|
||||||
</div>
|
|
||||||
<div class="pt-4">The following commands are for advanced use cases. Only modify them if you
|
|
||||||
know what are
|
|
||||||
you doing.</div>
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-forms.input placeholder="docker compose build" x-bind:disabled="initLoadingCompose"
|
|
||||||
id="application.docker_compose_custom_build_command"
|
|
||||||
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} build</span>"
|
|
||||||
label="Custom Build Command" />
|
|
||||||
<x-forms.input placeholder="docker compose up -d" x-bind:disabled="initLoadingCompose"
|
|
||||||
id="application.docker_compose_custom_start_command"
|
|
||||||
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} up -d</span>"
|
|
||||||
label="Custom Start Command" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
|
||||||
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
|
||||||
helper="Directory to use as root. Useful for monorepos." />
|
|
||||||
@if ($application->build_pack === 'dockerfile' && !$application->dockerfile)
|
|
||||||
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location"
|
|
||||||
label="Dockerfile Location"
|
|
||||||
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}</span>" />
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@if ($application->build_pack === 'dockerfile')
|
|
||||||
<x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target"
|
|
||||||
helper="Useful if you have multi-staged dockerfile." />
|
|
||||||
@endif
|
|
||||||
@if ($application->could_set_build_commands())
|
|
||||||
@if ($application->settings->is_static)
|
|
||||||
<x-forms.input placeholder="/dist" id="application.publish_directory"
|
|
||||||
label="Publish Directory" required />
|
|
||||||
@else
|
|
||||||
<x-forms.input placeholder="/" id="application.publish_directory"
|
|
||||||
label="Publish Directory" />
|
|
||||||
@endif
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@if ($this->application->is_github_based() && !$this->application->is_public_repository())
|
|
||||||
<div class="pb-4">
|
|
||||||
<x-forms.textarea helper="Gitignore-style rules to filter Git based webhook deployments."
|
|
||||||
placeholder="src/pages/**" id="application.watch_paths" label="Watch Paths" />
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
<x-forms.input
|
<x-forms.input
|
||||||
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
|
@else
|
||||||
|
@if ($application->could_set_build_commands())
|
||||||
|
@if ($application->build_pack === 'nixpacks')
|
||||||
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
|
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||||
|
id="application.install_command" label="Install Command" />
|
||||||
|
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||||
|
id="application.build_command" label="Build Command" />
|
||||||
|
<x-forms.input helper="If you modify this, you probably need to have a nixpacks.toml"
|
||||||
|
id="application.start_command" label="Start Command" />
|
||||||
|
</div>
|
||||||
|
<div class="pt-1 text-xs">Nixpacks will detect the required configuration
|
||||||
|
automatically.
|
||||||
|
<a class="underline"
|
||||||
|
href="https://coolify.io/docs/resources/applications/index">Framework
|
||||||
|
Specific Docs</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@endif
|
||||||
|
<div class="flex flex-col gap-2 pt-6 pb-10">
|
||||||
|
@if ($application->build_pack === 'dockercompose')
|
||||||
|
<div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)">
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/"
|
||||||
|
id="application.base_directory" label="Base Directory"
|
||||||
|
helper="Directory to use as root. Useful for monorepos." />
|
||||||
|
<x-forms.input x-bind:disabled="initLoadingCompose"
|
||||||
|
placeholder="/docker-compose.yaml" id="application.docker_compose_location"
|
||||||
|
label="Docker Compose Location"
|
||||||
|
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
|
||||||
|
</div>
|
||||||
|
<div class="w-96">
|
||||||
|
<x-forms.checkbox instantSave
|
||||||
|
id="application.settings.is_preserve_repository_enabled"
|
||||||
|
label="Preserve Repository During Deployment"
|
||||||
|
helper="Git repository (based on the base directory settings) will be copied to the deployment directory." />
|
||||||
|
</div>
|
||||||
|
<div class="pt-4">The following commands are for advanced use cases.
|
||||||
|
Only
|
||||||
|
modify them if you
|
||||||
|
know what are
|
||||||
|
you doing.</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input placeholder="docker compose build"
|
||||||
|
x-bind:disabled="initLoadingCompose"
|
||||||
|
id="application.docker_compose_custom_build_command"
|
||||||
|
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} build</span>"
|
||||||
|
label="Custom Build Command" />
|
||||||
|
<x-forms.input placeholder="docker compose up -d"
|
||||||
|
x-bind:disabled="initLoadingCompose"
|
||||||
|
id="application.docker_compose_custom_start_command"
|
||||||
|
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} up -d</span>"
|
||||||
|
label="Custom Start Command" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
|
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
||||||
|
helper="Directory to use as root. Useful for monorepos." />
|
||||||
|
@if ($application->build_pack === 'dockerfile' && !$application->dockerfile)
|
||||||
|
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location"
|
||||||
|
label="Dockerfile Location"
|
||||||
|
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}</span>" />
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($application->build_pack === 'dockerfile')
|
||||||
|
<x-forms.input id="application.dockerfile_target_build"
|
||||||
|
label="Docker Build Stage Target"
|
||||||
|
helper="Useful if you have multi-staged dockerfile." />
|
||||||
|
@endif
|
||||||
|
@if ($application->could_set_build_commands())
|
||||||
|
@if ($application->settings->is_static)
|
||||||
|
<x-forms.input placeholder="/dist" id="application.publish_directory"
|
||||||
|
label="Publish Directory" required />
|
||||||
|
@else
|
||||||
|
<x-forms.input placeholder="/" id="application.publish_directory"
|
||||||
|
label="Publish Directory" />
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
@if ($this->application->is_github_based() && !$this->application->is_public_repository())
|
||||||
|
<div class="pb-4">
|
||||||
|
<x-forms.textarea
|
||||||
|
helper="Gitignore-style rules to filter Git based webhook deployments."
|
||||||
|
placeholder="src/pages/**" id="application.watch_paths"
|
||||||
|
label="Watch Paths" />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
|
|
||||||
|
@if ($application->build_pack !== 'dockercompose')
|
||||||
|
<div class="pt-2 w-96">
|
||||||
|
<x-forms.checkbox
|
||||||
|
helper="Use a build server to build your application. You can configure your build server in the Server settings. This is experimental. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
|
||||||
|
instantSave id="application.settings.is_build_server_enabled"
|
||||||
|
label="Use a Build Server? (experimental)" />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if ($application->could_set_build_commands())
|
||||||
|
<div class="w-96">
|
||||||
|
<x-forms.checkbox instantSave id="application.settings.is_static"
|
||||||
|
label="Is it a static site?"
|
||||||
|
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@else
|
</div>
|
||||||
<x-forms.input
|
|
||||||
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
|
||||||
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
|
||||||
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
|
||||||
@endif
|
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
<x-forms.button wire:target='initLoadingCompose'
|
<x-forms.button wire:target='initLoadingCompose'
|
||||||
x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
|
x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
|
||||||
@@ -235,13 +258,15 @@
|
|||||||
label="Docker Compose Content" helper="You need to modify the docker compose file."
|
label="Docker Compose Content" helper="You need to modify the docker compose file."
|
||||||
monacoEditorLanguage="yaml" useMonacoEditor />
|
monacoEditorLanguage="yaml" useMonacoEditor />
|
||||||
@endif
|
@endif
|
||||||
<div class="w-72">
|
<div class="w-96">
|
||||||
<x-forms.checkbox label="Escape special characters in labels?"
|
<x-forms.checkbox label="Escape special characters in labels?"
|
||||||
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||||
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
||||||
|
<x-forms.checkbox label="Readonly labels"
|
||||||
|
helper="If you know what are you doing, you can enable this to edit the labels directly. Coolify won't update labels automatically. <br><br>Be careful, it could break the proxy configuration after you restart the container."
|
||||||
|
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($application->dockerfile)
|
@if ($application->dockerfile)
|
||||||
<x-forms.textarea label="Dockerfile" id="application.dockerfile" monacoEditorLanguage="dockerfile"
|
<x-forms.textarea label="Dockerfile" id="application.dockerfile" monacoEditorLanguage="dockerfile"
|
||||||
useMonacoEditor rows="6"> </x-forms.textarea>
|
useMonacoEditor rows="6"> </x-forms.textarea>
|
||||||
@@ -264,10 +289,13 @@
|
|||||||
|
|
||||||
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
|
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"
|
||||||
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
|
monacoEditorLanguage="ini" useMonacoEditor></x-forms.textarea>
|
||||||
<div class="w-72">
|
<div class="w-96">
|
||||||
<x-forms.checkbox label="Escape special characters in labels?"
|
<x-forms.checkbox label="Escape special characters in labels?"
|
||||||
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||||
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
||||||
|
<x-forms.checkbox label="Readonly labels"
|
||||||
|
helper="If you know what are you doing, you can enable this to edit the labels directly. Coolify won't update labels automatically. <br><br>Be careful, it could break the proxy configuration after you restart the container."
|
||||||
|
id="application.settings.is_container_label_readonly_enabled" instantSave></x-forms.checkbox>
|
||||||
</div>
|
</div>
|
||||||
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels"
|
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels"
|
||||||
buttonTitle="Reset to Coolify Generated Labels">
|
buttonTitle="Reset to Coolify Generated Labels">
|
||||||
|
|||||||
@@ -18,7 +18,12 @@
|
|||||||
@endif
|
@endif
|
||||||
SHA: {{ data_get($image, 'tag') }}
|
SHA: {{ data_get($image, 'tag') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs">{{ data_get($image, 'created_at') }}</div>
|
@php
|
||||||
|
$date = data_get($image, 'created_at');
|
||||||
|
$interval = \Illuminate\Support\Carbon::parse($date);
|
||||||
|
@endphp
|
||||||
|
<div class="text-xs">{{ $interval->diffForHumans() }}</div>
|
||||||
|
<div class="text-xs">{{ $date }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-end p-2">
|
<div class="flex justify-end p-2">
|
||||||
@if (data_get($image, 'is_current'))
|
@if (data_get($image, 'is_current'))
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
<livewire:project.shared.metrics :resource="$database" />
|
<livewire:project.shared.metrics :resource="$database" />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'tags'">
|
<div x-cloak x-show="activeTab === 'tags'">
|
||||||
<livewire:project.shared.tags :resource="$database" />
|
<livewire:project.shared.tags :resource="$database" lazy />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'danger'">
|
<div x-cloak x-show="activeTab === 'danger'">
|
||||||
<livewire:project.shared.danger :resource="$database" />
|
<livewire:project.shared.danger :resource="$database" />
|
||||||
|
|||||||
@@ -48,8 +48,7 @@
|
|||||||
@if ($current_step === 'repository')
|
@if ($current_step === 'repository')
|
||||||
<h2 class="pb-4">Select a repository</h2>
|
<h2 class="pb-4">Select a repository</h2>
|
||||||
<form class="flex flex-col gap-2 pt-2" wire:submit='submit'>
|
<form class="flex flex-col gap-2 pt-2" wire:submit='submit'>
|
||||||
<x-forms.input id="repository_url" required label="Repository Url (https:// or git@)"
|
<x-forms.input id="repository_url" required label="Repository Url (https:// or git@)" />
|
||||||
helper="{!! __('repository.url') !!}" />
|
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input id="branch" required label="Branch" />
|
<x-forms.input id="branch" required label="Branch" />
|
||||||
<x-forms.select wire:model.live="build_pack" label="Build Pack" required>
|
<x-forms.select wire:model.live="build_pack" label="Build Pack" required>
|
||||||
@@ -62,6 +61,15 @@
|
|||||||
<x-forms.input id="publish_directory" required label="Publish Directory" />
|
<x-forms.input id="publish_directory" required label="Publish Directory" />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@if ($build_pack === 'dockercompose')
|
||||||
|
<x-forms.input placeholder="/" wire:model.blur="base_directory" label="Base Directory"
|
||||||
|
helper="Directory to use as root. Useful for monorepos." />
|
||||||
|
<x-forms.input placeholder="/docker-compose.yaml" id="docker_compose_location"
|
||||||
|
label="Docker Compose Location"
|
||||||
|
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>" />
|
||||||
|
Compose file location in your repository:<span
|
||||||
|
class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>
|
||||||
|
@endif
|
||||||
@if ($show_is_static)
|
@if ($show_is_static)
|
||||||
<x-forms.input type="number" required id="port" label="Port" :readonly="$is_static || $build_pack === 'static'" />
|
<x-forms.input type="number" required id="port" label="Port" :readonly="$is_static || $build_pack === 'static'" />
|
||||||
<div class="w-52">
|
<div class="w-52">
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="pb-4">Deploy any public or private Git repositories through a GitHub App.</div>
|
<div class="pb-4">Deploy any public or private Git repositories through a GitHub App.</div>
|
||||||
|
|
||||||
@if ($github_apps->count() !== 0)
|
@if ($github_apps->count() !== 0)
|
||||||
<h2 class="pt-4 pb-4">Select a Github App</h2>
|
<h2 class="pt-4 pb-4">Select a Github App</h2>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@@ -92,6 +91,16 @@
|
|||||||
helper="If there is a build process involved (like Svelte, React, Next, etc..), please specify the output directory for the build assets." />
|
helper="If there is a build process involved (like Svelte, React, Next, etc..), please specify the output directory for the build assets." />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@if ($build_pack === 'dockercompose')
|
||||||
|
<x-forms.input placeholder="/" wire:model.blur="base_directory"
|
||||||
|
label="Base Directory"
|
||||||
|
helper="Directory to use as root. Useful for monorepos." />
|
||||||
|
<x-forms.input placeholder="/docker-compose.yaml" id="docker_compose_location"
|
||||||
|
label="Docker Compose Location"
|
||||||
|
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>" />
|
||||||
|
Compose file location in your repository:<span
|
||||||
|
class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>
|
||||||
|
@endif
|
||||||
@if ($show_is_static)
|
@if ($show_is_static)
|
||||||
<x-forms.input type="number" id="port" label="Port" :readonly="$is_static || $build_pack === 'static'"
|
<x-forms.input type="number" id="port" label="Port" :readonly="$is_static || $build_pack === 'static'"
|
||||||
helper="The port your application listens on." />
|
helper="The port your application listens on." />
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user