Compare commits

...

127 Commits

Author SHA1 Message Date
Andras Bacsai
2978042162 Merge pull request #2835 from coollabsio/next
v4.0.0-beta.314
2024-07-15 16:42:04 +02:00
Andras Bacsai
4225ec7060 feat: Improve error handling in loadComposeFile method 2024-07-15 16:39:40 +02:00
Andras Bacsai
893339fc8e refactor: Update Docker Compose build command to include --pull flag 2024-07-15 16:39:28 +02:00
Andras Bacsai
356e7b57d2 improvement: add basedir + compose file in new compose based apps 2024-07-15 16:39:22 +02:00
Andras Bacsai
4ee1f1a507 fix: improve github source creation 2024-07-15 15:33:46 +02:00
Andras Bacsai
7d64df60cd fix: drupal 2024-07-15 13:59:33 +02:00
Andras Bacsai
eb3a4ca157 Merge pull request #2463 from emircanerkul/main
Add drupal-with-postgresql service template
2024-07-15 13:51:43 +02:00
Andras Bacsai
a7b5157fa6 fix: docmost template 2024-07-15 12:58:29 +02:00
Andras Bacsai
793e6d19eb Merge pull request #2747 from alfinauzikri/main
[TEMPLATE] Add Docmost Template
2024-07-15 12:54:36 +02:00
Andras Bacsai
674fa4d09c fix: vikunja 2024-07-15 12:51:04 +02:00
Andras Bacsai
0089e86dd1 refactor: Remove unused code and fix storage form layout 2024-07-15 12:23:06 +02:00
Andras Bacsai
e1d802b507 Merge pull request #2817 from luckydonald/patch-2
[TEMPLATE] fix vikunja, add postgres variant.
2024-07-15 12:18:21 +02:00
Andras Bacsai
9927b71af9 fix: plane service template 2024-07-15 12:13:34 +02:00
Andras Bacsai
b1c0f105ab fix: update docker compose pull command with --policy always 2024-07-15 12:13:21 +02:00
Andras Bacsai
35cae1d4dc Merge pull request #2831 from MrAlexand0r/main
[Feature] #2354 - Add Plane Service
2024-07-15 11:48:53 +02:00
Andras Bacsai
3dab3365e2 fix service-templates 2024-07-15 11:40:12 +02:00
Andras Bacsai
1bdc7c87ba Delete templates/service-templates.json 2024-07-15 11:34:40 +02:00
Andras Bacsai
cab8ad0ca0 Merge pull request #2826 from truemiller/patch-1
Fix typo in "Is Literal?" checkbox in Environment Variables
2024-07-15 11:32:08 +02:00
Andras Bacsai
43409f3ff0 fix: add validation for missing docker compose file 2024-07-15 11:31:18 +02:00
Andras Bacsai
a5dd4cab52 fix: update minio hc in services 2024-07-15 11:31:13 +02:00
Andras Bacsai
a815240f4e Merge pull request #2827 from Megumiso/fix-placement-constraints
fix placement constraints were ignored
2024-07-15 11:18:27 +02:00
Andras Bacsai
2a44e7c5bd Merge pull request #2829 from mateusfmello/fix-minio-healthcheck
fix(MinIO): error in healthcheck command
2024-07-15 11:14:55 +02:00
Andras Bacsai
28c7e439b1 fix: service domains and envs are properly updated 2024-07-15 10:55:04 +02:00
Andras Bacsai
4396c786b4 refactor: Update version numbers to 4.0.0-beta.314 2024-07-15 10:54:50 +02:00
Alexander Gratzl
b67bb8595f removed health checks none 2024-07-14 00:36:14 +02:00
Mateus Fernandes
bec47487dd fix(MinIO): new command healthcheck
MinIO container were not available, as they do not contain the CURL or WGET commands, but MinIO has its own verification command:
https://github.com/minio/minio/issues/18389
2024-07-13 12:33:54 -03:00
Mateus Fernandes
b110d0c12b fix(reactive-resume): new healthcheck command for MinIO
MinIO container were not available, as they do not contain the CURL or WGET commands, but MinIO has its own verification command:
https://github.com/minio/minio/issues/18389
2024-07-13 12:33:12 -03:00
Alexander G
ae425475b4 #2354 added healthchecks for the services 2024-07-13 10:41:04 +02:00
Megumiso
dc6aee44b3 changed variable name for better readability 2024-07-13 13:26:51 +09:00
Megumiso
4ffea311e8 placement constraints is now working 2024-07-13 13:15:17 +09:00
Alexander
77a6a6e46a Merge branch 'main' of https://github.com/coollabsio/coolify
# Conflicts:
#	templates/service-templates.json
2024-07-13 01:08:17 +02:00
Alexander
2278ba31e7 #2354 WIP plane feature 2024-07-13 00:38:41 +02:00
Josh Miller
aaeec3d340 fix: env is_literal helper text typo 2024-07-12 19:00:20 +01:00
Josh Miller
2cbe530b7e fix: typo in is_literal helper 2024-07-12 18:59:06 +01:00
Andras Bacsai
6ada6d145c Merge pull request #2821 from coollabsio/next
v4.0.0-beta.313
2024-07-12 15:46:08 +02:00
Andras Bacsai
0f55e83591 revert: instancesettings 2024-07-12 15:45:36 +02:00
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
Luckydonald
7de2b8cbd7 Create vikunja-with-postgres.yaml
to have a db variant.
2024-07-12 13:58:14 +02:00
Luckydonald
852e906736 Update vikunja.yaml, follow recommended docker-compose. 2024-07-12 13:55:37 +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
Alfin Auzikri
0c40c0d795 Add files via upload 2024-07-04 23:38:52 +07:00
Alfin Auzikri
25f0a8f0b7 Create docmost.yaml 2024-07-04 23:38:16 +07: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
Emircan ERKUL
e7e85456ea Drupal svg logo 2024-06-12 06:54:59 +03:00
Emircan ERKUL
440baf6009 Create drupal-with-postgresql.yaml 2024-06-12 06:50:50 +03:00
102 changed files with 2520 additions and 2016 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

@@ -20,7 +20,7 @@ class StartService
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
$commands[] = 'echo Starting service.';
$commands[] = "echo 'Pulling images.'";
$commands[] = 'docker compose pull';
$commands[] = 'docker compose pull --policy always';
$commands[] = "echo 'Starting containers.'";
$commands[] = 'docker compose up -d --remove-orphans --force-recreate --build';
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";

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

@@ -15,7 +15,7 @@ class PullImage
$commands[] = 'cd '.$resource->workdir();
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
$commands[] = 'docker compose pull';
$commands[] = 'docker compose pull --policy always';
$server = data_get($resource, 'server');

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 = \App\Models\InstanceSettings::get();
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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
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 = \App\Models\InstanceSettings::get();
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 --pull";
$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],
);
@@ -1624,12 +1624,15 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
],
],
];
if (data_get($this->application, 'swarm_placement_constraints')) {
$swarm_placement_constraints = Yaml::parse(base64_decode(data_get($this->application, 'swarm_placement_constraints')));
$docker_compose['services'][$this->container_name]['deploy'] = array_merge(
$docker_compose['services'][$this->container_name]['deploy'],
$swarm_placement_constraints
);
}
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
$docker_compose['services'][$this->container_name]['deploy']['placement'] = [
'constraints' => [
'node.role == worker',
],
];
$docker_compose['services'][$this->container_name]['deploy']['placement']['constraints'][] = 'node.role == worker';
}
if ($this->pull_request_id !== 0) {
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
@@ -2028,39 +2031,22 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
}
}
private function build_by_compose_file()
{
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
if ($this->application->build_pack === 'dockerimage') {
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true],
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), 'hidden' => true],
);
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), 'hidden' => true],
);
}
$this->application_deployment_queue->addLogEntry('New images built.');
}
private function start_by_compose_file()
{
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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
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

@@ -53,6 +53,12 @@ class GithubPrivateRepository extends Component
public ?string $publish_directory = null;
// In case of docker compose
public ?string $base_directory = null;
public ?string $docker_compose_location = '/docker-compose.yaml';
// End of docker compose
protected int $page = 1;
public $build_pack = 'nixpacks';
@@ -68,6 +74,16 @@ class GithubPrivateRepository extends Component
$this->github_apps = GithubApp::private();
}
public function updatedBaseDirectory()
{
if ($this->base_directory) {
$this->base_directory = rtrim($this->base_directory, '/');
if (! str($this->base_directory)->startsWith('/')) {
$this->base_directory = '/'.$this->base_directory;
}
}
}
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
@@ -184,6 +200,10 @@ class GithubPrivateRepository extends Component
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
$application->health_check_enabled = false;
}
if ($this->build_pack === 'dockercompose') {
$application['docker_compose_location'] = $this->docker_compose_location;
$application['base_directory'] = $this->base_directory;
}
$fqdn = generateFqdn($destination->server, $application->uuid);
$application->fqdn = $fqdn;

View File

@@ -33,6 +33,12 @@ class GithubPrivateRepositoryDeployKey extends Component
public ?string $publish_directory = null;
// In case of docker compose
public ?string $base_directory = null;
public ?string $docker_compose_location = '/docker-compose.yaml';
// End of docker compose
public string $repository_url;
public string $branch;
@@ -163,6 +169,10 @@ class GithubPrivateRepositoryDeployKey extends Component
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
$application_init['health_check_enabled'] = false;
}
if ($this->build_pack === 'dockercompose') {
$application_init['docker_compose_location'] = $this->docker_compose_location;
$application_init['base_directory'] = $this->base_directory;
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;
$application->settings->save();

View File

@@ -33,6 +33,12 @@ class PublicGitRepository extends Component
public ?string $publish_directory = null;
// In case of docker compose
public ?string $base_directory = null;
public ?string $docker_compose_location = '/docker-compose.yaml';
// End of docker compose
public string $git_branch = 'main';
public int $rate_limit_remaining = 0;
@@ -59,6 +65,8 @@ class PublicGitRepository extends Component
'is_static' => 'required|boolean',
'publish_directory' => 'nullable|string',
'build_pack' => 'required|string',
'base_directory' => 'nullable|string',
'docker_compose_location' => 'nullable|string',
];
protected $validationAttributes = [
@@ -67,6 +75,8 @@ class PublicGitRepository extends Component
'is_static' => 'static',
'publish_directory' => 'publish directory',
'build_pack' => 'build pack',
'base_directory' => 'base directory',
'docker_compose_location' => 'docker compose location',
];
public function mount()
@@ -79,6 +89,16 @@ class PublicGitRepository extends Component
$this->query = request()->query();
}
public function updatedBaseDirectory()
{
if ($this->base_directory) {
$this->base_directory = rtrim($this->base_directory, '/');
if (! str($this->base_directory)->startsWith('/')) {
$this->base_directory = '/'.$this->base_directory;
}
}
}
public function updatedBuildPack()
{
if ($this->build_pack === 'nixpacks') {
@@ -261,6 +281,10 @@ class PublicGitRepository extends Component
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
$application_init['health_check_enabled'] = false;
}
if ($this->build_pack === 'dockercompose') {
$application_init['docker_compose_location'] = $this->docker_compose_location;
$application_init['base_directory'] = $this->base_directory;
}
$application = Application::create($application_init);
$application->settings->is_static = $this->is_static;

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

@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Shared\Storages;
use App\Models\LocalPersistentVolume;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Show extends Component
{
@@ -12,8 +11,6 @@ class Show extends Component
public bool $isReadOnly = false;
public ?string $modalId = null;
public bool $isFirst = true;
public bool $isService = false;
@@ -32,11 +29,6 @@ class Show extends Component
'host_path' => 'host',
];
public function mount()
{
$this->modalId = new Cuid2(7);
}
public function submit()
{
$this->validate();

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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
}
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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
$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'"));
@@ -1063,45 +1081,55 @@ class Application extends BaseModel
'git read-tree -mu HEAD',
"cat .$workdir$composeFile",
]);
$composeFileContent = instant_remote_process($commands, $this->destination->server, false);
if (! $composeFileContent) {
try {
$composeFileContent = instant_remote_process($commands, $this->destination->server);
} catch (\Exception $e) {
if (str($e->getMessage())->contains('No such file')) {
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
}
if (str($e->getMessage())->contains('fatal: repository') && str($e->getMessage())->contains('does not exist')) {
if ($this->deploymentType() === 'deploy_key') {
throw new \RuntimeException('Your deploy key does not have access to the repository. Please check your deploy key and try again.');
}
throw new \RuntimeException('Repository does not exist. Please check your repository URL and try again.');
}
throw new \RuntimeException($e->getMessage());
} finally {
$this->docker_compose_location = $initialDockerComposeLocation;
$this->save();
$commands = collect([
"rm -rf /tmp/{$uuid}",
]);
instant_remote_process($commands, $this->destination->server, false);
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
} else {
}
if ($composeFileContent) {
$this->docker_compose_raw = $composeFileContent;
$this->save();
}
$commands = collect([
"rm -rf /tmp/{$uuid}",
]);
instant_remote_process($commands, $this->destination->server, false);
$parsedServices = $this->parseCompose();
if ($this->docker_compose_domains) {
$json = collect(json_decode($this->docker_compose_domains));
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
$jsonNames = $json->keys()->toArray();
$diff = array_diff($jsonNames, $names);
$json = $json->filter(function ($value, $key) use ($diff) {
return ! in_array($key, $diff);
});
if ($json) {
$this->docker_compose_domains = json_encode($json);
} else {
$this->docker_compose_domains = null;
$parsedServices = $this->parseCompose();
if ($this->docker_compose_domains) {
$json = collect(json_decode($this->docker_compose_domains));
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
$jsonNames = $json->keys()->toArray();
$diff = array_diff($jsonNames, $names);
$json = $json->filter(function ($value, $key) use ($diff) {
return ! in_array($key, $diff);
});
if ($json) {
$this->docker_compose_domains = json_encode($json);
} else {
$this->docker_compose_domains = null;
}
$this->save();
}
$this->save();
return [
'parsedServices' => $parsedServices,
'initialDockerComposeLocation' => $this->docker_compose_location,
];
} else {
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile<br><br>Check if you used the right extension (.yaml or .yml) in the compose file name.");
}
return [
'parsedServices' => $parsedServices,
'initialDockerComposeLocation' => $this->docker_compose_location,
];
}
public function parseContainerLabels(?ApplicationPreview $preview = null)

View File

@@ -318,11 +318,11 @@ respond 404
public function setupDynamicProxyConfiguration()
{
$settings = InstanceSettings::get();
$settings = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
if (! $settings->is_registration_enabled) {
return redirect()->route('login');
}
@@ -57,7 +56,7 @@ class FortifyServiceProvider extends ServiceProvider
});
Fortify::loginView(function () {
$settings = InstanceSettings::get();
$settings = \App\Models\InstanceSettings::get();
$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

@@ -88,6 +88,9 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
try {
$name = data_get($resource, 'name');
$dockerComposeRaw = data_get($resource, 'service.docker_compose_raw');
if (! $dockerComposeRaw) {
throw new \Exception('No compose file found or not a valid YAML file.');
}
$dockerCompose = Yaml::parse($dockerComposeRaw);
// Switch Image
@@ -106,7 +109,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
if ($resourceFqdns->count() === 1) {
$resourceFqdns = $resourceFqdns->first();
$variableName = 'SERVICE_FQDN_'.str($resource->name)->upper()->replace('-', '');
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'LIKE', "{$variableName}_%")->first();
$fqdn = Url::fromString($resourceFqdns);
$port = $fqdn->getPort();
$path = $fqdn->getPath();
@@ -125,7 +128,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
}
}
$variableName = 'SERVICE_URL_'.str($resource->name)->upper()->replace('-', '');
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', 'LIKE', "{$variableName}_%")->first();
$url = Url::fromString($fqdn);
$port = $url->getPort();
$path = $url->getPath();

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(\App\Models\InstanceSettings::get());
}
function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string
{
if (! $settings) {
$settings = InstanceSettings::get();
$settings = \App\Models\InstanceSettings::get();
}
config()->set('mail.from.address', data_get($settings, 'smtp_from_address'));
config()->set('mail.from.name', data_get($settings, 'smtp_from_name'));
@@ -284,7 +284,7 @@ function base_ip(): string
if (isDev()) {
return 'localhost';
}
$settings = InstanceSettings::get();
$settings = \App\Models\InstanceSettings::get();
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 = \App\Models\InstanceSettings::get();
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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
$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 = \App\Models\InstanceSettings::get();
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 = \App\Models\InstanceSettings::get();
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 = \App\Models\InstanceSettings::get();
[$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.314',
// 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.314';

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 = \App\Models\InstanceSettings::get();
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 = \App\Models\InstanceSettings::get();
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

BIN
public/svgs/docmost.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

1
public/svgs/drupal.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="220.03239mm" height="167.50914mm" viewBox="0 0 623.71387 474.82906"><title>Risorsa 28</title><g id="Livello_2" data-name="Livello 2"><g id="Livello_1-2" data-name="Livello 1"><path d="M292.12129,345.00234h-26.212l.04275,49.04508c0,14.33211-6.14488,25.92692-20.477,25.92692-14.33924,0-20.54117-11.59481-20.54117-25.92692V345.04868H198.78953l-.00713,48.99874c0,28.32213,18.20655,51.27636,46.53223,51.27636,28.31849,0,46.79948-22.95423,46.79948-51.27636Z" style="fill:#009cde"/><rect x="528.48024" y="315.11614" width="26.08367" height="127.67755" style="fill:#009cde"/><polygon points="573.823 319.826 573.823 315.239 594.15 315.239 594.15 319.826 586.715 319.826 586.715 340.043 581.261 340.043 581.261 319.826 573.823 319.826" style="fill:#009cde"/><polygon points="604.673 315.24 610.476 332.295 610.544 332.295 616.029 315.24 623.714 315.24 623.714 340.044 618.606 340.044 618.606 322.465 618.535 322.465 612.458 340.044 608.252 340.044 602.175 322.64 602.103 322.64 602.103 340.044 596.999 340.044 596.999 315.24 604.673 315.24" style="fill:#009cde"/><path d="M177.46784,343.93624c-22.48375-5.18962-36.66617,17.148-37.25785,18.34557-.28871.58814-.2994.93035-1.29384.90533-.82337-.01773-.916-.90533-.916-.90533l-2.79086-17.08726H111.84157v97.51614h26.19419v-52.784c0-4.3128,11.61256-24.993,34.11766-19.67153,11.38088,2.69467,16.21054,7.52436,16.21054,7.52436V348.07087a41.85,41.85,0,0,0-10.89612-4.13463" style="fill:#009cde"/><path d="M353.05258,368.64446a26.13539,26.13539,0,1,1-26.13,26.13,26.13748,26.13748,0,0,1,26.13-26.13M327.664,474.82906V439.74191l.00712.00718.00713-13.14169s.03921-1.05141.98729-1.06218c.84474-.01066,1.03368.549,1.24041,1.06218,1.98529,4.94369,12.90641,23.76689,37.14374,17.86435A51.631,51.631,0,1,0,301.402,394.77446v80.0546Z" style="fill:#009cde"/><path d="M492.37655,394.77479a26.13539,26.13539,0,1,1-26.1336-26.13,26.139,26.139,0,0,1,26.1336,26.13m-.74494,47.9793H517.8935v-47.9793a51.62918,51.62918,0,1,0-65.64764,49.69381c24.23739,5.906,35.15845-12.92066,37.14374-17.86087.20673-.5132.39208-1.07284,1.24041-1.06566.94808.01425.98729,1.06566.98729,1.06566" style="fill:#009cde"/><path d="M36.905,337.16373h-10.529v83.26605l10.81417.278c22.18076,0,36.46656-2.01733,36.46656-41.90208,0-38.24519-12.61057-41.642-36.75172-41.642m-7.1108,105.38619H0V315.0649H31.96836c38.70852,0,68.06785,7.10374,68.06785,63.74083,0,56.09518-31.09872,63.74419-70.24206,63.74419" style="fill:#009cde"/><path d="M336.513,60.04433C316.67656,40.21728,297.75071,21.31654,292.11812,0c-5.63286,21.31654-24.56176,40.21728-44.39487,60.04433-29.74983,29.731-63.47995,63.42669-63.47995,113.96434a107.878,107.878,0,1,0,215.756,0c0-50.53435-33.72736-84.23329-63.48628-113.96434M230.09481,199.146c-6.61462-.22461-31.02654-42.30194,14.26152-87.1038l29.96891,32.73593a2.56174,2.56174,0,0,1-.20005,3.82276c-7.15132,7.33453-37.63207,37.90055-41.42061,48.46955-.782,2.18152-1.92407,2.099-2.60977,2.07556m62.02662,55.45668a37.10175,37.10175,0,0,1-37.102-37.102c0-9.39381,3.73446-17.765,9.24757-24.50685,6.68995-8.18054,27.84947-31.18907,27.84947-31.18907s20.83558,23.34627,27.79953,31.111a36.28369,36.28369,0,0,1,9.30744,24.58494,37.10209,37.10209,0,0,1-37.102,37.102m71.01314-60.16628c-.79965,1.74885-2.61363,4.66848-5.062,4.75761-4.36413.15894-4.83045-2.07721-8.05609-6.8511-7.08179-10.47987-68.88406-75.07043-80.44365-87.56214-10.16779-10.987-1.43181-18.733,2.62052-22.79219,5.084-5.09314,19.92417-19.92418,19.92417-19.92418s44.24974,41.984,62.6825,70.67071,12.08027,53.50972,8.33451,61.70129" style="fill:#009cde"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

6
public/svgs/plane.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="155" height="155" viewBox="0 0 155 155" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="37" y="64" width="27" height="27" fill="#4075FC"/>
<rect x="91" y="64" width="27" height="27" fill="#4075FC"/>
<rect x="64" y="91" width="27" height="27" fill="#4075FC"/>
<rect x="64" y="36" width="54" height="28" fill="#4075FC"/>
</svg>

After

Width:  |  Height:  |  Size: 346 B

View File

@@ -8,7 +8,7 @@
@use('App\Models\InstanceSettings')
@php
$instanceSettings = InstanceSettings::first();
$instanceSettings = \App\Models\InstanceSettings::get();
$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

@@ -62,6 +62,15 @@
<x-forms.input id="publish_directory" required label="Publish Directory" />
@endif
</div>
@if ($build_pack === 'dockercompose')
<x-forms.input placeholder="/" wire:model.blur="base_directory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." />
<x-forms.input placeholder="/docker-compose.yaml" id="docker_compose_location"
label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>" />
Compose file location in your repository:<span
class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>
@endif
@if ($show_is_static)
<x-forms.input type="number" required id="port" label="Port" :readonly="$is_static || $build_pack === 'static'" />
<div class="w-52">

View File

@@ -14,7 +14,6 @@
@endif
</div>
<div class="pb-4">Deploy any public or private Git repositories through a GitHub App.</div>
@if ($github_apps->count() !== 0)
<h2 class="pt-4 pb-4">Select a Github App</h2>
<div class="flex flex-col gap-2">
@@ -92,6 +91,16 @@
helper="If there is a build process involved (like Svelte, React, Next, etc..), please specify the output directory for the build assets." />
@endif
</div>
@if ($build_pack === 'dockercompose')
<x-forms.input placeholder="/" wire:model.blur="base_directory"
label="Base Directory"
helper="Directory to use as root. Useful for monorepos." />
<x-forms.input placeholder="/docker-compose.yaml" id="docker_compose_location"
label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>" />
Compose file location in your repository:<span
class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>
@endif
@if ($show_is_static)
<x-forms.input type="number" id="port" label="Port" :readonly="$is_static || $build_pack === 'static'"
helper="The port your application listens on." />

View File

@@ -13,10 +13,6 @@
</div>
@if (!$branch_found)
<div class="px-2 pt-4">
<div class="flex flex-col pb-4">
<div>Preselect branch (eg: main):</div>
<div class='text-helper'>https://github.com/coollabsio/coolify-examples/tree/main</div>
</div>
<div>
For example application deployments, checkout <a class="underline dark:text-white"
href="https://github.com/coollabsio/coolify-examples/" target="_blank">Coolify
@@ -51,6 +47,15 @@
helper="If there is a build process involved (like Svelte, React, Next, etc..), please specify the output directory for the build assets." />
@endif
</div>
@if ($build_pack === 'dockercompose')
<x-forms.input placeholder="/" wire:model.blur="base_directory" label="Base Directory"
helper="Directory to use as root. Useful for monorepos." />
<x-forms.input placeholder="/docker-compose.yaml" id="docker_compose_location"
label="Docker Compose Location"
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>" />
Compose file location in your repository:<span
class='dark:text-warning'>{{ Str::start($base_directory . $docker_compose_location, '/') }}</span>
@endif
@if ($show_is_static)
<x-forms.input type="number" id="port" label="Port" :readonly="$is_static || $build_pack === 'static'"
helper="The port your application listens on." />
@@ -59,10 +64,12 @@
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
</div>
@endif
@if ($build_pack === 'dockercompose' && isDev())
<div class="dark:text-warning">If you choose Docker Compose based deployments, you cannot change it afterwards.</div>
<x-forms.checkbox instantSave label="New Compose Services (only in dev mode)" id="new_compose_services"></x-forms.checkbox>
@endif
{{-- @if ($build_pack === 'dockercompose' && isDev())
<div class="dark:text-warning">If you choose Docker Compose based deployments, you cannot
change it afterwards.</div>
<x-forms.checkbox instantSave label="New Compose Services (only in dev mode)"
id="new_compose_services"></x-forms.checkbox>
@endif --}}
</div>
<x-forms.button wire:click.prevent='submit'>
Continue

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

@@ -9,7 +9,7 @@
<x-forms.checkbox id="is_multiline" label="Is Multiline?" />
@if (!$shared)
<x-forms.checkbox id="is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it form another value. In this case, you should set this to true."
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
<x-forms.button type="submit" @click="slideOverOpen=false">

View File

@@ -55,7 +55,7 @@
@if ($env->is_shared)
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
<x-forms.checkbox instantSave id="env.is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it form another value. In this case, you should set this to true."
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@else
@if ($isSharedVariable)
@@ -65,7 +65,7 @@
<x-forms.checkbox instantSave id="env.is_multiline" label="Is Multiline?" />
@if (!data_get($env, 'is_multiline'))
<x-forms.checkbox instantSave id="env.is_literal"
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it form another value. In this case, you should set this to true."
helper="This means that when you use $VARIABLES in a value, it should be interpreted as the actual characters '$VARIABLES' and not as the value of a variable named VARIABLE.<br><br>Useful if you have $ sign in your value and there are some characters after it, but you would not like to interpolate it from another value. In this case, you should set this to true."
label="Is Literal?" />
@endif
@endif

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

@@ -15,15 +15,17 @@
<x-forms.input id="storage.host_path" readonly helper="Directory on the host system."
label="Source Path"
helper="Warning: Changing the source path after the initial start could cause problems. Only use it when you know what are you doing." />
<x-forms.input id="storage.mount_path" label="Destination Path"
helper="Directory inside the container." required readonly />
@else
<x-forms.input id="storage.host_path" helper="Directory on the host system." label="Source Path"
helper="Warning: Changing the source path after the initial start could cause problems. Only use it when you know what are you doing." />
<x-forms.input id="storage.mount_path" label="Destination Path"
helper="Directory inside the container." required readonly />
<x-forms.button type="submit">
Update
</x-forms.button>
@endif
<x-forms.input id="storage.mount_path" label="Destination Path" helper="Directory inside the container."
required readonly />
<x-forms.button type="submit">
Update
</x-forms.button>
@else
<x-forms.input id="storage.name" required readonly />
<x-forms.input id="storage.host_path" readonly />

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

@@ -1,16 +1,45 @@
<form wire:submit='createGitHubApp' class="flex flex-col w-full gap-2">
<div class="pb-2">This is required, if you would like to get full integration (commit / pull request
deployments, etc)
with GitHub.</div>
<div class="flex gap-2">
<x-forms.input id="name" label="Name" required />
<x-forms.input helper="If empty, your GitHub user will be used." id="organization" label="Organization" />
<x-forms.input helper="If empty, your GitHub user will be used."
placeholder="If empty, your GitHub user will be used." id="organization" label="Organization (on GitHub)" />
</div>
<div class="flex gap-2">
<x-forms.input id="html_url" label="HTML Url" required />
<x-forms.input id="api_url" label="API Url" required />
</div>
<div class="flex gap-2">
<x-forms.input id="custom_user" label="Custom Git User" required />
<x-forms.input id="custom_port" type="number" label="Custom Git Port" required />
<div x-data="{
activeAccordion: '',
setActiveAccordion(id) {
this.activeAccordion = (this.activeAccordion == id) ? '' : id
}
}" class="relative w-full py-2 mx-auto overflow-hidden text-sm font-normal rounded-md">
<div x-data="{ id: $id('accordion') }" class="cursor-pointer group">
<button @click="setActiveAccordion(id)"
class="flex items-center justify-between w-full px-1 py-2 text-left select-none hover:dark:text-white hover:bg-white/5"
type="button">
<h4>Advanced</h4>
<svg class="w-4 h-4 duration-200 ease-out" :class="{ 'rotate-180': activeAccordion == id }"
viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div x-show="activeAccordion==id" x-collapse x-cloak class="px-2">
<div class="py-2">Self-hosted / Enterprise GitHub details.</div>
<div class="flex flex-col gap-2 pt-0 opacity-70">
<div class="flex gap-2">
<x-forms.input id="html_url" label="HTML Url" required />
<x-forms.input id="api_url" label="API Url" required />
</div>
<div class="flex gap-2">
<x-forms.input id="custom_user" label="Custom Git User" required />
<x-forms.input id="custom_port" type="number" label="Custom Git Port" required />
</div>
</div>
</div>
</div>
</div>
@if (!isCloud())
<x-forms.checkbox id="is_system_wide" label="System Wide" />
@endif

View File

@@ -0,0 +1,58 @@
# documentation: https://docmost.com/docs/
# slogan: Open-source collaborative wiki and documentation software
# tags: documentation, opensource, wiki, confluence, knowledge-base, notion, realtime-collaboration, notion-alternative
# logo: svgs/docmost.png
# port: 3000
services:
docmost:
image: "docmost/docmost:latest"
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
environment:
- SERVICE_FQDN_DOCMOST_3000
- APP_SECRET=$SERVICE_BASE64_APPKEY
- APP_URL=$SERVICE_FQDN_DOCMOST_3000
- DATABASE_URL=postgresql://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@postgresql/docmost?schema=public
- REDIS_URL=redis://redis:6379
volumes:
- "docmost:/app/data/storage"
healthcheck:
test:
- CMD
- curl
- "-f"
- "http://127.0.0.1:3000"
interval: 2s
timeout: 10s
retries: 20
postgresql:
image: "postgres:16-alpine"
environment:
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- POSTGRES_DB=docmost
volumes:
- "postgresql-data:/var/lib/postgresql/data"
healthcheck:
test:
- CMD-SHELL
- "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"
interval: 5s
timeout: 10s
retries: 20
redis:
image: "redis:7.2-alpine"
volumes:
- "redis-data:/data"
healthcheck:
test:
- CMD
- redis-cli
- PING
interval: 5s
timeout: 10s
retries: 20

View File

@@ -0,0 +1,53 @@
# documentation: https://www.drupal.org/about
# slogan: Drupal is a free and open-source web content management system written in PHP and distributed under the GNU General Public License.
# tags: cms, blog, content, management, postgresql
# logo: svgs/drupal.svg
services:
drupal:
image: "drupal:10-apache"
environment:
- SERVICE_FQDN_DRUPAL
- DB_HOST=postgres
- DB_NAME=postgres
- DB_USER=postgres
- DB_PASSWORD=$SERVICE_PASSWORD_POSTGRES
volumes:
- type: volume
source: drupal_modules
target: /var/www/html/modules
is_directory: true
- type: volume
source: drupal_profiles
target: /var/www/html/profiles
is_directory: true
- type: volume
source: drupal_themes
target: /var/www/html/themes
is_directory: true
- type: volume
source: drupal_sites
target: /var/www/html/sites
is_directory: true
depends_on:
- postgres
healthcheck:
test:
- CMD-SHELL
- "curl -f http://localhost:80 || exit 1"
interval: 30s
timeout: 10s
retries: 5
postgres:
image: "postgres:16"
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
healthcheck:
test:
- CMD-SHELL
- "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"
interval: 5s
timeout: 10s
retries: 20

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

@@ -15,7 +15,7 @@ services:
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"]
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 20s
retries: 10

View File

@@ -0,0 +1,190 @@
# documentation: https://docs.plane.so/self-hosting/methods/docker-compose
# slogan: The open source project management tool
# tags: plane,project-management,tool,open,source,api,nextjs,redis,postgresql,django,pm
# logo: svgs/plane.svg
x-app-env: &app-env
environment:
- WEB_URL=http://localhost
- DEBUG=${DEBUG:-0}
- CORS_ALLOWED_ORIGINS=http://localhost
# Gunicorn Workers
- GUNICORN_WORKERS=${GUNICORN_WORKERS:-1}
#DB SETTINGS
- PGHOST=plane-db
- PGDATABASE=plane
- POSTGRES_USER=$SERVICE_USER_POSTGRES
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
- POSTGRES_DB=plane
- POSTGRES_PORT=5432
- PGDATA=/var/lib/postgresql/data
- DATABASE_URL=postgresql://$SERVICE_USER_POSTGRES:$SERVICE_PASSWORD_POSTGRES@plane-db/plane
# REDIS SETTINGS
- REDIS_HOST=plane-redis
- REDIS_PORT=6379
- REDIS_URL=${REDIS_URL:-redis://plane-redis:6379/}
# Application secret
- SECRET_KEY=$SERVICE_PASSWORD_64_SECRETKEY
# DATA STORE SETTINGS
- USE_MINIO=${USE_MINIO:-1}
- AWS_REGION=${AWS_REGION}
- AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
- AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
- AWS_S3_ENDPOINT_URL=${AWS_S3_ENDPOINT_URL:-http://plane-minio:9000}
- AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME:-uploads}
- MINIO_ROOT_USER=$SERVICE_USER_MINIO
- MINIO_ROOT_PASSWORD=$SERVICE_PASSWORD_MINIO
- BUCKET_NAME=${BUCKET_NAME:-uploads}
- FILE_SIZE_LIMIT=${FILE_SIZE_LIMIT:-5242880}
# Admin and Space URLs
- ADMIN_BASE_URL=${ADMIN_BASE_URL}
- SPACE_BASE_URL=${SPACE_BASE_URL}
- APP_BASE_URL=${APP_BASE_URL}
services:
proxy:
environment:
- SERVICE_FQDN_PLANE
- FILE_SIZE_LIMIT=${FILE_SIZE_LIMIT:-5242880}
image: makeplane/plane-proxy:stable
depends_on:
- web
- api
- space
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:80"]
interval: 2s
timeout: 10s
retries: 15
web:
<<: *app-env
image: makeplane/plane-frontend:stable
command: node web/server.js web
depends_on:
- api
- worker
healthcheck:
test: "wget -qO- http://`hostname`:3000"
interval: 2s
timeout: 10s
retries: 15
space:
<<: *app-env
image: makeplane/plane-space:stable
command: node space/server.js space
depends_on:
- api
- worker
- web
healthcheck:
test: ["CMD", "echo", "hey whats up"]
interval: 2s
timeout: 10s
retries: 15
admin:
<<: *app-env
image: makeplane/plane-admin:stable
command: node admin/server.js admin
depends_on:
- api
- web
healthcheck:
test: ["CMD", "echo", "hey whats up"]
interval: 2s
timeout: 10s
retries: 15
api:
<<: *app-env
image: makeplane/plane-backend:stable
command: ./bin/docker-entrypoint-api.sh
volumes:
- logs_api:/code/plane/logs
depends_on:
- plane-db
- plane-redis
healthcheck:
test: ["CMD", "echo", "hey whats up"]
interval: 2s
timeout: 10s
retries: 15
worker:
<<: *app-env
image: makeplane/plane-backend:stable
command: ./bin/docker-entrypoint-worker.sh
volumes:
- logs_worker:/code/plane/logs
depends_on:
- api
- plane-db
- plane-redis
healthcheck:
test: ["CMD", "echo", "hey whats up"]
interval: 2s
timeout: 10s
retries: 15
beat-worker:
<<: *app-env
image: makeplane/plane-backend:stable
command: ./bin/docker-entrypoint-beat.sh
volumes:
- logs_beat-worker:/code/plane/logs
depends_on:
- api
- plane-db
- plane-redis
healthcheck:
test: ["CMD", "echo", "hey whats up"]
interval: 2s
timeout: 10s
retries: 15
migrator:
<<: *app-env
image: makeplane/plane-backend:stable
restart: "no"
command: ./bin/docker-entrypoint-migrator.sh
volumes:
- logs_migrator:/code/plane/logs
depends_on:
- plane-db
- plane-redis
plane-db:
<<: *app-env
image: postgres:15.5-alpine
command: postgres -c 'max_connections=1000'
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10
plane-redis:
<<: *app-env
image: valkey/valkey:7.2.5-alpine
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 20s
retries: 10
plane-minio:
<<: *app-env
image: minio/minio:latest
command: server /export --console-address ":9090"
volumes:
- uploads:/export
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 20s
retries: 10

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
@@ -51,7 +53,7 @@ services:
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://127.0.0.1:9000/minio/health/live"]
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 20s
retries: 10

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

View File

@@ -0,0 +1,37 @@
# documentation: https://vikunja.io
# slogan: The open-source, self-hostable to-do app. Organize everything, on all platforms.
# tags: productivity,todo
# logo: svgs/vikunja.svg
# port: 3456
services:
vikunja:
image: vikunja/vikunja
environment:
- SERVICE_FQDN_VIKUNJA
- VIKUNJA_SERVICE_PUBLICURL=$SERVICE_FQDN_VIKUNJA
- VIKUNJA_SERVICE_JWTSECRET=$SERVICE_PASSWORD_JWTSECRET
- VIKUNJA_SERVICE_ENABLEREGISTRATION=true
- VIKUNJA_DATABASE_TYPE=postgres
- VIKUNJA_DATABASE_HOST=postgresql
- VIKUNJA_DATABASE_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
- VIKUNJA_DATABASE_USER=${SERVICE_USER_POSTGRESQL}
- VIKUNJA_DATABASE_DATABASE=${POSTGRESQL_DATABASE}
volumes:
- vikunja-data:/app/vikunja/
depends_on:
postgresql:
condition: service_healthy
postgresql:
image: postgres:16-alpine
volumes:
- vikunja-postgresql-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
- POSTGRES_DB=${POSTGRESQL_DATABASE}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 20s
retries: 10

View File

@@ -12,10 +12,16 @@ services:
- VIKUNJA_SERVICE_PUBLICURL=$SERVICE_FQDN_VIKUNJA
- VIKUNJA_SERVICE_JWTSECRET=$SERVICE_PASSWORD_JWTSECRET
- VIKUNJA_SERVICE_ENABLEREGISTRATION=true
- VIKUNJA_DATABASE_PATH=/db/vikunja.db
- VIKUNJA_DATABASE_TYPE=sqlite
volumes:
- vikunja-data:/app/vikunja/
healthcheck:
test: ["CMD", "wget", "--spider", "http://127.0.0.1:3456"]
interval: 5s
timeout: 20s
retries: 10
- vikunja-sqlite-data:/db
depends_on:
- init
init:
image: busybox
restart: no
volumes:
- vikunja-sqlite-data:/db
command: ["sh", "-c", "touch /db/vikunja.db && chown -R 1000 /db"]

Some files were not shown because too many files have changed in this diff Show More