Compare commits

...

85 Commits

Author SHA1 Message Date
Andras Bacsai
4017ea7b65 Merge pull request #2819 from coollabsio/next
v4.0.0-beta.312
2024-07-12 15:06:15 +02:00
Andras Bacsai
a85066c644 fix: disable sentinel until a few bugs are fixed 2024-07-12 15:05:12 +02:00
Andras Bacsai
b08d38f339 refactor: Update version numbers to 4.0.0-beta.312 2024-07-12 14:54:54 +02:00
Andras Bacsai
d4f4632461 Merge pull request #2812 from coollabsio/next
v4.0.0-beta.311
2024-07-12 14:15:15 +02:00
Andras Bacsai
666aa041f4 refactor: Update metrics.blade.php to improve alert message clarity 2024-07-12 14:12:44 +02:00
Andras Bacsai
1c565fd502 refactor: Add lazy loading to tags in Livewire configuration view 2024-07-12 14:00:39 +02:00
Andras Bacsai
5778466947 refactor: Update Webhooks.php to use nullable type for webhook URLs 2024-07-12 13:54:12 +02:00
Andras Bacsai
7006239b0d refactor: Update Livewire configuration views 2024-07-12 13:40:11 +02:00
Andras Bacsai
49d011574d refactor: Remove unnecessary code in AppServiceProvider.php 2024-07-12 13:34:48 +02:00
Andras Bacsai
046a358ae0 refactor: Update Dockerfile to set CI environment variable to true 2024-07-12 13:02:37 +02:00
Andras Bacsai
d23f5af957 hmmm 2024-07-12 12:59:53 +02:00
Andras Bacsai
20a3f4b200 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-07-12 12:53:09 +02:00
Andras Bacsai
73acda833e feat: Enable legacy model binding in Livewire configuration 2024-07-12 12:53:07 +02:00
andrasbacsai
fa895db76e Fix styling 2024-07-12 10:53:07 +00:00
Andras Bacsai
88f33be5b6 refactor: only get instanceSettings once from db 2024-07-12 12:51:55 +02:00
Andras Bacsai
21612cccf7 refactor: tags view 2024-07-12 12:51:13 +02:00
Andras Bacsai
39a7332343 refactored: webhooks view 2024-07-12 11:52:32 +02:00
Andras Bacsai
21825876fb fix: service status changed event 2024-07-12 11:27:08 +02:00
Andras Bacsai
aaee887d3e fix: respect top-level configs and secrets 2024-07-12 11:21:22 +02:00
Andras Bacsai
cb44373eff chore: Bump version to 4.0.0-beta.311 2024-07-12 11:20:44 +02:00
Andras Bacsai
4e6ea4f584 Merge pull request #2809 from coollabsio/next
Another hoopsy
2024-07-12 10:39:05 +02:00
Andras Bacsai
62a93d3e51 feat: Add new logo for Latitude 2024-07-12 10:38:16 +02:00
Andras Bacsai
f60c281e80 Merge pull request #2808 from coollabsio/next
Forgot to commit, oopsy
2024-07-12 10:36:37 +02:00
Andras Bacsai
43c40cdb09 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-07-12 10:35:52 +02:00
Andras Bacsai
c851262d81 refactor: Reset default labels when docker_compose_domains is modified 2024-07-12 10:35:50 +02:00
Andras Bacsai
91783ccc3e Merge pull request #2805 from coollabsio/next
v4.0.0-beta.310
2024-07-12 10:35:47 +02:00
Andras Bacsai
6ba3d5f86e Merge pull request #2737 from DerrikMilligan/patch-1
fix: Add arch as supported os
2024-07-12 10:00:48 +02:00
Andras Bacsai
a9a20755a9 Merge pull request #2799 from janbiasi/template-twenty-update-env
[TEMPLATE] feat: add security and storage access key env to twenty template
2024-07-12 10:00:29 +02:00
Andras Bacsai
d2693c1ac8 chore: Add new logo for Latitude 2024-07-12 09:39:06 +02:00
Jan Biasi
aaa6f434a9 feat: add security and storage access key env to twenty template 2024-07-12 09:23:51 +02:00
Andras Bacsai
314a3ac83f chore: update composer dependencies 2024-07-12 09:05:31 +02:00
Andras Bacsai
36e177479e chore: update version to 4.0.0-beta.310 2024-07-12 09:05:25 +02:00
Andras Bacsai
cbeebed6c9 Merge pull request #2798 from coollabsio/next
v4.0.0-beta.309
2024-07-11 14:12:58 +02:00
Andras Bacsai
4b905dbfad fix: update redirect URL in unauthenticated exception handler 2024-07-11 14:12:28 +02:00
Andras Bacsai
6072e7efc7 Merge pull request #2797 from coollabsio/next
refactor: comment out unused code for network cleanup
2024-07-11 13:04:36 +02:00
Andras Bacsai
19097c6692 refactor: comment out unused code for network cleanup 2024-07-11 13:04:01 +02:00
Andras Bacsai
d37f63c63c Merge pull request #2789 from coollabsio/next
v4.0.0-beta.308
2024-07-11 13:01:13 +02:00
Andras Bacsai
574bafd950 fix: cleanup parameter 2024-07-11 12:50:12 +02:00
Andras Bacsai
2b805f869a fix/feat: better volume cleanups 2024-07-11 12:38:54 +02:00
Andras Bacsai
36c4be1d17 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-07-11 11:30:23 +02:00
Andras Bacsai
f2d82e16d6 fix: remove volumes as well 2024-07-11 11:30:20 +02:00
andrasbacsai
c6658e1ac7 Fix styling 2024-07-11 09:20:09 +00:00
Andras Bacsai
22a7d85e58 Merge pull request #2760 from KobyW/next
fix: prevent instance fqdn persisting to other servers dynamic proxy config
2024-07-11 11:19:29 +02:00
Andras Bacsai
b3421b47b6 Merge pull request #2762 from Xiloe/gitea-pr-preview
fix: Gitea PR preview not working as intended
2024-07-11 11:17:35 +02:00
Andras Bacsai
b5247f77ec Merge pull request #2795 from alexzvn/feat/display-rollback-interval
feat: display time interval for rollback images
2024-07-11 11:16:05 +02:00
Andras Bacsai
e63e806572 fix: always set project name during app deployments 2024-07-11 11:14:20 +02:00
Andras Bacsai
62b84add36 feat: compose parser v2 2024-07-11 10:55:15 +02:00
Andras Bacsai
858ae1266f chore: Update storage.blade.php view for livewire project service 2024-07-11 10:55:04 +02:00
Andras Bacsai
3ae990aa40 fix: api 2024-07-11 10:17:20 +02:00
Andras Bacsai
deb4b16ae1 feat: cleanup unused docker networks from proxy 2024-07-11 10:17:15 +02:00
Andras Bacsai
b37dc4c73e fix: remove networks when deleting a docker compose based app 2024-07-11 10:16:56 +02:00
Andras Bacsai
6b08100819 chore: Refactor checkIfDomainIsAlreadyUsed function 2024-07-11 10:02:35 +02:00
Alexzvn
2c45e7146b feat: display time interval for rollback images 2024-07-11 02:56:31 +00:00
Andras Bacsai
7c4a722d72 refactor: Add force parameter to StartProxy handle method 2024-07-10 15:53:56 +02:00
Andras Bacsai
f4bccefaba chore: Update livewire/livewire dependency to version 3.4.9 2024-07-10 15:53:52 +02:00
Andras Bacsai
491bb93e95 fix: do not overwrite hardcoded variables if they rely on another variable 2024-07-10 15:53:46 +02:00
Andras Bacsai
f35700c9ee chore: Update Plausible docker compose template to Plausible 2.1.0 2024-07-10 14:02:05 +02:00
Andras Bacsai
bd26aca3d9 Merge branch 'next' of github.com:coollabsio/coolify into next 2024-07-10 13:58:56 +02:00
Andras Bacsai
71d24773b6 chore: Update Plausible docker compose template to Plausible 2.1.0 2024-07-10 13:58:53 +02:00
Andras Bacsai
781bd29f40 Merge pull request #2689 from mtctonyhkhk2010/add-traditional-chinese-translation
Add Traditional Chinese translation
2024-07-10 13:30:31 +02:00
Andras Bacsai
fd4dd1edfa Merge pull request #2688 from ari-party/next
Multiple CSS changes
2024-07-10 13:29:04 +02:00
Andras Bacsai
d2db26cb5e Merge pull request #2575 from Nsbx/patch-1
Update reactive-resume.yaml
2024-07-10 13:25:55 +02:00
Andras Bacsai
01977839f7 Merge branch 'next' into patch-1 2024-07-10 13:25:42 +02:00
Andras Bacsai
0116892b6b Merge pull request #2774 from OlegWock/fix-gitea-installations
Update volumes for Gitea with DB templates
2024-07-10 13:24:55 +02:00
Andras Bacsai
4d88873524 Merge pull request #2779 from Lukyrouge3/patch-1
Fixing supabase service
2024-07-10 13:19:45 +02:00
Andras Bacsai
8e46c0186d Merge pull request #2785 from saeedesmaili/patch-1
Update Plausible docker compose template to Plausible 2.1.0
2024-07-10 13:18:13 +02:00
Andras Bacsai
1b0e589aab update packages 2024-07-10 13:10:21 +02:00
Saeed Esmaili
02ba149e26 Update to community-edition:v2.1.1 2024-07-10 11:53:49 +02:00
Saeed Esmaili
2981aa876c Update plausible.yaml to plausible v2.1.0 2024-07-10 11:43:35 +02:00
Torrenté Florian
cbcc7f6d88 Update supabase.yaml
Based on solution here:
https://github.com/coollabsio/coolify/issues/2696
Tested and working !
2024-07-10 00:10:17 +02:00
OlegWock
906a3dc9b4 Update volumes for Gitea with DB templates 2024-07-09 11:36:24 +02:00
Tom Ferriere
01abc26316 removed redundant if statement 2024-07-07 10:45:44 +02:00
Xiloe
2dfe43fc3c Fix styling 2024-07-07 08:02:36 +00:00
Tom Ferriere
f71861300a fix: gitea pr previews 2024-07-07 10:01:11 +02:00
Koby Wood
52d7841334 fix: prevent instance fqdn persisting to other servers dynamic proxy configs
fixes: 2650
2024-07-06 19:33:42 -04:00
Derrik Milligan
65a618d019 Add arch as supported os
Update `SUPPORTED_OS` to include the id `arch`. The install script supports `arch` but you can't proceed with a server install because `arch` isn't a `SUPPORTED_OS`
2024-07-02 12:02:38 -06:00
Bruce Mak
7af151d44e add Traditional Chinese translation 2024-06-26 00:05:52 +08:00
Astrid
7028391e57 remove unused li element? 2024-06-25 17:21:23 +02:00
Astrid
cbae0845e7 h2 instead of h3 as element is child of h2 2024-06-25 17:10:06 +02:00
Astrid
0e512962c6 padding same as other tabs
from: ![from](https://astrid.email/u/chrome_3IcAbmCNKW.png)
to: ![to](https://astrid.email/u/chrome_8Cz5rx30wn.png)
2024-06-25 17:09:44 +02:00
Astrid
ac694b855b change gap of proxy buttons
from: ![from](https://astrid.email/u/chrome_ducsHvMI4w.png)
to: ![to](https://astrid.email/u/chrome_L4ncORPQtD.png)
2024-06-25 17:01:32 +02:00
Astrid
490d45e788 server settings css changes
from: ![from](https://astrid.email/u/chrome_REEIhjc2Yp.png)
to: ![to](https://astrid.email/u/chrome_J5XwGaOs84.png)
2024-06-25 17:01:17 +02:00
Astrid
b0863eb5ea remove h4 padding on server proxy settings
from: ![from](https://astrid.email/u/chrome_9wK3HTTy12.png)
to: ![to](https://astrid.email/u/chrome_7m5jXr1aWH.png)
2024-06-25 17:01:17 +02:00
Nicolas Bondoux
2f87deb10b Update reactive-resume.yaml 2024-06-16 21:54:53 +02:00
Nicolas Bondoux
65253ca54e Update reactive-resume.yaml 2024-06-16 21:51:00 +02:00
79 changed files with 1977 additions and 1925 deletions

View File

@@ -2,8 +2,6 @@
)
[![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new)
[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open)
[![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed)
# About the Project
@@ -49,6 +47,7 @@ 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://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://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
## Github Sponsors ($40+)
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>

View File

@@ -38,6 +38,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);
}
}
}
}

View File

@@ -11,11 +11,11 @@ class StartProxy
{
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 {
$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';
}
$commands = collect([]);

View File

@@ -11,6 +11,8 @@ class CleanupDocker
public function handle(Server $server, bool $force = true)
{
// cleanup docker images, containers, and builder caches
if ($force) {
instant_remote_process(['docker image prune -af'], $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 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);
// });
// }
}
}

View File

@@ -19,18 +19,16 @@ class StopService
ray('Stopping service: '.$service->name);
$applications = $service->applications()->get();
foreach ($applications as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
$application->update(['status' => 'exited']);
}
$dbs = $service->databases()->get();
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']);
}
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
// TODO: make notification for databases
// $service->environment->project->team->notify(new StatusChanged($service));
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
} catch (\Exception $e) {
echo $e->getMessage();
ray($e->getMessage());

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Actions\Server\StopSentinel;
use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob;
use App\Models\ApplicationDeploymentQueue;
@@ -23,6 +24,16 @@ class Init extends Command
{
$this->alive();
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');
$cleanup_deployments = $this->option('cleanup-deployments');

View File

@@ -27,7 +27,7 @@ class ServiceStatusChanged implements ShouldBroadcast
public function broadcastOn(): ?array
{
if ($this->userId) {
if (! is_null($this->userId)) {
return [
new PrivateChannel("user.{$this->userId}"),
];

View File

@@ -50,7 +50,7 @@ class Handler extends ExceptionHandler
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) {
return;
}
$this->settings = InstanceSettings::get();
$this->settings = view()->shared('instanceSettings');
if ($this->settings->do_not_track) {
return;
}

View File

@@ -683,6 +683,9 @@ class ApplicationsController extends Controller
if (! $request->has('name')) {
$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(), [
sharedDataApplications(),
'git_repository' => 'string|required',
@@ -756,6 +759,9 @@ class ApplicationsController extends Controller
if (! $request->has('name')) {
$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(), [
sharedDataApplications(),
'git_repository' => 'string|required',
@@ -847,6 +853,9 @@ class ApplicationsController extends Controller
if (! $request->has('name')) {
$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(), [
sharedDataApplications(),
'git_repository' => 'string|required',
@@ -1231,6 +1240,16 @@ class ApplicationsController extends Controller
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: [
new OA\Response(
@@ -1264,15 +1283,12 @@ class ApplicationsController extends Controller
public function delete_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
$cleanup = $request->query->get('cleanup') ?? false;
$cleanup = filter_var($request->query->get('cleanup', true), FILTER_VALIDATE_BOOLEAN);
if (is_null($teamId)) {
return invalidTokenResponse();
}
if ($request->collect()->count() == 0) {
return response()->json([
'message' => 'Invalid request.',
], 400);
if (! $request->uuid) {
return response()->json(['message' => 'UUID is required.'], 404);
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
@@ -1281,7 +1297,10 @@ class ApplicationsController extends Controller
'message' => 'Application not found',
], 404);
}
DeleteResourceJob::dispatch($application, $cleanup);
DeleteResourceJob::dispatch(
resource: $application,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
return response()->json([
'message' => 'Application deletion request queued.',

View File

@@ -9,6 +9,7 @@ use App\Actions\Database\StopDatabase;
use App\Actions\Database\StopDatabaseProxy;
use App\Enums\NewDatabaseTypes;
use App\Http\Controllers\Controller;
use App\Jobs\DeleteResourceJob;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Http\Request;
@@ -1528,6 +1529,16 @@ class DatabasesController extends Controller
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: [
new OA\Response(
@@ -1561,6 +1572,7 @@ class DatabasesController extends Controller
public function delete_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
$cleanup = filter_var($request->query->get('cleanup', true), FILTER_VALIDATE_BOOLEAN);
if (is_null($teamId)) {
return invalidTokenResponse();
}
@@ -1571,8 +1583,10 @@ class DatabasesController extends Controller
if (! $database) {
return response()->json(['message' => 'Database not found.'], 404);
}
StopDatabase::dispatch($database);
$database->forceDelete();
DeleteResourceJob::dispatch(
resource: $database,
deleteConfigurations: $cleanup,
deleteVolumes: $cleanup);
return response()->json([
'message' => 'Database deletion request queued.',

View File

@@ -3,7 +3,6 @@
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\InstanceSettings;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use OpenApi\Attributes as OA;
@@ -85,7 +84,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
}
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$settings->update(['is_api_enabled' => true]);
return response()->json(['message' => 'API enabled.'], 200);
@@ -136,7 +135,7 @@ class OtherController extends Controller
if ($teamId !== '0') {
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
}
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$settings->update(['is_api_enabled' => false]);
return response()->json(['message' => 'API disabled.'], 200);

View File

@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\Server as ModelsServer;
use Illuminate\Http\Request;
@@ -301,7 +300,7 @@ class ServersController extends Controller
$projects = Project::where('team_id', $teamId)->get();
$domains = collect();
$applications = $projects->pluck('applications')->flatten();
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if ($applications->count() > 0) {
foreach ($applications as $application) {
$ip = $application->destination->server->ip;

View File

@@ -2,7 +2,6 @@
namespace App\Http\Middleware;
use App\Models\InstanceSettings;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -15,7 +14,7 @@ class ApiAllowed
if (isCloud()) {
return $next($request);
}
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if ($settings->is_api_enabled === false) {
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
}

View File

@@ -462,7 +462,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($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";
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
);
@@ -516,7 +516,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($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->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
);
@@ -2034,12 +2034,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
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],
[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-name {$this->application->uuid} --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],
[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} build"), 'hidden' => true],
);
}
$this->application_deployment_queue->addLogEntry('New images built.');
@@ -2050,17 +2050,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
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} up --build -d"), '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-name {$this->application->uuid} --project-directory {$this->workdir} up --build -d"), 'hidden' => true],
);
} else {
if ($this->use_build_server) {
$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 {
$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],
);
}
}

View File

@@ -28,14 +28,18 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {}
public function __construct(
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
public bool $deleteConfigurations = false,
public bool $deleteVolumes = false) {}
public function handle()
{
try {
$this->resource->forceDelete();
$persistentStorages = collect();
switch ($this->resource->type()) {
case 'application':
$persistentStorages = $this->resource?->persistentStorages()?->get();
StopApplication::run($this->resource);
break;
case 'standalone-postgresql':
@@ -46,6 +50,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
case 'standalone-keydb':
case 'standalone-dragonfly':
case 'standalone-clickhouse':
$persistentStorages = $this->resource?->persistentStorages()?->get();
StopDatabase::run($this->resource);
break;
case 'service':
@@ -53,6 +58,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
DeleteService::run($this->resource);
break;
}
if ($this->deleteVolumes && $this->resource->type() !== 'service') {
$this->resource?->delete_volumes($persistentStorages);
}
if ($this->deleteConfigurations) {
$this->resource?->delete_configurations();
}
@@ -61,6 +70,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
throw $e;
} finally {
$this->resource->forceDelete();
Artisan::queue('cleanup:stucked-resources');
}
}

View File

@@ -2,7 +2,6 @@
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Server;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
@@ -36,7 +35,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
$latest_version = get_latest_version_of_coolify();
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$current_version = config('version');
if (! $settings->is_auto_update_enabled) {
return;

View File

@@ -2,7 +2,6 @@
namespace App\Livewire;
use App\Models\InstanceSettings;
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Http;
@@ -48,7 +47,7 @@ class Help extends Component
]
);
$mail->subject("[HELP]: {$this->subject}");
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$type = set_transanctional_email_settings($settings);
if (! $type) {
$url = 'https://app.coolify.io/api/feedback';

View File

@@ -2,7 +2,6 @@
namespace App\Livewire\Notifications;
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
@@ -173,7 +172,7 @@ class Email extends Component
public function copyFromInstanceSettings()
{
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if ($settings->smtp_enabled) {
$team = currentTeam();
$team->update([

View File

@@ -350,7 +350,6 @@ class General extends Component
$this->checkFqdns();
$this->application->save();
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
$this->application->custom_labels = base64_encode($this->customLabels);
@@ -364,6 +363,7 @@ class General extends Component
}
}
$this->validate();
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
$this->resetDefaultLabels();
}
@@ -390,6 +390,7 @@ class General extends Component
}
if ($this->application->build_pack === 'dockercompose') {
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
foreach ($this->parsedServiceDomains as $serviceName => $service) {
$domain = data_get($service, 'domain');
if ($domain) {
@@ -399,6 +400,9 @@ class General extends Component
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->save();

View File

@@ -16,6 +16,8 @@ class Danger extends Component
public bool $delete_configurations = true;
public bool $delete_volumes = true;
public ?string $modalId = null;
public function mount()
@@ -31,7 +33,7 @@ class Danger extends Component
try {
// $this->authorize('delete', $this->resource);
$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', [
'project_uuid' => $this->projectUuid,

View File

@@ -3,32 +3,63 @@
namespace App\Livewire\Project\Shared;
use App\Models\Tag;
use Livewire\Attributes\Validate;
use Livewire\Component;
// Refactored ✅
class Tags extends Component
{
public $resource = null;
public ?string $new_tag = null;
#[Validate('required|string|min:2')]
public string $newTags;
public $tags = [];
protected $listeners = [
'refresh' => '$refresh',
];
protected $rules = [
'resource.tags.*.name' => 'required|string|min:2',
'new_tag' => 'required|string|min:2',
];
protected $validationAttributes = [
'new_tag' => 'tag',
];
public $filteredTags = [];
public function mount()
{
$this->loadTags();
}
public function loadTags()
{
$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)
@@ -39,8 +70,9 @@ class Tags extends Component
return;
}
$this->resource->tags()->syncWithoutDetaching($id);
$this->resource->tags()->attach($id);
$this->refresh();
$this->dispatch('success', 'Tag added.');
} catch (\Exception $e) {
return handleError($e, $this);
}
@@ -50,12 +82,12 @@ class Tags extends Component
{
try {
$this->resource->tags()->detach($id);
$found_more_tags = Tag::where(['id' => $id, 'team_id' => currentTeam()->id])->first();
if ($found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) {
$found_more_tags = Tag::ownedByCurrentTeam()->find($id);
if ($found_more_tags && $found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) {
$found_more_tags->delete();
}
$this->refresh();
$this->dispatch('success', 'Tag deleted.');
} catch (\Exception $e) {
return handleError($e, $this);
}
@@ -63,41 +95,8 @@ class Tags extends Component
public function refresh()
{
$this->resource->load(['tags']);
$this->tags = Tag::ownedByCurrentTeam()->get();
$this->new_tag = null;
}
public function submit()
{
try {
$this->validate([
'new_tag' => 'required|string|min:2',
]);
$tags = str($this->new_tag)->trim()->explode(' ');
foreach ($tags as $tag) {
if ($this->resource->tags()->where('name', $tag)->exists()) {
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='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');
$this->resource->refresh(); // Remove this when legacy_model_binding is false
$this->loadTags();
$this->reset('newTags');
}
}

View File

@@ -4,49 +4,61 @@ namespace App\Livewire\Project\Shared;
use Livewire\Component;
// Refactored ✅
class Webhooks extends Component
{
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 = [
'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 ?string $githubManualWebhookSecret = null;
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 {
$this->validate();
$this->resource->save();
$this->authorize('update', $this->resource);
$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.');
} catch (\Exception $e) {
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');
}
}

View File

@@ -84,7 +84,7 @@ class Deploy extends Component
try {
$this->server->proxy->force_stop = false;
$this->server->save();
$activity = StartProxy::run($this->server);
$activity = StartProxy::run($this->server, force: true);
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -18,7 +18,7 @@ class Index extends Component
public function mount()
{
if (isInstanceAdmin()) {
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$database = StandalonePostgresql::whereName('coolify-db')->first();
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
if ($database) {

View File

@@ -29,7 +29,7 @@ class License extends Component
abort(404);
}
$this->instance_id = config('app.id');
$this->settings = InstanceSettings::get();
$this->settings = view()->shared('instanceSettings');
}
public function render()

View File

@@ -4,7 +4,6 @@ namespace App\Livewire\Source\Github;
use App\Jobs\GithubAppPermissionJob;
use App\Models\GithubApp;
use App\Models\InstanceSettings;
use Illuminate\Support\Facades\Http;
use Livewire\Component;
@@ -100,7 +99,7 @@ class Change extends Component
return redirect()->route('source.all');
}
$this->applications = $this->github_app->applications;
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret');
$this->name = str($this->github_app->name)->kebab();

View File

@@ -23,7 +23,7 @@ class Index extends Component
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
return redirect()->route('subscription.show');
}
$this->settings = InstanceSettings::get();
$this->settings = view()->shared('instanceSettings');
$this->alreadySubscribed = currentTeam()->subscription()->exists();
}

View File

@@ -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.'],
'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.'],
'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'],
]
)]
@@ -122,17 +123,12 @@ class Application extends BaseModel
ApplicationSetting::create([
'application_id' => $application->id,
]);
$application->compose_parsing_version = '2';
$application->save();
});
static::deleting(function ($application) {
static::forceDeleting(function ($application) {
$application->update(['fqdn' => null]);
$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->environment_variables()->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()
{
return $this->belongsToMany(Server::class, 'additional_destinations')
@@ -775,6 +788,11 @@ class Application extends BaseModel
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)
{
$baseDir = $this->generateBaseDir($deployment_uuid);
@@ -897,7 +915,7 @@ class Application extends BaseModel
$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);
} elseif ($git_type === 'github') {
} elseif ($git_type === 'github' || $git_type === 'gitea') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
@@ -941,7 +959,7 @@ class Application extends BaseModel
$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);
} elseif ($git_type === 'github') {
} elseif ($git_type === 'github' || $git_type === 'gitea') {
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
if ($exec_in_docker) {
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));

View File

@@ -318,11 +318,11 @@ respond 404
public function setupDynamicProxyConfiguration()
{
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$dynamic_config_path = $this->proxyPath().'/dynamic';
if ($this->proxyType() === 'TRAEFIK_V2') {
$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([
"rm -f $file",
], $this);
@@ -428,7 +428,7 @@ respond 404
}
} elseif ($this->proxyType() === '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([
"rm -f $file",
], $this);

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -31,16 +32,9 @@ class StandaloneClickhouse extends BaseModel
'is_readonly' => true,
]);
});
static::deleting(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();
static::forceDeleting(function ($database) {
$database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete();
$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()
{
return $this->getRawOriginal('status');

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -31,16 +32,9 @@ class StandaloneDragonfly extends BaseModel
'is_readonly' => true,
]);
});
static::deleting(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);
}
}
static::forceDeleting(function ($database) {
$database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete();
$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()
{
return $this->getRawOriginal('status');

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -31,16 +32,9 @@ class StandaloneKeydb extends BaseModel
'is_readonly' => true,
]);
});
static::deleting(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);
}
}
static::forceDeleting(function ($database) {
$database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete();
$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()
{
return $this->getRawOriginal('status');

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -31,16 +32,9 @@ class StandaloneMariadb extends BaseModel
'is_readonly' => true,
]);
});
static::deleting(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();
static::forceDeleting(function ($database) {
$database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete();
$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()
{
return $this->getRawOriginal('status');

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -35,16 +36,9 @@ class StandaloneMongodb extends BaseModel
'is_readonly' => true,
]);
});
static::deleting(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();
static::forceDeleting(function ($database) {
$database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete();
$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()
{
return $this->getRawOriginal('status');

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -32,16 +33,9 @@ class StandaloneMysql extends BaseModel
'is_readonly' => true,
]);
});
static::deleting(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();
static::forceDeleting(function ($database) {
$database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete();
$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()
{
return $this->getRawOriginal('status');

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -32,16 +33,9 @@ class StandalonePostgresql extends BaseModel
'is_readonly' => true,
]);
});
static::deleting(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();
static::forceDeleting(function ($database) {
$database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete();
$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)
{
$newConfigHash = $this->image.$this->ports_mappings.$this->postgres_initdb_args.$this->postgres_host_auth_method;

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -27,16 +28,9 @@ class StandaloneRedis extends BaseModel
'is_readonly' => true,
]);
});
static::deleting(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);
}
}
static::forceDeleting(function ($database) {
$database->persistentStorages()->delete();
$database->scheduledBackups()->delete();
$database->environment_variables()->delete();
$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()
{
return $this->getRawOriginal('status');

View File

@@ -2,7 +2,6 @@
namespace App\Notifications\Channels;
use App\Models\InstanceSettings;
use App\Models\User;
use Exception;
use Illuminate\Mail\Message;
@@ -14,7 +13,7 @@ class TransactionalEmailChannel
{
public function send(User $notifiable, Notification $notification): void
{
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) {
Log::info('SMTP/Resend not enabled');

View File

@@ -18,7 +18,7 @@ class ResetPassword extends Notification
public function __construct($token)
{
$this->settings = InstanceSettings::get();
$this->settings = view()->shared('instanceSettings');
$this->token = $token;
}

View File

@@ -2,8 +2,10 @@
namespace App\Providers;
use App\Models\InstanceSettings;
use App\Models\PersonalAccessToken;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Laravel\Sanctum\Sanctum;
@@ -14,6 +16,7 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void
{
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
Http::macro('github', function (string $api_url, ?string $github_access_token = null) {
if ($github_access_token) {
return Http::withHeaders([
@@ -27,5 +30,9 @@ class AppServiceProvider extends ServiceProvider
])->baseUrl($api_url);
}
});
if (! env('CI')) {
View::share('instanceSettings', InstanceSettings::get());
}
}
}

View File

@@ -6,7 +6,6 @@ use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Models\InstanceSettings;
use App\Models\OauthSetting;
use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit;
@@ -45,7 +44,7 @@ class FortifyServiceProvider extends ServiceProvider
{
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::registerView(function () {
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if (! $settings->is_registration_enabled) {
return redirect()->route('login');
}
@@ -57,7 +56,7 @@ class FortifyServiceProvider extends ServiceProvider
});
Fortify::loginView(function () {
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
$users = User::count();
if ($users == 0) {

View File

@@ -40,6 +40,7 @@ const SUPPORTED_OS = [
'ubuntu debian raspbian',
'centos fedora rhel ol rocky amzn almalinux',
'sles opensuse-leap opensuse-tumbleweed',
'arch',
];
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];

View File

@@ -5,7 +5,24 @@ use App\Models\Application;
use App\Models\Server;
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()) {
$networks = collect($server->swarmDockers)->map(function ($docker) {
@@ -43,6 +60,18 @@ function connectProxyToNetworks(Server $server)
if ($networks->count() === 0) {
$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) {
return [
"echo 'Connecting coolify-proxy to $network network...'",
@@ -51,9 +80,6 @@ function connectProxyToNetworks(Server $server)
];
});
} else {
if ($networks->count() === 0) {
$networks = collect(['coolify']);
}
$commands = $networks->map(function ($network) {
return [
"echo 'Connecting coolify-proxy to $network network...'",

View File

@@ -244,13 +244,13 @@ function generate_application_name(string $git_repository, string $git_branch, ?
function is_transactional_emails_active(): bool
{
return isEmailEnabled(InstanceSettings::get());
return isEmailEnabled(view()->shared('instanceSettings'));
}
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
{
if (! $settings) {
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
}
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
@@ -284,7 +284,7 @@ function base_ip(): string
if (isDev()) {
return 'localhost';
}
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if ($settings->public_ipv4) {
return "$settings->public_ipv4";
}
@@ -312,7 +312,7 @@ function getFqdnWithoutPort(string $fqdn)
*/
function base_url(bool $withPort = true): string
{
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if ($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
{
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$type = set_transanctional_email_settings($settings);
if (! $type) {
throw new Exception('No email settings found.');
@@ -774,6 +774,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$allServices = get_service_templates();
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
$topLevelConfigs = collect(data_get($yaml, 'configs', []));
$topLevelSecrets = collect(data_get($yaml, 'secrets', []));
$services = data_get($yaml, 'services');
$generatedServiceFQDNS = collect([]);
@@ -1384,9 +1386,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
$found_env = $envs_from_coolify->where('key', $key)->first();
if ($found_env) {
return $found_env->value;
if (! str($value)->startsWith('$')) {
$found_env = $envs_from_coolify->where('key', $key)->first();
if ($found_env) {
return $found_env->value;
}
}
return $value;
@@ -1400,6 +1404,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'services' => $services->toArray(),
'volumes' => $topLevelVolumes->toArray(),
'networks' => $topLevelNetworks->toArray(),
'configs' => $topLevelConfigs->toArray(),
'secrets' => $topLevelSecrets->toArray(),
];
$yaml = data_forget($yaml, 'services.*.volumes.*.content');
$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', []));
$topLevelConfigs = collect(data_get($yaml, 'configs', []));
$topLevelSecrets = collect(data_get($yaml, 'secrets', []));
$services = data_get($yaml, 'services');
$generatedServiceFQDNS = collect([]);
@@ -1480,128 +1488,263 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$baseName = generateApplicationContainerName($resource, $pull_request_id);
$containerName = "$serviceName-$baseName";
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) {
$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('/')) {
if ($resource->compose_parsing_version === '1') {
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 ($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');
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 {
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);
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 {
$topLevelVolumes->put($source, [
'name' => $source,
]);
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) {
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)) {
return data_get($volume, 'source');
}
if (is_array($volume)) {
return data_get($volume, 'source');
}
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray());
return $volume->value();
});
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('~'))) {
$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 = $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);
}
} 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');
}
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray());
}
}
if ($pull_request_id !== 0 && count($serviceDependencies) > 0) {
@@ -1890,14 +2033,20 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
domains: $fqdns,
serviceLabels: $serviceLabels,
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(
network: $resource->destination->network,
uuid: $resource->uuid,
domains: $fqdns,
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 +2092,8 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'services' => $services->toArray(),
'volumes' => $topLevelVolumes->toArray(),
'networks' => $topLevelNetworks->toArray(),
'configs' => $topLevelConfigs->toArray(),
'secrets' => $topLevelSecrets->toArray(),
];
if ($isSameDockerComposeFile) {
$resource->docker_compose_raw = Yaml::dump($yaml, 10, 2);
@@ -2107,7 +2258,7 @@ function validate_dns_entry(string $fqdn, Server $server)
if (str($host)->contains('sslip.io')) {
return true;
}
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
$is_dns_validation_enabled = data_get($settings, 'is_dns_validation_enabled');
if (! $is_dns_validation_enabled) {
return true;
@@ -2166,7 +2317,7 @@ function ip_match($ip, $cidrs, &$match = null)
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)) {
return response()->json(['error' => 'Team ID is required.'], 400);
@@ -2182,8 +2333,12 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId,
return str($domain);
});
$applications = Application::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid);
$serviceApplications = ServiceApplication::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']);
if ($uuid) {
$applications = $applications->filter(fn ($app) => $app->uuid !== $uuid);
$serviceApplications = $serviceApplications->filter(fn ($app) => $app->uuid !== $uuid);
}
$domainFound = false;
foreach ($applications as $app) {
if (is_null($app->fqdn)) {
@@ -2223,7 +2378,7 @@ function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId,
if ($domainFound) {
return true;
}
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -2240,7 +2395,6 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
if ($resource) {
if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose') {
$domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain');
ray($domains);
$domains = collect($domains);
} else {
$domains = collect($resource->fqdns);
@@ -2296,7 +2450,7 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
}
}
if ($resource) {
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if (data_get($settings, 'fqdn')) {
$domain = data_get($settings, 'fqdn');
if (str($domain)->endsWith('/')) {
@@ -2371,7 +2525,7 @@ function get_public_ips()
{
try {
echo "Refreshing public ips!\n";
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
[$first, $second] = Process::concurrently(function (Pool $pool) {
$pool->path(__DIR__)->command('curl -4s https://ifconfig.io');
$pool->path(__DIR__)->command('curl -6s https://ifconfig.io');

View File

@@ -13,10 +13,10 @@
"doctrine/dbal": "^3.6",
"guzzlehttp/guzzle": "^7.5.0",
"laravel/fortify": "^v1.16.0",
"laravel/framework": "^v10.7.1",
"laravel/framework": "^v11",
"laravel/horizon": "^5.23.1",
"laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v3.2.1",
"laravel/sanctum": "^v4.0",
"laravel/socialite": "^v5.14.0",
"laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2",
@@ -32,8 +32,8 @@
"poliander/cron": "^3.0",
"purplepixie/phpdns": "^2.1",
"pusher/pusher-php-server": "^7.2",
"resend/resend-laravel": "^0.5.0",
"sentry/sentry-laravel": "^3.4",
"resend/resend-laravel": "^0.13.0",
"sentry/sentry-laravel": "^4.6",
"socialiteproviders/microsoft-azure": "^5.1",
"spatie/laravel-activitylog": "^4.7.3",
"spatie/laravel-data": "^3.4.3",
@@ -48,10 +48,10 @@
},
"require-dev": {
"fakerphp/faker": "^v1.21.0",
"laravel/dusk": "^v7.7.0",
"laravel/dusk": "^v8.0",
"laravel/pint": "^1.16",
"mockery/mockery": "^1.5.1",
"nunomaduro/collision": "^v7.4.0",
"nunomaduro/collision": "^v8.1",
"pestphp/pest": "^2.16",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.0.19",

2545
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -60,8 +60,9 @@ return [
*/
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.307',
'release' => '4.0.0-beta.312',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.307';
return '4.0.0-beta.312';

View File

@@ -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');
});
}
};

View File

@@ -27,14 +27,14 @@ class InstanceSettingsSeeder extends Seeder
$ipv4 = Process::run('curl -4s https://ifconfig.io')->output();
$ipv4 = trim($ipv4);
$ipv4 = filter_var($ipv4, FILTER_VALIDATE_IP);
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if (is_null($settings->public_ipv4) && $ipv4) {
$settings->update(['public_ipv4' => $ipv4]);
}
$ipv6 = Process::run('curl -6s https://ifconfig.io')->output();
$ipv6 = trim($ipv6);
$ipv6 = filter_var($ipv6, FILTER_VALIDATE_IP);
$settings = InstanceSettings::get();
$settings = view()->shared('instanceSettings');
if (is_null($settings->public_ipv6) && $ipv6) {
$settings->update(['public_ipv6' => $ipv6]);
}

View File

@@ -17,6 +17,7 @@ ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2024.4.1
ARG POSTGRES_VERSION=15
ARG CI=true
WORKDIR /var/www/html

30
lang/zh-tw.json Normal file
View 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> 分支將被選擇。"
}

View File

@@ -1343,6 +1343,14 @@ paths:
schema:
type: string
format: uuid
-
name: cleanup
in: query
description: 'Delete configurations and volumes.'
required: false
schema:
type: boolean
default: true
responses:
'200':
description: 'Application deleted.'
@@ -1799,6 +1807,14 @@ paths:
schema:
type: string
format: uuid
-
name: cleanup
in: query
description: 'Delete configurations and volumes.'
required: false
schema:
type: boolean
default: true
responses:
'200':
description: 'Database deleted.'
@@ -4026,6 +4042,9 @@ components:
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.'
type: object
ApplicationDeploymentQueue:
description: 'Project model'

1
other/logos/latitude.svg Normal file
View 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

View File

@@ -8,7 +8,7 @@
@use('App\Models\InstanceSettings')
@php
$instanceSettings = InstanceSettings::first();
$instanceSettings = view()->shared('instanceSettings');
$name = null;
if ($instanceSettings) {

View File

@@ -3,7 +3,7 @@
Notifications | Coolify
</x-slot>
<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">
<h2>Email</h2>
<x-forms.button type="submit">
@@ -33,7 +33,7 @@
label="Use Hosted Email Service" />
</div>
@else
<div class="pb-4 w-96">
<div class="w-96 pb-4">
<x-forms.checkbox disabled id="team.use_instance_email_settings"
label="Use Hosted Email Service (Pro+ subscription required)" />
</div>
@@ -45,7 +45,7 @@
</div>
@endif
@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_address" helper="Email address used in emails."
label="From Address" />

View File

@@ -109,7 +109,7 @@
<livewire:project.service.storage :resource="$application" />
</div>
<div x-cloak x-show="activeTab === 'webhooks'">
<livewire:project.shared.webhooks :resource="$application" />
<livewire:project.shared.webhooks :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'previews'">
<livewire:project.application.previews :application="$application" />
@@ -133,7 +133,7 @@
<livewire:project.shared.metrics :resource="$application" />
</div>
<div x-cloak x-show="activeTab === 'tags'">
<livewire:project.shared.tags :resource="$application" />
<livewire:project.shared.tags :resource="$application" lazy />
</div>
<div x-cloak x-show="activeTab === 'danger'">
<livewire:project.shared.danger :resource="$application" />

View File

@@ -18,7 +18,12 @@
@endif
SHA: {{ data_get($image, 'tag') }}
</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 class="flex justify-end p-2">
@if (data_get($image, 'is_current'))

View File

@@ -99,7 +99,7 @@
<livewire:project.shared.metrics :resource="$database" />
</div>
<div x-cloak x-show="activeTab === 'tags'">
<livewire:project.shared.tags :resource="$database" />
<livewire:project.shared.tags :resource="$database" lazy />
</div>
<div x-cloak x-show="activeTab === 'danger'">
<livewire:project.shared.danger :resource="$database" />

View File

@@ -1,5 +1,5 @@
<x-forms.select wire:model.live="selectedEnvironment">
<option value="edit">Create/Edit Environments</option>
<option value="edit">Create / Edit</option>
<option disabled>-----</option>
@foreach ($environments as $environment)
<option value="{{ $environment->name }}">{{ $environment->name }}

View File

@@ -40,9 +40,6 @@
<livewire:project.resource.environment-select :environments="$project->environments" />
</div>
</li>
<li>
</li>
</ol>
</nav>
</div>

View File

@@ -16,7 +16,7 @@
name, example: <span class='text-helper'>-pr-1</span>" />
@if ($resource?->build_pack !== 'dockercompose')
<x-modal-input :closeOutside="false" buttonTitle="+ Add" title="New Persistent Storage">
<livewire:project.shared.storages.add :resource="$resource"/>
<livewire:project.shared.storages.add :resource="$resource" />
</x-modal-input>
@endif
</div>
@@ -24,10 +24,12 @@
@if ($resource?->build_pack === 'dockercompose')
<span class="dark:text-warning text-coollabs">Please modify storage layout in your Docker Compose
file or reload the compose file to reread the storage layout.</span>
@else
@if ($resource->persistentStorages()->get()->count() === 0 && $resource->fileStorages()->get()->count() == 0)
<div class="pt-4">No storage found.</div>
@endif
@endif
@if ($resource->persistentStorages()->get()->count() === 0 && $resource->fileStorages()->get()->count() == 0)
<div class="pt-4">No storage found.</div>
@endif
@if ($resource->persistentStorages()->get()->count() > 0)
<livewire:project.shared.storages.all :resource="$resource" />
@endif

View File

@@ -11,5 +11,6 @@
<h4>Actions</h4>
<x-forms.checkbox id="delete_configurations"
label="Permanently delete configuration files from the server?"></x-forms.checkbox>
<x-forms.checkbox id="delete_volumes" label="Permanently delete associated volumes?"></x-forms.checkbox>
</x-modal-confirmation>
</div>

View File

@@ -13,7 +13,7 @@
it.</div>
@else
@if (!str($resource->status)->contains('running'))
<div class="alert alert-warning">Metrics are only available when the application is running!</div>
<div class="alert alert-warning">Metrics are only available when this resource is running!</div>
@else
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
<option value="5">5 minutes (live)</option>

View File

@@ -1,10 +1,18 @@
<div>
<h2>Tags</h2>
<div class="flex flex-wrap gap-2 pt-4">
@if (data_get($this->resource, 'tags'))
@forelse (data_get($this->resource,'tags') as $tagId => $tag)
<div
class="px-2 py-1 text-center rounded select-none dark:text-white w-fit bg-neutral-200 hover:bg-neutral-300 dark:bg-coolgray-100 dark:hover:bg-coolgray-200">
<form wire:submit='submit' class="flex items-end gap-2">
<div class="w-64">
<x-forms.input label="Create new or assign existing tags"
helper="You add more at once with space separated list: web api something<br><br>If the tag does not exists, it will be created."
wire:model="newTags" placeholder="example: prod app1 user" />
</div>
<x-forms.button type="submit">Add</x-forms.button>
</form>
@if (data_get($this->resource, 'tags') && count(data_get($this->resource, 'tags')) > 0)
<h3 class="pt-4">Assigned Tags</h3>
<div class="flex flex-wrap gap-2 pt-4">
@foreach (data_get($this->resource, 'tags') as $tagId => $tag)
<div class="button">
{{ $tag->name }}
<svg wire:click="deleteTag('{{ $tag->id }}')" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24"
@@ -13,24 +21,14 @@
</path>
</svg>
</div>
@empty
<div class="py-1">No tags yet</div>
@endforelse
@endif
</div>
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
<div class="w-64">
<x-forms.input label="Create new or assign existing tags"
helper="You add more at once with space separated list: web api something<br><br>If the tag does not exists, it will be created."
wire:model="new_tag" />
@endforeach
</div>
<x-forms.button type="submit">Add</x-forms.button>
</form>
@if (count($tags) > 0)
@endif
@if (count($filteredTags) > 0)
<h3 class="pt-4">Exisiting Tags</h3>
<div>Click to add quickly</div>
<div class="flex flex-wrap gap-2 pt-4">
@foreach ($tags as $tag)
@foreach ($filteredTags as $tag)
<x-forms.button wire:click="addTag('{{ $tag->id }}','{{ $tag->name }}')">
{{ $tag->name }}</x-forms.button>
@endforeach

View File

@@ -13,13 +13,13 @@
<div>
<h3>Manual Git Webhooks</h3>
@if ($githubManualWebhook && $gitlabManualWebhook)
<form wire:submit='saveSecret' class="flex flex-col gap-2">
<form wire:submit='submit' class="flex flex-col gap-2">
<div class="flex items-end gap-2">
<x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded."
readonly label="GitHub" id="githubManualWebhook"></x-forms.input>
<x-forms.input type="password"
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitHub."
label="GitHub Webhook Secret" id="resource.manual_webhook_secret_github"></x-forms.input>
label="GitHub Webhook Secret" id="githubManualWebhookSecret"></x-forms.input>
</div>
<a target="_blank" class="flex hover:no-underline" href="{{ $resource?->gitWebhook }}">
@@ -31,21 +31,19 @@
<x-forms.input readonly label="GitLab" id="gitlabManualWebhook"></x-forms.input>
<x-forms.input type="password"
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab."
label="GitLab Webhook Secret" id="resource.manual_webhook_secret_gitlab"></x-forms.input>
label="GitLab Webhook Secret" id="gitlabManualWebhookSecret"></x-forms.input>
</div>
<div class="flex gap-2">
<x-forms.input readonly label="Bitbucket" id="bitbucketManualWebhook"></x-forms.input>
<x-forms.input type="password"
helper="Need to set a secret to be able to use this webhook. It should match with the secret in Bitbucket."
label="Bitbucket Webhook Secret"
id="resource.manual_webhook_secret_bitbucket"></x-forms.input>
label="Bitbucket Webhook Secret" id="bitbucketManualWebhookSecret"></x-forms.input>
</div>
<div class="flex gap-2">
<x-forms.input readonly label="Gitea" id="giteaManualWebhook"></x-forms.input>
<x-forms.input type="password"
helper="Need to set a secret to be able to use this webhook. It should match with the secret in Gitea."
label="Gitea Webhook Secret"
id="resource.manual_webhook_secret_gitea"></x-forms.input>
label="Gitea Webhook Secret" id="giteaManualWebhookSecret"></x-forms.input>
</div>
<x-forms.button type="submit">Save</x-forms.button>
</form>

View File

@@ -135,7 +135,7 @@
</div>
@if ($server->isFunctional())
<h3 class="py-4">Settings</h3>
<h3 class="pt-4">Settings</h3>
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
@@ -144,17 +144,15 @@
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
helper="You can define the maximum duration for a deployment to run before timing it out." />
</div>
<div class="flex items-center gap-2">
<h3 class="py-4">Sentinel</h3>
@if ($server->isSentinelEnabled())
<x-forms.button wire:click='restartSentinel'>Restart</x-forms.button>
@endif
<div class="flex items-center gap-2 pt-4 pb-2">
<h3>Sentinel</h3>
{{-- @if ($server->isSentinelEnabled()) --}}
{{-- <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> --}}
{{-- @endif --}}
</div>
<div class="w-64">
<div>Metrics are disabled until a few bugs are fixed.</div>
{{-- <div class="w-64">
<x-forms.checkbox instantSave id="server.settings.is_metrics_enabled" label="Enable Metrics" />
{{-- <x-forms.checkbox instantSave id="server.settings.is_server_api_enabled" label="Enable Server API"
helper="You need to open port 12172 on your firewall. This API will be used to gather data from your server, which makes Coolify a lot faster than relying on SSH connections." />
<x-forms.button wire:click='checkPortForServerApi'>Check Port for Server API</x-forms.button> --}}
</div>
<div class="pt-4">
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
@@ -166,7 +164,7 @@
<x-forms.input id="server.settings.metrics_history_days" label="Metrics history (days)" required
helper="How many days should the metrics data should be reserved." />
</div>
</div>
</div> --}}
@endif
</form>
</div>

View File

@@ -21,9 +21,9 @@
href="https://coolify.io/docs/knowledge-base/server/proxies#switch-between-proxies">this</a>.
</div>
@if ($server->proxyType() === 'TRAEFIK_V2')
<h4 class="pb-4">Traefik</h4>
<h4>Traefik</h4>
@elseif ($server->proxyType() === 'CADDY')
<h4 class="pb-4 ">Caddy</h4>
<h4>Caddy</h4>
@endif
@if (
$server->proxy->last_applied_settings &&

View File

@@ -11,7 +11,7 @@
</x-slot:content>
</x-slide-over>
@if (data_get($server, 'proxy.status') === 'running')
<div class="flex gap-4">
<div class="flex gap-2">
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable && $server->proxyType() === 'TRAEFIK_V2')
<button>
<a target="_blank" href="http://{{ $serverIp }}:8080">

View File

@@ -16,7 +16,7 @@ services:
- GITEA__database__USER=$SERVICE_USER_MYSQL
- GITEA__database__PASSWD=$SERVICE_PASSWORD_MYSQL
volumes:
- gitea-data:/var/lib/gitea
- gitea-data:/data
- gitea-timezone:/etc/timezone:ro
- gitea-localtime:/etc/localtime:ro
ports:

View File

@@ -16,7 +16,7 @@ services:
- GITEA__database__USER=$SERVICE_USER_MYSQL
- GITEA__database__PASSWD=$SERVICE_PASSWORD_MYSQL
volumes:
- gitea-data:/var/lib/gitea
- gitea-data:/data
- gitea-timezone:/etc/timezone:ro
- gitea-localtime:/etc/localtime:ro
ports:

View File

@@ -16,7 +16,7 @@ services:
- GITEA__database__USER=$SERVICE_USER_POSTGRESQL
- GITEA__database__PASSWD=$SERVICE_PASSWORD_POSTGRESQL
volumes:
- gitea-data:/var/lib/gitea
- gitea-data:/data
- gitea-timezone:/etc/timezone:ro
- gitea-localtime:/etc/localtime:ro
ports:

View File

@@ -6,31 +6,30 @@
services:
plausible:
image: plausible/analytics:v2.0
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
image: "ghcr.io/plausible/community-edition:v2.1.0"
command: 'sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"'
environment:
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@plausible_db/plausible
- BASE_URL=$SERVICE_FQDN_PLAUSIBLE_8000
- "DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@plausible_db/plausible"
- BASE_URL=$SERVICE_FQDN_PLAUSIBLE
- SECRET_KEY_BASE=$SERVICE_BASE64_64_PLAUSIBLE
- TOTP_VAULT_KEY=$SERVICE_REALBASE64_TOTP
- TOTP_VAULT_KEY=$SERVICE_BASE64_TOTP
depends_on:
- plausible_db
- plausible_events_db
- mail
mail:
image: bytemark/smtp
plausible_db:
image: postgres:14-alpine
image: "postgres:14-alpine"
volumes:
- db-data:/var/lib/postgresql/data
- "db-data:/var/lib/postgresql/data"
environment:
- POSTGRES_DB=plausible
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
plausible_events_db:
image: clickhouse/clickhouse-server:23.3.7.5-alpine
image: "clickhouse/clickhouse-server:24.3.3.102-alpine"
volumes:
- type: volume
source: event-data
@@ -39,19 +38,12 @@ services:
source: ./clickhouse/clickhouse-config.xml
target: /etc/clickhouse-server/config.d/logging.xml
read_only: true
content: >-
<clickhouse><profiles><default><log_queries>0</log_queries><log_query_threads>0</log_query_threads></default></profiles></clickhouse>
content: "<clickhouse><profiles><default><log_queries>0</log_queries><log_query_threads>0</log_query_threads></default></profiles></clickhouse>"
- type: bind
source: ./clickhouse/clickhouse-user-config.xml
target: /etc/clickhouse-server/users.d/logging.xml
read_only: true
content: >-
<clickhouse><logger><level>warning</level><console>true</console></logger><query_thread_log
remove="remove"/><query_log remove="remove"/><text_log
remove="remove"/><trace_log remove="remove"/><metric_log
remove="remove"/><asynchronous_metric_log
remove="remove"/><session_log remove="remove"/><part_log
remove="remove"/></clickhouse>
content: '<clickhouse><logger><level>warning</level><console>true</console></logger><query_thread_log remove="remove"/><query_log remove="remove"/><text_log remove="remove"/><trace_log remove="remove"/><metric_log remove="remove"/><asynchronous_metric_log remove="remove"/><session_log remove="remove"/><part_log remove="remove"/></clickhouse>'
ulimits:
nofile:
soft: 262144

View File

@@ -24,6 +24,8 @@ services:
- STORAGE_ACCESS_KEY=$SERVICE_USER_MINIO
- STORAGE_SECRET_KEY=$SERVICE_PASSWORD_MINIO
- STORAGE_USE_SSL=false
- DISABLE_SIGNUPS=$SERVICE_DISABLE_SIGNUPS
- DISABLE_EMAIL_AUTH=$SERVICE_DISABLE_EMAIL_AUTH
depends_on:
- postgres
- minio

View File

@@ -617,7 +617,7 @@ services:
- LOGFLARE_NODE_HOST=127.0.0.1
- DB_USERNAME=supabase_admin
- DB_DATABASE=${POSTGRES_DB:-postgres}
- DB_HOSTNAME=${POSTGRES_HOST:-supabase-db}
- DB_HOSTNAME=${POSTGRES_HOSTNAME:-supabase-db}
- DB_PORT=${POSTGRES_PORT:-5432}
- DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- DB_SCHEMA=_analytics
@@ -628,7 +628,7 @@ services:
- LOGFLARE_MIN_CLUSTER_SIZE=1
# Comment variables to use Big Query backend for analytics
- POSTGRES_BACKEND_URL=postgresql://supabase_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- POSTGRES_BACKEND_URL=postgresql://supabase_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- POSTGRES_BACKEND_SCHEMA=_analytics
- LOGFLARE_FEATURE_FLAG_OVERRIDE=multibackend=true
@@ -904,7 +904,7 @@ services:
condition: service_healthy
restart: unless-stopped
environment:
- PGRST_DB_URI=postgres://authenticator:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- PGRST_DB_URI=postgres://authenticator:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- PGRST_DB_SCHEMAS=${PGRST_DB_SCHEMAS:-public}
- PGRST_DB_ANON_ROLE=anon
- PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT}
@@ -940,7 +940,7 @@ services:
- API_EXTERNAL_URL=${API_EXTERNAL_URL:-http://supabase-kong:8000}
- GOTRUE_DB_DRIVER=postgres
- GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- GOTRUE_DB_DATABASE_URL=postgres://supabase_auth_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- GOTRUE_SITE_URL=${SERVICE_FQDN_SUPABASEKONG}
- GOTRUE_URI_ALLOW_LIST=${ADDITIONAL_REDIRECT_URLS}
@@ -1020,7 +1020,7 @@ services:
retries: 3
environment:
- PORT=4000
- DB_HOST=${POSTGRES_HOST:-supabase-db}
- DB_HOST=${POSTGRES_HOSTNAME:-supabase-db}
- DB_PORT=${POSTGRES_PORT:-5432}
- DB_USER=supabase_admin
- DB_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
@@ -1098,7 +1098,7 @@ services:
- SERVER_REGION=local
- MULTI_TENANT=false
- AUTH_JWT_SECRET=${SERVICE_PASSWORD_JWT}
- DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- DB_INSTALL_ROLES=false
- STORAGE_BACKEND=s3
- STORAGE_S3_BUCKET=stub
@@ -1121,7 +1121,7 @@ services:
# - SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJyb2xlIjogInNlcnZpY2Vfcm9sZSIsCiAgImlzcyI6ICJzdXBhYmFzZSIsCiAgImlhdCI6IDE3MDg5ODg0MDAsCiAgImV4cCI6IDE4NjY4NDEyMDAKfQ.GA7yF2BmqTzqGkP_oqDdJAQVt0djjIxGYuhE0zFDJV4
# - POSTGREST_URL=http://supabase-rest:3000
# - PGRST_JWT_SECRET=${SERVICE_PASSWORD_JWT}
# - DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
# - DATABASE_URL=postgres://supabase_storage_admin:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
# - FILE_SIZE_LIMIT=52428800
# - STORAGE_BACKEND=s3
# - STORAGE_S3_BUCKET=stub
@@ -1164,7 +1164,7 @@ services:
condition: service_healthy
environment:
- PG_META_PORT=8080
- PG_META_DB_HOST=${POSTGRES_HOST:-supabase-db}
- PG_META_DB_HOST=${POSTGRES_HOSTNAME:-supabase-db}
- PG_META_DB_PORT=${POSTGRES_PORT:-5432}
- PG_META_DB_NAME=${POSTGRES_DB:-postgres}
- PG_META_DB_USER=supabase_admin
@@ -1185,7 +1185,7 @@ services:
- SUPABASE_URL=http://supabase-kong:8000
- SUPABASE_ANON_KEY=${SERVICE_SUPABASEANON_KEY}
- SUPABASE_SERVICE_ROLE_KEY=${SERVICE_SUPABASESERVICE_KEY}
- SUPABASE_DB_URL=postgresql://postgres:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOST:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
- SUPABASE_DB_URL=postgresql://postgres:${SERVICE_PASSWORD_POSTGRES}@${POSTGRES_HOSTNAME:-supabase-db}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres}
# TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786
- VERIFY_JWT=${FUNCTIONS_VERIFY_JWT:-false}
volumes:

View File

@@ -12,13 +12,15 @@ services:
- SERVER_URL=$SERVICE_FQDN_TWENTY
- FRONT_BASE_URL=$SERVICE_FQDN_TWENTY
- ENABLE_DB_MIGRATIONS=true
- SIGN_IN_PREFILLED=false
- CACHE_STORAGE_TYPE=${CACHE_STORAGE_TYPE:-redis}
- REDIS_HOST=redis
- REDIS_PORT=6379
- STORAGE_TYPE=${STORAGE_TYPE:-local}
- STORAGE_S3_REGION=$STORAGE_S3_REGION
- STORAGE_S3_NAME=$STORAGE_S3_NAME
- STORAGE_S3_ENDPOINT=$STORAGE_S3_ENDPOINT
# https://twenty.com/developers/section/self-hosting/self-hosting-var#security
- API_RATE_LIMITING_TTL=${API_RATE_LIMITING_TTL:-100}
- API_RATE_LIMITING_LIMIT=${API_RATE_LIMITING_LIMIT:-100}
# https://twenty.com/developers/section/self-hosting/self-hosting-var#tokens
- ACCESS_TOKEN_SECRET=$SERVICE_BASE64_32_ACCESS
- LOGIN_TOKEN_SECRET=$SERVICE_BASE64_32_LOGIN
- REFRESH_TOKEN_SECRET=$SERVICE_BASE64_32_REFRESH
@@ -26,6 +28,26 @@ services:
- POSTGRES_ADMIN_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- PG_DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@postgres:5432/default
# https://twenty.com/developers/section/self-hosting/self-hosting-var#auth
- IS_SIGN_UP_DISABLED=${IS_SIGN_UP_DISABLED:-false}
- PASSWORD_RESET_TOKEN_EXPIRES_IN=${PASSWORD_RESET_TOKEN_EXPIRES_IN:-5m}
# https://twenty.com/developers/section/self-hosting/self-hosting-var#workspace-cleaning
- WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION=$WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION
- WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION=$WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION
# https://twenty.com/developers/section/self-hosting/self-hosting-var#captcha
- STORAGE_TYPE=${STORAGE_TYPE:-local}
- STORAGE_S3_REGION=$STORAGE_S3_REGION
- STORAGE_S3_NAME=$STORAGE_S3_NAME
- STORAGE_S3_ENDPOINT=$STORAGE_S3_ENDPOINT
- STORAGE_S3_ACCESS_KEY_ID=$STORAGE_S3_ACCESS_KEY_ID
- STORAGE_S3_SECRET_ACCESS_KEY=$STORAGE_S3_SECRET_ACCESS_KEY
# https://twenty.com/developers/section/self-hosting/self-hosting-var#message-queue
- MESSAGE_QUEUE_TYPE=$MESSAGE_QUEUE_TYPE
# https://twenty.com/developers/section/self-hosting/self-hosting-var#email
- EMAIL_FROM_ADDRESS=$EMAIL_FROM_ADDRESS
- EMAIL_FROM_NAME=$EMAIL_FROM_NAME
- EMAIL_SYSTEM_ADDRESS=$EMAIL_SYSTEM_ADDRESS
@@ -35,10 +57,12 @@ services:
- EMAIL_SMTP_USER=$EMAIL_SMTP_USER
- EMAIL_SMTP_PASSWORD=$EMAIL_SMTP_PASSWORD
# https://twenty.com/developers/section/self-hosting/self-hosting-var#debug-/-development
- SIGN_IN_PREFILLED=false
- DEBUG_MODE=${DEBUG_MODE:-false}
# https://twenty.com/developers/section/self-hosting/self-hosting-var#telemetry
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false}
- CACHE_STORAGE_TYPE=${CACHE_STORAGE_TYPE:-redis}
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
postgres:
condition: service_healthy

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
{
"coolify": {
"v4": {
"version": "4.0.0-beta.307"
"version": "4.0.0-beta.312"
}
}
}